helper region fn

This commit is contained in:
2ManyProjects 2026-01-12 18:20:53 -06:00
parent 4eaf692edb
commit 07067cd2a5

View file

@ -207,106 +207,166 @@ class StitchingScanner:
return (dx, dy) return (dx, dy)
def _get_row_start_regions(self, frame: np.ndarray, direction: ScanDirection):
"""
Helper method to calculate the comparison regions (ROIs) for row start alignment.
Returns a dictionary containing the coordinates for Y (vertical) and X (horizontal) checks.
Returns:
dict or None: Keys 'y_check', 'x_check', 'debug_info'. Returns None if mosaic not ready.
Each check contains:
'mosaic_roi': (y1, y2, x1, x2),
'frame_roi': (y1, y2, x1, x2)
"""
if self.mosaic is None:
return None
mh, mw = self.mosaic.shape[:2]
fh, fw = frame.shape[:2]
# Calculate layout
# x_offset is where the DOWN transition strips were placed horizontally
x_offset = max(0, mw - self.state.mosaic_init_width)
# Transition height is how much was added during DOWN transition
transition_height = mh - fh
regions = {
'y_check': None,
'x_check': None,
'debug_info': {
'x_offset': x_offset,
'transition_height': transition_height,
'mw': mw, 'mh': mh
}
}
vertical_overlap = min(200, fh // 3)
min_overlap = 50
# ---------------------------------------------
# 1. Y Alignment Regions (Top of frame vs Bottom of transition)
# ---------------------------------------------
transition_compare_y_start = transition_height
transition_compare_y_end = max(0, transition_height + vertical_overlap)
# X range centered on transition strips
x_end = x_offset
x_start = min(x_offset - fw, mw)
compare_width = x_end - x_start
if transition_compare_y_start >= 0 and compare_width >= min_overlap:
# Mosaic ROI coordinates
m_y1, m_y2 = transition_compare_y_start, transition_compare_y_end
m_x1, m_x2 = x_start, x_end
# Frame ROI coordinates (Top portion)
f_y1, f_y2 = 0, vertical_overlap
f_x1, f_x2 = 0, compare_width
regions['y_check'] = {
'mosaic_roi': (m_y1, m_y2, m_x1, m_x2),
'frame_roi': (f_y1, f_y2, f_x1, f_x2)
}
# ---------------------------------------------
# 2. X Alignment Regions
# ---------------------------------------------
horizontal_overlap = min(200, fw // 3)
# Y range for X check
y_start = 0
y_end = min(transition_height, fh)
if direction == ScanDirection.LEFT:
transition_x_start = x_offset
transition_x_end = max(x_offset, x_offset + horizontal_overlap)
if transition_x_end - transition_x_start >= min_overlap:
# Mosaic ROI
m_y1, m_y2 = y_start, y_end
m_x1, m_x2 = transition_x_start, transition_x_end
# Frame ROI (Right edge)
f_y1, f_y2 = 0, y_end
f_x1, f_x2 = fw - (transition_x_end - transition_x_start), fw
regions['x_check'] = {
'mosaic_roi': (m_y1, m_y2, m_x1, m_x2),
'frame_roi': (f_y1, f_y2, f_x1, f_x2)
}
else: # ScanDirection.RIGHT or others
transition_x_start = x_offset
transition_x_end = min(x_offset + horizontal_overlap, mw)
if transition_x_end - transition_x_start >= min_overlap:
# Mosaic ROI
m_y1, m_y2 = y_start, y_end
m_x1, m_x2 = transition_x_start, transition_x_end
# Frame ROI (Left edge)
f_y1, f_y2 = 0, y_end
f_x1, f_x2 = 0, transition_x_end - transition_x_start
regions['x_check'] = {
'mosaic_roi': (m_y1, m_y2, m_x1, m_x2),
'frame_roi': (f_y1, f_y2, f_x1, f_x2)
}
return regions
def _detect_row_start_alignment(self, frame: np.ndarray, direction: ScanDirection) -> AlignmentOffset: def _detect_row_start_alignment(self, frame: np.ndarray, direction: ScanDirection) -> AlignmentOffset:
""" """
Detect alignment at the START of a new row. Detect alignment at the START of a new row.
Uses _get_row_start_regions to determine overlap areas.
After a DOWN transition with prepend, the mosaic layout is:
- Y=0 to Ytransition_height: TRANSITION STRIPS (placed at x_offset)
- Ytransition_height to Y=mosaic_height: OLD row 1 content (shifted down)
The current frame should overlap with the TRANSITION STRIPS, not old row 1.
The camera is at the X position where transition strips were placed.
""" """
offset = AlignmentOffset() offset = AlignmentOffset()
with self._mosaic_lock: with self._mosaic_lock:
if self.mosaic is None: # --- Get the standard regions ---
regions = self._get_row_start_regions(frame, direction)
if not regions:
return offset return offset
mh, mw = self.mosaic.shape[:2] debug_info = regions['debug_info']
x_offset = debug_info['x_offset']
transition_height = debug_info['transition_height']
mw, mh = debug_info['mw'], debug_info['mh']
fh, fw = frame.shape[:2] fh, fw = frame.shape[:2]
# Calculate where transition strips are in the mosaic
# x_offset is where the DOWN transition strips were placed horizontally
x_offset = max(0, mw - self.state.mosaic_init_width)
# Transition height is how much was added during DOWN transition
# transition_height = mh - original_height_before_transition
# But since row 1 was just fh tall, transition_height = mh - fh
transition_height = mh - fh
self.log(f" Row-start alignment: mosaic {mw}x{mh}, frame {fw}x{fh}") self.log(f" Row-start alignment: mosaic {mw}x{mh}, frame {fw}x{fh}")
self.log(f" Transition strips at: X={x_offset}, Y=0 to Y={transition_height}")
self.log(f" Old row 1 shifted to: Y={transition_height} to Y={mh}")
# ========== DEBUG: Draw borders showing layout ========== # ========== DEBUG: Draw borders showing layout (Visuals only) ==========
# WHITE border: Where TRANSITION STRIPS are (this is what we should compare with) # WHITE border: Where TRANSITION STRIPS are
cv2.rectangle(self.mosaic, cv2.rectangle(self.mosaic,
(x_offset, 0), (x_offset, 0),
(min(x_offset + fw, mw), transition_height), (min(x_offset + fw, mw), transition_height),
(255, 255, 255), 3) # WHITE - transition strips location (255, 255, 255), 3)
self.log(f" DEBUG: WHITE border - TRANSITION STRIPS at X={x_offset}:{min(x_offset + fw, mw)}, Y=0:{transition_height}")
# BLUE line at transition boundary (where transition ends and old row 1 begins) # BLUE line at transition boundary
cv2.line(self.mosaic, (0, transition_height), (mw, transition_height), (255, 0, 0), 3) cv2.line(self.mosaic, (0, transition_height), (mw, transition_height), (255, 0, 0), 3)
self.log(f" DEBUG: BLUE line at Y={transition_height} (transition/row1 boundary)")
vertical_overlap = min(200, fh // 3)
min_overlap = 50 min_overlap = 50
# ============================================= # =============================================
# Step 1: Detect Y alignment # Step 1: Detect Y alignment
# ============================================= # =============================================
# Compare frame's TOP with the BOTTOM of transition strips y_check = regions.get('y_check')
# Frame's top portion overlaps with mosaic's transition bottom if y_check:
# # Extract coordinates
# Transition strips are at Y=0 to Y=transition_height my1, my2, mx1, mx2 = y_check['mosaic_roi']
# So transition bottom is around Y=(transition_height - overlap) to Y=transition_height fy1, fy2, fx1, fx2 = y_check['frame_roi']
transition_compare_y_start = transition_height # Extract image slices
transition_compare_y_end = max(0, transition_height + vertical_overlap) mosaic_transition_bottom = self.mosaic[my1:my2, mx1:mx2]
frame_compare = frame[fy1:fy2, fx1:fx2]
# Frame's TOP should overlap with transition BOTTOM # Draw Debug (Cyan)
frame_top = frame[:vertical_overlap, :] cv2.rectangle(self.mosaic, (mx1, my1), (mx2, my2), (255, 255, 0), 2)
self.log(f" DEBUG: CYAN border - mosaic Y comparison region")
# Get the X range - centered on where transition strips are
x_end = x_offset
x_start = min(x_offset - fw, mw)
compare_width = x_end - x_start
if transition_compare_y_start >= 0 and compare_width >= min_overlap:
# Mosaic region: bottom of transition strips
mosaic_transition_bottom = self.mosaic[transition_compare_y_start:transition_compare_y_end,
x_start:x_end]
# Frame region: top portion (what overlaps with transition)
frame_compare = frame_top[:, :compare_width]
# ========== DEBUG: Save frame with comparison region marked ==========
debug_frame = frame.copy()
cv2.rectangle(debug_frame, (0, 0), (compare_width, vertical_overlap),
(255, 255, 0), 2) # CYAN - frame's top region used for Y comparison
self.log(f" DEBUG: Frame Y comparison region: X=0:{compare_width}, Y=0:{vertical_overlap}")
try:
cv2.imwrite('/tmp/debug_frame_row2_start.png', debug_frame)
except:
pass
# ========== DEBUG: Draw CYAN border on mosaic for Y comparison region ==========
cv2.rectangle(self.mosaic,
(x_start, transition_compare_y_start),
(x_end, transition_compare_y_end),
(255, 255, 0), 2) # CYAN - mosaic comparison region for Y
self.log(f" DEBUG: CYAN border - mosaic Y comparison region X={x_start}:{x_end}, Y={transition_compare_y_start}:{transition_compare_y_end}")
# MAGENTA border: Where frame is EXPECTED to be placed (at transition position)
cv2.rectangle(self.mosaic,
(x_start, 0),
(x_end, fh),
(255, 0, 255), 2) # MAGENTA - expected frame position
self.log(f" DEBUG: MAGENTA border - expected frame position X={x_start}:{x_end}, Y=0:{fh}")
# Ensure dimensions match for displacement
min_w = min(frame_compare.shape[1], mosaic_transition_bottom.shape[1]) min_w = min(frame_compare.shape[1], mosaic_transition_bottom.shape[1])
min_h = min(frame_compare.shape[0], mosaic_transition_bottom.shape[0]) min_h = min(frame_compare.shape[0], mosaic_transition_bottom.shape[0])
@ -314,20 +374,11 @@ class StitchingScanner:
frame_compare = frame_compare[:min_h, :min_w] frame_compare = frame_compare[:min_h, :min_w]
mosaic_transition_bottom = mosaic_transition_bottom[:min_h, :min_w] mosaic_transition_bottom = mosaic_transition_bottom[:min_h, :min_w]
# ========== DEBUG: Save the comparison regions as images ==========
try:
cv2.imwrite('/tmp/debug_frame_top_region.png', frame_compare)
cv2.imwrite('/tmp/debug_mosaic_transition_region.png', mosaic_transition_bottom)
self.log(f" DEBUG: Saved comparison regions to /tmp/debug_*.png")
except:
pass
# Detect displacement # Detect displacement
dx_v, dy_v, conf_v = self._detect_displacement_with_confidence( dx_v, dy_v, conf_v = self._detect_displacement_with_confidence(
mosaic_transition_bottom, frame_compare) mosaic_transition_bottom, frame_compare)
self.log(f" Row-start Y alignment: dx={dx_v:.1f}, dy={dy_v:.1f}, conf={conf_v:.3f}") self.log(f" Row-start Y alignment: dx={dx_v:.1f}, dy={dy_v:.1f}, conf={conf_v:.3f}")
self.log(f" Compared frame[0:{vertical_overlap}] with mosaic[{transition_compare_y_start}:{transition_compare_y_end}]")
if conf_v > 0.1: if conf_v > 0.1:
offset.y_offset = dy_v offset.y_offset = dy_v
@ -336,95 +387,41 @@ class StitchingScanner:
# ============================================= # =============================================
# Step 2: Detect X alignment # Step 2: Detect X alignment
# ============================================= # =============================================
horizontal_overlap = min(200, fw // 3) x_check = regions.get('x_check')
if x_check:
my1, my2, mx1, mx2 = x_check['mosaic_roi']
fy1, fy2, fx1, fx2 = x_check['frame_roi']
if direction == ScanDirection.LEFT: mosaic_edge = self.mosaic[my1:my2, mx1:mx2]
# For LEFT scan: frame starts at the transition X position frame_edge = frame[fy1:fy2, fx1:fx2]
# Compare frame's RIGHT edge with mosaic's transition strip RIGHT edge
transition_x_start = x_offset # Draw Debug (Yellow)
transition_x_end = max(x_offset, x_offset + horizontal_overlap) cv2.rectangle(self.mosaic, (mx1, my1), (mx2, my2), (0, 255, 255), 2)
self.log(f" DEBUG: YELLOW border - mosaic X comparison region")
# Y range: within the transition strip area min_h = min(mosaic_edge.shape[0], frame_edge.shape[0])
y_start = 0 min_w = min(mosaic_edge.shape[1], frame_edge.shape[1])
y_end = min(transition_height, fh)
if transition_x_end - transition_x_start >= min_overlap: if min_h >= min_overlap and min_w >= min_overlap:
mosaic_edge = self.mosaic[y_start:y_end, transition_x_start:transition_x_end] mosaic_edge = mosaic_edge[:min_h, :min_w]
frame_edge = frame[:y_end, fw - (transition_x_end - transition_x_start):fw] frame_edge = frame_edge[:min_h, :min_w]
# ========== DEBUG: Draw YELLOW border for X comparison region ========== dx_h, dy_h, conf_h = self._detect_displacement_with_confidence(
cv2.rectangle(self.mosaic, mosaic_edge, frame_edge)
(transition_x_start, y_start),
(transition_x_end, y_end),
(0, 255, 255), 2) # YELLOW - mosaic comparison region for X
self.log(f" DEBUG: YELLOW border - mosaic X comparison region X={transition_x_start}:{transition_x_end}, Y={y_start}:{y_end}")
min_h = min(mosaic_edge.shape[0], frame_edge.shape[0]) self.log(f" Row-start X alignment: dx={dx_h:.1f}, dy={dy_h:.1f}, conf={conf_h:.3f}")
min_w = min(mosaic_edge.shape[1], frame_edge.shape[1])
if min_h >= min_overlap and min_w >= min_overlap: if conf_h > 0.1:
mosaic_edge = mosaic_edge[:min_h, :min_w] offset.x_offset = -dx_h
frame_edge = frame_edge[:min_h, :min_w] if conf_h > offset.confidence:
offset.confidence = conf_h
dx_h, dy_h, conf_h = self._detect_displacement_with_confidence(
mosaic_edge, frame_edge)
self.log(f" Row-start X alignment: dx={dx_h:.1f}, dy={dy_h:.1f}, conf={conf_h:.3f}")
if conf_h > 0.1:
offset.x_offset = -dx_h
if conf_h > offset.confidence:
offset.confidence = conf_h
else:
# For RIGHT scan at row start (similar logic but for left edge)
transition_x_start = x_offset
transition_x_end = min(x_offset + horizontal_overlap, mw)
y_start = 0
y_end = min(transition_height, fh)
if transition_x_end - transition_x_start >= min_overlap:
mosaic_edge = self.mosaic[y_start:y_end, transition_x_start:transition_x_end]
frame_edge = frame[:y_end, :transition_x_end - transition_x_start]
cv2.rectangle(self.mosaic,
(transition_x_start, y_start),
(transition_x_end, y_end),
(0, 255, 255), 2) # YELLOW
self.log(f" DEBUG: YELLOW border - mosaic X comparison region X={transition_x_start}:{transition_x_end}, Y={y_start}:{y_end}")
min_h = min(mosaic_edge.shape[0], frame_edge.shape[0])
min_w = min(mosaic_edge.shape[1], frame_edge.shape[1])
if min_h >= min_overlap and min_w >= min_overlap:
mosaic_edge = mosaic_edge[:min_h, :min_w]
frame_edge = frame_edge[:min_h, :min_w]
dx_h, dy_h, conf_h = self._detect_displacement_with_confidence(
mosaic_edge, frame_edge)
self.log(f" Row-start X alignment: dx={dx_h:.1f}, dy={dy_h:.1f}, conf={conf_h:.3f}")
if conf_h > 0.1:
offset.x_offset = -dx_h
if conf_h > offset.confidence:
offset.confidence = conf_h
# Limit maximum adjustment # Limit maximum adjustment
max_adjust = 80 max_adjust = 80
if abs(offset.x_offset) > max_adjust: offset.x_offset = max(-max_adjust, min(max_adjust, offset.x_offset))
self.log(f" Limiting X offset from {offset.x_offset:.1f} to ±{max_adjust}") offset.y_offset = max(-max_adjust, min(max_adjust, offset.y_offset))
offset.x_offset = max(-max_adjust, min(max_adjust, offset.x_offset))
if abs(offset.y_offset) > max_adjust:
self.log(f" Limiting Y offset from {offset.y_offset:.1f} to ±{max_adjust}")
offset.y_offset = max(-max_adjust, min(max_adjust, offset.y_offset))
offset.valid = offset.confidence > 0.1 offset.valid = offset.confidence > 0.1
if offset.valid:
self.log(f" Row-start alignment FINAL: X={offset.x_offset:.1f}, Y={offset.y_offset:.1f}, conf={offset.confidence:.3f}")
return offset return offset
def _detect_strip_alignment(self, frame: np.ndarray, direction: ScanDirection, def _detect_strip_alignment(self, frame: np.ndarray, direction: ScanDirection,