Skip to content

Commit cf9780f

Browse files
committed
Added filtering based on gradient in direction
1 parent 84e1cf5 commit cf9780f

File tree

1 file changed

+131
-0
lines changed

1 file changed

+131
-0
lines changed

pyidi/selection/main_window.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ def __init__(self, video):
5555
self.brush_deselect_mode = False
5656
self.installEventFilter(self)
5757

58+
self.gradient_direction_points = []
59+
self.gradient_direction = None
60+
self.setting_direction = False
61+
5862
self.selected_points = []
5963
self.manual_points = []
6064
self.candidate_points = []
@@ -185,13 +189,15 @@ def ui_graphics(self):
185189
brush=pg.mkBrush(0, 255, 0, 200),
186190
size=6
187191
)
192+
self.direction_line = pg.PlotDataItem(pen=pg.mkPen('r', width=2))
188193

189194
self.view.addItem(self.image_item)
190195
self.view.addItem(self.polygon_line)
191196
self.view.addItem(self.polygon_points_scatter)
192197
self.view.addItem(self.roi_overlay) # Add scatter for showing square points
193198
self.view.addItem(self.scatter) # Add scatter for showing points
194199
self.view.addItem(self.candidate_scatter)
200+
self.view.addItem(self.direction_line)
195201

196202
self.splitter.addWidget(self.pg_widget)
197203

@@ -372,6 +378,7 @@ def ui_auto_right_menu(self):
372378
self.auto_method_buttons = {}
373379
method_names = [
374380
"Shi-Tomasi",
381+
"Gradient in direction",
375382
]
376383
for i, name in enumerate(method_names):
377384
button = QtWidgets.QPushButton(name)
@@ -426,19 +433,51 @@ def update_label_and_recompute(val):
426433
self.update_threshold_and_show_shi_tomsi() # Placeholder method
427434
self.threshold_slider.valueChanged.connect(update_label_and_recompute)
428435

436+
# Gradient in a specified direction settings
437+
self.direction_button = QtWidgets.QPushButton("Set direction on image")
438+
self.direction_button.setVisible(False)
439+
self.direction_button.setCheckable(True)
440+
self.direction_button.clicked.connect(self.set_gradient_direction_mode)
441+
self.automatic_layout.addWidget(self.direction_button)
442+
443+
self.direction_threshold = 10
444+
self.gradient_thresh_label = QtWidgets.QLabel(f"Threshold (grad): {self.direction_threshold}")
445+
self.gradient_thresh_label.setVisible(False)
446+
self.automatic_layout.addWidget(self.gradient_thresh_label)
447+
448+
self.gradient_thresh_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
449+
self.gradient_thresh_slider.setRange(1, 100)
450+
self.gradient_thresh_slider.setSingleStep(1)
451+
self.gradient_thresh_slider.setValue(self.direction_threshold)
452+
self.gradient_thresh_slider.setVisible(False)
453+
self.automatic_layout.addWidget(self.gradient_thresh_slider)
454+
455+
def update_direction_thresh(val):
456+
self.gradient_thresh_label.setText(f"Threshold (grad): {val}")
457+
self.update_threshold_and_show_gradient_direction()
458+
self.gradient_thresh_slider.valueChanged.connect(update_direction_thresh)
459+
429460
self.automatic_layout.addStretch(1)
430461

431462
def auto_method_selected(self, id: int):
432463
method_name = list(self.auto_method_buttons.keys())[id]
433464
print(f"Selected automatic method: {method_name}")
434465
# Here you can switch method behavior, show/hide widgets, etc.
435466
is_shi_tomasi = method_name == "Shi-Tomasi"
467+
is_gradient_dir = method_name == "Gradient in direction"
468+
436469
self.threshold_label.setVisible(is_shi_tomasi)
437470
self.threshold_slider.setVisible(is_shi_tomasi)
438471

439472
if is_shi_tomasi:
440473
self.compute_candidate_points_shi_tomasi()
441474

475+
self.direction_button.setVisible(is_gradient_dir)
476+
self.gradient_thresh_label.setVisible(is_gradient_dir)
477+
self.gradient_thresh_slider.setVisible(is_gradient_dir)
478+
if is_gradient_dir and self.gradient_direction is not None:
479+
self.compute_candidate_points_gradient_direction()
480+
442481
def method_selected(self, id: int):
443482
method_name = list(self.method_buttons.keys())[id]
444483
print(f"Selected method: {method_name}")
@@ -487,6 +526,20 @@ def switch_mode(self, mode: str):
487526
# self.candidate_scatter.setVisible(True)
488527

489528
def on_mouse_click(self, event):
529+
if self.setting_direction:
530+
pos = event.scenePos()
531+
if self.view.sceneBoundingRect().contains(pos):
532+
point = self.view.mapSceneToView(pos)
533+
self.gradient_direction_points.append((point.x(), point.y()))
534+
if len(self.gradient_direction_points) == 2:
535+
self.compute_direction_vector()
536+
self.update_direction_line()
537+
self.setting_direction = False
538+
self.direction_button.setChecked(False)
539+
print(f"Gradient direction set: {self.gradient_direction}")
540+
self.compute_candidate_points_gradient_direction()
541+
return
542+
490543
if self.mode == "automatic":
491544
return
492545

@@ -820,6 +873,7 @@ def handle_remove_point(self, event):
820873
self.update_selected_points()
821874

822875
# Automatic filtering
876+
# Shi-Tomasi method
823877
def compute_candidate_points_shi_tomasi(self):
824878
"""Compute good feature points using structure tensor analysis (Shi–Tomasi style)."""
825879
from scipy.ndimage import sobel
@@ -914,6 +968,83 @@ def clear_candidates(self):
914968

915969
self.update_selected_points() # Update main display to remove candidates
916970

971+
# Gradient in a specified direction
972+
def set_gradient_direction_mode(self):
973+
self.setting_direction = True
974+
self.gradient_direction_points = []
975+
self.direction_button.setChecked(True) # Keep it visually pressed
976+
print("Click two points to set the gradient direction.")
977+
978+
def compute_direction_vector(self):
979+
p1, p2 = self.gradient_direction_points
980+
dx, dy = p2[0] - p1[0], p2[1] - p1[1]
981+
norm = np.sqrt(dx**2 + dy**2)
982+
if norm == 0:
983+
self.gradient_direction = None
984+
else:
985+
self.gradient_direction = (dx / norm, dy / norm)
986+
987+
def compute_candidate_points_gradient_direction(self):
988+
from scipy.ndimage import sobel
989+
990+
if self.gradient_direction is None:
991+
return
992+
993+
dy, dx = self.gradient_direction
994+
subset_size = self.subset_size_spinbox.value()
995+
roi_size = subset_size // 2
996+
997+
img = self.image_item.image.astype(np.float32)
998+
candidates = []
999+
1000+
for row, col in self.selected_points:
1001+
y, x = int(round(row)), int(round(col))
1002+
1003+
if (y - roi_size < 0 or y + roi_size + 1 > img.shape[0] or
1004+
x - roi_size < 0 or x + roi_size + 1 > img.shape[1]):
1005+
continue
1006+
1007+
roi = img[y - roi_size: y + roi_size + 1,
1008+
x - roi_size: x + roi_size + 1]
1009+
1010+
gx = sobel(roi, axis=1)
1011+
gy = sobel(roi, axis=0)
1012+
1013+
gdir = np.abs(gx * dx) + np.abs(gy * dy)
1014+
strength = np.sum(np.abs(gdir))
1015+
1016+
candidates.append((x + 0.0, y + 0.0, strength))
1017+
1018+
if not candidates:
1019+
self.candidate_points = []
1020+
self.update_candidate_display()
1021+
return
1022+
1023+
values = np.array([v[2] for v in candidates])
1024+
self.max_grad_dir = np.max(values)
1025+
self.candidates_grad_dir = candidates
1026+
self.update_threshold_and_show_gradient_direction()
1027+
1028+
def update_threshold_and_show_gradient_direction(self):
1029+
threshold_ratio = self.gradient_thresh_slider.value() / 100.0
1030+
threshold = self.max_grad_dir * threshold_ratio
1031+
1032+
self.candidate_points = [
1033+
(round(y)+0.5, round(x)+0.5)
1034+
for (x, y, v) in self.candidates_grad_dir
1035+
if v > threshold
1036+
]
1037+
self.update_candidate_display()
1038+
self.update_candidate_points_count()
1039+
1040+
def update_direction_line(self):
1041+
if len(self.gradient_direction_points) == 2:
1042+
xs = [p[0] for p in self.gradient_direction_points]
1043+
ys = [p[1] for p in self.gradient_direction_points]
1044+
self.direction_line.setData(xs, ys)
1045+
else:
1046+
self.direction_line.clear()
1047+
9171048
# Brush
9181049
def handle_brush_start(self, ev):
9191050
if self.image_item.image is None:

0 commit comments

Comments
 (0)