From 63827734925aaa9069ad021832ffac604a5019e7 Mon Sep 17 00:00:00 2001 From: kprokofi Date: Thu, 4 Jun 2026 00:59:20 +0900 Subject: [PATCH 1/4] add wrapper for inst seg yolo --- model_api/src/model_api/models/__init__.py | 2 + model_api/src/model_api/models/yolo_seg.py | 276 ++++++++++++++++++ .../tests/unit/models/test_yolo_seg_model.py | 218 ++++++++++++++ 3 files changed, 496 insertions(+) create mode 100644 model_api/src/model_api/models/yolo_seg.py create mode 100644 model_api/tests/unit/models/test_yolo_seg_model.py diff --git a/model_api/src/model_api/models/__init__.py b/model_api/src/model_api/models/__init__.py index 5be946cf..4415b1ea 100644 --- a/model_api/src/model_api/models/__init__.py +++ b/model_api/src/model_api/models/__init__.py @@ -35,6 +35,7 @@ ) from .visual_prompting import Prompt, SAMLearnableVisualPrompter, SAMVisualPrompter from .yolo import YOLO, YOLO11, YOLOF, YOLOX, YoloV3ONNX, YoloV4, YOLOv5, YOLOv8 +from .yolo_seg import YOLOSeg classification_models = [ "resnet-18-pytorch", @@ -95,6 +96,7 @@ "VisualPromptingResult", "YOLO", "YOLO11", + "YOLOSeg", "YOLOF", "YOLOv3ONNX", "YOLOv4", diff --git a/model_api/src/model_api/models/yolo_seg.py b/model_api/src/model_api/models/yolo_seg.py new file mode 100644 index 00000000..4d90520f --- /dev/null +++ b/model_api/src/model_api/models/yolo_seg.py @@ -0,0 +1,276 @@ +# +# Copyright (C) 2026 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +"""Custom ModelAPI wrapper for YOLO instance-segmentation inference. + +YOLO segmentation models exported with ``end2end=False`` produce two outputs: + * Output 0: ``[1, num_classes + 4 + mask_dim, num_boxes]`` — detection + predictions concatenated with mask coefficients. + * Output 1: ``[1, mask_dim, proto_h, proto_w]`` — mask prototypes. + +The standard ModelAPI ``YOLO11`` wrapper only handles a single detection +output. This wrapper extends it to decode instance masks by multiplying +mask coefficients by prototypes, cropping to bounding boxes, and resizing +to original image dimensions. + +The wrapper registers itself as ``"YOLO11-seg"`` so OVEngine can discover +it via ``model_type`` metadata in the exported IR. +""" + +from __future__ import annotations + +from typing import Any, cast + +import numpy as np + +from model_api.adapters.utils import resize_image_ocv +from model_api.models.detection_model import DetectionModel +from model_api.models.result import InstanceSegmentationResult +from model_api.models.utils import ResizeMetadata +from model_api.models.yolo import xywh2xyxy + + +class YOLOSeg(DetectionModel): + """ModelAPI wrapper for YOLO instance-segmentation models. + + Expects 2 outputs: + * detection output: ``[1, 4 + num_classes + mask_dim, num_boxes]`` + * prototype output: ``[1, mask_dim, proto_h, proto_w]`` + + Post-processing: + 1. Parse detection predictions (boxes + class scores + mask coefficients). + 2. Filter by confidence, apply NMS. + 3. Decode masks: ``coefficients @ protos.reshape(mask_dim, -1)`` → sigmoid → crop → resize. + 4. Return ``InstanceSegmentationResult``. + """ + + __model__ = "YOLO-seg" + + def __init__(self, inference_adapter: object, configuration: dict | None = None, preload: bool = False) -> None: + super().__init__(inference_adapter, configuration or {}, preload) + self._check_io_number(1, 2) + + self._det_output_name: str = "" + self._proto_output_name: str = "" + outputs = cast("dict[str, Any]", self.outputs or {}) + + for name, output in outputs.items(): + shape = output.shape + if len(shape) == 3: + self._det_output_name = name + elif len(shape) == 4: + self._proto_output_name = name + + if not self._det_output_name or not self._proto_output_name: + self.raise_error( + "Expected one rank-3 detection output and one rank-4 prototype output, " + f"but got shapes: {[(name, out.shape) for name, out in outputs.items()]}", + ) + + det_shape = outputs[self._det_output_name].shape + proto_shape = outputs[self._proto_output_name].shape + self._mask_dim = proto_shape[1] + self._proto_h = proto_shape[2] + self._proto_w = proto_shape[3] + + self._num_classes = det_shape[1] - 4 - self._mask_dim + if self._num_classes <= 0: + self.raise_error(f"Detection output channel dim ({det_shape[1]}) must be > 4 + mask_dim ({self._mask_dim})") + + @classmethod + def parameters(cls): + parameters = super().parameters() + parameters["pad_value"].update_default_value(114) + parameters["resize_type"].update_default_value("fit_to_window_letterbox") + parameters["reverse_input_channels"].update_default_value(default_value=False) + parameters["confidence_threshold"].update_default_value(0.25) + parameters["iou_threshold"].update_default_value(0.5) + return parameters + + def postprocess(self, outputs: dict[str, Any], meta: dict[str, Any]) -> InstanceSegmentationResult: + """Decode detections and instance masks from raw model outputs. + + Args: + outputs: Raw model outputs keyed by output tensor name. + meta: Preprocessing metadata from ModelAPI (original_shape, etc.). + + Returns: + InstanceSegmentationResult with boxes in original image coordinates + and binary masks at original image resolution. + """ + det_output = outputs[self._det_output_name] + proto_output = outputs[self._proto_output_name] + + prediction = det_output.astype(np.float32) + protos = proto_output[0].astype(np.float32) + + pred = prediction[0].T + + boxes_xywh = pred[:, :4] + class_scores = pred[:, 4 : 4 + self._num_classes] + mask_coeffs = pred[:, 4 + self._num_classes :] + + params = cast("Any", self.params) + conf_threshold = params.confidence_threshold + max_scores = class_scores.max(axis=1) + keep_conf = max_scores > conf_threshold + + if not keep_conf.any(): + return self._empty_result(meta) + + boxes_xywh = boxes_xywh[keep_conf] + class_scores = class_scores[keep_conf] + mask_coeffs = mask_coeffs[keep_conf] + + labels = class_scores.argmax(axis=1) + confidences = class_scores[np.arange(len(labels)), labels] + + boxes_xyxy = xywh2xyxy(boxes_xywh.copy()) + + keep_nms = self._calculate_nms( + boxes=boxes_xyxy, + scores=confidences, + labels=labels.astype(np.float32), + ) + boxes_xyxy = boxes_xyxy[keep_nms] + confidences = confidences[keep_nms] + labels = labels[keep_nms] + mask_coeffs = mask_coeffs[keep_nms] + + if len(boxes_xyxy) == 0: + return self._empty_result(meta) + + masks = self._decode_masks(mask_coeffs, protos, boxes_xyxy, meta) + + input_img_w = meta["original_shape"][1] + input_img_h = meta["original_shape"][0] + resize_meta = ResizeMetadata.compute( + original_width=input_img_w, + original_height=input_img_h, + model_width=self.orig_width, + model_height=self.orig_height, + resize_type=params.resize_type, + ) + + coords = boxes_xyxy.copy() + coords -= (resize_meta.pad_left, resize_meta.pad_top, resize_meta.pad_left, resize_meta.pad_top) + coords *= ( + resize_meta.inverted_scale_x, + resize_meta.inverted_scale_y, + resize_meta.inverted_scale_x, + resize_meta.inverted_scale_y, + ) + + int_boxes = np.round(coords).astype(np.int32) + np.clip( + int_boxes, + 0, + [input_img_w, input_img_h, input_img_w, input_img_h], + out=int_boxes, + ) + + int_labels = labels.astype(np.int32) + return InstanceSegmentationResult( + bboxes=int_boxes, + scores=confidences, + labels=int_labels + 1, + masks=masks, + label_names=[self.get_label_name(i) for i in int_labels], + saliency_map=[], + feature_vector=np.ndarray(0), + ) + + def _decode_masks( + self, + mask_coeffs: np.ndarray, + protos: np.ndarray, + boxes_xyxy: np.ndarray, + meta: dict, + ) -> np.ndarray: + """Decode instance masks from mask coefficients and prototypes. + + Args: + mask_coeffs: Mask coefficients ``(N, mask_dim)``. + protos: Prototype masks ``(mask_dim, proto_h, proto_w)``. + boxes_xyxy: Bounding boxes in model input coordinates ``(N, 4)``. + meta: Preprocessing metadata (original_shape, etc.). + + Returns: + Binary masks at original image resolution ``(N, orig_h, orig_w)``. + """ + mask_dim, proto_h, proto_w = protos.shape + raw_masks = mask_coeffs @ protos.reshape(mask_dim, -1) + raw_masks = raw_masks.reshape(-1, proto_h, proto_w) + + raw_masks = 1.0 / (1.0 + np.exp(-raw_masks)) + + model_h, model_w = self.orig_height, self.orig_width + scale_x = proto_w / model_w + scale_y = proto_h / model_h + proto_boxes = boxes_xyxy * np.array([scale_x, scale_y, scale_x, scale_y], dtype=np.float32) + + raw_masks = self.crop_mask(raw_masks, proto_boxes) + + input_img_h = meta["original_shape"][0] + input_img_w = meta["original_shape"][1] + + resize_meta = ResizeMetadata.compute( + original_width=input_img_w, + original_height=input_img_h, + model_width=model_w, + model_height=model_h, + resize_type=cast("Any", self.params).resize_type, + ) + + n = raw_masks.shape[0] + upsampled = np.zeros((n, model_h, model_w), dtype=np.float32) + for i in range(n): + upsampled[i] = resize_image_ocv(raw_masks[i], (model_w, model_h)) + + pad_t = resize_meta.pad_top + pad_l = resize_meta.pad_left + effective_w = round(input_img_w / resize_meta.inverted_scale_x) + effective_h = round(input_img_h / resize_meta.inverted_scale_y) + cropped = upsampled[:, pad_t : pad_t + effective_h, pad_l : pad_l + effective_w] + + final_masks = np.zeros((n, input_img_h, input_img_w), dtype=np.uint8) + for i in range(n): + resized = resize_image_ocv(cropped[i], (input_img_w, input_img_h)) + final_masks[i] = (resized > 0.5).astype(np.uint8) + + return final_masks + + def _empty_result(self, meta: dict) -> InstanceSegmentationResult: + """Return an empty result when no detections pass filtering.""" + return InstanceSegmentationResult( + bboxes=np.empty((0, 4), dtype=np.int32), + scores=np.empty(0, dtype=np.float32), + labels=np.empty(0, dtype=np.int32), + masks=np.empty((0, meta["original_shape"][0], meta["original_shape"][1]), dtype=np.uint8), + label_names=[], + saliency_map=[], + feature_vector=np.ndarray(0), + ) + + @staticmethod + def crop_mask(masks: np.ndarray, boxes: np.ndarray) -> np.ndarray: + """Zero-out mask pixels outside the bounding box. + + Args: + masks: Binary or float masks of shape ``(N, H, W)``. + boxes: Bounding boxes ``(N, 4)`` in xyxy format, scaled to mask dims. + + Returns: + Cropped masks of shape ``(N, H, W)``. + """ + n, h, w = masks.shape + rows = np.arange(h, dtype=np.float32).reshape(1, h, 1) + cols = np.arange(w, dtype=np.float32).reshape(1, 1, w) + x1 = boxes[:, 0].reshape(n, 1, 1) + y1 = boxes[:, 1].reshape(n, 1, 1) + x2 = boxes[:, 2].reshape(n, 1, 1) + y2 = boxes[:, 3].reshape(n, 1, 1) + inside = (cols >= x1) & (cols < x2) & (rows >= y1) & (rows < y2) + return masks * inside diff --git a/model_api/tests/unit/models/test_yolo_seg_model.py b/model_api/tests/unit/models/test_yolo_seg_model.py new file mode 100644 index 00000000..042f0e62 --- /dev/null +++ b/model_api/tests/unit/models/test_yolo_seg_model.py @@ -0,0 +1,218 @@ +# +# Copyright (C) 2026 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +"""Unit tests for YOLOSeg instance-segmentation wrapper.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from unittest.mock import MagicMock + +import numpy as np +import pytest + +from model_api.adapters.inference_adapter import InferenceAdapter +from model_api.models.model import WrapperError +from model_api.models.result import InstanceSegmentationResult +from model_api.models.yolo_seg import YOLOSeg + +rng = np.random.default_rng(42) + +_RT_INFO_ERROR = RuntimeError( + "Cannot get runtime attribute. Path to runtime attribute is incorrect.", +) + + +@dataclass +class FakeMetadata: + names: set = field(default_factory=set) + shape: list = field(default_factory=list) + layout: str = "" + precision: str = "" + type: str = "" + meta: dict = field(default_factory=dict) + + +def _make_seg_adapter( + n_classes: int = 2, + mask_dim: int = 32, + n_boxes: int = 100, + proto_h: int = 160, + proto_w: int = 160, +): + det_shape = (1, 4 + n_classes + mask_dim, n_boxes) + proto_shape = (1, mask_dim, proto_h, proto_w) + adapter = MagicMock(spec=InferenceAdapter) + image_meta = FakeMetadata(shape=[1, 3, 640, 640], layout="NCHW") + adapter.get_input_layers.return_value = {"image": image_meta} + outputs = { + "detection": FakeMetadata(shape=list(det_shape), precision="f32"), + "protos": FakeMetadata(shape=list(proto_shape), precision="f32"), + } + adapter.get_output_layers.return_value = outputs + adapter.get_rt_info.side_effect = _RT_INFO_ERROR + adapter.embed_preprocessing = MagicMock() + adapter.load_model.return_value = None + return adapter + + +class TestYOLOSeg: + def test_init(self): + adapter = _make_seg_adapter() + model = YOLOSeg(adapter, configuration={}) + assert model.__model__ == "YOLO-seg" + + def test_init_invalid_output_count(self): + adapter = MagicMock(spec=InferenceAdapter) + image_meta = FakeMetadata(shape=[1, 3, 640, 640], layout="NCHW") + adapter.get_input_layers.return_value = {"image": image_meta} + adapter.get_output_layers.return_value = { + "single": FakeMetadata(shape=[1, 10, 10]), + } + adapter.get_rt_info.side_effect = _RT_INFO_ERROR + adapter.embed_preprocessing = MagicMock() + adapter.load_model.return_value = None + with pytest.raises(WrapperError): + YOLOSeg(adapter, configuration={}) + + def test_init_invalid_channel_dim(self): + adapter = MagicMock(spec=InferenceAdapter) + image_meta = FakeMetadata(shape=[1, 3, 640, 640], layout="NCHW") + adapter.get_input_layers.return_value = {"image": image_meta} + det_shape = (1, 4 + 32, 100) + adapter.get_output_layers.return_value = { + "detection": FakeMetadata(shape=list(det_shape)), + "protos": FakeMetadata(shape=[1, 32, 160, 160]), + } + adapter.get_rt_info.side_effect = _RT_INFO_ERROR + adapter.embed_preprocessing = MagicMock() + adapter.load_model.return_value = None + with pytest.raises(WrapperError): + YOLOSeg(adapter, configuration={}) + + def test_parameters(self): + params = YOLOSeg.parameters() + assert params["pad_value"].default_value == 114 + assert params["resize_type"].default_value == "fit_to_window_letterbox" + assert params["reverse_input_channels"].default_value is False + assert params["scale_values"].default_value == [255.0] + assert params["confidence_threshold"].default_value == pytest.approx(0.25) + assert params["iou_threshold"].default_value == pytest.approx(0.5) + + def test_postprocess_no_detections(self): + n_classes = 2 + mask_dim = 32 + n_boxes = 10 + adapter = _make_seg_adapter( + n_classes=n_classes, + mask_dim=mask_dim, + n_boxes=n_boxes, + ) + model = YOLOSeg(adapter, configuration={"confidence_threshold": 0.9}) + det = np.zeros((1, 4 + n_classes + mask_dim, n_boxes), dtype=np.float32) + protos = np.zeros((1, mask_dim, 160, 160), dtype=np.float32) + meta = {"original_shape": (480, 640, 3)} + result = model.postprocess({"detection": det, "protos": protos}, meta) + assert isinstance(result, InstanceSegmentationResult) + assert len(result.bboxes) == 0 + + def test_postprocess_with_detection(self): + n_classes = 2 + mask_dim = 32 + n_boxes = 10 + adapter = _make_seg_adapter( + n_classes=n_classes, + mask_dim=mask_dim, + n_boxes=n_boxes, + ) + model = YOLOSeg( + adapter, + configuration={ + "confidence_threshold": 0.1, + "labels": ["class_a", "class_b"], + }, + ) + det = np.zeros((1, 4 + n_classes + mask_dim, n_boxes), dtype=np.float32) + det[0, 0, 0] = 320.0 + det[0, 1, 0] = 320.0 + det[0, 2, 0] = 100.0 + det[0, 3, 0] = 100.0 + det[0, 4, 0] = 0.9 + protos = rng.uniform(-1, 1, size=(1, mask_dim, 160, 160)).astype(np.float32) + meta = { + "original_shape": (480, 640, 3), + "resized_shape": (640, 640, 3), + } + result = model.postprocess({"detection": det, "protos": protos}, meta) + assert isinstance(result, InstanceSegmentationResult) + assert len(result.bboxes) >= 1 + assert result.bboxes.shape[1] == 4 + assert result.masks.shape[0] >= 1 + assert result.masks.shape[1] == 480 + assert result.masks.shape[2] == 640 + assert result.masks.dtype == np.uint8 + + def test_crop_mask(self): + # Test full coverage + masks = np.ones((2, 4, 4), dtype=np.float32) + boxes = np.array([[0.0, 0.0, 4.0, 4.0], [1.0, 1.0, 3.0, 3.0]]) + result = YOLOSeg.crop_mask(masks, boxes) + expected = np.array([ + [[1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0]], + [[0.0, 0.0, 0.0, 0.0], [0.0, 1.0, 1.0, 0.0], [0.0, 1.0, 1.0, 0.0], [0.0, 0.0, 0.0, 0.0]], + ]) + np.testing.assert_array_equal(result, expected) + + # Test no overlap + masks = np.ones((1, 3, 3), dtype=np.float32) + boxes = np.array([[-1.0, -1.0, 0.0, 0.0]]) + result = YOLOSeg.crop_mask(masks, boxes) + assert result.sum() == 0 + + # Test partial overlap + masks = np.ones((1, 5, 5), dtype=np.float32) + boxes = np.array([[2.0, 2.0, 4.0, 4.0]]) + result = YOLOSeg.crop_mask(masks, boxes) + expected = np.zeros((1, 5, 5)) + expected[0, 2:4, 2:4] = 1 + np.testing.assert_array_equal(result, expected) + + def test_postprocess_multiple_detections(self): + n_classes = 2 + mask_dim = 32 + n_boxes = 10 + adapter = _make_seg_adapter( + n_classes=n_classes, + mask_dim=mask_dim, + n_boxes=n_boxes, + ) + model = YOLOSeg( + adapter, + configuration={ + "confidence_threshold": 0.1, + "labels": ["class_a", "class_b"], + }, + ) + det = np.zeros((1, 4 + n_classes + mask_dim, n_boxes), dtype=np.float32) + det[0, 0, 0] = 100.0 + det[0, 1, 0] = 100.0 + det[0, 2, 0] = 50.0 + det[0, 3, 0] = 50.0 + det[0, 4, 0] = 0.8 + + det[0, 0, 1] = 400.0 + det[0, 1, 1] = 400.0 + det[0, 2, 1] = 80.0 + det[0, 3, 1] = 80.0 + det[0, 4, 1] = 0.6 + + protos = rng.uniform(-1, 1, size=(1, mask_dim, 160, 160)).astype(np.float32) + meta = { + "original_shape": (480, 640, 3), + "resized_shape": (640, 640, 3), + } + result = model.postprocess({"detection": det, "protos": protos}, meta) + assert isinstance(result, InstanceSegmentationResult) + assert len(result.bboxes) >= 1 From e14364644a5940efed73a9b01313f3d3da2eafe1 Mon Sep 17 00:00:00 2001 From: kprokofi Date: Thu, 4 Jun 2026 01:02:42 +0900 Subject: [PATCH 2/4] shorter description --- model_api/src/model_api/models/yolo_seg.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/model_api/src/model_api/models/yolo_seg.py b/model_api/src/model_api/models/yolo_seg.py index 4d90520f..fbb6ddb1 100644 --- a/model_api/src/model_api/models/yolo_seg.py +++ b/model_api/src/model_api/models/yolo_seg.py @@ -3,21 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 # -"""Custom ModelAPI wrapper for YOLO instance-segmentation inference. - -YOLO segmentation models exported with ``end2end=False`` produce two outputs: - * Output 0: ``[1, num_classes + 4 + mask_dim, num_boxes]`` — detection - predictions concatenated with mask coefficients. - * Output 1: ``[1, mask_dim, proto_h, proto_w]`` — mask prototypes. - -The standard ModelAPI ``YOLO11`` wrapper only handles a single detection -output. This wrapper extends it to decode instance masks by multiplying -mask coefficients by prototypes, cropping to bounding boxes, and resizing -to original image dimensions. - -The wrapper registers itself as ``"YOLO11-seg"`` so OVEngine can discover -it via ``model_type`` metadata in the exported IR. -""" +"""Custom ModelAPI wrapper for Ultralytics YOLO instance-segmentation inference.""" from __future__ import annotations From 2284e7713d0745547de3e4907d2d4f5129c527e9 Mon Sep 17 00:00:00 2001 From: kprokofi Date: Thu, 4 Jun 2026 01:29:35 +0900 Subject: [PATCH 3/4] add unit test --- model_api/src/model_api/models/yolo_seg.py | 4 +--- model_api/tests/unit/models/test_yolo_seg_model.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/model_api/src/model_api/models/yolo_seg.py b/model_api/src/model_api/models/yolo_seg.py index fbb6ddb1..4fc3f403 100644 --- a/model_api/src/model_api/models/yolo_seg.py +++ b/model_api/src/model_api/models/yolo_seg.py @@ -71,6 +71,7 @@ def parameters(cls): parameters["pad_value"].update_default_value(114) parameters["resize_type"].update_default_value("fit_to_window_letterbox") parameters["reverse_input_channels"].update_default_value(default_value=False) + parameters["scale_values"].update_default_value([255.0]) parameters["confidence_threshold"].update_default_value(0.25) parameters["iou_threshold"].update_default_value(0.5) return parameters @@ -125,9 +126,6 @@ def postprocess(self, outputs: dict[str, Any], meta: dict[str, Any]) -> Instance labels = labels[keep_nms] mask_coeffs = mask_coeffs[keep_nms] - if len(boxes_xyxy) == 0: - return self._empty_result(meta) - masks = self._decode_masks(mask_coeffs, protos, boxes_xyxy, meta) input_img_w = meta["original_shape"][1] diff --git a/model_api/tests/unit/models/test_yolo_seg_model.py b/model_api/tests/unit/models/test_yolo_seg_model.py index 042f0e62..18f4317f 100644 --- a/model_api/tests/unit/models/test_yolo_seg_model.py +++ b/model_api/tests/unit/models/test_yolo_seg_model.py @@ -77,6 +77,20 @@ def test_init_invalid_output_count(self): with pytest.raises(WrapperError): YOLOSeg(adapter, configuration={}) + def test_init_invalid_output_shapes(self): + adapter = MagicMock(spec=InferenceAdapter) + image_meta = FakeMetadata(shape=[1, 3, 640, 640], layout="NCHW") + adapter.get_input_layers.return_value = {"image": image_meta} + adapter.get_output_layers.return_value = { + "a": FakeMetadata(shape=[1, 10]), + "b": FakeMetadata(shape=[1, 20]), + } + adapter.get_rt_info.side_effect = _RT_INFO_ERROR + adapter.embed_preprocessing = MagicMock() + adapter.load_model.return_value = None + with pytest.raises(WrapperError, match="Expected one rank-3 detection output"): + YOLOSeg(adapter, configuration={}) + def test_init_invalid_channel_dim(self): adapter = MagicMock(spec=InferenceAdapter) image_meta = FakeMetadata(shape=[1, 3, 640, 640], layout="NCHW") From db94c2983da10c56b30c0c5634a31c30cd3da43e Mon Sep 17 00:00:00 2001 From: kprokofi Date: Thu, 4 Jun 2026 06:47:16 +0900 Subject: [PATCH 4/4] minor fix linter --- model_api/tests/unit/models/test_yolo_seg_model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/model_api/tests/unit/models/test_yolo_seg_model.py b/model_api/tests/unit/models/test_yolo_seg_model.py index 18f4317f..e7814100 100644 --- a/model_api/tests/unit/models/test_yolo_seg_model.py +++ b/model_api/tests/unit/models/test_yolo_seg_model.py @@ -12,7 +12,6 @@ import numpy as np import pytest - from model_api.adapters.inference_adapter import InferenceAdapter from model_api.models.model import WrapperError from model_api.models.result import InstanceSegmentationResult