Skip to content

Commit 93a1be6

Browse files
authored
Visualization bug fixes (#334)
* Minor fixes in iseg vis scene * Add none check for sseg saliency map visualization * Update pre-commit config * Update unit tests for scene * Apply pre-commit * Add render method to visualizer * Del unused imports * Fix linter * Fix naming in unit test
1 parent 4636e5c commit 93a1be6

File tree

5 files changed

+73
-9
lines changed

5 files changed

+73
-9
lines changed

src/python/model_api/visualizer/scene/segmentation/instance_segmentation.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ class InstanceSegmentationScene(Scene):
2020

2121
def __init__(self, image: Image, result: InstanceSegmentationResult, layout: Union[Layout, None] = None) -> None:
2222
# nosec as random is used for color generation
23-
self.color_per_label = {label: f"#{random.randint(0, 0xFFFFFF):06x}" for label in set(result.label_names)} # noqa: S311 # nosec B311
23+
g = random.Random(0) # noqa: S311 # nosec B311
24+
self.color_per_label = {label: f"#{g.randint(0, 0xFFFFFF):06x}" for label in set(result.label_names)} # nosec B311
2425
super().__init__(
2526
base=image,
2627
label=self._get_labels(result),
@@ -54,7 +55,7 @@ def _get_bounding_boxes(self, result: InstanceSegmentationResult) -> list[Boundi
5455

5556
def _get_overlays(self, result: InstanceSegmentationResult) -> list[Overlay]:
5657
overlays = []
57-
if len(result.saliency_map) > 0:
58+
if result.saliency_map is not None and len(result.saliency_map) > 0:
5859
labels_label_names_mapping = dict(zip(result.labels, result.label_names))
5960
for label, label_name in labels_label_names_mapping.items():
6061
saliency_map = result.saliency_map[label - 1]

src/python/model_api/visualizer/scene/segmentation/segmentation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def _get_overlays(self, result: ImageResultWithSoftPrediction) -> list[Overlay]:
3737
overlays.append(Overlay(class_map, label=f"Class {i}"))
3838

3939
# Add saliency map
40-
if result.saliency_map.size > 0:
40+
if result.saliency_map is not None and result.saliency_map.size > 0:
4141
saliency_map = cv2.cvtColor(result.saliency_map, cv2.COLOR_BGR2RGB)
4242
overlays.append(Overlay(saliency_map, label="Saliency Map"))
4343

src/python/model_api/visualizer/visualizer.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Visualizer for modelAPI."""
22

3-
# Copyright (C) 2024 Intel Corporation
3+
# Copyright (C) 2024-2025 Intel Corporation
44
# SPDX-License-Identifier: Apache-2.0
55

66
from __future__ import annotations # TODO: remove when Python3.9 support is dropped
@@ -42,18 +42,32 @@ class Visualizer:
4242
def __init__(self, layout: Layout | None = None) -> None:
4343
self.layout = layout
4444

45-
def show(self, image: Image | np.ndarray, result: Result) -> None:
45+
def show(self, image: Image.Image | np.ndarray, result: Result) -> None:
4646
if isinstance(image, np.ndarray):
4747
image = Image.fromarray(image)
4848
scene = self._scene_from_result(image, result)
4949
return scene.show()
5050

51-
def save(self, image: Image | np.ndarray, result: Result, path: Path) -> None:
51+
def save(self, image: Image.Image | np.ndarray, result: Result, path: Path) -> None:
5252
if isinstance(image, np.ndarray):
5353
image = Image.fromarray(image)
5454
scene = self._scene_from_result(image, result)
5555
scene.save(path)
5656

57+
def render(self, image: Image.Image | np.ndarray, result: Result) -> Image.Image | np.ndarray:
58+
is_numpy = isinstance(image, np.ndarray)
59+
60+
if is_numpy:
61+
image = Image.fromarray(image)
62+
63+
scene = self._scene_from_result(image, result)
64+
result_img: Image = scene.render()
65+
66+
if is_numpy:
67+
return np.array(result_img)
68+
69+
return result_img
70+
5771
def _scene_from_result(self, image: Image, result: Result) -> Scene:
5872
scene: Scene
5973
if isinstance(result, AnomalyResult):

tests/python/unit/visualizer/test_scene.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import numpy as np
99
from PIL import Image
10+
import pytest
1011

1112
from model_api.models.result import (
1213
AnomalyResult,
@@ -71,7 +72,8 @@ def test_detection_scene(mock_image: Image, tmpdir: Path):
7172
assert Path(tmpdir / "detection_scene.jpg").exists()
7273

7374

74-
def test_segmentation_scene(mock_image: Image, tmpdir: Path):
75+
@pytest.mark.parametrize("with_saliency_map", [True, False])
76+
def test_segmentation_scene(mock_image: Image, tmpdir: Path, with_saliency_map: bool):
7577
"""Test if the segmentation scene is created."""
7678
visualizer = Visualizer()
7779

@@ -85,7 +87,9 @@ def test_segmentation_scene(mock_image: Image, tmpdir: Path):
8587
),
8688
scores=np.array([0.85, 0.75]),
8789
label_names=["person", "car"],
88-
saliency_map=[np.ones((128, 128), dtype=np.uint8) * 255],
90+
saliency_map=[np.ones((128, 128), dtype=np.uint8) * 255]
91+
if with_saliency_map
92+
else None,
8993
feature_vector=np.array([1, 2, 3, 4]),
9094
)
9195

@@ -104,7 +108,9 @@ def test_segmentation_scene(mock_image: Image, tmpdir: Path):
104108
soft_prediction=np.ones(
105109
(3, 3, 3), dtype=np.float32
106110
), # 3 classes, 3x3 prediction
107-
saliency_map=np.ones((3, 3), dtype=np.uint8) * 255,
111+
saliency_map=np.ones((3, 3), dtype=np.uint8) * 255
112+
if with_saliency_map
113+
else None,
108114
feature_vector=np.array([1, 2, 3, 4]),
109115
)
110116

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Tests for visualizer."""
2+
3+
# Copyright (C) 2025 Intel Corporation
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
from pathlib import Path
7+
8+
import numpy as np
9+
from PIL import Image
10+
11+
from model_api.models.result import (
12+
AnomalyResult,
13+
)
14+
from model_api.visualizer import Visualizer
15+
16+
17+
def test_render(mock_image: Image, tmpdir: Path):
18+
"""Test Visualizer.render()."""
19+
heatmap = np.ones(mock_image.size, dtype=np.uint8)
20+
heatmap *= 255
21+
22+
mask = np.zeros(mock_image.size, dtype=np.uint8)
23+
mask[32:96, 32:96] = 255
24+
mask[40:80, 0:128] = 255
25+
26+
anomaly_result = AnomalyResult(
27+
anomaly_map=heatmap,
28+
pred_boxes=np.array([[0, 0, 128, 128], [32, 32, 96, 96]]),
29+
pred_label="Anomaly",
30+
pred_mask=mask,
31+
pred_score=0.85,
32+
)
33+
34+
visualizer = Visualizer()
35+
rendered_img = visualizer.render(mock_image, anomaly_result)
36+
37+
assert isinstance(rendered_img, Image.Image)
38+
assert np.array(rendered_img).shape == np.array(mock_image).shape
39+
40+
rendered_img_np = visualizer.render(np.array(mock_image), anomaly_result)
41+
42+
assert isinstance(rendered_img_np, np.ndarray)
43+
assert rendered_img_np.shape == np.array(mock_image).shape

0 commit comments

Comments
 (0)