diff --git a/model_api/python/model_api/models/__init__.py b/model_api/python/model_api/models/__init__.py index 70b7e33d..eba5deeb 100644 --- a/model_api/python/model_api/models/__init__.py +++ b/model_api/python/model_api/models/__init__.py @@ -11,7 +11,7 @@ from .instance_segmentation import MaskRCNNModel from .keypoint_detection import KeypointDetectionModel, TopDownKeypointDetectionPipeline from .model import Model -from .result_types import ( +from .result import ( AnomalyResult, ClassificationResult, Contour, diff --git a/model_api/python/model_api/models/action_classification.py b/model_api/python/model_api/models/action_classification.py index 318f2c9b..efed98ad 100644 --- a/model_api/python/model_api/models/action_classification.py +++ b/model_api/python/model_api/models/action_classification.py @@ -10,10 +10,10 @@ import numpy as np from model_api.adapters.utils import RESIZE_TYPES, InputTransform -from model_api.models.result_types import Label +from model_api.models.result import Label from .model import Model -from .result_types import ClassificationResult +from .result import ClassificationResult from .types import BooleanValue, ListValue, NumericalValue, StringValue from .utils import load_labels diff --git a/model_api/python/model_api/models/anomaly.py b/model_api/python/model_api/models/anomaly.py index 1bfab5ee..7affdd94 100644 --- a/model_api/python/model_api/models/anomaly.py +++ b/model_api/python/model_api/models/anomaly.py @@ -15,7 +15,7 @@ import numpy as np from model_api.models.image_model import ImageModel -from model_api.models.result_types import AnomalyResult +from model_api.models.result import AnomalyResult from model_api.models.types import ListValue, NumericalValue, StringValue if TYPE_CHECKING: diff --git a/model_api/python/model_api/models/classification.py b/model_api/python/model_api/models/classification.py index 1318580b..154278aa 100644 --- a/model_api/python/model_api/models/classification.py +++ b/model_api/python/model_api/models/classification.py @@ -17,7 +17,7 @@ from openvino.runtime import opset10 as opset from model_api.models.image_model import ImageModel -from model_api.models.result_types import ClassificationResult, Label +from model_api.models.result import ClassificationResult, Label from model_api.models.types import BooleanValue, ListValue, NumericalValue, StringValue from model_api.models.utils import softmax diff --git a/model_api/python/model_api/models/detection_model.py b/model_api/python/model_api/models/detection_model.py index 4c22d83f..4e2d4c0a 100644 --- a/model_api/python/model_api/models/detection_model.py +++ b/model_api/python/model_api/models/detection_model.py @@ -6,7 +6,7 @@ import numpy as np from .image_model import ImageModel -from .result_types import DetectionResult +from .result import DetectionResult from .types import ListValue, NumericalValue, StringValue from .utils import load_labels diff --git a/model_api/python/model_api/models/instance_segmentation.py b/model_api/python/model_api/models/instance_segmentation.py index 1e3eddf4..7f605130 100644 --- a/model_api/python/model_api/models/instance_segmentation.py +++ b/model_api/python/model_api/models/instance_segmentation.py @@ -9,7 +9,7 @@ from model_api.adapters.inference_adapter import InferenceAdapter from .image_model import ImageModel -from .result_types import InstanceSegmentationResult +from .result import InstanceSegmentationResult from .types import BooleanValue, ListValue, NumericalValue, StringValue from .utils import load_labels diff --git a/model_api/python/model_api/models/keypoint_detection.py b/model_api/python/model_api/models/keypoint_detection.py index da16778a..5ea9ef1d 100644 --- a/model_api/python/model_api/models/keypoint_detection.py +++ b/model_api/python/model_api/models/keypoint_detection.py @@ -10,7 +10,7 @@ import numpy as np from .image_model import ImageModel -from .result_types import DetectedKeypoints, DetectionResult +from .result import DetectedKeypoints, DetectionResult from .types import ListValue diff --git a/model_api/python/model_api/models/result/__init__.py b/model_api/python/model_api/models/result/__init__.py new file mode 100644 index 00000000..6f10071b --- /dev/null +++ b/model_api/python/model_api/models/result/__init__.py @@ -0,0 +1,36 @@ +"""Model results.""" + +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from .types import ( + AnomalyResult, + ClassificationResult, + Contour, + DetectedKeypoints, + DetectionResult, + ImageResultWithSoftPrediction, + InstanceSegmentationResult, + Label, + PredictedMask, + Result, + RotatedSegmentationResult, + VisualPromptingResult, + ZSLVisualPromptingResult, +) + +__all__ = [ + "AnomalyResult", + "ClassificationResult", + "Contour", + "DetectionResult", + "DetectedKeypoints", + "ImageResultWithSoftPrediction", + "InstanceSegmentationResult", + "Label", + "PredictedMask", + "Result", + "VisualPromptingResult", + "ZSLVisualPromptingResult", + "RotatedSegmentationResult", +] diff --git a/model_api/python/model_api/models/result/scene/__init__.py b/model_api/python/model_api/models/result/scene/__init__.py new file mode 100644 index 00000000..bf383d1a --- /dev/null +++ b/model_api/python/model_api/models/result/scene/__init__.py @@ -0,0 +1,4 @@ +"""Result visualization Scene.""" + +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 diff --git a/model_api/python/model_api/models/result_types/__init__.py b/model_api/python/model_api/models/result/types/__init__.py similarity index 95% rename from model_api/python/model_api/models/result_types/__init__.py rename to model_api/python/model_api/models/result/types/__init__.py index 2352d462..5b33d9fb 100644 --- a/model_api/python/model_api/models/result_types/__init__.py +++ b/model_api/python/model_api/models/result/types/__init__.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 from .anomaly import AnomalyResult +from .base import Result from .classification import ClassificationResult, Label from .detection import DetectionResult from .keypoint import DetectedKeypoints @@ -25,6 +26,7 @@ "ImageResultWithSoftPrediction", "InstanceSegmentationResult", "PredictedMask", + "Result", "VisualPromptingResult", "ZSLVisualPromptingResult", "RotatedSegmentationResult", diff --git a/model_api/python/model_api/models/result_types/anomaly.py b/model_api/python/model_api/models/result/types/anomaly.py similarity index 96% rename from model_api/python/model_api/models/result_types/anomaly.py rename to model_api/python/model_api/models/result/types/anomaly.py index 61f5384c..375d97f3 100644 --- a/model_api/python/model_api/models/result_types/anomaly.py +++ b/model_api/python/model_api/models/result/types/anomaly.py @@ -7,8 +7,10 @@ import numpy as np +from .base import Result -class AnomalyResult: + +class AnomalyResult(Result): """Results for anomaly models.""" def __init__( diff --git a/model_api/python/model_api/models/result/types/base.py b/model_api/python/model_api/models/result/types/base.py new file mode 100644 index 00000000..1d9624cd --- /dev/null +++ b/model_api/python/model_api/models/result/types/base.py @@ -0,0 +1,10 @@ +"""Base result type""" + +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from abc import ABC + + +class Result(ABC): + """Base result type.""" diff --git a/model_api/python/model_api/models/result_types/classification.py b/model_api/python/model_api/models/result/types/classification.py similarity index 96% rename from model_api/python/model_api/models/result_types/classification.py rename to model_api/python/model_api/models/result/types/classification.py index 49559756..2f5a80ab 100644 --- a/model_api/python/model_api/models/result_types/classification.py +++ b/model_api/python/model_api/models/result/types/classification.py @@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Generator +from .base import Result from .utils import array_shape_to_str if TYPE_CHECKING: @@ -35,7 +36,7 @@ def __str__(self) -> str: return f"{self.id} ({self.name}): {self.confidence:.3f}" -class ClassificationResult: +class ClassificationResult(Result): """Results for classification models.""" def __init__( diff --git a/model_api/python/model_api/models/result_types/detection.py b/model_api/python/model_api/models/result/types/detection.py similarity index 98% rename from model_api/python/model_api/models/result_types/detection.py rename to model_api/python/model_api/models/result/types/detection.py index 49bf3f20..40df2b0e 100644 --- a/model_api/python/model_api/models/result_types/detection.py +++ b/model_api/python/model_api/models/result/types/detection.py @@ -7,10 +7,11 @@ import numpy as np +from .base import Result from .utils import array_shape_to_str -class DetectionResult: +class DetectionResult(Result): """Result for detection model. Args: diff --git a/model_api/python/model_api/models/result_types/keypoint.py b/model_api/python/model_api/models/result/types/keypoint.py similarity index 89% rename from model_api/python/model_api/models/result_types/keypoint.py rename to model_api/python/model_api/models/result/types/keypoint.py index 58c8acd6..8c79c585 100644 --- a/model_api/python/model_api/models/result_types/keypoint.py +++ b/model_api/python/model_api/models/result/types/keypoint.py @@ -5,8 +5,10 @@ import numpy as np +from .base import Result -class DetectedKeypoints: + +class DetectedKeypoints(Result): def __init__(self, keypoints: np.ndarray, scores: np.ndarray) -> None: self.keypoints = keypoints self.scores = scores diff --git a/model_api/python/model_api/models/result_types/segmentation.py b/model_api/python/model_api/models/result/types/segmentation.py similarity index 98% rename from model_api/python/model_api/models/result_types/segmentation.py rename to model_api/python/model_api/models/result/types/segmentation.py index b7c82e23..37ffff85 100644 --- a/model_api/python/model_api/models/result_types/segmentation.py +++ b/model_api/python/model_api/models/result/types/segmentation.py @@ -10,6 +10,7 @@ import cv2 import numpy as np +from .base import Result from .detection import DetectionResult from .utils import array_shape_to_str @@ -157,7 +158,7 @@ def __str__(self): return f"{self.label}: {self.probability:.3f}, {len(self.shape)}" -class ImageResultWithSoftPrediction: +class ImageResultWithSoftPrediction(Result): def __init__( self, resultImage: np.ndarray, diff --git a/model_api/python/model_api/models/result_types/utils.py b/model_api/python/model_api/models/result/types/utils.py similarity index 100% rename from model_api/python/model_api/models/result_types/utils.py rename to model_api/python/model_api/models/result/types/utils.py diff --git a/model_api/python/model_api/models/result_types/visual_prompting.py b/model_api/python/model_api/models/result/types/visual_prompting.py similarity index 96% rename from model_api/python/model_api/models/result_types/visual_prompting.py rename to model_api/python/model_api/models/result/types/visual_prompting.py index f82870d0..b59af96d 100644 --- a/model_api/python/model_api/models/result_types/visual_prompting.py +++ b/model_api/python/model_api/models/result/types/visual_prompting.py @@ -7,8 +7,10 @@ import numpy as np +from .base import Result -class VisualPromptingResult: + +class VisualPromptingResult(Result): def __init__( self, upscaled_masks: list[np.ndarray] | None = None, @@ -79,7 +81,7 @@ def __str__(self) -> str: return obj_str.strip() -class ZSLVisualPromptingResult: +class ZSLVisualPromptingResult(Result): def __init__(self, data: dict[int, PredictedMask]) -> None: self.data = data diff --git a/model_api/python/model_api/models/segmentation.py b/model_api/python/model_api/models/segmentation.py index cbb5f7c8..dc530e11 100644 --- a/model_api/python/model_api/models/segmentation.py +++ b/model_api/python/model_api/models/segmentation.py @@ -11,7 +11,7 @@ import numpy as np from model_api.models.image_model import ImageModel -from model_api.models.result_types import Contour, ImageResultWithSoftPrediction +from model_api.models.result import Contour, ImageResultWithSoftPrediction from model_api.models.types import BooleanValue, ListValue, NumericalValue, StringValue from model_api.models.utils import load_labels diff --git a/model_api/python/model_api/models/ssd.py b/model_api/python/model_api/models/ssd.py index 947fbac6..a07e56c4 100644 --- a/model_api/python/model_api/models/ssd.py +++ b/model_api/python/model_api/models/ssd.py @@ -6,7 +6,7 @@ import numpy as np from .detection_model import DetectionModel -from .result_types import DetectionResult +from .result import DetectionResult BBOX_AREA_THRESHOLD = 1.0 SALIENCY_MAP_NAME = "saliency_map" diff --git a/model_api/python/model_api/models/utils.py b/model_api/python/model_api/models/utils.py index a04e761f..abc21c0c 100644 --- a/model_api/python/model_api/models/utils.py +++ b/model_api/python/model_api/models/utils.py @@ -11,10 +11,10 @@ import cv2 import numpy as np -from model_api.models.result_types import Contour, InstanceSegmentationResult, RotatedSegmentationResult +from model_api.models.result import Contour, InstanceSegmentationResult, RotatedSegmentationResult if TYPE_CHECKING: - from model_api.models.result_types.detection import DetectionResult + from model_api.models.result.detection import DetectionResult def add_rotated_rects(inst_seg_result: InstanceSegmentationResult) -> RotatedSegmentationResult: diff --git a/model_api/python/model_api/models/yolo.py b/model_api/python/model_api/models/yolo.py index d72c2ae4..a7a17bbf 100644 --- a/model_api/python/model_api/models/yolo.py +++ b/model_api/python/model_api/models/yolo.py @@ -10,7 +10,7 @@ from model_api.adapters.utils import INTERPOLATION_TYPES, resize_image_ocv from .detection_model import DetectionModel -from .result_types import DetectionResult +from .result import DetectionResult from .types import BooleanValue, ListValue, NumericalValue from .utils import clip_detections, multiclass_nms, nms diff --git a/model_api/python/model_api/visualizer/__init__.py b/model_api/python/model_api/visualizer/__init__.py new file mode 100644 index 00000000..ad194fef --- /dev/null +++ b/model_api/python/model_api/visualizer/__init__.py @@ -0,0 +1,9 @@ +"""Visualizer.""" + +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from .scene import Scene +from .visualizer import Visualizer + +__all__ = ["Scene", "Visualizer"] diff --git a/model_api/python/model_api/visualizer/layout.py b/model_api/python/model_api/visualizer/layout.py new file mode 100644 index 00000000..aa18490a --- /dev/null +++ b/model_api/python/model_api/visualizer/layout.py @@ -0,0 +1,28 @@ +"""Visualization Layout""" + +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from abc import ABC +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import PIL + + from model_api.visualizer.primitive import Primitive + + from .scene import Scene + + +class Layout(ABC): + """Base class for layouts.""" + + def _compute_on_primitive(self, primitive: Primitive, image: PIL.Image, scene: Scene) -> PIL.Image | None: + if scene.has_primitives(type(primitive)): + primitives = scene.get_primitives(type(primitive)) + for primitive in primitives: + image = primitive.compute(image) + return image + return None diff --git a/model_api/python/model_api/visualizer/primitive.py b/model_api/python/model_api/visualizer/primitive.py new file mode 100644 index 00000000..7afaee24 --- /dev/null +++ b/model_api/python/model_api/visualizer/primitive.py @@ -0,0 +1,20 @@ +"""Base class for primitives.""" + +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import PIL + + +class Primitive(ABC): + """Primitive class.""" + + @abstractmethod + def compute(self, image: PIL.Image, **kwargs) -> PIL.Image: + pass diff --git a/model_api/python/model_api/visualizer/scene.py b/model_api/python/model_api/visualizer/scene.py new file mode 100644 index 00000000..e8065365 --- /dev/null +++ b/model_api/python/model_api/visualizer/scene.py @@ -0,0 +1,40 @@ +"""Scene object.""" + +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from abc import abstractmethod +from typing import Type, Union + +import PIL + +from .layout import Layout +from .primitive import Primitive + + +class Scene: + """Scene object. + + Used by the visualizer to render. + """ + + def __init__( + self, + base: PIL.Image, + layout: Union[Layout, list[Layout], None] = None, + ) -> None: ... + + def show(self) -> PIL.Image: ... + + def save(self, path: str) -> None: ... + + def has_primitives(self, primitive: Type[Primitive]) -> bool: + return False + + def get_primitives(self, primitive: Type[Primitive]) -> list[Primitive]: + return [] + + @property + @abstractmethod + def default_layout(self) -> Layout: + """Default layout for the media.""" diff --git a/model_api/python/model_api/visualizer/visualizer.py b/model_api/python/model_api/visualizer/visualizer.py new file mode 100644 index 00000000..9519a9a1 --- /dev/null +++ b/model_api/python/model_api/visualizer/visualizer.py @@ -0,0 +1,12 @@ +"""Visualizer for modelAPI.""" + +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import PIL + +from model_api.models.result import Result + + +class Visualizer: + def show(self, image: PIL.Image, result: Result) -> PIL.Image: ... diff --git a/model_api/python/pyproject.toml b/model_api/python/pyproject.toml index 11d8b2d0..4ba2c7a2 100644 --- a/model_api/python/pyproject.toml +++ b/model_api/python/pyproject.toml @@ -30,6 +30,7 @@ dependencies = [ "openvino>=2024.0", "openvino-dev>=2024.0", "omz_tools @ git+https://github.com/openvinotoolkit/open_model_zoo.git@master#egg=omz_tools&subdirectory=tools/model_tools", + "pillow", ] [project.optional-dependencies] diff --git a/tests/python/unit/results/test_cls_result.py b/tests/python/unit/results/test_cls_result.py index aebb0a63..7110e4a1 100644 --- a/tests/python/unit/results/test_cls_result.py +++ b/tests/python/unit/results/test_cls_result.py @@ -4,7 +4,7 @@ # import numpy as np -from model_api.models.result_types import ClassificationResult, Label +from model_api.models.result import ClassificationResult, Label def test_cls_result():