/ concept-collection / mri-scanner
Sign in
concept-collection / mri-scanner
mri-scanner / CLAUDE.md
94 lines · 4.9 KBCodeBlameHistory
3Tips for future agents working in this repo.
5## What this is
7mri-scanner is a companion to [seqlab](https://github.com/concept-collection/seqlab).
8You upload a pulseq `.seq` file (e.g. one exported from seqlab), pick a digital
9phantom, and it runs a Bloch simulation **in the browser** (Web Worker) and
10shows the raw k-space (the acquired signal laid out one row per ADC readout).
11Reconstruction is intentionally deferred — this is the "raw data" stage.
13## Architecture
15```
16src/seq/ .seq parser + reconstruction, COPIED from seqlab
17 (parseSeq.ts, reconstruct.ts, types.ts, md5.ts). If you
18 fix a parser bug, fix it in seqlab too. summary.ts wraps
19 them for a display/validate summary.
20src/phantom/ phantomTypes.ts (Phantom = spin cloud + tissue props),
21 loadPhantom.ts (read a KomaMRI .phantom HDF5 via h5wasm,
22 in the browser), builtins.ts (the two bundled phantoms,
23 imported with Vite ?url), data/{cube,sphere}.phantom
24 (GENERATED — do not hand-edit).
25src/sim/ simulate.ts = the Bloch simulator (pure, no DOM — so it
26 runs headlessly in Node too). simWorker.ts runs it off
27 the main thread; useSimulation.ts is the React hook
28 (progress streaming; cancel = terminate the worker).
29src/kspace/ kspace.ts (RawSignal -> RGBA image, DOM-free),
30 KspaceView.tsx (canvas + mode selector).
31src/storage/ seqStore.ts — uploaded .seq files in IndexedDB.
32src/ui/ SequencePanel, PhantomPanel.
33src/App.tsx orchestration.
34scripts/gen-phantoms.mjs regenerate the two .phantom files (h5wasm/node).
35scripts/sim-test.mjs headless physics smoke test.
36test-data/ fid.seq, gre.seq goldens (from seqlab) for sim-test.
37```
39## The simulator (src/sim/simulate.ts)
41Isochromat Bloch simulation in the rotating frame. The timeline is split at the
42union of gradient vertices, RF samples and ADC samples (`boundaries`); within a
43segment gradients are linear and RF is ~one raster. Two regimes:
45- **Free precession** (no RF): transverse magnetisation rotates about z by
46 `dφ = 2π·(g·r)·dt + Δw·dt` and relaxes — exact for a linear gradient, so these
47 segments can be long (a whole dwell/delay). This is what makes it fast.
48- **Excitation** (RF present): full 3-D Rodrigues rotation about
49 `Ω = (2π·B1·cosθ, 2π·B1·sinθ, 2π·g·r + Δw)`.
51Signal at each ADC sample = `Σ_j ρ_j·(Mx+iMy)`, demodulated by the receiver
52phase (ADC phase + frequency offset). Units: pulseq gradients are **Hz/m**, RF
53amplitude **Hz**, positions **m**, so `g·r` and `B1` are already in Hz — no γ
54needed. Sign convention is internally consistent (excitation reduces to the
55free-precession z-rotation when B1=0) but not pinned to a physical handedness;
56that only matters once we add reconstruction. Relaxation has a uniform fast
57path (our built-in phantoms are single-tissue) and a per-spin fallback.
59Cost ≈ O(numSegments · numSpins). A 128×128 GRE on the 2197-spin cube is ~400k
60segments → ~20 s. FID/EPI are much cheaper. The sphere (~1000 spins) is ~2×
61faster than the cube. Progress is streamed; cancel terminates the worker.
63## The .phantom format
65Genuine KomaMRI `.phantom` = HDF5 (see `../KomaMRI.jl` Phantom.jl):
66root attrs `Version`/`Name`/`Ns`/`Dims`; group `position` with `x`/`y`/`z`
67(metres); group `contrast` with `ρ`, `T1`, `T2`, `T2s` (s) and `Δw` (rad/s).
68Note the **Unicode** dataset names `ρ` and `Δw` — h5wasm reads/writes them
69fine. `loadPhantom.ts` maps aliases so it also opens arbitrary KomaMRI
70phantoms. The two built-ins are ~10 mm, 0.8 mm uniform spin spacing, single
71water-like tissue (T1 1000 ms, T2 100 ms, Δw 0).
73## Gotchas
75- **h5wasm inlines its WASM** into the main JS bundle (~5 MB raw, ~1.1 MB gz).
76 That's expected; there is no separate `.wasm` asset. It's used on the main
77 thread (loadPhantom); the worker only needs parse+simulate, so the worker
78 chunk stays tiny.
79- **The worker gets plain typed arrays**, not the h5wasm-decoded file — the
80 Phantom is structured-cloned to the worker. Don't move HDF5 decoding into the
81 worker.
82- **Node runs the `.ts` sources directly** (Node ≥ 22.6 type stripping), which
83 is why src imports use explicit `.ts` extensions (inherited from seqlab).
84- **Regenerating phantoms**: `npm run gen-phantoms`. Change SIZE/SPACING there.
85 Denser phantoms = slower sims; keep the balance.
86- **base: './'** in vite.config so it works from the Pages subpath.
88## Testing
90- `npm run sim-test` — headless physics checks: FID |S(0)| ≈ Σρ and decays at
91 exactly 1/T2; GRE echo peaks at the readout centre. Uses test-data goldens.
92- `npm run build` — tsc typecheck + vite build.
93- Browser verification (h5wasm load, worker run, canvas, drag & drop,
94 IndexedDB persistence) is manual: `npm run dev`.
moveopenescclose