diff --git a/CHANGELOG.md b/CHANGELOG.md index d29d96df..b1c1fd19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## 1.3.10 + +### Added + +- Weight upload support for yolo26-sem semantic segmentation models via + `version.deploy()` and `workspace.deploy_model()` + ## 1.3.9 ### Added — Model evaluations SDK & CLI diff --git a/roboflow/__init__.py b/roboflow/__init__.py index 79822649..560ffbbc 100644 --- a/roboflow/__init__.py +++ b/roboflow/__init__.py @@ -21,7 +21,7 @@ CLIPModel = None # type: ignore[assignment,misc] GazeModel = None # type: ignore[assignment,misc] -__version__ = "1.3.9" +__version__ = "1.3.10" def check_key(api_key, model, notebook, num_retries=0): diff --git a/roboflow/config.py b/roboflow/config.py index 1b3d5a1c..0d569e95 100644 --- a/roboflow/config.py +++ b/roboflow/config.py @@ -76,6 +76,7 @@ def get_conditional_configuration_variable(key, default): TASK_DET = "det" TASK_SEG = "seg" +TASK_SEM = "sem" TASK_POSE = "pose" TASK_CLS = "cls" TASK_OBB = "obb" diff --git a/roboflow/util/model_processor.py b/roboflow/util/model_processor.py index 4ff0ce1a..0022c11a 100644 --- a/roboflow/util/model_processor.py +++ b/roboflow/util/model_processor.py @@ -12,10 +12,12 @@ TASK_OBB, TASK_POSE, TASK_SEG, + TASK_SEM, TYPE_CLASSICATION, TYPE_INSTANCE_SEGMENTATION, TYPE_KEYPOINT_DETECTION, TYPE_OBJECT_DETECTION, + TYPE_SEMANTIC_SEGMENTATION, ) from roboflow.util.versions import print_warn_for_wrong_dependencies_versions @@ -27,21 +29,18 @@ def task_of_model_type(model_type: str) -> str: (e.g. 'yolov11-seg' -> TASK_SEG). Plain 'yolov11' / 'rfdetr-base' -> TASK_DET. """ s = model_type.lower() - for task in (TASK_SEG, TASK_POSE, TASK_CLS, TASK_OBB): + for task in (TASK_SEM, TASK_SEG, TASK_POSE, TASK_CLS, TASK_OBB): if task in s: return task return TASK_DET def validate_model_type_for_project(model_type: str, project_type: str, project_id: str) -> None: - """Raise ValueError if model_type's task doesn't match the Roboflow project type. - - No-op when project_type has no uploader-relevant task (e.g. semantic-segmentation). - """ - # TYPE_SEMANTIC_SEGMENTATION intentionally omitted — no uploader emits it. + """Raise ValueError if model_type's task doesn't match the Roboflow project type.""" expected = { TYPE_OBJECT_DETECTION: TASK_DET, TYPE_INSTANCE_SEGMENTATION: TASK_SEG, + TYPE_SEMANTIC_SEGMENTATION: TASK_SEM, TYPE_KEYPOINT_DETECTION: TASK_POSE, TYPE_CLASSICATION: TASK_CLS, }.get(project_type) @@ -119,6 +118,7 @@ def _detect_yolo_task(model_instance) -> Optional[str]: return { "DetectionModel": TASK_DET, "SegmentationModel": TASK_SEG, + "SemanticSegmentationModel": TASK_SEM, "PoseModel": TASK_POSE, "ClassificationModel": TASK_CLS, "OBBModel": TASK_OBB, diff --git a/tests/test_version.py b/tests/test_version.py index b8cab69c..64b8874e 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -10,6 +10,7 @@ TYPE_INSTANCE_SEGMENTATION, TYPE_KEYPOINT_DETECTION, TYPE_OBJECT_DETECTION, + TYPE_SEMANTIC_SEGMENTATION, ) from roboflow.core.version import Version, unwrap_version_id from tests.helpers import get_version @@ -243,6 +244,25 @@ def test_keypoint_project_rejects_detection(self): def test_classification_project_accepts_cls(self): self._version(TYPE_CLASSICATION)._validate_against_project_type("yolov11-cls") + def test_semantic_seg_project_accepts_sem_model(self): + self._version(TYPE_SEMANTIC_SEGMENTATION)._validate_against_project_type("yolo26-sem") + + def test_semantic_seg_project_rejects_detection(self): + with self.assertRaises(ValueError): + self._version(TYPE_SEMANTIC_SEGMENTATION)._validate_against_project_type("yolov11") + + def test_semantic_seg_project_rejects_instance_seg(self): + with self.assertRaises(ValueError): + self._version(TYPE_SEMANTIC_SEGMENTATION)._validate_against_project_type("yolov11-seg") + + def test_instance_seg_project_rejects_sem_model(self): + with self.assertRaises(ValueError): + self._version(TYPE_INSTANCE_SEGMENTATION)._validate_against_project_type("yolo26-sem") + + def test_detection_project_rejects_sem_model(self): + with self.assertRaises(ValueError): + self._version(TYPE_OBJECT_DETECTION)._validate_against_project_type("yolo26-sem") + def test_classification_project_rejects_detection(self): with self.assertRaises(ValueError): self._version(TYPE_CLASSICATION)._validate_against_project_type("yolov11") diff --git a/tests/util/test_model_processor.py b/tests/util/test_model_processor.py index 37ecb186..80408602 100644 --- a/tests/util/test_model_processor.py +++ b/tests/util/test_model_processor.py @@ -3,7 +3,7 @@ import unittest from types import SimpleNamespace -from roboflow.config import TASK_CLS, TASK_DET, TASK_OBB, TASK_POSE, TASK_SEG +from roboflow.config import TASK_CLS, TASK_DET, TASK_OBB, TASK_POSE, TASK_SEG, TASK_SEM from roboflow.util.model_processor import ( _detect_rfdetr_task, _detect_yolo_task, @@ -38,6 +38,9 @@ def test_pose(self): def test_classify(self): self.assertEqual(task_of_model_type("yolov11-cls"), TASK_CLS) + def test_semantic(self): + self.assertEqual(task_of_model_type("yolo26-sem"), TASK_SEM) + def test_obb(self): self.assertEqual(task_of_model_type("yolov11-obb"), TASK_OBB) @@ -54,6 +57,9 @@ def test_ultralytics_class_names(self): for cls_name, expected in cases.items(): self.assertEqual(_detect_yolo_task(_make_fake(cls_name)), expected, cls_name) + def test_semantic_segmentation_model(self): + self.assertEqual(_detect_yolo_task(_make_fake("SemanticSegmentationModel")), TASK_SEM) + def test_unrecognized_returns_none(self): self.assertIsNone(_detect_yolo_task(_make_fake("SomeOtherModel"))) self.assertIsNone(_detect_yolo_task(None))