Systems anchor on the vertical bracket and expand over nearby ink, per ADR 0006. Two corrections the corpus forced: - Expansion measures distance from the bracket, never from the growing extent. A chaining expansion hops between the closely-stacked lines of a title block and walks the whole way up the page — on Engel p1 it swallowed the title into system 1 and the copyright footer into system 3. - Reach is 1.5 staff heights, which takes in the lyrics below the last staff and the tempo mark and INTRO box above the first, while leaving the title block and footer out. Verified against the corpus. Ketun joululaulu: 2 systems per page except p7 (3) and p12 (1), staff 48px throughout, skew -2.7 to +1.2 per page, and p2 system 1 spans 179-1071 where its bracket is 177-994 — the difference being the bottom voice's lyric line. Engel: 2-3 systems per page, staff 70-72px. Elaman nalka: 3 systems per page, 0 skew, staff 118px at 600 DPI. The overlay dump is what made both bugs visible, and stays as the tool for diagnosing a page that comes out wrong. Closes #4, #5, #6, #7, #8, #9
56 lines
1.8 KiB
Python
56 lines
1.8 KiB
Python
"""Debug overlay: what detection proposed, drawn on the page.
|
|
|
|
The fastest way to judge a detection change, and the tool for working out why
|
|
song #40 came out wrong. Kept after release for that reason.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import cv2
|
|
import numpy as np
|
|
|
|
from .detect import PageDetection
|
|
|
|
_SYSTEM = (0, 160, 0)
|
|
_CUT = (0, 0, 255)
|
|
_PROFILE = (220, 120, 0)
|
|
_PREVIEW_WIDTH = 1100
|
|
|
|
|
|
def draw(gray: np.ndarray, detection: PageDetection) -> np.ndarray:
|
|
"""Straightened page with systems boxed, cuts lined, row profile down the side."""
|
|
from .detect import deskew, row_darkness
|
|
|
|
straight = deskew(gray, detection.skew)
|
|
vis = cv2.cvtColor(straight, cv2.COLOR_GRAY2BGR)
|
|
h, w = straight.shape
|
|
thickness = max(1, w // 700)
|
|
|
|
profile = row_darkness(straight)
|
|
if profile.max() > 0:
|
|
scaled = (profile / profile.max() * (w * 0.08)).astype(int)
|
|
for y in range(0, h, max(1, h // 900)):
|
|
cv2.line(vis, (0, y), (int(scaled[y]), y), _PROFILE, 1)
|
|
|
|
for i, system in enumerate(detection.systems):
|
|
cv2.rectangle(vis, (2, system.top), (w - 3, system.bottom), _SYSTEM, thickness)
|
|
label = f"{i + 1}"
|
|
if system.staff_height:
|
|
label += f" staff {system.staff_height:.0f}px"
|
|
cv2.putText(vis, label, (int(w * 0.10), system.top + int(h * 0.02)),
|
|
cv2.FONT_HERSHEY_SIMPLEX, w / 1400, _SYSTEM, thickness)
|
|
|
|
for y in detection.cuts:
|
|
cv2.line(vis, (0, y), (w, y), _CUT, thickness)
|
|
|
|
return vis
|
|
|
|
|
|
def write(gray: np.ndarray, detection: PageDetection, path: Path) -> Path:
|
|
vis = draw(gray, detection)
|
|
height = int(vis.shape[0] * _PREVIEW_WIDTH / vis.shape[1])
|
|
cv2.imwrite(str(path), cv2.resize(vis, (_PREVIEW_WIDTH, height), interpolation=cv2.INTER_AREA))
|
|
return path
|