Scanning + frame interpolation
This commit is contained in:
parent
2acfccf6e1
commit
078274ddbd
4 changed files with 1146 additions and 592 deletions
BIN
Mos1.png
Normal file
BIN
Mos1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 513 KiB |
BIN
Mos2.png
Normal file
BIN
Mos2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
629
src/gui.py
629
src/gui.py
|
|
@ -1,7 +1,8 @@
|
||||||
"""
|
"""
|
||||||
AutoScope GUI - Vertical Monitor Layout with Feature Visualization
|
AutoScope GUI - Vertical Monitor Layout with Edge Comparison Overlay
|
||||||
|
|
||||||
Enhanced with feature overlay debugging for scanner development.
|
Shows reference and current binary edge images overlaid directly on camera view.
|
||||||
|
Updated with interpolation controls and improved scanner UI.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
|
@ -97,30 +98,65 @@ class AppGUI:
|
||||||
self.camera_label = ttk.Label(camera_frame)
|
self.camera_label = ttk.Label(camera_frame)
|
||||||
self.camera_label.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
self.camera_label.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||||
|
|
||||||
# Camera options row
|
# Camera options row 1: overlays
|
||||||
cam_opts = ttk.Frame(camera_frame)
|
cam_opts1 = ttk.Frame(camera_frame)
|
||||||
cam_opts.pack(fill=tk.X, padx=5, pady=(0, 5))
|
cam_opts1.pack(fill=tk.X, padx=5, pady=(0, 2))
|
||||||
|
|
||||||
self.show_tile_overlay_var = tk.BooleanVar(value=True)
|
self.show_tile_overlay_var = tk.BooleanVar(value=True)
|
||||||
ttk.Checkbutton(cam_opts, text="Tile bounds",
|
ttk.Checkbutton(cam_opts1, text="Tile bounds",
|
||||||
variable=self.show_tile_overlay_var).pack(side=tk.LEFT)
|
variable=self.show_tile_overlay_var).pack(side=tk.LEFT)
|
||||||
|
|
||||||
self.show_edge_regions_var = tk.BooleanVar(value=False)
|
self.show_edge_regions_var = tk.BooleanVar(value=False)
|
||||||
ttk.Checkbutton(cam_opts, text="Track regions",
|
ttk.Checkbutton(cam_opts1, text="Edge regions",
|
||||||
variable=self.show_edge_regions_var).pack(side=tk.LEFT, padx=(10, 0))
|
variable=self.show_edge_regions_var).pack(side=tk.LEFT, padx=(10, 0))
|
||||||
|
|
||||||
# NEW: Feature overlay checkbox
|
self.show_comparison_var = tk.BooleanVar(value=True)
|
||||||
self.show_features_var = tk.BooleanVar(value=False)
|
ttk.Checkbutton(cam_opts1, text="Comparison overlay",
|
||||||
ttk.Checkbutton(cam_opts, text="Features",
|
variable=self.show_comparison_var).pack(side=tk.LEFT, padx=(10, 0))
|
||||||
variable=self.show_features_var).pack(side=tk.LEFT, padx=(10, 0))
|
|
||||||
|
|
||||||
self.live_focus_var = tk.BooleanVar(value=True)
|
self.live_focus_var = tk.BooleanVar(value=True)
|
||||||
ttk.Checkbutton(cam_opts, text="Live focus",
|
ttk.Checkbutton(cam_opts1, text="Live focus",
|
||||||
variable=self.live_focus_var).pack(side=tk.LEFT, padx=(10, 0))
|
variable=self.live_focus_var).pack(side=tk.LEFT, padx=(10, 0))
|
||||||
|
|
||||||
self.focus_score_label = ttk.Label(cam_opts, text="Focus: --", font=('Arial', 11, 'bold'))
|
self.focus_score_label = ttk.Label(cam_opts1, text="Focus: --", font=('Arial', 11, 'bold'))
|
||||||
self.focus_score_label.pack(side=tk.RIGHT)
|
self.focus_score_label.pack(side=tk.RIGHT)
|
||||||
|
|
||||||
|
# Camera options row 2: threshold and similarity
|
||||||
|
cam_opts2 = ttk.Frame(camera_frame)
|
||||||
|
cam_opts2.pack(fill=tk.X, padx=5, pady=(0, 5))
|
||||||
|
|
||||||
|
ttk.Label(cam_opts2, text="Match threshold:").pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
self.threshold_var = tk.DoubleVar(value=0.12)
|
||||||
|
self.threshold_slider = ttk.Scale(
|
||||||
|
cam_opts2,
|
||||||
|
from_=0.05,
|
||||||
|
to=0.8,
|
||||||
|
orient=tk.HORIZONTAL,
|
||||||
|
variable=self.threshold_var,
|
||||||
|
command=self._on_threshold_change,
|
||||||
|
length=100
|
||||||
|
)
|
||||||
|
self.threshold_slider.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
self.threshold_label = ttk.Label(cam_opts2, text="0.12", width=5)
|
||||||
|
self.threshold_label.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
ttk.Button(cam_opts2, text="Test Edges", width=10,
|
||||||
|
command=self._test_edge_comparison).pack(side=tk.LEFT, padx=(10, 0))
|
||||||
|
|
||||||
|
ttk.Button(cam_opts2, text="Test Interp", width=10,
|
||||||
|
command=self._test_interpolation).pack(side=tk.LEFT, padx=(5, 0))
|
||||||
|
|
||||||
|
# Similarity display
|
||||||
|
ttk.Separator(cam_opts2, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=10)
|
||||||
|
ttk.Label(cam_opts2, text="Sim:").pack(side=tk.LEFT)
|
||||||
|
self.similarity_label = ttk.Label(cam_opts2, text="--", font=('Arial', 10, 'bold'), width=6)
|
||||||
|
self.similarity_label.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
self.match_status_label = ttk.Label(cam_opts2, text="", width=10)
|
||||||
|
self.match_status_label.pack(side=tk.LEFT, padx=(3, 0))
|
||||||
|
|
||||||
# === BOTTOM: Control Panel ===
|
# === BOTTOM: Control Panel ===
|
||||||
control_frame = ttk.Frame(main_frame)
|
control_frame = ttk.Frame(main_frame)
|
||||||
control_frame.pack(fill=tk.X)
|
control_frame.pack(fill=tk.X)
|
||||||
|
|
@ -128,14 +164,17 @@ class AppGUI:
|
||||||
# Row 1: Emergency Stop + Scanner Controls
|
# Row 1: Emergency Stop + Scanner Controls
|
||||||
self._build_row1_emergency_scanner(control_frame)
|
self._build_row1_emergency_scanner(control_frame)
|
||||||
|
|
||||||
# Row 2: Movement Controls
|
# Row 2: Interpolation Settings (NEW)
|
||||||
self._build_row2_movement(control_frame)
|
self._build_row2_interpolation(control_frame)
|
||||||
|
|
||||||
# Row 3: Speed + Autofocus
|
# Row 3: Movement Controls
|
||||||
self._build_row3_speed_autofocus(control_frame)
|
self._build_row3_movement(control_frame)
|
||||||
|
|
||||||
# Row 4: Status + Log
|
# Row 4: Speed + Autofocus
|
||||||
self._build_row4_status_log(control_frame)
|
self._build_row4_speed_autofocus(control_frame)
|
||||||
|
|
||||||
|
# Row 5: Status + Log
|
||||||
|
self._build_row5_status_log(control_frame)
|
||||||
|
|
||||||
def _build_row1_emergency_scanner(self, parent):
|
def _build_row1_emergency_scanner(self, parent):
|
||||||
"""Row 1: Emergency stop and scanner controls"""
|
"""Row 1: Emergency stop and scanner controls"""
|
||||||
|
|
@ -145,10 +184,10 @@ class AppGUI:
|
||||||
# Emergency stop button
|
# Emergency stop button
|
||||||
self.emergency_btn = tk.Button(
|
self.emergency_btn = tk.Button(
|
||||||
row, text="⚠ STOP", command=self._emergency_stop,
|
row, text="⚠ STOP", command=self._emergency_stop,
|
||||||
bg='red', fg='white', font=('Arial', 12, 'bold'),
|
bg='#d32f2f', fg='white', font=('Arial', 11, 'bold'),
|
||||||
width=8, height=1
|
width=7, height=1, relief=tk.RAISED, bd=2
|
||||||
)
|
)
|
||||||
self.emergency_btn.pack(side=tk.LEFT, padx=(0, 10))
|
self.emergency_btn.pack(side=tk.LEFT, padx=(0, 8))
|
||||||
|
|
||||||
# Scanner controls
|
# Scanner controls
|
||||||
scanner_frame = ttk.LabelFrame(row, text="Scanner")
|
scanner_frame = ttk.LabelFrame(row, text="Scanner")
|
||||||
|
|
@ -157,50 +196,141 @@ class AppGUI:
|
||||||
sf = ttk.Frame(scanner_frame)
|
sf = ttk.Frame(scanner_frame)
|
||||||
sf.pack(fill=tk.X, padx=5, pady=3)
|
sf.pack(fill=tk.X, padx=5, pady=3)
|
||||||
|
|
||||||
# Status + Progress
|
# Status indicator
|
||||||
self.scan_status_label = ttk.Label(sf, text="Idle", font=('Arial', 9, 'bold'), width=8)
|
self.scan_status_label = ttk.Label(sf, text="Idle", font=('Arial', 9, 'bold'), width=8)
|
||||||
self.scan_status_label.pack(side=tk.LEFT)
|
self.scan_status_label.pack(side=tk.LEFT)
|
||||||
|
|
||||||
self.scan_progress_bar = ttk.Progressbar(sf, mode='indeterminate', length=80)
|
# Progress
|
||||||
self.scan_progress_bar.pack(side=tk.LEFT, padx=5)
|
self.scan_progress_bar = ttk.Progressbar(sf, mode='indeterminate', length=60)
|
||||||
|
self.scan_progress_bar.pack(side=tk.LEFT, padx=3)
|
||||||
|
|
||||||
self.scan_progress_label = ttk.Label(sf, text="", width=10)
|
self.scan_progress_label = ttk.Label(sf, text="0 tiles", width=8)
|
||||||
self.scan_progress_label.pack(side=tk.LEFT)
|
self.scan_progress_label.pack(side=tk.LEFT)
|
||||||
|
|
||||||
# Buttons
|
# Separator
|
||||||
|
ttk.Separator(sf, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=5)
|
||||||
|
|
||||||
|
# Control buttons with proper styling
|
||||||
|
btn_frame = ttk.Frame(sf)
|
||||||
|
btn_frame.pack(side=tk.LEFT)
|
||||||
|
|
||||||
self.scan_start_btn = tk.Button(
|
self.scan_start_btn = tk.Button(
|
||||||
sf, text="▶ Start", width=7, bg='green', fg='white',
|
btn_frame, text="▶ Start", width=7,
|
||||||
font=('Arial', 9, 'bold'), command=self._start_scan
|
bg='#4CAF50', fg='white', font=('Arial', 9, 'bold'),
|
||||||
|
activebackground='#45a049', relief=tk.RAISED, bd=2,
|
||||||
|
command=self._start_scan
|
||||||
)
|
)
|
||||||
self.scan_start_btn.pack(side=tk.LEFT, padx=2)
|
self.scan_start_btn.pack(side=tk.LEFT, padx=2)
|
||||||
|
|
||||||
self.scan_pause_btn = ttk.Button(sf, text="⏸", width=3,
|
self.scan_pause_btn = tk.Button(
|
||||||
command=self._pause_scan, state='disabled')
|
btn_frame, text="⏸", width=3,
|
||||||
self.scan_pause_btn.pack(side=tk.LEFT, padx=1)
|
bg='#2196F3', fg='white', font=('Arial', 10, 'bold'),
|
||||||
|
activebackground='#1976D2', relief=tk.RAISED, bd=2,
|
||||||
|
command=self._pause_scan, state='disabled'
|
||||||
|
)
|
||||||
|
self.scan_pause_btn.pack(side=tk.LEFT, padx=2)
|
||||||
|
|
||||||
self.scan_stop_btn = tk.Button(
|
self.scan_stop_btn = tk.Button(
|
||||||
sf, text="⏹", width=3, bg='orange', fg='white',
|
btn_frame, text="⏹", width=3,
|
||||||
|
bg='#FF9800', fg='white', font=('Arial', 10, 'bold'),
|
||||||
|
activebackground='#F57C00', relief=tk.RAISED, bd=2,
|
||||||
command=self._stop_scan, state='disabled'
|
command=self._stop_scan, state='disabled'
|
||||||
)
|
)
|
||||||
self.scan_stop_btn.pack(side=tk.LEFT, padx=1)
|
self.scan_stop_btn.pack(side=tk.LEFT, padx=2)
|
||||||
|
|
||||||
ttk.Button(sf, text="📷", width=3,
|
# Capture single tile
|
||||||
command=self._capture_single_tile).pack(side=tk.LEFT, padx=1)
|
ttk.Button(btn_frame, text="📷", width=3,
|
||||||
|
command=self._capture_single_tile).pack(side=tk.LEFT, padx=2)
|
||||||
|
|
||||||
|
# Mosaic button
|
||||||
ttk.Button(sf, text="Mosaic", width=6,
|
ttk.Button(sf, text="Mosaic", width=6,
|
||||||
command=self._show_mosaic_window).pack(side=tk.LEFT, padx=(5, 2))
|
command=self._show_mosaic_window).pack(side=tk.RIGHT, padx=2)
|
||||||
|
|
||||||
# AF interval
|
def _build_row2_interpolation(self, parent):
|
||||||
ttk.Label(sf, text="AF:").pack(side=tk.LEFT, padx=(10, 2))
|
"""Row 2: Interpolation and peak detection settings"""
|
||||||
self.af_every_var = tk.StringVar(value="5")
|
row = ttk.LabelFrame(parent, text="Matching Settings")
|
||||||
ttk.Spinbox(sf, from_=1, to=20, width=3,
|
row.pack(fill=tk.X, pady=(0, 3))
|
||||||
textvariable=self.af_every_var).pack(side=tk.LEFT)
|
|
||||||
|
|
||||||
self.af_every_row_var = tk.BooleanVar(value=True)
|
inner = ttk.Frame(row)
|
||||||
ttk.Checkbutton(sf, text="Row", variable=self.af_every_row_var).pack(side=tk.LEFT)
|
inner.pack(fill=tk.X, padx=5, pady=3)
|
||||||
|
|
||||||
def _build_row2_movement(self, parent):
|
# Interpolation toggle
|
||||||
"""Row 2: Movement controls for all axes"""
|
self.use_interpolation_var = tk.BooleanVar(value=True)
|
||||||
|
ttk.Checkbutton(
|
||||||
|
inner, text="Interpolation",
|
||||||
|
variable=self.use_interpolation_var,
|
||||||
|
command=self._update_scan_config
|
||||||
|
).pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
# Number of interpolations
|
||||||
|
ttk.Label(inner, text="Steps:").pack(side=tk.LEFT, padx=(10, 2))
|
||||||
|
self.num_interp_var = tk.IntVar(value=10)
|
||||||
|
self.num_interp_spinbox = ttk.Spinbox(
|
||||||
|
inner, from_=5, to=30, width=4,
|
||||||
|
textvariable=self.num_interp_var,
|
||||||
|
command=self._update_scan_config
|
||||||
|
)
|
||||||
|
self.num_interp_spinbox.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
# Separator
|
||||||
|
ttk.Separator(inner, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=10)
|
||||||
|
|
||||||
|
# Peak detection toggle
|
||||||
|
self.use_peak_detection_var = tk.BooleanVar(value=True)
|
||||||
|
ttk.Checkbutton(
|
||||||
|
inner, text="Peak Detection",
|
||||||
|
variable=self.use_peak_detection_var,
|
||||||
|
command=self._update_scan_config
|
||||||
|
).pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
# Peak window size
|
||||||
|
ttk.Label(inner, text="Window:").pack(side=tk.LEFT, padx=(10, 2))
|
||||||
|
self.peak_window_var = tk.IntVar(value=5)
|
||||||
|
self.peak_window_spinbox = ttk.Spinbox(
|
||||||
|
inner, from_=3, to=15, width=3,
|
||||||
|
textvariable=self.peak_window_var,
|
||||||
|
command=self._update_scan_config
|
||||||
|
)
|
||||||
|
self.peak_window_spinbox.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
# Peak drop threshold
|
||||||
|
ttk.Label(inner, text="Drop:").pack(side=tk.LEFT, padx=(10, 2))
|
||||||
|
self.peak_drop_var = tk.DoubleVar(value=0.02)
|
||||||
|
self.peak_drop_spinbox = ttk.Spinbox(
|
||||||
|
inner, from_=0.01, to=0.1, increment=0.01, width=5,
|
||||||
|
textvariable=self.peak_drop_var,
|
||||||
|
command=self._update_scan_config
|
||||||
|
)
|
||||||
|
self.peak_drop_spinbox.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
# Separator
|
||||||
|
ttk.Separator(inner, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=10)
|
||||||
|
|
||||||
|
# Comparison method dropdown
|
||||||
|
ttk.Label(inner, text="Method:").pack(side=tk.LEFT, padx=(0, 2))
|
||||||
|
self.comparison_method_var = tk.StringVar(value='template')
|
||||||
|
method_combo = ttk.Combobox(
|
||||||
|
inner, textvariable=self.comparison_method_var,
|
||||||
|
values=['template', 'ssim', 'mse', 'hst', 'phase'],
|
||||||
|
width=8, state='readonly'
|
||||||
|
)
|
||||||
|
method_combo.pack(side=tk.LEFT)
|
||||||
|
method_combo.bind('<<ComboboxSelected>>', lambda e: self._update_scan_config())
|
||||||
|
|
||||||
|
# Status indicators on right
|
||||||
|
status_frame = ttk.Frame(inner)
|
||||||
|
status_frame.pack(side=tk.RIGHT)
|
||||||
|
|
||||||
|
ttk.Label(status_frame, text="Offset:").pack(side=tk.LEFT)
|
||||||
|
self.offset_label = ttk.Label(status_frame, text="--", width=5, font=('Arial', 9))
|
||||||
|
self.offset_label.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
ttk.Label(status_frame, text="Peak:").pack(side=tk.LEFT, padx=(8, 0))
|
||||||
|
self.peak_label = ttk.Label(status_frame, text="--", width=5, font=('Arial', 9))
|
||||||
|
self.peak_label.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
def _build_row3_movement(self, parent):
|
||||||
|
"""Row 3: Movement controls for all axes"""
|
||||||
row = ttk.LabelFrame(parent, text="Movement")
|
row = ttk.LabelFrame(parent, text="Movement")
|
||||||
row.pack(fill=tk.X, pady=(0, 3))
|
row.pack(fill=tk.X, pady=(0, 3))
|
||||||
|
|
||||||
|
|
@ -212,7 +342,7 @@ class AppGUI:
|
||||||
|
|
||||||
for axis in ["X", "Y", "Z"]:
|
for axis in ["X", "Y", "Z"]:
|
||||||
af = ttk.Frame(inner)
|
af = ttk.Frame(inner)
|
||||||
af.pack(side=tk.LEFT, padx=(0, 20))
|
af.pack(side=tk.LEFT, padx=(0, 15))
|
||||||
|
|
||||||
ttk.Label(af, text=f"{axis}:", font=('Arial', 10, 'bold')).pack(side=tk.LEFT)
|
ttk.Label(af, text=f"{axis}:", font=('Arial', 10, 'bold')).pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
|
@ -221,25 +351,28 @@ class AppGUI:
|
||||||
dir_btn.pack(side=tk.LEFT, padx=2)
|
dir_btn.pack(side=tk.LEFT, padx=2)
|
||||||
self.dir_labels[axis] = dir_btn
|
self.dir_labels[axis] = dir_btn
|
||||||
|
|
||||||
move_btn = tk.Button(af, text="Move", width=6, bg='#4CAF50', fg='white',
|
move_btn = tk.Button(
|
||||||
command=lambda a=axis: self._toggle_movement(a))
|
af, text="Move", width=6,
|
||||||
|
bg='#4CAF50', fg='white', font=('Arial', 9),
|
||||||
|
activebackground='#45a049', relief=tk.RAISED,
|
||||||
|
command=lambda a=axis: self._toggle_movement(a)
|
||||||
|
)
|
||||||
move_btn.pack(side=tk.LEFT, padx=2)
|
move_btn.pack(side=tk.LEFT, padx=2)
|
||||||
self.move_buttons[axis] = move_btn
|
self.move_buttons[axis] = move_btn
|
||||||
|
|
||||||
ttk.Button(af, text="⏹", width=2,
|
ttk.Button(af, text="⏹", width=2,
|
||||||
command=lambda a=axis: self._stop_axis(a)).pack(side=tk.LEFT)
|
command=lambda a=axis: self._stop_axis(a)).pack(side=tk.LEFT)
|
||||||
|
|
||||||
# Stop all
|
# Stop all button
|
||||||
tk.Button(inner, text="STOP ALL", width=10, bg='#f44336', fg='white',
|
tk.Button(
|
||||||
font=('Arial', 9, 'bold'),
|
inner, text="STOP ALL", width=10,
|
||||||
command=self.motion.stop_all).pack(side=tk.RIGHT)
|
bg='#f44336', fg='white', font=('Arial', 9, 'bold'),
|
||||||
|
activebackground='#d32f2f', relief=tk.RAISED, bd=2,
|
||||||
|
command=self.motion.stop_all
|
||||||
|
).pack(side=tk.RIGHT)
|
||||||
|
|
||||||
# Test feature detection button
|
def _build_row4_speed_autofocus(self, parent):
|
||||||
ttk.Button(inner, text="Test Features", width=11,
|
"""Row 4: Speed and Autofocus controls"""
|
||||||
command=self._test_feature_detection).pack(side=tk.RIGHT, padx=(0, 10))
|
|
||||||
|
|
||||||
def _build_row3_speed_autofocus(self, parent):
|
|
||||||
"""Row 3: Speed and Autofocus controls"""
|
|
||||||
row = ttk.Frame(parent)
|
row = ttk.Frame(parent)
|
||||||
row.pack(fill=tk.X, pady=(0, 3))
|
row.pack(fill=tk.X, pady=(0, 3))
|
||||||
|
|
||||||
|
|
@ -257,12 +390,13 @@ class AppGUI:
|
||||||
|
|
||||||
ttk.Separator(sf, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=5)
|
ttk.Separator(sf, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=5)
|
||||||
|
|
||||||
self.fine_speed_label = ttk.Label(sf, text="50", width=3)
|
self.fine_speed_label = ttk.Label(sf, text="1", width=3)
|
||||||
self.fine_speed_label.pack(side=tk.LEFT)
|
self.fine_speed_label.pack(side=tk.LEFT)
|
||||||
|
|
||||||
self.speed_slider = ttk.Scale(sf, from_=0, to=6, orient=tk.HORIZONTAL, length=80)
|
self.speed_slider = ttk.Scale(sf, from_=0, to=6, orient=tk.HORIZONTAL, length=80)
|
||||||
self.speed_slider.set(5)
|
self.speed_slider.set(1)
|
||||||
self.speed_slider.config(command=self._on_speed_slider_change)
|
self.speed_slider.config(command=self._on_speed_slider_change)
|
||||||
|
self._on_speed_slider_change(1)
|
||||||
self.speed_slider.pack(side=tk.LEFT, padx=3)
|
self.speed_slider.pack(side=tk.LEFT, padx=3)
|
||||||
|
|
||||||
# Autofocus controls
|
# Autofocus controls
|
||||||
|
|
@ -282,8 +416,8 @@ class AppGUI:
|
||||||
ttk.Button(af, text="Stop", width=5,
|
ttk.Button(af, text="Stop", width=5,
|
||||||
command=self._stop_autofocus).pack(side=tk.LEFT, padx=2)
|
command=self._stop_autofocus).pack(side=tk.LEFT, padx=2)
|
||||||
|
|
||||||
def _build_row4_status_log(self, parent):
|
def _build_row5_status_log(self, parent):
|
||||||
"""Row 4: Status and Log"""
|
"""Row 5: Status and Log"""
|
||||||
row = ttk.Frame(parent)
|
row = ttk.Frame(parent)
|
||||||
row.pack(fill=tk.X, pady=(0, 3))
|
row.pack(fill=tk.X, pady=(0, 3))
|
||||||
|
|
||||||
|
|
@ -317,35 +451,320 @@ class AppGUI:
|
||||||
command=self._send_custom_command).pack(side=tk.RIGHT, padx=(5, 0))
|
command=self._send_custom_command).pack(side=tk.RIGHT, padx=(5, 0))
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Feature Detection Testing
|
# Comparison Overlay Drawing
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
||||||
def _test_feature_detection(self):
|
def _draw_comparison_overlay(self, frame: np.ndarray) -> np.ndarray:
|
||||||
"""Test and log feature detection on current frame"""
|
"""
|
||||||
|
Draw edge comparison overlay directly on camera frame.
|
||||||
|
Shows reference, current, and interpolated binary images.
|
||||||
|
"""
|
||||||
|
if not self.scanner or not self.show_comparison_var.get():
|
||||||
|
return frame
|
||||||
|
|
||||||
|
state = self.scanner.get_comparison_state()
|
||||||
|
|
||||||
|
# Show overlay if tracking OR if paused with valid reference image
|
||||||
|
if not state.is_tracking and not (self.scanner.paused and state.reference_image is not None):
|
||||||
|
return frame
|
||||||
|
|
||||||
|
h, w = frame.shape[:2]
|
||||||
|
border_pct = self.scan_config.border_percentage
|
||||||
|
|
||||||
|
# Map raw edges to display positions after 90° CW rotation
|
||||||
|
edge_to_display = {
|
||||||
|
'top': 'right',
|
||||||
|
'bottom': 'left',
|
||||||
|
'left': 'top',
|
||||||
|
'right': 'bottom'
|
||||||
|
}
|
||||||
|
|
||||||
|
ref_display_edge = edge_to_display.get(state.reference_edge, '')
|
||||||
|
target_display_edge = edge_to_display.get(state.target_edge, '')
|
||||||
|
|
||||||
|
# Draw reference image (cyan)
|
||||||
|
if state.reference_image is not None:
|
||||||
|
frame = self._draw_binary_at_edge(
|
||||||
|
frame, state.reference_image, ref_display_edge,
|
||||||
|
border_pct, color=(0, 255, 255), label="REF"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Draw current/interpolated image (yellow)
|
||||||
|
# Prefer interpolated if available, otherwise current
|
||||||
|
display_image = state.interpolated_image if state.interpolated_image is not None else state.current_image
|
||||||
|
if display_image is not None:
|
||||||
|
label = "INT" if state.interpolated_image is not None else "CUR"
|
||||||
|
frame = self._draw_binary_at_edge(
|
||||||
|
frame, display_image, target_display_edge,
|
||||||
|
border_pct, color=(255, 255, 0), label=label
|
||||||
|
)
|
||||||
|
|
||||||
|
# Draw status box with extended info
|
||||||
|
frame = self._draw_comparison_status_box(frame, state)
|
||||||
|
|
||||||
|
# Draw similarity history graph if available
|
||||||
|
if state.similarity_history and len(state.similarity_history) > 1:
|
||||||
|
frame = self._draw_similarity_graph(frame, state.similarity_history, state.threshold)
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def _draw_binary_at_edge(self, frame: np.ndarray, binary_img: np.ndarray,
|
||||||
|
display_edge: str, border_pct: float,
|
||||||
|
color: tuple, label: str) -> np.ndarray:
|
||||||
|
"""Draw a binary image overlaid at the specified display edge."""
|
||||||
|
h, w = frame.shape[:2]
|
||||||
|
|
||||||
|
# Convert binary to BGR and apply color tint
|
||||||
|
binary_bgr = cv2.cvtColor(binary_img, cv2.COLOR_GRAY2BGR)
|
||||||
|
mask = binary_img > 127
|
||||||
|
binary_bgr[mask] = color
|
||||||
|
|
||||||
|
# Rotate binary to match 90° CW display rotation
|
||||||
|
binary_bgr = cv2.rotate(binary_bgr, cv2.ROTATE_90_CLOCKWISE)
|
||||||
|
|
||||||
|
alpha = 0.7
|
||||||
|
|
||||||
|
if display_edge == 'left':
|
||||||
|
border_w = int(w * border_pct)
|
||||||
|
resized = cv2.resize(binary_bgr, (border_w, h))
|
||||||
|
frame[:, :border_w] = cv2.addWeighted(
|
||||||
|
frame[:, :border_w], 1 - alpha, resized, alpha, 0
|
||||||
|
)
|
||||||
|
cv2.putText(frame, label, (5, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
|
||||||
|
|
||||||
|
elif display_edge == 'right':
|
||||||
|
border_w = int(w * border_pct)
|
||||||
|
resized = cv2.resize(binary_bgr, (border_w, h))
|
||||||
|
frame[:, w-border_w:] = cv2.addWeighted(
|
||||||
|
frame[:, w-border_w:], 1 - alpha, resized, alpha, 0
|
||||||
|
)
|
||||||
|
cv2.putText(frame, label, (w - border_w + 5, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
|
||||||
|
|
||||||
|
elif display_edge == 'top':
|
||||||
|
border_h = int(h * border_pct)
|
||||||
|
resized = cv2.resize(binary_bgr, (w, border_h))
|
||||||
|
frame[:border_h, :] = cv2.addWeighted(
|
||||||
|
frame[:border_h, :], 1 - alpha, resized, alpha, 0
|
||||||
|
)
|
||||||
|
cv2.putText(frame, label, (5, border_h - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
|
||||||
|
|
||||||
|
elif display_edge == 'bottom':
|
||||||
|
border_h = int(h * border_pct)
|
||||||
|
resized = cv2.resize(binary_bgr, (w, border_h))
|
||||||
|
frame[h-border_h:, :] = cv2.addWeighted(
|
||||||
|
frame[h-border_h:, :], 1 - alpha, resized, alpha, 0
|
||||||
|
)
|
||||||
|
cv2.putText(frame, label, (5, h - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def _draw_comparison_status_box(self, frame: np.ndarray, state) -> np.ndarray:
|
||||||
|
"""Draw a status box showing similarity, offset, and match status"""
|
||||||
|
h, w = frame.shape[:2]
|
||||||
|
|
||||||
|
# Position: top center
|
||||||
|
box_w, box_h = 240, 90
|
||||||
|
box_x = (w - box_w) // 2
|
||||||
|
box_y = 10
|
||||||
|
|
||||||
|
# Semi-transparent background
|
||||||
|
overlay = frame.copy()
|
||||||
|
cv2.rectangle(overlay, (box_x, box_y), (box_x + box_w, box_y + box_h), (0, 0, 0), -1)
|
||||||
|
frame = cv2.addWeighted(overlay, 0.6, frame, 0.4, 0)
|
||||||
|
|
||||||
|
# Text positioning
|
||||||
|
tx = box_x + 10
|
||||||
|
ty = box_y + 18
|
||||||
|
line_h = 18
|
||||||
|
|
||||||
|
# Direction info
|
||||||
|
cv2.putText(frame, f"Dir: {state.direction}", (tx, ty),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.45, (255, 255, 255), 1)
|
||||||
|
ty += line_h
|
||||||
|
|
||||||
|
# Similarity with color coding
|
||||||
|
sim = state.similarity
|
||||||
|
threshold = state.threshold
|
||||||
|
is_paused = self.scanner and self.scanner.paused
|
||||||
|
|
||||||
|
if state.shift_detected or sim >= threshold:
|
||||||
|
sim_color = (0, 255, 0) # Green
|
||||||
|
elif is_paused:
|
||||||
|
sim_color = (255, 200, 0) # Cyan
|
||||||
|
else:
|
||||||
|
sim_color = (0, 165, 255) # Orange
|
||||||
|
|
||||||
|
cv2.putText(frame, f"Similarity: {sim:.3f}", (tx, ty),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.45, sim_color, 1)
|
||||||
|
|
||||||
|
# Threshold on same line
|
||||||
|
cv2.putText(frame, f"(thr: {threshold:.2f})", (tx + 120, ty),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.4, (150, 150, 150), 1)
|
||||||
|
ty += line_h
|
||||||
|
|
||||||
|
# Offset info (from interpolation)
|
||||||
|
offset_text = f"Offset: {state.best_offset:.2f}" if state.best_offset > 0 else "Offset: --"
|
||||||
|
cv2.putText(frame, offset_text, (tx, ty),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.45, (200, 200, 200), 1)
|
||||||
|
ty += line_h
|
||||||
|
|
||||||
|
# Status
|
||||||
|
if state.shift_detected:
|
||||||
|
if state.peak_detected:
|
||||||
|
status = "PEAK MATCHED!"
|
||||||
|
else:
|
||||||
|
status = "MATCHED!"
|
||||||
|
status_color = (0, 255, 0)
|
||||||
|
elif sim >= threshold:
|
||||||
|
status = "ABOVE THRESHOLD"
|
||||||
|
status_color = (0, 255, 0)
|
||||||
|
elif is_paused:
|
||||||
|
status = "PAUSED"
|
||||||
|
status_color = (255, 200, 0)
|
||||||
|
else:
|
||||||
|
status = "Searching..."
|
||||||
|
status_color = (200, 200, 200)
|
||||||
|
|
||||||
|
cv2.putText(frame, status, (tx, ty),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.45, status_color, 1)
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def _draw_similarity_graph(self, frame: np.ndarray, history: list, threshold: float) -> np.ndarray:
|
||||||
|
"""Draw a small similarity history graph in the corner"""
|
||||||
|
h, w = frame.shape[:2]
|
||||||
|
|
||||||
|
# Graph dimensions and position (bottom right)
|
||||||
|
graph_w, graph_h = 150, 60
|
||||||
|
margin = 10
|
||||||
|
gx = w - graph_w - margin
|
||||||
|
gy = h - graph_h - margin - 50 # Above the bottom edge region
|
||||||
|
|
||||||
|
# Semi-transparent background
|
||||||
|
overlay = frame.copy()
|
||||||
|
cv2.rectangle(overlay, (gx, gy), (gx + graph_w, gy + graph_h), (0, 0, 0), -1)
|
||||||
|
frame = cv2.addWeighted(overlay, 0.5, frame, 0.5, 0)
|
||||||
|
|
||||||
|
# Draw threshold line
|
||||||
|
thr_y = int(gy + graph_h - (threshold * graph_h))
|
||||||
|
cv2.line(frame, (gx, thr_y), (gx + graph_w, thr_y), (0, 100, 255), 1)
|
||||||
|
|
||||||
|
# Draw history line
|
||||||
|
if len(history) > 1:
|
||||||
|
# Take last N points that fit in the graph
|
||||||
|
max_points = graph_w // 2
|
||||||
|
plot_history = history[-max_points:] if len(history) > max_points else history
|
||||||
|
|
||||||
|
points = []
|
||||||
|
for i, val in enumerate(plot_history):
|
||||||
|
px = gx + int((i / max(len(plot_history) - 1, 1)) * (graph_w - 1))
|
||||||
|
py = int(gy + graph_h - (min(val, 1.0) * graph_h))
|
||||||
|
points.append((px, py))
|
||||||
|
|
||||||
|
# Draw the line
|
||||||
|
for i in range(1, len(points)):
|
||||||
|
color = (0, 255, 0) if plot_history[i] >= threshold else (0, 165, 255)
|
||||||
|
cv2.line(frame, points[i-1], points[i], color, 2)
|
||||||
|
|
||||||
|
# Label
|
||||||
|
cv2.putText(frame, "History", (gx + 5, gy + 12),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.35, (200, 200, 200), 1)
|
||||||
|
|
||||||
|
# Max value in history
|
||||||
|
if history:
|
||||||
|
max_sim = max(history)
|
||||||
|
cv2.putText(frame, f"max:{max_sim:.2f}", (gx + graph_w - 55, gy + 12),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.3, (150, 255, 150), 1)
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def _on_threshold_change(self, value):
|
||||||
|
"""Handle threshold slider change"""
|
||||||
|
val = float(value)
|
||||||
|
self.threshold_label.config(text=f"{val:.2f}")
|
||||||
|
if self.scanner:
|
||||||
|
self.scanner.config.similarity_threshold = val
|
||||||
|
|
||||||
|
def _update_similarity_display(self):
|
||||||
|
"""Update the similarity labels in the UI"""
|
||||||
|
if not self.scanner:
|
||||||
|
return
|
||||||
|
|
||||||
|
state = self.scanner.get_comparison_state()
|
||||||
|
|
||||||
|
# Show status if tracking OR if paused with valid data
|
||||||
|
if state.is_tracking or (self.scanner.paused and state.reference_image is not None):
|
||||||
|
sim = state.similarity
|
||||||
|
self.similarity_label.config(text=f"{sim:.3f}")
|
||||||
|
|
||||||
|
# Update offset and peak labels
|
||||||
|
self.offset_label.config(text=f"{state.best_offset:.2f}")
|
||||||
|
if state.similarity_history:
|
||||||
|
max_sim = max(state.similarity_history)
|
||||||
|
self.peak_label.config(text=f"{max_sim:.2f}")
|
||||||
|
|
||||||
|
if state.shift_detected or sim >= state.threshold:
|
||||||
|
self.similarity_label.config(foreground='green')
|
||||||
|
status_text = "PEAK!" if state.peak_detected else "MATCH!"
|
||||||
|
self.match_status_label.config(text=status_text, foreground='green')
|
||||||
|
elif self.scanner.paused:
|
||||||
|
self.similarity_label.config(foreground='blue')
|
||||||
|
self.match_status_label.config(text="PAUSED", foreground='blue')
|
||||||
|
else:
|
||||||
|
self.similarity_label.config(foreground='orange')
|
||||||
|
self.match_status_label.config(text="Searching", foreground='gray')
|
||||||
|
else:
|
||||||
|
self.similarity_label.config(text="--", foreground='black')
|
||||||
|
self.match_status_label.config(text="", foreground='black')
|
||||||
|
self.offset_label.config(text="--")
|
||||||
|
self.peak_label.config(text="--")
|
||||||
|
|
||||||
|
def _test_edge_comparison(self):
|
||||||
|
"""Test edge detection on current frame"""
|
||||||
if not self.scanner:
|
if not self.scanner:
|
||||||
self.log_message("Scanner not initialized")
|
self.log_message("Scanner not initialized")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
frame = self.camera.capture_frame()
|
results = self.scanner.test_edge_comparison()
|
||||||
|
|
||||||
# Detect features on all edges
|
self.log_message("Edge test results (raw frame coordinates):")
|
||||||
for edge in ['left', 'right', 'top', 'bottom']:
|
for edge in ['left', 'right', 'top', 'bottom']:
|
||||||
region = self.scanner.get_edge_region(frame, edge)
|
r = results[edge]
|
||||||
kp, desc = self.scanner.detect_features(region)
|
self.log_message(f" {edge.upper()}: {r['shape']}, white: {r['white_ratio']:.1%}")
|
||||||
count = len(kp) if kp else 0
|
|
||||||
self.log_message(f" {edge.upper()}: {count} features")
|
|
||||||
|
|
||||||
# Also update scanner's visualization data
|
self.log_message(f" X-axis (top↔bottom): {results['x_axis_sim']:.3f}")
|
||||||
self.scanner._update_edge_features(frame)
|
self.log_message(f" Y-axis (left↔right): {results['y_axis_sim']:.3f}")
|
||||||
|
|
||||||
# Enable feature view
|
|
||||||
self.show_features_var.set(True)
|
|
||||||
|
|
||||||
self.log_message("Feature detection test complete - enable 'Features' checkbox to see overlay")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log_message(f"Feature detection error: {e}")
|
self.log_message(f"Edge test error: {e}")
|
||||||
|
|
||||||
|
def _test_interpolation(self):
|
||||||
|
"""Test interpolation matching"""
|
||||||
|
if not self.scanner:
|
||||||
|
self.log_message("Scanner not initialized")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.log_message("Starting interpolation test (5 frames)...")
|
||||||
|
|
||||||
|
def run_test():
|
||||||
|
results = self.scanner.test_interpolation(num_frames=5)
|
||||||
|
self.root.after(0, lambda: self._show_interp_results(results))
|
||||||
|
|
||||||
|
threading.Thread(target=run_test, daemon=True).start()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log_message(f"Interpolation test error: {e}")
|
||||||
|
|
||||||
|
def _show_interp_results(self, results):
|
||||||
|
"""Display interpolation test results"""
|
||||||
|
self.log_message("Interpolation test results:")
|
||||||
|
for fr in results['frames']:
|
||||||
|
self.log_message(f" Frame {fr['frame_idx']}: "
|
||||||
|
f"direct={fr['direct_similarity']:.3f}, "
|
||||||
|
f"interp={fr['interpolated_similarity']:.3f}, "
|
||||||
|
f"offset={fr['best_offset']:.2f}")
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Scanner Handlers
|
# Scanner Handlers
|
||||||
|
|
@ -353,16 +772,21 @@ class AppGUI:
|
||||||
|
|
||||||
def _update_scan_config(self):
|
def _update_scan_config(self):
|
||||||
"""Update scanner config from GUI values"""
|
"""Update scanner config from GUI values"""
|
||||||
self.scan_config.autofocus_every_n_tiles = int(self.af_every_var.get())
|
self.scan_config.similarity_threshold = self.threshold_var.get()
|
||||||
self.scan_config.autofocus_every_row = self.af_every_row_var.get()
|
self.scan_config.use_interpolation = self.use_interpolation_var.get()
|
||||||
|
self.scan_config.num_interpolations = self.num_interp_var.get()
|
||||||
|
self.scan_config.use_peak_detection = self.use_peak_detection_var.get()
|
||||||
|
self.scan_config.peak_window_size = self.peak_window_var.get()
|
||||||
|
self.scan_config.peak_drop_threshold = self.peak_drop_var.get()
|
||||||
|
self.scan_config.comparison_method = self.comparison_method_var.get()
|
||||||
|
|
||||||
|
# Reinitialize scanner with new config
|
||||||
self._init_scanner()
|
self._init_scanner()
|
||||||
|
|
||||||
def _start_scan(self):
|
def _start_scan(self):
|
||||||
self._update_scan_config()
|
self._update_scan_config()
|
||||||
# Enable feature view during scan
|
|
||||||
self.show_features_var.set(True)
|
|
||||||
if self.scanner.start():
|
if self.scanner.start():
|
||||||
self.scan_start_btn.config(state='disabled')
|
self.scan_start_btn.config(state='disabled', bg='#888888')
|
||||||
self.scan_pause_btn.config(state='normal')
|
self.scan_pause_btn.config(state='normal')
|
||||||
self.scan_stop_btn.config(state='normal')
|
self.scan_stop_btn.config(state='normal')
|
||||||
self.scan_status_label.config(text="Scanning")
|
self.scan_status_label.config(text="Scanning")
|
||||||
|
|
@ -371,12 +795,12 @@ class AppGUI:
|
||||||
def _pause_scan(self):
|
def _pause_scan(self):
|
||||||
if self.scanner.paused:
|
if self.scanner.paused:
|
||||||
self.scanner.resume()
|
self.scanner.resume()
|
||||||
self.scan_pause_btn.config(text="⏸")
|
self.scan_pause_btn.config(text="⏸", bg='#2196F3')
|
||||||
self.scan_status_label.config(text="Scanning")
|
self.scan_status_label.config(text="Scanning")
|
||||||
self.scan_progress_bar.start(10)
|
self.scan_progress_bar.start(10)
|
||||||
else:
|
else:
|
||||||
self.scanner.pause()
|
self.scanner.pause()
|
||||||
self.scan_pause_btn.config(text="▶")
|
self.scan_pause_btn.config(text="▶", bg='#4CAF50')
|
||||||
self.scan_status_label.config(text="Paused")
|
self.scan_status_label.config(text="Paused")
|
||||||
self.scan_progress_bar.stop()
|
self.scan_progress_bar.stop()
|
||||||
|
|
||||||
|
|
@ -385,8 +809,8 @@ class AppGUI:
|
||||||
self._scan_finished()
|
self._scan_finished()
|
||||||
|
|
||||||
def _scan_finished(self):
|
def _scan_finished(self):
|
||||||
self.scan_start_btn.config(state='normal')
|
self.scan_start_btn.config(state='normal', bg='#4CAF50')
|
||||||
self.scan_pause_btn.config(state='disabled', text="⏸")
|
self.scan_pause_btn.config(state='disabled', text="⏸", bg='#2196F3')
|
||||||
self.scan_stop_btn.config(state='disabled')
|
self.scan_stop_btn.config(state='disabled')
|
||||||
self.scan_status_label.config(text="Idle")
|
self.scan_status_label.config(text="Idle")
|
||||||
self.scan_progress_bar.stop()
|
self.scan_progress_bar.stop()
|
||||||
|
|
@ -487,14 +911,14 @@ class AppGUI:
|
||||||
if self.motion.is_moving(axis):
|
if self.motion.is_moving(axis):
|
||||||
self.motion.stop_axis(axis)
|
self.motion.stop_axis(axis)
|
||||||
self.motion.start_movement(axis)
|
self.motion.start_movement(axis)
|
||||||
self.move_buttons[axis].config(text="Moving", bg='orange')
|
self.move_buttons[axis].config(text="Moving", bg='#FF9800')
|
||||||
|
|
||||||
def _toggle_movement(self, axis):
|
def _toggle_movement(self, axis):
|
||||||
if self.motion.is_moving(axis):
|
if self.motion.is_moving(axis):
|
||||||
self._stop_axis(axis)
|
self._stop_axis(axis)
|
||||||
else:
|
else:
|
||||||
self.motion.start_movement(axis)
|
self.motion.start_movement(axis)
|
||||||
self.move_buttons[axis].config(text="Moving", bg='orange')
|
self.move_buttons[axis].config(text="Moving", bg='#FF9800')
|
||||||
|
|
||||||
def _stop_axis(self, axis):
|
def _stop_axis(self, axis):
|
||||||
self.motion.stop_axis(axis)
|
self.motion.stop_axis(axis)
|
||||||
|
|
@ -579,22 +1003,18 @@ class AppGUI:
|
||||||
score = self.autofocus.get_focus_score()
|
score = self.autofocus.get_focus_score()
|
||||||
self.focus_score_label.config(text=f"Focus: {score:.1f}")
|
self.focus_score_label.config(text=f"Focus: {score:.1f}")
|
||||||
|
|
||||||
# Apply overlays
|
# Apply standard overlays
|
||||||
frame = self._draw_overlays(frame)
|
frame = self._draw_overlays(frame)
|
||||||
|
|
||||||
# Apply feature overlay if enabled
|
# Apply comparison overlay (shows binary edges during tracking)
|
||||||
if self.show_features_var.get() and self.scanner:
|
frame = self._draw_comparison_overlay(frame)
|
||||||
# Update edge features periodically
|
|
||||||
self.scanner._update_edge_features(frame)
|
|
||||||
# Get overlay from scanner
|
|
||||||
frame = self.scanner.get_feature_overlay(frame)
|
|
||||||
|
|
||||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||||
|
|
||||||
# Scale to fit - maximize for vertical monitor
|
# Scale to fit
|
||||||
h, w = frame.shape[:2]
|
h, w = frame.shape[:2]
|
||||||
max_h = 700
|
max_h = 700
|
||||||
max_w = 650
|
max_w = 680
|
||||||
|
|
||||||
scale = min(max_h / h, max_w / w)
|
scale = min(max_h / h, max_w / w)
|
||||||
if scale < 1:
|
if scale < 1:
|
||||||
|
|
@ -632,10 +1052,10 @@ class AppGUI:
|
||||||
def _draw_edge_regions(self, frame, color=(255, 255, 0), thickness=1):
|
def _draw_edge_regions(self, frame, color=(255, 255, 0), thickness=1):
|
||||||
h, w = frame.shape[:2]
|
h, w = frame.shape[:2]
|
||||||
bh, bw = int(h * 0.10), int(w * 0.10)
|
bh, bw = int(h * 0.10), int(w * 0.10)
|
||||||
cv2.rectangle(frame, (0, 0), (bw, h), color, thickness)
|
cv2.rectangle(frame, (0, 0), (bw, h), color, thickness) # Left
|
||||||
cv2.rectangle(frame, (w - bw, 0), (w, h), color, thickness)
|
cv2.rectangle(frame, (w - bw, 0), (w, h), color, thickness) # Right
|
||||||
cv2.rectangle(frame, (0, 0), (w, bh), color, thickness)
|
cv2.rectangle(frame, (0, 0), (w, bh), color, thickness) # Top
|
||||||
cv2.rectangle(frame, (0, h - bh), (w, h), color, thickness)
|
cv2.rectangle(frame, (0, h - bh), (w, h), color, thickness) # Bottom
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
@ -648,6 +1068,7 @@ class AppGUI:
|
||||||
self.update_camera()
|
self.update_camera()
|
||||||
self._process_serial_queue()
|
self._process_serial_queue()
|
||||||
self._process_tile_queue()
|
self._process_tile_queue()
|
||||||
|
self._update_similarity_display()
|
||||||
|
|
||||||
if not self.autofocus.is_running():
|
if not self.autofocus.is_running():
|
||||||
self.af_button.config(state='normal')
|
self.af_button.config(state='normal')
|
||||||
|
|
|
||||||
1107
src/scanner.py
1107
src/scanner.py
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue