LungLens

Every chest X-ray deserves a second look.

LungLens screens a chest X-ray for tuberculosis in about a second, sorting it into healthy, sick but not TB or TB, and lights up where in the lungs it found the evidence. The model runs in your browser, so the X-ray is never uploaded anywhere.

Chest X-ray with tuberculosis from the TBX11K test set
radiologist
model's hottest point
TBX11K test X-ray · tb0564.pngtrue label: TBLungLens: refer for a lab test
3-class accuracy
99.2%
TB X-rays caught
200/200
TB vs not-TB AUC
0.9996
Where it runs
your device
Reading room

Try it on a real X-ray.

Upload a frontal chest X-ray (PNG or JPG), or tap one of the 12 test X-rays below. Each sample shows its true label; the dashed boxes on TB samples were drawn by doctors.

Drop an X-ray here or click to choose a filestays on this device
No X-ray on the lightbox yettap a sample below
LungLens · screening reportDenseNet-121 (ImageNet start)

Impression

Waiting for an X-rayUpload one, or tap a sample.

Loading…

Probabilities

Model: DenseNet-121 (ImageNet start) · state: loading · screening threshold 0.7% for the deployed model (95% specificity on validation X-rays).

Research and teaching demo, not a medical device. A flagged X-ray needs a lab test; a clear one does not rule TB out. Not for children under 15.

Indication

The problem

Pakistan had about 670,000 TB cases in 2024, the fifth-highest burden in the world. Chest X-rays are cheap and available almost everywhere; people trained to read them for TB are not.

~670,000TB cases in Pakistan in 2024 (WHO Global TB Report 2025)
5thhighest TB burden in the world: 6.3% of all cases
2021WHO first recommended computer-aided detection (CAD) software in place of a human reader for TB screening, age 15+
3 classesbecause the hard part is telling TB from other lung disease, not from a healthy chest

What LungLens does

  • X-ray in → probabilities for healthy, sick-but-not-TB and TB, plus a heatmap.
  • Screening rule: refer for a lab test if TB probability ≥ 0.7%. The threshold is fixed on validation data at 95% specificity; on the test set that catches 100.0% of TB cases, above WHO's 90% minimum for a triage test.
  • Runs entirely in the browser (ONNX Runtime Web): works offline once loaded, costs nothing to serve, uploads nothing.

Why it is not a toy

  • Most student TB projects use a dataset where TB and normal X-rays come from different hospitals, so a model can learn "which hospital" instead of "which disease".
  • TBX11K's classes share sources, and its "sick but not TB" class forces the model to separate TB from pneumonia-like disease.
  • We test where the model looks against boxes doctors drew around TB: the pointing game.
Technique · data

Dataset: TBX11K

Liu et al., Rethinking Computer-aided Tuberculosis Diagnosis, CVPR 2020. Simplified Kaggle release, licence CC BY 4.0. 512×512 chest X-rays with TB areas boxed by radiologists.

Train
5,939
Validation
660
Test
1,800
  • Healthy
  • Sick, not TB
  • TB
SplitHealthySick, not TBTBTotal
Train (90% of TBX11K train)2,7002,7005395,939
Validation (10%, stratified, seed 42)30030060660
Test (TBX11K's labelled validation set)8008002001,800

TBX11K's own test-set labels are not public, so its labelled validation set is our untouched test set. Exact file lists: data/splits.csv.

Handling the data

  • Grayscale, resized to 384×384 (TB lesions are small; 224 px loses them).
  • TB is 9% of the training set: the loss weights TB about 5× more.
  • Augmentation on the GPU: small rotations, shifts, zooms, exposure/contrast/gamma changes, noise, blur.
  • No horizontal flips: a flipped X-ray puts the heart on the wrong side, which never happens in real life.

Dataset on Kaggle

Technique · models

Models: one question at a time.

Three models, trained with identical data, augmentation, loss and schedule. The two DenseNets differ only in their starting weights, which isolates one question: does it help to start from a network that has already seen chest X-rays?

CNN from scratch

random weights

Baseline: 5 conv blocks (2.4M params). What deep learning does with no prior knowledge.

DenseNet-121

ImageNet (photos of objects)

Standard transfer learning; same architecture as Stanford's CheXNet. Deployed.

DenseNet-121

chest X-rays from 7 public datasets: NIH, CheXpert, MIMIC-CXR, PadChest and more (TorchXRayVision)

Domain-specific pretraining: has it learned what lungs look like?

Training

  • AdamW, weight decay 0.05, cosine schedule with warm-up; 20 epochs (DenseNets), 40 (scratch).
  • Batch 32, mixed precision, one NVIDIA L4 GPU on Google Colab.
  • Best epoch chosen by validation balanced accuracy; test set never used for any choice.

Heatmaps without gradients

  • Every model ends in global average pooling + one linear layer, so the class activation map (CAM, Zhou et al. 2016) comes out of the same forward pass.
  • That is why the browser can draw the heatmap: the ONNX model returns probabilities and maps.
Findings

Results on 1,800 test X-rays

The deployed model is DenseNet-121 (ImageNet start), chosen on validation data. On the 1,800 untouched test X-rays it is 99.2% accurate across the three classes and separates TB from everything else with an AUC of 0.9996. At the screening threshold it catches 100.0% of TB cases (200 of 200). Starting from chest X-rays instead of ImageNet changed TB AUC by -0.0031 and balanced accuracy by -1.4 points.

200 of 200

TB X-rays flagged for a lab test. One dot per X-ray.

200 of 200 TB X-rays flagged for a lab test

65 of 1,600

X-rays without TB referred anyway: the price of missing none.

65 of 1600 X-rays without TB referred for a lab test
3-class accuracy
99.2%
95% CI 98.8%–99.6%
TB vs not-TB AUC
0.9996
95% CI 0.9991–0.9999
TB sensitivity at the screening threshold
100.0%
95.9% specificity
of TB X-rays: the heatmap's hottest point is inside a doctor's box
36%
a random point: 10%
TB vs not-TB ROCzoomed on the top-left corner; rings are each model's screening threshold
0%80%5%85%10%90%15%95%20%100%false-positive rate (1 − specificity)sensitivity
  • CNN from scratchAUC 0.9952
  • DenseNet-121 · ImageNet startAUC 0.9996
  • DenseNet-121 · chest X-ray startAUC 0.9965
Confusion matrixdeployed model, row-normalised
true ↓ · predicted →
Healthy
Sick, not TB
TB
Healthy
79699.5%
30.4%
10.1%
Sick, not TB
00.0%
79899.8%
20.2%
TB
00.0%
84.0%
19296.0%
Pointing gamedoes the heatmap's hottest point land in a doctor's TB box?
DenseNet-121 · chest X-ray start
57%
DenseNet-121 · ImageNet start
36%
CNN from scratch
34%
A random point
10%
ModelParamsAccuracyBalanced acc.TB AUCSensitivitySpecificityPointing gameCPU ms
CNN from scratch2.35M96.9%97.0%0.995298.0%94.6%34%246
DenseNet-121 (ImageNet start) (deployed)6.96M99.2%98.4%0.9996100.0%95.9%36%313
DenseNet-121 (chest X-ray start)6.95M98.3%97.0%0.996598.0%95.8%57%294
Heatmaps of the deployed model on TB test X-rays, with doctors' TB boxes in green
Fig. 1 Where the deployed model looked (heatmap) vs where doctors marked TB (green boxes), on test X-rays it called TB.
ROC curves for TB vs not-TB, three models
Fig. 2 TB vs not-TB ROC; dots are the operating points at each model's screening threshold.
Confusion matrix of the deployed model
Fig. 3 Confusion matrix, deployed model, 1,800 test X-rays.
Pointing game hit rate by model
Fig. 4 Pointing game: does the heatmap's hottest point land in a doctor's TB box?
Training loss and validation balanced accuracy by epoch
Fig. 5 Training loss and validation balanced accuracy per epoch.
Impression

Honest limits, and how to check us.

Honest limits

  • TBX11K comes from Chinese hospitals; the model has not been tested on X-rays from Pakistani hospitals.
  • Trained on adults; WHO's CAD recommendation is for people aged 15 and over.
  • The screening threshold was set on validation X-rays from the same hospitals; a new population needs its own threshold (our first rule, set on only 60 validation TB X-rays, did not transfer to the test set).
  • A heatmap shows where the evidence is, not a lesion outline; a high pointing-game score is not a diagnosis.
  • The official TBX11K test labels are hidden; our test set is its labelled validation set, so numbers are not directly comparable to the leaderboard.

Test it yourself (3 commands)

git clone https://github.com/zyna-b/lunglens
cd lunglens && pip install -r requirements.txt
python evaluate.py          # 12 bundled X-rays
python predict.py samples/tb_1.png --heatmaps out/

Full test set: python evaluate.py --data /path/to/tbx11k. Weights: weights/lunglens.onnx (ONNX), weights/lunglens_torchscript.pt (TorchScript), weights/<model>.pt (PyTorch checkpoints).