relative horizontal blend
This commit is contained in:
parent
86d9b817e5
commit
5937d5fb68
1 changed files with 89 additions and 145 deletions
|
|
@ -154,179 +154,119 @@ class StitchingScanner:
|
||||||
self.state.mosaic_init_height = h
|
self.state.mosaic_init_height = h
|
||||||
self.state.frame_count = 1
|
self.state.frame_count = 1
|
||||||
self.state.append_count = 0
|
self.state.append_count = 0
|
||||||
|
self.state.current_y = 0
|
||||||
|
self.state.current_x = 0
|
||||||
|
|
||||||
self.log(f"Initialized mosaic: {frame.shape[1]}x{frame.shape[0]}")
|
self.log(f"Initialized mosaic: {frame.shape[1]}x{frame.shape[0]}")
|
||||||
|
|
||||||
def _blend_horizontal(self, base: np.ndarray, strip: np.ndarray,
|
def _blend_horizontal_at_y(self, base: np.ndarray, strip: np.ndarray,
|
||||||
blend_width: int, append_right: bool) -> np.ndarray:
|
blend_width: int, append_right: bool,
|
||||||
self.log(f"=== _blend_horizontal ===")
|
y_offset: int = 0) -> np.ndarray:
|
||||||
self.log(f" base.shape: {base.shape}, strip.shape: {strip.shape}")
|
"""Blend strip horizontally at a specific Y position in the mosaic."""
|
||||||
self.log(f" blend_width: {blend_width}, append_right: {append_right}")
|
h_base, w_base = base.shape[:2]
|
||||||
|
h_strip, w_strip = strip.shape[:2]
|
||||||
|
|
||||||
|
# Clamp y_offset to valid range
|
||||||
|
y_offset = max(0, min(y_offset, h_base - h_strip))
|
||||||
|
|
||||||
|
# Early exit: no blending possible
|
||||||
|
if blend_width <= 0 or blend_width >= w_strip:
|
||||||
|
if append_right:
|
||||||
|
# Create result with expanded width
|
||||||
|
result_width = w_base + w_strip
|
||||||
|
result = np.zeros((h_base, result_width, 3), dtype=np.uint8)
|
||||||
|
result[:, :w_base] = base
|
||||||
|
result[y_offset:y_offset + h_strip, w_base:] = strip
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
result_width = w_base + w_strip
|
||||||
|
result = np.zeros((h_base, result_width, 3), dtype=np.uint8)
|
||||||
|
result[y_offset:y_offset + h_strip, :w_strip] = strip
|
||||||
|
result[:, w_strip:] = base
|
||||||
|
return result
|
||||||
|
|
||||||
|
blend_w = min(blend_width, w_strip, w_base)
|
||||||
|
|
||||||
|
if append_right:
|
||||||
|
result_width = w_base + w_strip - blend_w
|
||||||
|
result = np.zeros((h_base, result_width, 3), dtype=np.uint8)
|
||||||
|
result[:, :w_base] = base
|
||||||
|
|
||||||
|
# Extract overlaps at the correct Y position
|
||||||
|
alpha = np.linspace(1, 0, blend_w, dtype=np.float32)[np.newaxis, :, np.newaxis]
|
||||||
|
base_overlap = base[y_offset:y_offset + h_strip, -blend_w:].astype(np.float32)
|
||||||
|
strip_overlap = strip[:, :blend_w].astype(np.float32)
|
||||||
|
blended = (base_overlap * alpha + strip_overlap * (1 - alpha)).astype(np.uint8)
|
||||||
|
|
||||||
|
# Place blended region and remainder at correct Y
|
||||||
|
result[y_offset:y_offset + h_strip, w_base - blend_w:w_base] = blended
|
||||||
|
result[y_offset:y_offset + h_strip, w_base:] = strip[:, blend_w:]
|
||||||
|
return result
|
||||||
|
|
||||||
|
else: # append_left
|
||||||
|
result_width = w_base + w_strip - blend_w
|
||||||
|
result = np.zeros((h_base, result_width, 3), dtype=np.uint8)
|
||||||
|
|
||||||
|
# Place strip at correct Y position
|
||||||
|
result[y_offset:y_offset + h_strip, :w_strip] = strip
|
||||||
|
|
||||||
|
# Copy base (shifted right)
|
||||||
|
result[:, w_strip:] = base[:, blend_w:]
|
||||||
|
|
||||||
|
# Extract overlaps at correct Y position
|
||||||
|
alpha = np.linspace(0, 1, blend_w, dtype=np.float32)[np.newaxis, :, np.newaxis]
|
||||||
|
strip_overlap = strip[:, -blend_w:].astype(np.float32)
|
||||||
|
base_overlap = base[y_offset:y_offset + h_strip, :blend_w].astype(np.float32)
|
||||||
|
blended = (strip_overlap * (1 - alpha) + base_overlap * alpha).astype(np.uint8)
|
||||||
|
|
||||||
|
result[y_offset:y_offset + h_strip, w_strip - blend_w:w_strip] = blended
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _blend_horizontal(self, base: np.ndarray, strip: np.ndarray,
|
||||||
|
blend_width: int, append_right: bool) -> np.ndarray:
|
||||||
if blend_width <= 0 or blend_width >= strip.shape[1]:
|
if blend_width <= 0 or blend_width >= strip.shape[1]:
|
||||||
self.log(f" Early exit: blend_width out of range")
|
|
||||||
if append_right:
|
if append_right:
|
||||||
return np.hstack([base, strip])
|
return np.hstack([base, strip])
|
||||||
return np.hstack([strip, base])
|
return np.hstack([strip, base])
|
||||||
|
|
||||||
h_base, w_base = base.shape[:2]
|
h_base, w_base = base.shape[:2]
|
||||||
h_strip, w_strip = strip.shape[:2]
|
h_strip, w_strip = strip.shape[:2]
|
||||||
|
self.log(f"Base Width: {w_base}px")
|
||||||
# Check for channel dimension
|
|
||||||
c_base = base.shape[2] if len(base.shape) > 2 else 1
|
|
||||||
c_strip = strip.shape[2] if len(strip.shape) > 2 else 1
|
|
||||||
self.log(f" h_base: {h_base}, w_base: {w_base}, channels_base: {c_base}")
|
|
||||||
self.log(f" h_strip: {h_strip}, w_strip: {w_strip}, channels_strip: {c_strip}")
|
|
||||||
|
|
||||||
if h_strip != h_base:
|
if h_strip != h_base:
|
||||||
self.log(f" WARNING: Height mismatch! h_base={h_base}, h_strip={h_strip}")
|
|
||||||
if append_right:
|
if append_right:
|
||||||
return np.hstack([base, strip])
|
return np.hstack([base, strip])
|
||||||
return np.hstack([strip, base])
|
return np.hstack([strip, base])
|
||||||
|
|
||||||
if c_base != c_strip:
|
|
||||||
self.log(f" ERROR: Channel mismatch! c_base={c_base}, c_strip={c_strip}")
|
|
||||||
|
|
||||||
blend_w = min(blend_width, w_strip, w_base)
|
blend_w = min(blend_width, w_strip, w_base)
|
||||||
self.log(f" blend_w (clamped): {blend_w}")
|
|
||||||
|
|
||||||
if append_right:
|
if append_right:
|
||||||
result_width = w_base + w_strip - blend_w
|
result_width = w_base + w_strip - blend_w
|
||||||
self.log(f" result_width: {result_width} = {w_base} + {w_strip} - {blend_w}")
|
|
||||||
|
|
||||||
result = np.zeros((h_base, result_width, 3), dtype=np.uint8)
|
result = np.zeros((h_base, result_width, 3), dtype=np.uint8)
|
||||||
self.log(f" result.shape: {result.shape}")
|
result[:, :w_base] = base
|
||||||
|
|
||||||
# Step 1: Copy base
|
|
||||||
self.log(f" Step 1: result[:, :w_base] = base -> result[:, :{w_base}]")
|
|
||||||
try:
|
|
||||||
result[:, :w_base] = base
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f" ERROR Step 1: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Step 2: Create alpha and overlaps
|
|
||||||
self.log(f" Step 2: Creating overlaps")
|
|
||||||
self.log(f" base_overlap = base[:, -{blend_w}:] -> base[:, {w_base - blend_w}:]")
|
|
||||||
self.log(f" strip_overlap = strip[:, :{blend_w}]")
|
|
||||||
|
|
||||||
alpha = np.linspace(1, 0, blend_w, dtype=np.float32)[np.newaxis, :, np.newaxis]
|
alpha = np.linspace(1, 0, blend_w, dtype=np.float32)[np.newaxis, :, np.newaxis]
|
||||||
self.log(f" alpha.shape: {alpha.shape}")
|
base_overlap = base[:, -blend_w:].astype(np.float32)
|
||||||
|
strip_overlap = strip[:, :blend_w].astype(np.float32)
|
||||||
|
blended = (base_overlap * alpha + strip_overlap * (1 - alpha)).astype(np.uint8)
|
||||||
|
|
||||||
try:
|
result[:, w_base - blend_w:w_base] = blended
|
||||||
base_overlap = base[:, -blend_w:].astype(np.float32)
|
result[:, w_base:] = strip[:, blend_w:]
|
||||||
self.log(f" base_overlap.shape: {base_overlap.shape}")
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f" ERROR base_overlap: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
try:
|
|
||||||
strip_overlap = strip[:, :blend_w].astype(np.float32)
|
|
||||||
self.log(f" strip_overlap.shape: {strip_overlap.shape}")
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f" ERROR strip_overlap: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Step 3: Blend
|
|
||||||
self.log(f" Step 3: Blending")
|
|
||||||
try:
|
|
||||||
blended = (base_overlap * alpha + strip_overlap * (1 - alpha)).astype(np.uint8)
|
|
||||||
self.log(f" blended.shape: {blended.shape}")
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f" ERROR blending: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Step 4: Assign blended region
|
|
||||||
self.log(f" Step 4: result[:, {w_base - blend_w}:{w_base}] = blended")
|
|
||||||
try:
|
|
||||||
result[:, w_base - blend_w:w_base] = blended
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f" ERROR Step 4: {e}")
|
|
||||||
self.log(f" Slice size: [:, {w_base - blend_w}:{w_base}] = {w_base - (w_base - blend_w)} cols")
|
|
||||||
self.log(f" blended.shape: {blended.shape}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Step 5: Assign remainder of strip
|
|
||||||
self.log(f" Step 5: result[:, {w_base}:] = strip[:, {blend_w}:]")
|
|
||||||
try:
|
|
||||||
remainder = strip[:, blend_w:]
|
|
||||||
self.log(f" remainder.shape: {remainder.shape}")
|
|
||||||
self.log(f" target slice width: {result_width - w_base}")
|
|
||||||
result[:, w_base:] = remainder
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f" ERROR Step 5: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
self.log(f" Success! Returning result.shape: {result.shape}")
|
|
||||||
return result
|
return result
|
||||||
|
else:
|
||||||
else: # append_left
|
|
||||||
result_width = w_base + w_strip - blend_w
|
result_width = w_base + w_strip - blend_w
|
||||||
self.log(f" result_width: {result_width} = {w_base} + {w_strip} - {blend_w}")
|
|
||||||
|
|
||||||
result = np.zeros((h_base, result_width, 3), dtype=np.uint8)
|
result = np.zeros((h_base, result_width, 3), dtype=np.uint8)
|
||||||
self.log(f" result.shape: {result.shape}")
|
result[:, :w_strip] = strip
|
||||||
|
|
||||||
# Step 1: Copy strip
|
|
||||||
self.log(f" Step 1: result[:, :w_strip] = strip -> result[:, :{w_strip}]")
|
|
||||||
try:
|
|
||||||
result[:, :w_strip] = strip
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f" ERROR Step 1: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Step 2: Create alpha and overlaps
|
|
||||||
self.log(f" Step 2: Creating overlaps")
|
|
||||||
self.log(f" strip_overlap = strip[:, -{blend_w}:] -> strip[:, {w_strip - blend_w}:]")
|
|
||||||
self.log(f" base_overlap = base[:, :{blend_w}]")
|
|
||||||
|
|
||||||
alpha = np.linspace(0, 1, blend_w, dtype=np.float32)[np.newaxis, :, np.newaxis]
|
alpha = np.linspace(0, 1, blend_w, dtype=np.float32)[np.newaxis, :, np.newaxis]
|
||||||
self.log(f" alpha.shape: {alpha.shape}")
|
strip_overlap = strip[:, -blend_w:].astype(np.float32)
|
||||||
|
base_overlap = base[:, :blend_w].astype(np.float32)
|
||||||
|
blended = (strip_overlap * (1 - alpha) + base_overlap * alpha).astype(np.uint8)
|
||||||
|
|
||||||
try:
|
result[:, w_strip - blend_w:w_strip] = blended
|
||||||
strip_overlap = strip[:, -blend_w:].astype(np.float32)
|
result[:, w_strip:] = base[:, blend_w:]
|
||||||
self.log(f" strip_overlap.shape: {strip_overlap.shape}")
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f" ERROR strip_overlap: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
try:
|
|
||||||
base_overlap = base[:, :blend_w].astype(np.float32)
|
|
||||||
self.log(f" base_overlap.shape: {base_overlap.shape}")
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f" ERROR base_overlap: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Step 3: Blend
|
|
||||||
self.log(f" Step 3: Blending")
|
|
||||||
try:
|
|
||||||
blended = (strip_overlap * (1 - alpha) + base_overlap * alpha).astype(np.uint8)
|
|
||||||
self.log(f" blended.shape: {blended.shape}")
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f" ERROR blending: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Step 4: Assign blended region
|
|
||||||
self.log(f" Step 4: result[:, {w_strip - blend_w}:{w_strip}] = blended")
|
|
||||||
try:
|
|
||||||
result[:, w_strip - blend_w:w_strip] = blended
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f" ERROR Step 4: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Step 5: Assign remainder of base
|
|
||||||
self.log(f" Step 5: result[:, {w_strip}:] = base[:, {blend_w}:]")
|
|
||||||
try:
|
|
||||||
remainder = base[:, blend_w:]
|
|
||||||
self.log(f" remainder.shape: {remainder.shape}")
|
|
||||||
self.log(f" target slice width: {result_width - w_strip}")
|
|
||||||
result[:, w_strip:] = remainder
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f" ERROR Step 5: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
self.log(f" Success! Returning result.shape: {result.shape}")
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _blend_vertical_at_x(self, base: np.ndarray, strip: np.ndarray,
|
def _blend_vertical_at_x(self, base: np.ndarray, strip: np.ndarray,
|
||||||
blend_height: int, append_below: bool,
|
blend_height: int, append_below: bool,
|
||||||
x_off: int = 0) -> np.ndarray:
|
x_off: int = 0) -> np.ndarray:
|
||||||
|
|
@ -449,16 +389,19 @@ class StitchingScanner:
|
||||||
pixels_consumed = append_width - SAFETY_MARGIN
|
pixels_consumed = append_width - SAFETY_MARGIN
|
||||||
fractional_remainder = dx - pixels_consumed
|
fractional_remainder = dx - pixels_consumed
|
||||||
|
|
||||||
|
# Calculate Y offset for current row
|
||||||
|
y_offset = int(self.state.current_y)
|
||||||
|
|
||||||
if direction == ScanDirection.RIGHT:
|
if direction == ScanDirection.RIGHT:
|
||||||
strip_start = max(0, w - append_width - BLEND_WIDTH)
|
strip_start = max(0, w - append_width - BLEND_WIDTH)
|
||||||
new_strip = frame[:, strip_start:]
|
new_strip = frame[:, strip_start:]
|
||||||
self.mosaic = self._blend_horizontal(
|
self.mosaic = self._blend_horizontal_at_y(
|
||||||
self.mosaic, new_strip, BLEND_WIDTH, append_right=True)
|
self.mosaic, new_strip, BLEND_WIDTH, append_right=True, y_offset=y_offset)
|
||||||
else:
|
else:
|
||||||
strip_end = min(w, append_width + BLEND_WIDTH)
|
strip_end = min(w, append_width + BLEND_WIDTH)
|
||||||
new_strip = frame[:, :strip_end]
|
new_strip = frame[:, :strip_end]
|
||||||
self.mosaic = self._blend_horizontal(
|
self.mosaic = self._blend_horizontal_at_y(
|
||||||
self.mosaic, new_strip, BLEND_WIDTH, append_right=False)
|
self.mosaic, new_strip, BLEND_WIDTH, append_right=False, y_offset=y_offset)
|
||||||
|
|
||||||
self._displacement_since_append_x = fractional_remainder
|
self._displacement_since_append_x = fractional_remainder
|
||||||
self._displacement_since_append_y = 0.0
|
self._displacement_since_append_y = 0.0
|
||||||
|
|
@ -777,6 +720,7 @@ class StitchingScanner:
|
||||||
# Done when we've moved enough
|
# Done when we've moved enough
|
||||||
if abs(total_y) >= target_displacement:
|
if abs(total_y) >= target_displacement:
|
||||||
self.log(f"Row transition complete: {abs(total_y):.1f}px")
|
self.log(f"Row transition complete: {abs(total_y):.1f}px")
|
||||||
|
self.state.current_y = 0
|
||||||
self.motion.send_command('s')
|
self.motion.send_command('s')
|
||||||
time.sleep(self.config.settle_time)
|
time.sleep(self.config.settle_time)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue