YOLO26m β€” OralXrays-9

⚠️ Research and education only β€” not a medical device

This is an independent personal project. It has no clinical validation, no regulatory approval and no prospective study behind it, and no institution stands behind it.

Do not use it for diagnosis, screening, triage, or any decision concerning a patient. It is not a diagnostic aid and not a clinical decision support tool, and it must not be described as either.

In Brazil, software with a diagnostic purpose is regulated by ANVISA. Nothing here has been through that process, or through any equivalent process in any other jurisdiction.

Description

A YOLO26m object detector fine-tuned on the OralXrays-9 benchmark to localise nine common findings in dental panoramic radiographs.

Task Object detection (9 classes)
Input Panoramic dental radiograph, 1280 px
Architecture YOLO26m, fused: 132 layers, 20,356,391 parameters, 68.1 GFLOPs
Weights best.pt (44 MB, optimizer state stripped)
Framework Ultralytics β€” checkpoint metadata reports 8.4.117 (training), evaluated with 8.4.115 β€” PyTorch 2.13.0

Classes: Apical Periodontitis, Decay, Wisdom Tooth, Missing Tooth, Dental Filling, Root Canal Filling, Implant, Porcelain Crown, Ceramic Bridge.

There are no example prediction images in this repository. Every radiograph in the dataset is a real patient image and none of them may be redistributed, so illustrating the output would mean either publishing patient data or sourcing an unrelated openly-licensed radiograph that would not represent this model's actual test distribution. Neither was worth doing.

Intended use and out-of-scope use

In scope

  • Research on anomaly detection in panoramic radiography.
  • Benchmarking and comparison against other detectors on OralXrays-9.
  • Teaching and coursework in medical imaging or computer vision.
  • A starting point for further fine-tuning on your own annotated data.

Out of scope

  • Any clinical use whatsoever: diagnosis, screening, patient triage, treatment planning, or second-opinion review.
  • Any deployment where a person could be affected by the output.
  • Any closed-source or commercial product. The AGPL-3.0 does not allow it without an Ultralytics Enterprise License.
  • Any use implying validation, accuracy or safety that this model has not demonstrated.

Training data

Trained on OralXrays-9 (Chen et al., CVPR 2025) β€” 12,688 panoramic X-ray images with 84,113 annotated instances across nine oral anomalies.

The dataset is not redistributed here. No image from it appears in this repository in any form. To reproduce this work you must obtain the benchmark from its authors:

https://github.com/Binz-Chan/CVPR2025_OralXrays-9

Access is entirely theirs to grant. This repository does not mirror their download link and does not reproduce their archive password.

The condition the authors state is a request to cite the paper, below. The AGPL-3.0 on this repository covers the code and the trained weights; it does not extend to the underlying images, which remain the dataset holders'.

@inproceedings{chen2025OralXrays,
  title={OralXrays-9: Towards Hospital-Scale Panoramic X-ray Anomaly Detection
         via Personalized Multi-Object Query-Aware Mining},
  author={Chen, Bingzhi and Fu, Sisi and Fang, Xiaocheng and Cai, Jieyi and
          Zhang, Boya and Lu, Minhua and Liu, Yishu},
  booktitle={Proceedings of the IEEE/CVF Computer Vision and Pattern
             Recognition Conference (CVPR)},
  year={2025}
}

Splits

Split Images Instances Origin
Train 8,000 52,299 80% of the benchmark's original train
Validation 2,688 18,638 the benchmark's original validation, untouched
Test 2,000 13,092 20% held out of the original train

The benchmark ships a 10,000-image train and a 2,688-image validation split, and no test split. The test set here was carved out of the original train by multilabel stratified shuffle (MultilabelStratifiedShuffleSplit, test_size=0.2, random_state=42), stratifying on the multi-hot class-presence vector. The three splits are disjoint at image level (verified: 0 overlap in all three pairs).

The instance counts above sum to 84,029, which is 84 fewer than the 84,113 reported in the paper. The notebook's export cell drops any box that is degenerate after the COCO-to-YOLO conversion (zero width or height); that is the most likely source of the difference. The counts in this table are what the model actually saw.

The validation split is distributed differently from train and test

This matters for how you read the numbers, so it is stated plainly rather than buried. Instances per image:

Class Train Validation Test
Apical Periodontitis 0.449 0.398 0.461
Decay 0.961 0.598 0.966
Wisdom Tooth 2.205 1.961 2.206
Missing Tooth 0.344 0.246 0.339
Dental Filling 0.941 0.823 0.951
Root Canal Filling 0.614 0.871 0.591
Implant 0.220 0.866 0.222
Porcelain Crown 0.375 0.615 0.389
Ceramic Bridge 0.430 0.557 0.423

Implant appears roughly four times more often per image in validation than in train or test. Train and test track each other closely, as the stratified split intends; validation does not, because it is the benchmark's own split and was never resampled.

Checkpoint selection and early stopping both used validation. Validation metrics are therefore not an unbiased estimate of this model, and are not reported here. The test-split results below are the honest reference.

Results

Held-out test split, 2,000 images, 13,092 instances, imgsz=1280.

Class Images Instances P R mAP50 mAP50-95
All 2000 13092 0.874 0.881 0.919 0.723
Apical Periodontitis 598 922 0.722 0.676 0.747 0.462
Decay 1036 1931 0.810 0.762 0.848 0.641
Wisdom Tooth 1574 4412 0.955 0.973 0.988 0.853
Missing Tooth 433 677 0.846 0.855 0.900 0.626
Dental Filling 782 1901 0.817 0.847 0.905 0.639
Root Canal Filling 659 1182 0.843 0.898 0.928 0.636
Implant 217 443 0.982 0.990 0.992 0.868
Porcelain Crown 474 778 0.942 0.959 0.976 0.874
Ceramic Bridge 501 846 0.948 0.966 0.986 0.907

Mean F1 peaks at 0.88 at confidence 0.348.

Speed: 1.1 ms preprocessing and 25.9 ms inference per image on an NVIDIA GeForce RTX 4060 Ti at 1280 px, batch 4, AMP. A latency figure without its hardware is meaningless, so: that is the hardware.

Machine-readable metrics: results/test_metrics.json.

Figures

All from the test split. Every one is a derived plot; none contains any part of a radiograph.

File What it shows
results/BoxPR_curve.png Precision-recall per class
results/BoxF1_curve.png F1 against confidence
results/BoxP_curve.png Precision against confidence
results/BoxR_curve.png Recall against confidence
results/confusion_matrix.png Raw counts
results/confusion_matrix_normalized.png Column-normalised

Read the confusion matrices with care: they are computed at a single fixed operating point, unlike the table above, which integrates across confidence thresholds. That is why their large background column β€” unmatched low-confidence predictions β€” and their diagonal do not line up with the precision and recall figures. They also show substantial mutual confusion between Missing Tooth and Root Canal Filling, two findings that frequently co-locate on the same tooth site.

Training

46 epochs total, stopped early (patience=10) out of a requested 100. imgsz=1280, batch=4, seed=42, optimizer=auto resolving to MuSGD (lr 0.01, momentum 0.9), AMP enabled, single GPU. Run across three Kaggle sessions on a Tesla T4, each resuming the previous session's last.pt.

The full resolved configuration is at results/args.yaml.

Caveat on results/results.csv. Because the run was resumed twice, the run directory was recreated each session, and the surviving log covers only epochs 36 to 46. The training curves for the first 35 epochs are lost. The file is published as-is rather than rendered into a plot that would look like a complete training history when it is not.

Limitations

  • The two most clinically relevant findings are the two worst classes. Apical Periodontitis (mAP50 0.747, mAP50-95 0.462) and Decay (0.848 / 0.641) trail every restoration class. Implants, crowns and bridges are radiopaque, high-contrast and geometrically regular, and the model finds them easily; that is what lifts the headline average. Pathology is the hard part, and it is where this model is weakest.
  • No external validation. Train, validation and test all come from the same hospital-scale source. There is no evidence at all about generalisation to a different X-ray unit, a different acquisition protocol, or a different patient population. Expect degradation off-distribution, and do not assume the numbers above transfer.
  • No bias analysis. Nothing has been measured across age, sex, or any other stratum. The demographics of the source population are not documented here.
  • Validation-selected checkpoint on a differently-distributed validation set, as described under Splits.
  • Single run, single seed. No repeated runs, so no confidence intervals and no estimate of seed variance.
  • Fixed input scale. Trained and evaluated only at 1280 px. Behaviour at other resolutions is unmeasured.

Reproduction

src/yolo26m-oralxrays-9.ipynb is the notebook that produced these weights, published as it ran, with its original cell outputs intact β€” those outputs are the source of every number reported above.

The dataset is not included. Obtain it from the authors and point the loading cell at your own copy.

pip install -r requirements.txt
jupyter notebook src/yolo26m-oralxrays-9.ipynb

What it does, in order:

  1. Loads OralXrays-9 and carves a 2,000-image test split out of the original train by multilabel stratified shuffle, random_state=42.
  2. Exports all three splits to YOLO format and writes their data.yaml.
  3. Trains YOLO26m at imgsz=1280, batch=4, patience=10, seed=42, mirroring checkpoints to a private hub repo so that a Kaggle session timing out does not cost the training state. Resuming a session is the commented-out cell β€” the published run used it twice.
  4. Strips the optimizer from best.pt and evaluates on the test split at 1280 px.
  5. Deletes the local dataset copy, which is why no image from OralXrays-9 exists anywhere in this repository.

Two mechanical edits were made to the notebook before publishing, and nothing else: machine-specific absolute paths were replaced, and 383 KB of orphaned ipywidgets progress-bar state was dropped from its metadata. No code, output or result was altered. The notebook's opening cell records this.

Inference

from ultralytics import YOLO

model = YOLO("best.pt")
results = model.predict("radiograph.png", imgsz=1280, conf=0.348)

for box in results[0].boxes:
    print(model.names[int(box.cls)], float(box.conf), box.xyxy[0].tolist())

Mean F1 peaked at confidence 0.348 on the test split, which is a reasonable default threshold and nothing more β€” it is not a clinical operating point.

Research and education only. See the notice at the top of this card.

License and attribution

AGPL-3.0-or-later. Full text in LICENSE; attributions in NOTICE.md.

Copyright (C) 2026 Eurico Gabriel Vasconcelos Pereira. Independent work, not affiliated with any institution.

This license is inherited, not chosen. Ultralytics distributes YOLO under the AGPL-3.0 and requires derivative works β€” including fine-tuned models β€” to be released under the same terms, or else to be covered by a commercial license. Publishing the weights alone would not satisfy that, which is why the training, evaluation and inference code is here alongside them.

  • Ultralytics licensing: https://www.ultralytics.com/license
  • Commercial or closed-source use requires an Enterprise License from Ultralytics. It is not available under the AGPL-3.0.

Please cite the dataset paper (BibTeX under Training data) in any work that uses these weights.

Downloads last month
13
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support