Spaces:
Sleeping
Sleeping
| """ | |
| Single-staff dewarp: sample mid-staff ink curve along x, fit a low-degree polynomial, | |
| apply vertical remap so the curve becomes flat (OpenCV remap). | |
| """ | |
| from __future__ import annotations | |
| from typing import Any, Dict, List, Optional, Tuple | |
| import cv2 | |
| import numpy as np | |
| def _column_mid_peak( | |
| horizontal: np.ndarray, | |
| x: int, | |
| line_ys_local: List[int], | |
| ) -> Optional[int]: | |
| """One y per column near expected staff band (middle line anchor).""" | |
| h = horizontal.shape[0] | |
| if h < 8: | |
| return None | |
| mid = int(round((line_ys_local[0] + line_ys_local[-1]) / 2.0)) | |
| span = max(int(line_ys_local[-1] - line_ys_local[0]) * 2, h // 3) | |
| y0 = max(0, mid - span) | |
| y1 = min(h, mid + span) | |
| col = horizontal[y0:y1, x] | |
| if col.size == 0 or float(np.max(col)) < 1.0: | |
| return None | |
| return int(y0 + int(np.argmax(col))) | |
| def rectify_staff_crop_bgr( | |
| work_bgr: np.ndarray, | |
| staff_x0: int, | |
| staff_y0: int, | |
| staff_w: int, | |
| staff_h: int, | |
| line_ys_global: List[int], | |
| ) -> Tuple[np.ndarray, Dict[str, Any]]: | |
| """ | |
| Crop staff from work image, estimate vertical curvature of staff ink, remap to flatten. | |
| Returns (rectified_bgr_crop, meta) where meta includes staff_dewarp status. | |
| line_ys_global: five staff line y positions in full work image coordinates. | |
| """ | |
| meta: Dict[str, Any] = { | |
| "staff_dewarp": "skipped", | |
| "staff_dewarp_model": None, | |
| "staff_dewarp_detail": None, | |
| } | |
| h_img, w_img = work_bgr.shape[:2] | |
| x0 = max(0, int(staff_x0)) | |
| y0 = max(0, int(staff_y0)) | |
| x1 = min(w_img, x0 + max(1, int(staff_w))) | |
| y1 = min(h_img, y0 + max(1, int(staff_h))) | |
| roi = work_bgr[y0:y1, x0:x1].copy() | |
| if roi.size == 0: | |
| return roi, meta | |
| line_local = [int(round(y - y0)) for y in line_ys_global] | |
| if len(line_local) < 5 or min(line_local) < 0 or max(line_local) >= roi.shape[0]: | |
| meta["staff_dewarp_detail"] = "invalid_line_ys" | |
| return roi, meta | |
| gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) | |
| blur = cv2.GaussianBlur(gray, (3, 3), 0) | |
| binary = cv2.adaptiveThreshold( | |
| blur, | |
| 255, | |
| cv2.ADAPTIVE_THRESH_GAUSSIAN_C, | |
| cv2.THRESH_BINARY_INV, | |
| 25, | |
| 11, | |
| ) | |
| kw = max(15, roi.shape[1] // 24) | |
| horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kw, 1)) | |
| horizontal = cv2.morphologyEx(binary, cv2.MORPH_OPEN, horizontal_kernel, iterations=1) | |
| w = roi.shape[1] | |
| step = max(1, w // 120) | |
| xs_list: List[float] = [] | |
| ys_list: List[float] = [] | |
| for x in range(0, w, step): | |
| yp = _column_mid_peak(horizontal, x, line_local) | |
| if yp is not None: | |
| xs_list.append(float(x)) | |
| ys_list.append(float(yp)) | |
| if len(xs_list) < 6: | |
| meta["staff_dewarp_detail"] = "too_few_samples" | |
| return roi, meta | |
| xs = np.array(xs_list, dtype=np.float64) | |
| ys = np.array(ys_list, dtype=np.float64) | |
| deg = 2 if len(xs_list) >= 8 else 1 | |
| try: | |
| coeffs = np.polyfit(xs, ys, deg) | |
| except (np.linalg.LinAlgError, ValueError): | |
| meta["staff_dewarp_detail"] = "polyfit_failed" | |
| return roi, meta | |
| h, w = roi.shape[:2] | |
| x_grid = np.arange(w, dtype=np.float64) | |
| curve = np.polyval(coeffs, x_grid) | |
| c = float(np.mean(curve)) | |
| delta = (curve - c).astype(np.float32) | |
| map_x = np.tile(np.arange(w, dtype=np.float32), (h, 1)) | |
| map_y = np.arange(h, dtype=np.float32)[:, None] + delta[None, :] | |
| out = cv2.remap(roi, map_x, map_y, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE) | |
| meta["staff_dewarp"] = "applied" | |
| meta["staff_dewarp_model"] = f"poly{deg}" | |
| meta["staff_dewarp_detail"] = "midline_peak_column_samples" | |
| return out, meta | |