Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 98 additions & 4 deletions src/main/java/dev/zarr/zarrjava/core/Array.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import dev.zarr.zarrjava.ZarrException;
import dev.zarr.zarrjava.core.codec.CodecPipeline;
import dev.zarr.zarrjava.store.FilesystemStore;
import dev.zarr.zarrjava.store.Store;
import dev.zarr.zarrjava.store.StoreHandle;
import dev.zarr.zarrjava.utils.IndexingUtils;
import dev.zarr.zarrjava.utils.MultiArrayUtils;
Expand All @@ -17,9 +16,6 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public abstract class Array extends AbstractNode {
Expand Down Expand Up @@ -178,6 +174,104 @@ public ucar.ma2.Array readChunk(long[] chunkCoords) throws ZarrException {
return codecPipeline.decode(chunkBytes);
}

/**
* Deletes chunks that are completely outside the new shape and trims boundary chunks.
*
* @param newShape the new shape of the array
*/
protected void cleanupChunksForResize(long[] newShape) {
ArrayMetadata metadata = metadata();
final int[] chunkShape = metadata.chunkShape();
final int ndim = metadata.ndim();
final dev.zarr.zarrjava.core.chunkkeyencoding.ChunkKeyEncoding chunkKeyEncoding = metadata.chunkKeyEncoding();

// Calculate max valid chunk coordinates for the new shape
long[] newMaxChunkCoords = new long[ndim];
for (int i = 0; i < ndim; i++) {
newMaxChunkCoords[i] = (newShape[i] + chunkShape[i] - 1) / chunkShape[i];
}

// Iterate over all possible chunk coordinates in the old shape
long[][] allOldChunkCoords = IndexingUtils.computeChunkCoords(metadata.shape, chunkShape);

for (long[] chunkCoords : allOldChunkCoords) {
boolean isOutsideBounds = false;
boolean isOnBoundary = false;

for (int dimIdx = 0; dimIdx < ndim; dimIdx++) {
if (chunkCoords[dimIdx] >= newMaxChunkCoords[dimIdx]) {
isOutsideBounds = true;
break;
}
// Check if this chunk is on the boundary (partially outside new shape)
long chunkEnd = (chunkCoords[dimIdx] + 1) * chunkShape[dimIdx];
if (chunkEnd > newShape[dimIdx]) {
isOnBoundary = true;
}
}

String[] chunkKeys = chunkKeyEncoding.encodeChunkKey(chunkCoords);
StoreHandle chunkHandle = storeHandle.resolve(chunkKeys);

if (isOutsideBounds) {
// Delete chunk that is completely outside
chunkHandle.delete();
} else if (isOnBoundary) {
// Trim boundary chunk - read, clear out-of-bounds data, write back
try {
trimBoundaryChunk(chunkCoords, newShape, chunkShape);
} catch (ZarrException e) {
throw new RuntimeException(e);
}
}
}
}

/**
* Trims a boundary chunk by reading it, clearing the out-of-bounds portion, and writing it back.
*
* @param chunkCoords the coordinates of the chunk to trim
* @param newShape the new shape of the array
* @param chunkShape the shape of the chunks
* @throws ZarrException if reading or writing the chunk fails
*/
protected void trimBoundaryChunk(long[] chunkCoords, long[] newShape, int[] chunkShape) throws ZarrException {
ArrayMetadata metadata = metadata();
final int ndim = metadata.ndim();

// Calculate the valid region within this chunk
int[] validShape = new int[ndim];
boolean needsTrimming = false;
for (int dimIdx = 0; dimIdx < ndim; dimIdx++) {
long chunkStart = chunkCoords[dimIdx] * chunkShape[dimIdx];
long chunkEnd = chunkStart + chunkShape[dimIdx];
if (chunkEnd > newShape[dimIdx]) {
validShape[dimIdx] = (int) (newShape[dimIdx] - chunkStart);
needsTrimming = true;
} else {
validShape[dimIdx] = chunkShape[dimIdx];
}
}

if (!needsTrimming) {
return;
}

// Read the existing chunk
ucar.ma2.Array chunkData = readChunk(chunkCoords);

// Create a new chunk filled with fill value
ucar.ma2.Array newChunkData = metadata.allocateFillValueChunk();

// Copy only the valid region
MultiArrayUtils.copyRegion(
chunkData, new int[ndim], newChunkData, new int[ndim], validShape
);

// Write the trimmed chunk back
writeChunk(chunkCoords, newChunkData);
}


/**
* Writes a ucar.ma2.Array into the Zarr array at the beginning of the Zarr array. The shape of
Expand Down
28 changes: 25 additions & 3 deletions src/main/java/dev/zarr/zarrjava/v2/Array.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,26 +198,47 @@ private Array writeMetadata(ArrayMetadata newArrayMetadata) throws ZarrException
}

/**
* Sets a new shape for the Zarr array. It only changes the metadata, no array data is modified or
* deleted. This method returns a new instance of the Zarr array class and the old instance
* Sets a new shape for the Zarr array. Old array data outside the new shape will be deleted.
* If data deletion is not desired, use {@link #resize(long[], boolean)} with
* `resizeMetadataOnly` set to true.
* This method returns a new instance of the Zarr array class and the old instance
* becomes invalid.
*
* @param newShape the new shape of the Zarr array
* @throws ZarrException if the new metadata is invalid
* @throws IOException throws IOException if the new metadata cannot be serialized
*/
public Array resize(long[] newShape) throws ZarrException, IOException {
return resize(newShape, false);
}

/**
* Sets a new shape for the Zarr array. This method returns a new instance of the Zarr array class
* and the old instance becomes invalid.
*
* @param newShape the new shape of the Zarr array
* @param resizeMetadataOnly if true, only the metadata is updated; if false, chunks outside the new
* bounds are deleted and boundary chunks are trimmed
* @throws ZarrException if the new metadata is invalid
* @throws IOException throws IOException if the new metadata cannot be serialized
*/
public Array resize(long[] newShape, boolean resizeMetadataOnly) throws ZarrException, IOException {
if (newShape.length != metadata.ndim()) {
throw new IllegalArgumentException(
"'newShape' needs to have rank '" + metadata.ndim() + "'.");
}

if (!resizeMetadataOnly) {
cleanupChunksForResize(newShape);
}

ArrayMetadata newArrayMetadata = ArrayMetadataBuilder.fromArrayMetadata(metadata)
.withShape(newShape)
.build();
return writeMetadata(newArrayMetadata);
}


/**
* Sets the attributes of the Zarr array. It overwrites and removes any existing attributes. This
* method returns a new instance of the Zarr array class and the old instance becomes invalid.
Expand Down Expand Up @@ -245,7 +266,8 @@ public Array setAttributes(Attributes newAttributes) throws ZarrException, IOExc
* @throws IOException throws IOException if the new metadata cannot be serialized
*/
public Array updateAttributes(Function<Attributes, Attributes> attributeMapper) throws ZarrException, IOException {
return setAttributes(attributeMapper.apply(metadata.attributes));
Attributes currentAttributes = metadata.attributes != null ? new Attributes(metadata.attributes) : new Attributes();
return setAttributes(attributeMapper.apply(currentAttributes));
}

@Override
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/dev/zarr/zarrjava/v2/Group.java
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ public Group setAttributes(Attributes newAttributes) throws ZarrException, IOExc
*/
public Group updateAttributes(Function<Attributes, Attributes> attributeMapper)
throws ZarrException, IOException {
return setAttributes(attributeMapper.apply(metadata.attributes));
Attributes currentAttributes = metadata.attributes != null ? new Attributes(metadata.attributes) : new Attributes();
return setAttributes(attributeMapper.apply(currentAttributes));
}


Expand Down
28 changes: 25 additions & 3 deletions src/main/java/dev/zarr/zarrjava/v3/Array.java
Original file line number Diff line number Diff line change
Expand Up @@ -201,26 +201,47 @@ private Array writeMetadata(ArrayMetadata newArrayMetadata) throws ZarrException
}

/**
* Sets a new shape for the Zarr array. It only changes the metadata, no array data is modified or
* deleted. This method returns a new instance of the Zarr array class and the old instance
* Sets a new shape for the Zarr array. Old array data outside the new shape will be deleted.
* If data deletion is not desired, use {@link #resize(long[], boolean)} with
* `resizeMetadataOnly` set to true.
* This method returns a new instance of the Zarr array class and the old instance
* becomes invalid.
*
* @param newShape the new shape of the Zarr array
* @throws ZarrException if the new metadata is invalid
* @throws IOException throws IOException if the new metadata cannot be serialized
*/
public Array resize(long[] newShape) throws ZarrException, IOException {
return resize(newShape, false);
}

/**
* Sets a new shape for the Zarr array. This method returns a new instance of the Zarr array class
* and the old instance becomes invalid.
*
* @param newShape the new shape of the Zarr array
* @param resizeMetadataOnly if true, only the metadata is updated; if false, chunks outside the new
* bounds are deleted and boundary chunks are trimmed
* @throws ZarrException if the new metadata is invalid
* @throws IOException throws IOException if the new metadata cannot be serialized
*/
public Array resize(long[] newShape, boolean resizeMetadataOnly) throws ZarrException, IOException {
if (newShape.length != metadata.ndim()) {
throw new IllegalArgumentException(
"'newShape' needs to have rank '" + metadata.ndim() + "'.");
}

if (!resizeMetadataOnly) {
cleanupChunksForResize(newShape);
}

ArrayMetadata newArrayMetadata = ArrayMetadataBuilder.fromArrayMetadata(metadata)
.withShape(newShape)
.build();
return writeMetadata(newArrayMetadata);
}


/**
* Sets the attributes of the Zarr array. It overwrites and removes any existing attributes. This
* method returns a new instance of the Zarr array class and the old instance becomes invalid.
Expand Down Expand Up @@ -248,7 +269,8 @@ public Array setAttributes(Attributes newAttributes) throws ZarrException, IOExc
* @throws IOException throws IOException if the new metadata cannot be serialized
*/
public Array updateAttributes(Function<Attributes, Attributes> attributeMapper) throws ZarrException, IOException {
return setAttributes(attributeMapper.apply(metadata.attributes));
Attributes currentAttributes = metadata.attributes != null ? new Attributes(metadata.attributes) : new Attributes();
return setAttributes(attributeMapper.apply(currentAttributes));
}

@Override
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/dev/zarr/zarrjava/v3/Group.java
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@ private Group writeMetadata(GroupMetadata newGroupMetadata) throws IOException {
* @throws IOException if the metadata cannot be serialized
*/
public Group updateAttributes(Function<Attributes, Attributes> attributeMapper) throws ZarrException, IOException {
return setAttributes(attributeMapper.apply(metadata.attributes));
Attributes currentAttributes = metadata.attributes != null ? new Attributes(metadata.attributes) : new Attributes();
return setAttributes(attributeMapper.apply(currentAttributes));
}

/**
Expand Down
Loading
Loading