Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions USERGUIDE-OME-ZARR.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,25 @@ Scene roots (groups with `ome.scene`) are supported via `dev.zarr.zarrjava.exper
Notes:
- Parsing is permissive and explicit (no strict full-spec validation).
- Scene-level references (`input`/`output`) are resolved against scene-root coordinate systems and child image coordinate systems for graph inspection.

### v0.6 coordinate system references

In v0.6, the `input`/`output` of a coordinate transformation is a `CoordinateSystemRef` (`{"name": ..., "path": ...}`, both optional):

```java
import dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateSystemRef;
import dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateTransformation;

// multiscales > datasets: input references the dataset path, output the intrinsic coordinate system
CoordinateTransformation.scale(Arrays.asList(0.5, 0.5),
CoordinateSystemRef.ofPath("s0"), CoordinateSystemRef.ofName("physical"));
// scene > coordinateTransformations: name + path of the image group
CoordinateTransformation.identity(
CoordinateSystemRef.of("physical", "imageA"), CoordinateSystemRef.of("physical", "imageB"));
```

`createScaleLevel(...)` on a v0.6 image fills these in automatically (input `{"path": <level path>}`, output `{"name": <intrinsic>}`, where the intrinsic name is taken from existing datasets or the first entry of `coordinateSystems`); passing several transformations wraps them in a `sequence`.
Pre-release string references (e.g. `"input": "s0"`, `"output": "physical"`) are still read: a bare string is a coordinate system name, except the input of a dataset transformation, which is the dataset path. They are always written back in object form.
- Path-based transform assets can be normalized with `Scene.normalizeCoordinateTransformPath(...)` and grouped under `coordinateTransformations/` via `createCoordinateTransformationsGroup()`.

## Read example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,21 @@ public void createScaleLevel(
if (!(arrayMetadata instanceof dev.zarr.zarrjava.v3.ArrayMetadata)) {
throw new ZarrException("Expected v3.ArrayMetadata for OME-Zarr v0.6, got " + arrayMetadata.getClass());
}
String intrinsicName = intrinsicCoordinateSystemName(omeMetadata.multiscales.get(0));
Array.create(storeHandle.resolve(path), (dev.zarr.zarrjava.v3.ArrayMetadata) arrayMetadata);

// Convert ome.metadata.CoordinateTransformation to v0.6 CoordinateTransformation
// Per the 0.6 spec, a dataset carries a single transformation (scale, identity, or a sequence of
// scale + translation) whose input is {"path": <dataset path>} and whose output is
// {"name": <intrinsic coordinate system>}. Multiple transformations are wrapped in a sequence;
// nested transformations carry no input/output.
boolean wrapInSequence = coordinateTransformations.size() > 1;
dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateSystemRef datasetInput =
dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateSystemRef.ofPath(path);
dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateSystemRef datasetOutput =
dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateSystemRef.ofName(intrinsicName);
dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateSystemRef input = wrapInSequence ? null : datasetInput;
dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateSystemRef output = wrapInSequence ? null : datasetOutput;
List<dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateTransformation> v06Transforms = new ArrayList<>();
for (CoordinateTransformation ct : coordinateTransformations) {
String type = ct.type;
Expand Down Expand Up @@ -156,63 +168,68 @@ public void createScaleLevel(

if ("sequence".equals(type)) {
v06Transforms.add(new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.SequenceCoordinateTransformation(
null, null, null, castV06TransformList(raw.get("transformations"))));
input, output, null, castV06TransformList(raw.get("transformations"))));
continue;
}
if ("mapAxis".equals(type)) {
v06Transforms.add(new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.MapAxisCoordinateTransformation(
null, null, null, castIntList(raw.get("mapAxis")), castV06Transform(raw.get("transformation"))));
input, output, null, castIntList(raw.get("mapAxis")), castV06Transform(raw.get("transformation"))));
continue;
}
if ("affine".equals(type)) {
v06Transforms.add(new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.AffineCoordinateTransformation(
null, null, null, castMatrix(raw.get("affine")), rawPath));
input, output, null, castMatrix(raw.get("affine")), rawPath));
continue;
}
if ("rotation".equals(type)) {
v06Transforms.add(new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.RotationCoordinateTransformation(
null, null, null, castMatrix(raw.get("rotation")), rawPath));
input, output, null, castMatrix(raw.get("rotation")), rawPath));
continue;
}
if ("displacements".equals(type)) {
v06Transforms.add(new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.DisplacementsCoordinateTransformation(
null, null, null, rawPath));
input, output, null, rawPath));
continue;
}
if ("coordinates".equals(type)) {
v06Transforms.add(new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinatesCoordinateTransformation(
null, null, null, rawPath));
input, output, null, rawPath));
continue;
}
if ("bijection".equals(type)) {
v06Transforms.add(new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.BijectionCoordinateTransformation(
null, null, null, castV06Transform(raw.get("forward")), castV06Transform(raw.get("inverse"))));
input, output, null, castV06Transform(raw.get("forward")), castV06Transform(raw.get("inverse"))));
continue;
}
if ("byDimension".equals(type)) {
v06Transforms.add(new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.ByDimensionCoordinateTransformation(
null, null, null, castByDimensionTransformList(raw.get("transformations"))));
input, output, null, castByDimensionTransformList(raw.get("transformations"))));
continue;
}
}
if ("scale".equals(type)) {
v06Transforms.add(new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.ScaleCoordinateTransformation(
null, null, null, scale, rawPath));
input, output, null, scale, rawPath));
} else if ("translation".equals(type)) {
v06Transforms.add(new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.TranslationCoordinateTransformation(
null, null, null, translation, rawPath));
input, output, null, translation, rawPath));
} else if ("identity".equals(type)) {
v06Transforms.add(new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.IdentityCoordinateTransformation(
null, null, null, rawPath));
input, output, null, rawPath));
} else {
dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.GenericCoordinateTransformation generic =
new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.GenericCoordinateTransformation(type, null, null, null);
new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.GenericCoordinateTransformation(type, input, output, null);
if (scale != null) generic.raw.put("scale", scale);
if (translation != null) generic.raw.put("translation", translation);
if (rawPath != null) generic.raw.put("path", rawPath);
v06Transforms.add(generic);
}
}
if (wrapInSequence) {
v06Transforms = Collections.<dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateTransformation>singletonList(
new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.SequenceCoordinateTransformation(
datasetInput, datasetOutput, null, v06Transforms));
}

MultiscalesEntry current = omeMetadata.multiscales.get(0);
MultiscalesEntry updated = current.withDataset(new dev.zarr.zarrjava.experimental.ome.v0_6.metadata.Dataset(path, v06Transforms));
Expand All @@ -229,13 +246,47 @@ public void createScaleLevel(
setAttributes(omeAttributes(omeMetadata));
}

/**
* Name of the "intrinsic" coordinate system of a multiscale: the output of the existing dataset
* transformations if any, otherwise the first entry of {@code coordinateSystems}.
*/
private static String intrinsicCoordinateSystemName(MultiscalesEntry entry) throws ZarrException {
if (entry.datasets != null) {
for (dev.zarr.zarrjava.experimental.ome.v0_6.metadata.Dataset ds : entry.datasets) {
if (ds.coordinateTransformations == null) {
continue;
}
for (dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateTransformation ct : ds.coordinateTransformations) {
if (ct != null && ct.getOutput() != null && ct.getOutput().name != null) {
return ct.getOutput().name;
}
}
}
}
if (entry.coordinateSystems != null && !entry.coordinateSystems.isEmpty()
&& entry.coordinateSystems.get(0).name != null) {
return entry.coordinateSystems.get(0).name;
}
throw new ZarrException(
"Cannot determine the intrinsic coordinate system for a new OME-Zarr v0.6 scale level: "
+ "the multiscales entry has neither dataset transformations with an output nor coordinateSystems");
}

@Override
public dev.zarr.zarrjava.experimental.ome.metadata.MultiscalesEntry getMultiscaleNode(int i) throws ZarrException {
MultiscalesEntry entry = getMultiscalesEntry(i);
List<dev.zarr.zarrjava.experimental.ome.metadata.Dataset> mappedDatasets = new ArrayList<>();
for (dev.zarr.zarrjava.experimental.ome.v0_6.metadata.Dataset ds : entry.datasets) {
List<CoordinateTransformation> mapped = new ArrayList<>();
for (dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateTransformation ct : ds.coordinateTransformations) {
if (ct instanceof dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.SequenceCoordinateTransformation
&& ((dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.SequenceCoordinateTransformation) ct).transformations != null) {
// 0.6 datasets wrap scale + translation in a sequence; the version-independent view
// uses the flat [scale, translation] list of earlier versions.
mapped.addAll(mapTransformList(
((dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.SequenceCoordinateTransformation) ct).transformations));
continue;
}
mapped.add(mapTransform(ct));
}
mappedDatasets.add(new dev.zarr.zarrjava.experimental.ome.metadata.Dataset(ds.path, mapped));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dev.zarr.zarrjava.experimental.ome.v0_6.metadata.CoordinateSystem;
import dev.zarr.zarrjava.experimental.ome.v0_6.metadata.MultiscalesEntry;
import dev.zarr.zarrjava.experimental.ome.v0_6.metadata.SceneMetadata;
import dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateSystemRef;

import javax.annotation.Nullable;
import java.util.ArrayList;
Expand Down Expand Up @@ -40,15 +41,16 @@ final class SceneReferenceResolver {
}
}

/**
* Resolves a scene-level reference. A {@code null}/empty path refers to the scene's own
* {@code coordinateSystems}; otherwise the path names an image subgroup of the scene.
*/
@Nullable
ResolvedCoordinateSystem resolve(@Nullable String reference) {
if (reference == null || reference.isEmpty()) {
ResolvedCoordinateSystem resolve(@Nullable CoordinateSystemRef reference) {
if (reference == null || reference.name == null) {
return null;
}
if (!reference.contains("#")) {
return null;
}
return index.get(reference);
return index.get(nodeId(reference.path, reference.name));
}

List<ResolvedCoordinateSystem> list() {
Expand All @@ -59,11 +61,24 @@ private void addCoordinateSystem(String groupPath, CoordinateSystem coordinateSy
if (coordinateSystem == null || coordinateSystem.name == null) {
return;
}
String canonicalPath = groupPath == null || groupPath.isEmpty() ? "." : groupPath;
String id = canonicalPath + "#" + coordinateSystem.name;
String canonicalPath = canonicalGroupPath(groupPath);
String id = nodeId(canonicalPath, coordinateSystem.name);
index.put(id, new ResolvedCoordinateSystem(id, canonicalPath, coordinateSystem));
}

private static String canonicalGroupPath(@Nullable String groupPath) {
String normalized = Scene.normalizeCoordinateTransformPath(groupPath);
while (normalized != null && normalized.endsWith("/")) {
normalized = normalized.substring(0, normalized.length() - 1);
}
return normalized == null || normalized.isEmpty() || ".".equals(normalized) ? "." : normalized;
}

/** Graph node id: {@code "<groupPath>#<name>"}, with {@code "."} as the scene root's group path. */
private static String nodeId(@Nullable String groupPath, String name) {
return canonicalGroupPath(groupPath) + "#" + name;
}

static final class ResolvedCoordinateSystem {
final String id;
final String groupPath;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.node.ObjectNode;
import dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateTransformation;

import java.io.IOException;
import java.util.List;

@JsonInclude(JsonInclude.Include.NON_NULL)
Expand All @@ -18,9 +26,38 @@ public final class Dataset {
public Dataset(
@JsonProperty(value = "path", required = true) String path,
@JsonProperty(value = "coordinateTransformations", required = true)
@JsonDeserialize(using = TransformationsDeserializer.class)
List<CoordinateTransformation> coordinateTransformations
) {
this.path = path;
this.coordinateTransformations = coordinateTransformations;
}

/**
* Reads dataset-level transformations, mapping the legacy (pre-release 0.6) bare-string
* {@code "input": "<dataset path>"} to {@code {"path": "<dataset path>"}}. Everywhere else a bare
* string reference is read as a coordinate system name (see
* {@link dev.zarr.zarrjava.experimental.ome.v0_6.metadata.transform.CoordinateSystemRef}).
*/
static final class TransformationsDeserializer extends JsonDeserializer<List<CoordinateTransformation>> {
@Override
public List<CoordinateTransformation> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode tree = p.readValueAsTree();
if (tree != null && tree.isArray()) {
for (JsonNode item : tree) {
if (item instanceof ObjectNode) {
ObjectNode obj = (ObjectNode) item;
JsonNode input = obj.get("input");
if (input != null && input.isTextual()) {
ObjectNode ref = obj.objectNode();
ref.put("path", input.asText());
obj.set("input", ref);
}
}
}
}
JavaType type = ctxt.getTypeFactory().constructCollectionType(List.class, CoordinateTransformation.class);
return ctxt.readTreeAsValue(tree, type);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import javax.annotation.Nullable;
import java.util.List;
Expand All @@ -13,8 +12,8 @@ public final class AffineCoordinateTransformation extends BaseCoordinateTransfor

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public AffineCoordinateTransformation(
@Nullable @JsonProperty("input") @JsonDeserialize(using = CoordinateSystemRefSerde.Deserializer.class) String input,
@Nullable @JsonProperty("output") @JsonDeserialize(using = CoordinateSystemRefSerde.Deserializer.class) String output,
@Nullable @JsonProperty("input") CoordinateSystemRef input,
@Nullable @JsonProperty("output") CoordinateSystemRef output,
@Nullable @JsonProperty("name") String name,
@Nullable @JsonProperty("affine") List<List<Double>> affine,
@Nullable @JsonProperty("path") String path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

abstract class BaseCoordinateTransformation implements CoordinateTransformation {
public final String type;
@Nullable public final String input;
@Nullable public final String output;
@Nullable public final CoordinateSystemRef input;
@Nullable public final CoordinateSystemRef output;
@Nullable public final String name;

protected BaseCoordinateTransformation(
String type,
@Nullable String input,
@Nullable String output,
@Nullable CoordinateSystemRef input,
@Nullable CoordinateSystemRef output,
@Nullable String name
) {
this.type = type;
Expand All @@ -26,12 +26,12 @@ public String getType() {
}

@Override
public String getInput() {
public CoordinateSystemRef getInput() {
return input;
}

@Override
public String getOutput() {
public CoordinateSystemRef getOutput() {
return output;
}

Expand Down
Loading
Loading