From 094eed771a0e81deb3cfef03a8c55cfe4a2b1858 Mon Sep 17 00:00:00 2001 From: Cristian Goina Date: Tue, 28 Oct 2025 14:53:44 -0400 Subject: [PATCH 1/6] attempt to fix the scale values for OME-ZARR; the fix was to scale translation based on the voxel resolution --- .../fiji/plugin/resave/Resave_N5Api.java | 2 +- .../spimdata/imgloaders/OMEZarrAttibutes.java | 23 ++++--------------- .../mvrecon/process/export/ExportN5Api.java | 4 ++-- .../mvrecon/process/n5api/N5ApiTools.java | 14 ++--------- 4 files changed, 10 insertions(+), 33 deletions(-) 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 8fbf0aafd..3ae54dc25 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 @@ -194,7 +194,7 @@ else if ( n5Params.format == StorageFormat.HDF5 ) viewId, dataTypes.get( viewId.getViewSetupId() ), dimensions.get( viewId.getViewSetupId() ), - //data.getSequenceDescription().getViewDescription( viewId ).getViewSetup().getVoxelSize().dimensionsAsDoubleArray(), + data.getSequenceDescription().getViewDescription( viewId ).getViewSetup().getVoxelSize().dimensionsAsDoubleArray(), // resolutionS0 compression, blockSize, downsamplings); diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttibutes.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttibutes.java index 0739a00db..f1726d4b0 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttibutes.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttibutes.java @@ -99,8 +99,8 @@ public static OmeNgffMultiScaleMetadata[] createOMEZarrMetadata( for ( int d = 0; d < 3; ++d ) { - translation[ d ] = m.getTranslation()[ d ]; - scale[ d ] = resolutionS0[ d ] * m.get( d, d ); + translation[ d ] = resolutionS0[d] * m.getTranslation()[ d ]; + scale[ d ] = resolutionS0[d] * m.get( d, d ); } // if 4d and 5d, add 1's for C and T @@ -130,23 +130,10 @@ public static OmeNgffMultiScaleMetadata[] createOMEZarrMetadata( return meta; } - // TODO: this is inaccurate, we should actually estimate it from the final transformn that is applied - public static double[] getResolutionS0( final VoxelDimensions vx, final double anisoF, final double downsamplingF ) + // The method assumes that the voxel dimensions are at scale S0, so it simply returns the dimensions as double array. + public static double[] getResolutionS0( final VoxelDimensions vx ) { - final double[] resolutionS0 = vx.dimensionsAsDoubleArray(); - - // not preserving anisotropy - if ( Double.isNaN( anisoF ) ) - resolutionS0[ 2 ] = resolutionS0[ 0 ]; - - // 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; + return vx.dimensionsAsDoubleArray(); } public static void loadOMEZarr( final N5Reader n5, final String dataset ) 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 c3942b099..59d0415d0 100644 --- a/src/main/java/net/preibisch/mvrecon/process/export/ExportN5Api.java +++ b/src/main/java/net/preibisch/mvrecon/process/export/ExportN5Api.java @@ -245,7 +245,7 @@ else if ( storageType == StorageFormat.N5 || storageType == StorageFormat.ZARR ) // TODO: this is inaccurate, we should actually estimate it from the final transformn that is applied // TODO: this is a hack (returns 1,1,1) so the export downsampling pyramid is working final VoxelDimensions vx = fusionGroup.iterator().next().getViewSetup().getVoxelSize(); - final double[] resolutionS0 = OMEZarrAttibutes.getResolutionS0( vx, anisoF, downsamplingF ); + final double[] resolutionS0 = OMEZarrAttibutes.getResolutionS0( vx ); IOFunctions.println( "Resolution of level 0: " + Util.printCoordinates( resolutionS0 ) + " " + "m" ); //vx.unit() might not be OME-ZARR compatiblevx.unit() ); @@ -368,7 +368,7 @@ else if ( storageType == StorageFormat.ZARR ) // OME-Zarr export // TODO: this is inaccurate, we should actually estimate it from the final transformn that is applied // TODO: this is a hack (returns 1,1,1) so the export downsampling pyramid is working final VoxelDimensions vx = fusionGroup.iterator().next().getViewSetup().getVoxelSize(); - final double[] resolutionS0 = OMEZarrAttibutes.getResolutionS0( vx, anisoF, downsamplingF ); + final double[] resolutionS0 = OMEZarrAttibutes.getResolutionS0( vx ); IOFunctions.println( "Resolution of level 0: " + Util.printCoordinates( resolutionS0 ) + " micrometer" ); 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 271051475..50976b507 100644 --- a/src/main/java/net/preibisch/mvrecon/process/n5api/N5ApiTools.java +++ b/src/main/java/net/preibisch/mvrecon/process/n5api/N5ApiTools.java @@ -33,7 +33,6 @@ import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.function.BiFunction; @@ -42,7 +41,6 @@ import org.janelia.saalfeldlab.n5.Compression; import org.janelia.saalfeldlab.n5.DataType; import org.janelia.saalfeldlab.n5.DatasetAttributes; -import org.janelia.saalfeldlab.n5.GzipCompression; import org.janelia.saalfeldlab.n5.N5Writer; import org.janelia.saalfeldlab.n5.RawCompression; import org.janelia.saalfeldlab.n5.imglib2.N5Utils; @@ -382,7 +380,7 @@ public static MultiResolutionLevelInfo[] setupBdvDatasetsOMEZARR( 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 Compression compression, final int[] blockSize, int[][] downsamplings ) @@ -414,25 +412,17 @@ public static MultiResolutionLevelInfo[] setupBdvDatasetsOMEZARR( 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 = OMEZarrAttibutes.getResolutionS0( vx, anisoF, downsamplingF ); - // create metadata final OmeNgffMultiScaleMetadata[] meta = OMEZarrAttibutes.createOMEZarrMetadata( 5, // int n "/", // String name, I also saw "/" - new double[] { 1, 1, 1 }, //resolutionS0, // double[] resolutionS0, + resolutionS0, // double[] resolutionS0, "micrometer", //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer mrInfo.length, // int numResolutionLevels, levelToName, levelToMipmapTransform ); // save metadata - - //org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.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 ); return mrInfo; From 4cc633d89557fc97cade55311090abd71a140a9e Mon Sep 17 00:00:00 2001 From: Cristian Goina Date: Wed, 29 Oct 2025 14:10:05 -0400 Subject: [PATCH 2/6] preserve user entered voxel units as well --- .../preibisch/mvrecon/fiji/plugin/resave/Resave_N5Api.java | 5 ++++- .../net/preibisch/mvrecon/process/export/ExportN5Api.java | 4 ++-- .../java/net/preibisch/mvrecon/process/n5api/N5ApiTools.java | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) 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 3ae54dc25..92b1bd3f5 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; @@ -188,13 +189,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( n5Writer, viewId, dataTypes.get( viewId.getViewSetupId() ), dimensions.get( viewId.getViewSetupId() ), - data.getSequenceDescription().getViewDescription( viewId ).getViewSetup().getVoxelSize().dimensionsAsDoubleArray(), // resolutionS0 + vx.dimensionsAsDoubleArray(), // resolutionS0 + vx.unit(), compression, blockSize, downsamplings); 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 59d0415d0..a72a22545 100644 --- a/src/main/java/net/preibisch/mvrecon/process/export/ExportN5Api.java +++ b/src/main/java/net/preibisch/mvrecon/process/export/ExportN5Api.java @@ -254,7 +254,7 @@ else if ( storageType == StorageFormat.N5 || storageType == StorageFormat.ZARR ) 5, // int n "/", // String name, I also saw "/" resolutionS0, // double[] resolutionS0, - "micrometer", //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer + vx.unit(), //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer mrInfoZarr.length, // int numResolutionLevels, levelToName, levelToMipmapTransform ); @@ -377,7 +377,7 @@ else if ( storageType == StorageFormat.ZARR ) // OME-Zarr export 3, // int n omeZarrSubContainer, // String name, I also saw "/" resolutionS0, // double[] resolutionS0, - "micrometer", //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer + vx.unit(), //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer mrInfo.length, // int numResolutionLevels, (level) -> "/" + level, levelToMipmapTransform ); 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 50976b507..acec6e528 100644 --- a/src/main/java/net/preibisch/mvrecon/process/n5api/N5ApiTools.java +++ b/src/main/java/net/preibisch/mvrecon/process/n5api/N5ApiTools.java @@ -381,6 +381,7 @@ public static MultiResolutionLevelInfo[] setupBdvDatasetsOMEZARR( final DataType dataType, final long[] dimensions, final double[] resolutionS0, + final String resolutionUnit, final Compression compression, final int[] blockSize, int[][] downsamplings ) @@ -417,7 +418,7 @@ public static MultiResolutionLevelInfo[] setupBdvDatasetsOMEZARR( 5, // int n "/", // String name, I also saw "/" resolutionS0, // double[] resolutionS0, - "micrometer", //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer + resolutionUnit, //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer mrInfo.length, // int numResolutionLevels, levelToName, levelToMipmapTransform ); From 15053f1c702f8658fc9929c124ac55d7458bc881 Mon Sep 17 00:00:00 2001 From: Cristian Goina Date: Thu, 30 Oct 2025 14:41:06 -0400 Subject: [PATCH 3/6] Compute resolution at S0 using calibration values, downsampling and anisotropy --- .../spimdata/imgloaders/OMEZarrAttibutes.java | 82 +++++++++++++++++-- .../mvrecon/process/export/ExportN5Api.java | 21 +++-- .../TransformationTools.java | 4 +- 3 files changed, 91 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttibutes.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttibutes.java index f57743c89..5ec555bce 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttibutes.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttibutes.java @@ -61,12 +61,6 @@ public static OmeNgffMultiScaleMetadata[] createOMEZarrMetadata( 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 @@ -85,9 +79,10 @@ public static OmeNgffMultiScaleMetadata[] createOMEZarrMetadata( 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 ); + String unit = adaptSpaceUnit( unitXYZ ); + axes[ index ] = new Axis( "space", "z", unit ); + axes[ index + 1 ] = new Axis( "space", "y", unit ); + axes[ index + 2 ] = new Axis( "space", "x", unit ); // multiresolution-pyramid // TODO: seem to be in XYZCT order (but in the file it seems reversed) @@ -137,6 +132,75 @@ public static OmeNgffMultiScaleMetadata[] createOMEZarrMetadata( return meta; } + public static double[] getResolutionS0( final double[] cal, final double anisoF, final double downsamplingF ) + { + double[] resolutionS0 = Arrays.copyOf( cal, cal.length ); + + if ( !Double.isNaN( anisoF ) ) { + // preserving anisotropy + resolutionS0[2] = cal[2] * anisoF; + } + + // downsampling + if ( !Double.isNaN( downsamplingF ) ) + Arrays.setAll( resolutionS0, d -> resolutionS0[ d ] * downsamplingF ); + + 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 a few the ones that end in meter or the US customary units. + * @param unit + * @return + */ + private static String adaptSpaceUnit(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 ) { //org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.OmeNgffMetadata 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 d24a7db56..29ab699d5 100644 --- a/src/main/java/net/preibisch/mvrecon/process/export/ExportN5Api.java +++ b/src/main/java/net/preibisch/mvrecon/process/export/ExportN5Api.java @@ -245,13 +245,19 @@ 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 + double[] resolutionS0 = OMEZarrAttibutes.getResolutionS0( cal, anisoF, downsamplingF ); + + IOFunctions.println( "!!!!!!!ANISOTROPY: " + anisoF + " DOWNSAMPLING: " + downsamplingF ); + + IOFunctions.println( "Calibration: " + Util.printCoordinates( cal ) + " micrometer; resolution at S0: " + Util.printCoordinates( resolutionS0 )); + + System.out.println("!!!!!!!!!!!!!!!!!!! CALIBRATION FOR OME-ZARR: " + Util.printCoordinates( cal ) + " " + Util.printCoordinates(resolutionS0) + " " + unit ); // create metadata final OmeNgffMultiScaleMetadata[] meta = OMEZarrAttibutes.createOMEZarrMetadata( 5, // int n "/", // String name, I also saw "/" - cal, // double[] resolutionS0, + resolutionS0, // double[] resolutionS0, unit, //"micrometer", //vx.unit() might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer mrInfoZarr.length, // int numResolutionLevels, levelToName, @@ -362,14 +368,19 @@ else if ( storageType == StorageFormat.ZARR ) // OME-Zarr export final Function levelToMipmapTransform = (level) -> MipmapTransforms.getMipmapTransformDefault( mrInfo[level].absoluteDownsamplingDouble() ); - IOFunctions.println( "Resolution of level 0: " + Util.printCoordinates( cal ) + " micrometer" ); + double[] resolutionS0 = OMEZarrAttibutes.getResolutionS0( cal, anisoF, downsamplingF ); + + IOFunctions.println( "!!!!!!!ANISOTROPY: " + anisoF + " DOWNSAMPLING: " + downsamplingF ); + + IOFunctions.println( "!!!!!!!Calibration: " + Util.printCoordinates( cal ) + " micrometer; resolution at S0: " + Util.printCoordinates( resolutionS0 )); + System.out.println("!!!!!!!!!!!!!!!!!!! CALIBRATION FOR OME-ZARR: " + Util.printCoordinates( cal ) + " " + Util.printCoordinates(resolutionS0) + " " + unit ); // create metadata final OmeNgffMultiScaleMetadata[] meta = OMEZarrAttibutes.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 + resolutionS0, // double[] resolutionS0, + unit, // might not be OME-ZARR compatible // String unitXYZ, // e.g micrometer mrInfo.length, // int numResolutionLevels, (level) -> "/" + level, levelToMipmapTransform ); 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 58a50899b..967a153ac 100644 --- a/src/main/java/net/preibisch/mvrecon/process/interestpointregistration/TransformationTools.java +++ b/src/main/java/net/preibisch/mvrecon/process/interestpointregistration/TransformationTools.java @@ -318,13 +318,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 ); } From 6c10ce0e50909655fe07fc9ba4f8d002f8333dfd Mon Sep 17 00:00:00 2001 From: Cristian Goina Date: Thu, 30 Oct 2025 22:25:34 -0400 Subject: [PATCH 4/6] removed debug print statements --- .../spimdata/imgloaders/OMEZarrAttibutes.java | 8 +++----- .../mvrecon/process/export/ExportN5Api.java | 15 ++------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttibutes.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttibutes.java index 5ec555bce..1d274bce3 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttibutes.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/OMEZarrAttibutes.java @@ -39,9 +39,7 @@ import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.coordinateTransformations.ScaleCoordinateTransformation; import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.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 OMEZarrAttibutes @@ -79,7 +77,7 @@ public static OmeNgffMultiScaleMetadata[] createOMEZarrMetadata( if ( n >= 4 ) axes[ index++ ] = new Axis( "channel", "c", null ); - String unit = adaptSpaceUnit( unitXYZ ); + String unit = adaptSpatialUnit( unitXYZ ); axes[ index ] = new Axis( "space", "z", unit ); axes[ index + 1 ] = new Axis( "space", "y", unit ); axes[ index + 2 ] = new Axis( "space", "x", unit ); @@ -150,11 +148,11 @@ public static double[] getResolutionS0( final double[] cal, final double anisoF, /** * 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 a few the ones that end in meter or the US customary units. + * 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 adaptSpaceUnit(String unit) + private static String adaptSpatialUnit(String unit) { if ( unit == null ) return "micrometer"; 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 29ab699d5..bcc45e0cf 100644 --- a/src/main/java/net/preibisch/mvrecon/process/export/ExportN5Api.java +++ b/src/main/java/net/preibisch/mvrecon/process/export/ExportN5Api.java @@ -247,11 +247,7 @@ else if ( storageType == StorageFormat.N5 || storageType == StorageFormat.ZARR ) double[] resolutionS0 = OMEZarrAttibutes.getResolutionS0( cal, anisoF, downsamplingF ); - IOFunctions.println( "!!!!!!!ANISOTROPY: " + anisoF + " DOWNSAMPLING: " + downsamplingF ); - - IOFunctions.println( "Calibration: " + Util.printCoordinates( cal ) + " micrometer; resolution at S0: " + Util.printCoordinates( resolutionS0 )); - - System.out.println("!!!!!!!!!!!!!!!!!!! CALIBRATION FOR OME-ZARR: " + Util.printCoordinates( cal ) + " " + Util.printCoordinates(resolutionS0) + " " + unit ); + IOFunctions.println( "Calibration: " + Util.printCoordinates( cal ) + " micrometer; resolution at S0: " + Util.printCoordinates( resolutionS0 ) + " " + unit); // create metadata final OmeNgffMultiScaleMetadata[] meta = OMEZarrAttibutes.createOMEZarrMetadata( @@ -264,10 +260,6 @@ else if ( storageType == StorageFormat.N5 || storageType == StorageFormat.ZARR ) levelToMipmapTransform ); // save metadata - - //org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.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 ); } } @@ -370,11 +362,8 @@ else if ( storageType == StorageFormat.ZARR ) // OME-Zarr export double[] resolutionS0 = OMEZarrAttibutes.getResolutionS0( cal, anisoF, downsamplingF ); - IOFunctions.println( "!!!!!!!ANISOTROPY: " + anisoF + " DOWNSAMPLING: " + downsamplingF ); - - IOFunctions.println( "!!!!!!!Calibration: " + Util.printCoordinates( cal ) + " micrometer; resolution at S0: " + Util.printCoordinates( resolutionS0 )); + IOFunctions.println( "Calibration: " + Util.printCoordinates( cal ) + " micrometer; resolution at S0: " + Util.printCoordinates( resolutionS0 ) + " " + unit); - System.out.println("!!!!!!!!!!!!!!!!!!! CALIBRATION FOR OME-ZARR: " + Util.printCoordinates( cal ) + " " + Util.printCoordinates(resolutionS0) + " " + unit ); // create metadata final OmeNgffMultiScaleMetadata[] meta = OMEZarrAttibutes.createOMEZarrMetadata( 3, // int n From 538df33bced7e0a4681cebab7a42d8269ce17157 Mon Sep 17 00:00:00 2001 From: Cristian Goina Date: Tue, 13 Jan 2026 16:22:21 -0500 Subject: [PATCH 5/6] fixed a NPE --- .../net/preibisch/mvrecon/fiji/datasetmanager/OMEZARR.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/datasetmanager/OMEZARR.java b/src/main/java/net/preibisch/mvrecon/fiji/datasetmanager/OMEZARR.java index 9927a0117..e7e6b99a1 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/datasetmanager/OMEZARR.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/datasetmanager/OMEZARR.java @@ -262,6 +262,9 @@ public SpimData2 createDataset( final String xmlFileName ) final DatasetAttributes attr = reader.getDatasetAttributes( dataset + "/" + path ); + if (attr == null) { + continue; + } IOFunctions.println( "NumDimensions: " + attr.getNumDimensions() ); IOFunctions.println( "Dimensions: " + Arrays.toString( attr.getDimensions() ) ); IOFunctions.println( "BlockSize: " + Arrays.toString( attr.getBlockSize() ) ); From 53be075ddeb520b2c1474d4cab79c97bcc2104a6 Mon Sep 17 00:00:00 2001 From: Cristian Goina Date: Tue, 13 Jan 2026 18:47:03 -0500 Subject: [PATCH 6/6] use dataset paths from the metadata if available --- .../imgloaders/AllenOMEZarrProperties.java | 83 +++++++++++++------ 1 file changed, 58 insertions(+), 25 deletions(-) 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 094b24284..2b47a9b35 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,6 +23,7 @@ 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; @@ -42,25 +43,30 @@ 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 OME multiscale metadata per setupId so that we can retrieve the appropriate dataset path (e.g., "s0", "s1", "s2" or "0", "1", "2") + private final Map< ViewId, OmeNgffMultiScaleMetadata > viewIdToOmeMetadata = new HashMap<>(); 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 int setupId, final int timepointId, final int level ) { - return String.format( getPath( setupId, timepointId )+ "/%d", level ); + // Note: if the OME metadata has not been cached yet this method will return the default path, because the reader is not available + return getMultiscaleDatasetPathOrDefault(null, timepointId, setupId, level); } @Override @@ -78,7 +84,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( 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 ); @@ -96,13 +102,14 @@ private static int getFirstAvailableTimepointId( final AbstractSequenceDescripti return tp.getId(); } - throw new RuntimeException( "All timepoints for setupId " + setupId + " are declared missing. Stopping." ); + throw new IllegalStateException( "All timepoints for setupId " + setupId + " are declared missing. Stopping." ); } private static DataType getDataType( final AllenOMEZarrProperties n5properties, final N5Reader n5, final int setupId ) { final int timePointId = getFirstAvailableTimepointId( n5properties.sequenceDescription, setupId ); - return n5.getDatasetAttributes( n5properties.getDatasetPath( setupId, timePointId, 0 ) ).getDataType(); + String datasetPath = n5properties.getMultiscaleDatasetPathOrDefault(n5, timePointId, setupId, 0); + return n5.getDatasetAttributes( datasetPath ).getDataType(); } private static double[][] getMipMapResolutions( final AllenOMEZarrProperties n5properties, final N5Reader n5, final int setupId ) @@ -110,26 +117,14 @@ private static double[][] getMipMapResolutions( final AllenOMEZarrProperties n5p final int timePointId = getFirstAvailableTimepointId( n5properties.sequenceDescription, setupId ); // multiresolution pyramid + OmeNgffMultiScaleMetadata multiScaleMetadata = n5properties.getViewSetupMultiscaleMetadata(n5, timePointId, setupId); - //org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.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 OmeNgffMultiScaleMetadata[] multiscales = n5.getAttribute( n5properties.getPath( setupId, timePointId ), "multiscales", OmeNgffMultiScaleMetadata[].class ); - - if ( multiscales == null || multiscales.length == 0 ) - throw new RuntimeException( "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." ); - - //System.out.println( "AllenOMEZarrLoader.getMipmapResolutions() for " + setupId + " using " + n5properties.getPath( setupId, timePointId ) + ": found " + multiscales[ 0 ].datasets.length + " multi-resolution levels." ); - - double[][] mipMapResolutions = new double[ multiscales[ 0 ].datasets.length ][ 3 ]; + double[][] mipMapResolutions = new double[ multiScaleMetadata.datasets.length ][ 3 ]; double[] firstScale = null; - for ( int i = 0; i < multiscales[ 0 ].datasets.length; ++i ) + for ( int i = 0; i < multiScaleMetadata.datasets.length; ++i ) { - final OmeNgffDataset ds = multiscales[ 0 ].datasets[ i ]; + final OmeNgffDataset ds = multiScaleMetadata.datasets[ i ]; for ( final CoordinateTransformation< ? > c : ds.coordinateTransformations ) { @@ -145,11 +140,49 @@ private static double[][] getMipMapResolutions( final AllenOMEZarrProperties n5p mipMapResolutions[ i ][ d ] = s.getScale()[ d ] / firstScale[ d ]; mipMapResolutions[ i ][ d ] = Math.round(mipMapResolutions[ i ][ d ]*10000)/10000d; // round to the 5th digit } - //System.out.println( "AllenOMEZarrLoader.getMipmapResolutions(), level " + i + ": " + Arrays.toString( s.getScale() ) + " >> " + Arrays.toString( mipMapResolutions[ i ] ) ); } } } return mipMapResolutions; } + + private String getMultiscaleDatasetPathOrDefault( N5Reader n5, int timepointId, int setupId, int level ) + { + OmeNgffMultiScaleMetadata omeNgffMultiScaleMetadata = getViewSetupMultiscaleMetadata(n5, timepointId, setupId); + + String viewSetupPath = getPath( setupId, timepointId ); + String datasetPath; + + if ( omeNgffMultiScaleMetadata != null ) { + // get the first scale path from the metadata + datasetPath = omeNgffMultiScaleMetadata.datasets[level].path; + } else { + // use the default level value + datasetPath = String.valueOf( level ); + } + + return String.format( "%s/%s", viewSetupPath, datasetPath); + } + + // retrieve and cache the multiscale metadata + private OmeNgffMultiScaleMetadata getViewSetupMultiscaleMetadata(N5Reader n5, int timePointId, int setupId) { + ViewId viewId = new ViewId(timePointId, setupId); + + return viewIdToOmeMetadata.computeIfAbsent(viewId, k -> { + if (n5 == null) { + return null; // no mapping will be cached + } + + final OmeNgffMultiScaleMetadata[] multiscales = n5.getAttribute( getPath( setupId, timePointId ), "multiscales", OmeNgffMultiScaleMetadata[].class ); + + 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]; + }); + } }