Source code for neuroreg.transforms.niftyreg
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import numpy as np
from .lta import LTA, _AnyHeader, _header_info, _header_to_vol_info, _invalid_vol_info
[docs]
@dataclass(slots=True)
class NiftyRegTransform:
"""NiftyReg 3D affine text matrix.
FreeSurfer ``lta_convert`` treats the stored matrix as the inverse of the
canonical scanner-RAS transform, i.e. target/reference RAS to
source/moving RAS.
"""
matrix: np.ndarray
def __post_init__(self) -> None:
self.matrix = np.asarray(self.matrix, dtype=float).reshape(4, 4)
[docs]
@classmethod
def read(cls, filename: str | Path) -> NiftyRegTransform:
"""Read a NiftyReg affine text matrix from disk.
Parameters
----------
filename : str or Path
Path to a NiftyReg affine text file.
Returns
-------
NiftyRegTransform
Parsed NiftyReg transform wrapper.
Raises
------
ValueError
If the file does not contain exactly four rows of four values.
"""
path = Path(filename)
rows = []
for raw_line in path.read_text().splitlines():
line = raw_line.strip()
if not line or line.startswith("#"):
continue
values = [float(v) for v in line.split()]
if len(values) != 4:
raise ValueError(f"{path}: expected 4 columns per row in NiftyReg affine matrix")
rows.append(values)
if len(rows) != 4:
raise ValueError(f"{path}: expected 4 rows in NiftyReg affine matrix")
return cls(np.asarray(rows, dtype=float))
[docs]
@classmethod
def from_lta(cls, lta: LTA) -> NiftyRegTransform:
"""Create a NiftyReg transform wrapper from a canonical LTA.
Parameters
----------
lta : LTA
Canonical scanner-RAS transform mapping moving to reference space.
Returns
-------
NiftyRegTransform
Wrapper containing the equivalent NiftyReg file-space matrix.
"""
return cls(np.linalg.inv(lta.r2r()))
[docs]
def to_lta(
self,
src_fname: str | None = None,
src_img: _AnyHeader | None = None,
dst_fname: str | None = None,
dst_img: _AnyHeader | None = None,
) -> LTA:
"""Convert the NiftyReg matrix to canonical scanner-RAS LTA form.
Parameters
----------
src_fname, dst_fname : str or None, optional
Optional filenames to store in the output LTA metadata.
src_img, dst_img : header-like or None, optional
Optional source and destination image headers used to populate LTA
volume information.
Returns
-------
LTA
Canonical RAS-to-RAS transform wrapper.
"""
src_fname = "" if src_fname is None else src_fname
dst_fname = "" if dst_fname is None else dst_fname
src = _invalid_vol_info(src_fname) if src_img is None else _header_to_vol_info(_header_info(src_img), src_fname)
dst = _invalid_vol_info(dst_fname) if dst_img is None else _header_to_vol_info(_header_info(dst_img), dst_fname)
return LTA(np.linalg.inv(self.matrix), 1, src, dst)
[docs]
def write(self, filename: str | Path) -> None:
"""Write the matrix in NiftyReg text format.
Parameters
----------
filename : str or Path
Output transform path.
Returns
-------
None
Writes the transform to ``filename``.
"""
path = Path(filename)
with path.open("w") as f:
for row in self.matrix:
f.write(" ".join(f"{float(v):.7g}" for v in row) + "\n")