Skip to content

Commit

Permalink
feat: Add metadata viewer as napari widget (#1195)
Browse files Browse the repository at this point in the history
<!-- Generated by sourcery-ai[bot]: start summary -->

## Summary by Sourcery

Add a new LayerMetadata widget to the napari viewer for displaying layer
metadata, update the DictViewer class to use a set_data method, and
include tests for the new widget.

New Features:
- Introduce a new LayerMetadata widget for viewing metadata of layers in
the napari viewer.

Enhancements:
- Modify the DictViewer class to use a set_data method for initializing
data.

Tests:
- Add tests for the LayerMetadata widget to verify its initialization
and functionality with and without layers.

<!-- Generated by sourcery-ai[bot]: end summary -->

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

## Release Notes

- **New Features**
- Introduced the "View Layer Metadata" feature in the Napari interface,
allowing users to view metadata associated with image layers.
- Added a new `LayerMetadata` widget for selecting layers and displaying
their metadata.

- **Bug Fixes**
- Enhanced metadata handling for image layers, ensuring relevant
information is included in layer configurations.

- **Tests**
- Added a new test class for the `LayerMetadata` widget to ensure proper
functionality during initialization and layer updates.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
Czaki authored Sep 20, 2024
1 parent 48f82c1 commit 6ee3209
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package/PartSeg/common_gui/dict_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def __init__(self, data: dict | None = None):
self.layout.addWidget(self.tree)
self.setLayout(self.layout)
self.tree.setHeaderLabels(["Key", "Value"])
self.data = data
self.set_data(data)

def set_data(self, data: dict | None):
if data is None:
Expand Down
5 changes: 5 additions & 0 deletions package/PartSeg/napari.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ contributions:
- id: PartSeg.CopyLabels
title: Create Copy Labels
python_name: PartSeg.plugins.napari_widgets:CopyLabelsWidget
- id: PartSeg.Metadata
title: View Layer Metadata
python_name: PartSeg.plugins.napari_widgets:LayerMetadata
readers:
- command: PartSeg.load_roi_project
filename_patterns:
Expand Down Expand Up @@ -157,3 +160,5 @@ contributions:
display_name: Threshold
- command: PartSeg.Watershed
display_name: Watershed
- command: PartSeg.Metadata
display_name: Layer Metadata
2 changes: 2 additions & 0 deletions package/PartSeg/plugins/napari_widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from PartSeg.plugins.napari_widgets.copy_labels import CopyLabelsWidget
from PartSeg.plugins.napari_widgets.lables_control import LabelSelector
from PartSeg.plugins.napari_widgets.mask_create_widget import MaskCreate
from PartSeg.plugins.napari_widgets.metadata_viewer import LayerMetadata
from PartSeg.plugins.napari_widgets.roi_extraction_algorithms import ROIAnalysisExtraction, ROIMaskExtraction
from PartSeg.plugins.napari_widgets.search_label_widget import SearchLabel

Expand All @@ -27,6 +28,7 @@
"Watershed",
"ImageColormap",
"LabelSelector",
"LayerMetadata",
"MaskCreate",
"ROIAnalysisExtraction",
"ROIMaskExtraction",
Expand Down
29 changes: 29 additions & 0 deletions package/PartSeg/plugins/napari_widgets/metadata_viewer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import napari
from magicgui.widgets import create_widget
from napari.layers import Image as NapariImage
from qtpy.QtWidgets import QVBoxLayout, QWidget

from PartSeg.common_gui.dict_viewer import DictViewer


class LayerMetadata(QWidget):
def __init__(self, viewer: napari.Viewer):
super().__init__()
self._viewer = viewer
self.layer_selector = create_widget(annotation=NapariImage, label="Layer", options={})
self._dict_viewer = DictViewer()
layout = QVBoxLayout()
layout.addWidget(self.layer_selector.native)
layout.addWidget(self._dict_viewer)

self.setLayout(layout)
self.update_metadata()
self.layer_selector.changed.connect(self.update_metadata)

def reset_choices(self):
self.layer_selector.reset_choices()

def update_metadata(self):
if self.layer_selector.value is None:
return
self._dict_viewer.set_data(self.layer_selector.value.metadata)
1 change: 1 addition & 0 deletions package/PartSegCore/napari_plugins/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def _image_to_layers(project_info, scale, translate):
"name": project_info.image.channel_names[i],
"blending": "additive",
"translate": translate,
"metadata": project_info.image.metadata,
},
"image",
)
Expand Down
36 changes: 36 additions & 0 deletions package/tests/test_PartSeg/test_napari_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from PartSeg.plugins.napari_widgets import (
CopyLabelsWidget,
ImageColormap,
LayerMetadata,
MaskCreate,
ROIAnalysisExtraction,
ROIMaskExtraction,
Expand Down Expand Up @@ -600,6 +601,41 @@ def test_check_uncheck(self, copy_labels, qtbot):
assert not copy_labels.checkbox_layout.itemAt(0).widget().isChecked()


class TestLayerMetadata:
def test_init(self, make_napari_viewer, qtbot):
viewer = make_napari_viewer()
widget = LayerMetadata(viewer)
qtbot.addWidget(widget)
assert widget._dict_viewer._data == {}

def test_init_with_layer(self, make_napari_viewer, qtbot):
viewer = make_napari_viewer()
viewer.add_image(
np.ones((10, 10)),
contrast_limits=[0, 1],
metadata={"foo": "bar"},
)
widget = LayerMetadata(viewer)
viewer.window.add_dock_widget(widget)
widget.reset_choices()
assert widget.layer_selector.value is not None
assert widget._dict_viewer._data == {"foo": "bar"}

def test_add_layer_post_init(self, make_napari_viewer, qtbot):
viewer = make_napari_viewer()
widget = LayerMetadata(viewer)
viewer.window.add_dock_widget(widget)
assert widget._dict_viewer._data == {}
viewer.add_image(
np.ones((10, 10)),
contrast_limits=[0, 1],
metadata={"foo": "bar"},
)
widget.reset_choices()
assert widget.layer_selector.value is not None
assert widget._dict_viewer._data == {"foo": "bar"}


def test_enum():
assert " " in str(CompareType.lower_threshold)
assert " " in str(FlowType.dark_center)

0 comments on commit 6ee3209

Please sign in to comment.