diff --git a/.gitignore b/.gitignore
index 8502caedf..4959fdacb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,5 @@
# IntelliJ #
.idea/
SPIM_Registration.iml
+
+.claude
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 11927a283..b5c633db4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
net.preibisch
multiview-reconstruction
- 9.0.0-SNAPSHOT
+ 9.0.1-SNAPSHOT
Multiview Reconstruction
Software for the reconstruction of multi-view microscopic acquisitions
@@ -98,6 +98,10 @@ like Selective Plane Illumination Microscopy (SPIM) Data.
gpl_v2
Multiview Reconstruction developers.
+ 17
+ https://javadoc.scijava.org/Java11/
+ https://javadoc.scijava.org/JavaFX11/
+ true
sign,deploy-to-scijava
@@ -119,6 +123,8 @@ like Selective Plane Illumination Microscopy (SPIM) Data.
1.0.0-beta-20
4.0.5
0.18.3
+ 10.6.11
+ 19.0.2.1
0.3.1
1.6.6
@@ -318,6 +324,11 @@ like Selective Plane Illumination Microscopy (SPIM) Data.
ijp-kheops
${ijp-kheops.version}
+
+ ch.epfl.biop
+ quick-start-czi-reader
+ 0.3.0
+
com.google.code.gson
gson
@@ -361,6 +372,23 @@ like Selective Plane Illumination Microscopy (SPIM) Data.
slf4j-api
+
+
+ org.openjfx
+ javafx-base
+ ${scijava.natives.classifier.javafx}
+
+
+ org.openjfx
+ javafx-controls
+ ${scijava.natives.classifier.javafx}
+
+
+ org.openjfx
+ javafx-swing
+ ${scijava.natives.classifier.javafx}
+
+
org.junit.jupiter
@@ -405,7 +433,7 @@ like Selective Plane Illumination Microscopy (SPIM) Data.
-
+
fatjar
@@ -455,6 +483,28 @@ like Selective Plane Illumination Microscopy (SPIM) Data.
+
+ with-deps
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-dependencies
+ package
+ copy-dependencies
+
+ ${project.build.directory}/deps
+ runtime
+
+
+
+
+
+
+
diff --git a/src/main/java/net/preibisch/mvrecon/fiji/datasetmanager/SmartSPIM.java b/src/main/java/net/preibisch/mvrecon/fiji/datasetmanager/SmartSPIM.java
index f810e8a19..c92368572 100644
--- a/src/main/java/net/preibisch/mvrecon/fiji/datasetmanager/SmartSPIM.java
+++ b/src/main/java/net/preibisch/mvrecon/fiji/datasetmanager/SmartSPIM.java
@@ -227,7 +227,7 @@ public static boolean populateImageSize( final SmartSPIMMetaData metadata, final
IOFunctions.println( "file names are not equal. Stopping." );
return false;
}
-
+
}
return true;
diff --git a/src/main/java/net/preibisch/mvrecon/fiji/plugin/resave/Resave_N5Api.java b/src/main/java/net/preibisch/mvrecon/fiji/plugin/resave/Resave_N5Api.java
index 47366a303..4266f6f0b 100644
--- a/src/main/java/net/preibisch/mvrecon/fiji/plugin/resave/Resave_N5Api.java
+++ b/src/main/java/net/preibisch/mvrecon/fiji/plugin/resave/Resave_N5Api.java
@@ -39,6 +39,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
+import mpicbg.spim.data.sequence.VoxelDimensions;
import org.bigdataviewer.n5.N5CloudImageLoader;
import org.janelia.saalfeldlab.n5.Compression;
import org.janelia.saalfeldlab.n5.DataType;
@@ -192,13 +193,15 @@ else if ( n5Params.format == StorageFormat.HDF5 )
}
else
{
+ VoxelDimensions vx = data.getSequenceDescription().getViewDescription( viewId ).getViewSetup().getVoxelSize();
// 5d OME-ZARR with dimension=1 in c and t
mrInfo = N5ApiTools.setupBdvDatasetsOMEZARR_ResaveRaw(
n5Writer,
viewId,
dataTypes.get( viewId.getViewSetupId() ),
dimensions.get( viewId.getViewSetupId() ),
- //data.getSequenceDescription().getViewDescription( viewId ).getViewSetup().getVoxelSize().dimensionsAsDoubleArray(),
+ vx.dimensionsAsDoubleArray(), // resolutionS0
+ vx.unit(),
compression,
blockSize,
downsamplings,
diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/explorer/popup/DisplayRawImagesPopup.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/explorer/popup/DisplayRawImagesPopup.java
index 753c523da..61f25e725 100644
--- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/explorer/popup/DisplayRawImagesPopup.java
+++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/explorer/popup/DisplayRawImagesPopup.java
@@ -29,7 +29,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.stream.Collectors;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/AllenOMEZarrProperties.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/AllenOMEZarrProperties.java
index 65eb8beb2..8c403936f 100644
--- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/AllenOMEZarrProperties.java
+++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/AllenOMEZarrProperties.java
@@ -23,64 +23,52 @@
package net.preibisch.mvrecon.fiji.spimdata.imgloaders;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.Map;
import org.janelia.saalfeldlab.n5.DataType;
import org.janelia.saalfeldlab.n5.DatasetAttributes;
import org.janelia.saalfeldlab.n5.N5Reader;
+import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMetadata;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMultiScaleMetadata;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMultiScaleMetadata.OmeNgffDataset;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.coordinateTransformations.CoordinateTransformation;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.coordinateTransformations.ScaleCoordinateTransformation;
+import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.coordinateTransformations.TranslationCoordinateTransformation;
import bdv.img.n5.N5Properties;
import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription;
import mpicbg.spim.data.sequence.TimePoint;
import mpicbg.spim.data.sequence.ViewId;
+import net.preibisch.legacy.io.IOFunctions;
import net.preibisch.mvrecon.fiji.spimdata.imgloaders.AllenOMEZarrLoader.OMEZARREntry;
public class AllenOMEZarrProperties implements N5Properties
{
private final AbstractSequenceDescription< ?, ?, ? > sequenceDescription;
- private final Map< ViewId, OMEZARREntry > viewIdToPath;
+ // mapping of viewIDs to corresponding OME-ZARRs
+ private final Map< ViewId, OMEZARREntry > viewIdToOmeZarrPath;
- // Cache for actual dataset paths from multiscales metadata: setupId -> (level -> datasetPath)
- private final Map< Integer, String[] > levelPathCache = new HashMap<>();
+ // only warn once per opened XML if TranslationCoordinateTransformation is missing
+ private boolean warnedMissingTranslation = false;
public AllenOMEZarrProperties(
final AbstractSequenceDescription< ?, ?, ? > sequenceDescription,
- final Map< ViewId, OMEZARREntry > viewIdToPath )
+ final Map< ViewId, OMEZARREntry > viewIdToOmeZarrPath)
{
this.sequenceDescription = sequenceDescription;
- this.viewIdToPath = viewIdToPath;
+ this.viewIdToOmeZarrPath = viewIdToOmeZarrPath;
}
private String getPath( final int setupId, final int timepointId )
{
- return viewIdToPath.get( new ViewId( timepointId, setupId ) ).getPath();
+ return viewIdToOmeZarrPath.get( new ViewId( timepointId, setupId ) ).getPath();
}
@Override
public String getDatasetPath( final N5Reader n5, final int setupId, final int timepointId, final int level )
{
- // TODO: use the N5Reader @goinac @schweinfurthl, Claude wanted to add:
- /*
- // Ensure the level-path cache is populated from the multiscales metadata,
- // otherwise we would fall back to numeric levels which are wrong for v0.5 nested paths
- if ( levelPathCache.get( setupId ) == null )
- getMipMapResolutions( this, n5, setupId );
- */
-
- // Check if we have cached the actual paths from multiscales metadata
- final String[] cachedPaths = levelPathCache.get( setupId );
- if ( cachedPaths != null && level < cachedPaths.length )
- {
- return getPath( setupId, timepointId ) + "/" + cachedPaths[ level ];
- }
- // Fallback to numeric level (for v0.4 or if cache not populated)
- return String.format( getPath( setupId, timepointId ) + "/%d", level );
+ return getMultiscaleDatasetPathOrDefault( n5, timepointId, setupId, level );
}
@Override
@@ -98,7 +86,7 @@ public double[][] getMipmapResolutions( final N5Reader n5, final int setupId )
@Override
public long[] getDimensions( final N5Reader n5, final int setupId, final int timepointId, final int level )
{
- final String path = getDatasetPath( n5, setupId, timepointId, level );
+ final String path = getMultiscaleDatasetPathOrDefault(n5, timepointId, setupId, level);
final long[] dimensions = n5.getDatasetAttributes( path ).getDimensions();
// dataset dimensions is 5D, remove the channel and time dimensions
return Arrays.copyOf( dimensions, 3 );
@@ -126,17 +114,13 @@ private static int getFirstAvailableTimepointId( final AbstractSequenceDescripti
private static DataType getDataType( final AllenOMEZarrProperties n5properties, final N5Reader n5, final int setupId )
{
- // we need to make sure the cache is populated, otherwise getDatasetPath(...) might return the wrong path
- if ( n5properties.levelPathCache.get( setupId ) == null )
- getMipMapResolutions( n5properties, n5, setupId );
-
final int timePointId = getFirstAvailableTimepointId( n5properties.sequenceDescription, setupId );
// If all timepoints are missing, return a default data type
if ( timePointId < 0 )
return DataType.UINT16;
- final String path = n5properties.getDatasetPath( n5, setupId, timePointId, 0 );
+ final String path = n5properties.getMultiscaleDatasetPathOrDefault(n5, timePointId, setupId, 0);
final DatasetAttributes attributes = n5.getDatasetAttributes( path );
if ( attributes == null )
@@ -154,61 +138,153 @@ private static double[][] getMipMapResolutions( final AllenOMEZarrProperties n5p
return new double[][] { { 1.0, 1.0, 1.0 } };
// multiresolution pyramid
+ final OmeNgffMultiScaleMetadata multiScaleMetadata = n5properties.getViewSetupMultiscaleMetadata(n5, timePointId, setupId);
+ final double[][] mipMapResolutions = new double[ multiScaleMetadata.datasets.length ][ 3 ];
+ double[] scaleS0 = null;
//org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMetadata
// for this to work you need to register an adapter in the N5Factory class
// final GsonBuilder builder = new GsonBuilder().registerTypeAdapter( CoordinateTransformation.class, new CoordinateTransformationAdapter() );
final String path = n5properties.getPath( setupId, timePointId );
- // Try v0.4 structure first (multiscales at root), then v0.5/Zarr v3 structure (nested under ome)
- OmeNgffMultiScaleMetadata[] multiscales = n5.getAttribute( path, "multiscales", OmeNgffMultiScaleMetadata[].class );
-
- if ( multiscales == null || multiscales.length == 0 )
- multiscales = n5.getAttribute( path, "ome/multiscales", OmeNgffMultiScaleMetadata[].class );
+ // iterate over all resolution levels for scale
+ for ( int s = 0; s < multiScaleMetadata.datasets.length; ++s )
+ {
+ final OmeNgffDataset ds = multiScaleMetadata.datasets[ s ];
- if ( multiscales == null || multiscales.length == 0 )
- throw new RuntimeException( "Could not parse OME-ZARR multiscales object (tried 'multiscales' and 'ome/multiscales'). stopping." );
+ for ( final CoordinateTransformation< ? > c : ds.coordinateTransformations )
+ {
+ if ( c instanceof ScaleCoordinateTransformation )
+ {
+ final ScaleCoordinateTransformation scale = ( ScaleCoordinateTransformation ) c;
- if ( multiscales.length != 1 )
- System.out.println( "This dataset has " + multiscales.length + " objects, we expected 1. Picking the first one." );
+ if ( scaleS0 == null )
+ scaleS0 = scale.getScale().clone();
- //System.out.println( "AllenOMEZarrLoader.getMipmapResolutions() for " + setupId + " using " + n5properties.getPath( setupId, timePointId ) + ": found " + multiscales[ 0 ].datasets.length + " multi-resolution levels." );
+ for ( int d = 0; d < mipMapResolutions[ s ].length; ++d )
+ {
+ mipMapResolutions[ s ][ d ] = scale.getScale()[ d ] / scaleS0[ d ];
+ mipMapResolutions[ s ][ d ] = Math.round(mipMapResolutions[ s ][ d ]*10000)/10000d; // round to the 5th digit
+ }
+ }
+ }
+ }
- double[][] mipMapResolutions = new double[ multiscales[ 0 ].datasets.length ][ 3 ];
- double[] firstScale = null;
+ // iterate over all resolution levels for translation (to make sure scaleS0 is assigned if it existed)
+ // OME-Zarr 0.4: physical = pixel * scale + translation
+ // For averaging downsampling by factor r: expected relative shift = (r-1)/2 in s0 pixels
+ // For non-averaging (strided) downsampling: expected relative shift = 0
+ double[] translationS0 = null;
+ Boolean isAveraging = null; // determined from first downsampled level with r>1, then verified for subsequent levels
- // Cache the actual dataset paths from metadata (they may not be just "0", "1", "2")
- final String[] levelPaths = new String[ multiscales[ 0 ].datasets.length ];
+ //System.out.println( "\nsetup " + setupId );
- for ( int i = 0; i < multiscales[ 0 ].datasets.length; ++i )
+ for ( int s = 0; s < multiScaleMetadata.datasets.length; ++s )
{
- final OmeNgffDataset ds = multiscales[ 0 ].datasets[ i ];
+ final OmeNgffDataset ds = multiScaleMetadata.datasets[ s ];
- // Store the actual path for this resolution level
- levelPaths[ i ] = ds.path;
+ boolean foundTranslation = false;
for ( final CoordinateTransformation< ? > c : ds.coordinateTransformations )
{
- if ( c instanceof ScaleCoordinateTransformation )
+ if ( c instanceof TranslationCoordinateTransformation )
{
- final ScaleCoordinateTransformation s = ( ScaleCoordinateTransformation ) c;
+ final TranslationCoordinateTransformation t = ( TranslationCoordinateTransformation ) c;
+ foundTranslation = true;
+
+ if ( scaleS0 == null )
+ throw new IllegalStateException( "Expected first scale to be set before the translation for level " + s + " dataset is processed" );
+
+ //System.out.println( "s=" + s + ", translation: " + Arrays.toString( t.getTranslation() ));
- if ( firstScale == null )
- firstScale = s.getScale().clone();
+ // capture s0's translation
+ if ( translationS0 == null )
+ {
+ translationS0 = t.getTranslation().clone();
+ // s0: no validation needed, just capture
+ break;
+ }
- for ( int d = 0; d < mipMapResolutions[ i ].length; ++d )
+ for ( int d = 0; d < mipMapResolutions[ s ].length; ++d )
{
- mipMapResolutions[ i ][ d ] = s.getScale()[ d ] / firstScale[ d ];
- mipMapResolutions[ i ][ d ] = Math.round(mipMapResolutions[ i ][ d ]*10000)/10000d; // round to the 5th digit
+ final double r = mipMapResolutions[ s ][ d ];
+
+ // skip dimensions that are not downsampled (r=1), both modes have shift=0
+ if ( Math.abs( r - 1.0 ) < 0.01 )
+ continue;
+
+ // relative pixel translation: (translation_s - translationS0) / scaleS0
+ final double relPxTranslation = ( t.getTranslation()[ d ] - translationS0[ d ] ) / scaleS0[ d ];
+ final double expectedAveraging = ( r - 1.0 ) / 2.0; // 0.5, 1.5, 3.5, 7.5, ...
+
+ final boolean matchesAveraging = Math.abs( relPxTranslation - expectedAveraging ) < 0.01;
+ final boolean matchesNonAveraging = Math.abs( relPxTranslation ) < 0.01;
+
+ //System.out.println( "s=" + s + ", d=" + d + ", relPxTranslation=" + relPxTranslation + " @ scale=" + r );
+
+ if ( isAveraging == null )
+ {
+ // determine mode from first downsampled dimension
+ if ( matchesAveraging )
+ isAveraging = true;
+ else if ( matchesNonAveraging )
+ throw new IllegalStateException( "Non-averaging downsampling detected (translation=0 for level " + s + " dim " + d + "), which is currently not supported." );
+ else
+ throw new IllegalStateException( "Unsupported translation for level " + s + " dim " + d + ": relative pixel translation=" + relPxTranslation + " (expected " + expectedAveraging + " for averaging or 0.0 for non-averaging)." );
+ }
+ else
+ {
+ // verify consistency with detected mode
+ final double expected = isAveraging ? expectedAveraging : 0.0;
+ if ( Math.abs( relPxTranslation - expected ) >= 0.01 )
+ throw new IllegalStateException( "Inconsistent translation for level " + s + " dim " + d + ": relative pixel translation=" + relPxTranslation + ", expected " + expected + " based on detected " + ( isAveraging ? "averaging" : "non-averaging" ) + " downsampling." );
+ }
}
- //System.out.println( "AllenOMEZarrLoader.getMipmapResolutions(), level " + i + ": " + Arrays.toString( s.getScale() ) + " >> " + Arrays.toString( mipMapResolutions[ i ] ) );
}
}
- }
- // Cache the level paths for this setup
- n5properties.levelPathCache.put( setupId, levelPaths );
+ if ( !foundTranslation && s > 0 && !n5properties.warnedMissingTranslation )
+ {
+ IOFunctions.println( "WARNING: No TranslationCoordinateTransformation found, assuming half-pixel shifts for averaging-based downsampling." );
+ n5properties.warnedMissingTranslation = true;
+ }
+ }
return mipMapResolutions;
}
+
+ private String getMultiscaleDatasetPathOrDefault( N5Reader n5, int timepointId, int setupId, int level )
+ {
+ OmeNgffMultiScaleMetadata omeNgffMultiScaleMetadata = getViewSetupMultiscaleMetadata(n5, timepointId, setupId);
+
+ String viewSetupPath = getPath( setupId, timepointId );
+ // get the first scale path from the metadata
+ String datasetPath = omeNgffMultiScaleMetadata.datasets[level].path;
+ return String.format( "%s/%s", viewSetupPath, datasetPath);
+ }
+
+ // retrieve and cache the multiscale metadata
+ private OmeNgffMultiScaleMetadata getViewSetupMultiscaleMetadata(N5Reader n5, int timePointId, int setupId) {
+ String datasetPath = getPath( setupId, timePointId );
+
+ System.out.println( "Getting multiscale metadata for " + datasetPath );
+ OmeNgffMetadata omeMetadata = n5.getAttribute(datasetPath, "ome", OmeNgffMetadata.class);
+
+ final OmeNgffMultiScaleMetadata[] multiscales;
+ if (omeMetadata != null) {
+ System.out.println( "Found OME metadata at " + datasetPath + ": " + omeMetadata);
+ multiscales = omeMetadata.multiscales;
+ } else {
+ multiscales = n5.getAttribute( datasetPath, "multiscales", OmeNgffMultiScaleMetadata[].class );
+ System.out.println( "Retrieved OME multiscales at " + datasetPath + ": " + multiscales);
+ }
+
+ if ( multiscales == null || multiscales.length == 0 )
+ throw new IllegalStateException( "Could not parse OME-ZARR multiscales object. stopping." );
+
+ if ( multiscales.length > 1 )
+ System.out.println( "This dataset has " + multiscales.length + " objects, we expected 1. Picking the first one." );
+
+ return multiscales[0];
+ }
}
diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/LegacyFileMapImgLoaderLOCI.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/LegacyFileMapImgLoaderLOCI.java
index 0e8f63874..479d36f30 100644
--- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/LegacyFileMapImgLoaderLOCI.java
+++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/LegacyFileMapImgLoaderLOCI.java
@@ -192,7 +192,7 @@ protected < T extends RealType< T > & NativeType< T > > RandomAccessibleInterval
// use a new ImageReader since we might be loading multi-threaded and BioFormats is not thread-save
// use Memoizer to cache ReaderState for each File on disk
// see: https://www-legacy.openmicroscopy.org/site/support/bio-formats5.1/developers/matlab-dev.html#reader-performance
- IFormatReader reader = null;
+ IFormatReader reader;
if (zGrouped)
{
final FileStitcher fs = new FileStitcher(true);
@@ -239,8 +239,14 @@ protected < T extends RealType< T > & NativeType< T > > RandomAccessibleInterval
final int height = dim[ 1 ];
final int depth = dim[ 2 ];
final int numPx = width * height;
-
- final byte[] b = new byte[ numPx * reader.getBitsPerPixel() / 8 ];
+ final int bitsPerPixels = reader.getBitsPerPixel();
+ final int bytesPerPixel = BioformatsReaderUtils.getBytesPerPixel( reader );
+ IOFunctions.println(String.format(
+ "Reader=%s Create %d buffer for (%d, %d, %d) image with pixelType %d, %d bits/pixel, %d bytes/pixel",
+ reader.getClass().getName(), (numPx * bytesPerPixel), width, height, depth, pixelType, bitsPerPixels, bytesPerPixel)
+ );
+ final int bufferSize = Math.multiplyExact( numPx, bytesPerPixel );
+ final byte[] b = new byte[ bufferSize ];
try
{
@@ -539,8 +545,9 @@ public static > long getMaxNonzero(RandomAccessibleInter
public static long getMaxNonzero(IFormatReader reader)
{
- final int siz = reader.getBitsPerPixel() / 8 * reader.getRGBChannelCount() * reader.getSizeX()
- * reader.getSizeY();
+ final int siz = Math.multiplyExact(
+ BioformatsReaderUtils.getPlaneSizeInBytes( reader ),
+ reader.getRGBChannelCount() );
final byte[] buffer = new byte[siz];
final int nC = reader.getSizeC() == reader.getRGBChannelCount() ? 1 : reader.getSizeC();
diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttributes.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttributes.java
index dfed47383..6fe9f0c8e 100644
--- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttributes.java
+++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttributes.java
@@ -27,21 +27,17 @@
import java.util.Arrays;
import java.util.function.Function;
+import net.preibisch.mvrecon.process.interestpointregistration.TransformationTools;
import org.janelia.saalfeldlab.n5.DatasetAttributes;
import org.janelia.saalfeldlab.n5.N5Reader;
import org.janelia.saalfeldlab.n5.universe.N5Factory;
import org.janelia.saalfeldlab.n5.universe.StorageFormat;
import org.janelia.saalfeldlab.n5.universe.metadata.axes.Axis;
+import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMetadata;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMultiScaleMetadata;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMultiScaleMetadata.OmeNgffDataset;
-import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMultiScaleMetadata.OmeNgffDownsamplingMetadata;
-import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.coordinateTransformations.CoordinateTransformation;
-import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.coordinateTransformations.ScaleCoordinateTransformation;
-import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.coordinateTransformations.TranslationCoordinateTransformation;
-import mpicbg.spim.data.sequence.VoxelDimensions;
import net.imglib2.realtransform.AffineTransform3D;
-import net.preibisch.mvrecon.process.interestpointregistration.TransformationTools;
import util.URITools;
public class OMEZarrAttributes
@@ -52,112 +48,125 @@ public class OMEZarrAttributes
* @param numResolutionLevels - number of multiresolution levels (e.g. s0, s1, s2 would be 3), always includes full res
* @return
*/
- public static OmeNgffMultiScaleMetadata[] createOMEZarrMetadata(
+ public static OmeNgffMetadata createOMEZarrMetadata(
final int n,
- final String name, // I also saw "/"
+ final String name,
+ final String version,
final double[] resolutionS0,
- final String unitXYZ, // e.g micrometer
+ final String unitXYZ,
final int numResolutionLevels,
final Function levelToName,
final Function levelToMipmapTransform )
{
- // TODO: make sure the unit is supported by OME-ZARR, if not replace it because otherwise readers will fail
- // TODO: e.g. um -> micrometer
- // TODO: etc.
- // TODO: can you find out what the correct unit for 'unit unknown' is, because that is what I would replace it with, otherwise micrometer
- // TOOD: then please also change in TransformationTools.computeCalibration
-
- final OmeNgffMultiScaleMetadata[] meta = new OmeNgffMultiScaleMetadata[ 1 ];
-
- // dataset name and co
- final String path = "";
- final String type = null;
-
- // axis descriptions
- // TODO: seem to be TCZYX order
+ // axis descriptions (TCZYX order, same as v0.4)
final Axis[] axes = new Axis[ n ];
- int index = 0;
+ int axisIndex = 0;
if ( n >= 5 )
- axes[ index++ ] = new Axis( "time", "t", "millisecond" );
-
+ axes[ axisIndex++ ] = new Axis( "time", "t", "millisecond" );
if ( n >= 4 )
- axes[ index++ ] = new Axis( "channel", "c", null );
-
- axes[ index++ ] = new Axis( "space", "z", unitXYZ );
- axes[ index++ ] = new Axis( "space", "y", unitXYZ );
- axes[ index++ ] = new Axis( "space", "x", unitXYZ );
+ axes[ axisIndex++ ] = new Axis( "channel", "c", null );
+ final String spatialUnit = adaptSpatialUnit( unitXYZ );
+ axes[ axisIndex++ ] = new Axis( "space", "z", spatialUnit );
+ axes[ axisIndex++ ] = new Axis( "space", "y", spatialUnit );
+ axes[ axisIndex++ ] = new Axis( "space", "x", spatialUnit );
- // multiresolution-pyramid
- // TODO: seem to be in XYZCT order (but in the file it seems reversed)
- final OmeNgffDataset[] datasets = new OmeNgffDataset[ numResolutionLevels ];
+ // per-level scales / translations / paths
+ final String[] scalePaths = new String[ numResolutionLevels ];
+ final double[][] scales = new double[ numResolutionLevels ][ n ];
+ final double[][] translations = new double[ numResolutionLevels ][ n ];
for ( int s = 0; s < numResolutionLevels; ++s )
{
- datasets[ s ] = new OmeNgffDataset();
-
- datasets[ s ].path = levelToName.apply( s );
+ scalePaths[ s ] = levelToName.apply( s );
final AffineTransform3D m = levelToMipmapTransform.apply( s );
- final double[] translation = new double[ n ];
- final double[] scale = new double[ n ];
-
for ( int d = 0; d < 3; ++d )
{
- translation[ d ] = m.getTranslation()[ d ];
- scale[ d ] = resolutionS0[ d ] * m.get( d, d );
+ translations[ s ][ d ] = resolutionS0[ d ] * m.getTranslation()[ d ];
+ scales[ s ][ d ] = resolutionS0[ d ] * m.get( d, d );
}
// if 4d and 5d, add 1's for C and T
for ( int d = 3; d < n; ++d )
{
- translation[ d ] = 0.0;
- scale[ d ] = 1.0;
+ translations[ s ][ d ] = 0.0;
+ scales[ s ][ d ] = 1.0;
}
-
- datasets[ s ].coordinateTransformations = new CoordinateTransformation[ 2 ];
- datasets[ s ].coordinateTransformations[ 0 ] = new ScaleCoordinateTransformation( scale ); // TODO: check
- datasets[ s ].coordinateTransformations[ 1 ] = new TranslationCoordinateTransformation( translation ); // TODO: check
}
-
- final double[] scale = new double[ n ];
-
- for ( int d = 0; d < n; ++d )
- scale[ d ] = 1.0;
-
- // just saw these being null everywhere
- final DatasetAttributes[] childrenAttributes = null;
- final CoordinateTransformation>[] coordinateTransformations = new CoordinateTransformation[] { new ScaleCoordinateTransformation( scale ) }; // I also saw null
- final OmeNgffDownsamplingMetadata metadata = null;
-
- meta[ 0 ] = new OmeNgffMultiScaleMetadata( n, path, name, type, "0.4", axes, datasets, coordinateTransformations, childrenAttributes, metadata );
-
- return meta;
+ return OmeNgffMetadata.buildForWriting(n, name, version, axes, scalePaths, scales, translations);
}
-
- // Note: TransformationTools.computeAverageCalibration does this reasonably correct
- /*
- public static double[] getResolutionS0( final VoxelDimensions vx, final double anisoF, final double downsamplingF )
+ public static double[] getResolutionS0( final double[] cal, final double anisoF, final double downsamplingF )
{
- final double[] resolutionS0 = vx.dimensionsAsDoubleArray();
+ double[] resolutionS0 = Arrays.copyOf( cal, cal.length );
- // not preserving anisotropy
- if ( Double.isNaN( anisoF ) )
- resolutionS0[ 2 ] = resolutionS0[ 0 ];
+ if ( !Double.isNaN( anisoF ) ) {
+ // preserving anisotropy
+ resolutionS0[2] = cal[2] * anisoF;
+ }
// downsampling
if ( !Double.isNaN( downsamplingF ) )
Arrays.setAll( resolutionS0, d -> resolutionS0[ d ] * downsamplingF );
- // TODO: this is a hack so the export downsampling pyramid is working
- Arrays.setAll( resolutionS0, d -> 1 );
-
return resolutionS0;
}
- */
+
+ /**
+ * Adapt various space unit namings to the units supported by Neuroglancer.
+ * OME NGFF spec does not have any restrictions on units but Neuroglancer only supports the ones that end in meter or the US customary units.
+ * @param unit
+ * @return
+ */
+ private static String adaptSpatialUnit(String unit)
+ {
+ if ( unit == null )
+ return "micrometer";
+
+ switch ( unit.toLowerCase() ) {
+ case "angstrom":
+ case "ångström":
+ case "ångströms":
+ return "angstrom";
+ case "nm":
+ case "nanometers":
+ case "nanometer":
+ return "nanometer";
+ case "mm":
+ case "millimeters":
+ case "millimeter":
+ return "millimeter";
+ case "m":
+ case "meters":
+ case "meter":
+ return "meter";
+ case "km":
+ case "kilometer":
+ case "kilometers":
+ return "kilometer";
+ case "inch":
+ case "inches":
+ return "inch";
+ case "foot":
+ case "feet":
+ return "foot";
+ case "yard":
+ case "yards":
+ return "yard";
+ case "mile":
+ case "miles":
+ return "mile";
+ case "um":
+ case "μm":
+ case "microns":
+ case "micron":
+ default:
+ return "micrometer";
+ }
+ }
public static void loadOMEZarr( final N5Reader n5, final String dataset )
{
diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/filemap2/VirtualRAIFactoryLOCI.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/filemap2/VirtualRAIFactoryLOCI.java
index e83761e67..7ea88a638 100644
--- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/filemap2/VirtualRAIFactoryLOCI.java
+++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/filemap2/VirtualRAIFactoryLOCI.java
@@ -53,6 +53,7 @@
import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.util.Cast;
+import net.preibisch.mvrecon.fiji.spimdata.imgloaders.util.BioformatsReaderUtils;
class VirtualRAIFactoryLOCI
{
@@ -178,7 +179,7 @@ private static ByteBuffer readIntoBuffer(
setReaderFileAndSeriesIfNecessary( reader, file, series );
// System.out.println( "reading z plane " + z + " from series " + series + " in file " + file.getAbsolutePath() );
- final int planeSize = ( reader.getBitsPerPixel() / 8 ) * reader.getSizeX() * reader.getSizeY();
+ final int planeSize = BioformatsReaderUtils.getPlaneSizeInBytes( reader );
final int size = planeSize * reader.getRGBChannelCount();
final byte[] buffer = new byte[ size ];
diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/util/BioformatsReaderUtils.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/util/BioformatsReaderUtils.java
index 405e5883f..f747cd6ba 100644
--- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/util/BioformatsReaderUtils.java
+++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/util/BioformatsReaderUtils.java
@@ -24,6 +24,8 @@
import java.util.HashMap;
+import ch.epfl.biop.formats.in.ZeissQuickStartCZIReader;
+import loci.formats.FormatTools;
import loci.formats.IFormatReader;
import loci.formats.ImageReader;
import loci.formats.in.ZeissCZIReader;
@@ -33,6 +35,7 @@ public class BioformatsReaderUtils {
static HashMap, BioformatsReaderSetupHook> setupHooks = new HashMap<>();
static {
setupHooks.put(ZeissCZIReader.class, CZIReaderSetupHook.getInstance());
+ setupHooks.put(ZeissQuickStartCZIReader.class, CZIReaderSetupHook.getInstance());
}
public static ImageReader createImageReaderWithSetupHooks()
@@ -46,4 +49,30 @@ public static ImageReader createImageReaderWithSetupHooks()
return reader;
}
+
+ public static int getBytesPerPixel(final IFormatReader reader)
+ {
+ final int bytesPerPixel = FormatTools.getBytesPerPixel(reader.getPixelType());
+
+ if (bytesPerPixel <= 0)
+ {
+ final int bitsPerPixel = reader.getBitsPerPixel();
+
+ if (bitsPerPixel > 0 && bitsPerPixel % 8 == 0)
+ return bitsPerPixel / 8;
+
+ throw new IllegalArgumentException(
+ "Could not determine bytes per pixel for pixelType=" + reader.getPixelType() +
+ ", bitsPerPixel=" + bitsPerPixel);
+ }
+
+ return bytesPerPixel;
+ }
+
+ public static int getPlaneSizeInBytes(final IFormatReader reader)
+ {
+ return Math.multiplyExact(
+ Math.multiplyExact(getBytesPerPixel(reader), reader.getSizeX()),
+ reader.getSizeY());
+ }
}
diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/util/CZIReaderSetupHook.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/util/CZIReaderSetupHook.java
index 4b1121192..2d38e7907 100644
--- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/util/CZIReaderSetupHook.java
+++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/util/CZIReaderSetupHook.java
@@ -22,6 +22,7 @@
*/
package net.preibisch.mvrecon.fiji.spimdata.imgloaders.util;
+import ch.epfl.biop.formats.in.ZeissQuickStartCZIReader;
import loci.formats.IFormatReader;
import loci.formats.in.DynamicMetadataOptions;
import loci.formats.in.MetadataOptions;
@@ -56,22 +57,47 @@ public void setRelativePositions(boolean relativePositions)
@Override
public void runSetup(IFormatReader reader) {
- if (!(reader instanceof ZeissCZIReader)) {
- return;
+ if (reader instanceof ZeissCZIReader) {
+ setupZeissCZIReader(reader);
+ } else if (reader instanceof ZeissQuickStartCZIReader) {
+ setupQuickCZIReader(reader);
}
+ }
+
+ private void setupZeissCZIReader(final IFormatReader reader)
+ {
+ configureCZIReader(
+ reader,
+ ZeissCZIReader.ALLOW_AUTOSTITCHING_KEY,
+ ZeissCZIReader.RELATIVE_POSITIONS_KEY
+ );
+ }
+ private void setupQuickCZIReader(final IFormatReader reader)
+ {
+ configureCZIReader(
+ reader,
+ ZeissQuickStartCZIReader.ALLOW_AUTOSTITCHING_KEY,
+ ZeissQuickStartCZIReader.RELATIVE_POSITIONS_KEY
+ );
+ }
+
+ private void configureCZIReader(
+ final IFormatReader reader,
+ final String autoStitchKey,
+ final String relativePositionsKey)
+ {
// disable auto stitching, following solutions by @CellKai and @NicoKiaru in
// https://forum.image.sc/t/change-in-czi-tile-info-metadata-after-upgrade-to-zeiss-lightsheet-7/49414/15
- MetadataOptions options = reader.getMetadataOptions();
+ final MetadataOptions options = reader.getMetadataOptions();
if (options instanceof DynamicMetadataOptions) {
- ((DynamicMetadataOptions) options).setBoolean(
- ZeissCZIReader.ALLOW_AUTOSTITCHING_KEY, allowAutostitch);
- ((DynamicMetadataOptions) options).setBoolean(
- ZeissCZIReader.RELATIVE_POSITIONS_KEY, relativePositions);
+ final DynamicMetadataOptions dynamicOptions = (DynamicMetadataOptions) options;
+ dynamicOptions.setBoolean(autoStitchKey, allowAutostitch);
+ dynamicOptions.setBoolean(relativePositionsKey, relativePositions);
+ reader.setMetadataOptions(dynamicOptions);
} else {
- System.err.println("WARNING: could not set CZI autostitching option.");
+ System.err.println("WARNING: could not set CZI metadata options for " + reader.getClass().getSimpleName() + ".");
}
- reader.setMetadataOptions(options);
}
}
diff --git a/src/main/java/net/preibisch/mvrecon/process/export/ExportN5Api.java b/src/main/java/net/preibisch/mvrecon/process/export/ExportN5Api.java
index 251656495..c68d86a68 100644
--- a/src/main/java/net/preibisch/mvrecon/process/export/ExportN5Api.java
+++ b/src/main/java/net/preibisch/mvrecon/process/export/ExportN5Api.java
@@ -47,7 +47,7 @@
import org.janelia.saalfeldlab.n5.hdf5.N5HDF5Writer;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
import org.janelia.saalfeldlab.n5.universe.StorageFormat;
-import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMultiScaleMetadata;
+import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMetadata;
import bdv.export.ExportMipmapInfo;
import bdv.export.ProposeMipmaps;
@@ -256,24 +256,42 @@ else if ( storageType == StorageFormat.N5 || storageType == StorageFormat.ZARR |
final Function levelToMipmapTransform =
(level) -> MipmapTransforms.getMipmapTransformDefault( mrInfoZarr[level].absoluteDownsamplingDouble() );
- IOFunctions.println( "Resolution of level 0: " + Util.printCoordinates( cal ) + " " + unit ); //vx.unit() might not be OME-ZARR compatible
+ // at this point the pixel size has already been set
+ // - here we adjust the S0 resolution based on selected downsampling and anisotropy before exporting to N5
+ double[] resolutionS0 = OMEZarrAttributes.getResolutionS0( cal, anisoF, downsamplingF );
- // create metadata
- final OmeNgffMultiScaleMetadata[] meta = OMEZarrAttributes.createOMEZarrMetadata(
- 5, // int n
- "/", // String name, I also saw "/"
- cal, // double[] resolutionS0,
- unit, //"micrometer", //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer
- mrInfoZarr.length, // int numResolutionLevels,
- levelToName,
- levelToMipmapTransform );
+ IOFunctions.println( "Calibration: " + Util.printCoordinates( cal ) + " micrometer; resolution at S0: " + Util.printCoordinates( resolutionS0 ) + " " + unit);
- // save metadata
-
- //org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMetadata
- // for this to work you need to register an adapter in the N5Factory class
- // final GsonBuilder builder = new GsonBuilder().registerTypeAdapter( CoordinateTransformation.class, new CoordinateTransformationAdapter() );
- driverVolumeWriter.setAttribute( "/", "multiscales", meta );
+ // create metadata
+ if (storageType == StorageFormat.ZARR2) {
+ final OmeNgffMetadata meta = OMEZarrAttributes.createOMEZarrMetadata(
+ 5, // int n
+ "/", // String name, I also saw "/"
+ "0.4",
+ resolutionS0, // double[] resolutionS0,
+ unit, //"micrometer", //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer
+ mrInfoZarr.length, // int numResolutionLevels,
+ levelToName,
+ levelToMipmapTransform );
+
+ // save metadata
+ driverVolumeWriter.setAttribute( "/", "multiscales", meta.multiscales );
+ } else {
+ // ZARR3
+ final OmeNgffMetadata meta = OMEZarrAttributes.createOMEZarrMetadata(
+ 5, // int n
+ "", // String name, I also saw "/"
+ "0.5",
+ resolutionS0, // double[] resolutionS0,
+ unit, //"micrometer", //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer
+ mrInfoZarr.length, // int numResolutionLevels,
+ levelToName,
+ levelToMipmapTransform );
+ // save metadata
+ driverVolumeWriter.setAttribute( "/", "ome", meta );
+ // this is hacky until OmeNgffMetadata gets fixed to output version
+ driverVolumeWriter.setAttribute( "/", "ome/version", meta.version );
+ }
}
}
else
@@ -375,24 +393,28 @@ else if ( storageType == StorageFormat.ZARR || storageType == StorageFormat.ZARR
final Function levelToMipmapTransform =
(level) -> MipmapTransforms.getMipmapTransformDefault( mrInfo[level].absoluteDownsamplingDouble() );
- IOFunctions.println( "Resolution of level 0: " + Util.printCoordinates( cal ) + " micrometer" );
+ double[] resolutionS0 = OMEZarrAttributes.getResolutionS0( cal, anisoF, downsamplingF );
+
+ IOFunctions.println( "Calibration: " + Util.printCoordinates( cal ) + " micrometer; resolution at S0: " + Util.printCoordinates( resolutionS0 ) + " " + unit);
// create metadata
- final OmeNgffMultiScaleMetadata[] meta = OMEZarrAttributes.createOMEZarrMetadata(
+ final OmeNgffMetadata meta = OMEZarrAttributes.createOMEZarrMetadata(
3, // int n
omeZarrSubContainer, // String name, I also saw "/"
- cal, // double[] resolutionS0,
- unit, //"micrometer", //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer
+ storageType == StorageFormat.ZARR2 ? "0.4" : "0.5",
+ resolutionS0, // double[] resolutionS0,
+ unit, // might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer
mrInfo.length, // int numResolutionLevels,
(level) -> "/" + level,
levelToMipmapTransform );
// save metadata
-
- //org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMetadata
- // for this to work you need to register an adapter in the N5Factory class
- // final GsonBuilder builder = new GsonBuilder().registerTypeAdapter( CoordinateTransformation.class, new CoordinateTransformationAdapter() );
- driverVolumeWriter.setAttribute( omeZarrSubContainer, "multiscales", meta );
+ if (storageType == StorageFormat.ZARR2)
+ driverVolumeWriter.setAttribute( omeZarrSubContainer, "multiscales", meta.multiscales );
+ else {
+ driverVolumeWriter.setAttribute( omeZarrSubContainer, "ome", meta );
+ driverVolumeWriter.setAttribute( omeZarrSubContainer, "ome/version", meta.version );
+ }
omeZarrEntry = new OMEZARREntry(
mrInfo[ 0 ].dataset.substring(0, mrInfo[ 0 ].dataset.lastIndexOf( "/" ) ),
diff --git a/src/main/java/net/preibisch/mvrecon/process/interestpointregistration/TransformationTools.java b/src/main/java/net/preibisch/mvrecon/process/interestpointregistration/TransformationTools.java
index 2543905b8..ab1c91ee8 100644
--- a/src/main/java/net/preibisch/mvrecon/process/interestpointregistration/TransformationTools.java
+++ b/src/main/java/net/preibisch/mvrecon/process/interestpointregistration/TransformationTools.java
@@ -334,13 +334,13 @@ public static Pair< double[], String > computeAverageCalibration(
else if ( unit.equalsIgnoreCase( transformedCal.getB() ) )
unit = transformedCal.getB();
else
- unit = "inconsisistent";
+ unit = "inconsistent";
System.out.println( "Calibration (transformed): " + Util.printCoordinates( transformedCal.getA() ) + " " + transformedCal.getB() );
}
if ( count == 0 )
- return new ValuePair<>( new double[] { 1, 1, 1 }, "px" );
+ return new ValuePair<>( new double[] { 1, 1, 1 }, "micrometer" );
else
return new ValuePair<>( new double[] { avgCalX / (double)count, avgCalY / (double)count, avgCalZ / (double)count }, unit );
}
diff --git a/src/main/java/net/preibisch/mvrecon/process/n5api/N5ApiTools.java b/src/main/java/net/preibisch/mvrecon/process/n5api/N5ApiTools.java
index 0aa5db7f8..e16e05da5 100644
--- a/src/main/java/net/preibisch/mvrecon/process/n5api/N5ApiTools.java
+++ b/src/main/java/net/preibisch/mvrecon/process/n5api/N5ApiTools.java
@@ -43,10 +43,10 @@
import org.janelia.saalfeldlab.n5.DatasetAttributes;
import org.janelia.saalfeldlab.n5.N5Writer;
import org.janelia.saalfeldlab.n5.RawCompression;
+import org.janelia.saalfeldlab.n5.codec.checksum.Crc32cChecksumCodec;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
import org.janelia.saalfeldlab.n5.universe.StorageFormat;
-import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMultiScaleMetadata;
-import org.janelia.saalfeldlab.n5.zarr.v3.ZarrV3Compressor;
+import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMetadata;
import org.janelia.saalfeldlab.n5.zarr.v3.ZarrV3DatasetAttributes;
import bdv.export.ExportMipmapInfo;
@@ -293,16 +293,13 @@ public static MultiResolutionLevelInfo[] setupMultiResolutionPyramid(
if ( useSharding )
{
- // Use Zarr v3 sharding via ZarrV3DatasetAttributes constructor
- // Convert Compression to DataCodecInfo (only Blosc and Zstandard officially supported)
- final ZarrV3Compressor codec = ZarrV3Compressor.fromCompression( compression );
- final DatasetAttributes attributes = new ZarrV3DatasetAttributes(
- dimensionsS0, // shape
- shardSize, // shard dimensions
- blockSize, // inner chunk size within shards
- dataType,
- codec ); // compression codec (can be null for no compression)
-
+ // Use Zarr v3 sharding via ZarrV3DatasetAttributes builder
+ final DatasetAttributes attributes = ZarrV3DatasetAttributes.builder(dimensionsS0, dataType)
+ .blockSize(shardSize) // shard dimensions
+ .chunkSize(blockSize) // inner chunk size within shards
+ .compression(compression)
+ .shardIndexDataCodecInfos(new Crc32cChecksumCodec())
+ .build();
driverVolumeWriter.createDataset( viewIdToDataset.apply( viewId, 0 ), attributes );
}
else
@@ -334,14 +331,13 @@ public static MultiResolutionLevelInfo[] setupMultiResolutionPyramid(
if ( useSharding )
{
- final ZarrV3Compressor codec = ZarrV3Compressor.fromCompression( compression );
- final DatasetAttributes attributes = new ZarrV3DatasetAttributes(
- dim, // shape
- shardSize, // shard dimensions
- blockSize, // inner chunk size within shards
- dataType,
- codec ); // compression codec
-
+ // Use Zarr v3 sharding via ZarrV3DatasetAttributes builder
+ final DatasetAttributes attributes = ZarrV3DatasetAttributes.builder(dim, dataType)
+ .blockSize(shardSize) // shard dimensions
+ .chunkSize(blockSize) // inner chunk size within shards
+ .compression(compression)
+ .shardIndexDataCodecInfos(new Crc32cChecksumCodec())
+ .build();
driverVolumeWriter.createDataset( datasetLevel, attributes );
}
else
@@ -456,7 +452,8 @@ public static MultiResolutionLevelInfo[] setupBdvDatasetsOMEZARR_ResaveRaw(
final ViewId viewId,
final DataType dataType,
final long[] dimensions,
- //final double[] resolutionS0, // TODO: this is a hack (uses 1,1,1) so the export downsampling pyramid is working
+ final double[] resolutionS0,
+ final String resolutionUnit,
final Compression compression,
final int[] blockSize,
int[][] downsamplings,
@@ -497,16 +494,13 @@ public static MultiResolutionLevelInfo[] setupBdvDatasetsOMEZARR_ResaveRaw(
final Function levelToMipmapTransform =
(level) -> MipmapTransforms.getMipmapTransformDefault( mrInfo[level].absoluteDownsamplingDouble() );
- // extract the resolution of the s0 export
- //final VoxelDimensions vx = fusionGroup.iterator().next().getViewSetup().getVoxelSize();
- //final double[] resolutionS0 = OMEZarrAttributes.getResolutionS0( vx, anisoF, downsamplingF );
-
// create metadata
- final OmeNgffMultiScaleMetadata[] meta = OMEZarrAttributes.createOMEZarrMetadata(
+ final OmeNgffMetadata meta = OMEZarrAttributes.createOMEZarrMetadata(
5, // int n
"/", // String name, I also saw "/"
- new double[] { 1, 1, 1 }, //resolutionS0, // double[] resolutionS0,
- "micrometer", //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer
+ "0.5",
+ resolutionS0, // double[] resolutionS0,
+ resolutionUnit, //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer
mrInfo.length, // int numResolutionLevels,
levelToName,
levelToMipmapTransform );
@@ -516,7 +510,8 @@ public static MultiResolutionLevelInfo[] setupBdvDatasetsOMEZARR_ResaveRaw(
//org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMetadata
// for this to work you need to register an adapter in the N5Factory class
// final GsonBuilder builder = new GsonBuilder().registerTypeAdapter( CoordinateTransformation.class, new CoordinateTransformationAdapter() );
- driverVolumeWriter.setAttribute( baseDataset, "multiscales", meta );
+ driverVolumeWriter.setAttribute( baseDataset, "ome", meta );
+ driverVolumeWriter.setAttribute( baseDataset, "ome/version", meta.version );
return mrInfo;
}
@@ -619,8 +614,9 @@ public static < T extends NativeType< T > & RealType< T > > void writeDownsample
// For sharded datasets, we must write complete shards including empty blocks
// because sparse shard reading is not fully supported yet
- //N5Utils.saveNonEmptyBlock(sourceGridBlock, n5, dataset, gridBlock[2], previousScale.getType().createVariable() );
- N5Utils.saveBlock(sourceGridBlock, n5, dataset, gridBlock[2]);
+ // using saveNonEmptyBlock again - need to test if this is supported with sharding
+ N5Utils.saveNonEmptyBlock(sourceGridBlock, n5, dataset, gridBlock[2], previousScale.getType().createVariable() );
+// N5Utils.saveBlock(sourceGridBlock, n5, dataset, gridBlock[2]);
}
public static < T extends NativeType< T > & RealType< T > > void writeDownsampledBlock5dOMEZARR(
@@ -661,15 +657,17 @@ public static < T extends NativeType< T > & RealType< T > > void writeDownsample
final long[] dimensionsRaw = attrs.getDimensions();
final long[] dimensions = new long[] { dimensionsRaw[ 0 ], dimensionsRaw[ 1 ], dimensionsRaw[ 2 ] };
- final RandomAccessibleInterval< T > downsampled3d = BlockAlgoUtils.cellImg( blocks, dimensions, new int[] { 64 } );
+ final int[] cellSize = new int[] { mrInfo.blockSize[0], mrInfo.blockSize[1], mrInfo.blockSize[2] };
+ final RandomAccessibleInterval downsampled3d = BlockAlgoUtils.cellImg( blocks, dimensions, cellSize );
// the same information is returned no matter which index is queried in C and T
final RandomAccessible< T > downsampled5d = Views.addDimension( Views.addDimension( downsampled3d ) );
final RandomAccessibleInterval sourceGridBlock = Views.offsetInterval(downsampled5d, blockOffset, blockSize);
- //N5Utils.saveNonEmptyBlock(sourceGridBlock, n5, dataset, gridOffset, previousScale.getType().createVariable());
- N5Utils.saveBlock(sourceGridBlock, n5, dataset, gridOffset );
+ // using saveNonEmptyBlock again - need to test if this is supported with sharding
+ N5Utils.saveNonEmptyBlock( sourceGridBlock, n5, dataset, gridOffset, downsampled5d.getType().createVariable() );
+// N5Utils.saveBlock(sourceGridBlock, n5, dataset, gridOffset );
}
public static List assembleJobs( final MultiResolutionLevelInfo mrInfo )
@@ -686,7 +684,8 @@ public static List assembleJobs(
final ViewId viewId,
final MultiResolutionLevelInfo mrInfo )
{
- return assembleJobs( viewId, mrInfo.dimensions, mrInfo.blockSize, mrInfo.blockSize );
+ return assembleJobs( viewId, mrInfo.dimensions, mrInfo.blockSize,
+ mrInfo.shardSize != null ? mrInfo.shardSize : mrInfo.blockSize );
}
public static List assembleJobs(
@@ -809,8 +808,9 @@ public static > void resaveS0Block(
}
final RandomAccessibleInterval< T > sourceGridBlock = Views.offsetInterval( image, blockOffset, blockSize );
- //N5Utils.saveNonEmptyBlock( sourceGridBlock, n5, dataset, gridOffset, image.getType().createVariable() );
- N5Utils.saveBlock( sourceGridBlock, n5, dataset, gridOffset );
+ // using saveNonEmptyBlock again - need to test if this is supported with sharding
+ N5Utils.saveNonEmptyBlock( sourceGridBlock, n5, dataset, gridOffset, image.getType().createVariable() );
+// N5Utils.saveBlock( sourceGridBlock, n5, dataset, gridOffset );
System.out.println( "ViewId " + Group.pvid( viewId ) + ", written block: offset=" + Util.printCoordinates( blockOffset ) + ", dimension=" + Util.printCoordinates( blockSize ) );
}
diff --git a/src/main/java/util/URITools.java b/src/main/java/util/URITools.java
index 2ab710626..24b927ae4 100644
--- a/src/main/java/util/URITools.java
+++ b/src/main/java/util/URITools.java
@@ -36,10 +36,10 @@
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
-import java.nio.file.FileSystems;
import java.util.Date;
import java.util.regex.Pattern;
+import com.google.gson.JsonObject;
import org.janelia.saalfeldlab.googlecloud.GoogleCloudUtils;
import org.janelia.saalfeldlab.n5.FileSystemKeyValueAccess;
import org.janelia.saalfeldlab.n5.GsonKeyValueN5Reader;
@@ -51,7 +51,6 @@
import org.janelia.saalfeldlab.n5.N5Writer;
import org.janelia.saalfeldlab.n5.hdf5.N5HDF5Reader;
import org.janelia.saalfeldlab.n5.hdf5.N5HDF5Writer;
-import org.janelia.saalfeldlab.n5.s3.AmazonS3Utils;
import org.janelia.saalfeldlab.n5.universe.N5Factory;
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
import software.amazon.awssdk.regions.Region;
diff --git a/src/test/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/ExampleOMEZarrXMLGeneration.java b/src/test/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/ExampleOMEZarrXMLGeneration.java
index 7b04fd473..b5b30b0f9 100644
--- a/src/test/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/ExampleOMEZarrXMLGeneration.java
+++ b/src/test/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/ExampleOMEZarrXMLGeneration.java
@@ -37,6 +37,7 @@
import org.janelia.saalfeldlab.n5.N5Writer;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
import org.janelia.saalfeldlab.n5.universe.StorageFormat;
+import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMetadata;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMultiScaleMetadata;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.coordinateTransformations.ScaleCoordinateTransformation;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.coordinateTransformations.TranslationCoordinateTransformation;
@@ -190,19 +191,19 @@ private static void createZarr( N5Writer writer, String datasetBaseName, int siz
// Add OME-ZARR multiscales metadata to the container root
if ( datasetBaseName.isEmpty() )
{
- org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.OmeNgffMultiScaleMetadata[] metadata =
- OMEZarrAttributes.createOMEZarrMetadata(
+ OmeNgffMetadata metadata = OMEZarrAttributes.createOMEZarrMetadata(
is3D ? 3 : 4,
"/",
- is3D ? new double[] { voxelSizeX, voxelSizeY, voxelSizeZ } :
- new double[] { voxelSizeX, voxelSizeY, voxelSizeZ, 1.0 },
+ "0.4",
+ is3D ? new double[]{voxelSizeX, voxelSizeY, voxelSizeZ} :
+ new double[]{voxelSizeX, voxelSizeY, voxelSizeZ, 1.0},
"um",
1, // single resolution level
- level -> String.valueOf( level ),
+ String::valueOf,
level -> new AffineTransform3D()
- );
+ );
- writer.setAttribute( "/", "multiscales", metadata );
+ writer.setAttribute( "/", "multiscales", metadata.multiscales );
}
System.out.println( " ✓ Created " + (is3D ? "3D" : "4D") + " with dimensions: " + java.util.Arrays.toString( dimensions ) );