Skip to content

Commit 83edd4e

Browse files
committed
feat: add readShard writeShard methods
* add a few tests
1 parent 7b68e47 commit 83edd4e

7 files changed

Lines changed: 166 additions & 4 deletions

File tree

src/main/java/org/janelia/saalfeldlab/n5/GsonKeyValueN5Reader.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,24 @@ default <T> List<DataBlock<T>> readBlocks(
119119
return convertedDatasetAttributes.<T> getDatasetAccess().readBlocks(posKva, blockPositions);
120120
}
121121

122+
@Override
123+
default <T> DataBlock<T> readShard(
124+
final String pathName,
125+
final DatasetAttributes datasetAttributes,
126+
final long... gridPosition) throws N5Exception {
127+
128+
final DatasetAttributes convertedDatasetAttributes = getConvertedDatasetAttributes(datasetAttributes);
129+
final int shardLevel = convertedDatasetAttributes.getNestedBlockGrid().numLevels() - 1;
130+
try {
131+
final PositionValueAccess posKva = PositionValueAccess.fromKva(getKeyValueAccess(), getURI(), N5URI.normalizeGroupPath(pathName),
132+
convertedDatasetAttributes);
133+
return convertedDatasetAttributes.<T> getDatasetAccess().readShard(posKva, gridPosition, shardLevel);
134+
135+
} catch (N5Exception.N5NoSuchKeyException e) {
136+
return null;
137+
}
138+
}
139+
122140
@Override
123141
default String[] list(final String pathName) throws N5Exception {
124142

src/main/java/org/janelia/saalfeldlab/n5/GsonKeyValueN5Writer.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,24 @@ default <T> void writeBlock(
280280
"Failed to write block " + Arrays.toString(dataBlock.getGridPosition()) + " into dataset " + path,
281281
e);
282282
}
283+
}
284+
285+
@Override
286+
default <T> void writeShard(
287+
final String path,
288+
final DatasetAttributes datasetAttributes,
289+
final DataBlock<T> shard) throws N5Exception {
283290

291+
final DatasetAttributes convertedDatasetAttributes = getConvertedDatasetAttributes(datasetAttributes);
292+
final int shardLevel = convertedDatasetAttributes.getNestedBlockGrid().numLevels() - 1;
293+
try {
294+
final PositionValueAccess posKva = PositionValueAccess.fromKva(getKeyValueAccess(), getURI(), N5URI.normalizeGroupPath(path), convertedDatasetAttributes);
295+
convertedDatasetAttributes.<T> getDatasetAccess().writeShard(posKva, shard, shardLevel);
296+
} catch (final UncheckedIOException e) {
297+
throw new N5IOException(
298+
"Failed to write block " + Arrays.toString(shard.getGridPosition()) + " into dataset " + path,
299+
e);
300+
}
284301
}
285302

286303
@Override

src/main/java/org/janelia/saalfeldlab/n5/N5Reader.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,31 @@ default <T> List<DataBlock<T>> readBlocks(
348348
return blocks;
349349
}
350350

351+
/**
352+
* Reads a shard as a {@link DataBlock}.
353+
* <p>
354+
* A "shard" is the largest level of the datasets {@link NestedGrid}
355+
* This method's behavior is identical to readBlock for un-sharded datasets.
356+
*
357+
* @param <T>
358+
* the DataBlock data type
359+
* @param pathName
360+
* dataset path
361+
* @param datasetAttributes
362+
* the dataset attributes
363+
* @param gridPosition
364+
* the position in the shard grid
365+
* @return the data block
366+
* @throws N5Exception
367+
* the exception
368+
*
369+
* @see DatasetAttributes#getNestedBlockGrid()
370+
*/
371+
<T> DataBlock<T> readShard(
372+
final String pathName,
373+
final DatasetAttributes datasetAttributes,
374+
final long... gridPosition) throws N5Exception;
375+
351376
/**
352377
* Checks if a shard (or block for non-sharded datasets) exists at the
353378
* given grid position without reading the data.

src/main/java/org/janelia/saalfeldlab/n5/N5Writer.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,25 @@ default <T> void writeBlocks(
278278
}
279279
}
280280

281+
/**
282+
* Writes a shard stored as a {@link DataBlock}.
283+
* <p>
284+
* A "shard" is the largest level of the datasets {@link NestedGrid}.
285+
* This method's behavior is identical to writeBlock for un-sharded datasets.
286+
*
287+
* @param datasetPath dataset path
288+
* @param datasetAttributes the dataset attributes
289+
* @param dataBlock the data block
290+
* @param <T> the data block data type
291+
* @throws N5Exception the exception
292+
*
293+
* @see DatasetAttributes#getNestedBlockGrid()
294+
*/
295+
<T> void writeShard(
296+
final String pathName,
297+
final DatasetAttributes datasetAttributes,
298+
final DataBlock<T> dataBlock) throws N5Exception;
299+
281300
@FunctionalInterface
282301
interface DataBlockSupplier<T> {
283302

src/test/java/org/janelia/saalfeldlab/n5/AbstractN5Test.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,33 @@ public void testWriteReadDoubleBlock() {
474474
}
475475
}
476476

477+
@Test
478+
public void testWriteReadShard() {
479+
480+
// test that writeShard behaves the same as writeBlock
481+
// for unsharded datasets
482+
483+
for (final Compression compression : getCompressions()) {
484+
try (final N5Writer n5 = createTempN5Writer()) {
485+
486+
n5.createDataset(datasetName, dimensions, blockSize, DataType.INT16, compression);
487+
final DatasetAttributes attributes = n5.getDatasetAttributes(datasetName);
488+
final ShortArrayDataBlock dataBlock = new ShortArrayDataBlock(blockSize, new long[]{0, 0, 0}, shortBlock);
489+
490+
n5.writeShard(datasetName, attributes, dataBlock);
491+
492+
// read with readShard
493+
final DataBlock<?> loadedShard = n5.readShard(datasetName, attributes, 0, 0, 0);
494+
assertArrayEquals(shortBlock, (short[])loadedShard.getData());
495+
496+
// read with readBlock
497+
final DataBlock<?> loadedDataBlock = n5.readShard(datasetName, attributes, 0, 0, 0);
498+
assertArrayEquals(shortBlock, (short[])loadedDataBlock.getData());
499+
}
500+
}
501+
}
502+
503+
477504
@Test
478505
public void testMode1WriteReadByteBlock() {
479506

src/test/java/org/janelia/saalfeldlab/n5/http/HttpReaderFsWriter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,10 @@ public <W extends GsonKeyValueN5Writer, R extends GsonKeyValueN5Reader> HttpRead
276276
writer.writeBlock(datasetPath, getConvertedDatasetAttributes(datasetAttributes), dataBlock);
277277
}
278278

279+
@Override public <T> void writeShard(String datasetPath, DatasetAttributes datasetAttributes, DataBlock<T> dataBlock) throws N5Exception {
280+
writer.writeShard(datasetPath, getConvertedDatasetAttributes(datasetAttributes), dataBlock);
281+
}
282+
279283
@Override public boolean deleteBlock(String datasetPath, long... gridPosition) throws N5Exception {
280284

281285
return writer.deleteBlock(datasetPath, gridPosition);

src/test/java/org/janelia/saalfeldlab/n5/shard/ShardTest.java

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@
5656
import java.nio.file.NoSuchFileException;
5757
import java.util.*;
5858
import java.util.function.Function;
59+
import java.util.stream.IntStream;
5960

61+
import static org.junit.Assert.assertArrayEquals;
6062
import static org.junit.Assert.assertEquals;
6163
import static org.junit.Assert.assertTrue;
6264

@@ -128,6 +130,11 @@ public void removeTempWriters() {
128130

129131
private DatasetAttributes getTestAttributes(long[] dimensions, int[] shardSize, int[] blockSize) {
130132

133+
return getTestAttributes(DataType.UINT8, dimensions, shardSize, blockSize);
134+
}
135+
136+
private DatasetAttributes getTestAttributes(DataType dataType, long[] dimensions, int[] shardSize, int[] blockSize) {
137+
131138
DefaultShardCodecInfo blockCodec = new DefaultShardCodecInfo(
132139
blockSize,
133140
new N5BlockCodecInfo(),
@@ -139,7 +146,7 @@ private DatasetAttributes getTestAttributes(long[] dimensions, int[] shardSize,
139146
return new DatasetAttributes(
140147
dimensions,
141148
shardSize,
142-
DataType.UINT8,
149+
dataType,
143150
blockCodec);
144151
}
145152

@@ -412,6 +419,51 @@ public void writeReadBlockTest() {
412419
}
413420
}
414421

422+
@Test
423+
public void writeReadShardTest() {
424+
425+
try ( final N5Writer n5 = tempN5Factory.createTempN5Writer() ) {
426+
427+
final int[] shardSize = new int[] {4,4};
428+
final int shardN = 16;
429+
430+
final int[] blockSize = new int[] {2,2};
431+
final int blockN = 4;
432+
433+
final String dataset = "writeReadShard";
434+
DatasetAttributes attrs = getTestAttributes(DataType.INT32, new long[]{8, 8}, shardSize, blockSize);
435+
436+
final int[] shardData = range(shardN);
437+
IntArrayDataBlock shard = new IntArrayDataBlock(shardSize, new long[]{0, 0}, shardData);
438+
439+
n5.writeShard(dataset, attrs, shard);
440+
DataBlock<int[]> readShard = n5.readShard(dataset, attrs, 0, 0);
441+
assertArrayEquals(shardData, readShard.getData());
442+
443+
444+
/**
445+
* The 4x4 shard at (0,0)
446+
* and the 2x2 blocks it contains
447+
*
448+
*
449+
* 0 1 | 2 3
450+
* 4 5 | 6 7
451+
* ----------------
452+
* 8 9 | 10 11
453+
* 12 13 | 14 15
454+
*/
455+
456+
assertArrayEquals(new int[]{0, 1, 4, 5}, (int[])n5.readBlock(dataset, attrs, 0, 0).getData());
457+
assertArrayEquals(new int[]{2, 3, 6, 7}, (int[])n5.readBlock(dataset, attrs, 1, 0).getData());
458+
assertArrayEquals(new int[]{8, 9, 12, 13}, (int[])n5.readBlock(dataset, attrs, 0, 1).getData());
459+
assertArrayEquals(new int[]{10, 11, 14, 15}, (int[])n5.readBlock(dataset, attrs, 1, 1).getData());
460+
}
461+
}
462+
463+
private int[] range(int N) {
464+
return IntStream.range(0, N).toArray();
465+
}
466+
415467
/**
416468
* Checks how many read calls to the backend are performed for a particular readBlocks
417469
* call. At this time (Nov 4 2025), one read for the index, and one read per block are performed.
@@ -484,7 +536,7 @@ public void shardExistsTest() {
484536

485537
final String dataset = "shardExists";
486538
writer.remove(dataset);
487-
writer.createDataset(dataset, datasetAttributes);
539+
DatasetAttributes attrs = writer.createDataset(dataset, datasetAttributes);
488540

489541
final int[] blockSize = datasetAttributes.getBlockSize();
490542
final int numElements = blockSize[0] * blockSize[1];
@@ -497,7 +549,7 @@ public void shardExistsTest() {
497549
/* write blocks to shards (0,0), (1,0), and (2,2) */
498550
writer.writeBlocks(
499551
dataset,
500-
datasetAttributes,
552+
attrs,
501553
new ByteArrayDataBlock(blockSize, new long[]{0, 0}, data), /* shard (0, 0) */
502554
new ByteArrayDataBlock(blockSize, new long[]{4, 0}, data), /* shard (1, 0) */
503555
new ByteArrayDataBlock(blockSize, new long[]{11, 11}, data) /* shard (2, 2) */
@@ -507,7 +559,7 @@ public void shardExistsTest() {
507559

508560
Function<long[], Boolean> assertShardExistsTracking = (gridPosition) -> {
509561
trackingWriter.resetAllTracking();
510-
final Boolean exists = writer.shardExists(dataset, datasetAttributes, gridPosition);
562+
final Boolean exists = writer.shardExists(dataset, attrs, gridPosition);
511563
assertEquals("isFileCheck incremented", 1, trackingWriter.getNumIsFileCalls());
512564
assertEquals("No Bytes Read", 0, trackingWriter.getTotalBytesRead());
513565
return exists;

0 commit comments

Comments
 (0)