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.
- {patholens-2.0.0 → patholens-2.0.2}/PKG-INFO +1 -1
- {patholens-2.0.0 → patholens-2.0.2}/patholens-backend/app/main.py +1 -1
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/globals.css +8 -1
- patholens-2.0.2/patholens-ui/next-env.d.ts +6 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens_cli/__init__.py +1 -1
- {patholens-2.0.0 → patholens-2.0.2}/patholens_cli/main.py +179 -69
- {patholens-2.0.0 → patholens-2.0.2}/pyproject.toml +1 -1
- {patholens-2.0.0 → patholens-2.0.2}/.gitignore +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/README.md +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-backend/app/database.py +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-backend/app/models.py +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-backend/requirements.txt +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/.gitignore +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/OncoGemma-logo-no-bg_low.png +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/OncoGemma-no-bg-high_gemini.png +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/README.md +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/dashboard/page.tsx +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/favicon.ico +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/layout.tsx +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/lib/auth.ts +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/lib/config.ts +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/login/page.tsx +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/page.tsx +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/app/viewer/page.tsx +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/components/AIResults.tsx +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/components/ClinicalChat.tsx +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/components/OncoLLM.tsx +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/components/WSIViewer.tsx +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/eslint.config.mjs +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/next.config.ts +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/package-lock.json +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/package.json +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/postcss.config.mjs +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/file.svg +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/globe.svg +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/hawkfranklin_logo.png +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/next.svg +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/onco-gemma-logo.png +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/oncogemma_full_high_crop.png +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/oncogemma_logo_only.png +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/oncogemma_text_only.png +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/pathogemma_icon.png +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/vercel.svg +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/public/window.svg +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/tsconfig.json +0 -0
- {patholens-2.0.0 → patholens-2.0.2}/patholens-ui/types.d.ts +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
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# PathoLens CLI module
|
|
2
|
-
__version__ = "2.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.
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
[
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
[
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
196
|
+
# Simple fallback timeout for other platforms
|
|
197
|
+
time.sleep(3)
|
|
198
|
+
use_saved_config = True
|
|
167
199
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
"
|
|
175
|
-
["
|
|
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 ==
|
|
178
|
-
litert_model = download_litert_model()
|
|
222
|
+
launch_target = "local" if choice == 1 else "colab"
|
|
179
223
|
|
|
180
|
-
|
|
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
|
-
"
|
|
183
|
-
["
|
|
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
|
-
|
|
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
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
"
|
|
223
|
-
["Yes,
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|