diff --git a/package/PartSeg/plugins/napari_io/loader.py b/package/PartSeg/plugins/napari_io/loader.py index 158068697..3b2da536e 100644 --- a/package/PartSeg/plugins/napari_io/loader.py +++ b/package/PartSeg/plugins/napari_io/loader.py @@ -1,3 +1,4 @@ +import os import typing from importlib.metadata import version @@ -9,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 @@ -54,12 +56,20 @@ def add_color(image: Image, idx: int) -> dict: # noqa: ARG001 def _image_to_layers(project_info, scale, translate): + 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( ( project_info.image.get_channel(0), - {"scale": scale, "name": project_info.image.channel_names[0], "translate": translate}, + { + "scale": scale, + "name": format_layer_name( + settings.layer_naming_format, filename, project_info.image.channel_names[0] + ), + "translate": translate, + }, "labels", ) ) @@ -69,7 +79,9 @@ def _image_to_layers(project_info, scale, translate): project_info.image.get_channel(i), { "scale": scale, - "name": 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..c2d914355 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") # pragma: no cover 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,