lazylabel-gui 1.0.6__tar.gz → 1.0.7__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.6 → lazylabel_gui-1.0.7}/PKG-INFO +2 -2
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/README.md +1 -1
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/pyproject.toml +1 -1
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel/controls.py +59 -4
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel/custom_file_system_model.py +10 -4
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel/editable_vertex.py +7 -3
- lazylabel_gui-1.0.7/src/lazylabel/hoverable_pixelmap_item.py +22 -0
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel/main.py +492 -96
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel/photo_viewer.py +6 -3
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel/reorderable_class_table.py +10 -7
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel/utils.py +2 -2
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel_gui.egg-info/PKG-INFO +2 -2
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel_gui.egg-info/SOURCES.txt +1 -0
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/LICENSE +0 -0
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/setup.cfg +0 -0
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel/hoverable_polygon_item.py +0 -0
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel/numeric_table_widget_item.py +0 -0
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel/sam_model.py +0 -0
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel_gui.egg-info/dependency_links.txt +0 -0
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel_gui.egg-info/entry_points.txt +0 -0
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/src/lazylabel_gui.egg-info/requires.txt +0 -0
- {lazylabel_gui-1.0.6 → lazylabel_gui-1.0.7}/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
|
+
Version: 1.0.7
|
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
|
@@ -48,7 +48,7 @@ Requires-Dist: tqdm>=4.67.1
|
|
48
48
|
Dynamic: license-file
|
49
49
|
|
50
50
|
# <img src="https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/logo2.png" alt="LazyLabel Logo" style="height:60px; vertical-align:middle;" /> <img src="https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/logo_black.png" alt="LazyLabel Cursive" style="height:60px; vertical-align:middle;" />
|
51
|
-
LazyLabel is an intuitive, AI-assisted image segmentation tool. It uses Meta's Segment Anything Model (SAM) for quick, precise mask generation, alongside advanced polygon editing for fine-tuned control. Outputs are saved in a clean, one-hot encoded format for easy machine learning integration.
|
51
|
+
LazyLabel is an intuitive, AI-assisted image segmentation tool. It uses Meta's Segment Anything Model (SAM) for quick, precise mask generation, alongside advanced polygon editing for fine-tuned control. Outputs are saved in a clean, one-hot encoded `.npz` format for easy machine learning integration and in YOLO `.txt` format.
|
52
52
|
|
53
53
|
Inspired by [LabelMe](https://github.com/wkentaro/labelme?tab=readme-ov-file#installation) and [Segment-Anything-UI](https://github.com/branislavhesko/segment-anything-ui/tree/main).
|
54
54
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# <img src="https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/logo2.png" alt="LazyLabel Logo" style="height:60px; vertical-align:middle;" /> <img src="https://raw.githubusercontent.com/dnzckn/LazyLabel/main/src/lazylabel/demo_pictures/logo_black.png" alt="LazyLabel Cursive" style="height:60px; vertical-align:middle;" />
|
2
|
-
LazyLabel is an intuitive, AI-assisted image segmentation tool. It uses Meta's Segment Anything Model (SAM) for quick, precise mask generation, alongside advanced polygon editing for fine-tuned control. Outputs are saved in a clean, one-hot encoded format for easy machine learning integration.
|
2
|
+
LazyLabel is an intuitive, AI-assisted image segmentation tool. It uses Meta's Segment Anything Model (SAM) for quick, precise mask generation, alongside advanced polygon editing for fine-tuned control. Outputs are saved in a clean, one-hot encoded `.npz` format for easy machine learning integration and in YOLO `.txt` format.
|
3
3
|
|
4
4
|
Inspired by [LabelMe](https://github.com/wkentaro/labelme?tab=readme-ov-file#installation) and [Segment-Anything-UI](https://github.com/branislavhesko/segment-anything-ui/tree/main).
|
5
5
|
|
@@ -10,6 +10,7 @@ from PyQt6.QtWidgets import (
|
|
10
10
|
QHBoxLayout,
|
11
11
|
QComboBox,
|
12
12
|
QHeaderView,
|
13
|
+
QCheckBox,
|
13
14
|
)
|
14
15
|
from PyQt6.QtCore import Qt
|
15
16
|
from .reorderable_class_table import ReorderableClassTable
|
@@ -26,20 +27,56 @@ class ControlPanel(QWidget):
|
|
26
27
|
font.setBold(True)
|
27
28
|
self.mode_label.setFont(font)
|
28
29
|
layout.addWidget(self.mode_label)
|
30
|
+
|
31
|
+
# Mode Buttons
|
29
32
|
self.btn_sam_mode = QPushButton("Point Mode (1)")
|
33
|
+
self.btn_sam_mode.setToolTip("Switch to Point Mode for AI segmentation (1)")
|
30
34
|
self.btn_polygon_mode = QPushButton("Polygon Mode (2)")
|
35
|
+
self.btn_polygon_mode.setToolTip("Switch to Polygon Drawing Mode (2)")
|
31
36
|
self.btn_selection_mode = QPushButton("Selection Mode (E)")
|
37
|
+
self.btn_selection_mode.setToolTip("Toggle segment selection (E)")
|
32
38
|
layout.addWidget(self.btn_sam_mode)
|
33
39
|
layout.addWidget(self.btn_polygon_mode)
|
34
40
|
layout.addWidget(self.btn_selection_mode)
|
41
|
+
|
35
42
|
layout.addSpacing(20)
|
36
43
|
line1 = QFrame()
|
37
44
|
line1.setFrameShape(QFrame.Shape.HLine)
|
38
45
|
layout.addWidget(line1)
|
39
46
|
layout.addSpacing(10)
|
47
|
+
|
48
|
+
# Action Buttons
|
49
|
+
self.btn_fit_view = QPushButton("Fit View (.)")
|
50
|
+
self.btn_fit_view.setToolTip("Reset image zoom and pan to fit the view (.)")
|
40
51
|
self.btn_clear_points = QPushButton("Clear Clicks (C)")
|
52
|
+
self.btn_clear_points.setToolTip("Clear current temporary points/vertices (C)")
|
53
|
+
layout.addWidget(self.btn_fit_view)
|
41
54
|
layout.addWidget(self.btn_clear_points)
|
55
|
+
|
56
|
+
layout.addSpacing(10)
|
57
|
+
|
58
|
+
# Settings
|
59
|
+
self.chk_auto_save = QCheckBox("Auto-Save on Navigate")
|
60
|
+
self.chk_auto_save.setToolTip(
|
61
|
+
"Automatically save work when using arrow keys to change images."
|
62
|
+
)
|
63
|
+
self.chk_auto_save.setChecked(True)
|
64
|
+
layout.addWidget(self.chk_auto_save)
|
65
|
+
|
42
66
|
layout.addStretch()
|
67
|
+
|
68
|
+
# Notification Label
|
69
|
+
self.notification_label = QLabel("")
|
70
|
+
font = self.notification_label.font()
|
71
|
+
font.setItalic(True)
|
72
|
+
self.notification_label.setFont(font)
|
73
|
+
self.notification_label.setStyleSheet(
|
74
|
+
"color: #ffa500;"
|
75
|
+
) # Orange color for visibility
|
76
|
+
self.notification_label.setWordWrap(True)
|
77
|
+
layout.addWidget(self.notification_label)
|
78
|
+
|
79
|
+
# Device Label
|
43
80
|
self.device_label = QLabel("Device: Unknown")
|
44
81
|
layout.addWidget(self.device_label)
|
45
82
|
self.setFixedWidth(250)
|
@@ -53,6 +90,7 @@ class RightPanel(QWidget):
|
|
53
90
|
# File Explorer
|
54
91
|
file_explorer_layout = QVBoxLayout()
|
55
92
|
self.btn_open_folder = QPushButton("Open Image Folder")
|
93
|
+
self.btn_open_folder.setToolTip("Open a directory of images")
|
56
94
|
self.file_tree = QTreeView()
|
57
95
|
file_explorer_layout.addWidget(self.btn_open_folder)
|
58
96
|
file_explorer_layout.addWidget(self.file_tree)
|
@@ -68,12 +106,13 @@ class RightPanel(QWidget):
|
|
68
106
|
class_filter_layout = QHBoxLayout()
|
69
107
|
class_filter_layout.addWidget(QLabel("Filter Class:"))
|
70
108
|
self.class_filter_combo = QComboBox()
|
109
|
+
self.class_filter_combo.setToolTip("Filter segments list by class")
|
71
110
|
class_filter_layout.addWidget(self.class_filter_combo)
|
72
111
|
segment_layout.addLayout(class_filter_layout)
|
73
112
|
|
74
113
|
self.segment_table = QTableWidget()
|
75
114
|
self.segment_table.setColumnCount(3)
|
76
|
-
self.segment_table.setHorizontalHeaderLabels(["Index", "Class", "Type"])
|
115
|
+
self.segment_table.setHorizontalHeaderLabels(["Index", "Class ID", "Type"])
|
77
116
|
self.segment_table.horizontalHeader().setSectionResizeMode(
|
78
117
|
QHeaderView.ResizeMode.Stretch
|
79
118
|
)
|
@@ -85,7 +124,13 @@ class RightPanel(QWidget):
|
|
85
124
|
|
86
125
|
segment_action_layout = QHBoxLayout()
|
87
126
|
self.btn_merge_selection = QPushButton("Merge to Class")
|
127
|
+
self.btn_merge_selection.setToolTip(
|
128
|
+
"Merge selected segments into a single class (M)"
|
129
|
+
)
|
88
130
|
self.btn_delete_selection = QPushButton("Delete")
|
131
|
+
self.btn_delete_selection.setToolTip(
|
132
|
+
"Delete selected segments (Delete/Backspace)"
|
133
|
+
)
|
89
134
|
segment_action_layout.addWidget(self.btn_merge_selection)
|
90
135
|
segment_action_layout.addWidget(self.btn_delete_selection)
|
91
136
|
segment_layout.addLayout(segment_action_layout)
|
@@ -95,13 +140,23 @@ class RightPanel(QWidget):
|
|
95
140
|
class_layout = QVBoxLayout()
|
96
141
|
class_layout.addWidget(QLabel("Class Order:"))
|
97
142
|
self.class_table = ReorderableClassTable()
|
98
|
-
self.class_table.
|
99
|
-
|
143
|
+
self.class_table.setToolTip(
|
144
|
+
"Set class aliases and drag to reorder channels for saving."
|
145
|
+
)
|
146
|
+
self.class_table.setColumnCount(2)
|
147
|
+
self.class_table.setHorizontalHeaderLabels(["Alias", "Channel Index"])
|
100
148
|
self.class_table.horizontalHeader().setSectionResizeMode(
|
101
|
-
QHeaderView.ResizeMode.Stretch
|
149
|
+
0, QHeaderView.ResizeMode.Stretch
|
102
150
|
)
|
151
|
+
self.class_table.horizontalHeader().setSectionResizeMode(
|
152
|
+
1, QHeaderView.ResizeMode.ResizeToContents
|
153
|
+
)
|
154
|
+
|
103
155
|
class_layout.addWidget(self.class_table)
|
104
156
|
self.btn_reassign_classes = QPushButton("Reassign Class IDs")
|
157
|
+
self.btn_reassign_classes.setToolTip(
|
158
|
+
"Re-index class channels based on the current order in this table"
|
159
|
+
)
|
105
160
|
class_layout.addWidget(self.btn_reassign_classes)
|
106
161
|
layout.addLayout(class_layout, 1)
|
107
162
|
|
@@ -12,7 +12,9 @@ class CustomFileSystemModel(QFileSystemModel):
|
|
12
12
|
self.highlighted_path = None
|
13
13
|
|
14
14
|
def set_highlighted_path(self, path):
|
15
|
-
self.highlighted_path = path
|
15
|
+
self.highlighted_path = os.path.normpath(path) if path else None
|
16
|
+
# Trigger repaint of the entire view
|
17
|
+
self.layoutChanged.emit()
|
16
18
|
|
17
19
|
def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
18
20
|
return 2
|
@@ -39,9 +41,13 @@ class CustomFileSystemModel(QFileSystemModel):
|
|
39
41
|
|
40
42
|
# Handle the temporary highlight for saving
|
41
43
|
if role == Qt.ItemDataRole.BackgroundRole:
|
42
|
-
filePath = self.filePath(index)
|
43
|
-
if
|
44
|
-
|
44
|
+
filePath = os.path.normpath(self.filePath(index))
|
45
|
+
if (
|
46
|
+
self.highlighted_path
|
47
|
+
and os.path.splitext(filePath)[0]
|
48
|
+
== os.path.splitext(self.highlighted_path)[0]
|
49
|
+
):
|
50
|
+
return QBrush(QColor(40, 80, 40)) # Dark green highlight
|
45
51
|
|
46
52
|
if index.column() == 1:
|
47
53
|
if role == Qt.ItemDataRole.CheckStateRole:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from PyQt6.QtWidgets import QGraphicsEllipseItem, QGraphicsItem
|
2
2
|
from PyQt6.QtCore import Qt
|
3
|
-
from PyQt6.QtGui import QBrush, QPen
|
3
|
+
from PyQt6.QtGui import QBrush, QPen, QColor
|
4
4
|
|
5
5
|
|
6
6
|
class EditableVertexItem(QGraphicsEllipseItem):
|
@@ -11,8 +11,12 @@ class EditableVertexItem(QGraphicsEllipseItem):
|
|
11
11
|
self.vertex_index = vertex_index
|
12
12
|
|
13
13
|
self.setZValue(200)
|
14
|
-
|
15
|
-
|
14
|
+
|
15
|
+
color = QColor(Qt.GlobalColor.cyan)
|
16
|
+
color.setAlpha(180)
|
17
|
+
self.setBrush(QBrush(color))
|
18
|
+
|
19
|
+
self.setPen(QPen(Qt.GlobalColor.transparent))
|
16
20
|
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable)
|
17
21
|
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges)
|
18
22
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from PyQt6.QtWidgets import QGraphicsPixmapItem
|
2
|
+
|
3
|
+
|
4
|
+
class HoverablePixmapItem(QGraphicsPixmapItem):
|
5
|
+
def __init__(self, parent=None):
|
6
|
+
super().__init__(parent)
|
7
|
+
self.setAcceptHoverEvents(True)
|
8
|
+
self.default_pixmap = None
|
9
|
+
self.hover_pixmap = None
|
10
|
+
|
11
|
+
def set_pixmaps(self, default_pixmap, hover_pixmap):
|
12
|
+
self.default_pixmap = default_pixmap
|
13
|
+
self.hover_pixmap = hover_pixmap
|
14
|
+
self.setPixmap(self.default_pixmap)
|
15
|
+
|
16
|
+
def hoverEnterEvent(self, event):
|
17
|
+
self.setPixmap(self.hover_pixmap)
|
18
|
+
super().hoverEnterEvent(event)
|
19
|
+
|
20
|
+
def hoverLeaveEvent(self, event):
|
21
|
+
self.setPixmap(self.default_pixmap)
|
22
|
+
super().hoverLeaveEvent(event)
|