"""Independent image-only inverse for the band checkpoint count.""" from __future__ import annotations import math import numpy as np from PIL import Image PLOT_RECT = (110, 105, 990, 525) X_FRACTIONS = (0.12, 0.272, 0.424, 0.576, 0.728, 0.88) def _blue_mask(pixels: np.ndarray) -> np.ndarray: red = pixels[:, :, 0].astype(np.int16) green = pixels[:, :, 1].astype(np.int16) blue = pixels[:, :, 2].astype(np.int16) return (blue - red > 70) & (green - red > 28) & (blue > 135) def _dark_mask(pixels: np.ndarray) -> np.ndarray: return np.max(pixels, axis=2) < 95 def _groups(values: np.ndarray, gap: int = 8) -> list[np.ndarray]: if len(values) == 0: return [] values = np.unique(values) breaks = np.flatnonzero(np.diff(values) > gap) + 1 return list(np.split(values, breaks)) def _edge_heights(pixels: np.ndarray, x: int) -> tuple[float, float]: x0, y0, x1, y1 = PLOT_RECT mask = _blue_mask(pixels) x_left = max(x0 + 12, x - 7) x_right = min(x1 - 12, x + 8) ys = np.flatnonzero(mask[y0 + 12 : y1 - 12, x_left:x_right]).astype(float) if len(ys) == 0: raise ValueError("blue boundary is not visible") # Flattened row indices are not directly recoverable from flatnonzero on a # 2-D crop, so recover row coordinates explicitly. row_coords = np.nonzero(mask[y0 + 12 : y1 - 12, x_left:x_right])[0] + y0 + 12 groups = [group for group in _groups(row_coords) if len(group) >= 2] if len(groups) < 2: raise ValueError("two blue boundaries are not separable") return float(np.mean(groups[0])), float(np.mean(groups[-1])) def _trace_height(pixels: np.ndarray, x: int) -> float: x0, y0, x1, y1 = PLOT_RECT mask = _dark_mask(pixels) x_left = max(x0 + 18, x - 13) x_right = min(x1 - 18, x + 14) row_coords = np.nonzero(mask[y0 + 18 : y1 - 18, x_left:x_right])[0] + y0 + 18 if len(row_coords) < 20: raise ValueError("black checkpoint marker is not visible") return float(np.median(row_coords)) def decision_from_image(image: Image.Image) -> str: """Measure blue boundaries and black marker centers from the final raster only.""" pixels = np.asarray(image.convert("RGB")) x0, _, x1, _ = PLOT_RECT xs = [round(x0 + fraction * (x1 - x0)) for fraction in X_FRACTIONS] count = 0 for x in xs: upper_y, lower_y = _edge_heights(pixels, x) trace_y = _trace_height(pixels, x) if upper_y + 4.0 < trace_y < lower_y - 4.0: count += 1 return str(count)