Plugin refactor [6]: small UX improvements (dotsize, % labeled, folder name)#182
Conversation
Import and call align_old_new in the frame extraction flow to normalize df_prev and df_new before pd.concat, preventing misalignment issues when merging previous and newly extracted labels. Also update a comment in core.dataframes.keypoint_conflicts to document that pandas no longer supports groupby on axis=1 by level names and that the .T -> groupby -> .T workaround is used.
Add a small LayerStatusPanel UI and config sync for point size, plus supporting layer utilities and wiring in KeypointControls. - New src/napari_deeplabcut/ui/layer_stats.py: a QGroupBox panel showing folder name, labeling progress, and a point-size slider (emits change and commit signals). - New src/napari_deeplabcut/core/config_sync.py: lightweight helpers to resolve a project config.yaml from a layer, load/save a `dotsize` key, and coerce valid sizes; writes are debounced and logged on failure. - Updated src/napari_deeplabcut/core/layers.py: added utilities for uniform point size (get/set), inference helpers (frame/bodypart/individual counts), a LabelProgress dataclass, compute_label_progress, infer_folder_display_name, and find_relevant_image_layer to support the new panel and progress calculation. - Modified src/napari_deeplabcut/_widgets.py (KeypointControls): integrate LayerStatusPanel, connect signals, initialize layer point size from project config (conservative init), schedule debounced writes to config on size change, refresh the panel on relevant layer events, and connect lightweight layer event hooks to keep the panel live. Purpose: improve UX by surfacing folder/progress info and a point-size control that can persist to the project's config.yaml (debounced) while keeping initialization conservative to avoid overwriting user adjustments.
Replace local QPushButton variables with instance attributes (self.show_shortcuts_btn, self.tutorial_btn) in KeypointControls._form_help_buttons so other methods can access or modify these buttons (e.g., enable/disable, update labels, or change connections).
Introduce a layer-aware config resolution helper and strengthen config discovery logic; update widgets to use the new resolver. config_sync.resolve_config_path_from_layer was rewritten to use points metadata, image-layer inference, generic path hints, and a bounded upward search (using infer_* helpers and find_nearest_config), with added logging and safety checks. Add a small _layer_source_path helper and improve save_point_size_to_config logging/early-exit when dotsize is unchanged. In KeypointControls, replace direct resolve calls with _resolve_config_path_for_layer (which passes image layer and fallbacks). Simplify the layer stats label and move detailed counts into a tooltip for a cleaner UI.
Add a new test module covering resolve_config_path_from_layer. Tests verify that points-metadata inference is preferred, that image-layer inference is used when points meta contains no config, and that a generic fallback project hint is respected. Uses a DummyLayer and monkeypatching to stub read_points_meta and various infer_* helpers.
Change tutorial count text from "Tip X of Y" to "Tip X|Y" in the Tutorial dialog, and update unit tests accordingly. Tests were relaxed/adjusted by removing strict assertions about the dialog window title and specific emoji/prefixes in tip messages, and by expecting the new pipe-separated count string.
Add and improve tooltips in LayerStatusPanel to give clearer user feedback. A tooltip was added to the size label to explain it controls the global point size and that the value is saved to config.yaml as 'dotsize'. The progress tooltip wording was clarified to indicate the count is out of all possible points ("of all possible points labeled") to reduce ambiguity.
Initialize and refine UI behavior for point-size controls and progress text/tooltips. Set default disabled appearance on construction, expand set_point_size_enabled to accept a reason and update enabled/disabled tooltips and value styling, and propagate the reason when disabling. Simplify progress label text and move the detailed breakdown into a tooltip; clear tooltips when no/invalid data. Add set_invalid_points_layer to handle non-DLC keypoints layers and update callers to pass contextual disable reasons to improve UX and clarity.
Introduce _current_dlc_points_layer that verifies a selected Points layer contains valid DLC metadata (uses read_points_meta, handles exceptions/ValidationError and missing header). Update layer-status logic to: use the raw active layer for folder inference, distinguish between no active layer, non-Points layers, and invalid DLC Points layers (set_no_active_points_layer / set_invalid_points_layer), and only enable point-size/progress when the layer is a validated DLC Points layer. Also update size-change and config-commit handlers to use the new validated accessor.
Add tests for LayerStatusPanel to validate UI state updates: verify set_invalid_points_layer disables the size slider/value and updates the progress text, and verify set_no_active_points_layer disables the controls and shows the "No active keypoints layer" message. Tests use qtbot to instantiate the panel and exercise set_point_size_enabled to confirm enabling/disabling behavior.
Rename label to "Points size" and simplify enabling logic so the entire size control container is enabled/disabled (ensuring native disabled styling for both slider and label). Consolidate tooltip creation and apply it to the container, slider, and value, removing manual stylesheet tweaks. Update the disabled hint used when no active layer to reference the global points size.
Wrap the point-size slider and value in a dedicated QWidget so the whole control can have its own layout and tooltip; remove redundant docstring note. Unify and clarify the point-size tooltip, apply it to slider/label/container, and change the form label to "Point size". Initialize the disabled state with a user-facing reason. Improve the progress tooltip to include labeled/remaining percentages and a clearer breakdown. Misc wording tweaks for disabled/reason messages.
There was a problem hiding this comment.
Pull request overview
This PR adds a new “Layer status” UX panel to the napari-deeplabcut widget, providing folder context, labeling progress, and a point-size slider that syncs dotsize with the project’s config.yaml. It also includes supporting backend utilities (progress computation, folder-name inference, config resolution/sync) and updates/extends tests accordingly.
Changes:
- Added a
LayerStatusPanelUI component and wired it into the main widget to show folder name, progress, and control point size. - Implemented config resolution +
dotsizeload/save helpers to keep point size synchronized withconfig.yaml(debounced writes). - Improved extracted-label merging robustness via dataframe alignment; updated/added tests for new behavior and UI robustness.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/napari_deeplabcut/ui/layer_stats.py |
New status panel widget (folder/progress/point-size slider). |
src/napari_deeplabcut/_widgets.py |
Integrates the panel; hooks layer events; debounced dotsize sync to config. |
src/napari_deeplabcut/core/config_sync.py |
New helpers for resolving config path and loading/saving dotsize. |
src/napari_deeplabcut/core/layers.py |
Adds progress computation, folder-name inference, and point-size helpers. |
src/napari_deeplabcut/ui/cropping.py |
Aligns old/new label dataframes before merging after frame extraction. |
src/napari_deeplabcut/core/dataframes.py |
Clarifies pandas grouping approach (and relies on .T.groupby(...).T). |
src/napari_deeplabcut/ui/dialogs.py |
Tutorial counter text formatting changed. |
src/napari_deeplabcut/_tests/ui/test_layer_stats.py |
New unit tests for status panel enable/disable states. |
src/napari_deeplabcut/_tests/ui/test_dialogs.py |
Tutorial tests made less brittle to text/emoji changes. |
src/napari_deeplabcut/_tests/core/test_config_sync.py |
New tests covering config-path resolution logic. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Add a QGraphicsOpacityEffect to the point size slider and toggle its opacity (1.0 when enabled, 0.35 when disabled) to visually indicate the control's disabled state. Also import QGraphicsOpacityEffect and apply a small tooltip formatting cleanup for the progress label.
Add a comprehensive suite of unit tests for napari_deeplabcut.core.config_sync: small helper functions and higher-level behaviors. Tests cover _coerce_point_size (rounding/clamping/coercion), _layer_source_path (normal, missing, and failing path access), many resolve_config_path_from_layer scenarios (points meta inference, generic fallback hints, nearest-config fallback, error handling when reading points meta or find_nearest_config fails, passing dataset/anchor candidates to inference, and image-layer inference), and load_point_size_from_config / save_point_size_to_config behaviors (missing path, load/write failures, missing keys, coercion/clamping, unchanged values, and write error handling). Also add a pathlib.Path import used in tests.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…/napari-deeplabcut into cy/refactor-ux-tweaks
Remove debounced/pending write machinery and write point-size to config immediately when committing. Deleted the QTimer, _pending_config_point_size_write attribute, and _flush_pending_point_size_config_write method. _commit_active_points_size_to_config now calls save_point_size_to_config directly, reports success via viewer.status, and logs failures; related scheduling code in the size setter was removed. This simplifies point-size persistence and improves error handling.
Change slider behavior so moves provide a live visual preview while commits occur only on release. setTracking(False) is set on the size slider; sliderMoved is connected to _on_slider_moved_preview to update the label and emit point_size_changed for visual-only updates, while valueChanged is connected to _on_value_changed_commit to update the label and emit point_size_commit_requested for persisting the change. This improves UX by avoiding premature commits during dragging and clarifies signal semantics.
Drop strict comparisons of dlg.count.text() in two tutorial dialog tests. The assertions matching exact "Tip X|{len(dlg._tips)}" formatting were removed from test_tutorial_next_advances_to_first_tip_and_updates_position and test_tutorial_navigation_enables_and_disables_buttons to avoid brittle failures while preserving the button enable/disable and index behavior checks.
Centralize and harden config.yaml discovery and scorer resolution across the plugin. Key changes: - Prefer find_nearest_config for automatic config lookup and switch core/io to use it when loading colormap for HDF files. - Remove legacy misc.find_project_config_path and provenance.find_config_scorer_nearby; provenance now imports find_nearest_config. - Overhauled the save flow in KeypointControls: auto-load scorer from an auto-discovered config, present a project-config chooser when no config is found, validate unreadable/missing scorers, fall back to sidecar or manual prompt only when appropriate, and persist sidecar defaults when possible. - Add UI dialog helpers in ui/dialogs.py: load_scorer_from_config, warn_invalid_config_for_scorer, and extend prompt_for_project_config_for_save to optionally resolve and return a scorer. - Add module-level note in _widgets.py recommending future refactors to keep the file small. These changes make project association and scorer selection more robust and user-friendly, add clearer warnings for invalid configs, and consolidate config lookup logic.
Introduce skip_project_config_dialog fixture and expand e2e tests to cover the new project-config save flow (auto-discovery, user-specified external config, sidecar precedence, and skip behavior). Update several test names and expectations to assert config-selection dialog behavior and scorer resolution, and add new tests for config-winning logic. Also tidy _widgets.py imports by importing find_nearest_config from project_paths (removing the duplicate import from provenance).
Extend dialog tests to cover project config and scorer resolution flows. Adds pytest import, new fake Qt helpers (_FakeMessageBox, _FakeFileDialog, _FakeButton) and a fake_config_prompt_qt fixture that monkeypatches ui_dialogs.QMessageBox and QFileDialog. Introduces tests for load_scorer_from_config (trim, missing, blank), prompt_for_project_config_for_save (skip, cancel, associate valid config, handle missing scorer, unreadable config, custom UI text) and warn_invalid_config_for_scorer. These tests exercise scorer resolution logic and error handling using tmp_path and monkeypatching.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Guard against non-dict parsed configs when loading/saving the point-size key. load_point_size_from_config now logs and returns None if the config is not a mapping; save_point_size_to_config logs and replaces a non-mapping config with an empty dict before updating the point-size value. This prevents type errors when the config file's top-level YAML value isn't a mapping.
Ensure non-mouse or programmatic size changes also update the visual layer. Previously only point_size_commit_requested was emitted (for saving/persistence), which could leave visuals out-of-sync. This adds a call to point_size_changed.emit(int(value)) in _on_value_changed_commit to trigger an immediate visual update when the value is committed.
Scope & goals
Since they are small and easy-to-implement changes, this PR closes #7 and closes #33 by adding an extra widget that displays current folder name, and shows information about the fraction of points labeled out of the theoretical maximum (based on the number of bodyparts and individuals in config + the number of frames in folder).
Additionally, this implements an improvement requested in #147, in the form of a point size slider which controls the dot size for all points in the selected layer.
This was previously implemented by replacing the napari slider; the new version offers a safer alternative that does not depend on napari internals, and saves dotsize to config when the slider is updated for convenience.
Automated summary
This pull request introduces a new user experience (UX) panel for displaying and managing the status of DeepLabCut (DLC) keypoints layers in the plugin.
The main focus is on providing real-time feedback and controls related to the active points layer, including folder context, annotation progress, and point size, as well as ensuring that changes to point size are synchronized with the project's configuration file.
The implementation includes a new
LayerStatusPanelwidget, integration with the main widget, and comprehensive tests. Additionally, some UI tutorial tests were updated for robustness.Key changes:
1. New Layer Status Panel and Integration
LayerStatusPanelwidget to display folder name, annotation progress, and allow users to view and adjust point size for the active DLC keypoints layer. The panel is integrated into the main widget layout, and its state is kept up-to-date with layer selection and changes.2. Point Size Synchronization with Config
config.yaml(if available), and to save any user changes to point size back to the config file, using a debounced update strategy. This ensures consistency between the UI and project settings.3. Enhanced Layer Event Handling
4. New and Updated Tests
LayerStatusPanelto verify correct enabling/disabling of controls and text updates for invalid or missing layers. Also, added tests for configuration resolution logic to ensure correct config file inference in different scenarios.5. UI Tutorial Test Adjustments