From 0984e4bbe389d47b6b12a900881eafcd6cc737cc Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Tue, 5 May 2026 15:48:01 -0400 Subject: [PATCH 1/2] feat: add DatasetAttributes.builder --- .../saalfeldlab/n5/DatasetAttributes.java | 77 +++++++++++++++++++ .../saalfeldlab/n5/DatasetAttributesTest.java | 37 ++++++++- 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/janelia/saalfeldlab/n5/DatasetAttributes.java b/src/main/java/org/janelia/saalfeldlab/n5/DatasetAttributes.java index 0b30076d..491c760d 100644 --- a/src/main/java/org/janelia/saalfeldlab/n5/DatasetAttributes.java +++ b/src/main/java/org/janelia/saalfeldlab/n5/DatasetAttributes.java @@ -341,6 +341,83 @@ public HashMap asMap() { return map; } + public static Builder builder(final long[] dimensions, final DataType dataType) { + + return new Builder(dimensions, dataType); + } + + public static Builder builder(final DatasetAttributes attributes) { + + return new Builder(attributes); + } + + protected static int[] defaultBlockSize(final long[] dimensions) { + + final int[] blockSize; + if (dimensions.length == 1) + blockSize = new int[]{1024}; + else if (dimensions.length == 2) + blockSize = new int[]{256, 256}; + else if (dimensions.length == 3) + blockSize = new int[]{128, 128, 128}; + else { + blockSize = new int[dimensions.length]; + Arrays.fill(blockSize, 64); + } + for (int i = 0; i < blockSize.length; i++) + blockSize[i] = (int)Math.min(blockSize[i], dimensions[i]); + return blockSize; + } + + public static class Builder { + + private final long[] dimensions; + private final DataType dataType; + + private int[] blockSize; + private DataCodecInfo[] dataCodecInfos = new DataCodecInfo[0]; + + public Builder(final long[] dimensions, final DataType dataType) { + + this.dimensions = dimensions.clone(); + this.dataType = dataType; + this.blockSize = defaultBlockSize(dimensions); + } + + public Builder(final DatasetAttributes attributes) { + + this.dimensions = attributes.getDimensions(); + this.dataType = attributes.getDataType(); + this.blockSize = attributes.getBlockSize(); + this.dataCodecInfos = attributes.getDataCodecInfos(); + } + + public Builder blockSize(final int[] blockSize) { + + this.blockSize = blockSize.clone(); + return this; + } + + /** + * Sets the compression codec. Has no effect if {@code compression} is + * null or {@link RawCompression}. + * + * @param compression the compression to use + * @return this builder + */ + public Builder compression(final Compression compression) { + + if (compression != null && !(compression instanceof RawCompression)) + this.dataCodecInfos = new DataCodecInfo[]{compression}; + return this; + } + + public DatasetAttributes build() { + final int[] resolvedBlockSize = blockSize != null ? blockSize : defaultBlockSize(dimensions); + return new DatasetAttributes(dimensions, resolvedBlockSize, dataType, new N5BlockCodecInfo(), null, dataCodecInfos); + } + } + private static DatasetAttributesAdapter adapter = null; public static DatasetAttributesAdapter getJsonAdapter() { diff --git a/src/test/java/org/janelia/saalfeldlab/n5/DatasetAttributesTest.java b/src/test/java/org/janelia/saalfeldlab/n5/DatasetAttributesTest.java index 1b91a0ca..c26aa0f1 100644 --- a/src/test/java/org/janelia/saalfeldlab/n5/DatasetAttributesTest.java +++ b/src/test/java/org/janelia/saalfeldlab/n5/DatasetAttributesTest.java @@ -1,6 +1,8 @@ package org.janelia.saalfeldlab.n5; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -82,6 +84,39 @@ private static DatasetAttributes shardDatasetAttributes( return new DatasetAttributes(dimensions, shardSize, dataType, blockCodecInfo); } + @Test + public void builderTests() { + + final long[] dims = new long[]{100, 200, 300}; + final int[] blk = new int[]{32, 32, 32}; + + // default blockSize uses defaultBlockSize, not full dimensions + final DatasetAttributes defaultBlk = DatasetAttributes.builder(dims, DataType.FLOAT32).build(); + assertArrayEquals(DatasetAttributes.defaultBlockSize(dims), defaultBlk.getBlockSize()); + + // blockSize is reflected in output + final DatasetAttributes withBlk = DatasetAttributes.builder(dims, DataType.FLOAT32) + .blockSize(blk).build(); + assertArrayEquals(blk, withBlk.getBlockSize()); + assertFalse(withBlk.isSharded()); + + // compression sets a data codec; RawCompression is a no-op + final DatasetAttributes withGzip = DatasetAttributes.builder(dims, DataType.FLOAT32) + .blockSize(blk).compression(new GzipCompression()).build(); + assertEquals(1, withGzip.getDataCodecInfos().length); + + final DatasetAttributes withRaw = DatasetAttributes.builder(dims, DataType.FLOAT32) + .blockSize(blk).compression(new RawCompression()).build(); + assertEquals(0, withRaw.getDataCodecInfos().length); + + // round-trip through Builder(DatasetAttributes) + final DatasetAttributes roundTrip = DatasetAttributes.builder(withGzip).build(); + assertArrayEquals(withGzip.getDimensions(), roundTrip.getDimensions()); + assertArrayEquals(withGzip.getBlockSize(), roundTrip.getBlockSize()); + assertEquals(withGzip.getDataType(), roundTrip.getDataType()); + assertEquals(withGzip.getDataCodecInfos().length, roundTrip.getDataCodecInfos().length); + } + /** * Test that validateBlockShardSizes method rejects invalid shard and block size combinations. */ @@ -91,7 +126,7 @@ public void testValidateBlockShardSizesInvalid() { final long[] dimensions = new long[]{100, 200, 300}; final DataType dataType = DataType.UINT8; - // Block size too small + // Block size too smallcompression != null && !(compression instanceof RawCompression) IllegalArgumentException ex0 = assertThrows( IllegalArgumentException.class, () -> shardDatasetAttributes(dimensions, new int[]{1, 1, 1}, new int[]{1, 0, -1}, dataType)); From e25ef7b05518cc3faddb2500436307c28a0127b1 Mon Sep 17 00:00:00 2001 From: Caleb Hulbert Date: Wed, 6 May 2026 15:02:44 -0400 Subject: [PATCH 2/2] feat: change default block sizes in builder Signed-off-by: Caleb Hulbert --- .../janelia/saalfeldlab/n5/DatasetAttributes.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/janelia/saalfeldlab/n5/DatasetAttributes.java b/src/main/java/org/janelia/saalfeldlab/n5/DatasetAttributes.java index 491c760d..0696559e 100644 --- a/src/main/java/org/janelia/saalfeldlab/n5/DatasetAttributes.java +++ b/src/main/java/org/janelia/saalfeldlab/n5/DatasetAttributes.java @@ -351,18 +351,23 @@ public static Builder builder(final DatasetAttributes attributes) { return new Builder(attributes); } + private static final int[] DEFAULT_1D_BLOCK_SIZE = new int[]{65536}; + private static final int[] DEFAULT_2D_BLOCK_SIZE = new int[]{512,512}; + private static final int[] DEFAULT_3D_BLOCK_SIZE = new int[]{128,128,128}; + private static final int DEFAULT_ND_DIM_LEN = 64; + protected static int[] defaultBlockSize(final long[] dimensions) { final int[] blockSize; if (dimensions.length == 1) - blockSize = new int[]{1024}; + blockSize = DEFAULT_1D_BLOCK_SIZE.clone(); else if (dimensions.length == 2) - blockSize = new int[]{256, 256}; + blockSize = DEFAULT_2D_BLOCK_SIZE.clone(); else if (dimensions.length == 3) - blockSize = new int[]{128, 128, 128}; + blockSize = DEFAULT_3D_BLOCK_SIZE.clone(); else { blockSize = new int[dimensions.length]; - Arrays.fill(blockSize, 64); + Arrays.fill(blockSize, DEFAULT_ND_DIM_LEN); } for (int i = 0; i < blockSize.length; i++) blockSize[i] = (int)Math.min(blockSize[i], dimensions[i]);