Add package skeleton, source classification and raster loading
The pdf module is the whole of M1 except the debug overlay, which needs detection results to draw. Source type is detected per PDF by looking for a page-covering image, and is always reported for confirmation rather than applied silently (ADR 0004). Rasters are extracted via Pixmap(doc, xref) rather than by decoding extract_image() bytes, because MuPDF handles JBIG2 and CCITT scans that no image library will. Scanned pages are extracted at the embedded image's native resolution; only vector pages are rendered, at 600 DPI. Verified against the corpus: Elaman nalka (vector) renders 4959x7017, Ketun joululaulu (scan) loads 1653x2332, Engel (scan) 2552x3504 — and Engel's page 2 is 2480 wide where page 1 is 2552, so scan width varies within one PDF. tests/test_pdf.py builds its own PDFs so the check runs without corpus files, which are copyrighted and gitignored. Closes #1, #2, #3
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"""Runnable check for source classification and raster loading.
|
||||
|
||||
Builds its own PDFs so it needs no corpus files (scores are copyrighted and
|
||||
gitignored). Run with `python tests/test_pdf.py`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pymupdf
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from noteman_slicer.pdf import SourceType, open_source, page_raster # noqa: E402
|
||||
|
||||
A4 = pymupdf.paper_rect("a4")
|
||||
|
||||
|
||||
def _vector_pdf(path: Path, pages: int = 2) -> None:
|
||||
doc = pymupdf.open()
|
||||
for _ in range(pages):
|
||||
page = doc.new_page(width=A4.width, height=A4.height)
|
||||
page.draw_line((50, 100), (A4.width - 50, 100))
|
||||
page.insert_text((50, 150), "notation", fontsize=24)
|
||||
doc.save(path)
|
||||
|
||||
|
||||
def _scan_pdf(path: Path, pages: int = 2, w: int = 1653, h: int = 2332) -> None:
|
||||
"""Each page is one full-page grayscale image — what a real scan looks like."""
|
||||
art = np.full((h, w), 255, np.uint8)
|
||||
art[500:505, 100 : w - 100] = 0 # a staff line, so it isn't uniform
|
||||
pix = pymupdf.Pixmap(pymupdf.csGRAY, w, h, bytearray(art.tobytes()), False)
|
||||
doc = pymupdf.open()
|
||||
for _ in range(pages):
|
||||
page = doc.new_page(width=A4.width, height=A4.height)
|
||||
page.insert_image(page.rect, pixmap=pix)
|
||||
doc.save(path)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
tmp = Path(__file__).with_name("_tmp")
|
||||
tmp.mkdir(exist_ok=True)
|
||||
vec, scan = tmp / "vector.pdf", tmp / "scan.pdf"
|
||||
_vector_pdf(vec)
|
||||
_scan_pdf(scan)
|
||||
|
||||
src = open_source(vec)
|
||||
assert src.type is SourceType.VECTOR, src.type
|
||||
assert not src.overridden
|
||||
page = page_raster(src, 0)
|
||||
# Rendered at 600 DPI, so an A4 page is ~4960px wide.
|
||||
assert page.ndim == 2 and page.dtype == np.uint8, (page.ndim, page.dtype)
|
||||
assert 4900 < page.shape[1] < 5000, page.shape
|
||||
src.close()
|
||||
|
||||
src = open_source(scan)
|
||||
assert src.type is SourceType.RASTER, src.type
|
||||
page = page_raster(src, 0)
|
||||
# Native resolution of the embedded image, NOT a 600 DPI re-render.
|
||||
assert page.shape == (2332, 1653), page.shape
|
||||
assert page.min() == 0 and page.max() == 255, (page.min(), page.max())
|
||||
src.close()
|
||||
|
||||
# An override must win over detection, and say so.
|
||||
src = open_source(scan, SourceType.VECTOR)
|
||||
assert src.type is SourceType.VECTOR and src.detected is SourceType.RASTER
|
||||
assert src.overridden
|
||||
assert page_raster(src, 0).shape[1] > 4000, "override must force a render"
|
||||
src.close()
|
||||
|
||||
for f in (vec, scan):
|
||||
f.unlink()
|
||||
tmp.rmdir()
|
||||
print("ok")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user