|
52 | 52 | import org.apache.spark.api.java.JavaSparkContext; |
53 | 53 | import org.janelia.saalfeldlab.n5.DatasetAttributes; |
54 | 54 | import org.janelia.saalfeldlab.n5.N5FSWriter; |
| 55 | +import org.janelia.saalfeldlab.n5.zarr.v3.ZarrV3DatasetAttributes; |
| 56 | +import org.janelia.saalfeldlab.n5.zarr.v3.ZarrV3KeyValueWriter; |
55 | 57 | import org.janelia.saalfeldlab.n5.N5Writer; |
56 | 58 | import org.janelia.saalfeldlab.n5.imglib2.N5Utils; |
57 | 59 | import org.janelia.saalfeldlab.n5.spark.supplier.N5WriterSupplier; |
58 | 60 | import org.janelia.saalfeldlab.n5.spark.util.CmdUtils; |
| 61 | +import org.janelia.scicomp.n5.zstandard.ZstandardCompression; |
59 | 62 | import org.kohsuke.args4j.CmdLineException; |
60 | 63 | import org.kohsuke.args4j.CmdLineParser; |
61 | 64 | import org.kohsuke.args4j.Option; |
@@ -159,6 +162,43 @@ public static <T extends NativeType<T> & IntegerType<T>> void downsampleLabel( |
159 | 162 | final int[] blockSize, |
160 | 163 | final boolean overwriteExisting) throws IOException { |
161 | 164 |
|
| 165 | + downsampleLabel( |
| 166 | + sparkContext, |
| 167 | + n5Supplier, |
| 168 | + inputDatasetPath, |
| 169 | + outputDatasetPath, |
| 170 | + downsamplingFactors, |
| 171 | + blockSize, |
| 172 | + null, |
| 173 | + overwriteExisting |
| 174 | + ); |
| 175 | + } |
| 176 | + |
| 177 | + /** |
| 178 | + * Downsamples the given input dataset with respect to the given downsampling factors, storing the output blocks |
| 179 | + * in shards of {@code chunksPerShard} chunks per axis. A {@code null} {@code chunksPerShard} writes an unsharded |
| 180 | + * dataset (identical to the other overloads). Sharding requires a zarr3 ({@link ZarrV3KeyValueWriter}) writer. |
| 181 | + * |
| 182 | + * @param sparkContext |
| 183 | + * @param n5Supplier |
| 184 | + * @param inputDatasetPath |
| 185 | + * @param outputDatasetPath |
| 186 | + * @param downsamplingFactors |
| 187 | + * @param blockSize |
| 188 | + * @param chunksPerShard number of chunks per shard per axis, or {@code null} for an unsharded output |
| 189 | + * @param overwriteExisting |
| 190 | + * @throws IOException |
| 191 | + */ |
| 192 | + public static <T extends NativeType<T> & IntegerType<T>> void downsampleLabel( |
| 193 | + final JavaSparkContext sparkContext, |
| 194 | + final N5WriterSupplier n5Supplier, |
| 195 | + final String inputDatasetPath, |
| 196 | + final String outputDatasetPath, |
| 197 | + final int[] downsamplingFactors, |
| 198 | + final int[] blockSize, |
| 199 | + final int[] chunksPerShard, |
| 200 | + final boolean overwriteExisting) throws IOException { |
| 201 | + |
162 | 202 | final N5Writer n5 = n5Supplier.get(); |
163 | 203 | if (!n5.datasetExists(inputDatasetPath)) |
164 | 204 | throw new IllegalArgumentException("Input N5 dataset " + inputDatasetPath + " does not exist"); |
@@ -189,22 +229,38 @@ public static <T extends NativeType<T> & IntegerType<T>> void downsampleLabel( |
189 | 229 | } |
190 | 230 | } |
191 | 231 |
|
192 | | - n5.createDataset( |
193 | | - outputDatasetPath, |
194 | | - outputDimensions, |
195 | | - outputBlockSize, |
196 | | - inputAttributes.getDataType(), |
197 | | - inputAttributes.getCompression() |
198 | | - ); |
| 232 | + if (chunksPerShard != null) { |
| 233 | + if (!(n5 instanceof ZarrV3KeyValueWriter)) |
| 234 | + throw new IllegalArgumentException("Sharded downsampling requires a ZarrV3 writer"); |
| 235 | + final int[] shardSize = new int[dim]; |
| 236 | + for (int d = 0; d < dim; ++d) |
| 237 | + shardSize[d] = chunksPerShard[d] * outputBlockSize[d]; |
199 | 238 |
|
200 | | - final CellGrid outputCellGrid = new CellGrid(outputDimensions, outputBlockSize); |
| 239 | + n5.createDataset( |
| 240 | + outputDatasetPath, |
| 241 | + new ZarrV3DatasetAttributes(outputDimensions, shardSize, outputBlockSize, inputAttributes.getDataType(), new ZstandardCompression()) |
| 242 | + ); |
| 243 | + } else { |
| 244 | + n5.createDataset( |
| 245 | + outputDatasetPath, |
| 246 | + outputDimensions, |
| 247 | + outputBlockSize, |
| 248 | + inputAttributes.getDataType(), |
| 249 | + new ZstandardCompression() |
| 250 | + ); |
| 251 | + } |
| 252 | + |
| 253 | + /* the parallel-write unit is DatasetAttributes#getBlockSize: the shard for a sharded dataset, the chunk otherwise */ |
| 254 | + final int[] writeBlockSize = n5.getDatasetAttributes(outputDatasetPath).getBlockSize(); |
| 255 | + |
| 256 | + final CellGrid outputCellGrid = new CellGrid(outputDimensions, writeBlockSize); |
201 | 257 | final long numDownsampledBlocks = Intervals.numElements(outputCellGrid.getGridDimensions()); |
202 | 258 | final List<Long> blockIndexes = LongStream.range(0, numDownsampledBlocks).boxed().collect(Collectors.toList()); |
203 | 259 |
|
204 | 260 | sparkContext |
205 | 261 | .parallelize(blockIndexes, Math.min(blockIndexes.size(), MAX_PARTITIONS)) |
206 | 262 | .foreach(blockIndex -> { |
207 | | - final CellGrid cellGrid = new CellGrid(outputDimensions, outputBlockSize); |
| 263 | + final CellGrid cellGrid = new CellGrid(outputDimensions, writeBlockSize); |
208 | 264 | final long[] blockGridPosition = new long[cellGrid.numDimensions()]; |
209 | 265 | cellGrid.getCellGridPositionFlat(blockIndex, blockGridPosition); |
210 | 266 |
|
@@ -253,17 +309,23 @@ public static <T extends NativeType<T> & IntegerType<T>> void downsampleLabel( |
253 | 309 | final RandomAccessibleInterval<T> targetBlock = new ArrayImgFactory<>(defaultValue).create(targetInterval); |
254 | 310 | downsampleLabel(sourceBlock, targetBlock, downsamplingFactors); |
255 | 311 |
|
| 312 | + final DatasetAttributes outputAttributes = n5Writer.getDatasetAttributes(outputDatasetPath); |
| 313 | + final int[] shardBlockSize = outputAttributes.getBlockSize(); |
| 314 | + final int[] innerChunkSize = outputAttributes.getChunkSize(); |
| 315 | + final long[] chunkGridOffset = new long[dim]; |
| 316 | + for (int d = 0; d < dim; ++d) |
| 317 | + chunkGridOffset[d] = blockGridPosition[d] * (shardBlockSize[d] / innerChunkSize[d]); |
| 318 | + |
256 | 319 | if (overwriteExisting) { |
257 | 320 | // Empty blocks will not be written out. Delete blocks to avoid remnant blocks if overwriting. |
258 | | - N5Utils.deleteBlock(targetBlock, n5Writer, outputDatasetPath, blockGridPosition); |
| 321 | + N5Utils.deleteBlock(targetBlock, n5Writer, outputDatasetPath, chunkGridOffset); |
259 | 322 | } |
260 | | - |
261 | | - N5Utils.saveNonEmptyBlock(targetBlock, n5Writer, outputDatasetPath, blockGridPosition, defaultValue); |
| 323 | + N5Utils.saveNonEmptyBlock(targetBlock, n5Writer, outputDatasetPath, outputAttributes, chunkGridOffset, defaultValue); |
262 | 324 | }); |
263 | 325 | } |
264 | 326 |
|
265 | 327 | /** |
266 | | - * Based on {@link bdv.export.Downsample}. |
| 328 | + * Based on bdv.export.Downsample. |
267 | 329 | */ |
268 | 330 | private static <T extends IntegerType<T>> void downsampleLabel( |
269 | 331 | final RandomAccessible<T> input, |
|
0 commit comments