2424
2525import java .util .Arrays ;
2626import java .util .Map ;
27+ import java .util .concurrent .ConcurrentHashMap ;
28+ import java .util .concurrent .ConcurrentMap ;
2729
2830import org .janelia .saalfeldlab .n5 .DataType ;
2931import org .janelia .saalfeldlab .n5 .N5Reader ;
3032import org .janelia .saalfeldlab .n5 .universe .metadata .ome .ngff .v04 .OmeNgffMultiScaleMetadata ;
3133import org .janelia .saalfeldlab .n5 .universe .metadata .ome .ngff .v04 .OmeNgffMultiScaleMetadata .OmeNgffDataset ;
3234import org .janelia .saalfeldlab .n5 .universe .metadata .ome .ngff .v04 .coordinateTransformations .CoordinateTransformation ;
3335import org .janelia .saalfeldlab .n5 .universe .metadata .ome .ngff .v04 .coordinateTransformations .ScaleCoordinateTransformation ;
36+ import org .janelia .saalfeldlab .n5 .universe .metadata .ome .ngff .v04 .coordinateTransformations .TranslationCoordinateTransformation ;
3437
3538import bdv .img .n5 .N5Properties ;
3639import mpicbg .spim .data .generic .sequence .AbstractSequenceDescription ;
@@ -42,25 +45,33 @@ public class AllenOMEZarrProperties implements N5Properties
4245{
4346 private final AbstractSequenceDescription < ?, ?, ? > sequenceDescription ;
4447
45- private final Map < ViewId , OMEZARREntry > viewIdToPath ;
48+ // mapping of viewIDs to corresponding OME-ZARRs
49+ private final Map < ViewId , OMEZARREntry > viewIdToOmeZarrPath ;
50+
51+ // N5Properties.getDatasetPath should require an N5Reader so that the dataset path could always be retrieved correctly (e.g., "s0", "s1", "s2" or "0", "1", "2")
52+ // To work around this problem for now, first time we retrieve the view setup we cache it so that next time
53+ // the dataset path is needed - we use the cached value.
54+ // TODO: Remove this and the method that populates it, once the signature for N5Properties.getDatasetPath was updated to use the N5 Reader
55+ private final ConcurrentMap < ViewId , OmeNgffMultiScaleMetadata > viewIdToOmeMetadata = new ConcurrentHashMap <>();
4656
4757 public AllenOMEZarrProperties (
4858 final AbstractSequenceDescription < ?, ?, ? > sequenceDescription ,
49- final Map < ViewId , OMEZARREntry > viewIdToPath )
59+ final Map < ViewId , OMEZARREntry > viewIdToOmeZarrPath )
5060 {
5161 this .sequenceDescription = sequenceDescription ;
52- this .viewIdToPath = viewIdToPath ;
62+ this .viewIdToOmeZarrPath = viewIdToOmeZarrPath ;
5363 }
5464
5565 private String getPath ( final int setupId , final int timepointId )
5666 {
57- return viewIdToPath .get ( new ViewId ( timepointId , setupId ) ).getPath ();
67+ return viewIdToOmeZarrPath .get ( new ViewId ( timepointId , setupId ) ).getPath ();
5868 }
5969
6070 @ Override
6171 public String getDatasetPath ( final int setupId , final int timepointId , final int level )
6272 {
63- return String .format ( getPath ( setupId , timepointId )+ "/%d" , level );
73+ // Note: if the OME metadata has not been cached yet this method will return the default path, because the reader is not available
74+ return getMultiscaleDatasetPathOrDefault (null , timepointId , setupId , level );
6475 }
6576
6677 @ Override
@@ -78,7 +89,7 @@ public double[][] getMipmapResolutions( final N5Reader n5, final int setupId )
7889 @ Override
7990 public long [] getDimensions ( final N5Reader n5 , final int setupId , final int timepointId , final int level )
8091 {
81- final String path = getDatasetPath ( setupId , timepointId , level );
92+ final String path = getMultiscaleDatasetPathOrDefault ( n5 , timepointId , setupId , level );
8293 final long [] dimensions = n5 .getDatasetAttributes ( path ).getDimensions ();
8394 // dataset dimensions is 5D, remove the channel and time dimensions
8495 return Arrays .copyOf ( dimensions , 3 );
@@ -96,40 +107,29 @@ private static int getFirstAvailableTimepointId( final AbstractSequenceDescripti
96107 return tp .getId ();
97108 }
98109
99- throw new RuntimeException ( "All timepoints for setupId " + setupId + " are declared missing. Stopping." );
110+ throw new IllegalStateException ( "All timepoints for setupId " + setupId + " are declared missing. Stopping." );
100111 }
101112
102113 private static DataType getDataType ( final AllenOMEZarrProperties n5properties , final N5Reader n5 , final int setupId )
103114 {
104115 final int timePointId = getFirstAvailableTimepointId ( n5properties .sequenceDescription , setupId );
105- return n5 .getDatasetAttributes ( n5properties .getDatasetPath ( setupId , timePointId , 0 ) ).getDataType ();
116+ String datasetPath = n5properties .getMultiscaleDatasetPathOrDefault (n5 , timePointId , setupId , 0 );
117+ return n5 .getDatasetAttributes ( datasetPath ).getDataType ();
106118 }
107119
108120 private static double [][] getMipMapResolutions ( final AllenOMEZarrProperties n5properties , final N5Reader n5 , final int setupId )
109121 {
110122 final int timePointId = getFirstAvailableTimepointId ( n5properties .sequenceDescription , setupId );
111123
112124 // multiresolution pyramid
125+ OmeNgffMultiScaleMetadata multiScaleMetadata = n5properties .getViewSetupMultiscaleMetadata (n5 , timePointId , setupId );
113126
114- //org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.OmeNgffMetadata
115- // for this to work you need to register an adapter in the N5Factory class
116- // final GsonBuilder builder = new GsonBuilder().registerTypeAdapter( CoordinateTransformation.class, new CoordinateTransformationAdapter() );
117- final OmeNgffMultiScaleMetadata [] multiscales = n5 .getAttribute ( n5properties .getPath ( setupId , timePointId ), "multiscales" , OmeNgffMultiScaleMetadata [].class );
118-
119- if ( multiscales == null || multiscales .length == 0 )
120- throw new RuntimeException ( "Could not parse OME-ZARR multiscales object. stopping." );
121-
122- if ( multiscales .length != 1 )
123- System .out .println ( "This dataset has " + multiscales .length + " objects, we expected 1. Picking the first one." );
124-
125- //System.out.println( "AllenOMEZarrLoader.getMipmapResolutions() for " + setupId + " using " + n5properties.getPath( setupId, timePointId ) + ": found " + multiscales[ 0 ].datasets.length + " multi-resolution levels." );
126-
127- double [][] mipMapResolutions = new double [ multiscales [ 0 ].datasets .length ][ 3 ];
127+ double [][] mipMapResolutions = new double [ multiScaleMetadata .datasets .length ][ 3 ];
128128 double [] firstScale = null ;
129129
130- for ( int i = 0 ; i < multiscales [ 0 ] .datasets .length ; ++i )
130+ for ( int i = 0 ; i < multiScaleMetadata .datasets .length ; ++i )
131131 {
132- final OmeNgffDataset ds = multiscales [ 0 ] .datasets [ i ];
132+ final OmeNgffDataset ds = multiScaleMetadata .datasets [ i ];
133133
134134 for ( final CoordinateTransformation < ? > c : ds .coordinateTransformations )
135135 {
@@ -145,11 +145,65 @@ private static double[][] getMipMapResolutions( final AllenOMEZarrProperties n5p
145145 mipMapResolutions [ i ][ d ] = s .getScale ()[ d ] / firstScale [ d ];
146146 mipMapResolutions [ i ][ d ] = Math .round (mipMapResolutions [ i ][ d ]*10000 )/10000d ; // round to the 5th digit
147147 }
148- //System.out.println( "AllenOMEZarrLoader.getMipmapResolutions(), level " + i + ": " + Arrays.toString( s.getScale() ) + " >> " + Arrays.toString( mipMapResolutions[ i ] ) );
148+ }
149+ if ( c instanceof TranslationCoordinateTransformation )
150+ {
151+ final TranslationCoordinateTransformation t = ( TranslationCoordinateTransformation ) c ;
152+
153+ if (firstScale == null ) {
154+ throw new IllegalStateException ("Expected first scale to be set before the translation for level " + i + " dataset is processed" );
155+ }
156+
157+ for ( int d = 0 ; d < mipMapResolutions [ i ].length ; ++d )
158+ {
159+ // at this point firstScale should be available
160+ double pxTranslation = t .getTranslation ()[ d ] / firstScale [ d ];
161+ double pxTranslationCorrection = (pxTranslation + 0.5 ) / mipMapResolutions [i ][d ] - 0.5 ;
162+ if (Math .abs (pxTranslationCorrection ) >= 0.5 ) {
163+ System .out .printf ("Pixel translation[%d][%d]=%f (=%fpx) and the pixel correction %f is more than 0.5px\n " ,
164+ i , d , t .getTranslation ()[ d ], pxTranslation , pxTranslationCorrection );
165+ }
166+ }
149167 }
150168 }
151169 }
152170
153171 return mipMapResolutions ;
154172 }
173+
174+ private String getMultiscaleDatasetPathOrDefault ( N5Reader n5 , int timepointId , int setupId , int level )
175+ {
176+ OmeNgffMultiScaleMetadata omeNgffMultiScaleMetadata = getViewSetupMultiscaleMetadata (n5 , timepointId , setupId );
177+
178+ if (omeNgffMultiScaleMetadata == null ) {
179+ throw new IllegalStateException ("OME multiscale metadata could not be cached for (tp, setup) = (" +
180+ timepointId + "," + setupId + ") - current N5Reader is " + n5 );
181+ }
182+
183+ String viewSetupPath = getPath ( setupId , timepointId );
184+ // get the first scale path from the metadata
185+ String datasetPath = omeNgffMultiScaleMetadata .datasets [level ].path ;
186+ return String .format ( "%s/%s" , viewSetupPath , datasetPath );
187+ }
188+
189+ // retrieve and cache the multiscale metadata
190+ private OmeNgffMultiScaleMetadata getViewSetupMultiscaleMetadata (N5Reader n5 , int timePointId , int setupId ) {
191+ ViewId viewId = new ViewId (timePointId , setupId );
192+
193+ return viewIdToOmeMetadata .computeIfAbsent (viewId , k -> {
194+ if (n5 == null ) {
195+ return null ; // no mapping will be cached
196+ }
197+
198+ final OmeNgffMultiScaleMetadata [] multiscales = n5 .getAttribute ( getPath ( setupId , timePointId ), "multiscales" , OmeNgffMultiScaleMetadata [].class );
199+
200+ if ( multiscales == null || multiscales .length == 0 )
201+ throw new IllegalStateException ( "Could not parse OME-ZARR multiscales object. stopping." );
202+
203+ if ( multiscales .length > 1 )
204+ System .out .println ( "This dataset has " + multiscales .length + " objects, we expected 1. Picking the first one." );
205+
206+ return multiscales [0 ];
207+ });
208+ }
155209}
0 commit comments