Skip to content

Commit

Permalink
feat: Show filename when importing with PartSeg in napari (#1226)
Browse files Browse the repository at this point in the history
Hi,

Thank you for developing this fantastic tool! It has been incredibly
valuable for our research.

While working with PartSeg in Napari, we noticed that the filename does
not appear in the layer names, making distinguishing between multiple
imported files challenging. To address this, I’ve updated the code in
this pull request to add the filename as a prefix to the layer names.

I’m open to feedback on this approach and would love your thoughts or
suggestions for further improvement!

Best,
David

## Summary by Sourcery

New Features:
- Add filename as a prefix to layer names when importing with PartSeg in
Napari.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a new method for adjusting color values, enhancing color
processing capabilities.
- Added conditional functionality for adding color based on the version
of the application.
- Improved layer naming conventions by incorporating file name prefixes
for better clarity.
	- Added a new property for selecting layer naming formats in settings.
- Introduced a new enumeration for layer naming formats with multiple
options.
	- Added new test cases to validate layer naming format functionality.

- **Bug Fixes**
- Enhanced handling of alpha channels in color strings to ensure
accurate color representation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Grzegorz Bokota <[email protected]>
  • Loading branch information
3 people authored Dec 18, 2024
1 parent 4118e35 commit 1f1a35f
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 2 deletions.
16 changes: 14 additions & 2 deletions package/PartSeg/plugins/napari_io/loader.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import typing
from importlib.metadata import version

Expand All @@ -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


Expand Down Expand Up @@ -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",
)
)
Expand All @@ -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,
Expand Down
22 changes: 22 additions & 0 deletions package/PartSeg/plugins/napari_widgets/_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -51,10 +60,23 @@ 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
self.settings.dump()

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
23 changes: 23 additions & 0 deletions package/PartSegCore/universal_const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
11 changes: 11 additions & 0 deletions package/tests/test_PartSeg/test_napari_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
14 changes: 14 additions & 0 deletions package/tests/test_PartSegCore/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 1f1a35f

Please sign in to comment.