brain_deer.infrastructure.orientation¶
Single source of truth for spatial orientation in BrainDeer.
The whole app agrees on one world frame: RAS+ world millimetres, exactly
as defined by a NIfTI image’s affine (the 4x4 voxel-index -> world-mm map that
nibabel resolves from the qform/sform). Every coordinate that leaves voxel space
- slice planes, crosshairs, tractography, DBS electrodes, atlas overlays - lives
in this frame. Nothing invents its own centred/offset frame.
Why this module exists¶
Historically the 3D scene placed volumes in a synthetic frame,
(voxel - (n-1)/2) * spacing, which silently discards the affine’s origin and
rotation. That frame only coincides with true world mm when a volume’s field of
view happens to be centred on the world origin, so objects authored in real world
mm (MNI coordinates, Lead-DBS electrode markers, RASmm streamlines) drifted by a
per-volume offset. Routing all voxel<->world conversions through the affine here
removes that entire class of bugs: things already in world mm simply coincide.
Everything in this module is pure numpy/nibabel (no VTK, no Qt) so it can
be unit-tested against synthetic affines without a display.
Functions¶
|
Map voxel indices |
|
Inverse of |
|
Anatomical axis codes for an affine, e.g. |
|
True when the affine is in strict RAS+ orientation (axis codes |
|
Split an affine into |
|
Reorient any NIfTI image to strict RAS+ without moving it in world space. |
Module Contents¶
- brain_deer.infrastructure.orientation.voxel_to_world(affine: numpy.ndarray, voxel: numpy.ndarray) numpy.ndarray¶
Map voxel indices
(i, j, k)to world mm(x, y, z)viaaffine.Accepts a single point of shape
(3,)or a batch of shape(N, 3)and returns the matching shape. This isworld = affine @ [i, j, k, 1]done for every point; it is the only correct voxel->world map because it honours the affine’s rotation and origin, not just its diagonal spacing.
- brain_deer.infrastructure.orientation.world_to_voxel(affine: numpy.ndarray, world: numpy.ndarray, *, integer: bool = False) numpy.ndarray¶
Inverse of
voxel_to_world()usinginv(affine).Accepts
(3,)or(N, 3)and returns the matching shape. Withinteger=Truethe result is rounded to nearest voxel indices (int64); otherwise continuous indices are returned for sub-voxel accuracy.
- brain_deer.infrastructure.orientation.axis_codes(affine: numpy.ndarray) AxisCodes¶
Anatomical axis codes for an affine, e.g.
("R", "A", "S")or("L", "P", "S").
- brain_deer.infrastructure.orientation.is_ras(affine: numpy.ndarray, *, tol: float = 0.0001) bool¶
True when the affine is in strict RAS+ orientation (axis codes
R, A, S).tolis accepted for API symmetry with callers that pass one; axis-code comparison is exact, so it is currently unused but kept to avoid churn.
- brain_deer.infrastructure.orientation.decompose_affine(affine: numpy.ndarray) tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]¶
Split an affine into
(spacing, direction, origin).spacing(3,): voxel size along each axis (Euclidean norm of each column).direction(3, 3): unit direction cosines (columns), i.e. the pure rotation/flip.origin(3,): world position of voxel(0, 0, 0)(the affine’s translation).
affine[:3, :3] == direction @ diag(spacing), so this cleanly separates the “where/orientation” from the “how big” of a volume.
- brain_deer.infrastructure.orientation.to_ras(img: nibabel.Nifti1Image) nibabel.Nifti1Image¶
Reorient any NIfTI image to strict RAS+ without moving it in world space.
Reorientation only relabels/reorders/flips voxel axes; the world-mm location of every voxel is preserved (
voxel_to_worldgives the same answer before and after). This is the single canonicaliser all loaders route through - NIfTI, DICOM (LPS patient space -> RAS), ROI masks, and Lead-DBS volumes - so that once data is inside BrainDeer it has exactly one orientation convention.Memory notes: after
nib.as_closest_canonical()volumes are usually already RAS, so the second (array-materialising) reorientation is skipped when possible - important for 4-D fMRI series. The rare manual-flip path usesfloat32from the array proxy to avoidget_fdata()’s float64 doubling.