Metadata-Version: 2.4
Name: mrsandbox
Version: 0.1.0
Summary: A simulation sandbox for magnetic resonance spectroscopy.
Author-email: "Julian P. Merkofer" <j.p.merkofer@tue.nl>
License-Expression: MIT
Project-URL: Homepage, https://github.com/julianmer/MRSandbox
Project-URL: Source, https://github.com/julianmer/MRSandbox
Project-URL: Issues, https://github.com/julianmer/MRSandbox/issues
Keywords: MRS,magnetic resonance spectroscopy,simulation,density matrix,NIfTI-MRS
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Requires-Dist: scipy
Requires-Dist: nibabel
Requires-Dist: nifti-mrs
Requires-Dist: keras>=3.15
Requires-Dist: nifti-mrs-plus>=0.2.0
Requires-Dist: augmentrum>=0.0.3
Requires-Dist: jax
Requires-Dist: flatbuffers
Provides-Extra: jax
Requires-Dist: jax; extra == "jax"
Requires-Dist: flatbuffers; extra == "jax"
Provides-Extra: torch
Requires-Dist: torch; extra == "torch"
Provides-Extra: tensorflow
Requires-Dist: tensorflow; extra == "tensorflow"
Provides-Extra: console
Requires-Dist: nicegui; extra == "console"
Requires-Dist: pywebview; extra == "console"
Requires-Dist: pillow; extra == "console"
Requires-Dist: dcm2niix; extra == "console"
Requires-Dist: pandas; extra == "console"
Requires-Dist: mrsandbox[jax]; extra == "console"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pypulseq; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: mrsandbox[jax]; extra == "dev"
Dynamic: license-file

<div align="center">
  <img src="https://raw.githubusercontent.com/julianmer/MRSandbox/main/assets/logo/logo.png" alt="MRSandbox Logo" width="130" style="margin-bottom: -30px;"/>
  <h1 style="margin-top: 5px; margin-bottom: 5px;">MRSandbox</h1>
  <p style="margin-top: 0px;"><em>A simulation sandbox for magnetic resonance spectroscopy</em></p>
</div>

---

MRSandbox creates magnetic resonance spectra from first principles. A virtual brain provides the
tissue composition at any voxel position, a virtual scanner provides the field strength and the
hardware limits, and fully flexible pulse sequences probe both through a density matrix engine.
Every quantity is a configurable parameter that can be fixed, sampled from literature ranges, or
read from a quantitative map. The simulations are written with Keras operations and run batched
and differentiable on JAX, PyTorch, and TensorFlow.

```python
from mrsandbox import Brain, Press, Scanner, VirtualScan

brain = Brain.load("icbm152")                      # the MNI average brain, fetched once and cached
scan  = VirtualScan(brain, Scanner.preset("generic_3t"), Press(te1_s=0.011, te2_s=0.019))
data  = scan.voxel(position_mm=(10, -20, 5), size_mm=20).acquire(seed=7)    # NIfTI-MRS
```

> MRSandbox is under active development and grows with my research. It is a personal research toolbox and interfaces are adjusted as we see fit.

## Install

```
pip install mrsandbox
```

Python 3.11 or newer. JAX comes with the install as the Keras backend the physics is measured on; `mrsandbox[torch]` and `mrsandbox[tensorflow]` add the other two. Acquisitions play their spurious echoes and eddy phase through [Augmentrum](https://github.com/julianmer/Augmentrum), which comes with the install. The console comes with `mrsandbox[console]`, the tests, ruff and pypulseq with `mrsandbox[dev]`. The wheel ships no data: the measured cohorts' readings come from this repository and the large public data (the digital heads, the measured maps, the raw datasets the calibrations are read from) from their publishers, fetched on first use into `~/.cache/mrsandbox`, checksum verified where the publisher states one; `MRSANDBOX_CACHE` moves the cache.

## What it does

Every example runs as it stands after `from mrsandbox import *`.

```python
# a scanner as the data know it: the Prisma's noise record, read off raw in vivo scans, comes with it
profile = ScannerProfile.preset("siemens_prisma_3t")
scan = profile.scan(brain, SemiLaser(te_s=0.026, tr_s=2.0))

# or your own scanner, replicated from one of its raw files (coils and transients apart)
mine = ScannerProfile.read("my_scan.nii.gz", scan.voxel((0, -45, 35), 20, compiled=True), "my Prisma")
mine.save("my_prisma.json")

# or a whole folder of scans at once (spectra with their water references, a T1 image, maps, a
# noise scan): the room that comes back says what every file gave, and scans as that scanner
room = replicate("my_session_folder")
scan = room.twin(SemiLaser(te_s=0.026, tr_s=2.0))
```

```python
# every quantity is a parameter: fix it, draw it from a range, or read it from a map
fever = VirtualScan(brain, Scanner.preset("generic_3t"), Press(te1_s=0.011, te2_s=0.019),
                    params=Params(water=dict(temperature_c=(36.0, 40.0)), conc=dict(GABA=2.5)))
```

```python
# J-difference editing emerges from the density matrix; HERMES and HERCULES ship too
gaba = Multiplex(brain, Scanner.preset("generic_3t"), MegaPress.pair(),
                 combinations={"difference": {"on": 1.0, "off": -1.0}})
stack = gaba.acquire((0, -45, 35), 30.0, seed=1)          # NIfTI-MRS on DIM_EDIT, one truth
edited = gaba.combine(stack, "difference")
```

```python
# spectroscopic imaging, a phantom on the table, a stream of training data
image = scan.mrsi(center_mm=(0, -10, 35), matrix=(8, 8), voxel_mm=12.0).acquire(seed=1)
sphere = VirtualScan(Phantom.spectre(temperature_c=21.0), Scanner.preset("generic_3t"), Press(te1_s=0.011, te2_s=0.019))
batches = scan.generator((0, -45, 35), 20.0, batch_size=16, seed=0)
```

The data are NIfTI-MRS with the receiver's centre at 4.65 ppm. The header of every acquisition carries its ground truth: the drawn parameters, the tissue in the voxel, the calibration records in force and where the scan departs from the conditions they were measured under.

## The console

<img src="https://raw.githubusercontent.com/julianmer/MRSandbox/main/assets/console.png" alt="The virtual scanner console: three exam cards planned and run on the QSM challenge head, the last one's spectrum live" width="100%"/>

<sub>The head is the QSM reconstruction challenge 2.0 head (Marques et al. 2021, MRM 86:526). Data were provided (in part) by the Donders Institute for Brain, Cognition and Behaviour.</sub>

```
uvx --from "mrsandbox[console]" mrsandbox-console
```

A virtual scanner console. Choose the scanner (a preset, or your own saved profile), the receive array, and who lies on the table, or bring your own anatomy, B0, and B1+ maps. Leaf through three planes, place the voxel or the MRSI grid, switch water suppression and OVS, fill the exam cards, and run them: the spectrum builds up over the transients, which come with the drift and motion of a real run. Design your own sequence from blocks, your own pulse shapes among them, and see its pulse sequence diagram and its spectrum. Open any parameter of the simulation through the gears, as a number, a range, or a map. Review the spectra beside the truth, and export what a scanner exports: the raw transients as NIfTI-MRS and the sequence as Pulseq.

## Overview

```mermaid
flowchart LR
    subgraph values [Values]
        DB[(Database)] --> PR[Params]
    end
    subgraph world [Virtual World]
        BR[Brain<br/>Phantom]
        HW[Scanner<br/>Hardware]
        SQ[Sequences<br/>Events, Shapes, Timings]
    end
    subgraph physics [Physics]
        SP[Spins] --> EN[Engine]
        BK[Backend] --> EN
    end
    PR --> BR
    PR --> HW
    PR --> SQ
    BR --> SC[Virtual Scan<br/>Voxel]
    HW --> SC
    SQ --> SC
    SC --> EN
    EN --> OUT[[NIfTI-MRS+]]
```

One line per folder; the docstrings hold the rest.

* `params/` holds the parameter system: values that are fixed, drawn, per tissue, or mapped, in composable trees.
* `database/` stores literature values as cited Python dicts: concentrations, relaxation, shifts, pulses, phantoms.
* `phantom/` defines the subjects: brains with continuous tissue fractions, phantoms of solution, and the water, lipid, macromolecule, and downfield sources.
* `resources/` fetches large public datasets on first use into a checksummed cache. The wheel ships no data.
* `hardware/` describes scanners with their limits, the digital receiver, and receive arrays with their noise.
* `sequences/` builds sequences as event lists: PRESS, STEAM, semi-LASER, MEGA-PRESS, HERMES, HERCULES, water suppression, ideal or with the localization and the pulses played.
* `spins/` and `engine/` hold the spin systems and run the density matrix, plain or as one compiled graph per design.
* `scan/` composes it all: `VirtualScan`, `Voxel`, `MRSI`, `Multiplex`, the training `Generator`, the `ScannerProfile`, the calibration records, and the readers that make a record from raw data.
* `io/` reads and writes Pulseq, and reads Philips (data/list, sdat/spar, lab/raw) and Varian files.
* `console/` is the virtual scanner console.
* `backend/` contains the propagator mathematics; `tests/` mirrors the package and asserts the physics.

## Validation

Every physical claim is gated by a test, and the simulation is checked against independent references.

* An in-test complex128 oracle and Clarke's `fsl_denmatsim` (correlation above 0.9999) check the engine. The parity tests need the `externals/denmatsim` submodule (FMRIB's GitLab); without it they skip.
* Shaped pulses reproduce the bandwidth products of de Graaf's Table 5.1, and analytic laws hold exactly where they should.
* Raw in vivo data check the whole: played HERMES and HERCULES reproduce the edited spectra of two scanners
  of two vendors (public OpenNeuro data) without a fit.
* Numbers calibrated on in vivo data live as records with the exact conditions of their measurement. Only
  quantities read off raw data enter a record, never what a fitting program reported.

## Citation

MRSandbox is described in an ISMRM 2027 abstract (submitted). Until it is out, cite the repository:

> Merkofer JP. MRSandbox: a simulation sandbox for magnetic resonance spectroscopy. 2026. https://github.com/julianmer/MRSandbox

## Licence

MIT, see the LICENSE file.
