Inspiration

COPD is almost always caught late, once spirometry crosses FEV₁/FVC < 0.7. But damage exists in the lung years earlier. The challenge was to find it on CT, in people whose breathing tests are still normal.

What we found

Not a biomarker. Something we think matters more.

The same lungs, one scan, two reconstructions:

Sharp kernel Soft kernel
%LAA-950 8.45% 0.68%
Lung volume 5.86 L 5.86 L

On the left that patient has emphysema; on the right they do not. Across 15 paired patients the median shift is 164%. Perc15, read from the same scans, shifts 2.9%.

A sharp kernel adds noise, widening the density distribution without moving its centre. %LAA-950 counts voxels past a fixed line, so it reads that spread as disease:

%LAA-950 = the percentage of lung voxels below −950 HU.

We then scored every feature against the clinical label. Acquisition parameters beat every imaging feature — slice count AUC 0.833, tube voltage 0.750, against 0.771 for the best measurement of the lung. A model trained without protocol control learns the scanner.

How we built it

Python, SimpleITK, lungmask (R231 U-net), Frangi vesselness, scikit-learn.

One scan chosen per patient by explicit rules — reject expiratory, prefer soft kernel and 120 kVp, verify the plane from the direction cosines, never pair across studies. Every decision logged.

The key idea: each patient's scan is stored at several reconstructions. The patient did not change between them, so any difference is the machine. That makes a paired experiment possible with no labels at all.

Challenges

Our first segmentation deleted the lungs. It discarded air touching the volume border — but the trachea reaches the top slice, so lungs, airway and outside air are one component. One patient came out at 0.036 L against a true 6.2 L, and nothing in the table looked wrong.

Every new batch broke something. An angiogram treated as non-contrast. Two reconstructions paired across studies three years apart. A coronal reformat chosen over a real acquisition. Each would have produced a plausible, wrong number.

Our most impressive result was an artefact. Vascular pruning reached AUC 0.875 — the largest effect in the project — from the one feature that had already failed the robustness test. A proper vessel segmentation erased it.

What we learned

Measure your measurement first. Of 26 features, only 8 survived a change of kernel.

A negative result with a number beats a positive one without. Detecting the effect we see at 80% power needs roughly 313 patients per group. This cohort has 8 pre-COPD patients; we scanned all of them.

Clinical data told us nothing — age, pack-years, baseline FEV₁, BMI and sex predicted decline worse than chance (AUC 0.417). CT reached 0.613. The case for imaging is not that it works well; it is that nothing else works at all.

What's next

Report Perc15, not %LAA-950 — same scans, ρ = 0.961 against 0.664. Free improvement for anyone doing lung densitometry.

A cohort large enough to settle this is necessarily multi-centre, and that is exactly where reconstruction kernels get mixed. Protocol standardisation is a prerequisite, not a detail.

Still missing: expiratory scans, more early-damage patients, a third breathing test, and aetiologies beyond smoking — GOLD defines six, this cohort has one.


All data de-identified. Raw imaging deleted after analysis; no patient identifiers in the repository or any figure.

Built With

  • biomarkers
  • computer-vision
  • copd
  • ct-scan
  • data-science
  • databases
  • healthcare
  • image-segmentation
  • medical-imaging
  • numpy
  • public-health
  • pulmonology
  • pydicom
  • python
  • pytorch
  • quantitative-imaging
  • radiology
  • reproducibility
  • scikit-image
  • scikit-learn
  • scipy
  • simpleitk
Share this project:

Updates

Submission history