A two-pass Vision Language Model (VLM) OCR pipeline designed to segment, filter, and extract structured Arabic table data from the Algerian University Orientation Guide (دليل التوجيه الجامعي).
The pipeline extracts three core fields per academic program:
- الرمز (Registration Code): Alphanumeric code identifier (e.g.
B02TPN01). - مؤسسة_التكوين (Institution Name): University, institute, or school name in Arabic.
- الدوائر_الجغرافية_للتسجيل (Geographic Districts / Wilayas): List of eligible 2-digit wilaya codes (e.g.
["04", "05", "16"]) or["00"]for national registration (تسجيل وطني).
OCR-BAC2026/
├── config.py # Central configuration & tunable hyperparameters
├── prompts.py # VLM System/User prompts & Ollama native JSON schema
├── main.py # End-to-end pipeline execution entrypoint
├── run_pass1.py # Pass 1: PDF rendering & page triage runner
├── run_pass2.py # Pass 2: Row segmentation & structured JSON extraction runner
├── models/
│ ├── __init__.py
│ └── vlm_client.py # Ollama client initialization, retries & VLM caller methods
├── utils/
│ ├── __init__.py
│ ├── image_processing.py # Computer vision (DPI rendering, band cropping, HSV filter, row segmentation)
│ └── output_handler.py # Wrapper stripping, JSON parsing, diagnostic logging & CSV exporter
├── data/
│ ├── input/ # Directory for source PDF files
│ ├── intermediate/ # Directory for filtered pages, crop rows, manifest, and JSONL/CSV outputs
│ └── output/ # Final exported output destination
├── requirements.txt # Required Python dependencies
├── .gitignore # Git exclusion rules
└── README.md # Project documentation
- Renders PDF pages at high resolution (300 DPI).
- Splits each page into 3 horizontal bands with a 10% overlap to handle split tables.
- Calls a lightweight VLM (
qwen3-vl:4b-instruct) with native Ollama JSON schema constraint (JUDGE_JSON_SCHEMA) to detect 3 key data signals (Registration Codes, Institution Names, Wilaya Codes). - Strict Decision Logic:
- Mandatory Registration Code: A page MUST contain at least one valid registration code adhering strictly to the
LDDLLLDDformat (1 letter + 2 digits + 3 letters + 2 digits, e.g.B02TPN01). If no valid code is found anywhere on the page, the page is skipped directly. - Multi-Signal Verification: Requires at least 2 total data signals (e.g. code + institution or district) to accept the page.
- Mandatory Registration Code: A page MUST contain at least one valid registration code adhering strictly to the
- Applies HSV Color Thresholding (turning stamps/highlights white and text pure black) and saves accepted pages to
data/intermediate/filtered_pages/.
- Segments accepted page images into individual table row crops using OpenCV contour detection of horizontal line separators.
- Row Triage Filter: Calls
qwen3-vl:8b-instructto classify rows intoYES FULL,YES PARTIAL, orNO FULL. - Structured Extraction: For valid rows, calls the model using the BALANCED prompt with Ollama's native grammar-constrained
format=EXTRACT_JSON_SCHEMAfor guaranteed valid JSON output. - Merged-Cell Context: Uses previous row context to complete partial rows (where codes or institutions span multiple lines).
- Outputs raw diagnostic JSONL logs and formatted CSV files under
data/intermediate/raw_json/.
All pipeline hyperparameters can be tweaked in config.py to optimize for speed, memory, precision, or different visual document layouts.
JUDGE_MODEL(default:"qwen3-vl:4b-instruct"): VLM used for fast page-level triage in Pass 1.MODEL_NAME(default:"qwen3-vl:8b-instruct"): Primary VLM used for Pass 2 row filtering and extraction.MODEL_NUM_CTX(default:16384): Token context window allocated for image encoding and extraction response.IMAGE_TIMEOUT(default:90): Network timeout in seconds for VLM API requests.MAX_RETRIES&RETRY_DELAY(default:3,2): Number of retry attempts with exponential backoff on network failures.
DPI(default:300): PDF rendering resolution. Lower (e.g.150) speeds up rendering; higher (e.g.300) improves fine Arabic OCR text clarity.NUM_CROP_BANDS(default:3): Number of horizontal slices per page in Pass 1 triage.BAND_OVERLAP(default:0.10): Overlap ratio between adjacent crop bands.SATURATION_THRESH(default:15): HSV saturation ceiling. Pixels above this are removed (colored stamps, highlighters).BRIGHTNESS_THRESH(default:200): HSV value floor. Pixels darker than this are enhanced to pure black text.KERNEL_LEN_RATIO(default:0.40): Minimum horizontal line length ratio relative to page width for row segmentation.MIN_ROW_HEIGHT_PX(default:15): Minimum pixel height threshold between horizontal table dividers.
temperature(default:0.0): Deterministic greedy decoding.top_p(default:0.9) &top_k(default:20): Nucleus sampling bounds.num_predict(default:8192): Maximum output token generation limit per row extraction call.
-
System Dependencies: Install
poppler-utils(required forpdf2image):# Ubuntu/Debian sudo apt-get update && sudo apt-get install -y poppler-utils
-
Ollama VLM Models: Pull the required vision models:
ollama pull qwen3-vl:4b-instruct ollama pull qwen3-vl:8b-instruct
-
Python Environment:
pip install -r requirements.txt
python main.pypython run_pass1.pypython run_pass2.pydata/intermediate/filtered_pages/manifest.json: Contains page triage evidence and list of accepted/rejected pages.data/intermediate/raw_json/page_{N}_raw.jsonl: Detailed line-by-line JSONL records containing raw model inputs/outputs.data/intermediate/raw_json/page_{N}_raw.csv: UTF-8 encoded CSV file of extracted program codes, Arabic institution names, and wilaya lists.data/intermediate/raw_model_output/thinking_process.txt: Timestamped text log capturing full model responses and token evaluation statistics.