brainhops.io.images.afni
Readers and writers for images stored as AFNI datasets.
AfniImage reads and writes AFNI's
native datasets, with no dependency beyond numpy: a text header,
prefix+view.HEAD, and the voxel values, prefix+view.BRIK, which may
be compressed (.BRIK.gz, .BRIK.bz2). Either file, or the dataset's
name without extension, can be given.
The header format, and every convention checked against the AFNI
sources, is described in brainhops.io.base.afni,
which the image reader shares with the AFNI transformation formats.
import brainhops.io as io
image = io.load("epi+orig.HEAD") # an AfniImage
image.data # [x, y, z, sub-brick], scaled
image.transformation # voxel -> "orig", LPS mm (Affine)
image.header["HISTORY_NOTE"] # any attribute of the header
image.header.labels # sub-brick labels (BRICK_LABS)
image.save("copy+orig.BRIK.gz") # .HEAD + gzipped .BRIK
io.save(image, "epi.nii.gz") # or any other image format
Data. The sub-bricks are stored one after the other, x fastest:
the array is indexed [x, y, z], or [x, y, z, sub-brick] when there
are several sub-bricks, in F order. The fourth axis is t (of type
time) for a time series -- a dataset with a TAXIS_NUMS attribute --
and brick otherwise. The data of an uncompressed local BRIK stay
memory-mapped until they are indexed; a compressed BRIK, or one whose
sub-bricks have different types, is read into memory. The scaling
factor of each sub-brick (BRICK_FLOAT_FACS, zero meaning none) is
applied when data is first accessed; dataobj holds the stored
values.
Coordinate systems. AFNI's world is "DICOM order": LPS millimetres
(x to the left, y to the back, z up), which AFNI calls RAI. The
world spaces are named after the dataset's view: orig, acpc or
tlrc. The transformations are, in order:
voxel->physical: aScalingby the voxel sizes (|DELTA|), and by the repetition time of a time series;voxel-><view>-cardinal: the cardinal grid AFNI programs compute on, fromORIENT_SPECIFIC,ORIGINandDELTA;voxel-><view>: the true, possibly oblique, geometry ofIJK_TO_DICOM_REAL(the cardinal grid when the header has none). It is the preferred transformation, and the matrix AFNI itself exports to NIfTI (3dAFNItoNIFTI) andnibabelreads.
The two affines are the same unless the dataset is oblique.
Writing. The preferred transformation is converted to
voxel-to-DICOM (an RAS world is flipped) and written as
IJK_TO_DICOM_REAL; its closest cardinal grid becomes
ORIENT_SPECIFIC, ORIGIN, DELTA and IJK_TO_DICOM, as AFNI computes
it when it reads a NIfTI file. A 3D array is one sub-brick, and a 4D
array has one sub-brick per volume. Writer options set the view
(default: from the file name, out+tlrc.HEAD, else from the name of the
world space, else the view read, else orig), the stored datatype
(default: the data's own type, or the closest AFNI has) and extra or
removed attributes. The attributes read from the source header
(HISTORY_NOTE, BRICK_LABS, ...) are written back, except those that
no longer describe the data. The data are written unscaled, in
little-endian order, and a .BRIK.gz or .BRIK.bz2 name compresses
them.
One class for every view
The view (+orig, +acpc, +tlrc) is a property of the dataset --
the world space its coordinates are in, recorded in SCENE_DATA --
not a different file format: the three views are read and written
the same way, so a single class reads them all, and names its world
space after the view.
Classes
AfniImage
AfniImage(
_header: AfniHeader | None = None,
dataobj: Any | None = None,
)
Bases: AfniParser, WritableFileBasedImage, SingleScaleImage
An image that is encoded by an AFNI dataset (.HEAD + .BRIK).
The data are indexed [x, y, z], or [x, y, z, sub-brick] for a
dataset with several sub-bricks, in F order. The data of an
uncompressed local BRIK stay memory-mapped until they are indexed.
The scaling factors of the sub-bricks (BRICK_FLOAT_FACS) are applied
on access; dataobj holds the stored values.
The voxel-to-world transformations are, in order:
voxel->physical: aScalingby the voxel sizes (|DELTA|, in mm), and the repetition time of a time series;voxel-><view>-cardinal(orig-cardinal,tlrc-cardinal, ...): theAffinefromORIENT_SPECIFIC,ORIGINandDELTA, the grid AFNI programs compute on;voxel-><view>(orig,acpcortlrc): theAffineofIJK_TO_DICOM_REAL, the true (possibly oblique) geometry, which AFNI exports to NIfTI. Without that attribute, it is the cardinal one.
The world spaces are LPS millimetres (AFNI's "DICOM order"). The
last transformation is the preferred one. The attributes the data
model has no slot for are kept in header and written back.
Why the bases are in this order
As for NiftiImage: SingleScaleImage comes last so that its
data field follows the defaulted fields of the parser, and the
lazy properties of this class take precedence over the plain
fields.
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.
grid
property
grid: CartesianField
The Cartesian field that defines the sampling grid of the image.
This is the grid of the image's geometry.
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
header: AfniHeader | None
The AFNI header this object was read from, if any.
system
property
writable
system: CoordinateSystem | None
The voxel coordinate system, derived from the header, unless
set explicitly. None when there is no header.
transformations
property
writable
transformations: list[Transformation]
The voxel-to-world transformations recorded by the header, decoded on 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
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_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 AFNI header.
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 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_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
An AFNI dataset is two files, so bytes alone cannot hold one.
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 to a path; an AFNI dataset cannot be written to a stream.
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_fileobj
to_fileobj(file: IO, **kwargs) -> None
An AFNI dataset is two files, which a stream cannot hold.
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.