Skip to content

Extended axes support#28

Merged
sbesson merged 33 commits intoglencoesoftware:masterfrom
ome:ext_axes_support
Aug 26, 2025
Merged

Extended axes support#28
sbesson merged 33 commits intoglencoesoftware:masterfrom
ome:ext_axes_support

Conversation

@dominikl
Copy link
Copy Markdown
Contributor

@dominikl dominikl commented Jul 4, 2025

This PR adds support for different axes order and zarrs with less than 5 dimensions.
(Replaces #19)

Path output = tmpDir.getRoot().toPath().resolve("output.zarr");
assertBioFormats2Raw(input, output);
if (order != null) {
assertBioFormats2Raw(input, output, "--dimension-order", order);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we probably need to review that strategy

Remove usage of bioformats order argument
@sbesson
Copy link
Copy Markdown
Member

sbesson commented Aug 15, 2025

As a confirmation of the ground truth, I generated a series of test files of various dimensionality using glencoesoftware/bioformats2raw#279 and ome/bioformats#4339

bioformats2raw "test&labelPlanes=true&sizeX=8192&sizeY=8192&sizeC=3.fake" XYZCT_8192x8192x1x3x1.zarr
bioformats2raw "test&labelPlanes=true&sizeX=8192&sizeY=8192&sizeC=3.fake" XYC_8192x8192x3.zarr --compact
bioformats2raw "test&labelPlanes=true.fake" XYZCT_512x512x1x1x1.zarr
bioformats2raw "test&labelPlanes=true.fake" XY_512x512.zarr --compact
bioformats2raw "test&labelPlanes=true&sizeZ=50.fake" XYZCT_512x512x50x1x1.zarr
bioformats2raw "test&labelPlanes=true&sizeZ=50.fake" XYZ_512x512x50.zarr --compact
bioformats2raw "test&labelPlanes=true&sizeC=5.fake" XYZCT_512x512x1x5x1.zarr
bioformats2raw "test&labelPlanes=true&sizeC=5.fake" XYC_512x512x5.zarr --compact
bioformats2raw "test&labelPlanes=true&sizeT=20.fake" XYZCT_512x512x1x1x20.zarr
bioformats2raw "test&labelPlanes=true&sizeT=20.fake" XYT_512x512x20.zarr --compact
bioformats2raw "test&labelPlanes=true&sizeZ=50&sizeC=5.fake" XYZCT_512x512x50x5x1.zarr
bioformats2raw "test&labelPlanes=true&sizeZ=50&sizeC=5.fake" XYZC_512x512x50x5.zarr --compact
bioformats2raw "test&labelPlanes=true&sizeZ=50&sizeT=20.fake" XYZCT_512x512x50x1x20.zarr
bioformats2raw "test&labelPlanes=true&sizeZ=50&sizeT=20.fake" XYZT_512x512x50x20.zarr --compact
bioformats2raw "test&labelPlanes=true&sizeC=5&sizeT=20.fake" XYZCT_512x512x1x5x20.zarr
bioformats2raw "test&labelPlanes=true&sizeC=5&sizeT=20.fake" XYCT_512x512x5x20.zarr --compact
bioformats2raw "test&labelPlanes=true&sizeZ=50&sizeC=5&sizeT=20.fake" XYZCT_512x512x50x5x20.zarr
bioformats2raw "test&labelPlanes=true&sizeZ=50&sizeC=5&sizeT=20.fake" XYZCT_512x512x50x5x20_2.zarr --compact

As expected, importing these Zarr datasets into OMERO (Plus) with omero-zarr-pixel-buffer 0.5.0, all the datasets with XYZCT axes are properly registered with the binary data readable while the binary data of the rest is incorrectly read

Screenshot 2025-08-15 at 16 36 22

Copy link
Copy Markdown
Member

@sbesson sbesson left a comment

Choose a reason for hiding this comment

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

With a build of omero-zarr-pixel-buffer including this PR deployed in OMERO.server (lib/server) as well as the image-region micro-service, all test images imported above now render as expected for all axes variants

Screenshot 2025-08-18 at 09 52 24

In addition, tested the conversion and import of a couple of Zarr datasets generated from real-world examples across different modalities, already used in previous testing

Zarr datasets were generated with and without --compact and imported into OMERO. The tile loading is consistent between the 5D and reduced dimensionality representation for all samples

Screenshot 2025-08-18 at 09 47 53

Additionally, the 5D and 3D label images both render consistently

Screenshot 2025-08-18 at 09 46 12 Screenshot 2025-08-18 at 09 46 30

From a functional perspective, everything works as expected. The latest version of the code addresses some of the feedback raised in #19 but I'll leave @kkoz @melissalinkert who also has some knowledge of this code to run a round of code review.

From the testing perspective, since #19 (review), @melissalinkert has opened 2 contributions (mentioned previously as glencoesoftware/bioformats2raw#279 and ome/bioformats#4339) to generate sample Zarr datasets of reduced dimensionalities via bioformats2raw which can also be used for unit test. My impression is that this approach covers most of the logic of the TestZarr. Consuming it will require a Bio-Formats and bioformats2raw release. Depending on the timeline, we could integrate this as it is and capture the refactoring of the tests as a follow-up issue to consume the new functionalities?

}
if (axesOrder.containsKey(Axis.Z)) {
offset[axesOrder.get(Axis.Z)] = z;
}
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.

Might be worth condensing lines 566-589, so that for each of the 3 axes there is something like:

if (axesOrder.containsKey(Axis.T)) {
    int tIndex = axesOrder.get(Axis.T);
    shape[tIndex] = 1;
    offset[tIndex] = t;
}

That would reduce the number of axesOrder.containsKey and axesOrder.get calls, which at least can't hurt tile read performance.

}
if (axesOrder.containsKey(Axis.Z)) {
offset[axesOrder.get(Axis.Z)] = z;
}
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.

As above, this could be condensed to a single containsKey(...) check for each axis.

}
if (axesOrder.containsKey(Axis.Z)) {
offset[axesOrder.get(Axis.Z)] = z;
}
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.

Same here as for lines 566-589 and 693-715.

public TestZarr createImage() throws IOException, InvalidRangeException {
for (int c = 0; c <= sizeC; c++) {
if (c == sizeC && sizeC > 0)
break;
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.

Is the idea here to execute once if sizeC is 0, and up to sizeC - 1 otherwise? If so, that might be easier to follow by removing lines 179-180, and using something like this on line 178:

for (int c = 0; c < Math.max(1, sizeC); c++)

break;
for (int t = 0; t <= sizeT; t++) {
if (t == sizeT && sizeT > 0)
break;
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.

Same question as on in the loop over sizeC.

break;
for (int z = 0; z <= sizeZ; z++) {
if (z == sizeZ && sizeZ > 0)
break;
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.

...and same question as for sizeC and sizeT above.

int fontSize = Math.min(sizeX, sizeY) / 20; // Scale font size based on image dimensions
g2d.setFont(new Font("Arial", Font.BOLD, fontSize));

String planeText = text.replace("<C>", String.valueOf(c)).replace("<T>", String.valueOf(t)).replace("<Z>", String.valueOf(z));
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.

If text were Channel %s, Timepoint %s, Z-plane %s, then this line could be simplified to something like String planeText = String.format(text, c, t, z).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

👍 Thanks @melissalinkert !

Comment on lines +221 to +223
offset[axesOrder.get(Axis.Z)] = zIndexMap.get(originalZIndex);
planes = shape[axesOrder.get(Axis.Z)];
shape[axesOrder.get(Axis.Z)] = 1;
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.

Since these require axesOrder to contain Axis.Z, should they be inside the conditional above?

* Retrieves the axes metadata 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.

order.put(Axis.Y, 3);
order.put(Axis.X, 4);
} else {
for (int i=0; i<axesData.size(); i++) {
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.

For formatting, do we want spaces between the values and operators here?

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.

@kkoz should we review whether/how to enforce formatting on this repository as a follow up of this work?

My feeling is that if we go down this route, we should encode the decisions under the form of checkstyle constraints similarly to https://github.com/glencoesoftware/bioformats2raw/blob/97f8a26dd120d854d3e03ab70f1c481b524ff025/config/checkstyle/checkstyle.xml and let Gradle lint the code as part of the build.

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 agree. I was under the impression that there was already an OME style somewhere, but after poking around a bit I couldn't find it. Automatically applying consistent formatting in all repos would definitely be a plus.

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.

https://ome-contributing.readthedocs.io/en/latest/code-contributions.html would be the most generic guidelines and Bio-Formats has some additional instructions https://bio-formats.readthedocs.io/en/latest/developers/code-formatting.html

For OMERO, there are no written guidelines but https://github.com/ome/openmicroscopy/tree/develop/docs/styles will contain the historical style definition.

// 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++) {
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.

Same formatting comment here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's true, I've not set any particular formatting style in my IDE, sorry. Happy to use the bioformats2raw one directly!

@dominikl
Copy link
Copy Markdown
Contributor Author

Addressed @kkoz suggestions. Reformatting to the bioformats2raw style rules would introduce a huge amount of changes, make review of this PR impossible. Probably better done in follow-up?

@sbesson
Copy link
Copy Markdown
Member

sbesson commented Aug 21, 2025

Agreed, let's separate the formatting discussion. On this front, one of the major divergences between the Bio-Formats codebase (incl. bioformats2raw) and the OMERO codebases is the 2 spaces vs 4 spaces indentations. The value of unifying everything to one style would quickly become neglectable compared to the impact of rewriting almost every line ...

I would propose to start by reviewing the set of existing and expected rules for each component and try to identify a sensible overlap that we can enforce

@sbesson sbesson requested review from kkoz and melissalinkert August 21, 2025 13:20
Copy link
Copy Markdown
Member

@melissalinkert melissalinkert left a comment

Choose a reason for hiding this comment

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

Minor comment aside, it looks like a couple of the review comments from @kkoz were missed as they were separate from the formatting discussion.

break;
for (int c = 0; c <= sizeC; c++) {
if (c == sizeC && sizeC > 0)
break;
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.

As in TestZarr lines 178-180, this would be easier to follow if using something like for (int t = 0; t < Math.max(sizeT, 1); t++).

Copy link
Copy Markdown
Member

@sbesson sbesson left a comment

Choose a reason for hiding this comment

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

Retested the last commit against a set of images with XY, XYC, XYZ, XYT, XYZC, XYZT, XYZCT as well as XYZCT dimensions as well as labels with XY, XYC and ZYXCT dimensions. Images and labels are imported correctly with the thumbnails generated and the binary data is correctly read by the viewers.

@melissalinkert and @kkoz can you take a look at the code changes addressing your feedback?

@sbesson sbesson merged commit 0f80674 into glencoesoftware:master Aug 26, 2025
8 of 9 checks passed
@sbesson sbesson mentioned this pull request Sep 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle images that are not 5D

6 participants