Spread Gutter Locator: where to cut a two-page spread

Finds the gutter (centre fold) line on a scan of a two-page spread, so the spread can be cut into left and right pages. Works on scans with extra background on one or both sides, when the gutter is not in the middle of the image.

Use it after Zhnivo/spread-classifier, which decides whether a scan is a spread at all.

Released by Zhnivo, a non-profit project preserving the archival heritage of Belarus.

Model

Architecture small convolutional network (about 90K parameters): 3ร—3 convolutions pooled over height only, then a 1D head that scores every column
Input grayscale scan resized to height 640; the central part of the width (30% to 70%) is resized to 256ร—192
Output gutter position as a fraction of image width, a confidence score (0 to 1) and the source of the answer (network or fold_rule)
Fold check by default the answer is checked against a classic fold rule (a column darker than its neighbours in all three thirds of the page, near the centre); if the rule finds a clear fold more than 5% of the width away, the rule wins. Turn it off with predict(..., use_rule=False)
Limitation by design the gutter must lie within the central 40% of the image width
Hardware and speed runs on an ordinary CPU, no GPU needed: about 6 spreads per second with the fold check and 12 without it, on a laptop CPU (AMD Ryzen 7, 6 threads), including reading the scans
Files model.safetensors, config.json, inference.py

Training data

Trained on spreads of printed and handwritten books, registers and archival files, mostly from the 19th and 20th centuries, with the gutter marked by hand. Validation spreads come from other books than the training spreads.

Usage

pip install torch safetensors pillow numpy scipy huggingface_hub
from huggingface_hub import snapshot_download
from PIL import Image
import sys

folder = snapshot_download("Zhnivo/spread-gutter-locator")
sys.path.insert(0, folder)
from inference import load_model, predict

model, cfg = load_model(folder)
(x, confidence, source), = predict(["spread.jpg"], model, cfg)

im = Image.open("spread.jpg")
cut = round(x * im.width)
im.crop((0, 0, cut, im.height)).save("left.jpg")
im.crop((cut, 0, im.width, im.height)).save("right.jpg")

Limitations

  • Expects a two-page spread; on a single page it still returns a position.
  • Tables whose wide gap between columns lies near the centre can mislead the model. Check results with low confidence.

License and attribution

MIT, free for any use. If you use the model, a link to the project zhnivo.com is appreciated.

Downloads last month
-
Safetensors
Model size
89k params
Tensor type
F32
ยท
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support