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.
@@ -1,8 +1,19 @@
1
- """UI widgets for LazyLabel."""
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__ = ["ModelSelectionWidget", "SettingsWidget", "AdjustmentsWidget", "StatusBar"]
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
+ """