Skip to content

brainhops.io.transformations.x5

Reader and writer for BIDS X5 (.x5) transformation files.

X5 is the HDF5-based transformation format drafted by the BIDS extension proposal BEP014 ("Transforms"). The draft is not final; this module follows what its two implementations read and write, for interoperability with them:

  • nitransforms >= 25.0 (nitransforms/io/x5.py, and the to_x5 / from_x5 functions of linear.py, nonlinear.py and manip.py), which writes the current layout, Version = 1;
  • fslpy 3.x (fsl/transform/x5.py), which writes an earlier layout, Version = "0.1.0". It is read, and written back in the current layout.

A draft format

Everything below that is not in nitransforms or fslpy is left unimplemented rather than guessed: see "Not supported".

Layout

/                          attrs: Format = "X5", Version = 1
/TransformGroup/0          attrs: Type, SubType, Representation,
                                  Metadata (JSON), ArrayLength
    Transform              the parameters: a 4x4 matrix, or a field
    DimensionKinds         what each axis of Transform holds,
                           e.g. ["space", "space", "space", "vector"]
    Inverse                optional precomputed inverse
    Jacobian               optional cached Jacobian determinant
    AdditionalParameters   optional, depends on SubType
    Domain/                REQUIRED for nonlinear, RECOMMENDED for linear
        Grid               1 if the samples lie on a regular grid
        Size               the number of samples per dimension
        Mapping            voxel-to-world affine of the grid
        attrs: Coordinates e.g. "cartesian"
/TransformGroup/1 ...
/TransformChain/0          optional: a string "0/1/2" of node indices

Unlike ITK's .h5, which also keeps its transforms under a TransformGroup, an X5 file has no ITKVersion, and says what it is in its root Format attribute, which is what tells the two apart. An X5 file named .h5 is therefore read by this reader, not by brainhops.io.transformations.itk.h5.

Arrays are read as h5py returns them: a field written by nitransforms or fslpy from a numpy array of shape (X, Y, Z, 3) is read with that shape, with DimensionKinds ("space", "space", "space", "vector"). When DimensionKinds puts the "vector" axis elsewhere, it is moved last.

Direction

There are no named source and target spaces in X5. Every transform maps points of one world space, A, to points of another, B, in RAS millimetres -- so each one is read as a brainhops transformation whose input and output are both RASmm, and which maps an input point to an output point.

  • nitransforms: the affine "maps coordinates from reference space into moving space" (linear.py, Affine.__init__), and a field is sampled on the reference grid (Domain), mapping each reference point to x + u(x) (nonlinear.py, DenseFieldTransform.map). A is the reference (fixed) space: the transform "pulls" the moving image onto the reference grid.
  • fslpy: "X5 files enable a transformation from the world coordinate system of image A to the world coordinate system of image B" (x5.py, module docstring). For a linear file, A is the source and B the reference (writeLinearX5(fname, xform, src, ref) writes src to /A), so the matrix maps source points to reference points. For a nonlinear file, A is the reference (writeNonLinearX5 writes field.ref to /A), as with FNIRT.

Both agree on "A to B", which is all that brainhops represents. Which image A is differs between fslpy's linear files and every other file. Resampling an image with a transformation read here is a matter of the caller knowing which of the two images it was registered from.

Fields

A nonlinear node with SubType = "densefield" (or none) is read as:

Representation Transformation
"displacements" (fslpy relative) X5DisplacementField
"deformations" (fslpy absolute) X5CoordinatesField

nitransforms writes "deformations" for a field of absolute coordinates; "coordinates" and "absolute" are read as the same. A field of displacements holds, at each voxel of the Domain grid, the RAS displacement of the point at its centre, and is read as the chain RAS to voxel, displacements in voxel units, voxel to RAS -- the same chain as a NIfTI DISPVECT field (brainhops.io.transformations.nifti), built by the same shared helpers. It is interpolated linearly, and extended with its nearest value outside its grid. nitransforms interpolates it with cubic splines and treats points outside the grid as not displaced, so the two can differ off the grid nodes.

B-splines

A nonlinear node with SubType = "bspline" and Representation = "coefficients" is nitransforms' BSplineFieldTransform (nonlinear.py, to_x5 / from_x5), and is read as an X5BSplineField:

  • Transform holds the B-spline coefficients of a displacement in RAS millimetres, one 3-vector per knot, (X, Y, Z, 3) (DimensionKinds ("space", "space", "space", "vector"));
  • AdditionalParameters is the voxel-to-RAS affine of the grid of knots: knot k sits at voxel k of that grid;
  • Domain is the grid of the reference image, on which nitransforms' to_field samples the field. The transform itself does not depend on it.

nitransforms maps a RAS point x to x + sum_k c_k B3(i(x) - k) (nonlinear.py, _map_xyz), where i(x) are the coordinates of x in the grid of knots, and B3 is the tensor product of centred cubic B-splines (interp/bspline.py, _cubic_bspline); only knots that exist contribute, so coefficients beyond the grid are zero. The degree is not stored: nitransforms evaluates cubics only. This is the chain RAS to knot voxel, a DisplacementField of coefficients (coeff=True, degree 3, zero boundary) rotated into knot units, knot voxel to RAS -- the same chain as a dense field of displacements, and the same knot-grid convention as an ITK BSplineTransform ([brainhops.io.transformations.itk][]), whose fixed parameters place its coefficient grid in LPS.

Any such chain whose input and output are RASmm is written as a bspline node, with the knot grid as its Domain when it was not read from a file (nitransforms requires a Domain, and has no other grid to give it).

Chains

A chain is stored by nitransforms (manip.py, TransformChain.to_filename) as a string dataset /TransformChain/<n> listing the indices of its nodes, "0/1/2". TransformChain.map applies them in that order, f2(f1(f0(x))), which is the order of a brainhops Sequence, so the chain "0/1/2" is read as Sequence([t0, t1, t2]). Which nodes are read is described in X5Transform.selection.

Metadata

The datamodel holds no metadata. The JSON Metadata of every node -- and its Domain, Inverse, Jacobian and other attributes -- is kept, as read, on the reader (X5Transform.nodes, X5Transform.header), and written back. A transformation built from scratch is written with no metadata.

Not supported

These raise an error when the transformation is read (its raw nodes are still read, and written back unchanged):

  • Type = "composite": the draft lists it, but does not say how its parts are stored, and neither nitransforms nor fslpy writes one;
  • ArrayLength > 1: a stack of affines, one per volume of a series (nitransforms' LinearTransformsMapping), which the datamodel has no transformation for;
  • domains that are not regular 3-D cartesian grids (surfaces).

These are refused when written: a transformation that does not map RASmm to RASmm, or that is neither an affine, a dense field, nor a cubic B-spline with a zero boundary.

Classes

X5BSplineField

X5BSplineField(
    _transformations: tuple[Transformation, ...]
    | None = None,
)

Bases: _X5RASDisplacements

A nonlinear X5 transform of SubType bspline.

The field holds, at each knot of a regular grid, the cubic B-spline coefficients of a displacement in RAS millimetres; the grid's voxel-to-RAS affine is the node's AdditionalParameters (its Domain is the reference grid, which the transform does not depend on). The displacement of a point x is u(x) = sum_k c_k B3(i(x) - k), where i(x) are the coordinates of x in the knot grid, B3 is the tensor-product centred cubic B-spline and coefficients beyond the grid are zero; the field maps RAS to RAS as x -> x + u(x).

Slot Transformation
ras2voxel RAS world coordinates to the knot grid
displacement the coefficients, in knot-grid units (coeff)
voxel2ras the knot grid back to RAS world

Attributes

ras2voxel property
ras2voxel: Transformation

The affine from RAS world coordinates to the field's voxels.

displacement property
displacement: Transformation

The displacement field, in the voxel units of its grid.

voxel2ras property
voxel2ras: Transformation

The affine from the field's voxels back to RAS world.

Methods:

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: Self, *args, **kwargs) -> Self

Create an instance of the class from an instance of a similar class.

Only attributes of the other instance that match keyword-like fields of this class will be used. An attribute that is None is unset, and leaves the default of this class in place.

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

Unless the other instance is already an instance of this class, an attribute naming a field that this class fixes (a field that cannot be passed to its constructor) is checked instead of used: an instance that sets it to anything other than None or the value of this class is refused with a ValueError. A generic Axis whose orientation is right-to-left, for example, cannot be read as a LeftToRightAxis.

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

Create an instance of the class from any object that can be interpreted as a dictionary, or an instance of a similar class, or an arguments to be passed to the constructor.

A similar class is this class or one of its parents within the data model, or another member of a polymorphic family this class belongs to: calling a polymorphic class such as Axis builds the subclass its arguments select, so a "generic" axis is usually an instance of a sibling (a RightToLeftAxis, a TimeAxis) rather than of a parent. Any other object, including an instance of a parent that is not a data model (such as a plain object), is passed to the constructor.

Unlike from_dict, a dictionary with a key that matches no field of this class is refused with a TypeError naming the keys, so that a misspelt key is not silently dropped.

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

Compute the resulting transform of the sequence of transformations.

Assuming that mode=True:

  • If all transformations in the sequence are affine-like transformations, compute() returns an affine-like transform.

  • If the first (= rightmost) transform in the sequence is a coordinate field, compute() returns a coordinate field.

  • If the first (= rightmost) transform in the sequence is an affine-like transform, and the sequence contains at least one non-affine-like transform, compute() returns a sequence of two transformations:

  • the composition of all affine-like transformations that appear before the first non-affine-like transform in the sequence, and

  • the composition of all transformations in the sequence, starting from the first non-affine-like transform in the sequence.
Parameters

mode : [list of] name or type, optional Kinds of transformations to compose. * If True (default): compose every kind in the sequence. * If False: compose nothing (simplify-only). * If a (list of) transformation type(s): compose only pairs of transformations of these kinds. simplify : simplify policy, default="analytic" Whether to simplify sub-transformations prior to composition, and how hard to try to simplify them. * "analytic" (the default) looks at the type structure only; * "numeric" looks at the numeric values of the transformation; * False/"none"/None disables simplification. factor : bool, default=False Whether to rewrite the sequence into its axis-group normal form [grid?, F_1..F_m, Pi_perm?]: a leading grid (if any), one axis-preserving subspace factor per group of axes that transform together, and a trailing reindex permutation. Off by default, so the result is byte-for-byte the plain compute() result. Nothing is ever composed across groups; mode still decides whether the restricted pieces inside a group compose. A chain that creates or drops axes is left unfactored. With mode=False nothing is computed, so factor has nothing to act on and is ignored.

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.

from_ras classmethod
from_ras(vectors: ArrayProtocol, vox2ras: ndarray) -> Self

Build the field from RAS vectors and their grid.

X5CoordinatesField

X5CoordinatesField(
    _transformations: tuple[Transformation, ...]
    | None = None,
)

Bases: ImmutableSequence

A nonlinear X5 transform that stores absolute coordinates.

Each sample of the field holds the RAS coordinates, in millimetres, that the world point at its centre maps to. The Domain/Mapping of the node is the voxel-to-RAS affine of its grid.

Slot Transformation
ras2voxel RAS world coordinates to the field's voxels
coordinates the field of RAS coordinates

Attributes

ras2voxel property
ras2voxel: Transformation

The affine from RAS world coordinates to the field's voxels.

coordinates property
coordinates: Transformation

The field of RAS coordinates, defined on the field's voxels.

Methods:

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: Self, *args, **kwargs) -> Self

Create an instance of the class from an instance of a similar class.

Only attributes of the other instance that match keyword-like fields of this class will be used. An attribute that is None is unset, and leaves the default of this class in place.

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

Unless the other instance is already an instance of this class, an attribute naming a field that this class fixes (a field that cannot be passed to its constructor) is checked instead of used: an instance that sets it to anything other than None or the value of this class is refused with a ValueError. A generic Axis whose orientation is right-to-left, for example, cannot be read as a LeftToRightAxis.

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

Create an instance of the class from any object that can be interpreted as a dictionary, or an instance of a similar class, or an arguments to be passed to the constructor.

A similar class is this class or one of its parents within the data model, or another member of a polymorphic family this class belongs to: calling a polymorphic class such as Axis builds the subclass its arguments select, so a "generic" axis is usually an instance of a sibling (a RightToLeftAxis, a TimeAxis) rather than of a parent. Any other object, including an instance of a parent that is not a data model (such as a plain object), is passed to the constructor.

Unlike from_dict, a dictionary with a key that matches no field of this class is refused with a TypeError naming the keys, so that a misspelt key is not silently dropped.

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

Compute the resulting transform of the sequence of transformations.

Assuming that mode=True:

  • If all transformations in the sequence are affine-like transformations, compute() returns an affine-like transform.

  • If the first (= rightmost) transform in the sequence is a coordinate field, compute() returns a coordinate field.

  • If the first (= rightmost) transform in the sequence is an affine-like transform, and the sequence contains at least one non-affine-like transform, compute() returns a sequence of two transformations:

  • the composition of all affine-like transformations that appear before the first non-affine-like transform in the sequence, and

  • the composition of all transformations in the sequence, starting from the first non-affine-like transform in the sequence.
Parameters

mode : [list of] name or type, optional Kinds of transformations to compose. * If True (default): compose every kind in the sequence. * If False: compose nothing (simplify-only). * If a (list of) transformation type(s): compose only pairs of transformations of these kinds. simplify : simplify policy, default="analytic" Whether to simplify sub-transformations prior to composition, and how hard to try to simplify them. * "analytic" (the default) looks at the type structure only; * "numeric" looks at the numeric values of the transformation; * False/"none"/None disables simplification. factor : bool, default=False Whether to rewrite the sequence into its axis-group normal form [grid?, F_1..F_m, Pi_perm?]: a leading grid (if any), one axis-preserving subspace factor per group of axes that transform together, and a trailing reindex permutation. Off by default, so the result is byte-for-byte the plain compute() result. Nothing is ever composed across groups; mode still decides whether the restricted pieces inside a group compose. A chain that creates or drops axes is left unfactored. With mode=False nothing is computed, so factor has nothing to act on and is ignored.

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.

from_ras classmethod
from_ras(
    coordinates: ArrayProtocol, vox2ras: ndarray
) -> Self

Build the field from RAS coordinates and their grid.

X5DisplacementField

X5DisplacementField(
    _transformations: tuple[Transformation, ...]
    | None = None,
)

Bases: _X5RASDisplacements

A nonlinear X5 transform that stores relative displacements.

Each sample of the field holds the displacement, in RAS millimetres, of the world point at its centre: the field maps RAS to RAS as x -> x + u(x). The Domain/Mapping of the node is the voxel-to-RAS affine of its grid.

Slot Transformation
ras2voxel RAS world coordinates to the field's voxels
displacement the displacement field, in voxel units
voxel2ras the field's voxels back to RAS world

Attributes

degree class-attribute
degree: int = 1

The spline degree used to interpolate the field.

bound class-attribute

The boundary condition used outside of the field of view.

coeff class-attribute
coeff: bool = False

Whether the field holds spline coefficients rather than values.

ras2voxel property
ras2voxel: Transformation

The affine from RAS world coordinates to the field's voxels.

displacement property
displacement: Transformation

The displacement field, in the voxel units of its grid.

voxel2ras property
voxel2ras: Transformation

The affine from the field's voxels back to RAS world.

Methods:

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: Self, *args, **kwargs) -> Self

Create an instance of the class from an instance of a similar class.

Only attributes of the other instance that match keyword-like fields of this class will be used. An attribute that is None is unset, and leaves the default of this class in place.

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

Unless the other instance is already an instance of this class, an attribute naming a field that this class fixes (a field that cannot be passed to its constructor) is checked instead of used: an instance that sets it to anything other than None or the value of this class is refused with a ValueError. A generic Axis whose orientation is right-to-left, for example, cannot be read as a LeftToRightAxis.

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

Create an instance of the class from any object that can be interpreted as a dictionary, or an instance of a similar class, or an arguments to be passed to the constructor.

A similar class is this class or one of its parents within the data model, or another member of a polymorphic family this class belongs to: calling a polymorphic class such as Axis builds the subclass its arguments select, so a "generic" axis is usually an instance of a sibling (a RightToLeftAxis, a TimeAxis) rather than of a parent. Any other object, including an instance of a parent that is not a data model (such as a plain object), is passed to the constructor.

Unlike from_dict, a dictionary with a key that matches no field of this class is refused with a TypeError naming the keys, so that a misspelt key is not silently dropped.

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

Compute the resulting transform of the sequence of transformations.

Assuming that mode=True:

  • If all transformations in the sequence are affine-like transformations, compute() returns an affine-like transform.

  • If the first (= rightmost) transform in the sequence is a coordinate field, compute() returns a coordinate field.

  • If the first (= rightmost) transform in the sequence is an affine-like transform, and the sequence contains at least one non-affine-like transform, compute() returns a sequence of two transformations:

  • the composition of all affine-like transformations that appear before the first non-affine-like transform in the sequence, and

  • the composition of all transformations in the sequence, starting from the first non-affine-like transform in the sequence.
Parameters

mode : [list of] name or type, optional Kinds of transformations to compose. * If True (default): compose every kind in the sequence. * If False: compose nothing (simplify-only). * If a (list of) transformation type(s): compose only pairs of transformations of these kinds. simplify : simplify policy, default="analytic" Whether to simplify sub-transformations prior to composition, and how hard to try to simplify them. * "analytic" (the default) looks at the type structure only; * "numeric" looks at the numeric values of the transformation; * False/"none"/None disables simplification. factor : bool, default=False Whether to rewrite the sequence into its axis-group normal form [grid?, F_1..F_m, Pi_perm?]: a leading grid (if any), one axis-preserving subspace factor per group of axes that transform together, and a trailing reindex permutation. Off by default, so the result is byte-for-byte the plain compute() result. Nothing is ever composed across groups; mode still decides whether the restricted pieces inside a group compose. A chain that creates or drops axes is left unfactored. With mode=False nothing is computed, so factor has nothing to act on and is ignored.

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.

from_ras classmethod
from_ras(vectors: ArrayProtocol, vox2ras: ndarray) -> Self

Build the field from RAS vectors and their grid.

X5Domain magic

X5Domain(
    grid: bool = True,
    size: tuple[int, ...] = (),
    mapping: Any = None,
    coordinates: str | None = None,
)

Bases: Magic

The Domain group of a transform: the grid it is sampled on.

REQUIRED for a nonlinear transform, RECOMMENDED for a linear one.

Attributes

grid class-attribute instance-attribute
grid: bool = True

Whether the samples lie on a regular grid (Grid dataset).

size class-attribute instance-attribute
size: tuple[int, ...] = ()

The number of samples per dimension (Size dataset).

mapping class-attribute instance-attribute
mapping: Any = None

The (D + 1, D + 1) affine from sample indices to world (RAS) coordinates (Mapping dataset): the voxel-to-RAS affine of the grid.

coordinates class-attribute instance-attribute
coordinates: str | None = None

The kind of world coordinates (Coordinates attribute), e.g. "cartesian".

X5Header magic

X5Header(
    format: str = X5_FORMAT,
    version: Any = X5_VERSION,
    attrs: dict[str, Any] = Factory(dict),
    chains: list[tuple[int, ...]] = Factory(list),
    legacy: bool = False,
)

Bases: Magic

The root of an X5 file: its attributes and its chains.

Attributes

format class-attribute instance-attribute
format: str = X5_FORMAT

The root Format attribute.

version class-attribute instance-attribute
version: Any = X5_VERSION

The root Version attribute, as read: 1 for the current draft, "0.1.0" for fslpy's earlier layout.

attrs class-attribute instance-attribute
attrs: dict[str, Any] = Factory(dict)

Any other root attribute, as read.

chains class-attribute instance-attribute
chains: list[tuple[int, ...]] = Factory(list)

The chains of /TransformChain, in order: each lists, in the order they are applied, the indices of the transforms it chains.

legacy class-attribute instance-attribute
legacy: bool = False

Whether the file used fslpy's earlier (0.x) layout.

X5Node magic

X5Node(
    type: str = "linear",
    transform: Any = None,
    subtype: str | None = None,
    representation: str | None = None,
    metadata: Any = None,
    dimension_kinds: tuple[str, ...] | None = None,
    domain: X5Domain | None = None,
    inverse: Any = None,
    jacobian: Any = None,
    additional_parameters: Any = None,
    array_length: int = 1,
    attrs: dict[str, Any] = Factory(dict),
)

Bases: Magic

One numbered group /TransformGroup/<i> of an X5 file.

Every field mirrors an attribute or a dataset of the group. Large datasets (Transform, Inverse, Jacobian) are read lazily when the file is opened with load=False.

Attributes

type class-attribute instance-attribute
type: str = 'linear'

Type: "linear", "nonlinear" or "composite". fslpy's "affine" is read as "linear".

transform class-attribute instance-attribute
transform: Any = None

Transform: the parameters -- a (D + 1, D + 1) matrix, a stack of them, or a dense field.

subtype class-attribute instance-attribute
subtype: str | None = None

SubType, e.g. "affine", "densefield" or "bspline".

representation class-attribute instance-attribute
representation: str | None = None

Representation, e.g. "matrix", "displacements" or "deformations".

metadata class-attribute instance-attribute
metadata: Any = None

Metadata: the JSON attribute, decoded (a dict), or the raw string if it is not valid JSON.

dimension_kinds class-attribute instance-attribute
dimension_kinds: tuple[str, ...] | None = None

DimensionKinds: what each axis of transform holds, e.g. ("space", "space", "space", "vector").

domain class-attribute instance-attribute
domain: X5Domain | None = None

The Domain group.

inverse class-attribute instance-attribute
inverse: Any = None

Inverse: an optional precomputed inverse.

jacobian class-attribute instance-attribute
jacobian: Any = None

Jacobian: an optional cached Jacobian determinant.

additional_parameters class-attribute instance-attribute
additional_parameters: Any = None

AdditionalParameters: subtype-specific parameters (for a bspline, the affine of the grid of knots).

array_length class-attribute instance-attribute
array_length: int = 1

ArrayLength: how many transforms transform stacks.

attrs class-attribute instance-attribute
attrs: dict[str, Any] = Factory(dict)

Any other attribute of the group, as read.

X5Transform magic

X5Transform(
    header: X5Header = Factory(X5Header, repr=False),
    nodes: list[X5Node] = Factory(list, repr=False),
    chain: int | None = None,
    position: int | None = None,
    file: File | None = None,
)

Bases: X5TransformParser, Sequence, WritableFileBasedTransformation

A transformation stored in a BIDS X5 (.x5) file.

It is the Sequence of the transforms that the file chains, in the order in which they are applied (see selection). Every transform maps RAS world coordinates, in millimetres, to RAS world coordinates:

X5 node Transformation
linear, a 4x4 matrix Affine (RASmm to RASmm)
nonlinear, displacements X5DisplacementField
nonlinear, deformations X5CoordinatesField
nonlinear, bspline X5BSplineField

The raw content of the file -- every node, with its JSON Metadata, its Domain, its precomputed Inverse and any other attribute -- is kept in header and nodes.

What is written

A transformation read from a file, whose chain has not been assigned, is written back as it was read: every node and chain of the file, in the current layout. One whose transformations were assigned -- including one built from scratch -- is written as the nodes those transformations encode, plus one /TransformChain that applies them in order when there are several. An element that is one of the transformations read from a node is written as that node, unchanged, so its metadata survives. The package documentation lists what can be encoded.

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.

header class-attribute instance-attribute
header: X5Header = Factory(X5Header, repr=False)

The root attributes and the chains of the file.

nodes class-attribute instance-attribute
nodes: list[X5Node] = Factory(list, repr=False)

Every transform of /TransformGroup, as stored.

chain class-attribute instance-attribute
chain: int | None = None

Which chain of /TransformChain the transformation is, if any. See X5Transform.selection.

position class-attribute instance-attribute
position: int | None = None

Which single transform of /TransformGroup the transformation is, if any. See X5Transform.selection.

file class-attribute instance-attribute
file: File | None = None

The open HDF5 file, when read with keep_open=True.

selection property
selection: tuple[int, ...]

The indices of the nodes this transformation chains, in order.

  1. The chain chain of /TransformChain, if one was asked for.
  2. The node position, if one was asked for.
  3. Otherwise the first chain of /TransformChain, if the file has one, as nitransforms' TransformChain.from_filename does.
  4. Otherwise the file's single node.
  5. Otherwise, in a file with several nodes and no chain, the first node, as nitransforms' Affine.from_filename and DenseFieldTransform.from_filename do, with a warning: the draft says nothing of how unchained nodes relate.

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_file classmethod
sniff_file(
    file: H5Like,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> float

Score a path, open HDF5 file, or binary stream.

sniff_filename classmethod
sniff_filename(
    filename: str | PathLike,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> float

Score the HDF5 file found at a path.

sniff_fileobj classmethod
sniff_fileobj(
    file: IO,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> float

Score an open, seekable binary stream.

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_bytes classmethod
sniff_bytes(
    content: bytes,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> float

Score the bytes of an HDF5 file.

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_file classmethod
from_file(
    file: H5Like,
    keep_open: bool = False,
    load: bool = True,
    **kwargs,
) -> Self

Build an object from a file (path, file-like object, or HDF5 file).

Parameters:

Name Type Description Default
file str | PathLike | IO | File

Input file.

required
keep_open bool

If True, keep the HDF5 file open after loading. If False, close the file after loading. If load=False and keep_open=False, the file is reopened every time a lazily read dataset is accessed.

False
load bool

If True, read large datasets into memory. If False, keep them on disk.

True
from_filename classmethod
from_filename(
    filename: str | PathLike,
    keep_open: bool = False,
    load: bool = True,
    **kwargs,
) -> Self

Build an object from the HDF5 file found at a path.

from_fileobj classmethod
from_fileobj(
    file: IO,
    keep_open: bool = False,
    load: bool = True,
    **kwargs,
) -> Self

Build an object from an open, seekable binary stream.

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_bytes classmethod
from_bytes(content: BinaryContentLike, **kwargs) -> Self

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

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_file
to_file(file: H5Like, **kwargs) -> None

Write to a path, an open binary stream, or an HDF5 file.

to_filename
to_filename(filename: str | PathLike, **kwargs) -> None

Write to the file found at a path, replacing it.

to_fileobj
to_fileobj(file: IO, **kwargs) -> None

Write to a binary stream open for writing.

to_bytes
to_bytes(**kwargs) -> bytes

The bytes of the HDF5 file that encodes this object.

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 = True,
    *,
    simplify: SimplifyLike = "analytic",
    factor: bool = False,
) -> Transformation

Compute the resulting transform of the sequence of transformations.

Assuming that mode=True:

  • If all transformations in the sequence are affine-like transformations, compute() returns an affine-like transform.

  • If the first (= rightmost) transform in the sequence is a coordinate field, compute() returns a coordinate field.

  • If the first (= rightmost) transform in the sequence is an affine-like transform, and the sequence contains at least one non-affine-like transform, compute() returns a sequence of two transformations:

  • the composition of all affine-like transformations that appear before the first non-affine-like transform in the sequence, and

  • the composition of all transformations in the sequence, starting from the first non-affine-like transform in the sequence.
Parameters

mode : [list of] name or type, optional Kinds of transformations to compose. * If True (default): compose every kind in the sequence. * If False: compose nothing (simplify-only). * If a (list of) transformation type(s): compose only pairs of transformations of these kinds. simplify : simplify policy, default="analytic" Whether to simplify sub-transformations prior to composition, and how hard to try to simplify them. * "analytic" (the default) looks at the type structure only; * "numeric" looks at the numeric values of the transformation; * False/"none"/None disables simplification. factor : bool, default=False Whether to rewrite the sequence into its axis-group normal form [grid?, F_1..F_m, Pi_perm?]: a leading grid (if any), one axis-preserving subspace factor per group of axes that transform together, and a trailing reindex permutation. Off by default, so the result is byte-for-byte the plain compute() result. Nothing is ever composed across groups; mode still decides whether the restricted pieces inside a group compose. A chain that creates or drops axes is left unfactored. With mode=False nothing is computed, so factor has nothing to act on and is ignored.

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.

sniff_h5 classmethod
sniff_h5(
    h5file: File, error: bool | Type[Exception] = False
) -> float

Score an open HDF5 file: an X5 file says so in its root Format attribute.

from_h5 classmethod
from_h5(
    h5file: File,
    keep_open: bool = False,
    load: bool = True,
    chain: int | None = None,
    position: int | None = None,
    **kwargs,
) -> Self

Build an object from an open HDF5 file.

Parameters:

Name Type Description Default
h5file File

Input HDF5 file.

required
keep_open bool

Keep the file open after reading it.

False
load bool

Read the fields into memory. If False, they are read lazily, when used.

True
chain int

Which chain of /TransformChain to read.

None
position int

Which single transform of /TransformGroup to read.

None
to_h5
to_h5(h5file: File, **kwargs) -> None

Write this transformation into an empty HDF5 file.

__del__
__del__() -> None

Close the underlying HDF5 file, if one is still open.

node_transformation
node_transformation(index: int) -> Transformation

The transformation that node index encodes.

It is decoded once, and the same object is returned afterwards, which is how the writer recognises it.

transformations
transformations() -> tuple[Transformation, ...]

The chain of transformations, in the order they are applied.

It is decoded from the nodes on first access, and cached, as a tuple. Assigning to it overrides the decoded chain, and is what the writer then encodes.

to_struct
to_struct() -> tuple[X5Header, list[X5Node]]

The header and the nodes that encode this transformation.

Raises:

Type Description
UnrepresentableTransformationError

If an element of the chain cannot be encoded in X5.

X5TransformParser magic

X5TransformParser(
    header: X5Header = Factory(X5Header, repr=False),
    nodes: list[X5Node] = Factory(list, repr=False),
    chain: int | None = None,
    position: int | None = None,
    file: File | None = None,
)

Bases: Magic, Hdf5ParserWriter

Reads and writes the raw content of a BIDS X5 file.

Attributes

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

File extensions handled by this parser, e.g. (".nii", ".nii.gz").

Used as a first, cheap dispatch pass. When several parsers match, the longest matching extension wins, so a parser declaring ".nii.gz" takes precedence over one declaring ".gz".

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.

header class-attribute instance-attribute
header: X5Header = Factory(X5Header, repr=False)

The root attributes and the chains of the file.

nodes class-attribute instance-attribute
nodes: list[X5Node] = Factory(list, repr=False)

Every transform of /TransformGroup, as stored.

chain class-attribute instance-attribute
chain: int | None = None

Which chain of /TransformChain the transformation is, if any. See X5Transform.selection.

position class-attribute instance-attribute
position: int | None = None

Which single transform of /TransformGroup the transformation is, if any. See X5Transform.selection.

file class-attribute instance-attribute
file: File | None = None

The open HDF5 file, when read with keep_open=True.

Methods:

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

Determine if the given file is of the type that this parser can handle.

Parameters:

Name Type Description Default
file FileOrContentLike

The file to sniff.

required
error bool | type[Exception]

If not False, raise an error if the file cannot be sniffed.

False
**kwargs

Parser-specific options.

{}

Returns:

Type Description
float

Confidence that the file is of this type, in [0, 1].

sniff_file classmethod
sniff_file(
    file: H5Like,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> float

Score a path, open HDF5 file, or binary stream.

sniff_filename classmethod
sniff_filename(
    filename: str | PathLike,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> float

Score the HDF5 file found at a path.

sniff_fileobj classmethod
sniff_fileobj(
    file: IO,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> float

Score an open, seekable binary stream.

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

Determine if the given content is of the type that this parser can handle.

Parameters:

Name Type Description Default
content ContentLike

The content to sniff.

required
error bool | type[Exception]

If not False, raise an error if the content cannot be sniffed.

False
**kwargs

Parser-specific options.

{}

Returns:

Type Description
float

Confidence that the content is of this type, in [0, 1].

sniff_bytes classmethod
sniff_bytes(
    content: bytes,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> float

Score the bytes of an HDF5 file.

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

Determine if the given text is of the type that this parser can handle.

Parameters:

Name Type Description Default
text str

The text to sniff.

required
error bool | type[Exception]

If not False, raise an error if the content cannot be sniffed.

False
**kwargs

Parser-specific options.

{}

Returns:

Type Description
float

Confidence that the text is of this type, in [0, 1].

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

Determine if the given lines are of the type that this parser can handle.

Parameters:

Name Type Description Default
lines Iterable[str]

The lines to sniff.

required
error bool | type[Exception]

If not False, raise an error if the content cannot be sniffed.

False
**kwargs

Parser-specific options.

{}

Returns:

Type Description
float

Confidence that the lines is of this type, in [0, 1].

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

Determine if the given line is of the type that this parser can handle.

Parameters:

Name Type Description Default
line str

The line to sniff.

required
error bool | type[Exception]

If not False, raise an error if the content cannot be sniffed.

False
**kwargs

Parser-specific options.

{}

Returns:

Type Description
float

Confidence that the line is of this type, in [0, 1].

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

Build an object from a file (path, file-like object or iterable of lines).

This is the generic front door to the from_* family: it looks at what it was handed and calls the right one.

A str is always a path, whether or not the file exists, so a missing file raises FileNotFoundError whichever way its path was spelled. Text held in memory is read with from_text or from_content.

Parameters:

Name Type Description Default
other FileOrContentLike

Input file, or its content.

required
**kwargs

Parser-specific options.

{}

Returns:

Type Description
obj

The parsed object.

Raises:

Type Description
ParserExistsError

If other is a path to a file that does not exist. It is a FileNotFoundError.

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

Build an object from an unqualified structured source.

from_file classmethod
from_file(
    file: H5Like,
    keep_open: bool = False,
    load: bool = True,
    **kwargs,
) -> Self

Build an object from a file (path, file-like object, or HDF5 file).

Parameters:

Name Type Description Default
file str | PathLike | IO | File

Input file.

required
keep_open bool

If True, keep the HDF5 file open after loading. If False, close the file after loading. If load=False and keep_open=False, the file is reopened every time a lazily read dataset is accessed.

False
load bool

If True, read large datasets into memory. If False, keep them on disk.

True
from_filename classmethod
from_filename(
    filename: str | PathLike,
    keep_open: bool = False,
    load: bool = True,
    **kwargs,
) -> Self

Build an object from the HDF5 file found at a path.

from_fileobj classmethod
from_fileobj(
    file: IO,
    keep_open: bool = False,
    load: bool = True,
    **kwargs,
) -> Self

Build an object from an open, seekable binary stream.

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

Build an object from a file content (bytes, str, or iterable of lines).

Parameters:

Name Type Description Default
content ContentLike

The content to parse.

required
**kwargs

Parser-specific options.

{}

Returns:

Type Description
obj

The parsed object.

from_bytes classmethod
from_bytes(content: BinaryContentLike, **kwargs) -> Self

Build an object from a binary representation of a file.

If the class implements from_fileobj itself, the bytes are wrapped in an io.BytesIO stream and handed to from_fileobj. Otherwise, this raises ParserNotImplementedError: the default from_fileobj delegates to from_bytes, so falling back to it would recurse. A from_fileobj override that only forwards to super().from_fileobj should be decorated with _passthrough_from_fileobj; if it is not, the loop is still detected when from_bytes is re-entered for the same class, and ParserNotImplementedError is raised.

Parameters:

Name Type Description Default
content BinaryContentLike

The content to parse.

required
**kwargs

Parser-specific options.

{}

Returns:

Type Description
obj

The parsed object.

Raises:

Type Description
ParserNotImplementedError

If neither from_bytes nor from_fileobj is implemented.

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

Build an object from a text representation of a file.

Parameters:

Name Type Description Default
text str

The text to parse.

required
**kwargs

Parser-specific options.

{}

Returns:

Type Description
obj

The parsed object.

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

Build an object from an iterable of lines (e.g., the content of a file).

Parameters:

Name Type Description Default
lines Iterable[str]

The lines to sniff.

required
**kwargs

Parser-specific options.

{}

Returns:

Type Description
obj

The parsed object.

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

Build an object from a single line of text.

Parameters:

Name Type Description Default
line str

The line to parse.

required
**kwargs

Parser-specific options.

{}

Returns:

Type Description
obj

The parsed object.

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_file
to_file(file: H5Like, **kwargs) -> None

Write to a path, an open binary stream, or an HDF5 file.

to_filename
to_filename(filename: str | PathLike, **kwargs) -> None

Write to the file found at a path, replacing it.

to_fileobj
to_fileobj(file: IO, **kwargs) -> None

Write to a binary stream open for writing.

to_bytes
to_bytes(**kwargs) -> bytes

The bytes of the HDF5 file that encodes this object.

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.

sniff_h5 classmethod
sniff_h5(
    h5file: File, error: bool | Type[Exception] = False
) -> float

Score an open HDF5 file: an X5 file says so in its root Format attribute.

from_h5 classmethod
from_h5(
    h5file: File,
    keep_open: bool = False,
    load: bool = True,
    chain: int | None = None,
    position: int | None = None,
    **kwargs,
) -> Self

Build an object from an open HDF5 file.

Parameters:

Name Type Description Default
h5file File

Input HDF5 file.

required
keep_open bool

Keep the file open after reading it.

False
load bool

Read the fields into memory. If False, they are read lazily, when used.

True
chain int

Which chain of /TransformChain to read.

None
position int

Which single transform of /TransformGroup to read.

None
to_h5
to_h5(h5file: File, **kwargs) -> None

Write this transformation into an empty HDF5 file.

to_struct
to_struct() -> tuple[X5Header, list[X5Node]]

The header and the nodes that encode this object.

__del__
__del__() -> None

Close the underlying HDF5 file, if one is still open.