A classifier tells you something is there. A detector tells you where. Neither alone is enough for clinical triage. Here is the two-stage chest X-ray pipeline I built, why the architecture is deliberately unfashionable, what the preprocessing actually contributes, and how to read the 96.93% accuracy and 99.71% AUC honestly.
There are two questions a radiologist asks about a chest X-ray in the first few seconds, and a triage system that answers only one of them is not useful.
The first is is there anything here. The second is where.
A classifier answers the first and gives you a number. A detector answers the second and gives you boxes. Most academic chest X-ray work picks one and optimises it. A system meant to sit in a real workflow needs both, arranged so that the cheap question is asked first.
This post is the architecture I built, in detail: why two stages rather than one, why ResNet50 and YOLOv8 rather than something more current, what the preprocessing contributes, how it was evaluated, and what the numbers do and do not mean.
TL;DR — key takeaways
- Two stages, cascaded. A ResNet50 classifier screens every study; a YOLOv8 detector localises findings only on studies the classifier flags. - The cascade is a cost decision, not an accuracy one. Most studies are normal, and running detection on all of them wastes compute for no clinical gain. - Results: 96.93% accuracy, 99.71% AUC, 97.94% F1, 98.20% sensitivity, 93.28% specificity on a 522-image held-out test set. - Sensitivity is deliberately favoured over specificity. In triage, a missed finding costs far more than an extra study a radiologist glances at. - CLAHE preprocessing does real work. Contrast normalisation reduces variance introduced by acquisition differences before the model sees anything. - The architecture is intentionally boring. ResNet50 is well-understood, well-supported, fast, and trains reliably on a modest dataset — all of which matter more in a clinical setting than a benchmark point. - The numbers are conditional. Read them alongside the test-set size, the split discipline, and the prevalence they were measured at.
What the system does
Chest X-ray triage is not diagnosis. The system does not decide what a patient has; it decides which studies a radiologist should look at first.
That reframing changes every design decision. A diagnostic system needs to be right. A triage system needs to be ordered correctly and never silently drop something urgent. Those are different optimisation targets, and the second one tolerates false positives in a way the first does not.
The output is a flag with a confidence score and, when flagged, bounding boxes over the regions that drove the decision. A radiologist sees the boxes and either agrees or does not. Their agreement or disagreement is captured, which is the mechanism by which the system can improve after deployment — I have written about why that capture matters more than almost anything else in clinical AI.
Why two stages
The obvious single-stage design is to run a detector on every image. It answers both questions at once and it is simpler.
It is also wasteful, and the waste is structural.
In a screening population, most studies are normal. Running a detector over every one means spending detection compute — which is meaningfully more expensive than classification — to produce an empty result the overwhelming majority of the time.
The cascade inverts this:
| Stage | Model | Runs on | Question answered | |---|---|---|---| | 1 — Screening | ResNet50 classifier | Every study | Is there anything abnormal here? | | 2 — Localisation | YOLOv8 detector | Only flagged studies | Where is it? |
The classifier is fast and its job is narrow: a binary decision with a confidence score. The detector only runs on the minority of studies that cleared the first gate.
There is a second, less obvious benefit. The two stages can be tuned independently against different objectives. The classifier is tuned for sensitivity — it must not drop anything. The detector is tuned for localisation quality on cases already known to be abnormal, which is an easier problem than detecting-from-scratch and produces better boxes.
The cost of the cascade is that a stage-one false negative is unrecoverable. If the classifier says normal, the detector never sees the image. That is precisely why stage one is tuned to 98.20% sensitivity and accepts 93.28% specificity — the asymmetry is designed in.
Why ResNet50 in 2026
This is the question I get asked most, usually with an implication that a Vision Transformer would be better.
Sometimes it would be, on a benchmark. Here is why it was not the right choice for this system.
It trains reliably on a modest dataset. Transformers are data-hungry. With a clinical dataset measured in thousands rather than millions of images, a well-initialised CNN with strong inductive biases about locality frequently outperforms a transformer that has to learn those biases from data it does not have.
Transfer learning from ImageNet works well. Low-level features — edges, textures, gradients — transfer to radiographs even though the domains look nothing alike. This is well established and it is most of why ResNet50 gets to competitive accuracy on a small medical dataset at all.
It is fast, and stage one runs on everything. Screening throughput is a real constraint. The classifier's latency multiplies across every study in the department.
It is boring, and boring is a feature. ResNet50 is a decade of accumulated tooling, known failure modes, predictable memory behaviour, and an enormous body of people who have debugged it. In a clinical deployment, "widely understood" has genuine operational value that does not appear in any accuracy table.
Gradient-based attribution is well-trodden on CNNs. Class activation mapping on a ResNet is mature and produces explanations clinicians find legible. Explainability is not optional in this setting.
The honest version: an architecture change was never the binding constraint. As I have argued at more length, the data work dominates. Spending weeks on a newer backbone to chase a benchmark point, while the split discipline and the label provenance go unexamined, is the standard way medical AI projects produce impressive numbers that do not survive deployment.
Why YOLOv8 for stage two
Detection had different requirements.
Single-shot speed. Two-stage detectors are typically more accurate at the margin, but stage two is already gated behind a screening step, and its latency sits directly in a clinician's workflow.
Mature small-object handling. Findings on a radiograph can be subtle and small relative to the image. YOLOv8's multi-scale feature pyramid handles that reasonably.
Good behaviour with limited annotation. Bounding-box annotation on medical images requires expert time and is therefore scarce. YOLOv8's augmentation pipeline and pretrained initialisation get useful results from fewer boxes than training from scratch would need.
Operational simplicity. One model, one export path, straightforward deployment. In a hospital environment, the number of moving parts is a maintenance cost somebody eventually pays.
Preprocessing does more than it gets credit for
The preprocessing chain is short and every step is there for a measurable reason.
CLAHE — contrast limited adaptive histogram equalisation. The most important step. Radiographs vary in contrast because of acquisition settings, patient body habitus, and vendor-side post-processing applied before the image ever reaches you. CLAHE equalises contrast locally rather than globally, which brings out structure in both dense and lucent regions of the same image without blowing out either.
The "contrast limited" part matters: plain adaptive histogram equalisation amplifies noise aggressively in uniform regions, and on a radiograph that means amplifying sensor noise into something that looks like texture. The clip limit prevents it.
The deeper argument for CLAHE is distribution shift. Acquisition differences are the dominant source of input variance in medical imaging, and normalising contrast before the model reduces how much of that variance the network has to spend capacity becoming invariant to. It makes the model more robust to a scanner it has not seen — which is the failure mode that most often kills medical imaging models in production.
Resizing with aspect-ratio preservation. Chest radiographs are roughly consistent in aspect ratio but not identical. Distorting them changes the apparent shape of anatomy, which is exactly the signal the model needs.
Windowing and normalisation from DICOM. Raw pixel values in a DICOM are not display values. Getting the windowing right — and consistently — before anything else is table stakes, and getting it inconsistently is a subtle bug that produces a model that works on one export pipeline and not another.
Augmentation, conservatively. Small rotations, small translations, mild brightness variation. Deliberately not horizontal flips: a flipped chest X-ray is anatomically wrong, situs inversus is rare and clinically significant, and training a model to treat left and right as interchangeable destroys information that matters.
That last point is a good example of why domain knowledge beats defaults. Horizontal flip is in every standard augmentation recipe and it is actively harmful here.
The numbers, and how to read them
On a 522-image held-out test set:
| Metric | Value | What it means here | |---|---|---| | Accuracy | 96.93% | Overall correct rate — the least informative number in the table | | AUC | 99.71% | Ranking quality across all thresholds, independent of the chosen operating point | | F1 | 97.94% | Balance of precision and recall at the operating threshold | | Sensitivity | 98.20% | Of studies with a finding, the proportion flagged | | Specificity | 93.28% | Of normal studies, the proportion correctly not flagged |
Reading these honestly requires a few caveats stated plainly.
The test set is 522 images. That is a reasonable size for a clinical dataset and small in absolute terms. Every one of these numbers carries a confidence interval, and sensitivity's interval is set by the number of positive cases specifically, not by 522. A point estimate quoted without that interval overstates its own precision.
Specificity of 93.28% means roughly one in fifteen normal studies gets flagged. Whether that is acceptable depends entirely on the workflow. In triage, where a flag means a radiologist looks sooner at something they were going to read anyway, it is a good trade. If a flag triggered an expensive downstream test, it would not be.
Accuracy is the weakest number here and it is the one that would go on a slide. It depends on the prevalence in the test set, which is a property of how the dataset was assembled rather than of the model. Sensitivity and specificity are the pair that transfer, because neither depends on prevalence.
AUC of 99.71% describes ranking, not decisions. It says the model separates the classes well across thresholds. It does not tell you the system works at the threshold you actually chose, which is a separate and more operationally relevant question.
The split was by patient. Worth stating explicitly because splitting by image instead is the most common way chest X-ray results get silently inflated — patients have multiple studies, and a model can learn the patient rather than the pathology.
What I would do differently
Being specific rather than diplomatic:
External validation. Everything above is single-source. The number I would most like to have is performance on data from a different institution with different hardware, which is the closest thing to a real deployment estimate. Until that exists, these figures describe the dataset as much as the model.
Confidence intervals in the headline. I quote point estimates above because they are what was measured, but a 522-image test set deserves intervals stated alongside them every time.
Calibration, not just discrimination. AUC says the ranking is good. It says nothing about whether a confidence of 0.8 means an 80% chance of a finding. For a triage system where clinicians see the score, calibration is arguably more important than discrimination, and it is measured separately.
Prospective evaluation. Retrospective performance on curated data and real performance in a live workflow are different quantities. The gap between them is where most medical AI disappointment lives.
Closing
The instinct with a medical imaging project is to reach for the newest architecture, because that is the part that feels like the work.
The parts that actually determined whether this system was usable were: cascading two stages so the expensive one runs rarely, tuning stage one for sensitivity because of a clinical cost asymmetry, normalising contrast so acquisition differences did not become the model's problem, refusing an augmentation that every default recipe includes, and splitting by patient so the numbers meant something.
None of that is architectural. All of it is domain reasoning applied to ordinary engineering decisions.
The question to ask about a medical imaging model is not:
"What accuracy did it reach?"
It is:
"On which studies is it wrong, at what threshold, measured how, and what happens next?"
If you are building something in medical imaging and want a second pair of eyes on a pipeline or an evaluation design, get in touch.
Frequently asked questions
Why use a two-stage pipeline for chest X-ray analysis?
Because the two clinical questions — is there a finding, and where is it — have very different costs. In a screening population most studies are normal, so running a detector on every image spends expensive detection compute to produce an empty result most of the time. A fast classifier screens everything and a detector runs only on flagged studies. It also lets each stage be tuned against its own objective: the classifier for sensitivity, the detector for localisation quality on cases already known to be abnormal.
Is ResNet50 still a good choice for medical imaging?
For clinical datasets of moderate size, frequently yes. Vision Transformers need substantially more data to learn the locality biases a CNN has built in, and clinical datasets are usually measured in thousands rather than millions of images. ResNet50 also transfers well from ImageNet, runs fast enough to screen every study, has mature attribution methods that produce explanations clinicians find legible, and is well-understood operationally — which has real value in a hospital deployment even though it appears in no accuracy table.
What does CLAHE do for X-ray preprocessing?
Contrast limited adaptive histogram equalisation equalises contrast locally rather than across the whole image, bringing out structure in both dense and lucent regions without blowing out either. The clip limit prevents the noise amplification that plain adaptive equalisation causes in uniform regions. Its deeper value is robustness: acquisition settings and vendor post-processing are the dominant source of input variance in medical imaging, and normalising contrast beforehand means the network spends less capacity becoming invariant to hardware differences.
What is a good sensitivity and specificity for a triage model?
It depends on the cost asymmetry of the workflow, not on a universal target. This pipeline runs at 98.20% sensitivity and 93.28% specificity, deliberately favouring sensitivity because a missed finding in triage costs far more than an extra study a radiologist glances at. That specificity means roughly one in fifteen normal studies is flagged — acceptable when a flag reorders a reading queue, and potentially unacceptable if a flag triggered an expensive downstream test.
Why should you not use horizontal flip augmentation on chest X-rays?
Because a horizontally flipped chest radiograph is anatomically wrong. The heart, aortic arch and gastric bubble are asymmetric, and situs inversus is rare but clinically significant. Training a model to treat left and right as interchangeable destroys information that matters diagnostically. Horizontal flip appears in nearly every standard augmentation recipe and is a good example of a default that domain knowledge should override.
How large should a test set be for a medical imaging model?
Large enough that your sensitivity estimate has a usable confidence interval — which is determined by the number of positive cases, not the total. A 522-image held-out set is reasonable for a clinical dataset and small in absolute terms, so point estimates from it should always be quoted with intervals. More valuable than raw size is provenance: a smaller test set from a different institution than the training data tells you considerably more about deployment performance than a larger same-source one.
What is the difference between AUC and accuracy for medical AI?
AUC measures how well the model ranks positive cases above negative ones across all possible thresholds, and does not depend on which threshold you chose or on class prevalence. Accuracy measures correctness at one specific threshold and does depend on prevalence, which makes it a property of your dataset's composition as much as of your model. For medical work, report sensitivity and specificity as the primary pair — neither depends on prevalence — and treat accuracy as the least informative number available.
Related reading
- Why Medical AI Fails in Production — and Why Data Quality Decides It — the data discipline that makes these numbers meaningful. - From 50% to 100% Emergency Recall: Debugging Safety Routing in a Clinical RAG Assistant — the other half of the clinical AI system. - What GPU Should a Company Buy for Internal AI Infrastructure? — the hardware profile of a workload like this one.