lazylabel-gui 1.2.1__py3-none-any.whl → 1.3.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.
@@ -24,6 +24,100 @@ from .channel_threshold_widget import MultiIndicatorSlider
24
24
  class FFTThresholdSlider(MultiIndicatorSlider):
25
25
  """Custom slider for FFT thresholds that allows removing all indicators."""
26
26
 
27
+ def paintEvent(self, event):
28
+ """Paint the slider with appropriate labels."""
29
+ # Call parent paint method but skip its label drawing
30
+ from PyQt6.QtCore import QRect, Qt
31
+ from PyQt6.QtGui import QBrush, QColor, QFont, QPainter, QPen
32
+
33
+ painter = QPainter(self)
34
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
35
+
36
+ # Draw channel label
37
+ font = QFont()
38
+ font.setPointSize(9)
39
+ painter.setFont(font)
40
+ painter.setPen(QPen(QColor(255, 255, 255)))
41
+ painter.drawText(5, 15, f"{self.channel_name}")
42
+
43
+ # Draw slider track
44
+ slider_rect = self.get_slider_rect()
45
+ painter.setPen(QPen(QColor(100, 100, 100), 2))
46
+ painter.setBrush(QBrush(QColor(50, 50, 50)))
47
+ painter.drawRoundedRect(slider_rect, 5, 5)
48
+
49
+ # Draw value segments (copied from parent)
50
+ channel_color = self.get_channel_color()
51
+ sorted_indicators = sorted(self.indicators)
52
+
53
+ # Handle case with no indicators - draw single segment
54
+ if not sorted_indicators:
55
+ segment_rect = QRect(
56
+ slider_rect.left(),
57
+ slider_rect.top(),
58
+ slider_rect.width(),
59
+ slider_rect.height(),
60
+ )
61
+ segment_color = QColor(channel_color)
62
+ segment_color.setAlpha(50)
63
+ painter.setBrush(QBrush(segment_color))
64
+ painter.setPen(QPen(Qt.GlobalColor.transparent))
65
+ painter.drawRoundedRect(segment_rect, 5, 5)
66
+ else:
67
+ # Draw segments between indicators
68
+ for i in range(len(sorted_indicators) + 1):
69
+ start_val = self.minimum if i == 0 else sorted_indicators[i - 1]
70
+ end_val = (
71
+ self.maximum
72
+ if i == len(sorted_indicators)
73
+ else sorted_indicators[i]
74
+ )
75
+
76
+ start_x = self.value_to_x(start_val)
77
+ end_x = self.value_to_x(end_val)
78
+
79
+ segment_value = (
80
+ i / len(sorted_indicators) if len(sorted_indicators) > 0 else 0
81
+ )
82
+ alpha = int(50 + segment_value * 150)
83
+
84
+ segment_color = QColor(channel_color)
85
+ segment_color.setAlpha(alpha)
86
+
87
+ segment_rect = QRect(
88
+ start_x, slider_rect.top(), end_x - start_x, slider_rect.height()
89
+ )
90
+ painter.setBrush(QBrush(segment_color))
91
+ painter.setPen(QPen(Qt.GlobalColor.transparent))
92
+ painter.drawRoundedRect(segment_rect, 5, 5)
93
+
94
+ # Draw indicators without labels
95
+ for i, value in enumerate(self.indicators):
96
+ x = self.value_to_x(value)
97
+ handle_rect = QRect(
98
+ x - 6, slider_rect.top() - 3, 12, slider_rect.height() + 6
99
+ )
100
+
101
+ if i == self.dragging_index:
102
+ painter.setBrush(QBrush(QColor(255, 255, 100)))
103
+ painter.setPen(QPen(QColor(200, 200, 50), 2))
104
+ else:
105
+ painter.setBrush(QBrush(QColor(255, 255, 255)))
106
+ painter.setPen(QPen(QColor(150, 150, 150), 1))
107
+
108
+ painter.drawRoundedRect(handle_rect, 3, 3)
109
+
110
+ # Now draw our custom labels
111
+ painter.setPen(QPen(QColor(255, 255, 255)))
112
+ for value in self.indicators:
113
+ x = self.value_to_x(value)
114
+ if self.maximum == 10000: # Frequency slider (percentage)
115
+ percentage = round(value / 100.0)
116
+ label = f"{percentage}%"
117
+ else: # Intensity slider (integer pixel values)
118
+ label = f"{int(value)}"
119
+ painter.drawText(x - 15, slider_rect.bottom() + 15, label)
120
+
27
121
  def contextMenuEvent(self, event):
28
122
  """Handle right-click to remove indicator (allows removing all indicators)."""
29
123
  from PyQt6.QtCore import QRect
@@ -58,7 +152,7 @@ class FFTThresholdWidget(QWidget):
58
152
  0 # 0 = no image, 1 = grayscale, 3+ = not supported
59
153
  )
60
154
  self.frequency_thresholds = [] # List of frequency threshold percentages (0-100)
61
- self.intensity_thresholds = [] # List of intensity threshold percentages (0-100)
155
+ self.intensity_thresholds = [] # List of intensity threshold pixel values (0-255)
62
156
  self._setup_ui()
63
157
  self._connect_signals()
64
158
 
@@ -66,7 +160,7 @@ class FFTThresholdWidget(QWidget):
66
160
  """Setup the UI layout."""
67
161
  group = QGroupBox("FFT Frequency Band Thresholding")
68
162
  layout = QVBoxLayout(group)
69
- layout.setSpacing(8)
163
+ layout.setSpacing(4) # Reduce spacing between widgets
70
164
 
71
165
  # Enable checkbox
72
166
  self.enable_checkbox = QCheckBox("Enable FFT Frequency Thresholding")
@@ -81,45 +175,45 @@ class FFTThresholdWidget(QWidget):
81
175
  layout.addWidget(self.status_label)
82
176
 
83
177
  # Frequency threshold slider (percentage-based)
84
- freq_label = QLabel("Frequency Thresholds (Double-click to add):")
85
- freq_label.setStyleSheet("font-weight: bold; margin-top: 5px;")
178
+ freq_label = QLabel("Frequency Thresholds\n(Double-click to add):")
179
+ freq_label.setStyleSheet("font-weight: bold; margin-top: 2px; font-size: 10px;")
86
180
  layout.addWidget(freq_label)
87
181
 
88
182
  self.frequency_slider = FFTThresholdSlider(
89
- channel_name="Frequency Bands", minimum=0, maximum=100, parent=self
183
+ channel_name="Frequency Bands", minimum=0, maximum=10000, parent=self
90
184
  )
91
185
  self.frequency_slider.setEnabled(False)
92
186
  self.frequency_slider.setToolTip(
93
- "Double-click to add frequency cutoff points. Each frequency band gets mapped to a different intensity level."
187
+ "Double-click to add frequency cutoff points.\nEach band gets mapped to different intensity."
94
188
  )
95
189
  layout.addWidget(self.frequency_slider)
96
190
 
97
191
  # Intensity threshold slider (percentage-based)
98
- intensity_label = QLabel("Intensity Thresholds (Double-click to add):")
99
- intensity_label.setStyleSheet("font-weight: bold; margin-top: 10px;")
192
+ intensity_label = QLabel("Intensity Thresholds\n(Double-click to add):")
193
+ intensity_label.setStyleSheet(
194
+ "font-weight: bold; margin-top: 5px; font-size: 10px;"
195
+ )
100
196
  layout.addWidget(intensity_label)
101
197
 
102
198
  self.intensity_slider = FFTThresholdSlider(
103
- channel_name="Intensity Levels", minimum=0, maximum=100, parent=self
199
+ channel_name="Intensity Levels", minimum=0, maximum=255, parent=self
104
200
  )
105
201
  self.intensity_slider.setEnabled(False)
106
202
  self.intensity_slider.setToolTip(
107
- "Double-click to add intensity threshold points. Applied after frequency band processing."
203
+ "Double-click to add intensity threshold points.\nApplied after frequency band processing."
108
204
  )
109
205
  layout.addWidget(self.intensity_slider)
110
206
 
111
- # Instructions
112
- instructions = QLabel(
113
- "1. Add frequency thresholds to create bands: low freq → dark, high freq → bright.\n"
114
- "2. Add intensity thresholds to further process the result with quantization levels."
115
- )
116
- instructions.setStyleSheet("color: #888; font-size: 9px;")
207
+ # Compact instructions
208
+ instructions = QLabel("Freq: low→dark, high→bright | Intensity: quantization")
209
+ instructions.setStyleSheet("color: #888; font-size: 8px; margin-top: 2px;")
117
210
  instructions.setWordWrap(True)
118
211
  layout.addWidget(instructions)
119
212
 
120
- # Main layout
213
+ # Main layout with reduced spacing
121
214
  main_layout = QVBoxLayout(self)
122
215
  main_layout.setContentsMargins(0, 0, 0, 0)
216
+ main_layout.setSpacing(2) # Reduce spacing between elements
123
217
  main_layout.addWidget(group)
124
218
 
125
219
  def _connect_signals(self):
@@ -162,7 +256,7 @@ class FFTThresholdWidget(QWidget):
162
256
 
163
257
  def _on_intensity_slider_changed(self, indicators):
164
258
  """Handle intensity threshold slider change (receives list of threshold indicators)."""
165
- # Store the intensity threshold indicators (percentages 0-100)
259
+ # Store the intensity threshold indicators (pixel values 0-255)
166
260
  self.intensity_thresholds = indicators[:] # Copy the list
167
261
  self._emit_change_if_active()
168
262
 
@@ -295,8 +389,8 @@ class FFTThresholdWidget(QWidget):
295
389
  # Create frequency bands based on thresholds
296
390
  sorted_thresholds = sorted(self.frequency_thresholds)
297
391
  freq_thresholds_normalized = [
298
- t / 100.0 for t in sorted_thresholds
299
- ] # Convert to 0-1
392
+ t / 10000.0 for t in sorted_thresholds
393
+ ] # Convert to 0-1 (from 0-10000 range, giving 0.01% precision)
300
394
 
301
395
  # Number of bands = number of thresholds + 1
302
396
  num_bands = len(freq_thresholds_normalized) + 1
@@ -347,8 +441,8 @@ class FFTThresholdWidget(QWidget):
347
441
  if not sorted_thresholds:
348
442
  return image_array
349
443
 
350
- # Convert thresholds from percentages to intensity values (0-255)
351
- intensity_thresholds = [t * 255 / 100.0 for t in sorted_thresholds]
444
+ # Thresholds are already in pixel values (0-255), no conversion needed
445
+ intensity_thresholds = sorted_thresholds
352
446
 
353
447
  # Number of levels = number of thresholds + 1
354
448
  num_levels = len(intensity_thresholds) + 1
@@ -1,17 +1,121 @@
1
1
  """Model selection widget."""
2
2
 
3
- from PyQt6.QtCore import pyqtSignal
3
+ from PyQt6.QtCore import Qt, pyqtSignal
4
4
  from PyQt6.QtWidgets import (
5
- QComboBox,
6
5
  QGroupBox,
7
6
  QHBoxLayout,
8
7
  QLabel,
8
+ QMenu,
9
9
  QPushButton,
10
+ QToolButton,
10
11
  QVBoxLayout,
11
12
  QWidget,
12
13
  )
13
14
 
14
15
 
16
+ class CustomDropdown(QToolButton):
17
+ """Custom dropdown using QToolButton + QMenu for reliable closing behavior."""
18
+
19
+ activated = pyqtSignal(int)
20
+
21
+ def __init__(self, parent=None):
22
+ super().__init__(parent)
23
+ self.setText("Default (vit_h)")
24
+ self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
25
+ self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextOnly)
26
+
27
+ # Create the menu
28
+ self.menu = QMenu(self)
29
+ self.setMenu(self.menu)
30
+
31
+ # Store items for access
32
+ self.items = []
33
+
34
+ # Style to match app theme (dark theme with consistent colors)
35
+ self.setStyleSheet("""
36
+ QToolButton {
37
+ background-color: rgba(40, 40, 40, 0.8);
38
+ border: 1px solid rgba(80, 80, 80, 0.6);
39
+ border-radius: 6px;
40
+ color: #E0E0E0;
41
+ font-size: 10px;
42
+ padding: 5px 8px;
43
+ text-align: left;
44
+ min-width: 150px;
45
+ }
46
+ QToolButton:hover {
47
+ background-color: rgba(60, 60, 60, 0.8);
48
+ border-color: rgba(90, 120, 150, 0.8);
49
+ }
50
+ QToolButton:pressed {
51
+ background-color: rgba(70, 100, 130, 0.8);
52
+ }
53
+ QToolButton::menu-indicator {
54
+ subcontrol-origin: padding;
55
+ subcontrol-position: top right;
56
+ width: 16px;
57
+ border-left: 1px solid rgba(80, 80, 80, 0.6);
58
+ }
59
+ """)
60
+
61
+ def addItem(self, text, data=None):
62
+ """Add an item to the dropdown."""
63
+ action = self.menu.addAction(text)
64
+ action.setData(data)
65
+ self.items.append((text, data))
66
+
67
+ # Connect to selection handler
68
+ action.triggered.connect(
69
+ lambda checked, idx=len(self.items) - 1: self._on_item_selected(idx)
70
+ )
71
+
72
+ # Set first item as current
73
+ if len(self.items) == 1:
74
+ self.setText(text)
75
+
76
+ def clear(self):
77
+ """Clear all items."""
78
+ self.menu.clear()
79
+ self.items.clear()
80
+
81
+ def _on_item_selected(self, index):
82
+ """Handle item selection."""
83
+ if 0 <= index < len(self.items):
84
+ text, data = self.items[index]
85
+ self.setText(text)
86
+ self.activated.emit(index)
87
+
88
+ def itemText(self, index):
89
+ """Get text of item at index."""
90
+ if 0 <= index < len(self.items):
91
+ return self.items[index][0]
92
+ return ""
93
+
94
+ def itemData(self, index):
95
+ """Get data of item at index."""
96
+ if 0 <= index < len(self.items):
97
+ return self.items[index][1]
98
+ return None
99
+
100
+ def currentIndex(self):
101
+ """Get current selected index."""
102
+ current_text = self.text()
103
+ for i, (text, _) in enumerate(self.items):
104
+ if text == current_text:
105
+ return i
106
+ return 0
107
+
108
+ def setCurrentIndex(self, index):
109
+ """Set current selected index."""
110
+ if 0 <= index < len(self.items):
111
+ text, _ = self.items[index]
112
+ self.setText(text)
113
+
114
+ def blockSignals(self, block):
115
+ """Block/unblock signals."""
116
+ super().blockSignals(block)
117
+
118
+
15
119
  class ModelSelectionWidget(QWidget):
16
120
  """Widget for model selection and management."""
17
121
 
@@ -47,7 +151,7 @@ class ModelSelectionWidget(QWidget):
47
151
 
48
152
  # Model combo
49
153
  layout.addWidget(QLabel("Available Models:"))
50
- self.model_combo = QComboBox()
154
+ self.model_combo = CustomDropdown()
51
155
  self.model_combo.setToolTip("Select a .pth model file to use")
52
156
  self.model_combo.addItem("Default (vit_h)")
53
157
  layout.addWidget(self.model_combo)
@@ -67,7 +171,16 @@ class ModelSelectionWidget(QWidget):
67
171
  """Connect internal signals."""
68
172
  self.btn_browse.clicked.connect(self.browse_requested)
69
173
  self.btn_refresh.clicked.connect(self.refresh_requested)
70
- self.model_combo.currentTextChanged.connect(self.model_selected)
174
+ # Use activated signal which fires when user actually selects an item
175
+ self.model_combo.activated.connect(self._on_model_activated)
176
+
177
+ def _on_model_activated(self, index):
178
+ """Handle model selection when user clicks on an item."""
179
+ # Get the selected text
180
+ selected_text = self.model_combo.itemText(index)
181
+
182
+ # Emit the signal immediately
183
+ self.model_selected.emit(selected_text)
71
184
 
72
185
  def populate_models(self, models: list[tuple[str, str]]):
73
186
  """Populate the models combo box.