MR Spectroscopy with Osprey#

Analyzing MR Spectra from the Human Brain using Single Voxel Spectroscopy (SVS)#

Author: Michal Toth

Date: 24 Sep 2025

This notebook was prepared for an audience of clinicians at the 2025 PACTALS (Pan-Asian consortium of Treatment and Research in ALS) conference in Melbourne (link)

📘 How to Use This Notebook

This notebook is an interactive document. It mixes short explanations (like this box) with computer code that runs automatically.

⚡ The basics

  • 👆 Click on a cell (the boxes with text or code)
  • ▶️ Run it by pressing Command + Enter (Mac) or Control + Enter (Windows/Linux)
  • ⏭️ Or use the play button in the toolbar above
  • ⬇️ Move to the next cell and repeat

🧩 What happens when you run a cell?

The computer will either:

  • ✏️ Show you some text or figures
  • 🖥️ Run an analysis step in the background
  • 📂 Save results into a folder

MR Spectroscopy (MRS)#

  • Quantification of concentration of various metabolites (chemical compounds)

  • Each metabolite has a distinct spectral (frequency-domain) signature based on its chemical structure

My Image

Introduction#

This example demonstrates the processing of MR spectra acquired from the human motor cortex using Osprey, a standalone tool for processing and quantification of magnetic resonance spectroscopy (MRS) data.

Citation and Resources:#

Tools included in this workflow#

Osprey:

  • Oeltzschner, G., Zöllner, H. J., Hui, S. C. N., Mikkelsen, M., Saleh, M. G., Tapper, S., & Edden, R. A. E. (2020). Osprey: Open-source processing, reconstruction & estimation of magnetic resonance spectroscopy data. Journal of Neuroscience Methods, 343, 108827. https://doi.org/10.1016/j.jneumeth.2020.108827

  • Osprey documentation

Python:

Dataset#

  • Collected at UQ as part of the BeLong study

  • T1 weighted MP2RAGE at 3T (healthy control)

  • SEMI-LASER MRS at 3T (healthy control)

    • Oz, G., & Tkáč, I. (2011). Short-echo, single-shot, full-intensity proton magnetic resonance spectroscopy for neurochemical profiling at 4 T: validation in the cerebellum and brainstem. Magn Reson Med, 65(4), 901-910. https://doi.org/10.1002/mrm.22708

    • Deelchand, D. K., Berrington, A., Noeske, R., Joers, J. M., Arani, A., Gillen, J., Schär, M., Nielsen, J. F., Peltier, S., Seraji-Bozorgzad, N., Landheer, K., Juchem, C., Soher, B. J., Noll, D. C., Kantarci, K., Ratai, E. M., Mareci, T. H., Barker, P. B., & Öz, G. (2021). Across-vendor standardization of semi-LASER for single-voxel MRS at 3T. NMR Biomed, 34(5), e4218. https://doi.org/10.1002/nbm.4218

    • The basis file used in this example is a placeholder, the SLaser basis set used in the acquisition (Eftekhari, Z., Shaw, T., Deelchand, D., Marjańska, M., Bogner, W., & Barth, M. (2025). Reliability and Reproducibility of Metabolite Quantification Using 1H MRS in the Human Brain at 3 T and 7 T. NMR in biomedicine, 38, e70087. https://doi.org/10.1002/nbm.70087) is not shareable at the moment.

Osprey input#

Description

Filename

Raw metabolite data

mrs/…svs_slaser_dkd_ul.dat

Water reference data

mrs/…svs_slaser_dkd_ul_wref.dat

T1-weighted anatomical image

anat/…acq-MP2RAGE_T1w.nii

JSON file configuring the pipeline. See Osprey’s documentation for more details

osprey-job.json

A BASIS file containing simulated model spectra for known metabolites. Used by LCModel to fit data.

set.BASIS

Osprey output#

Description

Filename

MRS voxel aligned with the anatomy

osprey-output/VoxelMasks/…svs_slaser_dkd_ul_space-scanner_mask.nii

MRS voxel segmentation masks (GM, WM, CSF)

osprey-output/SegMaps/…svs_slaser_dkd_ul_space-scanner_Voxel-1_label-CSF.nii

Results of spectral fitting (from LCmodel)

osprey-output/LCMOutput/

Summary of results from different steps of the pipeline. It includes some QC metrics

osprey-output/Reports/sub-002-report.html

Tables with concentration values for metabolites using different references

osprey-output/QuantifyResults/

Load software tools and import python libraries#

import module
await module.load('osprey/2.9.0')
await module.list()
['osprey/2.9.0']
%%capture
!pip install nibabel numpy pandas
import os
from pathlib import Path
import shutil
from IPython.display import Image as ipython_image, display, HTML
from PIL import Image, ImageDraw, ImageFont
from ipyniivue import NiiVue
import nibabel as nib
import numpy as np
import ipywidgets as widgets
import pandas as pd
import matplotlib.pyplot as plt
import json
import subprocess

Set up Osprey Job configuration#

  1. Download and prepare data

  2. Set up paths to the relevant MRS/MRI files

  3. Set up the configuration JSON file

  4. Check that all specified paths exist

NOTE: For a more detailed explanation of all the configuration options see Osprey’s documentation

%%bash
echo "Downloading data"
osf -p 3a2ws fetch osfstorage/osprey.zip ./data_osprey/osprey.zip
echo "Unzipping data"
unzip ./data_osprey/osprey.zip -d ./data_osprey/
Downloading data
Unzipping data
Archive:  ./data_osprey/osprey.zip
   creating: ./data_osprey/sub-002/ses-01/
   creating: ./data_osprey/sub-002/ses-01/anat/
  inflating: ./data_osprey/sub-002/ses-01/anat/sub-002_ses-01_acq-MP2RAGE_desc-defaced_T1w.nii  
   creating: ./data_osprey/sub-002/ses-01/mrs/
  inflating: ./data_osprey/sub-002/ses-01/mrs/meas_MID00104_FID130221_svs_slaser_dkd_ul_wref.dat  
  inflating: ./data_osprey/sub-002/ses-01/mrs/meas_MID00103_FID130220_svs_slaser_dkd_ul.dat
100%|██████████| 150M/150M [00:16<00:00, 9.38Mbytes/s]
# Get path to Osprey executable.
osprey_path = subprocess.check_output("which ospreyCMD", shell=True, text=True).strip()

# Extract container base and image path.
container_base = os.path.dirname(osprey_path)
container_id = os.path.basename(container_base)
simg_path = os.path.join(container_base, f"{container_id}.simg")

# Construct the basis set path (from inside the container).
basis_path = os.path.join(
    simg_path,
    "opt/basissets/3T/siemens/unedited/slaser/30"
)

# Osprey will try to convert this .mat basis file into a .BASIS type
# file. It will try to write the new .BASIS file into the same folder
# where the .mat basis file is. Thus, the .mat basis file needs to be
# copied into a writeable location.
!cp {basis_path}/basis_siemens_slaser30.mat ./data_osprey/basis_siemens_slaser30.mat
base_path = os.getcwd()
data_path = f'{base_path}/data_osprey'
output_path = f'{base_path}/osprey-output'
basis_set = f'{data_path}/basis_siemens_slaser30.mat'
job_path = f'{base_path}/osprey-job.json'

sub = 'sub-002'
ses = 'ses-01'

# Metabolite data.
files = [
    f'{data_path}/{sub}/{ses}/mrs/meas_MID00103_FID130220_svs_slaser_dkd_ul.dat'
]
# Water reference data.
files_ref = [
    f'{data_path}/{sub}/{ses}/mrs/meas_MID00104_FID130221_svs_slaser_dkd_ul_wref.dat'
]
# T1.
files_nii = [
    f'{data_path}/{sub}/{ses}/anat/{sub}_{ses}_acq-MP2RAGE_desc-defaced_T1w.nii'
]
mrs_options = {
    'seqType': 'unedited',
    'editTarget': ['none'],
    'dataScenario': 'invivo',
    'outputFolder': [output_path],
    'SpecReg': 'RobSpecReg',
    'SubSpecAlignment': 'none',
    'EECraw': '1',
    'ECCmm': '1',
    'method': 'LCModel',
    'saveLCM': '1',
    'savejMRUI': '0',
    'saveVendor': '1',
    'saveNII': '1',
    'savePDF': '0',
    'includeMetabs': ['default'],
    'style': 'Separate',
    'range': ['0.5', '4.2'],
    'rangeWater': ['2.0', '7.4'],
    'bLineKnotSpace': '5',
    'fitMM': '1',
    'basisSet': basis_set,
    'files': files,
    'files_ref': files_ref,
    'files_nii': files_nii
}

with open(job_path, 'w') as f:
    json.dump(mrs_options, f)
check_paths = [data_path, output_path, basis_set, job_path]
check_paths = check_paths + files + files_ref + files_nii

# Create the results directory if it doesn't exist yet
os.makedirs(output_path, exist_ok=True)

any_warning = False
for pth in check_paths:
    pth_exists = Path(pth).exists()
    print(f"{'✅' if pth_exists else '❌'}  '{pth}'")
✅  '/home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/data_osprey'
✅  '/home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output'
✅  '/home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/data_osprey/basis_siemens_slaser30.mat'
✅  '/home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-job.json'
✅  '/home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/data_osprey/sub-002/ses-01/mrs/meas_MID00103_FID130220_svs_slaser_dkd_ul.dat'
✅  '/home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/data_osprey/sub-002/ses-01/mrs/meas_MID00104_FID130221_svs_slaser_dkd_ul_wref.dat'
✅  '/home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/data_osprey/sub-002/ses-01/anat/sub-002_ses-01_acq-MP2RAGE_desc-defaced_T1w.nii'

Run Osprey processing#

The following command runs the Oprey CLI software with specifications from the job file defined above.

!ospreyCMD {job_path}
------------------------------------------
Setting up environment variables
---
LD_LIBRARY_PATH is .:/opt/MCR/R2023a//runtime/glnxa64:/opt/MCR/R2023a//bin/glnxa64:/opt/MCR/R2023a//sys/os/glnxa64:/opt/MCR/R2023a//sys/opengl/lib/glnxa64
Spectral fitting parameters will not be saved (default). Please indicate otherwise in the csv-file or the GUI 
/home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-job.json
Timestamp October 30, 2025 00:07:41 Osprey 2.9.0
Timestamp October 30, 2025 00:07:41 Osprey 2.9.0  OspreyLoad
Loading raw data from dataset   1 out of   1 total datasets...
Software version: VD (!?)
Reader version: 1496740353 (UTC: 06-Jun-2017 09:12:33)
Scan 1/2, read all mdhs:
    35.3 MB read in    0 s
Scan 2/2, read all mdhs:
    83.9 MB read in    0 s

ans =

     0

multi RAID file detected.
Software version: VD (!?)
Reader version: 1496740353 (UTC: 06-Jun-2017 09:12:33)
Scan 1/2, read all mdhs:
    35.3 MB read in    0 s
Scan 2/2, read all mdhs:
    19.0 MB read in    0 s

ans =

     0

multi RAID file detected.

... done.
 Elapsed time 15.933984 seconds
Timestamp October 30, 2025 00:07:57 Osprey 2.9.0  OspreyProcess
Processing data from dataset   1 out of   1 total datasets...

... done.
 Elapsed time 57.194783 seconds
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QM_processed_spectra.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QM_processed_spectra.json
Timestamp October 30, 2025 00:08:56 Osprey 2.9.0  OspreyFit
Fitting metabolite spectra from dataset   1 out of   1 total datasets...

... done.
 Elapsed time 4.912133 seconds

... done.
 Elapsed time 4.912133 seconds
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QM_processed_spectra.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QM_processed_spectra.json
Timestamp October 30, 2025 00:09:07 Osprey 2.9.0  OspreyCoreg
Coregistering voxel from dataset   1 out of   1 total datasets...

... done.
 Elapsed time 6.378909 seconds
Timestamp October 30, 2025 00:09:14 Osprey 2.9.0  OspreySeg

Segmenting structural image from dataset   1 out of   1 total datasets...


------------------------------------------------------------------------
30-Oct-2025 00:09:22 - Running job #1
------------------------------------------------------------------------
30-Oct-2025 00:09:22 - Running 'Segment'

SPM12: spm_preproc_run (v7670)                     00:09:22 - 30/10/2025
========================================================================
Segment /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/data_osprey/sub-002/ses-01/anat/sub-002_ses-01_acq-MP2RAGE_desc-defaced_T1w.nii,1
Completed                               :          00:12:55 - 30/10/2025
30-Oct-2025 00:12:55 - Done    'Segment'
30-Oct-2025 00:12:55 - Done



------------------------------------------------------------------------
30-Oct-2025 00:12:59 - Running job #1
------------------------------------------------------------------------
30-Oct-2025 00:12:59 - Running 'Normalise: Write'
30-Oct-2025 00:13:10 - Done    'Normalise: Write'
30-Oct-2025 00:13:10 - Done



------------------------------------------------------------------------
30-Oct-2025 00:13:14 - Running job #1
------------------------------------------------------------------------
30-Oct-2025 00:13:14 - Running 'Image Calculator'

SPM12: spm_imcalc (v6961)                          00:13:14 - 30/10/2025
========================================================================
ImCalc Image: /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/VoxelMasks/MID00103_FID130220_svs_slaser_dkd_ul_space-spm152_mask_VoxelOverlap.nii
30-Oct-2025 00:13:15 - Done    'Image Calculator'
30-Oct-2025 00:13:15 - Done


... done.
 Elapsed time 234.532095 seconds
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/SegMaps/TissueFractions_Voxel_1.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/SegMaps/TissueFractions_Voxel_1.json
Timestamp October 30, 2025 00:13:16 Osprey 2.9.0  OspreyQuantify

Quantifying dataset   1 out of   1 total datasets...

... done.
 Elapsed time 0.064912 seconds
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_amplMets_Voxel_1_Basis_1.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_amplMets_Voxel_1_Basis_1.json
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_tCr_Voxel_1_Basis_1.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_tCr_Voxel_1_Basis_1.json
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_rawWaterScaled_Voxel_1_Basis_1.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_rawWaterScaled_Voxel_1_Basis_1.json
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_CSFWaterScaled_Voxel_1_Basis_1.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_CSFWaterScaled_Voxel_1_Basis_1.json
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_TissCorrWaterScaled_Voxel_1_Basis_1.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_TissCorrWaterScaled_Voxel_1_Basis_1.json
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_AlphaCorrWaterScaled_Voxel_1_Basis_1.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_AlphaCorrWaterScaled_Voxel_1_Basis_1.json
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_AlphaCorrWaterScaledGroupNormed_Voxel_1_Basis_1.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_AlphaCorrWaterScaledGroupNormed_Voxel_1_Basis_1.json
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_CRLB_Voxel_1_Basis_1.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_CRLB_Voxel_1_Basis_1.json
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_h2oarea_Voxel_1_Basis_1.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/QuantifyResults/A_h2oarea_Voxel_1_Basis_1.json
Timestamp October 30, 2025 00:13:18 Osprey 2.9.0  OspreyOverview

Gathering spectra from subspectrum 1 out of 2 total subspectra..Gathering spectra from subspectrum 1 out of 2 total subspectra..Gathering spectra from subspectrum 2 out of 2 total subspectra...
... done.
Gathering fit models from fit 1 out of 1 total fits.Gathering fit models from fit 1 out of 1 total fits...
... done.
Interpolating fit models from fit 1 out of 1 total fits.Interpolating fit models from fit 1 out of 1 total fits...
... done.

Scaling data from dataset 1 out of 1 total datasets.Scaling data from dataset 1 out of 1 total datasets.Scaling data from dataset 1 out of 1 total datasets...
... done.
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/subject_names_and_excluded.tsv
Writing table to file = /home/jovyan/Git_repositories/example-notebooks/books/spectroscopy/osprey-output/subject_names_and_excluded.json
... done.
 Elapsed time 0.289561 seconds
Runtime Breakdown................
OspreyLoad runtime: 15.933984 seconds
OspreyProcess runtime: 57.194783 seconds
OspreyFit runtime: 4.912133 seconds
	OspreyFit metab runtime: 4.912133 seconds
OspreyCoreg runtime: 6.378909 seconds
OspreySeg runtime: 234.532095 seconds
OspreyOverview runtime: 0.289561 seconds
Full Osprey runtime: 319.306377 seconds

Results#

T1s with the MRS voxel aligned#

  • Osprey performs alignment with T1

  • Osprey also performs segmentation to determine the proportion of tissue types within the voxel (for WM and CSF correction)

# Prepare the main viewer withot the 3D render.
nv = NiiVue(
    height=600,
    multiplanar_layout="ROW",
    multiplanar_show_render=False,
    multiplanar_pad_pixels=10,
    is_ruler=False,
    is_colorbar=False,
    is_orient_cube=False,
    is_radiological_convention=False,
    back_color=(0.1,0.1,0.1,1.0),
    crosshair_color=[0,0,0,1]
)

# Robust intensity window (ignore zeros and outliers)
t1 = nib.load(files_nii[0]).get_fdata()
vals = t1[t1 > 0]
vmin, vmax = np.percentile(vals, [0.5, 99.5]) if vals.size else (None, None)

# T1 with contrast adjusted.
nv.add_volume({
    "path": files_nii[0],
    "name": "native",
    "opacity": 1.0,
    "colormap": "gray",
    "cal_min": float(vmin) if vmin is not None else None,
    "cal_max": float(vmax) if vmax is not None else None,
    "ignore_zero_voxels": True,
})

# WM segmentation.
nv.add_volume({
    "path": f'{output_path}/SegMaps/meas_MID00103_FID130220_svs_slaser_dkd_ul_space-scanner_Voxel-1_label-WM.nii.gz',
    "name": "wm",
    "opacity": 0.5,
    "colormap": "red",
    "ignore_zero_voxels": True,
})

# GM segmentation.
nv.add_volume({
    "path": f'{output_path}/SegMaps/meas_MID00103_FID130220_svs_slaser_dkd_ul_space-scanner_Voxel-1_label-GM.nii.gz',
    "name": "gm",
    "opacity": 0.5,
    "colormap": "blue",
    "ignore_zero_voxels": True,
})

# CSF segmentation.
nv.add_volume({
    "path": f'{output_path}/SegMaps/meas_MID00103_FID130220_svs_slaser_dkd_ul_space-scanner_Voxel-1_label-CSF.nii.gz',
    "name": "csf",
    "opacity": 0.5,
    "colormap": "green",
    "ignore_zero_voxels": True,
})

# Add legend (in HTML so we can style).
fontsize = 25
entry_sep = 25
text_sep = 10
cube_size = 20
capt = widgets.HTML(
    f"<div style='font:{fontsize}px sans-serif; margin-left:285px;'>"
    f"<div style='display:inline-block; margin-right:{entry_sep}px;'>"
    f"<span style='display:inline-block; width:{cube_size}px; height:{cube_size}px; background-color:#de4743; margin-right:{text_sep}px;'></span>WM"
    "</div>"
    f"<div style='display:inline-block; margin-right:{entry_sep}px;'>"
    f"<span style='display:inline-block; width:{cube_size}px; height:{cube_size}px; background-color:#4037a4; margin-right:{text_sep}px;'></span>GM"
    "</div>"
    f"<div style='display:inline-block; margin-right:{entry_sep}px;'>"
    f"<span style='display:inline-block; width:{cube_size}px; height:{cube_size}px; background-color:#479a2e; margin-right:{text_sep}px;'></span>CSF"
    "</div>"
    "</div>"
)
# Put the legend above the NiiVue viewer.
panel = widgets.VBox([capt, nv])

display(panel)
display(ipython_image(url='https://raw.githubusercontent.com/neurodesk/example-notebooks/refs/heads/main/books/images/osprey-notebook.png'))
display(ipython_image(filename=f"{output_path}/Reports/reportFigures/sub-002/sub-002_seg_svs_space-scanner_mask.jpg"))
../_images/3ee3a31d64b49cf6d0b2391f0d076647e9fd60830f3e8cceb773a61e685c9245.jpg

Spectral modelling#

  • Osprey runs LCModel for spectral fitting

crlb_threshold = 20

# Make sure to only include the metabolites in the LCmodel plot.
metabolites = [
    'Asp', 'Asc', 'Cr', 'GABA', 'Gln', 'Glu', 'GPC', 'GSH', 'Lac',
    'PCr', 'PE', 'NAA', 'NAAG', 'Tau', 'CrCH2', 'Lip13a', 'Lip13b',
    'Lip09', 'MM09', 'Lip20', 'MM20', 'MM12', 'MM14', 'MM17'
]

# crlbs = pd.read_csv(f'{output_path}/QuantifyResults/A_CRLB_Voxel_1_Basis_1.tsv', delimiter='\t')[metabolites]
crlbs = pd.read_csv(f'{output_path}/QuantifyResults/A_CRLB_Voxel_1_Basis_1.tsv', delimiter='\t')[metabolites]
crlb_dict = crlbs.iloc[0, :].to_dict()

# Osprey fitting results image.
img_path = f"{output_path}/Reports/reportFigures/sub-002/sub-002_metab_A_model.jpg"
img = Image.open(img_path)

# Draw CRLBs on top of the image.
draw = ImageDraw.Draw(img)
font = ImageFont.load_default(size=15)

x, y = 1135, 253
for met, crlb in crlb_dict.items():
    y += 20.67  # shift down for next line
    # Only show
    if crlb < crlb_threshold:
        draw.text((x, y), f"({crlb:.1f})", fill="#2e4b67", font=font)

display(img)
../_images/651471e90dc32d319255ac3a7126592a1bf8a40fe9e66812cbda8e49077c877c.png
# Centered title
display(HTML("<h2 style='text-align: center;'>Averaged Spectra</h2>"))

# Image (keeps default alignment unless styled otherwise)
display(ipython_image(filename=f"{output_path}/Reports/reportFigures/sub-002/sub-002_metab_A.jpg"))

Averaged Spectra

../_images/2392edb5615535e863b57a6de3855cefee9728f81de79f3b52fd0e508b12aa65.jpg

Metabolite concetrations#

  • Osprey calculates metabolite concentrations relative to different references

metabolites = ['Cr', 'GABA', 'Gln', 'Glu', 'GPC', 'GSH', 'Lac', 'PCr', 'PE', 'NAA', 'NAAG', 'Tau', 'CrCH2', 'tCr', 'tNAA', 'Glx']

def plot_metabolites(data, figsize=(20, 5), label='(raw values in arbitrary units)', ytick_step=0.2):
    fig = plt.figure(figsize=figsize)
    ax = fig.add_subplot()
    ax.bar(height=data.values[0], x=data.columns, zorder=2)
    ax.set_title(f'Metabolites\n{label}')
    
    ylims = ax.get_ylim()
    yticks = np.arange(ylims[0], ylims[1], ytick_step)
    ax.set_yticks(yticks)
    
    ax.grid(axis='y', zorder=1)
met_data = pd.read_csv(f'{output_path}/QuantifyResults/A_tCr_Voxel_1_Basis_1.tsv', delimiter='\t')
plot_metabolites(met_data[metabolites], label='(ratio of tCR)')
../_images/ec1407ac17360a12855512c8ae8b43d93c7723b0491328d5cc218aa4ab6f2791.png
met_data = pd.read_csv(f'{output_path}/QuantifyResults/A_TissCorrWaterScaled_Voxel_1_Basis_1.tsv', delimiter='\t')
plot_metabolites(met_data[metabolites], label='(molal concentration, using tissue corrected water; mol/kg)', ytick_step=2.5)
../_images/b1c4a74effa5a2240631f710ecd7c609222632e37acaee8846ec5ed707fb8255.png

Dependencies in Jupyter/Python#

  • Using the package watermark to document system environment and software versions used in this notebook

%load_ext watermark

%watermark
%watermark --iversions
Last updated: 2025-10-30T00:13:39.768416+00:00

Python implementation: CPython
Python version       : 3.11.6
IPython version      : 8.16.1

Compiler    : GCC 12.3.0
OS          : Linux
Release     : 5.4.0-204-generic
Machine     : x86_64
Processor   : x86_64
CPU cores   : 32
Architecture: 64bit

PIL       : 10.3.0
matplotlib: 3.8.4
numpy     : 2.2.6
ipywidgets: 8.1.2
nibabel   : 5.2.1
json      : 2.0.9
ipyniivue : 2.3.2
IPython   : 8.16.1
pandas    : 2.3.3