microlive 1.0.12__py3-none-any.whl → 1.0.19__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.
- microlive/__init__.py +1 -1
- microlive/data/models/spot_detection_cnn.pth +0 -0
- microlive/gui/app.py +1412 -58
- microlive/imports.py +5 -1
- microlive/microscopy.py +51 -9
- microlive/pipelines/pipeline_FRAP.py +212 -23
- microlive/pipelines/pipeline_folding_efficiency.py +29 -25
- microlive/pipelines/pipeline_particle_tracking.py +616 -176
- microlive/utils/__init__.py +11 -0
- microlive/utils/model_downloader.py +293 -0
- {microlive-1.0.12.dist-info → microlive-1.0.19.dist-info}/METADATA +2 -1
- microlive-1.0.19.dist-info/RECORD +27 -0
- microlive/pipelines/pipeline_spot_detection_no_tracking.py +0 -368
- microlive-1.0.12.dist-info/RECORD +0 -26
- {microlive-1.0.12.dist-info → microlive-1.0.19.dist-info}/WHEEL +0 -0
- {microlive-1.0.12.dist-info → microlive-1.0.19.dist-info}/entry_points.txt +0 -0
- {microlive-1.0.12.dist-info → microlive-1.0.19.dist-info}/licenses/LICENSE +0 -0
microlive/utils/__init__.py
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from .device import get_device, is_gpu_available, get_device_info, check_gpu_status
|
|
4
4
|
from .resources import get_icon_path, get_model_path
|
|
5
|
+
from .model_downloader import (
|
|
6
|
+
get_frap_nuclei_model_path,
|
|
7
|
+
cache_model,
|
|
8
|
+
list_cached_models,
|
|
9
|
+
MODEL_DIR,
|
|
10
|
+
)
|
|
5
11
|
|
|
6
12
|
__all__ = [
|
|
7
13
|
"get_device",
|
|
@@ -10,4 +16,9 @@ __all__ = [
|
|
|
10
16
|
"check_gpu_status",
|
|
11
17
|
"get_icon_path",
|
|
12
18
|
"get_model_path",
|
|
19
|
+
# Model downloader
|
|
20
|
+
"get_frap_nuclei_model_path",
|
|
21
|
+
"cache_model",
|
|
22
|
+
"list_cached_models",
|
|
23
|
+
"MODEL_DIR",
|
|
13
24
|
]
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Model download utilities for MicroLive.
|
|
3
|
+
|
|
4
|
+
This module provides functions to download and cache pretrained models from
|
|
5
|
+
the MicroLive GitHub repository. It follows the same patterns used by Cellpose
|
|
6
|
+
for robust model provisioning.
|
|
7
|
+
|
|
8
|
+
Models are downloaded on first use and cached locally in ~/.microlive/models/
|
|
9
|
+
to avoid repeated downloads.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import ssl
|
|
14
|
+
import shutil
|
|
15
|
+
import tempfile
|
|
16
|
+
import logging
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from urllib.request import urlopen
|
|
19
|
+
from urllib.error import URLError, HTTPError
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# =============================================================================
|
|
24
|
+
# Configuration
|
|
25
|
+
# =============================================================================
|
|
26
|
+
|
|
27
|
+
# Base URL for raw GitHub content
|
|
28
|
+
_GITHUB_RAW_BASE = "https://raw.githubusercontent.com/ningzhaoAnschutz/microlive/main"
|
|
29
|
+
|
|
30
|
+
# Model URLs - Add new models here
|
|
31
|
+
MODEL_URLS = {
|
|
32
|
+
"frap_nuclei": f"{_GITHUB_RAW_BASE}/modeling/cellpose_models/cellpose_models/FRAP_nuclei_model/models/cellpose_1728581750.581418",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Local cache directory (similar to Cellpose's ~/.cellpose/models/)
|
|
36
|
+
_MODEL_DIR_ENV = os.environ.get("MICROLIVE_LOCAL_MODELS_PATH")
|
|
37
|
+
_MODEL_DIR_DEFAULT = Path.home() / ".microlive" / "models"
|
|
38
|
+
MODEL_DIR = Path(_MODEL_DIR_ENV) if _MODEL_DIR_ENV else _MODEL_DIR_DEFAULT
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# =============================================================================
|
|
42
|
+
# Download Utilities (adapted from Cellpose)
|
|
43
|
+
# =============================================================================
|
|
44
|
+
|
|
45
|
+
def download_url_to_file(url: str, dst: str, progress: bool = True) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Download object at the given URL to a local path.
|
|
48
|
+
|
|
49
|
+
Adapted from Cellpose/torch implementation for robustness.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
url: URL of the object to download.
|
|
53
|
+
dst: Full path where object will be saved.
|
|
54
|
+
progress: Whether to display a progress bar. Default: True.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
HTTPError: If the server returns an error status.
|
|
58
|
+
URLError: If the URL cannot be reached.
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
from tqdm import tqdm
|
|
62
|
+
HAS_TQDM = True
|
|
63
|
+
except ImportError:
|
|
64
|
+
HAS_TQDM = False
|
|
65
|
+
progress = False
|
|
66
|
+
|
|
67
|
+
file_size = None
|
|
68
|
+
|
|
69
|
+
# Handle SSL certificate verification issues
|
|
70
|
+
ssl_context = ssl.create_default_context()
|
|
71
|
+
ssl_context.check_hostname = False
|
|
72
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
u = urlopen(url, context=ssl_context)
|
|
76
|
+
except URLError as e:
|
|
77
|
+
raise URLError(f"Failed to connect to {url}: {e}")
|
|
78
|
+
|
|
79
|
+
meta = u.info()
|
|
80
|
+
if hasattr(meta, "getheaders"):
|
|
81
|
+
content_length = meta.getheaders("Content-Length")
|
|
82
|
+
else:
|
|
83
|
+
content_length = meta.get_all("Content-Length")
|
|
84
|
+
|
|
85
|
+
if content_length is not None and len(content_length) > 0:
|
|
86
|
+
file_size = int(content_length[0])
|
|
87
|
+
|
|
88
|
+
# Save to temp file first, then move (atomic operation)
|
|
89
|
+
dst = os.path.expanduser(dst)
|
|
90
|
+
dst_dir = os.path.dirname(dst)
|
|
91
|
+
os.makedirs(dst_dir, exist_ok=True)
|
|
92
|
+
|
|
93
|
+
f = tempfile.NamedTemporaryFile(delete=False, dir=dst_dir)
|
|
94
|
+
try:
|
|
95
|
+
if HAS_TQDM and progress:
|
|
96
|
+
with tqdm(total=file_size, disable=not progress, unit="B",
|
|
97
|
+
unit_scale=True, unit_divisor=1024,
|
|
98
|
+
desc=f"Downloading {Path(dst).name}") as pbar:
|
|
99
|
+
while True:
|
|
100
|
+
buffer = u.read(8192)
|
|
101
|
+
if len(buffer) == 0:
|
|
102
|
+
break
|
|
103
|
+
f.write(buffer)
|
|
104
|
+
pbar.update(len(buffer))
|
|
105
|
+
else:
|
|
106
|
+
# Simple download without progress bar
|
|
107
|
+
while True:
|
|
108
|
+
buffer = u.read(8192)
|
|
109
|
+
if len(buffer) == 0:
|
|
110
|
+
break
|
|
111
|
+
f.write(buffer)
|
|
112
|
+
|
|
113
|
+
f.close()
|
|
114
|
+
shutil.move(f.name, dst)
|
|
115
|
+
logger.info(f"Successfully downloaded model to {dst}")
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
f.close()
|
|
119
|
+
if os.path.exists(f.name):
|
|
120
|
+
os.remove(f.name)
|
|
121
|
+
raise RuntimeError(f"Download failed: {e}")
|
|
122
|
+
finally:
|
|
123
|
+
if os.path.exists(f.name):
|
|
124
|
+
try:
|
|
125
|
+
os.remove(f.name)
|
|
126
|
+
except OSError:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# =============================================================================
|
|
131
|
+
# Model Cache Functions
|
|
132
|
+
# =============================================================================
|
|
133
|
+
|
|
134
|
+
def get_model_path(model_name: str) -> Path:
|
|
135
|
+
"""
|
|
136
|
+
Get the local cache path for a model.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
model_name: Name of the model (e.g., "frap_nuclei").
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Path to the cached model file.
|
|
143
|
+
"""
|
|
144
|
+
return MODEL_DIR / model_name
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def is_model_cached(model_name: str) -> bool:
|
|
148
|
+
"""
|
|
149
|
+
Check if a model is already cached locally.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
model_name: Name of the model.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
True if the model exists locally, False otherwise.
|
|
156
|
+
"""
|
|
157
|
+
return get_model_path(model_name).exists()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def cache_model(model_name: str, force_download: bool = False) -> str:
|
|
161
|
+
"""
|
|
162
|
+
Ensure a model is cached locally, downloading if necessary.
|
|
163
|
+
|
|
164
|
+
This function follows the Cellpose pattern:
|
|
165
|
+
1. Check if model exists in local cache
|
|
166
|
+
2. If not (or force_download=True), download from GitHub
|
|
167
|
+
3. Return the local path
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
model_name: Name of the model (must be in MODEL_URLS).
|
|
171
|
+
force_download: If True, re-download even if cached.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
String path to the cached model file.
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
ValueError: If model_name is not recognized.
|
|
178
|
+
RuntimeError: If download fails.
|
|
179
|
+
"""
|
|
180
|
+
if model_name not in MODEL_URLS:
|
|
181
|
+
available = ", ".join(MODEL_URLS.keys())
|
|
182
|
+
raise ValueError(f"Unknown model '{model_name}'. Available: {available}")
|
|
183
|
+
|
|
184
|
+
MODEL_DIR.mkdir(parents=True, exist_ok=True)
|
|
185
|
+
cached_file = get_model_path(model_name)
|
|
186
|
+
|
|
187
|
+
if not cached_file.exists() or force_download:
|
|
188
|
+
url = MODEL_URLS[model_name]
|
|
189
|
+
logger.info(f"Downloading model '{model_name}' from {url}")
|
|
190
|
+
print(f"Downloading MicroLive model '{model_name}' (first time only)...")
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
download_url_to_file(url, str(cached_file), progress=True)
|
|
194
|
+
except (HTTPError, URLError) as e:
|
|
195
|
+
raise RuntimeError(
|
|
196
|
+
f"Failed to download model '{model_name}' from GitHub. "
|
|
197
|
+
f"Error: {e}\n\n"
|
|
198
|
+
f"If this persists, you can manually download from:\n"
|
|
199
|
+
f" {url}\n"
|
|
200
|
+
f"And place it at:\n"
|
|
201
|
+
f" {cached_file}"
|
|
202
|
+
)
|
|
203
|
+
else:
|
|
204
|
+
logger.debug(f"Model '{model_name}' already cached at {cached_file}")
|
|
205
|
+
|
|
206
|
+
return str(cached_file)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# =============================================================================
|
|
210
|
+
# Convenience Functions for Specific Models
|
|
211
|
+
# =============================================================================
|
|
212
|
+
|
|
213
|
+
def get_frap_nuclei_model_path() -> str:
|
|
214
|
+
"""
|
|
215
|
+
Get the path to the FRAP nuclei segmentation model.
|
|
216
|
+
|
|
217
|
+
Downloads the model from GitHub if not already cached locally.
|
|
218
|
+
The model is stored in ~/.microlive/models/frap_nuclei
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
String path to the FRAP nuclei model file.
|
|
222
|
+
|
|
223
|
+
Example:
|
|
224
|
+
>>> from microlive.utils.model_downloader import get_frap_nuclei_model_path
|
|
225
|
+
>>> model_path = get_frap_nuclei_model_path()
|
|
226
|
+
>>> # Use with Cellpose
|
|
227
|
+
>>> from cellpose import models
|
|
228
|
+
>>> model = models.CellposeModel(pretrained_model=model_path)
|
|
229
|
+
"""
|
|
230
|
+
return cache_model("frap_nuclei")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# =============================================================================
|
|
234
|
+
# Verification and Diagnostics
|
|
235
|
+
# =============================================================================
|
|
236
|
+
|
|
237
|
+
def verify_model_integrity(model_name: str) -> bool:
|
|
238
|
+
"""
|
|
239
|
+
Verify that a cached model file exists and has non-zero size.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
model_name: Name of the model to verify.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
True if the model file exists and is valid.
|
|
246
|
+
"""
|
|
247
|
+
model_path = get_model_path(model_name)
|
|
248
|
+
if not model_path.exists():
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
# Check file size (should be > 1MB for a real model)
|
|
252
|
+
size_bytes = model_path.stat().st_size
|
|
253
|
+
if size_bytes < 1_000_000:
|
|
254
|
+
logger.warning(f"Model file seems too small ({size_bytes} bytes): {model_path}")
|
|
255
|
+
return False
|
|
256
|
+
|
|
257
|
+
return True
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def list_cached_models() -> dict:
|
|
261
|
+
"""
|
|
262
|
+
List all cached models and their status.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Dictionary mapping model names to their cache status and size.
|
|
266
|
+
"""
|
|
267
|
+
result = {}
|
|
268
|
+
for name in MODEL_URLS:
|
|
269
|
+
path = get_model_path(name)
|
|
270
|
+
if path.exists():
|
|
271
|
+
size_mb = path.stat().st_size / (1024 * 1024)
|
|
272
|
+
result[name] = {"cached": True, "size_mb": round(size_mb, 2), "path": str(path)}
|
|
273
|
+
else:
|
|
274
|
+
result[name] = {"cached": False, "size_mb": 0, "path": str(path)}
|
|
275
|
+
return result
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def clear_model_cache(model_name: str = None) -> None:
|
|
279
|
+
"""
|
|
280
|
+
Clear cached models.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
model_name: Specific model to clear, or None to clear all.
|
|
284
|
+
"""
|
|
285
|
+
if model_name:
|
|
286
|
+
path = get_model_path(model_name)
|
|
287
|
+
if path.exists():
|
|
288
|
+
path.unlink()
|
|
289
|
+
logger.info(f"Cleared cached model: {model_name}")
|
|
290
|
+
else:
|
|
291
|
+
if MODEL_DIR.exists():
|
|
292
|
+
shutil.rmtree(MODEL_DIR)
|
|
293
|
+
logger.info(f"Cleared all cached models from {MODEL_DIR}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microlive
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.19
|
|
4
4
|
Summary: Live-cell microscopy image analysis and single-molecule measurements
|
|
5
5
|
Project-URL: Homepage, https://github.com/ningzhaoAnschutz/microlive
|
|
6
6
|
Project-URL: Documentation, https://github.com/ningzhaoAnschutz/microlive/blob/main/docs/user_guide.md
|
|
@@ -67,6 +67,7 @@ Requires-Dist: scipy==1.13.1
|
|
|
67
67
|
Requires-Dist: seaborn==0.13.2
|
|
68
68
|
Requires-Dist: snapgene-reader>=0.1
|
|
69
69
|
Requires-Dist: statsmodels==0.14.4
|
|
70
|
+
Requires-Dist: tasep-models
|
|
70
71
|
Requires-Dist: tifffile>=2024.1
|
|
71
72
|
Requires-Dist: tqdm>=4.66
|
|
72
73
|
Requires-Dist: trackpy>=0.6
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
microlive/__init__.py,sha256=x5itFGRF8M9JE08BfNbfCkp9JerbxKsTXr7mskW6CUE,1385
|
|
2
|
+
microlive/imports.py,sha256=wMJNmtG06joCJNPryktCwEKz1HCJhfGcm3et3boINuc,7676
|
|
3
|
+
microlive/microscopy.py,sha256=OFqf0JXJW4-2cLHvXnwwp_SfMFsUXwp5lDKbkCRR4ok,710841
|
|
4
|
+
microlive/ml_spot_detection.py,sha256=pVbOSGNJ0WWMuPRML42rFwvjKVZ0B1fJux1179OIbAg,10603
|
|
5
|
+
microlive/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
microlive/data/icons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
microlive/data/icons/icon_micro.png,sha256=b5tFv4E6vUmLwYmYeM4PJuxLV_XqEzN14ueolekTFW0,370236
|
|
8
|
+
microlive/data/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
microlive/gui/__init__.py,sha256=tB-CdDC7x5OwYFAQxLOUvfVnUThaXKXVRsB68YP0Y6Q,28
|
|
10
|
+
microlive/gui/app.py,sha256=tQTFbTkbT_NRMFVdw8fnsA5krrUczZANEscQOcuZRGQ,848759
|
|
11
|
+
microlive/gui/main.py,sha256=b66W_2V-pclGKOozfs75pwrCGbL_jkVU3kFt8RFMZIc,2520
|
|
12
|
+
microlive/gui/micro_mac.command,sha256=TkxYOO_5A2AiNJMz3_--1geBYfl77THpOLFZnV4J2ac,444
|
|
13
|
+
microlive/gui/micro_windows.bat,sha256=DJUKPhDbCO4HToLwSMT-QTYRe9Kr1wn5A2Ijy2klIrw,773
|
|
14
|
+
microlive/pipelines/__init__.py,sha256=VimchYrIWalFs_edRmjR1zBHIg2CcpRceZoRmB1e8kA,764
|
|
15
|
+
microlive/pipelines/pipeline_FRAP.py,sha256=AItojd471cAJoybW-PNrs3u8pwmuWU-UAjlY4C7oin4,63324
|
|
16
|
+
microlive/pipelines/pipeline_folding_efficiency.py,sha256=qR-DycvSuMRsjmCVRJWxWy9XTDF9Zd3fkTRsYxiEHso,22846
|
|
17
|
+
microlive/pipelines/pipeline_particle_tracking.py,sha256=ATrJs1ajs2pNcRmTlgaulWSmV9UNAmJ_0MMBybsseMk,37469
|
|
18
|
+
microlive/utils/__init__.py,sha256=metAf2zPS8w23d8dyM7-ld1ovrOKBdx3y3zu5IVrzIg,564
|
|
19
|
+
microlive/utils/device.py,sha256=tcPMU8UiXL-DuGwhudUgrbjW1lgIK_EUKIOeOn0U6q4,2533
|
|
20
|
+
microlive/utils/model_downloader.py,sha256=EruviTEh75YBekpznn1RZ1Nj8lnDmeC4TKEnFLOow6Y,9448
|
|
21
|
+
microlive/utils/resources.py,sha256=Jz7kPI75xMLCBJMyX7Y_3ixKi_UgydfQkF0BlFtLCKs,1753
|
|
22
|
+
microlive/data/models/spot_detection_cnn.pth,sha256=Np7vpPJIbKQmuKY0Hx-4IkeEDsnks_QEgs7TqaYgZmI,8468580
|
|
23
|
+
microlive-1.0.19.dist-info/METADATA,sha256=XJvzWLd4-xz5XX-D67zxOWPrE_-XM76g5GScvzUyubw,12462
|
|
24
|
+
microlive-1.0.19.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
25
|
+
microlive-1.0.19.dist-info/entry_points.txt,sha256=Zqp2vixyD8lngcfEmOi8fkCj7vPhesz5xlGBI-EubRw,54
|
|
26
|
+
microlive-1.0.19.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
|
27
|
+
microlive-1.0.19.dist-info/RECORD,,
|