From 25d8169e67660ac67da2a3d096db1dc150a06c7d Mon Sep 17 00:00:00 2001 From: brokkoli71 Date: Fri, 23 Jan 2026 12:01:25 +0100 Subject: [PATCH 1/2] test and fix IndexingUtils.computeProjection --- .../zarr/zarrjava/utils/IndexingUtils.java | 12 ++++- .../java/dev/zarr/zarrjava/TestUtils.java | 49 +++++++++++++++++++ .../java/dev/zarr/zarrjava/ZarrV3Test.java | 40 ++++++++++++++- 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/main/java/dev/zarr/zarrjava/utils/IndexingUtils.java b/src/main/java/dev/zarr/zarrjava/utils/IndexingUtils.java index b108aca..a885813 100644 --- a/src/main/java/dev/zarr/zarrjava/utils/IndexingUtils.java +++ b/src/main/java/dev/zarr/zarrjava/utils/IndexingUtils.java @@ -87,7 +87,7 @@ public static ChunkProjection computeProjection( if (selOffset[dimIdx] + selShape[dimIdx] > dimLimit) { // selection ends after current chunk - shape[dimIdx] = (int) (chunkShape[dimIdx] - (selOffset[dimIdx] % chunkShape[dimIdx])); + shape[dimIdx] = (int) (chunkShape[dimIdx] - chunkOffset[dimIdx]); } else { // selection ends within current chunk shape[dimIdx] = (int) (selOffset[dimIdx] + selShape[dimIdx] - dimOffset @@ -187,5 +187,15 @@ public ChunkProjection( this.outOffset = outOffset; this.shape = shape; } + + @Override + public String toString() { + return "ChunkProjection{" + + "chunkCoords=" + Arrays.toString(chunkCoords) + + ", chunkOffset=" + Arrays.toString(chunkOffset) + + ", outOffset=" + Arrays.toString(outOffset) + + ", shape=" + Arrays.toString(shape) + + '}'; + } } } \ No newline at end of file diff --git a/src/test/java/dev/zarr/zarrjava/TestUtils.java b/src/test/java/dev/zarr/zarrjava/TestUtils.java index 2a49f6b..582dfab 100644 --- a/src/test/java/dev/zarr/zarrjava/TestUtils.java +++ b/src/test/java/dev/zarr/zarrjava/TestUtils.java @@ -1,11 +1,13 @@ package dev.zarr.zarrjava; +import dev.zarr.zarrjava.utils.IndexingUtils; import org.junit.Test; import org.junit.jupiter.api.Assertions; import java.util.Arrays; +import static dev.zarr.zarrjava.utils.IndexingUtils.computeChunkCoords; import static dev.zarr.zarrjava.utils.Utils.inversePermutation; import static dev.zarr.zarrjava.utils.Utils.isPermutation; @@ -27,5 +29,52 @@ public void testInversePermutation() { Assertions.assertFalse(Arrays.equals(new int[]{2, 0, 1}, inversePermutation(new int[]{2, 0, 1}))); } + @Test + public void testComputeChunkCoords(){ + long[] arrayShape = new long[]{100, 100}; + int[] chunkShape = new int[]{30, 30}; + long[] selOffset = new long[]{50, 20}; + int[] selShape = new int[]{20, 1}; + long[][] chunkCoords = computeChunkCoords(arrayShape, chunkShape, selOffset, selShape); + long[][] expectedChunkCoords = new long[][]{ + {1, 0}, + {2, 0}, + }; + Assertions.assertArrayEquals(expectedChunkCoords, chunkCoords); + + arrayShape = new long[]{1, 52}; + chunkShape = new int[]{1, 17}; + selOffset = new long[]{0, 32}; + selShape = new int[]{1, 20}; + chunkCoords = computeChunkCoords(arrayShape, chunkShape, selOffset, selShape); + expectedChunkCoords = new long[][]{ + {0, 1}, + {0, 2}, + {0, 3}, + }; + Assertions.assertArrayEquals(expectedChunkCoords, chunkCoords); + } + + @Test + public void testComputeProjection(){ + // chunk (0,2) contains indexes 34-50 along axis 1 + // thus the overlap with selection 32-52 is 34-50 + // which is offset 2 in the selection and offset 0 in the chunk + // and has full chunk length 17 + final long[] chunkCoords = new long[]{0, 2}; + final long[] arrayShape = new long[]{1, 52}; + final int[] chunkShape = new int[]{1, 17}; + final long[] selOffset = new long[]{0, 32}; + final int[] selShape = new int[]{1, 20}; + + IndexingUtils.ChunkProjection projection = IndexingUtils.computeProjection( + chunkCoords, arrayShape, chunkShape, selOffset, selShape + ); + Assertions.assertArrayEquals(chunkCoords, projection.chunkCoords); + Assertions.assertArrayEquals(new int[]{0,0}, projection.chunkOffset); + Assertions.assertArrayEquals(new int[]{0,2}, projection.outOffset); + Assertions.assertArrayEquals(new int[]{1, 17}, projection.shape); + } + } diff --git a/src/test/java/dev/zarr/zarrjava/ZarrV3Test.java b/src/test/java/dev/zarr/zarrjava/ZarrV3Test.java index 9a669be..02781cc 100644 --- a/src/test/java/dev/zarr/zarrjava/ZarrV3Test.java +++ b/src/test/java/dev/zarr/zarrjava/ZarrV3Test.java @@ -26,6 +26,7 @@ import java.io.BufferedReader; import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; @@ -735,4 +736,41 @@ public void testGroupAttributes() throws IOException, ZarrException { group = Group.open(storeHandle); Assertions.assertEquals("group_value", group.metadata().attributes().getString("group_attr")); } -} + + @Test + public void testTemp() throws ZarrException, IOException { + String filePath = TESTOUTPUT + "/testTempV3.txt"; + int imageWidth = 52; + int imageHeight = 1; + int chunkWidth = 17; + Array input = Array.create( + new FilesystemStore(filePath).resolve("0"), + Array.metadataBuilder() + .withShape(imageHeight, imageWidth) + .withDataType(DataType.UINT8) + .withChunkShape(imageHeight, chunkWidth) + .withFillValue(0) + .build() + ); + + byte[] buf = new byte[imageWidth]; + for (int i = 0; i < buf.length; i++) { + buf[i] = (byte) i; + } + ByteBuffer bytes = ByteBuffer.wrap(buf); + ucar.ma2.Array data = ucar.ma2.Array.factory(ucar.ma2.DataType.BYTE, new int[]{imageHeight, imageWidth}, bytes); + input.write(new long[]{0, 0}, data); + + long[] readPosition = new long[]{0, 0}; + int[] readSize = new int[]{1, 32}; + for (int tile = 0; tile < buf.length; tile += readSize[1]) { + readPosition[1] = tile; + readSize[1] = (int) Math.min(readSize[1], buf.length - readPosition[1]); + ucar.ma2.Array readTile = input.read(readPosition, readSize); + for (int i = 0; i < readSize[1]; i++) { + byte pixel = readTile.getByte(i); + Assertions.assertEquals(buf[tile + i], pixel); + } + } + } +} \ No newline at end of file From 91778d84d25207c2df1c4d0089c24a2af6776712 Mon Sep 17 00:00:00 2001 From: brokkoli71 Date: Fri, 23 Jan 2026 12:52:45 +0100 Subject: [PATCH 2/2] add tests --- .../zarr/zarrjava/utils/IndexingUtils.java | 2 +- .../java/dev/zarr/zarrjava/ZarrV3Test.java | 60 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/main/java/dev/zarr/zarrjava/utils/IndexingUtils.java b/src/main/java/dev/zarr/zarrjava/utils/IndexingUtils.java index a885813..9ff0764 100644 --- a/src/main/java/dev/zarr/zarrjava/utils/IndexingUtils.java +++ b/src/main/java/dev/zarr/zarrjava/utils/IndexingUtils.java @@ -87,7 +87,7 @@ public static ChunkProjection computeProjection( if (selOffset[dimIdx] + selShape[dimIdx] > dimLimit) { // selection ends after current chunk - shape[dimIdx] = (int) (chunkShape[dimIdx] - chunkOffset[dimIdx]); + shape[dimIdx] = chunkShape[dimIdx] - chunkOffset[dimIdx]; } else { // selection ends within current chunk shape[dimIdx] = (int) (selOffset[dimIdx] + selShape[dimIdx] - dimOffset diff --git a/src/test/java/dev/zarr/zarrjava/ZarrV3Test.java b/src/test/java/dev/zarr/zarrjava/ZarrV3Test.java index 02781cc..8390c3c 100644 --- a/src/test/java/dev/zarr/zarrjava/ZarrV3Test.java +++ b/src/test/java/dev/zarr/zarrjava/ZarrV3Test.java @@ -6,6 +6,7 @@ import dev.zarr.zarrjava.core.Attributes; import dev.zarr.zarrjava.store.FilesystemStore; import dev.zarr.zarrjava.store.HttpStore; +import dev.zarr.zarrjava.store.MemoryStore; import dev.zarr.zarrjava.store.StoreHandle; import dev.zarr.zarrjava.utils.MultiArrayUtils; import dev.zarr.zarrjava.v3.*; @@ -26,7 +27,6 @@ import java.io.BufferedReader; import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; @@ -107,6 +107,17 @@ static Stream> chunkKeyEnco return Stream.concat(builders, codecBuilders().map(codecFunc -> b -> b.withCodecs(codecFunc))); } + static Stream unalignedArrayAccessProvider() { + Stream.Builder builder = Stream.builder(); + builder.add(Arguments.of(52, 17, 32)); + builder.add(Arguments.of(71, 11, 12)); + builder.add(Arguments.of(52, 17, 17)); + builder.add(Arguments.of(50, 3, 7)); + builder.add(Arguments.of(50, 3, 22)); + builder.add(Arguments.of(13, 31, 21)); + return builder.build(); + } + @ParameterizedTest @MethodSource("invalidCodecBuilder") public void testCheckInvalidCodecConfiguration(Function codecBuilder) { @@ -737,40 +748,29 @@ public void testGroupAttributes() throws IOException, ZarrException { Assertions.assertEquals("group_value", group.metadata().attributes().getString("group_attr")); } - @Test - public void testTemp() throws ZarrException, IOException { - String filePath = TESTOUTPUT + "/testTempV3.txt"; - int imageWidth = 52; - int imageHeight = 1; - int chunkWidth = 17; - Array input = Array.create( - new FilesystemStore(filePath).resolve("0"), + @ParameterizedTest + @MethodSource("unalignedArrayAccessProvider") + public void testUnalignedArrayAccess(int arrayShape, int chunkShape, int accessShape) throws ZarrException, IOException { + Array array = Array.create( + new MemoryStore().resolve(), Array.metadataBuilder() - .withShape(imageHeight, imageWidth) - .withDataType(DataType.UINT8) - .withChunkShape(imageHeight, chunkWidth) + .withShape(arrayShape) + .withDataType(DataType.UINT32) + .withChunkShape(chunkShape) .withFillValue(0) .build() ); - byte[] buf = new byte[imageWidth]; - for (int i = 0; i < buf.length; i++) { - buf[i] = (byte) i; - } - ByteBuffer bytes = ByteBuffer.wrap(buf); - ucar.ma2.Array data = ucar.ma2.Array.factory(ucar.ma2.DataType.BYTE, new int[]{imageHeight, imageWidth}, bytes); - input.write(new long[]{0, 0}, data); - - long[] readPosition = new long[]{0, 0}; - int[] readSize = new int[]{1, 32}; - for (int tile = 0; tile < buf.length; tile += readSize[1]) { - readPosition[1] = tile; - readSize[1] = (int) Math.min(readSize[1], buf.length - readPosition[1]); - ucar.ma2.Array readTile = input.read(readPosition, readSize); - for (int i = 0; i < readSize[1]; i++) { - byte pixel = readTile.getByte(i); - Assertions.assertEquals(buf[tile + i], pixel); - } + int[] testData = new int[arrayShape]; + Arrays.setAll(testData, p -> (byte) p); + ucar.ma2.Array data = ucar.ma2.Array.factory(ucar.ma2.DataType.UINT, new int[]{arrayShape}, testData); + array.write(data); + + for (int i = 0; i < arrayShape; i += accessShape) { + accessShape = Math.min(accessShape, arrayShape - i); + ucar.ma2.Array result = array.read(new long[]{i}, new int[]{accessShape}); + int[] expectedData = Arrays.copyOfRange(testData, i, i + accessShape); + Assertions.assertArrayEquals(expectedData, (int[]) result.get1DJavaArray(ucar.ma2.DataType.UINT)); } } } \ No newline at end of file