lazylabel-gui 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.
- lazylabel/controls.py +108 -0
- lazylabel/custom_file_system_model.py +57 -0
- lazylabel/editable_vertex.py +25 -0
- lazylabel/hoverable_polygon_item.py +23 -0
- lazylabel/main.py +864 -0
- lazylabel/numeric_table_widget_item.py +10 -0
- lazylabel/photo_viewer.py +51 -0
- lazylabel/reorderable_class_table.py +58 -0
- lazylabel/sam_model.py +70 -0
- lazylabel/utils.py +12 -0
- lazylabel_gui-1.0.0.dist-info/METADATA +147 -0
- lazylabel_gui-1.0.0.dist-info/RECORD +16 -0
- lazylabel_gui-1.0.0.dist-info/WHEEL +5 -0
- lazylabel_gui-1.0.0.dist-info/entry_points.txt +2 -0
- lazylabel_gui-1.0.0.dist-info/licenses/LICENSE +21 -0
- lazylabel_gui-1.0.0.dist-info/top_level.txt +1 -0
lazylabel/controls.py
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
from PyQt6.QtWidgets import (
|
2
|
+
QWidget,
|
3
|
+
QVBoxLayout,
|
4
|
+
QPushButton,
|
5
|
+
QLabel,
|
6
|
+
QFrame,
|
7
|
+
QTableWidget,
|
8
|
+
QTreeView,
|
9
|
+
QAbstractItemView,
|
10
|
+
QHBoxLayout,
|
11
|
+
QComboBox,
|
12
|
+
QHeaderView,
|
13
|
+
)
|
14
|
+
from PyQt6.QtCore import Qt
|
15
|
+
from .reorderable_class_table import ReorderableClassTable
|
16
|
+
|
17
|
+
|
18
|
+
class ControlPanel(QWidget):
|
19
|
+
def __init__(self, parent=None):
|
20
|
+
super().__init__(parent)
|
21
|
+
layout = QVBoxLayout(self)
|
22
|
+
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
23
|
+
self.mode_label = QLabel("Mode: Points")
|
24
|
+
font = self.mode_label.font()
|
25
|
+
font.setPointSize(14)
|
26
|
+
font.setBold(True)
|
27
|
+
self.mode_label.setFont(font)
|
28
|
+
layout.addWidget(self.mode_label)
|
29
|
+
self.btn_sam_mode = QPushButton("Point Mode (1)")
|
30
|
+
self.btn_polygon_mode = QPushButton("Polygon Mode (2)")
|
31
|
+
self.btn_selection_mode = QPushButton("Selection Mode (E)")
|
32
|
+
layout.addWidget(self.btn_sam_mode)
|
33
|
+
layout.addWidget(self.btn_polygon_mode)
|
34
|
+
layout.addWidget(self.btn_selection_mode)
|
35
|
+
layout.addSpacing(20)
|
36
|
+
line1 = QFrame()
|
37
|
+
line1.setFrameShape(QFrame.Shape.HLine)
|
38
|
+
layout.addWidget(line1)
|
39
|
+
layout.addSpacing(10)
|
40
|
+
self.btn_clear_points = QPushButton("Clear Clicks (C)")
|
41
|
+
layout.addWidget(self.btn_clear_points)
|
42
|
+
layout.addStretch()
|
43
|
+
self.device_label = QLabel("Device: Unknown")
|
44
|
+
layout.addWidget(self.device_label)
|
45
|
+
self.setFixedWidth(250)
|
46
|
+
|
47
|
+
|
48
|
+
class RightPanel(QWidget):
|
49
|
+
def __init__(self, parent=None):
|
50
|
+
super().__init__(parent)
|
51
|
+
layout = QVBoxLayout(self)
|
52
|
+
|
53
|
+
# File Explorer
|
54
|
+
file_explorer_layout = QVBoxLayout()
|
55
|
+
self.btn_open_folder = QPushButton("Open Image Folder")
|
56
|
+
self.file_tree = QTreeView()
|
57
|
+
file_explorer_layout.addWidget(self.btn_open_folder)
|
58
|
+
file_explorer_layout.addWidget(self.file_tree)
|
59
|
+
layout.addLayout(file_explorer_layout)
|
60
|
+
|
61
|
+
# Status Label
|
62
|
+
self.status_label = QLabel("")
|
63
|
+
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
64
|
+
layout.addWidget(self.status_label)
|
65
|
+
|
66
|
+
# Segment Table
|
67
|
+
segment_layout = QVBoxLayout()
|
68
|
+
class_filter_layout = QHBoxLayout()
|
69
|
+
class_filter_layout.addWidget(QLabel("Filter Class:"))
|
70
|
+
self.class_filter_combo = QComboBox()
|
71
|
+
class_filter_layout.addWidget(self.class_filter_combo)
|
72
|
+
segment_layout.addLayout(class_filter_layout)
|
73
|
+
|
74
|
+
self.segment_table = QTableWidget()
|
75
|
+
self.segment_table.setColumnCount(3)
|
76
|
+
self.segment_table.setHorizontalHeaderLabels(["Index", "Class", "Type"])
|
77
|
+
self.segment_table.horizontalHeader().setSectionResizeMode(
|
78
|
+
QHeaderView.ResizeMode.Stretch
|
79
|
+
)
|
80
|
+
self.segment_table.setSelectionBehavior(
|
81
|
+
QAbstractItemView.SelectionBehavior.SelectRows
|
82
|
+
)
|
83
|
+
self.segment_table.setSortingEnabled(True)
|
84
|
+
segment_layout.addWidget(self.segment_table)
|
85
|
+
|
86
|
+
segment_action_layout = QHBoxLayout()
|
87
|
+
self.btn_merge_selection = QPushButton("Merge to Class")
|
88
|
+
self.btn_delete_selection = QPushButton("Delete")
|
89
|
+
segment_action_layout.addWidget(self.btn_merge_selection)
|
90
|
+
segment_action_layout.addWidget(self.btn_delete_selection)
|
91
|
+
segment_layout.addLayout(segment_action_layout)
|
92
|
+
layout.addLayout(segment_layout, 2)
|
93
|
+
|
94
|
+
# Class Table
|
95
|
+
class_layout = QVBoxLayout()
|
96
|
+
class_layout.addWidget(QLabel("Class Order:"))
|
97
|
+
self.class_table = ReorderableClassTable()
|
98
|
+
self.class_table.setColumnCount(1)
|
99
|
+
self.class_table.setHorizontalHeaderLabels(["Class ID"])
|
100
|
+
self.class_table.horizontalHeader().setSectionResizeMode(
|
101
|
+
QHeaderView.ResizeMode.Stretch
|
102
|
+
)
|
103
|
+
class_layout.addWidget(self.class_table)
|
104
|
+
self.btn_reassign_classes = QPushButton("Reassign Class IDs")
|
105
|
+
class_layout.addWidget(self.btn_reassign_classes)
|
106
|
+
layout.addLayout(class_layout, 1)
|
107
|
+
|
108
|
+
self.setFixedWidth(350)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import os
|
2
|
+
from PyQt6.QtCore import Qt, QModelIndex, QDir
|
3
|
+
from PyQt6.QtGui import QFileSystemModel, QBrush, QColor
|
4
|
+
|
5
|
+
|
6
|
+
class CustomFileSystemModel(QFileSystemModel):
|
7
|
+
def __init__(self, parent=None):
|
8
|
+
super().__init__(parent)
|
9
|
+
self.setFilter(QDir.Filter.NoDotAndDotDot | QDir.Filter.Files)
|
10
|
+
self.setNameFilterDisables(False)
|
11
|
+
self.setNameFilters(["*.png", "*.jpg", "*.jpeg", "*.tiff"])
|
12
|
+
self.highlighted_path = None
|
13
|
+
|
14
|
+
def set_highlighted_path(self, path):
|
15
|
+
self.highlighted_path = path
|
16
|
+
|
17
|
+
def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
18
|
+
return 2
|
19
|
+
|
20
|
+
def headerData(
|
21
|
+
self,
|
22
|
+
section: int,
|
23
|
+
orientation: Qt.Orientation,
|
24
|
+
role: int = Qt.ItemDataRole.DisplayRole,
|
25
|
+
):
|
26
|
+
if (
|
27
|
+
orientation == Qt.Orientation.Horizontal
|
28
|
+
and role == Qt.ItemDataRole.DisplayRole
|
29
|
+
):
|
30
|
+
if section == 0:
|
31
|
+
return "File Name"
|
32
|
+
if section == 1:
|
33
|
+
return "Mask"
|
34
|
+
return super().headerData(section, orientation, role)
|
35
|
+
|
36
|
+
def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole):
|
37
|
+
if not index.isValid():
|
38
|
+
return None
|
39
|
+
|
40
|
+
# Handle the temporary highlight for saving
|
41
|
+
if role == Qt.ItemDataRole.BackgroundRole:
|
42
|
+
filePath = self.filePath(index)
|
43
|
+
if filePath == self.highlighted_path:
|
44
|
+
return QBrush(QColor("yellow"))
|
45
|
+
|
46
|
+
if index.column() == 1:
|
47
|
+
if role == Qt.ItemDataRole.CheckStateRole:
|
48
|
+
filePath = self.filePath(index.siblingAtColumn(0))
|
49
|
+
mask_path = os.path.splitext(filePath)[0] + ".npz"
|
50
|
+
return (
|
51
|
+
Qt.CheckState.Checked
|
52
|
+
if os.path.exists(mask_path)
|
53
|
+
else Qt.CheckState.Unchecked
|
54
|
+
)
|
55
|
+
return None
|
56
|
+
|
57
|
+
return super().data(index, role)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from PyQt6.QtWidgets import QGraphicsEllipseItem, QGraphicsItem
|
2
|
+
from PyQt6.QtCore import Qt
|
3
|
+
from PyQt6.QtGui import QBrush, QPen
|
4
|
+
|
5
|
+
|
6
|
+
class EditableVertexItem(QGraphicsEllipseItem):
|
7
|
+
def __init__(self, main_window, segment_index, vertex_index, x, y, w, h):
|
8
|
+
super().__init__(x, y, w, h)
|
9
|
+
self.main_window = main_window
|
10
|
+
self.segment_index = segment_index
|
11
|
+
self.vertex_index = vertex_index
|
12
|
+
|
13
|
+
self.setZValue(200)
|
14
|
+
self.setBrush(QBrush(Qt.GlobalColor.cyan))
|
15
|
+
self.setPen(QPen(Qt.GlobalColor.white, 1))
|
16
|
+
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable)
|
17
|
+
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges)
|
18
|
+
|
19
|
+
def itemChange(self, change, value):
|
20
|
+
if change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
|
21
|
+
new_pos = value
|
22
|
+
self.main_window.update_vertex_pos(
|
23
|
+
self.segment_index, self.vertex_index, new_pos
|
24
|
+
)
|
25
|
+
return super().itemChange(change, value)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from PyQt6.QtWidgets import QGraphicsPolygonItem
|
2
|
+
from PyQt6.QtGui import QBrush
|
3
|
+
|
4
|
+
|
5
|
+
class HoverablePolygonItem(QGraphicsPolygonItem):
|
6
|
+
def __init__(self, polygon, parent=None):
|
7
|
+
super().__init__(polygon, parent)
|
8
|
+
self.setAcceptHoverEvents(True)
|
9
|
+
self.default_brush = QBrush()
|
10
|
+
self.hover_brush = QBrush()
|
11
|
+
|
12
|
+
def set_brushes(self, default_brush, hover_brush):
|
13
|
+
self.default_brush = default_brush
|
14
|
+
self.hover_brush = hover_brush
|
15
|
+
self.setBrush(self.default_brush)
|
16
|
+
|
17
|
+
def hoverEnterEvent(self, event):
|
18
|
+
self.setBrush(self.hover_brush)
|
19
|
+
super().hoverEnterEvent(event)
|
20
|
+
|
21
|
+
def hoverLeaveEvent(self, event):
|
22
|
+
self.setBrush(self.default_brush)
|
23
|
+
super().hoverLeaveEvent(event)
|