A Python pipeline to count text lines in scanned pages of medieval manuscripts using Kraken neural baseline segmentation, with Streamlit tools for parameter tuning, manual validation, and embedding exploration.
line_counting/
├── pipeline/
│ ├── __init__.py
│ ├── preprocessing.py # Stages 1–2: binarisation, deskew, border/binding masking
│ ├── masking.py # Stage 3: Kraken non-text masking (figures, illustrations)
│ ├── postprocessing.py # Stage 4: corner/narrow filter, gutter detection & line splitting
│ ├── segmentation.py # (legacy helpers)
│ ├── plots.py # Shared visualisation utilities
│ └── embeddings.py # Structural + ViT embeddings, IsolationForest anomaly scoring, UMAP
├── 0_TuningPreprocess.py # Streamlit: preprocessing parameter calibration
├── 0_TuningPostprocess.py # Streamlit: postprocessing parameter tuning
├── 1_ValidateCounting.py # Streamlit: human line-count validation
├── 2_VisualizeEmbeddings.py # Streamlit: 3-D UMAP embedding explorer (WIP)
├── run_pipeline.py # CLI batch runner
├── tune_masking.py # Standalone colour-masking tuner
├── notebooks/
│ └── explore_embeddings.ipynb
├── docs/
│ └── pickle_format.md # Pickle file format reference
├── requirements.txt
└── data/
├── all_images/ # Full image corpus
├── exemplars/ # Labelled subset for tuning
└── exemplars_complexity.csv
The pipeline consists of three main stages, each returning a typed dataclass that is stored in a per-image .pkl file (see docs/pickle_format.md for the full schema).
Image
│
├─ Stage 1 ─ Sauvola adaptive binarisation
├─ Stage 2 ─ Projection-variance deskew · border crop · binding-side detection
│ · black-margin removal · fold-valley detection
│ → PreprocessResult
│
├─ Stage 3 ─ Kraken neural baseline segmentation
│ · text-line polygon extraction · figure / illustration separation
│ → SegmentKrakenResult
│
└─ Stage 4 ─ Corner artefact filter · narrow bbox filter
· double-column gutter detection · polygon splitting at gutter
→ PostprocessResult
(optional)
└─ Embeddings ─ 24-d structural vector + ViT CLS token (RGB / mask overlay)
· IsolationForest anomaly score · UMAP 2-D / 3-D projection
→ PageEmbedding
Each image's results are saved as {images_dir}/results/{stem}.pkl, and a side-by-side annotated preview as {images_dir}/results/{stem}_result{ext}.
- Python 3.13 (the project
.venvuses 3.13) - macOS with Apple Silicon recommended — MPS acceleration is picked up automatically for ViT embeddings; falls back to CPU on other hardware.
cd line_counting
python3.13 -m venv .venv
source .venv/bin/activatepip install --upgrade pip
pip install torch torchvision --index-url https://download.pytorch.org/whl/cpuOn Apple Silicon the standard
cpuwheel includes MPS support.
Verify:python -c "import torch; print(torch.backends.mps.is_available())"→True.
pip install -r requirements.txtKraken is required for Stage 3 segmentation.
pip install kraken
kraken get 10.5281/zenodo.10592135 # download the default blla modelAfter downloading, Kraken stores the model under your home directory:
~/Library/Application Support/htrmopo/97665cf3-f83d-5594-8855-f28d3af9df7a/blla.mlmodel
This path is hardcoded as _DEFAULT_MODEL at the top of run_pipeline.py (line 50). If you download the model on a different machine or to a different location, update that constant or pass the path explicitly with --model-path.
# Process all images in a folder
python run_pipeline.py --all --dir data/all_images
# Process a single image
python run_pipeline.py path/to/page.jpg
# Skip embedding computation
python run_pipeline.py --all --dir data/all_images --no-embedFull option reference:
| Flag | Default | Description |
|---|---|---|
image |
— | Path to a single image |
--all |
off | Process every image in --dir |
--dir |
data/exemplars |
Input directory |
--output |
{dir}/results/results.csv |
Output CSV path |
--model-path |
Kraken default | Path to a .mlmodel segmentation file |
--device |
cpu |
PyTorch device (cpu, mps, cuda) |
--corner-fraction |
0.10 |
Corner zone size as fraction of image dimensions |
--min-dimension-px |
70 |
Min short-side pixels to keep a detection |
--no-embed |
off | Skip embedding / anomaly scoring |
--vit-mask |
off | Also compute ViT embedding from the text+figure overlay |
--verbose |
off | Print per-page feature details |
Results are written to {dir}/results/:
results.csv— one row per page with line counts and pipeline features{stem}.pkl— full dataclass output for each image (see docs/pickle_format.md){stem}_result{ext}— side-by-side annotated preview (original | annotated)
All apps are launched with streamlit run <script> and open at http://localhost:8501.
Interactively tune the parameters that control border/binding detection on a single image or all exemplars:
- Sauvola binarisation window and
k dark_thresholdfor black-margin detection- Binding valley detection (
strip_frac,valley_frac,smooth_frac) - Deskew range
streamlit run 0_TuningPreprocess.pyTune the Stage 4 filters on the full exemplar set; displays before/after overlays and aggregate statistics:
corner_fraction— size of corner artefact exclusion zonesmin_dimension_px— minimum detection sizemin_gutter_fraction— sensitivity of double-column gutter detectionsingle_col_threshold— fraction of lines that must span the full width to declare single-column
Saves parameters to a JSON config and re-runs the full Stage 4 over all .pkl files in a folder.
streamlit run 0_TuningPostprocess.pyReview pipeline results image by image, correct the line count if needed, and record ground-truth labels:
- Displays the annotated result image alongside pipeline metrics
- Editable line-count field with accept / reject controls
- Persists all labels to
{folder}/results/validation_counting.csv - Summary table showing validated vs. unvalidated pages
streamlit run 1_ValidateCounting.pyExplore the embedding space of a processed image folder:
- Loads all
.pklfiles from a chosen folder and computes (or re-computes) 3-D UMAP projections - Interactive 3-D Plotly scatter; clicking a point highlights it and loads the annotated image on the right
- Selectbox stays in sync with plot clicks (bidirectional)
- Anomaly score colour scale (red = anomalous, blue = typical)
- Structural feature table per selected page
- Sidebar controls: embedding type selection (structural / ViT RGB / ViT mask), UMAP
n_neighborsandmin_dist, dark/light mode
streamlit run 2_VisualizeEmbeddings.py- The ViT model (
vit_base_patch16_224) is downloaded from HuggingFace/timm on first run (~330 MB) and cached locally. - Kraken baseline segmentation requires a
.mlmodelfile; the default is downloaded viakraken get(see Installation). - MPS acceleration is used automatically on Apple Silicon; no code changes are needed for CPU-only hardware.