Skip to content

Fix/channel persistence on dataset switch#2

Open
hongquanli wants to merge 11 commits intomaragall:mainfrom
Cephla-Lab:fix/channel-persistence-on-dataset-switch
Open

Fix/channel persistence on dataset switch#2
hongquanli wants to merge 11 commits intomaragall:mainfrom
Cephla-Lab:fix/channel-persistence-on-dataset-switch

Conversation

@hongquanli
Copy link
Contributor

No description provided.

hongquanli and others added 11 commits January 6, 2026 12:18
When switching from an acquisition with multiple channels (e.g., 405, 488, 561)
to one with fewer channels (e.g., 405 only), the old channel controls would
persist in the viewer UI, showing phantom channels that don't exist in the
new dataset.

Changes:
- Add _data_structure_changed() to detect when channels, dims, or LUTs changed
- Update _maybe_refresh() to force full viewer rebuild when structure changes
- Update load_dataset() to reset state before loading new dataset

This ensures the NDV viewer is properly rebuilt when the data structure
changes, rather than just swapping data in-place which leaves stale UI state.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use xarray's .sizes.get() instead of .coords.get() for cleaner
  channel count comparison
- Add comprehensive test suite for _data_structure_changed() with
  23 test cases covering:
  - Dimension changes
  - Channel count changes
  - Channel name changes
  - LUT changes
  - Edge cases (None, empty attrs, no channel dim)
  - Real-world scenarios (3ch->1ch, live acquisition)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Address reviewer feedback: Extract _data_structure_changed from both
test classes into a shared module-level data_structure_changed() function
to eliminate code duplication and improve maintainability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Args section to _data_structure_changed() docstring
- Update comment to list all checks (dims, channels, channel names, LUTs)
- Add NOTE in test helper about duplication risk and sync requirement

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove try-except from test helper - test code should fail visibly
- Add test_luts_removed_returns_true for symmetry with luts_added test

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Prevents resource leaks when switching between datasets by calling
_close_open_handles() at the start of load_dataset().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move comparison logic to module-level function for single source of truth
- Add dtype change detection (uint8 vs uint16, float vs int)
- Update tests to import from ndviewer_light (removes code duplication)
- Add 4 new tests: dtype changes (3) and exception propagation (1)
- Class method now delegates to module function with exception wrapper

This addresses code review feedback:
- Eliminates duplicate logic between tests and implementation
- Adds missing dtype detection for contrast limit changes
- Adds exception path test coverage

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Public API method to navigate the viewer to a specific position
along any dimension (fov, time, z, channel, etc.).

- Encapsulates NDV's display_model.current_index API
- Includes fallback for older NDV versions (dims.current_step)
- Returns bool for success/failure
- Validates dimension exists before setting

This enables clean integration when used as a submodule,
without callers needing to know NDV internals.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The instance property assignment had no effect - only the type
property assignment works for MagicMock objects.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
maragall pushed a commit that referenced this pull request Jan 22, 2026
maragall pushed a commit that referenced this pull request Jan 22, 2026
* Display channel names instead of numeric indices

- Extract channel names from OME metadata or filenames
- Store channel names in xarray attrs for later use
- Add _update_channel_labels() method to update NDV's internal
  _lut_controllers with actual channel names
- Use QTimer delay to ensure NDV initialization completes before
  updating labels

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Address PR review comments for channel name display

- Keep xarray coords numeric instead of string channel names to avoid
  luts/coords type mismatch (addresses comments #2 and #3)
- Replace fixed 500ms QTimer delay with retry mechanism that polls for
  _lut_controllers readiness with bounded retries (addresses comment #4)
- Add documentation noting _lut_controllers is a private API that may
  change in future ndv versions (addresses comment #1)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Address PR review comments (round 2)

- Fix single-TIFF path to use numeric coords instead of string channel
  names, consistent with OME-TIFF path (comment #1)
- Add generation counter to cancel stale retry callbacks when viewer is
  replaced by a new dataset load (comment #2)
- Add hasattr check for synchronize() method with debug logging when
  missing (comment #3)
- Remove placeholder issue URL, keep explanation about private API
  (comment #4)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add comment explaining synchronize() purpose

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Address PR review comments (round 4)

- Fix misleading "all axes" comments to accurately describe which axes
  use numeric indices vs actual values (comments #3, #5)
- Preserve partial channel names from OME metadata instead of discarding
  all when count doesn't match n_c (comment #4)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Address PR review comments (round 5)

- Initialize _channel_label_generation in __init__ for clarity instead
  of relying on getattr defaults (comment #5)
- Add debug logging when channel label update succeeds (comment #6)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Address PR review comments (round 6)

- Rename 'channels' to 'channel_names' in single-TIFF path for
  consistency with OME-TIFF path (comment #1)
- Initialize _pending_channel_label_retries in __init__ alongside
  _channel_label_generation for consistency (comment #3)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add unit tests for channel name display feature

Test coverage includes:
- OME-TIFF channel name extraction and fallback logic
- Single-TIFF channel name extraction from filenames
- Retry mechanism with generation counter
- Channel label update on NDV controllers
- Integration tests for end-to-end behavior

25 tests, all passing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Address PR review comments (round 7)

- Clarify that numeric coordinates convention applies to both OME-TIFF
  and single-TIFF paths (comment #5)
- Document graceful failure mode if private _lut_controllers API changes
  (comment #9)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Address PR review comments (round 8)

- Change timeout log from debug to warning for user visibility (comment #2)
- Add pytest install note to tests/README.md (comment #3)
- Remove unused pytest import (comment #8)
- Remove unused ch2 variable (comment #6)
- Remove unnecessary pending_retries assignment (comment #7)
- Add explanatory comments to except clauses (comments #9, #10, #11)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Address PR review comments (round 9)

- Fix debug log to track actual updated names instead of slicing list
  (handles controller gaps correctly) (comment #15)
- Remove unnecessary getattr for _pending_channel_label_retries since
  it's now initialized in __init__ (comment #21)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix channel labels not updating during in-place refresh

During live acquisition, if _try_inplace_ndv_update succeeds, the code
returned early without scheduling channel label updates. This caused
channel labels to show numeric indices instead of names (e.g., "DAPI",
"GFP") after in-place data swaps.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix CI: black formatting and skip downsampling tests when unavailable

- Apply black formatting to ndviewer_light.py and test_channel_names.py
- Add pytest skipif marker to test_downsampling_wrapper.py to skip tests
  when Downsampling3DXarrayWrapper is not available (requires scipy)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add scipy to CI environment for 3D downsampling tests

scipy.ndimage.zoom is used by Downsampling3DXarrayWrapper for
resampling large 3D volumes to fit within OpenGL texture limits.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Remove accidentally committed IDE config files

- Remove .claude/agents/ directory (IDE-specific configuration)
- Remove .coverage file (test artifact)
- Add both to .gitignore to prevent future commits

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Replace line number references with method names in tests

Line numbers drift as code evolves, making references misleading.
Method names are more stable and searchable with IDE navigation.

Updated:
- test_channel_names.py: All inline comments now reference method names
- tests/README.md: Documentation uses method names instead of line numbers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix README: "three main areas" → "five main areas"

The test file has five test classes, not three.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Change synchronize() fallback log from debug to warning

If synchronize() method doesn't exist on LUT controller, channel labels
may not appear in the UI. A warning is more appropriate than debug level.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Use _ = for intentionally unused ET.SubElement return value

Makes explicit that the return value is intentionally unused.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Extract constants and helper for channel label update retry

- Add CHANNEL_LABEL_UPDATE_MAX_RETRIES (20) constant
- Add CHANNEL_LABEL_UPDATE_RETRY_DELAY_MS (100) constant
- Extract _initiate_channel_label_update() helper method to DRY up
  duplicated code between _store_data_from_loader and _set_ndv_data

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
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