A lot of analysis collapses a 4-D BOLD across time into a 3-D map — temporal SNR, a contrast, a searchlight statistic. The map you get back is a normal volume you can index, save, or plot; it just also remembers which image and mask produced it, so you can answer that question later without keeping notes.
Temporal SNR — the canonical case
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 =="start"else Path.cwd()bold = ni.read_image(str(repo /"golden_tests/fixtures/tiny_bold.nii.gz"))mask = ni.read_image(str(repo /"golden_tests/fixtures/tiny_mask.nii.gz"), type="vol")tsnr = bold.temporal_snr(mask=mask)type(tsnr).__name__, tsnr.shape
temporal_snr returns a DenseNeuroVol — the same type a 3-D NIfTI would read as. The numeric payload is in tsnr.data; the spatial frame is in tsnr.space. The new field is .provenance.
The projected volume is again a DenseNeuroVol with .provenance. The shape is the same as tsnr above; the method_name distinguishes them. Two analyses, two maps, same carrier.
Why the carrier matters
A workflow that produces N maps over N subjects ends up with a list of DenseNeuroVols, each carrying its .provenance. A group-mean operation can verify every input’s input_space_hash matches before reducing; the next step’s Receipt records the inputs it consumed. None of this requires a sidecar manifest the user has to remember to write.
When you write tsnr to disk, the Receipt travels with the file: ni.write_vol(...) routes through to_nibabel() and embeds it, and the explicit to_nibabel() + nib.save() form does the same thing — the subject of the next page.
What it refuses
Skippable on a first read — the edges, for when you hit them.
temporal_snr(mask=...) with a mask from another space. The mask must share the BOLD image’s spatial frame. Matching array shapes are not enough; the affine and dimensions are checked before the reduction runs.
searchlight_apply(mask=..., data=bold) with a shifted same-shape mask. Shape agreement is not space agreement. searchlight_apply checks the mask’s affine before building neighborhoods, so a shifted mask raises instead of mixing values from the wrong locations.
Hand back an anonymous 3-D array from a reduction. Every reduction over a typed object returns a typed object. temporal_snr does not return np.ndarray; searchlight_apply does not return a tuple.
Next
05 — Round-trip is where the Receipt crosses the file boundary. The carrier on this page is DenseNeuroVol with .provenance; the carrier on the next page is .nii.gz with a comment extension. The contract is that the round-trip is provenance-safe.