lazylabel-gui 1.1.1__py3-none-any.whl → 1.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lazylabel/__init__.py +1 -1
- lazylabel/config/__init__.py +3 -3
- lazylabel/config/hotkeys.py +96 -58
- lazylabel/config/paths.py +8 -9
- lazylabel/config/settings.py +15 -16
- lazylabel/core/__init__.py +3 -3
- lazylabel/core/file_manager.py +49 -33
- lazylabel/core/model_manager.py +9 -11
- lazylabel/core/segment_manager.py +21 -22
- lazylabel/main.py +1 -0
- lazylabel/models/__init__.py +1 -1
- lazylabel/models/sam_model.py +24 -19
- lazylabel/ui/__init__.py +3 -3
- lazylabel/ui/control_panel.py +21 -19
- lazylabel/ui/editable_vertex.py +16 -3
- lazylabel/ui/hotkey_dialog.py +125 -93
- lazylabel/ui/hoverable_polygon_item.py +1 -2
- lazylabel/ui/main_window.py +290 -49
- lazylabel/ui/photo_viewer.py +4 -7
- lazylabel/ui/reorderable_class_table.py +2 -3
- lazylabel/ui/right_panel.py +15 -16
- lazylabel/ui/widgets/__init__.py +1 -1
- lazylabel/ui/widgets/adjustments_widget.py +22 -21
- lazylabel/ui/widgets/model_selection_widget.py +28 -21
- lazylabel/ui/widgets/settings_widget.py +35 -28
- lazylabel/ui/widgets/status_bar.py +2 -2
- lazylabel/utils/__init__.py +2 -2
- lazylabel/utils/custom_file_system_model.py +3 -2
- {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.3.dist-info}/METADATA +48 -2
- lazylabel_gui-1.1.3.dist-info/RECORD +37 -0
- lazylabel_gui-1.1.1.dist-info/RECORD +0 -37
- {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.3.dist-info}/WHEEL +0 -0
- {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.3.dist-info}/entry_points.txt +0 -0
- {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.3.dist-info}/licenses/LICENSE +0 -0
- {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.3.dist-info}/top_level.txt +0 -0
lazylabel/ui/main_window.py
CHANGED
@@ -1,48 +1,46 @@
|
|
1
1
|
"""Main application window."""
|
2
2
|
|
3
3
|
import os
|
4
|
-
|
4
|
+
|
5
5
|
import cv2
|
6
|
+
import numpy as np
|
7
|
+
from PyQt6.QtCore import QModelIndex, QPointF, Qt, QTimer, pyqtSignal
|
8
|
+
from PyQt6.QtGui import (
|
9
|
+
QBrush,
|
10
|
+
QColor,
|
11
|
+
QIcon,
|
12
|
+
QKeySequence,
|
13
|
+
QPen,
|
14
|
+
QPixmap,
|
15
|
+
QPolygonF,
|
16
|
+
QShortcut,
|
17
|
+
)
|
6
18
|
from PyQt6.QtWidgets import (
|
7
|
-
QMainWindow,
|
8
|
-
QWidget,
|
9
|
-
QHBoxLayout,
|
10
|
-
QVBoxLayout,
|
11
|
-
QFileDialog,
|
12
19
|
QApplication,
|
20
|
+
QDialog,
|
21
|
+
QFileDialog,
|
13
22
|
QGraphicsEllipseItem,
|
14
23
|
QGraphicsLineItem,
|
15
24
|
QGraphicsPolygonItem,
|
25
|
+
QMainWindow,
|
26
|
+
QSplitter,
|
16
27
|
QTableWidgetItem,
|
17
28
|
QTableWidgetSelectionRange,
|
18
|
-
|
19
|
-
|
20
|
-
QDialog,
|
21
|
-
)
|
22
|
-
from PyQt6.QtGui import (
|
23
|
-
QIcon,
|
24
|
-
QKeySequence,
|
25
|
-
QShortcut,
|
26
|
-
QPixmap,
|
27
|
-
QColor,
|
28
|
-
QPen,
|
29
|
-
QBrush,
|
30
|
-
QPolygonF,
|
31
|
-
QImage,
|
29
|
+
QVBoxLayout,
|
30
|
+
QWidget,
|
32
31
|
)
|
33
|
-
from PyQt6.QtCore import Qt, QTimer, QModelIndex, QPointF, pyqtSignal
|
34
32
|
|
33
|
+
from ..config import HotkeyManager, Paths, Settings
|
34
|
+
from ..core import FileManager, ModelManager, SegmentManager
|
35
|
+
from ..utils import CustomFileSystemModel, mask_to_pixmap
|
35
36
|
from .control_panel import ControlPanel
|
36
|
-
from .right_panel import RightPanel
|
37
|
-
from .photo_viewer import PhotoViewer
|
38
|
-
from .hoverable_polygon_item import HoverablePolygonItem
|
39
|
-
from .hoverable_pixelmap_item import HoverablePixmapItem
|
40
37
|
from .editable_vertex import EditableVertexItem
|
41
|
-
from .numeric_table_widget_item import NumericTableWidgetItem
|
42
|
-
from ..core import SegmentManager, ModelManager, FileManager
|
43
|
-
from ..config import Settings, Paths, HotkeyManager
|
44
|
-
from ..utils import CustomFileSystemModel, mask_to_pixmap
|
45
38
|
from .hotkey_dialog import HotkeyDialog
|
39
|
+
from .hoverable_pixelmap_item import HoverablePixmapItem
|
40
|
+
from .hoverable_polygon_item import HoverablePolygonItem
|
41
|
+
from .numeric_table_widget_item import NumericTableWidgetItem
|
42
|
+
from .photo_viewer import PhotoViewer
|
43
|
+
from .right_panel import RightPanel
|
46
44
|
from .widgets import StatusBar
|
47
45
|
|
48
46
|
|
@@ -126,6 +124,8 @@ class MainWindow(QMainWindow):
|
|
126
124
|
None,
|
127
125
|
{},
|
128
126
|
)
|
127
|
+
self.action_history = []
|
128
|
+
self.redo_history = []
|
129
129
|
|
130
130
|
# Update state flags to prevent recursion
|
131
131
|
self._updating_lists = False
|
@@ -311,6 +311,7 @@ class MainWindow(QMainWindow):
|
|
311
311
|
"delete_segments_alt": self._delete_selected_segments,
|
312
312
|
"merge_segments": self._handle_merge_press,
|
313
313
|
"undo": self._undo_last_action,
|
314
|
+
"redo": self._redo_last_action,
|
314
315
|
"select_all": lambda: self.right_panel.select_all_segments(),
|
315
316
|
"save_segment": self._handle_space_press,
|
316
317
|
"save_output": self._handle_enter_press,
|
@@ -346,6 +347,7 @@ class MainWindow(QMainWindow):
|
|
346
347
|
self.control_panel.set_annotation_size(
|
347
348
|
int(self.settings.annotation_size_multiplier * 10)
|
348
349
|
)
|
350
|
+
self.control_panel.set_join_threshold(self.settings.polygon_join_threshold)
|
349
351
|
# Set initial mode based on model availability
|
350
352
|
if self.model_manager.is_model_available():
|
351
353
|
self.set_sam_mode()
|
@@ -795,7 +797,7 @@ class MainWindow(QMainWindow):
|
|
795
797
|
def _display_all_segments(self):
|
796
798
|
"""Display all segments on the viewer."""
|
797
799
|
# Clear existing segment items
|
798
|
-
for
|
800
|
+
for _i, items in self.segment_items.items():
|
799
801
|
for item in items:
|
800
802
|
if item.scene():
|
801
803
|
self.viewer.scene().removeItem(item)
|
@@ -868,13 +870,21 @@ class MainWindow(QMainWindow):
|
|
868
870
|
self.positive_points, self.negative_points
|
869
871
|
)
|
870
872
|
if mask is not None:
|
871
|
-
|
873
|
+
new_segment = {
|
874
|
+
"mask": mask,
|
875
|
+
"type": "SAM",
|
876
|
+
"vertices": None,
|
877
|
+
}
|
878
|
+
self.segment_manager.add_segment(new_segment)
|
879
|
+
# Record the action for undo
|
880
|
+
self.action_history.append(
|
872
881
|
{
|
873
|
-
"
|
874
|
-
"
|
875
|
-
"vertices": None,
|
882
|
+
"type": "add_segment",
|
883
|
+
"segment_index": len(self.segment_manager.segments) - 1,
|
876
884
|
}
|
877
885
|
)
|
886
|
+
# Clear redo history when a new action is performed
|
887
|
+
self.redo_history.clear()
|
878
888
|
self.clear_all_points()
|
879
889
|
self._update_all_lists()
|
880
890
|
|
@@ -883,13 +893,22 @@ class MainWindow(QMainWindow):
|
|
883
893
|
if len(self.polygon_points) < 3:
|
884
894
|
return
|
885
895
|
|
886
|
-
|
896
|
+
new_segment = {
|
897
|
+
"vertices": list(self.polygon_points),
|
898
|
+
"type": "Polygon",
|
899
|
+
"mask": None,
|
900
|
+
}
|
901
|
+
self.segment_manager.add_segment(new_segment)
|
902
|
+
# Record the action for undo
|
903
|
+
self.action_history.append(
|
887
904
|
{
|
888
|
-
"
|
889
|
-
"
|
890
|
-
"mask": None,
|
905
|
+
"type": "add_segment",
|
906
|
+
"segment_index": len(self.segment_manager.segments) - 1,
|
891
907
|
}
|
892
908
|
)
|
909
|
+
# Clear redo history when a new action is performed
|
910
|
+
self.redo_history.clear()
|
911
|
+
|
893
912
|
self.polygon_points.clear()
|
894
913
|
self.clear_all_points()
|
895
914
|
self._update_all_lists()
|
@@ -980,9 +999,176 @@ class MainWindow(QMainWindow):
|
|
980
999
|
self.right_panel.clear_selections()
|
981
1000
|
|
982
1001
|
def _undo_last_action(self):
|
983
|
-
"""Undo last action."""
|
984
|
-
|
985
|
-
|
1002
|
+
"""Undo the last action recorded in the history."""
|
1003
|
+
if not self.action_history:
|
1004
|
+
self._show_notification("Nothing to undo.")
|
1005
|
+
return
|
1006
|
+
|
1007
|
+
last_action = self.action_history.pop()
|
1008
|
+
action_type = last_action.get("type")
|
1009
|
+
|
1010
|
+
# Save to redo history before undoing
|
1011
|
+
self.redo_history.append(last_action)
|
1012
|
+
|
1013
|
+
if action_type == "add_segment":
|
1014
|
+
segment_index = last_action.get("segment_index")
|
1015
|
+
if segment_index is not None and 0 <= segment_index < len(
|
1016
|
+
self.segment_manager.segments
|
1017
|
+
):
|
1018
|
+
# Store the segment data for redo
|
1019
|
+
last_action["segment_data"] = self.segment_manager.segments[
|
1020
|
+
segment_index
|
1021
|
+
].copy()
|
1022
|
+
|
1023
|
+
# Remove the segment that was added
|
1024
|
+
self.segment_manager.delete_segments([segment_index])
|
1025
|
+
self.right_panel.clear_selections() # Clear selection to prevent phantom highlights
|
1026
|
+
self._update_all_lists()
|
1027
|
+
self._show_notification("Undid: Add Segment")
|
1028
|
+
elif action_type == "add_point":
|
1029
|
+
point_type = last_action.get("point_type")
|
1030
|
+
point_item = last_action.get("point_item")
|
1031
|
+
point_list = (
|
1032
|
+
self.positive_points
|
1033
|
+
if point_type == "positive"
|
1034
|
+
else self.negative_points
|
1035
|
+
)
|
1036
|
+
if point_list:
|
1037
|
+
point_list.pop()
|
1038
|
+
if point_item in self.point_items:
|
1039
|
+
self.point_items.remove(point_item)
|
1040
|
+
self.viewer.scene().removeItem(point_item)
|
1041
|
+
self._update_segmentation()
|
1042
|
+
self._show_notification("Undid: Add Point")
|
1043
|
+
elif action_type == "add_polygon_point":
|
1044
|
+
dot_item = last_action.get("dot_item")
|
1045
|
+
if self.polygon_points:
|
1046
|
+
self.polygon_points.pop()
|
1047
|
+
if dot_item in self.polygon_preview_items:
|
1048
|
+
self.polygon_preview_items.remove(dot_item)
|
1049
|
+
self.viewer.scene().removeItem(dot_item)
|
1050
|
+
self._draw_polygon_preview()
|
1051
|
+
self._show_notification("Undid: Add Polygon Point")
|
1052
|
+
elif action_type == "move_polygon":
|
1053
|
+
initial_vertices = last_action.get("initial_vertices")
|
1054
|
+
for i, vertices in initial_vertices.items():
|
1055
|
+
self.segment_manager.segments[i]["vertices"] = vertices
|
1056
|
+
self._update_polygon_item(i)
|
1057
|
+
self._display_edit_handles()
|
1058
|
+
self._highlight_selected_segments()
|
1059
|
+
self._show_notification("Undid: Move Polygon")
|
1060
|
+
elif action_type == "move_vertex":
|
1061
|
+
segment_index = last_action.get("segment_index")
|
1062
|
+
vertex_index = last_action.get("vertex_index")
|
1063
|
+
old_pos = last_action.get("old_pos")
|
1064
|
+
self.segment_manager.segments[segment_index]["vertices"][vertex_index] = (
|
1065
|
+
old_pos
|
1066
|
+
)
|
1067
|
+
self._update_polygon_item(segment_index)
|
1068
|
+
self._display_edit_handles()
|
1069
|
+
self._highlight_selected_segments()
|
1070
|
+
self._show_notification("Undid: Move Vertex")
|
1071
|
+
|
1072
|
+
# Add more undo logic for other action types here in the future
|
1073
|
+
else:
|
1074
|
+
self._show_warning_notification(
|
1075
|
+
f"Undo for action '{action_type}' not implemented."
|
1076
|
+
)
|
1077
|
+
# Remove from redo history if we couldn't undo it
|
1078
|
+
self.redo_history.pop()
|
1079
|
+
|
1080
|
+
def _redo_last_action(self):
|
1081
|
+
"""Redo the last undone action."""
|
1082
|
+
if not self.redo_history:
|
1083
|
+
self._show_notification("Nothing to redo.")
|
1084
|
+
return
|
1085
|
+
|
1086
|
+
last_action = self.redo_history.pop()
|
1087
|
+
action_type = last_action.get("type")
|
1088
|
+
|
1089
|
+
# Add back to action history for potential future undo
|
1090
|
+
self.action_history.append(last_action)
|
1091
|
+
|
1092
|
+
if action_type == "add_segment":
|
1093
|
+
# Restore the segment that was removed
|
1094
|
+
if "segment_data" in last_action:
|
1095
|
+
segment_data = last_action["segment_data"]
|
1096
|
+
self.segment_manager.add_segment(segment_data)
|
1097
|
+
self._update_all_lists()
|
1098
|
+
self._show_notification("Redid: Add Segment")
|
1099
|
+
else:
|
1100
|
+
# If we don't have the segment data (shouldn't happen), we can't redo
|
1101
|
+
self._show_warning_notification("Cannot redo: Missing segment data")
|
1102
|
+
self.action_history.pop() # Remove from action history
|
1103
|
+
elif action_type == "add_point":
|
1104
|
+
point_type = last_action.get("point_type")
|
1105
|
+
point_coords = last_action.get("point_coords")
|
1106
|
+
if point_coords:
|
1107
|
+
pos = QPointF(point_coords[0], point_coords[1])
|
1108
|
+
self._add_point(pos, positive=(point_type == "positive"))
|
1109
|
+
self._update_segmentation()
|
1110
|
+
self._show_notification("Redid: Add Point")
|
1111
|
+
else:
|
1112
|
+
self._show_warning_notification(
|
1113
|
+
"Cannot redo: Missing point coordinates"
|
1114
|
+
)
|
1115
|
+
self.action_history.pop()
|
1116
|
+
elif action_type == "add_polygon_point":
|
1117
|
+
point_coords = last_action.get("point_coords")
|
1118
|
+
if point_coords:
|
1119
|
+
self._handle_polygon_click(point_coords)
|
1120
|
+
self._show_notification("Redid: Add Polygon Point")
|
1121
|
+
else:
|
1122
|
+
self._show_warning_notification(
|
1123
|
+
"Cannot redo: Missing polygon point coordinates"
|
1124
|
+
)
|
1125
|
+
self.action_history.pop()
|
1126
|
+
elif action_type == "move_polygon":
|
1127
|
+
final_vertices = last_action.get("final_vertices")
|
1128
|
+
if final_vertices:
|
1129
|
+
for i, vertices in final_vertices.items():
|
1130
|
+
if i < len(self.segment_manager.segments):
|
1131
|
+
self.segment_manager.segments[i]["vertices"] = [
|
1132
|
+
QPointF(v.x(), v.y()) for v in vertices
|
1133
|
+
]
|
1134
|
+
self._update_polygon_item(i)
|
1135
|
+
self._display_edit_handles()
|
1136
|
+
self._highlight_selected_segments()
|
1137
|
+
self._show_notification("Redid: Move Polygon")
|
1138
|
+
else:
|
1139
|
+
self._show_warning_notification("Cannot redo: Missing final vertices")
|
1140
|
+
self.action_history.pop()
|
1141
|
+
elif action_type == "move_vertex":
|
1142
|
+
segment_index = last_action.get("segment_index")
|
1143
|
+
vertex_index = last_action.get("vertex_index")
|
1144
|
+
new_pos = last_action.get("new_pos")
|
1145
|
+
if (
|
1146
|
+
segment_index is not None
|
1147
|
+
and vertex_index is not None
|
1148
|
+
and new_pos is not None
|
1149
|
+
):
|
1150
|
+
if segment_index < len(self.segment_manager.segments):
|
1151
|
+
self.segment_manager.segments[segment_index]["vertices"][
|
1152
|
+
vertex_index
|
1153
|
+
] = new_pos
|
1154
|
+
self._update_polygon_item(segment_index)
|
1155
|
+
self._display_edit_handles()
|
1156
|
+
self._highlight_selected_segments()
|
1157
|
+
self._show_notification("Redid: Move Vertex")
|
1158
|
+
else:
|
1159
|
+
self._show_warning_notification(
|
1160
|
+
"Cannot redo: Segment no longer exists"
|
1161
|
+
)
|
1162
|
+
self.action_history.pop()
|
1163
|
+
else:
|
1164
|
+
self._show_warning_notification("Cannot redo: Missing vertex data")
|
1165
|
+
self.action_history.pop()
|
1166
|
+
else:
|
1167
|
+
self._show_warning_notification(
|
1168
|
+
f"Redo for action '{action_type}' not implemented."
|
1169
|
+
)
|
1170
|
+
# Remove from action history if we couldn't redo it
|
1171
|
+
self.action_history.pop()
|
986
1172
|
|
987
1173
|
def clear_all_points(self):
|
988
1174
|
"""Clear all temporary points."""
|
@@ -1091,6 +1277,8 @@ class MainWindow(QMainWindow):
|
|
1091
1277
|
self.viewer.scene().removeItem(item)
|
1092
1278
|
self.segment_items.clear()
|
1093
1279
|
self.highlight_items.clear()
|
1280
|
+
self.action_history.clear()
|
1281
|
+
self.redo_history.clear()
|
1094
1282
|
|
1095
1283
|
def _scene_mouse_press(self, event):
|
1096
1284
|
"""Handle mouse press events in the scene."""
|
@@ -1113,7 +1301,9 @@ class MainWindow(QMainWindow):
|
|
1113
1301
|
self.drag_start_pos = pos
|
1114
1302
|
selected_indices = self.right_panel.get_selected_segment_indices()
|
1115
1303
|
self.drag_initial_vertices = {
|
1116
|
-
i:
|
1304
|
+
i: [
|
1305
|
+
QPointF(p) for p in self.segment_manager.segments[i]["vertices"]
|
1306
|
+
]
|
1117
1307
|
for i in selected_indices
|
1118
1308
|
if self.segment_manager.segments[i].get("type") == "Polygon"
|
1119
1309
|
}
|
@@ -1148,9 +1338,8 @@ class MainWindow(QMainWindow):
|
|
1148
1338
|
elif self.mode == "polygon":
|
1149
1339
|
if event.button() == Qt.MouseButton.LeftButton:
|
1150
1340
|
self._handle_polygon_click(pos)
|
1151
|
-
elif self.mode == "selection":
|
1152
|
-
|
1153
|
-
self._handle_segment_selection_click(pos)
|
1341
|
+
elif self.mode == "selection" and event.button() == Qt.MouseButton.LeftButton:
|
1342
|
+
self._handle_segment_selection_click(pos)
|
1154
1343
|
|
1155
1344
|
def _scene_mouse_move(self, event):
|
1156
1345
|
"""Handle mouse move events in the scene."""
|
@@ -1171,6 +1360,22 @@ class MainWindow(QMainWindow):
|
|
1171
1360
|
def _scene_mouse_release(self, event):
|
1172
1361
|
"""Handle mouse release events in the scene."""
|
1173
1362
|
if self.mode == "edit" and self.is_dragging_polygon:
|
1363
|
+
# Record the action for undo
|
1364
|
+
final_vertices = {
|
1365
|
+
i: list(self.segment_manager.segments[i]["vertices"])
|
1366
|
+
for i in self.drag_initial_vertices
|
1367
|
+
}
|
1368
|
+
self.action_history.append(
|
1369
|
+
{
|
1370
|
+
"type": "move_polygon",
|
1371
|
+
"initial_vertices": {
|
1372
|
+
k: list(v) for k, v in self.drag_initial_vertices.items()
|
1373
|
+
},
|
1374
|
+
"final_vertices": final_vertices,
|
1375
|
+
}
|
1376
|
+
)
|
1377
|
+
# Clear redo history when a new action is performed
|
1378
|
+
self.redo_history.clear()
|
1174
1379
|
self.is_dragging_polygon = False
|
1175
1380
|
self.drag_initial_vertices.clear()
|
1176
1381
|
event.accept()
|
@@ -1202,6 +1407,18 @@ class MainWindow(QMainWindow):
|
|
1202
1407
|
self.viewer.scene().addItem(point_item)
|
1203
1408
|
self.point_items.append(point_item)
|
1204
1409
|
|
1410
|
+
# Record the action for undo
|
1411
|
+
self.action_history.append(
|
1412
|
+
{
|
1413
|
+
"type": "add_point",
|
1414
|
+
"point_type": "positive" if positive else "negative",
|
1415
|
+
"point_coords": [int(pos.x()), int(pos.y())],
|
1416
|
+
"point_item": point_item,
|
1417
|
+
}
|
1418
|
+
)
|
1419
|
+
# Clear redo history when a new action is performed
|
1420
|
+
self.redo_history.clear()
|
1421
|
+
|
1205
1422
|
def _update_segmentation(self):
|
1206
1423
|
"""Update SAM segmentation preview."""
|
1207
1424
|
if hasattr(self, "preview_mask_item") and self.preview_mask_item:
|
@@ -1250,6 +1467,17 @@ class MainWindow(QMainWindow):
|
|
1250
1467
|
# Update polygon preview
|
1251
1468
|
self._draw_polygon_preview()
|
1252
1469
|
|
1470
|
+
# Record the action for undo
|
1471
|
+
self.action_history.append(
|
1472
|
+
{
|
1473
|
+
"type": "add_polygon_point",
|
1474
|
+
"point_coords": pos,
|
1475
|
+
"dot_item": dot,
|
1476
|
+
}
|
1477
|
+
)
|
1478
|
+
# Clear redo history when a new action is performed
|
1479
|
+
self.redo_history.clear()
|
1480
|
+
|
1253
1481
|
def _draw_polygon_preview(self):
|
1254
1482
|
"""Draw polygon preview lines and fill."""
|
1255
1483
|
# Remove old preview lines and polygons (keep dots)
|
@@ -1371,13 +1599,26 @@ class MainWindow(QMainWindow):
|
|
1371
1599
|
self.viewer.scene().removeItem(h)
|
1372
1600
|
self.edit_handles = []
|
1373
1601
|
|
1374
|
-
def update_vertex_pos(self, segment_index, vertex_index, new_pos):
|
1602
|
+
def update_vertex_pos(self, segment_index, vertex_index, new_pos, record_undo=True):
|
1375
1603
|
"""Update the position of a vertex in a polygon segment."""
|
1376
1604
|
seg = self.segment_manager.segments[segment_index]
|
1377
1605
|
if seg.get("type") == "Polygon":
|
1378
|
-
seg["vertices"][
|
1379
|
-
|
1380
|
-
|
1606
|
+
old_pos = seg["vertices"][vertex_index]
|
1607
|
+
if record_undo:
|
1608
|
+
self.action_history.append(
|
1609
|
+
{
|
1610
|
+
"type": "move_vertex",
|
1611
|
+
"segment_index": segment_index,
|
1612
|
+
"vertex_index": vertex_index,
|
1613
|
+
"old_pos": old_pos,
|
1614
|
+
"new_pos": new_pos,
|
1615
|
+
}
|
1616
|
+
)
|
1617
|
+
# Clear redo history when a new action is performed
|
1618
|
+
self.redo_history.clear()
|
1619
|
+
seg["vertices"][vertex_index] = (
|
1620
|
+
new_pos # new_pos is already the correct scene coordinate
|
1621
|
+
)
|
1381
1622
|
self._update_polygon_item(segment_index)
|
1382
1623
|
self._highlight_selected_segments() # Keep the highlight in sync with the new shape
|
1383
1624
|
|
lazylabel/ui/photo_viewer.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
from PyQt6.QtCore import
|
2
|
-
from PyQt6.QtGui import
|
3
|
-
from PyQt6.QtWidgets import
|
1
|
+
from PyQt6.QtCore import QRectF, Qt
|
2
|
+
from PyQt6.QtGui import QCursor, QPixmap
|
3
|
+
from PyQt6.QtWidgets import QGraphicsPixmapItem, QGraphicsScene, QGraphicsView
|
4
4
|
|
5
5
|
|
6
6
|
class PhotoViewer(QGraphicsView):
|
@@ -47,8 +47,5 @@ class PhotoViewer(QGraphicsView):
|
|
47
47
|
|
48
48
|
def wheelEvent(self, event):
|
49
49
|
if not self._pixmap_item.pixmap().isNull():
|
50
|
-
if event.angleDelta().y() > 0
|
51
|
-
factor = 1.25
|
52
|
-
else:
|
53
|
-
factor = 0.8
|
50
|
+
factor = 1.25 if event.angleDelta().y() > 0 else 0.8
|
54
51
|
self.scale(factor, factor)
|
@@ -1,5 +1,4 @@
|
|
1
|
-
from PyQt6.QtWidgets import
|
2
|
-
from PyQt6.QtCore import Qt
|
1
|
+
from PyQt6.QtWidgets import QAbstractItemView, QTableWidget
|
3
2
|
|
4
3
|
|
5
4
|
class ReorderableClassTable(QTableWidget):
|
@@ -31,7 +30,7 @@ class ReorderableClassTable(QTableWidget):
|
|
31
30
|
drop_row = self.rowCount()
|
32
31
|
|
33
32
|
selected_rows = sorted(
|
34
|
-
|
33
|
+
{index.row() for index in self.selectedIndexes()}, reverse=True
|
35
34
|
)
|
36
35
|
|
37
36
|
dragged_rows_data = []
|
lazylabel/ui/right_panel.py
CHANGED
@@ -1,23 +1,20 @@
|
|
1
1
|
"""Right panel with file explorer and segment management."""
|
2
2
|
|
3
|
+
from PyQt6.QtCore import Qt, pyqtSignal
|
3
4
|
from PyQt6.QtWidgets import (
|
4
|
-
|
5
|
-
QVBoxLayout,
|
6
|
-
QPushButton,
|
7
|
-
QLabel,
|
5
|
+
QComboBox,
|
8
6
|
QHBoxLayout,
|
7
|
+
QHeaderView,
|
8
|
+
QLabel,
|
9
|
+
QPushButton,
|
10
|
+
QSplitter,
|
9
11
|
QTableWidget,
|
10
12
|
QTreeView,
|
11
|
-
|
12
|
-
|
13
|
-
QSpacerItem,
|
14
|
-
QHeaderView,
|
13
|
+
QVBoxLayout,
|
14
|
+
QWidget,
|
15
15
|
)
|
16
|
-
from PyQt6.QtCore import Qt, pyqtSignal
|
17
|
-
from PyQt6.QtGui import QBrush, QColor
|
18
16
|
|
19
17
|
from .reorderable_class_table import ReorderableClassTable
|
20
|
-
from .numeric_table_widget_item import NumericTableWidgetItem
|
21
18
|
|
22
19
|
|
23
20
|
class RightPanel(QWidget):
|
@@ -191,10 +188,12 @@ class RightPanel(QWidget):
|
|
191
188
|
|
192
189
|
def mouseDoubleClickEvent(self, event):
|
193
190
|
"""Handle double-click to expand collapsed panel."""
|
194
|
-
if
|
195
|
-
|
196
|
-
|
197
|
-
|
191
|
+
if (
|
192
|
+
self.width() < 50
|
193
|
+
and self.parent()
|
194
|
+
and hasattr(self.parent(), "_expand_right_panel")
|
195
|
+
):
|
196
|
+
self.parent()._expand_right_panel()
|
198
197
|
super().mouseDoubleClickEvent(event)
|
199
198
|
|
200
199
|
def _handle_class_alias_change(self, item):
|
@@ -269,7 +268,7 @@ class RightPanel(QWidget):
|
|
269
268
|
def get_selected_segment_indices(self):
|
270
269
|
"""Get indices of selected segments."""
|
271
270
|
selected_items = self.segment_table.selectedItems()
|
272
|
-
selected_rows = sorted(
|
271
|
+
selected_rows = sorted({item.row() for item in selected_items})
|
273
272
|
return [
|
274
273
|
self.segment_table.item(row, 0).data(Qt.ItemDataRole.UserRole)
|
275
274
|
for row in selected_rows
|
lazylabel/ui/widgets/__init__.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
"""UI widgets for LazyLabel."""
|
2
2
|
|
3
|
+
from .adjustments_widget import AdjustmentsWidget
|
3
4
|
from .model_selection_widget import ModelSelectionWidget
|
4
5
|
from .settings_widget import SettingsWidget
|
5
|
-
from .adjustments_widget import AdjustmentsWidget
|
6
6
|
from .status_bar import StatusBar
|
7
7
|
|
8
8
|
__all__ = ["ModelSelectionWidget", "SettingsWidget", "AdjustmentsWidget", "StatusBar"]
|