lazylabel-gui 1.0.5__tar.gz → 1.0.6__tar.gz
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_gui-1.0.5 → lazylabel_gui-1.0.6}/PKG-INFO +1 -1
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/pyproject.toml +1 -1
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel/main.py +76 -165
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel_gui.egg-info/PKG-INFO +1 -1
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/LICENSE +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/README.md +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/setup.cfg +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel/controls.py +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel/custom_file_system_model.py +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel/editable_vertex.py +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel/hoverable_polygon_item.py +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel/numeric_table_widget_item.py +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel/photo_viewer.py +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel/reorderable_class_table.py +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel/sam_model.py +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel/utils.py +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel_gui.egg-info/SOURCES.txt +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel_gui.egg-info/dependency_links.txt +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel_gui.egg-info/entry_points.txt +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel_gui.egg-info/requires.txt +0 -0
- {lazylabel_gui-1.0.5 → lazylabel_gui-1.0.6}/src/lazylabel_gui.egg-info/top_level.txt +0 -0
@@ -34,9 +34,7 @@ class MainWindow(QMainWindow):
|
|
34
34
|
super().__init__()
|
35
35
|
self.setWindowTitle("LazyLabel by DNC")
|
36
36
|
|
37
|
-
icon_path = os.path.join(
|
38
|
-
os.path.dirname(__file__), "demo_pictures", "logo2.png"
|
39
|
-
)
|
37
|
+
icon_path = os.path.join(os.path.dirname(__file__), "demo_pictures", "logo2.png")
|
40
38
|
if os.path.exists(icon_path):
|
41
39
|
self.setWindowIcon(QIcon(icon_path))
|
42
40
|
|
@@ -76,9 +74,7 @@ class MainWindow(QMainWindow):
|
|
76
74
|
central_widget.setLayout(main_layout)
|
77
75
|
self.setCentralWidget(central_widget)
|
78
76
|
|
79
|
-
self.control_panel.device_label.setText(
|
80
|
-
f"Device: {str(self.sam_model.device).upper()}"
|
81
|
-
)
|
77
|
+
self.control_panel.device_label.setText(f"Device: {str(self.sam_model.device).upper()}")
|
82
78
|
self.setup_connections()
|
83
79
|
self.set_sam_mode()
|
84
80
|
|
@@ -93,26 +89,16 @@ class MainWindow(QMainWindow):
|
|
93
89
|
|
94
90
|
self.right_panel.btn_open_folder.clicked.connect(self.open_folder_dialog)
|
95
91
|
self.right_panel.file_tree.doubleClicked.connect(self.load_selected_image)
|
96
|
-
self.right_panel.btn_merge_selection.clicked.connect(
|
97
|
-
|
98
|
-
)
|
99
|
-
self.right_panel.btn_delete_selection.clicked.connect(
|
100
|
-
self.delete_selected_segments
|
101
|
-
)
|
102
|
-
self.right_panel.segment_table.itemSelectionChanged.connect(
|
103
|
-
self.highlight_selected_segments
|
104
|
-
)
|
92
|
+
self.right_panel.btn_merge_selection.clicked.connect(self.assign_selected_to_class)
|
93
|
+
self.right_panel.btn_delete_selection.clicked.connect(self.delete_selected_segments)
|
94
|
+
self.right_panel.segment_table.itemSelectionChanged.connect(self.highlight_selected_segments)
|
105
95
|
self.right_panel.segment_table.itemChanged.connect(self.handle_class_id_change)
|
106
96
|
self.right_panel.btn_reassign_classes.clicked.connect(self.reassign_class_ids)
|
107
|
-
self.right_panel.class_filter_combo.currentIndexChanged.connect(
|
108
|
-
self.update_segment_table
|
109
|
-
)
|
97
|
+
self.right_panel.class_filter_combo.currentIndexChanged.connect(self.update_segment_table)
|
110
98
|
|
111
99
|
self.control_panel.btn_sam_mode.clicked.connect(self.set_sam_mode)
|
112
100
|
self.control_panel.btn_polygon_mode.clicked.connect(self.set_polygon_mode)
|
113
|
-
self.control_panel.btn_selection_mode.clicked.connect(
|
114
|
-
self.toggle_selection_mode
|
115
|
-
)
|
101
|
+
self.control_panel.btn_selection_mode.clicked.connect(self.toggle_selection_mode)
|
116
102
|
self.control_panel.btn_clear_points.clicked.connect(self.clear_all_points)
|
117
103
|
|
118
104
|
def _get_color_for_class(self, class_id, saturation, value):
|
@@ -136,15 +122,9 @@ class MainWindow(QMainWindow):
|
|
136
122
|
self.previous_mode = self.mode
|
137
123
|
|
138
124
|
self.mode = mode_name
|
139
|
-
self.control_panel.mode_label.setText(
|
140
|
-
f"Mode: {mode_name.replace('_', ' ').title()}"
|
141
|
-
)
|
125
|
+
self.control_panel.mode_label.setText(f"Mode: {mode_name.replace('_', ' ').title()}")
|
142
126
|
self.clear_all_points()
|
143
|
-
self.viewer.setDragMode(
|
144
|
-
self.viewer.DragMode.ScrollHandDrag
|
145
|
-
if self.mode == "pan"
|
146
|
-
else self.viewer.DragMode.NoDrag
|
147
|
-
)
|
127
|
+
self.viewer.setDragMode(self.viewer.DragMode.ScrollHandDrag if self.mode == "pan" else self.viewer.DragMode.NoDrag)
|
148
128
|
|
149
129
|
def set_sam_mode(self):
|
150
130
|
self.set_mode("sam_points")
|
@@ -168,9 +148,7 @@ class MainWindow(QMainWindow):
|
|
168
148
|
|
169
149
|
def toggle_edit_mode(self):
|
170
150
|
selected_indices = self.get_selected_segment_indices()
|
171
|
-
can_edit = any(
|
172
|
-
self.segments[i].get("type") == "Polygon" for i in selected_indices
|
173
|
-
)
|
151
|
+
can_edit = any(self.segments[i].get("type") == "Polygon" for i in selected_indices)
|
174
152
|
if self.mode == "edit":
|
175
153
|
self.set_mode("selection", is_toggle=True)
|
176
154
|
elif self.mode == "selection" and can_edit:
|
@@ -180,9 +158,7 @@ class MainWindow(QMainWindow):
|
|
180
158
|
def open_folder_dialog(self):
|
181
159
|
folder_path = QFileDialog.getExistingDirectory(self, "Select Image Folder")
|
182
160
|
if folder_path:
|
183
|
-
self.right_panel.file_tree.setRootIndex(
|
184
|
-
self.file_model.setRootPath(folder_path)
|
185
|
-
)
|
161
|
+
self.right_panel.file_tree.setRootIndex(self.file_model.setRootPath(folder_path))
|
186
162
|
self.viewer.setFocus()
|
187
163
|
|
188
164
|
def load_selected_image(self, index):
|
@@ -192,9 +168,7 @@ class MainWindow(QMainWindow):
|
|
192
168
|
self.current_file_index = index
|
193
169
|
path = self.file_model.filePath(index)
|
194
170
|
|
195
|
-
if os.path.isfile(path) and path.lower().endswith(
|
196
|
-
(".png", ".jpg", ".jpeg", ".tiff", ".tif")
|
197
|
-
):
|
171
|
+
if os.path.isfile(path) and path.lower().endswith((".png", ".jpg", ".jpeg", ".tiff", ".tif")):
|
198
172
|
self.current_image_path = path
|
199
173
|
pixmap = QPixmap(self.current_image_path)
|
200
174
|
if not pixmap.isNull():
|
@@ -209,11 +183,7 @@ class MainWindow(QMainWindow):
|
|
209
183
|
self.segments.clear()
|
210
184
|
self.next_class_id = 0
|
211
185
|
self.update_all_lists()
|
212
|
-
items_to_remove = [
|
213
|
-
item
|
214
|
-
for item in self.viewer.scene().items()
|
215
|
-
if item is not self.viewer._pixmap_item
|
216
|
-
]
|
186
|
+
items_to_remove = [item for item in self.viewer.scene().items() if item is not self.viewer._pixmap_item]
|
217
187
|
for item in items_to_remove:
|
218
188
|
self.viewer.scene().removeItem(item)
|
219
189
|
self.segment_items.clear()
|
@@ -224,25 +194,13 @@ class MainWindow(QMainWindow):
|
|
224
194
|
if event.isAutoRepeat():
|
225
195
|
return
|
226
196
|
if key == Qt.Key.Key_W:
|
227
|
-
self.viewer.verticalScrollBar().setValue(
|
228
|
-
self.viewer.verticalScrollBar().value()
|
229
|
-
- int(self.viewer.height() * 0.1)
|
230
|
-
)
|
197
|
+
self.viewer.verticalScrollBar().setValue(self.viewer.verticalScrollBar().value() - int(self.viewer.height() * 0.1))
|
231
198
|
elif key == Qt.Key.Key_S and not mods:
|
232
|
-
self.viewer.verticalScrollBar().setValue(
|
233
|
-
self.viewer.verticalScrollBar().value()
|
234
|
-
+ int(self.viewer.height() * 0.1)
|
235
|
-
)
|
199
|
+
self.viewer.verticalScrollBar().setValue(self.viewer.verticalScrollBar().value() + int(self.viewer.height() * 0.1))
|
236
200
|
elif key == Qt.Key.Key_A and not (mods & Qt.KeyboardModifier.ControlModifier):
|
237
|
-
self.viewer.horizontalScrollBar().setValue(
|
238
|
-
self.viewer.horizontalScrollBar().value()
|
239
|
-
- int(self.viewer.width() * 0.1)
|
240
|
-
)
|
201
|
+
self.viewer.horizontalScrollBar().setValue(self.viewer.horizontalScrollBar().value() - int(self.viewer.width() * 0.1))
|
241
202
|
elif key == Qt.Key.Key_D:
|
242
|
-
self.viewer.horizontalScrollBar().setValue(
|
243
|
-
self.viewer.horizontalScrollBar().value()
|
244
|
-
+ int(self.viewer.width() * 0.1)
|
245
|
-
)
|
203
|
+
self.viewer.horizontalScrollBar().setValue(self.viewer.horizontalScrollBar().value() + int(self.viewer.width() * 0.1))
|
246
204
|
elif key == Qt.Key.Key_1:
|
247
205
|
self.set_sam_mode()
|
248
206
|
elif key == Qt.Key.Key_2:
|
@@ -274,10 +232,7 @@ class MainWindow(QMainWindow):
|
|
274
232
|
if event.isAccepted():
|
275
233
|
return
|
276
234
|
pos = event.scenePos()
|
277
|
-
if (
|
278
|
-
self.viewer._pixmap_item.pixmap().isNull()
|
279
|
-
or not self.viewer._pixmap_item.pixmap().rect().contains(pos.toPoint())
|
280
|
-
):
|
235
|
+
if self.viewer._pixmap_item.pixmap().isNull() or not self.viewer._pixmap_item.pixmap().rect().contains(pos.toPoint()):
|
281
236
|
return
|
282
237
|
if self.mode == "sam_points":
|
283
238
|
if event.button() == Qt.MouseButton.LeftButton:
|
@@ -297,9 +252,7 @@ class MainWindow(QMainWindow):
|
|
297
252
|
self.is_dragging_polygon = True
|
298
253
|
selected_indices = self.get_selected_segment_indices()
|
299
254
|
self.drag_initial_vertices = {
|
300
|
-
i: list(self.segments[i]["vertices"])
|
301
|
-
for i in selected_indices
|
302
|
-
if self.segments[i].get("type") == "Polygon"
|
255
|
+
i: list(self.segments[i]["vertices"]) for i in selected_indices if self.segments[i].get("type") == "Polygon"
|
303
256
|
}
|
304
257
|
|
305
258
|
def scene_mouse_move(self, event):
|
@@ -307,16 +260,12 @@ class MainWindow(QMainWindow):
|
|
307
260
|
if self.mode == "edit" and self.is_dragging_polygon:
|
308
261
|
delta = pos - self.drag_start_pos
|
309
262
|
for i, initial_verts in self.drag_initial_vertices.items():
|
310
|
-
self.segments[i]["vertices"] = [
|
311
|
-
QPointF(v.x() + delta.x(), v.y() + delta.y()) for v in initial_verts
|
312
|
-
]
|
263
|
+
self.segments[i]["vertices"] = [QPointF(v.x() + delta.x(), v.y() + delta.y()) for v in initial_verts]
|
313
264
|
self.update_polygon_visuals(i)
|
314
265
|
elif self.mode == "polygon" and self.polygon_points:
|
315
266
|
if self.rubber_band_line is None:
|
316
267
|
self.rubber_band_line = QGraphicsLineItem()
|
317
|
-
self.rubber_band_line.setPen(
|
318
|
-
QPen(Qt.GlobalColor.white, 2, Qt.PenStyle.DotLine)
|
319
|
-
)
|
268
|
+
self.rubber_band_line.setPen(QPen(Qt.GlobalColor.white, 2, Qt.PenStyle.DotLine))
|
320
269
|
self.viewer.scene().addItem(self.rubber_band_line)
|
321
270
|
self.rubber_band_line.setLine(
|
322
271
|
self.polygon_points[-1].x(),
|
@@ -376,25 +325,14 @@ class MainWindow(QMainWindow):
|
|
376
325
|
x, y = int(pos.x()), int(pos.y())
|
377
326
|
for i in range(len(self.segments) - 1, -1, -1):
|
378
327
|
seg = self.segments[i]
|
379
|
-
mask = (
|
380
|
-
|
381
|
-
if seg["type"] == "Polygon"
|
382
|
-
else seg.get("mask")
|
383
|
-
)
|
384
|
-
if (
|
385
|
-
mask is not None
|
386
|
-
and y < mask.shape[0]
|
387
|
-
and x < mask.shape[1]
|
388
|
-
and mask[y, x]
|
389
|
-
):
|
328
|
+
mask = self.rasterize_polygon(seg["vertices"]) if seg["type"] == "Polygon" else seg.get("mask")
|
329
|
+
if mask is not None and y < mask.shape[0] and x < mask.shape[1] and mask[y, x]:
|
390
330
|
for j in range(self.right_panel.segment_table.rowCount()):
|
391
331
|
item = self.right_panel.segment_table.item(j, 0)
|
392
332
|
if item and item.data(Qt.ItemDataRole.UserRole) == i:
|
393
333
|
table = self.right_panel.segment_table
|
394
334
|
is_selected = table.item(j, 0).isSelected()
|
395
|
-
range_to_select = QTableWidgetSelectionRange(
|
396
|
-
j, 0, j, table.columnCount() - 1
|
397
|
-
)
|
335
|
+
range_to_select = QTableWidgetSelectionRange(j, 0, j, table.columnCount() - 1)
|
398
336
|
table.setRangeSelected(range_to_select, not is_selected)
|
399
337
|
return
|
400
338
|
self.viewer.setFocus()
|
@@ -404,11 +342,7 @@ class MainWindow(QMainWindow):
|
|
404
342
|
if not selected_indices:
|
405
343
|
return
|
406
344
|
|
407
|
-
existing_class_ids = [
|
408
|
-
self.segments[i]["class_id"]
|
409
|
-
for i in selected_indices
|
410
|
-
if self.segments[i].get("class_id") is not None
|
411
|
-
]
|
345
|
+
existing_class_ids = [self.segments[i]["class_id"] for i in selected_indices if self.segments[i].get("class_id") is not None]
|
412
346
|
|
413
347
|
if existing_class_ids:
|
414
348
|
target_class_id = min(existing_class_ids)
|
@@ -448,12 +382,8 @@ class MainWindow(QMainWindow):
|
|
448
382
|
|
449
383
|
if seg_dict["type"] == "Polygon":
|
450
384
|
poly_item = HoverablePolygonItem(QPolygonF(seg_dict["vertices"]))
|
451
|
-
default_brush = QBrush(
|
452
|
-
|
453
|
-
)
|
454
|
-
hover_brush = QBrush(
|
455
|
-
QColor(base_color.red(), base_color.green(), base_color.blue(), 170)
|
456
|
-
)
|
385
|
+
default_brush = QBrush(QColor(base_color.red(), base_color.green(), base_color.blue(), 70))
|
386
|
+
hover_brush = QBrush(QColor(base_color.red(), base_color.green(), base_color.blue(), 170))
|
457
387
|
poly_item.set_brushes(default_brush, hover_brush)
|
458
388
|
poly_item.setPen(QPen(Qt.GlobalColor.transparent))
|
459
389
|
self.viewer.scene().addItem(poly_item)
|
@@ -496,11 +426,7 @@ class MainWindow(QMainWindow):
|
|
496
426
|
selected_indices = self.get_selected_segment_indices()
|
497
427
|
for i in selected_indices:
|
498
428
|
seg = self.segments[i]
|
499
|
-
mask = (
|
500
|
-
self.rasterize_polygon(seg["vertices"])
|
501
|
-
if seg["type"] == "Polygon"
|
502
|
-
else seg.get("mask")
|
503
|
-
)
|
429
|
+
mask = self.rasterize_polygon(seg["vertices"]) if seg["type"] == "Polygon" else seg.get("mask")
|
504
430
|
if mask is not None:
|
505
431
|
pixmap = mask_to_pixmap(mask, (255, 255, 255))
|
506
432
|
highlight_item = self.viewer.scene().addPixmap(pixmap)
|
@@ -571,15 +497,7 @@ class MainWindow(QMainWindow):
|
|
571
497
|
class_table.blockSignals(True)
|
572
498
|
class_table.clearContents()
|
573
499
|
|
574
|
-
unique_class_ids = sorted(
|
575
|
-
list(
|
576
|
-
{
|
577
|
-
seg.get("class_id")
|
578
|
-
for seg in self.segments
|
579
|
-
if seg.get("class_id") is not None
|
580
|
-
}
|
581
|
-
)
|
582
|
-
)
|
500
|
+
unique_class_ids = sorted(list({seg.get("class_id") for seg in self.segments if seg.get("class_id") is not None}))
|
583
501
|
class_table.setRowCount(len(unique_class_ids))
|
584
502
|
|
585
503
|
for row, cid in enumerate(unique_class_ids):
|
@@ -595,15 +513,7 @@ class MainWindow(QMainWindow):
|
|
595
513
|
|
596
514
|
def update_class_filter_combo(self):
|
597
515
|
combo = self.right_panel.class_filter_combo
|
598
|
-
unique_class_ids = sorted(
|
599
|
-
list(
|
600
|
-
{
|
601
|
-
seg.get("class_id")
|
602
|
-
for seg in self.segments
|
603
|
-
if seg.get("class_id") is not None
|
604
|
-
}
|
605
|
-
)
|
606
|
-
)
|
516
|
+
unique_class_ids = sorted(list({seg.get("class_id") for seg in self.segments if seg.get("class_id") is not None}))
|
607
517
|
current_selection = combo.currentText()
|
608
518
|
combo.blockSignals(True)
|
609
519
|
combo.clear()
|
@@ -618,9 +528,7 @@ class MainWindow(QMainWindow):
|
|
618
528
|
def reassign_class_ids(self):
|
619
529
|
class_table = self.right_panel.class_table
|
620
530
|
ordered_ids = [
|
621
|
-
int(class_table.item(row, 0).text())
|
622
|
-
for row in range(class_table.rowCount())
|
623
|
-
if class_table.item(row, 0) is not None
|
531
|
+
int(class_table.item(row, 0).text()) for row in range(class_table.rowCount()) if class_table.item(row, 0) is not None
|
624
532
|
]
|
625
533
|
id_map = {old_id: new_id for new_id, old_id in enumerate(ordered_ids)}
|
626
534
|
for seg in self.segments:
|
@@ -658,9 +566,7 @@ class MainWindow(QMainWindow):
|
|
658
566
|
original_index = index_item.data(Qt.ItemDataRole.UserRole)
|
659
567
|
if original_index is not None and original_index < len(self.segments):
|
660
568
|
original_class_id = self.segments[original_index].get("class_id")
|
661
|
-
item.setText(
|
662
|
-
str(original_class_id) if original_class_id is not None else "N/A"
|
663
|
-
)
|
569
|
+
item.setText(str(original_class_id) if original_class_id is not None else "N/A")
|
664
570
|
finally:
|
665
571
|
table.blockSignals(False)
|
666
572
|
self.viewer.setFocus()
|
@@ -669,11 +575,7 @@ class MainWindow(QMainWindow):
|
|
669
575
|
table = self.right_panel.segment_table
|
670
576
|
selected_items = table.selectedItems()
|
671
577
|
selected_rows = sorted(list({item.row() for item in selected_items}))
|
672
|
-
return [
|
673
|
-
table.item(row, 0).data(Qt.ItemDataRole.UserRole)
|
674
|
-
for row in selected_rows
|
675
|
-
if table.item(row, 0)
|
676
|
-
]
|
578
|
+
return [table.item(row, 0).data(Qt.ItemDataRole.UserRole) for row in selected_rows if table.item(row, 0)]
|
677
579
|
|
678
580
|
def save_output_to_npz(self):
|
679
581
|
if not self.segments or not self.current_image_path:
|
@@ -686,15 +588,7 @@ class MainWindow(QMainWindow):
|
|
686
588
|
self.viewer._pixmap_item.pixmap().height(),
|
687
589
|
self.viewer._pixmap_item.pixmap().width(),
|
688
590
|
)
|
689
|
-
unique_class_ids = sorted(
|
690
|
-
list(
|
691
|
-
{
|
692
|
-
seg["class_id"]
|
693
|
-
for seg in self.segments
|
694
|
-
if seg.get("class_id") is not None
|
695
|
-
}
|
696
|
-
)
|
697
|
-
)
|
591
|
+
unique_class_ids = sorted(list({seg["class_id"] for seg in self.segments if seg.get("class_id") is not None}))
|
698
592
|
if not unique_class_ids:
|
699
593
|
self.right_panel.status_label.setText("Save failed: No classes.")
|
700
594
|
QTimer.singleShot(3000, lambda: self.right_panel.status_label.clear())
|
@@ -709,28 +603,55 @@ class MainWindow(QMainWindow):
|
|
709
603
|
if class_id not in id_map:
|
710
604
|
continue
|
711
605
|
new_channel_idx = id_map[class_id]
|
712
|
-
mask = (
|
713
|
-
self.rasterize_polygon(seg["vertices"])
|
714
|
-
if seg["type"] == "Polygon"
|
715
|
-
else seg.get("mask")
|
716
|
-
)
|
606
|
+
mask = self.rasterize_polygon(seg["vertices"]) if seg["type"] == "Polygon" else seg.get("mask")
|
717
607
|
if mask is not None:
|
718
|
-
final_mask_tensor[:, :, new_channel_idx] = np.logical_or(
|
719
|
-
final_mask_tensor[:, :, new_channel_idx], mask
|
720
|
-
)
|
608
|
+
final_mask_tensor[:, :, new_channel_idx] = np.logical_or(final_mask_tensor[:, :, new_channel_idx], mask)
|
721
609
|
|
722
610
|
np.savez_compressed(output_path, mask=final_mask_tensor.astype(np.uint8))
|
723
611
|
self.file_model.setRootPath(self.file_model.rootPath())
|
724
612
|
|
725
613
|
self.right_panel.status_label.setText("Saved!")
|
614
|
+
self.generate_yolo_annotations(npz_file_path=output_path)
|
726
615
|
QTimer.singleShot(3000, lambda: self.right_panel.status_label.clear())
|
727
616
|
|
617
|
+
def generate_yolo_annotations(self, npz_file_path):
|
618
|
+
output_path = os.path.splitext(self.current_image_path)[0] + ".txt"
|
619
|
+
npz_data = np.load(npz_file_path) # Load the saved npz file
|
620
|
+
|
621
|
+
img = npz_data["mask"][:, :, :]
|
622
|
+
num_channels = img.shape[2] # C
|
623
|
+
h, w = img.shape[:2] # H, W
|
624
|
+
|
625
|
+
directory_path = os.path.dirname(output_path)
|
626
|
+
os.makedirs(directory_path, exist_ok=True)
|
627
|
+
|
628
|
+
yolo_annotations = []
|
629
|
+
|
630
|
+
for channel in range(num_channels):
|
631
|
+
single_channel_image = img[:, :, channel]
|
632
|
+
contours, _ = cv2.findContours(single_channel_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
633
|
+
|
634
|
+
class_id = channel # Use the channel index as the class ID
|
635
|
+
|
636
|
+
for contour in contours:
|
637
|
+
x, y, width, height = cv2.boundingRect(contour)
|
638
|
+
center_x = x + width / 2
|
639
|
+
center_y = y + height / 2
|
640
|
+
|
641
|
+
normalized_center_x = center_x / w
|
642
|
+
normalized_center_y = center_y / h
|
643
|
+
normalized_width = width / w
|
644
|
+
normalized_height = height / h
|
645
|
+
|
646
|
+
yolo_entry = f"{class_id} {normalized_center_x} {normalized_center_y} {normalized_width} {normalized_height}"
|
647
|
+
yolo_annotations.append(yolo_entry)
|
648
|
+
|
649
|
+
with open(output_path, "w") as file:
|
650
|
+
for annotation in yolo_annotations:
|
651
|
+
file.write(annotation + "\n")
|
652
|
+
|
728
653
|
def save_current_segment(self):
|
729
|
-
if (
|
730
|
-
self.mode != "sam_points"
|
731
|
-
or not hasattr(self, "preview_mask_item")
|
732
|
-
or not self.preview_mask_item
|
733
|
-
):
|
654
|
+
if self.mode != "sam_points" or not hasattr(self, "preview_mask_item") or not self.preview_mask_item:
|
734
655
|
return
|
735
656
|
mask = self.sam_model.predict(self.positive_points, self.negative_points)
|
736
657
|
if mask is not None:
|
@@ -819,13 +740,7 @@ class MainWindow(QMainWindow):
|
|
819
740
|
self.preview_mask_item = None
|
820
741
|
|
821
742
|
def handle_polygon_click(self, pos):
|
822
|
-
if self.polygon_points and (
|
823
|
-
(
|
824
|
-
(pos.x() - self.polygon_points[0].x()) ** 2
|
825
|
-
+ (pos.y() - self.polygon_points[0].y()) ** 2
|
826
|
-
)
|
827
|
-
< 25
|
828
|
-
):
|
743
|
+
if self.polygon_points and (((pos.x() - self.polygon_points[0].x()) ** 2 + (pos.y() - self.polygon_points[0].y()) ** 2) < 25):
|
829
744
|
if len(self.polygon_points) > 2:
|
830
745
|
self.finalize_polygon()
|
831
746
|
return
|
@@ -844,11 +759,7 @@ class MainWindow(QMainWindow):
|
|
844
759
|
for item in self.polygon_preview_items:
|
845
760
|
if not isinstance(item, QGraphicsEllipseItem):
|
846
761
|
self.viewer.scene().removeItem(item)
|
847
|
-
self.polygon_preview_items = [
|
848
|
-
item
|
849
|
-
for item in self.polygon_preview_items
|
850
|
-
if isinstance(item, QGraphicsEllipseItem)
|
851
|
-
]
|
762
|
+
self.polygon_preview_items = [item for item in self.polygon_preview_items if isinstance(item, QGraphicsEllipseItem)]
|
852
763
|
|
853
764
|
if len(self.polygon_points) > 2:
|
854
765
|
preview_poly = QGraphicsPolygonItem(QPolygonF(self.polygon_points))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|