lazylabel-gui 1.3.3__tar.gz → 1.3.4__tar.gz
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_gui-1.3.3 → lazylabel_gui-1.3.4}/PKG-INFO +1 -1
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/pyproject.toml +1 -1
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/core/file_manager.py +1 -1
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/control_panel.py +7 -2
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/main_window.py +164 -63
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/widgets/channel_threshold_widget.py +8 -9
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/widgets/fft_threshold_widget.py +4 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/widgets/model_selection_widget.py +9 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/utils/fast_file_manager.py +422 -78
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel_gui.egg-info/PKG-INFO +1 -1
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/LICENSE +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/README.md +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/setup.cfg +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/__init__.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/__main__.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/config/__init__.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/config/hotkeys.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/config/paths.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/config/settings.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/core/__init__.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/core/model_manager.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/core/segment_manager.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/main.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/models/__init__.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/models/sam2_model.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/models/sam_model.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/__init__.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/editable_vertex.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/hotkey_dialog.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/hoverable_pixelmap_item.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/hoverable_polygon_item.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/modes/__init__.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/modes/base_mode.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/modes/multi_view_mode.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/modes/single_view_mode.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/numeric_table_widget_item.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/photo_viewer.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/reorderable_class_table.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/right_panel.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/widgets/__init__.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/widgets/adjustments_widget.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/widgets/border_crop_widget.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/widgets/fragment_threshold_widget.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/widgets/settings_widget.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/widgets/status_bar.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/utils/__init__.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/utils/custom_file_system_model.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/utils/logger.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/utils/utils.py +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel_gui.egg-info/SOURCES.txt +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel_gui.egg-info/dependency_links.txt +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel_gui.egg-info/entry_points.txt +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel_gui.egg-info/requires.txt +0 -0
- {lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel_gui.egg-info/top_level.txt +0 -0
@@ -65,7 +65,7 @@ class FileManager:
|
|
65
65
|
if not os.path.exists(npz_path):
|
66
66
|
raise OSError(f"NPZ file was not created: {npz_path}")
|
67
67
|
|
68
|
-
logger.
|
68
|
+
logger.debug(f"Successfully saved NPZ: {os.path.basename(npz_path)}")
|
69
69
|
return npz_path
|
70
70
|
|
71
71
|
def save_yolo_txt(
|
@@ -612,10 +612,10 @@ class ControlPanel(QWidget):
|
|
612
612
|
layout.addWidget(crop_collapsible)
|
613
613
|
|
614
614
|
# Channel Threshold - collapsible
|
615
|
-
|
615
|
+
self.channel_threshold_collapsible = SimpleCollapsible(
|
616
616
|
"Channel Threshold", self.channel_threshold_widget
|
617
617
|
)
|
618
|
-
layout.addWidget(
|
618
|
+
layout.addWidget(self.channel_threshold_collapsible)
|
619
619
|
|
620
620
|
# FFT Threshold - collapsible (default collapsed)
|
621
621
|
self.fft_threshold_collapsible = SimpleCollapsible(
|
@@ -880,6 +880,11 @@ class ControlPanel(QWidget):
|
|
880
880
|
"""Update channel threshold widget for new image."""
|
881
881
|
self.channel_threshold_widget.update_for_image(image_array)
|
882
882
|
|
883
|
+
# Auto-expand channel threshold panel when any image is loaded
|
884
|
+
if image_array is not None and hasattr(self, "channel_threshold_collapsible"):
|
885
|
+
# Find and expand the Channel Threshold panel
|
886
|
+
self.channel_threshold_collapsible.set_collapsed(False)
|
887
|
+
|
883
888
|
def get_channel_threshold_widget(self):
|
884
889
|
"""Get the channel threshold widget."""
|
885
890
|
return self.channel_threshold_widget
|
@@ -19,12 +19,14 @@ from PyQt6.QtGui import (
|
|
19
19
|
QShortcut,
|
20
20
|
)
|
21
21
|
from PyQt6.QtWidgets import (
|
22
|
+
QApplication,
|
22
23
|
QDialog,
|
23
24
|
QFileDialog,
|
24
25
|
QGraphicsEllipseItem,
|
25
26
|
QGraphicsLineItem,
|
26
27
|
QGraphicsPolygonItem,
|
27
28
|
QGraphicsRectItem,
|
29
|
+
QGridLayout,
|
28
30
|
QHBoxLayout,
|
29
31
|
QLabel,
|
30
32
|
QMainWindow,
|
@@ -772,6 +774,7 @@ class MainWindow(QMainWindow):
|
|
772
774
|
logger.info("Step 5/8: Discovering available models...")
|
773
775
|
self._setup_model_manager() # Just setup manager, don't load model
|
774
776
|
self._setup_connections()
|
777
|
+
self._fix_fft_connection() # Workaround for FFT signal connection issue
|
775
778
|
self._setup_shortcuts()
|
776
779
|
self._load_settings()
|
777
780
|
|
@@ -877,12 +880,7 @@ class MainWindow(QMainWindow):
|
|
877
880
|
grid_widget = QWidget()
|
878
881
|
|
879
882
|
# Use grid layout for 4-view, horizontal layout for 2-view
|
880
|
-
if use_grid
|
881
|
-
from PyQt6.QtWidgets import QGridLayout
|
882
|
-
|
883
|
-
grid_layout = QGridLayout(grid_widget)
|
884
|
-
else:
|
885
|
-
grid_layout = QHBoxLayout(grid_widget)
|
883
|
+
grid_layout = QGridLayout(grid_widget) if use_grid else QHBoxLayout(grid_widget)
|
886
884
|
grid_layout.setSpacing(5)
|
887
885
|
|
888
886
|
self.multi_view_viewers = []
|
@@ -951,29 +949,30 @@ class MainWindow(QMainWindow):
|
|
951
949
|
grid_mode_label = QLabel("View Mode:")
|
952
950
|
controls_layout.addWidget(grid_mode_label)
|
953
951
|
|
954
|
-
from
|
952
|
+
from lazylabel.ui.widgets.model_selection_widget import CustomDropdown
|
955
953
|
|
956
|
-
self.grid_mode_combo =
|
954
|
+
self.grid_mode_combo = CustomDropdown()
|
955
|
+
self.grid_mode_combo.setText("View Mode") # Default text
|
957
956
|
self.grid_mode_combo.addItem("2 Views (1x2)", "2_view")
|
958
957
|
self.grid_mode_combo.addItem("4 Views (2x2)", "4_view")
|
959
958
|
|
960
959
|
# Set current selection based on settings
|
961
960
|
current_mode = self.settings.multi_view_grid_mode
|
962
|
-
for i in range(self.grid_mode_combo.
|
961
|
+
for i in range(len(self.grid_mode_combo.items)):
|
963
962
|
if self.grid_mode_combo.itemData(i) == current_mode:
|
964
963
|
self.grid_mode_combo.setCurrentIndex(i)
|
965
964
|
break
|
966
965
|
|
967
|
-
self.grid_mode_combo.
|
966
|
+
self.grid_mode_combo.activated.connect(self._on_grid_mode_changed)
|
968
967
|
controls_layout.addWidget(self.grid_mode_combo)
|
969
968
|
|
970
969
|
controls_layout.addStretch()
|
971
970
|
|
972
971
|
layout.addWidget(controls_widget)
|
973
972
|
|
974
|
-
def _on_grid_mode_changed(self):
|
973
|
+
def _on_grid_mode_changed(self, index):
|
975
974
|
"""Handle grid mode change from combo box."""
|
976
|
-
current_data = self.grid_mode_combo.
|
975
|
+
current_data = self.grid_mode_combo.itemData(index)
|
977
976
|
if current_data and current_data != self.settings.multi_view_grid_mode:
|
978
977
|
# Update settings
|
979
978
|
self.settings.multi_view_grid_mode = current_data
|
@@ -1062,8 +1061,7 @@ class MainWindow(QMainWindow):
|
|
1062
1061
|
layout.deleteLater()
|
1063
1062
|
except Exception as e:
|
1064
1063
|
# If layout clearing fails, just reset the viewer lists
|
1065
|
-
|
1066
|
-
pass
|
1064
|
+
logger.error(f"Layout clearing failed: {e}")
|
1067
1065
|
|
1068
1066
|
def _clear_multi_view_layout(self):
|
1069
1067
|
"""Clear the existing multi-view layout."""
|
@@ -1121,6 +1119,48 @@ class MainWindow(QMainWindow):
|
|
1121
1119
|
# Switch to polygon mode if SAM is disabled and we're in SAM/AI mode
|
1122
1120
|
self.set_polygon_mode()
|
1123
1121
|
|
1122
|
+
def _fix_fft_connection(self):
|
1123
|
+
"""Fix FFT signal connection issue - workaround for connection timing problem."""
|
1124
|
+
try:
|
1125
|
+
# Get the FFT widget directly and connect to its signal
|
1126
|
+
fft_widget = self.control_panel.get_fft_threshold_widget()
|
1127
|
+
if fft_widget:
|
1128
|
+
# Direct connection bypass - connect FFT widget directly to main window handler
|
1129
|
+
# This bypasses the control panel signal forwarding which has timing issues
|
1130
|
+
# Use a wrapper to ensure the connection works reliably
|
1131
|
+
def fft_signal_wrapper():
|
1132
|
+
self._handle_fft_threshold_changed()
|
1133
|
+
|
1134
|
+
fft_widget.fft_threshold_changed.connect(fft_signal_wrapper)
|
1135
|
+
|
1136
|
+
logger.info("FFT signal connection bypass established successfully")
|
1137
|
+
else:
|
1138
|
+
logger.warning("FFT widget not found during connection fix")
|
1139
|
+
except Exception as e:
|
1140
|
+
logger.warning(f"Failed to establish FFT connection bypass: {e}")
|
1141
|
+
|
1142
|
+
# Also fix channel threshold connection for RGB images
|
1143
|
+
try:
|
1144
|
+
channel_widget = self.control_panel.get_channel_threshold_widget()
|
1145
|
+
if channel_widget:
|
1146
|
+
# Direct connection bypass for channel threshold widget too
|
1147
|
+
def channel_signal_wrapper():
|
1148
|
+
self._handle_channel_threshold_changed()
|
1149
|
+
|
1150
|
+
channel_widget.thresholdChanged.connect(channel_signal_wrapper)
|
1151
|
+
|
1152
|
+
logger.info(
|
1153
|
+
"Channel threshold signal connection bypass established successfully"
|
1154
|
+
)
|
1155
|
+
else:
|
1156
|
+
logger.warning(
|
1157
|
+
"Channel threshold widget not found during connection fix"
|
1158
|
+
)
|
1159
|
+
except Exception as e:
|
1160
|
+
logger.warning(
|
1161
|
+
f"Failed to establish channel threshold connection bypass: {e}"
|
1162
|
+
)
|
1163
|
+
|
1124
1164
|
def _setup_connections(self):
|
1125
1165
|
"""Setup signal connections."""
|
1126
1166
|
# Control panel connections
|
@@ -1169,9 +1209,13 @@ class MainWindow(QMainWindow):
|
|
1169
1209
|
)
|
1170
1210
|
|
1171
1211
|
# FFT threshold connections
|
1172
|
-
|
1173
|
-
self.
|
1174
|
-
|
1212
|
+
try:
|
1213
|
+
self.control_panel.fft_threshold_changed.connect(
|
1214
|
+
self._handle_fft_threshold_changed
|
1215
|
+
)
|
1216
|
+
logger.debug("FFT threshold connection established in _setup_connections")
|
1217
|
+
except Exception as e:
|
1218
|
+
logger.error(f"Failed to establish FFT threshold connection: {e}")
|
1175
1219
|
|
1176
1220
|
# Right panel connections
|
1177
1221
|
self.right_panel.open_folder_requested.connect(self._open_folder_dialog)
|
@@ -1774,6 +1818,9 @@ class MainWindow(QMainWindow):
|
|
1774
1818
|
# Update file selection in the file manager
|
1775
1819
|
self.right_panel.select_file(Path(path))
|
1776
1820
|
|
1821
|
+
# Update threshold widgets for new image (this was missing!)
|
1822
|
+
self._update_channel_threshold_for_image(pixmap)
|
1823
|
+
|
1777
1824
|
def _load_multi_view_from_path(self, path: str):
|
1778
1825
|
"""Load multi-view starting from a specific path using FastFileManager."""
|
1779
1826
|
config = self._get_multi_view_config()
|
@@ -1948,6 +1995,9 @@ class MainWindow(QMainWindow):
|
|
1948
1995
|
if changed_indices:
|
1949
1996
|
self._fast_update_multi_view_images(changed_indices)
|
1950
1997
|
|
1998
|
+
# Update threshold widgets for the loaded images
|
1999
|
+
self._update_multi_view_channel_threshold_for_images()
|
2000
|
+
|
1951
2001
|
# Load existing segments for all loaded images
|
1952
2002
|
valid_image_paths = [path for path in image_paths if path is not None]
|
1953
2003
|
if valid_image_paths:
|
@@ -2005,7 +2055,9 @@ class MainWindow(QMainWindow):
|
|
2005
2055
|
all_segments.extend(viewer_segments)
|
2006
2056
|
self.segment_manager.segments.clear()
|
2007
2057
|
except Exception as e:
|
2008
|
-
|
2058
|
+
logger.error(
|
2059
|
+
f"Error loading segments for viewer {viewer_index}: {e}"
|
2060
|
+
)
|
2009
2061
|
|
2010
2062
|
# Set all segments at once
|
2011
2063
|
self.segment_manager.segments = all_segments
|
@@ -2025,7 +2077,7 @@ class MainWindow(QMainWindow):
|
|
2025
2077
|
self._update_all_lists()
|
2026
2078
|
|
2027
2079
|
except Exception as e:
|
2028
|
-
|
2080
|
+
logger.error(f"Error in _load_multi_view_segments: {e}")
|
2029
2081
|
self.segment_manager.segments.clear()
|
2030
2082
|
|
2031
2083
|
def _cancel_multi_view_sam_loading(self):
|
@@ -3353,6 +3405,8 @@ class MainWindow(QMainWindow):
|
|
3353
3405
|
valid_paths = [Path(img) for img in self.multi_view_images if img]
|
3354
3406
|
if valid_paths:
|
3355
3407
|
self.right_panel.file_manager.batchUpdateFileStatus(valid_paths)
|
3408
|
+
# Force immediate GUI update
|
3409
|
+
QApplication.processEvents()
|
3356
3410
|
# Clear the tracking list for next save
|
3357
3411
|
self._saved_file_paths = []
|
3358
3412
|
|
@@ -3401,6 +3455,8 @@ class MainWindow(QMainWindow):
|
|
3401
3455
|
)
|
3402
3456
|
# Update UI immediately when files are deleted
|
3403
3457
|
self._update_all_lists()
|
3458
|
+
# Force immediate GUI update
|
3459
|
+
QApplication.processEvents()
|
3404
3460
|
else:
|
3405
3461
|
self._show_warning_notification("No segments to save.")
|
3406
3462
|
return
|
@@ -3478,15 +3534,16 @@ class MainWindow(QMainWindow):
|
|
3478
3534
|
)
|
3479
3535
|
|
3480
3536
|
# Update FastFileManager to show NPZ/TXT checkmarks
|
3481
|
-
|
3482
|
-
|
3483
|
-
|
3484
|
-
and hasattr(self.right_panel, "file_manager")
|
3537
|
+
# Always update file status after save attempt (regardless of what was saved)
|
3538
|
+
if hasattr(self, "right_panel") and hasattr(
|
3539
|
+
self.right_panel, "file_manager"
|
3485
3540
|
):
|
3486
3541
|
# Update the file status in the FastFileManager
|
3487
3542
|
self.right_panel.file_manager.updateFileStatus(
|
3488
3543
|
Path(self.current_image_path)
|
3489
3544
|
)
|
3545
|
+
# Force immediate GUI update
|
3546
|
+
QApplication.processEvents()
|
3490
3547
|
except Exception as e:
|
3491
3548
|
logger.error(f"Error saving file: {str(e)}", exc_info=True)
|
3492
3549
|
self._show_error_notification(f"Error saving: {str(e)}")
|
@@ -5714,27 +5771,50 @@ class MainWindow(QMainWindow):
|
|
5714
5771
|
|
5715
5772
|
def _update_channel_threshold_for_image(self, pixmap):
|
5716
5773
|
"""Update channel threshold widget for the given image pixmap."""
|
5717
|
-
if pixmap.isNull():
|
5774
|
+
if pixmap.isNull() or not self.current_image_path:
|
5718
5775
|
self.control_panel.update_channel_threshold_for_image(None)
|
5719
5776
|
return
|
5720
5777
|
|
5721
|
-
#
|
5722
|
-
|
5723
|
-
|
5724
|
-
ptr.setsize(qimage.bytesPerLine() * qimage.height())
|
5725
|
-
image_np = np.array(ptr).reshape(qimage.height(), qimage.width(), 4)
|
5726
|
-
# Convert from BGRA to RGB, ignore alpha
|
5727
|
-
image_rgb = image_np[:, :, [2, 1, 0]]
|
5778
|
+
# Use cv2.imread for more robust loading instead of QPixmap conversion
|
5779
|
+
try:
|
5780
|
+
import cv2
|
5728
5781
|
|
5729
|
-
|
5730
|
-
|
5731
|
-
|
5732
|
-
|
5733
|
-
|
5734
|
-
|
5735
|
-
|
5736
|
-
|
5737
|
-
|
5782
|
+
image_array = cv2.imread(self.current_image_path)
|
5783
|
+
if image_array is None:
|
5784
|
+
self.control_panel.update_channel_threshold_for_image(None)
|
5785
|
+
return
|
5786
|
+
|
5787
|
+
# Convert from BGR to RGB
|
5788
|
+
if len(image_array.shape) == 3 and image_array.shape[2] == 3:
|
5789
|
+
image_array = cv2.cvtColor(image_array, cv2.COLOR_BGR2RGB)
|
5790
|
+
|
5791
|
+
# Check if image is grayscale (all channels are the same)
|
5792
|
+
if (
|
5793
|
+
len(image_array.shape) == 3
|
5794
|
+
and np.array_equal(image_array[:, :, 0], image_array[:, :, 1])
|
5795
|
+
and np.array_equal(image_array[:, :, 1], image_array[:, :, 2])
|
5796
|
+
):
|
5797
|
+
# Convert to single channel grayscale
|
5798
|
+
image_array = image_array[:, :, 0]
|
5799
|
+
|
5800
|
+
except Exception:
|
5801
|
+
# Fallback to QPixmap conversion if cv2 fails
|
5802
|
+
qimage = pixmap.toImage()
|
5803
|
+
ptr = qimage.constBits()
|
5804
|
+
ptr.setsize(qimage.bytesPerLine() * qimage.height())
|
5805
|
+
image_np = np.array(ptr).reshape(qimage.height(), qimage.width(), 4)
|
5806
|
+
# Convert from BGRA to RGB, ignore alpha
|
5807
|
+
image_rgb = image_np[:, :, [2, 1, 0]]
|
5808
|
+
|
5809
|
+
# Check if image is grayscale (all channels are the same)
|
5810
|
+
if np.array_equal(
|
5811
|
+
image_rgb[:, :, 0], image_rgb[:, :, 1]
|
5812
|
+
) and np.array_equal(image_rgb[:, :, 1], image_rgb[:, :, 2]):
|
5813
|
+
# Convert to single channel grayscale
|
5814
|
+
image_array = image_rgb[:, :, 0]
|
5815
|
+
else:
|
5816
|
+
# Keep as RGB
|
5817
|
+
image_array = image_rgb
|
5738
5818
|
|
5739
5819
|
# Update the channel threshold widget
|
5740
5820
|
self.control_panel.update_channel_threshold_for_image(image_array)
|
@@ -5762,29 +5842,51 @@ class MainWindow(QMainWindow):
|
|
5762
5842
|
self.control_panel.update_channel_threshold_for_image(None)
|
5763
5843
|
return
|
5764
5844
|
|
5765
|
-
#
|
5766
|
-
|
5767
|
-
|
5768
|
-
self.control_panel.update_channel_threshold_for_image(None)
|
5769
|
-
return
|
5845
|
+
# Use cv2.imread for more robust loading instead of QPixmap conversion
|
5846
|
+
try:
|
5847
|
+
import cv2
|
5770
5848
|
|
5771
|
-
|
5772
|
-
|
5773
|
-
|
5774
|
-
|
5775
|
-
image_np = np.array(ptr).reshape(qimage.height(), qimage.width(), 4)
|
5776
|
-
# Convert from BGRA to RGB, ignore alpha
|
5777
|
-
image_rgb = image_np[:, :, [2, 1, 0]]
|
5849
|
+
image_array = cv2.imread(first_image_path)
|
5850
|
+
if image_array is None:
|
5851
|
+
self.control_panel.update_channel_threshold_for_image(None)
|
5852
|
+
return
|
5778
5853
|
|
5779
|
-
|
5780
|
-
|
5781
|
-
|
5782
|
-
|
5783
|
-
#
|
5784
|
-
|
5785
|
-
|
5786
|
-
|
5787
|
-
|
5854
|
+
# Convert from BGR to RGB
|
5855
|
+
if len(image_array.shape) == 3 and image_array.shape[2] == 3:
|
5856
|
+
image_array = cv2.cvtColor(image_array, cv2.COLOR_BGR2RGB)
|
5857
|
+
|
5858
|
+
# Check if image is grayscale (all channels are the same)
|
5859
|
+
if (
|
5860
|
+
len(image_array.shape) == 3
|
5861
|
+
and np.array_equal(image_array[:, :, 0], image_array[:, :, 1])
|
5862
|
+
and np.array_equal(image_array[:, :, 1], image_array[:, :, 2])
|
5863
|
+
):
|
5864
|
+
# Convert to single channel grayscale
|
5865
|
+
image_array = image_array[:, :, 0]
|
5866
|
+
|
5867
|
+
except Exception:
|
5868
|
+
# Fallback to QPixmap conversion if cv2 fails
|
5869
|
+
pixmap = QPixmap(first_image_path)
|
5870
|
+
if pixmap.isNull():
|
5871
|
+
self.control_panel.update_channel_threshold_for_image(None)
|
5872
|
+
return
|
5873
|
+
|
5874
|
+
qimage = pixmap.toImage()
|
5875
|
+
ptr = qimage.constBits()
|
5876
|
+
ptr.setsize(qimage.bytesPerLine() * qimage.height())
|
5877
|
+
image_np = np.array(ptr).reshape(qimage.height(), qimage.width(), 4)
|
5878
|
+
# Convert from BGRA to RGB, ignore alpha
|
5879
|
+
image_rgb = image_np[:, :, [2, 1, 0]]
|
5880
|
+
|
5881
|
+
# Check if image is grayscale (all channels are the same)
|
5882
|
+
if np.array_equal(
|
5883
|
+
image_rgb[:, :, 0], image_rgb[:, :, 1]
|
5884
|
+
) and np.array_equal(image_rgb[:, :, 1], image_rgb[:, :, 2]):
|
5885
|
+
# Convert to single channel grayscale
|
5886
|
+
image_array = image_rgb[:, :, 0]
|
5887
|
+
else:
|
5888
|
+
# Keep as RGB
|
5889
|
+
image_array = image_rgb
|
5788
5890
|
|
5789
5891
|
# Update the channel threshold widget
|
5790
5892
|
self.control_panel.update_channel_threshold_for_image(image_array)
|
@@ -7097,7 +7199,6 @@ class MainWindow(QMainWindow):
|
|
7097
7199
|
)
|
7098
7200
|
|
7099
7201
|
# Load existing segments for the current image
|
7100
|
-
print(f"Loading segments for single-view: {self.current_image_path}")
|
7101
7202
|
try:
|
7102
7203
|
# Clear any leftover multi-view segments first
|
7103
7204
|
self.segment_manager.clear()
|
@@ -7114,7 +7215,7 @@ class MainWindow(QMainWindow):
|
|
7114
7215
|
self._update_all_lists()
|
7115
7216
|
|
7116
7217
|
except Exception as e:
|
7117
|
-
|
7218
|
+
logger.error(f"Error loading segments for single-view: {e}")
|
7118
7219
|
|
7119
7220
|
# Redisplay segments for single view
|
7120
7221
|
if hasattr(self, "single_view_mode_handler"):
|
{lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/widgets/channel_threshold_widget.py
RENAMED
@@ -270,10 +270,6 @@ class MultiIndicatorSlider(QWidget):
|
|
270
270
|
"""Handle right-click to remove indicator."""
|
271
271
|
slider_rect = self.get_slider_rect()
|
272
272
|
|
273
|
-
# Only allow removal if more than 1 indicator
|
274
|
-
if len(self.indicators) <= 1:
|
275
|
-
return
|
276
|
-
|
277
273
|
# Check if right-clicking on an indicator
|
278
274
|
for i, value in enumerate(self.indicators):
|
279
275
|
x = self.value_to_x(value)
|
@@ -473,7 +469,7 @@ class ChannelThresholdWidget(QWidget):
|
|
473
469
|
|
474
470
|
if self.current_image_channels == 1:
|
475
471
|
# Grayscale image
|
476
|
-
if "Gray" in self.sliders:
|
472
|
+
if "Gray" in self.sliders and self.sliders["Gray"].is_enabled():
|
477
473
|
result = self._apply_channel_thresholding(
|
478
474
|
result, self.sliders["Gray"].get_indicators()
|
479
475
|
)
|
@@ -481,7 +477,10 @@ class ChannelThresholdWidget(QWidget):
|
|
481
477
|
# RGB image
|
482
478
|
channel_names = ["Red", "Green", "Blue"]
|
483
479
|
for i, channel_name in enumerate(channel_names):
|
484
|
-
if
|
480
|
+
if (
|
481
|
+
channel_name in self.sliders
|
482
|
+
and self.sliders[channel_name].is_enabled()
|
483
|
+
):
|
485
484
|
result[:, :, i] = self._apply_channel_thresholding(
|
486
485
|
result[:, :, i], self.sliders[channel_name].get_indicators()
|
487
486
|
)
|
@@ -494,7 +493,7 @@ class ChannelThresholdWidget(QWidget):
|
|
494
493
|
if not indicators:
|
495
494
|
return channel_data
|
496
495
|
|
497
|
-
|
496
|
+
# Sort indicators
|
498
497
|
sorted_indicators = sorted(indicators)
|
499
498
|
|
500
499
|
# Create output array
|
@@ -527,9 +526,9 @@ class ChannelThresholdWidget(QWidget):
|
|
527
526
|
return result
|
528
527
|
|
529
528
|
def has_active_thresholding(self):
|
530
|
-
"""Check if any channel has active thresholding (indicators present)."""
|
529
|
+
"""Check if any channel has active thresholding (enabled and indicators present)."""
|
531
530
|
for slider_widget in self.sliders.values():
|
532
|
-
if slider_widget.get_indicators():
|
531
|
+
if slider_widget.is_enabled() and slider_widget.get_indicators():
|
533
532
|
return True
|
534
533
|
return False
|
535
534
|
|
{lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/widgets/fft_threshold_widget.py
RENAMED
@@ -308,6 +308,8 @@ class FFTThresholdWidget(QWidget):
|
|
308
308
|
self.status_label.setStyleSheet(
|
309
309
|
"color: #F44336; font-size: 9px; font-style: italic;"
|
310
310
|
)
|
311
|
+
# Disable FFT processing for color images
|
312
|
+
self.enable_checkbox.setChecked(False)
|
311
313
|
else:
|
312
314
|
# Unknown format
|
313
315
|
self.current_image_channels = 0
|
@@ -315,6 +317,8 @@ class FFTThresholdWidget(QWidget):
|
|
315
317
|
self.status_label.setStyleSheet(
|
316
318
|
"color: #F44336; font-size: 9px; font-style: italic;"
|
317
319
|
)
|
320
|
+
# Disable FFT processing for unsupported formats
|
321
|
+
self.enable_checkbox.setChecked(False)
|
318
322
|
|
319
323
|
def is_active(self):
|
320
324
|
"""Check if FFT processing is active (checkbox enabled and image is grayscale)."""
|
{lazylabel_gui-1.3.3 → lazylabel_gui-1.3.4}/src/lazylabel/ui/widgets/model_selection_widget.py
RENAMED
@@ -111,6 +111,15 @@ class CustomDropdown(QToolButton):
|
|
111
111
|
text, _ = self.items[index]
|
112
112
|
self.setText(text)
|
113
113
|
|
114
|
+
def count(self):
|
115
|
+
"""Get number of items."""
|
116
|
+
return len(self.items)
|
117
|
+
|
118
|
+
def currentData(self):
|
119
|
+
"""Get data of currently selected item."""
|
120
|
+
current_idx = self.currentIndex()
|
121
|
+
return self.itemData(current_idx)
|
122
|
+
|
114
123
|
def blockSignals(self, block):
|
115
124
|
"""Block/unblock signals."""
|
116
125
|
super().blockSignals(block)
|