Harmonize qform/sform of inputs to avoid mislocated segmentation (#44) - #45
Harmonize qform/sform of inputs to avoid mislocated segmentation (#44)#45CedricConday wants to merge 1 commit into
Conversation
…pImg#44) nibabel (used throughout the pipeline) reads img.affine, which prefers the sform when sform_code > 0, while greedy reads the qform. When a prior co-registration (e.g. SPM) updates only the sform, the two disagree and the segmentation is mislocated in FLAIR space. Harmonize both forms to the resolved affine once, on the inputs, before any tool reads them. No-op when the forms already agree. Adds a unit test. Fixes CompImg#44
|
Hey @CedricConday, thank you very much for the PR and contribution. We will have a look asap and integrate with the next version bump! |
A dependency-light QC gate for NIfTI images. The headline check catches the silent qform/sform disagreement that mislocates images in world space (the same class of bug fixed in CompImg/LST-AI#45): tools reading the qform and tools reading the sform place the image differently, with no error raised. Also checks affine validity, voxel anisotropy, direction-cosine shear, non-finite/empty data, and cross-image grid/world-space alignment (T1+FLAIR). CLI exits non-zero on error-severity findings for use as a CI/preprocessing gate. 20 oracle-verified tests: each check has a synthetic NIfTI built to trigger it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011LLkhd8Cv4YfiCNB2SptYi
6b4a00c to
152b2ff
Compare
Port of CompImg#45 (fixes CompImg#44) to the PyTorch branch. A NIfTI stores two affines; nibabel prefers the sform while the v1.x greedy binary read the qform, so inputs whose sform was updated by a prior co-registration (e.g. SPM) produced mislocated segmentations. The fix writes work-dir copies of --t1/--flair with qform and sform both set to nibabel's resolved affine before anything reads them; originals are untouched and consistent inputs pass through unchanged. Beyond the original PR, --existing_seg inputs are harmonized the same way: a mask read in FLAIR space has the same two-affine ambiguity. Controlled end-to-end check on this branch (subject with FLAIR qform shifted 10 mm against a correct sform): the v2 stack does NOT reproduce the v1 mislocation - picsl-greedy 1.4.0 resolves the sform like nibabel, and the un-fixed pipeline already matched the clean-input reference (Dice 0.969). The harmonization therefore lands as defense in depth: a no-op for consistent inputs that removes the ambiguity for every downstream reader. Co-authored-by: Julian McGinnis <julian.mcginnis@tum.de>
|
Adressed in #48, closing this. |
|
Closing as superseded — but merged in substance, with your authorship: the v2.0.0 line (PR #48) includes your fix ported to the PyTorch codebase, committed with you as the git author (2ecc545), extended to One finding from validating it end to end that you'll appreciate, given your PR noted you couldn't run the full pipeline: on the v2 stack the original mislocation no longer reproduces even on a deliberately corrupted input (FLAIR qform shifted 10 mm against a correct sform) — |
Problem (#44)
A NIfTI stores two affines. The pipeline reads images with nibabel's
img.affine, which prefers the sform whensform_code > 0, butgreedy(registration) reads the qform. When an earlier co-registration (e.g. SPM) updates only the sform, the two disagree, so the warped/annotated mask is mislocated in FLAIR space. The reporter confirmed that settingqform = sformresolves it.Quick demonstration of the divergence:
Fix
Add
harmonize_affines()(inutils.py) and call it once on the--t1/--flairinputs, right after the work dir is created, before any tool reads them. It sets both the qform and sform to nibabel's resolved affine, sogreedyand nibabel agree. It is a no-op when the two forms already match, so existing inputs are unaffected.Tests
Added
tests/test_utils.py: one test reproducing the SPM-style mismatch (assertsqform == sform ==resolved affine after harmonization), one asserting it is a no-op (data + affine unchanged) when the forms already agree. Both pass.Note: I validated the affine harmonization in isolation; I do not have a
greedy/GPU setup to run the full pipeline end-to-end, so a sanity check on a real mismatched scan would be worth confirming. The fix matches the reporter's verifiedqform = sformworkaround.Disclosure: authored with AI assistance (Claude Code); reviewed by me.