patholens 2.0.0__tar.gz → 2.0.2__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.
Files changed (46) hide show
  1. {patholens-2.0.0 → patholens-2.0.2}/PKG-INFO +1 -1
  2. {patholens-2.0.0 → patholens-2.0.2}/patholens-backend/app/main.py +1 -1
  3. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/globals.css +8 -1
  4. patholens-2.0.2/patholens-ui/next-env.d.ts +6 -0
  5. {patholens-2.0.0 → patholens-2.0.2}/patholens_cli/__init__.py +1 -1
  6. {patholens-2.0.0 → patholens-2.0.2}/patholens_cli/main.py +179 -69
  7. {patholens-2.0.0 → patholens-2.0.2}/pyproject.toml +1 -1
  8. {patholens-2.0.0 → patholens-2.0.2}/.gitignore +0 -0
  9. {patholens-2.0.0 → patholens-2.0.2}/README.md +0 -0
  10. {patholens-2.0.0 → patholens-2.0.2}/patholens-backend/app/database.py +0 -0
  11. {patholens-2.0.0 → patholens-2.0.2}/patholens-backend/app/models.py +0 -0
  12. {patholens-2.0.0 → patholens-2.0.2}/patholens-backend/requirements.txt +0 -0
  13. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/.gitignore +0 -0
  14. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/OncoGemma-logo-no-bg_low.png +0 -0
  15. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/OncoGemma-no-bg-high_gemini.png +0 -0
  16. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/README.md +0 -0
  17. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/dashboard/page.tsx +0 -0
  18. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/favicon.ico +0 -0
  19. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/layout.tsx +0 -0
  20. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/lib/auth.ts +0 -0
  21. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/lib/config.ts +0 -0
  22. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/login/page.tsx +0 -0
  23. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/page.tsx +0 -0
  24. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/viewer/page.tsx +0 -0
  25. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/components/AIResults.tsx +0 -0
  26. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/components/ClinicalChat.tsx +0 -0
  27. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/components/OncoLLM.tsx +0 -0
  28. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/components/WSIViewer.tsx +0 -0
  29. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/eslint.config.mjs +0 -0
  30. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/next.config.ts +0 -0
  31. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/package-lock.json +0 -0
  32. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/package.json +0 -0
  33. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/postcss.config.mjs +0 -0
  34. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/file.svg +0 -0
  35. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/globe.svg +0 -0
  36. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/hawkfranklin_logo.png +0 -0
  37. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/next.svg +0 -0
  38. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/onco-gemma-logo.png +0 -0
  39. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/oncogemma_full_high_crop.png +0 -0
  40. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/oncogemma_logo_only.png +0 -0
  41. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/oncogemma_text_only.png +0 -0
  42. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/pathogemma_icon.png +0 -0
  43. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/vercel.svg +0 -0
  44. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/window.svg +0 -0
  45. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/tsconfig.json +0 -0
  46. {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/types.d.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patholens
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: A command-line tool to manage, deploy, and launch PathoLens with Google Colab or local backends.
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: click>=8.0.0
@@ -470,7 +470,7 @@ async def get_toxicity(current_user: dict = Depends(get_current_user)):
470
470
 
471
471
  class OncoLLMQuery(pydantic.BaseModel):
472
472
  query: str
473
- context: dict = {}
473
+ context: Optional[dict] = None
474
474
 
475
475
  @app.post("/ai/oncollm/query")
476
476
  async def query_oncollm(request: OncoLLMQuery, current_user: dict = Depends(get_current_user)):
@@ -1,4 +1,11 @@
1
1
  @import "tailwindcss";
2
+ @source "../components/**/*.ts";
3
+ @source "../components/**/*.tsx";
4
+ @source "../types.d.ts";
5
+ @source inline("h-screen min-h-screen h-14 h-16 h-10 h-8 h-5 h-2.5 h-[40%] h-[45%] w-80 w-[400px] w-64 w-48 w-32 w-16 w-10 w-8 w-5 w-2.5 w-2 w-[40%] w-[45%] max-w-7xl max-w-md p-12 p-6 px-6 py-3.5 py-3 py-1 pr-4 pl-10 z-10 block hidden md:block sr-only");
6
+ @source inline("top-1/2 top-4 top-[-10%] right-4 right-[-5%] bottom-[-15%] left-1/2 left-3 left-[-10%] -translate-x-1/2 -translate-y-1/2 transform gap-3 gap-8 space-y-8 divide-y divide-gray-100 border-r border-2 border-b-2 border-gray-300 border-blue-600 border-purple-600 border-red-400 border-t-white border-white/40");
7
+ @source inline("bg-black bg-gray-400 bg-blue-50/50 bg-blue-200/30 bg-cyan-200/30 bg-purple-50/50 bg-purple-600 bg-green-500 bg-yellow-500 bg-red-500 bg-orange-500 from-gray-50 from-blue-600 via-white to-blue-50 to-indigo-600 to-cyan-500 blur-[120px] blur-[140px] drop-shadow-sm drop-shadow-md shadow-lg shadow-md shadow-blue-200 shadow-[0_0_8px_rgba(34,197,94,0.6)] shadow-[0_0_8px_rgba(234,179,8,0.6)] shadow-[0_0_8px_rgba(239,68,68,0.6)]");
8
+ @source inline("mt-8 mt-20 mb-6 mb-8 mb-10 opacity-20 opacity-75 cursor-not-allowed font-mono text-xl text-right uppercase tracking-wider transition-all active:scale-[0.99] hover:scale-[1.01] hover:bg-blue-50 hover:bg-gray-50 hover:bg-gray-200 hover:from-blue-500 hover:from-blue-700 hover:to-cyan-400 hover:to-indigo-700 hover:text-gray-700 hover:text-red-600 hover:shadow-lg group-hover:bg-blue-200 group-hover:bg-green-200 group-hover:text-blue-700 group-hover:text-green-700 focus:ring-1 placeholder-gray-400");
2
9
 
3
10
  @theme {
4
11
  --font-sans: var(--font-geist-sans);
@@ -77,4 +84,4 @@ body {
77
84
 
78
85
  .scrollbar-thin::-webkit-scrollbar-thumb:hover {
79
86
  background-color: rgba(75, 85, 99, 0.8);
80
- }
87
+ }
@@ -0,0 +1,6 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ import "./.next/types/routes.d.ts";
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -1,2 +1,2 @@
1
1
  # PathoLens CLI module
2
- __version__ = "2.0.0"
2
+ __version__ = "2.0.2"
@@ -6,20 +6,45 @@ import tempfile
6
6
  import time
7
7
  import webbrowser
8
8
  import shutil
9
+ import json
10
+ import sys
11
+
12
+ # Platform-specific import for non-blocking console input
13
+ if sys.platform != 'win32':
14
+ import select
15
+
16
+ CONFIG_DIR = os.path.expanduser("~/.config/patholens")
17
+ CONFIG_PATH = os.path.join(CONFIG_DIR, "config.json")
9
18
 
10
19
  BANNER = """\033[95m
11
20
  █▀█ █▀█ ▀█▀ █▄█ █▀█ █  █▀▀ █▄ █ █▀
12
- █▀▀ █▀█  █  █ █ █▄█ █▄▄ ██▄ █ ▀█ ▄█\033[0m \033[93mcli v2.0.0\033[0m
21
+ █▀▀ █▀█  █  █ █ █▄█ █▄▄ ██▄ █ ▀█ ▄█\033[0m \033[93mcli v2.0.2\033[0m
13
22
  \033[90m🔬 Digital Pathology Workstation & AI Workspace\033[0m
14
23
  """
15
24
 
16
25
  def print_banner():
17
26
  click.echo(BANNER)
18
- # Print status details similarly to the Gemini CLI screenshot
19
27
  click.echo(" \033[93m}\033[0m \033[1mEnvironment:\033[0m Cloud-Scalable / Multi-Tenant Ready")
20
28
  click.echo(" \033[93m}\033[0m \033[1mLocal Fallback:\033[0m SQLite + Local Slide Storage")
21
29
  click.echo()
22
30
 
31
+ def load_config() -> dict:
32
+ if os.path.exists(CONFIG_PATH):
33
+ try:
34
+ with open(CONFIG_PATH, 'r') as f:
35
+ return json.load(f)
36
+ except Exception:
37
+ pass
38
+ return {}
39
+
40
+ def save_config(config_data: dict):
41
+ try:
42
+ os.makedirs(CONFIG_DIR, exist_ok=True)
43
+ with open(CONFIG_PATH, 'w') as f:
44
+ json.dump(config_data, f, indent=2)
45
+ except Exception as e:
46
+ click.echo(f"[patholens] Warning: Could not save configuration: {e}")
47
+
23
48
  def prompt_choice(question: str, options: list) -> int:
24
49
  """Prompts the user with numbered options and returns the 1-based index selection."""
25
50
  click.echo(f"\033[96m? \033[1m{question}\033[0m")
@@ -33,10 +58,11 @@ def prompt_choice(question: str, options: list) -> int:
33
58
  click.echo(f" \033[91mInvalid choice. Select 1-{len(options)}.\033[0m")
34
59
 
35
60
  def zip_project(src_dir: str, zip_path: str):
36
- """Zips the PathoLens project directories, excluding heavy build caches and libraries."""
37
- ignore_dirs = {".git", ".next", "node_modules", "out", "__pycache__", "cptac_samples", ".pytest_cache", ".gemini", "brain"}
61
+ """Zips the PathoLens project directories, excluding heavy build caches, local slides, and databases."""
62
+ ignore_dirs = {".git", ".next", "node_modules", "out", "__pycache__", "cptac_samples", "sample_data", ".pytest_cache", ".gemini", "brain"}
38
63
  with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
39
64
  for root, dirs, files in os.walk(src_dir):
65
+ # Prune directories in place to prevent os.walk from scanning them
40
66
  dirs[:] = [d for d in dirs if d not in ignore_dirs]
41
67
  for file in files:
42
68
  if file.endswith(".zip") or file.endswith(".db") or file.endswith(".svs"):
@@ -70,7 +96,7 @@ def build_frontend_locally(project_root: str) -> bool:
70
96
 
71
97
  try:
72
98
  subprocess.run(["npm", "install"], cwd=ui_dir, check=True)
73
- subprocess.run(["npm", "run build"], cwd=ui_dir, check=True)
99
+ subprocess.run(["npm", "run", "build"], cwd=ui_dir, check=True)
74
100
  return os.path.exists(out_dir)
75
101
  except Exception as e:
76
102
  click.echo(f"[patholens] Failed to compile frontend: {e}")
@@ -80,7 +106,6 @@ def check_and_install_litert_dependencies():
80
106
  """Checks and installs local litert_lm dependencies."""
81
107
  try:
82
108
  import litert_lm
83
- click.echo("[patholens] litert_lm library is already installed.")
84
109
  except ImportError:
85
110
  click.echo("[patholens] Installing litert_lm dependency locally...")
86
111
  subprocess.run(["pip", "install", "litert-lm"], check=True)
@@ -105,13 +130,16 @@ def cli():
105
130
  @click.option("--static-dir", default=None, help="Directory serving static frontend files")
106
131
  @click.option("--litert-model", default=None, help="Optional local LiteRT model path or HF repo ID")
107
132
  @click.option("--litert-backend", default="cpu", help="LiteRT backend (cpu or gpu)")
108
- def start_backend(port, static_dir, litert_model, litert_backend):
133
+ @click.option("--sample-data-path", default=None, help="Directory storing SVS histopathology slides")
134
+ def start_backend(port, static_dir, litert_model, litert_backend, sample_data_path):
109
135
  """Starts the FastAPI backend server (called on Colab or locally)."""
110
136
  if static_dir:
111
137
  os.environ["STATIC_FRONTEND_DIR"] = os.path.abspath(static_dir)
112
138
  if litert_model:
113
139
  os.environ["LITERT_MODEL_PATH"] = litert_model
114
140
  os.environ["LITERT_BACKEND"] = litert_backend
141
+ if sample_data_path:
142
+ os.environ["SAMPLE_DATA_PATH"] = os.path.abspath(sample_data_path)
115
143
 
116
144
  import uvicorn
117
145
  import sys
@@ -137,52 +165,101 @@ def launch(colab, gpu, session, local, litert_model, litert_backend):
137
165
  project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
138
166
  print_banner()
139
167
 
140
- # Determine launch target interactively if not provided in CLI flags
141
- launch_target = None
142
- if colab:
143
- launch_target = "colab"
144
- elif local:
145
- launch_target = "local"
146
- else:
147
- choice = prompt_choice(
148
- "Where would you like to run the PathoLens backend?",
149
- ["Local Machine (Your computer's CPU/GPU)", "Google Colab VM (Free cloud CPU/GPU accelerators)"]
150
- )
151
- launch_target = "local" if choice == 1 else "colab"
152
-
153
- # --- LOCAL RUN FLOW ---
154
- if launch_target == "local":
155
- click.echo("[patholens] Configuring Local Launch...")
168
+ # Try loading cached config
169
+ config = load_config()
170
+ use_saved_config = False
171
+
172
+ if config and not (colab or local):
173
+ click.echo("\033[94m[patholens]\033[0m Loaded saved configuration:")
174
+ target_name = "Google Colab VM" if config.get("target") == "colab" else "Local Machine"
175
+ click.echo(f" \033[1mTarget Environment:\033[0m {target_name}")
176
+ if config.get("target") == "colab":
177
+ click.echo(f" \033[1mSession Name:\033[0m {config.get('session')}")
178
+ click.echo(f" \033[1mGPU Option:\033[0m {config.get('gpu') or 'None'}")
179
+ click.echo(f" \033[1mModel Profile:\033[0m {config.get('litert_model') or 'None'}")
180
+ click.echo(f" \033[1mDrive Slides Folder:\033[0m {config.get('drive_slides_path') or 'None'}")
181
+ else:
182
+ click.echo(f" \033[1mModel Profile:\033[0m {config.get('litert_model') or 'None'}")
183
+
184
+ click.echo("\033[93mPress any key within 3 seconds to reconfigure...\033[0m")
156
185
 
157
- # Ask about model running
158
- run_model = False
159
- if litert_model is None:
160
- choice = prompt_choice(
161
- "Would you like to run a local Gemma 4 LiteRT model for AI Diagnostics?",
162
- ["Yes (Runs locally using litert_lm)", "No (Use mock diagnostics / API endpoints)"]
163
- )
164
- run_model = (choice == 1)
186
+ # Read keypress with timeout on Linux/Unix
187
+ if sys.platform != 'win32':
188
+ i, o, e = select.select([sys.stdin], [], [], 3.0)
189
+ if i:
190
+ sys.stdin.readline() # Consume key
191
+ click.echo("\033[94m[patholens]\033[0m Entering reconfiguration flow...\033[0m")
192
+ else:
193
+ use_saved_config = True
194
+ click.echo("\033[94m[patholens]\033[0m Booting with saved configuration...\033[0m")
165
195
  else:
166
- run_model = True
196
+ # Simple fallback timeout for other platforms
197
+ time.sleep(3)
198
+ use_saved_config = True
167
199
 
168
- if run_model:
169
- check_and_install_litert_dependencies()
170
- if litert_model is None:
171
- litert_model = "litert-community/gemma-4-E2B-it-litert-lm"
172
-
200
+ # Parse parameters based on selections
201
+ launch_target = None
202
+ drive_slides_path = None
203
+
204
+ if use_saved_config:
205
+ launch_target = config.get("target")
206
+ session = config.get("session")
207
+ gpu = config.get("gpu")
208
+ litert_model = config.get("litert_model")
209
+ litert_backend = config.get("litert_backend")
210
+ drive_slides_path = config.get("drive_slides_path")
211
+ else:
212
+ # Determine launch target
213
+ if colab:
214
+ launch_target = "colab"
215
+ elif local:
216
+ launch_target = "local"
217
+ else:
173
218
  choice = prompt_choice(
174
- "Verify model artifact caching:",
175
- ["Use default Hugging Face download (will download if not cached)", "Download model manually now"]
219
+ "Where would you like to run the PathoLens backend?",
220
+ ["Local Machine (Your computer's CPU/GPU)", "Google Colab VM (Free cloud CPU/GPU accelerators)"]
176
221
  )
177
- if choice == 2:
178
- litert_model = download_litert_model()
222
+ launch_target = "local" if choice == 1 else "colab"
179
223
 
180
- if litert_backend is None:
224
+ # --- LOCAL RUN FLOW ---
225
+ if launch_target == "local":
226
+ if not use_saved_config:
227
+ # Ask about model running
228
+ run_model = False
229
+ if litert_model is None:
181
230
  choice = prompt_choice(
182
- "Which hardware backend should the local model use?",
183
- ["CPU (Standard)", "GPU (Requires CUDA/MPS compatible graphics card)"]
231
+ "Would you like to run a local Gemma 4 LiteRT model for AI Diagnostics?",
232
+ ["Yes (Runs locally using litert_lm)", "No (Use mock diagnostics / API endpoints)"]
184
233
  )
185
- litert_backend = "gpu" if choice == 2 else "cpu"
234
+ run_model = (choice == 1)
235
+ else:
236
+ run_model = True
237
+
238
+ if run_model:
239
+ check_and_install_litert_dependencies()
240
+ if litert_model is None:
241
+ litert_model = "litert-community/gemma-4-E2B-it-litert-lm"
242
+
243
+ choice = prompt_choice(
244
+ "Verify model artifact caching:",
245
+ ["Use default Hugging Face download (will download if not cached)", "Download model manually now"]
246
+ )
247
+ if choice == 2:
248
+ litert_model = download_litert_model()
249
+
250
+ if litert_backend is None:
251
+ choice = prompt_choice(
252
+ "Which hardware backend should the local model use?",
253
+ ["CPU (Standard)", "GPU (Requires CUDA/MPS compatible graphics card)"]
254
+ )
255
+ litert_backend = "gpu" if choice == 2 else "cpu"
256
+
257
+ # Save configuration
258
+ save_config({
259
+ "target": "local",
260
+ "litert_model": litert_model,
261
+ "litert_backend": litert_backend
262
+ })
186
263
 
187
264
  # Build UI
188
265
  build_frontend_locally(project_root)
@@ -204,33 +281,55 @@ def launch(colab, gpu, session, local, litert_model, litert_backend):
204
281
  # --- COLAB RUN FLOW ---
205
282
  click.echo("[patholens] Configuring Google Colab Launch...")
206
283
 
207
- if session is None:
208
- session = click.prompt(" \033[90mEnter Colab session name\033[0m", default="patholens-server")
209
-
210
- if gpu is None:
211
- choice = prompt_choice(
212
- "Which GPU accelerator would you like to request on Colab?",
213
- ["None (CPU only)", "T4 GPU (Standard)", "L4 GPU (High performance)", "A100 GPU (Premium)"]
214
- )
215
- gpus = [None, "T4", "L4", "A100"]
216
- gpu = gpus[choice - 1]
284
+ if not use_saved_config:
285
+ if session is None:
286
+ session = click.prompt(" \033[90mEnter Colab session name\033[0m", default="patholens-server")
287
+
288
+ if gpu is None:
289
+ choice = prompt_choice(
290
+ "Which GPU accelerator would you like to request on Colab?",
291
+ ["None (CPU only)", "T4 GPU (Standard)", "L4 GPU (High performance)", "A100 GPU (Premium)"]
292
+ )
293
+ gpus = [None, "T4", "L4", "A100"]
294
+ gpu = gpus[choice - 1]
295
+
296
+ # Ask about model running
297
+ run_model_colab = False
298
+ if litert_model is None:
299
+ choice = prompt_choice(
300
+ "Would you like to load a LiteRT model on the Colab VM?",
301
+ ["Yes, load Gemma 4 E2B LiteRT (default)", "No, run backend only"]
302
+ )
303
+ run_model_colab = (choice == 1)
304
+ else:
305
+ run_model_colab = True
217
306
 
218
- # Ask about model running
219
- run_model_colab = False
220
- if litert_model is None:
307
+ if run_model_colab:
308
+ if litert_model is None:
309
+ litert_model = "litert-community/gemma-4-E2B-it-litert-lm"
310
+ if litert_backend is None:
311
+ litert_backend = "gpu" if gpu else "cpu"
312
+
313
+ # Ask about slide directory on Google Drive
221
314
  choice = prompt_choice(
222
- "Would you like to load a LiteRT model on the Colab VM?",
223
- ["Yes, load Gemma 4 E2B LiteRT (default)", "No, run backend only"]
315
+ "Do you want to mount Google Drive to access your histopathology .svs slides directly?",
316
+ ["Yes, access slides from Google Drive", "No, use default samples on VM"]
224
317
  )
225
- run_model_colab = (choice == 1)
226
- else:
227
- run_model_colab = True
318
+ if choice == 1:
319
+ drive_slides_path = click.prompt(
320
+ " \033[90mEnter path to slides folder in Google Drive\033[0m",
321
+ default="/content/drive/MyDrive/PathoLens/slides"
322
+ )
228
323
 
229
- if run_model_colab:
230
- if litert_model is None:
231
- litert_model = "litert-community/gemma-4-E2B-it-litert-lm"
232
- if litert_backend is None:
233
- litert_backend = "gpu" if gpu else "cpu"
324
+ # Save config
325
+ save_config({
326
+ "target": "colab",
327
+ "session": session,
328
+ "gpu": gpu,
329
+ "litert_model": litert_model,
330
+ "litert_backend": litert_backend,
331
+ "drive_slides_path": drive_slides_path
332
+ })
234
333
 
235
334
  # Build UI
236
335
  if not build_frontend_locally(project_root):
@@ -239,7 +338,7 @@ def launch(colab, gpu, session, local, litert_model, litert_backend):
239
338
 
240
339
  # Create temporary zip archive of whole project and static frontend
241
340
  with tempfile.TemporaryDirectory() as temp_dir:
242
- click.echo("[patholens] Packaging codebase for Colab installation...")
341
+ click.echo("[patholens] Packaging codebase for Colab installation (excluding local slide directories)...")
243
342
  project_zip = os.path.join(temp_dir, "patholens.zip")
244
343
  zip_project(project_root, project_zip)
245
344
 
@@ -258,6 +357,10 @@ def launch(colab, gpu, session, local, litert_model, litert_backend):
258
357
  click.echo("[patholens] Failed to allocate Colab session VM.")
259
358
  return
260
359
 
360
+ # Wait 10 seconds for Colab proxy server to fully stabilize before uploading files
361
+ click.echo("[patholens] Waiting for Google Colab VM proxy to stabilize...")
362
+ time.sleep(10)
363
+
261
364
  # Upload files
262
365
  click.echo("[patholens] Uploading project codebase...")
263
366
  subprocess.run(["colab", "upload", "-s", session, project_zip, "content/patholens.zip"], check=True)
@@ -269,6 +372,8 @@ def launch(colab, gpu, session, local, litert_model, litert_backend):
269
372
  backend_args = "--static-dir /content/static"
270
373
  if litert_model:
271
374
  backend_args += f" --litert-model {litert_model} --litert-backend {litert_backend}"
375
+ if drive_slides_path:
376
+ backend_args += f" --sample-data-path {drive_slides_path}"
272
377
 
273
378
  # Execute VM initialization script using uv
274
379
  vm_setup_script = f"""
@@ -327,6 +432,11 @@ print("[colab-setup] Error: Failed to retrieve tunnel URL.")
327
432
  with open(setup_script_path, "w") as f:
328
433
  f.write(vm_setup_script)
329
434
 
435
+ # Run Drivemount if configured
436
+ if drive_slides_path:
437
+ click.echo("[patholens] Mounting Google Drive to Colab VM (accept prompts)...")
438
+ subprocess.run(["colab", "drivemount", "-s", session], check=True)
439
+
330
440
  click.echo("[patholens] Installing uv packages and tunnels on Colab...")
331
441
  exec_res = subprocess.run(["colab", "exec", "-s", session, "-f", setup_script_path], capture_output=True, text=True)
332
442
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "patholens"
7
- version = "2.0.0"
7
+ version = "2.0.2"
8
8
  description = "A command-line tool to manage, deploy, and launch PathoLens with Google Colab or local backends."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes