Skip to content

brainhops.io.transformations.freesurfer.m3z

FreeSurfer non-linear morphs (.m3z), such as talairach.m3z.

recon-all registers every subject to its Gaussian classifier atlas with mri_ca_register, and saves the result as mri/transforms/talairach.m3z: a "GCA morph" (GCA_MORPH, or GCAM), a regular grid of nodes placed on the atlas, each of which records where it lands in the subject (source) image.

from brainhops import io

morph = io.load("talairach.m3z")              # or hint="m3z"
morph.save("copy.m3z")                         # the same file

Specification

The layout is that of __m3zRead and __m3zWrite in FreeSurfer's utils/gcamorph.cpp; MATLAB's mris_read_m3z.m and surfa (surfa/io/fsio.py, surfa/io/utils.py) agree with it. A .m3z file is gzipped, a .m3d file is not (FreeSurfer gzips a morph whose name contains .m3z). Every value is big-endian, whatever the machine that wrote it: FreeSurfer writes and reads through znzwriteInt, znzwriteFloat, znzreadInt, ... (utils/fio.cpp), which byte-swap on little-endian hosts.

1. Header

Type Field Meaning
float32 version always 1.0
int32 width number of nodes along x
int32 height number of nodes along y
int32 depth number of nodes along z
int32 spacing distance between nodes, in atlas voxels
float32 exp_k exponent of the registration's area term

2. Nodes

width * height * depth records of 36 bytes, x slowest and z fastest (for x: for y: for z:), so that a C-ordered array of shape (width, height, depth) indexes them by [x, y, z]:

Type Fields Meaning
float32 x3 origx, origy, origz original (linear) position
float32 x3 x, y, z position
int32 x3 xn, yn, zn the GCA node it is attached to

A node whose six coordinates are all zero is invalid (GCAM_POSITION_INVALID): FreeSurfer does not sample through it.

3. Tags

Tags follow until the end of the file (or a zero tag). Each is an int32, and, unlike the tags of an MGH file, those of a morph have no length:

Tag Content
TAG_GCAMORPH_GEOM (10) the source geometry, then the atlas's
TAG_GCAMORPH_TYPE (11) int32: GCAM_VOX (2) or GCAM_RAS (1)
TAG_GCAMORPH_LABELS (12) int32 per node, in node order: its label
TAG_MGH_XFORM (31) a linear transform, as text (see below)

A geometry (VOL_GEOM::write) is four int32 -- valid, width, height, depth -- then fifteen float32 -- xsize, ysize, zsize, the direction cosines x_r, x_a, x_s, y_r, y_a, y_s, z_r, z_a, z_s and the centre c_r, c_a, c_s -- then a 512-byte, NUL-padded file name: the FreeSurfer volume geometry of brainhops.io.base.freesurfer. A file without this tag has FreeSurfer's default geometry for both volumes (initVolGeom: 256^3 voxels of 1 mm, LIA, centred on the origin).

TAG_MGH_XFORM is followed by a tag of its own (0; TAG_AUTO_ALIGN, 33, before FreeSurfer 7.2), an int64 length (1600) and a 1600-byte text buffer: a keyword (Matrix; AutoAlign) and the 16 values of a 4x4 matrix, row by row. FreeSurfer uses its determinant to normalise the node areas; it plays no part in applying the morph.

FreeSurfer writes the tags in this order, all of them except the matrix, which it writes only if it has one. A tag it does not know ends the reading; what follows is kept verbatim.

Geometry

mri_vol2vol --m3z (through GCAMmorphToAtlas and GCAMsampleMorph) resamples the source image on the atlas grid: for each atlas voxel v, it interpolates the positions of the nodes trilinearly at node coordinate v / spacing, and samples the source image at that position, in source voxels. A morph therefore pulls the source onto the atlas, and, in brainhops' convention, maps atlas coordinates to source coordinates:

atlas RAS --(node vox2ras)^-1--> node voxels --positions--> source voxels
          --(source vox2ras)--> source RAS
  • The positions are source voxel coordinates (0-based, integers at voxel centres) when the type is GCAM_VOX, FreeSurfer's default and what mri_ca_register writes. When the type is GCAM_RAS, they are source scanner RAS (GCAMvoxToRas, through the source geometry), and the last step is dropped.
  • The node grid is the atlas grid subsampled by spacing: node n is atlas voxel n * spacing, so its voxel-to-RAS matrix is the atlas's times diag(spacing, spacing, spacing, 1).
  • Both voxel-to-RAS matrices are scanner RAS (mri_info --vox2ras), not tkr RAS, built from the stored geometries as FreeSurfer does (VGgetVoxelToRasXform).
  • FreeSurfer samples nothing outside the node grid. The field's boundary condition is nearest, which matches inside the last half-cell where FreeSurfer clamps, and extends the field beyond it.

M3zMorph.struct, an M3zStruct, keeps the content of the file, whose members can be queried:

morph.struct.spacing                  # distance between nodes
morph.struct.atlas_geometry.vox2ras   # atlas voxel-to-RAS
morph.struct.image_geometry.vox2ras   # source voxel-to-RAS
morph.struct.xform.matrix             # the linear transform, if any

Writing

A morph read from a file and left untouched is written back byte for byte (up to gzip). A morph whose chain was assigned -- a field changed, or one built from scratch -- is written from the chain (see M3zMorph.to_struct): the geometries are rebuilt from its affines, and what it does not say (original positions, labels, ...) is kept from the struct when the node grid is unchanged.

Out of scope

FreeSurfer 8 can also save a morph as an MGH or NIfTI "warpfield" (mri_warp_convert), which these classes do not read. Inverse morphs (talairach.m3z.inv.{x,y,z}.mgz) are plain MGH images.

Attributes

GCAM_RAS module-attribute

GCAM_RAS = 1

Node positions are scanner RAS coordinates of the source image.

GCAM_VOX module-attribute

GCAM_VOX = 2

Node positions are voxel coordinates of the source image (default).

Classes

M3zGeometry magic

M3zGeometry(
    valid: int = 0,
    shape: _3Ints = (256, 256, 256),
    voxel_size: _3Floats = (1.0, 1.0, 1.0),
    xras: _3Floats = FS_DEFAULT_XRAS,
    yras: _3Floats = FS_DEFAULT_YRAS,
    zras: _3Floats = FS_DEFAULT_ZRAS,
    cras: _3Floats = (0.0, 0.0, 0.0),
    fname: bytes = field(
        default=ljust(_FNAME_LEN, b"\x00"), repr=False
    ),
)

Bases: Magic

The geometry of a volume, as a morph stores it (VOL_GEOM).

The defaults are FreeSurfer's (initVolGeom): an invalid 256^3 volume of 1 mm voxels in coronal LIA orientation, centred on the origin -- the geometry a morph has when its file records none.

Attributes

valid class-attribute instance-attribute
valid: int = 0

Whether the geometry is valid (1) or not (0).

shape class-attribute instance-attribute
shape: _3Ints = (256, 256, 256)

The shape of the volume, (width, height, depth).

voxel_size class-attribute instance-attribute
voxel_size: _3Floats = (1.0, 1.0, 1.0)

The voxel size, in millimetres.

xras class-attribute instance-attribute
xras: _3Floats = FS_DEFAULT_XRAS

Direction cosine of the first voxel axis, in RAS.

yras class-attribute instance-attribute
yras: _3Floats = FS_DEFAULT_YRAS

Direction cosine of the second voxel axis, in RAS.

zras class-attribute instance-attribute
zras: _3Floats = FS_DEFAULT_ZRAS

Direction cosine of the third voxel axis, in RAS.

cras class-attribute instance-attribute
cras: _3Floats = (0.0, 0.0, 0.0)

The RAS coordinates of the centre of the volume (voxel shape / 2).

fname class-attribute instance-attribute
fname: bytes = field(
    default=b"unknown".ljust(_FNAME_LEN, b"\x00"),
    repr=False,
)

The 512-byte, NUL-padded file name buffer, as stored.

filename property
filename: str

The file name of the volume.

vox2ras property
vox2ras: ndarray

The (4, 4) voxel-to-scanner-RAS matrix of the volume.

Methods:

from_vox2ras classmethod
from_vox2ras(
    vox2ras: ndarray,
    shape: Sequence[int],
    valid: int = 1,
    filename: str | bytes = "",
) -> Self

The geometry of a volume of shape shape placed by vox2ras (see fs_geometry_from_vox2ras).

M3zStruct magic

M3zStruct(
    version: float = GCAM_VERSION,
    spacing: int = 1,
    exp_k: float = 20.0,
    original: ndarray | None = field(
        default=None, repr=False
    ),
    positions: ndarray | None = field(
        default=None, repr=False
    ),
    index: ndarray | None = field(default=None, repr=False),
    image: M3zGeometry | None = None,
    atlas: M3zGeometry | None = None,
    type: int | None = None,
    labels: ndarray | None = field(
        default=None, repr=False
    ),
    xform: M3zXform | None = None,
    tags: tuple[int, ...] = (),
    trailing: bytes = field(default=b"", repr=False),
)

Bases: Magic

The content of a morph file, as M3zMorph.struct holds it.

The node arrays are indexed by node [x, y, z], the order in which FreeSurfer writes them (x slowest), which is the F order of the node grid: positions[i, j, k] is the node at column i, row j and slice k. Equality is identity, since the arrays are large.

Attributes:

Name Type Description
version float

The file version, always 1.0.

spacing int

The distance between nodes, in atlas voxels: node n is atlas voxel n * spacing.

exp_k float

The exponent of the morph's area-preserving penalty.

original (W, H, D, 3) float32 array

The node positions before the non-linear registration (origx, origy, origz), in the units of positions.

positions (W, H, D, 3) float32 array

The node positions (x, y, z), in source voxels (GCAM_VOX) or source scanner RAS (GCAM_RAS).

index (W, H, D, 3) int32 array

The GCA node each node maps to (xn, yn, zn).

image M3zGeometry or None

The geometry of the source image (TAG_GCAMORPH_GEOM), or None if the file has no such tag.

atlas M3zGeometry or None

The geometry of the atlas, the target (TAG_GCAMORPH_GEOM), or None if the file has no such tag.

type int or None

GCAM_VOX or GCAM_RAS (TAG_GCAMORPH_TYPE), or None if the file has no such tag, in which case positions are voxels.

labels (W, H, D) int32 array or None

The label of each node (TAG_GCAMORPH_LABELS).

xform M3zXform or None

The linear transform the morph records (TAG_MGH_XFORM); its (4, 4) matrix is xform.matrix.

tags tuple of int

The tags, in the order the file stores them.

trailing bytes

Bytes that follow a tag FreeSurfer does not know, verbatim.

shape (int, int, int)

Read-only: the shape of the node grid, (W, H, D).

coordinates int

Read-only: GCAM_VOX or GCAM_RAS, the units of the positions.

image_geometry M3zGeometry

Read-only: image, or FreeSurfer's default geometry if None. Its voxel-to-scanner-RAS matrix is image_geometry.vox2ras.

atlas_geometry M3zGeometry

Read-only: atlas, or FreeSurfer's default geometry if None. Its voxel-to-scanner-RAS matrix is atlas_geometry.vox2ras.

invalid (W, H, D) bool array

Read-only: the nodes FreeSurfer marks invalid (GCAM_POSITION_INVALID), whose positions and original positions are all zero.

Attributes

shape property
shape: _3Ints

The shape of the node grid, (width, height, depth).

coordinates property
coordinates: int

GCAM_VOX or GCAM_RAS: the units of the positions.

image_geometry property
image_geometry: M3zGeometry

The source geometry, or FreeSurfer's default if none.

atlas_geometry property
atlas_geometry: M3zGeometry

The atlas geometry, or FreeSurfer's default if none.

invalid property
invalid: ndarray

(W, H, D) bool: the nodes FreeSurfer marks invalid (GCAM_POSITION_INVALID), whose positions and original positions are all zero. FreeSurfer does not sample there.

M3zXform magic

M3zXform(
    tag: int = 0,
    length: int = MATRIX_STRLEN,
    buffer: bytes = field(
        default=b"\x00" * MATRIX_STRLEN, repr=False
    ),
)

Bases: Magic

The matrix of a TAG_MGH_XFORM tag: the linear transform the morph was initialised with.

FreeSurfer writes it as text, in a fixed-size buffer, behind a tag of its own and a length. Recent versions write the tag 0 and the keyword Matrix, FreeSurfer 6 and 7.1 the tag TAG_AUTO_ALIGN (33) and the keyword AutoAlign. The buffer is kept verbatim.

Attributes

tag class-attribute instance-attribute
tag: int = 0

The inner tag.

length class-attribute instance-attribute
length: int = MATRIX_STRLEN

The length the inner tag records.

buffer class-attribute instance-attribute
buffer: bytes = field(
    default=b"\x00" * MATRIX_STRLEN, repr=False
)

The text buffer, as stored.

matrix property
matrix: ndarray

The (4, 4) matrix the buffer encodes.

Methods:

from_matrix classmethod
from_matrix(matrix: ndarray) -> Self

Encode a matrix as FreeSurfer's znzWriteMatrix does.

M3zFormat

Bases: FreesurferTransformationFormat

A non-linear transformation stored in a FreeSurfer morph file.

M3zMorph magic

M3zMorph(
    struct: M3zStruct | None = field(
        default=None, repr=False
    ),
)

Bases: M3zFormat, M3zParser, ImmutableSequence, WritableFileBasedTransformation

A non-linear transformation stored in a FreeSurfer morph (.m3z).

It maps atlas (target) scanner RAS to source scanner RAS, as mri_vol2vol --m3z applies it: the source image is resampled onto the atlas grid, pulled through the field. It is the ImmutableSequence of

  1. [RASToVoxel][brainhops.io.transformations.base.affines.RASToVoxel]: atlas RAS to the voxels of the node grid;
  2. a CoordinatesField: the position of each node in source voxels;
  3. [VoxelToRAS][brainhops.io.transformations.base.affines.VoxelToRAS]: source voxels to source RAS.

A morph whose positions are in RAS (GCAM_RAS) is the sequence of the first step and a [RASCoordinatesField][brainhops.io.transformations.base.fields.RASCoordinatesField].

The raw content -- the spacing, the geometries (and voxel-to-RAS matrices) of both volumes, original positions, GCA node indices, labels, the linear transform -- stays in struct, an M3zStruct whose members can be queried, e.g. morph.struct.spacing, morph.struct.atlas_geometry.vox2ras or morph.struct.xform.matrix.

What is written

A morph read from a file, whose chain has not been assigned, is written back as it was read. One whose transformations were assigned -- including one built from scratch -- is written from them (see to_struct).

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.

struct class-attribute instance-attribute
struct: M3zStruct | None = field(default=None, repr=False)

The raw content of the file, every node and every tag (see M3zStruct for its members).

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

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

It is derived from struct unless it has been assigned.

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: FileLike,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> type | None

On a dispatcher, identify which registered format would read the file (path or file-like object). On a concrete format, score how confident it is that the file is its own.

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

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

Parameters:

Name Type Description Default
filename FilenameLike

The filename to sniff.

required
error bool | type[Exception]

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

False
**kwargs

Parser-specific options.

{}

Returns:

Type Description
float

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

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

Score an open binary file from its first bytes only.

The base class reads the whole file, and a morph is large (tens of megabytes) while its header is 24 bytes.

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 bytes, gzipped or not: a morph starts with the version 1.0, then a positive shape and spacing (see read_header).

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

On a dispatcher, pick the best-matching registered format and build an instance of it from the file (path or file-like object). On a concrete format, build an instance of this class from the file.

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_fileobj classmethod
from_fileobj(file: IO, **kwargs) -> Self

On a dispatcher, pick the best-matching registered format and build an instance of it from the open file object. On a concrete format, build an instance of this class from the file 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_bytes classmethod
from_bytes(content: bytes, **kwargs) -> Self

Build the object from the bytes of a .m3z (gzipped) or .m3d (plain) file.

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: FileLike, **kwargs) -> None

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

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 morph to a file, gzipped unless its name ends in .m3d (FreeSurfer gzips a morph whose name contains .m3z).

The content is built before the file is opened, so a transformation that the format cannot hold is refused without creating or truncating the file.

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

Write the object to a file-like object open for writing.

Parameters:

Name Type Description Default
file IO

A file object open for writing.

required
**kwargs

Parser-specific options.

{}
to_bytes
to_bytes(compress: bool = True, **kwargs) -> bytes

The content of the morph file, gzipped (.m3z) unless compress=False (.m3d).

Other keyword arguments go to to_struct.

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.

from_struct classmethod
from_struct(struct: M3zStruct, **kwargs) -> Self

Build the object from the raw content of a morph.

to_struct
to_struct(
    spacing: int | None = None,
    image_shape: Sequence[int] | None = None,
    atlas_shape: Sequence[int] | None = None,
    **kwargs,
) -> M3zStruct

The raw content that encodes this morph.

A morph whose chain has not been assigned is its struct. One whose chain was assigned must hold, as the reader builds it, either three transformations -- atlas RAS to node voxels (an affine), a field of source voxel coordinates, source voxels to RAS (an affine) -- or two -- the affine and a field of source RAS coordinates. The geometries of both volumes are rebuilt from the affines; what the chain does not say is taken from struct when it has one, and from the arguments otherwise.

Parameters:

Name Type Description Default
spacing int

The distance between nodes in atlas voxels. By default, that of struct, or 1.

None
image_shape (int, int, int)

The shape of the source image, which its RAS centre depends on. By default, that of struct. Required when there is no struct and the field is in voxels.

None
atlas_shape (int, int, int)

The shape of the atlas. By default, that of struct, or the shape of the node grid times the spacing.

None

Raises:

Type Description
UnrepresentableTransformationError

If the chain does not have one of these two shapes.

WriterError

If the shape of the source image is needed and unknown.

M3zParser magic

M3zParser(
    struct: M3zStruct | None = field(
        default=None, repr=False
    ),
)

Bases: Magic, BinaryFileParserWriter

Reads and writes the raw content of a FreeSurfer morph file.

It compares by identity (eq=False), as its struct holds arrays. The morph built on it is a transformation, which compares by identity too, as every transformation does.

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.

struct class-attribute instance-attribute
struct: M3zStruct | None = field(default=None, repr=False)

The raw content of the file, every node and every tag (see M3zStruct for its members).

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: FileLike,
    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 FileLike

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_filename classmethod
sniff_filename(
    filename: FilenameLike,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> float

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

Parameters:

Name Type Description Default
filename FilenameLike

The filename to sniff.

required
error bool | type[Exception]

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

False
**kwargs

Parser-specific options.

{}

Returns:

Type Description
float

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

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

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

Parameters:

Name Type Description Default
file FileLike

The file to parse.

required
**kwargs

Parser-specific options.

{}

Returns:

Type Description
obj

The parsed object.

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_fileobj classmethod
from_fileobj(file: IO, **kwargs) -> Self

Build an object from a file-like object.

The default implementation reads the whole stream and hands its content to from_content (hence to from_bytes for binary streams). Parsers that only need part of the stream (e.g., a header) should override this method; from_bytes then falls back to it.

Parameters:

Name Type Description Default
file IO

A file object open for reading.

required
**kwargs

Parser-specific options.

{}

Returns:

Type Description
obj

The parsed object.

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_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: FileLike, **kwargs) -> None

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

Parameters:

Name Type Description Default
file FileLike

The file to write to.

required
**kwargs

Parser-specific options.

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

Write the object to a file-like object open for writing.

Parameters:

Name Type Description Default
file IO

A file object open for writing.

required
**kwargs

Parser-specific options.

{}
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_fileobj classmethod
sniff_fileobj(
    file: IO,
    error: bool | Type[Exception] = False,
    **kwargs,
) -> float

Score an open binary file from its first bytes only.

The base class reads the whole file, and a morph is large (tens of megabytes) while its header is 24 bytes.

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

Score bytes, gzipped or not: a morph starts with the version 1.0, then a positive shape and spacing (see read_header).

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

Build the object from the bytes of a .m3z (gzipped) or .m3d (plain) file.

from_struct classmethod
from_struct(struct: M3zStruct, **kwargs) -> Self

Build the object from the raw content of a morph.

to_struct
to_struct(**kwargs) -> M3zStruct

The raw content that encodes this object.

to_bytes
to_bytes(compress: bool = True, **kwargs) -> bytes

The content of the morph file, gzipped (.m3z) unless compress=False (.m3d).

Other keyword arguments go to to_struct.

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

Write the morph to a file, gzipped unless its name ends in .m3d (FreeSurfer gzips a morph whose name contains .m3z).

The content is built before the file is opened, so a transformation that the format cannot hold is refused without creating or truncating the file.