Codync
A free, open-source 1:1 alternative to Grok Bot and Muse. Message Claude Code, Codex, Cursor and 40+ coding agents as persistent bots from your iPhone, Mac or Linux desktop. Group chats, threads, appr
Unified APIs for streaming frames to OME-TIFF and OME-ZARR.
Developer toolsPythonGitHub @pymmcore-plusOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Unified API for streaming camera frames to either OME-TIFF or OME-Zarr format
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗OME-TIFF and OME-ZARR writer APIs designed for microscopy acquisition.
Detailed documentation is available at https://pymmcore-plus.github.io/ome-writers/.
ome-writers provides a unified interface for writing microscopy image data to
OME-compliant formats (OME-TIFF and OME-Zarr) using various different backends.
It is designed for streaming acquisition: receiving 2D camera frames one at
a time and writing them to multi-dimensional arrays with proper metadata.
The core problem ome-writers solves:
Map a stream of 2D frames (arriving in acquisition order) to storage locations in multi-dimensional arrays, while generating OME-compliant metadata for both TIFF and Zarr formats.
We prioritize:
You can install ome-writers via pip. You must also select select at least
one backend extra:
pip install ome-writers[<backend>]
...where <backend> is a comma-separated list of one or more of the following:
tensorstore — Uses tensorstore, supports
OME-Zarr v0.5.acquire-zarr — Uses
acquire-zarr, supports
OME-Zarr v0.5.zarr-python — Uses zarr-python, supports
OME-Zarr v0.5.zarrs-python — Uses zarrs-python, supports
OME-Zarr v0.5.tifffile — Uses tifffile, supports
OME-TIFF.all — install all backends.[!Note] All zarr-backends use yaozarrs to generate OME-Zarr metadata and create zarr hierarchies (only array-writing is handled by the selected backend).
(Developers using uv sync will end up with all backends installed by default.)
[!Note] More complete usage examples are available in the usage documentation
from ome_writers import AcquisitionSettings, Dimension, create_stream
settings = AcquisitionSettings(
root_path="example_5d_image.ome.zarr",
dimensions=[
Dimension(name="t", count=10, chunk_size=1, type="time"),
Dimension(name="c", count=2, chunk_size=1, type="channel"),
Dimension(name="z", count=5, chunk_size=1, type="space", scale=5),
Dimension(name="y", count=256, chunk_size=64, type="space", scale=0.1),
Dimension(name="x", count=256, chunk_size=64, type="space", scale=0.1),
],
dtype="uint16",
overwrite=True,
)
with create_stream(settings) as stream:
for frame in ...:
stream.append(frame)
┌───────────────────────┐ ┌─────────────────┐ ┌───────────────────────┐
│ AcquisitionSettings │─────▶│ FrameRouter │─────▶│ ArrayBackend │
│ │ │ │ │ │
│ Declarative model │ │ __next__() -> │ │ write(pos,idx,frame) │
│ of acquisition order │ │ (pos, idx) │ │ finalize() │
└───────────────────────┘ └─────────────────┘ └───────────────────────┘
schema.py)The schema is the declarative description of what to create. In addition to other storage details such as data types, chunking, compression, and other metadata, it must fully describe the dimensionality of the data and the exact order in which frames will arrive.
Explicit non-goal:
ome-writersdoes not attempt to handle non-deterministic acquisition patterns (e.g., event-driven acquisitions where data shape is unknown ahead of time). However, we do support an unbounded first dimension (e.g., time or whatever). For this case, we recommend a flat 3D structure (e.g., FYX with unbounded F) where F is "any frame", storing metadata for mapping frames to logical dimensions externally.
It answers:
The schema separates acquisition order (the order dimensions appear in the
dimensions list) from storage order (controlled by the storage_order
field). This allows data to arrive in one order (e.g., TZCYX) but be stored in
another (e.g., TCZYX for NGFF compliance).
The router is the stateful iterator that maps frame numbers to storage locations. It:
(position_key, storage_index) tuples for each frameThe router is the only component that knows about both orderings. It iterates in acquisition order (because that's how frames arrive) and emits storage-order indices (because that's what backends need).
Backends are format-specific writers that handle the actual I/O. They:
Supported backends:
Backends receive indices in storage order and don't need to know about acquisition order.
The separation of schema, router, and backend allows us to leave the performance-critical tasks to C++ libraries (like tensorstore, acquire-zarr), while keeping "fiddly" metadata logic and frame routing in Python (where it's easier to maintain).
The API of this library is heavily inspired by the acquire-zarr API
(declare deterministic experiment with schema, append frames with single append() calls).
But we also:
ome-types for
OME-XML generation and yaozarrs for OME-Zarr metadata)append() in the right order for each buffer)We welcome contributions to ome-writers! See our contributing
guide for details.