Bundle the trained heatmap/image orientation model and run it after provisional blank-page and split analysis. Confirm candidates with Gemini using low thinking; resolve mixed A3 sheet ordering with the complete statement and medium thinking. Keep A4 scans independent and export paired A3 scans in booklet reading order. Reanalyse uniformly inverted source sheets, validate ordering permutations, and preserve source provenance, corrected rotations and reading slots in manual review. Document the experiments, rejected approaches, calibration and runtime limits. Validation: focused splitting/orientation regression suites pass; bundled model checksum and wheel assets verified. Full Interro07 offline run: 44.26 seconds, eight candidates, mocked upright confirmations, source hashes and geometry intact.
17 KiB
Detecting upside-down handwritten pages
Decision record, 2026-11-04. The recommended experiment combines writing-density heatmaps with a small pretrained image-orientation classifier, then sends flagged pages for LLM confirmation. The trained bundle defaults to a conservative threshold. It is now enabled in the automatic evaluation splitting step; see the integration rules. The experiment results below predate live confirmation integration and exclude Gemini latency.
Requirements and result
The target is to process all of Interro07 in less than a minute, including normal splitting. Recognition of the handwriting is unnecessary. Some missed rotations are acceptable; false alarms can be checked individually, provided referrals stay around or below 5% on an ordinarily upright batch. That percentage is a workload target, not a cap to enforce by silently dropping low-ranked candidates. A batch with many genuinely inverted pages should produce more referrals.
The final experimental splitting run processed 47 original PDFs / 214 scans, retaining 347 logical pages and removing 73 blank halves, in 50.92 seconds. The conservative screen flagged eight upright logical pages (2.3%). Its simulated inversion audit detected 345/347 rotations, including all 24 sparse examples and all 64 pages from the initial test sample. Output rectangles, keep/remove decisions and original source hashes matched the baseline.
Direct checks of the reusable predictor on actual rotated RGB arrays give:
| Profile | Assessment | Rotations detected | Upright false alarms | Screening time for that assessment |
|---|---|---|---|---|
| Conservative, recommended | Interro04 | 372/373 | 11/373 (2.9%) | 14.91 s |
| Conservative, recommended | Interro07 | 345/347 | 6/347 (1.7%) | 13.48 s |
| Balanced | Interro04 | 373/373 | 19/373 (5.1%) | 14.91 s |
| Balanced | Interro07 | 346/347 | 16/347 (4.6%) | 13.48 s |
Screening times average one upright and one rotated pass, excluding file loading, PDF rendering and model startup. Both profiles use the same weights and computation. The 50.92-second full-run timing includes splitting, previews, exports, startup and an inexpensive inverted-feature audit, but excludes LLM confirmation. It was measured once on a Ryzen 7 PRO 7840U with four copy workers and one ONNX inference thread per worker; it is not a latency guarantee. Separate-image and splitter preview counts differ because their raster alignment and clip boundaries differ.
Recommended algorithm
Operate on each retained logical A4 page independently, reusing the splitter's existing 75 dpi RGB overview. A complete A3 spread makes writing too small and can mix orientation evidence from its two halves. Preserve the source-page grouping for later confirmation and repair.
- Respect the PDF's displayed rotation and retain the full logical page area. Existing margin crops would remove useful layout evidence. During dataset preparation, full MediaBoxes were restored in memory without saving changes to the PDFs.
- Extract a cheap ink mask: a pixel counts as ink when its minimum RGB channel is below 100. Exclude the outer 15 pixels on each edge. This is not the full content/blank detector; do not use this orientation mask to discard pages.
- Define a writing crop using the 0.5th and 99.5th percentiles of ink coordinates,
with 12 pixels of padding. The implementation floors both percentile endpoints
and uses an exclusive upper bound of
endpoint + 13. If fewer than five ink pixels exist, use the full page. See the rounding caveat below. - Build density grids at 8×12 and 16×24 for three views: the full threshold-100
mask, its writing crop, and the full-page mask at threshold 140 for faint ink.
Area-resize each mask, take the square root of the densities, and divide each
grid by its L2 norm plus
1e-6. Concatenate the six grids: 1,440 features. - A standardized RBF SVM (
C=3,gamma="scale") converts those features into one layout score. Global maps preserve margins and blank space; crop-relative maps preserve line/block arrangement when writing occupies a small area. - Run PP-LCNet_x1_0_doc_ori on the RGB full page and writing crop, each in its
supplied orientation and rotated 180°. Resize each view directly to 224×224;
normalize RGB channels with means
[.485, .456, .406]and standard deviations[.229, .224, .225]. Retain all four orientation probabilities from each view: 16 neural features. No text recognition is performed. - Append the SVM score to those 16 features. An ExtraTrees classifier with
200 trees, minimum leaf size 5,
max_features=1.0, and seed 514 predicts the final screening score. Usescore > threshold, not>=. - The conservative threshold is 0.3044920634920634; the balanced threshold is 0.17323015873015868. These values belong to the saved weights and feature pipeline. Retraining or changing preprocessing requires recalibration.
The final score is not a calibrated probability. Confirm flagged pages before rotating them. Formula-only, blank and unreadable pages can remain unresolved. Do not let an uncertain orientation score change blank-page decisions.
An upside-down A3 source reverses its two halves as well as their orientation. Retain provenance and use the existing orientation-repair logic when applying confirmed corrections; rotating each exported half alone can leave their order wrong. The automatic splitter now applies the confirmed-sheet rules before export.
Training and validation
All seven original interros contributed to preparation: 2,762 logical pages, each with a synthetic 180° counterpart. Duplicate Interro03 pilot folders were excluded.
- Fit on Interro01 (392), Interro02 (423), Interro03 (431), and Interro05 (406): 1,652 original pages.
- Set numeric thresholds using Interro06: 390 pages.
- Reserve Interro04: 373 pages. Check Interro07 separately: 347 pages.
To train the fusion, fit the heatmap SVM on three training assessments and score the fourth, repeating for all four. Train ExtraTrees using these held-out-assessment SVM scores and the pretrained neural features. Finally refit the SVM on all four training assessments. In-sample SVM scores would give the fusion an unrealistically easy training problem. Complete learned-model fitting took 20.76 seconds; training is not repeated during splitting.
The balanced weights and threshold were frozen before predicting Interro04. The conservative fallback was evaluated after the balanced profile slightly exceeded the holdout referral budget. Its numeric threshold still comes solely from Interro06, but its policy selection is a follow-up experiment, not a second untouched holdout validation. Interro07 had already informed earlier architecture choices. These assessments share a cohort, so the result does not establish unseen-writer generalization.
Originals are presumed upright from the corrected workflow. Representative samples, the initial 64-page set, its 24 sparse examples, and all nineteen balanced Interro04 false alarms were visually reviewed. The entire corpus was not independently labelled. Synthetic rotations do not cover every real scanner fault.
What was tried and why it was not selected
These stages used different training populations and thresholds. Their figures are diagnostic records, not one common leaderboard. Historical rotated-feature results are also subject to the crop-rounding caveat below.
OCR, whitespace and pretrained classifiers
- Tesseract OSD: caught only 9/64 rotations and flagged 23/64 upright pages. It returned no orientation for 29 upright and 28 rotated examples. Four workers processed all 128 calls in 6.07 seconds. It was fast enough but poorly matched to sparse handwritten mathematics; unknown outputs must count as misses.
- Top/bottom whitespace alone: caught 49/64 rotations, but flagged 16/64 upright pages and 139/347 corpus originals. Its full-corpus computation took 3.91 s. A single margin-asymmetry number discards line layout and mistakes legitimate bottom-heavy writing for inverted pages.
- Pretrained whole-page orientation: the official short-side resize/centre-crop preprocessing caught 51/64 rotations with four false alarms; resizing the entire page to a square caught 50 with one. Their early measured wrapper times were 1.43 s and 1.35 s for 347 pages, excluding shared file loading. Centre cropping can hide sparse writing. These early partial timings must not be compared directly with the final complete screening times.
- Small text-line classifier: voting over up to five cheaply extracted writing components caught 30/64 rotations with seven false alarms; extraction and classification took 5.69 s for 347 originals. Equation fragments and ruling make these imperfect text-line crops. This rejects that cheap baseline, not all possible line classifiers.
- Fixed whole-page/crop combination: averaging
P180(image)withP0(rotated image)for each view, then taking the larger view score, caught 61/64 rotations with one false alarm. A complete later screen took 11.17 s for 347 pages. It recovered all sparse examples but missed three formula-heavy rotations. Learning from all 16 neural outputs and a layout score worked better than this hand-chosen maximum rule.
See the first benchmark for all early variants.
Initial heatmap experiments
Five-fold out-of-copy evaluation within Interro07 used the original 64-page sample. Each time below includes feature extraction and prediction for 347 pages, excluding file loading, rendering, training and startup.
| Variant | Rotations caught /64 | Upright false alarms /64 | Sparse rotations /24 | Screening time /347 pages |
|---|---|---|---|---|
| Coarse heatmap, linear classifier | 47 | 4 | 16 | 0.97 s |
| Finer heatmap, linear classifier | 40 | 4 | 14 | 0.72 s |
| Two scales, nonlinear SVM | 55 | 3 | 19 | 1.52 s |
| Two scales plus long-grid-line removal | 54 | 3 | 19 | 2.01 s |
| Heatmap plus line endpoints, nonlinear SVM | 49 | 7 | 16 | 2.31 s |
| Heatmap plus line endpoints, tree classifier | 54 | 4 | 21 | 2.07 s |
Finer grids alone did not compensate for a weak classifier. Removing long ruling strokes slightly hurt quality and cost more time. Explicit line starts/ends did not improve on the simpler two-scale SVM. Symmetric scoring of both orientations gave no gain for heatmap-only models and worsened the line-layout SVM.
Heatmaps recovered all three original image-classifier misses, establishing useful complementarity. However, a simple OR of both screens flagged 28/347 originals (8.1%) despite catching all 64 sample rotations. Its summed measured component time was 12.70 s. Inner-calibrated OR rules either retained too many referrals or lost recall; a grid search on Interro06 later selected effectively fusion-only rules. Catching every sample rotation was not sufficient to select a method.
See the heatmap benchmark for those folds and threshold experiments.
Adding other assessments and richer features
Training on Interro05 and calibrating on Interro06 gave the following Interro07 results. Times are complete measured per-page screening loops for 347 pages, excluding file loading, rendering and startup.
| Variant | Rotations caught /347 | Upright flags /347 | Screening time /347 pages |
|---|---|---|---|
| Global heatmaps | 325 | 12 | 2.28 s |
| Global plus writing-crop heatmaps | 343 | 16 | 2.85 s |
| Global, crop-relative and faint-ink heatmaps | 343 | 8 | 4.14 s |
| Learned neural-output combination alone | 328 | 15 | 8.16 s |
| Rich heatmap plus neural outputs, tree fusion | 346 | 10 | 13.51 s |
The crop-relative maps and second ink threshold were retained. Pure heatmaps were an attractive inexpensive alternative, but neural evidence recovered additional cases. Logistic and SVM fusion candidates did not offer a better overall recall/referral tradeoff than the selected tree fusion at the tested calibration policies. Their complete threshold curves remain in the raw experiment results.
That narrower tree-fusion model produced 345 detections and 11 upright flags in a 48.58 s full splitting trial. Adding the richer heatmap flags by simple OR caught all 347 simulated rotations but produced 18 flags (5.19%), just above the workload target. Such post-hoc unions were not independently validated.
More training data improved the held-out-assessment result, but did not uniformly reduce every error rate. The final balanced model has more Interro07 referrals than this narrower model. Prefer the final conservative profile for the measured workload, rather than assuming that more training always wins at a fixed threshold.
See the expanded benchmark and the final training record for the complete evidence.
Measurement lessons and reproducibility
- Actually rotate and recrop test images. Flooring percentile bounds makes crop creation non-equivariant by a pixel under 180° rotation. Swapping cached views therefore does not exactly reproduce a fresh detector call on a rotated page. The final direct-image results above supersede that approximation for rotation accuracy; the full splitter audit remains explicitly labelled simulated.
- Do not mix render paths silently. Rendering an exported clipped PDF and slicing the source overview can shift pixels. Measure the integrated path too.
- Separate timing scopes. Model-only inference, full screening, image decoding, training and full splitting are different measurements. An initially omitted faint-map resize cost was corrected in the expanded standalone benchmark. Interrupted runs and a run overlapping another CPU benchmark were excluded from the selected timings. Faster classifier kernels did not always produce faster complete splitting runs.
- Preserve full-page geometry. Training on already cropped content would hide the margins and blank vertical spaces the heatmap is intended to analyse.
- Keep assessment/copy groups together. A page and its rotated counterpart must never straddle training and test sets. Use held-out scores when stacking classifiers. Report which data selected thresholds and which selected policies.
- Confirm before repair. Keep source-page provenance, A3 half ordering and unresolved verdicts intact; orientation screening is not proof of a safe repair.
The detailed records above are repository documentation. Large generated datasets,
models, galleries and most experiment scripts are under ignored tmp/ and are
local artifacts, not assets guaranteed by a fresh checkout:
tmp/orientation-benchmark/: initial sample, actual randomly rotated PDF copies, paired scores, Tesseract results and reference full splitting runs.tmp/orientation-heatmaps/: first heatmap features, grouped folds and rejected layout/fusion variants.tmp/orientation-expanded/: Interro05/06 features, cross-assessment candidates, live-preview scores and timing corrections.tmp/orientation-final/: corpus, frozen design/weights, profiles, direct rotated predictions, package checks, final full-run report and source verification.
The final folder's prepare.py, extract.py, train.py, profiles.py,
benchmark.py, direct_rotations.py and integration.py document the executable
workflow. They rely on earlier experiment artifacts and local assessment files;
they are not a clean-checkout training command. Existing-output refusal applies to
the full splitting experiments. frozen-model.json and bundle/sha256.json
identify the saved weights; profiles.json records the conservative follow-up.
The standalone bundle contains predict.py, model.joblib, doc.onnx,
settings.json, dependencies and its validation record. Instantiate one Detector
per worker and reuse it; pass full logical-page RGB uint8 images at 75 dpi.
The returned fields are flag, score, threshold and heatmap_score. The
pretrained ONNX model is PaddlePaddle's
PP-LCNet_x1_0_doc_ori,
Apache-2.0. The recorded runtime uses scikit-learn 1.9.1 and ONNX Runtime 1.31.1.
Integration into the maintained workflow
The runtime is maintained in copienator/page_orientation_detector.py, with assets
under copienator/data/page_orientation/ and declared wheel dependencies. It no
longer depends on the local tmp/ experiment folders. copienator/split_orientation.py
handles Gemini verdicts, physical-sheet grouping and validated ordering. The GUI
and page-split --automatic enable this after provisional geometry/blank analysis.
The standalone geometry tool enables it through --orientation --statement ....
See automatic splitting for A4/A3 rules, failure handling, provenance and manual-review behaviour. Unit tests use mocked Gemini responses; local full-batch benchmarks can do the same. Such benchmarks establish local computation time, not live API latency or LLM accuracy.