WordDetectorYOLO model
YOLO-based word-level bounding-box detector, fine-tuned from YOLOv8s on the IAM Handwriting Database. Integrated into Xournal++ HTR according to the ADRs (in particular ADR 006 and ADR 007).
The source lives under
xournalpp_htr/training/word_detector_yolo/.
Structure (ADR 006)
This is no longer a standalone uv project; it is part of the main package.
| File | Purpose | Deps |
|---|---|---|
config.py |
Hydra structured config (single source of truth for all constants) | --- |
train.py |
Training entrypoint (Hydra CLI), includes dataset download + conversion | training-word-detector-yolo |
export.py |
ONNX + config.json export, HF Hub upload |
training-word-detector-yolo |
predict.py |
Local inference from a .pt checkpoint |
training-word-detector-yolo |
demo.py |
Local Gradio demo (run locally, not a HF Space, ADR 007) | training-word-detector-yolo |
run_training.sh |
Hyperparameter sweep | training-word-detector-yolo |
The HF-Hub-backed inference class lives in
xournalpp_htr/inference_models.py as YOLOWordDetectorModel.
GPU training setup (step-by-step)
Prerequisites: a Linux machine with an NVIDIA GPU, CUDA drivers installed
(nvidia-smi should work), and uv installed (pip install uv).
1. Clone and install the base package
git clone https://github.com/PellelNitram/xournalpp_htr.git
cd xournalpp_htr
bash INSTALL_LINUX.sh
2. Install the training extra (with CUDA PyTorch)
uv sync --extra training-word-detector-yolo
Verify GPU access:
uv run python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"
3. Verify the installation
make tests-not-slow
4. Authenticate with HuggingFace
Required for downloading the training dataset and (later) uploading the exported model:
hf auth login
5. Train
Single training run (uses Hydra for configuration):
uv run python -m xournalpp_htr.training.word_detector_yolo.train \
training.epochs=50 training.batch=16 training.lr0=0.001
The dataset is downloaded and converted automatically on the first run.
Show all configurable parameters and their defaults:
uv run python -m xournalpp_htr.training.word_detector_yolo.train --cfg job
Or run the full hyperparameter sweep:
cd xournalpp_htr/training/word_detector_yolo
bash run_training.sh
Results are written to runs/detect/train_<timestamp>/. Each run
produces weights/best.pt, results.csv, and TensorBoard logs.
Monitor training with TensorBoard (forward port 6006 if remote):
tensorboard --logdir runs/detect/ --port 6006
6. Inspect the best model
Use the Gradio demo to visually check detections:
uv run python -m xournalpp_htr.training.word_detector_yolo.demo \
--model-path runs/detect/train_<timestamp>/weights/best.pt \
--device auto --share
7. Export to ONNX
uv run python -m xournalpp_htr.training.word_detector_yolo.export \
--checkpoint runs/detect/train_<timestamp>/weights/best.pt \
--output-dir exports/
Produces exports/model.onnx and exports/config.json.
8. Upload to HuggingFace Hub
Once satisfied with the model quality:
uv run python -m xournalpp_htr.training.word_detector_yolo.export \
--checkpoint runs/detect/train_<timestamp>/weights/best.pt \
--output-dir exports/ --upload
Requires write access to PellelNitram/xournalpp-htr-word-detector-yolo.
Inference
Once model.onnx + config.json are on the Hub, inference uses
onnxruntime only (no ultralytics dependency):
from xournalpp_htr.inference_models import YOLOWordDetectorModel
model = YOLOWordDetectorModel.from_pretrained()
boxes = model.detect(grayscale_image) # list[BoundingBox]
WordDetectorYOLO is detection-only: it produces word bounding boxes but no
transcription. The 2026-09-02_yolo_detector benchmark pipeline pairs it
with SimpleHTRModel for end-to-end HTR.
Best model
Experiment 1 baseline (experiments/experiment1/baseline/), exported to
ONNX and uploaded to
PellelNitram/xournalpp-htr-word-detector-yolo.
Benchmark results (2026-09-02_yolo_detector pipeline, ONNX inference):
| Metric | Value |
|---|---|
| Precision | 73.8% |
| Recall | 80.1% |
| CER (case-sensitive) | 34.9% |
| CER (case-insensitive) | 34.4% |
| Recall × (1 − CER_ci) | 52.5% |
| Word accuracy | 39.1% |
| Predicted words | 229 |
| GT words | 211 |
| Matched | 169 |
Experiments
2026-09-04 -- Hydra sweep (experiment 2) and baseline re-run
- Goal: compare batch size (8, 16) and learning rate (0.0005, 0.001).
- Setup: same as initial training, Hydra config, output to
experiments/experiment2/. - Results: all four runs performed very similarly to the baseline. Experiment 1 baseline selected for deployment.
- Selected checkpoint:
experiments/experiment1/baseline/train_20260904_223247/weights/best.pt
2026-09-02 -- Initial training
- Goal: establish a baseline with YOLOv8s fine-tuned on IAM.
- Setup: IAM-DB from HF Hub, 85/15 train/val split (seed 42), YOLOv8s pretrained, lr0=0.001, batch=16, imgsz=1024, 50 epochs, patience=10, AdamW optimizer, mosaic=0.5.
- Command:
uv run python train.py --device 0(pre-Hydra version). - Results: YOLO detector achieves the highest recall (80.1%) among all pipelines, at the cost of lower precision due to more predicted boxes (229 vs ~190).
- Conclusion: YOLO is a viable word detector for this task.
Current status
Training, ONNX export, HF Hub upload, demo and lean ONNX inference (no ultralytics dependency) are fully implemented. The model has been benchmarked against the existing pipelines and shows the best recall and CER. Hydra config and experiment management are in place.
Outlook
- Confidence threshold sweep for precision/recall trade-off.
- Try YOLOv8m or larger variants.
- Increase imgsz to 1280 for better small-word detection.
- Augmentation tuning (mosaic strength, scale range).
- Validate ONNX export against PyTorch checkpoint with a Jupyter notebook.