1// Numeric details of one selected block: every event it references, with
2// the library parameters spelled out in display units.
3import type { ParsedSeq } from '../seq/types.ts'
4import type { Reconstructed } from '../seq/reconstruct.ts'
5import { formatNumber, formatTime, GAMMA } from './format'
7export interface BlockInspectorProps {
8 seq: ParsedSeq
9 rec: Reconstructed
10 blockIndex: number
11 onNavigate: (index: number) => void
12}
14const RF_USE: Record<string, string> = {
15 e: 'excitation',
16 r: 'refocusing',
17 i: 'inversion',
18 s: 'saturation',
19 p: 'preparation',
20 o: 'other',
21 u: 'undefined',
22}
24function gradCell(seq: ParsedSeq, id: number): React.ReactNode {
25 if (id === 0) return <span className="muted">—</span>
26 const g = seq.grads.get(id)
27 if (!g) return <span className="muted">missing #{id}</span>
28 const mTm = (g.amp / GAMMA) * 1e3
29 if (g.kind === 'trap') {
30 return (
31 <>
32 trap #{g.id}: {formatNumber(g.amp, 6)} Hz/m ({mTm.toFixed(2)} mT/m), rise{' '}
33 {formatTime(g.rise, 3)}, flat {formatTime(g.flat, 3)}, fall {formatTime(g.fall, 3)}
34 {g.delay > 0 ? `, delay ${formatTime(g.delay, 3)}` : ''}
35 </>
36 )
37 }
38 const shape = seq.shapes.get(g.shapeId)
39 return (
40 <>
41 arbitrary #{g.id}: peak {formatNumber(g.amp, 6)} Hz/m ({mTm.toFixed(2)} mT/m), shape #
42 {g.shapeId} ({shape?.numSamples ?? '?'} samples)
43 {g.timeShapeId !== 0 ? `, time shape #${g.timeShapeId}` : ''}
44 {g.delay > 0 ? `, delay ${formatTime(g.delay, 3)}` : ''}
45 </>
46 )
47}
49export default function BlockInspector({ seq, rec, blockIndex, onNavigate }: BlockInspectorProps) {
50 const block = seq.blocks[blockIndex]
51 const span = rec.blockSpans[blockIndex]
52 if (!block || !span) return null
54 const rf = block.rfId !== 0 ? seq.rf.get(block.rfId) : undefined
55 const adc = block.adcId !== 0 ? seq.adcs.get(block.adcId) : undefined
57 // Resolve the extension linked list into readable entries
58 const extEntries: string[] = []
59 let extId = block.extId
60 let guard = 0
61 while (extId !== 0 && guard++ < 100) {
62 const ref = seq.extensions.get(extId)
63 if (!ref) break
64 const spec = seq.extensionSpecs.find((s) => s.typeId === ref.type)
65 if (spec) {
66 const row = spec.rows.find((r) => Number(r[0]) === ref.ref)
67 extEntries.push(`${spec.name} ${row ? row.slice(1).join(' ') : `#${ref.ref}`}`)
68 } else {
69 extEntries.push(`type ${ref.type} #${ref.ref}`)
70 }
71 extId = ref.next
72 }
74 return (
75 <div className="inspector">
76 <div className="nav">
77 <button onClick={() => onNavigate(blockIndex - 1)} disabled={blockIndex <= 0}>
78 ←
79 </button>
80 <button
81 onClick={() => onNavigate(blockIndex + 1)}
82 disabled={blockIndex >= seq.blocks.length - 1}
83 >
84 →
85 </button>
86 <span className="where">
87 block {block.id} of {seq.blocks.length} · start {formatTime(span.start)} · duration{' '}
88 {formatTime(span.duration)}
89 </span>
90 </div>
91 <table>
92 <tbody>
93 <tr>
94 <th>RF</th>
95 <td>
96 {rf ? (
97 <>
98 #{rf.id}: {RF_USE[rf.use] ?? rf.use}, peak {formatNumber(rf.amp, 5)} Hz, delay{' '}
99 {formatTime(rf.delay, 3)}
100 {Number.isFinite(rf.center) ? `, center ${formatTime(rf.center, 3)}` : ''}
101 {rf.freq !== 0 ? `, freq ${formatNumber(rf.freq, 5)} Hz` : ''}
102 {rf.phase !== 0 ? `, phase ${formatNumber(rf.phase, 5)} rad` : ''}
103 {`, shapes m#${rf.magShapeId}/p#${rf.phaseShapeId}` +
104 (rf.timeShapeId !== 0 ? `/t#${rf.timeShapeId}` : '')}
105 </>
106 ) : (
107 <span className="muted">—</span>
108 )}
109 </td>
110 </tr>
111 <tr>
112 <th>GX</th>
113 <td>{gradCell(seq, block.gxId)}</td>
114 </tr>
115 <tr>
116 <th>GY</th>
117 <td>{gradCell(seq, block.gyId)}</td>
118 </tr>
119 <tr>
120 <th>GZ</th>
121 <td>{gradCell(seq, block.gzId)}</td>
122 </tr>
123 <tr>
124 <th>ADC</th>
125 <td>
126 {adc ? (
127 <>
128 #{adc.id}: {adc.num} samples, dwell {formatNumber(adc.dwell * 1e9, 6)} ns, delay{' '}
129 {formatTime(adc.delay, 3)}
130 {adc.freq !== 0 ? `, freq ${formatNumber(adc.freq, 5)} Hz` : ''}
131 {adc.phase !== 0 ? `, phase ${formatNumber(adc.phase, 5)} rad` : ''}
132 </>
133 ) : (
134 <span className="muted">—</span>
135 )}
136 </td>
137 </tr>
138 <tr>
139 <th>Extensions</th>
140 <td>
141 {extEntries.length > 0 ? extEntries.join(' · ') : <span className="muted">—</span>}
142 </td>
143 </tr>
144 </tbody>
145 </table>
146 </div>
147 )
148}