Skip to content

Commit 67c0970

Browse files
committed
add ome v0.4 and v0.5 implementation
1 parent 6b4877e commit 67c0970

11 files changed

Lines changed: 575 additions & 0 deletions
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package dev.zarr.zarrjava.ome;
2+
3+
import dev.zarr.zarrjava.ZarrException;
4+
import dev.zarr.zarrjava.core.Node;
5+
import dev.zarr.zarrjava.store.StoreHandle;
6+
import dev.zarr.zarrjava.utils.Utils;
7+
8+
import java.io.IOException;
9+
import java.nio.ByteBuffer;
10+
import java.util.List;
11+
12+
/**
13+
* Unified interface for reading OME-Zarr multiscale images across Zarr format versions.
14+
*/
15+
public interface MultiscaleImage {
16+
17+
/**
18+
* Returns the multiscale node descriptor at index {@code i}.
19+
*/
20+
UnifiedMultiscaleNode getMultiscaleNode(int i) throws ZarrException;
21+
22+
/**
23+
* Opens the scale level array at index {@code i} within the first multiscale entry.
24+
*/
25+
dev.zarr.zarrjava.core.Array openScaleLevel(int i) throws IOException, ZarrException;
26+
27+
/**
28+
* Returns the number of scale levels in the first multiscale entry.
29+
*/
30+
int getScaleLevelCount() throws ZarrException;
31+
32+
/**
33+
* Returns the axis names of the first multiscale entry.
34+
*/
35+
default List<String> getAxisNames() throws ZarrException {
36+
UnifiedMultiscaleNode node = getMultiscaleNode(0);
37+
List<String> names = new java.util.ArrayList<>();
38+
for (dev.zarr.zarrjava.ome.metadata.Axis axis : node.axes) {
39+
names.add(axis.name);
40+
}
41+
return names;
42+
}
43+
44+
/**
45+
* Opens an OME-Zarr multiscale image at the given store handle, auto-detecting the Zarr version.
46+
*
47+
* <p>Tries v0.5 (zarr.json with "ome" key) first, then v0.4 (.zattrs with "multiscales" key).
48+
*/
49+
static MultiscaleImage open(StoreHandle storeHandle) throws IOException, ZarrException {
50+
// Try v0.5: zarr.json with "ome" key
51+
StoreHandle zarrJson = storeHandle.resolve(Node.ZARR_JSON);
52+
if (zarrJson.exists()) {
53+
com.fasterxml.jackson.databind.ObjectMapper mapper = dev.zarr.zarrjava.v3.Node.makeObjectMapper();
54+
byte[] bytes = Utils.toArray(zarrJson.readNonNull());
55+
com.fasterxml.jackson.databind.JsonNode root = mapper.readTree(bytes);
56+
com.fasterxml.jackson.databind.JsonNode attrs = root.get("attributes");
57+
if (attrs != null && attrs.has("ome")) {
58+
return dev.zarr.zarrjava.ome.v0_5.MultiscaleImage.openMultiscaleImage(storeHandle);
59+
}
60+
}
61+
62+
// Try v0.4: .zattrs with "multiscales" key
63+
StoreHandle zattrs = storeHandle.resolve(Node.ZATTRS);
64+
if (zattrs.exists()) {
65+
com.fasterxml.jackson.databind.ObjectMapper mapper = dev.zarr.zarrjava.v2.Node.makeObjectMapper();
66+
byte[] bytes = Utils.toArray(zattrs.readNonNull());
67+
com.fasterxml.jackson.databind.JsonNode root = mapper.readTree(bytes);
68+
if (root.has("multiscales")) {
69+
return dev.zarr.zarrjava.ome.v0_4.MultiscaleImage.openMultiscaleImage(storeHandle);
70+
}
71+
}
72+
73+
throw new ZarrException("No OME-Zarr multiscale metadata found at " + storeHandle);
74+
}
75+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package dev.zarr.zarrjava.ome;
2+
3+
import dev.zarr.zarrjava.ZarrException;
4+
import dev.zarr.zarrjava.ome.metadata.CoordinateTransformation;
5+
import dev.zarr.zarrjava.ome.metadata.MultiscalesEntry;
6+
7+
import java.io.IOException;
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
11+
/**
12+
* Extension of {@link MultiscaleImage} that provides typed access to OME-Zarr multiscales metadata
13+
* and supports creating new scale levels.
14+
*
15+
* @param <M> the concrete multiscales entry type
16+
*/
17+
public interface MultiscalesMetadataImage<M extends MultiscalesEntry> extends MultiscaleImage {
18+
19+
/**
20+
* Returns the raw multiscales entry at index {@code i}.
21+
*/
22+
M getMultiscalesEntry(int i) throws ZarrException;
23+
24+
/**
25+
* Creates a new scale level array at {@code path} with the given metadata and coordinate transformations,
26+
* then registers it in the multiscales metadata.
27+
*/
28+
void createScaleLevel(
29+
String path,
30+
dev.zarr.zarrjava.core.ArrayMetadata arrayMetadata,
31+
List<CoordinateTransformation> coordinateTransformations
32+
) throws IOException, ZarrException;
33+
34+
@Override
35+
default UnifiedMultiscaleNode getMultiscaleNode(int i) throws ZarrException {
36+
M entry = getMultiscalesEntry(i);
37+
List<UnifiedSinglescaleNode> nodes = new ArrayList<>();
38+
for (dev.zarr.zarrjava.ome.metadata.Dataset dataset : entry.datasets) {
39+
nodes.add(new UnifiedSinglescaleNode(dataset.path, dataset.coordinateTransformations));
40+
}
41+
return new UnifiedMultiscaleNode(entry.name, entry.axes, nodes);
42+
}
43+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package dev.zarr.zarrjava.ome;
2+
3+
import dev.zarr.zarrjava.ome.metadata.Axis;
4+
5+
import javax.annotation.Nullable;
6+
import java.util.List;
7+
8+
/** A multiscale image node, using v1.0 RFC-8 terminology ("nodes" not "datasets"). */
9+
public final class UnifiedMultiscaleNode {
10+
11+
@Nullable
12+
public final String name;
13+
public final List<Axis> axes;
14+
public final List<UnifiedSinglescaleNode> nodes;
15+
16+
public UnifiedMultiscaleNode(@Nullable String name, List<Axis> axes, List<UnifiedSinglescaleNode> nodes) {
17+
this.name = name;
18+
this.axes = axes;
19+
this.nodes = nodes;
20+
}
21+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package dev.zarr.zarrjava.ome;
2+
3+
import dev.zarr.zarrjava.ome.metadata.CoordinateTransformation;
4+
5+
import java.util.List;
6+
7+
/** A single scale level within a multiscale image, using v1.0 RFC-8 terminology. */
8+
public final class UnifiedSinglescaleNode {
9+
10+
public final String path;
11+
public final List<CoordinateTransformation> coordinateTransformations;
12+
13+
public UnifiedSinglescaleNode(String path, List<CoordinateTransformation> coordinateTransformations) {
14+
this.path = path;
15+
this.coordinateTransformations = coordinateTransformations;
16+
}
17+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package dev.zarr.zarrjava.ome.metadata;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
7+
import javax.annotation.Nullable;
8+
9+
@JsonInclude(JsonInclude.Include.NON_NULL)
10+
public final class Axis {
11+
12+
public final String name;
13+
@Nullable
14+
public final String type;
15+
@Nullable
16+
public final String unit;
17+
@Nullable
18+
public final Boolean discrete;
19+
@Nullable
20+
@JsonProperty("long_name")
21+
public final String longName;
22+
23+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
24+
public Axis(
25+
@JsonProperty(value = "name", required = true) String name,
26+
@Nullable @JsonProperty("type") String type,
27+
@Nullable @JsonProperty("unit") String unit,
28+
@Nullable @JsonProperty("discrete") Boolean discrete,
29+
@Nullable @JsonProperty("long_name") String longName
30+
) {
31+
this.name = name;
32+
this.type = type;
33+
this.unit = unit;
34+
this.discrete = discrete;
35+
this.longName = longName;
36+
}
37+
38+
public Axis(String name, @Nullable String type, @Nullable String unit) {
39+
this(name, type, unit, null, null);
40+
}
41+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package dev.zarr.zarrjava.ome.metadata;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
7+
import javax.annotation.Nullable;
8+
import java.util.List;
9+
10+
@JsonInclude(JsonInclude.Include.NON_NULL)
11+
public final class CoordinateTransformation {
12+
13+
public final String type;
14+
@Nullable
15+
public final List<Double> scale;
16+
@Nullable
17+
public final List<Double> translation;
18+
@Nullable
19+
public final String path;
20+
21+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
22+
public CoordinateTransformation(
23+
@JsonProperty(value = "type", required = true) String type,
24+
@Nullable @JsonProperty("scale") List<Double> scale,
25+
@Nullable @JsonProperty("translation") List<Double> translation,
26+
@Nullable @JsonProperty("path") String path
27+
) {
28+
this.type = type;
29+
this.scale = scale;
30+
this.translation = translation;
31+
this.path = path;
32+
}
33+
34+
public static CoordinateTransformation scale(List<Double> scale) {
35+
return new CoordinateTransformation("scale", scale, null, null);
36+
}
37+
38+
public static CoordinateTransformation translation(List<Double> translation) {
39+
return new CoordinateTransformation("translation", null, translation, null);
40+
}
41+
42+
public static CoordinateTransformation identity() {
43+
return new CoordinateTransformation("identity", null, null, null);
44+
}
45+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dev.zarr.zarrjava.ome.metadata;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
6+
import java.util.List;
7+
8+
public final class Dataset {
9+
10+
public final String path;
11+
@JsonProperty("coordinateTransformations")
12+
public final List<CoordinateTransformation> coordinateTransformations;
13+
14+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
15+
public Dataset(
16+
@JsonProperty(value = "path", required = true) String path,
17+
@JsonProperty(value = "coordinateTransformations", required = true)
18+
List<CoordinateTransformation> coordinateTransformations
19+
) {
20+
this.path = path;
21+
this.coordinateTransformations = coordinateTransformations;
22+
}
23+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package dev.zarr.zarrjava.ome.metadata;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
7+
import javax.annotation.Nullable;
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
@JsonInclude(JsonInclude.Include.NON_NULL)
13+
public final class MultiscalesEntry {
14+
15+
public final List<Axis> axes;
16+
public final List<Dataset> datasets;
17+
@Nullable
18+
@JsonProperty("coordinateTransformations")
19+
public final List<CoordinateTransformation> coordinateTransformations;
20+
@Nullable
21+
public final String name;
22+
@Nullable
23+
public final String type;
24+
@Nullable
25+
public final Map<String, Object> metadata;
26+
@Nullable
27+
public final String version;
28+
29+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
30+
public MultiscalesEntry(
31+
@JsonProperty(value = "axes", required = true) List<Axis> axes,
32+
@JsonProperty(value = "datasets", required = true) List<Dataset> datasets,
33+
@Nullable @JsonProperty("coordinateTransformations") List<CoordinateTransformation> coordinateTransformations,
34+
@Nullable @JsonProperty("name") String name,
35+
@Nullable @JsonProperty("type") String type,
36+
@Nullable @JsonProperty("metadata") Map<String, Object> metadata,
37+
@Nullable @JsonProperty("version") String version
38+
) {
39+
this.axes = axes;
40+
this.datasets = datasets;
41+
this.coordinateTransformations = coordinateTransformations;
42+
this.name = name;
43+
this.type = type;
44+
this.metadata = metadata;
45+
this.version = version;
46+
}
47+
48+
public MultiscalesEntry(List<Axis> axes, List<Dataset> datasets) {
49+
this(axes, datasets, null, null, null, null, null);
50+
}
51+
52+
/** Returns a new MultiscalesEntry with the given dataset appended. */
53+
public MultiscalesEntry withDataset(Dataset dataset) {
54+
List<Dataset> updated = new ArrayList<>(this.datasets);
55+
updated.add(dataset);
56+
return new MultiscalesEntry(axes, updated, coordinateTransformations, name, type, metadata, version);
57+
}
58+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package dev.zarr.zarrjava.ome.metadata;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
6+
import java.util.List;
7+
8+
/** OME-Zarr metadata stored under {@code attributes["ome"]} (v0.5). */
9+
public final class OmeMetadata {
10+
11+
public final String version;
12+
public final List<MultiscalesEntry> multiscales;
13+
14+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
15+
public OmeMetadata(
16+
@JsonProperty(value = "version", required = true) String version,
17+
@JsonProperty(value = "multiscales", required = true) List<MultiscalesEntry> multiscales
18+
) {
19+
this.version = version;
20+
this.multiscales = multiscales;
21+
}
22+
}

0 commit comments

Comments
 (0)