From 3f490d0bc1aed32f49f2d7277ad85a2e4297d6e4 Mon Sep 17 00:00:00 2001 From: David Day Date: Fri, 6 Dec 2024 12:08:14 -0800 Subject: [PATCH 1/5] feat: Show filename when importing with PartSeg in napari Add filename as a prefix to layer names to improve usability. --- package/PartSegCore/napari_plugins/loader.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package/PartSegCore/napari_plugins/loader.py b/package/PartSegCore/napari_plugins/loader.py index 35e1961cb..cc8a70703 100644 --- a/package/PartSegCore/napari_plugins/loader.py +++ b/package/PartSegCore/napari_plugins/loader.py @@ -1,3 +1,4 @@ +from os import path import typing from importlib.metadata import version @@ -52,12 +53,13 @@ def add_color(image: Image, idx: int) -> dict: # noqa: ARG001 def _image_to_layers(project_info, scale, translate): + layer_name_prefix = path.basename(project_info.file_path) res_layers = [] if project_info.image.name == "ROI" and project_info.image.channels == 1: res_layers.append( ( project_info.image.get_channel(0), - {"scale": scale, "name": project_info.image.channel_names[0], "translate": translate}, + {"scale": scale, "name": f"{layer_name_prefix} {project_info.image.channel_names[0]}", "translate": translate}, "labels", ) ) @@ -67,7 +69,7 @@ def _image_to_layers(project_info, scale, translate): project_info.image.get_channel(i), { "scale": scale, - "name": project_info.image.channel_names[i], + "name": f"{layer_name_prefix} {project_info.image.channel_names[i]}", "blending": "additive", "translate": translate, "metadata": project_info.image.metadata, From 5db81bc017f19e3c0d964e6dc1c5afdc07f7f65c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 20:13:26 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- package/PartSegCore/napari_plugins/loader.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/package/PartSegCore/napari_plugins/loader.py b/package/PartSegCore/napari_plugins/loader.py index cc8a70703..e7df16841 100644 --- a/package/PartSegCore/napari_plugins/loader.py +++ b/package/PartSegCore/napari_plugins/loader.py @@ -1,6 +1,6 @@ -from os import path import typing from importlib.metadata import version +from os import path import numpy as np from packaging.version import parse as parse_version @@ -59,7 +59,11 @@ def _image_to_layers(project_info, scale, translate): res_layers.append( ( project_info.image.get_channel(0), - {"scale": scale, "name": f"{layer_name_prefix} {project_info.image.channel_names[0]}", "translate": translate}, + { + "scale": scale, + "name": f"{layer_name_prefix} {project_info.image.channel_names[0]}", + "translate": translate, + }, "labels", ) ) From 50bb0e667158bc0a38410bd6628da70d900d4476 Mon Sep 17 00:00:00 2001 From: David Day Date: Sat, 14 Dec 2024 16:14:43 -0800 Subject: [PATCH 3/5] feat: Use SettingEditor to control layer naming format --- package/PartSeg/plugins/napari_io/loader.py | 14 +++++++---- .../plugins/napari_widgets/_settings.py | 22 ++++++++++++++++++ package/PartSegCore/universal_const.py | 23 +++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/package/PartSeg/plugins/napari_io/loader.py b/package/PartSeg/plugins/napari_io/loader.py index d3162d619..3b2da536e 100644 --- a/package/PartSeg/plugins/napari_io/loader.py +++ b/package/PartSeg/plugins/napari_io/loader.py @@ -1,6 +1,6 @@ +import os import typing from importlib.metadata import version -from os import path import numpy as np from packaging.version import parse as parse_version @@ -10,6 +10,7 @@ from PartSegCore.analysis import ProjectTuple from PartSegCore.io_utils import LoadBase, WrongFileTypeException from PartSegCore.mask.io_functions import MaskProjectTuple +from PartSegCore.universal_const import format_layer_name from PartSegImage import Image @@ -55,7 +56,8 @@ def add_color(image: Image, idx: int) -> dict: # noqa: ARG001 def _image_to_layers(project_info, scale, translate): - layer_name_prefix = path.basename(project_info.file_path) + settings = get_settings() + filename = os.path.basename(project_info.file_path) res_layers = [] if project_info.image.name == "ROI" and project_info.image.channels == 1: res_layers.append( @@ -63,7 +65,9 @@ def _image_to_layers(project_info, scale, translate): project_info.image.get_channel(0), { "scale": scale, - "name": f"{layer_name_prefix} {project_info.image.channel_names[0]}", + "name": format_layer_name( + settings.layer_naming_format, filename, project_info.image.channel_names[0] + ), "translate": translate, }, "labels", @@ -75,7 +79,9 @@ def _image_to_layers(project_info, scale, translate): project_info.image.get_channel(i), { "scale": scale, - "name": f"{layer_name_prefix} {project_info.image.channel_names[i]}", + "name": format_layer_name( + settings.layer_naming_format, filename, project_info.image.channel_names[i] + ), "blending": "additive", "translate": translate, "metadata": project_info.image.metadata, diff --git a/package/PartSeg/plugins/napari_widgets/_settings.py b/package/PartSeg/plugins/napari_widgets/_settings.py index d83807455..2781916d1 100644 --- a/package/PartSeg/plugins/napari_widgets/_settings.py +++ b/package/PartSeg/plugins/napari_widgets/_settings.py @@ -7,6 +7,7 @@ from PartSeg.common_backend import napari_get_settings from PartSegCore import Units from PartSegCore.json_hooks import PartSegEncoder +from PartSegCore.universal_const import LayerNamingFormat _SETTINGS = None @@ -29,6 +30,14 @@ def io_units(self) -> Units: def io_units(self, value: Units): self.set("io_units", value) + @property + def layer_naming_format(self) -> LayerNamingFormat: + return self.get("layer_naming_format", LayerNamingFormat.channel_only) + + @layer_naming_format.setter + def layer_naming_format(self, value: LayerNamingFormat): + self.set("layer_naming_format", value) + def get_settings() -> PartSegNapariSettings: global _SETTINGS # noqa: PLW0603 # pylint: disable=global-statement @@ -51,6 +60,12 @@ def __init__(self): self.units_select.changed.connect(self.units_selection_changed) self.settings.connect_("io_units", self.units_changed) self.append(self.units_select) + self.layer_naming_select = create_widget( + self.settings.layer_naming_format, annotation=LayerNamingFormat, label="Format for Layer Name" + ) + self.layer_naming_select.changed.connect(self.layer_naming_format_selection_changed) + self.settings.connect_("layer_naming_format", self.layer_naming_format_changed) + self.append(self.layer_naming_select) def units_selection_changed(self, value): self.settings.io_units = value @@ -58,3 +73,10 @@ def units_selection_changed(self, value): def units_changed(self): self.units_select.value = self.settings.io_units + + def layer_naming_format_selection_changed(self, value): + self.settings.layer_naming_format = value + self.settings.dump() + + def layer_naming_format_changed(self): + self.layer_naming_select.value = self.settings.layer_naming_format diff --git a/package/PartSegCore/universal_const.py b/package/PartSegCore/universal_const.py index f5c56728f..7c98db401 100644 --- a/package/PartSegCore/universal_const.py +++ b/package/PartSegCore/universal_const.py @@ -17,3 +17,26 @@ def __str__(self): _UNITS_LIST = ["mm", "µm", "nm", "pm"] UNIT_SCALE = [10**3, 10**6, 10**9, 10**12] + + +@register_class() +class LayerNamingFormat(Enum): + channel_only = 0 + filename_only = 1 + filename_channel = 2 + channel_filename = 3 + + def __str__(self): + return self.name.replace("_", " ") + + +def format_layer_name(layer_format: LayerNamingFormat, file_name: str, channel_name: str) -> str: + if layer_format == LayerNamingFormat.channel_only: + return channel_name + if layer_format == LayerNamingFormat.filename_only: + return file_name + if layer_format == LayerNamingFormat.filename_channel: + return f"{file_name} | {channel_name}" + if layer_format == LayerNamingFormat.channel_filename: + return f"{channel_name} | {file_name}" + raise ValueError("Unknown format") From 1bd93f7a1d116889c683aba7d9c81ae028a29fcc Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 17 Dec 2024 10:05:39 +0100 Subject: [PATCH 4/5] add tests --- package/tests/test_PartSeg/test_napari_widgets.py | 11 +++++++++++ package/tests/test_PartSegCore/test_io.py | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/package/tests/test_PartSeg/test_napari_widgets.py b/package/tests/test_PartSeg/test_napari_widgets.py index 32f37125c..c0fe4e084 100644 --- a/package/tests/test_PartSeg/test_napari_widgets.py +++ b/package/tests/test_PartSeg/test_napari_widgets.py @@ -57,6 +57,7 @@ from PartSegCore.segmentation.noise_filtering import NoiseFilterSelection from PartSegCore.segmentation.threshold import DoubleThresholdSelection, ThresholdSelection from PartSegCore.segmentation.watershed import WatershedSelection +from PartSegCore.universal_const import LayerNamingFormat NAPARI_GE_5_0 = parse_version(version("napari")) >= parse_version("0.5.0a1") NAPARI_GE_4_19 = parse_version(version("napari")) >= parse_version("0.4.19a1") @@ -655,3 +656,13 @@ def test_change_units(self, qtbot): assert s.io_units == Units.nm s.io_units = Units.mm assert w.units_select.value == Units.mm + + def test_change_layer_name_format(self, qtbot): + s = _settings.get_settings() + s.layer_naming_format = LayerNamingFormat.channel_only + w = _settings.SettingsEditor() + qtbot.addWidget(w.native) + w.layer_naming_select.value = LayerNamingFormat.channel_filename + assert s.layer_naming_format == LayerNamingFormat.channel_filename + s.layer_naming_format = LayerNamingFormat.channel_only + assert w.layer_naming_select.value == LayerNamingFormat.channel_only diff --git a/package/tests/test_PartSegCore/test_io.py b/package/tests/test_PartSegCore/test_io.py index d57fd8788..e42d51cff 100644 --- a/package/tests/test_PartSegCore/test_io.py +++ b/package/tests/test_PartSegCore/test_io.py @@ -62,6 +62,7 @@ from PartSegCore.segmentation.noise_filtering import DimensionType from PartSegCore.segmentation.segmentation_algorithm import ThresholdAlgorithm from PartSegCore.segmentation.threshold import RangeThresholdSelection +from PartSegCore.universal_const import LayerNamingFormat, format_layer_name from PartSegCore.utils import ProfileDict, check_loaded_dict from PartSegImage import Image @@ -911,6 +912,19 @@ def test_load(self, tmp_path): assert res.roi_info.roi.shape == (1, 1, 100, 100) +@pytest.mark.parametrize( + ("value", "expected"), + [ + (LayerNamingFormat.channel_only, "b"), + (LayerNamingFormat.filename_only, "a"), + (LayerNamingFormat.filename_channel, "a | b"), + (LayerNamingFormat.channel_filename, "b | a"), + ], +) +def test_format_layer_name(value, expected): + assert format_layer_name(value, "a", "b") == expected + + UPDATE_NAME_JSON = """ {"problematic set": { "__MeasurementProfile__": true, From 6b7e582bdf18345cb43655d6b16b31c8de5ffe55 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 17 Dec 2024 13:03:38 +0100 Subject: [PATCH 5/5] no cover --- package/PartSegCore/universal_const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/PartSegCore/universal_const.py b/package/PartSegCore/universal_const.py index 7c98db401..c2d914355 100644 --- a/package/PartSegCore/universal_const.py +++ b/package/PartSegCore/universal_const.py @@ -39,4 +39,4 @@ def format_layer_name(layer_format: LayerNamingFormat, file_name: str, channel_n return f"{file_name} | {channel_name}" if layer_format == LayerNamingFormat.channel_filename: return f"{channel_name} | {file_name}" - raise ValueError("Unknown format") + raise ValueError("Unknown format") # pragma: no cover