Metadata-Version: 2.5
Name: asl-mri-utils
Version: 0.17.0
Summary: Utilities for Siemens arterial spin labeling (ASL) DICOM volume-order analysis and DICOM-to-NIfTI conversion
Project-URL: Homepage, https://github.com/srikash/asl-mri-utils
Project-URL: Repository, https://github.com/srikash/asl-mri-utils
Project-URL: Issues, https://github.com/srikash/asl-mri-utils/issues
Project-URL: Documentation, https://github.com/srikash/asl-mri-utils/tree/main/docs
Author-email: Sriranga Kashyap <srikashmri@gmail.com>
Maintainer-email: Sriranga Kashyap <srikashmri@gmail.com>
License: MIT
License-File: LICENSE
Keywords: ASL,DICOM,MRI,NIfTI,Siemens,arterial spin labeling,neuroimaging,perfusion
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Image Processing
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
Requires-Python: >=3.10
Requires-Dist: nibabel>=5.0
Requires-Dist: numpy>=1.26
Requires-Dist: pydicom>=3.0
Description-Content-Type: text/markdown

# asl-mri-utils

Command-line tools for post-processing Siemens arterial spin labeling (ASL) MRI data: recovering
true acquisition order from DICOM, converting to NIfTI, splitting timeseries by TI/label/control,
and computing perfusion-weighted difference images.

Siemens-only by design. The whole pipeline reads the true LABEL/CONTROL/M0/TI order straight from
per-frame DICOM tags, not from filenames or protocol names, so it needs the Siemens tags that
provide that: the standard ASL DICOM tags plus one Siemens private tag for TI ordering. If an
expected tag is missing, a command raises a specific exception rather than guessing.

## Table of contents

- [Install](#install)
- [Requirements](#requirements)
- [Quickstart](#quickstart)
- [Commands](#commands)
  - [`order`](#order-recover-acquisition-order)
  - [`convert`](#convert-dicom-to-nifti)
  - [`split`](#split-per-ti-labelcontrol-timeseries)
  - [`subtract`](#subtract-perfusion-weighted-difference-images)
  - [`discover`](#discover-find-series-directories)
- [Further reading](#further-reading)

## Install

```
pip install asl-mri-utils
```

For local development, from a clone of this repository:

```
pip install -e .
```

## Requirements

| Tool | Needed for | Not found? |
|---|---|---|
| `dcm2niix` or `dcm2niix_afni` | `convert` (always) | `convert` fails -- not bundled, must be installed separately |
| AFNI (`3drefit`) | `convert`'s slice-timing embedding | skipped silently, not an error |
| AFNI (`3dvolreg`, `3dAllineate`, `1dplot`) | `subtract --mc` / `--split-mc` | `subtract --mc` fails -- not bundled (`1dplot` only makes the motion plot; optional) |

Each external binary is found via an explicit flag first, then an environment variable, then
`PATH`: `--dcm2niix-bin`/`$ASL_MRI_UTILS_DCM2NIIX` for `dcm2niix`, `--afni-bindir`/
`$ASL_MRI_UTILS_AFNI_BINDIR` (a directory) for the AFNI programs.

## Quickstart

A typical run, starting from one Siemens ASL series' exported DICOM folder:

```bash
# 1. See what dcm2niix would even produce: LABEL/CONTROL/M0 order, TI index, and timing,
#    read straight from the DICOM headers.
asl-mri-utils order 021-my_asl_series

# 2. Convert that series to NIfTI, per echo. This also builds each echo's oxasl-ready
#    *_asl.nii.gz/*_mzero.nii.gz and oxasl_params.json.
asl-mri-utils convert 021-my_asl_series ./niftis

# 3. (Optional) Split echo 1 into per-TI, per-label/control NIfTIs -- useful to inspect, or to
#    hand to other tools directly.
asl-mri-utils split ./niftis/021_my_asl_series_echo-01_asl.nii.gz \
                     ./niftis/021_my_asl_series_echo-01_order.tsv

# 4. Compute perfusion-weighted difference images. --mc motion-corrects first; --mean adds a
#    temporal-mean companion to every diff file (the actual perfusion-weighted map).
asl-mri-utils subtract ./niftis/021_my_asl_series_echo-01_asl.nii.gz \
                        ./niftis/021_my_asl_series_echo-01_order.tsv \
                        --mc --mean
```

(The `*_asl.nii.gz` files above only exist when `convert` had something to remove -- M0 volumes or a
trailing unpaired block. Otherwise use the raw `*_echo-01.nii.gz`, which `oxasl_params.json`'s
`asl_file` names; a dedicated `M0` series yields only `*_mzero.nii.gz`.)

Step 3 is optional: `subtract` does not need `split`'s output. Running it separately just lets you
inspect the per-TI/label/control NIfTIs on their own, e.g. in `fsleyes` or `afni`.

## Commands

### `order`: recover acquisition order

Recovers the true LABEL/CONTROL/M0/TI acquisition order of a Siemens ASL series directly from
per-frame DICOM tags (ASL Context, a Siemens private ordering tag), independent of filenames or
protocol names. Writes one `order.tsv` per echo: `volume_index`, `echo`, `ti_index`,
`temporal_index`, `asl_context`, `acq_time`, `delta_t` (seconds since the first image).

```
asl-mri-utils order <dicom_series_dir> [<dicom_series_dir> ...] [--outdir DIR]
asl-mri-utils order --all [ROOT] [--outdir DIR]
```

### `convert`: DICOM to NIfTI

Wraps a local `dcm2niix`/`dcm2niix_afni` install to convert one series directory to NIfTI, entirely
per echo. For each echo, writes:

- the raw NIfTI + JSON sidecar, renamed to this package's own `*_echo-0N` convention
- `*_echo-0N_order.tsv` (same schema as `order`, above)
- an oxasl-ready `*_echo-0N_asl.nii.gz` (label/control volumes) and `*_echo-0N_mzero.nii.gz` (M0
  volumes), with a matching `*_echo-0N_aslcontext.tsv`. The `_asl` file is only written when it
  removes something from the raw file (M0 volumes or a trailing unpaired block); a series with
  neither is already oxasl-ready as-is, and `oxasl_params.json`'s `asl_file` points at the raw
  NIfTI. A dedicated M0 series (folder name has an `M0` token, or `--m0-series`) gets only
  `_mzero.nii.gz`. `oxasl_params.json` is the source of truth for which files to feed `oxasl`.
- `*_echo-0N_oxasl_params.json`, recording the `(iaf, order, ntis, tis_ms, rpts)` shape `oxasl`
  needs. `tis_ms` comes from `--slice-timing FILE` (the sequence's TimingInfo file, which also gives per-slice
  TIs), else `--tis`, else the DICOM header where the protocol stored it there; otherwise `null`, with a
  note explaining why, not a guess.

If an AFNI install is available and the JSON sidecar has a `SliceTiming` field, that's also burned
into every NIfTI's header via `3drefit -Tslices`.

```
asl-mri-utils convert <in_dir> <out_dir> [--dcm2niix-bin PATH] [--overwrite] [--m0-series | --no-m0-series]
```

### `split`: per-TI, label/control timeseries

Splits one echo's timeseries into per-TI NIfTIs, each further split into its label-only and
control-only subseries:

```
<stem>_TI<k>.nii.gz          # that TI's label+control volumes, chronological order
<stem>_TI<k>_label.nii.gz    # that TI's label volumes only
<stem>_TI<k>_control.nii.gz  # that TI's control volumes only
```

Point it at either the raw per-echo timeseries from `convert`, or its `*_asl.nii.gz` -- either
way, pass the matching `order.tsv`. Which one you gave it is detected automatically, from its own
volume count.

```
asl-mri-utils split <nii> <order_tsv> [--echo N] [--ti N ...] [--outdir DIR]
```

### `subtract`: perfusion-weighted difference images

Computes a perfusion-weighted difference image per TI. Like `split`, it accepts either the raw
timeseries or `*_asl.nii.gz`.

The default method, `simple`, is just `label - control` and its negation (the same decimated
pairs as `standard`, written as `*_LCdiff`/`*_CLdiff`). Pass
`--methods standard surround sinc` instead for Liu & Wong (2005, *NeuroImage* 24:207-215)
pairwise/surround/sinc subtraction -- three different ways of handling the tradeoff between
temporal resolution and noise when differencing an alternating label/control series. `--recommend
block|event_related` picks one of those three automatically, per the paper's own guidance, instead
of listing `--methods` by hand.

- `--methods running`: pairwise difference at *every* adjacent label/control pair (2N-1 outputs),
  vs `standard`'s decimated pairs (N outputs). `surround` and `sinc` also give one output per
  acquired volume. No method adds information, and full-rate outputs are autocorrelated (lag-1
  ~0.5 running, ~0.67 surround, ~0.64 sinc, ~0 standard) in a way that cannot be prewhitened (spectral
  nulls of the filter), so fit the unsubtracted data or use decimated `standard` pairs. See
  [docs/subtract.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/subtract.md) for the
  L C L C illustration.
- `--combine diff|avg|both|auto`: besides the perfusion difference (`control - label`), write the
  BOLD-weighted average `(control + label) / 2` through the same filter (`*_avg.nii.gz`). `auto`
  writes the difference for the lowest echo and the average for later echoes. Full-rate averages
  cannot be prewhitened any more than the differences can; for BOLD statistics use the raw
  later-echo data or the decimated `standard --combine avg` with an AR fit.
- `--grid midpoint`: `sinc` interpolates both conditions to the midpoints between volumes.
- `--label-first`: write the difference as `label - control` (files get an `_LC` suffix) for
  sequences where the label image is the brighter one. Sign convention only; see
  [docs/subtract.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/subtract.md).
- `--mc`: motion-correct the whole timeseries first (AFNI `3dvolreg`, base volume 0), then split
  into TIs and label/control from the corrected file. Every output is then named off the resulting
  `_volreg` file. Also saves `*_volreg.aff12.1D` (matrices), `*_volreg_motion.1D` and a motion plot.
  On a multi-echo dataset, run the lowest echo first: `--mc` on every other echo then automatically
  applies that echo's `*_volreg.aff12.1D` (`3dAllineate`), so all echoes share one motion estimate.
- `--split-mc`: like `--mc` (implies it), but estimate motion in two passes -- controls registered to
  control volume 0, labels registered to that same control volume 0 -- then interleave back to the
  original volume order. Saves the same `*_volreg.aff12.1D` (one matrix per original volume), which
  other echoes apply unchanged.
- `--mean` / `--tsnr`: add a temporal-mean, or temporal-mean/temporal-std, companion to every diff
  file produced. `--mean`'s output is the perfusion-weighted map most people actually want.
- `--sinc-interp`: for the `sinc` method only, swap its estimator for an alternative (windowed-sinc,
  FFT, Gaussian process regression, or a Kalman filter/RTS smoother -- see
  [docs/interpolate.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/interpolate.md)).

```
asl-mri-utils subtract <nii> <order_tsv> [--ti N ...]
    [--methods simple|standard|running|surround|sinc ...] [--recommend block|event_related]
    [--combine diff|avg|both|auto] [--grid sample|midpoint] [--label-first]
    [--sinc-interp truncated|windowed_sinc|fft|gp|kalman]
    [--mc | --split-mc] [--afni-bindir DIR] [--mean] [--tsnr] [--outdir DIR]
```

### `glm`: OLS on subtracted ASL, corrected for the filter

`asl-mri-utils glm <nii> <order_tsv> --method surround --design regressors.tsv --contrast act=perfact_ti1:1`
fits contrasts on the subtracted series of each TI with the variance and degrees of freedom corrected
for the subtraction filter (FEAT's perfusion-subtraction route, which cannot use prewhitening because
the filter's correlation has spectral nulls). It builds the filter from the raw volumes, passes the
design through it (a `ti<k>` baseline is annihilated and dropped, `perf_ti<k>` becomes the intercept),
and writes `*_cope`/`*_varcope`/`*_tstat` maps plus a `*_glm.json` with the effective degrees of
freedom. It warns when the brain-mean C/L differs from 1 by more than 2% (BOLD then leaks into the
perfusion contrast). See [docs/glm.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/glm.md)
and [docs/filters.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/filters.md).

### `regressors`: design columns for unsubtracted ASL

`asl-mri-utils regressors <nii> <order_tsv>` writes `<stem>_regressors.tsv`: per volume, the
control/label modulation (+0.5 / -0.5, 0 for M0) and, per TI, a baseline indicator `ti<k>` and a
perfusion modulation `perf_ti<k>`, for fitting the *unsubtracted* data in FEAT or AFNI (Mumford et
al. 2006, *NeuroImage* 33:103-14) with an AR noise model (FILM / `3dREMLfit`).

`--task NAME=FILE` (repeatable; `onset duration [amplitude]` per line, seconds) adds FEAT's full-model
EVs per TI, at each volume's true acquisition time: `bold_ti<k>_NAME` (task x HRF) and
`perfact_ti<k>_NAME` (task x `perf_ti<k>`). `--format afni|fsl|all` also exports `.1D` / per-EV text files.
See [docs/perfusion-fmri.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/perfusion-fmri.md).

### `lcratio` and `autocorr`: noise and contrast diagnostics

`asl-mri-utils lcratio <nii> <order_tsv>` reports the brain-mean label/control ratio per TI and warns
about a large static difference (BOLD leaking into the perfusion contrast) or a negative `C - L`.
`asl-mri-utils autocorr <nii> <order_tsv> [--method running]` maps the residual autocorrelation (raw, or
through a subtraction filter next to the filter's exact value). See
[docs/autocorr.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/autocorr.md).

### `discover`: find series directories

Finds candidate DICOM series directories under a root, matching the `NNN-SeriesDescription`
folder convention `order --all`/`convert` expect.

```
asl-mri-utils discover [ROOT]
```

## Further reading

Each module has its own page under `docs/`, with the implementation detail this README leaves out
(exact DICOM tags, the Liu & Wong equations, worked examples):

| Doc | Covers |
|---|---|
| [docs/dicom.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/dicom.md) | Siemens tag reading, the ASL Context classification rule, header-derived TI values |
| [docs/order.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/order.md) | Ordering/pairing logic, the `order.tsv` schema |
| [docs/discovery.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/discovery.md) | Series discovery, DICM magic-byte sniffing |
| [docs/binaries.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/binaries.md) | Shared external-binary resolution (`dcm2niix`, AFNI) |
| [docs/convert.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/convert.md) | dcm2niix wrapping, per-echo `oxasl_params.json` fields, slice-timing embedding |
| [docs/merge.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/merge.md) | Splitting one echo's output into its oxasl-ready asl/mzero pair |
| [docs/split.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/split.md) | Per-TI, per-label/control timeseries splitting |
| [docs/subtract.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/subtract.md) | The `simple` method, plus the Liu & Wong (2005) equations for standard/surround/sinc |
| [docs/autocorr.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/autocorr.md) | `autocorr` and `lcratio` noise diagnostics |
| [docs/perfusion-fmri.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/perfusion-fmri.md) | Perfusion fMRI analysis plan, task EVs, AFNI recipe |
| [docs/interpolate.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/interpolate.md) | Windowed-sinc, FFT, Gaussian process, and Kalman/RTS interpolation, with worked examples |
