Compare commits

...

88 commits

Author SHA1 Message Date
d0180d8985 eextend green for test 2026-01-17 12:48:00 -06:00
553afb6fe1 eextend green for test 2026-01-17 12:33:07 -06:00
4c261b895b Invert red 2026-01-16 23:35:10 -06:00
193e7b58ec reset region comp 2026-01-12 18:38:40 -06:00
b6b678a077 full region redraw 2026-01-12 18:30:37 -06:00
07067cd2a5 helper region fn 2026-01-12 18:20:53 -06:00
4eaf692edb reset region comp 2026-01-12 18:11:47 -06:00
c8695c1216 region matched 2026-01-12 01:27:10 -06:00
3f935a59bb invert x offset 2026-01-12 01:07:13 -06:00
e6871620f4 increase max adjustment 2026-01-12 00:59:40 -06:00
e2b7fa70fe fixed y comp 2026-01-12 00:50:02 -06:00
84a698bff5 shift x comp 2026-01-12 00:43:18 -06:00
44f54aa3e1 Shift y comp 2026-01-12 00:33:57 -06:00
b770bce4e4 flipped Y axis compare 2026-01-12 00:28:47 -06:00
b2a0d798ab Prev region matching 2026-01-11 21:46:06 -06:00
63c65ee018 Region matching 2026-01-11 21:32:15 -06:00
26bfd663d5 better region matching 2026-01-11 21:11:34 -06:00
b8a51dc3cf debug images 2026-01-11 17:55:55 -06:00
88ffea8807 bool 2026-01-11 13:49:36 -06:00
69a638f439 force confidence 2026-01-11 13:43:40 -06:00
1f18b4cd1a Displacement log 2026-01-11 13:38:14 -06:00
04d5c3bf5c displacement too large 2026-01-11 10:58:56 -06:00
e9436f1bb5 larger overlap 2026-01-11 09:54:16 -06:00
c31220532b testing pos tracking 2026-01-11 02:23:41 -06:00
34f84d760e testing pos tracking 2026-01-11 02:16:17 -06:00
8ebe7a6e97 rm test btn 2026-01-11 01:46:09 -06:00
083c4454d6 larger fram for comparison 2026-01-11 00:42:40 -06:00
750f7164c7 horizontal and veritcal drift 2026-01-11 00:31:27 -06:00
d7bd2fe6cd continuous drift detection 2026-01-11 00:10:43 -06:00
5177eea77e constant displacement check 2026-01-10 18:20:24 -06:00
16cb8b360b fixed x offset 2026-01-10 18:10:42 -06:00
db6e225a07 Displacement checking for subsequent ropws 2026-01-10 17:59:34 -06:00
6ab292a410 Close working version 2026-01-10 17:57:03 -06:00
c7b5ca1a2e Dims 2026-01-10 13:41:14 -06:00
4918c35c72 dims 2026-01-10 13:37:38 -06:00
222e47ccfd invert x offset 2026-01-10 13:33:03 -06:00
8dcd686add reset offset clamp 2026-01-10 13:29:37 -06:00
afc3b2e6f2 cropping new strip if needed 2026-01-10 13:27:08 -06:00
513428523f square 2026-01-10 12:42:34 -06:00
a7c2d89cc0 zoomable mosiac 2026-01-10 12:36:27 -06:00
d7321c5ef4 Magic numbetrs 2026-01-10 12:15:14 -06:00
9e61a21dfe reset 2026-01-10 12:05:57 -06:00
80f6b1b625 Magic numbers 2026-01-10 11:44:09 -06:00
ac8657f2ed reset 2026-01-10 03:55:22 -06:00
4ad089c698 fix 2026-01-10 03:31:41 -06:00
91d95561f6 stichting 2026-01-10 03:28:36 -06:00
2fed9ad474 reset 2026-01-10 02:53:01 -06:00
d8553d4a84 abs x 2026-01-10 02:41:24 -06:00
50a8be5bc9 reset offset 2026-01-10 02:22:49 -06:00
9af75de663 tab 2026-01-10 02:20:04 -06:00
35c27baafb reset ofset 2026-01-10 02:19:21 -06:00
afd5e8a14c strip width 2026-01-10 02:11:37 -06:00
d128808a07 fixing left appenmd 2026-01-10 02:07:21 -06:00
56d31edf8e better offset 2026-01-10 01:40:52 -06:00
65ec427a0d x logging 2026-01-10 01:32:59 -06:00
2108ce78fc fixed state lock 2026-01-10 01:27:32 -06:00
96422fe0d2 better x offset 2026-01-10 00:47:56 -06:00
ef611b6798 undid offset 2026-01-10 00:43:00 -06:00
a838deb9e2 Fixed left x offset 2026-01-10 00:37:40 -06:00
4a326e0c9f logging 2026-01-10 00:29:31 -06:00
a7215e496f fixed offset 2026-01-09 19:56:49 -06:00
5937d5fb68 relative horizontal blend 2026-01-09 19:34:51 -06:00
86d9b817e5 Way too much logging on blend horizontal 2026-01-09 19:19:38 -06:00
f0cc558b79 scan dir 2026-01-09 19:01:23 -06:00
5a1e9b1360 fix 2026-01-08 22:43:15 -06:00
0079417e1c better left scan 2026-01-08 22:38:41 -06:00
dc5cc01dc8 logging 2026-01-08 22:35:08 -06:00
3dca7fd79a fixed scan 2026-01-08 22:15:04 -06:00
4785a99a63 xoffset fix 2026-01-08 22:11:07 -06:00
c8e6a1f506 vfertical at x style rewrite 2026-01-08 22:06:04 -06:00
f3682dbd31 vfertical at x style rewrite 2026-01-08 22:04:29 -06:00
1accbe27b6 vfertical at x style rewrite 2026-01-08 21:51:44 -06:00
77e5968236 vertical mosiacv addition alignment fic 2026-01-08 21:30:51 -06:00
b102d3eed7 vertical mosiacv addition alignment fic 2026-01-08 21:27:18 -06:00
f30158fd18 vertical mosiacv addition alignment fic 2026-01-08 21:24:11 -06:00
fc0f48eed4 stich logging 2026-01-08 21:13:01 -06:00
57399de03f Fixed max dimensions and scan direction on serpentine pattern 2026-01-08 20:31:58 -06:00
9a49488760 removed row 2026-01-08 02:07:40 -06:00
2ac118737a limits 2026-01-08 02:02:26 -06:00
8caa578608 better w calc 2026-01-08 01:55:33 -06:00
125bfeea11 fixed edge scan 2026-01-08 01:49:14 -06:00
128df0cd01 logging 2026-01-08 01:47:28 -06:00
961e828adc X offset 2026-01-08 00:49:02 -06:00
baf0f2a782 fixed ref 2026-01-08 00:43:38 -06:00
4e11c851e7 Fixing mosaic 2026-01-08 00:41:30 -06:00
5cf6ec0794 fixed 2026-01-07 21:55:26 -06:00
6c710ba182 limit on horizontal scanning 2026-01-07 21:51:33 -06:00
56f6138d09 Changing vertical motion 2026-01-07 21:36:43 -06:00
2 changed files with 1093 additions and 40 deletions

View file

@ -280,10 +280,6 @@ class AppGUI:
)
self.scan_stop_btn.pack(side=tk.LEFT, padx=2)
# Test button
ttk.Button(btn_frame, text="Test", width=4,
command=self._test_displacement).pack(side=tk.LEFT, padx=2)
# Mosaic button
ttk.Button(sf, text="Mosaic", width=6,
command=self._show_mosaic_window).pack(side=tk.RIGHT, padx=2)
@ -353,14 +349,14 @@ class AppGUI:
# Max Width
ttk.Label(inner2, text="Max W:").pack(side=tk.LEFT)
self.max_width_var = tk.IntVar(value=5000)
self.max_width_var = tk.IntVar(value=960)
self.max_width_entry = ttk.Entry(inner2, textvariable=self.max_width_var, width=7)
self.max_width_entry.pack(side=tk.LEFT, padx=(2, 5))
self.max_width_entry.bind('<Return>', lambda e: self._update_stitch_config())
# Max Height
ttk.Label(inner2, text="Max H:").pack(side=tk.LEFT)
self.max_height_var = tk.IntVar(value=5000)
self.max_height_var = tk.IntVar(value=1280)
self.max_height_entry = ttk.Entry(inner2, textvariable=self.max_height_var, width=7)
self.max_height_entry.pack(side=tk.LEFT, padx=(2, 5))
self.max_height_entry.bind('<Return>', lambda e: self._update_stitch_config())
@ -856,6 +852,10 @@ class AppGUI:
# Mosaic Window
# =========================================================================
# =========================================================================
# Mosaic Window
# =========================================================================
def _show_mosaic_window(self):
if self.mosaic_window is not None:
self.mosaic_window.lift()
@ -867,9 +867,30 @@ class AppGUI:
self.mosaic_window.geometry("600x800")
self.mosaic_window.protocol("WM_DELETE_WINDOW", self._close_mosaic_window)
self.mosaic_label = ttk.Label(self.mosaic_window, text="No mosaic yet")
self.mosaic_label.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Canvas for zoomable/pannable mosaic
self.mosaic_canvas = tk.Canvas(self.mosaic_window, bg='#333333', highlightthickness=0)
self.mosaic_canvas.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# Zoom and pan state
self.mosaic_zoom = 1.0
self.mosaic_pan_x = 0
self.mosaic_pan_y = 0
self.mosaic_drag_start = None
self.mosaic_full_image = None # Store full resolution for zooming
# Bind mouse events
self.mosaic_canvas.bind('<MouseWheel>', self._on_mosaic_scroll) # Windows
self.mosaic_canvas.bind('<Button-4>', self._on_mosaic_scroll) # Linux scroll up
self.mosaic_canvas.bind('<Button-5>', self._on_mosaic_scroll) # Linux scroll down
self.mosaic_canvas.bind('<ButtonPress-1>', self._on_mosaic_drag_start)
self.mosaic_canvas.bind('<B1-Motion>', self._on_mosaic_drag)
self.mosaic_canvas.bind('<ButtonRelease-1>', self._on_mosaic_drag_end)
self.mosaic_canvas.bind('<Configure>', lambda e: self._render_mosaic())
# Double-click to reset view
self.mosaic_canvas.bind('<Double-Button-1>', self._reset_mosaic_view)
# Button frame
btn_frame = ttk.Frame(self.mosaic_window)
btn_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
@ -877,31 +898,204 @@ class AppGUI:
command=self._save_mosaic).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="Refresh",
command=self._update_mosaic_window).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="Fit",
command=self._reset_mosaic_view).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="1:1",
command=self._mosaic_zoom_100).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="Clear",
command=self._clear_mosaic).pack(side=tk.LEFT, padx=5)
# Zoom label
self.mosaic_zoom_label = ttk.Label(btn_frame, text="100%", width=6)
self.mosaic_zoom_label.pack(side=tk.RIGHT, padx=5)
ttk.Label(btn_frame, text="Zoom:").pack(side=tk.RIGHT)
self._update_mosaic_window()
def _on_mosaic_scroll(self, event):
"""Handle mouse wheel zoom"""
if self.mosaic_full_image is None:
return
# Get mouse position relative to canvas
canvas_x = self.mosaic_canvas.canvasx(event.x)
canvas_y = self.mosaic_canvas.canvasy(event.y)
# Determine zoom direction
if event.num == 4 or (hasattr(event, 'delta') and event.delta > 0):
scale_factor = 1.15
elif event.num == 5 or (hasattr(event, 'delta') and event.delta < 0):
scale_factor = 1 / 1.15
else:
return
old_zoom = self.mosaic_zoom
self.mosaic_zoom *= scale_factor
# Clamp zoom
self.mosaic_zoom = max(0.1, min(10.0, self.mosaic_zoom))
# Adjust pan to zoom toward mouse position
if old_zoom != self.mosaic_zoom:
# Calculate the point in image space under the mouse
img_x = (canvas_x - self.mosaic_pan_x) / old_zoom
img_y = (canvas_y - self.mosaic_pan_y) / old_zoom
# Adjust pan so that same image point stays under mouse
self.mosaic_pan_x = canvas_x - img_x * self.mosaic_zoom
self.mosaic_pan_y = canvas_y - img_y * self.mosaic_zoom
self._render_mosaic()
def _on_mosaic_drag_start(self, event):
"""Start panning"""
self.mosaic_drag_start = (event.x, event.y)
self.mosaic_canvas.config(cursor='fleur')
def _on_mosaic_drag(self, event):
"""Handle panning"""
if self.mosaic_drag_start is None:
return
dx = event.x - self.mosaic_drag_start[0]
dy = event.y - self.mosaic_drag_start[1]
self.mosaic_pan_x += dx
self.mosaic_pan_y += dy
self.mosaic_drag_start = (event.x, event.y)
self._render_mosaic()
def _on_mosaic_drag_end(self, event):
"""End panning"""
self.mosaic_drag_start = None
self.mosaic_canvas.config(cursor='')
def _reset_mosaic_view(self, event=None):
"""Reset zoom and pan to fit image in canvas"""
if self.mosaic_full_image is None:
return
canvas_w = self.mosaic_canvas.winfo_width()
canvas_h = self.mosaic_canvas.winfo_height()
img_h, img_w = self.mosaic_full_image.shape[:2]
if img_w == 0 or img_h == 0:
return
# Calculate zoom to fit
self.mosaic_zoom = min(canvas_w / img_w, canvas_h / img_h)
# Center the image
scaled_w = img_w * self.mosaic_zoom
scaled_h = img_h * self.mosaic_zoom
self.mosaic_pan_x = (canvas_w - scaled_w) / 2
self.mosaic_pan_y = (canvas_h - scaled_h) / 2
self._render_mosaic()
def _mosaic_zoom_100(self):
"""Set zoom to 100% (1:1 pixels)"""
if self.mosaic_full_image is None:
return
canvas_w = self.mosaic_canvas.winfo_width()
canvas_h = self.mosaic_canvas.winfo_height()
img_h, img_w = self.mosaic_full_image.shape[:2]
self.mosaic_zoom = 1.0
# Center the image
self.mosaic_pan_x = (canvas_w - img_w) / 2
self.mosaic_pan_y = (canvas_h - img_h) / 2
self._render_mosaic()
def _render_mosaic(self):
"""Render the mosaic at current zoom/pan"""
if self.mosaic_window is None or self.mosaic_full_image is None:
return
canvas_w = self.mosaic_canvas.winfo_width()
canvas_h = self.mosaic_canvas.winfo_height()
if canvas_w <= 1 or canvas_h <= 1:
return
img_h, img_w = self.mosaic_full_image.shape[:2]
# Calculate visible region in image space
view_x1 = max(0, int(-self.mosaic_pan_x / self.mosaic_zoom))
view_y1 = max(0, int(-self.mosaic_pan_y / self.mosaic_zoom))
view_x2 = min(img_w, int((canvas_w - self.mosaic_pan_x) / self.mosaic_zoom))
view_y2 = min(img_h, int((canvas_h - self.mosaic_pan_y) / self.mosaic_zoom))
if view_x2 <= view_x1 or view_y2 <= view_y1:
self.mosaic_canvas.delete('all')
return
# Extract visible region
visible = self.mosaic_full_image[view_y1:view_y2, view_x1:view_x2]
# Scale the visible region
new_w = int((view_x2 - view_x1) * self.mosaic_zoom)
new_h = int((view_y2 - view_y1) * self.mosaic_zoom)
if new_w <= 0 or new_h <= 0:
return
# Use appropriate interpolation
interp = cv2.INTER_NEAREST if self.mosaic_zoom > 1 else cv2.INTER_AREA
scaled = cv2.resize(visible, (new_w, new_h), interpolation=interp)
# Convert to PhotoImage
rgb = cv2.cvtColor(scaled, cv2.COLOR_BGR2RGB)
img = Image.fromarray(rgb)
self.mosaic_photo = ImageTk.PhotoImage(image=img)
# Calculate position on canvas
pos_x = max(0, self.mosaic_pan_x + view_x1 * self.mosaic_zoom)
pos_y = max(0, self.mosaic_pan_y + view_y1 * self.mosaic_zoom)
# Update canvas
self.mosaic_canvas.delete('all')
self.mosaic_canvas.create_image(pos_x, pos_y, anchor=tk.NW, image=self.mosaic_photo)
# Update zoom label
self.mosaic_zoom_label.config(text=f"{self.mosaic_zoom * 100:.0f}%")
def _update_mosaic_window(self):
if self.mosaic_window is None:
return
# Get mosaic from appropriate scanner
# Get mosaic from appropriate scanner (full resolution for zooming)
mosaic = None
if self.scanner_mode == 'stitch' and self.stitch_scanner:
mosaic = self.stitch_scanner.get_mosaic_preview(max_size=580)
mosaic = self.stitch_scanner.mosaic # Get full resolution
if mosaic is None:
# Try preview if no full mosaic
mosaic = self.stitch_scanner.get_mosaic_preview(max_size=2000)
elif self.scanner and self.scanner.tiles:
mosaic = self.scanner.get_mosaic_preview(max_size=580)
mosaic = self.scanner.build_mosaic(scale=1.0)
if mosaic is None:
self.mosaic_canvas.delete('all')
self.mosaic_canvas.create_text(
self.mosaic_canvas.winfo_width() // 2,
self.mosaic_canvas.winfo_height() // 2,
text="No mosaic yet", fill='white', font=('Arial', 14)
)
return
mosaic_rgb = cv2.cvtColor(mosaic, cv2.COLOR_BGR2RGB)
img = Image.fromarray(mosaic_rgb)
imgtk = ImageTk.PhotoImage(image=img)
# Store full image and render
self.mosaic_full_image = mosaic.copy()
self.mosaic_label.imgtk = imgtk
self.mosaic_label.config(image=imgtk, text="")
# If first time or image size changed significantly, reset view
if not hasattr(self, '_last_mosaic_size') or self._last_mosaic_size != mosaic.shape[:2]:
self._last_mosaic_size = mosaic.shape[:2]
self.mosaic_window.after(10, self._reset_mosaic_view)
else:
self._render_mosaic()
# Update size label
if self.stitch_scanner:
@ -912,6 +1106,9 @@ class AppGUI:
if self.mosaic_window:
self.mosaic_window.destroy()
self.mosaic_window = None
self.mosaic_full_image = None
if hasattr(self, '_last_mosaic_size'):
del self._last_mosaic_size
def _save_mosaic(self):
filename = filedialog.asksaveasfilename(

File diff suppressed because it is too large Load diff