Drive an archive from a printout, and check it on a scanner - #16
Merged
Conversation
The capabilities archive was rebuilt as CAPABILITIES_TEST and came back as CAPABILITIES_TEST, so generate.rename does what it claims where it counts. The rest of the round trip is worth recording as much as the name. All 54 scans returned in their original order, all 25 edits held, all ten links came back with the same source, target and group, and their eleven copies were still byte-identical. 44 of the 54 protocols were byte-identical in ASCCONV; nine differed only in the derived scan times Manifest.stale already names. rslh_ep3d_vaso reproduced the same three coupled formulas to the digit -- alTI[0] = 24*TR + 10320, alTI[1] = 72*TR + 10320, alTR[1] = 96*TR + 20640. This was a rebuild with every GUID regenerated rather than a re-export of the file already loaded, so the console derived them again from scratch: two independent passes agreeing, not one observation repeated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Driving an archive from its own PDF must write nothing. That check has been here a while on one archive, and it is the strongest one available offline -- units, scales, the derived basis, sparse arrays and change detection at once. Run across every console-authored pair it caught Table Position writing a sign flip on the nineteen scans holding -32. The first diagnosis was wrong in an instructive way. No export prints a negative table position across 300 comparisons, so the printed value looked like a magnitude with the sign unrecoverable, and the mapping looked like it had to go. It does not: a Siemens printout gives a coordinate as a magnitude beside a direction letter -- F32 for a protocol holding -32 -- and on a two-column card the letter lands in a field of its own, which the parser names "Table Position #2". The number alone is a magnitude because the sign is in the letter, not because it is missing. Supplied by the user; nothing in the corpus would have said so, since reading the number and reading the pair agree wherever the value is positive. Mapping.sign_from names that companion field. L, P, F and I are the negative halves of the three axes and R, A, S and H the positive ones; a letter that is neither is refused rather than read as positive by default, since the alternative is a silent flip on a release spelling one differently. H against a non-negative stored value and F against a negative one, on all 300 comparisons, none against. Five pairs remain named exceptions where printout and storage legitimately disagree -- four scanner returns, where an off-grid value is stored faithfully and displayed snapped, and the 31P export, whose spectroscopy scan prints 400 under FOV Phase where the stored ratio is 100% of a 400 mm read FOV. A second test requires each to still write something, so an exception that stops being one is removed rather than left hiding the next offender. Two refusals in the driver's manifest were untrue rather than honest. expand searched for the mapped field across array indices, so a sparse member like sGroupArray.asGroup[0].dDistFact resolved to nothing while its group sat right there. And lRepetitions is sparse -- absent on 585 corpus scans, which is one measurement -- so Measurements was unwritable on every one of them. Coverage goes from a tenth of printed parameters to about 30%, and the driver's output now has an answer key: driving Potpourri_P1 with the Potpourri_P1_changed printout against the console's own edit of the same protocol gives 158 fields matching exactly, 173 left alone as unmapped, and 0 wrong. The 68 that differ are the two documented classes -- 67 dPhaseFOV within 0.083 mm of the console's quantised ratio, and one alFree[0] differing in bit 29, which no mapping claims. Also folds in the confirmation that the renamed protocol round-tripped: CAPABILITIES_TEST arrived and returned under its own name, 54 scans, 25 edits and 10 links intact, with rslh_ep3d_vaso reproducing the same three coupled formulas on a rebuild with every GUID regenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A scanner loaded the first archive built end to end by the exar driver and
returned it. All 18 scans loaded, none greyed out, and all 226 ASCCONV fields
the driver wrote came back byte-identical -- and that comparison could never
have found what was actually wrong, because the defect was in fields nobody
wrote.
Slice Thickness went 2.3 to 2.2 across all 64 elements of a CMRR EPI while
sPosition kept describing the 2.3 mm geometry, putting the outermost slices
(64 - 1) / 2 * 0.1 = 3.15 mm from where they belong. The console recomputes
all 64 on its own edit of the same protocol; we did not. The scanner accepted
the scan anyway, printed no complaint and returned the array untouched, so its
consistency check does not cover the slice array against its group inputs --
which makes "it loaded" a weaker signal than the greying-out rule suggests.
build.recentre rebuilds an array that this write invalidated, leaves one that
arrived broken alone, and skips multi-group arrays that read_group declines to
describe. Against the same answer key that is 201 fields exact where it was
158: 43 slice positions became right, and 85 moved out of the inherited column
to carry the template's group centre visibly rather than silently, since the
printed Position is still unmapped.
The return ships as examples/XA60/driver_loadtest.{exar1,pdf} with the bad
array intact, pinned as KNOWN_INCONSISTENT_ARRAYS rather than excluded, so a
second one fails. With its printout beside it the pair joins the self-drive
sweep -- 18 scans matched, 0 values written, the first time that invariant runs
against a protocol this package wrote -- and adds 50 comparisons to the flag
sweep against a card the scanner printed from what we stored.
Measured at the printout, 80 of the 87 parameters the console changed were
reproduced. Six of the seven misses are Special-card parameters nothing maps;
Bandwidth is neither the target nor the template because the console derived
it from the base resolution and FOV we did change. The seventh is Averaging,
which is mapped for tfl_mgh_multiecho and refused on tfl_mgh_epinav_ABCD --
correctly, since a sWipMemBlock index means whatever its own sequence says.
BuildReport.out_of_scope now names that class instead of burying it among the
forty parameters nothing has ever looked at; covered_elsewhere asks the mapping
table rather than parsing resolve's prose.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three commits taking the
exardriver from "produces a file" to "produces a file a scanner accepts", and fixing what the scanner's return exposed.The driver's output has now been loaded
examples/XA60/driver_loadtest.{exar1,pdf}isPotpourri_P1driven by thePotpourri_P1_changedprintout, loaded on an XA60 scanner and re-exported. 18 scans in, 18 out, running order preserved, program still namedDRIVER_TEST, nothing greyed out.That is what the answer-key comparison could not establish. Only ~30% of what a protocol prints has a verified mapping, so the result is a hybrid — 226 fields from the printout beside 173 inherited from the template — and a hybrid of two consistent parameter sets is not itself consistent. For this protocol it is.
insert_ascconvis validated on a scanner for the first time:sWipMemBlock.adFree[3]is absent from the template, was created between its siblings, and came back in place with the console's spelling.And it exposed a defect that field-by-field agreement could not
Slice Thicknesswent 2.3 → 2.2 across all 64 elements of a CMRR EPI whilesPositionkept describing the 2.3 mm geometry — the outermost slices(64-1)/2 × 0.1= 3.15 mm from where they belong. The console recomputes all 64 on its own edit; we did not.The comparison above agreed on all 226 written fields and could never have found this, because the defect is in fields nobody wrote: correct in the template, correct in what we sent, correct in what came back, and wrong relative to the thickness beside them. The corpus sweep found it, which is the argument for shipping the return as an example.
The scanner did not catch it either. The scan loaded, was not greyed out, printed no complaint, and returned the array untouched. So the console's consistency check does not cover the slice array against its group inputs —
Include Nav. = Offgets refused, an incoherent 64-slice group does not. "It loaded" is a weaker signal than the greying-out rule suggests.build.recentrerebuilds an array this write invalidated, leaves one that arrived broken alone, and skips multi-group arrays. Against the same answer key: 201 fields exact, up from 158.The return keeps its bad array, pinned as
KNOWN_INCONSISTENT_ARRAYSrather than excluded, so a second one fails.Reading the direction letter
A Siemens printout gives a coordinate as a magnitude beside a direction letter —
F32for a stored-32— and on a two-column card the letter lands in a field of its own.Table Positionwas reading the number alone, which flipped the sign on every scan whose position is negative.Mapping.sign_fromnow reads the letter:Hagainst non-negative,Fagainst negative, on all 300 corpus comparisons, and a letter that is neither is refused rather than defaulted.Also fixed:
expandsearched for the mapped field across array indices instead of asking which elements exist, so a sparse member likedDistFactwas refused with a reason that was not true; andlRepetitionswas not marked sparse, makingMeasurementsunwritable on 585 scans. Both surfaced from the manifest reporting refusals.Coverage the pair adds
With its printout beside it,
driver_loadtestjoins two corpus sweeps:One actionable gap named
Averagingis mapped fortfl_mgh_multiechoand printed bytfl_mgh_epinav_ABCD, so the driver refuses it — correctly, since asWipMemBlockindex means whatever its own sequence says. DecodingalFree[4]with the same table nonetheless agrees on 37 of 37 corpus scans across both sequences. That is evidence of the wrong kind: printout-against-stored tests the decoder, since the console displays what it stores. Settling it needs an option scan varyingAveragingon the second sequence.BuildReport.out_of_scopenow names that class instead of burying it among the forty parameters nothing has looked at.build.covered_elsewhereasks the mapping table, notresolve's prose, so rewording a reason cannot reclassify anything.Verification
recentre's three branches, and the manifest classification🤖 Generated with Claude Code