Source code for mrsiprep.parcellation.extraction

"""Regional metabolite extraction."""

from __future__ import annotations

from pathlib import Path

import numpy as np

from mrsiprep.io.naming import parcellation_derivative
from mrsiprep.parcellation.base import ParcellationResult
from mrsiprep.utils.images import load_3d_data
from mrsiprep.utils.tables import read_labels, write_tsv


[docs] def extract_regional_metabolites( config, subject: str, session: str | None, metabolite_maps: dict[str, Path], parcels: ParcellationResult, qcmasks: dict[str, Path], snr_map: Path | None, linewidth_map: Path | None, crlb_maps: dict[str, Path], tissue_mrsi: dict[str, Path], ) -> Path: out = parcellation_derivative(config.derivative_dir, subject, session, space="MRSI", atlas=parcels.atlas_name, scale=parcels.scale, desc="regional_metabolites", suffix_override="tsv") labels_df = read_labels(parcels.labels) atlas_data = load_3d_data(parcels.atlas_mrsi, dtype=np.float32, label="MRSI atlas")[1].astype(int) snr = _load_optional(snr_map) linewidth = _load_optional(linewidth_map) tissue = {label: _load_optional(path) for label, path in tissue_mrsi.items()} rows = [] for _, label_row in labels_df.iterrows(): parcel_id = int(label_row["parcel_id"]) parcel_mask = atlas_data == parcel_id if not np.any(parcel_mask): continue for met, path in metabolite_maps.items(): data = load_3d_data(path, dtype=np.float32, label=f"{met} map")[1] qmask = load_3d_data(qcmasks[met], dtype=np.float32, label=f"{met} QC mask")[1].astype(bool) if met in qcmasks else np.isfinite(data) valid = parcel_mask & qmask & np.isfinite(data) values = data[valid] weights = snr[valid] if snr is not None else np.ones_like(values) weights = np.nan_to_num(weights, nan=0.0) weighted_mean = np.nan if values.size and np.sum(weights) > 0: weighted_mean = float(np.average(values, weights=weights)) rows.append( { "subject": f"sub-{subject}", "session": f"ses-{session}" if session else "", "atlas": parcels.atlas_name, "scale": parcels.scale or "", "parcel_id": parcel_id, "parcel_name": label_row.get("parcel_name", parcel_id), "hemisphere": label_row.get("hemisphere", "NA"), "metabolite": met, "mean": float(np.nanmean(values)) if values.size else np.nan, "median": float(np.nanmedian(values)) if values.size else np.nan, "std": float(np.nanstd(values)) if values.size else np.nan, "weighted_mean": weighted_mean, "n_voxels": int(valid.sum()), "coverage": float(valid.sum() / max(parcel_mask.sum(), 1)), "mean_snr": _masked_mean(snr, valid), "mean_linewidth": _masked_mean(linewidth, valid), "mean_crlb": _masked_mean(_load_optional(crlb_maps.get(met)), valid), "mean_gm_fraction": _masked_mean(tissue.get("GM"), valid), "mean_wm_fraction": _masked_mean(tissue.get("WM"), valid), "mean_csf_fraction": _masked_mean(tissue.get("CSF"), valid), } ) write_tsv(rows, out) return out
def _load_optional(path): if path is None: return None path = Path(path) if not path.exists(): return None return load_3d_data(path, dtype=np.float32)[1] def _masked_mean(data, mask): if data is None or not np.any(mask): return np.nan return float(np.nanmean(data[mask]))