Skip to content

Resolve node axis names from display_hints - #10

Merged
ksugar merged 15 commits into
mainfrom
fix/displayhint-handling
Sep 8, 2026
Merged

ksugar merged 15 commits into
mainfrom
fix/displayhint-handling

Conversation

@ksugar

@ksugar ksugar commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Resolve node axis names from display_hints, falling back to axes order

Add GeffDisplayHint to model the optional geff/display_hints attribute (display_horizontal/vertical/depth/time), and have GeffMetadata resolve each t/x/y/z axis name by combining display_hints with the declared axes in the following order:

  1. the matching display hint, which names the axis explicitly;
  2. a spatial axis named x, y or z (case-insensitive), or one whose name ends in _x, _y, _z (e.g. cell_x);
  3. the position in the axes list, which the spec defines as image dimension order, i.e. slowest to fastest (z, y, x) and therefore counted from the end;
  4. the standard name (x, y, z) for datasets that declare no spatial axes at all.

GeffNode now reads/writes coordinates using these resolved names, and correctly omits the Z property for datasets with no third spatial axis instead of guessing "z".

Did this in part with Claude

tinevez and others added 10 commits July 20, 2026 16:01
Previously, VarlengthProperty held a flattened data array and an offset
table covering all nodes at once. This forced callers to pass a node
index to getData (getNodeData(i)) and isMissing(i), and every node in a
list ended up sharing the same VarlengthProperty object — a confusing
mismatch with the single-node abstraction of GeffNode and GeffEdge.

Each VarlengthProperty now stores the data slice for exactly one
node/edge. The per-node constructors are:

    new VarlengthProperty(name, dtype, data)
    new VarlengthProperty(name, dtype, data, missing)

getData() and isMissing() take no index. The old bulk constructor and
getNodeData(int) / getOffsets() are removed.

GeffUtils.readVarlengthProperty now returns VarlengthProperty[] (one
entry per node). GeffNode and GeffEdge split the returned array when
building node/edge objects on read, and assemble per-node data into the
bulk format required by writeVarlengthProperty on write.
Add proper uint8 read/write support to varlength properties so that
variable-length byte sequences — such as per-node labels encoded as
UTF-8 — can be stored and retrieved correctly.

On write, Integer elements are now converted to a byte[] before being
stored when the declared dtype is "uint8", avoiding the int-to-byte
sign mismatch that would otherwise occur via SubArrayCopy.

On read, the UINT8 zarr array is returned by N5 as a Java byte[].
convertVarlengthData now handles byte[] by applying "& 0xFF" to each
element, yielding unsigned Integer values (0–255) in the Object[] that
callers receive from VarlengthProperty.getData().

Multibyte characters (e.g. Japanese) work correctly because the
encoding/decoding is entirely UTF-8 byte-level; the varlength layer
treats the data as opaque uint8 values.

Update README to show the label/uint8 pattern in place of the polygon
example, which already has dedicated polygonX/polygonY fields and is
therefore a misleading illustration of arbitrary varlength properties.

Add testUint8LabelRoundTrip to VarlengthPropertyWriteTest covering both
ASCII ("cell_A") and multibyte ("細胞_B") labels.
Careful: double check deserialization.
Still hacky, but temporary. We miss the support of querying labels
paths anyway.
Resolve node axis names from display_hints, falling back to axes order

Add GeffDisplayHint to model the optional geff/display_hints attribute (display_horizontal/vertical/depth/time), and have GeffMetadata resolve each t/x/y/z axis name by combining display_hints with the declared axes in the following order:

1. the matching display hint, which names the axis explicitly;
2. a spatial axis named `x`, `y` or `z` (case-insensitive), or one whose name ends in `_x`, `_y`, `_z` (e.g. `cell_x`);
3. the position in the `axes` list, which the spec defines as image dimension order, i.e. slowest to fastest (`z`, `y`, `x`) and therefore counted from the end;
4. the standard name (`x`, `y`, `z`) for datasets that declare no spatial axes at all.

GeffNode now reads/writes coordinates using these resolved names, and correctly omits the Z property for datasets with no third spatial axis instead of guessing "z".

Did this in part with Claude
@ksugar
ksugar requested a review from tinevez September 8, 2026 07:55
@tinevez

tinevez commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Hello @ksugar

Could you also check the work done in this branch: https://github.com/live-image-tracking-tools/geff-java/commits/metadata-extra/

@ksugar

ksugar commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

@tinevez Thank you for the feedback. You have already worked on it. I will pick it up here.

@tinevez

tinevez commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Actually your version is much much better.
I would be in favor of merging this PR, and me rebasing my work on it.

DisplayHints class was renamed to GeffDisplayHints, and was made gson-serializable.
Add validation code for GeffDisplayHints.
GeffDisplayHints, RelatedObjects, extra metadata were taken into account for the toString(), equals( Object obj ), and hashCode() methods.
@ksugar

ksugar commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

@tinevez I put back my implementation. The only difference from the initial commit was the following point:

  • GeffDisplayHints is now public static class inside GeffMetadata, which is consistent with RelatedObjects.

@ksugar
ksugar merged commit 2392b51 into main Sep 8, 2026
1 check failed
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.

2 participants