PyImageLabeling 1.0.0__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.
- PyImageLabeling/__init__.py +22 -0
- PyImageLabeling/config.json +289 -0
- PyImageLabeling/controller/Controller.py +25 -0
- PyImageLabeling/controller/Events.py +147 -0
- PyImageLabeling/controller/FileEvents.py +69 -0
- PyImageLabeling/controller/ImageEvents.py +32 -0
- PyImageLabeling/controller/LabelEvents.py +219 -0
- PyImageLabeling/controller/LabelingEvents.py +123 -0
- PyImageLabeling/controller/settings/ContourFillinSetting.py +93 -0
- PyImageLabeling/controller/settings/CoutourFillingApplyCancel.py +37 -0
- PyImageLabeling/controller/settings/EraserSetting.py +73 -0
- PyImageLabeling/controller/settings/LabelSetting.py +91 -0
- PyImageLabeling/controller/settings/MagicPenSetting.py +125 -0
- PyImageLabeling/controller/settings/OpacitySetting.py +66 -0
- PyImageLabeling/controller/settings/PaintBrushSetting.py +66 -0
- PyImageLabeling/icons/apply.png +0 -0
- PyImageLabeling/icons/asterisk-green.png +0 -0
- PyImageLabeling/icons/asterisk-red.png +0 -0
- PyImageLabeling/icons/back.png +0 -0
- PyImageLabeling/icons/border.png +0 -0
- PyImageLabeling/icons/cancel.png +0 -0
- PyImageLabeling/icons/cleaner.png +0 -0
- PyImageLabeling/icons/close.png +0 -0
- PyImageLabeling/icons/down.png +0 -0
- PyImageLabeling/icons/ellipse.png +0 -0
- PyImageLabeling/icons/eraser.png +0 -0
- PyImageLabeling/icons/filling.png +0 -0
- PyImageLabeling/icons/logoMAIA.png +0 -0
- PyImageLabeling/icons/magic.png +0 -0
- PyImageLabeling/icons/maia.png +0 -0
- PyImageLabeling/icons/maia1.png +0 -0
- PyImageLabeling/icons/maia3.ico +0 -0
- PyImageLabeling/icons/maia_icon.png +0 -0
- PyImageLabeling/icons/move.png +0 -0
- PyImageLabeling/icons/opacity.png +0 -0
- PyImageLabeling/icons/open_image.png +0 -0
- PyImageLabeling/icons/open_layer.png +0 -0
- PyImageLabeling/icons/paint.png +0 -0
- PyImageLabeling/icons/plus.png +0 -0
- PyImageLabeling/icons/polygon.png +0 -0
- PyImageLabeling/icons/rectangle.png +0 -0
- PyImageLabeling/icons/reset.png +0 -0
- PyImageLabeling/icons/save.png +0 -0
- PyImageLabeling/icons/setting.png +0 -0
- PyImageLabeling/icons/transparency.png:Zone.Identifier +4 -0
- PyImageLabeling/icons/up.png +0 -0
- PyImageLabeling/icons/visibility.png +0 -0
- PyImageLabeling/icons/zoom_minus.png +0 -0
- PyImageLabeling/icons/zoom_plus.png +0 -0
- PyImageLabeling/model/Core.py +795 -0
- PyImageLabeling/model/File/Files.py +166 -0
- PyImageLabeling/model/File/NextImage.py +36 -0
- PyImageLabeling/model/File/PreviousImage.py +19 -0
- PyImageLabeling/model/Image/MoveImage.py +32 -0
- PyImageLabeling/model/Image/ResetMoveZoomImage.py +16 -0
- PyImageLabeling/model/Image/ZoomMinus.py +25 -0
- PyImageLabeling/model/Image/ZoomPlus.py +16 -0
- PyImageLabeling/model/Labeling/ClearAll.py +22 -0
- PyImageLabeling/model/Labeling/ContourFilling.py +135 -0
- PyImageLabeling/model/Labeling/Ellipse.py +350 -0
- PyImageLabeling/model/Labeling/Eraser.py +131 -0
- PyImageLabeling/model/Labeling/MagicPen.py +131 -0
- PyImageLabeling/model/Labeling/PaintBrush.py +207 -0
- PyImageLabeling/model/Labeling/Polygon.py +279 -0
- PyImageLabeling/model/Labeling/Rectangle.py +248 -0
- PyImageLabeling/model/Labeling/Undo.py +12 -0
- PyImageLabeling/model/Model.py +40 -0
- PyImageLabeling/model/Utils.py +40 -0
- PyImageLabeling/old_version/label_rectangle_properties.json +6 -0
- PyImageLabeling/old_version/main.py +2073 -0
- PyImageLabeling/old_version/models/EraseSettingsDialog.py +51 -0
- PyImageLabeling/old_version/models/LabeledRectangle.py +80 -0
- PyImageLabeling/old_version/models/MagicSettingsDialog.py +119 -0
- PyImageLabeling/old_version/models/OverlayOpacityDialog.py +63 -0
- PyImageLabeling/old_version/models/PaintSettingsDialog.py +289 -0
- PyImageLabeling/old_version/models/PointItem.py +66 -0
- PyImageLabeling/old_version/models/ProcessWorker.py +52 -0
- PyImageLabeling/old_version/models/ZoomableGraphicsView.py +1214 -0
- PyImageLabeling/old_version/models/tools/ContourTool.py +279 -0
- PyImageLabeling/old_version/models/tools/EraserTool.py +290 -0
- PyImageLabeling/old_version/models/tools/MagicPenTool.py +199 -0
- PyImageLabeling/old_version/models/tools/OverlayTool.py +179 -0
- PyImageLabeling/old_version/models/tools/PaintTool.py +68 -0
- PyImageLabeling/old_version/models/tools/PolygonTool.py +786 -0
- PyImageLabeling/old_version/models/tools/RectangleTool.py +1036 -0
- PyImageLabeling/parameters.json +1 -0
- PyImageLabeling/style.css +611 -0
- PyImageLabeling/view/Builder.py +333 -0
- PyImageLabeling/view/QBackgroundItem.py +30 -0
- PyImageLabeling/view/QWidgets.py +10 -0
- PyImageLabeling/view/View.py +226 -0
- PyImageLabeling/view/ZoomableGraphicsView.py +91 -0
- PyImageLabeling/view/__init__.py +0 -0
- pyimagelabeling-1.0.0.dist-info/METADATA +55 -0
- pyimagelabeling-1.0.0.dist-info/RECORD +99 -0
- pyimagelabeling-1.0.0.dist-info/WHEEL +5 -0
- pyimagelabeling-1.0.0.dist-info/licenses/LICENCE +22 -0
- pyimagelabeling-1.0.0.dist-info/top_level.txt +2 -0
- pypi/publish_pypi.py +18 -0
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
from PyQt6.QtCore import Qt, QRectF, QPointF, QLineF
|
|
2
|
+
from PyQt6.QtGui import QPen, QColor, QCursor, QImage, QPainter, QPolygonF
|
|
3
|
+
from PyQt6.QtWidgets import (QInputDialog, QDialog, QVBoxLayout, QComboBox, QLabel,
|
|
4
|
+
QDialogButtonBox, QMenu, QColorDialog, QSpinBox, QHBoxLayout,
|
|
5
|
+
QPushButton, QGroupBox, QFormLayout, QGraphicsItem, QGraphicsEllipseItem, QLineEdit)
|
|
6
|
+
import os
|
|
7
|
+
import time
|
|
8
|
+
import math
|
|
9
|
+
|
|
10
|
+
class LabelPolygonPropertiesDialog(QDialog):
|
|
11
|
+
def __init__(self, parent=None):
|
|
12
|
+
super().__init__(parent)
|
|
13
|
+
self.setWindowTitle("Label Properties")
|
|
14
|
+
self.setMinimumWidth(300)
|
|
15
|
+
self.setStyleSheet("""
|
|
16
|
+
QDialog {
|
|
17
|
+
background-color: #000000;
|
|
18
|
+
color: white;
|
|
19
|
+
border: 1px solid #444444;
|
|
20
|
+
}
|
|
21
|
+
QLabel {
|
|
22
|
+
color: white;
|
|
23
|
+
font-size: 14px;
|
|
24
|
+
background-color: #000000;
|
|
25
|
+
border: none;
|
|
26
|
+
}
|
|
27
|
+
""")
|
|
28
|
+
|
|
29
|
+
layout = QVBoxLayout()
|
|
30
|
+
|
|
31
|
+
self.label_name = QLabel("Label: ")
|
|
32
|
+
self.label_color = QLabel("Color: ")
|
|
33
|
+
self.label_thickness= QLabel("thickness: ")
|
|
34
|
+
|
|
35
|
+
layout.addWidget(self.label_name)
|
|
36
|
+
layout.addWidget(self.label_color)
|
|
37
|
+
layout.addWidget(self.label_thickness)
|
|
38
|
+
|
|
39
|
+
self.setLayout(layout)
|
|
40
|
+
|
|
41
|
+
def update_properties(self, label, color, thickness):
|
|
42
|
+
self.label_name.setText(f"Label: {label}")
|
|
43
|
+
self.label_color.setText(f"Color: {color.name()}")
|
|
44
|
+
self.label_thickness.setText(f"thickness: {thickness}")
|
|
45
|
+
|
|
46
|
+
class DraggableVertex(QGraphicsEllipseItem):
|
|
47
|
+
"""Custom draggable vertex point for polygon editing"""
|
|
48
|
+
def __init__(self, x, y, vertex_index, polygon_tool, polygon_item):
|
|
49
|
+
super().__init__(x - 4, y - 4, 8, 8)
|
|
50
|
+
self.vertex_index = vertex_index
|
|
51
|
+
self.polygon_tool = polygon_tool
|
|
52
|
+
self.polygon_item = polygon_item
|
|
53
|
+
|
|
54
|
+
# Visual appearance
|
|
55
|
+
self.setPen(QPen(QColor(255, 255, 0), 2)) # Yellow outline
|
|
56
|
+
self.setBrush(QColor(255, 0, 0)) # Red fill
|
|
57
|
+
|
|
58
|
+
# Make it draggable and send position changes
|
|
59
|
+
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
|
|
60
|
+
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True)
|
|
61
|
+
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemSendsScenePositionChanges, True)
|
|
62
|
+
self.setZValue(1000) # Ensure points are on top
|
|
63
|
+
|
|
64
|
+
def itemChange(self, change, value):
|
|
65
|
+
"""Handle item changes, particularly position changes"""
|
|
66
|
+
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
|
67
|
+
# Update the polygon when this vertex moves
|
|
68
|
+
if self.polygon_tool and hasattr(self.polygon_tool, 'update_polygon_from_vertex'):
|
|
69
|
+
# Get the center of the item in scene coordinates
|
|
70
|
+
new_pos = self.mapToScene(value)
|
|
71
|
+
self.polygon_tool.update_polygon_from_vertex(self.vertex_index, new_pos)
|
|
72
|
+
return value # Return the original value to ensure it is set correctly
|
|
73
|
+
|
|
74
|
+
return super().itemChange(change, value)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class CustomizePolygonDialog(QDialog):
|
|
78
|
+
"""Dialog for customizing polygon appearance"""
|
|
79
|
+
def __init__(self, current_color=None, current_thickness=2, properties_manager=None, parent=None):
|
|
80
|
+
super().__init__(parent)
|
|
81
|
+
self.properties_manager = properties_manager
|
|
82
|
+
self.setWindowTitle("Customize Polygon")
|
|
83
|
+
self.setModal(True)
|
|
84
|
+
self.resize(300, 300) # Increased height for label selection
|
|
85
|
+
|
|
86
|
+
# CONSISTENT DARK THEME STYLESHEET
|
|
87
|
+
self.setStyleSheet("""
|
|
88
|
+
QDialog {
|
|
89
|
+
background-color: #000000;
|
|
90
|
+
color: white;
|
|
91
|
+
font-size: 14px;
|
|
92
|
+
border: 1px solid #444444;
|
|
93
|
+
}
|
|
94
|
+
QLabel {
|
|
95
|
+
color: white;
|
|
96
|
+
background-color: transparent;
|
|
97
|
+
font-size: 12px;
|
|
98
|
+
}
|
|
99
|
+
QSpinBox {
|
|
100
|
+
background-color: #111111;
|
|
101
|
+
color: white;
|
|
102
|
+
border: 1px solid #555555;
|
|
103
|
+
padding: 5px;
|
|
104
|
+
}
|
|
105
|
+
QSpinBox:focus {
|
|
106
|
+
border: 1px solid #666666;
|
|
107
|
+
}
|
|
108
|
+
QPushButton {
|
|
109
|
+
background-color: #111111;
|
|
110
|
+
color: white;
|
|
111
|
+
border: 1px solid #666666;
|
|
112
|
+
border-radius: 5px;
|
|
113
|
+
padding: 6px 12px;
|
|
114
|
+
}
|
|
115
|
+
QPushButton:hover {
|
|
116
|
+
background-color: #222222;
|
|
117
|
+
}
|
|
118
|
+
QPushButton:pressed {
|
|
119
|
+
background-color: #333333;
|
|
120
|
+
}
|
|
121
|
+
QGroupBox {
|
|
122
|
+
color: white;
|
|
123
|
+
font-weight: bold;
|
|
124
|
+
border: 1px solid #444444;
|
|
125
|
+
margin-top: 10px;
|
|
126
|
+
padding-top: 10px;
|
|
127
|
+
background-color: transparent;
|
|
128
|
+
}
|
|
129
|
+
QGroupBox::title {
|
|
130
|
+
subcontrol-origin: margin;
|
|
131
|
+
left: 10px;
|
|
132
|
+
padding: 0 5px 0 5px;
|
|
133
|
+
color: white;
|
|
134
|
+
}
|
|
135
|
+
QDialogButtonBox QPushButton {
|
|
136
|
+
background-color: #111111;
|
|
137
|
+
color: white;
|
|
138
|
+
border: 1px solid #666666;
|
|
139
|
+
min-width: 80px;
|
|
140
|
+
padding: 6px 12px;
|
|
141
|
+
}
|
|
142
|
+
QDialogButtonBox QPushButton:hover {
|
|
143
|
+
background-color: #222222;
|
|
144
|
+
}
|
|
145
|
+
QComboBox {
|
|
146
|
+
background-color: #111111;
|
|
147
|
+
color: white;
|
|
148
|
+
border: 1px solid #555555;
|
|
149
|
+
padding: 5px;
|
|
150
|
+
}
|
|
151
|
+
QComboBox QAbstractItemView {
|
|
152
|
+
background-color: #000000;
|
|
153
|
+
color: white;
|
|
154
|
+
selection-background-color: #222222;
|
|
155
|
+
}
|
|
156
|
+
""")
|
|
157
|
+
|
|
158
|
+
layout = QVBoxLayout(self)
|
|
159
|
+
|
|
160
|
+
# Label selection group
|
|
161
|
+
if self.properties_manager:
|
|
162
|
+
label_group = QGroupBox("Label")
|
|
163
|
+
label_layout = QFormLayout()
|
|
164
|
+
|
|
165
|
+
self.label_combo = QComboBox()
|
|
166
|
+
self.label_combo.addItem("") # Empty option
|
|
167
|
+
self.label_combo.addItems(self.properties_manager.get_all_labels())
|
|
168
|
+
self.label_combo.setEditable(True)
|
|
169
|
+
self.label_combo.currentTextChanged.connect(self.update_properties_from_label)
|
|
170
|
+
|
|
171
|
+
label_layout.addRow("Label:", self.label_combo)
|
|
172
|
+
label_group.setLayout(label_layout)
|
|
173
|
+
layout.addWidget(label_group)
|
|
174
|
+
|
|
175
|
+
# Color selection group
|
|
176
|
+
color_group = QGroupBox("Polygon Color")
|
|
177
|
+
color_layout = QFormLayout()
|
|
178
|
+
|
|
179
|
+
# Color selection
|
|
180
|
+
color_selection_layout = QHBoxLayout()
|
|
181
|
+
self.color_button = QPushButton("Choose Color")
|
|
182
|
+
self.color_button.clicked.connect(self.choose_color)
|
|
183
|
+
|
|
184
|
+
# Set initial color
|
|
185
|
+
self.selected_color = current_color if current_color else QColor(0, 255, 0) # Default green
|
|
186
|
+
self.update_color_button()
|
|
187
|
+
|
|
188
|
+
color_selection_layout.addWidget(self.color_button)
|
|
189
|
+
color_selection_layout.addStretch()
|
|
190
|
+
|
|
191
|
+
color_layout.addRow("Color:", color_selection_layout)
|
|
192
|
+
color_group.setLayout(color_layout)
|
|
193
|
+
layout.addWidget(color_group)
|
|
194
|
+
|
|
195
|
+
# Thickness selection group
|
|
196
|
+
thickness_group = QGroupBox("Polygon Thickness")
|
|
197
|
+
thickness_layout = QFormLayout()
|
|
198
|
+
|
|
199
|
+
self.thickness_spinbox = QSpinBox()
|
|
200
|
+
self.thickness_spinbox.setMinimum(1)
|
|
201
|
+
self.thickness_spinbox.setMaximum(10)
|
|
202
|
+
self.thickness_spinbox.setValue(current_thickness)
|
|
203
|
+
self.thickness_spinbox.setSuffix(" px")
|
|
204
|
+
|
|
205
|
+
thickness_layout.addRow("Thickness:", self.thickness_spinbox)
|
|
206
|
+
thickness_group.setLayout(thickness_layout)
|
|
207
|
+
layout.addWidget(thickness_group)
|
|
208
|
+
|
|
209
|
+
# Dialog buttons
|
|
210
|
+
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
|
211
|
+
button_box.accepted.connect(self.accept)
|
|
212
|
+
button_box.rejected.connect(self.reject)
|
|
213
|
+
|
|
214
|
+
layout.addWidget(button_box)
|
|
215
|
+
|
|
216
|
+
def update_properties_from_label(self, label):
|
|
217
|
+
"""Update color and thickness based on the selected label"""
|
|
218
|
+
if not label or not self.properties_manager:
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
props = self.properties_manager.get_label_property(label)
|
|
222
|
+
if props:
|
|
223
|
+
self.selected_color = props['color']
|
|
224
|
+
self.update_color_button()
|
|
225
|
+
self.thickness_spinbox.setValue(props['thickness'])
|
|
226
|
+
|
|
227
|
+
# UPDATE PARENT'S DEFAULT VALUES IF PARENT IS POLYGON TOOL
|
|
228
|
+
if hasattr(self.parent(), 'default_polygon_color'):
|
|
229
|
+
self.parent().default_polygon_color = props['color']
|
|
230
|
+
self.parent().default_polygon_thickness = props['thickness']
|
|
231
|
+
|
|
232
|
+
def get_settings(self):
|
|
233
|
+
"""Return the selected color, thickness, and label"""
|
|
234
|
+
color = self.selected_color
|
|
235
|
+
thickness = self.thickness_spinbox.value()
|
|
236
|
+
label = self.label_combo.currentText().strip() if hasattr(self, 'label_combo') else None
|
|
237
|
+
return color, thickness, label
|
|
238
|
+
|
|
239
|
+
def choose_color(self):
|
|
240
|
+
"""Open color dialog to choose polygon color"""
|
|
241
|
+
color_dialog = QColorDialog(self.selected_color, self)
|
|
242
|
+
color_dialog.setStyleSheet("""
|
|
243
|
+
QColorDialog {
|
|
244
|
+
background-color: #000000;
|
|
245
|
+
color: white;
|
|
246
|
+
}
|
|
247
|
+
QColorDialog QLabel {
|
|
248
|
+
color: white;
|
|
249
|
+
background-color: transparent;
|
|
250
|
+
}
|
|
251
|
+
QColorDialog QPushButton {
|
|
252
|
+
background-color: #111111;
|
|
253
|
+
color: white;
|
|
254
|
+
border: 1px solid #666666;
|
|
255
|
+
border-radius: 5px;
|
|
256
|
+
padding: 6px 12px;
|
|
257
|
+
}
|
|
258
|
+
QColorDialog QPushButton:hover {
|
|
259
|
+
background-color: #222222;
|
|
260
|
+
}
|
|
261
|
+
QColorDialog QSpinBox {
|
|
262
|
+
background-color: #111111;
|
|
263
|
+
color: white;
|
|
264
|
+
border: 1px solid #555555;
|
|
265
|
+
padding: 5px;
|
|
266
|
+
}
|
|
267
|
+
QColorDialog QLineEdit {
|
|
268
|
+
background-color: #222222;
|
|
269
|
+
color: white;
|
|
270
|
+
border: 1px solid #555555;
|
|
271
|
+
padding: 5px;
|
|
272
|
+
}
|
|
273
|
+
""")
|
|
274
|
+
|
|
275
|
+
if color_dialog.exec() == QDialog.DialogCode.Accepted:
|
|
276
|
+
self.selected_color = color_dialog.currentColor()
|
|
277
|
+
self.update_color_button()
|
|
278
|
+
|
|
279
|
+
def update_color_button(self):
|
|
280
|
+
"""Update the color button to show the selected color"""
|
|
281
|
+
color_name = self.selected_color.name()
|
|
282
|
+
self.color_button.setText(f"Color: {color_name}")
|
|
283
|
+
text_color = 'white' if self.selected_color.lightness() < 128 else 'black'
|
|
284
|
+
self.color_button.setStyleSheet(f"""
|
|
285
|
+
QPushButton {{
|
|
286
|
+
background-color: {color_name};
|
|
287
|
+
color: {text_color};
|
|
288
|
+
border: 1px solid #555555;
|
|
289
|
+
padding: 6px 12px;
|
|
290
|
+
border-radius: 5px;
|
|
291
|
+
font-weight: bold;
|
|
292
|
+
}}
|
|
293
|
+
QPushButton:hover {{
|
|
294
|
+
border: 2px solid #777777;
|
|
295
|
+
background-color: {color_name};
|
|
296
|
+
}}
|
|
297
|
+
""")
|
|
298
|
+
|
|
299
|
+
def get_settings(self):
|
|
300
|
+
"""Return the selected color, thickness, and label"""
|
|
301
|
+
color = self.selected_color
|
|
302
|
+
thickness = self.thickness_spinbox.value()
|
|
303
|
+
label = self.label_combo.currentText().strip() if hasattr(self, 'label_combo') else None
|
|
304
|
+
return color, thickness, label
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class PolygonTool:
|
|
308
|
+
last_used_label = None
|
|
309
|
+
def __init__(self):
|
|
310
|
+
# Default polygon appearance settings
|
|
311
|
+
self.default_polygon_color = QColor(0, 255, 0) # Green
|
|
312
|
+
self.default_polygon_thickness = 2
|
|
313
|
+
def add_polygon_point(self, point):
|
|
314
|
+
"""Add a point to the current polygon being created"""
|
|
315
|
+
self.current_polygon_points.append(point)
|
|
316
|
+
|
|
317
|
+
# Draw a small circle at the point
|
|
318
|
+
circle_radius = 3
|
|
319
|
+
circle = self.scene.addEllipse(
|
|
320
|
+
point.x() - circle_radius, point.y() - circle_radius,
|
|
321
|
+
circle_radius * 2, circle_radius * 2,
|
|
322
|
+
QPen(self.default_polygon_color, 1)
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# Connect to previous point with a line
|
|
326
|
+
if len(self.current_polygon_points) > 1:
|
|
327
|
+
prev_point = self.current_polygon_points[-2]
|
|
328
|
+
line = self.scene.addLine(
|
|
329
|
+
prev_point.x(), prev_point.y(),
|
|
330
|
+
point.x(), point.y(),
|
|
331
|
+
QPen(self.default_polygon_color, self.default_polygon_thickness)
|
|
332
|
+
)
|
|
333
|
+
self.current_polygon_lines.append(line)
|
|
334
|
+
|
|
335
|
+
self.current_polygon_lines.append(circle)
|
|
336
|
+
|
|
337
|
+
def update_polygon_preview(self, current_pos):
|
|
338
|
+
"""Update the preview line showing where the next polygon edge will be"""
|
|
339
|
+
if not self.current_polygon_points:
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
# Remove existing preview line
|
|
343
|
+
if hasattr(self, 'preview_line') and self.preview_line:
|
|
344
|
+
self.scene.removeItem(self.preview_line)
|
|
345
|
+
|
|
346
|
+
# Create new preview line from last point to current mouse position
|
|
347
|
+
last_point = self.current_polygon_points[-1]
|
|
348
|
+
self.preview_line = self.scene.addLine(
|
|
349
|
+
last_point.x(), last_point.y(),
|
|
350
|
+
current_pos.x(), current_pos.y(),
|
|
351
|
+
QPen(self.default_polygon_color, 1, Qt.PenStyle.DashLine)
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# If we have at least 3 points, also show a line to the first point
|
|
355
|
+
if len(self.current_polygon_points) >= 3:
|
|
356
|
+
if hasattr(self, 'close_preview_line') and self.close_preview_line:
|
|
357
|
+
self.scene.removeItem(self.close_preview_line)
|
|
358
|
+
|
|
359
|
+
first_point = self.current_polygon_points[0]
|
|
360
|
+
distance = math.sqrt((current_pos.x() - first_point.x())**2 +
|
|
361
|
+
(current_pos.y() - first_point.y())**2)
|
|
362
|
+
|
|
363
|
+
# Show close preview line if mouse is close to first point
|
|
364
|
+
if distance <= self.close_distance_threshold:
|
|
365
|
+
self.close_preview_line = self.scene.addLine(
|
|
366
|
+
current_pos.x(), current_pos.y(),
|
|
367
|
+
first_point.x(), first_point.y(),
|
|
368
|
+
QPen(QColor(255, 255, 0), 2) # Yellow line to indicate close
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def close_polygon(self):
|
|
372
|
+
"""Close the current polygon and create a polygon item"""
|
|
373
|
+
if len(self.current_polygon_points) < 3:
|
|
374
|
+
return
|
|
375
|
+
|
|
376
|
+
# Remove preview lines
|
|
377
|
+
if hasattr(self, 'preview_line') and self.preview_line:
|
|
378
|
+
self.scene.removeItem(self.preview_line)
|
|
379
|
+
self.preview_line = None
|
|
380
|
+
if hasattr(self, 'close_preview_line') and self.close_preview_line:
|
|
381
|
+
self.scene.removeItem(self.close_preview_line)
|
|
382
|
+
self.close_preview_line = None
|
|
383
|
+
|
|
384
|
+
# Remove temporary drawing elements
|
|
385
|
+
for item in self.current_polygon_lines:
|
|
386
|
+
self.scene.removeItem(item)
|
|
387
|
+
|
|
388
|
+
# Create the final polygon
|
|
389
|
+
polygon = QPolygonF(self.current_polygon_points)
|
|
390
|
+
|
|
391
|
+
# Use last used label's properties if available
|
|
392
|
+
if PolygonTool.last_used_label and hasattr(self, 'label_properties_manager'):
|
|
393
|
+
props = self.label_properties_manager.get_label_property(PolygonTool.last_used_label)
|
|
394
|
+
if props:
|
|
395
|
+
color = props['color']
|
|
396
|
+
thickness = props['thickness']
|
|
397
|
+
else:
|
|
398
|
+
color = self.default_polygon_color
|
|
399
|
+
thickness = self.default_polygon_thickness
|
|
400
|
+
else:
|
|
401
|
+
color = self.default_polygon_color
|
|
402
|
+
thickness = self.default_polygon_thickness
|
|
403
|
+
|
|
404
|
+
dialog = CustomizePolygonDialog(color, thickness, self.label_properties_manager if hasattr(self, 'label_properties_manager') else None, self)
|
|
405
|
+
dialog.setWindowTitle("Customize Polygon")
|
|
406
|
+
|
|
407
|
+
# Set the label combo box to the last used label if available
|
|
408
|
+
if PolygonTool.last_used_label:
|
|
409
|
+
dialog.label_combo.setCurrentText(PolygonTool.last_used_label)
|
|
410
|
+
|
|
411
|
+
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
412
|
+
new_color, new_thickness, new_label = dialog.get_settings()
|
|
413
|
+
|
|
414
|
+
# UPDATE DEFAULT VALUES TO MATCH SELECTED VALUES
|
|
415
|
+
self.default_polygon_color = new_color
|
|
416
|
+
self.default_polygon_thickness = new_thickness
|
|
417
|
+
|
|
418
|
+
pen = QPen(new_color, new_thickness)
|
|
419
|
+
polygon_item = self.scene.addPolygon(polygon, pen)
|
|
420
|
+
|
|
421
|
+
# Store custom settings on the polygon
|
|
422
|
+
polygon_item.custom_color = new_color
|
|
423
|
+
polygon_item.custom_thickness = new_thickness
|
|
424
|
+
polygon_item.label_name = new_label # Store the label name
|
|
425
|
+
polygon_item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True)
|
|
426
|
+
polygon_item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
|
|
427
|
+
|
|
428
|
+
# Save the label properties if a new label was created
|
|
429
|
+
if new_label and hasattr(self, 'label_properties_manager'):
|
|
430
|
+
self.label_properties_manager.add_label_property(new_label, new_color, new_thickness)
|
|
431
|
+
|
|
432
|
+
# Add to polygon items list
|
|
433
|
+
self.polygon_items.append(polygon_item)
|
|
434
|
+
|
|
435
|
+
# Update the last used label
|
|
436
|
+
if new_label:
|
|
437
|
+
PolygonTool.last_used_label = new_label
|
|
438
|
+
|
|
439
|
+
# Reset polygon creation state
|
|
440
|
+
self.current_polygon_points = []
|
|
441
|
+
self.current_polygon_lines = []
|
|
442
|
+
|
|
443
|
+
return polygon_item if 'polygon_item' in locals() else None
|
|
444
|
+
|
|
445
|
+
def cancel_polygon_creation(self):
|
|
446
|
+
"""Cancel the current polygon creation"""
|
|
447
|
+
# Remove preview lines
|
|
448
|
+
if hasattr(self, 'preview_line') and self.preview_line:
|
|
449
|
+
self.scene.removeItem(self.preview_line)
|
|
450
|
+
self.preview_line = None
|
|
451
|
+
if hasattr(self, 'close_preview_line') and self.close_preview_line:
|
|
452
|
+
self.scene.removeItem(self.close_preview_line)
|
|
453
|
+
self.close_preview_line = None
|
|
454
|
+
|
|
455
|
+
# Remove temporary drawing elements
|
|
456
|
+
for item in self.current_polygon_lines:
|
|
457
|
+
self.scene.removeItem(item)
|
|
458
|
+
|
|
459
|
+
# Reset state
|
|
460
|
+
self.current_polygon_points = []
|
|
461
|
+
self.current_polygon_lines = []
|
|
462
|
+
|
|
463
|
+
print("Polygon creation cancelled")
|
|
464
|
+
|
|
465
|
+
def show_polygon_context_menu(self, labeled_polygon, global_pos):
|
|
466
|
+
"""Show context menu for polygon manipulation"""
|
|
467
|
+
context_menu = QMenu(self)
|
|
468
|
+
|
|
469
|
+
# CONSISTENT DARK THEME FOR CONTEXT MENU
|
|
470
|
+
context_menu.setStyleSheet("""
|
|
471
|
+
QMenu {
|
|
472
|
+
background-color: #000000;
|
|
473
|
+
color: white;
|
|
474
|
+
border: 1px solid #444444;
|
|
475
|
+
font-size: 14px;
|
|
476
|
+
}
|
|
477
|
+
QMenu::item {
|
|
478
|
+
padding: 8px 20px;
|
|
479
|
+
background-color: transparent;
|
|
480
|
+
}
|
|
481
|
+
QMenu::item:selected {
|
|
482
|
+
background-color: #222222;
|
|
483
|
+
}
|
|
484
|
+
QMenu::separator {
|
|
485
|
+
height: 1px;
|
|
486
|
+
background-color: #444444;
|
|
487
|
+
margin: 2px 0px;
|
|
488
|
+
}
|
|
489
|
+
""")
|
|
490
|
+
|
|
491
|
+
custom_action = context_menu.addAction("Custom")
|
|
492
|
+
modify_action = context_menu.addAction("Modify")
|
|
493
|
+
context_menu.addSeparator()
|
|
494
|
+
delete_action = context_menu.addAction("Delete")
|
|
495
|
+
|
|
496
|
+
# Execute the menu and handle the selected action
|
|
497
|
+
action = context_menu.exec(global_pos)
|
|
498
|
+
|
|
499
|
+
if action == custom_action:
|
|
500
|
+
self.customize_polygon(labeled_polygon)
|
|
501
|
+
elif action == modify_action:
|
|
502
|
+
self.start_polygon_edit_mode(labeled_polygon)
|
|
503
|
+
elif action == delete_action:
|
|
504
|
+
self.delete_polygon(labeled_polygon)
|
|
505
|
+
|
|
506
|
+
def customize_polygon(self, labeled_polygon):
|
|
507
|
+
"""Show dialog to customize polygon appearance"""
|
|
508
|
+
current_pen = labeled_polygon.pen()
|
|
509
|
+
current_color = current_pen.color()
|
|
510
|
+
current_thickness = current_pen.width()
|
|
511
|
+
current_label = getattr(labeled_polygon, 'label_name', '')
|
|
512
|
+
|
|
513
|
+
dialog = CustomizePolygonDialog(
|
|
514
|
+
current_color,
|
|
515
|
+
current_thickness,
|
|
516
|
+
self.label_properties_manager if hasattr(self, 'label_properties_manager') else None,
|
|
517
|
+
self
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
if hasattr(dialog, 'label_combo'):
|
|
521
|
+
dialog.label_combo.setCurrentText(current_label)
|
|
522
|
+
|
|
523
|
+
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
524
|
+
new_color, new_thickness, new_label = dialog.get_settings()
|
|
525
|
+
new_pen = QPen(new_color, new_thickness)
|
|
526
|
+
|
|
527
|
+
self.default_polygon_color = new_color
|
|
528
|
+
self.default_polygon_thickness = new_thickness
|
|
529
|
+
|
|
530
|
+
if not hasattr(labeled_polygon, 'original_pen'):
|
|
531
|
+
labeled_polygon.original_pen = current_pen
|
|
532
|
+
|
|
533
|
+
labeled_polygon.setPen(new_pen)
|
|
534
|
+
labeled_polygon.custom_color = new_color
|
|
535
|
+
labeled_polygon.custom_thickness = new_thickness
|
|
536
|
+
|
|
537
|
+
# Update label if changed
|
|
538
|
+
if new_label:
|
|
539
|
+
labeled_polygon.label_name = new_label
|
|
540
|
+
PolygonTool.last_used_label = new_label
|
|
541
|
+
|
|
542
|
+
# Update label properties if this is a new label
|
|
543
|
+
if hasattr(self, 'label_properties_manager') and new_label:
|
|
544
|
+
self.label_properties_manager.add_label_property(
|
|
545
|
+
new_label, new_color, new_thickness
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
self.scene.update()
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def delete_polygon(self, labeled_polygon):
|
|
552
|
+
"""Delete the selected polygon"""
|
|
553
|
+
if labeled_polygon in self.polygon_items:
|
|
554
|
+
self.polygon_items.remove(labeled_polygon)
|
|
555
|
+
if labeled_polygon in self.labeled_polygons:
|
|
556
|
+
self.labeled_polygons.remove(labeled_polygon)
|
|
557
|
+
self.scene.removeItem(labeled_polygon)
|
|
558
|
+
|
|
559
|
+
def save_polygon_to_jpeg(self, labeled_polygon):
|
|
560
|
+
"""Save the polygon selection area to a JPEG file"""
|
|
561
|
+
# Get the bounding rectangle of the polygon
|
|
562
|
+
bounding_rect = labeled_polygon.boundingRect()
|
|
563
|
+
|
|
564
|
+
# Create a QImage with the size of the bounding rectangle
|
|
565
|
+
image = QImage(int(bounding_rect.width()), int(bounding_rect.height()), QImage.Format.Format_RGB888)
|
|
566
|
+
image.fill(Qt.GlobalColor.white)
|
|
567
|
+
|
|
568
|
+
# Create a painter for the image
|
|
569
|
+
painter = QPainter(image)
|
|
570
|
+
|
|
571
|
+
# Render the scene portion
|
|
572
|
+
source_rect = bounding_rect
|
|
573
|
+
target_rect = QRectF(0, 0, bounding_rect.width(), bounding_rect.height())
|
|
574
|
+
self.scene.render(painter, target_rect, source_rect)
|
|
575
|
+
|
|
576
|
+
# Draw the polygon on the image
|
|
577
|
+
pen = QPen(labeled_polygon.pen().color(), labeled_polygon.pen().width())
|
|
578
|
+
painter.setPen(pen)
|
|
579
|
+
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
580
|
+
polygon = labeled_polygon.polygon()
|
|
581
|
+
polygon_translated = QPolygonF()
|
|
582
|
+
for point in polygon:
|
|
583
|
+
polygon_translated.append(QPointF(point.x() - bounding_rect.x(), point.y() - bounding_rect.y()))
|
|
584
|
+
painter.drawPolygon(polygon_translated)
|
|
585
|
+
|
|
586
|
+
painter.end()
|
|
587
|
+
|
|
588
|
+
# Save the image
|
|
589
|
+
save_dir = os.path.join(os.getcwd(), 'save', 'polygons')
|
|
590
|
+
if not os.path.exists(save_dir):
|
|
591
|
+
os.makedirs(save_dir)
|
|
592
|
+
|
|
593
|
+
file_name = f"polygon_{int(bounding_rect.x())}_{int(bounding_rect.y())}.jpeg"
|
|
594
|
+
file_path = os.path.join(save_dir, file_name)
|
|
595
|
+
|
|
596
|
+
if image.save(file_path, "JPEG"):
|
|
597
|
+
print(f"Polygon selection saved successfully at {file_path}")
|
|
598
|
+
else:
|
|
599
|
+
print("Failed to save the polygon selection.")
|
|
600
|
+
|
|
601
|
+
def toggle_polygon_mode(self, enabled):
|
|
602
|
+
"""Toggle polygon selection mode on/off"""
|
|
603
|
+
self.polygon_mode = enabled
|
|
604
|
+
|
|
605
|
+
if not enabled:
|
|
606
|
+
# Cancel any current polygon creation
|
|
607
|
+
self.cancel_polygon_creation()
|
|
608
|
+
|
|
609
|
+
def clear_polygons(self):
|
|
610
|
+
"""Remove all polygon selections from the scene"""
|
|
611
|
+
if hasattr(self, 'polygon_items') and self.polygon_items:
|
|
612
|
+
for item in self.polygon_items:
|
|
613
|
+
if item in self.scene.items():
|
|
614
|
+
self.scene.removeItem(item)
|
|
615
|
+
self.polygon_items = []
|
|
616
|
+
|
|
617
|
+
if hasattr(self, 'labeled_polygons') and self.labeled_polygons:
|
|
618
|
+
for item in self.labeled_polygons:
|
|
619
|
+
if item in self.scene.items():
|
|
620
|
+
self.scene.removeItem(item)
|
|
621
|
+
self.labeled_polygons = []
|
|
622
|
+
|
|
623
|
+
# Cancel any current polygon creation
|
|
624
|
+
self.cancel_polygon_creation()
|
|
625
|
+
|
|
626
|
+
self.scene.update()
|
|
627
|
+
|
|
628
|
+
def set_default_polygon_style(self):
|
|
629
|
+
"""Show dialog to set default polygon style"""
|
|
630
|
+
dialog = CustomizePolygonDialog(self.default_polygon_color,
|
|
631
|
+
self.default_polygon_thickness, self)
|
|
632
|
+
dialog.setWindowTitle("Set Default Polygon Style")
|
|
633
|
+
|
|
634
|
+
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
635
|
+
self.default_polygon_color, self.default_polygon_thickness = dialog.get_settings()
|
|
636
|
+
print(f"Default polygon style updated: Color={self.default_polygon_color.name()}, Thickness={self.default_polygon_thickness}px")
|
|
637
|
+
|
|
638
|
+
def save_entire_image_with_polygons(self):
|
|
639
|
+
"""Save the entire image with rectangles and polygons drawn on it."""
|
|
640
|
+
if not hasattr(self, 'base_pixmap') or self.base_pixmap is None:
|
|
641
|
+
print("No base image to save.")
|
|
642
|
+
return False
|
|
643
|
+
|
|
644
|
+
# Create a QImage with the same size as the base pixmap
|
|
645
|
+
image = QImage(self.base_pixmap.size(), QImage.Format.Format_ARGB32)
|
|
646
|
+
image.fill(Qt.GlobalColor.transparent) # Start with a transparent background
|
|
647
|
+
|
|
648
|
+
# Create a painter for the image
|
|
649
|
+
painter = QPainter(image)
|
|
650
|
+
|
|
651
|
+
# Draw the base image
|
|
652
|
+
painter.drawPixmap(0, 0, self.base_pixmap)
|
|
653
|
+
|
|
654
|
+
# Draw all polygons with their custom colors and thickness
|
|
655
|
+
if hasattr(self, 'polygon_items'):
|
|
656
|
+
for polygon_item in self.polygon_items:
|
|
657
|
+
polygon = polygon_item.polygon()
|
|
658
|
+
pen = polygon_item.pen()
|
|
659
|
+
painter.setPen(pen) # Use the polygon's custom pen
|
|
660
|
+
painter.drawPolygon(polygon)
|
|
661
|
+
|
|
662
|
+
if hasattr(self, 'labeled_rectangles'):
|
|
663
|
+
for rect_item in self.labeled_rectangles:
|
|
664
|
+
rect = rect_item.rect()
|
|
665
|
+
pen = rect_item.pen()
|
|
666
|
+
painter.setPen(pen) # Use the rectangle's custom pen
|
|
667
|
+
painter.drawRect(rect)
|
|
668
|
+
|
|
669
|
+
if hasattr(self, 'rectangle_items'):
|
|
670
|
+
for rect_item in self.rectangle_items:
|
|
671
|
+
rect = rect_item.rect()
|
|
672
|
+
pen = rect_item.pen()
|
|
673
|
+
painter.setPen(pen) # Use the rectangle's custom pen
|
|
674
|
+
painter.drawRect(rect)
|
|
675
|
+
|
|
676
|
+
painter.end()
|
|
677
|
+
|
|
678
|
+
# Create the folder to save the image
|
|
679
|
+
save_dir = os.path.join(os.getcwd(), 'save')
|
|
680
|
+
if not os.path.exists(save_dir):
|
|
681
|
+
os.makedirs(save_dir)
|
|
682
|
+
|
|
683
|
+
# Generate a unique file name
|
|
684
|
+
file_name = f"entire_image_with_shapes_{int(time.time())}.png"
|
|
685
|
+
file_path = os.path.join(save_dir, file_name)
|
|
686
|
+
|
|
687
|
+
# Save the image as PNG
|
|
688
|
+
if image.save(file_path, "PNG"):
|
|
689
|
+
print(f"Image saved successfully at {file_path}")
|
|
690
|
+
return True
|
|
691
|
+
else:
|
|
692
|
+
print("Failed to save the image.")
|
|
693
|
+
return False
|
|
694
|
+
|
|
695
|
+
def start_polygon_edit_mode(self, polygon_item):
|
|
696
|
+
"""Start editing mode for the polygon with draggable vertices"""
|
|
697
|
+
if hasattr(self, 'polygon_edit_mode') and self.polygon_edit_mode:
|
|
698
|
+
self.end_polygon_edit_mode()
|
|
699
|
+
|
|
700
|
+
self.polygon_edit_mode = True
|
|
701
|
+
self.editing_polygon = polygon_item
|
|
702
|
+
self.polygon_point_items = []
|
|
703
|
+
|
|
704
|
+
# Create draggable vertex points for each vertex
|
|
705
|
+
polygon = polygon_item.polygon()
|
|
706
|
+
for i, point in enumerate(polygon):
|
|
707
|
+
vertex_item = DraggableVertex(point.x(), point.y(), i, self, polygon_item)
|
|
708
|
+
self.scene.addItem(vertex_item)
|
|
709
|
+
self.polygon_point_items.append(vertex_item)
|
|
710
|
+
|
|
711
|
+
# Make the polygon itself non-movable during edit
|
|
712
|
+
polygon_item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
|
|
713
|
+
|
|
714
|
+
def update_polygon_from_vertex(self, vertex_index, new_position):
|
|
715
|
+
"""Update the polygon shape when a vertex is moved"""
|
|
716
|
+
if not self.polygon_edit_mode or not self.editing_polygon:
|
|
717
|
+
return
|
|
718
|
+
|
|
719
|
+
# Get current polygon
|
|
720
|
+
current_polygon = self.editing_polygon.polygon()
|
|
721
|
+
|
|
722
|
+
# Update the specific vertex
|
|
723
|
+
if 0 <= vertex_index < len(current_polygon):
|
|
724
|
+
# Create new polygon with updated vertex
|
|
725
|
+
new_points = []
|
|
726
|
+
for i, point in enumerate(current_polygon):
|
|
727
|
+
if i == vertex_index:
|
|
728
|
+
new_points.append(new_position)
|
|
729
|
+
else:
|
|
730
|
+
new_points.append(point)
|
|
731
|
+
|
|
732
|
+
# Update the polygon
|
|
733
|
+
new_polygon = QPolygonF(new_points)
|
|
734
|
+
self.editing_polygon.setPolygon(new_polygon)
|
|
735
|
+
|
|
736
|
+
def update_polygon_from_points(self):
|
|
737
|
+
"""Update the polygon shape based on current point positions"""
|
|
738
|
+
if not hasattr(self, 'polygon_edit_mode') or not self.polygon_edit_mode or not self.editing_polygon:
|
|
739
|
+
return
|
|
740
|
+
|
|
741
|
+
# Get current positions of all points
|
|
742
|
+
new_points = []
|
|
743
|
+
for point_item in self.polygon_point_items:
|
|
744
|
+
# Get the center of the point item in scene coordinates
|
|
745
|
+
point_center = point_item.boundingRect().center()
|
|
746
|
+
scene_center = point_item.mapToScene(point_center)
|
|
747
|
+
new_points.append(QPointF(scene_center.x(), scene_center.y()))
|
|
748
|
+
|
|
749
|
+
# Update the polygon
|
|
750
|
+
new_polygon = QPolygonF(new_points)
|
|
751
|
+
self.editing_polygon.setPolygon(new_polygon)
|
|
752
|
+
|
|
753
|
+
def end_polygon_edit_mode(self):
|
|
754
|
+
"""End polygon editing mode"""
|
|
755
|
+
if not hasattr(self, 'polygon_edit_mode') or not self.polygon_edit_mode:
|
|
756
|
+
return
|
|
757
|
+
|
|
758
|
+
# Remove all point items
|
|
759
|
+
for point_item in self.polygon_point_items:
|
|
760
|
+
self.scene.removeItem(point_item)
|
|
761
|
+
self.polygon_point_items = []
|
|
762
|
+
|
|
763
|
+
# Make the polygon movable again
|
|
764
|
+
if self.editing_polygon:
|
|
765
|
+
self.editing_polygon.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
|
|
766
|
+
|
|
767
|
+
self.polygon_edit_mode = False
|
|
768
|
+
self.editing_polygon = None
|
|
769
|
+
self.dragging_polygon_point = False
|
|
770
|
+
self.dragged_point_item = None
|
|
771
|
+
|
|
772
|
+
print("Polygon edit mode ended.")
|
|
773
|
+
|
|
774
|
+
# Add this method to handle mouse events during polygon editing:
|
|
775
|
+
def handle_polygon_edit_mouse_move(self, event):
|
|
776
|
+
"""Handle mouse move events during polygon editing"""
|
|
777
|
+
if self.polygon_edit_mode:
|
|
778
|
+
self.update_polygon_from_points()
|
|
779
|
+
|
|
780
|
+
def handle_polygon_edit_mouse_press(self, event):
|
|
781
|
+
"""Handle mouse press events during polygon editing"""
|
|
782
|
+
if self.polygon_edit_mode and event.button() == Qt.MouseButton.RightButton:
|
|
783
|
+
self.end_polygon_edit_mode()
|
|
784
|
+
return True # Event handled
|
|
785
|
+
return False # Event not handled
|
|
786
|
+
|