Skip to content

brainhops.io.transformations.zarr

Readers for OME-Zarr transformation fields.

Classes

OmeFieldError

Bases: ValueError

Raised when an OME-Zarr field cannot be read.

A displacement field placed by a non-affine transformation is refused with this error, because its world-unit displacements cannot be rescaled to voxel units without a linear part. A field whose axes cannot be read as one vector field is refused separately, with an AxisError.

OmeZarrField magic

OmeZarrField(
    raw_levels: list[ArrayProtocol] | None = None,
    voxel2world: Transformation | None = None,
    level_transforms: list[Transformation] | None = None,
    axes: list[Axis] | None = None,
    ome: Any | None = None,
)

Bases: ZarrParserWriter, WritableFileBasedTransformation, MultiscaleField

A coordinate or displacement field stored as OME-Zarr.

An OME-Zarr field is a Zarr store, so this is a file format. It is read from a store with from_store and written with [to_store][brainhops.io.base.zarr.ZarrParser.to_store], and it is discoverable through load like any other transformation format. An already-opened Zarr node is read with from_node and written with [to_node][brainhops.io.base.zarr.ZarrParser.to_node].

The field is a MultiscaleField, so it composes with the rest of the data model exactly as any multiscale field does, and a reslice onto a coarser grid selects the matching resolution level. The finest level is used unless a level is selected.

The reader holds the array of every level, the finest level's voxel-to-world transformation, the axes, and the raw OME metadata, all exactly as they were read. A field that is read and not modified therefore re-emits its OME metadata unchanged through to_ome.

A displacement field placed by a non-affine transformation is refused with an OmeFieldError. A field whose axes mix the displacement and coordinate types is refused with an AxisError.

Attributes

PREFIXES class-attribute
PREFIXES: tuple[str, ...] = ()

Filename prefixes required by this parser, e.g. ("y_", "iy_").

An empty tuple means "no constraint". A parser that constrains the prefix is more specific than one that does not, and wins ties.

Declaring EXTENSIONS and PREFIXES separately states the cross-product implicitly, which is how these conventions actually work: SPM's four names are {y_, iy_} x {.nii, .nii.gz}.

PRIORITY class-attribute
PRIORITY: int = 0

Explicit tie-breaker, consulted only when specificity cannot decide. Higher wins. Leave at 0 unless two parsers genuinely collide.

transformations property writable
transformations: tuple[Transformation, ...]

The transformations of the finest scale.

nscales property
nscales: int

The number of resolution scales.

scales property
scales: list[Sequence] | None

The resolution scales, each built as a sequence.

Methods:

sniff classmethod
sniff(
    file: FileOrContentLike,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> type | None

On a dispatcher, identify which registered format would read file. On a concrete format, score how confident it is that file, in any supported form, is its own.

sniff_content classmethod
sniff_content(
    content: ContentLike,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> type | None

On a dispatcher, identify which registered format would read the content (text or bytes). On a concrete format, score how confident it is that the content is its own.

sniff_text classmethod
sniff_text(
    text: str,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> type | None

On a dispatcher, identify which registered format would read the text. On a concrete format, score how confident it is that the text is its own.

sniff_lines classmethod
sniff_lines(
    lines: Iterable[str],
    error: bool | Type[Exception] = False,
    **kwargs,
) -> type | None

On a dispatcher, identify which registered format would read the lines. On a concrete format, score how confident it is that the lines are its own.

sniff_line classmethod
sniff_line(
    line: str,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> type | None

On a dispatcher, identify which registered format would read the line. On a concrete format, score how confident it is that the line is its own.

load classmethod
load(other: FileOrContentLike, **kwargs) -> Self

On a dispatcher, pick the best-matching registered format and build an instance of it from other. On a concrete format, build an instance of this class from other, in any supported form.

from_spec classmethod
from_spec(spec: SourceSpec, **kwargs) -> Self

Load a structured source specification through this dispatcher.

from_filename classmethod
from_filename(filename: FilenameLike, **kwargs) -> Self

Build an object from a filename.

Parameters:

Name Type Description Default
filename FilenameLike

The filename to parse.

required
**kwargs

Parser-specific options.

{}

Returns:

Type Description
obj

The parsed object.

from_content classmethod
from_content(content: ContentLike, **kwargs) -> Self

On a dispatcher, pick the best-matching registered format and build an instance of it from the content (text or bytes). On a concrete format, build an instance of this class from the content.

from_text classmethod
from_text(text: str, **kwargs) -> Self

On a dispatcher, pick the best-matching registered format and build an instance of it from the text. On a concrete format, build an instance of this class from the text.

from_lines classmethod
from_lines(lines: Iterable[str], **kwargs) -> Self

On a dispatcher, pick the best-matching registered format and build an instance of it from the lines. On a concrete format, build an instance of this class from the lines.

from_line classmethod
from_line(line: str, **kwargs) -> Self

On a dispatcher, pick the best-matching registered format and build an instance of it from the line. On a concrete format, build an instance of this class from the line.

save
save(file: FileLike, **kwargs) -> None

Write the object to a file (path or file-like object).

This is the generic front door to the to_* family. It is named save rather than to because to already means something else on the data models these parsers are mixed into: Transformation.to converts an object to another type. A writer's to was shadowed by it on every writable transformation.

Parameters:

Name Type Description Default
file FileLike

The file to write to.

required
**kwargs

Parser-specific options.

{}
to_filename
to_filename(filename: FilenameLike, **kwargs) -> None

Write the object to a filename.

Parameters:

Name Type Description Default
filename FilenameLike

The filename to write to.

required
**kwargs

Parser-specific options.

{}
to_bytes
to_bytes(**kwargs) -> bytes

Return a binary version of the file.

Parameters:

Name Type Description Default
**kwargs

Parser-specific options.

{}

Returns:

Type Description
bytes

A binary version of the file.

to_text
to_text(**kwargs) -> str

Return a text version of the file.

Parameters:

Name Type Description Default
**kwargs

Parser-specific options.

{}

Returns:

Type Description
str

A text version of the file.

to_lines
to_lines(**kwargs) -> Iterator[str]

Return a text version of the file as an iterable of lines.

Parameters:

Name Type Description Default
**kwargs

Parser-specific options.

{}

Returns:

Type Description
Iterator[str]

An iterable of lines representing the object.

to_line
to_line(**kwargs) -> str

Return a line representing the object.

Parameters:

Name Type Description Default
**kwargs

Parser-specific options.

{}

Returns:

Type Description
str

A line representing the object.

from_dict classmethod
from_dict(other: Mapping, *args, **kwargs) -> Self

Create an instance of the class from a dictionary-like object.

Only keys in the dictionary that match keyword-like fields of this class, or the keywords its constructor takes without storing them (its InitVars, such as the matrix= of an Affine), will be used. Other keys are ignored, but see from_other, which refuses them.

Additional positional and/or keyword arguments can be provided, and will take precedence over the values in the dictionary.

A key naming a field that this class fixes (a field that cannot be passed to its constructor) is checked instead of used: a dictionary that sets it to anything other than None or the value of this class is refused with a ValueError.

from_instance classmethod
from_instance(other: Any, *args, **kwargs) -> Self

Create an instance from an instance of a similar class.

The data model copies the fields both classes share, by name. A field that a file format declares for its own use -- such as the nibabel image and header of the NIfTI and MGH formats -- is only copied from an object of that same format: from any other object, a field of the same name holds something else (a NIfTI image is no MGH image), so this class's default is kept instead. Saving a NIfTI image to MGH, or the converse, therefore converts the data model only, and the format-specific state is rebuilt by the writer.

from_other classmethod
from_other(other: Any, *args, **kwargs) -> Self

Create an instance from a file, or from anything the data model reads.

A path (str or os.PathLike), an open file, bytes or a structured source (SourceSpec) is read with load: on a dispatcher such as FileBasedImage, the best-matching registered format reads it, and on a concrete format, that format does. Any other value is handed to the data model's own from_other, which reads a mapping field by field, copies an instance of a similar class, and passes anything else to the constructor.

Parameters:

Name Type Description Default
other Any

A file, its content, a mapping, or an instance of a similar class.

required
*args

Constructor arguments. A file is read with keyword options only.

()
**kwargs

Format-specific options when reading a file, and field values otherwise.

{}

Returns:

Type Description
obj

The object that was built.

Raises:

Type Description
TypeError

If positional arguments come with a file to read.

compute
compute(
    mode: ModeLike | None = None,
    *,
    simplify: SimplifyLike = "analytic",
    factor: bool = False,
) -> Transformation

Compute the field as a plain transformation.

The finest scale is composed and returned. The result is an ordinary transformation, with no pyramid, so it computes exactly as the finest scale would on its own.

simplify
simplify(
    policy: SimplifyLike = "analytic",
    *,
    compute: ModeLike | bool | None = False,
) -> Self

Simplify this transformation under a per-kind policy.

Convenience sugar for compute: t.simplify(policy, compute=mode) is t.compute(mode, simplify=policy).

By default simplify() does no computation at all: compute=False maps to mode=False, which composes nothing (no matrices multiplied, no fields sampled, no lazy inverse materialized). It only downcasts each leaf under policy (analytic by default). Pass an explicit compute=<mode> to also compose that kind.

Parameters:

Name Type Description Default
policy simplify policy

The simplify policy, in the grammar compute accepts.

"analytic"
compute [list of] name or type

The compose mode. The default, False, composes nothing (mode=False in compute); None would compose every kind. A real mode passes straight through.

False
square
square(compute: bool = False, **kwargs) -> Transformation

Return the square of this transformation, self @ self.

The square is the sequence [self, self], which composes when it is computed. It is defined for a transformation that maps a space to itself.

Parameters:

Name Type Description Default
compute bool

Whether to compute the result now rather than return it lazily.

False
**kwargs

Passed to compute when compute is true.

{}

Raises:

Type Description
DomainError

If the transformation does not map a space to itself.

sqrt
sqrt(compute: bool = False, **kwargs) -> Transformation

Return the principal square root of this chain.

The chain is first simplified, which costs nothing. A chain [P, *X, P^-1], where P^-1 is the lazy inverse of P, or both are affines whose product is exactly the identity, is a change of coordinates around X, and its square root is [P, sqrt(X), P^-1]: a field stored in voxels between a world-to-voxel affine and its lazy inverse keeps that form. Any other chain is composed now, and the square root of the transformation it composes to is returned.

Raises:

Type Description
DomainError

If the chain does not map a space to itself, or if the square root of what it reduces to is not defined.

NotImplementedError

If the chain does not compose to a single transformation.

to
to(
    cls: Type[Transformation] | None = None, **kwargs
) -> Transformation

Convert this chain to a different type or encoding.

See Transformation.to. A chain has no tangent of its own -- the tangent of a composition is not the sum of the tangents -- so log= re-encodes the transformation it reduces to, and anything else is refused before it is computed:

  • a chain that simplifies to one transformation is that one;
  • a change of coordinates [P, *X, P^-1] (see sqrt) keeps its ends, and re-encodes X: the flow of a velocity commutes with the conjugation, so this is exact. A velocity read between a world-to-voxel affine and its inverse (|svf) is turned into its displacement that way;
  • a chain of affines is composed, which is cheap and exact.

Any other chain -- one with a field, between ends that do not undo each other -- raises ConversionError.

to_singlescale
to_singlescale(index: int = 0) -> Any

Return the resolution scale at a given index.

Index 0 is the finest scale. The scale is returned as it is stored, so for a field it is the plain transformation of that scale rather than the multiscale field.

from_store classmethod
from_store(location: StoreLike, **kwargs) -> Self

Read the object from a store location or an opened store.

location is a path, given as a string or an os.PathLike, or an already-opened abczarr or driver-native store.

to_ome
to_ome() -> Any

Return the OME metadata to write for this field.

An untouched field returns the metadata it was read with, the identical object, so a read followed by a write re-emits the OME metadata unchanged.

from_node classmethod
from_node(node: Any, **kwargs) -> Self

Read the field from an opened Zarr group.

The node's own OME metadata describes the field, so it is read here rather than on first access: the arrays of every level are held, and the metadata is kept exactly as read.

to_node
to_node(node: Any, **kwargs) -> Any

Write the field into an opened Zarr group, and return it.

to_store
to_store(location: StoreLike, **kwargs) -> None

Write the field to a store location, or into an opened store.

An opened group is written into as it stands. A location names a store that does not exist yet, so the group is created there first.