concept-collection / mri-scanner
mri-scanner / CLAUDE.md
4.9 KBPreviewCodeBlameHistoryRaw

CLAUDE.md#

Tips for future agents working in this repo.

What this is#

mri-scanner is a companion to seqlab. You upload a pulseq .seq file (e.g. one exported from seqlab), pick a digital phantom, and it runs a Bloch simulation in the browser (Web Worker) and shows the raw k-space (the acquired signal laid out one row per ADC readout). Reconstruction is intentionally deferred — this is the "raw data" stage.

Architecture#

src/seq/              .seq parser + reconstruction, COPIED from seqlab
                      (parseSeq.ts, reconstruct.ts, types.ts, md5.ts). If you
                      fix a parser bug, fix it in seqlab too. summary.ts wraps
                      them for a display/validate summary.
src/phantom/          phantomTypes.ts (Phantom = spin cloud + tissue props),
                      loadPhantom.ts (read a KomaMRI .phantom HDF5 via h5wasm,
                      in the browser), builtins.ts (the two bundled phantoms,
                      imported with Vite ?url), data/{cube,sphere}.phantom
                      (GENERATED — do not hand-edit).
src/sim/              simulate.ts = the Bloch simulator (pure, no DOM — so it
                      runs headlessly in Node too). simWorker.ts runs it off
                      the main thread; useSimulation.ts is the React hook
                      (progress streaming; cancel = terminate the worker).
src/kspace/           kspace.ts (RawSignal -> RGBA image, DOM-free),
                      KspaceView.tsx (canvas + mode selector).
src/storage/          seqStore.ts — uploaded .seq files in IndexedDB.
src/ui/               SequencePanel, PhantomPanel.
src/App.tsx           orchestration.
scripts/gen-phantoms.mjs   regenerate the two .phantom files (h5wasm/node).
scripts/sim-test.mjs       headless physics smoke test.
test-data/            fid.seq, gre.seq goldens (from seqlab) for sim-test.

The simulator (src/sim/simulate.ts)#

Isochromat Bloch simulation in the rotating frame. The timeline is split at the union of gradient vertices, RF samples and ADC samples (boundaries); within a segment gradients are linear and RF is ~one raster. Two regimes:

Signal at each ADC sample = Σ_j ρ_j·(Mx+iMy), demodulated by the receiver phase (ADC phase + frequency offset). Units: pulseq gradients are Hz/m, RF amplitude Hz, positions m, so g·r and B1 are already in Hz — no γ needed. Sign convention is internally consistent (excitation reduces to the free-precession z-rotation when B1=0) but not pinned to a physical handedness; that only matters once we add reconstruction. Relaxation has a uniform fast path (our built-in phantoms are single-tissue) and a per-spin fallback.

Cost ≈ O(numSegments · numSpins). A 128×128 GRE on the 2197-spin cube is ~400k segments → ~20 s. FID/EPI are much cheaper. The sphere (~1000 spins) is ~2× faster than the cube. Progress is streamed; cancel terminates the worker.

The .phantom format#

Genuine KomaMRI .phantom = HDF5 (see ../KomaMRI.jl Phantom.jl): root attrs Version/Name/Ns/Dims; group position with x/y/z (metres); group contrast with ρ, T1, T2, T2s (s) and Δw (rad/s). Note the Unicode dataset names ρ and Δw — h5wasm reads/writes them fine. loadPhantom.ts maps aliases so it also opens arbitrary KomaMRI phantoms. The two built-ins are ~10 mm, 0.8 mm uniform spin spacing, single water-like tissue (T1 1000 ms, T2 100 ms, Δw 0).

Gotchas#

Testing#