brainhops.io.images.freesurfer.mgh
Readers and writers for images stored in FreeSurfer's MGH / MGZ format.
An .mgh file holds a 3D or 4D volume and its geometry; an .mgz file
is the same bytes, gzipped. Both are read and written through nibabel
(the nibabel extra, also available as brainhops[mgh]), and are
recognised from their content -- a big-endian version number of 1 at the
start of the (decompressed) stream -- whatever their name.
import brainhops.io as io
image = io.images.load("orig.mgz")
image.data.shape # (x, y, z) or (x, y, z, frames), F order
image.transformation # voxel -> scanner RAS (preferred)
image.transformations[1] # voxel -> tkr (surface) RAS
image.vox2ras, image.vox2tkr # the same, as (4, 4) arrays
image.mri_params # {"tr": ..., "flip_angle": ..., ...}
image.save("copy.mgz") # gzipped because of the name
Coordinate systems. The voxel axes are (x, y, z[, t]), x fastest
on disk. The transformations are a scaling to the "physical" scaled
voxel space (voxel size in mm, and TR in ms for the frames when the
footer records one), the voxel-to-tkr RAS affine ("tkr", FreeSurfer's
Torig, the space surfaces live in) and the voxel-to-scanner RAS affine
("scanner", FreeSurfer's Norig), which is preferred. See
brainhops.io.base.freesurfer for how both derive from the header,
and brainhops.io.base.mgh for the file layout.
goodRASFlag. When it is not positive, FreeSurfer ignores the stored
geometry and uses 1 mm voxels, coronal LIA direction cosines and a zero
centre; so does this reader (unlike nibabel, whose default direction
cosines differ). The raw flag is kept, privately, as _good_ras. A
written file always records its geometry, with the flag set.
Metadata. The MRI parameters of the footer (TR, flip angle, TE, TI, FoV) and the raw trailing tags are kept on the object and written back, so an MGH file round-trips. They are not part of the datamodel, so they do not survive a conversion to another format.
Classes
MghImage
MghImage(
image: MGHImage | None = None,
_header: MGHHeader | None = None,
_good_ras: bool | None = None,
_tags: bytes | None = None,
)
Bases: MghParser, WritableFileBasedImage, SingleScaleImage
An image that is encoded by a FreeSurfer MGH or MGZ file.
The voxels are stored x fastest (F order), so data has shape
(x, y, z) or, for a multi-frame volume, (x, y, z, frames), with
the frames read as a time axis.
The transformations are, in order:
- a
Scalingfrom voxels to the scaled voxel space"physical": the voxel size in mm, and, for a multi-frame volume, the repetition time in ms (or 1, with no unit, when the footer records none); - the voxel-to-tkr (surface) RAS affine, whose output is named
"tkr"(header.get_vox2ras_tkr()); - the voxel-to-scanner RAS affine, whose output is named
"scanner"(header.get_vox2ras()). It is the last one, so it is the preferred transformation.
FreeSurfer-specific header content -- the MRI acquisition parameters
of the footer, the raw goodRASFlag and the trailing tags -- is kept
on the object ([mri_params][brainhops.io.base.mgh.MghParser.
mri_params], the private _good_ras and
tags) and written back.
goodRASFlag
When the header's goodRASFlag is not positive, FreeSurfer
ignores the stored geometry and uses 1 mm voxels, coronal (LIA)
direction cosines and a zero centre. So does this reader. A file
written back always records its geometry, with goodRASFlag = 1.
Why the bases are in this order
As for NiftiImage:
SingleScaleImage.data has no default, so it comes last, and
MghParser leads so that its lazy data/system properties win.
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 = 10
Kind precedence, used only to break ties that confidence could not.
A NIfTI file is legitimately both an image and a set of affines, so when nothing else separates them the image wins. Scoring sniffers (e.g. NIfTI intent codes) normally decide well before this matters.
shape
property
shape: tuple[int, ...] | None
The shape of the volume, (x, y, z) or (x, y, z, frames).
grid
property
grid: CartesianField
The Cartesian field that defines the sampling grid of the image.
This is the grid of the image's geometry.
data
property
writable
The voxels, (x, y, z) or (x, y, z, frames), read lazily
from image and cached, unless set explicitly.
transformation
property
writable
transformation: Transformation
The preferred transformation.
It is always the last transformation in the list.
Assigning a transformation appends it as the new preferred transformation. Assigning an integer or a string selects an existing transformation by position or by output-space name and moves it to the end. Assigning a transformation that is already in the list moves it to the end instead of adding a copy.
A transformation is recognized as already present by identity (transformations compare by identity): a distinct transformation with the same parameters is appended as a new preferred transformation.
geometry
property
geometry: Geometry
A transformation that is the composition of the preferred voxel-to-world transformation and the cartesian field corresponding to the image's shape.
This transformation can be used to reslice any image onto the same grid as this image.
header
property
writable
The nibabel MGH header: the one set explicitly, or else the
header of image, or None.
tags
property
writable
tags: bytes
The raw bytes of the trailing tags (empty when there are none).
They are kept verbatim and written back after the footer, so that they survive a round trip. They are read lazily from the source file when the object was loaded from a path.
mri_params
property
The MRI acquisition parameters of the footer.
tr, te and ti are in milliseconds, flip_angle in radians
and fov in millimetres. A value of zero means "not recorded".
voxel_size
property
The voxel size in millimetres, None without a header.
vox2ras
property
The (4, 4) voxel-to-scanner-RAS matrix (mri_info --vox2ras).
Equal to nibabel's header.get_vox2ras(), except when
goodRASFlag was not positive, where FreeSurfer's defaults are
used.
vox2tkr
property
The (4, 4) voxel-to-tkr (surface) RAS matrix
(mri_info --vox2ras-tkr, header.get_vox2ras_tkr()).
system
property
writable
system: CoordinateSystem | None
The voxel coordinate system (x, y, z[, t]), in F order,
derived from header unless set explicitly.
transformations
property
writable
transformations: list[Transformation]
The voxel-to-world transformations recorded by the header, decoded on first access unless set explicitly.
An image built from data alone has no header, so it records no transformation and the list is empty.
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
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 |
sniff_fileobj
classmethod
Score how confident the class is that an open file object holds an MGH header, gzipped or not.
The content is recognised from its leading fields -- a version of 1, four positive dimensions and a known voxel type -- not from the file name.
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
Score how confident the class is that bytes hold an MGH header, gzipped or not.
sniff_text
classmethod
On a dispatcher, identify which registered format would read the text. On a concrete format, score how confident it is that the text is its own.
sniff_lines
classmethod
sniff_lines(
lines: Iterable[str],
error: bool | Type[Exception] = False,
**kwargs,
) -> type | None
On a dispatcher, identify which registered format would read the lines. On a concrete format, score how confident it is that the lines are its own.
sniff_line
classmethod
On a dispatcher, identify which registered format would read the line. On a concrete format, score how confident it is that the line is its own.
load
classmethod
load(other: FileOrContentLike, **kwargs) -> Self
On a dispatcher, pick the best-matching registered format and
build an instance of it from other. On a concrete format,
build an instance of this class from other, in any supported
form.
from_spec
classmethod
from_spec(spec: SourceSpec, **kwargs) -> Self
Load a structured source specification through this dispatcher.
from_file
classmethod
Build the object from an MGH or MGZ file.
A local file whose name matches its content (.mgz or .mgh.gz
when gzipped, .mgh when not) is handed to nibabel by
path, through from_filename, so that the voxels are
memory-mapped and read lazily.
nibabel cannot open any other file by name: a remote one (which
has no local path), or one whose name it would take for another
codec (it picks the codec from the name). Such a file is read
into memory and handed to nibabel as a stream (see
from_fileobj): the stream nibabel reads the voxels from
lazily must outlive the file, which is closed on return.
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
Build the object from an open MGH or MGZ file object.
The image is read with nibabel's stream API, gzipped or not
(the compression is sniffed from the magic bytes, as a stream has
no name). As for NIfTI, the voxels are read lazily from the
stream, so the caller keeps it open for as long as they may be
read; the header, goodRASFlag and trailing tags are read right
away.
A stream that cannot seek is read into memory first: the leading
bytes must be read twice (for goodRASFlag, then by nibabel),
and its compression cannot be sniffed otherwise.
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
Build the object from MGH bytes (or gzipped MGZ bytes).
from_text
classmethod
On a dispatcher, pick the best-matching registered format and build an instance of it from the text. On a concrete format, build an instance of this class from the text.
from_lines
classmethod
On a dispatcher, pick the best-matching registered format and build an instance of it from the lines. On a concrete format, build an instance of this class from the lines.
from_line
classmethod
On a dispatcher, pick the best-matching registered format and build an instance of it from the line. On a concrete format, build an instance of this class from the line.
save
save(file: FileLike, **kwargs) -> None
Write the object to a file (path or file-like object).
This is the generic front door to the to_* family. It is named
save rather than to because to already means something else
on the data models these parsers are mixed into: Transformation.to
converts an object to another type. A writer's to was shadowed
by it on every writable transformation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file
|
FileLike
|
The file to write to. |
required |
**kwargs
|
Parser-specific options. |
{}
|
to_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 object to a file, gzipped (MGZ) when its name ends
with .mgz or .gz, unless compress says otherwise.
to_fileobj
to_fileobj(file: IO, **kwargs) -> None
Write the MGH encoding of the object (MGZ with
compress=True) to an open file object.
to_bytes
Return the MGH encoding of the object: header, voxels, footer and
trailing tags. With compress=True, return the gzipped (MGZ)
encoding instead.
to_text
to_text(**kwargs) -> str
Return a text version of the file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Parser-specific options. |
{}
|
Returns:
| Type | Description |
|---|---|
str
|
A text version of the file. |
to_lines
Return a text version of the file as an iterable of lines.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Parser-specific options. |
{}
|
Returns:
| Type | Description |
|---|---|
Iterator[str]
|
An iterable of lines representing the object. |
to_line
to_line(**kwargs) -> str
Return a line representing the object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Parser-specific options. |
{}
|
Returns:
| Type | Description |
|---|---|
str
|
A line representing the object. |
from_dict
classmethod
Create an instance of the class from a dictionary-like object.
Only keys in the dictionary that match keyword-like fields of
this class, or the keywords its constructor takes without
storing them (its InitVars, such as the matrix= of an
Affine), will be used. Other keys are ignored, but see
from_other,
which refuses them.
Additional positional and/or keyword arguments can be provided, and will take precedence over the values in the dictionary.
A key naming a field that this class fixes (a field that cannot
be passed to its constructor) is checked instead of used: a
dictionary that sets it to anything other than None or the
value of this class is refused with a ValueError.
from_instance
classmethod
Create an instance from an instance of a similar class.
The data model copies the fields both classes share, by name.
A field that a file format declares for its own use -- such as
the nibabel image and header of the NIfTI and MGH formats
-- is only copied from an object of that same format: from any
other object, a field of the same name holds something else
(a NIfTI image is no MGH image), so this class's default is
kept instead. Saving a NIfTI image to MGH, or the converse,
therefore converts the data model only, and the format-specific
state is rebuilt by the writer.
from_other
classmethod
Create an instance from a file, or from anything the data model reads.
A path (str or os.PathLike), an open file, bytes or a
structured source (SourceSpec)
is read with load: on a dispatcher such as FileBasedImage,
the best-matching registered format reads it, and on a concrete
format, that format does. Any other value is handed to the data
model's own from_other, which reads a mapping field by field,
copies an instance of a similar class, and passes anything else
to the constructor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Any
|
A file, its content, a mapping, or an instance of a similar class. |
required |
*args
|
Constructor arguments. A file is read with keyword options only. |
()
|
|
**kwargs
|
Format-specific options when reading a file, and field values otherwise. |
{}
|
Returns:
| Type | Description |
|---|---|
obj
|
The object that was built. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If positional arguments come with a file to read. |
reslice
reslice(
geometry: Self
| Geometry
| Transformation
| None = None,
degree: int = 1,
bound: str = "reflect",
coeff: bool = False,
copy: bool = False,
) -> Self
Apply transformations to current data and return new image.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
Image | Geometry | Transformation
|
Geometry of the output image. The geometry is a voxel-to-world transformation that defines the grid onto which the image will be resliced. If it is a If it is |
None
|
degree
|
0..5
|
The spline degree. 0=nearest, 1=linear, 2=quadratic, etc. |
0..5
|
bound
|
(nearest, reflect, mirror, grid - wrap, wrap)
|
The boundary condition. If a string, one of: - 'nearest': nearest edge value (a a a a | a b c d | d d d d) - 'reflect': reflect at edge (d c b a | a b c d | d c b a) - 'mirror': mirror at edge (d c b | a b c d | c b a) - 'grid-wrap': wrap around (a b c d | a b c d | a b c d) - 'wrap': wrap around with shift (d b c d | a b c d | b c a b) If a float, the constant value to use beyond the edge. |
'nearest'
|
coeff
|
bool
|
If True, the input image is assumed to already contain spline coefficients. If False, the input image is prefiltered before interpolation. |
False
|
copy
|
bool
|
Whether the output data must be a fresh array. As with
|
False
|
Returns:
| Type | Description |
|---|---|
Image
|
The resliced image. |
__call__
__call__(transform: Transformation) -> SingleScaleImage
Apply a transformation to the image, but do not compute.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transform
|
Transformation
|
The transformation to apply. The output space of this transformation should match
(or be compatible with) the output space of the preferred
transformation. That is, the new "voxel-to-world" transformation
is defined as |
required |
Returns:
| Type | Description |
|---|---|
Image
|
The updated (not-yet-resliced) image. |
__getitem__
__getitem__(
index: tuple[int | slice | None, ...],
) -> SingleScaleImage
Index into the image data while preserving the geometry of the image.
from_nibabel
classmethod
from_nibabel(mgh: _MghObject, **kwargs) -> Self
Build the object from an already-loaded nibabel MGH header
or image.
to_nibabel
to_nibabel(like: Any = None, **overrides) -> MGHImage
Build the nibabel image that encodes this image.
The image data becomes the voxels. The voxel-to-scanner RAS
matrix is taken from the transformation whose output is named
"scanner", or else from the preferred transformation unless it
maps to tkr RAS (which MGH cannot store, as it follows from the
shape and the voxel size). It is converted to millimetres and
decomposed into voxel sizes, direction cosines and a centre. An
image with no transformation is written with 1 mm voxels and RAS
axes. A transformation with no affine representation raises
UnrepresentableTransformationError.
The axes are placed by the types and names the voxel space of
that transformation declares, as NIfTI places them (see
[plan_axes][brainhops.io.base._geometry.plan_axes]): the
spatial axes first (x, y, z in that order when they are so
named), then the one axis MGH stores besides them, its frames.
The data is transposed to match (lazily, for a lazy array), and a
slice with frames is given a z axis of size one. A second axis
besides the spatial ones has no place in MGH, and raises
UnrepresentableTransformationError. A voxel space that declares
nothing is written in the order it has.
The frames of a time axis are spaced by the repetition time,
which MGH stores in milliseconds (tr): it is taken from the
time axis of the transformation, converted from its unit (a time
axis with no unit is taken to be in milliseconds already). A time
axis that still counts frames states no repetition time. MGH
stores no origin for the frames, so a time axis with one raises
UnrepresentableTransformationError, as do frames of another
kind that are scaled or shifted.
The MRI parameters of the footer (tr, flip_angle, te, ti,
fov) are copied from this image's header, then from like when
it is given (a path to an MGH/MGZ file, a nibabel MGH image or
header, or another object read from MGH); the repetition time the
transformation states replaces theirs; then the keyword arguments
apply. dtype sets the stored voxel type. Without it, the
array's type is kept when MGH can store it (uint8, int16, int32,
float32), and otherwise converted to the nearest one MGH can:
booleans to uint8, other floats to float32, other integers to
int16 or int32. Integers that int32 cannot hold raise
WriterError.