continuous drift detection

This commit is contained in:
2ManyProjects 2026-01-11 00:10:43 -06:00
parent 5177eea77e
commit d7bd2fe6cd

View file

@ -191,96 +191,95 @@ class StitchingScanner:
"""
offset = AlignmentOffset()
with self._mosaic_lock:
if self.mosaic is None:
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
mh, mw = self.mosaic.shape[:2]
fh, fw = frame.shape[:2]
# Extract regions
mosaic_region = self.mosaic[expected_y:expected_y + fh, mw - overlap_width:mw]
frame_region = frame[:, :overlap_width]
# Clamp expected positions
expected_y = max(0, min(expected_y, mh - fh))
expected_x = max(0, min(expected_x, mw - fw))
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 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})")
if overlap_width < 30:
return offset
mosaic_region = mosaic_region[:min_h, :min_w]
frame_region = frame_region[:min_h, :min_w]
# 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
# 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")
if actual_overlap < 30:
return offset
offset.x_offset = dx
offset.y_offset = dy
offset.confidence = confidence
offset.valid = confidence > 0.1 # Require minimum confidence
mosaic_region = self.mosaic[expected_y:expected_y + fh, mosaic_x_start:mosaic_x_end]
frame_region = frame[:, fw - actual_overlap:]
if offset.valid:
self.log(f" Strip alignment: X={dx:.1f}, Y={dy:.1f}, conf={confidence:.3f}")
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})")