API reference#
Public API#
The public surface of pagescan is intentionally small: two functions and one configuration class.
Scan a single document photo into a clean PDF. |
|
Process all images in a directory. |
|
Configuration for the document scanning pipeline. |
Configuration presets#
Four ScanConfig instances ready to use without further tuning:
pagescan.PRESET_A4_300— defaults; A4 at 300 DPI.pagescan.PRESET_LETTER_300— US Letter at 300 DPI.pagescan.PRESET_FAST—use_ml=False, auto_orient=False. ~10× faster, slightly worse corner accuracy.pagescan.PRESET_RAW— no enhancement, no white balance, no shadow removal. Crop + perspective only.
See the Configuration page for tuning guidance.
Pipeline#
Main scanning pipeline: photo of document -> clean PDF.
- pagescan.pipeline.scan(image_path, output_path=None, config=None)[source]#
Scan a single document photo into a clean PDF.
- Pipeline:
Load image
Detect corners (ML with validation, or conservative crop)
Perspective transform or direct crop
Auto-rotate (OCR-based orientation correction)
Deskew (Hough-based text tilt correction)
Shadow removal + white balance
Scan-like enhancement (grayscale, contrast, sharpen)
Place on canvas (A4) and save as PDF
- Parameters:
image_path (
str) – Path to input image (JPEG, PNG, TIFF).output_path (
str|None) – Path for output PDF. If None, replaces extension with .pdf.config (
ScanConfig|None) – Scan configuration. Uses defaults if None.
- Returns:
success, quality_score, quality_passed, message, output_path.
- Return type:
Configuration#
- class pagescan.config.ScanConfig(background_hsv_low=(0, 65, 30), background_hsv_high=(45, 255, 255), background_hsv_strict_s=90, output_width=2480, output_height=3508, output_dpi=300, output_margin=50, jpeg_quality=50, auto_orient=True, deskew=True, enhance=True, shadow_removal=True, white_balance=True, use_ml=True, use_cascade=True, detector_conf_threshold=0.25, min_doc_coverage=0.05, debug=False, debug_dir='pagescan_debug')[source]#
Bases:
objectConfiguration for the document scanning pipeline.
- Parameters:
background_hsv_strict_s (int)
output_width (int)
output_height (int)
output_dpi (int)
output_margin (int)
jpeg_quality (int)
auto_orient (bool)
deskew (bool)
enhance (bool)
shadow_removal (bool)
white_balance (bool)
use_ml (bool)
use_cascade (bool)
detector_conf_threshold (float)
min_doc_coverage (float)
debug (bool)
debug_dir (str)
- background_hsv_low#
Lower HSV bound for background detection (e.g. wood table). Default targets warm wood: H=0-45, S>=65 (paper is S=40-60), V>=30.
- background_hsv_high#
Upper HSV bound for background detection.
- background_hsv_strict_s#
Stricter saturation minimum for edge detection. Prevents cream-colored paper (S=65-85) from being matched as background.
- output_width#
Output canvas width in pixels.
- output_height#
Output canvas height in pixels.
- output_dpi#
DPI for PDF output.
- output_margin#
Margin in pixels when placing document on canvas.
- jpeg_quality#
JPEG quality for PDF embedding (lower = smaller file).
- auto_orient#
Run OCR-based auto-rotation to fix document orientation.
- deskew#
Run Hough-based deskew to correct text tilt.
- enhance#
Apply scan-like enhancement (grayscale, contrast, sharpen).
- shadow_removal#
Apply illumination normalization before enhancement.
- white_balance#
Adjust white balance so paper becomes pure white.
- use_ml#
Master switch for ML-based corner detection. When False, skips both cascade and legacy ML and goes straight to the conservative-crop fallback. Useful for fast/headless modes.
- use_cascade#
Use the YOLO11 + HQ-SAM cascade as the primary detection path. When False (or when the cascade weights are missing), pagescan falls back to the legacy SA24+LCNet ML chain. The cascade requires the optional [ml] extras.
- detector_conf_threshold#
Minimum YOLO confidence for accepting a detection. Below this, cascade falls through to legacy.
- min_doc_coverage#
Over-crop guard. Predicted quads whose area is below this fraction of the image are rejected as too small (typical failure: SAM segments an inner text block instead of the full page). Default 0.05; raise toward 0.10 if your documents always fill ≥10% of the frame.
- debug#
Save intermediate images to debug_dir.
- debug_dir#
Directory for debug output.
Internal modules#
These modules are documented for contributors and advanced users who want to compose the pipeline from individual stages. They are not part of the stable public API — signatures may change between minor versions.
pagescan.detector#
YOLO11 ONNX document detector. First stage of the production cascade.
YOLO11 document detector — first stage of the production cascade.
The detector finds an axis-aligned bounding box around the document; that bbox becomes the box prompt to the HQ-SAM segmenter (next stage). Pure ONNX-runtime inference with no torch dependency.
- Public API:
detector.detect(image_bgr, conf_threshold=0.25) -> (bbox_xyxy, conf) | None
pagescan.segmenter#
HQ-SAM ViT-B box-prompted segmentation. Second stage of the cascade. Imports torch lazily.
HQ-SAM ViT-B box-prompted segmentation — second stage of the cascade.
Takes the YOLO-detected document bbox as a box prompt to HQ-SAM and returns a precise binary mask. The mask is then fitted to a 4-corner quadrilateral via convex hull + polygon approximation.
- Public API:
segmenter.segment(image_bgr, bbox_xyxy) -> mask | None segmenter.mask_to_quad(mask) -> (4, 2) ndarray | None
Imports torch + segment_anything_hq lazily so that environments without the cascade extras still get a working import pagescan (the cascade just becomes unavailable and corners.detect_corners_ml falls back to the legacy SA24+LCNet path).
- pagescan.segmenter.segment(image, bbox)[source]#
Segment the document given an axis-aligned box prompt.
pagescan.corners#
Corner-detection orchestration: cascade and legacy ML paths share validate-and-repair logic here.
Document corner detection.
Two paths share the same validate-and-repair logic:
Cascade (production): YOLO11 detector -> HQ-SAM ViT-B segmenter -> convex-hull + approxPolyDP quad fit. Lives in pagescan.detector + pagescan.segmenter.
Legacy ML (fallback): SA24 + LCNet100 ONNX heatmap regression, in pagescan.model. Used when the cascade is disabled, unavailable (no weights / no torch), or fails on a particular image.
Both paths return raw 4-corner candidates; _validate_and_repair enforces coverage, dimension, and parallelism guards before the corners are accepted. The over-crop guard (config.min_doc_coverage) is applied here too — cascade or legacy, no quad smaller than the configured floor is accepted.
- pagescan.corners.order_corners(pts)[source]#
Order 4 points as: top-left, top-right, bottom-right, bottom-left.
Uses the spatial sum/difference heuristic — works for documents that are roughly axis-aligned. Heavily-rotated documents may need the orientation module to re-label after a perspective warp.
- pagescan.corners.detect_corners_ml(image, config=None)[source]#
Detect document corners on a single image (no rotation retry).
Tries the cascade first when config.use_cascade is True; falls back to the legacy ML chain otherwise or on cascade failure. Both paths are validated and repaired through the shared geometry pipeline.
pagescan.edges#
Contour-based fallback used when ML corner detection fails entirely.
Edge detection and background trimming for the conservative-crop fallback path.
Used only when ML corner detection fails entirely. The strategies are ordered by aggressiveness; the conservative path picks the first one that produces a plausible crop.
- Public surface:
find_paper_contour - Contour-based paper region detection find_document_edges - HSV-mask based bbox detect_corners_contour - 4-corner quad from largest contour detect_paper_quad - Stricter 4-corner quad with paper-shape priors estimate_paper_coverage - Sanity-check helper for over-crop guards
- pagescan.edges.find_paper_contour(image, config=None, min_area_ratio=0.05)[source]#
Find the largest paper region via contour detection.
Fallback for when find_precise_edges fails (e.g. small document on large background). Uses HSV paper mask (low saturation, bright) with morphology to find the largest contiguous paper region.
Returns (top, bottom, left, right) crop coordinates.
- pagescan.edges.detect_corners_contour(image, config=None)[source]#
Detect document corners via edge-based contour analysis.
Background-agnostic alternative to ML corner detection. Returns 4 corners as np.ndarray shape (4, 2) or None.
This is the key improvement over bounding-box fallback: returns a proper quadrilateral that matches the document’s actual tilt, dramatically improving IoU on tilted documents.
- Parameters:
image (ndarray)
config (ScanConfig | None)
- pagescan.edges.find_document_edges(image, config=None)[source]#
Background-agnostic document detection via edge analysis.
Works on ANY background by detecting the document’s sharp edges rather than trying to classify background pixels by color.
Returns (top, bottom, left, right) crop coordinates for the pipeline. Falls back to find_paper_contour if edge detection finds nothing.
- pagescan.edges.detect_paper_quad(image, config=None)[source]#
Detect document boundary via paper-mask segmentation.
Works by finding bright, low-saturation pixels (paper) and fitting a quadrilateral around them. Uses aggressive morphological closing to bridge fold lines, shadows, and other internal features that confuse edge-based detection.
Key advantage over edge-based detection: fold lines in letters create strong edges but do NOT break the paper mask, so this method handles folded documents naturally.
Returns 4 corners as np.ndarray shape (4, 2) or None.
- Return type:
- Parameters:
image (ndarray)
config (ScanConfig | None)
- pagescan.edges.estimate_paper_coverage(image)[source]#
Estimate what fraction of the image is paper (bright, low-saturation).
Quick check used to cross-validate ML corner detection: if ML detects a small region but paper fills most of the image, the ML corners are probably wrong (detecting a fold section, not the whole document).
pagescan.transform#
Perspective transform and canvas placement.
Perspective transform and A4/canvas placement.
- pagescan.transform.perspective_transform(image, corners)[source]#
Apply perspective transform using detected corners.
Preserves the document’s original aspect ratio (no A4 forcing). Output dimensions are derived from the corner positions.
- pagescan.transform.place_on_canvas(image, config=None)[source]#
Place document image on a white canvas (default A4 at 300 DPI) with margin.
- Return type:
- Parameters:
image (ndarray)
config (ScanConfig | None)
pagescan.enhance#
Shadow removal, white balance, contrast stretch, unsharp mask.
Image enhancement: shadow removal, white balance, scan-like output.
- pagescan.enhance.remove_shadows(image)[source]#
Remove uneven lighting via divide-by-background illumination.
Downscales to ~1000px for fast background estimation (illumination varies slowly). Per-channel morphological closing estimates the background, then divides the original by it. Multiplicative normalization — never paints white onto content.
- pagescan.enhance.white_balance(image)[source]#
Adjust white balance so paper background becomes pure white.
Samples the central 50% region, finds paper pixels (low saturation, bright), and computes per-channel gain to map paper color to white.
- pagescan.enhance.enhance_document(image)[source]#
Scan-like enhancement: grayscale + contrast stretch + gamma + sharpen + whiten.
Converts to grayscale, applies percentile-based contrast stretch, brightening gamma, unsharp mask for text sharpness, and pushes near-white pixels to pure white for a clean scan appearance.
pagescan.orientation#
Deskew and auto-rotation (CNN + optional Tesseract).
Document orientation correction: deskew and auto-rotation.
- pagescan.orientation.classify_orientation(image)[source]#
Classify document orientation using CNN model.
Returns (correction_angle, confidence) where correction_angle is one of [0, -90, 180, 90] indicating the rotation needed to make the document upright. Confidence is 0.0-1.0.
Returns (0, 0.0) if the model is not available.
- pagescan.orientation.deskew(image)[source]#
Correct text skew using Hough line detection.
Detects near-horizontal lines (text baselines, rules, table borders) via probabilistic Hough transform on the center 60% of the image (avoids background edges). Uses median angle for robustness to outliers.
Returns (corrected_image, detected_angle). Angle is 0.0 if no correction was needed.
- pagescan.orientation.auto_rotate(image)[source]#
Auto-rotate document to correct orientation.
Strategy:
For 90/270 rotations: CNN is reliable (aspect ratio change is obvious). Trust CNN at conf >= 0.6.
For 180 rotations: CNN is often wrong (text looks similar upside-down). Always verify 180 with OCR — only apply if OCR agrees.
Low CNN confidence: fall back to OCR word scoring on all 4 orientations.
The CNN model (docTR MobileNetV3-Small) is fast (~6ms) but has a known weakness for 180° detection. OCR verification adds ~2s but prevents wrong 180° flips.
pagescan.quality#
Heuristic quality scoring for scanned documents.
Quality assessment for scanned documents.
- pagescan.quality.check_quality(image, config=None)[source]#
Check for background contamination at document corners.
Samples 5% of each corner and measures background (wood/shadow) ratio using a strict saturation threshold to avoid false positives on cream-colored paper.
Returns (passed, score, message) where score is 1.0 (clean) to 0.0 (all background).
pagescan.output#
PDF rendering.
PDF output generation.
- pagescan.output.save_pdf(image, output_path, config=None)[source]#
Save image as a single-page PDF.
Encodes as JPEG and wraps in a PDF sized to the configured page dimensions (default A4).
- Return type:
- Parameters:
image (ndarray)
output_path (str)
config (ScanConfig | None)
pagescan.model#
Legacy SA24 + LCNet + DeepLabV3 ONNX inference. Used as the fallback when the cascade is unavailable.
ONNX inference for document corner detection.
Multi-model approach:
Primary: FastViT_SA24 heatmap regression — perspective-aware corners.
Fallback: LCNet100 heatmap regression — different backbone, catches images where SA24 fails.
Conservative: DeepLabV3-MobileNetV3 segmentation — pixel-level mask, used in conservative crop fallback only.
All models are ONNX and downloaded automatically on first use.
- pagescan.model.detect_corners_onnx(image)[source]#
Detect document corners using multi-model fallback chain.
FastViT_SA24 heatmap (primary) — perspective-aware corners
LCNet100 heatmap — different backbone, catches SA24 failures
Returns 4 corner points as float32 array of shape (4, 2), or None.
- pagescan.model.detect_corners_segmentation(image)[source]#
Detect document corners using DeepLabV3 segmentation.
Separate from the heatmap chain — used in conservative crop fallback. Segments the document region at pixel level, then fits a quadrilateral.
Returns 4 corner points as float32 array of shape (4, 2), or None.