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.
@@ -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.12
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,,