Skip to content

brainhops.io.base.mrtrix

The shared MRtrix-reading and MRtrix-writing machinery behind every MRtrix-based image and transformation format.

An MRtrix image is a text header followed by raw voxel data. The header and the data are either in the same file (.mif, or .mif.gz when the whole file is gzip-compressed) or in two files (.mih for the header, and the data file it names, conventionally .dat).

The header is a list of key: value lines, between a first line that reads mrtrix image and a last line that reads END::

mrtrix image
dim: 64,64,32,7
vox: 2,2,2.5,nan
layout: -0,-1,+2,+3
datatype: Float32LE
transform: 0.996, 0.087, 0, -61.2
transform: -0.087, 0.996, 0, -70.5
transform: 0, 0, 1, -40
scaling: 0,1
dw_scheme: 0,0,1,0
dw_scheme: 0,1,0,1000
file: . 552
END

The conventions below were checked against the MRtrix3 sources (core/formats/mrtrix_utils.{h,cpp}, core/formats/mrtrix{,_gz}.cpp, core/file/key_value.cpp, core/stride.h, core/raw.h, core/header.cpp, core/transform.h, core/datatype.cpp).

  • Comments. Everything after a # on a line is a comment, and is dropped. A value cannot therefore contain a #.
  • Keys. The compulsory keys (dim, vox, layout, datatype, transform, scaling) are matched case-insensitively. Any other key is kept as it is spelled. A key that appears on several lines (the three rows of transform, the rows of a dw_scheme, the entries of a command_history) has one value per line; the other keys are collected as their lines joined by newlines, which is how MRtrix holds them.
  • dim, vox. The size and the voxel size of each axis. vox may hold fewer entries than dim (but at least three, or as many as dim when it has fewer), and may hold nan for an axis that has no physical size.
  • layout. One signed integer per axis, such as -0,-1,+2. The absolute value is the rank of the axis in the file: the axis of rank 0 changes fastest, the one of rank 1 next, and so on. The sign says whether the voxels along the axis are stored in increasing (+) or decreasing (-) order of their index. Voxel [0, 0, ...] is therefore not the first value in the file when any axis is negative: it is sum((size[i] - 1) * |stride[i]|) values in, over the negative axes. layout: +0,+1,+2 is a plain Fortran-ordered (x fastest) array.
  • datatype. One of Bit, Int8, UInt8, [U]Int{16,32,64}, Float{32,64} and CFloat{32,64}, the multi-byte ones optionally suffixed with LE or BE. Without a suffix the byte order is the native one of the machine. Bit packs eight voxels per byte, the first one in the most significant bit.
  • transform. Three rows of four numbers, the top of a 4x4 matrix that maps voxel coordinates multiplied by the voxel sizes (not plain voxel indices) to scanner coordinates, in millimetres, in RAS+ (x to the right, y to the front, z up), like a NIfTI sform. The voxel-to-scanner matrix is transform @ diag(vox[:3], 1). MRtrix writes the three direction columns at unit length; one that is not is normalised and its length moved into the voxel size, which leaves the voxel-to-scanner matrix unchanged.
  • Missing transform. When transform is absent (or not finite), MRtrix centres the field of view on the origin: the rotation is the identity, and the translation is -0.5 * (size - 1) * vox along each of the first three axes. It is not the identity.
  • scaling. offset,scale: a stored value v means offset + scale * v.
  • file. file: <name> [<offset>]. A name of . means the data follow the header in the same file, offset bytes from its start. Any other name is the data file, relative to the header's directory, and its offset defaults to zero.

This module holds what an image reader and a transformation reader (an MRtrix warp, for instance) share: the header, the decoding of the voxel data into an array whose axes are the header's axes, and the encoding of such an array back into bytes.

Attributes

MRTRIX_MAGIC module-attribute

MRTRIX_MAGIC = 'mrtrix image'

The line every MRtrix header starts with.

Classes

MrtrixHeader

MrtrixHeader(
    dim: Sequence[int] = (),
    vox: Sequence[float] | None = None,
    layout: Sequence[int] | None = None,
    datatype: str = "Float32LE",
    transform: Any | None = None,
    scaling: Sequence[float] | None = None,
    file: tuple[str, int] | None = None,
    keyval: Mapping[str, str] | None = None,
)

The content of an MRtrix image header.

The keys MRtrix decodes are held decoded: dim, vox, layout, datatype, transform, scaling and file. Every other key is kept in keyval, in the order it was read, with the lines of a repeated key joined by newlines, so the header writes back as it was read.

The header is plain data: building or changing one never touches a file.

Attributes

dim instance-attribute
dim = tuple(int(d) for d in dim)

The size of each axis.

vox instance-attribute
vox = tuple(float(v) for v in vox)

The voxel size of each axis, as stored (possibly fewer entries than axes, possibly nan).

layout instance-attribute
layout = tuple(int(s) for s in layout)

The signed one-based symbolic strides (see parse_layout).

datatype instance-attribute
datatype = str(datatype)

The MRtrix data type, as spelled in the header.

transform instance-attribute
transform = transform

The (3, 4) stored transform, or None when there is none.

scaling instance-attribute
scaling = scaling

(offset, scale), or None when there is none.

file instance-attribute
file = file

(name, offset) of the data, or None when not known.

keyval instance-attribute
keyval = OrderedDict(keyval or {})

Every other key, with its lines joined by newlines.

ndim property
ndim: int

The number of axes.

dtype property
dtype: dtype | None

The numpy data type of the stored values, None for Bit.

is_bit property
is_bit: bool

Whether the values are packed bits.

count property
count: int

The number of voxels.

nbytes property
nbytes: int

The number of bytes the voxel data take in the file.

spacing property
spacing: tuple[float, ...]

The voxel size of each axis, as MRtrix uses it.

Missing entries are nan. A spatial voxel size that is not finite is replaced by the mean of the finite spatial ones (or 1 when there is none), which is what MRtrix does.

Methods:

default_transform
default_transform() -> ndarray

The (3, 4) transform MRtrix assumes when the header has none.

The rotation is the identity, and the field of view is centred on the origin: the translation is -0.5 * (size - 1) * vox along each of the first three axes (an axis the image does not have counts as one voxel).

voxel_to_scanner
voxel_to_scanner() -> ndarray

The (4, 4) matrix from voxel indices to scanner RAS+ mm.

The stored transform maps coordinates scaled by the voxel sizes, so the matrix is transform @ diag(vox[0], vox[1], vox[2], 1). An image with fewer than three axes is given unit-size extra axes, as MRtrix does. A missing or non-finite transform is replaced by default_transform.

from_lines classmethod
from_lines(lines: Iterable[str]) -> MrtrixHeader

Parse header lines, from mrtrix image up to END.

Lines after END are ignored, so the decoded text of a whole file can be handed over.

Raises:

Type Description
ParserContentError

If the first line is not mrtrix image, or a compulsory key (dim, vox, datatype) is missing, or a key is malformed.

from_text classmethod
from_text(text: str) -> MrtrixHeader

Parse a header from its text.

from_fileobj classmethod
from_fileobj(file: BinaryIO) -> tuple[MrtrixHeader, int]

Read a header from a binary stream, up to its END line.

The stream is left just after the END line. Returns the header and the number of bytes it took.

Raises:

Type Description
ParserContentError

If the stream does not hold an MRtrix header.

to_lines
to_lines(
    file: tuple[str, int | None] | None = None,
) -> list[str]

The header's lines, from mrtrix image up to END.

file is the (name, offset) of the data, written in the file: line; an offset of None is left out. It defaults to the header's own file, and no file: line is written when there is none.

to_text
to_text(*args, **kwargs) -> str

The header's text, newline-terminated.

embedded
embedded(align: int = 4) -> bytes

The header of a single-file image, padded up to its data.

The file: . <offset> line points just past the header, at an offset aligned on align bytes (MRtrix aligns on four). The offset is part of the header it measures, so it is found by iteration.

copy
copy() -> MrtrixHeader

A copy that can be changed without changing this one.

MrtrixParser magic

MrtrixParser(
    _header: MrtrixHeader | None = None,
    dataobj: Any | None = None,
)

Bases: DataModelBase, BinaryFileParserWriter

Base class for objects that are encoded by an MRtrix image file.

It reads and writes the container -- the header and the raw voxel values -- for every MRtrix-based format: an image, and later a warp. What the values mean is for the concrete format to say, through _mrtrix_header and _mrtrix_data when writing.

Reading a .mif or a .mih from a local path memory-maps the data, so nothing but the header is read until the data are indexed. A .mif.gz, a file object or bytes are read into memory.

Attributes

header property writable
header: MrtrixHeader | None

The MRtrix header this object was read from, if any.

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.

Methods:

from_file classmethod
from_file(file: FileLike, **kwargs) -> Self

Build the object from an MRtrix file (path or file object).

from_filename classmethod
from_filename(
    filename: FilenameLike, mmap: bool = True, **kwargs
) -> Self

Build the object from the path of a .mif, .mih or .mif.gz.

The data of an uncompressed local file are memory-mapped unless mmap is false. A .mih header is followed to the data file it names, relative to the header's directory.

from_fileobj classmethod
from_fileobj(file: BinaryIO, **kwargs) -> Self

Build the object from an open MRtrix file object, gzipped or not.

The data of a single-file image are read from the stream. A header that names a separate data file is resolved against the stream's name, when it has one.

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

Build the object from the bytes of a single-file MRtrix image (gzipped or not).

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

Score how confident the class is that a stream holds an MRtrix image, gzipped or not.

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

Score how confident the class is that bytes hold an MRtrix image, gzipped or not.

to_bytes
to_bytes(**kwargs) -> bytes

The bytes of a single-file, uncompressed .mif.

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

Write a single-file, uncompressed .mif to a stream.

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

Write to a path, in the variant its extension names.

  • .mif: header and data in one file;
  • .mif.gz: the same, gzip-compressed;
  • .mih: the header, and the data in a .dat file next to it (named after the header), which the header points to.
to_file
to_file(file: FileLike, **kwargs) -> None

Write to a path (variant chosen by extension) or a stream.

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_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_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: 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.