diff --git a/src/main/java/org/janelia/saalfeldlab/n5/DatasetAttributes.java b/src/main/java/org/janelia/saalfeldlab/n5/DatasetAttributes.java index 0b30076d..0696559e 100644 --- a/src/main/java/org/janelia/saalfeldlab/n5/DatasetAttributes.java +++ b/src/main/java/org/janelia/saalfeldlab/n5/DatasetAttributes.java @@ -341,6 +341,88 @@ 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); + } + + 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 = DEFAULT_1D_BLOCK_SIZE.clone(); + else if (dimensions.length == 2) + blockSize = DEFAULT_2D_BLOCK_SIZE.clone(); + else if (dimensions.length == 3) + blockSize = DEFAULT_3D_BLOCK_SIZE.clone(); + else { + blockSize = new int[dimensions.length]; + Arrays.fill(blockSize, DEFAULT_ND_DIM_LEN); + } + 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));