lazylabel-gui 1.3.3__py3-none-any.whl → 1.3.5__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 +1 -1
- lazylabel/models/sam2_model.py +253 -134
- lazylabel/ui/control_panel.py +7 -2
- lazylabel/ui/main_window.py +264 -593
- lazylabel/ui/photo_viewer.py +35 -11
- lazylabel/ui/widgets/channel_threshold_widget.py +8 -9
- lazylabel/ui/widgets/fft_threshold_widget.py +4 -0
- lazylabel/ui/widgets/model_selection_widget.py +9 -0
- lazylabel/ui/workers/__init__.py +15 -0
- lazylabel/ui/workers/image_discovery_worker.py +66 -0
- lazylabel/ui/workers/multi_view_sam_init_worker.py +135 -0
- lazylabel/ui/workers/multi_view_sam_update_worker.py +158 -0
- lazylabel/ui/workers/sam_update_worker.py +129 -0
- lazylabel/ui/workers/single_view_sam_init_worker.py +61 -0
- lazylabel/utils/fast_file_manager.py +422 -78
- {lazylabel_gui-1.3.3.dist-info → lazylabel_gui-1.3.5.dist-info}/METADATA +1 -1
- {lazylabel_gui-1.3.3.dist-info → lazylabel_gui-1.3.5.dist-info}/RECORD +21 -15
- {lazylabel_gui-1.3.3.dist-info → lazylabel_gui-1.3.5.dist-info}/WHEEL +0 -0
- {lazylabel_gui-1.3.3.dist-info → lazylabel_gui-1.3.5.dist-info}/entry_points.txt +0 -0
- {lazylabel_gui-1.3.3.dist-info → lazylabel_gui-1.3.5.dist-info}/licenses/LICENSE +0 -0
- {lazylabel_gui-1.3.3.dist-info → lazylabel_gui-1.3.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
"""Worker thread for updating SAM model in background."""
|
2
|
+
|
3
|
+
import cv2
|
4
|
+
import numpy as np
|
5
|
+
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
6
|
+
from PyQt6.QtGui import QPixmap
|
7
|
+
|
8
|
+
|
9
|
+
class SAMUpdateWorker(QThread):
|
10
|
+
"""Worker thread for updating SAM model in background."""
|
11
|
+
|
12
|
+
finished = pyqtSignal()
|
13
|
+
error = pyqtSignal(str)
|
14
|
+
|
15
|
+
def __init__(
|
16
|
+
self,
|
17
|
+
model_manager,
|
18
|
+
image_path,
|
19
|
+
operate_on_view,
|
20
|
+
current_image=None,
|
21
|
+
parent=None,
|
22
|
+
):
|
23
|
+
super().__init__(parent)
|
24
|
+
self.model_manager = model_manager
|
25
|
+
self.image_path = image_path
|
26
|
+
self.operate_on_view = operate_on_view
|
27
|
+
self.current_image = current_image # Numpy array of current modified image
|
28
|
+
self._should_stop = False
|
29
|
+
self.scale_factor = 1.0 # Track scaling factor for coordinate transformation
|
30
|
+
|
31
|
+
def stop(self):
|
32
|
+
"""Request the worker to stop."""
|
33
|
+
self._should_stop = True
|
34
|
+
|
35
|
+
def get_scale_factor(self):
|
36
|
+
"""Get the scale factor used for image resizing."""
|
37
|
+
return self.scale_factor
|
38
|
+
|
39
|
+
def run(self):
|
40
|
+
"""Run SAM update in background thread."""
|
41
|
+
try:
|
42
|
+
if self._should_stop:
|
43
|
+
return
|
44
|
+
|
45
|
+
if self.operate_on_view and self.current_image is not None:
|
46
|
+
# Use the provided modified image
|
47
|
+
if self._should_stop:
|
48
|
+
return
|
49
|
+
|
50
|
+
# Optimize image size for faster SAM processing
|
51
|
+
image = self.current_image
|
52
|
+
original_height, original_width = image.shape[:2]
|
53
|
+
max_size = 1024
|
54
|
+
|
55
|
+
if original_height > max_size or original_width > max_size:
|
56
|
+
# Calculate scaling factor
|
57
|
+
self.scale_factor = min(
|
58
|
+
max_size / original_width, max_size / original_height
|
59
|
+
)
|
60
|
+
new_width = int(original_width * self.scale_factor)
|
61
|
+
new_height = int(original_height * self.scale_factor)
|
62
|
+
|
63
|
+
# Resize using OpenCV for speed
|
64
|
+
image = cv2.resize(
|
65
|
+
image, (new_width, new_height), interpolation=cv2.INTER_AREA
|
66
|
+
)
|
67
|
+
else:
|
68
|
+
self.scale_factor = 1.0
|
69
|
+
|
70
|
+
if self._should_stop:
|
71
|
+
return
|
72
|
+
|
73
|
+
# Set image from numpy array (FIXED: use resized image, not original)
|
74
|
+
self.model_manager.set_image_from_array(image)
|
75
|
+
else:
|
76
|
+
# Load original image
|
77
|
+
pixmap = QPixmap(self.image_path)
|
78
|
+
if pixmap.isNull():
|
79
|
+
self.error.emit("Failed to load image")
|
80
|
+
return
|
81
|
+
|
82
|
+
if self._should_stop:
|
83
|
+
return
|
84
|
+
|
85
|
+
original_width = pixmap.width()
|
86
|
+
original_height = pixmap.height()
|
87
|
+
|
88
|
+
# Optimize image size for faster SAM processing
|
89
|
+
max_size = 1024
|
90
|
+
if original_width > max_size or original_height > max_size:
|
91
|
+
# Calculate scaling factor
|
92
|
+
self.scale_factor = min(
|
93
|
+
max_size / original_width, max_size / original_height
|
94
|
+
)
|
95
|
+
|
96
|
+
# Scale down while maintaining aspect ratio
|
97
|
+
scaled_pixmap = pixmap.scaled(
|
98
|
+
max_size,
|
99
|
+
max_size,
|
100
|
+
Qt.AspectRatioMode.KeepAspectRatio,
|
101
|
+
Qt.TransformationMode.SmoothTransformation,
|
102
|
+
)
|
103
|
+
|
104
|
+
# Convert to numpy array for SAM
|
105
|
+
qimage = scaled_pixmap.toImage()
|
106
|
+
width = qimage.width()
|
107
|
+
height = qimage.height()
|
108
|
+
ptr = qimage.bits()
|
109
|
+
ptr.setsize(height * width * 4)
|
110
|
+
arr = np.array(ptr).reshape(height, width, 4)
|
111
|
+
# Convert RGBA to RGB
|
112
|
+
image_array = arr[:, :, :3]
|
113
|
+
|
114
|
+
if self._should_stop:
|
115
|
+
return
|
116
|
+
|
117
|
+
# FIXED: Use the resized image array, not original path
|
118
|
+
self.model_manager.set_image_from_array(image_array)
|
119
|
+
else:
|
120
|
+
self.scale_factor = 1.0
|
121
|
+
# For images that don't need resizing, use original path
|
122
|
+
self.model_manager.set_image_from_path(self.image_path)
|
123
|
+
|
124
|
+
if not self._should_stop:
|
125
|
+
self.finished.emit()
|
126
|
+
|
127
|
+
except Exception as e:
|
128
|
+
if not self._should_stop:
|
129
|
+
self.error.emit(str(e))
|
@@ -0,0 +1,61 @@
|
|
1
|
+
"""Worker thread for initializing single-view SAM model in background."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
|
5
|
+
from PyQt6.QtCore import QThread, pyqtSignal
|
6
|
+
|
7
|
+
|
8
|
+
class SingleViewSAMInitWorker(QThread):
|
9
|
+
"""Worker thread for initializing single-view SAM model in background."""
|
10
|
+
|
11
|
+
model_initialized = pyqtSignal(object) # model_instance
|
12
|
+
finished = pyqtSignal()
|
13
|
+
error = pyqtSignal(str)
|
14
|
+
progress = pyqtSignal(str) # status message
|
15
|
+
|
16
|
+
def __init__(self, model_manager, default_model_type, custom_model_path=None):
|
17
|
+
super().__init__()
|
18
|
+
self.model_manager = model_manager
|
19
|
+
self.default_model_type = default_model_type
|
20
|
+
self.custom_model_path = custom_model_path
|
21
|
+
self._should_stop = False
|
22
|
+
|
23
|
+
def stop(self):
|
24
|
+
"""Stop the worker gracefully."""
|
25
|
+
self._should_stop = True
|
26
|
+
|
27
|
+
def run(self):
|
28
|
+
"""Initialize SAM model in background."""
|
29
|
+
try:
|
30
|
+
if self._should_stop:
|
31
|
+
return
|
32
|
+
|
33
|
+
if self.custom_model_path:
|
34
|
+
# Load custom model
|
35
|
+
model_name = os.path.basename(self.custom_model_path)
|
36
|
+
self.progress.emit(f"Loading {model_name}...")
|
37
|
+
|
38
|
+
success = self.model_manager.load_custom_model(self.custom_model_path)
|
39
|
+
if not success:
|
40
|
+
raise Exception(f"Failed to load custom model: {model_name}")
|
41
|
+
|
42
|
+
sam_model = self.model_manager.sam_model
|
43
|
+
else:
|
44
|
+
# Initialize the default model
|
45
|
+
self.progress.emit("Initializing AI model...")
|
46
|
+
sam_model = self.model_manager.initialize_default_model(
|
47
|
+
self.default_model_type
|
48
|
+
)
|
49
|
+
|
50
|
+
if self._should_stop:
|
51
|
+
return
|
52
|
+
|
53
|
+
if sam_model and sam_model.is_loaded:
|
54
|
+
self.model_initialized.emit(sam_model)
|
55
|
+
self.progress.emit("AI model initialized")
|
56
|
+
else:
|
57
|
+
self.error.emit("Model failed to load")
|
58
|
+
|
59
|
+
except Exception as e:
|
60
|
+
if not self._should_stop:
|
61
|
+
self.error.emit(f"Failed to load AI model: {str(e)}")
|