01 — Read an image

Load a NIfTI file and get your data — with the spatial frame still attached.

Reading an image is one function call. You get the array you expect, plus the spatial frame that came with the file — so later steps can’t quietly mix images that don’t line up.

Load a file

from pathlib import Path
import sys

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

import numpy as np
import neuroim as ni

repo = 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"))
arr = np.asarray(bold.data)

bold.shape, arr.dtype, float(arr.mean())
((8, 8, 4, 10), dtype('<f4'), 100.4787368774414)

That’s your data: a 4-D array, its dtype, and a value you can sanity-check. Indexing works the way you’d expect — bold.data[..., 0] is the first volume, bold.data[x, y, z, :] is one voxel’s time series.

Read a 3-D mask the same way:

mask = ni.read_image(str(repo / "golden_tests/fixtures/tiny_mask.nii.gz"), type="vol")
mask.shape, int(np.asarray(mask.data).sum())
((np.int64(8), np.int64(8), np.int64(4)), 108)

The one extra thing each object carries is .space — the spatial frame (dimensions, voxel sizes, orientation) read from the file. It travels with the data automatically; you never assemble it by hand. The next page is about what that frame does for you.

Which reader runs

read_image picked DenseNeuroVec for the 4-D BOLD (the trailing axis is time) and, with type="vol", DenseNeuroVol for the 3-D mask. type= chooses the reader, it does not reinterpret bytes:

  • type="auto" (default) — pick by dimensionality.
  • type="vol" — volume reader; on a 4-D file it takes volume 0.
  • type="vec" — vector reader; on a 3-D file it promotes to a singleton-time 4-D (…, 1).

For most workflows the default is what you want; pass type= only when you need a specific shape.

Why a typed object?

The return type is not bookkeeping — it’s what lets you write analysis code without defensive checks. A DenseNeuroVec is the 4-D, time-aware type, so its time methods are just there:

bold.temporal_snr  # a time-aware method, available because this is a NeuroVec
<bound method NeuroVec.temporal_snr of DenseNeuroVec
  Type      : DenseNeuroVec
  Dimension : 8 X 8 X 4 X 10
  Spacing   : 2.0 X 2.0 X 2.5 X 1.25
  Origin    : -8.0, -8.0, -5.0, 0.0>

You call bold.temporal_snr(mask=mask) directly — no if data.ndim == 4: guard, no passing the affine around by hand. If you call a time method on a 3-D volume, that’s a wrong call your editor and a type checker can catch, instead of a confusing array error deep in a reduction. The safety is a side effect of getting the right object back; you don’t have to think about it to benefit from it.

What it refuses

You can skip this on a first read — it’s here so you know the edges.

  • Unknown reader mode. type= accepts only "auto", "vol", or "vec". Anything else raises ValueError: type must be one of 'auto', 'vol', or 'vec' rather than guessing.
  • A bare array as the return. read_image always returns a typed object with .space attached; it never hands back a plain numpy.ndarray. When you explicitly want the raw array, bold.data[...] works for any image, vol.as_array() is available on volume objects, and neuroim.compat.as_array(obj) is the generic helper — opting out is deliberate, never accidental.

read_image is the entry point. It uses nibabel underneath, but that’s an implementation detail. If you already hold a nibabel image for some other reason, ni.from_nibabel(img) is the explicit bridge — see interop.

Next

02 — Spatial frames introduces NeuroSpace, the frame that travels with every image and quietly keeps later steps from mixing mismatched data.

Back to top