Skip to content

Commit 1489388

Browse files
committed
feat: implement shardExists and readBlocksExists methods.
see #186 Signed-off-by: Caleb Hulbert <cmhulbert@gmail.com>
1 parent 9a50c36 commit 1489388

7 files changed

Lines changed: 599 additions & 9 deletions

File tree

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,27 @@ default String absoluteAttributesPath(final String normalPath) {
148148

149149
return getKeyValueAccess().compose(getURI(), normalPath, getAttributesKey());
150150
}
151+
152+
@Override
153+
default boolean shardExists(
154+
final String pathName,
155+
final DatasetAttributes datasetAttributes,
156+
final long... gridPosition) throws N5Exception {
157+
158+
final String normalPath = N5URI.normalizeGroupPath(pathName);
159+
final String blockPath = getKeyValueAccess().compose(getURI(), normalPath,
160+
datasetAttributes.relativeBlockPath(gridPosition));
161+
return getKeyValueAccess().isFile(blockPath);
162+
}
163+
164+
@Override
165+
default <T> List<DataBlock<T>> readBlocksExists(
166+
final String pathName,
167+
final DatasetAttributes datasetAttributes,
168+
final List<long[]> gridPositions) throws N5Exception {
169+
170+
final PositionValueAccess posKva = PositionValueAccess.fromKva(getKeyValueAccess(), getURI(),
171+
N5URI.normalizeGroupPath(pathName), datasetAttributes);
172+
return datasetAttributes.<T>getDatasetAccess().readBlocksExists(posKva, gridPositions);
173+
}
151174
}

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,52 @@ default <T> List<DataBlock<T>> readBlocks(
331331
return blocks;
332332
}
333333

334+
/**
335+
* Checks if a shard (or block for non-sharded datasets) exists at the
336+
* given grid position without reading the data.
337+
* <p>
338+
* This method only checks for the presence of the key value for the gridPosition, it does not
339+
* read or validate the contents.
340+
*
341+
* @param pathName
342+
* dataset path
343+
* @param datasetAttributes
344+
* the dataset attributes
345+
* @param gridPosition
346+
* the shard grid position (or block position for non-sharded datasets)
347+
* @return true if the shard (or block) file exists
348+
* @throws N5Exception
349+
* the exception
350+
*/
351+
boolean shardExists(
352+
final String pathName,
353+
final DatasetAttributes datasetAttributes,
354+
final long... gridPosition) throws N5Exception;
355+
356+
/**
357+
* Reads and returns only the blocks that exist at the given grid positions.
358+
* <p>
359+
* For non-sharded datasets, this checks if each block file exists and reads those that do.
360+
* For sharded datasets, this reads the shard index first to determine which blocks exist,
361+
* then reads only those blocks.
362+
*
363+
* @param <T>
364+
* the DataBlock data type
365+
* @param pathName
366+
* dataset path
367+
* @param datasetAttributes
368+
* the dataset attributes
369+
* @param gridPositions
370+
* a list of grid positions to check
371+
* @return list of blocks that exist (excludes non-existent blocks)
372+
* @throws N5Exception
373+
* the exception
374+
*/
375+
<T> List<DataBlock<T>> readBlocksExists(
376+
final String pathName,
377+
final DatasetAttributes datasetAttributes,
378+
final List<long[]> gridPositions) throws N5Exception;
379+
334380
/**
335381
* Load a {@link DataBlock} as a {@link Serializable}. The offset is given
336382
* in

src/main/java/org/janelia/saalfeldlab/n5/shard/DatasetAccess.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,23 @@ public interface DatasetAccess<T> {
6060

6161
List<DataBlock<T>> readBlocks(PositionValueAccess pva, List<long[]> positions) throws N5IOException;
6262

63+
/**
64+
* Attempt to read all blocks at the given {@code positions}, and return a list of the blocks that exist.
65+
* <p>
66+
* For non-sharded datasets, this checks if each block value in the KV store exists, and read those that do.
67+
* For sharded datasets, this reads the shard index first to determine which blocks exist,
68+
* then reads only those blocks.
69+
*
70+
* @param pva
71+
* the position value access
72+
* @param positions
73+
* the list of block grid positions to check
74+
* @return list of blocks that exist (excludes non-existent blocks)
75+
* @throws N5IOException
76+
* if an error occurs
77+
*/
78+
List<DataBlock<T>> readBlocksExists(PositionValueAccess pva, List<long[]> positions) throws N5IOException;
79+
6380
void writeBlocks(PositionValueAccess pva, List<DataBlock<T>> blocks) throws N5IOException;
6481

6582
// TODO:

src/main/java/org/janelia/saalfeldlab/n5/shard/DefaultDatasetAccess.java

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,122 @@ public List<DataBlock<T>> readBlocks(PositionValueAccess pva, List<long[]> posit
127127
return blocks;
128128
}
129129

130+
/**
131+
* For a Non-Sharded dataset, reads all existing data blocks from the given positions.
132+
* The method checks if a block exists at a given position before attempting to read it.
133+
*
134+
* @param pva a {@code PositionValueAccess} instance to access data based on positions
135+
* @param positions a list of long arrays representing the grid positions of the blocks to be read
136+
* @return a list of {@code DataBlock<T>} objects for all valid and existing blocks at the specified positions
137+
*/
138+
private List<DataBlock<T>> readBlocksExistsNonSharded(PositionValueAccess pva, List<long[]> positions) {
139+
final ArrayList<DataBlock<T>> blocks = new ArrayList<>();
140+
for (long[] pos : positions) {
141+
final NestedPosition position = grid.nestedPosition(pos);
142+
if (pva.exists(position.key())) {
143+
final DataBlock<T> block = readBlock(pva, pos);
144+
if (block != null)
145+
blocks.add(block);
146+
}
147+
}
148+
return blocks;
149+
}
150+
151+
/**
152+
* For a Sharded Dataset, reads all existing data blocks from the given positions.
153+
* The method groups blocks by shard, checks shard existence, reads shard data,
154+
* and determines which blocks within the shard exist before returning them.
155+
* If the KVA supports partial reads, this should take advantage of reading
156+
* the index only to check existence and only read blocks that exist.
157+
*
158+
* @param pva a {@code PositionValueAccess} instance to access data based on shard keys
159+
* @param positions a list of long arrays representing the grid positions of the blocks to be read
160+
* @return a list of {@code DataBlock<T>} objects for all valid and existing blocks at the specified positions
161+
*/
162+
private List<DataBlock<T>> readBlocksExistsSharded(PositionValueAccess pva, List<long[]> positions) {
163+
/* group blocks by shard, read shard index to determine which blocks exist */
164+
final List<NestedPosition> blockPositions = positions.stream().map(grid::nestedPosition).collect(Collectors.toList());
165+
final int outermostLevel = grid.numLevels() - 1;
166+
final Collection<List<NestedPosition>> blocksPerOutermostShard = groupInnerPositions(grid, blockPositions, outermostLevel);
167+
168+
final ArrayList<DataBlock<T>> blocks = new ArrayList<>();
169+
for (List<NestedPosition> blocksInSingleShard : blocksPerOutermostShard) {
170+
if (blocksInSingleShard.isEmpty())
171+
continue;
172+
173+
final NestedPosition firstBlock = blocksInSingleShard.get(0);
174+
final long[] shardKey = firstBlock.key();
175+
176+
/* check if the shard key value exists */
177+
boolean shardExists = pva.exists(shardKey);
178+
if (!shardExists)
179+
continue;
180+
181+
182+
final ReadData shardData = pva.get(shardKey);
183+
final List<DataBlock<T>> shardBlocks = readBlocksExistsShardedRecursive(shardData, blocksInSingleShard, outermostLevel);
184+
blocks.addAll(shardBlocks);
185+
}
186+
return blocks;
187+
}
188+
189+
/**
190+
* Read only the blocks that exist within a shard by checking the shard index first.
191+
*/
192+
private List<DataBlock<T>> readBlocksExistsShardedRecursive(
193+
final ReadData readData,
194+
final List<NestedPosition> positions,
195+
final int level) {
196+
197+
if (readData == null || level == 0 || positions.isEmpty())
198+
return Collections.emptyList();
199+
200+
final NestedPosition firstBlock = positions.get(0);
201+
final long[] shardPosition = firstBlock.absolute(level);
202+
203+
final BlockCodec<RawShard> codec = (BlockCodec<RawShard>) codecs[level];
204+
final RawShard shard = codec.decode(readData, shardPosition).getData();
205+
206+
final ArrayList<DataBlock<T>> blocks = new ArrayList<>();
207+
if (level == 1) {
208+
/* base case: check the index and read blocks that exist */
209+
for (NestedPosition blockPosition : positions) {
210+
final long[] elementPos = blockPosition.relative(0);
211+
if (shard.index().get(elementPos) != null) {
212+
final ReadData elementData = shard.getElementData(elementPos);
213+
final DataBlock<T> block = readBlockRecursive(elementData, blockPosition, 0);
214+
if (block != null)
215+
blocks.add(block);
216+
}
217+
}
218+
} else {
219+
/* nested shards: group by next level and recurse */
220+
final Collection<List<NestedPosition>> nextLevelShards = groupInnerPositions(grid, positions, level - 1);
221+
for (List<NestedPosition> innerPositions : nextLevelShards) {
222+
if (innerPositions.isEmpty())
223+
continue;
224+
final NestedPosition firstInner = innerPositions.get(0);
225+
final long[] innerShardPos = firstInner.relative(level - 1);
226+
if (shard.index().get(innerShardPos) != null) {
227+
final ReadData innerShardData = shard.getElementData(innerShardPos);
228+
blocks.addAll(readBlocksExistsShardedRecursive(innerShardData, innerPositions, level - 1));
229+
}
230+
}
231+
}
232+
233+
return blocks;
234+
}
235+
236+
@Override
237+
public List<DataBlock<T>> readBlocksExists(PositionValueAccess pva, List<long[]> positions) throws N5IOException {
238+
239+
boolean unsharded = grid.numLevels() == 1;
240+
if (unsharded)
241+
return readBlocksExistsNonSharded(pva, positions);
242+
else
243+
return readBlocksExistsSharded(pva, positions);
244+
}
245+
130246
/**
131247
* Bulk Read operation on a shard. `positions` MUST all be in the same shard.
132248
* That is, for each `position` in `positions`, `position.absolute(level)` must be the same.

src/main/java/org/janelia/saalfeldlab/n5/shard/PositionValueAccess.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public interface PositionValueAccess {
5959

6060
void put(long[] key, ReadData data) throws N5Exception.N5IOException;
6161

62+
boolean exists(long[] key) throws N5Exception.N5IOException;
63+
6264
boolean remove(long[] key) throws N5Exception.N5IOException;
6365

6466
public static PositionValueAccess fromKva(
@@ -105,6 +107,11 @@ public ReadData get(long[] key) throws N5IOException {
105107
return kva.createReadData(absolutePath(key));
106108
}
107109

110+
@Override
111+
public boolean exists(long[] key) throws N5IOException {
112+
return kva.isFile(absolutePath(key));
113+
}
114+
108115
@Override
109116
public void put(long[] key, ReadData data) throws N5IOException {
110117

0 commit comments

Comments
 (0)