Skip to content
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: Allow set units that will be used for load/save data using PartSeg as napari plugin #1228

Merged
merged 7 commits into from
Dec 10, 2024

Conversation

Czaki
Copy link
Collaborator

@Czaki Czaki commented Dec 9, 2024

This PR add "Settings Editor" widget to allow edit PartSeg specific settings.

This PR add option to set to which units, the scale should be set when loading to the viewer.

This PR also contains refactor that is mentioned here: #1226 (comment)

Summary by Sourcery

Add a 'Settings Editor' widget to the PartSeg napari plugin to enable editing of specific settings, including setting units for data scaling. Refactor the plugin structure by relocating files to a new 'napari_io' directory for improved organization.

New Features:

  • Introduce a 'Settings Editor' widget to allow users to edit PartSeg specific settings within the napari plugin.
  • Add functionality to set the units for scaling when loading data into the viewer.

Enhancements:

  • Refactor the PartSeg napari plugin structure by moving files from 'napari_plugins' to 'napari_io' for better organization.

Summary by CodeRabbit

  • New Features
    • Introduced a new SettingsEditor for managing unit settings within the PartSeg interface.
    • Enhanced plugin handling based on the version of the napari library.
  • Bug Fixes
    • Improved clarity of error messages related to plugin loading.
  • Documentation
    • Updated command signatures in the configuration to reflect new module structure.
  • Chores
    • Restructured import paths across various modules to enhance organization and maintainability.

@Czaki Czaki added this to the 0.16.0 milestone Dec 9, 2024
Copy link
Contributor

sourcery-ai bot commented Dec 9, 2024

Reviewer's Guide by Sourcery

This PR implements a settings editor widget for PartSeg's napari plugin and refactors the plugin's IO handling. The main changes include adding functionality to specify units for data loading/saving and reorganizing the napari plugin-related code structure.

Class diagram for PartSegNapariSettings and SettingsEditor

classDiagram
    class PartSegNapariSettings {
        - PartSegNapariEncoder json_encoder_class
        + Units io_units
        + void set_io_units(Units value)
    }

    class SettingsEditor {
        - PartSegNapariSettings settings
        - Widget units_select
        + void units_changed(Units value)
    }

    PartSegNapariSettings <|-- SettingsEditor
    SettingsEditor o-- PartSegNapariSettings: uses
    SettingsEditor o-- Widget: contains
Loading

File-Level Changes

Change Details Files
Added new Settings Editor widget with unit selection functionality
  • Created new SettingsEditor class that inherits from Container
  • Added unit selection widget with live updates
  • Implemented settings persistence through dump() method
  • Added io_units property to PartSegNapariSettings class
package/PartSeg/plugins/napari_widgets/_settings.py
Refactored napari plugin structure by moving IO-related code
  • Moved napari plugin IO code from PartSegCore to PartSeg package
  • Updated import paths in napari.yaml configuration
  • Renamed napari_plugins directory to napari_io
package/PartSeg/napari.yaml
package/PartSeg/plugins/napari_io/load_image.py
package/PartSeg/plugins/napari_io/load_mask_project.py
package/PartSeg/plugins/napari_io/load_masked_image.py
package/PartSeg/plugins/napari_io/load_roi_project.py
Updated project loading to use configured units
  • Modified project_to_layers function to use settings-specified units for scaling
  • Added unit scale normalization during image loading
package/PartSeg/plugins/napari_io/loader.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time. You can also use
    this command to specify where the summary should be inserted.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

coderabbitai bot commented Dec 9, 2024

Walkthrough

The pull request introduces modifications to the PartSeg application, primarily focusing on updating hidden imports and import paths related to the napari plugin structure. The changes include altering the hiddenimports in launcher.spec, updating the error messages in the _test_imports function, and reorganizing the command references in the napari.yaml file. Additionally, new functionality is added through the introduction of a SettingsEditor class and enhancements to the PartSegNapariSettings class for managing unit settings.

Changes

File Change Summary
launcher.spec Updated hiddenimports from "PartSegCore.napari_plugins" to "PartSegCore.napari_io"; added conditional inclusion of builtins.yaml based on napari_version.
package/PartSeg/launcher_main.py Changed error message from "napari_plugins not loaded" to "napari_io not loaded"; adjusted import statements.
package/PartSeg/napari.yaml Updated python_name attributes for commands to point to PartSeg.plugins.napari_io; added new command PartSeg.Settings.
package/PartSeg/plugins/napari_io/__init__.py Changed import path for functions from PartSegCore.napari_plugins to PartSeg.plugins.napari_io.
package/PartSeg/plugins/napari_io/load_image.py Updated import path for partseg_loader from PartSegCore.napari_plugins.loader to PartSeg.plugins.napari_io.loader.
package/PartSeg/plugins/napari_io/load_mask_project.py Updated import path for partseg_loader from PartSegCore.napari_plugins.loader to PartSeg.plugins.napari_io.loader.
package/PartSeg/plugins/napari_io/load_masked_image.py Updated import path for partseg_loader from PartSegCore.napari_plugins.loader to PartSeg.plugins.napari_io.loader.
package/PartSeg/plugins/napari_io/load_roi_project.py Updated import path for partseg_loader from PartSegCore.napari_plugins.loader to PartSeg.plugins.napari_io.loader.
package/PartSeg/plugins/napari_io/loader.py Added new imports and modified logic in adjust_color and project_to_layers functions; updated function signatures.
package/PartSeg/plugins/napari_widgets/__init__.py Added SettingsEditor to the __all__ tuple.
package/PartSeg/plugins/napari_widgets/_settings.py Introduced io_units property and SettingsEditor class; changed return type of get_settings.
package/tests/test_PartSeg/test_napari_io.py Updated import paths to reflect new plugin structure; no changes to test logic.

Possibly related PRs

🐰 In the land of code, where plugins play,
A hop and a skip, we’ve changed the way.
From napari_plugins to napari_io,
New settings to manage, watch our features grow!
With colors adjusted and paths all set,
PartSeg's now ready, no need to fret! 🌟


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Experiment)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a 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 and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

self.units_select.changed.connect(self.units_changed)
self.append(self.units_select)

def units_changed(self, value):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Add error handling for settings.dump() operation

The settings.dump() operation could fail due to I/O errors. Consider wrapping it in a try-except block to handle potential failures gracefully.

Suggested implementation:

    def units_changed(self, value):
        self.settings.io_units = value
        try:
            self.settings.dump()
        except Exception as e:
            import logging
            logging.getLogger(__name__).error(f"Failed to save settings: {e}")
            # Show error to user if possible
            if hasattr(self, "show_error_message"):
                self.show_error_message("Failed to save settings")

Note: The error handling includes logging and attempts to show a user message if the widget has that capability. You may need to:

  1. Ensure logging is properly configured in your application
  2. Implement or modify the show_error_message method if you want to display errors to users
  3. Consider adding more specific exception types (like IOError) if you know what specific errors might occur

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (2)
package/PartSeg/plugins/napari_widgets/_settings.py (1)

24-31: Consider adding input validation for io_units.

The io_units property setter accepts any Units value without validation. Consider adding validation to ensure only supported units are accepted.

 @io_units.setter
 def io_units(self, value: Units):
+    if not isinstance(value, Units):
+        raise ValueError(f"Expected Units enum value, got {type(value)}")
     self.set("io_units", value)
package/PartSeg/plugins/napari_io/loader.py (1)

Line range hint 22-38: LGTM: Robust color handling implementation

The function correctly handles different color formats and edge cases. Consider adding more specific type hints for better code maintainability.

Consider updating the type hints to be more specific:

-def adjust_color(color: typing.Union[str, list[int]]) -> typing.Union[str, tuple[float]]:
+def adjust_color(color: typing.Union[str, list[int]]) -> typing.Union[str, tuple[float, float, float]]:
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b68237f and 3fa548f.

📒 Files selected for processing (12)
  • launcher.spec (1 hunks)
  • package/PartSeg/launcher_main.py (1 hunks)
  • package/PartSeg/napari.yaml (3 hunks)
  • package/PartSeg/plugins/napari_io/__init__.py (1 hunks)
  • package/PartSeg/plugins/napari_io/load_image.py (1 hunks)
  • package/PartSeg/plugins/napari_io/load_mask_project.py (1 hunks)
  • package/PartSeg/plugins/napari_io/load_masked_image.py (1 hunks)
  • package/PartSeg/plugins/napari_io/load_roi_project.py (1 hunks)
  • package/PartSeg/plugins/napari_io/loader.py (2 hunks)
  • package/PartSeg/plugins/napari_widgets/__init__.py (2 hunks)
  • package/PartSeg/plugins/napari_widgets/_settings.py (3 hunks)
  • package/tests/test_PartSeg/test_napari_io.py (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • package/PartSeg/plugins/napari_io/load_mask_project.py
  • package/PartSeg/plugins/napari_io/init.py
🔇 Additional comments (13)
package/PartSeg/plugins/napari_widgets/__init__.py (2)

2-2: LGTM: Clean import addition

The import of SettingsEditor is properly placed and follows the module structure, aligning with the PR's objective of introducing settings management functionality.


37-37: LGTM: Proper export declaration

The addition of SettingsEditor to __all__ correctly exposes the new functionality while maintaining alphabetical ordering in the exports list.

package/PartSeg/plugins/napari_io/load_masked_image.py (2)

Line range hint 8-15: LGTM! Well-structured napari reader implementation

The implementation follows napari reader plugin best practices:

  • Correctly implements the reader plugin specification
  • Efficiently uses generator expression with next() for path matching
  • Properly handles the case when no matching reader is found by returning None
  • Validates file existence before returning the loader

4-4: Verify the import path change across the codebase

The import path change from PartSegCore.napari_plugins.loader to PartSeg.plugins.napari_io.loader appears to be part of a larger refactoring effort to better organize plugin-related code. Let's verify this change is consistent across the codebase.

✅ Verification successful

Import path change is consistent and properly implemented

The verification shows:

  • No instances of the old import path (PartSegCore.napari_plugins.loader) remain in the codebase
  • The new import path (PartSeg.plugins.napari_io.loader) is consistently used across all napari plugin files
  • The loader.py file exists in the expected new location
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining old import paths and verify consistency of new imports

# Check for any remaining old imports
echo "Checking for old import paths..."
rg "from PartSegCore.napari_plugins.loader import partseg_loader"

# Verify new import usage
echo "Checking new import paths..."
rg "from PartSeg.plugins.napari_io.loader import partseg_loader"

# Check if loader.py exists in the new location
echo "Verifying loader.py location..."
fd "loader.py" "package/PartSeg/plugins/napari_io/"

Length of output: 895

package/PartSeg/plugins/napari_io/load_image.py (1)

3-3: Verify the import path change impact.

The import path has been updated from PartSegCore.napari_plugins.loader to PartSeg.plugins.napari_io.loader. While this aligns with the refactoring goals, we should verify the new path's existence and backward compatibility.

✅ Verification successful

Import path change is correctly implemented

The verification confirms that:

  • The partseg_loader function exists in the new location package/PartSeg/plugins/napari_io/loader.py
  • All imports have been updated to use the new path PartSeg.plugins.napari_io.loader
  • No references to the old import path PartSegCore.napari_plugins.loader remain in the codebase
  • The function is properly imported and used in multiple napari IO-related modules
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify the existence of the new loader module and its exports
ast-grep --pattern 'partseg_loader' package/PartSeg/plugins/napari_io/loader.py

# Check for any remaining references to the old import path
rg "PartSegCore.napari_plugins.loader" -g '!*.md'

Length of output: 232


Script:

#!/bin/bash
# Let's verify the function implementation and its exports
cat package/PartSeg/plugins/napari_io/loader.py

# Check if there are any imports of this function elsewhere
rg "from.*partseg_loader" -g '!*.md'

# Check for any direct usage of partseg_loader
rg "\bpartseg_loader\b" -g '!*.md'

Length of output: 6977

package/PartSeg/plugins/napari_io/load_roi_project.py (1)

4-4: LGTM - Consistent with the import path refactoring.

The import path change is consistent with the changes in load_image.py.

package/PartSeg/plugins/napari_widgets/_settings.py (1)

Line range hint 33-44: LGTM - Return type change is appropriate.

The return type change from PartSettings to PartSegNapariSettings correctly reflects the actual type being returned.

package/PartSeg/plugins/napari_io/loader.py (2)

7-8: LGTM: New imports support unit scaling functionality

The added imports properly support the new feature allowing users to specify units for scale when loading data.


89-90: Verify scaling calculation with different unit combinations

The scaling calculation looks correct, but it's important to verify the behavior with different combinations of input and output units.

✅ Verification successful

Based on the search results, I can verify the scaling calculation implementation. The code shows consistent unit handling across the codebase:

  1. UNIT_SCALE is defined in PartSegCore/universal_const.py as [10**3, 10**6, 10**9, 10**12], representing scaling factors for different units.
  2. The scaling calculation in loader.py uses project_info.image.normalized_scaling(UNIT_SCALE[settings.io_units.value]), which is consistent with other unit conversions in the codebase.
  3. The test files (particularly test_measurements.py and test_mask_partition.py) demonstrate proper unit conversion and validation of scaling calculations.

Unit scaling implementation is correctly implemented

The scaling calculation in loader.py follows the established pattern used throughout the codebase for unit conversions. The implementation is well-tested and consistent with other unit-handling code.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other places where UNIT_SCALE is used to ensure consistent unit handling
rg "UNIT_SCALE" -A 3

# Look for any existing unit conversion tests
fd -e py test_.*unit.*

Length of output: 24476

package/PartSeg/napari.yaml (2)

7-7: LGTM: Consistent plugin path reorganization

The command paths have been consistently updated to reflect the new plugin structure, improving code organization.

Also applies to: 10-10, 13-13, 16-16, 19-19, 22-22, 25-25


80-82: LGTM: Settings Editor widget properly integrated

The Settings Editor widget has been correctly added and registered in both the commands and widgets sections, implementing the PR objective.

Also applies to: 168-169

package/tests/test_PartSeg/test_napari_io.py (1)

11-22: Clean import restructuring for better modularity

The reorganization of imports from PartSegCore.napari_plugins to PartSeg.plugins.napari_io improves the code organization by:

  1. Separating IO-related functionality into a dedicated module
  2. Maintaining backward compatibility as test cases remain unchanged
  3. Following a more intuitive package structure

Let's verify that all napari-related imports are consistently updated across the codebase:

✅ Verification successful

Import restructuring is complete and consistent

The verification shows that:

  1. No references to the old import path PartSegCore.napari_plugins exist in the codebase
  2. The new import path PartSeg.plugins.napari_io is consistently used across:
    • The napari plugin configuration (napari.yaml)
    • Test files
    • Plugin implementation files
    • Internal imports within the plugin module
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining references to the old import path
rg "PartSegCore.napari_plugins" 

# Verify the new import path usage
rg "PartSeg.plugins.napari_io"

Length of output: 2344

launcher.spec (1)

93-93: Ensure PyInstaller includes napari IO functionality

The addition of "PartSegCore.napari_io" to hiddenimports is necessary to ensure proper packaging of the napari IO functionality. However, we should verify if both import paths are needed during the transition.

Let's check if both old and new import paths are being used:

✅ Verification successful

The addition of PartSegCore.napari_io to hiddenimports is correct and necessary

The codebase search confirms that the project has transitioned to using the napari_io module extensively, with multiple files under package/PartSeg/plugins/napari_io/ implementing various IO functionalities. The old napari_plugins references are minimal and appear to be for different purposes. The PyInstaller spec file's hiddenimports addition ensures proper packaging of the new napari IO functionality.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if both old and new paths are used in the codebase
echo "Checking for old import path usage..."
rg "from PartSegCore.napari_plugins import"

echo "Checking for new import path usage..."
rg "from PartSegCore.napari_io import"

Length of output: 256


Script:

#!/bin/bash
# Let's try a broader search for import patterns and module usage
echo "Checking for napari_plugins module references..."
rg "napari_plugins" -l

echo "Checking for napari_io module references..."
rg "napari_io" -l

echo "Checking PyInstaller spec file content..."
cat launcher.spec

Length of output: 8598

Comment on lines 46 to 56
class SettingsEditor(Container):
def __init__(self):
super().__init__()
self.settings = get_settings()
self.units_select = create_widget(self.settings.io_units, annotation=Units, label="Units for io")
self.units_select.changed.connect(self.units_changed)
self.append(self.units_select)

def units_changed(self, value):
self.settings.io_units = value
self.settings.dump()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for settings persistence.

The settings persistence in units_changed() should handle potential I/O errors when saving settings.

 def units_changed(self, value):
     self.settings.io_units = value
-    self.settings.dump()
+    try:
+        self.settings.dump()
+    except IOError as e:
+        # Consider using a proper logging mechanism or UI notification
+        print(f"Failed to save settings: {e}")

Also, consider adding a docstring to explain the widget's purpose and usage.

 class SettingsEditor(Container):
+    """Widget for editing PartSeg-specific napari plugin settings.
+
+    This widget provides UI controls for modifying settings such as:
+    - Units used for loading/saving data
+    """
     def __init__(self):
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class SettingsEditor(Container):
def __init__(self):
super().__init__()
self.settings = get_settings()
self.units_select = create_widget(self.settings.io_units, annotation=Units, label="Units for io")
self.units_select.changed.connect(self.units_changed)
self.append(self.units_select)
def units_changed(self, value):
self.settings.io_units = value
self.settings.dump()
class SettingsEditor(Container):
"""Widget for editing PartSeg-specific napari plugin settings.
This widget provides UI controls for modifying settings such as:
- Units used for loading/saving data
"""
def __init__(self):
super().__init__()
self.settings = get_settings()
self.units_select = create_widget(self.settings.io_units, annotation=Units, label="Units for io")
self.units_select.changed.connect(self.units_changed)
self.append(self.units_select)
def units_changed(self, value):
self.settings.io_units = value
try:
self.settings.dump()
except IOError as e:
# Consider using a proper logging mechanism or UI notification
print(f"Failed to save settings: {e}")

@@ -33,7 +33,7 @@ def _test_imports(): # pragma: no cover
raise ImportError("napari_widgets not loaded")

if "load_image" not in dir(napari_plugins):
raise ImportError("napari_plugins not loaded")
raise ImportError("napari_io not loaded")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Import statement needs to be updated for consistency

The import statement from PartSegCore import napari_plugins on line 29 needs to be updated since the codebase now uses napari_io module located in PartSeg.plugins.napari_io. The error message was updated but the import statement was not aligned with this change.

  • Update the import in package/PartSeg/launcher_main.py to use the new module path
  • The old import from PartSegCore is no longer valid as the functionality has moved to PartSeg.plugins.napari_io
🔗 Analysis chain

Verify import statement consistency

The error message has been updated to reflect the module reorganization, but we should verify that the import statement on line 29 (from PartSegCore import napari_plugins) is also updated accordingly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for remaining references to the old module name
rg "napari_plugins" --type py

# Search for the new module references
rg "napari_io" --type py

Length of output: 2139

Copy link

codecov bot commented Dec 10, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 93.15%. Comparing base (b68237f) to head (76a8702).
Report is 1 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1228      +/-   ##
===========================================
+ Coverage    93.12%   93.15%   +0.02%     
===========================================
  Files          210      210              
  Lines        33178    33217      +39     
===========================================
+ Hits         30897    30943      +46     
+ Misses        2281     2274       -7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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/tests/test_PartSeg/test_napari_widgets.py (2)

642-644: Remove unnecessary empty lines

These consecutive empty lines are not needed and should be removed.


644-655: Enhance test coverage and documentation

The test class could be improved in several ways:

  1. Add docstrings to explain the purpose of each test method
  2. Enhance test_create to verify the initial state of the widget
  3. Make test_change_units more comprehensive by:
    • Verifying the initial state
    • Testing multiple unit changes
    • Testing all available unit options

Consider applying this improvement:

 class TestSettingsWidget:
+    """Test cases for the Settings Editor widget."""
+
     def test_create(self, qtbot):
+        """Test that the Settings Editor widget is created correctly."""
         w = _settings.SettingsEditor()
         qtbot.addWidget(w.native)
+        assert w.units_select.value == _settings.get_settings().io_units
 
     def test_change_units(self, qtbot):
+        """Test that changing units in the widget updates the settings."""
         s = _settings.get_settings()
         s.io_units = Units.µm
         w = _settings.SettingsEditor()
         qtbot.addWidget(w.native)
+        # Verify initial state
+        assert w.units_select.value == Units.µm
+        
+        # Test multiple unit changes
         w.units_select.value = Units.nm
         assert s.io_units == Units.nm
+        
+        w.units_select.value = Units.mm
+        assert s.io_units == Units.mm
+        
+        # Test all available unit options
+        for unit in Units:
+            w.units_select.value = unit
+            assert s.io_units == unit
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3fa548f and e207ddd.

📒 Files selected for processing (4)
  • .github/project_dict.pws (2 hunks)
  • package/PartSeg/common_backend/__init__.py (2 hunks)
  • package/PartSeg/launcher_main.py (1 hunks)
  • package/tests/test_PartSeg/test_napari_widgets.py (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .github/project_dict.pws
🚧 Files skipped from review as they are similar to previous changes (1)
  • package/PartSeg/launcher_main.py
🔇 Additional comments (3)
package/tests/test_PartSeg/test_napari_widgets.py (1)

48-48: LGTM!

The import of Units from PartSegCore is correctly placed and necessary for the new unit tests.

package/PartSeg/common_backend/__init__.py (2)

5-5: LGTM: Clean import addition

The addition of contextlib import is appropriate for the new error handling approach.


18-21: 🛠️ Refactor suggestion

Consider more specific error handling

Using contextlib.suppress(Exception) silently catches all exceptions, which might hide important errors from users. Consider:

  1. Catching specific exceptions that you expect (e.g., FileNotFoundError, PermissionError)
  2. Logging the error for debugging purposes

Here's a suggested improvement:

     if path is not None:
-        with contextlib.suppress(Exception):
-            return _napari_get_settings(path)
+        try:
+            return _napari_get_settings(path)
+        except (FileNotFoundError, PermissionError) as e:
+            import logging
+            logging.debug(f"Failed to load settings from {path}: {e}")
     return _napari_get_settings()

Let's check what specific exceptions might occur when loading settings:

Copy link

sonarcloud bot commented Dec 10, 2024

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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/_settings.py (2)

Line range hint 33-43: Add error handling for file operations.

The function performs file operations without error handling, which could fail in various scenarios (permissions, disk space, etc.).

Consider wrapping the file operations in try-except blocks:

 def get_settings() -> PartSegNapariSettings:
     global _SETTINGS
     if _SETTINGS is None:
         napari_settings = napari_get_settings()
         if hasattr(napari_settings, "path"):
             save_path = napari_settings.path
         else:
             save_path = os.path.dirname(napari_settings.config_path)
-        _SETTINGS = PartSegNapariSettings(os.path.join(save_path, "PartSeg_napari_plugins"))
-        _SETTINGS.load()
+        try:
+            _SETTINGS = PartSegNapariSettings(os.path.join(save_path, "PartSeg_napari_plugins"))
+            _SETTINGS.load()
+        except (IOError, OSError) as e:
+            import logging
+            logging.getLogger(__name__).error(f"Failed to initialize settings: {e}")
+            # Fallback to in-memory settings
+            _SETTINGS = PartSegNapariSettings(None)
     return _SETTINGS

46-53: Add class documentation.

The class lacks a docstring explaining its purpose and usage.

Add a descriptive docstring:

 class SettingsEditor(Container):
+    """Widget for editing PartSeg-specific napari plugin settings.
+
+    This widget provides a user interface for modifying settings such as:
+    - Units used for loading/saving data
+    """
     def __init__(self):
         super().__init__()
         self.settings = get_settings()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e207ddd and 76a8702.

📒 Files selected for processing (2)
  • package/PartSeg/plugins/napari_widgets/_settings.py (3 hunks)
  • package/tests/test_PartSeg/test_napari_widgets.py (3 hunks)
🔇 Additional comments (3)
package/PartSeg/plugins/napari_widgets/_settings.py (2)

24-31: LGTM! Well-implemented property with proper type hints.

The implementation of the io_units property with getter and setter methods is clean and follows best practices.


55-57: Add error handling for settings persistence.

The settings persistence operation could fail and should be handled gracefully.

package/tests/test_PartSeg/test_napari_widgets.py (1)

644-657: LGTM! Well-structured tests with good coverage.

The test class provides comprehensive coverage of the settings editor functionality, including:

  • Widget creation
  • Unit value changes
  • Settings synchronization

@Czaki Czaki merged commit 917c93a into develop Dec 10, 2024
55 checks passed
@Czaki Czaki deleted the units_napari_settings branch December 10, 2024 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant