Time series at a coordinate

Pull one voxel’s series at a world-mm point.

Goal

Open a 4-D BOLD and extract the time series at a world-millimetre coordinate — with the affine inversion, rounding, and bounds check behind a named operation.

Code

from pathlib import Path
import sys

sys.path.insert(0, str(Path.cwd().parents[1] / "src"))

import neuroim as ni

repo = Path.cwd().parents[1] if Path.cwd().name == "cookbook" else Path.cwd()
bold = ni.read_image(str(repo / "golden_tests/fixtures/tiny_bold.nii.gz"))

xyz = bold.space.grid_to_world((4, 4, 2))     # a point known to be in-grid
ts = bold.series_at_world(xyz)                # bare ndarray, length n_timepoints
typed = bold.series_roi_world(xyz)            # typed result with a Receipt

ts.shape, type(typed).__name__, typed.provenance.method_name
((10,), 'ROIExtractionResult', 'series_roi_world')

series_at_world is the array form; series_roi_world is the typed form that carries provenance forward.

What’s guaranteed

  • The affine inversion and rounding happen inside the call. There is no hand-written np.linalg.inv(affine) @ [x, y, z, 1].
  • A coordinate that maps off-grid raises before any indexing.

What survives the file

series_roi_world returns an ROIExtractionResult. Its .provenance is a Receipt; if you build a volume from it and to_nibabel() it, the Receipt rides the NIfTI extension (see Save a derived map).

Trapdoors

bold.series_at_world((9999.0, 9999.0, 9999.0))
---------------------------------------------------------------------------
OutOfBoundsError                          Traceback (most recent call last)
File ~/code/pycode/neuroim-python/src/neuroim/neuro_vec.py:303, in NeuroVec.series_at_world(self, world_xyz, out_of_bounds)
    302 try:
--> 303     return self.series_at(
    304         int(voxel[0]),
    305         int(voxel[1]),
    306         int(voxel[2]),
    307         out_of_bounds=out_of_bounds,
    308     )
    309 except IndexError as exc:

File ~/code/pycode/neuroim-python/src/neuroim/neuro_vec.py:220, in NeuroVec.series_at(self, x, y, z, out_of_bounds)
    219         return np.zeros(self._time_length())
--> 220     self._raise_oob("coordinate", coord, valid)
    222 impl = getattr(self, "_series", None)

File ~/code/pycode/neuroim-python/src/neuroim/neuro_vec.py:200, in NeuroVec._raise_oob(self, label, values, valid_mask)
    199 suffix = "" if bad.shape[0] <= 5 else f" ... (+{bad.shape[0] - 5} more)"
--> 200 raise OutOfBoundsError(
    201     f"{label} out of bounds for spatial shape {self._spatial_shape()}: "
    202     f"{preview}{suffix}"
    203 )

OutOfBoundsError: coordinate out of bounds for spatial shape (8, 8, 4): [[5004, 5004, 4002]]

During handling of the above exception, another exception occurred:

WorldOutOfBoundsError                     Traceback (most recent call last)
Cell In[2], line 1
----> 1 bold.series_at_world((9999.0, 9999.0, 9999.0))

File ~/code/pycode/neuroim-python/src/neuroim/neuro_vec.py:312, in NeuroVec.series_at_world(self, world_xyz, out_of_bounds)
    310 if out_of_bounds == "zero":
    311     raise
--> 312 self._raise_world_oob(np.asarray(world_xyz, dtype=float), voxel)
    313 raise exc

File ~/code/pycode/neuroim-python/src/neuroim/neuro_vec.py:291, in NeuroVec._raise_world_oob(self, world_xyz, voxel)
    290 def _raise_world_oob(self, world_xyz: np.ndarray, voxel: np.ndarray) -> None:
--> 291     raise WorldOutOfBoundsError(
    292         f"world coord {tuple(float(x) for x in world_xyz)} mm maps to voxel "
    293         f"{tuple(int(v) for v in voxel)} which is outside the image grid "
    294         f"of shape {self._spatial_shape()}."
    295     )

WorldOutOfBoundsError: world coord (9999.0, 9999.0, 9999.0) mm maps to voxel (5004, 5004, 4002) which is outside the image grid of shape (8, 8, 4).

An off-grid world coordinate raises ValueError: world coord (…) mm maps to voxel (…) which is outside the image grid of shape (…). — before signed indexing can return a value from the opposite corner. The out_of_bounds="zero" opt-in exists for algorithms that intentionally want zero-fill; the default is "raise".

See also

Back to top