lazylabel-gui 1.0.3__tar.gz → 1.0.5__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.
Files changed (21) hide show
  1. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/PKG-INFO +1 -1
  2. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/pyproject.toml +2 -2
  3. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel/custom_file_system_model.py +1 -1
  4. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel/main.py +73 -61
  5. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel_gui.egg-info/PKG-INFO +1 -1
  6. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/LICENSE +0 -0
  7. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/README.md +0 -0
  8. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/setup.cfg +0 -0
  9. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel/controls.py +0 -0
  10. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel/editable_vertex.py +0 -0
  11. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel/hoverable_polygon_item.py +0 -0
  12. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel/numeric_table_widget_item.py +0 -0
  13. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel/photo_viewer.py +0 -0
  14. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel/reorderable_class_table.py +0 -0
  15. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel/sam_model.py +0 -0
  16. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel/utils.py +0 -0
  17. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel_gui.egg-info/SOURCES.txt +0 -0
  18. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel_gui.egg-info/dependency_links.txt +0 -0
  19. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel_gui.egg-info/entry_points.txt +0 -0
  20. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel_gui.egg-info/requires.txt +0 -0
  21. {lazylabel_gui-1.0.3 → lazylabel_gui-1.0.5}/src/lazylabel_gui.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lazylabel-gui
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: An image segmentation GUI for generating mask tensors.
5
5
  Author-email: "Deniz N. Cakan" <deniz.n.cakan@gmail.com>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lazylabel-gui"
7
- version = "1.0.3"
7
+ version = "1.0.5"
8
8
  authors = [
9
9
  { name="Deniz N. Cakan", email="deniz.n.cakan@gmail.com" },
10
10
  ]
@@ -37,4 +37,4 @@ dependencies = [
37
37
  "Bug Tracker" = "https://github.com/dnzckn/lazylabel/issues"
38
38
 
39
39
  [project.scripts]
40
- lazylabel-gui = "lazylabel.main:main"
40
+ lazylabel-gui = "lazylabel.main:main"
@@ -8,7 +8,7 @@ class CustomFileSystemModel(QFileSystemModel):
8
8
  super().__init__(parent)
9
9
  self.setFilter(QDir.Filter.NoDotAndDotDot | QDir.Filter.Files)
10
10
  self.setNameFilterDisables(False)
11
- self.setNameFilters(["*.png", "*.jpg", "*.jpeg", "*.tiff"])
11
+ self.setNameFilters(["*.png", "*.jpg", "*.jpeg", "*.tiff", "*.tif"])
12
12
  self.highlighted_path = None
13
13
 
14
14
  def set_highlighted_path(self, path):
@@ -115,15 +115,24 @@ class MainWindow(QMainWindow):
115
115
  )
116
116
  self.control_panel.btn_clear_points.clicked.connect(self.clear_all_points)
117
117
 
118
+ def _get_color_for_class(self, class_id, saturation, value):
119
+ if class_id is None:
120
+ return QColor.fromHsv(0, 0, 128)
121
+
122
+ hue = int((class_id * 222.4922359) % 360)
123
+ color = QColor.fromHsv(hue, saturation, value)
124
+
125
+ if not color.isValid():
126
+ return QColor(Qt.GlobalColor.white)
127
+ return color
128
+
118
129
  def set_mode(self, mode_name, is_toggle=False):
119
- # Clear selection if exiting selection mode
120
- if self.mode == "selection" and mode_name != "selection":
130
+ if self.mode == "selection" and mode_name not in ["selection", "edit"]:
121
131
  self.right_panel.segment_table.clearSelection()
122
-
123
132
  if self.mode == "edit" and mode_name != "edit":
124
133
  self.display_all_segments()
125
134
 
126
- if not is_toggle and self.mode not in ["pan", "selection", "edit"]:
135
+ if not is_toggle and self.mode not in ["selection", "edit"]:
127
136
  self.previous_mode = self.mode
128
137
 
129
138
  self.mode = mode_name
@@ -147,7 +156,7 @@ class MainWindow(QMainWindow):
147
156
  if self.mode == new_mode:
148
157
  self.set_mode(self.previous_mode, is_toggle=True)
149
158
  else:
150
- if self.mode not in ["pan", "selection", "edit"]:
159
+ if self.mode not in ["selection", "edit"]:
151
160
  self.previous_mode = self.mode
152
161
  self.set_mode(new_mode, is_toggle=True)
153
162
 
@@ -184,7 +193,7 @@ class MainWindow(QMainWindow):
184
193
  path = self.file_model.filePath(index)
185
194
 
186
195
  if os.path.isfile(path) and path.lower().endswith(
187
- (".png", ".jpg", ".jpeg", ".tiff")
196
+ (".png", ".jpg", ".jpeg", ".tiff", ".tif")
188
197
  ):
189
198
  self.current_image_path = path
190
199
  pixmap = QPixmap(self.current_image_path)
@@ -244,12 +253,13 @@ class MainWindow(QMainWindow):
244
253
  self.toggle_pan_mode()
245
254
  elif key == Qt.Key.Key_R:
246
255
  self.toggle_edit_mode()
247
- elif key == Qt.Key.Key_C:
256
+ elif key == Qt.Key.Key_C or key == Qt.Key.Key_Escape:
248
257
  self.clear_all_points()
249
258
  elif key == Qt.Key.Key_V or key == Qt.Key.Key_Backspace:
250
259
  self.delete_selected_segments()
251
260
  elif key == Qt.Key.Key_M:
252
261
  self.assign_selected_to_class()
262
+ self.right_panel.segment_table.clearSelection()
253
263
  elif key == Qt.Key.Key_Z and mods == Qt.KeyboardModifier.ControlModifier:
254
264
  self.undo_last_action()
255
265
  elif key == Qt.Key.Key_A and mods == Qt.KeyboardModifier.ControlModifier:
@@ -393,10 +403,23 @@ class MainWindow(QMainWindow):
393
403
  selected_indices = self.get_selected_segment_indices()
394
404
  if not selected_indices:
395
405
  return
396
- target_class_id = self.segments[selected_indices[0]]["class_id"]
406
+
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
+ ]
412
+
413
+ if existing_class_ids:
414
+ target_class_id = min(existing_class_ids)
415
+ else:
416
+ target_class_id = self.segments[selected_indices[0]].get("class_id")
417
+
397
418
  for i in selected_indices:
398
419
  self.segments[i]["class_id"] = target_class_id
420
+
399
421
  self.update_all_lists()
422
+ self.right_panel.segment_table.clearSelection()
400
423
  self.viewer.setFocus()
401
424
 
402
425
  def rasterize_polygon(self, vertices):
@@ -418,27 +441,10 @@ class MainWindow(QMainWindow):
418
441
  self.segment_items.clear()
419
442
  selected_indices = self.get_selected_segment_indices()
420
443
 
421
- unique_class_ids = sorted(
422
- list(
423
- {
424
- seg.get("class_id")
425
- for seg in self.segments
426
- if seg.get("class_id") is not None
427
- }
428
- )
429
- )
430
- num_classes = len(unique_class_ids) if unique_class_ids else 1
431
- class_id_to_hue_index = {
432
- class_id: i for i, class_id in enumerate(unique_class_ids)
433
- }
434
-
435
444
  for i, seg_dict in enumerate(self.segments):
436
445
  self.segment_items[i] = []
437
- class_id = seg_dict.get("class_id", 0)
438
-
439
- hue_index = class_id_to_hue_index.get(class_id, 0)
440
- hue = int((hue_index * 360 / num_classes)) % 360
441
- base_color = QColor.fromHsv(hue, 220, 220)
446
+ class_id = seg_dict.get("class_id")
447
+ base_color = self._get_color_for_class(class_id, saturation=220, value=220)
442
448
 
443
449
  if seg_dict["type"] == "Polygon":
444
450
  poly_item = HoverablePolygonItem(QPolygonF(seg_dict["vertices"]))
@@ -529,27 +535,14 @@ class MainWindow(QMainWindow):
529
535
 
530
536
  table.setRowCount(len(display_segments))
531
537
 
532
- unique_class_ids = sorted(
533
- list(
534
- {
535
- s.get("class_id")
536
- for s in self.segments
537
- if s.get("class_id") is not None
538
- }
539
- )
540
- )
541
- num_classes = len(unique_class_ids) if unique_class_ids else 1
542
- class_id_to_hue_index = {cid: i for i, cid in enumerate(unique_class_ids)}
543
-
544
538
  for row, (original_index, seg) in enumerate(display_segments):
545
- class_id = seg.get("class_id", 0)
546
- hue_index = class_id_to_hue_index.get(class_id, 0)
547
- hue = int((hue_index * 360 / num_classes)) % 360
548
- color = QColor.fromHsv(hue, 150, 100)
539
+ class_id = seg.get("class_id")
540
+ color = self._get_color_for_class(class_id, saturation=180, value=200)
549
541
 
542
+ class_id_str = str(class_id) if class_id is not None else "N/A"
550
543
  index_item = NumericTableWidgetItem(str(original_index + 1))
551
- class_item = NumericTableWidgetItem(str(class_id))
552
- type_item = QTableWidgetItem(seg["type"])
544
+ class_item = NumericTableWidgetItem(class_id_str)
545
+ type_item = QTableWidgetItem(seg.get("type", "N/A"))
553
546
 
554
547
  index_item.setFlags(index_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
555
548
  type_item.setFlags(type_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
@@ -558,12 +551,15 @@ class MainWindow(QMainWindow):
558
551
  table.setItem(row, 0, index_item)
559
552
  table.setItem(row, 1, class_item)
560
553
  table.setItem(row, 2, type_item)
554
+
561
555
  for col in range(3):
562
- table.item(row, col).setBackground(QBrush(color))
556
+ if table.item(row, col):
557
+ table.item(row, col).setBackground(QBrush(color))
563
558
 
564
559
  table.setSortingEnabled(False)
565
560
  for row in range(table.rowCount()):
566
- if table.item(row, 0).data(Qt.ItemDataRole.UserRole) in selected_indices:
561
+ item = table.item(row, 0)
562
+ if item and item.data(Qt.ItemDataRole.UserRole) in selected_indices:
567
563
  table.selectRow(row)
568
564
  table.setSortingEnabled(True)
569
565
 
@@ -573,6 +569,8 @@ class MainWindow(QMainWindow):
573
569
  def update_class_list(self):
574
570
  class_table = self.right_panel.class_table
575
571
  class_table.blockSignals(True)
572
+ class_table.clearContents()
573
+
576
574
  unique_class_ids = sorted(
577
575
  list(
578
576
  {
@@ -583,18 +581,16 @@ class MainWindow(QMainWindow):
583
581
  )
584
582
  )
585
583
  class_table.setRowCount(len(unique_class_ids))
586
- num_classes = len(unique_class_ids) if unique_class_ids else 1
587
- class_id_to_hue_index = {
588
- class_id: i for i, class_id in enumerate(unique_class_ids)
589
- }
584
+
590
585
  for row, cid in enumerate(unique_class_ids):
591
586
  item = QTableWidgetItem(str(cid))
592
587
  item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
593
- hue_index = class_id_to_hue_index.get(cid, 0)
594
- hue = int((hue_index * 360 / num_classes)) % 360
595
- color = QColor.fromHsv(hue, 150, 100)
588
+
589
+ color = self._get_color_for_class(cid, saturation=180, value=200)
590
+
596
591
  item.setBackground(QBrush(color))
597
592
  class_table.setItem(row, 0, item)
593
+
598
594
  class_table.blockSignals(False)
599
595
 
600
596
  def update_class_filter_combo(self):
@@ -639,19 +635,35 @@ class MainWindow(QMainWindow):
639
635
  if item.column() != 1:
640
636
  return
641
637
  table = self.right_panel.segment_table
638
+ index_item = table.item(item.row(), 0)
639
+ if not index_item:
640
+ return
641
+
642
642
  table.blockSignals(True)
643
643
  try:
644
- new_class_id = int(item.text())
645
- original_index = table.item(item.row(), 0).data(Qt.ItemDataRole.UserRole)
644
+ new_class_id_text = item.text()
645
+ if not new_class_id_text.strip():
646
+ raise ValueError("Class ID cannot be empty.")
647
+ new_class_id = int(new_class_id_text)
648
+ original_index = index_item.data(Qt.ItemDataRole.UserRole)
649
+
650
+ if original_index is None or original_index >= len(self.segments):
651
+ raise IndexError("Invalid segment index found in table.")
652
+
646
653
  self.segments[original_index]["class_id"] = new_class_id
647
654
  if new_class_id >= self.next_class_id:
648
655
  self.next_class_id = new_class_id + 1
649
656
  self.update_all_lists()
650
- except (ValueError, TypeError):
651
- original_index = table.item(item.row(), 0).data(Qt.ItemDataRole.UserRole)
652
- item.setText(str(self.segments[original_index]["class_id"]))
653
- table.blockSignals(False)
654
- self.viewer.setFocus()
657
+ except (ValueError, TypeError, AttributeError, IndexError) as e:
658
+ original_index = index_item.data(Qt.ItemDataRole.UserRole)
659
+ if original_index is not None and original_index < len(self.segments):
660
+ 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
+ )
664
+ finally:
665
+ table.blockSignals(False)
666
+ self.viewer.setFocus()
655
667
 
656
668
  def get_selected_segment_indices(self):
657
669
  table = self.right_panel.segment_table
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lazylabel-gui
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: An image segmentation GUI for generating mask tensors.
5
5
  Author-email: "Deniz N. Cakan" <deniz.n.cakan@gmail.com>
6
6
  License: MIT License
File without changes
File without changes
File without changes