From fefaccdc06b15f60f8dcb4243a2d4a39ae4eb5ff Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Sat, 21 Sep 2024 08:08:27 +0200 Subject: [PATCH] fix coverage --- package/PartSegImage/image.py | 35 ++++++++++++------- package/tests/test_PartSegImage/test_image.py | 14 ++++++-- .../test_PartSegImage/test_image_writer.py | 10 +++--- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/package/PartSegImage/image.py b/package/PartSegImage/image.py index cb66bd254..740fa0b35 100644 --- a/package/PartSegImage/image.py +++ b/package/PartSegImage/image.py @@ -1,6 +1,7 @@ from __future__ import annotations import re +import sys import typing import warnings from collections.abc import Iterable @@ -22,20 +23,31 @@ DEFAULT_SCALE_FACTOR = 10**9 +ch_par: dict[str, bool] -@dataclass +if sys.version_info[:2] > (3, 9): + ch_par = {"kw_only": True, "slots": True} +else: + ch_par = {} + + +@dataclass(**ch_par) class ChannelInfo: name: str - color_map: str | np.ndarray | None = None + color_map: str | np.ndarray | tuple | list | None = None contrast_limits: tuple[float, float] | None = None -@dataclass +@dataclass(**ch_par) class ChannelInfoFull: name: str color_map: str | np.ndarray contrast_limits: tuple[float, float] + def __post_init__(self): + if not isinstance(self.color_map, (str, np.ndarray)): + self.color_map = np.array(self.color_map) + def minimal_dtype(val: int): """ @@ -245,7 +257,8 @@ def _adjust_channel_info( if channel_info is None: ranges = [(np.min(x), np.max(x)) for x in channel_array] return [ - ChannelInfoFull(f"channel {i}", x[0], x[1]) for i, x in enumerate(zip(default_colors, ranges), start=1) + ChannelInfoFull(name=f"channel {i}", color_map=x[0], contrast_limits=x[1]) + for i, x in enumerate(zip(default_colors, ranges), start=1) ] channel_info = channel_info[: len(channel_array)] @@ -266,7 +279,11 @@ def _adjust_channel_info( ) for i, arr in enumerate(channel_array[len(res) :], start=len(channel_info)): - res.append(ChannelInfoFull(f"channel {i+1}", next(default_colors), (np.min(arr), np.max(arr)))) + res.append( + ChannelInfoFull( + name=f"channel {i+1}", color_map=next(default_colors), contrast_limits=(np.min(arr), np.max(arr)) + ) + ) return res @@ -892,14 +909,6 @@ def cut_image( ) def get_imagej_colors(self): - # TODO review - if self.default_coloring is None: - return None - try: - if len(self.default_coloring) != self.channels: - return None - except TypeError: - return None res = [] for color in self.default_coloring: diff --git a/package/tests/test_PartSegImage/test_image.py b/package/tests/test_PartSegImage/test_image.py index a4adb7268..596701508 100644 --- a/package/tests/test_PartSegImage/test_image.py +++ b/package/tests/test_PartSegImage/test_image.py @@ -6,7 +6,7 @@ from skimage.morphology import diamond from PartSegImage import Channel, ChannelInfo, Image, ImageWriter, TiffImageReader -from PartSegImage.image import FRAME_THICKNESS +from PartSegImage.image import FRAME_THICKNESS, _hex_to_rgb class TestImageBase: @@ -663,7 +663,8 @@ def test_merge_channel_props(): ) assert img.channel_names == ["channel 2", "strange"] - assert img.default_coloring == ["red", (128, 255, 0)] + assert img.default_coloring[0] == "red" + assert tuple(img.default_coloring[1]) == (128, 255, 0) assert img.ranges == [(0, 255), (0, 128)] @@ -695,3 +696,12 @@ def test_merge_channel_props_with_none(channel_name, default_coloring, ranges): assert img.default_coloring == (default_coloring or ["red"]) assert img.ranges == (ranges or [(0, 0)]) assert img.channel_names == (["channel"] if channel_name else ["channel 1"]) + + +def test_hex_to_rgb(): + assert _hex_to_rgb("#ff0000") == (255, 0, 0) + assert _hex_to_rgb("#00FF00") == (0, 255, 0) + assert _hex_to_rgb("#b00") == (187, 0, 0) + assert _hex_to_rgb("#B00") == (187, 0, 0) + with pytest.raises(ValueError, match="Invalid hex code format"): + _hex_to_rgb("#b000") diff --git a/package/tests/test_PartSegImage/test_image_writer.py b/package/tests/test_PartSegImage/test_image_writer.py index 491684218..5cb3535e3 100644 --- a/package/tests/test_PartSegImage/test_image_writer.py +++ b/package/tests/test_PartSegImage/test_image_writer.py @@ -46,7 +46,7 @@ def test_ome_save(tmp_path, bundle_test_dir, ome_xml, z_size): data, spacing=(27 * 10**-6, 6 * 10**-6, 6 * 10**-6), axes_order="ZYXC", - channel_info=[ChannelInfo("a"), ChannelInfo("b")], + channel_info=[ChannelInfo(name="a"), ChannelInfo(name="b")], shift=(10, 9, 8), name="Test", ) @@ -98,7 +98,7 @@ def test_imagej_write_all_metadata(tmp_path, data_test_dir): def test_imagej_save_color(tmp_path): - data = np.zeros((3, 20, 20), dtype=np.uint8) + data = np.zeros((4, 20, 20), dtype=np.uint8) data[:, 2:-2, 2:-2] = 20 img = Image( data, @@ -108,15 +108,17 @@ def test_imagej_save_color(tmp_path): ChannelInfo(name="ch1", color_map="blue", contrast_limits=(0, 20)), ChannelInfo(name="ch2", color_map="#FFAA00", contrast_limits=(0, 30)), ChannelInfo(name="ch3", color_map="#FB1", contrast_limits=(0, 25)), + ChannelInfo(name="ch4", color_map=(0, 180, 0), contrast_limits=(0, 22)), ], ) IMAGEJImageWriter.save(img, tmp_path / "image.tif") image2 = TiffImageReader.read_image(tmp_path / "image.tif") - assert image2.channel_names == ["ch1", "ch2", "ch3"] - assert image2.ranges == [(0, 20), (0, 30), (0, 25)] + assert image2.channel_names == ["ch1", "ch2", "ch3", "ch4"] + assert image2.ranges == [(0, 20), (0, 30), (0, 25), (0, 22)] assert tuple(image2.default_coloring[0][:, -1]) == (0, 0, 255) assert tuple(image2.default_coloring[1][:, -1]) == (255, 170, 0) assert tuple(image2.default_coloring[2][:, -1]) == (255, 187, 17) + assert tuple(image2.default_coloring[3][:, -1]) == (0, 180, 0) def test_save_mask_imagej(tmp_path):