Restructure: Move ocr_pipeline + labeling + crop into ocr/ package
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 29s
CI / test-go-edu-search (push) Successful in 29s
CI / test-python-klausur (push) Failing after 2m25s
CI / test-python-agent-core (push) Successful in 19s
CI / test-nodejs-website (push) Successful in 20s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 29s
CI / test-go-edu-search (push) Successful in 29s
CI / test-python-klausur (push) Failing after 2m25s
CI / test-python-agent-core (push) Successful in 19s
CI / test-nodejs-website (push) Successful in 20s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,102 +1,4 @@
|
||||
"""
|
||||
Scan Quality Assessment — Measures image quality before OCR.
|
||||
|
||||
Computes blur score, contrast score, and an overall quality rating.
|
||||
Used to gate enhancement steps and warn users about degraded scans.
|
||||
|
||||
All operations use OpenCV (Apache-2.0), no additional dependencies.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass, asdict
|
||||
from typing import Dict, Any
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Thresholds (empirically tuned on textbook scans)
|
||||
BLUR_THRESHOLD = 100.0 # Laplacian variance below this = blurry
|
||||
CONTRAST_THRESHOLD = 40.0 # Grayscale stddev below this = low contrast
|
||||
CONFIDENCE_GOOD = 40 # OCR min confidence for good scans
|
||||
CONFIDENCE_DEGRADED = 30 # OCR min confidence for degraded scans
|
||||
|
||||
|
||||
@dataclass
|
||||
class ScanQualityReport:
|
||||
"""Result of scan quality assessment."""
|
||||
blur_score: float # Laplacian variance (higher = sharper)
|
||||
contrast_score: float # Grayscale std deviation (higher = more contrast)
|
||||
brightness: float # Mean grayscale value (0-255)
|
||||
is_blurry: bool
|
||||
is_low_contrast: bool
|
||||
is_degraded: bool # True if any quality issue detected
|
||||
quality_pct: int # 0-100 overall quality estimate
|
||||
recommended_min_conf: int # Recommended OCR confidence threshold
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
def score_scan_quality(img_bgr: np.ndarray) -> ScanQualityReport:
|
||||
"""
|
||||
Assess the quality of a scanned image.
|
||||
|
||||
Uses:
|
||||
- Laplacian variance for blur detection
|
||||
- Grayscale standard deviation for contrast
|
||||
- Mean brightness for exposure assessment
|
||||
|
||||
Args:
|
||||
img_bgr: BGR image (numpy array from OpenCV)
|
||||
|
||||
Returns:
|
||||
ScanQualityReport with scores and recommendations
|
||||
"""
|
||||
gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# Blur detection: Laplacian variance
|
||||
# Higher = sharper edges = better quality
|
||||
laplacian = cv2.Laplacian(gray, cv2.CV_64F)
|
||||
blur_score = float(laplacian.var())
|
||||
|
||||
# Contrast: standard deviation of grayscale
|
||||
contrast_score = float(np.std(gray))
|
||||
|
||||
# Brightness: mean grayscale
|
||||
brightness = float(np.mean(gray))
|
||||
|
||||
# Quality flags
|
||||
is_blurry = blur_score < BLUR_THRESHOLD
|
||||
is_low_contrast = contrast_score < CONTRAST_THRESHOLD
|
||||
is_degraded = is_blurry or is_low_contrast
|
||||
|
||||
# Overall quality percentage (simple weighted combination)
|
||||
blur_pct = min(100, blur_score / BLUR_THRESHOLD * 50)
|
||||
contrast_pct = min(100, contrast_score / CONTRAST_THRESHOLD * 50)
|
||||
quality_pct = int(min(100, blur_pct + contrast_pct))
|
||||
|
||||
# Recommended confidence threshold
|
||||
recommended_min_conf = CONFIDENCE_DEGRADED if is_degraded else CONFIDENCE_GOOD
|
||||
|
||||
report = ScanQualityReport(
|
||||
blur_score=round(blur_score, 1),
|
||||
contrast_score=round(contrast_score, 1),
|
||||
brightness=round(brightness, 1),
|
||||
is_blurry=is_blurry,
|
||||
is_low_contrast=is_low_contrast,
|
||||
is_degraded=is_degraded,
|
||||
quality_pct=quality_pct,
|
||||
recommended_min_conf=recommended_min_conf,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Scan quality: blur={report.blur_score} "
|
||||
f"contrast={report.contrast_score} "
|
||||
f"quality={report.quality_pct}% "
|
||||
f"degraded={report.is_degraded} "
|
||||
f"min_conf={report.recommended_min_conf}"
|
||||
)
|
||||
|
||||
return report
|
||||
# Backward-compat shim -- module moved to ocr/pipeline/scan_quality.py
|
||||
import importlib as _importlib
|
||||
import sys as _sys
|
||||
_sys.modules[__name__] = _importlib.import_module("ocr.pipeline.scan_quality")
|
||||
|
||||
Reference in New Issue
Block a user