Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

The GESeries class hierarchy

A GE series folder can hold very different shapes of data depending on what was acquired: a 3D anatomical volume, a single-voxel spectrum averaged over time, a spectroscopic-imaging grid, or a dynamic wash-in series sampled every few seconds. GESeries isn’t one class trying to cover all of that — it’s a small hierarchy, and ExamBase picks the right subclass for a folder so you don’t have to guess it yourself.

ClassWhat it adds
NiiBasewraps a NIfTI image: affine, images(), display(), brain masking
MRISeries+ finds its NIfTI by sidecar SeriesNumber (anatomical scans)
RawMRISeries+ the raw .mat reconstruction, FIDs, and scanner header properties
MRSSeries+ single-voxel spectrum, averages × chemical_shift
MRSWashinSeries+ grouped, time-resolved fitting for a wash-in acquisition
MRSISeries+ spectroscopic-imaging grid, chemical_shift × i × j × k

The examples below load fixtures generated on the fly by mnutils.testing.build_fake_exam -- see the diary entry for why nothing here is a real scan.

NiiBase — a NIfTI image, oriented and displayable

Every series that has a NIfTI ends up with a NiiBase (or subclass) wrapping it: nii, affine, and helpers like images()/display() that apply a display orientation consistently across the toolbox, so plotting code never re-derives it.

MRISeries — a NIfTI series, found by its sidecar

MRISeries is what you get for a series that has a NIfTI but no raw scanner exam data — typically an anatomical scan. It finds the NIfTI through the SeriesNumber in its JSON sidecar, whatever the folder is called. MNUtils never converts DICOMs: run dcm2niix -b y first, or the series logs a warning that spells out the command and loads without a NIfTI.

Data shape: (188, 256, 186)
Voxel size (mm): (0.88, 0.88, 0.88)
Display affine:
[[  0.          -0.88         0.          81.43999636]
 [ -0.88         0.           0.         106.27999604]
 [  0.           0.           0.88       -77.12000275]
 [  0.           0.           0.           1.        ]]
Display zooms (mm): (0.8799999952316284, 0.8799999952316284)
Loading...
Display affine:
[[  0.          -0.88         0.          81.43999636]
 [  0.           0.          -0.88       106.27999604]
 [ -0.88         0.           0.          85.67999637]
 [  0.           0.           0.           1.        ]]
Display zooms (mm): (0.8799999952316284, 0.8799999952316284)
Loading...
Display affine:
[[  0.           0.          -0.88        81.43999636]
 [  0.          -0.88         0.         106.27999604]
 [ -0.88         0.           0.          85.67999637]
 [  0.           0.           0.           1.        ]]
Display zooms (mm): (0.8799999952316284, 0.8799999952316284)
Loading...

Anisotropic voxels: display()'s zooms correction

t1 above happens to look right in every plane, but only because it was acquired isotropically -- every voxel is a cube, so a square-pixel render matches real anatomy no matter which axis ends up in-plane. A real 2D-acquired scan usually isn’t a cube: thin in-plane pixels, thick slices. Reorient one of those to a different display_plane and the thick axis can land in-plane, where rendering it as square pixels stretches or squashes it relative to real anatomy.

NiiBase.display derives the physical (row, col) spacing of whichever two axes are currently in-plane (utils.nifti.get_display_zooms, built on the same reorientation images() applies) and passes it to display_images as zooms, so the render stays proportioned to real anatomy in every plane -- without resampling the underlying data.

Data shape: (191, 250, 54)
Voxel size (mm): (0.8578751, 0.8593749, 2.3999972)
Display affine:
[[ 5.19883633e-03 -8.57854128e-01  8.43399763e-03  8.08354541e+01]
 [-8.49813223e-01 -4.68388898e-03  3.56777370e-01  8.60981830e+01]
 [ 1.27732873e-01  3.75264464e-03  2.37331533e+00 -6.36348228e+01]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  1.00000000e+00]]
Display zooms (mm): (0.8593749056872297, 0.8578751225471344)
Loading...
Display affine:
[[-8.43399763e-03 -8.57854128e-01  5.19883633e-03  8.12824560e+01]
 [-3.56777370e-01 -4.68388898e-03 -8.49813223e-01  1.05007384e+02]
 [-2.37331533e+00  3.75264464e-03  1.27732873e-01  6.21508900e+01]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  1.00000000e+00]]
Display zooms (mm): (2.3999972708654984, 0.8578751225471344)
Loading...
Display affine:
[[-8.43399763e-03  5.19883633e-03 -8.57854128e-01  8.12824560e+01]
 [-3.56777370e-01 -8.49813223e-01 -4.68388898e-03  1.05007384e+02]
 [-2.37331533e+00  1.27732873e-01  3.75264464e-03  6.21508900e+01]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  1.00000000e+00]]
Display zooms (mm): (2.3999972708654984, 0.8593749056872297)
Loading...

MRSSeries — a single-voxel spectrum over time

A series with both raw exam data and a NIfTI (or no NIfTI at all, for a pure spectroscopy acquisition) loads as one of the RawMRISeries subclasses instead. MRSSeries holds a single-voxel spectrum with an averages dimension — one spectrum per repetition, not yet combined.

Loading...
<Axes: xlabel='$^2$H chemical shift [ppm]', ylabel='signal [a.u.]'>
<Figure size 640x480 with 1 Axes>

avg_fid/avg_spec collapse the averages dimension; see Loading a series and an exam for a worked example.

MRSWashinSeries — the same spectrum, resolved over time

A wash-in acquisition is structurally an MRSSeries — same averages × chemical_shift shape — but its averages are meant to be grouped into time bins and fit independently, tracking a metabolite as it washes in. MRSWashinSeries adds fit_grouped_by_duration() and plot_washin() on top of everything MRSSeries already gives you.

Loading...
<Axes: xlabel='$^2$H chemical shift [ppm]', ylabel='signal [a.u.]'>
<Figure size 640x480 with 1 Axes>

MRSISeries — a spectroscopic-imaging grid

MRSISeries is the spatial counterpart: a spectrum per voxel, laid out on a 3D grid (chemical_shift × i × j × k). It carries per-voxel fitting (fit_all_voxels, fit_single_voxel) and an SNR mask used to skip voxels with no real signal.

('chemical_shift', 'i', 'j', 'k') (700, 16, 16, 16)
(16, 16, 16)
Loading...
Loading...

NiiBase operations: resampling and overlays

Two NiiBase images at different resolutions come up constantly — an anatomical scan and an MRSI grid, say — and need to be compared voxel-for-voxel. resample_self_to reslices one onto another’s grid; overlay_nifti_data_on_T1 (from mnutils.plotting.images) plots the result on top of an anatomical volume. These examples use the fake 20250408-NIST-Mag2 exam, a phantom scan where series 2 is the anatomical T1 and series 5 is an MRSI grid.

True True

images() applies the display orientation and returns a plain array — what display() and the overlay helpers plot under the hood:

Loading...
Loading...

MRSISeries carries a few more properties worth knowing about beyond spec: an SNR-based mask for skipping low-signal voxels, a computed voxel size, and per-voxel spectrum access.

10.339999943971634
('i', 'j', 'k')
/home/runner/work/mnutils/mnutils/.venv/lib/python3.12/site-packages/pyAMARES/kernel/fid.py:357: RuntimeWarning: invalid value encountered in scalar divide
  return signal_p / noise_p
<Axes: xlabel='$^2$H chemical shift [ppm]', ylabel='signal [a.u.]'>
<Figure size 640x480 with 1 Axes>

A bounded panel_height keeps a three-panel overlay on one row

overlay_nifti_data_on_T1 lays out three panels -- T1, data, overlay -- in a CSS grid whose per-panel width cap is derived from panel_height times the frame’s aspect ratio. Bound that height down far enough and the cap used to fall under the grid’s fixed column floor (grid.panel_min_width, 9rem by default): the widget then constrained itself narrower than its own three columns needed, and auto-fit dropped one onto a second row. t1’s axial frame (aspect ≈0.73) only needs panel_height bounded below ~12rem to hit that:

Loading...

All three panels stay on one row -- the width cap is now clamped up to the column floor instead of falling below it (--mnu-panel-w-cap in image_grid.css).