From 4dac21b7c0d5a5e39eff001fdf1f826ff75c916d Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Thu, 16 Apr 2026 07:12:57 +0000 Subject: [PATCH] fix(vision): erode mask pre-CC to sever anti-aliasing bridges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With tol=25 all dots in the strip fused into one blob via 1px anti-aliased bridges between adjacent dots → centroid landed mid-strip instead of on the rightmost dot. Erosion (3x3 kernel, 2 iters) cleanly separates discrete dots before connected-components labelling. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/atm/vision.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/atm/vision.py b/src/atm/vision.py index 2a1016c..6821062 100644 --- a/src/atm/vision.py +++ b/src/atm/vision.py @@ -103,9 +103,11 @@ def find_rightmost_dot( diff = np.linalg.norm(roi_img.astype(np.float32) - bgr_bg, axis=2) mask = (diff > bg_tol).astype(np.uint8) - # Connected components → one component per dot. Anti-aliasing bridges - # between adjacent dots are small enough that 8-connectivity still - # separates them cleanly. + # Anti-aliasing bridges between adjacent dots can fuse the entire strip + # into one blob under 8-connectivity. Erode by 2px to sever those bridges + # before labelling — dots shrink but remain well above min_cluster_px. + kernel = np.ones((3, 3), dtype=np.uint8) + mask = cv2.erode(mask, kernel, iterations=2) n_labels, _labels, stats, centroids = cv2.connectedComponentsWithStats( mask, connectivity=8, )