Skip to content

Commit b1b51b0

Browse files
authored
Merge pull request #192 from saalfeldlab/mergeDevelopmentRebaseOnMaster
Align development and master histories by rebasing development onto master
2 parents 0e4fe69 + 9810190 commit b1b51b0

38 files changed

Lines changed: 2650 additions & 501 deletions

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ on:
44
push:
55
branches:
66
- master
7+
- development
78
tags:
89
- "*-[0-9]+.*"
910
pull_request:
1011
branches:
1112
- master
13+
- development
1214

1315
jobs:
1416
build:

LICENSE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## BSD 2-Clause License
22

3-
Copyright (c) 2017-2025, Stephan Saalfeld
3+
Copyright (c) 2017-2026, Stephan Saalfeld
44

55
All rights reserved.
66

pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@
175175
<groupId>commons-io</groupId>
176176
<artifactId>commons-io</artifactId>
177177
</dependency>
178+
<dependency>
179+
<groupId>commons-codec</groupId>
180+
<artifactId>commons-codec</artifactId>
181+
</dependency>
178182

179183
<!-- Test dependencies -->
180184
<dependency>

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

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.google.gson.JsonDeserializationContext;
3232
import com.google.gson.JsonDeserializer;
3333
import com.google.gson.JsonElement;
34+
import com.google.gson.JsonNull;
3435
import com.google.gson.JsonObject;
3536
import com.google.gson.JsonParseException;
3637
import com.google.gson.JsonSerializationContext;
@@ -52,6 +53,8 @@
5253
import java.util.stream.Collectors;
5354

5455
import org.janelia.saalfeldlab.n5.codec.DataCodecInfo;
56+
import org.janelia.saalfeldlab.n5.codec.DatasetCodec;
57+
import org.janelia.saalfeldlab.n5.codec.DatasetCodecInfo;
5558

5659

5760
/**
@@ -95,23 +98,30 @@ public class DatasetAttributes implements Serializable {
9598

9699
private final DataType dataType;
97100

101+
private final JsonElement defaultValue;
102+
98103
private final BlockCodecInfo blockCodecInfo;
99104
private final DataCodecInfo[] dataCodecInfos;
105+
private final DatasetCodecInfo[] datasetCodecInfos;
100106

101107
private transient final DatasetAccess<?> access;
102108

103109
public DatasetAttributes(
104110
final long[] dimensions,
105111
final int[] outerBlockSize,
106112
final DataType dataType,
113+
final JsonElement defaultValue,
107114
final BlockCodecInfo blockCodecInfo,
115+
final DatasetCodecInfo[] datasetCodecInfos,
108116
final DataCodecInfo... dataCodecInfos) {
109117

110118
this.dimensions = dimensions;
111119
this.dataType = dataType;
112120
this.outerBlockSize = outerBlockSize;
121+
this.defaultValue = defaultValue == null ? JsonNull.INSTANCE : defaultValue;
113122

114123
this.blockCodecInfo = blockCodecInfo == null ? defaultBlockCodecInfo() : blockCodecInfo;
124+
this.datasetCodecInfos = datasetCodecInfos;
115125

116126
if (dataCodecInfos == null)
117127
this.dataCodecInfos = new DataCodecInfo[0];
@@ -124,6 +134,28 @@ public DatasetAttributes(
124134
blockSize = access.getGrid().getBlockSize(0);
125135
}
126136

137+
public DatasetAttributes(
138+
final long[] dimensions,
139+
final int[] outerBlockSize,
140+
final DataType dataType,
141+
final BlockCodecInfo blockCodecInfo,
142+
final DatasetCodecInfo[] datasetCodecInfos,
143+
final DataCodecInfo... dataCodecInfos) {
144+
145+
this(dimensions, outerBlockSize, dataType, JsonNull.INSTANCE,
146+
blockCodecInfo, datasetCodecInfos, dataCodecInfos);
147+
}
148+
149+
public DatasetAttributes(
150+
final long[] dimensions,
151+
final int[] outerBlockSize,
152+
final DataType dataType,
153+
final BlockCodecInfo blockCodecInfo,
154+
final DataCodecInfo... dataCodecInfos) {
155+
156+
this(dimensions, outerBlockSize, dataType, blockCodecInfo, null, dataCodecInfos);
157+
}
158+
127159
/**
128160
* Constructs a DatasetAttributes instance with specified dimensions, block size, data type,
129161
* and single compressor with default codec.
@@ -165,7 +197,7 @@ protected DatasetAccess<?> createDatasetAccess() {
165197
// The inner-most codec (the DataBlock codec) is at index 0.
166198
final int[][] blockSizes = new int[m][];
167199

168-
// NestedGrid validates block sizes, so instantiate it before creating the blockCodecs
200+
// NestedGrid validates block sizes, so instantiate it before creating the blockCodecs
169201
// blockCodecInfo.create below could fail unexpecedly with invalid
170202
// blockSizes so validate first
171203
blockSizes[m - 1] = outerBlockSize;
@@ -179,17 +211,23 @@ protected DatasetAccess<?> createDatasetAccess() {
179211
BlockCodecInfo currentBlockCodecInfo = blockCodecInfo;
180212
DataCodecInfo[] currentDataCodecInfos = dataCodecInfos;
181213

182-
final NestedGrid grid = new NestedGrid(blockSizes);
214+
final NestedGrid grid = new NestedGrid(blockSizes, dimensions);
183215
final BlockCodec<?>[] blockCodecs = new BlockCodec[m];
184216
for (int l = m - 1; l >= 0; --l) {
185217
blockCodecs[l] = currentBlockCodecInfo.create(dataType, blockSizes[l], currentDataCodecInfos);
186218
if (l > 0) {
187-
final ShardCodecInfo info = (ShardCodecInfo)currentBlockCodecInfo;
219+
final ShardCodecInfo info = (ShardCodecInfo) currentBlockCodecInfo;
188220
currentBlockCodecInfo = info.getInnerBlockCodecInfo();
189221
currentDataCodecInfos = info.getInnerDataCodecInfos();
190222
}
191223
}
192224

225+
if (datasetCodecInfos != null) {
226+
for (final DatasetCodecInfo info : datasetCodecInfos) {
227+
blockCodecs[0] = DatasetCodec.concatenate(info.create(this), (BlockCodec) blockCodecs[0]);
228+
}
229+
}
230+
193231
return new DefaultDatasetAccess<>(grid, blockCodecs);
194232
}
195233

@@ -202,7 +240,6 @@ private static int nestingDepth(BlockCodecInfo info) {
202240
}
203241
}
204242

205-
206243
protected BlockCodecInfo defaultBlockCodecInfo() {
207244

208245
return new N5BlockCodecInfo();
@@ -223,6 +260,11 @@ public int[] getBlockSize() {
223260
return blockSize;
224261
}
225262

263+
public JsonElement getDefaultValue() {
264+
265+
return defaultValue;
266+
}
267+
226268
public boolean isSharded() {
227269

228270
return blockCodecInfo instanceof ShardCodecInfo;
@@ -284,6 +326,11 @@ public DataCodecInfo[] getDataCodecInfos() {
284326
return dataCodecInfos;
285327
}
286328

329+
public DatasetCodecInfo[] getDatasetCodecInfos() {
330+
331+
return datasetCodecInfos;
332+
}
333+
287334
public String relativeBlockPath(long... position) {
288335

289336
return Arrays.stream(position).mapToObj(Long::toString).collect(Collectors.joining("/"));

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,16 @@ 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+
}
151163
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ default <T> void writeRegion(
222222
final boolean writeFully) throws N5Exception {
223223
try {
224224
final PositionValueAccess posKva = PositionValueAccess.fromKva(getKeyValueAccess(), getURI(), N5URI.normalizeGroupPath(datasetPath), datasetAttributes);
225-
datasetAttributes.<T>getDatasetAccess().writeRegion(posKva, min, size, dataBlocks, datasetAttributes.getDimensions(), writeFully);
225+
datasetAttributes.<T>getDatasetAccess().writeRegion(posKva, min, size, dataBlocks, writeFully);
226226
} catch (final UncheckedIOException e) {
227227
throw new N5IOException(
228228
"Failed to write blocks into dataset " + datasetPath, e);
@@ -240,7 +240,7 @@ default <T> void writeRegion(
240240
final ExecutorService exec) throws N5Exception, InterruptedException, ExecutionException {
241241
try {
242242
final PositionValueAccess posKva = PositionValueAccess.fromKva(getKeyValueAccess(), getURI(), N5URI.normalizeGroupPath(datasetPath), datasetAttributes);
243-
datasetAttributes.<T>getDatasetAccess().writeRegion(posKva, min, size, dataBlocks, datasetAttributes.getDimensions(), writeFully, exec);
243+
datasetAttributes.<T>getDatasetAccess().writeRegion(posKva, min, size, dataBlocks, writeFully, exec);
244244
} catch (final UncheckedIOException e) {
245245
throw new N5IOException(
246246
"Failed to write blocks into dataset " + datasetPath, e);
@@ -276,6 +276,7 @@ default <T> void writeBlock(
276276
"Failed to write block " + Arrays.toString(dataBlock.getGridPosition()) + " into dataset " + path,
277277
e);
278278
}
279+
279280
}
280281

281282
@Override
@@ -300,4 +301,4 @@ default boolean deleteBlock(
300301
final PositionValueAccess posKva = PositionValueAccess.fromKva(getKeyValueAccess(), getURI(), N5URI.normalizeGroupPath(path), datasetAttributes);
301302
return datasetAttributes.getDatasetAccess().deleteBlock(posKva, gridPosition);
302303
}
303-
}
304+
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,27 @@ 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;
334355
/**
335356
* Load a {@link DataBlock} as a {@link Serializable}. The offset is given
336357
* in

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
*/
2929
package org.janelia.saalfeldlab.n5;
3030

31+
3132
import java.io.ByteArrayOutputStream;
3233
import java.io.IOException;
3334
import java.io.ObjectOutputStream;
@@ -196,39 +197,46 @@ default boolean remove() throws N5Exception {
196197

197198
/**
198199
* Creates a dataset. This does not create any data but the path and
199-
* mandatory attributes only.
200+
* mandatory attributes only. The returned DatasetAttributes should be used
201+
* for future read/write operations on this dataset. It may not be the same
202+
* DatasetAttributes object that was provided, depending on the implementation.
200203
*
201204
* @param datasetPath dataset path
202205
* @param datasetAttributes the dataset attributes
203-
* @throws N5Exception the exception
206+
* @return DatasetAttributes optimal attributes object to be used for read/write operations
207+
* @throws N5Exception
204208
*/
205-
default void createDataset(
209+
default DatasetAttributes createDataset(
206210
final String datasetPath,
207211
final DatasetAttributes datasetAttributes) throws N5Exception {
208212

209213
final String normalPath = N5URI.normalizeGroupPath(datasetPath);
210214
createGroup(normalPath);
211215
setDatasetAttributes(normalPath, datasetAttributes);
216+
return datasetAttributes;
212217
}
213218

214219
/**
215220
* Creates a dataset. This does not create any data but the path and
216-
* mandatory attributes only.
221+
* mandatory attributes only. Returns the DatasetAttributes object to be
222+
* used for future read/write operations on this dataset.
217223
*
218224
* @param datasetPath dataset path
219225
* @param dimensions the dataset dimensions
220226
* @param blockSize the block size
221227
* @param dataType the data type
222228
* @param compression the compression
229+
* @return DatasetAttributes optimal attributes object to be used for read/write operations
230+
* @throws N5Exception
223231
*/
224-
default void createDataset(
232+
default DatasetAttributes createDataset(
225233
final String datasetPath,
226234
final long[] dimensions,
227235
final int[] blockSize,
228236
final DataType dataType,
229237
final Compression compression) throws N5Exception {
230238

231-
createDataset(datasetPath, new DatasetAttributes(dimensions, blockSize, dataType, compression));
239+
return createDataset(datasetPath, new DatasetAttributes(dimensions, blockSize, dataType, compression));
232240
}
233241

234242
/**

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

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,3 @@
1-
/*-
2-
* #%L
3-
* Not HDF5
4-
* %%
5-
* Copyright (C) 2017 - 2025 Stephan Saalfeld
6-
* %%
7-
* Redistribution and use in source and binary forms, with or without
8-
* modification, are permitted provided that the following conditions are met:
9-
*
10-
* 1. Redistributions of source code must retain the above copyright notice,
11-
* this list of conditions and the following disclaimer.
12-
* 2. Redistributions in binary form must reproduce the above copyright notice,
13-
* this list of conditions and the following disclaimer in the documentation
14-
* and/or other materials provided with the distribution.
15-
*
16-
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17-
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18-
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19-
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20-
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21-
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22-
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23-
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24-
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25-
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26-
* POSSIBILITY OF SUCH DAMAGE.
27-
* #L%
28-
*/
291
/**
302
* Copyright (c) 2017, Stephan Saalfeld
313
* All rights reserved.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.janelia.saalfeldlab.n5.codec;
2+
3+
import org.janelia.saalfeldlab.n5.DataBlock;
4+
import org.janelia.saalfeldlab.n5.N5Exception.N5IOException;
5+
import org.janelia.saalfeldlab.n5.readdata.ReadData;
6+
7+
/**
8+
* A Codec that transforms the contents of a {@link DataBlock}.
9+
* <p>
10+
* This class is N5's analogue to Zarr's array-to-array codec.
11+
*
12+
* @param <S> source data type (data contained in decoded blocks)
13+
* @param <T> target data type (data contained in encoded blocks)
14+
*/
15+
public interface DatasetCodec<S, T> {
16+
17+
// TODO Name ideas:
18+
// "ImageCodec"?
19+
// BlockTransformationCodec
20+
21+
DataBlock<T> encode(DataBlock<S> block) throws N5IOException;
22+
23+
DataBlock<S> decode(DataBlock<T> dataBlock) throws N5IOException;
24+
25+
/**
26+
* Create a {@code BlockCodec} that, for encoding, first applies {@code
27+
* datasetCodec} and then {@code blockCodec} (and does the same in reverse
28+
* order for decoding).
29+
*
30+
* @return the concatenated BlockCodec
31+
*/
32+
static <S, T> BlockCodec<S> concatenate(final DatasetCodec<S, T> datasetCodec, final BlockCodec<T> blockCodec) {
33+
34+
return new BlockCodec<S>() {
35+
36+
@Override
37+
public ReadData encode(final DataBlock<S> dataBlock) throws N5IOException {
38+
return blockCodec.encode(datasetCodec.encode(dataBlock));
39+
}
40+
41+
@Override
42+
public DataBlock<S> decode(final ReadData readData, final long[] gridPosition) throws N5IOException {
43+
return datasetCodec.decode(blockCodec.decode(readData, gridPosition));
44+
}
45+
};
46+
}
47+
}

0 commit comments

Comments
 (0)