Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
63c9936
Take axes metadata into account
dominikl Apr 30, 2025
71c93bb
Add test checking default axes if not present
jburel Jun 4, 2025
dfe6319
Fix order since order in omero is reverse in ome.zarr
jburel Jun 4, 2025
38838db
Merge pull request #1 from jburel/axes
dominikl Jun 5, 2025
d8a7e3c
Remove printlns
dominikl Jun 5, 2025
7fef6bf
Remove commented out code
dominikl Jun 5, 2025
b6dc260
Remove unused imports
dominikl Jun 5, 2025
7d9f338
Support zarr without c, t or z axes
dominikl Jun 5, 2025
a7cd5c9
Fix swapped xy tile size
dominikl Jun 6, 2025
0020bb6
Fix issue when Z missing
dominikl Jun 6, 2025
e4eb3e9
Fix issue with checkReadSize
dominikl Jun 6, 2025
ed93541
Add integration tests for different dimensions
dominikl Jun 11, 2025
8995b11
remove logging from TestZarr
dominikl Jun 12, 2025
56df523
Moved classes into correct package
dominikl Jun 13, 2025
04fe46e
Fix package declaration
dominikl Jun 13, 2025
de1096e
Fix order
jburel Jun 13, 2025
51119b5
install blosc
jburel Jun 13, 2025
0a30109
Check repo owner
jburel Jun 13, 2025
d000619
Merge pull request #2 from jburel/axes
dominikl Jun 13, 2025
bd95004
Some refactoring
dominikl Jun 26, 2025
4d9ef53
Moved Utils method into TestZarr itself
dominikl Jun 26, 2025
c950c9c
Add more missing tests
dominikl Jun 26, 2025
ff75695
Use DimensionOrder
dominikl Jun 26, 2025
a0b3c47
Merge pull request #2 from dominikl/axes
dominikl Jul 4, 2025
6945b90
Clarify order
dominikl Jul 15, 2025
bd1bac4
Remove usage of BF --order argument
dominikl Jul 15, 2025
57bffda
Merge pull request #6 from dominikl/remove_bf_dim_order
jburel Jul 18, 2025
96afc5a
Reduce calls to axesOrder.containsKey
dominikl Aug 19, 2025
40c09cc
Simplify code
dominikl Aug 19, 2025
6194831
Fix formatting
dominikl Aug 21, 2025
3868956
Sanitise getAxisOrder
dominikl Aug 21, 2025
195ec0f
Fix for loops
dominikl Aug 22, 2025
beeacff
Move z axis access into conditional block
dominikl Aug 22, 2025
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
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ jobs:
with:
java-version: ${{ matrix.java }}
distribution: 'zulu'
- name: Install dependency
run: sudo apt-get install libblosc1
- name: Run commands
run: |
./gradlew ${{ env.gradle_commands }}
- name: Publish artifacts
if: github.event_name != 'pull_request' && matrix.java == 11
if: github.event_name != 'pull_request' && matrix.java == 11 && github.repository_owner == 'glencoesoftware'
run: |
./gradlew -PArtifactoryUserName=${ArtifactoryUserName} -PArtifactoryPassword=${ArtifactoryPassword} publish
184 changes: 151 additions & 33 deletions src/main/java/com/glencoesoftware/omero/zarr/ZarrPixelBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ public class ZarrPixelBuffer implements PixelBuffer {
/** Array path vs. ZarrArray cache */
private final AsyncLoadingCache<Path, ZarrArray> zarrArrayCache;

/** Supported axes, X and Y are essential */
public enum Axis {
X, Y, Z, C, T;
}

/** Maps axes to their corresponding indexes */
private Map<Axis, Integer> axesOrder;

/**
* Default constructor
* @param pixels Pixels metadata for the pixel buffer
Expand Down Expand Up @@ -122,12 +130,14 @@ public ZarrPixelBuffer(Pixels pixels, Path root, Integer maxPlaneWidth,
if (!rootGroupAttributes.containsKey("multiscales")) {
throw new IllegalArgumentException("Missing multiscales metadata!");
}
getAxesOrder();
this.resolutionLevels = this.getResolutionLevels();
setResolutionLevel(this.resolutionLevels - 1);
if (this.resolutionLevel < 0) {
throw new IllegalArgumentException(
"This Zarr file has no pixel data");
}

this.maxPlaneWidth = maxPlaneWidth;
this.maxPlaneHeight = maxPlaneHeight;

Expand Down Expand Up @@ -196,25 +206,29 @@ private long length(int[] shape) {
private void read(byte[] buffer, int[] shape, int[] offset)
throws IOException {
// Check planar read size (sizeX and sizeY only)
checkReadSize(Arrays.copyOfRange(shape, 3, 5));

checkReadSize(new int[] {shape[axesOrder.get(Axis.X)], shape[axesOrder.get(Axis.Y)]});
// if reading from a resolution downsampled in Z,
// adjust the shape/offset for the Z coordinate only
// this ensures that the correct Zs are read from the correct offsets
// since the requested shape/offset may not match the underlying array
int planes = 1;
int originalZIndex = offset[2];
if (getSizeZ() != getTrueSizeZ()) {
offset[2] = zIndexMap.get(originalZIndex);
planes = shape[2];
shape[2] = 1;
int originalZIndex = 1;
if (axesOrder.containsKey(Axis.Z)) {
originalZIndex = offset[axesOrder.get(Axis.Z)];
if (getSizeZ() != getTrueSizeZ()) {
offset[axesOrder.get(Axis.Z)] = zIndexMap.get(originalZIndex);
planes = shape[axesOrder.get(Axis.Z)];
shape[axesOrder.get(Axis.Z)] = 1;
}
}

try {
ByteBuffer asByteBuffer = ByteBuffer.wrap(buffer);
DataType dataType = array.getDataType();
for (int z=0; z<planes; z++) {
offset[2] = zIndexMap.get(originalZIndex + z);
for (int z = 0; z < planes; z++) {
if (axesOrder.containsKey(Axis.Z)) {
offset[axesOrder.get(Axis.Z)] = zIndexMap.get(originalZIndex + z);
}
switch (dataType) {
case u1:
case i1:
Expand Down Expand Up @@ -296,7 +310,7 @@ public int[][] getChunks() throws IOException {
}

/**
* Retrieves the datasets metadat of the first multiscale from the root
* Retrieves the datasets metadata of the first multiscale from the root
* group attributes.
* @return See above.
* @see #getRootGroupAttributes()
Expand All @@ -307,6 +321,40 @@ public List<Map<String, String>> getDatasets() {
getMultiscalesMetadata().get(0).get("datasets");
}

/**
* Retrieves the axes order of the first multiscale
* @return See above.
*/
public Map<Axis, Integer> getAxesOrder() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this should either be setAxesOrder and set the member variable from the zarr, or it should check to see if the value has been set and just return axesOrder if it has.

if (axesOrder != null) {
return axesOrder;
}
axesOrder = new HashMap<Axis, Integer>();
List<Map<String, Object>> axesData = (List<Map<String, Object>>) getMultiscalesMetadata().get(0).get("axes");
if (axesData == null) {
log.warn("No axes metadata found, defaulting to standard axes TCZYX");
axesOrder.put(Axis.T, 0);
axesOrder.put(Axis.C, 1);
axesOrder.put(Axis.Z, 2);
axesOrder.put(Axis.Y, 3);
axesOrder.put(Axis.X, 4);
} else {
for (int i = 0; i < axesData.size(); i++) {
Map<String, Object> axis = axesData.get(i);
String name = axis.get("name").toString().toUpperCase();
try {
axesOrder.put(Axis.valueOf(name), i);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid axis name (only T,C,Z,Y,X are supported): " + name);
}
}
}
if (!axesOrder.containsKey(Axis.X) || !axesOrder.containsKey(Axis.Y)) {
throw new IllegalArgumentException("Missing X or Y axis!");
}
return axesOrder;
}

/**
* Retrieves the multiscales metadata from the root group attributes.
* @return See above.
Expand Down Expand Up @@ -517,8 +565,26 @@ public byte[] getTileDirect(
checkBounds(x, y, z, c, t);
//Check check bottom-right of tile in bounds
checkBounds(x + w - 1, y + h - 1, z, c, t);
int[] shape = new int[] { 1, 1, 1, h, w };
int[] offset = new int[] { t, c, z, y, x };

int[] shape = new int[axesOrder.size()];
int[] offset = new int[axesOrder.size()];
if (axesOrder.containsKey(Axis.T)) {
shape[axesOrder.get(Axis.T)] = 1;
offset[axesOrder.get(Axis.T)] = t;
}
if (axesOrder.containsKey(Axis.C)) {
shape[axesOrder.get(Axis.C)] = 1;
offset[axesOrder.get(Axis.C)] = c;
}
if (axesOrder.containsKey(Axis.Z)) {
shape[axesOrder.get(Axis.Z)] = 1;
offset[axesOrder.get(Axis.Z)] = z;
}
shape[axesOrder.get(Axis.Y)] = h;
shape[axesOrder.get(Axis.X)] = w;
offset[axesOrder.get(Axis.Y)] = y;
offset[axesOrder.get(Axis.X)] = x;

read(buffer, shape, offset);
return buffer;
} catch (Exception e) {
Expand Down Expand Up @@ -618,8 +684,26 @@ public byte[] getStackDirect(Integer c, Integer t, byte[] buffer)
checkBounds(x, y, z, c, t);
//Check check bottom-right of tile in bounds
checkBounds(x + w - 1, y + h - 1, z, c, t);
int[] shape = new int[] { 1, 1, getSizeZ(), h, w };
int[] offset = new int[] { t, c, z, y, x };

int[] shape = new int[axesOrder.size()];
int[] offset = new int[axesOrder.size()];
if (axesOrder.containsKey(Axis.T)) {
shape[axesOrder.get(Axis.T)] = 1;
offset[axesOrder.get(Axis.T)] = t;
}
if (axesOrder.containsKey(Axis.C)) {
shape[axesOrder.get(Axis.C)] = 1;
offset[axesOrder.get(Axis.C)] = c;
}
if (axesOrder.containsKey(Axis.Z)) {
shape[axesOrder.get(Axis.Z)] = getSizeZ();
offset[axesOrder.get(Axis.Z)] = z;
}
shape[axesOrder.get(Axis.Y)] = h;
shape[axesOrder.get(Axis.X)] = w;
offset[axesOrder.get(Axis.Y)] = y;
offset[axesOrder.get(Axis.X)] = x;

read(buffer, shape, offset);
return buffer;
}
Expand Down Expand Up @@ -647,8 +731,26 @@ public byte[] getTimepointDirect(Integer t, byte[] buffer)
checkBounds(x, y, z, c, t);
//Check check bottom-right of tile in bounds
checkBounds(x + w - 1, y + h - 1, z, c, t);
int[] shape = new int[] { 1, getSizeC(), getSizeZ(), h, w };
int[] offset = new int[] { t, c, z, y, x };

int[] shape = new int[axesOrder.size()];
int[] offset = new int[axesOrder.size()];
if (axesOrder.containsKey(Axis.T)) {
shape[axesOrder.get(Axis.T)] = 1;
offset[axesOrder.get(Axis.T)] = t;
}
if (axesOrder.containsKey(Axis.C)) {
shape[axesOrder.get(Axis.C)] = getSizeC();
offset[axesOrder.get(Axis.C)] = c;
}
if (axesOrder.containsKey(Axis.Z)) {
shape[axesOrder.get(Axis.Z)] = getSizeZ();
offset[axesOrder.get(Axis.Z)] = z;
}
shape[axesOrder.get(Axis.Y)] = h;
shape[axesOrder.get(Axis.X)] = w;
offset[axesOrder.get(Axis.Y)] = y;
offset[axesOrder.get(Axis.X)] = x;

read(buffer, shape, offset);
return buffer;
}
Expand Down Expand Up @@ -757,35 +859,47 @@ public long getId() {

@Override
public int getSizeX() {
return array.getShape()[4];
return array.getShape()[axesOrder.get(Axis.X)];
}

@Override
public int getSizeY() {
return array.getShape()[3];
return array.getShape()[axesOrder.get(Axis.Y)];
}

@Override
public int getSizeZ() {
// this is expected to be the Z size of the full resolution array
return zIndexMap.size();
if (axesOrder.containsKey(Axis.Z)) {
// this is expected to be the Z size of the full resolution array
return zIndexMap.size();
}
return 1;
}

/**
* @return Z size of the current underlying Zarr array
*/
private int getTrueSizeZ() {
return array.getShape()[2];
if (axesOrder.containsKey(Axis.Z)) {
return array.getShape()[axesOrder.get(Axis.Z)];
}
return 1;
}

@Override
public int getSizeC() {
return array.getShape()[1];
if (axesOrder.containsKey(Axis.C)) {
return array.getShape()[axesOrder.get(Axis.C)];
}
return 1;
}

@Override
public int getSizeT() {
return array.getShape()[0];
if (axesOrder.containsKey(Axis.T)) {
return array.getShape()[axesOrder.get(Axis.T)];
}
return 1;
}

@Override
Expand Down Expand Up @@ -813,6 +927,7 @@ public void setResolutionLevel(int resolutionLevel) {
throw new IllegalArgumentException(
"This Zarr file has no pixel data");
}

if (zIndexMap == null) {
zIndexMap = new HashMap<Integer, Integer>();
}
Expand All @@ -825,26 +940,29 @@ public void setResolutionLevel(int resolutionLevel) {

ZarrArray fullResolutionArray = zarrArrayCache.get(
root.resolve("0")).get();

// map each Z index in the full resolution array
// to a Z index in the subresolution array
// if no Z downsampling, this is just an identity map
int fullResZ = fullResolutionArray.getShape()[2];
int arrayZ = array.getShape()[2];
for (int z=0; z<fullResZ; z++) {
zIndexMap.put(z, Math.round(z * arrayZ / fullResZ));

if (axesOrder.containsKey(Axis.Z)) {
// map each Z index in the full resolution array
// to a Z index in the subresolution array
// if no Z downsampling, this is just an identity map
int fullResZ = fullResolutionArray.getShape()[axesOrder.get(Axis.Z)];
int arrayZ = array.getShape()[axesOrder.get(Axis.Z)];
for (int z = 0; z < fullResZ; z++) {
zIndexMap.put(z, Math.round(z * arrayZ / fullResZ));
}
}
} catch (Exception e) {
// FIXME: Throw the right exception
throw new RuntimeException(e);
}

}

@Override
public Dimension getTileSize() {
try {
int[] chunks = getChunks()[resolutionLevel];
return new Dimension(chunks[4], chunks[3]);
return new Dimension(chunks[axesOrder.get(Axis.X)], chunks[axesOrder.get(Axis.Y)]);
} catch (Exception e) {
// FIXME: Throw the right exception
throw new RuntimeException(e);
Expand Down
Loading