5f2d2b3mri-scanner: in-browser Bloch simulation of pulseq sequencesJeremy Magland 1import { useEffect, useMemo, useRef, useState } from 'react'
2import type { RawSignal } from '../sim/simulate.ts'
3import { KSPACE_MODES, renderKspace } from './kspace.ts'
4import type { KspaceMode } from './kspace.ts'
6export function KspaceView({ signal, elapsedMs }: { signal: RawSignal; elapsedMs: number }) {
7 const [mode, setMode] = useState<KspaceMode>('log-magnitude')
8 const canvasRef = useRef<HTMLCanvasElement>(null)
10 const image = useMemo(() => renderKspace(signal, mode), [signal, mode])
12 useEffect(() => {
13 const canvas = canvasRef.current
14 if (!canvas) return
15 canvas.width = image.width
16 canvas.height = image.height
17 const ctx = canvas.getContext('2d')
18 if (!ctx) return
19 const imageData = ctx.createImageData(image.width, image.height)
20 imageData.data.set(image.rgba)
21 ctx.putImageData(imageData, 0, 0)
22 }, [image])
24 const totalSamples = signal.re.length
26 return (
27 <div className="kspace">
28 <div className="kspace-toolbar">
29 <div className="segmented">
30 {KSPACE_MODES.map((m) => (
31 <button
32 key={m.id}
33 className={m.id === mode ? 'active' : ''}
34 onClick={() => setMode(m.id)}
35 type="button"
36 >
37 {m.label}
38 </button>
39 ))}
40 </div>
41 </div>
42 <div className="kspace-canvas-wrap">
43 <canvas ref={canvasRef} className="kspace-canvas" />
44 </div>
45 <div className="kspace-info">
46 <span>
47 {signal.numReadouts} readouts × {signal.maxSamplesPerReadout} samples
48 </span>
49 <span>{totalSamples.toLocaleString()} complex points</span>
50 <span>
51 {image.scale.label
52 ? `${image.scale.label}: ${image.scale.min.toFixed(0)} … ${image.scale.max.toFixed(0)}`
53 : `scale: 0 … ${image.scale.max.toPrecision(3)}`}
54 </span>
55 <span>simulated in {(elapsedMs / 1000).toFixed(1)} s</span>
56 </div>
57 </div>
58 )
59}