@@ -52,6 +52,7 @@ def __init__(self, video):
5252 self ._paint_mask = None # Same shape as the image
5353 self ._paint_radius = 10 # pixels
5454 self .ctrl_held = False
55+ self .brush_deselect_mode = False
5556 self .installEventFilter (self )
5657
5758 self .selected_points = []
@@ -312,6 +313,13 @@ def update_label_and_recompute(val):
312313 self .start_new_line_button .setVisible (False ) # Hidden by default
313314 self .manual_layout .addWidget (self .start_new_line_button )
314315
316+ # Brush mode
317+ self .brush_deselect_button = QtWidgets .QPushButton ("Deselect painted area" )
318+ self .brush_deselect_button .setCheckable (True )
319+ self .brush_deselect_button .setVisible (False ) # shown only for Brush mode
320+ self .brush_deselect_button .clicked .connect (self .activate_brush_deselect )
321+ self .manual_layout .addWidget (self .brush_deselect_button )
322+
315323 self .manual_layout .addStretch (1 )
316324
317325 # Polygon manager (visible only for "Along the line")
@@ -440,6 +448,8 @@ def method_selected(self, id: int):
440448 self .distance_label .setVisible (show_spacing )
441449 self .distance_slider .setVisible (show_spacing )
442450
451+ self .brush_deselect_button .setVisible (is_brush )
452+
443453 def switch_mode (self , mode : str ):
444454 self .mode = mode
445455 if mode == "manual" :
@@ -885,7 +895,6 @@ def clear_candidates(self):
885895
886896 # Brush
887897 def handle_brush_start (self , ev ):
888- QtWidgets .QApplication .setOverrideCursor (QtCore .Qt .CursorShape .CrossCursor )
889898 if self .image_item .image is None :
890899 return
891900 h , w = self .image_item .image .shape [:2 ]
@@ -912,21 +921,49 @@ def handle_brush_move(self, ev):
912921 self .update_brush_overlay ()
913922
914923 def handle_brush_end (self , ev ):
915- QtWidgets .QApplication .restoreOverrideCursor ()
916-
917924 if self ._paint_mask is None :
918925 return
919926
920927 subset_size = self .subset_size_spinbox .value ()
921928 spacing = self .distance_slider .value ()
929+
930+ # Generate (row, col) points inside the painted mask
922931 brush_rois = rois_inside_mask (self ._paint_mask , subset_size , spacing )
923- self .manual_points .extend (brush_rois )
932+
933+ # Convert to set of tuples for fast comparison
934+ roi_set = set ((int (round (y )), int (round (x ))) for y , x in brush_rois )
935+
936+ if self .brush_deselect_mode :
937+ # Remove from manual points
938+ self .manual_points = [
939+ pt for pt in self .manual_points
940+ if (int (round (pt [0 ])), int (round (pt [1 ]))) not in roi_set
941+ ]
942+
943+ # Remove from polygon ROI points
944+ for poly in self .drawing_polygons :
945+ poly ['roi_points' ] = [
946+ pt for pt in poly ['roi_points' ]
947+ if (int (round (pt [0 ])), int (round (pt [1 ]))) not in roi_set
948+ ]
949+
950+ # Remove from grid ROI points
951+ for grid in self .grid_polygons :
952+ grid ['roi_points' ] = [
953+ pt for pt in grid ['roi_points' ]
954+ if (int (round (pt [0 ])), int (round (pt [1 ]))) not in roi_set
955+ ]
956+
957+ self .brush_deselect_mode = False
958+ self .brush_deselect_button .setChecked (False )
959+
960+ else :
961+ self .manual_points .extend (brush_rois )
924962
925963 self ._paint_mask = None
926964 self .update_selected_points ()
927965 self .update_brush_overlay ()
928966
929-
930967 def update_brush_overlay (self ):
931968 if not hasattr (self , 'brush_overlay' ):
932969 self .brush_overlay = ImageItem ()
@@ -939,6 +976,11 @@ def update_brush_overlay(self):
939976 self .brush_overlay .setZValue (2 )
940977 else :
941978 self .brush_overlay .clear ()
979+
980+ def activate_brush_deselect (self ):
981+ if self .brush_deselect_button .isChecked ():
982+ self .brush_deselect_mode = True
983+
942984 ################################################################################################
943985 # Automatic subset detection
944986 ################################################################################################
0 commit comments