Scanned books, read in page order
A book spread needs page order before line order. Split at the gutter, order text within each side, then join the pages. In my 14-page comparison, raw RapidOCR scored mean CER 0.458; the B.L.A.S.T. pipeline reached 0.108. Tesseract still won 9 of 14 pages against B.L.A.S.T. and was faster.
Why a readable scan can produce scrambled text
Text detection and reading order are different jobs. A detector can recognise each line and still return lines from both facing pages at the same height. Joining that output makes the left and right pages alternate. That is the failure documented in the RapidOCR vs Tesseract write-up.
Joined across the spread
The pages alternate: left, right, left, right.
Split at the gutter first
The left page reads through, then the right.
This is a schematic, not OCR output. The home page's scan exhibit shows a separate Darwin spread; it is an illustration, not the benchmark corpus.
Find the gutter before joining text
A fixed midpoint only works when the gutter is actually centred. Inspect the scan before assuming the middle of its image is a safe cut. The documented B.L.A.S.T. approach looks for a vertical white gap in the central band of a landscape spread, then handles each page separately. It does not establish a general curved-seam detector.
The blog describes a spread test of width greater than 1.1 times height and a search in the central 40 to 60% of the width. Those are pipeline heuristics, not rules for every book. A dark gutter, an off-centre scan or text close to the binding can defeat a simple gap search.
Illustrative Python sketch, not B.L.A.S.T. source
This runnable OpenCV/numpy sketch finds a straight low-ink column in that central band and writes two images. It does not perform OCR, detect columns or reproduce the benchmark pipeline. Install opencv-python and numpy, save as split_spread.py, then run python split_spread.py spread.png.
import argparse
from pathlib import Path
import cv2
import numpy as np
parser = argparse.ArgumentParser()
parser.add_argument("image", type=Path)
args = parser.parse_args()
image = cv2.imread(str(args.image))
if image is None:
raise SystemExit("Could not read the input image")
height, width = image.shape[:2]
if width < 10 or width <= 1.1 * height:
raise SystemExit("Expected a landscape book spread; inspect it first")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, ink = cv2.threshold(
gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU
)
column_ink = ink.mean(axis=0)
smoothed = np.convolve(column_ink, np.ones(9) / 9, mode="same")
start, stop = int(0.4 * width), int(0.6 * width)
split = start + int(np.argmin(smoothed[start:stop]))
for side, crop in (("left", image[:, :split]),
("right", image[:, split:])):
output = args.image.with_name(f"{args.image.stem}-{side}.png")
if not cv2.imwrite(str(output), crop):
raise SystemExit(f"Could not write {output}")
print(output)
Review both crops before a batch run. A minimum in an ink profile is only a candidate split; this sketch provides no confidence check and may choose blank page space instead of the gutter. Keep the original image so you can revise the split without losing text.
Preserve columns and reading order inside each page
For left-to-right text, finish the left page before starting the right page. Within each page, identify columns before sorting lines top to bottom. Sorting every text box by its vertical coordinate alone recreates the same interleaving problem at the column level.
The documented pipeline finds vertical gaps, groups boxes into lines using glyph-scaled tolerances, sorts within lines, and joins nearby lines into blocks. That is the level supported by the write-up and B.L.A.S.T. case study; use the pinned OCR repository for the implementation. The sketch above stops before these stages.
On this corpus, mean reading-order tau was 0.668 for raw RapidOCR,0.993 for B.L.A.S.T. and 0.995 for raw Tesseract. Null scores are excluded from those means. Use the benchmark tables and methodology to see the valid page counts and scoring limits.
When Tesseract is the better starting point
Start with Tesseract for printed prose when its automatic segmentation already reads the pages correctly. In the published comparison it won 9 of 14 pages against B.L.A.S.T. and had lower mean WER: 0.279 versus 0.449. It averaged 3.7 seconds per page against 16.5, a 4.5× speed advantage on the measured OCR calls.
Those times exclude restoration. They are not full document processing times. Adding a layout stage should solve an observed failure on your sample; it should not be a requirement imposed on pages that already read correctly.
When layout handling earns the extra work
Add explicit layout handling when inspection shows facing pages or columns being mixed. In this benchmark B.L.A.S.T. beat raw Tesseract on the contents page, some endnotes and the sparse pages. The sparse-page effect is why its lower mean CER needs the per-page results alongside it.
Correct order still leaves word cleanup. The comparison documents missing spaces after punctuation, joined words, line-end hyphens and running headers. Review these before sending text into search or retrieval. This benchmark does not establish performance on tables or handwriting; evaluate those separately if your documents contain them.
Pre-flight checks before processing a book
- Identify single pages and spreads; avoid splitting every image indiscriminately.
- Check page dimensions, orientation and whether the gutter is inside the search band.
- Verify language support and keep a raw Tesseract baseline.
- Set aside contents, notes, indexes and multi-column pages for layout review.
- Identify tables and handwriting that need a separate evaluation.
- Define the output format and whether headers, hyphens and page boundaries should be retained.
- Hand-transcribe a representative sample and compare CER, WER and reading order with fixed scoring.
- Measure the complete pipeline on your machine and budget for manual review of failures.
Frequently asked questions
Can Tesseract handle a two-page spread without a separate split?
Sometimes. Its automatic page segmentation handled the prose spreads well in this evaluation. Raw Tesseract won 9 of 14 pages against B.L.A.S.T. Try it as a baseline before adding layout work, then inspect reading order and word errors on your own pages.
Does finding the gutter solve every reading-order problem?
No. Splitting separates the facing pages. Columns, marginal notes, headers and footnotes still need ordering within each page. The illustrative code only finds a straight split; it does not deskew, follow a curved seam, detect columns or perform OCR.
Is lower CER enough to choose an OCR pipeline?
No. In this benchmark B.L.A.S.T. reached mean CER 0.108, but its mean WER was 0.449 against Tesseract's 0.279. Check word spacing, reading order, processing time and the exported format against the work you need to do.
Bring the pages that keep failing
I build document OCR pipelines with layout checks and structured exports. Send representative pages, the current output and the format you need. The full benchmark shows how I compare the alternatives.