diff --git a/model_api/pyproject.toml b/model_api/pyproject.toml index 7338eb24..1cffd3f4 100644 --- a/model_api/pyproject.toml +++ b/model_api/pyproject.toml @@ -28,7 +28,7 @@ classifiers = [ ] dependencies = [ "numpy>=1.16.6", - "opencv-python-headless~=4.0", + "opencv-python-headless", "openvino>=2025.3", "pillow", ] diff --git a/model_api/src/model_api/models/result/segmentation.py b/model_api/src/model_api/models/result/segmentation.py index a2cafa0c..4e4e06ab 100644 --- a/model_api/src/model_api/models/result/segmentation.py +++ b/model_api/src/model_api/models/result/segmentation.py @@ -212,9 +212,9 @@ def hist(self) -> dict[str, float]: ranges=[0, 255], ) hist = {} - for i, count in enumerate(outHist): + for i, count in enumerate(outHist.flatten()): if count > 0: - hist[str(i)] = count[0].item() / self.resultImage.size + hist[str(i)] = count.item() / self.resultImage.size return hist diff --git a/model_api/tests/unit/results/test_sseg_result.py b/model_api/tests/unit/results/test_sseg_result.py index 26d0f83d..4ed5921f 100644 --- a/model_api/tests/unit/results/test_sseg_result.py +++ b/model_api/tests/unit/results/test_sseg_result.py @@ -3,8 +3,80 @@ # SPDX-License-Identifier: Apache-2.0 # +from unittest.mock import patch + import numpy as np +import pytest from model_api.models.result import Contour +from model_api.models.result.segmentation import ImageResultWithSoftPrediction + + +class TestImageResultWithSoftPredictionHist: + """Tests for hist() method supporting OpenCV 4 and 5 return types.""" + + @pytest.fixture + def result_image(self): + """Create a simple test image with known pixel distribution.""" + # 10x10 image: 50 pixels of value 0, 30 pixels of value 128, 20 pixels of value 255 + img = np.zeros((10, 10), dtype=np.uint8) + img[0:5, :] = 0 # 50 pixels + img[5:8, :] = 128 # 30 pixels + img[8:10, :] = 255 # 20 pixels + return img + + @pytest.fixture + def image_result(self, result_image): + """Create ImageResultWithSoftPrediction instance.""" + return ImageResultWithSoftPrediction( + resultImage=result_image, + soft_prediction=np.zeros((10, 10, 3)), + saliency_map=np.zeros((10, 10)), + feature_vector=np.zeros((256,)), + ) + + def test_hist_with_opencv4_column_vector(self, image_result): + """OpenCV 4 returns column vector [N, 1] from calcHist.""" + # Simulate OpenCV 4 output: column vector shape [256, 1] + opencv4_hist = np.zeros((256, 1), dtype=np.float32) + opencv4_hist[0, 0] = 50.0 # 50 pixels of value 0 + opencv4_hist[128, 0] = 30.0 # 30 pixels of value 128 + opencv4_hist[255, 0] = 20.0 # 20 pixels of value 255 + + with patch("cv2.calcHist", return_value=opencv4_hist): + hist = image_result.hist() + + assert hist == {"0": 0.5, "128": 0.3, "255": 0.2} + + def test_hist_with_opencv5_1d_array(self, image_result): + """OpenCV 5+ returns 1D array [N] from calcHist.""" + # Simulate OpenCV 5 output: 1D array shape [256] + opencv5_hist = np.zeros((256,), dtype=np.float32) + opencv5_hist[0] = 50.0 # 50 pixels of value 0 + opencv5_hist[128] = 30.0 # 30 pixels of value 128 + opencv5_hist[255] = 20.0 # 20 pixels of value 255 + + with patch("cv2.calcHist", return_value=opencv5_hist): + hist = image_result.hist() + + assert hist == {"0": 0.5, "128": 0.3, "255": 0.2} + + def test_hist_both_formats_produce_same_result(self, image_result): + """Both OpenCV formats produce identical histogram output.""" + opencv4_hist = np.zeros((256, 1), dtype=np.float32) + opencv4_hist[10, 0] = 25.0 + opencv4_hist[20, 0] = 75.0 + + opencv5_hist = np.zeros((256,), dtype=np.float32) + opencv5_hist[10] = 25.0 + opencv5_hist[20] = 75.0 + + with patch("cv2.calcHist", return_value=opencv4_hist): + hist_v4 = image_result.hist() + + with patch("cv2.calcHist", return_value=opencv5_hist): + hist_v5 = image_result.hist() + + assert hist_v4 == hist_v5 def test_contour_type(): diff --git a/model_api/uv.lock b/model_api/uv.lock index c0015287..7b774415 100644 --- a/model_api/uv.lock +++ b/model_api/uv.lock @@ -2093,7 +2093,7 @@ requires-dist = [ { name = "onnx", marker = "extra == 'onnx'" }, { name = "onnxruntime", marker = "extra == 'examples'" }, { name = "onnxruntime", marker = "extra == 'onnx'" }, - { name = "opencv-python-headless", specifier = "~=4.0" }, + { name = "opencv-python-headless" }, { name = "openvino", specifier = ">=2025.3" }, { name = "pillow" }, { name = "sympy", marker = "extra == 'examples'" }, diff --git a/model_converter/uv.lock b/model_converter/uv.lock index 8f94d6fd..3823f4f7 100644 --- a/model_converter/uv.lock +++ b/model_converter/uv.lock @@ -1345,7 +1345,7 @@ requires-dist = [ { name = "onnx", marker = "extra == 'onnx'" }, { name = "onnxruntime", marker = "extra == 'examples'" }, { name = "onnxruntime", marker = "extra == 'onnx'" }, - { name = "opencv-python-headless", specifier = "~=4.0" }, + { name = "opencv-python-headless" }, { name = "openvino", specifier = ">=2025.3" }, { name = "pillow" }, { name = "sympy", marker = "extra == 'examples'" },