spacr.ome_zarr¶
OME-Zarr (OME-NGFF) — read and write, with the axes and the units taken seriously.
OME-NGFF is where large bioimaging data is going, and the two properties that put it there are the two this module is built around.
Chunked. The array is stored as a grid of independently compressed blocks,
so a 100 GB plate is readable a tile at a time. Nothing here ever reads an
array to answer a question about it: read_ome_zarr() opens a handful of
small JSON files and returns the levels, their shapes, their voxel sizes and
their chunk grids without touching a single chunk, and
OmeZarrImage.read() with a region= decodes only the chunks that
region intersects. Every byte of chunk data in the pure-Python path passes
through one function, _read_chunk_bytes(), precisely so that “it is
lazy” is a countable claim rather than a sentence in a docstring —
tests/test_ome_zarr.py counts its calls.
Multiscale. The group holds a resolution pyramid, so drawing a 200 px
plate overview does not decode full resolution. OmeZarrImage.level_for_size()
picks the level, and the per-level scale transformation is what makes the
picked level land in the same world coordinates as level 0.
What spaCR implements itself, and what the extra is for¶
The metadata — multiscales, axes, coordinateTransformations,
omero — is parsed and written here, in pure Python, because that layout is
the thing worth getting right and a library would only hide it behind a call
that returns “an array” with the voxel size quietly dropped.
The chunk codec is where the compiled code lives, and so it is where the
optional extra lives. This module decodes and encodes chunks compressed with
zlib, gzip, bz2, lzma or stored uncompressed using nothing but
the standard library, which is why a spaCR-written OME-Zarr round-trips on a
plain pip install spacr. A file compressed with blosc, zstd or
lz4 — the common defaults of other tools — raises
ZarrExtraMissing, whose message names the codec that was needed and
the one command that fixes it, instead of a ModuleNotFoundError five
frames down.
If zarr is importable, array access is delegated to it wholesale
(see prefer_zarr= on the readers). That is the intended path: zarr handles
v3 sharding, filter pipelines and every codec, and it is maintained by people
who do nothing else. The pure-Python reader is a fallback for the common
case, not a reimplementation of zarr, and it says so where it gives up.
Axes and units, which is the decision this module exists to get right¶
NGFF names its axes and gives each one a UDUNITS-2 unit
("micrometer", "nanometer", "second"). spacr.layers.Spacing
names its axes and carries one short unit token ("um", "px") that
spaCR compares by name when layers are stacked — that comparison, at
spacr/layers.py’s LayerStack._check_units, is the whole reason
Spacing has a units field at all. Mapping one onto the other has three
decisions in it, and all three are made here rather than left to the caller.
1. The Spacing is built from the SPACE axes only. A 5-D NGFF image has
axes (t, c, z, y, x): the time axis is in seconds, the channel axis has no
unit at all, and z/y/x are in micrometers. A single Spacing over all five
would have to answer “what unit is this?” with one string, and whatever it
answered would be wrong for three of the axes — a stack whose spacing claims
"um" while one of its axes steps in seconds passes the very check
layers.py exists to make, and then draws a plausible picture of the wrong
thing. So OmeZarrImage.spacing covers z/y/x, and t and c are reported
alongside as Axis records through OmeZarrImage.other_axes,
OmeZarrImage.time_axis and OmeZarrImage.channel_axis. Nothing
is lost; the reader does not treat seconds and micrometers as the same
kind of number.
2. Units are translated through an explicit table, and an unknown one is
refused. NGFF_UNIT_TO_SPACR is the mapping, written out. A unit that
is not in it raises OmeZarrError. It would be one line to default to
"px" instead, and that one line is how a 0.65 µm pixel becomes a 0.65 px
pixel and every downstream area comes out wrong by a factor of 10^6 — in a
column named cell_area that still looks entirely reasonable. The error
distinguishes the two cases that need different fixes: a unit NGFF has never
heard of (the file is wrong), and a legal NGFF unit spaCR has no short token
for (convert it, e.g. to micrometer).
3. No unit is pixels, said out loud. An NGFF file whose space axes carry
no unit is legal and common — it is what every tool that never knew the
pixel size writes. That reads back as units="px" with
OmeZarrImage.units_declared False and a
OmeZarrImage.describe() line that says the file declared none. It is
never silently upgraded to µm, and "px" is never written out as a unit
string, because NGFF has no pixel unit: writing a px spacing emits axes with
no unit, which is the same convention read back.
Space axes that disagree with each other — y in micrometers and x in
nanometers — are refused by name. A single Spacing has one units, so
there is no honest way to represent that; the numbers would have to be
converted, and converting the caller’s data behind their back is not this
module’s decision to make.
Versions¶
0.4 is implemented properly — it is what essentially all real data is in,
and it is zarr-format 2. 0.5 (zarr-format 3, metadata under an ome key in
zarr.json) is tolerated: its metadata is parsed in full, and its chunks
are decoded for the codec chains the standard library can do
(bytes/transpose/gzip/crc32c). Anything else routes through
the same require_codec() refusal as v2 does. .zgroup / .zarray /
zarr.json and the zarr_format field are read rather than assumed, and
an unsupported zarr_format is refused by number.
Typical use:
from spacr.ome_zarr import read_ome_zarr, write_ome_zarr
from spacr.layers import Spacing
img = read_ome_zarr("/data/plate.zarr/A/1/0")
print(img.describe())
print(img.spacing.describe()) # z 2, y 0.65, x 0.65 um
level = img.level_for_size(512) # coarse enough to draw, no coarser
tile = img.read(level, world_region={"y": (0.0, 100.0),
"x": (0.0, 100.0)})
write_ome_zarr(
"/data/out.zarr", stack,
spacing=Spacing.from_map({"z": 2.0, "y": 0.65, "x": 0.65}, units="um"),
levels=4, channel_names=("DAPI", "GFP"),
)
Downsampling, and the transformation bug it is easy to write¶
The writer builds a real pyramid by 2x block mean over the two
fastest-varying space axes (see write_ome_zarr()). It is a box filter
and not a Gaussian pyramid: there is no pre-filter beyond the box, so it
aliases where a properly filtered pyramid would not. It is chosen anyway
because it is separable, exact, dependency-free, and the same local_mean
the rest of the NGFF ecosystem writes — matching what other viewers produce
matters more here than a marginally better kernel. Striding
(downsample="stride") is offered and is mandatory for label/mask
arrays: the mean of labels 3 and 5 is 4, an object that does not exist.
Level k’s scale is level 0’s scale multiplied by the cumulative
factor 2^k on each downsampled axis, and leaving it at level 0’s value is the
single most common NGFF bug — it renders as a pyramid whose coarse levels are
a quarter the size of the fine ones and drift off the top-left corner as you
zoom. The translation moves too, and by a different rule per method: a
block mean puts the first output element at the centre of the block it
averaged, half a level-0 pixel in from the edge, so level k is translated by
scale_0 * (2^k - 1) / 2; a strided level samples element 0 exactly and is
not translated at all. Both are written, and asserted in the tests with the
numbers spelled out by hand.
Attributes¶
Exceptions¶
An OME-Zarr that cannot mean what it says, or a request that cannot be met. |
|
The optional |
Classes¶
One NGFF axis: what it is called, what kind it is, and how big a step is. |
|
One resolution level of a multiscale image — its metadata, not its data. |
|
An opened OME-Zarr multiscale image: metadata now, chunks on demand. |
Functions¶
|
Build the full NGFF axis list for an array from a spacing. |
|
Translate an NGFF unit name into the token |
|
Open an OME-Zarr group and read its metadata. No chunk is touched. |
|
Open an OME-Zarr and read one level (or a region of it) in one call. |
|
Return a |
Import and return |
|
|
Build a |
|
Translate a |
|
Write an array as an OME-NGFF 0.4 multiscale image. |
Module Contents¶
- exception spacr.ome_zarr.OmeZarrError[source]¶
Bases:
ValueErrorAn OME-Zarr that cannot mean what it says, or a request that cannot be met.
Raised rather than repaired, for the reason
spacr.layers.LayerErroris: every case this covers — an unknown unit, space axes in two different units, amultiscalesblock that is not there — has a “helpful” fallback that produces a plausible-looking image with the wrong scale on it, and a wrong scale reaches a figure without ever looking wrong.Initialize self. See help(type(self)) for accurate signature.
- exception spacr.ome_zarr.ZarrExtraMissing[source]¶
Bases:
OmeZarrError,ImportErrorThe optional
zarrextra is needed here and is not installed.Both parents are deliberate. It is an
ImportErrorbecause a caller guarding an optional feature writesexcept ImportError; it is anOmeZarrErrorbecause callers experience this as another file that could not be read, and code that wraps a whole read inexcept OmeZarrErrorshould not miss it.Initialize self. See help(type(self)) for accurate signature.
- class spacr.ome_zarr.Axis[source]¶
One NGFF axis: what it is called, what kind it is, and how big a step is.
A faithful record of the file, not an interpretation of it:
unitholds the NGFF name as written, even in the cases spaCR would not write itself (a channel axis with a unit, say). The interpretation happens inspacr_units()and inspacing_from_axes(), where it can refuse.- Parameters:
name – the axis name —
"t","c","z","y","x".type –
"space","time","channel"or, for a file that uses something else, whatever it says. Only"space"axes reach aspacr.layers.Spacing.unit – the UDUNITS-2 unit name, or
Nonewhen the file declares none.Noneon a space axis means pixels, explicitly.scale – world size of one element along this axis at level 0. The per-level values live on
Level; this one is the reference thespacr.layers.Spacingis built from.translate – world coordinate of element 0 at level 0.
- __post_init__() None[source]¶
Normalise the axis and reject a step that cannot address anything.
- Raises:
OmeZarrError – if the axis has no name, which NGFF requires; if the scale is zero or non-finite – a zero step collapses the axis, so every world coordinate on it resolves to element 0 and the image is drawn out of register with nothing to show for it; or if the translation is non-finite.
- classmethod channel(name: str = 'c') Axis[source]¶
A channel axis. No unit, ever: a channel index measures nothing.
- describe() str[source]¶
One clause for a status line:
x: space, 0.65 micrometer/step.A channel axis gets no step, because a channel index is not a measurement and
c: channel, 1/stepreads like one.
- classmethod space(name: str, scale: float = 1.0, unit: str | None = None, translate: float = 0.0) Axis[source]¶
A spatial axis — the kind that goes into a
spacr.layers.Spacing.- Parameters:
name – the axis name, stripped. The type is forced to space whatever the name says, so
Axis.space("t")puts a t axis inside the spacing; the name-based inference that applies when reading a file does not apply here.scale – world size of one element at level 0, and what every pyramid level’s
scaleis derived from. Zero or non-finite is refused; negative is allowed and reaches the file unchanged. It is the second positional argument here but the fourth onAxis, soAxis.space("x", "um")is afloat("um").unit – the NGFF (UDUNITS-2) name, stored verbatim and not checked here —
unit="furlong"builds, and only raises once a spacing is made from it, which forwrite_ome_zarr()is after the chunks are already on disk.None(the default) and""both mean pixels and write an axis carrying nounitkey.translate – world coordinate of element 0 at level 0; non-finite is refused. Seeds every level’s
translationin the written file.
- Raises:
OmeZarrError – from
Axisitself, on a blank name, a zero or non-finite scale, or a non-finite translation.
- spacr_units() str[source]¶
This axis’s unit as a spaCR token.
- Returns:
the token, or
PIXEL_UNITSwhen the file declared no unit.- Raises:
OmeZarrError – on a unit spaCR will not translate.
- classmethod time(name: str = 't', scale: float = 1.0, unit: str | None = 'second', translate: float = 0.0) Axis[source]¶
A time axis. Kept out of the spacing; reported beside it.
- Parameters:
name – defaults to
"t". The type is forced to time whatever the name says, soAxis.time("x")is an axis called x thatspacing_from_axes()leaves out.scale – world time per element at level 0. It never reaches a
spacr.layers.Spacing, butwrite_ome_zarr()still writes it into every level’sscale— unhalved, since only space axes are downsampled. Zero or non-finite is refused.unit – defaults to
"second", not toNoneas onspace(). Nothing validates it on the way in or out: reading and writing only check space-axis units, so an untranslatable one round-trips and surfaces only atspacr_units().Nonewrites nounit, which then reads back as pixels.translate – world time of element 0 at level 0; non-finite is refused. Written into every level’s
translation.
- Raises:
OmeZarrError – from
Axisitself, on a blank name, a zero or non-finite scale, or a non-finite translation.
- class spacr.ome_zarr.Level[source]¶
One resolution level of a multiscale image — its metadata, not its data.
Everything here comes out of one small JSON file. Building the whole pyramid’s worth of these costs a handful of stats and no chunk reads, which is what makes “list the levels and their shapes” free on a 100 GB plate.
- Parameters:
path – the dataset path inside the group, e.g.
"0".shape – array shape, in array order.
chunks – chunk shape — the unit of I/O, and therefore the smallest region that can be read.
dtype – the numpy dtype string as stored, e.g.
"<u2". Kept as written, byte order included.scale – per-axis world size of one element AT THIS LEVEL, already composed with any group-level transformation.
translation – per-axis world coordinate of element 0 at this level, likewise composed.
zarr_format – 2 or 3.
compressor – the codec id, or
Nonewhen chunks are stored uncompressed.
- __post_init__() None[source]¶
Coerce the level’s tuples and check every rank against the array’s.
- Raises:
OmeZarrError – if the chunks, the scale, or the translation do not have one entry per array axis. NGFF requires one each, and a mismatch means the transformation cannot be applied at all.
- class spacr.ome_zarr.OmeZarrImage[source]¶
An opened OME-Zarr multiscale image: metadata now, chunks on demand.
Returned by
read_ome_zarr()after reading only the group’s JSON, so holding one of these says nothing about how much data has been read — the answer is none untilread()is called, and then only the chunks the region touches.- Parameters:
path – the group directory.
axes – every axis, in array order, with level-0 scale and translation on each.
levels – the resolution pyramid, level 0 first.
ngff_version – what the file says it is.
name – the
multiscalesname, if any.channel_names – from the
omeroblock when present — spaCR has channel names to fill in, so they are read and written rather than dropped.omero – the raw
omeroblock, read-only, for the rendering settings this module does not interpret (colours, windows, rdefs).units_declared –
Falsewhen the space axes carried no unit, sospacingis in pixels because the file said nothing, not because it said pixels. Never quietly upgraded to micrometers.multiscale – the raw
multiscalesentry, for anything here does not model.
An image opened from cloud storage also keeps the opened location, with the credentials it was opened with, so that
read()reaches the chunks the same way.- __post_init__() None[source]¶
Freeze the image’s members and check the levels against the axes.
- Raises:
OmeZarrError – if the multiscales block lists no datasets – then there is no image here – or if a level’s rank differs from the number of declared axes, which NGFF requires to match.
- describe() str[source]¶
A short report: version, axes, spacing and every level.
The spacing line says
pixel units (file declares none)when that is what happened, because “0.65” with no unit beside it has been read as micrometers before.
- level(level: int | str = 0) Level[source]¶
Resolve a level index or dataset path to a
Level.- Parameters:
level – an index into
levels, or a datasetpath.- Returns:
the level.
- Raises:
OmeZarrError – when there is no such level.
- level_for_size(size: int, axes: Sequence[str] = ('y', 'x')) int[source]¶
Index of the coarsest level still at least
sizealongaxes.This is the multiscale payoff: drawing a 200 px thumbnail of a 40k x 40k plate should decode a 300 x 300 level, not decimate the full one. The rule is “coarsest that does not need upsampling” — never return a level smaller than asked for while a bigger one exists, because upsampling to fill the request shows a blur where there is detail. If every level is smaller than
size(a small image, a big request), the finest is returned.- Parameters:
size – the wanted output size in pixels along
axes.axes – which axes the size refers to. Defaults to y and x.
- Returns:
an index into
levels.- Raises:
OmeZarrError – when
axesnames an axis the image lacks.
- read(level: int | str = 0, region: Any = None, *, world_region: Mapping[str, Sequence[float]] | None = None, prefer_zarr: bool = True) numpy.ndarray[source]¶
Read one level, or a region of it, as a numpy array.
Only the chunks the region intersects are opened. On a 100 GB plate that is the difference between a tile and the plate.
- Parameters:
level – level index or dataset path. Level 0 is full resolution.
region – see
resolve_region().world_region – a
{axis: (low, high)}box in world units, resolved through the level’s own spacing.prefer_zarr – use
zarrwhen it is installed. That is the intended path — zarr handles sharding, filters and every codec — and the pure-Python reader is the fallback for the common case. PassFalseto force the fallback, which is what the tests do.
- Returns:
the region, in the file’s own dtype and byte order.
- Raises:
OmeZarrError – on an impossible region, and
ZarrExtraMissingon a codec that needs the extra.
- resolve_region(level: int | str = 0, region: Any = None, world_region: Mapping[str, Sequence[float]] | None = None) Tuple[Tuple[int, int], ...][source]¶
Turn a region request into a half-open index box, one per axis.
- Parameters:
level – which level the box is for. World boxes are resolved through that level’s spacing, so the same world region names the matching pixels at every resolution.
region –
Nonefor everything, a mapping of axis name toslice/(start, stop)/int, or a sequence of those with one entry per axis. Index regions are refused when they fall outside the array — that is a typo, not a view of an edge.world_region – a mapping of SPACE axis name to
(low, high)in world units, resolved withspacr.layers.Spacing.to_data()and clamped to the array. Clamping is right here and refusing is right above: a world box legitimately extends past the edge of a tile, an index box does not.
- Returns:
((start, stop), ...), one per axis, in array order.- Raises:
OmeZarrError – on an unknown axis, a reversed or out-of-range index box, or the same axis constrained both ways.
- spacing_at(level: int | str = 0) spacr.layers.Spacing[source]¶
The voxel size of one level, over the space axes only.
This is what makes a world coordinate mean the same thing at every level: the same world box resolves to the corresponding pixels of the overview and of full resolution.
- Parameters:
level – level index or dataset path.
- Returns:
the spacing at that level.
- property axis_names: Tuple[str, ...][source]¶
Axis names in array order, e.g.
("t", "c", "z", "y", "x").
- property dtype: numpy.dtype[source]¶
Level 0’s dtype, byte order included.
- property other_axes: Tuple[Axis, ...][source]¶
The non-spatial axes, reported beside the spacing rather than in it.
- property spacing: spacr.layers.Spacing[source]¶
Level 0’s voxel size, over the SPACE axes only.
- Raises:
OmeZarrError – when the space axes disagree about units or one of them cannot be translated. See
spacing_from_axes().
- spacr.ome_zarr.axes_from_spacing(spacing: spacr.layers.Spacing, ndim: int | None = None, names: Sequence[str] | None = None) Tuple[Axis, ...][source]¶
Build the full NGFF axis list for an array from a spacing.
- Parameters:
spacing – the spatial spacing. Its axis names become the space axes, and its units become theirs.
ndim – the array’s dimensionality. When it exceeds the spacing’s, the leading axes are taken from
CANONICAL_AXIS_ORDER— the only order NGFF 0.4 permits — so a(t, c, z, y, x)array written with a(z, y, x)spacing needs no extra argument.names – explicit names for every axis, overriding the derivation.
- Returns:
the axes, outermost first.
- Raises:
OmeZarrError – when the counts cannot be reconciled, or a unit has no NGFF name.
- spacr.ome_zarr.ngff_unit_to_spacr(unit: str | None, *, axis: str = '?') str[source]¶
Translate an NGFF unit name into the token
Spacing.unitscarries.- Parameters:
unit – the
unitfield of an NGFF axis, orNone/""when the file declares none.axis – the axis name, used only to make the error message point at the offending axis.
- Returns:
the spaCR token (
"um","nm","s", …), orPIXEL_UNITSwhen no unit was declared.- Raises:
OmeZarrError – on a unit spaCR will not translate. Two distinct messages: one for a name NGFF does not define, one for a legal NGFF unit spaCR has no short token for — the second tells the user to convert rather than implying their file is broken.
- spacr.ome_zarr.read_ome_zarr(path: str | os.PathLike, *, multiscale_index: int = 0) OmeZarrImage[source]¶
Open an OME-Zarr group and read its metadata. No chunk is touched.
Everything this reads is small JSON: the group’s
.zattrs(or theattributesof itszarr.json) and one.zarrayper level. So asking a 100 GB plate what its levels, shapes, voxel size and channels are costs a few kilobytes, which is the property the whole format exists for.- Parameters:
path – the group directory — the one holding
multiscales. For a plate that is<plate>.zarr/<row>/<column>/<field>. Ans3://,gs://,az://orhttps://address is read from cloud storage, fetching only the metadata files.multiscale_index – which
multiscalesentry to read. NGFF permits several; spaCR reads the first by default and says so here rather than pretending there can only be one.
- Returns:
the opened
OmeZarrImage.- Raises:
OmeZarrError – when the directory is not a zarr group, has no
multiscales, declares a zarr format spaCR does not read, or carries axis units spaCR will not translate.
- spacr.ome_zarr.read_ome_zarr_array(path: str | os.PathLike, level: int | str = 0, region: Any = None, *, world_region: Mapping[str, Sequence[float]] | None = None, prefer_zarr: bool = True) numpy.ndarray[source]¶
Open an OME-Zarr and read one level (or a region of it) in one call.
The convenience form of
read_ome_zarr(path).read(...), for when the metadata is not wanted afterwards.- Parameters:
path – the group directory.
level – level index or dataset path.
region – an index box — see
OmeZarrImage.resolve_region().world_region – a world-coordinate box, ditto.
prefer_zarr – delegate to
zarrwhen installed.
- Returns:
the array, in the file’s own dtype.
- spacr.ome_zarr.require_codec(codec_id: str, config: Mapping[str, Any] | None = None) Callable[[bytes], bytes][source]¶
Return a
bytes -> bytesdecoder for a zarr compressor id.The standard library is tried first, then
numcodecs. Only when both fail is anything raised, and what is raised names the codec.- Parameters:
codec_id – the
idfrom a zarr v2compressorblock, or thenameof a zarr v3 codec —"zlib","blosc","zstd".config – the rest of that block, passed to
numcodecswhen it is needed. Ignored by the standard-library codecs, whose decompressors read their parameters out of the stream.
- Returns:
a callable taking the stored chunk bytes and returning the raw bytes.
- Raises:
ZarrExtraMissing – when neither the standard library nor an installed
numcodecsprovides the codec.OmeZarrError – when
codec_idis empty or not a string.
- spacr.ome_zarr.require_zarr()[source]¶
Import and return
zarr, or raise a message worth reading.- Returns:
the imported
zarrmodule.- Raises:
ZarrExtraMissing – when the extra is not installed, with the
pip install "spacr[zarr]"line and a note that most files do not need it.
- spacr.ome_zarr.spacing_from_axes(axes: Sequence[Axis]) spacr.layers.Spacing[source]¶
Build a
spacr.layers.Spacingfrom the SPACE axes only.The exclusion is the point. A spacing has one
unitsstring andspacr.layers.LayerStackcompares it by name; a spacing that mixed a time axis in seconds with a y axis in micrometers would answer that comparison with a string that is wrong for one of them, and pass.- Parameters:
axes – every axis of the image, in array order.
- Returns:
a spacing over the spatial axes, in the same relative order, with
spacr.layers.Spacing.unitsset from their common unit.- Raises:
OmeZarrError – when there are no spatial axes; when they carry different units from each other (named); or when a unit does not translate.
- spacr.ome_zarr.spacr_unit_to_ngff(units: str) str | None[source]¶
Translate a
spacr.layers.Spacing.unitstoken into an NGFF unit.- Parameters:
units – the spaCR token, e.g.
"um"or"px".- Returns:
the UDUNITS-2 name to write into
axes, orNonefor pixels — NGFF has no pixel unit, and an axis with nounitis the spec’s way of saying the same thing.- Raises:
OmeZarrError – on a token with no NGFF name, naming what is available.
- spacr.ome_zarr.write_ome_zarr(path: str | os.PathLike, array: Any, *, spacing: spacr.layers.Spacing | None = None, axes: Sequence[str | Axis] | None = None, name: str | None = None, levels: int = 1, downsample: str = 'mean', downsample_axes: Sequence[str] | None = None, chunks: Sequence[int] | None = None, compressor: str | None = 'zlib', compression_level: int = 5, order: str = 'C', dimension_separator: str = '/', fill_value: Any = 0, write_empty_chunks: bool = False, channel_names: Sequence[str] | None = None, channel_colors: Sequence[str] | None = None, overwrite: bool = False, ngff_version: str = '0.4') OmeZarrImage[source]¶
Write an array as an OME-NGFF 0.4 multiscale image.
The defaults are chosen so the result is readable by anything: zarr-format 2,
zlibchunks (which need no extra),/separators, and theaxes/coordinateTransformationsmetadata filled in from the spacing rather than left at 1.0.The pyramid.
levels=1writes level 0 alone; more builds each level by halving the two fastest-varying space axes of the one above with a 2x block mean (downsample="mean") or by striding (downsample="stride"). Neither is a Gaussian pyramid — there is no pre-filter beyond the box — and the block mean is the default because striding aliases: a one-pixel-wide bright structure survives or vanishes with the parity of its coordinate, so the same object appears and disappears as a user zooms. Label and mask arrays must usedownsample="stride": the mean of labels 3 and 5 is 4, which is a different object, and averaging a boolean mask invents half-membership. Levels shrink byceil(n / 2), so nothing is cropped off an odd edge.The transformations. Level k’s
scaleis level 0’s multiplied by 2^k on each downsampled axis — writing level 0’s scale on every level is the most common NGFF bug there is, and it renders as coarse levels that are a quarter the size of the fine ones and slide off the corner as you zoom. Thetranslationmoves too, and differently per method: a block mean centres its first output element half a level-0 pixel inside the edge, so level k getstranslate_0 + scale_0 * (2^k - 1) / 2, while a strided level samples element 0 exactly and keepstranslate_0.The chunks. Default is 1 along t and c (a viewer draws one channel of one timepoint),
DEFAULT_TILEon y and x, andDEFAULT_Z_CHUNKon any other space axis — about 2 MiB per chunk for uint16. Passchunks=to override.- Parameters:
path – the group directory to create.
array – the image data, 2 to 5 dimensional.
spacing – the voxel size, over the SPACE axes. Defaults to an isotropic pixel grid, which writes axes with no
unit— legal NGFF and honestly what is known.axes – axis names, or
Axisobjects for full control of the non-spatial axes (a time step, say). Derived fromspacingand the array’s rank when omitted.name – the
multiscalesname. Defaults to the directory name.levels – how many resolution levels to write.
downsample –
"mean"or"stride".downsample_axes – which axes to halve. Defaults to the two fastest-varying space axes — halving z as well ruins an anisotropic stack, where 12 planes at 2 µm become 2 planes at 16 µm by level 3.
chunks – chunk shape, one per axis.
compressor –
"zlib"(default),"gzip","bz2","lzma",Nonefor stored, or any codecnumcodecsprovides. The default needs no extra by design.compression_level – for the codecs that take one.
order –
"C"or"F"chunk memory order.dimension_separator –
"/"(nested directories, the default, and much kinder to filesystems at 10^5 chunks) or"."(flat).fill_value – what an unwritten chunk reads back as.
write_empty_chunks – write chunks that are entirely
fill_value.False(the default) omits them, which is how a sparse mask stops costing what a dense one does.channel_names – channel labels, written into the
omeroblock along with per-channel display windows measured from the data.channel_colors – hex RGB per channel; defaults to
DEFAULT_CHANNEL_COLORS.overwrite – replace an existing group. Without it, an existing group is refused rather than merged — a pyramid written over another pyramid’s levels leaves the leftover deeper levels of the old one in place, and they read back as part of the new image.
ngff_version – the version to declare. 0.4 is what this writes.
- Returns:
the group, reopened with
read_ome_zarr()— so the return value is a read of what was actually written, not a description of what was intended.- Raises:
OmeZarrError – on an unwritable axis layout, a unit with no NGFF name, or an existing group without
overwrite=True.
- spacr.ome_zarr.CODEC_MISSING_MESSAGE = Multiline-String[source]¶
Show Value
"""This OME-Zarr stores its chunks with the {codec!r} codec, which spaCR cannot decode with the standard library alone, and the optional `zarr` extra that provides it is not installed (missing module: {module}). Install it with: python -m pip install "spacr[zarr]" Codecs that need no extra: {stdlib}, or uncompressed. spaCR-written OME-Zarr uses one of those by default, so it always round-trips on a plain `pip install spacr`."""
- spacr.ome_zarr.ZARR_MISSING_MESSAGE = Multiline-String[source]¶
Show Value
"""This needs the optional `zarr` extra, which is not installed in this environment (missing module: {module}). Install it with: python -m pip install "spacr[zarr]" spaCR reads and writes OME-Zarr metadata, and stored/zlib/gzip/bz2/lzma chunks, without it — the extra is only needed for third-party chunk codecs and for zarr-format 3 features such as sharding."""
Nested helpers¶
- _compose._pair(transforms: Sequence[Mapping[str, Any]]) Tuple[List[float], List[float]]¶
Reduce one captured-dimension NGFF transform sequence.
- Parameters:
transforms – identity, scale, and translation mappings; a falsey sequence represents the identity transform.
- Returns:
mutable scale and translation lists of captured length
ndim. Repeated scales multiply componentwise and repeated translations add componentwise.- Raises:
OmeZarrError – when an entry is not a mapping, a scale or translation has the wrong arity, or the transform type is not supported; messages include the captured source location.
spacr/ome_zarr.py:1761