"""Runnable check for detection, on a synthetic page. Draws the structure that matters — a bracket per system, staves, lyrics close below, and a title and footer far away — so the check is about the algorithm rather than about any one scan. Run with `python tests/test_detect.py`. """ from __future__ import annotations import sys from pathlib import Path import numpy as np sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from noteman_slicer.detect import deskew, deskew_angle, detect_page # noqa: E402 W, H = 1000, 1400 STAFF_GAP = 15 # → staff height 60, so expansion reaches 90px past a bracket def _system(page: np.ndarray, top: int) -> tuple[int, int]: """Two staves joined by a bracket, with a lyric line below. Returns its span.""" bottom = top + 200 page[top:bottom, 100:104] = 0 # the bracket for staff_top in (top, top + 140): for i in range(5): y = staff_top + i * STAFF_GAP page[y : y + 2, 110:900] = 0 page[staff_top + 90 : staff_top + 105, 200:800] = 0 # lyrics under the staff return top, bottom def _page() -> np.ndarray: page = np.full((H, W), 255, np.uint8) page[50:70, 300:700] = 0 # title, far above system 1 _system(page, 200) _system(page, 700) page[1350:1365, 100:600] = 0 # footer, far below system 2 return page def main() -> int: page = _page() det = detect_page(page) assert len(det.systems) == 2, f"expected 2 systems, got {len(det.systems)}" assert len(det.cuts) == 1, det.cuts first, second = det.systems # The bracket spans 200–400; the lyric line under the lower staff reaches # ~445 and must be absorbed. assert first.top == 200, first.top assert 400 < first.bottom < 500, first.bottom assert second.top == 700, second.top # The title and footer are far from any bracket and must not be swallowed — # the bug that a chaining expansion reintroduces. assert first.top > 70, "title block was swallowed" assert second.bottom < 1350, "footer was swallowed" # The cut falls between the two systems, in the whitespace. assert first.bottom < det.cuts[0] < second.top, det.cuts assert first.staff_height is not None assert abs(first.staff_height - STAFF_GAP * 4) < STAFF_GAP, first.staff_height # Skew is recovered to within one fine step. for angle in (-1.5, 0.8): found = deskew_angle(deskew(page, angle)) assert abs(found + angle) <= 0.15, f"skew {angle}: got {found}" # No brackets: every ink run is its own system. bare = np.full((H, W), 255, np.uint8) for y in (200, 500, 800): bare[y : y + 20, 100:900] = 0 assert len(detect_page(bare).systems) == 3 assert detect_page(bare).bracketless print("ok") return 0 if __name__ == "__main__": sys.exit(main())