From 00f7a7154c1048d571704e69f948b6926464d869 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sat, 11 Apr 2026 16:52:23 +0200 Subject: [PATCH] Fix left-side gutter detection: find peak instead of scanning from edge Left-side book fold shadows have a V-shape: brightness dips from the edge toward a peak at ~5-10% of width, then rises again. The previous algorithm scanned from the edge inward and immediately found a low dark fraction (0.13 at x=0), missing the gutter entirely. Now finds the PEAK of the dark fraction profile first, then scans from that peak toward the page center to find the transition point. Works for both V-shaped left gutters and edge-darkening right gutters. Co-Authored-By: Claude Opus 4.6 (1M context) --- klausur-service/backend/page_crop.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/klausur-service/backend/page_crop.py b/klausur-service/backend/page_crop.py index 1356fdd..108e095 100644 --- a/klausur-service/backend/page_crop.py +++ b/klausur-service/backend/page_crop.py @@ -535,19 +535,30 @@ def _detect_gutter_continuity( if region_w <= 2 * margin + 10: return None - # Scan from edge inward to find where frac drops below transition threshold + # Find the peak of dark fraction (gutter center). + # For right gutters the peak is near the edge; for left gutters + # (V-shaped spine shadow) the peak may be well inside the region. transition_thresh = 0.50 + peak_frac = float(np.max(frac_smooth[margin:region_w - margin])) + + if peak_frac < 0.70: + logger.debug( + "%s gutter: peak dark fraction %.2f < 0.70", side.capitalize(), peak_frac, + ) + return None + + peak_x = int(np.argmax(frac_smooth[margin:region_w - margin])) + margin gutter_inner = None # local x in search_region if side == "right": - # Scan from right edge (region_w - 1) inward (toward 0) - for x in range(region_w - 1 - margin, margin, -1): + # Scan from peak toward the page center (leftward) + for x in range(peak_x, margin, -1): if frac_smooth[x] < transition_thresh: - gutter_inner = x + 1 # crop just past the transition + gutter_inner = x + 1 break else: - # Scan from left edge (0) inward (toward region_w) - for x in range(margin, region_w - margin): + # Scan from peak toward the page center (rightward) + for x in range(peak_x, region_w - margin): if frac_smooth[x] < transition_thresh: gutter_inner = x - 1 break