Files
noteman-slicer/tests/test_pdf.py
T
Esa Kataja e2f3e8fbdc 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
2026-07-28 22:33:26 +03:00

83 lines
2.6 KiB
Python

"""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())