brainhops.io.base.afni
The shared AFNI-reading and AFNI-writing machinery behind every AFNI image and transformation format.
An AFNI dataset is a pair of files that share a prefix and a view:
prefix+view.HEAD, a text header made of attributes;prefix+view.BRIK, the raw voxel values, optionally compressed (.BRIK.gz,.BRIK.bz2).
The view is one of orig (the original, scanner-based coordinates),
acpc (AC-PC aligned) and tlrc (Talairach, or any other template).
The conventions below were checked against the AFNI sources
(README.attributes, 3ddata.h, mrilib.h, thd_atr.c,
thd_writeatr.c, thd_dsetdblk.c, thd_initdblk.c, thd_editdaxes.c,
thd_matdaxes.c, thd_dsetatr.c, thd_niftiread.c,
thd_niftiwrite.c, thd_compress.h).
Attributes. Each attribute is three lines -- type = <kind>-attribute,
name = <NAME>, count = <n> -- followed by its n values. The kind is
integer, float or string. The numbers are separated by any white
space. A string is n characters that follow a single quote ', in
which ~ stands for a NUL character (AFNI writes a ~ of the string
itself as *). A string attribute ends with a NUL, and may hold several
NUL-separated sub-strings (the sub-brick labels of BRICK_LABS). An
attribute with a count of zero is skipped, as AFNI does. A header whose
first kilobyte holds <AFNI_ is AFNI's alternative NIML (XML) header,
which is not read here.
Shape. DATASET_DIMENSIONS holds the number of voxels along the
three axes, nx, ny, nz, and DATASET_RANK[1] the number of
sub-bricks (nvals): the volumes of a time series, the statistics of
a "bucket", or the components of a warp. The BRIK holds the sub-bricks
one after the other, each with x fastest: the data are a
Fortran-ordered (nx, ny, nz, nvals) array.
Types and scaling. BRICK_TYPES holds one code per sub-brick
(mrilib.h): 0 = uint8, 1 = int16, 2 = int32, 3 = float32,
4 = float64, 5 = complex64 (6 = RGB and 7 = RGBA are not read). It
is int16 when absent, and a list shorter than nvals repeats its last
code. BYTEORDER_STRING is LSB_FIRST or MSB_FIRST, and the native
order when absent. BRICK_FLOAT_FACS holds one factor per sub-brick: a
stored value v means v * f, and a factor of zero means no scaling.
Orientation. ORIENT_SPECIFIC holds one code per voxel axis, saying
which way it runs: 0 = right-to-left, 1 = left-to-right,
2 = posterior-to-anterior, 3 = anterior-to-posterior,
4 = inferior-to-superior, 5 = superior-to-inferior.
World coordinates. AFNI's world is "DICOM order": x increases to
the left, y to the back and z up -- LPS millimetres, which AFNI
also calls RAI (R, A and I are negative). ORIGIN[i] is the DICOM
coordinate of the centre of voxel 0 along the DICOM axis voxel axis i
runs along, and DELTA[i] is the signed voxel size along it, so that
the centre of voxel n is at ORIGIN[i] + n * DELTA[i]: DELTA is
negative for an axis that runs towards R, A or I. The voxel to
DICOM matrix is therefore a signed permutation (THD_daxes_to_mat44):
row ORIENT_SPECIFIC[i] // 2, column i, holds DELTA[i], and the
same row of the last column holds ORIGIN[i]. This is the cardinal
matrix, which AFNI computes from ORIENT_SPECIFIC, ORIGIN and DELTA
whenever it reads a header (the IJK_TO_DICOM attribute it writes is
the same matrix, and is ignored on reading).
Obliquity. IJK_TO_DICOM_REAL, when present, is the true
(possibly oblique) voxel-to-DICOM matrix, as 12 values (three rows of
four). AFNI programs compute on the cardinal grid, ignoring obliquity,
but AFNI's own NIfTI export (3dAFNItoNIFTI) stores the real matrix
(with x and y negated into RAS) as the sform. When a matrix is turned
back into ORIENT_SPECIFIC, ORIGIN and DELTA (THD_daxes_from_mat44),
each axis gets the closest orientation, its voxel size is the length of
its column (signed by the orientation), and its origin is the projection
of the translation on the unit column (signed by the orientation).
View. SCENE_DATA[0] is the view: 0 = orig, 1 = acpc,
2 = tlrc. A NIfTI file read by AFNI goes to tlrc when its form names
a template (Talairach, MNI, other template), and to orig otherwise.
Time. A time series has TAXIS_NUMS ([ntt, nslices, units], with
units 77001 = ms, 77002 = s, 77003 = Hz) and TAXIS_FLOATS
([origin, TR, duration, z origin, z step]).
This module holds what an image reader and a transformation reader (an
AFNI warp, for instance) share: the header, the geometry, the decoding
of the voxel data and their encoding. Every AFNI format derives from
AfniFormat, so that the "afni"
hint selects them all.
Attributes
AFNI_VIEWS
module-attribute
AFNI_VIEWS: tuple[str, ...] = ('orig', 'acpc', 'tlrc')
The views, by their SCENE_DATA[0] code.
AFNI_ORIENTATIONS
module-attribute
AFNI_ORIENTATIONS: tuple[str, ...] = (
"right-to-left",
"left-to-right",
"posterior-to-anterior",
"anterior-to-posterior",
"inferior-to-superior",
"superior-to-inferior",
)
The anatomical orientation of each ORIENT_SPECIFIC code.
DICOM_TO_RAS
module-attribute
The (4, 4) matrix from AFNI's DICOM (LPS) coordinates to RAS. It is
its own inverse.
Classes
AfniFormat
A format of the AFNI family, whatever it stores.
It is the shared base of the AFNI image formats (BRIK/HEAD datasets)
and transformation formats, and carries the "afni" hint they all
answer to. Each format adds its own hints ("brik", ...), which are
then also reachable as "afni.brik", ...
AfniHeader
magic
AfniHeader(attributes: dict[str, _Value])
Bases: Magic
The attributes of an AFNI .HEAD file.
The attributes are kept in the order of the file, by name: a string
attribute as a str (its sub-strings separated by NUL characters),
a numeric one as a tuple of int or of float. The properties decode
the ones that describe the data and the geometry; the others
(HISTORY_NOTE, BRICK_LABS, BRICK_STATAUX, ...) are kept as they
are and written back.
Attributes
attributes
instance-attribute
attributes: dict[str, _Value]
Every attribute of the header, by name, in the order of the file.
brick_types
property
brick_types: tuple[int, ...]
The type code of each sub-brick (int16 when absent; a short
list repeats its last code).
byteorder
property
byteorder: str
The byte order of the BRIK: "<", ">", or "=" (native)
when the header does not say.
dtypes
property
The stored data type of each sub-brick, with its byte order.
float_facs
property
float_facs: tuple[float, ...]
The scaling factor of each sub-brick; 0 means "not scaled".
view
property
view: str
The view: "orig", "acpc" or "tlrc" (SCENE_DATA[0]);
"orig" when the header does not say.
real_matrix
property
The (4, 4) voxel-to-DICOM matrix of IJK_TO_DICOM_REAL, or
None when the header has none.
origin
property
The DICOM coordinate of the centre of voxel 0 along each axis
(ORIGIN).
cardinal_matrix
property
The (4, 4) cardinal voxel-to-DICOM matrix, from
ORIENT_SPECIFIC, ORIGIN and DELTA: the grid AFNI programs
compute on.
voxel_to_dicom
property
The (4, 4) true voxel-to-DICOM matrix: IJK_TO_DICOM_REAL
when the header has one, else the cardinal matrix.
taxis
property
(TR, unit) of a time series, or None when the header has
no time axis. The unit is "millisecond", "second",
"hertz", or None when unknown.
Methods:
get
The value of an attribute, or default if it is absent.
validate
validate() -> AfniHeader
Check that the header describes a dataset that can be read.
Raises:
| Type | Description |
|---|---|
ParserContentError
|
If a mandatory attribute is missing or invalid. |
replace
replace(**attributes: _Value | None) -> AfniHeader
A copy with some attributes set, or removed when None.
AfniParser
magic
AfniParser(
_header: AfniHeader | None = None,
dataobj: Any | None = None,
)
Bases: DataModelBase, AfniFormat, BinaryFileParserWriter
Base class for objects that are encoded by an AFNI dataset
(.HEAD + .BRIK).
It reads and writes the container -- the header and the sub-bricks --
for every AFNI dataset-based format: an image, and later a warp. What
the values mean is for the concrete format to say, through
_from_header when reading, and _afni_header and _afni_data when
writing.
Reading from a local path memory-maps an uncompressed BRIK, so nothing but the header is read until the data are indexed. A compressed BRIK is read into memory.
Attributes
header
property
writable
header: AfniHeader | None
The AFNI 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
Build the object from an AFNI dataset (path or file object).
from_filename
classmethod
from_filename(
filename: FilenameLike, mmap: bool = True, **kwargs
) -> Self
Build the object from the path of a .HEAD, of a .BRIK
(.BRIK.gz, .BRIK.bz2), or of the dataset without extension.
An uncompressed local BRIK is memory-mapped unless mmap is
false.
from_fileobj
classmethod
Build the object from an open .HEAD (or .BRIK) file object.
The other file of the dataset is found from the stream's name,
which it must therefore have.
from_bytes
classmethod
An AFNI dataset is two files, so bytes alone cannot hold one.
sniff_filename
classmethod
sniff_filename(
filename: FilenameLike,
error: bool | Type[Exception] = False,
**kwargs,
) -> float
Score how confident the class is that a path names an AFNI
dataset: its .HEAD is read, whichever of its files is named.
sniff_fileobj
classmethod
Score how confident the class is that a stream holds an AFNI header.
sniff_bytes
classmethod
Score how confident the class is that bytes hold an AFNI header.
to_filename
to_filename(filename: FilenameLike, **kwargs) -> None
Write the dataset: its .HEAD and its .BRIK.
The path may name the .HEAD, the .BRIK (.BRIK.gz or
.BRIK.bz2 to compress it), or the dataset without extension
(out+orig). Another BRIK of the same dataset, compressed
differently, is removed (as AFNI does), so that it cannot be read
in place of the new one.
to_file
to_file(file: FileLike, **kwargs) -> None
Write to a path; an AFNI dataset cannot be written to a stream.
to_fileobj
to_fileobj(file: IO, **kwargs) -> None
An AFNI dataset is two files, which a stream cannot hold.
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 |
sniff_file
classmethod
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 |
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 |
sniff_text
classmethod
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 |
sniff_lines
classmethod
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 |
sniff_line
classmethod
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 |
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 |
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
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_line
classmethod
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
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 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
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.
Functions:
afni_cardinal_matrix
afni_cardinal_matrix(
orient: Sequence[int],
origin: Sequence[float],
delta: Sequence[float],
) -> ndarray
The (4, 4) cardinal voxel-to-DICOM matrix (THD_daxes_to_mat44).
Voxel axis i runs along DICOM axis orient[i] // 2: that row of
column i holds delta[i], and that row of the last column holds
origin[i].
Raises:
| Type | Description |
|---|---|
ValueError
|
If the orientation codes are not valid or do not name three different DICOM axes. |
afni_geometry_from_matrix
afni_geometry_from_matrix(
matrix: ndarray,
) -> tuple[
tuple[int, int, int],
tuple[float, float, float],
tuple[float, float, float],
]
Decompose a voxel-to-DICOM matrix into AFNI's orientation codes,
origin and voxel sizes (THD_daxes_from_mat44).
Each voxel axis is given the DICOM axis and direction its column is
closest to, among the assignments that give the three axes different
DICOM axes. Its voxel size is the length of its column, and its
origin is the projection of the translation on the unit column, both
negated for an orientation that runs towards R, A or I. A
cardinal matrix gives back exactly its ORIGIN and DELTA.
Returns:
| Name | Type | Description |
|---|---|---|
orient |
(int, int, int)
|
|
origin |
(float, float, float)
|
|
delta |
(float, float, float)
|
|
afni_voxel_to_dicom
afni_voxel_to_dicom(xform: Transformation) -> ndarray
The (4, 4) voxel-to-DICOM (LPS) matrix of a voxel-to-world
transformation.
The transformation is reduced to an affine (a Scaling, a
Sequence of affines, ...), embedded in three dimensions, turned into
RAS from the anatomical orientation of its world axes (a world with
no orientation is taken to be RAS already, as every format does), and
then into DICOM.
Raises:
| Type | Description |
|---|---|
UnrepresentableTransformationError
|
If the transformation has no affine representation. |
WriterError
|
If it maps more than three spatial dimensions. |
afni_world
The DICOM (LPS millimetre) world space of an AFNI dataset, named
after its view ("orig", "tlrc", ...).
afni_view
The AFNI view a name stands for, or None.
name may be a view ("tlrc"), the name of a world space
("talairach" and "mni" mean tlrc, "orig-cardinal" means
orig), or a dataset file name ("anat+tlrc.HEAD").
brick_dtype
brick_dtype(dtype: Any) -> dtype
The AFNI data type that stores an array of type dtype (without byte
order), or the one named ("byte", "short", "int", "float",
"double", "complex").
A type AFNI has is kept. Booleans become bytes, int8 shorts,
uint16 and uint32 ints, wider integers doubles, float16 floats
and complex128 complex (single precision).
decode_bricks
decode_bricks(header: AfniHeader, buffer: Any) -> ndarray
Decode the BRIK into a (nx, ny, nz, nvals) array.
buffer holds the (uncompressed) BRIK: bytes, a memoryview or a
one-dimensional uint8 array (a memory map). The result is a
Fortran-ordered view of it when every sub-brick has the same type;
sub-bricks of different types are converted to a common type and
copied. Scaling (BRICK_FLOAT_FACS) is not applied.
scale_bricks
scale_bricks(header: AfniHeader, stored: Any) -> Any
Apply the BRICK_FLOAT_FACS of the header to the stored values
(whose last axis holds the sub-bricks, unless there is only one).
Without a factor, the stored values are returned as they are, so a memory map stays one. With one, they are read and scaled, to at least single precision.
encode_bricks
encode_bricks(
header: AfniHeader, data: Any
) -> Iterator[bytes]
Encode a (nx, ny, nz, nvals) array (or (nx, ny, nz) for a single
sub-brick) into the bytes of the BRIK, one sub-brick at a time.
The values are divided by the header's BRICK_FLOAT_FACS (where not
zero) and converted to its types and byte order, rounding when a
floating-point array is stored as integers.
Raises:
| Type | Description |
|---|---|
WriterError
|
If the shape disagrees with the header, or integers do not fit the stored type. |
afni_dataset_files
afni_dataset_files(
filename: FilenameLike,
) -> tuple[Any, Any, str]
The .HEAD and .BRIK files of the dataset a path names.
The path may be that of the .HEAD, of the .BRIK (compressed or
not), or the dataset's name without extension (anat+orig). The
BRIK is the first one found among .BRIK, .BRIK.gz, .BRIK.bz2,
.BRIK.Z (AFNI's own order), or the uncompressed name if none
exists.
Returns:
| Name | Type | Description |
|---|---|---|
head |
Path
|
The |
brik |
Path
|
The |
stem |
str
|
The dataset's name, without directory nor extension. |