Skip to content

Commit a0b3c47

Browse files
authored
Merge pull request #2 from dominikl/axes
Support different axes order and <5d zarrs
2 parents 7b25b24 + ff75695 commit a0b3c47

File tree

4 files changed

+874
-47
lines changed

4 files changed

+874
-47
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ jobs:
2323
with:
2424
java-version: ${{ matrix.java }}
2525
distribution: 'zulu'
26+
- name: Install dependency
27+
run: sudo apt-get install libblosc1
2628
- name: Run commands
2729
run: |
2830
./gradlew ${{ env.gradle_commands }}
2931
- name: Publish artifacts
30-
if: github.event_name != 'pull_request' && matrix.java == 11
32+
if: github.event_name != 'pull_request' && matrix.java == 11 && github.repository_owner == 'glencoesoftware'
3133
run: |
3234
./gradlew -PArtifactoryUserName=${ArtifactoryUserName} -PArtifactoryPassword=${ArtifactoryPassword} publish

src/main/java/com/glencoesoftware/omero/zarr/ZarrPixelBuffer.java

Lines changed: 167 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ public class ZarrPixelBuffer implements PixelBuffer {
9595
/** Array path vs. ZarrArray cache */
9696
private final AsyncLoadingCache<Path, ZarrArray> zarrArrayCache;
9797

98+
/** Supported axes, X and Y are essential */
99+
public enum Axis {
100+
X, Y, Z, C, T;
101+
}
102+
103+
/** Maps axes to their corresponding indexes */
104+
private Map<Axis, Integer> axesOrder;
105+
98106
/**
99107
* Default constructor
100108
* @param pixels Pixels metadata for the pixel buffer
@@ -122,12 +130,14 @@ public ZarrPixelBuffer(Pixels pixels, Path root, Integer maxPlaneWidth,
122130
if (!rootGroupAttributes.containsKey("multiscales")) {
123131
throw new IllegalArgumentException("Missing multiscales metadata!");
124132
}
133+
this.axesOrder = getAxesOrder();
125134
this.resolutionLevels = this.getResolutionLevels();
126135
setResolutionLevel(this.resolutionLevels - 1);
127136
if (this.resolutionLevel < 0) {
128137
throw new IllegalArgumentException(
129138
"This Zarr file has no pixel data");
130139
}
140+
131141
this.maxPlaneWidth = maxPlaneWidth;
132142
this.maxPlaneHeight = maxPlaneHeight;
133143

@@ -196,25 +206,30 @@ private long length(int[] shape) {
196206
private void read(byte[] buffer, int[] shape, int[] offset)
197207
throws IOException {
198208
// Check planar read size (sizeX and sizeY only)
199-
checkReadSize(Arrays.copyOfRange(shape, 3, 5));
200-
209+
checkReadSize(new int[] {shape[axesOrder.get(Axis.X)], shape[axesOrder.get(Axis.Y)]});
210+
201211
// if reading from a resolution downsampled in Z,
202212
// adjust the shape/offset for the Z coordinate only
203213
// this ensures that the correct Zs are read from the correct offsets
204214
// since the requested shape/offset may not match the underlying array
205215
int planes = 1;
206-
int originalZIndex = offset[2];
216+
int originalZIndex = 1;
217+
if (axesOrder.containsKey(Axis.Z)) {
218+
originalZIndex = offset[axesOrder.get(Axis.Z)];
219+
}
207220
if (getSizeZ() != getTrueSizeZ()) {
208-
offset[2] = zIndexMap.get(originalZIndex);
209-
planes = shape[2];
210-
shape[2] = 1;
221+
offset[axesOrder.get(Axis.Z)] = zIndexMap.get(originalZIndex);
222+
planes = shape[axesOrder.get(Axis.Z)];
223+
shape[axesOrder.get(Axis.Z)] = 1;
211224
}
212225

213226
try {
214227
ByteBuffer asByteBuffer = ByteBuffer.wrap(buffer);
215228
DataType dataType = array.getDataType();
216229
for (int z=0; z<planes; z++) {
217-
offset[2] = zIndexMap.get(originalZIndex + z);
230+
if (axesOrder.containsKey(Axis.Z)) {
231+
offset[axesOrder.get(Axis.Z)] = zIndexMap.get(originalZIndex + z);
232+
}
218233
switch (dataType) {
219234
case u1:
220235
case i1:
@@ -296,7 +311,7 @@ public int[][] getChunks() throws IOException {
296311
}
297312

298313
/**
299-
* Retrieves the datasets metadat of the first multiscale from the root
314+
* Retrieves the datasets metadata of the first multiscale from the root
300315
* group attributes.
301316
* @return See above.
302317
* @see #getRootGroupAttributes()
@@ -307,6 +322,37 @@ public List<Map<String, String>> getDatasets() {
307322
getMultiscalesMetadata().get(0).get("datasets");
308323
}
309324

325+
/**
326+
* Retrieves the axes metadata of the first multiscale
327+
* @return See above.
328+
*/
329+
public Map<Axis, Integer> getAxesOrder() {
330+
HashMap<Axis, Integer> order = new HashMap<>();
331+
List<Map<String, Object>> axesData = (List<Map<String, Object>>) getMultiscalesMetadata().get(0).get("axes");
332+
if (axesData == null) {
333+
log.warn("No axes metadata found, defaulting to standard axes TCZYX");
334+
order.put(Axis.T, 0);
335+
order.put(Axis.C, 1);
336+
order.put(Axis.Z, 2);
337+
order.put(Axis.Y, 3);
338+
order.put(Axis.X, 4);
339+
} else {
340+
for (int i=0; i<axesData.size(); i++) {
341+
Map<String, Object> axis = axesData.get(i);
342+
String name = axis.get("name").toString().toUpperCase();
343+
try {
344+
order.put(Axis.valueOf(name), i);
345+
} catch (IllegalArgumentException e) {
346+
throw new IllegalArgumentException("Invalid axis name (only T,C,Z,Y,X are supported): " + name);
347+
}
348+
}
349+
}
350+
if (!order.containsKey(Axis.X) || !order.containsKey(Axis.Y)) {
351+
throw new IllegalArgumentException("Missing X or Y axis!");
352+
}
353+
return order;
354+
}
355+
310356
/**
311357
* Retrieves the multiscales metadata from the root group attributes.
312358
* @return See above.
@@ -517,8 +563,33 @@ public byte[] getTileDirect(
517563
checkBounds(x, y, z, c, t);
518564
//Check check bottom-right of tile in bounds
519565
checkBounds(x + w - 1, y + h - 1, z, c, t);
520-
int[] shape = new int[] { 1, 1, 1, h, w };
521-
int[] offset = new int[] { t, c, z, y, x };
566+
567+
int[] shape = new int[axesOrder.size()];
568+
if (axesOrder.containsKey(Axis.T)) {
569+
shape[axesOrder.get(Axis.T)] = 1;
570+
}
571+
if (axesOrder.containsKey(Axis.C)) {
572+
shape[axesOrder.get(Axis.C)] = 1;
573+
}
574+
if (axesOrder.containsKey(Axis.Z)) {
575+
shape[axesOrder.get(Axis.Z)] = 1;
576+
}
577+
shape[axesOrder.get(Axis.Y)] = h;
578+
shape[axesOrder.get(Axis.X)] = w;
579+
580+
int[] offset = new int[axesOrder.size()];
581+
if (axesOrder.containsKey(Axis.T)) {
582+
offset[axesOrder.get(Axis.T)] = t;
583+
}
584+
if (axesOrder.containsKey(Axis.C)) {
585+
offset[axesOrder.get(Axis.C)] = c;
586+
}
587+
if (axesOrder.containsKey(Axis.Z)) {
588+
offset[axesOrder.get(Axis.Z)] = z;
589+
}
590+
offset[axesOrder.get(Axis.Y)] = y;
591+
offset[axesOrder.get(Axis.X)] = x;
592+
522593
read(buffer, shape, offset);
523594
return buffer;
524595
} catch (Exception e) {
@@ -618,8 +689,33 @@ public byte[] getStackDirect(Integer c, Integer t, byte[] buffer)
618689
checkBounds(x, y, z, c, t);
619690
//Check check bottom-right of tile in bounds
620691
checkBounds(x + w - 1, y + h - 1, z, c, t);
621-
int[] shape = new int[] { 1, 1, getSizeZ(), h, w };
622-
int[] offset = new int[] { t, c, z, y, x };
692+
693+
int[] shape = new int[axesOrder.size()];
694+
if (axesOrder.containsKey(Axis.T)) {
695+
shape[axesOrder.get(Axis.T)] = 1;
696+
}
697+
if (axesOrder.containsKey(Axis.C)) {
698+
shape[axesOrder.get(Axis.C)] = 1;
699+
}
700+
if (axesOrder.containsKey(Axis.Z)) {
701+
shape[axesOrder.get(Axis.Z)] = getSizeZ();
702+
}
703+
shape[axesOrder.get(Axis.Y)] = h;
704+
shape[axesOrder.get(Axis.X)] = w;
705+
706+
int[] offset = new int[axesOrder.size()];
707+
if (axesOrder.containsKey(Axis.T)) {
708+
offset[axesOrder.get(Axis.T)] = t;
709+
}
710+
if (axesOrder.containsKey(Axis.C)) {
711+
offset[axesOrder.get(Axis.C)] = c;
712+
}
713+
if (axesOrder.containsKey(Axis.Z)) {
714+
offset[axesOrder.get(Axis.Z)] = z;
715+
}
716+
offset[axesOrder.get(Axis.Y)] = y;
717+
offset[axesOrder.get(Axis.X)] = x;
718+
623719
read(buffer, shape, offset);
624720
return buffer;
625721
}
@@ -647,8 +743,33 @@ public byte[] getTimepointDirect(Integer t, byte[] buffer)
647743
checkBounds(x, y, z, c, t);
648744
//Check check bottom-right of tile in bounds
649745
checkBounds(x + w - 1, y + h - 1, z, c, t);
650-
int[] shape = new int[] { 1, getSizeC(), getSizeZ(), h, w };
651-
int[] offset = new int[] { t, c, z, y, x };
746+
747+
int[] shape = new int[axesOrder.size()];
748+
if (axesOrder.containsKey(Axis.T)) {
749+
shape[axesOrder.get(Axis.T)] = 1;
750+
}
751+
if (axesOrder.containsKey(Axis.C)) {
752+
shape[axesOrder.get(Axis.C)] = getSizeC();
753+
}
754+
if (axesOrder.containsKey(Axis.Z)) {
755+
shape[axesOrder.get(Axis.Z)] = getSizeZ();
756+
}
757+
shape[axesOrder.get(Axis.Y)] = h;
758+
shape[axesOrder.get(Axis.X)] = w;
759+
760+
int[] offset = new int[axesOrder.size()];
761+
if (axesOrder.containsKey(Axis.T)) {
762+
offset[axesOrder.get(Axis.T)] = t;
763+
}
764+
if (axesOrder.containsKey(Axis.C)) {
765+
offset[axesOrder.get(Axis.C)] = c;
766+
}
767+
if (axesOrder.containsKey(Axis.Z)) {
768+
offset[axesOrder.get(Axis.Z)] = z;
769+
}
770+
offset[axesOrder.get(Axis.Y)] = y;
771+
offset[axesOrder.get(Axis.X)] = x;
772+
652773
read(buffer, shape, offset);
653774
return buffer;
654775
}
@@ -757,35 +878,47 @@ public long getId() {
757878

758879
@Override
759880
public int getSizeX() {
760-
return array.getShape()[4];
881+
return array.getShape()[axesOrder.get(Axis.X)];
761882
}
762883

763884
@Override
764885
public int getSizeY() {
765-
return array.getShape()[3];
886+
return array.getShape()[axesOrder.get(Axis.Y)];
766887
}
767888

768889
@Override
769890
public int getSizeZ() {
770-
// this is expected to be the Z size of the full resolution array
771-
return zIndexMap.size();
891+
if (axesOrder.containsKey(Axis.Z)) {
892+
// this is expected to be the Z size of the full resolution array
893+
return zIndexMap.size();
894+
}
895+
return 1;
772896
}
773897

774898
/**
775899
* @return Z size of the current underlying Zarr array
776900
*/
777901
private int getTrueSizeZ() {
778-
return array.getShape()[2];
902+
if (axesOrder.containsKey(Axis.Z)) {
903+
return array.getShape()[axesOrder.get(Axis.Z)];
904+
}
905+
return 1;
779906
}
780907

781908
@Override
782909
public int getSizeC() {
783-
return array.getShape()[1];
910+
if (axesOrder.containsKey(Axis.C)) {
911+
return array.getShape()[axesOrder.get(Axis.C)];
912+
}
913+
return 1;
784914
}
785915

786916
@Override
787917
public int getSizeT() {
788-
return array.getShape()[0];
918+
if (axesOrder.containsKey(Axis.T)) {
919+
return array.getShape()[axesOrder.get(Axis.T)];
920+
}
921+
return 1;
789922
}
790923

791924
@Override
@@ -813,6 +946,7 @@ public void setResolutionLevel(int resolutionLevel) {
813946
throw new IllegalArgumentException(
814947
"This Zarr file has no pixel data");
815948
}
949+
816950
if (zIndexMap == null) {
817951
zIndexMap = new HashMap<Integer, Integer>();
818952
}
@@ -825,26 +959,29 @@ public void setResolutionLevel(int resolutionLevel) {
825959

826960
ZarrArray fullResolutionArray = zarrArrayCache.get(
827961
root.resolve("0")).get();
828-
829-
// map each Z index in the full resolution array
830-
// to a Z index in the subresolution array
831-
// if no Z downsampling, this is just an identity map
832-
int fullResZ = fullResolutionArray.getShape()[2];
833-
int arrayZ = array.getShape()[2];
834-
for (int z=0; z<fullResZ; z++) {
835-
zIndexMap.put(z, Math.round(z * arrayZ / fullResZ));
962+
963+
if (axesOrder.containsKey(Axis.Z)) {
964+
// map each Z index in the full resolution array
965+
// to a Z index in the subresolution array
966+
// if no Z downsampling, this is just an identity map
967+
int fullResZ = fullResolutionArray.getShape()[axesOrder.get(Axis.Z)];
968+
int arrayZ = array.getShape()[axesOrder.get(Axis.Z)];
969+
for (int z=0; z<fullResZ; z++) {
970+
zIndexMap.put(z, Math.round(z * arrayZ / fullResZ));
971+
}
836972
}
837973
} catch (Exception e) {
838974
// FIXME: Throw the right exception
839975
throw new RuntimeException(e);
840976
}
977+
841978
}
842979

843980
@Override
844981
public Dimension getTileSize() {
845982
try {
846983
int[] chunks = getChunks()[resolutionLevel];
847-
return new Dimension(chunks[4], chunks[3]);
984+
return new Dimension(chunks[axesOrder.get(Axis.X)], chunks[axesOrder.get(Axis.Y)]);
848985
} catch (Exception e) {
849986
// FIXME: Throw the right exception
850987
throw new RuntimeException(e);

0 commit comments

Comments
 (0)