2828 */
2929package org .janelia .saalfeldlab .n5 .spark .downsample ;
3030
31- import bdv . export .Downsample ;
31+ import org . janelia . saalfeldlab . n5 . spark . util .Downsample ;
3232import net .imglib2 .FinalInterval ;
3333import net .imglib2 .Interval ;
3434import net .imglib2 .RandomAccessibleInterval ;
4747import org .janelia .saalfeldlab .n5 .DatasetAttributes ;
4848import org .janelia .saalfeldlab .n5 .N5FSWriter ;
4949import org .janelia .saalfeldlab .n5 .N5Writer ;
50+ import org .janelia .saalfeldlab .n5 .zarr .v3 .ZarrV3DatasetAttributes ;
51+ import org .janelia .saalfeldlab .n5 .zarr .v3 .ZarrV3KeyValueWriter ;
5052import org .janelia .saalfeldlab .n5 .imglib2 .N5Utils ;
5153import org .janelia .saalfeldlab .n5 .spark .supplier .N5WriterSupplier ;
5254import org .janelia .saalfeldlab .n5 .spark .util .CmdUtils ;
@@ -176,6 +178,42 @@ public static <T extends NativeType<T> & RealType<T>> void downsample(
176178 final int [] blockSize ,
177179 final boolean overwriteExisting ) throws IOException {
178180
181+ downsample (
182+ sparkContext ,
183+ n5Supplier ,
184+ inputDatasetPath ,
185+ outputDatasetPath ,
186+ downsamplingFactors ,
187+ blockSize ,
188+ null ,
189+ overwriteExisting );
190+ }
191+
192+ /**
193+ * Downsamples the given input dataset with respect to the given downsampling factors, storing the output blocks in
194+ * shards of {@code chunksPerShard} chunks per axis. A {@code null} {@code chunksPerShard} writes an unsharded dataset
195+ * (identical to the other overloads). Sharding requires a zarr3 ({@link ZarrV3KeyValueWriter}) writer.
196+ *
197+ * @param sparkContext
198+ * @param n5Supplier
199+ * @param inputDatasetPath
200+ * @param outputDatasetPath
201+ * @param downsamplingFactors
202+ * @param blockSize
203+ * @param chunksPerShard number of chunks per shard per axis, or {@code null} for an unsharded output
204+ * @param overwriteExisting
205+ * @throws IOException
206+ */
207+ public static <T extends NativeType <T > & RealType <T >> void downsample (
208+ final JavaSparkContext sparkContext ,
209+ final N5WriterSupplier n5Supplier ,
210+ final String inputDatasetPath ,
211+ final String outputDatasetPath ,
212+ final int [] downsamplingFactors ,
213+ final int [] blockSize ,
214+ final int [] chunksPerShard ,
215+ final boolean overwriteExisting ) throws IOException {
216+
179217 final N5Writer n5 = n5Supplier .get ();
180218 if (!n5 .datasetExists (inputDatasetPath ))
181219 throw new IllegalArgumentException ("Input N5 dataset " + inputDatasetPath + " does not exist" );
@@ -206,13 +244,26 @@ public static <T extends NativeType<T> & RealType<T>> void downsample(
206244 }
207245 }
208246
209- n5 .createDataset (
210- outputDatasetPath ,
211- outputDimensions ,
212- outputBlockSize ,
213- inputAttributes .getDataType (),
214- inputAttributes .getCompression ()
215- );
247+ if (chunksPerShard != null ) {
248+ if (!(n5 instanceof ZarrV3KeyValueWriter ))
249+ throw new IllegalArgumentException ("Sharded downsampling requires a ZarrV3 writer" );
250+ final int [] shardSize = new int [dim ];
251+ for (int d = 0 ; d < dim ; ++d )
252+ shardSize [d ] = chunksPerShard [d ] * outputBlockSize [d ];
253+
254+ n5 .createDataset (
255+ outputDatasetPath ,
256+ new ZarrV3DatasetAttributes (outputDimensions , shardSize , outputBlockSize , inputAttributes .getDataType (), inputAttributes .getCompression ())
257+ );
258+ } else {
259+ n5 .createDataset (
260+ outputDatasetPath ,
261+ outputDimensions ,
262+ outputBlockSize ,
263+ inputAttributes .getDataType (),
264+ inputAttributes .getCompression ()
265+ );
266+ }
216267
217268 // set the downsampling factors attribute
218269 final int [] inputAbsoluteDownsamplingFactors = n5 .getAttribute (inputDatasetPath , DOWNSAMPLING_FACTORS_ATTRIBUTE_KEY , int [].class );
@@ -221,14 +272,17 @@ public static <T extends NativeType<T> & RealType<T>> void downsample(
221272 outputAbsoluteDownsamplingFactors [d ] = downsamplingFactors [d ] * (inputAbsoluteDownsamplingFactors != null ? inputAbsoluteDownsamplingFactors [d ] : 1 );
222273 n5 .setAttribute (outputDatasetPath , DOWNSAMPLING_FACTORS_ATTRIBUTE_KEY , outputAbsoluteDownsamplingFactors );
223274
224- final CellGrid outputCellGrid = new CellGrid (outputDimensions , outputBlockSize );
275+ /* the parallel-write unit is DatasetAttributes#getBlockSize: the shard for a sharded dataset, the chunk otherwise */
276+ final int [] writeBlockSize = n5 .getDatasetAttributes (outputDatasetPath ).getBlockSize ();
277+
278+ final CellGrid outputCellGrid = new CellGrid (outputDimensions , writeBlockSize );
225279 final long numDownsampledBlocks = Intervals .numElements (outputCellGrid .getGridDimensions ());
226280 final List <Long > blockIndexes = LongStream .range (0 , numDownsampledBlocks ).boxed ().collect (Collectors .toList ());
227281
228282 sparkContext
229283 .parallelize (blockIndexes , Math .min (blockIndexes .size (), MAX_PARTITIONS ))
230284 .foreach (blockIndex -> {
231- final CellGrid cellGrid = new CellGrid (outputDimensions , outputBlockSize );
285+ final CellGrid cellGrid = new CellGrid (outputDimensions , writeBlockSize );
232286 final long [] blockGridPosition = new long [cellGrid .numDimensions ()];
233287 cellGrid .getCellGridPositionFlat (blockIndex , blockGridPosition );
234288
@@ -261,7 +315,7 @@ public static <T extends NativeType<T> & RealType<T>> void downsample(
261315 new BasicNameValuePair ("call" , "n5-downsample-spark" )
262316 ).toString ();
263317
264- final RandomAccessibleInterval < T > source = Singleton .get (imgCacheKey , () -> N5Utils .open ( n5Writer , inputDatasetPath ));
318+ final RandomAccessibleInterval < T > source = Singleton .get (imgCacheKey , () -> ( RandomAccessibleInterval < T > ) N5Utils .open ( n5Writer , inputDatasetPath ));
265319 final RandomAccessibleInterval <T > sourceBlock = Views .offsetInterval (source , sourceInterval );
266320
267321 /* test if empty */
@@ -279,12 +333,19 @@ public static <T extends NativeType<T> & RealType<T>> void downsample(
279333 final RandomAccessibleInterval <T > targetBlock = new ArrayImgFactory <>(defaultValue ).create (targetInterval );
280334 Downsample .downsample (sourceBlock , targetBlock , downsamplingFactors );
281335
336+ final DatasetAttributes outputAttributes = n5Writer .getDatasetAttributes (outputDatasetPath );
337+ final int [] shardBlockSize = outputAttributes .getBlockSize ();
338+ final int [] innerChunkSize = outputAttributes .getChunkSize ();
339+ final long [] chunkGridOffset = new long [dim ];
340+ for (int d = 0 ; d < dim ; ++d )
341+ chunkGridOffset [d ] = blockGridPosition [d ] * (shardBlockSize [d ] / innerChunkSize [d ]);
342+
282343 if (overwriteExisting ) {
283344 // Empty blocks will not be written out. Delete blocks to avoid remnant blocks if overwriting.
284- N5Utils .deleteBlock (targetBlock , n5Writer , outputDatasetPath , blockGridPosition );
345+ N5Utils .deleteBlock (targetBlock , n5Writer , outputDatasetPath , chunkGridOffset );
285346 }
286347
287- N5Utils .saveNonEmptyBlock (targetBlock , n5Writer , outputDatasetPath , blockGridPosition , defaultValue );
348+ N5Utils .saveNonEmptyBlock (targetBlock , n5Writer , outputDatasetPath , outputAttributes , chunkGridOffset , defaultValue );
288349 });
289350 }
290351
0 commit comments