-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add metadata viewer as napari widget #1195
Conversation
Reviewer's Guide by SourceryThis pull request adds a new feature to display metadata for layers in napari as a widget. The implementation includes a new LayerMetadata class, updates to existing files to support this feature, and corresponding test cases. File-Level Changes
Tips
|
WalkthroughThe changes involve several enhancements to the PartSeg package, including the introduction of a new Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Napari
participant LayerMetadata
User->>Napari: Open viewer
Napari->>LayerMetadata: Initialize LayerMetadata
LayerMetadata->>LayerMetadata: Create layer selector
LayerMetadata->>LayerMetadata: Populate metadata
User->>LayerMetadata: Select image layer
LayerMetadata->>LayerMetadata: Update displayed metadata
Possibly related PRs
Recent review detailsConfiguration used: CodeRabbit UI Files selected for processing (6)
Additional comments not posted (15)
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @Czaki - I've reviewed your changes - here's some feedback:
Overall Comments:
- There's a typo in the filename 'metadata_wiewer.py'. It should be 'metadata_viewer.py'.
Here's what I looked at during the review
- 🟡 General issues: 3 issues found
- 🟢 Security: all looks good
- 🟡 Testing: 3 issues found
- 🟢 Complexity: all looks good
- 🟢 Documentation: all looks good
Help me be more useful! Please click 👍 or 👎 on each comment to tell me if it was helpful.
@@ -0,0 +1,29 @@ | |||
import napari |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (typo): Fix typo in filename: 'metadata_wiewer.py' should be 'metadata_viewer.py'
Ensure consistency in naming across the project. This typo should be corrected in all references to this file, including import statements and documentation.
def reset_choices(self): | ||
self.layer_selector.reset_choices() | ||
|
||
def update_metadata(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Consider adding error handling and user feedback in update_metadata method
The method could benefit from handling cases where the selected layer has no metadata. Consider adding a check and displaying a message to the user if no metadata is available.
def update_metadata(self):
if self.layer_selector.value is None:
return
try:
metadata = self.layer_selector.value.metadata
if not metadata:
raise ValueError("No metadata available")
# Process metadata here
except (AttributeError, ValueError) as e:
# Handle the error (e.g., display a message to the user)
print(f"Error updating metadata: {str(e)}")
|
||
self.setLayout(layout) | ||
self.update_metadata() | ||
self.layer_selector.changed.connect(self.update_metadata) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (performance): Evaluate performance impact of updating metadata on every layer change
For large metadata sets, updating on every layer change could potentially cause UI lag. Consider implementing a debounce mechanism or updating only when the metadata view is active.
from functools import debounce
# ... (in the __init__ method)
self.layer_selector.changed.connect(self.debounced_update_metadata)
@debounce(0.3)
def debounced_update_metadata(self):
self.update_metadata()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no debounce in funtols
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 == {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Test case for empty metadata initialization
This test case checks the initialization of LayerMetadata with an empty viewer. Consider adding assertions to verify the initial state of other widget components, such as the layer_selector.
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 == {} | |
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 == {} | |
assert widget.layer_selector.count() == 0 | |
assert widget.layer_selector.currentText() == "" | |
assert not widget.metadata_table.rowCount() |
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) | ||
qtbot.addWidget(widget) | ||
assert widget._dict_viewer._data == {"foo": "bar"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Test case for initialization with a layer
This test verifies the initialization of LayerMetadata with a viewer containing a layer with metadata. Consider adding assertions to check if the layer_selector is properly populated and if the correct layer is selected.
def test_init_with_layer(self, make_napari_viewer, qtbot):
viewer = make_napari_viewer()
layer = viewer.add_image(
np.ones((10, 10)),
contrast_limits=[0, 1],
metadata={"foo": "bar"},
)
widget = LayerMetadata(viewer)
qtbot.addWidget(widget)
assert widget._dict_viewer._data == {"foo": "bar"}
assert widget.layer_selector.count() == 1
assert widget.layer_selector.currentText() == layer.name
) | ||
widget = LayerMetadata(viewer) | ||
qtbot.addWidget(widget) | ||
assert widget._dict_viewer._data == {"foo": "bar"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Test case for adding a layer after initialization
This test checks the behavior when a layer is added after the widget is initialized. Consider adding a test to verify the behavior when multiple layers are added or when layers are removed.
def test_multiple_layers(self, make_napari_viewer, qtbot):
viewer = make_napari_viewer()
widget = LayerMetadata(viewer)
qtbot.addWidget(widget)
viewer.add_image(np.ones((10, 10)), metadata={"foo": "bar"})
viewer.add_image(np.zeros((5, 5)), metadata={"baz": "qux"})
widget.reset_choices()
assert "foo" in widget._dict_viewer._data
assert "baz" in widget._dict_viewer._data
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Outside diff range and nitpick comments (2)
package/PartSeg/plugins/napari_widgets/metadata_wiewer.py (1)
26-29
: Consider using a guard clause to simplify theupdate_metadata
method.The
update_metadata
method can be slightly refactored to use a guard clause, which can improve readability and reduce nesting.Apply this diff to refactor the method:
def update_metadata(self): - if self.layer_selector.value is None: - return - self._dict_viewer.set_data(self.layer_selector.value.metadata) + if self.layer_selector.value is not None: + self._dict_viewer.set_data(self.layer_selector.value.metadata)package/tests/test_PartSeg/test_napari_widgets.py (1)
633-636
: Please clarify the purpose of this test.The
test_enum
function checks if the string representation ofCompareType.lower_threshold
andFlowType.dark_center
enums contains a space character. It would be helpful to understand the significance of this check and why the presence of a space character is important. Consider adding comments to explain the reasoning behind this test.
Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files selected for processing (6)
- package/PartSeg/common_gui/dict_viewer.py (1 hunks)
- package/PartSeg/napari.yaml (2 hunks)
- package/PartSeg/plugins/napari_widgets/init.py (2 hunks)
- package/PartSeg/plugins/napari_widgets/metadata_wiewer.py (1 hunks)
- package/PartSegCore/napari_plugins/loader.py (1 hunks)
- package/tests/test_PartSeg/test_napari_widgets.py (2 hunks)
Additional comments not posted (10)
package/PartSeg/plugins/napari_widgets/metadata_wiewer.py (1)
9-29
: LGTM!The
LayerMetadata
class is well-structured and follows good practices:
- The class is properly initialized with a Napari viewer.
- The UI is set up using a
QVBoxLayout
with a layer selector and a dictionary viewer.- The
reset_choices
method provides a way to reset the layer selector choices.- The
update_metadata
method updates the dictionary viewer with the metadata of the selected layer.package/PartSeg/plugins/napari_widgets/__init__.py (2)
15-15
: LGTM!The new import statement for
LayerMetadata
is consistent with the existing imports and follows the correct syntax and naming conventions. The AI-generated summary confirms that this change is intentional and enhances the module's functionality by making theLayerMetadata
widget available for use.
31-31
: LGTM!The addition of
LayerMetadata
to the__all__
list is consistent with the new import statement added earlier. The AI-generated summary confirms that this change is intentional and reflects the addition of theLayerMetadata
widget to the list of exported entities.package/PartSeg/common_gui/dict_viewer.py (1)
19-19
: LGTM!The change to use a dedicated method
set_data
for setting the input data is a good practice for encapsulation. It allows for better control over the data assignment logic and provides a centralized place for handling the input data, including the case when it isNone
.The method also ensures that the widget state is consistent with the input data by clearing the tree and filling it with the new data.
Overall, this change enhances the structure of the code and does not introduce any apparent issues.
package/PartSegCore/napari_plugins/loader.py (1)
29-29
: LGTM!The change looks good. Adding the image metadata to the layer configuration can provide useful contextual information for further processing or display.
package/PartSeg/napari.yaml (2)
77-79
: LGTM!The new contribution entry for the "View Layer Metadata" feature is properly defined, and the
python_name
field correctly references theLayerMetadata
widget in thePartSeg.plugins.napari_widgets
module.
163-164
: LGTM!The new command entry in the
widgets
section for the "View Layer Metadata" feature is properly defined, and thecommand
field correctly references the corresponding contribution entry with the idPartSeg.Metadata
.package/tests/test_PartSeg/test_napari_widgets.py (3)
603-608
: LGTM!The
test_init
method correctly tests the initialization ofLayerMetadata
widget when no layers are present in the viewer. The assertion verifies the expected behavior.
609-619
: LGTM!The
test_init_with_layer
method correctly tests the initialization ofLayerMetadata
widget when an image layer with metadata is present in the viewer. The assertion verifies that the widget's internal data dictionary reflects the layer's metadata.
620-632
: LGTM!The
test_add_layer_post_init
method correctly tests the behavior ofLayerMetadata
widget when a layer is added after the widget initialization. The assertions verify that the widget's internal data dictionary is initially empty and updates correctly to reflect the metadata of the newly added image layer after callingwidget.reset_choices()
.
Quality Gate passedIssues Measures |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Outside diff range and nitpick comments (1)
package/PartSeg/plugins/napari_widgets/metadata_viewer.py (1)
9-29
: LGTM! TheLayerMetadata
class is well-designed and implemented.The class provides a useful feature to display layer metadata in the napari viewer. It integrates well with the existing napari viewer and layer system, and the use of
magicgui
andqtpy
widgets ensures a consistent UI experience.To further improve the code quality, consider adding the following:
- Type hints for the
layer_selector
and_dict_viewer
attributes:from magicgui.widgets import Widget from PartSeg.common_gui.dict_viewer import DictViewer class LayerMetadata(QWidget): def __init__(self, viewer: napari.Viewer): ... self.layer_selector: Widget = create_widget(annotation=NapariImage, label="Layer", options={}) self._dict_viewer: DictViewer = DictViewer() ...
- Docstrings for the class and its methods:
class LayerMetadata(QWidget): """A widget to display the metadata of a selected layer in a napari viewer.""" def __init__(self, viewer: napari.Viewer): """Initialize the LayerMetadata widget. Parameters ---------- viewer : napari.Viewer The napari viewer instance. """ ... def reset_choices(self): """Reset the choices in the layer selector widget.""" ... def update_metadata(self): """Update the displayed metadata based on the selected layer.""" ...
Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files selected for processing (3)
- package/PartSeg/plugins/napari_widgets/init.py (2 hunks)
- package/PartSeg/plugins/napari_widgets/metadata_viewer.py (1 hunks)
- package/tests/test_PartSeg/test_napari_widgets.py (2 hunks)
Files skipped from review as they are similar to previous changes (2)
- package/PartSeg/plugins/napari_widgets/init.py
- package/tests/test_PartSeg/test_napari_widgets.py
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## develop #1195 +/- ##
===========================================
+ Coverage 93.07% 93.08% +0.01%
===========================================
Files 209 210 +1
Lines 32927 32974 +47
===========================================
+ Hits 30646 30694 +48
+ Misses 2281 2280 -1 ☔ View full report in Codecov by Sentry. |
@coderabbitai full review |
Actions performedFull review triggered. |
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:
Enhancements:
Tests:
Summary by CodeRabbit
Release Notes
New Features
LayerMetadata
widget for selecting layers and displaying their metadata.Bug Fixes
Tests
LayerMetadata
widget to ensure proper functionality during initialization and layer updates.