๐Ÿ“˜ TypeScript / JavaScript Interfaceยถ

NGFF-Zarr provides a TypeScript implementation for working with OME-Zarr data structures in Node.js and browser environments. The TypeScript API mirrors the Python interface, providing a familiar and consistent experience across languages.

โœจ Featuresยถ

  • ๐Ÿ“˜ TypeScript-first: Written in TypeScript, shipped with declarations

  • ๐Ÿ“ฆ Universal compatibility: Works in Node.js and browsers

  • ๐Ÿ” Type-safe: Full TypeScript support with Zod schema validation

  • ๐Ÿ—‚๏ธ OME-Zarr support: Read OME-Zarr v0.4, v0.5, and v0.6, and write v0.5 and v0.6 (v0.6 adds RFC-5 coordinate systems and transformations), plus the opt-in development version 0.9.dev1 which adopts RFC-3 (extended axis count, names, types and order)

  • ๐Ÿงช Well-tested: Comprehensive test suite with browser validation

  • ๐Ÿ—๏ธ Mirrors Python API: Familiar interfaces for Python users

  • ๐Ÿ“– Lazy loading: Efficient handling of large datasets

  • ๐ŸŒ Web ready: No filesystem dependencies, works with remote stores

๐Ÿ“ฆ Installationยถ

npm (Node.js and Bundlers)ยถ

For Node.js projects and bundlers such as Vite or webpack:

npm install @fideus-labs/ngff-zarr
import * as ngffZarr from "@fideus-labs/ngff-zarr";

The codec worker ships as a separate file in dist/workers/ and is spawned with new Worker(new URL("../workers/omero_codec_worker.js", import.meta.url), { type: "module" }), the form bundlers such as Vite and webpack 5 recognise as a worker entry point and emit alongside your bundle without configuration.

Browser (CDN)ยถ

For direct browser usage via CDN:

<script type="module">
  import * as ngffZarr from "https://esm.sh/@fideus-labs/ngff-zarr";

  // Your code here
</script>

๐Ÿš€ Quick Startยถ

Reading OME-Zarr Filesยถ

Read an OME-Zarr file and access the multiscale data:

import { fromOmeZarr } from "@fideus-labs/ngff-zarr";

// Read from a local file (Node.js)
const multiscales = await fromOmeZarr("path/to/image.ome.zarr");

// Read from a remote URL (works in all environments)
const remoteMultiscales = await fromOmeZarr(
  "https://example.com/data.ome.zarr"
);

// Access the image data
console.log(`Loaded ${multiscales.images.length} scale levels`);
console.log(`Image shape: ${multiscales.images[0].data.shape}`);
console.log(`Data type: ${multiscales.images[0].data.dtype}`);

// Access metadata
console.log(`Axes: ${JSON.stringify(multiscales.metadata.axes)}`);
console.log(`Version: ${multiscales.metadata.version}`);

Validating OME-Zarr Metadataยถ

Enable validation to ensure OME-Zarr files conform to the specification:

import { fromOmeZarr } from "@fideus-labs/ngff-zarr";

// Validate during reading
const multiscales = await fromOmeZarr("image.ome.zarr", {
  validate: true,
});

// Specify expected version
const multiscalesV6 = await fromOmeZarr("image.ome.zarr", {
  validate: true,
  version: "0.6",
});

Creating and Writing OME-Zarr Filesยถ

Create a simple OME-Zarr file from scratch:

import {
  createAxis,
  createDataset,
  createMetadata,
  createMultiscales,
  createNgffImage,
  toOmeZarr,
} from "@fideus-labs/ngff-zarr";

// Create image data (256x256 grayscale)
const data = new Uint8Array(256 * 256);
for (let i = 0; i < data.length; i++) {
  data[i] = Math.floor(Math.random() * 256);
}

// Create an NgffImage with metadata
const image = createNgffImage(
  data.buffer,
  [256, 256],           // shape
  "uint8",              // dtype
  ["y", "x"],          // dimension names
  { y: 1.0, x: 1.0 },  // scale (pixel spacing)
  { y: 0.0, x: 0.0 }   // translation (origin)
);

// Create OME-Zarr metadata
const axes = [
  createAxis("y", "space", "micrometer"),
  createAxis("x", "space", "micrometer"),
];

const datasets = [
  createDataset(
    "0",                           // path
    [1.0, 1.0],                   // scale
    [0.0, 0.0]                    // translation
  ),
];

const metadata = createMetadata(axes, datasets, "my_image");

// Create multiscales container
const multiscales = createMultiscales([image], metadata);

// Write to OME-Zarr
await toOmeZarr("output.ome.zarr", multiscales);

Generating Multiscale Pyramidsยถ

Create multiscale image pyramids with downsampling:

import {
  createNgffImage,
  toMultiscales,
  toOmeZarr,
  Methods,
} from "@fideus-labs/ngff-zarr";

// Create base image
const data = new Uint8Array(512 * 512);
const image = createNgffImage(
  data.buffer,
  [512, 512],
  "uint8",
  ["y", "x"],
  { y: 1.0, x: 1.0 },
  { y: 0.0, x: 0.0 }
);

// Generate multiscale pyramid with 2x and 4x downsampling
const multiscales = await toMultiscales(image, {
  scaleFactors: [2, 4],
  method: Methods.ITKWASM_GAUSSIAN,
  chunks: 128,
});

// Write the pyramid
await toOmeZarr("pyramid.ome.zarr", multiscales);

Displacement and coordinate fields (v0.6)ยถ

OME-Zarr v0.6 (RFC-5) stores displacement and coordinate fields as ordinary multiscale images. The vector-component axis, in the channel position, carries type: "displacement" (or type: "coordinate") instead of type: "channel". Like a channel/component axis it is discrete (it indexes vector components rather than a continuous coordinate). Set the type with the axesTypes option of the image (createNgffImage / toNgffImage), which maps a dimension name to its axis type:

import {
  createNgffImage,
  toMultiscales,
  toOmeZarr,
} from "@fideus-labs/ngff-zarr";

// A 2-component displacement field over a yx image: the "c" dimension holds the
// (y, x) displacement vector, one component per output spatial axis.
const data = new Float32Array(2 * 256 * 256);
const field = await createNgffImage(
  data.buffer,
  [2, 256, 256],
  "float32",
  ["c", "y", "x"],
  { c: 1.0, y: 1.0, x: 1.0 },
  { c: 0.0, y: 0.0, x: 0.0 },
  "displacement_field",
  { c: "displacement" },
);

const multiscales = await toMultiscales(field);
await toOmeZarr("displacement.ome.zarr", multiscales, { version: "0.6" });

The axis type round-trips through reading and writing.

OME-Zarr v0.6 also models the displacements and coordinates coordinate transformations (the Displacements and Coordinates types) that reference such a field from a registered image. Like the other v0.6 transforms, they round-trip through reading and writing.

A typed component axis says what the channels are; it does not say between which coordinate systems the field maps, so nothing can apply it. declareFieldTransform adds that entry, validated:

import { declareFieldTransform } from "@fideus-labs/ngff-zarr";

// The standalone store -- the artifact a registration emits: the field maps a
// spatial coordinate system onto itself through its own level-0 array.
const declared = declareFieldTransform(multiscales);
await toOmeZarr("displacement.ome.zarr", declared, { version: "0.6" });

For a field written beside the image it displaces, declare the entry on the imageโ€™s multiscales instead, with path naming the fieldโ€™s array and inputSystem/outputSystem referencing the imageโ€™s own coordinate systems.

See RFC-5: Coordinate Systems and Transformations for the full transformation model, including how to write an image and its transformation (an affine, or a displacement/coordinate field) into a single store.

๐ŸŒ Platform Supportยถ

Node.jsยถ

Compatible with Node.js 22.18, 24.11, and newer, as an ES module:

import { fromOmeZarr } from "@fideus-labs/ngff-zarr";

const multiscales = await fromOmeZarr("./data.ome.zarr");

Browserยถ

Works in modern browsers with ES modules:

<!DOCTYPE html>
<html>
<head>
  <title>OME-Zarr in Browser</title>
</head>
<body>
  <script type="module">
    import { fromOmeZarr } from "https://esm.sh/@fideus-labs/ngff-zarr";

    // Load from remote URL
    const multiscales = await fromOmeZarr(
      "https://example.com/data.ome.zarr"
    );

    console.log("Loaded:", multiscales.images.length, "scales");
  </script>
</body>
</html>

Browser Limitations:

  • Cannot read from local filesystem (use HTTP URLs instead)

  • FileSystemStore is not available in browsers

  • Use FetchStore for remote data access

๐Ÿ“š API Referenceยถ

Core Typesยถ

NgffImageยถ

Represents a single-scale image with associated metadata:

interface NgffImage {
  data: DaskArray;              // Lazy array data
  dims: string[];               // Dimension names ["t", "c", "z", "y", "x"]
  scale: Record<string, number>; // Pixel spacing per dimension
  translation: Record<string, number>; // Origin offset
  name?: string;                 // Optional image name
  axesUnits?: Record<string, Units>; // Optional units per axis
}

Example:

import { createNgffImage } from "@fideus-labs/ngff-zarr";

const image = createNgffImage(
  arrayBuffer,
  [100, 100],
  "uint16",
  ["y", "x"],
  { y: 0.5, x: 0.5 },
  { y: 0.0, x: 0.0 }
);

NgffMultiscalesยถ

Container for multiple resolution levels:

interface NgffMultiscales {
  images: NgffImage[];          // Array of scale levels
  metadata: Metadata;           // OME-Zarr metadata
  scaleFactors?: (number | Record<string, number>)[]; // Scale factors
  method?: Methods;             // Downsampling method
  chunks?: Record<string, number>; // Chunk sizes
}

Metadataยถ

OME-Zarr metadata structure:

interface Metadata {
  axes: Axis[];                 // Dimension definitions
  datasets: Dataset[];          // Scale level paths and transforms
  coordinateTransformations?: CoordinateTransformation[];
  name?: string;
  version?: "0.4" | "0.5" | "0.6";
  type?: string;                // Downsampling method type
  metadata?: Record<string, unknown>;
}

Axisยถ

Dimension axis definition:

interface Axis {
  name: string;                 // "t", "c", "z", "y", "x"
  type?: "time" | "channel" | "space";
  unit?: Units;                 // UDUNITS-2 identifier
}

I/O Functionsยถ

fromOmeZarr()ยถ

Read an OME-Zarr file:

async function fromOmeZarr(
  store: string | MemoryStore | FetchStore,
  options?: {
    validate?: boolean;
    version?: "0.4" | "0.5" | "0.6" | "0.9.dev1";
  }
): Promise<NgffMultiscales>

Parameters:

  • store: Path to OME-Zarr (string), URL (string), or Zarr store object

  • options.validate: Enable metadata validation (default: false)

  • options.version: Expected OME-Zarr version

Returns: NgffMultiscales object with all scale levels

Example:

// Basic reading
const ms = await fromOmeZarr("data.ome.zarr");

// With validation
const validatedMs = await fromOmeZarr("data.ome.zarr", {
  validate: true,
  version: "0.6",
});

// From URL
const remoteMs = await fromOmeZarr("https://example.com/data.ome.zarr");

toOmeZarr()ยถ

Write an NgffMultiscales object to OME-Zarr:

async function toOmeZarr(
  store: string,
  multiscales: NgffMultiscales,
  options?: {
    version?: "0.4" | "0.5" | "0.6" | "0.9.dev1";
    chunksPerShard?: number | number[] | Record<string, number>;
    consolidateMetadata?: boolean;
  }
): Promise<void>

Parameters:

  • store: Output path for OME-Zarr

  • multiscales: NgffMultiscales object to write

  • options.version: OME-Zarr version (default: โ€œ0.5โ€). For a .ozx path it must be a version stored in Zarr v3 โ€“ โ€œ0.5โ€, โ€œ0.6โ€ or โ€œ0.9.dev1โ€

  • options.chunksPerShard: Store each block of chunks as one Zarr v3 shard โ€“ how many chunks a shard spans: one count for every axis, one per axis in order, or per axis name (an axis left out spans 1). v0.5 and later. Omitted, a directory store is not sharded, while a .ozx path is sharded 2 chunks a shard along every axis

  • options.consolidateMetadata: Inline every arrayโ€™s metadata into the root zarr.json (default: true)

Example:

// Basic writing
await toOmeZarr("output.ome.zarr", multiscales);

// With version specification
await toOmeZarr("output.ome.zarr", multiscales, { version: "0.6" });

// With sharding (v0.5)
await toOmeZarr("output.ome.zarr", multiscales, {
  version: "0.5",
  chunksPerShard: { z: 2, y: 2, x: 2 },
});

Sharding. A shard spans chunksPerShard chunks along each axis, held to the fewest whole chunks that cover the axis โ€“ the same grid the Python writer lays out for the same request. The chunks keep their codecs inside the sharding_indexed codec, whose index is checksummed with crc32c as zarr-python and zarrs write it. Shards are encoded on the codec workers, a whole shard per write. fromOmeZarr reads sharded stores back through the same workers, fetching a shardโ€™s index and then only the chunks a selection touches; an in-memory Map store works too.

Consolidated metadata. By default the writer finishes by inlining every arrayโ€™s zarr.json into the root groupโ€™s own zarr.json. A reader that understands the block resolves the whole hierarchy from that one document instead of one request per scale level, which matters most over HTTP and for RFC-9 archives. zarr-python is such a reader, and so is the Python package, which consolidates every store it writes โ€“ writing the block keeps the two implementationsโ€™ output equivalent. fromOmeZarr is one as well: it resolves every scale levelโ€™s array from the root document.

Some backends run their own consolidation and reject the block โ€“ Icechunk is the motivating case โ€“ so it can be turned off:

await toOmeZarr("output.ome.zarr", multiscales, {
  consolidateMetadata: false,
});

The flag governs the root document alone; the arrays are written identically either way. Re-writing a consolidated store with consolidateMetadata: false leaves it unconsolidated rather than stale, because a Zarr v3 write replaces the root document wholesale. toOmeZarrOzx / toOmeZarrOzxData accept the same option for RFC-9 archives. This mirrors the Python packageโ€™s to_ome_zarr(..., consolidate_metadata=False).

OME-Zarr Zip (.ozx) archives. A .ozx output path writes an RFC-9 zipped OME-Zarr: the store is built in memory and packed into a single ZIP with the root zarr.json as its first entry and the OME-Zarr version in the ZIP comment. RFC-9 is defined on Zarr v3, so any version stored in Zarr v3 can be zipped โ€“ โ€œ0.5โ€ (the default), โ€œ0.6โ€ or โ€œ0.9.dev1โ€ โ€“ while โ€œ0.4โ€ (Zarr v2) throws. The archive carries the same root document a directory store at that version would, so a v0.6 archive keeps its RFC-5 coordinate systems and transforms. chunksPerShard shards an archiveโ€™s arrays as it does a directory storeโ€™s; its entries are stored uncompressed, so a reader range-reads single chunks out of a shard. As in the Python writer, an archive is sharded by default: a shard spans 2 chunks along every axis unless chunksPerShard says otherwise.

// v0.5 archive (default)
await toOmeZarr("output.ozx", multiscales);

// v0.6 archive
await toOmeZarr("output.ozx", multiscales, { version: "0.6" });

// The ZIP bytes without touching the filesystem (browsers, downloads)
import { toOmeZarrOzxData } from "@fideus-labs/ngff-zarr";
const zipData = await toOmeZarrOzxData(multiscales, { version: "0.6" });

Read an archive back with fromOmeZarr and zarritaโ€™s ZipFileStore:

import { ZipFileStore } from "@zarrita/storage";

const store = ZipFileStore.fromBlob(new Blob([zipData]));
const multiscales = await fromOmeZarr(store, { validate: true });

toMultiscales()ยถ

Generate multiscale pyramid from a single image:

async function toMultiscales(
  image: NgffImage,
  options?: {
    scaleFactors?: (number | Record<string, number>)[];
    method?: Methods;
    chunks?: number | number[] | Record<string, number>;
  }
): Promise<NgffMultiscales>

Parameters:

  • image: Base NgffImage to downsample

  • options.scaleFactors: Downsampling factors (default: [2, 4])

  • options.method: Downsampling method (default: ITKWASM_GAUSSIAN)

  • options.chunks: Chunk sizes for output

Returns: NgffMultiscales with generated pyramid levels

Axes are normalized to the OME-Zarr order, time then channel then space, so a non-canonical input such as (z, y, c, x) yields (c, z, y, x) and the data is reordered with it. An axis model outside the (t, c, z, y, x) vocabulary is left alone: it carries no spec ordering to normalize to. A positional chunks array indexes the dims you passed and follows them through the reordering.

Example:

// Basic pyramid generation
const ms = await toMultiscales(image);

// Custom scale factors
const ms2 = await toMultiscales(image, {
  scaleFactors: [2, 4, 8],
  method: Methods.ITKWASM_GAUSSIAN,
});

// With custom chunks
const ms3 = await toMultiscales(image, {
  scaleFactors: [{ x: 2, y: 2 }, { x: 4, y: 4 }],
  chunks: 256,
});

Factory Functionsยถ

createNgffImage()ยถ

Create an NgffImage from raw data:

function createNgffImage(
  buffer: ArrayBuffer,
  shape: number[],
  dtype: string,
  dims: string[],
  scale: Record<string, number>,
  translation: Record<string, number>,
  name?: string
): NgffImage

createAxis()ยถ

Create an axis definition:

function createAxis(
  name: string,
  type: "time" | "channel" | "space",
  unit?: Units
): Axis

createDataset()ยถ

Create a dataset entry:

function createDataset(
  path: string,
  scale: number[],
  translation: number[]
): Dataset

createMetadata()ยถ

Create OME-Zarr metadata:

function createMetadata(
  axes: Axis[],
  datasets: Dataset[],
  name?: string
): Metadata

createMultiscales()ยถ

Create an NgffMultiscales container:

function createMultiscales(
  images: NgffImage[],
  metadata: Metadata,
  scaleFactors?: (number | Record<string, number>)[],
  method?: Methods
): NgffMultiscales

Validationยถ

Schema Validation with Zodยถ

The package includes Zod schemas for runtime validation:

import {
  MetadataSchema,
  NgffImageSchema,
  MultiscalesSchema,
  validateMetadata,
} from "@fideus-labs/ngff-zarr";

// Validate metadata
const metadata = { axes: [...], datasets: [...] };
const validated = MetadataSchema.parse(metadata);

// Safe parsing (returns result object)
const result = MetadataSchema.safeParse(metadata);
if (result.success) {
  console.log("Valid metadata:", result.data);
} else {
  console.error("Validation errors:", result.error.issues);
}

// Utility function
const validMetadata = validateMetadata(metadata);

Downsampling Methodsยถ

Available downsampling methods via the Methods enum:

enum Methods {
  ITKWASM_GAUSSIAN = "itkwasm_gaussian",        // Gaussian smoothing (default)
  ITKWASM_BIN_SHRINK = "itkwasm_bin_shrink",    // Bin shrinking
  ITKWASM_LABEL_IMAGE = "itkwasm_label_image",  // Label-aware downsampling
}

Example:

import { toMultiscales, Methods } from "@fideus-labs/ngff-zarr";

const pyramid = await toMultiscales(image, {
  method: Methods.ITKWASM_GAUSSIAN,
  scaleFactors: [2, 4],
});

๐Ÿ’ก Examplesยถ

Example 1: Convert Image Formatยถ

Read an image and convert to OME-Zarr:

import {
  itkImageToNgffImage,
  toMultiscales,
  toOmeZarr,
} from "@fideus-labs/ngff-zarr";

// Load image using itk-wasm
const itkImage = await loadImageFromFile("input.png");

// Convert to NgffImage
const ngffImage = await itkImageToNgffImage(itkImage);

// Generate pyramid
const multiscales = await toMultiscales(ngffImage);

// Write to OME-Zarr
await toOmeZarr("output.ome.zarr", multiscales);

Example 2: Process Remote Dataยถ

Work with remote OME-Zarr files:

import { fromOmeZarr } from "@fideus-labs/ngff-zarr";

// Read from S3 or other HTTP-accessible storage
const multiscales = await fromOmeZarr(
  "https://s3.amazonaws.com/bucket/data.ome.zarr"
);

// Access the highest resolution
const fullRes = multiscales.images[0];
console.log(`Shape: ${fullRes.data.shape}`);
console.log(`Spacing: ${JSON.stringify(fullRes.scale)}`);

// Access a lower resolution
const lowRes = multiscales.images[multiscales.images.length - 1];
console.log(`Low-res shape: ${lowRes.data.shape}`);

Example 3: Metadata Inspectionยถ

Examine OME-Zarr metadata:

import { fromOmeZarr } from "@fideus-labs/ngff-zarr";

const multiscales = await fromOmeZarr("data.ome.zarr");

// Inspect axes
multiscales.metadata.axes.forEach(axis => {
  console.log(`Axis ${axis.name}: type=${axis.type}, unit=${axis.unit}`);
});

// Inspect datasets
multiscales.metadata.datasets.forEach((dataset, i) => {
  console.log(`Scale ${i}: path=${dataset.path}`);
  dataset.coordinateTransformations.forEach(transform => {
    if (transform.type === "scale") {
      console.log(`  Scale: ${transform.scale}`);
    }
  });
});

// Check version
console.log(`OME-Zarr version: ${multiscales.metadata.version}`);

Example 4: Custom Chunk Sizesยถ

Control chunking for optimal performance:

import {
  createNgffImage,
  toMultiscales,
  toOmeZarr,
} from "@fideus-labs/ngff-zarr";

const image = createNgffImage(
  buffer,
  [1024, 1024, 1024], // Large 3D volume
  "uint16",
  ["z", "y", "x"],
  { z: 1.0, y: 0.5, x: 0.5 },
  { z: 0.0, y: 0.0, x: 0.0 }
);

// Use smaller chunks for better streaming
const multiscales = await toMultiscales(image, {
  chunks: { z: 64, y: 128, x: 128 },
  scaleFactors: [2, 4, 8],
});

await toOmeZarr("large_volume.ome.zarr", multiscales);

Example 5: TypeScript Type Safetyยถ

Leverage TypeScriptโ€™s type system:

import type {
  NgffImage,
  NgffMultiscales,
  Metadata,
  Axis,
} from "@fideus-labs/ngff-zarr";
import { fromOmeZarr, validateMetadata } from "@fideus-labs/ngff-zarr";

// Type-safe function
async function processImage(path: string): Promise<NgffMultiscales> {
  const multiscales: NgffMultiscales = await fromOmeZarr(path);

  // TypeScript knows the structure
  const axes: Axis[] = multiscales.metadata.axes;
  const firstImage: NgffImage = multiscales.images[0];

  return multiscales;
}

// Type-safe metadata validation
function ensureValidMetadata(metadata: unknown): Metadata {
  return validateMetadata(metadata);
}

Example 6: JavaScript Usageยถ

Pure JavaScript usage without TypeScript:

// JavaScript (Node.js or browser)
import { fromOmeZarr, toOmeZarr, createNgffImage } from "@fideus-labs/ngff-zarr";

// Read OME-Zarr
const multiscales = await fromOmeZarr("data.ome.zarr");

// Create new image
const data = new Uint8Array(100 * 100);
const image = createNgffImage(
  data.buffer,
  [100, 100],
  "uint8",
  ["y", "x"],
  { y: 1.0, x: 1.0 },
  { y: 0.0, x: 0.0 }
);

// Write to OME-Zarr
await toOmeZarr("output.ome.zarr", image);

๐Ÿ”ง Advanced Usageยถ

Working with Different Store Typesยถ

import * as zarr from "zarrita";
import { fromOmeZarr } from "@fideus-labs/ngff-zarr";

// Memory store
const memoryStore = new Map<string, Uint8Array>();
const ms1 = await fromOmeZarr(memoryStore);

// Fetch store (HTTP/HTTPS)
const fetchStore = new zarr.FetchStore("https://example.com/data.ome.zarr");
const ms2 = await fromOmeZarr(fetchStore);

// File system store (Node.js only)
import { FileSystemStore } from "@zarrita/storage";
const fsStore = new FileSystemStore("/path/to/data.ome.zarr");
const ms3 = await fromOmeZarr(fsStore);

Converting Between OME-Zarr Versionsยถ

import { fromOmeZarr, toOmeZarr } from "@fideus-labs/ngff-zarr";

// Convert from v0.4 to v0.5
const multiscales = await fromOmeZarr("data_v04.ome.zarr");
await toOmeZarr("data_v05.ome.zarr", multiscales, { version: "0.5" });

OME-Zarr 0.4 is stored as Zarr v2, which the TypeScript writer does not write: toOmeZarr with version: "0.4" throws. Use the Python package to write 0.4.

Upgrading OME-Zarr Store Versionsยถ

The conversion above re-reads and re-writes the entire pyramid. To change only the recorded specification version of an existing store, upgradeOmeZarr() mirrors the Python upgrade_ome_zarr and offers the same two modes:

function upgradeOmeZarr(
  input: string | MemoryStore | FetchStore | Readable,
  options?: {
    output?: string | MemoryStore; // FetchStore is read-only, not a destination
    version?: "0.4" | "0.5" | "0.6" | "0.9.dev1"; // default "0.6"
    validate?: boolean;
    overwrite?: boolean; // write-to-new-store only; default true
  },
): Promise<void>;

In-place, metadata-only. When output is omitted (or resolves to the same store as input) and the source and target share the same underlying Zarr format โ€“ 0.5 to 0.6, both Zarr v3 โ€“ only the root groupโ€™s zarr.json is rewritten and every array chunk in the store is left byte-for-byte untouched, avoiding the data loss of a naive โ€œread, erase, re-writeโ€ upgrade. An in-place upgrade that would cross the Zarr v2/v3 boundary (OME-Zarr 0.4 to 0.5/0.6) throws with guidance to pass output instead.

import {
  createAxis,
  createDataset,
  createMetadata,
  createMultiscales,
  createNgffImage,
  toOmeZarr,
  upgradeOmeZarr,
  type MemoryStore,
} from "@fideus-labs/ngff-zarr";

// Build a small image and write it to a MemoryStore at version 0.5 (Zarr v3).
const data = new Uint8Array(4 * 32 * 32);
const image = await createNgffImage(
  data.buffer,
  [4, 32, 32],
  "uint8",
  ["z", "y", "x"],
  { z: 1.0, y: 1.0, x: 1.0 },
  { z: 0.0, y: 0.0, x: 0.0 },
);
const axes = [
  createAxis("z", "space", "micrometer"),
  createAxis("y", "space", "micrometer"),
  createAxis("x", "space", "micrometer"),
];
const datasets = [createDataset("0", [1.0, 1.0, 1.0], [0.0, 0.0, 0.0])];
const multiscales = createMultiscales([image], createMetadata(axes, datasets));

const store: MemoryStore = new Map();
await toOmeZarr(store, multiscales, { version: "0.5" });

// Upgrade 0.5 -> 0.6 in place: only the root group's metadata is rewritten,
// leaving every array chunk in the store untouched.
await upgradeOmeZarr(store, { version: "0.6" });

Write-to-new-store. When output is a store distinct from input, the source is read lazily and re-written to output at the requested version through the standard write pipeline. Every transition to 0.5, 0.6 or 0.9.dev1 works in this mode whenever the target version can express the axis model (the TypeScript writer does not write 0.4), and the source store is never mutated. An RFC-3 axis model is refused below 0.9.dev1, so a store using one only converts upward.

import { upgradeOmeZarr, type MemoryStore } from "@fideus-labs/ngff-zarr";

// `source` is an existing 0.4 store (e.g. a MemoryStore or FetchStore).
const target: MemoryStore = new Map();
await upgradeOmeZarr(source, { output: target, version: "0.6" });

In-place upgrade of a local path store is Node-only โ€“ the browser build ships no filesystem store โ€“ while MemoryStore inputs work in every environment, consistent with the other I/O functions. In the browser, use a MemoryStore for an in-place upgrade, or pass output.

High Content Screening (HCS)ยถ

Reading and writing HCS plates is not supported yet in the TypeScript package: fromHcsZarr and toHcsZarr throw. Use the Python packageโ€™s from_hcs_zarr and to_hcs_zarr. The HCSPlate, HCSWell and plate metadata types and schemas are available for plate metadata you read yourself.

๐Ÿ†š Comparison with Pythonยถ

The TypeScript API closely mirrors the Python interface:

Python

TypeScript

from_ome_zarr()

fromOmeZarr()

to_ome_zarr()

toOmeZarr()

to_multiscales()

toMultiscales()

to_ngff_image()

createNgffImage()

NgffImage dataclass

NgffImage interface

NgffMultiscales dataclass

NgffMultiscales interface

dask.array.Array

DaskArray (metadata)

JSON Schema validation

JSON Schema validation

Key Differencesยถ

  1. Naming conventions: Python uses snake_case, TypeScript uses camelCase

  2. Data handling: Python uses Dask arrays, TypeScript uses array metadata

  3. Async: TypeScript I/O operations are async by default

๐Ÿ› Troubleshootingยถ

Browser CORS Issuesยถ

When accessing remote OME-Zarr files in the browser, ensure CORS headers are set:

// This may fail due to CORS
const ms = await fromOmeZarr("https://example.com/data.ome.zarr");

// Solution: Ensure server sends proper CORS headers
// Access-Control-Allow-Origin: *

File System Access in Browserยถ

Browsers cannot access local filesystem:

// โŒ Won't work in browser
const ms = await fromOmeZarr("/path/to/local/file.ome.zarr");

// โœ… Use HTTP URLs instead
const ms = await fromOmeZarr("http://localhost:8000/file.ome.zarr");

Memory Managementยถ

For large datasets, zarrita handles lazy loading automatically:

// Data is loaded lazily - metadata read immediately
const multiscales = await fromOmeZarr("large_file.ome.zarr");

// Actual array data loaded on access
const shape = multiscales.images[0].data.shape; // Fast
// Actual data retrieval would happen when reading chunks

๐Ÿ“š External Resourcesยถ

๐Ÿค Contributingยถ

Contributions are welcome! See the development documentation for details on:

  • Setting up the development environment

  • Running tests

  • Building the npm package

  • Browser testing with Playwright