Skip to content

Add anatomic-consistency vertebrae re-identification (stable replacement for size-based reallocation)#5

Open
farhatmasood wants to merge 1 commit into
BodyMaps:mainfrom
farhatmasood:feat/vertebrae-reidentification
Open

Add anatomic-consistency vertebrae re-identification (stable replacement for size-based reallocation)#5
farhatmasood wants to merge 1 commit into
BodyMaps:mainfrom
farhatmasood:feat/vertebrae-reidentification

Conversation

@farhatmasood

@farhatmasood farhatmasood commented Jul 8, 2026

Copy link
Copy Markdown

Summary

Adds utils/vertebrae_reidentification.py, a spacing-aware, non-destructive re-identification of vertebra levels. It provides a stable replacement for the currently-unfinished size-based reallocate_based_on_size step (marked "TODO Not finished").

Motivation

The dominant error in TotalSegmentator / SuPreM-style vertebra predictions is level mis-identification in the mid / thoraco-lumbar spine: the network segments the bone well but assigns the wrong level (e.g. it under-segments one vertebra and shifts every level above it by one). A per-level Dice therefore collapses in the middle (T11/T9 can drop to ~1%) while the anchored ends (lumbar, cervical) stay high — the signature of a one-level shift. Since Dice punishes deleted true-positive bone, the fix must be re-labelling, not deletion.

Method

  1. Instances. Isolate vertebral-body cores with a physical distance transform and grow them back over all bone by a nearest-core watershed → vertebra instances ordered inferior→superior.
  2. Anchor. Each confident instance votes an offset (model_label − rank); a strong agreement means the count is reliable (the two anchored ends must agree — the anatomic consistency cycle). If unreliable, the case is left as the model.
  3. Minimal-change rebuild. Keep the model's own (Dice-optimal) mask on every already-correct vertebra; rebuild only the mis-identified span — plus a small superior buffer whose model boundaries the compression corrupts — with a watershed of the shifted cores.
  4. Finish. Keep the largest component per vertebra, fill interior holes, and accept only if the result is strictly ordered along the spine (else revert to the model — non-destructive).

Results

On a SuPreM SwinUNETR demo case with a severe mid-thoracic shift, iterating this design lifted the mean vertebra Dice from 73.9% → 89.5% → ~90.5%, recovering the catastrophic levels (e.g. T11 1%→88%, T9 1.5%→88%) while preserving the already-correct ones (L3, T2, T3 at 96–98%). The method matches the standalone reference implementation bit-for-bit.

Changes

  • New: utils/vertebrae_reidentification.py (self-contained; deps already in requirements.txt: numpy, scipy, nibabel, cc3d with a scipy fallback).
  • utils/vertebrae_postprocessing.py: postprocessing_vertebrae() gains a reference_img arg and uses the re-identification when the affine is available; the legacy heuristics are kept as a fallback.
  • main.py: passes the reference image through.
  • docs/functions.md: documents the new function.

Notes

Enable via vertebrae in config.yaml target_organs (no other config change). Fully backward-compatible: with no reference_img, the legacy path runs unchanged.

Copilot AI review requested due to automatic review settings July 8, 2026 18:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new spacing-/orientation-aware vertebra re-identification algorithm and wires it into the existing vertebrae postprocessing flow when a reference image affine is available, while keeping the legacy heuristics as a fallback.

Changes:

  • Added utils/vertebrae_reidentification.py implementing anatomic-consistency vertebra level re-identification and non-destructive relabeling.
  • Updated utils/vertebrae_postprocessing.py to prefer the new re-identification path when reference_img is provided.
  • Passed reference_img through in main.py and documented the new function in docs/functions.md.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
utils/vertebrae_reidentification.py New core algorithm for spacing-aware vertebra level re-identification and post-cleanup (largest CC + hole fill + monotonicity gate).
utils/vertebrae_postprocessing.py Integrates re-identification as the preferred path (affine available), retains legacy heuristics fallback.
main.py Passes reference_img into vertebrae postprocessing to enable affine-aware behavior.
docs/functions.md Documents the new vertebrae re-identification utility.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +181 to +183
for v in range(1, NUM_CLASSES + 1): # never let a level vanish
if (Vs == v).any() and not (out == v).any():
out[Vs == v] = v
Comment on lines 6 to 10
from tqdm import tqdm
from scipy.ndimage import generate_binary_structure
from scipy.ndimage import label, binary_dilation, binary_erosion
from skimage.measure import label, regionprops
from scipy import ndimage
from .vertebrae_reidentification import reidentify_vertebrae_dict


#### @jliu452 postprocessing codes for the vertabreas part
Comment thread docs/functions.md

## Vertebrae re-identification (anatomic-consistency)

`vertebrae_reidentification.reidentify_vertebrae_dict(segmentation_dict, reference_img, logger=None, patient_id="")`
Adds utils/vertebrae_reidentification.py: a spacing-aware, non-destructive
re-identification of vertebra levels that provides a stable replacement for the
unfinished size-based reallocate_based_on_size step.

The dominant error in TotalSegmentator/SuPreM-style vertebra predictions is
level MIS-IDENTIFICATION in the mid/thoraco-lumbar spine: the network segments
the bone well but assigns the wrong level, so per-level Dice collapses in the
middle while the anchored ends stay high.

Method:
- isolate vertebral-body cores via a physical distance transform and grow them
  back over all bone -> ordered instances;
- anchor the sequence by offset-voting (the two ends must agree - the anatomic
  consistency cycle); fall back to the model labels if unreliable;
- keep the model's own (Dice-optimal) mask on every already-correct vertebra and
  rebuild only the mis-identified span (plus a small superior buffer whose model
  boundaries the compression corrupts) with a watershed of the shifted cores;
- keep the largest component per level, fill holes, and accept only if the
  result is strictly ordered along the spine.

Wiring: postprocessing_vertebrae() now takes reference_img and uses the
re-identification when the affine is available (legacy heuristics kept as
fallback); main.py passes the reference image through. Docs updated.
@farhatmasood farhatmasood force-pushed the feat/vertebrae-reidentification branch from 1c47079 to 9fc949e Compare July 8, 2026 18:50
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.

2 participants