nara-indra-ocr-r2
A fine-tune of the line recognizer in Datalab's Indra OCR for 18th- and 19th-century English handwriting in American archival records β trained on US Revolutionary War pension files held by the National Archives (NARA): declarations, affidavits, certificates and printed forms completed by hand.
Not affiliated with or endorsed by Datalab. This is an independent research derivative of their model. Indra OCR and its weights are Β© Datalab; this repo is distributed under their license, below.
Use it with detector threshold 0.8
Indra's line detector turns its line-probability map into boxes at a threshold of 0.5. On this handwriting, closely spaced lines touch at 0.5 and come out as single boxes the recognizer cannot read; at 0.8 they separate. This model's training crops were cut at 0.8 and it is measured at 0.8, so set it:
# pip install indra-ocr==0.3.1
import functools
import indra_ocr
from huggingface_hub import snapshot_download
# Detector threshold 0.8 (Datalab's default is 0.5). Patches indra-ocr 0.3.1's
# module-level decode(); set it before converting.
indra_ocr.pipeline.decode = functools.partial(indra_ocr.pipeline.decode, thresh=0.8)
conv = indra_ocr.load(path=snapshot_download("wjbmattingly/nara-indra-ocr-r2"), furniture=True)
for _, page in conv.convert("page.jpg"):
print(page.text)
furniture=True keeps page headers and footers, which Indra drops by default. Runs
on CPU (~0.1 s/page). Without the patch the model still works, at the 0.5 figures below.
Results
End to end with Datalab's unchanged detector, reading-order and layout models, on 762 held-out pages from 39 documents, none of which contributed any page to training. Micro CER and WER (NFD normalisation is a no-op on this English text).
| model | CER | macro CER | WER |
|---|---|---|---|
| Indra OCR as shipped, threshold 0.5 | 0.7195 | 0.7945 | 0.8851 |
| Indra OCR as shipped, threshold 0.8 | 0.6792 | 0.7609 | 0.8674 |
| this model, threshold 0.5 | 0.4301 | 0.5350 | 0.5949 |
| this model, threshold 0.8 (recommended) | 0.3604 | 0.4899 | 0.5423 |
| LightOnOCR-3 4B, full fine-tune | 0.2312 | ||
| LightOnOCR-3 0.8B, full fine-tune | 0.2550 | ||
| Qwen3.5-2B, LoRA | 0.2688 | ||
| Qwen3.5-0.8B, LoRA | 0.2868 |
The VLM rows are fine-tuned on the same training split and scored on the same
holdout with the same metric; they remain clearly ahead, at 650β3,300Γ the
parameters and a GPU. The references are page transcriptions written for reading,
not for line-by-line OCR β whole paragraphs on one line, editorial notes such as
[printed form completed in handwriting] that are not on the page β so no
line-based reader can reach zero, and line recall is not reported.
The main remaining limit is the line detector, which this fine-tune does not change: on faint or crowded handwriting it still misses some lines and splits others into word-sized fragments. This model outputs ~84% as many characters as the references contain.
What was changed, and what was not
Indra is a pipeline of small ONNX models. Only the line recognizer was retrained.
| file | status |
|---|---|
rec.onnx |
MODIFIED β all 1,236,110 recognizer weights fine-tuned; classifier head widened from 1,678 to 1698 classes |
meta.json |
MODIFIED β vocab extended by 20 characters; all other fields unchanged |
det.onnx, order.onnx, orient.onnx, script.onnx, rec_cjk.onnx |
unmodified copies of Datalab's files |
LICENSE |
Datalab's license, verbatim |
The weights were written back into Datalab's original graph (never re-exported), so its masking, length computation and decoding are theirs. The detector threshold is an inference setting, not part of these files.
Training
The source transcriptions are per page with no line geometry, so line labels were recovered by alignment, in 2 rounds. Indra's own detector (threshold 0.8) finds the lines and a recognizer reads them; the page's readings are joined and aligned to the whole transcription character by character, and each detected line takes the slice of the transcription its characters map to, snapped to word boundaries. A line is kept only if its reading and its slice are at least 40% similar and within 1.5Γ in length; editorial notes in brackets are removed first, and pages Indra rotated are skipped. Each round's labels come from the previous round's model; every round trains from Datalab's shipped recognizer.
CTC fine-tune on 685,666 line crops from 35,904 pages
(8 epochs, AdamW, one-cycle learning rate peaking at 3e-4, batch 64,
BatchNorm frozen, one NVIDIA RTX PRO 6000, 31 minutes). Transcriptions:
wjbmattingly/si-test.
License and use restrictions
Distributed under Datalab's modified OpenRAIL-M license β see LICENSE,
included verbatim. It is share-alike: this model, any derivative of it, and its
output are governed by that same license.
By using this model you agree to the use-based restrictions in Attachment A of the License, which apply to it as an enforceable provision. In summary β the License governs, not this summary β you may not use it, its derivatives or its output: unlawfully or to infringe intellectual-property rights; for any purpose if you or your employer have over US$20M in annual gross revenue or over US$20M in funding, except where your use is limited to personal use or research purposes; or if you or your employer offer a product or service that competes with Datalab's. Broader licensing is available from Datalab at https://www.datalab.to/.
Limits
A research artifact for cultural-heritage and archival work, trained on one collection's hands and conventions. Expect it to degrade on other scripts, periods and languages, and verify any reading before relying on it.
Model tree for wjbmattingly/nara-indra-ocr-r2
Base model
datalab-to/indra-ocr