lazylabel-gui 1.1.7__py3-none-any.whl → 1.1.8__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/file_manager.py +44 -1
- lazylabel/core/model_manager.py +12 -0
- lazylabel/ui/control_panel.py +676 -98
- lazylabel/ui/main_window.py +995 -8
- lazylabel/ui/widgets/__init__.py +13 -2
- lazylabel/ui/widgets/adjustments_widget.py +23 -40
- lazylabel/ui/widgets/border_crop_widget.py +210 -0
- lazylabel/ui/widgets/channel_threshold_widget.py +500 -0
- lazylabel/ui/widgets/fragment_threshold_widget.py +97 -0
- lazylabel/ui/widgets/model_selection_widget.py +26 -0
- {lazylabel_gui-1.1.7.dist-info → lazylabel_gui-1.1.8.dist-info}/METADATA +1 -1
- {lazylabel_gui-1.1.7.dist-info → lazylabel_gui-1.1.8.dist-info}/RECORD +16 -13
- {lazylabel_gui-1.1.7.dist-info → lazylabel_gui-1.1.8.dist-info}/WHEEL +0 -0
- {lazylabel_gui-1.1.7.dist-info → lazylabel_gui-1.1.8.dist-info}/entry_points.txt +0 -0
- {lazylabel_gui-1.1.7.dist-info → lazylabel_gui-1.1.8.dist-info}/licenses/LICENSE +0 -0
- {lazylabel_gui-1.1.7.dist-info → lazylabel_gui-1.1.8.dist-info}/top_level.txt +0 -0
lazylabel/ui/widgets/__init__.py
CHANGED
@@ -1,8 +1,19 @@
|
|
1
|
-
"""
|
1
|
+
"""Widget package initialization."""
|
2
2
|
|
3
3
|
from .adjustments_widget import AdjustmentsWidget
|
4
|
+
from .border_crop_widget import BorderCropWidget
|
5
|
+
from .channel_threshold_widget import ChannelThresholdWidget
|
6
|
+
from .fragment_threshold_widget import FragmentThresholdWidget
|
4
7
|
from .model_selection_widget import ModelSelectionWidget
|
5
8
|
from .settings_widget import SettingsWidget
|
6
9
|
from .status_bar import StatusBar
|
7
10
|
|
8
|
-
__all__ = [
|
11
|
+
__all__ = [
|
12
|
+
"AdjustmentsWidget",
|
13
|
+
"BorderCropWidget",
|
14
|
+
"ChannelThresholdWidget",
|
15
|
+
"FragmentThresholdWidget",
|
16
|
+
"ModelSelectionWidget",
|
17
|
+
"SettingsWidget",
|
18
|
+
"StatusBar",
|
19
|
+
]
|
@@ -19,7 +19,6 @@ class AdjustmentsWidget(QWidget):
|
|
19
19
|
annotation_size_changed = pyqtSignal(int)
|
20
20
|
pan_speed_changed = pyqtSignal(int)
|
21
21
|
join_threshold_changed = pyqtSignal(int)
|
22
|
-
fragment_threshold_changed = pyqtSignal(int)
|
23
22
|
brightness_changed = pyqtSignal(int)
|
24
23
|
contrast_changed = pyqtSignal(int)
|
25
24
|
gamma_changed = pyqtSignal(int)
|
@@ -101,17 +100,6 @@ class AdjustmentsWidget(QWidget):
|
|
101
100
|
)
|
102
101
|
layout.addLayout(join_row)
|
103
102
|
|
104
|
-
# Fragment threshold
|
105
|
-
fragment_row, self.fragment_label, self.fragment_edit, self.fragment_slider = (
|
106
|
-
create_slider_row(
|
107
|
-
"Fragment:",
|
108
|
-
"0",
|
109
|
-
(0, 100),
|
110
|
-
"Filter out small AI segments. 0=no filtering, 50=drop <50% of largest, 100=only keep largest",
|
111
|
-
)
|
112
|
-
)
|
113
|
-
layout.addLayout(fragment_row)
|
114
|
-
|
115
103
|
# Add separator for image adjustments
|
116
104
|
layout.addSpacing(8)
|
117
105
|
|
@@ -147,6 +135,8 @@ class AdjustmentsWidget(QWidget):
|
|
147
135
|
self.btn_reset.setToolTip(
|
148
136
|
"Reset all image adjustment and annotation size settings to their default values."
|
149
137
|
)
|
138
|
+
self.btn_reset.setMinimumHeight(28)
|
139
|
+
self.btn_reset.setStyleSheet(self._get_button_style())
|
150
140
|
main_layout.addWidget(self.btn_reset)
|
151
141
|
|
152
142
|
def _connect_signals(self):
|
@@ -157,8 +147,6 @@ class AdjustmentsWidget(QWidget):
|
|
157
147
|
self.pan_edit.editingFinished.connect(self._on_pan_edit_finished)
|
158
148
|
self.join_slider.valueChanged.connect(self._on_join_slider_changed)
|
159
149
|
self.join_edit.editingFinished.connect(self._on_join_edit_finished)
|
160
|
-
self.fragment_slider.valueChanged.connect(self._on_fragment_slider_changed)
|
161
|
-
self.fragment_edit.editingFinished.connect(self._on_fragment_edit_finished)
|
162
150
|
self.brightness_slider.valueChanged.connect(self._on_brightness_slider_changed)
|
163
151
|
self.brightness_slider.sliderReleased.connect(
|
164
152
|
self._on_image_adjustment_slider_released
|
@@ -222,20 +210,6 @@ class AdjustmentsWidget(QWidget):
|
|
222
210
|
except ValueError:
|
223
211
|
self.join_edit.setText(f"{self.join_slider.value()}")
|
224
212
|
|
225
|
-
def _on_fragment_slider_changed(self, value):
|
226
|
-
"""Handle fragment threshold slider change."""
|
227
|
-
self.fragment_edit.setText(f"{value}")
|
228
|
-
self.fragment_threshold_changed.emit(value)
|
229
|
-
|
230
|
-
def _on_fragment_edit_finished(self):
|
231
|
-
try:
|
232
|
-
value = int(self.fragment_edit.text())
|
233
|
-
slider_value = max(0, min(100, value))
|
234
|
-
self.fragment_slider.setValue(slider_value)
|
235
|
-
self.fragment_threshold_changed.emit(slider_value)
|
236
|
-
except ValueError:
|
237
|
-
self.fragment_edit.setText(f"{self.fragment_slider.value()}")
|
238
|
-
|
239
213
|
def _on_brightness_slider_changed(self, value):
|
240
214
|
"""Handle brightness slider change."""
|
241
215
|
self.brightness_edit.setText(f"{value}")
|
@@ -316,17 +290,6 @@ class AdjustmentsWidget(QWidget):
|
|
316
290
|
self.join_slider.setValue(value)
|
317
291
|
self.join_edit.setText(f"{value}")
|
318
292
|
|
319
|
-
def get_fragment_threshold(self):
|
320
|
-
"""Get current fragment threshold value."""
|
321
|
-
|
322
|
-
return self.fragment_slider.value()
|
323
|
-
|
324
|
-
def set_fragment_threshold(self, value):
|
325
|
-
"""Set fragment threshold value."""
|
326
|
-
|
327
|
-
self.fragment_slider.setValue(value)
|
328
|
-
self.fragment_edit.setText(f"{value}")
|
329
|
-
|
330
293
|
def get_brightness(self):
|
331
294
|
"""Get current brightness value."""
|
332
295
|
|
@@ -366,7 +329,27 @@ class AdjustmentsWidget(QWidget):
|
|
366
329
|
self.set_annotation_size(10) # Default value
|
367
330
|
self.set_pan_speed(10) # Default value
|
368
331
|
self.set_join_threshold(2) # Default value
|
369
|
-
self.set_fragment_threshold(0) # Default value
|
370
332
|
self.set_brightness(0) # Default value
|
371
333
|
self.set_contrast(0) # Default value
|
372
334
|
self.set_gamma(100) # Default value (1.0)
|
335
|
+
|
336
|
+
def _get_button_style(self):
|
337
|
+
"""Get consistent button styling."""
|
338
|
+
return """
|
339
|
+
QPushButton {
|
340
|
+
background-color: rgba(70, 100, 130, 0.8);
|
341
|
+
border: 1px solid rgba(90, 120, 150, 0.8);
|
342
|
+
border-radius: 6px;
|
343
|
+
color: #E0E0E0;
|
344
|
+
font-weight: bold;
|
345
|
+
font-size: 10px;
|
346
|
+
padding: 4px 8px;
|
347
|
+
}
|
348
|
+
QPushButton:hover {
|
349
|
+
background-color: rgba(90, 120, 150, 0.9);
|
350
|
+
border-color: rgba(110, 140, 170, 0.9);
|
351
|
+
}
|
352
|
+
QPushButton:pressed {
|
353
|
+
background-color: rgba(50, 80, 110, 0.9);
|
354
|
+
}
|
355
|
+
"""
|
@@ -0,0 +1,210 @@
|
|
1
|
+
"""Border crop widget for defining crop areas."""
|
2
|
+
|
3
|
+
from PyQt6.QtCore import Qt, pyqtSignal
|
4
|
+
from PyQt6.QtWidgets import (
|
5
|
+
QGroupBox,
|
6
|
+
QHBoxLayout,
|
7
|
+
QLabel,
|
8
|
+
QLineEdit,
|
9
|
+
QPushButton,
|
10
|
+
QVBoxLayout,
|
11
|
+
QWidget,
|
12
|
+
)
|
13
|
+
|
14
|
+
|
15
|
+
class BorderCropWidget(QWidget):
|
16
|
+
"""Widget for border crop controls."""
|
17
|
+
|
18
|
+
# Signals
|
19
|
+
crop_draw_requested = pyqtSignal()
|
20
|
+
crop_clear_requested = pyqtSignal()
|
21
|
+
crop_applied = pyqtSignal(int, int, int, int) # x1, y1, x2, y2
|
22
|
+
|
23
|
+
def __init__(self, parent=None):
|
24
|
+
super().__init__(parent)
|
25
|
+
self._setup_ui()
|
26
|
+
self._connect_signals()
|
27
|
+
|
28
|
+
def _setup_ui(self):
|
29
|
+
"""Setup the UI layout."""
|
30
|
+
group = QGroupBox("Border Crop")
|
31
|
+
layout = QVBoxLayout(group)
|
32
|
+
layout.setSpacing(8)
|
33
|
+
|
34
|
+
# X coordinates input
|
35
|
+
x_layout = QHBoxLayout()
|
36
|
+
x_label = QLabel("X:")
|
37
|
+
x_label.setFixedWidth(15)
|
38
|
+
self.x_edit = QLineEdit()
|
39
|
+
self.x_edit.setPlaceholderText("start:end (e.g., 20:460)")
|
40
|
+
self.x_edit.setToolTip("X coordinate range in format start:end")
|
41
|
+
x_layout.addWidget(x_label)
|
42
|
+
x_layout.addWidget(self.x_edit)
|
43
|
+
layout.addLayout(x_layout)
|
44
|
+
|
45
|
+
# Y coordinates input
|
46
|
+
y_layout = QHBoxLayout()
|
47
|
+
y_label = QLabel("Y:")
|
48
|
+
y_label.setFixedWidth(15)
|
49
|
+
self.y_edit = QLineEdit()
|
50
|
+
self.y_edit.setPlaceholderText("start:end (e.g., 20:460)")
|
51
|
+
self.y_edit.setToolTip("Y coordinate range in format start:end")
|
52
|
+
y_layout.addWidget(y_label)
|
53
|
+
y_layout.addWidget(self.y_edit)
|
54
|
+
layout.addLayout(y_layout)
|
55
|
+
|
56
|
+
# Button row
|
57
|
+
button_layout = QHBoxLayout()
|
58
|
+
|
59
|
+
# Draw button with square icon
|
60
|
+
self.btn_draw = QPushButton("⬚")
|
61
|
+
self.btn_draw.setToolTip("Draw crop rectangle")
|
62
|
+
self.btn_draw.setFixedWidth(30)
|
63
|
+
self.btn_draw.setFixedHeight(28)
|
64
|
+
self.btn_draw.setStyleSheet(self._get_button_style())
|
65
|
+
|
66
|
+
# Clear button
|
67
|
+
self.btn_clear = QPushButton("✕")
|
68
|
+
self.btn_clear.setToolTip("Clear crop")
|
69
|
+
self.btn_clear.setFixedWidth(30)
|
70
|
+
self.btn_clear.setFixedHeight(28)
|
71
|
+
self.btn_clear.setStyleSheet(self._get_button_style())
|
72
|
+
|
73
|
+
# Apply button
|
74
|
+
self.btn_apply = QPushButton("Apply")
|
75
|
+
self.btn_apply.setToolTip("Apply crop from coordinates")
|
76
|
+
self.btn_apply.setMinimumHeight(28)
|
77
|
+
self.btn_apply.setStyleSheet(self._get_button_style())
|
78
|
+
|
79
|
+
button_layout.addWidget(self.btn_draw)
|
80
|
+
button_layout.addWidget(self.btn_clear)
|
81
|
+
button_layout.addWidget(self.btn_apply)
|
82
|
+
|
83
|
+
layout.addLayout(button_layout)
|
84
|
+
|
85
|
+
# Status label
|
86
|
+
self.status_label = QLabel("")
|
87
|
+
self.status_label.setStyleSheet("color: #888; font-size: 10px;")
|
88
|
+
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
89
|
+
layout.addWidget(self.status_label)
|
90
|
+
|
91
|
+
# Main layout
|
92
|
+
main_layout = QVBoxLayout(self)
|
93
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
94
|
+
main_layout.addWidget(group)
|
95
|
+
|
96
|
+
def _connect_signals(self):
|
97
|
+
"""Connect internal signals."""
|
98
|
+
self.btn_draw.clicked.connect(self.crop_draw_requested)
|
99
|
+
self.btn_clear.clicked.connect(self.crop_clear_requested)
|
100
|
+
self.btn_apply.clicked.connect(self._apply_crop_from_text)
|
101
|
+
self.x_edit.returnPressed.connect(self._apply_crop_from_text)
|
102
|
+
self.y_edit.returnPressed.connect(self._apply_crop_from_text)
|
103
|
+
|
104
|
+
def _apply_crop_from_text(self):
|
105
|
+
"""Apply crop from text input."""
|
106
|
+
try:
|
107
|
+
x_text = self.x_edit.text().strip()
|
108
|
+
y_text = self.y_edit.text().strip()
|
109
|
+
|
110
|
+
if not x_text or not y_text:
|
111
|
+
self.set_status("Enter both X and Y coordinates")
|
112
|
+
return
|
113
|
+
|
114
|
+
# Parse X coordinates
|
115
|
+
x_parts = x_text.split(":")
|
116
|
+
if len(x_parts) != 2:
|
117
|
+
self.set_status("Invalid X format. Use start:end")
|
118
|
+
return
|
119
|
+
x1, x2 = int(x_parts[0]), int(x_parts[1])
|
120
|
+
|
121
|
+
# Parse Y coordinates
|
122
|
+
y_parts = y_text.split(":")
|
123
|
+
if len(y_parts) != 2:
|
124
|
+
self.set_status("Invalid Y format. Use start:end")
|
125
|
+
return
|
126
|
+
y1, y2 = int(y_parts[0]), int(y_parts[1])
|
127
|
+
|
128
|
+
# Ensure proper ordering
|
129
|
+
if x1 > x2:
|
130
|
+
x1, x2 = x2, x1
|
131
|
+
if y1 > y2:
|
132
|
+
y1, y2 = y2, y1
|
133
|
+
|
134
|
+
# Emit signal with coordinates
|
135
|
+
self.crop_applied.emit(x1, y1, x2, y2)
|
136
|
+
self.set_status(f"Crop: {x1}:{x2}, {y1}:{y2}")
|
137
|
+
|
138
|
+
except ValueError:
|
139
|
+
self.set_status("Invalid coordinates. Use numbers only.")
|
140
|
+
except Exception as e:
|
141
|
+
self.set_status(f"Error: {str(e)}")
|
142
|
+
|
143
|
+
def set_crop_coordinates(self, x1, y1, x2, y2):
|
144
|
+
"""Set crop coordinates in the text fields."""
|
145
|
+
self.x_edit.setText(f"{x1}:{x2}")
|
146
|
+
self.y_edit.setText(f"{y1}:{y2}")
|
147
|
+
self.set_status(f"Crop: {x1}:{x2}, {y1}:{y2}")
|
148
|
+
|
149
|
+
def clear_crop_coordinates(self):
|
150
|
+
"""Clear crop coordinates."""
|
151
|
+
self.x_edit.clear()
|
152
|
+
self.y_edit.clear()
|
153
|
+
self.set_status("")
|
154
|
+
|
155
|
+
def set_status(self, message):
|
156
|
+
"""Set status message."""
|
157
|
+
self.status_label.setText(message)
|
158
|
+
|
159
|
+
def get_crop_coordinates(self):
|
160
|
+
"""Get current crop coordinates if valid."""
|
161
|
+
try:
|
162
|
+
x_text = self.x_edit.text().strip()
|
163
|
+
y_text = self.y_edit.text().strip()
|
164
|
+
|
165
|
+
if not x_text or not y_text:
|
166
|
+
return None
|
167
|
+
|
168
|
+
x_parts = x_text.split(":")
|
169
|
+
y_parts = y_text.split(":")
|
170
|
+
|
171
|
+
if len(x_parts) != 2 or len(y_parts) != 2:
|
172
|
+
return None
|
173
|
+
|
174
|
+
x1, x2 = int(x_parts[0]), int(x_parts[1])
|
175
|
+
y1, y2 = int(y_parts[0]), int(y_parts[1])
|
176
|
+
|
177
|
+
# Ensure proper ordering
|
178
|
+
if x1 > x2:
|
179
|
+
x1, x2 = x2, x1
|
180
|
+
if y1 > y2:
|
181
|
+
y1, y2 = y2, y1
|
182
|
+
|
183
|
+
return (x1, y1, x2, y2)
|
184
|
+
except (ValueError, IndexError):
|
185
|
+
return None
|
186
|
+
|
187
|
+
def has_crop(self):
|
188
|
+
"""Check if crop coordinates are set."""
|
189
|
+
return self.get_crop_coordinates() is not None
|
190
|
+
|
191
|
+
def _get_button_style(self):
|
192
|
+
"""Get consistent button styling."""
|
193
|
+
return """
|
194
|
+
QPushButton {
|
195
|
+
background-color: rgba(70, 100, 130, 0.8);
|
196
|
+
border: 1px solid rgba(90, 120, 150, 0.8);
|
197
|
+
border-radius: 6px;
|
198
|
+
color: #E0E0E0;
|
199
|
+
font-weight: bold;
|
200
|
+
font-size: 10px;
|
201
|
+
padding: 4px 8px;
|
202
|
+
}
|
203
|
+
QPushButton:hover {
|
204
|
+
background-color: rgba(90, 120, 150, 0.9);
|
205
|
+
border-color: rgba(110, 140, 170, 0.9);
|
206
|
+
}
|
207
|
+
QPushButton:pressed {
|
208
|
+
background-color: rgba(50, 80, 110, 0.9);
|
209
|
+
}
|
210
|
+
"""
|