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