Skip to content

Commit 215de0f

Browse files
committed
add ome v1.0 and v0.6
1 parent f9ee1ed commit 215de0f

25 files changed

Lines changed: 1616 additions & 487 deletions

src/main/java/dev/zarr/zarrjava/ome/MultiscaleImage.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,17 @@ static MultiscaleImage open(StoreHandle storeHandle) throws IOException, ZarrExc
111111
com.fasterxml.jackson.databind.JsonNode root = mapper.readTree(bytes);
112112
com.fasterxml.jackson.databind.JsonNode attrs = root.get("attributes");
113113
if (attrs != null && attrs.has("ome")) {
114+
com.fasterxml.jackson.databind.JsonNode omeNode = attrs.get("ome");
115+
String version = omeNode.has("version") ? omeNode.get("version").asText() : "";
116+
if (version.startsWith("1.")) {
117+
if (omeNode.has("multiscale")) {
118+
return dev.zarr.zarrjava.ome.v1_0.MultiscaleImage.openMultiscaleImage(storeHandle);
119+
}
120+
throw new ZarrException("v1.0 store at " + storeHandle + " is a Collection, not a MultiscaleImage. Use v1_0.Collection.openCollection() instead.");
121+
}
122+
if (version.startsWith("0.6")) {
123+
return dev.zarr.zarrjava.ome.v0_6.MultiscaleImage.openMultiscaleImage(storeHandle);
124+
}
114125
return dev.zarr.zarrjava.ome.v0_5.MultiscaleImage.openMultiscaleImage(storeHandle);
115126
}
116127
}

src/main/java/dev/zarr/zarrjava/ome/MultiscalesMetadataImage.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
* Extension of {@link MultiscaleImage} that provides typed access to OME-Zarr multiscales metadata
1313
* and supports creating new scale levels.
1414
*
15-
* @param <M> the concrete multiscales entry type
15+
* @param <M> the concrete multiscales entry type (may be {@link MultiscalesEntry} or a version-specific subtype)
1616
*/
17-
public interface MultiscalesMetadataImage<M extends MultiscalesEntry> extends MultiscaleImage {
17+
public interface MultiscalesMetadataImage<M> extends MultiscaleImage {
1818

1919
/**
2020
* Returns the raw multiscales entry at index {@code i}.
@@ -33,11 +33,17 @@ void createScaleLevel(
3333

3434
@Override
3535
default UnifiedMultiscaleNode getMultiscaleNode(int i) throws ZarrException {
36-
M entry = getMultiscalesEntry(i);
36+
Object entry = getMultiscalesEntry(i);
37+
if (!(entry instanceof MultiscalesEntry)) {
38+
throw new ZarrException(
39+
"getMultiscaleNode() not supported for entry type " + entry.getClass().getName()
40+
+ "; override getMultiscaleNode() in your MultiscalesMetadataImage implementation.");
41+
}
42+
MultiscalesEntry mse = (MultiscalesEntry) entry;
3743
List<UnifiedSinglescaleNode> nodes = new ArrayList<>();
38-
for (dev.zarr.zarrjava.ome.metadata.Dataset dataset : entry.datasets) {
44+
for (dev.zarr.zarrjava.ome.metadata.Dataset dataset : mse.datasets) {
3945
nodes.add(new UnifiedSinglescaleNode(dataset.path, dataset.coordinateTransformations));
4046
}
41-
return new UnifiedMultiscaleNode(entry.name, entry.axes, nodes);
47+
return new UnifiedMultiscaleNode(mse.name, mse.axes, nodes);
4248
}
4349
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package dev.zarr.zarrjava.ome.v0_6;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import dev.zarr.zarrjava.ZarrException;
5+
import dev.zarr.zarrjava.core.Attributes;
6+
import dev.zarr.zarrjava.ome.MultiscalesMetadataImage;
7+
import dev.zarr.zarrjava.ome.UnifiedMultiscaleNode;
8+
import dev.zarr.zarrjava.ome.UnifiedSinglescaleNode;
9+
import dev.zarr.zarrjava.ome.metadata.Axis;
10+
import dev.zarr.zarrjava.ome.metadata.CoordinateTransformation;
11+
import dev.zarr.zarrjava.ome.v0_6.metadata.CoordinateSystem;
12+
import dev.zarr.zarrjava.ome.v0_6.metadata.Dataset;
13+
import dev.zarr.zarrjava.ome.v0_6.metadata.MultiscalesEntry;
14+
import dev.zarr.zarrjava.ome.v0_6.metadata.OmeMetadata;
15+
import dev.zarr.zarrjava.store.StoreHandle;
16+
import dev.zarr.zarrjava.v3.Array;
17+
import dev.zarr.zarrjava.v3.Group;
18+
import dev.zarr.zarrjava.v3.GroupMetadata;
19+
20+
import javax.annotation.Nonnull;
21+
import java.io.IOException;
22+
import java.util.ArrayList;
23+
import java.util.Collections;
24+
import java.util.List;
25+
import java.util.Map;
26+
27+
import static dev.zarr.zarrjava.v3.Node.makeObjectMapper;
28+
29+
/**
30+
* OME-Zarr v0.6 (RFC-5) multiscale image backed by a Zarr v3 group.
31+
*/
32+
public final class MultiscaleImage extends Group implements MultiscalesMetadataImage<MultiscalesEntry> {
33+
34+
private OmeMetadata omeMetadata;
35+
36+
private MultiscaleImage(
37+
@Nonnull StoreHandle storeHandle,
38+
@Nonnull GroupMetadata groupMetadata,
39+
@Nonnull OmeMetadata omeMetadata
40+
) throws IOException {
41+
super(storeHandle, groupMetadata);
42+
this.omeMetadata = omeMetadata;
43+
}
44+
45+
/**
46+
* Opens an existing OME-Zarr v0.6 multiscale image at the given store handle.
47+
*/
48+
public static MultiscaleImage openMultiscaleImage(@Nonnull StoreHandle storeHandle) throws IOException, ZarrException {
49+
Group group = Group.open(storeHandle);
50+
ObjectMapper mapper = makeObjectMapper();
51+
Attributes attributes = group.metadata.attributes;
52+
if (attributes == null || !attributes.containsKey("ome")) {
53+
throw new ZarrException("No 'ome' key found in attributes at " + storeHandle);
54+
}
55+
OmeMetadata omeMetadata = mapper.convertValue(attributes.get("ome"), OmeMetadata.class);
56+
if (!omeMetadata.version.startsWith("0.6")) {
57+
throw new ZarrException(
58+
"Expected OME-Zarr version '0.6', got '" + omeMetadata.version + "' at " + storeHandle);
59+
}
60+
return new MultiscaleImage(storeHandle, group.metadata, omeMetadata);
61+
}
62+
63+
/**
64+
* Creates a new OME-Zarr v0.6 multiscale image at the given store handle.
65+
*/
66+
public static MultiscaleImage create(
67+
@Nonnull StoreHandle storeHandle,
68+
@Nonnull MultiscalesEntry multiscalesEntry
69+
) throws IOException, ZarrException {
70+
ObjectMapper mapper = makeObjectMapper();
71+
OmeMetadata omeMetadata = new OmeMetadata("0.6", Collections.singletonList(multiscalesEntry));
72+
@SuppressWarnings("unchecked")
73+
Map<String, Object> omeMap = mapper.convertValue(omeMetadata, Map.class);
74+
Attributes attributes = new Attributes();
75+
attributes.put("ome", omeMap);
76+
Group group = Group.create(storeHandle, attributes);
77+
return new MultiscaleImage(storeHandle, group.metadata, omeMetadata);
78+
}
79+
80+
@Override
81+
public StoreHandle getStoreHandle() {
82+
return this.storeHandle;
83+
}
84+
85+
@Override
86+
public MultiscalesEntry getMultiscalesEntry(int i) throws ZarrException {
87+
return omeMetadata.multiscales.get(i);
88+
}
89+
90+
@Override
91+
public dev.zarr.zarrjava.core.Array openScaleLevel(int i) throws IOException, ZarrException {
92+
String path = getMultiscalesEntry(0).datasets.get(i).path;
93+
return Array.open(storeHandle.resolve(path));
94+
}
95+
96+
@Override
97+
public int getScaleLevelCount() throws ZarrException {
98+
return getMultiscalesEntry(0).datasets.size();
99+
}
100+
101+
@Override
102+
public void createScaleLevel(
103+
String path,
104+
dev.zarr.zarrjava.core.ArrayMetadata arrayMetadata,
105+
List<CoordinateTransformation> coordinateTransformations
106+
) throws IOException, ZarrException {
107+
if (!(arrayMetadata instanceof dev.zarr.zarrjava.v3.ArrayMetadata)) {
108+
throw new ZarrException("Expected v3.ArrayMetadata for OME-Zarr v0.6, got " + arrayMetadata.getClass());
109+
}
110+
Array.create(storeHandle.resolve(path), (dev.zarr.zarrjava.v3.ArrayMetadata) arrayMetadata);
111+
112+
// Convert ome.metadata.CoordinateTransformation to v0.6 CoordinateTransformation
113+
List<dev.zarr.zarrjava.ome.v0_6.metadata.CoordinateTransformation> v06Transforms = new ArrayList<>();
114+
for (CoordinateTransformation ct : coordinateTransformations) {
115+
v06Transforms.add(new dev.zarr.zarrjava.ome.v0_6.metadata.CoordinateTransformation(
116+
ct.type, null, null, null, ct.scale, ct.translation, ct.path, null, null, null, null));
117+
}
118+
119+
MultiscalesEntry current = omeMetadata.multiscales.get(0);
120+
MultiscalesEntry updated = current.withDataset(new Dataset(path, v06Transforms));
121+
List<MultiscalesEntry> updatedList = new ArrayList<>(omeMetadata.multiscales);
122+
updatedList.set(0, updated);
123+
omeMetadata = new OmeMetadata(omeMetadata.version, updatedList);
124+
125+
ObjectMapper mapper = makeObjectMapper();
126+
@SuppressWarnings("unchecked")
127+
Map<String, Object> omeMap = mapper.convertValue(omeMetadata, Map.class);
128+
Attributes newAttributes = new Attributes();
129+
newAttributes.put("ome", omeMap);
130+
setAttributes(newAttributes);
131+
}
132+
133+
@Override
134+
public UnifiedMultiscaleNode getMultiscaleNode(int i) throws ZarrException {
135+
MultiscalesEntry entry = getMultiscalesEntry(i);
136+
List<UnifiedSinglescaleNode> nodes = new ArrayList<>();
137+
for (Dataset ds : entry.datasets) {
138+
List<CoordinateTransformation> mapped = new ArrayList<>();
139+
for (dev.zarr.zarrjava.ome.v0_6.metadata.CoordinateTransformation ct : ds.coordinateTransformations) {
140+
mapped.add(new CoordinateTransformation(ct.type, ct.scale, ct.translation, ct.path));
141+
}
142+
nodes.add(new UnifiedSinglescaleNode(ds.path, mapped));
143+
}
144+
// Axes: prefer entry.axes; fall back to first coordinateSystem's axes
145+
List<Axis> axes = entry.axes;
146+
if ((axes == null || axes.isEmpty()) && entry.coordinateSystems != null && !entry.coordinateSystems.isEmpty()) {
147+
axes = entry.coordinateSystems.get(0).axes;
148+
}
149+
return new UnifiedMultiscaleNode(entry.name, axes != null ? axes : Collections.<Axis>emptyList(), nodes);
150+
}
151+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dev.zarr.zarrjava.ome.v0_6.metadata;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
import dev.zarr.zarrjava.ome.metadata.Axis;
7+
8+
import java.util.List;
9+
10+
@JsonInclude(JsonInclude.Include.NON_NULL)
11+
public final class CoordinateSystem {
12+
13+
public final String name;
14+
public final List<Axis> axes;
15+
16+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
17+
public CoordinateSystem(
18+
@JsonProperty(value = "name", required = true) String name,
19+
@JsonProperty(value = "axes", required = true) List<Axis> axes
20+
) {
21+
this.name = name;
22+
this.axes = axes;
23+
}
24+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package dev.zarr.zarrjava.ome.v0_6.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 public final String input;
15+
@Nullable public final String output;
16+
@Nullable public final String name;
17+
@Nullable public final List<Double> scale;
18+
@Nullable public final List<Double> translation;
19+
@Nullable public final String path;
20+
@Nullable public final List<CoordinateTransformation> transformations;
21+
@Nullable public final List<Integer> mapAxis;
22+
@Nullable public final List<Double> affine;
23+
@Nullable public final CoordinateTransformation transformation;
24+
25+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
26+
public CoordinateTransformation(
27+
@JsonProperty(value = "type", required = true) String type,
28+
@Nullable @JsonProperty("input") String input,
29+
@Nullable @JsonProperty("output") String output,
30+
@Nullable @JsonProperty("name") String name,
31+
@Nullable @JsonProperty("scale") List<Double> scale,
32+
@Nullable @JsonProperty("translation") List<Double> translation,
33+
@Nullable @JsonProperty("path") String path,
34+
@Nullable @JsonProperty("transformations") List<CoordinateTransformation> transformations,
35+
@Nullable @JsonProperty("mapAxis") List<Integer> mapAxis,
36+
@Nullable @JsonProperty("affine") List<Double> affine,
37+
@Nullable @JsonProperty("transformation") CoordinateTransformation transformation
38+
) {
39+
this.type = type;
40+
this.input = input;
41+
this.output = output;
42+
this.name = name;
43+
this.scale = scale;
44+
this.translation = translation;
45+
this.path = path;
46+
this.transformations = transformations;
47+
this.mapAxis = mapAxis;
48+
this.affine = affine;
49+
this.transformation = transformation;
50+
}
51+
52+
public static CoordinateTransformation scale(List<Double> scale, String input, String output) {
53+
return new CoordinateTransformation("scale", input, output, null, scale, null, null, null, null, null, null);
54+
}
55+
56+
public static CoordinateTransformation translation(List<Double> translation, String input, String output) {
57+
return new CoordinateTransformation("translation", input, output, null, null, translation, null, null, null, null, null);
58+
}
59+
60+
public static CoordinateTransformation identity(String input, String output) {
61+
return new CoordinateTransformation("identity", input, output, null, null, null, null, null, null, null, null);
62+
}
63+
64+
public static CoordinateTransformation sequence(List<CoordinateTransformation> transformations, String input, String output) {
65+
return new CoordinateTransformation("sequence", input, output, null, null, null, null, transformations, null, null, null);
66+
}
67+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package dev.zarr.zarrjava.ome.v0_6.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 java.util.List;
8+
9+
@JsonInclude(JsonInclude.Include.NON_NULL)
10+
public final class Dataset {
11+
12+
public final String path;
13+
@JsonProperty("coordinateTransformations")
14+
public final List<CoordinateTransformation> coordinateTransformations;
15+
16+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
17+
public Dataset(
18+
@JsonProperty(value = "path", required = true) String path,
19+
@JsonProperty(value = "coordinateTransformations", required = true)
20+
List<CoordinateTransformation> coordinateTransformations
21+
) {
22+
this.path = path;
23+
this.coordinateTransformations = coordinateTransformations;
24+
}
25+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package dev.zarr.zarrjava.ome.v0_6.metadata;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
import dev.zarr.zarrjava.ome.metadata.Axis;
7+
8+
import javax.annotation.Nullable;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.Map;
12+
13+
@JsonInclude(JsonInclude.Include.NON_NULL)
14+
public final class MultiscalesEntry {
15+
16+
@Nullable public final List<Axis> axes;
17+
public final List<Dataset> datasets;
18+
@Nullable public final List<CoordinateSystem> coordinateSystems;
19+
@Nullable public final String name;
20+
@Nullable public final String type;
21+
@Nullable public final Map<String, Object> metadata;
22+
23+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
24+
public MultiscalesEntry(
25+
@Nullable @JsonProperty("axes") List<Axis> axes,
26+
@JsonProperty(value = "datasets", required = true) List<Dataset> datasets,
27+
@Nullable @JsonProperty("coordinateSystems") List<CoordinateSystem> coordinateSystems,
28+
@Nullable @JsonProperty("name") String name,
29+
@Nullable @JsonProperty("type") String type,
30+
@Nullable @JsonProperty("metadata") Map<String, Object> metadata
31+
) {
32+
this.axes = axes;
33+
this.datasets = datasets;
34+
this.coordinateSystems = coordinateSystems;
35+
this.name = name;
36+
this.type = type;
37+
this.metadata = metadata;
38+
}
39+
40+
public MultiscalesEntry(List<Dataset> datasets, List<CoordinateSystem> coordinateSystems, String name) {
41+
this(null, datasets, coordinateSystems, name, null, null);
42+
}
43+
44+
/** Returns a new MultiscalesEntry with the given dataset appended. */
45+
public MultiscalesEntry withDataset(Dataset dataset) {
46+
List<Dataset> updated = new ArrayList<>(this.datasets);
47+
updated.add(dataset);
48+
return new MultiscalesEntry(axes, updated, coordinateSystems, name, type, metadata);
49+
}
50+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package dev.zarr.zarrjava.ome.v0_6.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+
/** OME-Zarr v0.6 metadata stored under {@code attributes["ome"]}. */
11+
@JsonInclude(JsonInclude.Include.NON_NULL)
12+
public final class OmeMetadata {
13+
14+
public final String version;
15+
@Nullable public final List<MultiscalesEntry> multiscales;
16+
17+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
18+
public OmeMetadata(
19+
@JsonProperty(value = "version", required = true) String version,
20+
@Nullable @JsonProperty("multiscales") List<MultiscalesEntry> multiscales
21+
) {
22+
this.version = version;
23+
this.multiscales = multiscales;
24+
}
25+
}

0 commit comments

Comments
 (0)