Skip to content

Nest the object loop inside the phase loop in SchemaRawdata.to_kspace - #211

Merged
headmeister merged 2 commits into
isi-nmr:masterfrom
gdevenyi:fix/rawdata-kspace-loop-nesting
Aug 10, 2026
Merged

Nest the object loop inside the phase loop in SchemaRawdata.to_kspace#211
headmeister merged 2 commits into
isi-nmr:masterfrom
gdevenyi:fix/rawdata-kspace-loop-nesting

Conversation

@gdevenyi

@gdevenyi gdevenyi commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #192.

The 2-D branch reshaped the PV360 scan stream as (readout, receivers, phase, objects, repetitions) in Fortran order — i.e. it read the scan index as phase + Nphase*object.

Spec 5.2, Acquisition loop nesting (default):

NS > ACQ_phase_factor > NSLICES > NI or NSLICES > NA > ACQ_size[1]/ACQ_phase_factor
   > ACQ_size[1] > ACQ_size[2] > NAE > NR

The object level is inside the phase-encode-group level, so consecutive scans step the object first. The library's own fid path already has this right (properties_fid_core.json, CART_2D), so to_kspace disagreed with both the spec and the rest of the library — and ACQ_phase_factor was missing from the layout entirely, mis-nesting RARE-style echo trains too.

Nothing raised: the array kept its shape and dtype, so every 2-D multi-slice PV360 job came back with its slice and phase-encode indices interleaved.

Evidence

k-space centre, mean |k| along the phase axis (slice 0 / channel 0):

scan before after
T1_FLASH 160/288, peak/median 1.26 144/288, 6.46
T1_RARE 170/256, 1.79 128/256, 4.06
T2_TurboRARE 143/256, 3.44 128/256, 10.63
T2map_MSME 187/192, 2.37 96/192, 10.44

Zero-filled sum-of-squares reconstruction correlated against the vendor 2dseq, per slice:

scan before after
T1_FLASH 0.07 – 0.36 0.47 – 0.56
T1_RARE 0.03 – 0.17 0.62 – 0.99
T2_TurboRARE 0.06 – 0.22 0.61 – 0.77

The 3-D branch is unchanged — with NI = 1 there is no object level to interleave.

Test

test_to_kspace_nests_the_object_loop_inside_the_phase_loop builds a synthetic PV360 job whose every scan carries 1000*object + group, so a misplaced sample is visible, and asserts each lands at k_space[:, group, object]. Fails on master.

Suite: 2141 passed, 12 skipped.

gdevenyi and others added 2 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 2-D branch reshaped the PV360 scan stream as
(readout, receivers, phase, objects, repetitions) in Fortran order, which
reads the scan index as phase + Nphase*object.

Spec 5.2 "Acquisition loop nesting (default)" puts it the other way round:

    NS > ACQ_phase_factor > NSLICES > NI or NSLICES > NA
       > ACQ_size[1]/ACQ_phase_factor > ACQ_size[1] > ACQ_size[2] > NAE > NR

The object level is INSIDE the phase-encode-group level, so consecutive scans
step the object first. The library's own fid path already has this right --
properties_fid_core.json CART_2D encoding_space is
(EncMatrix[0], EncNReceivers, ACQ_phase_factor, NI, EncMatrix[1]//ACQ_phase_factor, NR)
-- so to_kspace disagreed with both the spec and the rest of the library, and
ACQ_phase_factor was missing entirely, mis-nesting RARE-style echo trains too.

Nothing raised: the array kept its shape and dtype, so every 2-D multi-slice
PV360 job -- all of Bruker's standard FLASH/RARE/MSME/MGE protocols -- came
back with its slice and phase-encode indices interleaved.

Adopt the fid nesting. On the PV360 standard data the phase profile now peaks
at exactly N/2 for every 2-D scan, where before it was flat and off-centre:

    T1_FLASH        160/288, peak/median 1.26  ->  144/288, 6.46
    T1_RARE         170/256, 1.79              ->  128/256, 4.06
    T2_TurboRARE    143/256, 3.44              ->  128/256, 10.63
    T2map_MSME      187/192, 2.37              ->   96/192, 10.44

and a zero-filled sum-of-squares reconstruction correlates with the vendor
2dseq far better:

    T1_FLASH      0.07-0.36  ->  0.47-0.56
    T1_RARE       0.03-0.17  ->  0.62-0.99
    T2_TurboRARE  0.06-0.22  ->  0.61-0.77

The 3-D branch is unchanged; with NI = 1 there is no object level to interleave.

Closes isi-nmr#192

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/rawdata-kspace-loop-nesting branch from e4d671b to a158f8c 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 8aa0c89 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.

SchemaRawdata.to_kspace inverts the slice/phase acquisition-loop nesting, scrambling every 2-D multi-slice PV360 rawdata job (spec 5.2, 13.1)

2 participants