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.

Inspecting MRSI spectra interactively

I want to click around a T1 slice and see the MRSI spectrum under my cursor update live — without keeping a Python kernel running.

inspect_MRSI_spectra() shows a T1 image with an MRSI overlay next to a spectrum plot. Click a voxel, use the arrow keys, or drag the slice slider, and the spectrum panel updates to match. Every anatomical slice is pre-rendered once and every voxel’s spectrum is precomputed into a single buffer embedded in the widget, so all of that interaction runs client-side — it keeps working after this page is built into a static site, the same way the slider in Displaying image arrays of any shape does.

FunctionWhat it does here
inspect_MRSI_spectra()builds and displays the interactive inspector

1. Load a matching T1/MRSI pair

Same fake phantom exam as Drawing an MRSI voxel on an anatomical slice — series 2 is the T1-weighted anatomical scan, series 8 the MRSI acquisition covering it.

Loading...

2. Display the inspector

blocky=True (the default) resamples the raw, un-interpolated MRSI grid — one cell per acquired voxel — rather than the smoothed NIfTI, so the overlay and the voxel picker line up with what was actually measured. autophase=True (also the default) autophases each voxel’s spectrum before display; pass magnitude=True for the magnitude spectrum instead.

Loading...

Click anywhere on the left image to move the selected voxel there, use the arrow keys to nudge it by one voxel, or drag the slider below the image to change the anatomical slice — the MRSI slice shown updates to whichever one that anatomical slice actually falls inside, and the spectrum panel on the right always reflects the currently selected voxel.

The spectrum panel carries its own two range sliders: the horizontal one under the plot sets the ppm window (the whole acquired sweep is embedded, so it reaches every sample), and the vertical one beside it sets the y limits. Touching the vertical slider ticks Fixed y-axis for you — an axis you dialled in by hand only stays meaningful if it stops rescaling itself — and unticking the box hands the axis back to the automatic per-voxel scaling. Leaving the box ticked without touching the slider instead scales the axis to the loudest voxel anywhere in the grid, so amplitudes stay comparable as you step from voxel to voxel.

What embedding the whole sweep costs

The widget used to crop the spectra to a ppm window before embedding them, on the assumption that a wide sweep would make the payload unwieldy — a 2H acquisition at 3 T spans ~255 ppm, of which the interesting ±20 ppm is a sixth. In practice the compressed buffer is small enough either way: for the 16×16×16 grid at 700 points on this page it is ~6.7 MB embedded against ~1.1 MB cropped, and the widget stays responsive. Not cropping is what lets the ppm slider reach every acquired sample instead of a window fixed at call time, so the crop (and its ppm_range argument) is gone. Grids large enough to matter still log a warning while encoding.

3. Magnitude vs. autophased vs. raw real spectrum

The three display modes are mutually exclusive; magnitude=True takes priority; the spectrum label shown in the widget’s header names whichever one is active:

Loading...
Loading...