lazylabel-gui 1.1.2__py3-none-any.whl → 1.1.3__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/__init__.py +9 -9
- lazylabel/config/__init__.py +7 -7
- lazylabel/config/hotkeys.py +207 -169
- lazylabel/config/paths.py +40 -41
- lazylabel/config/settings.py +65 -66
- lazylabel/core/__init__.py +7 -7
- lazylabel/core/file_manager.py +122 -106
- lazylabel/core/model_manager.py +95 -97
- lazylabel/core/segment_manager.py +170 -171
- lazylabel/main.py +37 -36
- lazylabel/models/__init__.py +5 -5
- lazylabel/models/sam_model.py +200 -195
- lazylabel/ui/__init__.py +8 -8
- lazylabel/ui/control_panel.py +239 -241
- lazylabel/ui/editable_vertex.py +64 -64
- lazylabel/ui/hotkey_dialog.py +416 -384
- lazylabel/ui/hoverable_pixelmap_item.py +22 -22
- lazylabel/ui/hoverable_polygon_item.py +38 -39
- lazylabel/ui/main_window.py +1787 -1659
- lazylabel/ui/numeric_table_widget_item.py +9 -9
- lazylabel/ui/photo_viewer.py +51 -54
- lazylabel/ui/reorderable_class_table.py +60 -61
- lazylabel/ui/right_panel.py +314 -315
- lazylabel/ui/widgets/__init__.py +8 -8
- lazylabel/ui/widgets/adjustments_widget.py +108 -108
- lazylabel/ui/widgets/model_selection_widget.py +101 -94
- lazylabel/ui/widgets/settings_widget.py +113 -106
- lazylabel/ui/widgets/status_bar.py +109 -109
- lazylabel/utils/__init__.py +6 -6
- lazylabel/utils/custom_file_system_model.py +133 -132
- lazylabel/utils/utils.py +12 -12
- {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.3.dist-info}/METADATA +243 -197
- lazylabel_gui-1.1.3.dist-info/RECORD +37 -0
- {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.3.dist-info}/licenses/LICENSE +21 -21
- lazylabel_gui-1.1.2.dist-info/RECORD +0 -37
- {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.3.dist-info}/WHEEL +0 -0
- {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.3.dist-info}/entry_points.txt +0 -0
- {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.3.dist-info}/top_level.txt +0 -0
lazylabel/models/sam_model.py
CHANGED
@@ -1,195 +1,200 @@
|
|
1
|
-
import os
|
2
|
-
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import requests
|
6
|
-
|
7
|
-
from segment_anything import
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
print(
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
response.
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
self
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
self.
|
77
|
-
self.
|
78
|
-
|
79
|
-
self.
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
)
|
91
|
-
|
92
|
-
# Use
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
#
|
98
|
-
|
99
|
-
|
100
|
-
)
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
print(f"[
|
122
|
-
self.
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
self.
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
self.
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
return
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
1
|
+
import os
|
2
|
+
|
3
|
+
import cv2
|
4
|
+
import numpy as np
|
5
|
+
import requests
|
6
|
+
import torch
|
7
|
+
from segment_anything import SamPredictor, sam_model_registry
|
8
|
+
from tqdm import tqdm
|
9
|
+
|
10
|
+
|
11
|
+
def download_model(url, download_path):
|
12
|
+
"""Downloads file with a progress bar."""
|
13
|
+
print("[10/20] SAM model not found. Downloading from Meta's repository...")
|
14
|
+
print(f" Downloading to: {download_path}")
|
15
|
+
try:
|
16
|
+
print("[10/20] Connecting to download server...")
|
17
|
+
response = requests.get(url, stream=True, timeout=30)
|
18
|
+
response.raise_for_status()
|
19
|
+
total_size_in_bytes = int(response.headers.get("content-length", 0))
|
20
|
+
block_size = 1024 # 1 Kibibyte
|
21
|
+
|
22
|
+
print(
|
23
|
+
f"[10/20] Starting download ({total_size_in_bytes / (1024 * 1024 * 1024):.1f} GB)..."
|
24
|
+
)
|
25
|
+
progress_bar = tqdm(total=total_size_in_bytes, unit="iB", unit_scale=True)
|
26
|
+
with open(download_path, "wb") as file:
|
27
|
+
for data in response.iter_content(block_size):
|
28
|
+
progress_bar.update(len(data))
|
29
|
+
file.write(data)
|
30
|
+
progress_bar.close()
|
31
|
+
|
32
|
+
if total_size_in_bytes != 0 and progress_bar.n != total_size_in_bytes:
|
33
|
+
raise RuntimeError("Download incomplete - file size mismatch")
|
34
|
+
|
35
|
+
print("[10/20] Model download completed successfully.")
|
36
|
+
|
37
|
+
except requests.exceptions.ConnectionError as e:
|
38
|
+
raise RuntimeError(
|
39
|
+
"[10/20] Network connection failed: Check your internet connection"
|
40
|
+
) from e
|
41
|
+
except requests.exceptions.Timeout as e:
|
42
|
+
raise RuntimeError(
|
43
|
+
"[10/20] Download timeout: Server took too long to respond"
|
44
|
+
) from e
|
45
|
+
except requests.exceptions.HTTPError as e:
|
46
|
+
raise RuntimeError(
|
47
|
+
f"[10/20] HTTP error {e.response.status_code}: Server rejected request"
|
48
|
+
) from e
|
49
|
+
except requests.exceptions.RequestException as e:
|
50
|
+
raise RuntimeError(f"[10/20] Network error during download: {e}") from e
|
51
|
+
except PermissionError as e:
|
52
|
+
raise RuntimeError(
|
53
|
+
f"[10/20] Permission denied: Cannot write to {download_path}"
|
54
|
+
) from e
|
55
|
+
except OSError as e:
|
56
|
+
raise RuntimeError(
|
57
|
+
f"[10/20] Disk error: {e} (check available disk space)"
|
58
|
+
) from e
|
59
|
+
except Exception as e:
|
60
|
+
# Clean up partial download
|
61
|
+
if os.path.exists(download_path):
|
62
|
+
import contextlib
|
63
|
+
|
64
|
+
with contextlib.suppress(OSError):
|
65
|
+
os.remove(download_path)
|
66
|
+
raise RuntimeError(f"[10/20] Download failed: {e}") from e
|
67
|
+
|
68
|
+
|
69
|
+
class SamModel:
|
70
|
+
def __init__(
|
71
|
+
self,
|
72
|
+
model_type="vit_h",
|
73
|
+
model_filename="sam_vit_h_4b8939.pth",
|
74
|
+
custom_model_path=None,
|
75
|
+
):
|
76
|
+
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
77
|
+
print(f"[9/20] Detected device: {str(self.device).upper()}")
|
78
|
+
|
79
|
+
self.current_model_type = model_type
|
80
|
+
self.current_model_path = custom_model_path
|
81
|
+
self.model = None
|
82
|
+
self.predictor = None
|
83
|
+
self.image = None
|
84
|
+
self.is_loaded = False
|
85
|
+
|
86
|
+
try:
|
87
|
+
if custom_model_path and os.path.exists(custom_model_path):
|
88
|
+
# Use custom model path
|
89
|
+
model_path = custom_model_path
|
90
|
+
print(f"[10/20] Loading custom SAM model from {model_path}...")
|
91
|
+
else:
|
92
|
+
# Use default model with download if needed - store in models folder
|
93
|
+
model_url = (
|
94
|
+
f"https://dl.fbaipublicfiles.com/segment_anything/{model_filename}"
|
95
|
+
)
|
96
|
+
|
97
|
+
# Use models folder instead of cache folder
|
98
|
+
models_dir = os.path.dirname(__file__) # Already in models directory
|
99
|
+
os.makedirs(models_dir, exist_ok=True)
|
100
|
+
model_path = os.path.join(models_dir, model_filename)
|
101
|
+
|
102
|
+
# Also check the old cache location and move it if it exists
|
103
|
+
old_cache_dir = os.path.join(
|
104
|
+
os.path.expanduser("~"), ".cache", "lazylabel"
|
105
|
+
)
|
106
|
+
old_model_path = os.path.join(old_cache_dir, model_filename)
|
107
|
+
|
108
|
+
if os.path.exists(old_model_path) and not os.path.exists(model_path):
|
109
|
+
print(
|
110
|
+
"[10/20] Moving existing model from cache to models folder..."
|
111
|
+
)
|
112
|
+
import shutil
|
113
|
+
|
114
|
+
shutil.move(old_model_path, model_path)
|
115
|
+
elif not os.path.exists(model_path):
|
116
|
+
# Download the model if it doesn't exist
|
117
|
+
download_model(model_url, model_path)
|
118
|
+
|
119
|
+
print(f"[10/20] Loading default SAM model from {model_path}...")
|
120
|
+
|
121
|
+
print(f"[11/20] Initializing {model_type.upper()} model architecture...")
|
122
|
+
self.model = sam_model_registry[model_type](checkpoint=model_path).to(
|
123
|
+
self.device
|
124
|
+
)
|
125
|
+
|
126
|
+
print("[12/20] Setting up predictor...")
|
127
|
+
self.predictor = SamPredictor(self.model)
|
128
|
+
self.is_loaded = True
|
129
|
+
print("[13/20] SAM model loaded successfully.")
|
130
|
+
|
131
|
+
except Exception as e:
|
132
|
+
print(f"[8/20] Failed to load SAM model: {e}")
|
133
|
+
print("[8/20] SAM point functionality will be disabled.")
|
134
|
+
self.is_loaded = False
|
135
|
+
|
136
|
+
def load_custom_model(self, model_path, model_type="vit_h"):
|
137
|
+
"""Load a custom model from the specified path."""
|
138
|
+
if not os.path.exists(model_path):
|
139
|
+
print(f"Model file not found: {model_path}")
|
140
|
+
return False
|
141
|
+
|
142
|
+
print(f"Loading custom SAM model from {model_path}...")
|
143
|
+
try:
|
144
|
+
# Clear existing model from memory
|
145
|
+
if hasattr(self, "model") and self.model is not None:
|
146
|
+
del self.model
|
147
|
+
del self.predictor
|
148
|
+
torch.cuda.empty_cache() if torch.cuda.is_available() else None
|
149
|
+
|
150
|
+
# Load new model
|
151
|
+
self.model = sam_model_registry[model_type](checkpoint=model_path).to(
|
152
|
+
self.device
|
153
|
+
)
|
154
|
+
self.predictor = SamPredictor(self.model)
|
155
|
+
self.current_model_type = model_type
|
156
|
+
self.current_model_path = model_path
|
157
|
+
self.is_loaded = True
|
158
|
+
|
159
|
+
# Re-set image if one was previously loaded
|
160
|
+
if self.image is not None:
|
161
|
+
self.predictor.set_image(self.image)
|
162
|
+
|
163
|
+
print("Custom SAM model loaded successfully.")
|
164
|
+
return True
|
165
|
+
except Exception as e:
|
166
|
+
print(f"Error loading custom model: {e}")
|
167
|
+
self.is_loaded = False
|
168
|
+
self.model = None
|
169
|
+
self.predictor = None
|
170
|
+
return False
|
171
|
+
|
172
|
+
def set_image(self, image_path):
|
173
|
+
if not self.is_loaded:
|
174
|
+
return False
|
175
|
+
try:
|
176
|
+
self.image = cv2.imread(image_path)
|
177
|
+
self.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)
|
178
|
+
self.predictor.set_image(self.image)
|
179
|
+
return True
|
180
|
+
except Exception as e:
|
181
|
+
print(f"Error setting image: {e}")
|
182
|
+
return False
|
183
|
+
|
184
|
+
def predict(self, positive_points, negative_points):
|
185
|
+
if not self.is_loaded or not positive_points:
|
186
|
+
return None
|
187
|
+
|
188
|
+
try:
|
189
|
+
points = np.array(positive_points + negative_points)
|
190
|
+
labels = np.array([1] * len(positive_points) + [0] * len(negative_points))
|
191
|
+
|
192
|
+
masks, _, _ = self.predictor.predict(
|
193
|
+
point_coords=points,
|
194
|
+
point_labels=labels,
|
195
|
+
multimask_output=False,
|
196
|
+
)
|
197
|
+
return masks[0]
|
198
|
+
except Exception as e:
|
199
|
+
print(f"Error during prediction: {e}")
|
200
|
+
return None
|
lazylabel/ui/__init__.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
"""UI components for LazyLabel."""
|
2
|
-
|
3
|
-
from .
|
4
|
-
from .
|
5
|
-
from .
|
6
|
-
from .
|
7
|
-
|
8
|
-
__all__ = [
|
1
|
+
"""UI components for LazyLabel."""
|
2
|
+
|
3
|
+
from .control_panel import ControlPanel
|
4
|
+
from .hotkey_dialog import HotkeyDialog
|
5
|
+
from .main_window import MainWindow
|
6
|
+
from .right_panel import RightPanel
|
7
|
+
|
8
|
+
__all__ = ["MainWindow", "ControlPanel", "RightPanel", "HotkeyDialog"]
|