Skip to content
live-image-tracking-toolsPublic

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Repository files navigation

Build Status License Coverage Lines of Code

GEFF Java

This repository contains the Java implementation of the geff specification.

The Graph Exchange File Format (GEFF) is a standardized format for storing and exchanging graphs, particularly for cell tracking and lineage analysis. This Java implementation provides comprehensive support for reading and writing GEFF data using the Zarr storage format.

Features

  • GEFF v1 spec compliance - Reads and writes GEFF v0.2 through v1.x and beyond; version validation uses a semver pattern rather than an allowlist
  • Zarr Format 2 - Reads and writes Zarr Format 2 only; Zarr Format 3 is not supported
  • Complete data model - Support for nodes (spatial-temporal features), edges (connections), and metadata
  • Flexible metadata handling - Axis-based metadata with GeffAxis objects; supports time, space, and channel axis types with any axis name
  • Custom axis names - Node property paths for coordinates (t/x/y/z) are derived from display_hints combined with the axis names declared in axes, so non-standard names such as frame, cell_x, cell_y work out of the box; standard names are used as fallbacks when neither is declared
  • Display hints - Full display_hints support (display_horizontal, display_vertical, display_depth, display_time), validated against the declared axis names
  • Property metadata - Full node_props_metadata / edge_props_metadata support as required by the v1 spec
  • Variable-length properties - Read and write properties with varlength: true (e.g. polygon coordinates per node)
  • Type safety - Strong typing with comprehensive validation; graceful skip with warning for unsupported types (str, bytes)
  • Adaptive chunking - Chunk sizes targeting ~8 MiB per chunk (power-of-two on the first dimension)
  • Builder patterns - Convenient object construction with builder classes for GeffNode and GeffEdge

Core Classes

GeffNode

Represents nodes in tracking graphs with spatial and temporal attributes:

  • Time point information (t property)
  • Spatial coordinates (x, y, z)
  • Segment identifiers (dynamic property name via metadata)
  • Additional properties: color, radius, covariance2d, covariance3d
  • Polygon geometry stored as a varlength property under nodes/props/polygon/
  • Variable-length properties accessible via getVarlengthProperty(name) / setVarlengthProperty(name, ...)
  • Arbitrary scalar/vector properties accessible via getProp(name) / setProp(name, value) / getProps()
  • Axis-aware I/O: property paths for time and spatial coordinates are resolved from GeffMetadata by combining display_hints and axes; falls back to t, x, y, z when neither is defined
  • Builder pattern for convenient object construction
  • Chunked Zarr Format 2 I/O

GeffEdge

Represents connections between nodes in tracking graphs:

  • Source and target node references
  • Edge properties: score, distance
  • Builder pattern for convenient object construction
  • Chunked storage for efficient large-scale edge data

GeffAxis

Represents axis metadata for spatial and temporal dimensions:

  • Predefined constants for common axis names (t, x, y, z)
  • Type classifications: time, space, channel
  • Unit specifications with common constants
  • Optional min/max bounds for ROI definition

GeffMetadata

Handles GEFF metadata with schema validation:

  • Version validation via semver pattern (accepts any well-formed version)
  • GeffAxis array supporting any axis name and the three axis types
  • Node/edge property metadata maps (nodePropsMetadata, edgePropsMetadata)
  • Dynamic tracklet property name from track_node_props["tracklet"]
  • Graph properties (directed/undirected)
  • display_hints as a GeffDisplayHint object, validated against the declared axis names
  • getAxisNameByType(type) – returns the name of the first axis matching a given type (e.g. "time")
  • getAxisNamesByType(type) – returns all axis names matching a given type (e.g. all "space" axes in order)
  • getAxisNames() – returns all axis names, in declaration order
  • getTimeAxisName() / getHorizontalAxisName() / getVerticalAxisName() / getDepthAxisName() – resolve which axis carries the t/x/y/z coordinate (see below)

GeffDisplayHint

Represents the optional display_hints metadata, i.e. which axis a viewer shows horizontally, vertically, as depth and as time. Since it is optional, GeffMetadata resolves each spatial coordinate from the following sources, in 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.

Depth resolves to null for datasets that declare spatial axes but no third one, in which case no z property is read or written.

PropMetadata

Describes a single node or edge property as required by the v1 spec:

  • Fields: identifier, dtype, varlength, unit, name, description
  • Used to infer data types on write and skip unsupported types on read

VarlengthProperty

Stores a variable-length property (one array per node with potentially different shapes):

  • getNodeData(int nodeIndex) – extract the data array for a specific node
  • isMissing(int nodeIndex) – check the optional missing-value indicator
  • Backed by a flattened data array and an offset/shape index

GeffSerializableVertex

Lightweight geometry class internally used for storing polygon vertex coordinates:

  • Simple (x, y) coordinate storage
  • Part of the geometry package for efficient polygon handling

Geff

Main utility class demonstrating library usage and providing examples.

Requirements

  • Java 8 or higher
  • Maven 3.6 or higher

Dependencies

  • n5 - N5/Zarr core data model
  • n5-zarr - Zarr format reader/writer
  • n5-blosc - Optional Blosc compression (falls back to raw compression if the native library is absent)
  • imglib2 - Multi-dimensional array and interval utilities
  • slf4j-api - Logging facade

Usage Example

import org.mastodon.geff.GeffEdge;
import org.mastodon.geff.GeffMetadata;
import org.mastodon.geff.GeffNode;
import org.mastodon.geff.GeffAxis;
import java.util.List;
import java.util.ArrayList;

// Read Geff data from Zarr
List<GeffNode> nodes = GeffNode.readFromZarr("/path/to/data.zarr/tracks");
List<GeffEdge> edges = GeffEdge.readFromZarr("/path/to/data.zarr/tracks");
GeffMetadata metadata = GeffMetadata.readFromZarr("/path/to/data.zarr/tracks");

// Create new Geff data using builder pattern
List<GeffNode> newNodes = new ArrayList<>();
GeffNode node0 = new GeffNode.Builder()
    .id(0)
    .timepoint(0)
    .x(10.5)
    .y(20.3)
    .z(5.0)
    .segmentId(0)
    .color(new double[]{1.0, 0.0, 0.0, 1.0}) // Red color
    .radius(2.5)
    .covariance2d(new double[]{1.0, 0.2, 0.2, 1.5}) // 2x2 covariance matrix flattened
    .polygonX(new double[]{1.0, 2.0, 3.0, 4.0}) // Polygon X coordinates
    .polygonY(new double[]{5.0, 6.0, 7.0, 8.0}) // Polygon Y coordinates
    .build();
newNodes.add(node0);

GeffNode node1 = new GeffNode.Builder()
    .id(1)
    .timepoint(1)
    .x(11.5)
    .y(21.3)
    .z(6.0)
    .segmentId(1)
    .covariance2d(new double[]{0.8, 0.1, 0.1, 1.2}) // Different covariance
    .polygonX(new double[]{-1.0, -2.0, -3.0, -4.0}) // Different polygon X coordinates
    .polygonY(new double[]{-5.0, -6.0, -7.0, -8.0}) // Different polygon Y coordinates
    .build();
newNodes.add(node1);

// Write to Zarr format with version specification
GeffNode.writeToZarr(newNodes, "/path/to/output.zarr/tracks", "1.0.0");

// Create new edges using builder pattern
List<GeffEdge> newEdges = new ArrayList<>();
GeffEdge edge = new GeffEdge.Builder()
    .setId(0)
    .setSourceNodeId(0)
    .setTargetNodeId(1)
    .setScore(0.95)
    .setDistance(1.4)
    .build();
newEdges.add(edge);

// Write to Zarr format
GeffEdge.writeToZarr(newEdges, "/path/to/output.zarr/tracks", "1.0.0");

// Set a custom variable-length property per node.
// Example: per-node UTF-8 label stored as uint8 byte array.
// (Note: polygon vertices use the dedicated polygonX/polygonY fields instead.)
String label0 = "cell_A";
String label1 = "細胞_B"; // multibyte characters are supported via UTF-8 encoding
byte[] bytes0 = label0.getBytes(java.nio.charset.StandardCharsets.UTF_8);
byte[] bytes1 = label1.getBytes(java.nio.charset.StandardCharsets.UTF_8);

Object[] data0 = new Object[bytes0.length];
for (int i = 0; i < bytes0.length; i++) data0[i] = bytes0[i] & 0xFF;

Object[] data1 = new Object[bytes1.length];
for (int i = 0; i < bytes1.length; i++) data1[i] = bytes1[i] & 0xFF;

node0.setVarlengthProperty("label", new VarlengthProperty("label", "uint8", data0));
node1.setVarlengthProperty("label", new VarlengthProperty("label", "uint8", data1));

// Decode the label back after reading
List<GeffNode> readNodes = GeffNode.readFromZarr("/path/to/data.zarr/tracks");
VarlengthProperty labelProp = readNodes.get(0).getVarlengthProperty("label");
if (labelProp != null && !labelProp.isMissing()) {
    Object[] labelData = labelProp.getData();
    byte[] labelBytes = new byte[labelData.length];
    for (int i = 0; i < labelData.length; i++) labelBytes[i] = ((Number) labelData[i]).byteValue();
    String label = new String(labelBytes, java.nio.charset.StandardCharsets.UTF_8);
}

// Create metadata with axis information
GeffAxis[] axes = {
    new GeffAxis(GeffAxis.NAME_TIME, GeffAxis.TYPE_TIME, GeffAxis.UNIT_SECOND, 0.0, 100.0),
    new GeffAxis(GeffAxis.NAME_SPACE_X, GeffAxis.TYPE_SPACE, GeffAxis.UNIT_MICROMETER, 0.0, 1024.0),
    new GeffAxis(GeffAxis.NAME_SPACE_Y, GeffAxis.TYPE_SPACE, GeffAxis.UNIT_MICROMETER, 0.0, 1024.0),
    new GeffAxis(GeffAxis.NAME_SPACE_Z, GeffAxis.TYPE_SPACE, GeffAxis.UNIT_MICROMETER, 0.0, 100.0)
};
GeffMetadata metadata = new GeffMetadata("1.0.0", true, axes);
GeffMetadata.writeToZarr(metadata, "/path/to/output.zarr/tracks");

// Use custom axis names (e.g. "frame", "cell_x", "cell_y" instead of "t", "x", "y")
// Node property paths are resolved from the axis names declared in metadata.
GeffAxis[] customAxes = {
    new GeffAxis("frame",  GeffAxis.TYPE_TIME,  "frame",  0.0, 500.0),
    new GeffAxis("cell_x", GeffAxis.TYPE_SPACE, "pixel",  0.0, 1024.0),
    new GeffAxis("cell_y", GeffAxis.TYPE_SPACE, "pixel",  0.0, 768.0)
};
GeffMetadata customMetadata = new GeffMetadata("1.0.0", true, customAxes);
// GeffNode.writeToZarr will write to nodes/props/frame/values,
// nodes/props/cell_x/values, nodes/props/cell_y/values automatically.
GeffNode.writeToZarr(newNodes, "/path/to/output.zarr/tracks", customMetadata);
GeffMetadata.writeToZarr(customMetadata, "/path/to/output.zarr/tracks");

// When reading back, pass the metadata so axis names are resolved correctly:
GeffMetadata readMetadata = GeffMetadata.readFromZarr("/path/to/output.zarr/tracks");
List<GeffNode> readNodes = GeffNode.readFromZarr("/path/to/output.zarr/tracks", readMetadata);

// Query axis names from metadata directly:
String timeAxis  = readMetadata.getAxisNameByType("time");   // "frame"
String[] spaceAxes = readMetadata.getAxisNamesByType("space"); // ["cell_x", "cell_y"]

Building

geff-java prefers BloscCompression for writing datasets when c-blosc is available. If Blosc is not installed, it will print a warning and automatically fall back to RawCompression.

mvn clean compile
mvn test
mvn package

Cross-Language Tests

Round-trip tests validate interoperability between geff-java and the Python reference implementation.

Requirements:

  • uv (Python package manager)

Optional compression support:

  • If c-blosc is installed, geff-java will use BloscCompression for output datasets.
  • If Blosc is not available, geff-java prints a warning and automatically falls back to RawCompression, so end users can still run the library and the cross-language tests without extra native setup.
  • On macOS, Blosc can be installed with brew install c-blosc.

Run tests:

mvn package -DskipTests
cd cross-language-tests
uv run run_tests.py

The tests create GEFF files with Python, read/write them with Java, and validate the results.

Data Format

The library follows the Geff specification for biological tracking data:

dataset.zarr/
├── .zgroup
├── .zattrs                         # Geff metadata:
│                                   #   version, directed, axes,
│                                   #   node_props_metadata,
│                                   #   edge_props_metadata,
│                                   #   track_node_props, display_hints
└── tracks/
    ├── .zgroup
    ├── nodes/
    │   ├── ids/                    # Node IDs [N]
    │   ├── props/
    │   │   ├── <t>/values          # Time points [N]   (name from axes[type=time], default "t")
    │   │   ├── <x>/values          # X coordinates [N] (name from display_hints/axes, default "x")
    │   │   ├── <y>/values          # Y coordinates [N] (name from display_hints/axes, default "y")
    │   │   ├── <z>/values          # Z coordinates [N] (name from display_hints/axes, default "z", optional)
    │   │   ├── color/values        # RGBA colors [N, 4] (optional)
    │   │   ├── radius/values       # Node radii [N] (optional)
    │   │   ├── <tracklet>/values   # Track IDs [N] (name from track_node_props, optional)
    │   │   ├── covariance2d/values # Flattened 2D covariance [N, 4] (optional)
    │   │   ├── covariance3d/values # Flattened 3D covariance [N, 6] (optional)
    │   │   └── <varlength_prop>/   # Variable-length property, e.g. polygon (optional)
    │   │       ├── data            # Flattened values [V]
    │   │       ├── values          # Offsets and shapes [N, ndim+1]
    │   │       └── missing         # Missing-value mask [N] (optional)
    └── edges/
        ├── ids/                    # Source/target node ID pairs [N, 2]
        └── props/
            ├── distance/values     # Edge distances [N] (optional)
            └── score/values        # Edge scores [N] (optional)

Technical Information

Maintainer

Contributors

License

Contribute Code or Provide Feedback

  • You are welcome to submit Pull Requests to this repository. This repository runs code analyses on every Pull Request using SonarCloud.
  • Please read the general advice for contributing to Live Image Tracking Tools and its projects.

Contribute Documentation

  • If you would like to contribute to this documentation, feel free to open a pull request. The documentation is written in Markdown format.

Acknowledgements

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages