Add Open Resolution Level command and Pyramidal dataset lifecycle management#90
Merged
Conversation
Introduces the method signature for opening a specific pyramid level as an ImageJ dataset. Index 0 is the highest resolution; each increment is the next coarser level. Per the intended design, all datasets opened from the same pyramid (across IJ and BDV) will share the same Pyramidal5DImageData instance, so cachedCellImgs / volatileImgs are never loaded more than once. The body currently throws UnsupportedOperationException; it will be filled in as part of the resolution-level dataset lifecycle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Exposes the backing Pyramidal5DImageData reference held by a PyramidalDataset. This accessor is needed both for the upcoming "open specific resolution in IJ" command and for verifying in tests that multiple open datasets share the same underlying pyramid instance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two tests documenting the expected dataset-lifecycle behaviour
when opening a multi-resolution OME-Zarr in ImageJ and BigDataViewer:
Scenario – open each resolution level in IJ, then BDV twice:
- 2 openIJWithImage(level) calls → 2 datasets in DatasetService
- each openBDVWithImage() call → one additional dataset
- all datasets must be backed by the exact same Pyramidal5DImageData
instance (verified via assertSame on getPyramidData())
Scenario – open in BDV first, then a specific level in IJ:
- openBDVWithImage() → 1 dataset
- openIJWithImage(1) → 2nd dataset, sharing the same
Pyramidal5DImageData as the BDV dataset
Both tests currently error with UnsupportedOperationException because
openIJWithImage(int) is not yet implemented.
Test dataset: 5d_dataset_v4.ome.zarr (2 levels:
level 0 = [x=64,y=64,z=16,c=3,t=4],
level 1 = [x=32,y=32,z=8,c=3,t=4])
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously PyramidContents.axes held calibration only for the resolution level 0, so opening any other level would apply the wrong physical scales. Replace the single AxisCalibration[] with AxisCalibration[][] (one entry per level) in PyramidContents and update both backends to build the full per-level array during load. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add NonExistingResolutionLevelException (RuntimeException) for when a requested resolution level index is out of range. Add a bounds check in Pyramidal5DImageDataImpl.asPyramidalDataset(int) and asDataset(int) that throws this exception, and expose asPyramidalDataset(int) on the Pyramidal5DImageData interface. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- All three public openers (openIJWithImage, openIJWithImage(int), openBDVWithImage) and the test-facing openImage now delegate to a shared openPyramidImage that handles the three common exceptions (MultiImageDatasetException, NotAMultiscaleImageException, IllegalArgumentException/JsonSyntaxException). - Each caller adds only its own specific catch on top: NoMatchingResolutionException for the no-arg IJ and BDV openers, NonExistingResolutionLevelException for openIJWithImage(int). - getPyramid() now folds preferredWidth from settings so the cached pyramid is built with the correct initial resolution level for all callers; openMultiScaleImage() is removed. - Remove the unused message parameter from the test-facing openImage(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Confirm that all datasets backed by the same resolution level of the same pyramid wrap the identical CachedCellImg object, so that chunks loaded for one view are served from cache to any other view at the same level — not loaded twice. Also asserts that different resolution levels use distinct CachedCellImg instances, as expected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ialog Implements a DynamicCommand that populates a dropdown with one entry per resolution level of the active PyramidalDataset, then opens the chosen level as a new dataset backed by the same pyramid data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers: cancel on null dataset, cancel on non-pyramidal dataset, choice list population, opening the correct resolution level, and shared pyramid data identity across levels. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Provides a minimal 16x16 v0.5 dataset with exactly one resolution level, used to verify title-suffix behaviour that differs from multi-resolution pyramids. Includes creation script, conda.yml, and README. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Multi-resolution images get an (R) suffix in the dataset name so the title in ImageJ and BDV windows signals that other resolution levels are available. Single-resolution images are left unchanged. A null or empty base name produces just (R) rather than (null) (R). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…acking BdvHandleService had a mix of concerns: it managed BdvStackSource handles (openNewBdv, addToLastOrInNewBdv) and was later intended to track BDV window focus for command resolution. The stack-source API was not yet wired up in production and is currently not needed. The new BdvFocusService tracks which PyramidalDataset is in the currently focused BDV window via a WindowFocusListener registered in BdvUtils. This lets commands resolve the correct dataset when the @parameter injection via DisplayService does not find an active ImageDisplay (which is the case for BDV windows, which are plain Swing frames rather than SciJava displays). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the command is invoked from a BDV context, @parameter Dataset injection returns null because BDV windows are not SciJava ImageDisplays. Injecting BdvFocusService and calling resolveDataset() in initialize() covers this case by falling back to the most recently focused BDV window. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two new tests verify that opening a resolution level via the command shares the same Pyramidal5DImageData as the source dataset, both for the BDV (simulated via BdvFocusService#notifyWindowFocused) and IJ2 code paths. BdvHandleService reflects the previously existing class 1:1 and was now moved to the test resources for further reference. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two tests capture the expected behaviour that invoking the command always produces a second, distinct PyramidalDataset sharing the same Pyramidal5DImageData as the source: - runAfterIj2OpenCreatesSecondBdvDataset: source was opened in IJ2, dataset is set directly on the command - runAfterBdvOpenCreatesSecondBdvDataset: source was opened in BDV (simulated via BdvFocusService), dataset is null and must be resolved via the service Both tests currently fail: the first because the command opens the same dataset object in BDV rather than creating a new wrapper, the second because the command does not fall back to BdvFocusService when dataset is null. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the command called showBdvAndRegisterDataset with the same PyramidalDataset object that was already registered (from an earlier IJ2 or BDV open). Because incrementReferences() only fires DataCreatedEvent on the 0→1 transition, the second open never appeared as a separate entry in DatasetService. The fix creates a new PyramidalDataset wrapper via getPyramidal5DImageData().asPyramidalDataset() before passing it to BDV, so each "Open in BDV" produces a fresh registration with a refcount of its own. Also adds BdvFocusService fallback in run() so the command can resolve the active dataset when invoked from a BDV context (where @parameter Dataset injection returns null). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract registerDatasetLifecycle and registerFocusTracking as private helpers to clarify the two concerns in showBdvAndRegisterDataset. Also renames the bdvFocusService parameter (was bdvHandleService) and updates the stale javadoc that still referenced the old name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes Dataset as a @parameter so SciJava's input harvester never sees it and therefore never shows a chooser dialog when no ImageJ display is active. The active dataset is now resolved explicitly in run(): first via ImageDisplayService (FIJI case), then via BdvFocusService (BDV-only case). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the single logService.error call with two distinct IJ.error dialogs: one when no image is open at all, and one when the active image is not an OME-Zarr dataset. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Streamline reference counting and window-close event notification in `registerDatasetLifecycle`. - Move focus notifications to lifecycle registration and consolidate `WindowFocusListener` in `registerFocusTracking`
14 tasks
Adjust imports and types throughout the test suite: `PyramidalDataset<?>` casts become `PyramidalDataset`, `Function<PyramidalDataset<?>,…>` becomes `Function<PyramidalDataset,…>`, and command tests construct and inject `Pyramidal` objects directly instead of routing through dataset resolution helpers. Co-authored-by: tpietzsch <tobias.pietzsch@gmail.com>
Co-authored-by: tpietzsch <tobias.pietzsch@gmail.com>
Co-authored-by: tpietzsch <tobias.pietzsch@gmail.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…and also update javadocs of the new `PyramidalService`
…al5DImageDataImpl` to use `AbstractContextual`.
…lutionLevelCommand`
…te in `PyramidalService`.
edc1922 to
b76fd9b
Compare
b76fd9b to
9d88ea1
Compare
|
This was referenced Jun 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Replaces PR #80
Resolves Issue #41
Resolves Issue #79
Resolves Issue #14
Summary
OpenResolutionLevelCommand(menu: Plugins > OME-Zarr > Open Resolution Level…) to open a specific resolution level of an OME-Zarr multi-resolution dataset as a new ImageJ datasetasPyramidalDataset(int)) andNonExistingResolutionLevelExceptionin the pyramid APIPyramidalinterface (implemented byPyramidalDatasetandPyramidalBdvDataset)BdvHandleServicewithPyramidalService, which tracks the most recently focusedPyramidalwindow (BDV or IJ)PyramidalPreprocessorauto-resolves@Parameter Pyramidalcommand inputs from the active windowOpenInBDVCommandcreates a distinctPyramidalBdvDatasetfor each BDV opened, sharing the same underlyingPyramidal5DImageData(pixel data loaded only once)PyramidContents/AxisCalibration; physical extent (pixels × scale) is verified to be identical across all resolution levelsTest plan
mvn test— all existing and new tests passOpen in IJ → Open Resolution Level
Open in BDV → Open Resolution Level
Open in IJ → Open in BDV
PyramidalBdvsharing the same underlying pyramid data as the IJ2 datasetOpen in BDV → Open in BDV again
Open dataset A in IJ → Open dataset B in BDV → Open resolution level
Error cases
Jython Macro for testing