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.
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
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
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
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
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
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
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
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
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
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 |
"analytic"
|
compute
|
[list of] name or type
|
The compose mode. The default, |
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 |
{}
|
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](seesqrt) keeps its ends, and re-encodesX: 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
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
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
Write the field into an opened Zarr group, and return it.