Skip to content

Stop reading VisuCorePosition's leading size as a slice count - #210

Merged
headmeister merged 3 commits into
isi-nmr:masterfrom
gdevenyi:fix/visucore-position-cardinality
Aug 10, 2026
Merged

Stop reading VisuCorePosition's leading size as a slice count#210
headmeister merged 3 commits into
isi-nmr:masterfrom
gdevenyi:fix/visucore-position-cardinality

Conversation

@gdevenyi

@gdevenyi gdevenyi commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #195.

Stacked on #209 (fix/extent-slice-axis) — both touch the extent recipe, so this branch contains that commit too. Merge #209 first and this diff reduces to its own commit.

Spec 7.4:

  • Not frame-group dependent — it may have no value, exactly one value, or VisuCoreFrameCount values. […]

So a reader must not assume "one per slice" for any of these: check the actual count against VisuCoreFrameCount and the group lengths, and broadcast when only one value is present.

is_single_slice, extent and resolution all branched on #VisuCorePosition.size[0] > 1 as though it were a slice count, then took position[1] - position[0] as the step. An FG_ISA parameter-map PROCNO writes one position per map, all identical — so the step was the difference between two copies of one position, exactly zero.

resources/testdata/pv360/full/std_data/T2star_map_MGE/pdata/2
  FrameCount 6, VisuCoreDim 2, VisuFGOrderDesc [[6,'FG_ISA','Parameters',0,2]]
  VisuCorePosition: 6 rows, 1 unique;  VisuCoreFrameThickness [1 1 1 1 1 1]

before:  extent [20, 20, 0.0]   resolution [0.078, 0.078, 0.0]   shape (256,256,6)
after :  extent [20, 20, 1.0]   resolution [0.078, 0.078, 1.0]   shape (256,256,1,6)

Change

  • is_single_slice tests whether the positions actually differ, not how many there are.
  • extent and resolution follow is_single_slice instead of re-deriving it.
  • VisuCoreFrameThickness is read per frame — it is written once per frame on these datasets, and np.array([e0, e1, <six values>]) does not build a vector.

Ten reconstructions change, all of them the FG_ISA parameter maps; they now report the recorded 0.8 or 1.0 mm thickness and their maps sit on the singleton slice axis every other single-slice dataset already has. No other reconstruction of the 514 in the corpus changes.

The d3proc fixture in test_latent_conformance gave three FG_SLICE frames one repeated position, which no ParaVision file does; it now steps them.

Test

test_repeated_positions_are_one_slice_not_a_stack — a synthetic six-map FG_ISA PROCNO with repeated positions and per-frame thickness. Fails on master.

Suite: 2144 passed, 12 skipped.

gdevenyi and others added 3 commits August 8, 2026 16:33
`ruff format --check` reported twelve files as unformatted, so any change
touching one of them dragged unrelated reflow into its diff. Run the
formatter once, on its own, so subsequent changes show only what they
actually change.

Formatting only: no behaviour changes, and `ruff check` is clean before and
after. The suite is unchanged at 2140 passed, 12 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017SNrm3jFhpTPShP8xGkePC
The extent recipe built its third component from
abs(VisuCorePosition[1,2] - VisuCorePosition[0,2]) -- the patient-z component
of the step between two slice centres -- and multiplied it by the number of
positions plus one.

Spec 7.2 makes VisuCorePosition a 3-vector in patient coordinates, and spec
5.4/12 let the slice normal point anywhere: ACQ_slice_orient ranges over
sagittal, coronal and Arbitrary_Oblique, and the normal is the third row of
VisuCoreOrientation. Projecting onto z alone therefore reports exactly zero
for every coronal, sagittal or oblique stack, and the +1 overstates the
axial ones by one slice.

Across the pv5, pv6, pv7 and new-orientation corpora all 77 multi-slice 2-D
reconstructions were wrong: 14 reported 0.0, the other 63 were long by one
slice thickness. Both now report n * step, the length of the whole
difference -- which is what the sibling resolution recipe and
Dataset.slice_distance already use, and which keeps extent = size *
resolution as it is in plane.

The corpus property references move with it, 54 extent values in four files.

Closes isi-nmr#194

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017SNrm3jFhpTPShP8xGkePC
is_single_slice, extent and resolution branched on
#VisuCorePosition.size[0] > 1 as though it were the number of slices, then
took position[1] - position[0] as the slice step.

Spec 7.4's cardinality rule forbids that: a parameter that is not tied to a
frame group may carry no value, exactly one value, or VisuCoreFrameCount
values, and "a reader must not assume 'one per slice' for any of these". An
FG_ISA parameter-map PROCNO writes one position per map, all identical, and
is not in VisuGroupDepVals at all -- so the >1 branch fired and the "slice
step" was the difference between two copies of one position, exactly zero.
Ten reconstructions in the corpus reported a slice resolution of 0.0.

What makes a dataset multi-slice is the positions differing, so test that
instead, and let extent and resolution follow is_single_slice rather than
re-deriving it. VisuCoreFrameThickness is read per frame for the same reason:
it is written once per frame on these datasets, and np.array([e0, e1, <six
values>]) does not build a vector.

The ten affected reconstructions now report the recorded 0.8 or 1.0 mm slice
thickness, and their maps sit on the singleton slice axis every other
single-slice dataset already has. No other reconstruction of the 514 in the
corpus changes.

The d3proc fixture in test_latent_conformance gave three FG_SLICE frames one
repeated position, which no ParaVision file does; it now steps them.

Closes isi-nmr#195

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017SNrm3jFhpTPShP8xGkePC
@gdevenyi
gdevenyi force-pushed the fix/visucore-position-cardinality branch from b1b4e79 to a51ac1c Compare August 8, 2026 20:51
@gdevenyi

gdevenyi commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto #213 (ruff format), so this branch carries that commit until #213 merges — merge #213 first and this diff reduces to its own change. Verified after the rebase: ruff check and ruff format --check clean, full suite green.

Integration check across all eleven conformance PRs merged together: 2164 passed, 12 skipped; corpus load test 3202 → 3207 of 3478 with zero newly broken datasets.

@headmeister
headmeister merged commit 5b759aa into isi-nmr:master Aug 10, 2026
7 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.

resolution/extent treat VisuCorePosition's leading size as a slice count, so FG_ISA parameter maps get slice resolution 0.0 (spec 7.4)

2 participants