# CTSpinoPelvic1K

Spine, pelvis, per-level ribs and femurs in one coordinate frame, for 802 abdominal CT
records — built so that a lumbar vertebra can be identified when the count itself is in
doubt.

Vertebral numbering is conventionally established by counting down from C2. No abdominal CT
contains C2, so at the thoracolumbar junction a thirteenth thoracic vertebra, a rib borne by
a lumbar vertebra, and a stump rib all produce overlapping appearances, and which label is
correct depends on a count the field of view does not support. This release is annotated to
make that decidable where it can be, and to say so plainly where it cannot.

---

## v6 — surgical hardware

Identifiers 76-82 were declared in every previous release and populated in none. In v6 they
are. 11 of the 802 records carry instrumentation:

| id | class | n |
|---|---|---|
| 80 | `hardware_arthroplasty` | 8 |
| 82 | `hardware_osteosynthesis` | 1 |
| 81 | `hardware_si_screw` | 1 |
| 77 | `hardware_cage` | 1 |

**Metal outranks bone.** Where an implant lay inside a vertebra, hip or femur label, the
voxel now belongs to the implant, so naming the hardware reclaimed 1,538,852 voxels rather
than adding them. 80 and 82 are the two arms of one clinical decision and must not be
confused: arthroplasty *replaces a joint*, osteosynthesis holds parts of *the same bone*
together. Pelvic incidence and pelvic tilt are measured from the femoral head, which a
prosthesis replaces and fixation does not.

Case 0068 is also corrected: six lumbar bodies where the pseudolabel had five, renumbered
against the twelfth rib, with T10-T12 added and the vertebral-body mixing resolved.

**Read `KNOWN_ISSUES.md` before analysing.** In particular: a null Castellvi field means
ungraded rather than negative, prone and supine must not be pooled, and instrumented cases
must be excluded from any measurement of the gap between bones.

## What is in this deposit, and what is not

**Included** — about 1.8 GB:

| | |
|---|---|
| `labels.zip` | 802 gzipped NIfTI label volumes, `NNNN_label.nii.gz`; extracts to `labels/` |
| `manifest.json` | one record per case: TCIA series identifiers, demographics, scanner, LSTV label, Castellvi grade |
| `splits_5fold.json` | frozen patient-grouped, LSTV-stratified five-fold cross-validation splits |
| `dataset_labels.json` | the label scheme — identifier to structure name |
| `fetch_from_tcia.py` | rebuilds the image half end to end: download, convert, resample |
| `reconstruct_ct.py` | the resampling step alone, for an existing conversion |
| `KNOWN_ISSUES.md` | what to filter before which analysis; read it first |
| `croissant.json` | MLCommons Croissant metadata — the label scheme, the manifest's fields and the responsible-AI declarations, machine-readable |
| `SHA256SUMS.txt` | checksum for all 810 files — the 802 labels by their extracted path, and the eight loose files |
| `LICENSE` | CC BY-NC-SA 4.0, matching zenodo.json and the licence section below |

Everything except the labels is left unarchived so it can be read on the record page without
downloading anything: the manifest, the label scheme, the splits and this file are all
previewable in place.

**Extract first, then verify.** 1.8 GB that truncated silently looks exactly like 1.8 GB
that did not. The checksums describe the extracted tree rather than the archive, so they
confirm the 802 labels you end up with rather than the container they arrived in —
`labels.zip` itself is deliberately not listed, since it is gone once extracted:

```bash
unzip labels.zip          # -> labels/0001_label.nii.gz ... labels/1154_label.nii.gz
sha256sum -c SHA256SUMS.txt
```

Every path in the rest of this file assumes that extraction has happened, so
`labels/0007_label.nii.gz` means what it says.

**Not included** — the CT images, which are 193 GB against 1.8 GB of labels. There are two
ways to get them. Only the first works today.

**Start here — rebuild from TCIA.** This is the route that works today, and the one that
lasts: the images are already public in
[The Cancer Imaging Archive](https://www.cancerimagingarchive.net/), which is the archive of
record and will outlive any mirror.

What the source collections never published — and what this deposit does — is **the mapping
from each annotation to the CT series it belongs on.** `manifest.json` carries a
`spine_series_uid` and, where one exists, a `pelvic_series_uid` for every record: TCIA
SeriesInstanceUIDs. Every one of the 802 records has at least one.

**Second, a HuggingFace mirror — once it is published.** One volume per record, named to
match the labels, so that a case id means the same acquisition in both:

```python
from huggingface_hub import snapshot_download

snapshot_download(
    "gregoryschwingmdphd/CTSpinoPelvic1K",
    repo_type="dataset",
    revision="v6",
    allow_patterns=["ct/*"],
    local_dir="ct",
)
```

> **Status: pending — the `v6` branch above does not exist yet, and that call will fail
> until it does.** What the repository holds today is an earlier, larger export that does
> not line up with this deposit. Of these 802 ids: **236** have a `ct/<id>_ct.nii.gz`,
> **280** are present only under a per-acquisition name (`<id>_spine_ct.nii.gz`,
> `<id>_pelvic_ct.nii.gz`), and **286** are absent. Use the TCIA route above.
>
> `mirror_ct_to_hf.py` in this repository's source refuses to publish a mirror that is
> missing records, because a partial mirror is worse than none: the case ids still resolve
> and quietly return the wrong volume.

---

## Rebuilding the images

**One step is not optional and your masks will not line up without it.**

The released labels do not sit on the grid of the DICOM series as you will download it. Each
label was drawn on one grid, and at export the CT was resampled onto *that* grid so image and
label share an affine. Downloading the series and converting it to NIfTI gives you a volume
with the right anatomy on the wrong grid.

Everything needed to fix that is already in the label file: **the label's own affine is the
target.**

**All three steps, automated.** `fetch_from_tcia.py` reproduces what the release did,
in the same order and with the same settings: download the named series with
`tcia_utils`, convert with `dcm2niix`, resample onto the label grid trilinearly with
-1024 HU outside the original extent.

```bash
pip install tcia_utils nibabel scipy numpy       # and dcm2niix on PATH
python fetch_from_tcia.py --manifest manifest.json --labels labels --out ct
python fetch_from_tcia.py ... --cases 0007 0033  # or just a few
```

It downloads a *named series*, never a patient: every patient here was scanned twice,
prone and supine, and only one of those volumes is the one the annotation was drawn
on. Per record it reports what fraction of the label lands on bone and warns below
50% -- which indicates the wrong series rather than a resampling error.

Because the label's affine is the target, the released PIR orientation comes along
with it. There is nothing separate to rotate.

**If you already have your own conversion**, `reconstruct_ct.py` does the resampling
step alone:

```bash
python reconstruct_ct.py --label labels/0007_label.nii.gz \
    --ct my_conversion/0007.nii.gz --out ct/0007_ct.nii.gz --check
```

`--check` reports the fraction of labelled voxels sitting at bone attenuation. A low number
means you have the **wrong series**, not a resampling problem — every patient in this cohort
was scanned twice, prone and supine, and no amount of interpolation fixes the wrong one.

**Confirm a rebuilt pair before you train on 802 of them.** Image and label share an affine
exactly, so there is nothing to resample or reorient to overlay them, and that is the first
thing to check:

```python
import nibabel as nib
import numpy as np

lab = nib.load("labels/0007_label.nii.gz")
ct = nib.load("ct/0007_ct.nii.gz")

assert np.allclose(lab.affine, ct.affine)        # same grid, always
print("orientation:", nib.aff2axcodes(lab.affine))   # ('P', 'I', 'R')

L = np.asanyarray(lab.dataobj)
C = np.asanyarray(ct.dataobj)
print("structures present:", sorted(int(v) for v in np.unique(L) if v))
print("label on bone:", f"{(C[L > 0] > 200).mean():.1%}")   # ~55%; below 50% = wrong series
```

---

## What this is for

**Naming a level without counting to it.** Vertebral level is conventionally established by
counting down from C2, and wrong-level spine surgery — about one in 3,100 spinal procedures,
with half of surveyed neurosurgeons reporting at least one over a career — is most often
attributed to anatomic variation, specifically the sacralised and lumbarised segments this
cohort was built around. The count is ambiguous exactly where the variant is. That makes the
open question whether a level can be identified from the *local* shape of the vertebra
itself, and this release is shaped for it: per-level anterior and posterior body height,
canal width, endplate width, transverse-process span and wedge ratio for T11–L5 across all
802 records, with labels assigned against the twelfth rib rather than by enumeration — so a
model trained here is not learning to reproduce the convention that fails. The 17
sacralisations, 14 lumbarisations and 33 radiologist-graded records are the cases on which a
counting method and a geometric one should disagree.

**Reference morphometry at a larger sample size.** Textbook values for vertebral and canal
dimensions rest substantially on cadaveric series and small imaging cohorts. The measurements
here are computed identically across 802 records from segmentations rather than from
hand-placed landmarks, so they are re-derivable rather than quoted. The limit matters as much
as the number: this is a colorectal screening population aged 50 and over, imaged supine.
That updates the *n* behind a reference range; it does not make the range representative of a
young or surgical population.

**Patient-specific models and surgical simulation.** Planning is moving from static
radiographic targets toward patient-specific biomechanical models, on the argument that
aligning a patient to a population norm is the wrong objective when their own geometry is
measurable. Such a model needs spine, pelvis and femoral heads in one coordinate frame, which
is what this release is. Pelvic incidence, tilt and sacral slope come from the segmented
sacrum and femoral heads rather than from radiographic landmarks, so a model can start from
the anatomy instead of from a measurement of it. In 351 patients the annotation sits on two
acquisitions in different positions, giving a within-patient change in alignment to check a
predicted postural response against. The eleven instrumented records carry hardware as its
own structure rather than absorbed into bone, so a post-operative state is representable.

These are supine screening scans of an older cohort. They support building and checking such
models; they do not establish the alignment targets a deformity correction should aim for.

---

## The label scheme

VerSe-native: vertebrae keep their VerSe identifiers, and every non-VerSe structure takes a
fixed identifier above that range.

| ids | structures |
|---|---|
| 1–7 | C1–C7 |
| 8–19 | T1–T12 |
| 20–25 | **L1–L6** |
| 26 | sacrum |
| 29 | **S1**, carved from the sacrum |
| 30–31 | hips (left, right) |
| 32–33 | femurs (left, right) |
| 34–45 | ribs, left 1–12 |
| 46–57 | ribs, right 1–12 |
| 58–73 | soft tissue — *declared, empty in this release* |
| 74–75 | **lumbar rib** (left, right) |
| 76–79 | hardware — *declared, empty in this release* |
| 255 | ignore |

Two classes distinguish this scheme from a whole-body label map, and only one of them is
unusual:

- **L6 (identifier 25).** Not novel, and deliberately not: the vertebral identifiers follow
  VerSe, which defines 20–25 as L1–L6 and 28 as T13. An L6 label here therefore means the
  same thing it means in VerSe and in CTSpine1K rather than being local to this release.
  What matters is that a scheme *without* an L6 cannot record a six-lumbar spine at all and
  must renumber the column or drop a level to fit — which the widely used whole-body schemes
  do, since TotalSegmentator stops at L5. **18 records carry an L6.**
- **Lumbar rib (74/75).** This one has no counterpart in the public schemes. A scheme that
  numbers every rib 1–12 has nowhere to put a thirteenth: the annotator must either call it
  rib 12 — which asserts the vertebra beneath it is thoracic, the very question at issue —
  or discard it. TotalSegmentator has `rib_left_1`–`12` and `rib_right_1`–`12` and no lumbar
  rib class; VerSe's T13 is a vertebra, not a rib. **16 records carry a lumbar rib.**

Filter on `has_l6`, `has_lumbar_rib` and `lumbar_rib_side` in `manifest.json`; those fields
are derived from the released label volumes rather than carried forward from an earlier
pass.

---

## The transitional layer

33 records carry a radiologist Castellvi grade in `manifest.json`
(`castellvi_type`, with `castellvi_second_read` and `castellvi_agreement` where a second
read exists).

| grade | records |
|---|---|
| Ib | 2 |
| IIa | 2 |
| IIb | 4 |
| IIIa | 3 |
| IIIb | 18 |
| IV | 4 |

**The grade and the vertebral count are different axes**, and this release exists partly to
show how far apart they run: grade IIIb occurs here at rib-free counts of four, five *and*
six, and seven of the 33 graded records carry a perfectly normal count of five. A corpus
recording only the count would describe those seven as unremarkable.

The grades are **one reader's**, with five cases independently read a second time (three
agreed; of the two that did not, one crosses the boundary between bony fusion and
articulation without it). Confirmation by a board-certified neuroradiologist, blinded to the
existing grades and the counts, is in progress. Treat the current grades accordingly.

---

## Known limitations

Stated at the level of detail needed to catch them independently.

- **Thoracic coverage is field-of-view limited** and does not extend to T1. Two records
  (0068, 1106) carry no thoracic vertebra at all.
- **Structures are not universally present.** One record has no sacrum, hips or femurs; two
  have no S1; nine lack an L5 identifier — in each case because the structure is outside the
  field of view, or because the lowest lumbar segment is labelled L6 or incorporated into
  the sacrum. Filter explicitly rather than assuming.
- **The splits are cross-validation folds covering the whole cohort. There is no held-out
  test set.** Carve one and state which records it holds.
- **Label strength varies by structure and the release does not average over it.** Vertebral
  labels derive from radiologist-supervised source annotations, corrected where those sources
  were wrong. Pelvic labels on records that lacked one are pseudolabelled. The rib layer is a
  pseudolabel whose human review was triaged by an automated rule rather than exhaustive.
- **The rib layer has a specific blind spot.** Ribs come from a binary rib network unioned
  with TotalSegmentator, keeping only voxels connected to a numbered rib. A stump rib that
  the binary network segments but TotalSegmentator never *numbers* has nothing to attach to
  and is dropped — and quality control cannot see it, because an absent rib flags nothing.
- **Soft-tissue (58–73) and hardware (76–79) identifiers are declared and populated by no
  record.** Their absence is absence of annotation, not absence of the structure.
- **Postural angles are supine.** Pelvic incidence is a morphological property and needs no
  such caveat; sacral slope and pelvic tilt do.
- **The cohort is a colorectal screening population aged 50 and over.** Its distributions
  should not be read as representative of a surgical one.

---

## Sources and licence

Imaging from TCIA CT COLONOGRAPHY. Vertebral annotations derive from CTSpine1K and pelvic
annotations from CTPelvic1K, both produced under board-certified radiologist supervision;
neither published the mapping from an annotation to the CT series it was drawn on, which is
the gap this release closes.

Released under **CC BY-NC-SA 4.0**. The underlying TCIA imaging carries its own terms; cite
the source collections alongside this one.

**Research use only.** These labels are not a medical device and are not validated for
clinical decision-making.
