From d7bd2fe6cd6ecb59a3eec82a3b775a3e421b1431 Mon Sep 17 00:00:00 2001 From: 2ManyProjects Date: Sun, 11 Jan 2026 00:10:43 -0600 Subject: [PATCH] continuous drift detection --- src/stitching_scanner.py | 190 ++++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 92 deletions(-) diff --git a/src/stitching_scanner.py b/src/stitching_scanner.py index e0a404c..56845c1 100644 --- a/src/stitching_scanner.py +++ b/src/stitching_scanner.py @@ -191,97 +191,96 @@ class StitchingScanner: """ offset = AlignmentOffset() - with self._mosaic_lock: - if self.mosaic is None: - return offset - - mh, mw = self.mosaic.shape[:2] - fh, fw = frame.shape[:2] - - # Clamp expected positions - expected_y = max(0, min(expected_y, mh - fh)) - expected_x = max(0, min(expected_x, mw - fw)) - - if direction == ScanDirection.RIGHT: - # We're appending to the right - # Compare left portion of frame with right edge of mosaic - overlap_width = min(fw // 2, mw - expected_x, 200) # Use up to 200px overlap - - if overlap_width < 30: - return offset - - # Extract regions - mosaic_region = self.mosaic[expected_y:expected_y + fh, mw - overlap_width:mw] - frame_region = frame[:, :overlap_width] - - elif direction == ScanDirection.LEFT: - # We're placing within existing mosaic, moving left - # Compare right portion of frame with mosaic at expected position - overlap_width = min(fw // 2, mw - expected_x, 200) - - if overlap_width < 30: - return offset - - # The frame's right edge should align with mosaic at expected_x + fw - mosaic_x_end = min(expected_x + fw, mw) - mosaic_x_start = max(mosaic_x_end - overlap_width, 0) - actual_overlap = mosaic_x_end - mosaic_x_start - - if actual_overlap < 30: - return offset - - mosaic_region = self.mosaic[expected_y:expected_y + fh, mosaic_x_start:mosaic_x_end] - frame_region = frame[:, fw - actual_overlap:] - - elif direction == ScanDirection.DOWN: - # We're appending below - # Compare top portion of frame with bottom edge of mosaic - overlap_height = min(fh // 2, mh - expected_y, 200) - - if overlap_height < 30: - return offset - - mosaic_region = self.mosaic[mh - overlap_height:mh, expected_x:expected_x + fw] - frame_region = frame[:overlap_height, :] - - else: # UP - # Compare bottom portion of frame with top edge of mosaic - overlap_height = min(fh // 2, expected_y, 200) - - if overlap_height < 30: - return offset - - mosaic_region = self.mosaic[:overlap_height, expected_x:expected_x + fw] - frame_region = frame[fh - overlap_height:, :] - - # Ensure regions have the same size - min_h = min(mosaic_region.shape[0], frame_region.shape[0]) - min_w = min(mosaic_region.shape[1], frame_region.shape[1]) - - if min_h < 30 or min_w < 30: - self.log(f"Strip alignment: overlap too small ({min_w}x{min_h})") - return offset - - mosaic_region = mosaic_region[:min_h, :min_w] - frame_region = frame_region[:min_h, :min_w] - - # Detect displacement with confidence - dx, dy, confidence = self._detect_displacement_with_confidence(mosaic_region, frame_region) - - # Sanity check - reject large displacements - max_adjust = 50 # Max pixels to adjust - if abs(dx) > max_adjust or abs(dy) > max_adjust: - self.log(f"Strip alignment: displacement too large ({dx:.1f}, {dy:.1f}), ignoring") - return offset - - offset.x_offset = dx - offset.y_offset = dy - offset.confidence = confidence - offset.valid = confidence > 0.1 # Require minimum confidence - - if offset.valid: - self.log(f" Strip alignment: X={dx:.1f}, Y={dy:.1f}, conf={confidence:.3f}") + if self.mosaic is None: + return offset + mh, mw = self.mosaic.shape[:2] + fh, fw = frame.shape[:2] + + # Clamp expected positions + expected_y = max(0, min(expected_y, mh - fh)) + expected_x = max(0, min(expected_x, mw - fw)) + + if direction == ScanDirection.RIGHT: + # We're appending to the right + # Compare left portion of frame with right edge of mosaic + overlap_width = min(fw // 2, mw - expected_x, 200) # Use up to 200px overlap + + if overlap_width < 30: + return offset + + # Extract regions + mosaic_region = self.mosaic[expected_y:expected_y + fh, mw - overlap_width:mw] + frame_region = frame[:, :overlap_width] + + elif direction == ScanDirection.LEFT: + # We're placing within existing mosaic, moving left + # Compare right portion of frame with mosaic at expected position + overlap_width = min(fw // 2, mw - expected_x, 200) + + if overlap_width < 30: + return offset + + # The frame's right edge should align with mosaic at expected_x + fw + mosaic_x_end = min(expected_x + fw, mw) + mosaic_x_start = max(mosaic_x_end - overlap_width, 0) + actual_overlap = mosaic_x_end - mosaic_x_start + + if actual_overlap < 30: + return offset + + mosaic_region = self.mosaic[expected_y:expected_y + fh, mosaic_x_start:mosaic_x_end] + frame_region = frame[:, fw - actual_overlap:] + + elif direction == ScanDirection.DOWN: + # We're appending below + # Compare top portion of frame with bottom edge of mosaic + overlap_height = min(fh // 2, mh - expected_y, 200) + + if overlap_height < 30: + return offset + + mosaic_region = self.mosaic[mh - overlap_height:mh, expected_x:expected_x + fw] + frame_region = frame[:overlap_height, :] + + else: # UP + # Compare bottom portion of frame with top edge of mosaic + overlap_height = min(fh // 2, expected_y, 200) + + if overlap_height < 30: + return offset + + mosaic_region = self.mosaic[:overlap_height, expected_x:expected_x + fw] + frame_region = frame[fh - overlap_height:, :] + + # Ensure regions have the same size + min_h = min(mosaic_region.shape[0], frame_region.shape[0]) + min_w = min(mosaic_region.shape[1], frame_region.shape[1]) + + if min_h < 30 or min_w < 30: + self.log(f"Strip alignment: overlap too small ({min_w}x{min_h})") + return offset + + mosaic_region = mosaic_region[:min_h, :min_w] + frame_region = frame_region[:min_h, :min_w] + + # Detect displacement with confidence + dx, dy, confidence = self._detect_displacement_with_confidence(mosaic_region, frame_region) + + # Sanity check - reject large displacements + max_adjust = 50 # Max pixels to adjust + if abs(dx) > max_adjust or abs(dy) > max_adjust: + self.log(f"Strip alignment: displacement too large ({dx:.1f}, {dy:.1f}), ignoring") + return offset + + offset.x_offset = dx + offset.y_offset = dy + offset.confidence = confidence + offset.valid = confidence > 0.1 # Require minimum confidence + + if offset.valid: + self.log(f" Strip alignment: X={dx:.1f}, Y={dy:.1f}, conf={confidence:.3f}") + return offset # ========================================================================= @@ -381,7 +380,8 @@ class StitchingScanner: y_offset = y_offset - int(round(alignment_y)) # Clamp x_offset to valid range - x_offset = max(0, min(x_offset, w_base - blend_w)) + # x_offset = max(0, min(x_offset, w_base - blend_w)) + x_offset = 0 - min(x_offset, w_base) # Handle strip cropping if y_offset is negative (strip protrudes above frame) strip_y_start = 0 # How much to crop from top of strip @@ -837,7 +837,7 @@ class StitchingScanner: no_movement_count = 0 max_no_movement = 50 stop_reason = 'stopped' - + self.log(f"Scanning 2..") while self.running and not self.paused: if time.time() - start_time > self.config.max_scan_time: self.log("Scan timeout") @@ -871,6 +871,7 @@ class StitchingScanner: curr_frame = self._capture_frame() dx, dy = self._detect_displacement_robust(self._prev_frame, curr_frame) + self.log(f"Scanning dx{dx} dy{dy}..") self._displacement_since_append_x += dx self._displacement_since_append_y += dy total_x += dx @@ -885,6 +886,8 @@ class StitchingScanner: # Edge detection movement = abs(dx) if direction in [ScanDirection.RIGHT, ScanDirection.LEFT] else abs(dy) + + self.log(f"Scanning movement{movement}..") if movement < 1.0: no_movement_count += 1 if no_movement_count >= max_no_movement: @@ -896,7 +899,10 @@ class StitchingScanner: # Append when threshold reached (with continuous alignment) disp = abs(self._displacement_since_append_x) if direction in [ScanDirection.RIGHT, ScanDirection.LEFT] else abs(self._displacement_since_append_y) + + self.log(f"Scanning disp{disp}..") if disp >= threshold_pixels: + self.log(f"Scanning threshold_pixels..") self._append_strip(curr_frame, direction) self.log(f"Appended {disp:.1f}px, mosaic: {self.state.mosaic_width}x{self.state.mosaic_height}, align: ({self._cumulative_align_x:.1f}, {self._cumulative_align_y:.1f})")