lazylabel-gui 1.1.5__py3-none-any.whl → 1.1.7__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/__main__.py +6 -0
- lazylabel/config/settings.py +72 -65
- lazylabel/core/file_manager.py +123 -122
- lazylabel/core/model_manager.py +96 -95
- lazylabel/main.py +39 -37
- lazylabel/models/sam_model.py +211 -200
- lazylabel/ui/control_panel.py +283 -245
- lazylabel/ui/hotkey_dialog.py +417 -416
- lazylabel/ui/main_window.py +2020 -1882
- lazylabel/ui/photo_viewer.py +55 -1
- lazylabel/ui/widgets/adjustments_widget.py +372 -108
- lazylabel/ui/widgets/settings_widget.py +125 -113
- lazylabel/utils/logger.py +45 -0
- lazylabel_gui-1.1.7.dist-info/METADATA +240 -0
- {lazylabel_gui-1.1.5.dist-info → lazylabel_gui-1.1.7.dist-info}/RECORD +19 -17
- lazylabel_gui-1.1.5.dist-info/METADATA +0 -243
- {lazylabel_gui-1.1.5.dist-info → lazylabel_gui-1.1.7.dist-info}/WHEEL +0 -0
- {lazylabel_gui-1.1.5.dist-info → lazylabel_gui-1.1.7.dist-info}/entry_points.txt +0 -0
- {lazylabel_gui-1.1.5.dist-info → lazylabel_gui-1.1.7.dist-info}/licenses/LICENSE +0 -0
- {lazylabel_gui-1.1.5.dist-info → lazylabel_gui-1.1.7.dist-info}/top_level.txt +0 -0
lazylabel/ui/control_panel.py
CHANGED
@@ -1,245 +1,283 @@
|
|
1
|
-
"""Left control panel with mode controls and settings."""
|
2
|
-
|
3
|
-
from PyQt6.QtCore import Qt, pyqtSignal
|
4
|
-
from PyQt6.QtWidgets import (
|
5
|
-
QFrame,
|
6
|
-
QHBoxLayout,
|
7
|
-
QLabel,
|
8
|
-
QPushButton,
|
9
|
-
QVBoxLayout,
|
10
|
-
QWidget,
|
11
|
-
)
|
12
|
-
|
13
|
-
from .widgets import AdjustmentsWidget, ModelSelectionWidget, SettingsWidget
|
14
|
-
|
15
|
-
|
16
|
-
class ControlPanel(QWidget):
|
17
|
-
"""Left control panel with mode controls and settings."""
|
18
|
-
|
19
|
-
# Signals
|
20
|
-
sam_mode_requested = pyqtSignal()
|
21
|
-
polygon_mode_requested = pyqtSignal()
|
22
|
-
bbox_mode_requested = pyqtSignal() # New signal for bounding box mode
|
23
|
-
selection_mode_requested = pyqtSignal()
|
24
|
-
clear_points_requested = pyqtSignal()
|
25
|
-
fit_view_requested = pyqtSignal()
|
26
|
-
browse_models_requested = pyqtSignal()
|
27
|
-
refresh_models_requested = pyqtSignal()
|
28
|
-
model_selected = pyqtSignal(str)
|
29
|
-
annotation_size_changed = pyqtSignal(int)
|
30
|
-
pan_speed_changed = pyqtSignal(int)
|
31
|
-
join_threshold_changed = pyqtSignal(int)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
toggle_layout
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
self.
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
main_layout.
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
main_layout.
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
main_layout.addSpacing(10)
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
main_layout.
|
101
|
-
|
102
|
-
#
|
103
|
-
self.
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
self.
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
self.
|
122
|
-
self.
|
123
|
-
|
124
|
-
self.
|
125
|
-
self.
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
self.
|
141
|
-
self.
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
def
|
154
|
-
"""
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
self.
|
162
|
-
self.
|
163
|
-
|
164
|
-
|
165
|
-
self.
|
166
|
-
self.
|
167
|
-
self.
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
)
|
173
|
-
self.
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
self.
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
def
|
207
|
-
"""
|
208
|
-
self.
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
1
|
+
"""Left control panel with mode controls and settings."""
|
2
|
+
|
3
|
+
from PyQt6.QtCore import Qt, pyqtSignal
|
4
|
+
from PyQt6.QtWidgets import (
|
5
|
+
QFrame,
|
6
|
+
QHBoxLayout,
|
7
|
+
QLabel,
|
8
|
+
QPushButton,
|
9
|
+
QVBoxLayout,
|
10
|
+
QWidget,
|
11
|
+
)
|
12
|
+
|
13
|
+
from .widgets import AdjustmentsWidget, ModelSelectionWidget, SettingsWidget
|
14
|
+
|
15
|
+
|
16
|
+
class ControlPanel(QWidget):
|
17
|
+
"""Left control panel with mode controls and settings."""
|
18
|
+
|
19
|
+
# Signals
|
20
|
+
sam_mode_requested = pyqtSignal()
|
21
|
+
polygon_mode_requested = pyqtSignal()
|
22
|
+
bbox_mode_requested = pyqtSignal() # New signal for bounding box mode
|
23
|
+
selection_mode_requested = pyqtSignal()
|
24
|
+
clear_points_requested = pyqtSignal()
|
25
|
+
fit_view_requested = pyqtSignal()
|
26
|
+
browse_models_requested = pyqtSignal()
|
27
|
+
refresh_models_requested = pyqtSignal()
|
28
|
+
model_selected = pyqtSignal(str)
|
29
|
+
annotation_size_changed = pyqtSignal(int)
|
30
|
+
pan_speed_changed = pyqtSignal(int)
|
31
|
+
join_threshold_changed = pyqtSignal(int)
|
32
|
+
fragment_threshold_changed = pyqtSignal(int)
|
33
|
+
brightness_changed = pyqtSignal(int)
|
34
|
+
contrast_changed = pyqtSignal(int)
|
35
|
+
gamma_changed = pyqtSignal(int)
|
36
|
+
reset_adjustments_requested = pyqtSignal()
|
37
|
+
image_adjustment_changed = pyqtSignal()
|
38
|
+
hotkeys_requested = pyqtSignal()
|
39
|
+
pop_out_requested = pyqtSignal()
|
40
|
+
|
41
|
+
def __init__(self, parent=None):
|
42
|
+
super().__init__(parent)
|
43
|
+
self.setMinimumWidth(50) # Allow collapsing but maintain minimum
|
44
|
+
self.preferred_width = 250 # Store preferred width for expansion
|
45
|
+
self._setup_ui()
|
46
|
+
self._connect_signals()
|
47
|
+
|
48
|
+
def _setup_ui(self):
|
49
|
+
"""Setup the UI layout."""
|
50
|
+
layout = QVBoxLayout(self)
|
51
|
+
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
52
|
+
|
53
|
+
# Top button row
|
54
|
+
toggle_layout = QHBoxLayout()
|
55
|
+
toggle_layout.addStretch()
|
56
|
+
|
57
|
+
self.btn_popout = QPushButton("⋯")
|
58
|
+
self.btn_popout.setToolTip("Pop out panel to separate window")
|
59
|
+
self.btn_popout.setMaximumWidth(30)
|
60
|
+
toggle_layout.addWidget(self.btn_popout)
|
61
|
+
|
62
|
+
layout.addLayout(toggle_layout)
|
63
|
+
|
64
|
+
# Main controls widget
|
65
|
+
self.main_controls_widget = QWidget()
|
66
|
+
main_layout = QVBoxLayout(self.main_controls_widget)
|
67
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
68
|
+
|
69
|
+
# Mode label
|
70
|
+
self.mode_label = QLabel("Mode: Points")
|
71
|
+
font = self.mode_label.font()
|
72
|
+
font.setPointSize(14)
|
73
|
+
font.setBold(True)
|
74
|
+
self.mode_label.setFont(font)
|
75
|
+
main_layout.addWidget(self.mode_label)
|
76
|
+
|
77
|
+
# Mode buttons
|
78
|
+
self._add_mode_buttons(main_layout)
|
79
|
+
|
80
|
+
# Separator
|
81
|
+
main_layout.addSpacing(20)
|
82
|
+
main_layout.addWidget(self._create_separator())
|
83
|
+
main_layout.addSpacing(10)
|
84
|
+
|
85
|
+
# Model selection
|
86
|
+
self.model_widget = ModelSelectionWidget()
|
87
|
+
main_layout.addWidget(self.model_widget)
|
88
|
+
|
89
|
+
# Separator
|
90
|
+
main_layout.addSpacing(10)
|
91
|
+
main_layout.addWidget(self._create_separator())
|
92
|
+
main_layout.addSpacing(10)
|
93
|
+
|
94
|
+
# Action buttons
|
95
|
+
self._add_action_buttons(main_layout)
|
96
|
+
main_layout.addSpacing(10)
|
97
|
+
|
98
|
+
# Settings
|
99
|
+
self.settings_widget = SettingsWidget()
|
100
|
+
main_layout.addWidget(self.settings_widget)
|
101
|
+
|
102
|
+
# Adjustments
|
103
|
+
self.adjustments_widget = AdjustmentsWidget()
|
104
|
+
main_layout.addWidget(self.adjustments_widget)
|
105
|
+
|
106
|
+
main_layout.addStretch()
|
107
|
+
|
108
|
+
# Status labels
|
109
|
+
self.notification_label = QLabel("")
|
110
|
+
font = self.notification_label.font()
|
111
|
+
font.setItalic(True)
|
112
|
+
self.notification_label.setFont(font)
|
113
|
+
self.notification_label.setStyleSheet("color: #ffa500;")
|
114
|
+
self.notification_label.setWordWrap(True)
|
115
|
+
main_layout.addWidget(self.notification_label)
|
116
|
+
|
117
|
+
layout.addWidget(self.main_controls_widget)
|
118
|
+
|
119
|
+
def _add_mode_buttons(self, layout):
|
120
|
+
"""Add mode control buttons."""
|
121
|
+
self.btn_sam_mode = QPushButton("Point Mode (1)")
|
122
|
+
self.btn_sam_mode.setToolTip("Switch to Point Mode for AI segmentation (1)")
|
123
|
+
|
124
|
+
self.btn_polygon_mode = QPushButton("Polygon Mode (2)")
|
125
|
+
self.btn_polygon_mode.setToolTip("Switch to Polygon Drawing Mode (2)")
|
126
|
+
|
127
|
+
self.btn_bbox_mode = QPushButton("BBox Mode (3)")
|
128
|
+
self.btn_bbox_mode.setToolTip("Switch to Bounding Box Drawing Mode (3)")
|
129
|
+
|
130
|
+
self.btn_selection_mode = QPushButton("Selection Mode (E)")
|
131
|
+
self.btn_selection_mode.setToolTip("Toggle segment selection (E)")
|
132
|
+
|
133
|
+
layout.addWidget(self.btn_sam_mode)
|
134
|
+
layout.addWidget(self.btn_polygon_mode)
|
135
|
+
layout.addWidget(self.btn_bbox_mode)
|
136
|
+
layout.addWidget(self.btn_selection_mode)
|
137
|
+
|
138
|
+
def _add_action_buttons(self, layout):
|
139
|
+
"""Add action buttons."""
|
140
|
+
self.btn_fit_view = QPushButton("Fit View (.)")
|
141
|
+
self.btn_fit_view.setToolTip("Reset image zoom and pan to fit the view (.)")
|
142
|
+
|
143
|
+
self.btn_clear_points = QPushButton("Clear Clicks (C)")
|
144
|
+
self.btn_clear_points.setToolTip("Clear current temporary points/vertices (C)")
|
145
|
+
|
146
|
+
self.btn_hotkeys = QPushButton("Hotkeys")
|
147
|
+
self.btn_hotkeys.setToolTip("Configure keyboard shortcuts")
|
148
|
+
|
149
|
+
layout.addWidget(self.btn_fit_view)
|
150
|
+
layout.addWidget(self.btn_clear_points)
|
151
|
+
layout.addWidget(self.btn_hotkeys)
|
152
|
+
|
153
|
+
def _create_separator(self):
|
154
|
+
"""Create a horizontal separator line."""
|
155
|
+
line = QFrame()
|
156
|
+
line.setFrameShape(QFrame.Shape.HLine)
|
157
|
+
return line
|
158
|
+
|
159
|
+
def _connect_signals(self):
|
160
|
+
"""Connect internal signals."""
|
161
|
+
self.btn_sam_mode.clicked.connect(self.sam_mode_requested)
|
162
|
+
self.btn_polygon_mode.clicked.connect(self.polygon_mode_requested)
|
163
|
+
self.btn_bbox_mode.clicked.connect(self.bbox_mode_requested)
|
164
|
+
self.btn_selection_mode.clicked.connect(self.selection_mode_requested)
|
165
|
+
self.btn_clear_points.clicked.connect(self.clear_points_requested)
|
166
|
+
self.btn_fit_view.clicked.connect(self.fit_view_requested)
|
167
|
+
self.btn_hotkeys.clicked.connect(self.hotkeys_requested)
|
168
|
+
self.btn_popout.clicked.connect(self.pop_out_requested)
|
169
|
+
|
170
|
+
# Model widget signals
|
171
|
+
self.model_widget.browse_requested.connect(self.browse_models_requested)
|
172
|
+
self.model_widget.refresh_requested.connect(self.refresh_models_requested)
|
173
|
+
self.model_widget.model_selected.connect(self.model_selected)
|
174
|
+
|
175
|
+
# Adjustments widget signals
|
176
|
+
self.adjustments_widget.annotation_size_changed.connect(
|
177
|
+
self.annotation_size_changed
|
178
|
+
)
|
179
|
+
self.adjustments_widget.pan_speed_changed.connect(self.pan_speed_changed)
|
180
|
+
self.adjustments_widget.join_threshold_changed.connect(
|
181
|
+
self.join_threshold_changed
|
182
|
+
)
|
183
|
+
self.adjustments_widget.fragment_threshold_changed.connect(
|
184
|
+
self.fragment_threshold_changed
|
185
|
+
)
|
186
|
+
self.adjustments_widget.brightness_changed.connect(self.brightness_changed)
|
187
|
+
self.adjustments_widget.contrast_changed.connect(self.contrast_changed)
|
188
|
+
self.adjustments_widget.gamma_changed.connect(self.gamma_changed)
|
189
|
+
self.adjustments_widget.reset_requested.connect(
|
190
|
+
self.reset_adjustments_requested
|
191
|
+
)
|
192
|
+
self.adjustments_widget.image_adjustment_changed.connect(
|
193
|
+
self.image_adjustment_changed
|
194
|
+
)
|
195
|
+
|
196
|
+
def mouseDoubleClickEvent(self, event):
|
197
|
+
"""Handle double-click to expand collapsed panel."""
|
198
|
+
if (
|
199
|
+
self.width() < 50
|
200
|
+
and self.parent()
|
201
|
+
and hasattr(self.parent(), "_expand_left_panel")
|
202
|
+
):
|
203
|
+
self.parent()._expand_left_panel()
|
204
|
+
super().mouseDoubleClickEvent(event)
|
205
|
+
|
206
|
+
def show_notification(self, message: str, duration: int = 3000):
|
207
|
+
"""Show a notification message."""
|
208
|
+
self.notification_label.setText(message)
|
209
|
+
# Note: Timer should be handled by the caller
|
210
|
+
|
211
|
+
def clear_notification(self):
|
212
|
+
"""Clear the notification message."""
|
213
|
+
self.notification_label.clear()
|
214
|
+
|
215
|
+
def set_mode_text(self, mode: str):
|
216
|
+
"""Set the mode label text."""
|
217
|
+
self.mode_label.setText(f"Mode: {mode.replace('_', ' ').title()}")
|
218
|
+
|
219
|
+
# Delegate methods for sub-widgets
|
220
|
+
def populate_models(self, models):
|
221
|
+
"""Populate the models combo box."""
|
222
|
+
self.model_widget.populate_models(models)
|
223
|
+
|
224
|
+
def set_current_model(self, model_name):
|
225
|
+
"""Set the current model display."""
|
226
|
+
self.model_widget.set_current_model(model_name)
|
227
|
+
|
228
|
+
def get_settings(self):
|
229
|
+
"""Get current settings from the settings widget."""
|
230
|
+
return self.settings_widget.get_settings()
|
231
|
+
|
232
|
+
def set_settings(self, settings):
|
233
|
+
"""Set settings in the settings widget."""
|
234
|
+
self.settings_widget.set_settings(settings)
|
235
|
+
|
236
|
+
def get_annotation_size(self):
|
237
|
+
"""Get current annotation size."""
|
238
|
+
return self.adjustments_widget.get_annotation_size()
|
239
|
+
|
240
|
+
def set_annotation_size(self, value):
|
241
|
+
"""Set annotation size."""
|
242
|
+
self.adjustments_widget.set_annotation_size(value)
|
243
|
+
|
244
|
+
def set_pan_speed(self, value):
|
245
|
+
"""Set pan speed."""
|
246
|
+
self.adjustments_widget.set_pan_speed(value)
|
247
|
+
|
248
|
+
def set_join_threshold(self, value):
|
249
|
+
"""Set join threshold."""
|
250
|
+
self.adjustments_widget.set_join_threshold(value)
|
251
|
+
|
252
|
+
def set_fragment_threshold(self, value):
|
253
|
+
"""Set fragment threshold."""
|
254
|
+
self.adjustments_widget.set_fragment_threshold(value)
|
255
|
+
|
256
|
+
def set_brightness(self, value):
|
257
|
+
"""Set brightness."""
|
258
|
+
self.adjustments_widget.set_brightness(value)
|
259
|
+
|
260
|
+
def set_contrast(self, value):
|
261
|
+
"""Set contrast."""
|
262
|
+
self.adjustments_widget.set_contrast(value)
|
263
|
+
|
264
|
+
def set_gamma(self, value):
|
265
|
+
"""Set gamma."""
|
266
|
+
self.adjustments_widget.set_gamma(value)
|
267
|
+
|
268
|
+
def set_sam_mode_enabled(self, enabled: bool):
|
269
|
+
"""Enable or disable the SAM mode button."""
|
270
|
+
self.btn_sam_mode.setEnabled(enabled)
|
271
|
+
if not enabled:
|
272
|
+
self.btn_sam_mode.setToolTip("Point Mode (SAM model not available)")
|
273
|
+
else:
|
274
|
+
self.btn_sam_mode.setToolTip("Switch to Point Mode for AI segmentation (1)")
|
275
|
+
|
276
|
+
def set_popout_mode(self, is_popped_out: bool):
|
277
|
+
"""Update the pop-out button based on panel state."""
|
278
|
+
if is_popped_out:
|
279
|
+
self.btn_popout.setText("⇤")
|
280
|
+
self.btn_popout.setToolTip("Return panel to main window")
|
281
|
+
else:
|
282
|
+
self.btn_popout.setText("⋯")
|
283
|
+
self.btn_popout.setToolTip("Pop out panel to separate window")
|