@@ -931,6 +931,15 @@ def get_str(str_id):
931931 enabled = True ,
932932 )
933933
934+ convertToRect = action (
935+ get_str ("convertToRect" ),
936+ self .convertToRect ,
937+ "Ctrl+T" ,
938+ "edit" ,
939+ get_str ("convertToRectDetail" ),
940+ enabled = False ,
941+ )
942+
934943 settings_action = action (
935944 get_str ("settings" ),
936945 self .showSettingsDialog ,
@@ -1012,6 +1021,7 @@ def get_str(str_id):
10121021 delete = delete ,
10131022 edit = edit ,
10141023 focusAndZoom = focusAndZoom ,
1024+ convertToRect = convertToRect ,
10151025 copy = copy ,
10161026 saveRec = saveRec ,
10171027 singleRere = singleRere ,
@@ -1054,6 +1064,7 @@ def get_str(str_id):
10541064 createpoly ,
10551065 edit ,
10561066 focusAndZoom ,
1067+ convertToRect ,
10571068 copy ,
10581069 delete ,
10591070 singleRere ,
@@ -1078,6 +1089,7 @@ def get_str(str_id):
10781089 createpoly ,
10791090 edit ,
10801091 focusAndZoom ,
1092+ convertToRect ,
10811093 copy ,
10821094 delete ,
10831095 singleRere ,
@@ -1815,6 +1827,7 @@ def shapeSelectionChanged(self, selected_shapes):
18151827 self .actions .lock .setEnabled (n_selected )
18161828 self .actions .change_cls .setEnabled (n_selected )
18171829 self .actions .expand .setEnabled (n_selected )
1830+ self .actions .convertToRect .setEnabled (n_selected > 0 )
18181831
18191832 def addLabel (self , shape ):
18201833 shape .paintLabel = self .displayLabelOption .isChecked ()
@@ -3868,70 +3881,115 @@ def get_top_left(rect):
38683881 [max (p [1 ] for p in rect ) - min (p [1 ] for p in rect ) for rect in rectangles ]
38693882 ) / len (rectangles )
38703883 threshold = avg_height * row_height_threshold
3884+
3885+ # Keep track of original indices to handle duplicates correctly
38713886 indexed_rects = [(i , get_top_left (rect )) for i , rect in enumerate (rectangles )]
38723887 indexed_rects .sort (key = lambda x : x [1 ][1 ])
3888+
38733889 rows = []
38743890 current_row = []
3875- last_y = indexed_rects [0 ][1 ][1 ]
3876- for item in indexed_rects :
3877- i , (x , y ) = item
3878- if abs (y - last_y ) <= threshold :
3879- current_row .append (item )
3880- else :
3891+ if indexed_rects :
3892+ last_y = indexed_rects [0 ][1 ][1 ]
3893+ for item in indexed_rects :
3894+ i , (x , y ) = item
3895+ if abs (y - last_y ) <= threshold :
3896+ current_row .append (item )
3897+ else :
3898+ rows .append (current_row )
3899+ current_row = [item ]
3900+ last_y = y
3901+ if current_row :
38813902 rows .append (current_row )
3882- current_row = [item ]
3883- last_y = y
3884- if current_row :
3885- rows .append (current_row )
3886- sorted_rects = []
3903+
3904+ sorted_indices = []
38873905 for row in rows :
38883906 row .sort (key = lambda x : x [1 ][0 ])
3889- sorted_rects .extend ([rectangles [ i ] for i , _ in row ])
3890- return sorted_rects
3907+ sorted_indices .extend ([i for i , _ in row ])
3908+ return sorted_indices
38913909
38923910 def resortBoxPosition (self ):
3893- # get original elements
3894- items = []
3895- for i in range (self .BoxList .count ()):
3896- item = self .BoxList .item (i )
3897- items .append ({"text" : item .text (), "object" : item })
3898- # get coordinate points
3911+ # get coordinate points from shapes directly to be more reliable
38993912 rectangles = []
3900- for item in items :
3901- text = item ["text" ]
3902- try :
3903- rect = ast .literal_eval (text ) # 转为列表
3904- rectangles .append (rect )
3905- except (ValueError , SyntaxError ) as e :
3906- logger .error (f"Error parsing text: { text } " )
3907- continue
3908- # start resort
3909- sorted_rectangles = self .sort_rectangles (rectangles , row_height_threshold = 0.5 )
3910- # old_idx <--> new_idx
3911- index_map = []
3912- for sorted_rect in sorted_rectangles :
3913- for old_idx , rect in enumerate (rectangles ):
3914- if rect == sorted_rect :
3915- index_map .append (old_idx )
3916- break
3917- # resort BoxList labelList canvas.shapes
3918- items = [self .BoxList .takeItem (0 ) for _ in range (self .BoxList .count ())]
3913+ for shape in self .canvas .shapes :
3914+ rect = [[int (p .x ()), int (p .y ())] for p in shape .points ]
3915+ rectangles .append (rect )
3916+
3917+ if not rectangles :
3918+ return
3919+
3920+ # start resort - now returns indices
3921+ index_map = self .sort_rectangles (rectangles , row_height_threshold = 0.5 )
3922+
3923+ if len (index_map ) != len (self .canvas .shapes ):
3924+ logger .error ("Resort failed: index map size mismatch" )
3925+ return
3926+
3927+ # resort BoxList, labelList, and canvas.shapes
3928+ # Take all items out first
3929+ items_box = [self .BoxList .takeItem (0 ) for _ in range (self .BoxList .count ())]
39193930 items_label = [
39203931 self .labelList .takeItem (0 ) for _ in range (self .labelList .count ())
39213932 ]
3922- shapes = self .canvas .shapes
3933+ shapes = list (self .canvas .shapes )
3934+
39233935 self .canvas .shapes = []
3924- for new_idx in range (len (index_map )):
3925- old_idx = index_map [new_idx ]
3926- self .BoxList .insertItem (new_idx , items [old_idx ])
3936+ for new_idx , old_idx in enumerate (index_map ):
3937+ self .BoxList .insertItem (new_idx , items_box [old_idx ])
39273938 self .labelList .insertItem (new_idx , items_label [old_idx ])
3928- self .canvas .shapes .insert (new_idx , shapes [old_idx ])
3939+ self .canvas .shapes .append (shapes [old_idx ])
3940+
3941+ # Update internal indices and refresh UI
3942+ self .updateIndexList ()
3943+ for i , shape in enumerate (self .canvas .shapes ):
3944+ shape .idx = i
3945+
3946+ self .canvas .update ()
3947+ self .setDirty ()
3948+
39293949 QMessageBox .information (
39303950 self ,
39313951 "Information" ,
39323952 "resort success!" ,
39333953 )
39343954
3955+ def convertToRect (self ):
3956+ if not self .canvas .selectedShapes :
3957+ return
3958+
3959+ changed = False
3960+ for shape in self .canvas .selectedShapes :
3961+ if not shape .points :
3962+ continue
3963+
3964+ if len (shape .points ) == 4 :
3965+ p0 , p1 , p2 , p3 = shape .points
3966+ if (
3967+ p0 .x () == p3 .x ()
3968+ and p0 .y () == p1 .y ()
3969+ and p2 .x () == p1 .x ()
3970+ and p2 .y () == p3 .y ()
3971+ ):
3972+ continue
3973+
3974+ min_x = min (p .x () for p in shape .points )
3975+ max_x = max (p .x () for p in shape .points )
3976+ min_y = min (p .y () for p in shape .points )
3977+ max_y = max (p .y () for p in shape .points )
3978+
3979+ shape .points = [
3980+ QPointF (min_x , min_y ),
3981+ QPointF (max_x , min_y ),
3982+ QPointF (max_x , max_y ),
3983+ QPointF (min_x , max_y ),
3984+ ]
3985+ shape .close ()
3986+ changed = True
3987+
3988+ if changed :
3989+ self .updateBoxlist ()
3990+ self .setDirty ()
3991+ self .canvas .repaint ()
3992+
39353993
39363994def inverted (color ):
39373995 return QColor (* [255 - v for v in color .getRgb ()])
0 commit comments