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 Pathimport syssys.path.insert(0, str(Path.cwd().parents[1] /"src"))import neuroim as nirepo = 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-gridts = bold.series_at_world(xyz) # bare ndarray, length n_timepointstyped = bold.series_roi_world(xyz) # typed result with a Receiptts.shape, type(typed).__name__, typed.provenance.method_name
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) 302try:
--> 303returnself.series_at( 304int(voxel[0]), 305int(voxel[1]), 306int(voxel[2]), 307out_of_bounds=out_of_bounds, 308) 309exceptIndexErroras exc:
File ~/code/pycode/neuroim-python/src/neuroim/neuro_vec.py:220, in NeuroVec.series_at(self, x, y, z, out_of_bounds) 219return np.zeros(self._time_length())
--> 220self._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] <=5elsef" ... (+{bad.shape[0]-5} more)"--> 200raise OutOfBoundsError(
201f"{label} out of bounds for spatial shape {self._spatial_shape()}: " 202f"{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----> 1bold.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) 310if out_of_bounds =="zero":
311raise--> 312self._raise_world_oob(np.asarray(world_xyz,dtype=float),voxel) 313raise exc
File ~/code/pycode/neuroim-python/src/neuroim/neuro_vec.py:291, in NeuroVec._raise_world_oob(self, world_xyz, voxel) 290def_raise_world_oob(self, world_xyz: np.ndarray, voxel: np.ndarray) ->None:
--> 291raise WorldOutOfBoundsError(
292f"world coord {tuple(float(x)forxinworld_xyz)} mm maps to voxel " 293f"{tuple(int(v)forvinvoxel)} which is outside the image grid " 294f"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".