Skip to content

feat: Use QScrollArea for components selection#1328

Merged
Czaki merged 22 commits intodevelopfrom
scroll_area_select_components
Mar 24, 2026
Merged

feat: Use QScrollArea for components selection#1328
Czaki merged 22 commits intodevelopfrom
scroll_area_select_components

Conversation

@Czaki
Copy link
Collaborator

@Czaki Czaki commented Oct 22, 2025

Summary by Sourcery

Make component selection scrollable and update ROI handling hooks to keep chosen components in sync with ROI changes.

New Features:

  • Allow scrolling through the list of selectable components using a scrollable container.

Enhancements:

  • Ensure selected components are automatically brought into view when toggled or changed.
  • Separate initialization of component checkboxes from updating their checked state via a dedicated setter.
  • Adjust the main ROI mask layout to use a splitter between algorithm selection and component selection panels.
  • Introduce a post-ROI-set hook for subclasses to perform additional updates after ROI changes.
  • Tighten type annotations for napari layer addition utility.

Summary by CodeRabbit

  • New Features

    • Component list now scrolls vertically and auto-scrolls toggled items into view
    • Component and algorithm selectors placed in a resizable split layout
    • Settings run a post-ROI sync so component choices stay in sync after ROI changes
  • Refactor

    • Component-selection API reorganized to support typed calls and a lighter update method that updates checks without full rebuild
  • Bug Fixes

    • Batch select/unselect updates checkboxes while emitting a single consolidated change signal

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Oct 22, 2025

Reviewer's Guide

Wrap the components selection widget in a vertically scrollable QScrollArea, adjust its API to separate component list initialization from selection updates, and integrate it into the ROI/stack settings lifecycle while improving layout behavior and type hints.

Sequence diagram for ROI update and components list initialization

sequenceDiagram
    participant Client
    participant StackSettings
    participant BaseSettings
    participant ChosenComponents

    Client->>StackSettings: set_project_info(data)
    activate StackSettings
    StackSettings->>StackSettings: compute new_roi_info and selected_components
    StackSettings->>BaseSettings: roi = new_roi_info
    activate BaseSettings
    BaseSettings->>BaseSettings: set _roi_info
    BaseSettings->>BaseSettings: clear _additional_layers
    BaseSettings->>StackSettings: post_roi_set()
    deactivate BaseSettings
    activate StackSettings
    StackSettings->>ChosenComponents: set_chose(roi_info.bound_info.keys(), [])
    deactivate StackSettings
    Note over StackSettings,ChosenComponents: Later, when restoring selection
    StackSettings->>ChosenComponents: set_chosen(selected_components)
    deactivate StackSettings
Loading

Class diagram for updated ChosenComponents and settings integration

classDiagram
    class QScrollArea

    class ChosenComponents {
        +dict~int,ComponentCheckBox~ check_box
        +QPushButton check_all_btn
        +QPushButton uncheck_all_btn
        +FlowLayout check_layout
        +check_change_signal
        +mouse_enter
        +mouse_leave
        +ChosenComponents()
        +other_component_choose(num int)
        +check_all()
        +uncheck_all()
        +remove_components()
        +new_choose(num int, chosen_components Sequence_int_)
        +set_chose(components_index Sequence_int_, chosen_components Sequence_int_)
        +set_chosen(chosen_components Sequence_int_)
        +check_change()
        +change_state(num int, val bool)
        +get_state(num int) bool
        +get_chosen() list_int_
    }

    QScrollArea <|-- ChosenComponents

    class BaseSettings {
        -ROIInfo _roi_info
        -dict _additional_layers
        +roi ROIInfo
        +roi_changed
        +set_roi(val Union_np_ndarray_ROIInfo_)
        +post_roi_set()
    }

    class StackSettings {
        +ChosenComponents chosen_components_widget
        +set_project_info(data MaskProjectTuple_or_PointsInfo)
        +_set_roi_info(state, new_roi_info ROIInfo, segmentation_parameters dict, list_of_components list_int_, save_chosen bool)
        +post_roi_set()
    }

    BaseSettings <|-- StackSettings
    StackSettings --> ChosenComponents : manages_selection
Loading

File-Level Changes

Change Details Files
Make the components chooser a scrollable QScrollArea-based widget and adjust its internal layout/behavior.
  • Change ChosenComponents to inherit from QScrollArea instead of QWidget and embed an inner QWidget as its scrollable content.
  • Move the existing button and FlowLayout setup onto the inner widget’s layout and enable vertical-only scrolling with ScrollBarAsNeeded.
  • Automatically scroll to make a component’s checkbox visible when it is toggled via other_component_choose or change_state.
package/PartSeg/_roi_mask/main_window.py
Refine the ChosenComponents API to separate component list creation from selection updates and handle optional inputs safely.
  • Update set_chose to accept an optional chosen_components sequence, defaulting to an empty list when None is provided.
  • Add a new set_chosen method that only updates which components are checked without recreating the component list.
  • Use set_chosen wherever only selection needs updating, avoiding unnecessary recreation of checkboxes.
package/PartSeg/_roi_mask/main_window.py
package/PartSeg/_roi_mask/stack_settings.py
Integrate the scrollable chooser into the main UI layout using a splitter and hook chooser updates into the ROI lifecycle.
  • Replace direct stacking of algorithm_choose_widget and choose_components with a vertical QSplitter so both can share space and resize together.
  • Introduce post_roi_set in the settings base class and override it to repopulate the components chooser after ROI changes using set_chose.
  • Ensure ROI assignment calls post_roi_set after updating _roi_info so UI stays in sync.
package/PartSeg/_roi_mask/main_window.py
package/PartSeg/_roi_mask/stack_settings.py
package/PartSeg/common_backend/base_settings.py
Tighten typing for napari layer utility function.
  • Add explicit type annotations to _add_layer_util arguments and return type for better clarity and static checking.
package/PartSeg/common_gui/napari_image_view.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. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the 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 exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Converted the components chooser into a scrollable QScrollArea with a revised selection API, added ImageSettings.post_roi_set hook invoked when ROI changes, updated StackSettings to use the new chooser API and added post-ROI synchronization, and tightened a napari image view type signature.

Changes

Cohort / File(s) Summary
ChosenComponents UI & API
package/PartSeg/_roi_mask/main_window.py
Changed ChosenComponents base from QWidgetQScrollArea; embed prior layout in inner widget, enable vertical scrolling, auto-scroll toggled checkbox via ensureWidgetVisible(...). API reshaped: new_choose(num, chosen_components) → typed new_choose(num: int, chosen_components: Sequence[int]), set_chose(...)set_components(..., chosen_components: Union[Sequence[int], None]=None), added set_chosen(...). Batch select/unselect block signals and emit once. AlgorithmOptions now places chooser in a vertical QSplitter.
StackSettings integration
package/PartSeg/_roi_mask/stack_settings.py
Now calls set_chosen(selected_components) / set_components(...) directly using selected_components or list_of_components instead of reconstructing keys from ROI parameters. Removed intermediate mapping in non-saving path. Added post_roi_set(self) to synchronize chooser via chosen_components_widget.set_components(...) with signals blocked.
Settings base hook
package/PartSeg/common_backend/base_settings.py
Added ImageSettings.post_roi_set(self) -> None and invoke it inside the roi setter (both clear and assign paths) so subclasses receive ROI updates; set_segmentation_result now assigns via self.roi = roi_info.
Type annotation refinements
package/PartSeg/common_gui/napari_image_view.py
Tightened ImageView._add_layer_util signature to index: int, layer: _NapariImage, filters: list[tuple[NoiseFilterType, float]] -> None (no runtime change).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ImageSettings
    participant StackSettings
    participant ChosenComponents
    participant UI

    User->>ImageSettings: change/select ROI
    ImageSettings->>ImageSettings: set _roi_info / clear layers
    ImageSettings->>StackSettings: post_roi_set()
    StackSettings->>StackSettings: compute list_of_components
    StackSettings->>ChosenComponents: set_components(component_keys, [])
    ChosenComponents->>UI: update checkbox widgets
    UI->>ChosenComponents: ensureWidgetVisible(toggled_checkbox)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I nudged the scroll, the checkboxes slide,

chosen bits peek from their cozy hide,
a new hook listens when ROIs arrive,
signals hush, then sing, the view comes alive,
I hop — the chooser and I both thrive.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the primary change: wrapping the components selection widget in a QScrollArea to enable vertical scrolling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch scroll_area_select_components

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

@codecov
Copy link

codecov bot commented Oct 22, 2025

Codecov Report

❌ Patch coverage is 90.14085% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.13%. Comparing base (23de3ff) to head (9250d33).
⚠️ Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
package/PartSeg/_roi_mask/main_window.py 88.67% 6 Missing ⚠️
package/PartSeg/_roi_mask/stack_settings.py 90.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1328      +/-   ##
===========================================
- Coverage    93.13%   93.13%   -0.01%     
===========================================
  Files          211      211              
  Lines        33272    33306      +34     
===========================================
+ Hits         30988    31019      +31     
- Misses        2284     2287       +3     

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Czaki Czaki added the skip check PR title skip spellcheking PR title label Dec 7, 2025
@Czaki Czaki added this to the 0.16.5 milestone Dec 7, 2025
@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 7, 2025

@Czaki Czaki marked this pull request as ready for review March 20, 2026 11:08
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 - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="package/PartSeg/_roi_mask/stack_settings.py" line_range="303-311" />
<code_context>
         if save_chosen:
</code_context>
<issue_to_address>
**question (bug_risk):** set_chosen no longer constrains components to the available ROI parameters list.

Previously, the `save_chosen` path constrained components via `set_chosen(sorted(state2.roi_extraction_parameters.keys()))`. Now it uses only `state2.selected_components` and relies on `post_roi_set` / `bound_info.keys()`, so the component list is no longer tied to `roi_extraction_parameters`. If `roi_extraction_parameters` and `bound_info` diverge (e.g. params exist for components no longer in the ROI), some components with saved parameters may become unselectable. If that’s unintended, consider deriving the component list from the parameter keys, or intersecting `roi_extraction_parameters` and `bound_info` explicitly.
</issue_to_address>

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.

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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package/PartSeg/_roi_mask/main_window.py`:
- Around line 473-476: The loop in set_chosen causes many stateChanged signals
and repeated image refreshes; wrap per-checkbox updates with signal blocking and
then trigger a single update at the end: in set_chosen (method name) call
check.blockSignals(True) before check.setChecked(...) and
check.blockSignals(False) after the loop (or for each check), then explicitly
emit the existing check_change_signal or call image_view.refresh_selected() once
after the loop to perform a single refresh; this prevents per-checkbox
stateChanged handlers from firing during the batch update invoked by
StackSettings.

In `@package/PartSeg/_roi_mask/stack_settings.py`:
- Around line 313-315: post_roi_set may be called before the UI widget exists
(chosen_components_widget is initialized to None), causing an AttributeError;
update post_roi_set to guard the call by checking that
self.chosen_components_widget is truthy (or hasattr/set to non-None) before
calling set_chose(self.roi_info.bound_info.keys(), []), so headless or pre-UI
usage of post_roi_set/setting self.roi doesn't raise.

In `@package/PartSeg/common_backend/base_settings.py`:
- Around line 158-163: The post_roi_set() hook is only called in one branch, so
ensure it runs on every ROI update path: call self.post_roi_set() before any
early returns in the ROI setter (so it executes even when ROI is set to None)
and add a call to self.post_roi_set() inside
BaseSettings.set_segmentation_result (the method referenced as
set_segmentation_result) after the internal ROI/state is updated; keep the
existing self.roi_changed.emit(self._roi_info) behavior but ensure
post_roi_set() is invoked consistently before or right after emitting so
subclasses are always synchronized.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 767af4e3-be73-433d-b152-e3afa0efc1f2

📥 Commits

Reviewing files that changed from the base of the PR and between 979f440 and 6278963.

📒 Files selected for processing (4)
  • package/PartSeg/_roi_mask/main_window.py
  • package/PartSeg/_roi_mask/stack_settings.py
  • package/PartSeg/common_backend/base_settings.py
  • package/PartSeg/common_gui/napari_image_view.py

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.

♻️ Duplicate comments (1)
package/PartSeg/_roi_mask/main_window.py (1)

473-477: ⚠️ Potential issue | 🟠 Major

Batch set_chosen() into one signal emission.

Because Line 545 wires check_change_signal to image_view.refresh_selected, each changed setChecked() here can still trigger a full refresh before the final explicit emit. On ROI loads with many selected components, that turns one sync into N+1 view refreshes.

💡 Proposed fix
     def set_chosen(self, chosen_components: Sequence[int]):
         chosen_components = set(chosen_components)
         for num, check in self.check_box.items():
-            check.setChecked(num in chosen_components)
+            check.blockSignals(True)
+            try:
+                check.setChecked(num in chosen_components)
+            finally:
+                check.blockSignals(False)
         self.check_change_signal.emit()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package/PartSeg/_roi_mask/main_window.py` around lines 473 - 477, Batch the
per-checkbox updates in set_chosen by temporarily blocking signals on each
QCheckBox while calling setChecked so intermediate stateChanged signals don't
trigger image_view.refresh_selected; for example, in set_chosen iterate
self.check_box.items(), call check.blockSignals(True) (or use
QSignalBlocker(check)), call check.setChecked(...), then unblock
(blockSignals(False)) and after the loop emit self.check_change_signal.emit()
once. Ensure you reference the set_chosen method and self.check_box and leave
the final explicit emit as the single notification.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@package/PartSeg/_roi_mask/main_window.py`:
- Around line 473-477: Batch the per-checkbox updates in set_chosen by
temporarily blocking signals on each QCheckBox while calling setChecked so
intermediate stateChanged signals don't trigger image_view.refresh_selected; for
example, in set_chosen iterate self.check_box.items(), call
check.blockSignals(True) (or use QSignalBlocker(check)), call
check.setChecked(...), then unblock (blockSignals(False)) and after the loop
emit self.check_change_signal.emit() once. Ensure you reference the set_chosen
method and self.check_box and leave the final explicit emit as the single
notification.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9b9be0c1-0617-4b8b-a660-58ac2fce6f04

📥 Commits

Reviewing files that changed from the base of the PR and between 6278963 and 6b3bc30.

📒 Files selected for processing (3)
  • package/PartSeg/_roi_mask/main_window.py
  • package/PartSeg/_roi_mask/stack_settings.py
  • package/PartSeg/common_backend/base_settings.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • package/PartSeg/common_backend/base_settings.py
  • package/PartSeg/_roi_mask/stack_settings.py

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.

🧹 Nitpick comments (2)
package/PartSeg/_roi_mask/stack_settings.py (2)

306-311: Same null check concern applies here.

The set_chosen() calls at lines 306 and 310 lack the null guard that post_roi_set() has. For consistency, consider adding a similar check.

🛡️ Proposed defensive guard
             self.roi = state2.roi_info
-            self.chosen_components_widget.set_chosen(state2.selected_components)
+            if self.chosen_components_widget is not None:
+                self.chosen_components_widget.set_chosen(state2.selected_components)
             self.components_parameters_dict = state2.roi_extraction_parameters
         else:
             self.roi = new_roi_info
-            self.chosen_components_widget.set_chosen(list_of_components)
+            if self.chosen_components_widget is not None:
+                self.chosen_components_widget.set_chosen(list_of_components)
             self.components_parameters_dict = segmentation_parameters
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package/PartSeg/_roi_mask/stack_settings.py` around lines 306 - 311, The
calls to chosen_components_widget.set_chosen(...) in the conditional branches
(using state2.selected_components and list_of_components) lack a null guard like
the one used in post_roi_set(); update both usages to first check that
chosen_components_widget is not None (or is truthy) before calling set_chosen so
you don't call a method on a missing widget—apply the same defensive pattern
used in post_roi_set() around the set_chosen invocations and leave the rest of
the assignments (self.components_parameters_dict, self.roi) unchanged.

174-181: Consider adding null checks for chosen_components_widget for consistency.

Unlike post_roi_set() which guards against chosen_components_widget being None (line 314), these call sites don't have a similar guard. If set_project_info is invoked before the widget is attached, post_roi_set() will return early but the subsequent set_chosen() call will raise AttributeError.

🛡️ Proposed defensive guard
             self.roi = state2.roi_info
-            self.chosen_components_widget.set_chosen(state2.selected_components)
-
+            if self.chosen_components_widget is not None:
+                self.chosen_components_widget.set_chosen(state2.selected_components)
             self.components_parameters_dict = state2.roi_extraction_parameters
         else:
             self.set_history(data.history)
             self.roi = data.roi_info
-            self.chosen_components_widget.set_chosen(data.selected_components)
-
+            if self.chosen_components_widget is not None:
+                self.chosen_components_widget.set_chosen(data.selected_components)
             self.components_parameters_dict = data.roi_extraction_parameters
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package/PartSeg/_roi_mask/stack_settings.py` around lines 174 - 181, The
calls to self.chosen_components_widget.set_chosen in set_project_info lack a
null check and can raise AttributeError if the widget isn't attached; update
set_project_info to guard before calling set_chosen (e.g., if
self.chosen_components_widget is not None) in both branches where
state2.selected_components and data.selected_components are used, mirroring the
defensive check in post_roi_set so that when chosen_components_widget is None
the method returns or skips set_chosen safely while still setting
history/roi/components_parameters_dict.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@package/PartSeg/_roi_mask/stack_settings.py`:
- Around line 306-311: The calls to chosen_components_widget.set_chosen(...) in
the conditional branches (using state2.selected_components and
list_of_components) lack a null guard like the one used in post_roi_set();
update both usages to first check that chosen_components_widget is not None (or
is truthy) before calling set_chosen so you don't call a method on a missing
widget—apply the same defensive pattern used in post_roi_set() around the
set_chosen invocations and leave the rest of the assignments
(self.components_parameters_dict, self.roi) unchanged.
- Around line 174-181: The calls to self.chosen_components_widget.set_chosen in
set_project_info lack a null check and can raise AttributeError if the widget
isn't attached; update set_project_info to guard before calling set_chosen
(e.g., if self.chosen_components_widget is not None) in both branches where
state2.selected_components and data.selected_components are used, mirroring the
defensive check in post_roi_set so that when chosen_components_widget is None
the method returns or skips set_chosen safely while still setting
history/roi/components_parameters_dict.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 74abb391-3a08-49c5-bd38-19fc3b3f4a6b

📥 Commits

Reviewing files that changed from the base of the PR and between 590b71e and aaf4ad1.

📒 Files selected for processing (1)
  • package/PartSeg/_roi_mask/stack_settings.py

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.

🧹 Nitpick comments (1)
package/PartSeg/_roi_mask/main_window.py (1)

464-481: Inconsistent signal blocking pattern in set_components.

Other methods (check_all, un_check_all, set_chosen) use the safer pattern that saves the previous blocking state and restores it in a finally block. This method should follow the same pattern for consistency and to ensure signals are restored if an exception occurs.

♻️ Proposed fix for consistent signal blocking
     def set_components(self, components_index, chosen_components: Union[Sequence[int], None] = None):
         if chosen_components is None:
             chosen_components = []
         chosen_components = set(chosen_components)
-        self.blockSignals(True)
-        self.remove_components()
-        for el in components_index:
-            check = ComponentCheckBox(el)
-            if el in chosen_components:
-                check.setChecked(True)
-            check.stateChanged.connect(self.check_change)
-            check.mouse_enter.connect(self.mouse_enter.emit)
-            check.mouse_leave.connect(self.mouse_leave.emit)
-            self.check_box[el] = check
-            self.check_layout.addWidget(check)
-        self.blockSignals(False)
+        prev = self.blockSignals(True)
+        try:
+            self.remove_components()
+            for el in components_index:
+                check = ComponentCheckBox(el)
+                if el in chosen_components:
+                    check.setChecked(True)
+                check.stateChanged.connect(self.check_change)
+                check.mouse_enter.connect(self.mouse_enter.emit)
+                check.mouse_leave.connect(self.mouse_leave.emit)
+                self.check_box[el] = check
+                self.check_layout.addWidget(check)
+        finally:
+            self.blockSignals(prev)
         self.update()
         self.check_change_signal.emit()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package/PartSeg/_roi_mask/main_window.py` around lines 464 - 481, The
set_components method currently calls self.blockSignals(True) and later
self.blockSignals(False) directly; change it to mirror the safer pattern used in
check_all/un_check_all/set_chosen by saving the previous blocking state (e.g.,
prev = self.blockSignals()) then calling self.blockSignals(True) before
remove_components and the loop, and ensuring you restore the original state
inside a finally block (self.blockSignals(prev)). Keep existing logic that
creates ComponentCheckBox, connects check.stateChanged to self.check_change and
mouse_enter/mouse_leave to self.mouse_enter.emit/self.mouse_leave.emit, and
still emit self.check_change_signal at the end.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@package/PartSeg/_roi_mask/main_window.py`:
- Around line 464-481: The set_components method currently calls
self.blockSignals(True) and later self.blockSignals(False) directly; change it
to mirror the safer pattern used in check_all/un_check_all/set_chosen by saving
the previous blocking state (e.g., prev = self.blockSignals()) then calling
self.blockSignals(True) before remove_components and the loop, and ensuring you
restore the original state inside a finally block (self.blockSignals(prev)).
Keep existing logic that creates ComponentCheckBox, connects check.stateChanged
to self.check_change and mouse_enter/mouse_leave to
self.mouse_enter.emit/self.mouse_leave.emit, and still emit
self.check_change_signal at the end.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5dc4138b-7b9e-4450-8090-a8647b42969b

📥 Commits

Reviewing files that changed from the base of the PR and between aaf4ad1 and f5ac01f.

📒 Files selected for processing (2)
  • package/PartSeg/_roi_mask/main_window.py
  • package/PartSeg/common_backend/base_settings.py

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.

🧹 Nitpick comments (1)
package/PartSeg/common_backend/base_settings.py (1)

515-538: Previous review concern addressed: post_roi_set() now runs on all ROI update paths.

By using self.roi = roi_info (Line 538) instead of direct _roi_info assignment, the property setter is invoked, ensuring post_roi_set() is called consistently.

Minor inefficiency: fit_to_image called twice.

At Line 533, roi_info = result.roi_info.fit_to_image(self.image) fits the ROI. Then at Line 538, the property setter calls fit_to_image again (Line 155). This is redundant if the operation is idempotent. Consider an internal setter that bypasses the re-fit, or verify that double-fitting is cheap.

♻️ Potential optimization
         try:
             roi_info = result.roi_info.fit_to_image(self.image)
         except ValueError as e:  # pragma: no cover
             raise ValueError(ROI_NOT_FIT) from e
         if result.points is not None:
             self.points = result.points
-        self.roi = roi_info
+        # Already fitted, assign directly and call hook
+        self._roi_info = roi_info
+        self._additional_layers = {}
+        self.post_roi_set()
+        self.roi_changed.emit(self._roi_info)

Alternatively, keep the current approach if fit_to_image is cheap/idempotent and code simplicity is preferred.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package/PartSeg/common_backend/base_settings.py` around lines 515 - 538, The
code currently fits the ROI twice: set_segmentation_result calls
result.roi_info.fit_to_image(self.image) then assigns self.roi which triggers
the roi property setter that calls fit_to_image again; fix by providing a setter
path that accepts an already-fit ROI and bypasses the second fit (e.g., add an
internal method or a parameter to the roi property setter such as
_set_roi_info_no_refit or roi = fitted_roi with a flag already_fitted=True),
then in set_segmentation_result assign the pre-fitted roi via that internal
setter (and keep existing public roi property behavior unchanged elsewhere).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@package/PartSeg/common_backend/base_settings.py`:
- Around line 515-538: The code currently fits the ROI twice:
set_segmentation_result calls result.roi_info.fit_to_image(self.image) then
assigns self.roi which triggers the roi property setter that calls fit_to_image
again; fix by providing a setter path that accepts an already-fit ROI and
bypasses the second fit (e.g., add an internal method or a parameter to the roi
property setter such as _set_roi_info_no_refit or roi = fitted_roi with a flag
already_fitted=True), then in set_segmentation_result assign the pre-fitted roi
via that internal setter (and keep existing public roi property behavior
unchanged elsewhere).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9de4b143-256c-4ed4-9d2b-73ec39a02773

📥 Commits

Reviewing files that changed from the base of the PR and between aaf4ad1 and abc1a21.

📒 Files selected for processing (2)
  • package/PartSeg/_roi_mask/main_window.py
  • package/PartSeg/common_backend/base_settings.py

@sonarqubecloud
Copy link

@Czaki Czaki merged commit f63f37b into develop Mar 24, 2026
54 of 56 checks passed
@Czaki Czaki deleted the scroll_area_select_components branch March 24, 2026 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip check PR title skip spellcheking PR title

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant