diff --git a/CLAUDE.md b/CLAUDE.md index aaf1bb47c..223c464bf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -185,10 +185,11 @@ group first, then the array's own `dimension_names`) – and leaves three `prote reader-specific steps: `readMultiscale`, `tryReadLevelFromParent` and `tryReadArrayNodeOnly` (the two `try*` hooks return `null` for "not applicable, try the next"; the last one throws `NotAMultiscaleImageException` when there is no array at all, so a missing location is never reported as unknown axes). `read` and `readSingleArray` are `final`, so -the order is fixed for every backend. Both `PyramidContents` and the per-level `CachedCellImg`s, transforms, calibration -and optional OMERO metadata it holds are immutable. Each backend also exposes a static `readPyramid(URI)` convenience -entry point for outside API users — not named `read`, because Java forbids a static method hiding an inherited instance -method. +the order is fixed for every backend. `PyramidContents` is immutable except for pixel data (issue #114): accessors hand +out copies or unmodifiable views, but the lazily loaded `CachedCellImg`s are shared, since copying one would read the +whole dataset — this matters because `OmeZarr` caches one instance for every opener, third-party ones included. Each +backend also exposes a static `readPyramid(URI)` convenience entry point for outside API users — not named `read`, +because Java forbids a static method hiding an inherited instance method. **A too-old reader library is reported, not thrown at the console.** A Fiji-Stable installation whose N5 stack predates this plugin fails inside the backend with a `NoClassDefFoundError` (e.g. on `OmeNgffMetadataParser`), which is useless diff --git a/doc/DEVELOPERS.md b/doc/DEVELOPERS.md index 9c2943e2f..8feafd222 100644 --- a/doc/DEVELOPERS.md +++ b/doc/DEVELOPERS.md @@ -95,12 +95,14 @@ public interface PyramidBackend } ``` -`PyramidContents` is what you get back, and it is immutable — public final fields, no setters: +`PyramidContents` is what you get back, and it is immutable — accessors only, no setters. The one exception is +pixel data: every `asImg(…)` call returns the same lazily loaded image, so pixel writes show up for every user and +live in memory only — they may be lost when the cache frees memory. | | | |------------------------|----------------------------------------------------------------------------------------------------------------------------------------| -| **Pixel data** | `cachedCellImgs[]` (one per resolution level), `type`, `name` | -| **Visualisation data** | `transforms[]`, `axesPerLevel[][]`, `omero`, `hasPlaceholderCalibration` | +| **Pixel data** | `asImg(int)` (one per resolution level) · `type()` · `name()` | +| **Visualisation data** | `transform(int)` · `axes(int)` · `omero()` · `hasPlaceholderCalibration()` | | **Levels** | `asImg()` (= level 0) · `asLargestImg()` · `asSmallestImg()` · `asImg(int)` · `numResolutionLevels()` · `smallestResolutionLevel()` | | **Level selection** | `suggestResolutionLevel(Integer preferredMaxWidth)` | | **Shape by axis name** | `hasAxis("z")` · `axisIndex("c")` · `sizeAlongAxis("t")` · `numDimensions()` · `numChannels()` · `numTimepoints()` · `channelLabels()` | diff --git a/ome-zarr-fiji-ui/src/main/java/ome/zarr/fijiui/plugin/command/tools/OpenResolutionLevelCommand.java b/ome-zarr-fiji-ui/src/main/java/ome/zarr/fijiui/plugin/command/tools/OpenResolutionLevelCommand.java index ce5c137e5..aea492522 100644 --- a/ome-zarr-fiji-ui/src/main/java/ome/zarr/fijiui/plugin/command/tools/OpenResolutionLevelCommand.java +++ b/ome-zarr-fiji-ui/src/main/java/ome/zarr/fijiui/plugin/command/tools/OpenResolutionLevelCommand.java @@ -158,7 +158,7 @@ static < T extends NativeType< T > & RealType< T > > String describeLevel( final sb.append( ", c=" ).append( contents.sizeAlongAxis( AxisCalibration.C, level ) ); if ( contents.hasAxis( AxisCalibration.T ) ) sb.append( ", t=" ).append( contents.sizeAlongAxis( AxisCalibration.T, level ) ); - return sb.append( ", " ).append( dataTypeName( contents.type ) ) + return sb.append( ", " ).append( dataTypeName( contents.type() ) ) .append( ", slice " ).append( ImageSizes.formatPixels( contents.numXYSlicePixels( level ) ) ) .append( " ~ " ).append( ImageSizes.formatBytes( contents.uncompressedXYSliceBytes( level ) ) ) .append( ", total " ).append( ImageSizes.formatBytes( contents.uncompressedBytes( level ) ) ) diff --git a/ome-zarr-fiji-ui/src/test/java/ome/zarr/examples/demo/OmeroMetadataDemo.java b/ome-zarr-fiji-ui/src/test/java/ome/zarr/examples/demo/OmeroMetadataDemo.java index 0d243dd1f..454264d7d 100644 --- a/ome-zarr-fiji-ui/src/test/java/ome/zarr/examples/demo/OmeroMetadataDemo.java +++ b/ome-zarr-fiji-ui/src/test/java/ome/zarr/examples/demo/OmeroMetadataDemo.java @@ -64,7 +64,7 @@ public static void main( final String[] args ) throws Exception private static void printInfo( final PyramidContents< ? > contents ) { - final Omero omero = contents.omero; + final Omero omero = contents.omero(); if ( omero == null ) { System.out.println( "OMERO metadata : not available" ); @@ -72,27 +72,27 @@ private static void printInfo( final PyramidContents< ? > contents ) else { System.out.println( "--- OMERO metadata ---" ); - System.out.println( " id : " + omero.id ); - System.out.println( " name : " + omero.name ); + System.out.println( " id : " + omero.id() ); + System.out.println( " name : " + omero.name() ); - if ( omero.rdefs != null ) + if ( omero.rdefs() != null ) { - System.out.println( " rdefs.defaultT : " + omero.rdefs.defaultT ); - System.out.println( " rdefs.defaultZ : " + omero.rdefs.defaultZ ); - System.out.println( " rdefs.model : " + omero.rdefs.model ); + System.out.println( " rdefs.defaultT : " + omero.rdefs().defaultT() ); + System.out.println( " rdefs.defaultZ : " + omero.rdefs().defaultZ() ); + System.out.println( " rdefs.model : " + omero.rdefs().model() ); } - if ( omero.channels != null ) + if ( omero.channels() != null ) { - System.out.println( " channels (" + omero.channels.size() + "):" ); - for ( int i = 0; i < omero.channels.size(); i++ ) + System.out.println( " channels (" + omero.channels().size() + "):" ); + for ( int i = 0; i < omero.channels().size(); i++ ) { - final Omero.Channel ch = omero.channels.get( i ); + final Omero.Channel ch = omero.channels().get( i ); System.out.printf( " [%d] label=%-20s active=%-5b color=%s%n", - i, ch.label, ch.active, ch.color ); - if ( ch.window != null ) + i, ch.label(), ch.active(), ch.color() ); + if ( ch.window() != null ) System.out.printf( " window: start=%.1f end=%.1f min=%.1f max=%.1f%n", - ch.window.start, ch.window.end, ch.window.min, ch.window.max ); + ch.window().start(), ch.window().end(), ch.window().min(), ch.window().max() ); } } } diff --git a/ome-zarr-fiji/src/main/java/ome/zarr/fiji/PyramidalBdv.java b/ome-zarr-fiji/src/main/java/ome/zarr/fiji/PyramidalBdv.java index 524728744..643f0d3e6 100644 --- a/ome-zarr-fiji/src/main/java/ome/zarr/fiji/PyramidalBdv.java +++ b/ome-zarr-fiji/src/main/java/ome/zarr/fiji/PyramidalBdv.java @@ -94,7 +94,7 @@ public List< SourceAndConverter< T > > asSources() public String getName() { - return contents.name; + return contents.name(); } @SuppressWarnings( "unchecked" ) @@ -103,15 +103,16 @@ List< SourceAndConverter< T > > initSourceAndConverters( final PyramidContents< { final int nLevels = contents.numResolutionLevels(); final int numChannels = contents.numChannels(); - final T type = contents.type; - final AffineTransform3D[] mipmapTransforms = contents.transforms; + final T type = contents.type(); + final AffineTransform3D[] mipmapTransforms = new AffineTransform3D[ nLevels ]; + Arrays.setAll( mipmapTransforms, contents::transform ); final VoxelDimensions voxelDimensions = voxelDimensions( contents ); final RandomAccessibleInterval< V >[] volatileImgs = createVolatileImgs( contents ); final V volatileType = volatileImgs[ 0 ].getType(); final RandomAccessibleInterval< T >[][] levelToChannels = new RandomAccessibleInterval[ nLevels ][]; - Arrays.setAll( levelToChannels, level -> splitInputStackIntoSourceStacks( contents, contents.cachedCellImgs[ level ] ) ); + Arrays.setAll( levelToChannels, level -> splitInputStackIntoSourceStacks( contents, contents.asImg( level ) ) ); final RandomAccessibleInterval< V >[][] levelToVolatileChannels = new RandomAccessibleInterval[ nLevels ][]; Arrays.setAll( levelToVolatileChannels, level -> splitInputStackIntoSourceStacks( contents, volatileImgs[ level ] ) ); @@ -149,7 +150,7 @@ private static VoxelDimensions voxelDimensions( final PyramidContents< ? > conte double yScale = 1.0; double zScale = 1.0; String unit = ""; - for ( final AxisCalibration axis : contents.axesPerLevel[ 0 ] ) + for ( final AxisCalibration axis : contents.axes( 0 ) ) { switch ( axis.name ) { @@ -180,7 +181,7 @@ RandomAccessibleInterval< V >[] createVolatileImgs( final PyramidContents< T > c final SharedQueue sharedQueue = new SharedQueue( Math.max( 1, Runtime.getRuntime().availableProcessors() / 2 ) ); final RandomAccessibleInterval< V >[] volatileImgs = new RandomAccessibleInterval[ contents.numResolutionLevels() ]; for ( int level = 0; level < contents.numResolutionLevels(); level++ ) - volatileImgs[ level ] = VolatileViews.wrapAsVolatile( contents.cachedCellImgs[ level ], sharedQueue ); + volatileImgs[ level ] = VolatileViews.wrapAsVolatile( contents.asImg( level ), sharedQueue ); return volatileImgs; } diff --git a/ome-zarr-fiji/src/main/java/ome/zarr/fiji/PyramidalDataset.java b/ome-zarr-fiji/src/main/java/ome/zarr/fiji/PyramidalDataset.java index 9c4409f43..fdd0b04b3 100644 --- a/ome-zarr-fiji/src/main/java/ome/zarr/fiji/PyramidalDataset.java +++ b/ome-zarr-fiji/src/main/java/ome/zarr/fiji/PyramidalDataset.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.scijava.Context; @@ -79,7 +80,7 @@ public < T extends NativeType< T > & RealType< T > > PyramidalDataset( final Con this.resolutionLevel = resolutionLevel; setRGBMerged( false ); if ( contents.numResolutionLevels() > 1 ) - setName( multiResolutionName( contents.name, resolutionLevel, contents.numResolutionLevels() ) ); + setName( multiResolutionName( contents.name(), resolutionLevel, contents.numResolutionLevels() ) ); } public int resolutionLevel() @@ -136,12 +137,12 @@ private static < T extends NativeType< T > & RealType< T > > ImgPlus< T > create " (numResolutionLevels = " + contents.numResolutionLevels() + ")" ); } - final AxisCalibration[] selectedAxes = contents.axesPerLevel[ resolutionLevel ]; - final ImgPlus< T > imgPlus = new ImgPlus<>( contents.cachedCellImgs[ resolutionLevel ], contents.name ); - for ( int i = 0; i < selectedAxes.length; i++ ) + final List< AxisCalibration > selectedAxes = contents.axes( resolutionLevel ); + final ImgPlus< T > imgPlus = new ImgPlus<>( contents.asImg( resolutionLevel ), contents.name() ); + for ( int i = 0; i < selectedAxes.size(); i++ ) { - final AxisType axisType = AXIS_TYPE_MAP.getOrDefault( selectedAxes[ i ].name, Axes.unknown() ); - imgPlus.setAxis( new DefaultLinearAxis( axisType, selectedAxes[ i ].unit, selectedAxes[ i ].scale ), i ); + final AxisType axisType = AXIS_TYPE_MAP.getOrDefault( selectedAxes.get( i ).name, Axes.unknown() ); + imgPlus.setAxis( new DefaultLinearAxis( axisType, selectedAxes.get( i ).unit, selectedAxes.get( i ).scale ), i ); } return imgPlus; } diff --git a/ome-zarr-fiji/src/main/java/ome/zarr/fiji/read/OmeZarr.java b/ome-zarr-fiji/src/main/java/ome/zarr/fiji/read/OmeZarr.java index c850281ce..dbc0aacb3 100644 --- a/ome-zarr-fiji/src/main/java/ome/zarr/fiji/read/OmeZarr.java +++ b/ome-zarr-fiji/src/main/java/ome/zarr/fiji/read/OmeZarr.java @@ -76,7 +76,7 @@ * BigDataViewer displays all levels and streams them lazily. *

* Independently of the width, every display path refuses to show an image whose - * calibration is a placeholder ({@link PyramidContents#hasPlaceholderCalibration}) + * calibration is a placeholder ({@link PyramidContents#hasPlaceholderCalibration()}) * unless the user confirms. */ public class OmeZarr @@ -267,7 +267,7 @@ public PyramidalDataset showInImageJ() /** * Whether the image must not be shown: {@code true} only when its calibration is - * a placeholder (see {@link PyramidContents#hasPlaceholderCalibration}) and the + * a placeholder (see {@link PyramidContents#hasPlaceholderCalibration()}) and the * user declined to open it anyway. *

* Every display path asks this, because the invented scale and unit belong to @@ -276,7 +276,7 @@ public PyramidalDataset showInImageJ() */ private boolean uncalibratedAndDeclined( final PyramidContents< ? > contents ) { - if ( !contents.hasPlaceholderCalibration ) + if ( !contents.hasPlaceholderCalibration() ) return false; final boolean openAnyway = openAnywayConfirmation.test( uncalibratedMessage() ); logger.info( "{} has no scale or unit of its own. Opening it anyway: {}.", inputUri, openAnyway ); diff --git a/ome-zarr-fiji/src/main/java/ome/zarr/fiji/util/BdvUtils.java b/ome-zarr-fiji/src/main/java/ome/zarr/fiji/util/BdvUtils.java index 2ce31bd4e..bcf69334d 100644 --- a/ome-zarr-fiji/src/main/java/ome/zarr/fiji/util/BdvUtils.java +++ b/ome-zarr-fiji/src/main/java/ome/zarr/fiji/util/BdvUtils.java @@ -128,8 +128,8 @@ public static BdvHandle show( final PyramidalBdv< ? > pyramidalBdv, final BdvHan shownIn.getViewerPanel().putClientProperty( BDV_HANDLE_KEY, shownIn ); if ( bdvHandle == null ) - setTimepoint( pyramidContents.omero, shownIn.getViewerPanel().state() ); - final List< Omero.Channel > omeroChannels = omeroChannels( pyramidContents.omero, pyramidalBdv.asSources().size() ); + setTimepoint( pyramidContents.omero(), shownIn.getViewerPanel().state() ); + final List< Omero.Channel > omeroChannels = omeroChannels( pyramidContents.omero(), pyramidalBdv.asSources().size() ); setChannelProperties( omeroChannels, pyramidalBdv.asSources(), shownIn.getConverterSetups(), shownIn.getViewerPanel().state() ); Container topLevelContainer = shownIn.getViewerPanel().getRootPane().getParent(); @@ -213,15 +213,16 @@ private static boolean isCurrentSource( final PyramidalBdv< ? > pyramidalBdv, fi } /** - * Colors each of {@code sources} the way the dataset's OMERO metadata asks - * for, gives it that channel's display range, and shows or hides it. + * Colors each of {@code sources} the way encoded in the OMERO channel metadata. + * Sets each source's display range and bounds to what the metadata asks for. + * Shows or hides each source according to the metadata. *

- * @param omeroChannels the channels to apply. Nothing is applied if it is empty + * @param omeroChannels the list of OMERO channels to apply, one per source and in source order * @param sources the viewer's sources, one per channel and in channel order - * @param converterSetups the viewer's converter setups, to look {@code sources} up in + * @param converterSetups the viewer's converter setups to look {@code sources} up in * @param state the viewer state that decides which sources are visible */ - private static void setChannelProperties( final List< Omero.Channel > omeroChannels, + public static void setChannelProperties( final List< Omero.Channel > omeroChannels, final List< ? extends SourceAndConverter< ? > > sources, final ConverterSetups converterSetups, final ViewerState state ) { @@ -233,8 +234,8 @@ private static void setChannelProperties( final List< Omero.Channel > omeroChann final Omero.Channel omeroChannel = omeroChannels.get( channelNumber ); final ConverterSetup converterSetup = converterSetups.getConverterSetup( source ); converterSetup.setColor( channelColor( omeroChannel ) ); - if ( omeroChannel != null && omeroChannel.window != null ) - setDisplayRangeAndBounds( converterSetup, converterSetups.getBounds(), omeroChannel.window ); + if ( omeroChannel != null && omeroChannel.window() != null ) + setDisplayRangeAndBounds( converterSetup, converterSetups.getBounds(), omeroChannel.window() ); state.setSourceActive( source, isChannelActive( omeroChannel ) ); } } @@ -254,9 +255,9 @@ private static void setChannelProperties( final List< Omero.Channel > omeroChann private static void setDisplayRangeAndBounds( final ConverterSetup converterSetup, final ConverterSetupBounds converterSetupBounds, final Omero.Channel.Window window ) { - converterSetup.setDisplayRange( window.start, window.end ); - if ( window.max > window.min ) - converterSetupBounds.setBounds( converterSetup, new Bounds( window.min, window.max ) ); + converterSetup.setDisplayRange( window.start(), window.end() ); + if ( window.max() > window.min() ) + converterSetupBounds.setBounds( converterSetup, new Bounds( window.min(), window.max() ) ); } /** @@ -272,16 +273,16 @@ private static void setDisplayRangeAndBounds( final ConverterSetup converterSetu */ static List< Omero.Channel > omeroChannels( final Omero omero, final int numChannels ) { - if ( omero == null || omero.channels == null || omero.channels.isEmpty() ) + if ( omero == null || omero.channels() == null || omero.channels().isEmpty() ) return Collections.emptyList(); - if ( omero.channels.size() != numChannels ) + if ( omero.channels().size() != numChannels ) { logger.warn( "The number of channels in the Omero metadata ({}) does not match the number of sources in the dataset ({}). Channel properties will not be applied.", - omero.channels.size(), numChannels ); + omero.channels().size(), numChannels ); return Collections.emptyList(); } - return omero.channels; + return omero.channels(); } /** @@ -294,9 +295,9 @@ static List< Omero.Channel > omeroChannels( final Omero omero, final int numChan */ private static ARGBType channelColor( final Omero.Channel omeroChannel ) { - final Color color = omeroChannel == null || omeroChannel.color == null + final Color color = omeroChannel == null || omeroChannel.color() == null ? Color.white - : Color.decode( "#" + omeroChannel.color ); + : Color.decode( "#" + omeroChannel.color() ); final int opaque = 255; return new ARGBType( ARGBType.rgba( color.getRed(), color.getGreen(), color.getBlue(), opaque ) ); } @@ -311,7 +312,7 @@ private static ARGBType channelColor( final Omero.Channel omeroChannel ) */ private static boolean isChannelActive( final Omero.Channel omeroChannel ) { - return omeroChannel == null || omeroChannel.active; + return omeroChannel == null || omeroChannel.active(); } /** @@ -323,7 +324,7 @@ private static boolean isChannelActive( final Omero.Channel omeroChannel ) */ private static int defaultTimepoint( final Omero omero ) { - return omero == null || omero.rdefs == null ? 0 : omero.rdefs.defaultT; + return omero == null || omero.rdefs() == null ? 0 : omero.rdefs().defaultT(); } /** diff --git a/ome-zarr-fiji/src/main/java/ome/zarr/fiji/util/OmeZarrImagesChooser.java b/ome-zarr-fiji/src/main/java/ome/zarr/fiji/util/OmeZarrImagesChooser.java index e575b2813..b5e79a76b 100644 --- a/ome-zarr-fiji/src/main/java/ome/zarr/fiji/util/OmeZarrImagesChooser.java +++ b/ome-zarr-fiji/src/main/java/ome/zarr/fiji/util/OmeZarrImagesChooser.java @@ -61,7 +61,7 @@ private OmeZarrImagesChooser() public static List< PyramidContents< ? > > choose( final List< PyramidContents< ? > > candidates, final PyramidContents< ? > preselected ) { final List< JCheckBox > checkboxes = candidates.stream() - .map( contents -> new JCheckBox( contents.name, contents == preselected ) ) + .map( contents -> new JCheckBox( contents.name(), contents == preselected ) ) .collect( Collectors.toList() ); final JOptionPane pane = new JOptionPane( checkboxes.toArray(), JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION ); final JDialog dialog = pane.createDialog( "OME-Zarr Images to open" ); diff --git a/ome-zarr-fiji/src/test/java/ome/zarr/fiji/PyramidalBdvTestBase.java b/ome-zarr-fiji/src/test/java/ome/zarr/fiji/PyramidalBdvTestBase.java index 76eb3f8f1..491838ce9 100644 --- a/ome-zarr-fiji/src/test/java/ome/zarr/fiji/PyramidalBdvTestBase.java +++ b/ome-zarr-fiji/src/test/java/ome/zarr/fiji/PyramidalBdvTestBase.java @@ -45,6 +45,7 @@ import org.scijava.Context; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.List; import bdv.tools.brightness.ConverterSetup; @@ -56,6 +57,7 @@ import ome.zarr.ZarrTestUtils; import ome.zarr.fiji.util.BdvUtils; import ome.zarr.imglib2.PyramidContents; +import ome.zarr.imglib2.metadata.Omero; /** * Shared parameterized tests for the BigDataViewer Fiji wrapper @@ -265,13 +267,21 @@ default void testOmeroDisplaySettings( final String resource ) throws URISyntaxE try (Context context = new Context()) { PyramidContents< ? > contents = read( resource, context ); - // NB: neither example turns a channel off, so switch one off to cover both answers. - contents.omero.channels.get( 1 ).active = false; PyramidalBdv< ? > pyramidalBdv = new PyramidalBdv<>( context, contents ); BdvHandle bdvHandle = BdvUtils.show( pyramidalBdv, null ); ViewerState state = bdvHandle.getViewerPanel().state(); assertEquals( 1, state.getCurrentTimepoint(), "the timepoint the rendering defaults name" ); List< ? extends SourceAndConverter< ? > > sources = pyramidalBdv.asSources(); + assertNotNull( contents ); + Omero omero = contents.omero(); + assertNotNull( omero ); + assertNotNull( omero.channels() ); + // NB: neither example turns a channel off, so switch one off to cover both answers. + List< Omero.Channel > channels = new ArrayList<>( omero.channels() ); + Omero.Channel on = channels.get( 1 ); + channels.set( 1, new Omero.Channel( false, on.coefficient(), on.color(), on.family(), on.inverted(), + on.label(), on.window() ) ); + BdvUtils.setChannelProperties( channels, sources, bdvHandle.getConverterSetups(), state ); assertTrue( state.isSourceActive( sources.get( 0 ) ) ); assertFalse( state.isSourceActive( sources.get( 1 ) ) ); assertTrue( state.isSourceActive( sources.get( 2 ) ) ); diff --git a/ome-zarr-fiji/src/test/java/ome/zarr/fiji/read/OmeZarrTest.java b/ome-zarr-fiji/src/test/java/ome/zarr/fiji/read/OmeZarrTest.java index 283f6f579..55078eb7d 100644 --- a/ome-zarr-fiji/src/test/java/ome/zarr/fiji/read/OmeZarrTest.java +++ b/ome-zarr-fiji/src/test/java/ome/zarr/fiji/read/OmeZarrTest.java @@ -223,7 +223,7 @@ void showInImageJAsksBeforeOpeningUncalibratedImage( PyramidBackend backend ) th assertEquals( 1, datasetService.getDatasets().size(), "Accepting must open the image" ); PyramidalDataset opened = assertInstanceOf( PyramidalDataset.class, context.getService( PyramidalService.class ).getPyramidals().get( 0 ) ); - assertTrue( opened.getPyramidContents().hasPlaceholderCalibration, + assertTrue( opened.getPyramidContents().hasPlaceholderCalibration(), "The opened image still knows its calibration was made up" ); SwingUtilities.invokeAndWait( () -> {} ); // let Swing process the show diff --git a/ome-zarr-fiji/src/test/java/ome/zarr/fiji/util/BdvUtilsTest.java b/ome-zarr-fiji/src/test/java/ome/zarr/fiji/util/BdvUtilsTest.java index 62febe0d1..a95c33d17 100644 --- a/ome-zarr-fiji/src/test/java/ome/zarr/fiji/util/BdvUtilsTest.java +++ b/ome-zarr-fiji/src/test/java/ome/zarr/fiji/util/BdvUtilsTest.java @@ -90,9 +90,9 @@ void showInOneWindowOpensNoWindowForNothing() @Test void testOmeroChannelCountMismatch() { - final Omero omero = new Omero(); - omero.channels = Arrays.asList( new Omero.Channel(), new Omero.Channel(), new Omero.Channel() ); - final int numChannels = omero.channels.size(); + final Omero.Channel channel = new Omero.Channel( true, 1, null, null, false, null, null ); + final Omero omero = new Omero( 0, null, null, Arrays.asList( channel, channel, channel ) ); + final int numChannels = omero.channels().size(); assertEquals( numChannels, BdvUtils.omeroChannels( omero, numChannels ).size(), "as many channels as sources" ); assertTrue( BdvUtils.omeroChannels( omero, numChannels + 1 ).isEmpty(), "fewer channels than sources" ); assertTrue( BdvUtils.omeroChannels( omero, numChannels - 1 ).isEmpty(), "more channels than sources" ); diff --git a/ome-zarr-imglib2/src/main/java/ome/zarr/imglib2/PyramidContents.java b/ome-zarr-imglib2/src/main/java/ome/zarr/imglib2/PyramidContents.java index 0b58e0635..4838c771e 100644 --- a/ome-zarr-imglib2/src/main/java/ome/zarr/imglib2/PyramidContents.java +++ b/ome-zarr-imglib2/src/main/java/ome/zarr/imglib2/PyramidContents.java @@ -29,6 +29,9 @@ package ome.zarr.imglib2; import java.lang.invoke.MethodHandles; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import net.imglib2.cache.img.CachedCellImg; import net.imglib2.img.Img; @@ -46,20 +49,25 @@ /** * Immutable snapshot of everything a {@link PyramidBackend} produces when - * reading an OME-Zarr multi-resolution image. + * reading an OME-Zarr multi-resolution image, except for pixel data: the + * images are lazily loaded views of the store, so they are shared rather than + * copied, and every call of {@link #asImg(int)} returns the same instance. + * Pixel writes therefore show up for every user of these contents and live in + * memory only, so they may be lost when the cache frees memory. *

- * Indices in {@code cachedCellImgs} and {@code transforms} - * are in resolution-level order (index 0 is the highest resolution). + * Resolution levels are indexed from {@code 0} (the highest resolution) to + * {@link #smallestResolutionLevel()}. * The imglib2 axis indices follow F-order (x, y, z, c, t), the order produced * after the backend has reversed any zarr C-order shapes. *

* The resolution-level count and the per-axis sizes are derived on demand from - * the full-resolution image and its axis list ({@code cachedCellImgs[ 0 ]} and - * {@code axesPerLevel[ 0 ]}), rather than stored, so {@link #numDimensions()}, + * the full-resolution image and its axis list ({@code asImg( 0 )} and + * {@code axes( 0 )}), rather than stored, so {@link #numDimensions()}, * {@link #numChannels()}, {@link #numTimepoints()} and {@link #axisIndex} cannot * disagree with one another. The constructor enforces the one cross-axis * invariant they all rely on: the axis list and the image have the same number - * of dimensions. + * of dimensions. It also checks that images, transforms and axis lists cover the + * same number of resolution levels. * * @param pixel type */ @@ -73,63 +81,110 @@ public final class PyramidContents< T extends NativeType< T > & RealType< T > > */ public static final int NO_MATCHING_LEVEL = -1; - public final String name; + private final String name; - public final T type; + private final T type; - /** - * Per-resolution-level transform from that level's image coordinates to the - * shared physical world coordinate system, in resolution-level order (index 0 - * is the highest resolution; aligned with {@link #cachedCellImgs}). - *

- * Source coordinates are the discrete pixel/voxel coordinates of - * resolution level i, in imglib2 F-order (x, y, z). Target coordinates - * are a single physical world space — in the spatial axes' unit (see - * {@link #axesPerLevel}) — shared by all levels, so that every level overlays - * correctly when displayed together. The transform combines the level's voxel - * spacing (scale) with its origin offset (translation) in that world space. - */ - public final AffineTransform3D[] transforms; + private final AffineTransform3D[] transforms; - public final CachedCellImg< T, ? >[] cachedCellImgs; + private final CachedCellImg< T, ? >[] cachedCellImgs; - /** - * Axes per resolution level: {@code [resolutionLevel][axisIndex]}. - */ - public final AxisCalibration[][] axesPerLevel; + private final AxisCalibration[][] axesPerLevel; - /** OMERO rendering metadata, or {@code null} if unavailable. */ - public final Omero omero; + private final Omero omero; - /** - * Whether the scale and unit in {@link #axesPerLevel} are placeholders that no - * part of the dataset states, rather than real calibration (see - * {@link AxisCalibration#createPlaceholderCalibration}). - *

- * {@code true} only for a single array read from its own axis names alone, - * with no parent multiscales group to supply a scale or unit. - */ - public final boolean hasPlaceholderCalibration; + private final boolean hasPlaceholderCalibration; private PyramidContents( final Builder< T > b ) { this.name = b.name; this.type = b.type; - this.transforms = b.transforms; - this.cachedCellImgs = b.cachedCellImgs; - this.axesPerLevel = b.axesPerLevel; + this.transforms = new AffineTransform3D[ b.transforms.length ]; + for ( int level = 0; level < transforms.length; level++ ) + transforms[ level ] = b.transforms[ level ].copy(); + this.cachedCellImgs = b.cachedCellImgs.clone(); + this.axesPerLevel = new AxisCalibration[ b.axesPerLevel.length ][]; + for ( int level = 0; level < axesPerLevel.length; level++ ) + axesPerLevel[ level ] = b.axesPerLevel[ level ].clone(); this.omero = b.omero; this.hasPlaceholderCalibration = b.hasPlaceholderCalibration; + if ( transforms.length != cachedCellImgs.length || axesPerLevel.length != cachedCellImgs.length ) + throw new IllegalArgumentException( "Level counts differ: " + cachedCellImgs.length + " images, " + + transforms.length + " transforms, " + axesPerLevel.length + " axis lists." ); + final int numDimensions = cachedCellImgs[ 0 ].numDimensions(); if ( axesPerLevel[ 0 ].length != numDimensions ) throw new IllegalArgumentException( "Full-resolution axis count (" + axesPerLevel[ 0 ].length + ") does not match the number of image dimensions (" + numDimensions + ")." ); } + /** Dataset name. */ + public String name() + { + return name; + } + + /** + * Pixel type, as a new instance on every call. + */ + public T type() + { + return type.createVariable(); + } + + /** + * Whether the scale and unit in {@link #axes(int)} are placeholders that no + * part of the dataset states, rather than real calibration (see + * {@link AxisCalibration#createPlaceholderCalibration}). + *

+ * {@code true} only for a single array read from its own axis names alone, + * with no parent multiscales group to supply a scale or unit. + */ + public boolean hasPlaceholderCalibration() + { + return hasPlaceholderCalibration; + } + /** - * Number of resolution levels in the pyramid, i.e. the length of - * {@link #cachedCellImgs}. + * Transform from the image coordinates of the given resolution level to the + * shared physical world coordinate system, as a new copy on every call. + *

+ * Source coordinates are the discrete pixel/voxel coordinates of + * the resolution level, in imglib2 F-order (x, y, z). Target coordinates + * are a single physical world space — in the spatial axes' unit (see + * {@link #axes(int)}) — shared by all levels, so that every level overlays + * correctly when displayed together. The transform combines the level's voxel + * spacing (scale) with its origin offset (translation) in that world space. + * + * @throws IndexOutOfBoundsException if {@code resolutionLevel} is not in + * {@code [0, numResolutionLevels())} + */ + public AffineTransform3D transform( final int resolutionLevel ) + { + return transforms[ resolutionLevel ].copy(); + } + + /** + * Axes of the image at the given resolution level, unmodifiable and indexed + * like the image's dimensions (imglib2 F-order). + * + * @throws IndexOutOfBoundsException if {@code resolutionLevel} is not in + * {@code [0, numResolutionLevels())} + */ + public List< AxisCalibration > axes( final int resolutionLevel ) + { + return Collections.unmodifiableList( Arrays.asList( axesPerLevel[ resolutionLevel ] ) ); + } + + /** OMERO rendering metadata, or {@code null} if unavailable. */ + public Omero omero() + { + return omero; + } + + /** + * Number of resolution levels in the pyramid. */ public int numResolutionLevels() { @@ -150,17 +205,25 @@ public int smallestResolutionLevel() * {@link #asLargestImg()}. *

* The dimensions are in imglib2 F-order — a subset of (x, y, z, c, t) — so use - * {@link #axisIndex} / {@link #hasAxis} together with {@link #axesPerLevel} to + * {@link #axisIndex} / {@link #hasAxis} together with {@link #axes(int)} to * map dimension indices to logical axes. + *

+ * The image is shared, not copied: pixel writes show up for every user of + * these contents and live in memory only, so they may be lost when the cache + * frees memory. */ public Img< T > asImg() { - return asImg( 0 ); + return asLargestImg(); } /** * Largest image of the pyramid, i.e., the full-resolution level {@code 0}. See * {@link #asImg()} for the axis ordering. + *

+ * The image is shared, not copied: pixel writes show up for every user of + * these contents and live in memory only, so they may be lost when the cache + * frees memory. */ public Img< T > asLargestImg() { @@ -172,6 +235,10 @@ public Img< T > asLargestImg() * {@link #smallestResolutionLevel()}. For a single-level pyramid this is the * same image as {@link #asLargestImg()}. See {@link #asImg()} for the axis * ordering. + *

+ * The image is shared, not copied: pixel writes show up for every user of + * these contents and live in memory only, so they may be lost when the cache + * frees memory. */ public Img< T > asSmallestImg() { @@ -182,6 +249,10 @@ public Img< T > asSmallestImg() * Image at the given resolution level ({@code 0} = highest resolution). See * {@link #asImg()} for the axis ordering; {@link #suggestResolutionLevel} can * pick a level by preferred width. + *

+ * The image is shared, not copied: pixel writes show up for every user of + * these contents and live in memory only, so they may be lost when the cache + * frees memory. * * @throws IndexOutOfBoundsException if {@code resolutionLevel} is not in * {@code [0, numResolutionLevels())} @@ -230,12 +301,12 @@ public int numTimepoints() /** * One label per channel. * Derived from the OMERO channel metadata when available and consistent with - * {@link #numChannels()}, otherwise falling back to the dataset {@link #name}. + * {@link #numChannels()}, otherwise falling back to the dataset {@link #name()}. */ public String[] channelLabels() { final int numChannels = numChannels(); - final boolean omeroValid = omero != null && omero.channels != null && omero.channels.size() == numChannels; + final boolean omeroValid = omero != null && omero.channels() != null && omero.channels().size() == numChannels; if ( omeroValid ) logger.trace( "Creating with OMERO metadata: {}", omero ); else @@ -243,7 +314,7 @@ public String[] channelLabels() final String[] labels = new String[ numChannels ]; for ( int i = 0; i < numChannels; i++ ) - labels[ i ] = omeroValid ? omero.channels.get( i ).label : name; + labels[ i ] = omeroValid ? omero.channels().get( i ).label() : name; return labels; } @@ -256,8 +327,8 @@ public String[] channelLabels() * The axis is located by name rather than by a fixed position: axes * may be absent, so an axis' index shifts with the set of axes actually * present (in F-order, {@code c} is at index 2 in {@code xyct} but at index 3 - * in {@code xyzc}). The lookup goes through {@link #axesPerLevel}, which is - * aligned 1:1 with the dimensions of {@code cachedCellImgs[ 0 ]}. + * in {@code xyzc}). The lookup goes through {@link #axes(int)}, which is + * aligned 1:1 with the dimensions of {@code asImg( 0 )}. */ public int axisIndex( final String axisName ) { @@ -466,7 +537,7 @@ public static < T extends NativeType< T > & RealType< T > > PyramidContents< T > * Factory for one case where a level's calibration has to be made up: a bare * array node read from its own axis names alone, with no parent multiscales * group to state a scale or unit. The result has - * {@link #hasPlaceholderCalibration} set. + * {@link #hasPlaceholderCalibration()} set. * * @param pixel type * @param name dataset name @@ -540,7 +611,7 @@ public Builder< T > omero( final Omero o ) return this; } - /** See {@link PyramidContents#hasPlaceholderCalibration}. Defaults to {@code false}. */ + /** See {@link PyramidContents#hasPlaceholderCalibration()}. Defaults to {@code false}. */ public Builder< T > hasPlaceholderCalibration( final boolean h ) { this.hasPlaceholderCalibration = h; diff --git a/ome-zarr-imglib2/src/main/java/ome/zarr/imglib2/metadata/Omero.java b/ome-zarr-imglib2/src/main/java/ome/zarr/imglib2/metadata/Omero.java index a6c5fa153..d5638639b 100644 --- a/ome-zarr-imglib2/src/main/java/ome/zarr/imglib2/metadata/Omero.java +++ b/ome-zarr-imglib2/src/main/java/ome/zarr/imglib2/metadata/Omero.java @@ -28,58 +28,186 @@ */ package ome.zarr.imglib2.metadata; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import com.google.gson.Gson; /** - * The class is used to represent omero metadata. + * The class is used to represent omero metadata. Immutable. */ @SuppressWarnings( "all" ) -public class Omero +public final class Omero { - public int id; + private final int id; - public String name; + private final String name; - public Rdefs rdefs; + private final Rdefs rdefs; - public List< Channel > channels; + private final List< Channel > channels; - public static class Rdefs + public Omero( final int id, final String name, final Rdefs rdefs, final List< Channel > channels ) { - public int defaultT; + this.id = id; + this.name = name; + this.rdefs = rdefs; + this.channels = channels == null ? null : new ArrayList<>( channels ); + } + + public int id() + { + return id; + } + + public String name() + { + return name; + } + + public Rdefs rdefs() + { + return rdefs; + } + + /** Unmodifiable, or {@code null} if the metadata lists no channels. */ + public List< Channel > channels() + { + return channels == null ? null : Collections.unmodifiableList( channels ); + } + + public static final class Rdefs + { + private final int defaultT; + + private final int defaultZ; + + private final String model; + + public Rdefs( final int defaultT, final int defaultZ, final String model ) + { + this.defaultT = defaultT; + this.defaultZ = defaultZ; + this.model = model; + } + + public int defaultT() + { + return defaultT; + } - public int defaultZ; + public int defaultZ() + { + return defaultZ; + } - public String model; + public String model() + { + return model; + } } - public static class Channel + public static final class Channel { - public boolean active; + private final boolean active; + + private final double coefficient; - public double coefficient; + private final String color; - public String color; + private final String family; - public String family; + private final boolean inverted; - public boolean inverted; + private final String label; - public String label; + private final Window window; - public Window window; + public Channel( final boolean active, final double coefficient, final String color, final String family, + final boolean inverted, final String label, final Window window ) + { + this.active = active; + this.coefficient = coefficient; + this.color = color; + this.family = family; + this.inverted = inverted; + this.label = label; + this.window = window; + } + + public boolean active() + { + return active; + } + + public double coefficient() + { + return coefficient; + } + + public String color() + { + return color; + } + + public String family() + { + return family; + } - public static class Window + public boolean inverted() { - public double start; + return inverted; + } + + public String label() + { + return label; + } + + public Window window() + { + return window; + } + + public static final class Window + { + private final double start; + + private final double end; + + private final double min; + + private final double max; + + public Window( final double start, final double end, final double min, final double max ) + { + this.start = start; + this.end = end; + this.min = min; + this.max = max; + } + + public double start() + { + return start; + } - public double end; + public double end() + { + return end; + } - public double min; + public double min() + { + return min; + } - public double max; + public double max() + { + return max; + } } } diff --git a/ome-zarr-n5/src/test/java/ome/zarr/n5/N5PyramidBackendTest.java b/ome-zarr-n5/src/test/java/ome/zarr/n5/N5PyramidBackendTest.java index 68cf9c73b..82a19e240 100644 --- a/ome-zarr-n5/src/test/java/ome/zarr/n5/N5PyramidBackendTest.java +++ b/ome-zarr-n5/src/test/java/ome/zarr/n5/N5PyramidBackendTest.java @@ -62,7 +62,7 @@ void testStaticReadPyramid() throws URISyntaxException Path path = ZarrTestUtils.resourcePath( "ome/zarr/testdata/5d_testing/5d_dataset_v4.ome.zarr" ); PyramidContents< ? > contents = N5PyramidBackend.readPyramid( path.toUri() ); assertNotNull( contents ); - assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name ); + assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name() ); assertEquals( 5, contents.numDimensions() ); assertEquals( 2, contents.numResolutionLevels() ); } diff --git a/ome-zarr-zarrjava/src/main/java/ome/zarr/zarrjava/ZarrJavaPyramidBackend.java b/ome-zarr-zarrjava/src/main/java/ome/zarr/zarrjava/ZarrJavaPyramidBackend.java index e0cb857e1..784eb4f6d 100644 --- a/ome-zarr-zarrjava/src/main/java/ome/zarr/zarrjava/ZarrJavaPyramidBackend.java +++ b/ome-zarr-zarrjava/src/main/java/ome/zarr/zarrjava/ZarrJavaPyramidBackend.java @@ -190,56 +190,49 @@ private static Omero convertOmero( final OmeroMetadata source ) { if ( source == null ) return null; - final Omero omero = new Omero(); - omero.id = source.id != null ? source.id : 0; - omero.name = source.name; - omero.rdefs = convertRdefs( source.rdefs ); + List< Omero.Channel > channels = null; if ( source.channels != null ) { - final List< Omero.Channel > channels = new ArrayList<>( source.channels.size() ); + channels = new ArrayList<>( source.channels.size() ); for ( final OmeroChannel channel : source.channels ) channels.add( convertChannel( channel ) ); - omero.channels = channels; } - return omero; + return new Omero( source.id != null ? source.id : 0, source.name, convertRdefs( source.rdefs ), channels ); } private static Omero.Rdefs convertRdefs( final OmeroRdefs source ) { if ( source == null ) return null; - final Omero.Rdefs rdefs = new Omero.Rdefs(); - rdefs.defaultT = source.defaultT != null ? source.defaultT : 0; - rdefs.defaultZ = source.defaultZ != null ? source.defaultZ : 0; - rdefs.model = source.model; - return rdefs; + return new Omero.Rdefs( + source.defaultT != null ? source.defaultT : 0, + source.defaultZ != null ? source.defaultZ : 0, + source.model ); } private static Omero.Channel convertChannel( final OmeroChannel source ) { if ( source == null ) return null; - final Omero.Channel channel = new Omero.Channel(); - channel.active = source.active != null && source.active; - channel.coefficient = source.coefficient != null ? source.coefficient : 0.0; - channel.color = source.color; - channel.family = source.family; - channel.inverted = source.inverted != null && source.inverted; - channel.label = source.label; - channel.window = convertWindow( source.window ); - return channel; + return new Omero.Channel( + source.active != null && source.active, + source.coefficient != null ? source.coefficient : 0.0, + source.color, + source.family, + source.inverted != null && source.inverted, + source.label, + convertWindow( source.window ) ); } private static Omero.Channel.Window convertWindow( final OmeroWindow source ) { if ( source == null ) return null; - final Omero.Channel.Window window = new Omero.Channel.Window(); - window.start = source.start != null ? source.start : 0.0; - window.end = source.end != null ? source.end : 0.0; - window.min = source.min != null ? source.min : 0.0; - window.max = source.max != null ? source.max : 0.0; - return window; + return new Omero.Channel.Window( + source.start != null ? source.start : 0.0, + source.end != null ? source.end : 0.0, + source.min != null ? source.min : 0.0, + source.max != null ? source.max : 0.0 ); } // --------------------------------------------------------------------- diff --git a/ome-zarr-zarrjava/src/test/java/ome/zarr/zarrjava/ZarrJavaPyramidBackendTest.java b/ome-zarr-zarrjava/src/test/java/ome/zarr/zarrjava/ZarrJavaPyramidBackendTest.java index 953545794..fd3107920 100644 --- a/ome-zarr-zarrjava/src/test/java/ome/zarr/zarrjava/ZarrJavaPyramidBackendTest.java +++ b/ome-zarr-zarrjava/src/test/java/ome/zarr/zarrjava/ZarrJavaPyramidBackendTest.java @@ -64,7 +64,7 @@ void testStaticReadPyramid() throws URISyntaxException Path path = ZarrTestUtils.resourcePath( "ome/zarr/testdata/5d_testing/5d_dataset_v4.ome.zarr" ); PyramidContents< ? > contents = ZarrJavaPyramidBackend.readPyramid( path.toUri() ); assertNotNull( contents ); - assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name ); + assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name() ); assertEquals( 5, contents.numDimensions() ); assertEquals( 2, contents.numResolutionLevels() ); } @@ -75,7 +75,7 @@ void testReadZippedArchive( @TempDir final Path tempDir ) throws Exception Path archive = ZarrTestUtils.zipDataset( "ome/zarr/testdata/5d_testing/5d_dataset_v4.ome.zarr", tempDir.resolve( "5d.ozx" ) ); PyramidContents< ? > contents = ZarrJavaPyramidBackend.readPyramid( archive.toUri() ); assertNotNull( contents ); - assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name ); + assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name() ); assertEquals( 5, contents.numDimensions() ); assertEquals( 2, contents.numResolutionLevels() ); } @@ -91,7 +91,7 @@ void testReadZippedArchiveOverHttp( @TempDir final Path tempDir ) throws Excepti URI uri = URI.create( "http://localhost:" + server.getAddress().getPort() + "/5d.ozx" ); PyramidContents< ? > contents = ZarrJavaPyramidBackend.readPyramid( uri ); assertNotNull( contents ); - assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name ); + assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name() ); assertEquals( 5, contents.numDimensions() ); assertEquals( 2, contents.numResolutionLevels() ); } diff --git a/test-shared/java/ome/zarr/imglib2/PyramidBackendTestBase.java b/test-shared/java/ome/zarr/imglib2/PyramidBackendTestBase.java index cc9f21b20..04650c68e 100644 --- a/test-shared/java/ome/zarr/imglib2/PyramidBackendTestBase.java +++ b/test-shared/java/ome/zarr/imglib2/PyramidBackendTestBase.java @@ -50,6 +50,7 @@ import org.scijava.Context; import java.net.URISyntaxException; +import java.util.List; import java.util.stream.Stream; import ome.zarr.ZarrTestUtils; @@ -151,15 +152,15 @@ default void testSingleArrayWithoutParentMultiscale() throws URISyntaxException assertNotNull( contents ); assertEquals( 1, contents.numResolutionLevels() ); assertEquals( 2, contents.numDimensions() ); - assertEquals( "0", contents.name, "Without a multiscales name, the array node name is used" ); - assertNull( contents.omero ); - assertTrue( contents.hasPlaceholderCalibration, + assertEquals( "0", contents.name(), "Without a multiscales name, the array node name is used" ); + assertNull( contents.omero() ); + assertTrue( contents.hasPlaceholderCalibration(), "An array read from its own axis names has no real scale or unit, and must say so" ); // dimension_names are y, x; the imglib2 image is in F-order, so x comes first. assertEquals( 0, contents.axisIndex( AxisCalibration.X ) ); assertEquals( 1, contents.axisIndex( AxisCalibration.Y ) ); - for ( final AxisCalibration axis : contents.axesPerLevel[ 0 ] ) + for ( final AxisCalibration axis : contents.axes( 0 ) ) { assertEquals( 1.0, axis.scale, "An array without a parent multiscale reads uncalibrated" ); assertEquals( "", axis.unit ); @@ -219,7 +220,7 @@ default void testSingleLevelFromParentMultiscale( String resource ) throws URISy assertNotNull( contents ); assertEquals( 1, contents.numResolutionLevels(), "A single level is a one-level pyramid" ); assertEquals( 5, contents.numDimensions() ); - assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name, "The name comes from the parent multiscales entry" ); + assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name(), "The name comes from the parent multiscales entry" ); // Level 1 of the 5d dataset: zarr [t, c, z, y, x] = [4, 3, 8, 32, 32], // reversed into imglib2 F-order. @@ -234,19 +235,19 @@ default void testSingleLevelFromParentMultiscale( String resource ) throws URISy assertScaleAtAxis( contents, AxisCalibration.C, 1.0 ); assertScaleAtAxis( contents, AxisCalibration.T, 1.0 ); - AffineTransform3D transform = contents.transforms[ 0 ]; + AffineTransform3D transform = contents.transform( 0 ); assertArrayEquals( new double[] { 2.0, 2.0, 2.0 }, new double[] { transform.get( 0, 0 ), transform.get( 1, 1 ), transform.get( 2, 2 ) }, "The transform is the one of the level that was read, not of level 0" ); - assertFalse( contents.hasPlaceholderCalibration, + assertFalse( contents.hasPlaceholderCalibration(), "The scale and unit come from the parent multiscales group, so they are real" ); - Omero omero = contents.omero; + Omero omero = contents.omero(); assertNotNull( omero, "OMERO metadata is inherited from the parent group" ); - assertEquals( 3, omero.channels.size() ); - assertEquals( "lynEGFP", omero.channels.get( 0 ).label ); - assertEquals( "00FF00", omero.channels.get( 0 ).color ); + assertEquals( 3, omero.channels().size() ); + assertEquals( "lynEGFP", omero.channels().get( 0 ).label() ); + assertEquals( "00FF00", omero.channels().get( 0 ).color() ); } } @@ -255,7 +256,7 @@ static void assertScaleAtAxis( final PyramidContents< ? > contents, final String { final int index = contents.axisIndex( axisName ); assertNotEquals( -1, index, "Axis " + axisName + " is missing" ); - assertEquals( expectedScale, contents.axesPerLevel[ 0 ][ index ].scale, axisName + " scale" ); + assertEquals( expectedScale, contents.axes( 0 ).get( index ).scale, axisName + " scale" ); } /** @@ -269,7 +270,7 @@ default void testNestedMultiscaleDatasetPaths( String resource ) throws URISynta try (Context context = new Context()) { PyramidContents< ? > contents = this.read( resource, context ); - assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name ); + assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name() ); assertEquals( 2, contents.numResolutionLevels() ); assertEquals( 16, contents.asImg( 0 ).dimension( 0 ) ); assertEquals( 8, contents.asImg( 1 ).dimension( 0 ) ); @@ -389,7 +390,7 @@ default void testGetType( String resource ) throws URISyntaxException try (Context context = new Context()) { PyramidContents< ? > contents = this.read( resource, context ); - Object type = contents.type; + Object type = contents.type(); assertInstanceOf( UnsignedByteType.class, type ); } } @@ -426,7 +427,7 @@ default void testGetName( String resource ) throws URISyntaxException try (Context context = new Context()) { PyramidContents< ? > contents = this.read( resource, context ); - assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name ); + assertEquals( ZarrTestUtils.IMAGE_NAME, contents.name() ); } } @@ -438,7 +439,7 @@ default void testOmeroRdefs( String resource ) throws URISyntaxException { PyramidContents< ? > contents = this.read( resource, context ); if ( resource.contains( "5d_testing" ) ) // dataset with omero properties - assertEquals( 1, contents.omero.rdefs.defaultT ); + assertEquals( 1, contents.omero().rdefs().defaultT() ); } } @@ -538,11 +539,11 @@ default void testPhysicalSizeConsistentAcrossResolutionLevels( String resource ) */ static double[] physicalExtents( final PyramidContents< ? > contents, final int level ) { - final AxisCalibration[] axes = contents.axesPerLevel[ level ]; + final List< AxisCalibration > axes = contents.axes( level ); final Img< ? > img = contents.asImg( level ); - final double[] extents = new double[ axes.length ]; - for ( int d = 0; d < axes.length; d++ ) - extents[ d ] = axes[ d ].scale * img.dimension( d ); + final double[] extents = new double[ axes.size() ]; + for ( int d = 0; d < axes.size(); d++ ) + extents[ d ] = axes.get( d ).scale * img.dimension( d ); return extents; } @@ -560,7 +561,7 @@ default void testVoxelCalibration( String resource ) throws URISyntaxException final int index = contents.axisIndex( axisName ); if ( index < 0 ) continue; // axis not present (e.g. no z) - final AxisCalibration axis = contents.axesPerLevel[ 0 ][ index ]; + final AxisCalibration axis = contents.axes( 0 ).get( index ); assertEquals( 1.0, axis.scale ); assertEquals( "", axis.unit ); }