Repository navigation
feat: shift the scan inside the patch and fill what lies outside it with EM-synthesised background - #144
Draft
Hendrik-code wants to merge 1 commit into
Draft
feat: shift the scan inside the patch and fill what lies outside it with EM-synthesised background#144Hendrik-code wants to merge 1 commit into
Hendrik-code wants to merge 1 commit into
Conversation
…ith EM-synthesised background A model trained on sagittal spine slabs has never been shown what lies beside them. nnU-Net centres the ~8 cm slab in a 128 mm patch and pads the rest with 0, so every training window contains spine and everything beyond +-4 cm is a flat slab labelled background. On whole-body axial NAKO VIBE the Ours setup then predicts vertebrae in muscle, gluteals, fat and bowel: vertebra instance precision 0.01 with ~100 false-positive components per subject, and 60% of the false-positive voxels more than 5 cm from the spine. That is missing context, not a contrast failure, and no intensity augmentation reaches it. `RandomFOVExtensionGPU` (GEO, placed right after `RandomAffineGPU`) does, per selected sample: * shifts image and mask by the same whole-voxel offset, up to `shift` of the patch per axis, so the spine reaches the window edge or leaves it; * fills everything that is not scan -- the exact-`pad_value` padding plus the vacated area -- SynthSeg-style: EM splits the patch's own background (label 0, beyond `margin` of any label) into 3-10 intensity regions, a majority filter and a random diffeomorphic field post-process the region map, warped copies of it are composited into the fill, and each region is rendered from its own mean and std with spatially correlated texture, then blurred as SynthSeg blurs. The fill's label is 0. Row selection is pinned per sample: without a `pipeline` section the trainer forces `same_on_batch=True` onto every child, which would make `p` one coin per batch and could take the spine out of both samples of a step. The EM is a batched, weighted form of `_em_gmm_1d` with no boolean indexing and no host read. Its moment accumulation is a broadcast product rather than `resp.mT @ design`: cuBLAS takes ~7.5 ms for that reduction shape, the product ~0.04 ms. `transform_params_one-sequence-to-segment-them-all-fov.json` (ae065824) is the paper's Ours config (c69b9872) plus the transform at its defaults and nothing else. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
Why
A model trained on a narrow field of view has never been shown what lies outside it. On whole-body axial NAKO VIBE, the SpineGAN-trained models lose most of their Dice to false positives far from the spine. The contrast augmentations are not failing here: the network has never been asked to reject that context.
Diagnosis, measured on
Ours (fp32)(paper configc69b9872on01bf0b3), NAKO VIBE test split:The cause is the training geometry. Preprocessed 803 has a median of 85 (LR) × 297 × 187 voxels at 1 mm, with a 128³ patch. The foreground leaves only 22 mm of LR free, so nnU-Net centres the slab and pads the rest:
What
RandomFOVExtensionGPUis a new GEO transform insmauglab/transforms/gpu/context.py. It runs directly afterRandomAffineGPUand before every intensity transform. For each selected sample:shiftof the patch per axis. Atshift: 1.0the spine can leave the window entirely.pad_value) plus whatever the shift vacated.marginof any label and off the padding seam are used.random_svf_field) post-process the region map.layersrandomly rotated, scaled, mirrored and translated copies of the map are composited into the fill.Everything outside the fill region is the shifted input, bit for bit, and with
shift: 0only the padding changes.Other details:
pipelinesection the trainer forcessame_on_batch=Trueonto every child, which would makepone coin per batch and could empty both samples of a step of spine._em_gmm_1dwith no boolean indexing and no host read.resp.mT @ design: 0.04 ms against ~7.5 ms on this shape. Without it the transform cost 190 ms per batch.synthseg.functional._em_gmm_1dhas the same pattern and probably the same cost. It is untouched here and left for a separate PR.transform_params_one-sequence-to-segment-them-all-fov.json(ae065824) isc69b9872plus the transform at its defaults (shift 0.5,regions [3, 10],margin 4,layers 6,p 0.5) and nothing else.all_augmentations.jsonare regenerated.Checks
pytest unit_tests -m "not slow"(CPU): 485 passed, 1574 subtests.mainin the same venv: 468 passed, 1534 subtests.test_fov_extension.pycover:same_on_batch=True;-m slow: 3 passed, 3 skipped (no GPU visible).mypy smauglab/reports no issues, run locally with mypy 2.1.0 (CI pins 2.4.0).p=0.5against the paper config: 56 vs 26 ms per batch, including host-device copies.Known limits
Evaluation (running; results will be added here before this leaves draft)
Dataset803, fold 0, A40, against
ms0_base(the paper config on currentmain) andOurs (fp32):c69b9872fov_fillRandomFOVExtensionGPUwithshift 0(only the padding is filled)fov_shift050shift 0.5(= the shipped config)fov_shift100shift 1.0(spine-free windows are frequent)ms0_basevsOurs (fp32)replicate gap;🤖 Generated with Claude Code