Lightweight 2.5D pulmonary nodule detection on LUNA16

Reference implementation for "Inter-Slice Representation Outweighs Bounding-Box Supervision Extent in Lightweight 2.5D Pulmonary Nodule Detection: A Whole-Volume Benchmark on LUNA16" (MDPI Diagnostics, under revision).

The study compares two training-time design choices for a capacity-constrained detector - the inter-slice input representation and the spatial extent of bounding-box supervision - under the official LUNA16 evaluation protocol with whole-volume inference.

Headline findings. The inter-slice representation dominates: adjacent-slice 2.5D stacking exceeds a 2D central-slice baseline by +0.130 CPM, with an advantage in all ten official folds, while thin-slab maximum-intensity projection is worse than a plain 2D slice. Reducing the bounding-box supervision extent confers no benefit, and across the (r, w_min) design grid performance tracks the fraction of training boxes clamped at the minimum box size more closely than it tracks either factor alone. Replacing the adjacent-slice channels with copies of the centre slice costs 71 % of the detection confidence for nodules only the 2.5D detector finds, against 6 % for nodules both detectors find, which locates the advantage specifically in through-plane context.

The evaluation protocol is treated as an experimental factor. Scoring these same checkpoints over only the 1,176 slices that contain an annotated nodule centre - 0.52 % of the volume data - instead of all 227,225 slices reverses the supervision result (-0.0209 to +0.0027), attenuates the representation result fourfold (+0.1297 to +0.0348) and raises every CPM by 0.17-0.31. Nothing else changes: same weights, same folds, same aggregation, same evaluator. Reproduce it with python scripts/15_restricted_protocol.py; the outputs are results/table_protocol_effect.csv and results/table_protocol_contrasts.csv.

What is in this repository

Path Contents
weights/<config>/fold<k>/best.pt Ultralytics checkpoints, one per official LUNA16 subset
luna_rev/ the library: data generation, training, full-volume inference, official evaluation, attribution
scripts/ the end-to-end pipeline, in run order (01-07, 10, 12-15)
examples/predict_scan.py run a scan and emit a LUNA16-format candidate CSV
tests/test_evaluate.py correctness tests, including the reference verification
examples/evaluate_submission.py score any candidate CSV with the official evaluator
results/ every table and figure reported in the manuscript
PROTOCOL.md the evaluation protocol, stated precisely enough to reproduce

Evaluation protocol

  • Official LUNA16 10-fold cross-validation - fold k tests on subset<k>; the validation split used for checkpoint selection is a different held-out subset, so no evaluation scan influences model selection.
  • Official evaluation semantics - candidate matching by the centre-distance criterion, one candidate per nodule, and irrelevant findings from annotations_excluded.csv ignored rather than counted as false positives. The implementation reproduces the counters published in the official CADAnalysis.txt reference output exactly (TP 1120 / FP 548420 / ignored 1294 / double detections 231 on the bundled sample submission). That reference predates the diameter < 0 -> 10 mm fallback the script now applies, so reproducing it uses excluded_policy="legacy_abs"; the reported results use the current fallback ("official"). See PROTOCOL.md.
  • Full-volume inference - every axial slice of all 888 scans is scanned, so false positives per scan means what the FROC axis says it means.
  • CPM is the mean sensitivity at 0.125, 0.25, 0.5, 1, 2, 4 and 8 FP/scan, with scan-level bootstrap confidence intervals over 888 scans.

Results

label cpm ci_low ci_high candidates_per_scan
Exp1: 2D central slice, loose 0.6498 0.6181 0.6778 111.2
Exp2: thin-slab MIP, loose 0.5965 0.5703 0.6239 93.1
Exp3: adjacent-slice 2.5D, loose 0.7795 0.7517 0.8005 80.2
Exp4: adjacent-slice 2.5D, strict r=0.6 0.7586 0.7332 0.7821 83

Usage

from ultralytics import YOLO

# fold 0 was trained on subsets 2-9 and is therefore valid for subset0 scans
model = YOLO("weights/Exp4_2p5D_Strict/fold0/best.pt")
results = model.predict("slice.png", imgsz=512, conf=0.01)

Whole-scan inference, producing a LUNA16 submission CSV:

python examples/predict_scan.py --scan /path/to/series.mhd \
    --weights weights/Exp4_2p5D_Strict/fold0/best.pt --out candidates.csv

Scoring any candidate CSV with the official evaluator:

python examples/evaluate_submission.py --candidates candidates.csv

Verifying that the evaluator reproduces the official reference output (needs no imaging data):

python examples/evaluate_submission.py --self-test
python tests/test_evaluate.py

Input construction

Volumes are windowed to [-1000, 400] HU and rescaled to 8-bit. Three slice representations are supported:

  • 2d - the central slice, replicated across the three channels;
  • mip - maximum-intensity projection over the slab {z-1, z, z+1};
  • naive - adjacent-slice stacking, channels (S_z-1, S_z, S_z+1).

Training boxes come from the annotated diameter through the spherical-chord model described in PROTOCOL.md, scaled by the supervision-extent ratio r_sample and floored at w_min pixels.

Configurations

{
  "Exp1_2D_Loose": {
    "label": "Exp1: 2D central slice, loose",
    "model": "yolo11n.pt",
    "representation": "2d",
    "r_sample": 1.0,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 42
  },
  "Exp2_MIP_Loose": {
    "label": "Exp2: thin-slab MIP, loose",
    "model": "yolo11n.pt",
    "representation": "mip",
    "r_sample": 1.0,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 42
  },
  "Exp3_2p5D_Loose": {
    "label": "Exp3: adjacent-slice 2.5D, loose",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 1.0,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 42
  },
  "Exp4_2p5D_Strict": {
    "label": "Exp4: adjacent-slice 2.5D, strict r=0.6",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 0.6,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 42
  },
  "Rsweep_r0.4": {
    "label": "2.5D strict r=0.4",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 0.4,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 42
  },
  "Rsweep_r0.5": {
    "label": "2.5D strict r=0.5",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 0.5,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 42
  },
  "Rsweep_r0.7": {
    "label": "2.5D strict r=0.7",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 0.7,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 42
  },
  "Rsweep_r0.8": {
    "label": "2.5D strict r=0.8",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 0.8,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 42
  },
  "Wsweep_w4": {
    "label": "2.5D strict r=0.6, w_min=4 px",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 0.6,
    "w_min_px": 4.0,
    "negatives": "all888",
    "seed": 42
  },
  "Wsweep_w8": {
    "label": "2.5D strict r=0.6, w_min=8 px",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 0.6,
    "w_min_px": 8.0,
    "negatives": "all888",
    "seed": 42
  },
  "NegAbl_Exp3_posonly": {
    "label": "Exp3 without nodule-free scans",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 1.0,
    "w_min_px": 6.0,
    "negatives": "positive_scans_only",
    "seed": 42
  },
  "NegAbl_Exp4_posonly": {
    "label": "Exp4 without nodule-free scans",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 0.6,
    "w_min_px": 6.0,
    "negatives": "positive_scans_only",
    "seed": 42
  },
  "NegMatch_Exp3_posonly12": {
    "label": "Exp3, nodule-bearing scans only, negatives count-matched",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 1.0,
    "w_min_px": 6.0,
    "negatives": "positive_scans_only",
    "seed": 42
  },
  "Seed1337_Exp3_2p5D_Loose": {
    "label": "Exp3 (seed 1337)",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 1.0,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 1337
  },
  "Seed2026_Exp3_2p5D_Loose": {
    "label": "Exp3 (seed 2026)",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 1.0,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 2026
  },
  "Seed1337_Exp4_2p5D_Strict": {
    "label": "Exp4 (seed 1337)",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 0.6,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 1337
  },
  "Seed2026_Exp4_2p5D_Strict": {
    "label": "Exp4 (seed 2026)",
    "model": "yolo11n.pt",
    "representation": "naive",
    "r_sample": 0.6,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 2026
  },
  "Y26_Exp3_2p5D_Loose": {
    "label": "YOLO26n, 2.5D loose",
    "model": "yolo26n.pt",
    "representation": "naive",
    "r_sample": 1.0,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 42
  },
  "Y26_Exp4_2p5D_Strict": {
    "label": "YOLO26n, 2.5D strict r=0.6",
    "model": "yolo26n.pt",
    "representation": "naive",
    "r_sample": 0.6,
    "w_min_px": 6.0,
    "negatives": "all888",
    "seed": 42
  }
}

Intended use and limitations

Research use only. This is a candidate detector, not a diagnostic device: it localises nodule candidates and does not characterise malignancy. It is trained and evaluated on a single public cohort (LUNA16 / LIDC-IDRI) and has not been validated on external, multi-centre, or prospectively acquired data. Performance on scanners, reconstruction kernels, slice thicknesses or populations unlike LUNA16 is unknown. It must not be used for clinical decision-making.

Citation

Chou, L.-F.; Peng, B.-R.; Wei, C.-S.; Huang, Y.-M. Inter-Slice Representation Outweighs Bounding-Box Supervision Extent in Lightweight 2.5D Pulmonary Nodule Detection: A Whole-Volume Benchmark on LUNA16. Diagnostics (under revision).

Data: LUNA16 (https://luna16.grand-challenge.org/), derived from LIDC-IDRI via The Cancer Imaging Archive. Users must comply with the LUNA16/LIDC-IDRI terms.

Downloads last month
692
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support