Configuration#

All tunable parameters live on the ScanConfig dataclass. Construct one, pass it to scan() or scan_batch().

from pagescan import ScanConfig, scan

config = ScanConfig(jpeg_quality=80, auto_orient=False)
scan("photo.jpg", "out.pdf", config=config)

Field reference#

Background detection#

Field

Type

Default

Purpose

background_hsv_low

(int, int, int)

(0, 65, 30)

Lower HSV bound for background. Default targets warm wood (H=0–45, S≥65, V≥30).

background_hsv_high

(int, int, int)

(45, 255, 255)

Upper HSV bound for background.

background_hsv_strict_s

int

90

Stricter saturation minimum for edge detection. Prevents cream-coloured paper (S=65–85) from being matched as background.

# Blue tablecloth instead of wood
ScanConfig(
    background_hsv_low=(100, 50, 30),
    background_hsv_high=(130, 255, 255),
)

Output dimensions#

Field

Type

Default

Notes

output_width

int

2480

A4 width at 300 DPI.

output_height

int

3508

A4 height at 300 DPI.

output_dpi

int

300

DPI tag written to the PDF.

output_margin

int

50

Margin around document on the output canvas.

jpeg_quality

int

50

JPEG quality for PDF embedding. Lower = smaller file.

# US Letter
ScanConfig(output_width=2550, output_height=3300)

Pipeline toggles#

Field

Type

Default

What it controls

auto_orient

bool

True

OCR-based 90/180/270° rotation correction. Disable when input orientation is known.

deskew

bool

True

Hough-based skew correction for residual text tilt after perspective.

enhance

bool

True

Master switch for the enhancement stages.

shadow_removal

bool

True

Illumination normalisation before enhancement.

white_balance

bool

True

Push the paper background toward pure white.

use_ml

bool

True

Master switch for ML-based corner detection. When False, skips both cascade and legacy ML and goes straight to the contour fallback.

ML detection path#

Field

Type

Default

What it controls

use_cascade

bool

True

Use the YOLO + HQ-SAM cascade as the primary ML 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 [ml] extras.

detector_conf_threshold

float

0.25

Minimum YOLO confidence for accepting a detection. Below this, cascade falls through to legacy.

min_doc_coverage

float

0.05

Over-crop guard. Predicted quads with area below this fraction of the image are rejected as too small (typical failure: SAM segments an inner text block instead of the full page). Raise toward 0.10 if your documents always fill ≥10% of the frame.

Debug#

Field

Type

Default

Notes

debug

bool

False

When True, intermediate stages write images to debug_dir. Useful for diagnosing bad scans.

debug_dir

str

"pagescan_debug"

Directory for debug output. Created if missing.

Built-in presets#

The pagescan.config module exposes four preset configurations for the most common use cases:

Preset

Equivalent of

Use case

PRESET_A4_300

ScanConfig() (defaults)

Standard A4 office scan at 300 DPI.

PRESET_LETTER_300

output_width=2550, output_height=3300

US Letter at 300 DPI.

PRESET_FAST

use_ml=False, auto_orient=False

Headless / fast path. ~10× faster, slightly worse corner accuracy.

PRESET_RAW

enhance=False, shadow_removal=False, white_balance=False

Crop + perspective only. No tone changes — preserves original colours and texture.

from pagescan import scan, PRESET_FAST

scan("photo.jpg", "out.pdf", config=PRESET_FAST)

Tuning guide#

“It’s cropping too aggressively”#

Raise the over-crop guard:

ScanConfig(min_doc_coverage=0.15)

Then pagescan.scan(...) rejects any quad smaller than 15% of the frame, falling back to the conservative contour path. This is the single most common knob to adjust.

“It’s not detecting the document at all”#

Lower the detector confidence threshold:

ScanConfig(detector_conf_threshold=0.10)

This accepts weaker YOLO detections. Risk: more false positives on document-like background objects.

“Whites are coming out grey”#

Enable white balance (default), or raise JPEG quality so the post-enhancement whites don’t get crushed:

ScanConfig(white_balance=True, jpeg_quality=80)

“Output looks oversaturated / cartoonish”#

Disable enhancement entirely:

ScanConfig(enhance=False)
# Or use the raw preset:
from pagescan import PRESET_RAW

“First scan is slow”#

That’s the one-time HF download (~50 MB cascade weights, ~360 MB HQ-SAM checkpoint). Subsequent scans use the cached copy. Pre-warm in a build step:

from pagescan.detector import _ensure_model as ensure_detector
from pagescan.segmenter import _ensure_model as ensure_sam
ensure_detector()
ensure_sam()