Skip to content

Decode the Special card from the option-scan exports - #5

Merged
bbfrederick merged 1 commit into
mainfrom
exar1-special-card
Aug 28, 2026
Merged

Decode the Special card from the option-scan exports#5
bbfrederick merged 1 commit into
mainfrom
exar1-special-card

Conversation

@bbfrederick

Copy link
Copy Markdown
Owner

The Special card is sequence-private scratch memory, so nothing about it can be
read off a single export. sWipMemBlock.alFree[0] is MT Flip Angle on
can_neuromelanin and a packed word of checkbox flags on CMRR's multiband
sequences; alFree[1] is Readout polarity on tfl_mgh_epinav_ABCD and Protocol
filename on ep_moco_nav_set_ABCD; alFree[12] is Nav. location on the MPRAGE
navigator and Include Nav. on the SPACE one. Learning an index means changing one
option and exporting again.

The three option-scan archives are exactly that, batched: each holds one sequence
repeated with a single option altered per copy -- 33, 7 and 31 scans -- so diffing
each against its group's baseline gives one labelled change against one moved
field.

What that pinned

Three encodings, all now supported by the patch layer:

  • Direct values -- Feedback Delay, Remeasure, Reacq. threshold.
  • Small-integer enums, fully enumerated. Averaging is 1=None, 2=Linear,
    3=RMS, 4=RMS only, 5=Mean; also Moco ref. image, Apply moco to, K-space
    streaming, Nav. location, Protocol filename, Gradient spoiling.
  • CMRR's fourteen-bit flags word in alFree[0], consistent across its BOLD,
    SE and diffusion sequences: bit 0 Single-band images, 1 PF omits higher
    k-space, 4 SENSE1 coil combine, 8 Invert RO/PE polarity, 9 MB RF phase
    scramble, 10 Time-shifted MB RF, 12 MB LeakBlock kernel, 16 MB dual kernel,
    18 Disable freq. update, 20 Force equal slice timing, 22 Opt. MB RF pulse BW,
    25 Suppress 16-bit DICOM, 27 Disable B1 control loop, 28 Force GPA balance.

Mappings go from 15 to 41. The model grows three axes the data forced: enums,
bit flags, and per-sequence scope that a test now requires of anything touching
sWipMemBlock.

tests/test_exar_patch.py replays all 62 single-option toggles through the
patcher and requires every one to reproduce the console.

Two things only the option scans could reveal

sWipMemBlock arrays are sparse. An assignment holding zero is not written
at all: a CMRR protocol with every box unticked has no alFree[0] line, and
setting Remeasure to zero deleted its line rather than writing 0. Elements
run in ascending index order, so creating one means inserting it among its
siblings. A patcher that only overwrites can never turn on the first flag in a
word -- the case a user hits on a fresh protocol. Ten of the sixty-two replays
refused until insert and remove were added.

A flags word carries bits no mapping claims, so comparing whole words
against the console is wrong. The Potpourri edit toggled "Echoes in separate
series", which no option scan pins, and it shares alFree[0] with fourteen that
are. Compare the bit a mapping claims.

A reader bug the MEMPRAGE export exposed

A scan name lives in a locale table on an EdfString node, and the key is not
always the same: most archives use "" for the default, that one uses "en"
with no empty key at all. Reading only "" returned a nameless tree, which
looks like a reader that cannot find the labels rather than one looking under
the wrong key.

Corpus

Renames Potpourri to Potpourri_P2 and adds Potpourri_P1 from a second
scanner, its many-parameter changed pair with its own PDF, the three option
scans, and three spectroscopy protocols. 47 snapshots, 877 scans. The pinned
unaccounted set gains four spectroscopy scans, named individually rather than
counted.

Verification

1552 passed, 2 skipped
black / isort / codespell     clean

The two skips are pre-existing: two examples have no table of contents page.

Not yet done

No patched protocol has been loaded onto a scanner. Everything here is verified
against what the console writes, and nothing against what the loader
accepts -- a different claim. We keep existing GUIDs where the console
regenerates all 67, add a Content row without creating a ChangeSet, and
compress edited blobs with our own zlib. Any of those could be rejected, and no
test in this repo can tell.

Coverage is also worth stating plainly: 214 of the 2276 parameters Potpourri's
18 scans print are currently writable, about 9%.

🤖 Generated with Claude Code

The Special card is sequence-private scratch memory, so nothing about it can
be read off a single export: sWipMemBlock.alFree[0] is MT Flip Angle on
can_neuromelanin, a packed word of checkbox flags on CMRR's multiband
sequences, and alFree[1] is Readout polarity on tfl_mgh_epinav_ABCD but
Protocol filename on ep_moco_nav_set_ABCD. Learning an index means changing
one option and exporting again.

The three option-scan archives are exactly that, batched: each holds one
sequence repeated with a single option altered per copy -- 33, 7 and 31 scans
-- so diffing each against its group's baseline gives one labelled change
against one moved field. That yields three encodings. Values written directly
(Feedback Delay, Remeasure, Reacq. threshold). Small-integer enums, now fully
enumerated -- Averaging is 1=None 2=Linear 3=RMS 4=RMS only 5=Mean. And
CMRR's fourteen-bit flags word, consistent across its BOLD, SE and diffusion
sequences. test_exar_patch replays all 62 toggles and requires every one to
reproduce the console.

Mappings go from 15 to 41, and the model grows three axes the data forced:
enums, bit flags, and per-sequence scope that a test now requires of anything
touching sWipMemBlock.

Two things only the option scans could reveal:

sWipMemBlock arrays are sparse. An assignment holding zero is not written at
all -- a CMRR protocol with every box unticked has no alFree[0] line, and
setting Remeasure to zero deleted its line rather than writing 0. Elements run
in ascending index order, so creating one means inserting it among its
siblings. A patcher that only overwrites can never turn on the first flag in a
word, which is the case a user hits on a fresh protocol; ten of the sixty-two
replays refused until insert and remove were added.

A flags word carries bits no mapping claims, so comparing whole words against
the console is wrong. The Potpourri edit toggled "Echoes in separate series",
which no option scan pins, and it shares alFree[0] with fourteen that are.
Compare the bit a mapping claims.

Also fixes a reader bug the MEMPRAGE export exposed. A scan name lives in a
locale table on an EdfString node, and the key is not always the same: most
archives use "" for the default, that one uses "en" with no empty key. Reading
only "" returned a nameless tree, which looks like a reader that cannot find
the labels rather than one looking under the wrong key.

Renames Potpourri to Potpourri_P2 and adds Potpourri_P1 from a second scanner,
its many-parameter changed pair and PDF, three option scans, and three
spectroscopy protocols. Corpus is 47 snapshots and 877 scans; the pinned
unaccounted set gains four spectroscopy scans, named individually.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bbfrederick
bbfrederick merged commit d891437 into main Aug 28, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant