Fix clipped receipt margins, add time-of-day naming, speed up rotation detection

- vision/document.py: pad_quad() expands the detected quad outward
  before warping so a straight-line contour fit doesn't clip the first
  character of every line on a slightly curled/wrinkled receipt
- ocr/extract.py + llm/vision.py: extract transaction time (HH:MM) so
  same-day repeat visits to a vendor get distinct filenames
  (2023-03-20_1525_walmart.pdf vs. a later same-day trip)
- vision/orient.py: downscale before Tesseract OSD/confidence-sweep
  rotation detection — this was the dominant cost in the detection
  phase (7x speedup: 472s -> 67s on a 36s test video)
This commit is contained in:
2026-07-08 18:55:37 -04:00
parent a43ab20df6
commit 5be9d5935d
11 changed files with 160 additions and 12 deletions
+7 -3
View File
@@ -25,7 +25,7 @@ from paperpod.naming.filename import build_name, dedupe_stem
from paperpod.ocr.extract import run_ocr, extract_store_name
from paperpod.pdf.build import images_to_pdf
from paperpod.vision.capture_events import _plausible_capture, find_document_events
from paperpod.vision.document import detect_document, warp_document
from paperpod.vision.document import detect_document, pad_quad, warp_document
from paperpod.vision.enhance import enhance_for_ocr
from paperpod.vision.motion import find_segments, motion_score, prepare_gray
from paperpod.vision.orient import auto_rotate
@@ -132,7 +132,8 @@ def run_detection(video_path: str | Path, cfg: Config, out_dir: str | Path) -> d
cv2.imwrite(str(frame_path), frame.image)
record["frame_path"] = str(frame_path.relative_to(out_dir))
crop = warp_document(frame.image, detection.quad)
padded_quad = pad_quad(detection.quad, frame.image.shape, cfg.document.pad_margin)
crop = warp_document(frame.image, padded_quad)
refine_info: dict[str, Any] = {}
if cfg.document.refine:
@@ -285,6 +286,7 @@ def run_export(report: dict[str, Any], cfg: Config, out_dir: str | Path) -> dict
vendor = (llm.vendor if llm else None) or ocr.vendor
date = (llm.date if llm else None) or ocr.date
time = (llm.time if llm else None) or ocr.time
total = (llm.total if llm else None) or ocr.total
form_code = (llm.form_code if llm else None) or ocr.form_code
tax_year = (llm.tax_year if llm else None) or ocr.tax_year
@@ -312,6 +314,7 @@ def run_export(report: dict[str, Any], cfg: Config, out_dir: str | Path) -> dict
form_code=form_code,
tax_year=tax_year,
org_name=org_name,
time=time,
)
stem = dedupe_stem(naming.stem, used_stems)
@@ -327,6 +330,7 @@ def run_export(report: dict[str, Any], cfg: Config, out_dir: str | Path) -> dict
"ocr_vendor": vendor or "",
"ocr_form_code": form_code or "",
"ocr_date": date or "",
"ocr_time": time or "",
"ocr_total": total or "",
"ocr_confidence": round(ocr.mean_confidence, 1),
"llm_description": llm.description if llm else "",
@@ -341,7 +345,7 @@ def run_export(report: dict[str, Any], cfg: Config, out_dir: str | Path) -> dict
with open(out_dir / "export_summary.csv", "w", newline="") as f:
fieldnames = [
"timestamp", "pod_id", "page_count", "source_of_name",
"ocr_vendor", "ocr_form_code", "ocr_date", "ocr_total", "ocr_confidence",
"ocr_vendor", "ocr_form_code", "ocr_date", "ocr_time", "ocr_total", "ocr_confidence",
"llm_description", "llm_issues",
"detection_confidence", "rotation_applied",
"final_filename", "pdf_path",