Spaces:
Running on Zero
Running on Zero
Upload 13 files
Browse files- HF_V6_2_CHECKLIST.md +25 -0
- README.md +4 -6
- SHA256SUMS.txt +13 -0
- app.py +25 -1
- database.py +27 -28
- janus_engine.py +12 -2
- requirements.txt +9 -11
HF_V6_2_CHECKLIST.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# V6.2 deployment checklist
|
| 2 |
+
|
| 3 |
+
1. Upload all files to the root of the Gradio Space. Do not keep them inside a subfolder.
|
| 4 |
+
2. Upload `best_efficientnet.pth` separately (the ZIP intentionally does not contain the model weights).
|
| 5 |
+
3. Use ZeroGPU hardware.
|
| 6 |
+
4. Keep these secrets:
|
| 7 |
+
- `SUPABASE_URL`
|
| 8 |
+
- `SUPABASE_SERVICE_ROLE_KEY`
|
| 9 |
+
5. Variables:
|
| 10 |
+
- `SUPABASE_STORAGE_BUCKET=xray-images`
|
| 11 |
+
- `SUPABASE_STORAGE_PUBLIC=true`
|
| 12 |
+
- `JANUS_ENABLED=true`
|
| 13 |
+
- `JANUS_BILINGUAL=true`
|
| 14 |
+
- `JANUS_MODEL_ID=ZrH42/Janus-Pro-CXR-Final`
|
| 15 |
+
- `MODEL_PATH=/home/oai/share/best_efficientnet.pth` only if that is the actual path; otherwise omit it because the default is the Space root.
|
| 16 |
+
6. In Supabase, create/verify the `xray-images` bucket once. The server no longer tries to create it during inference.
|
| 17 |
+
7. The server stores Janus/integrity/safety inside `medical_report_json`; it does not send non-existent columns to `analyses`.
|
| 18 |
+
8. After build, open `/` and test `/health`, then `/gpu_health`, then Analyze.
|
| 19 |
+
9. A successful AI test should show:
|
| 20 |
+
- `janus.enabled: true`
|
| 21 |
+
- non-empty `janus.raw_text`
|
| 22 |
+
- English report source from Janus
|
| 23 |
+
- Arabic verified or explicitly marked for manual review
|
| 24 |
+
- `database.saved: true`
|
| 25 |
+
- `database.storage_saved: true`
|
README.md
CHANGED
|
@@ -1,15 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title: FeatureX Clinical AI
|
| 3 |
-
author: Laith Nadeem Alabsi
|
| 4 |
emoji: 🩻
|
| 5 |
colorFrom: blue
|
| 6 |
colorTo: indigo
|
| 7 |
sdk: gradio
|
| 8 |
-
sdk_version:
|
| 9 |
-
python_version: "3.12
|
| 10 |
app_file: app.py
|
| 11 |
-
|
| 12 |
-
- ZrH42/Janus-Pro-CXR-Final
|
| 13 |
---
|
| 14 |
|
| 15 |
# FeatureX Clinical AI — Hugging Face ZeroGPU
|
|
|
|
| 1 |
---
|
| 2 |
+
title: FeatureX Clinical AI
|
|
|
|
| 3 |
emoji: 🩻
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: indigo
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.29.1
|
| 8 |
+
python_version: "3.12"
|
| 9 |
app_file: app.py
|
| 10 |
+
suggested_hardware: cpu-basic
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
# FeatureX Clinical AI — Hugging Face ZeroGPU
|
SHA256SUMS.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
2c7ee9e77fa13e08f3e62a64fb400d3d7eeb1cacfc409482b6940d94c386da71 DEPLOY_V6_GRADIO6.md
|
| 2 |
+
5ff6aa4fba6c3b7474f32d9fcefdf060022cf03d7c025fbeb1fb08f39d3783ab HF_V6_2_CHECKLIST.md
|
| 3 |
+
e320901dec53dbb74dd0ffbbb631a98c036e9dab80f99e110c23e2da7f4c74cd README.md
|
| 4 |
+
d7632fa87691d1fbb05c64a58a31c2dcdddcbad7239f8b1136f4dc5e3b7c5bfb app.py
|
| 5 |
+
bb2a915c4e372a456d039dc7ec7350174b5032252ef610f17e8a602bc88976c6 arabic_translation.py
|
| 6 |
+
82800f6795317e1d41a11c40100cc0e8aa2d4a73cf4ba4c99b73251111601882 database.py
|
| 7 |
+
d5a0ea93b03a52eeb8fa12cc421be19626b0e1e5f3d5d56c67028ca56da0a344 env.example
|
| 8 |
+
3b27bb81bf2b11b7d4b266b86a4426ec3469af0f3235df0eab1ae8859fd0f98d flask_app_reference.py
|
| 9 |
+
acb0d739947c5aeeff0d91c7add3d041f40188a5ea4a82994b978bfd9621f91e inference.py
|
| 10 |
+
2d76e40f526f982847334fbf71824a238ccabb704098933f6ddb32d1c90ee762 janus_engine.py
|
| 11 |
+
fb3695147bc149917ab8264f4b0d6adc5115f1758801fc25db5b5a44f8f0e97e requirements.txt
|
| 12 |
+
b03844e23a2073511d00b05ff42045859e9d65fbe505b643dd7e31f148986410 supabase_002_bilingual_analyses.sql
|
| 13 |
+
f7bec1683c145d65f07d4f30040ab58e29b7ff32fd16b8c4b95c4e0d790f0dfe test_gradio_api.py
|
app.py
CHANGED
|
@@ -21,9 +21,30 @@ logging.basicConfig(
|
|
| 21 |
)
|
| 22 |
logger = logging.getLogger("featurex-zerogpu-server")
|
| 23 |
|
| 24 |
-
SERVER_VERSION = "3.
|
| 25 |
MAX_UPLOAD = 15 * 1024 * 1024
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
def _json(payload: Any) -> str:
|
| 29 |
return json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
|
|
@@ -302,6 +323,7 @@ def gpu_health() -> str:
|
|
| 302 |
"server_version": SERVER_VERSION,
|
| 303 |
"transport": "Gradio Server + ZeroGPU",
|
| 304 |
"database": database.config_info(),
|
|
|
|
| 305 |
"api": {"analyze": True, "health": True, "classes": True},
|
| 306 |
}
|
| 307 |
)
|
|
@@ -316,6 +338,7 @@ def health() -> str:
|
|
| 316 |
"transport": "Gradio Server + ZeroGPU",
|
| 317 |
"zero_gpu": True,
|
| 318 |
"database": database.config_info(),
|
|
|
|
| 319 |
}
|
| 320 |
)
|
| 321 |
return _json(info)
|
|
@@ -349,6 +372,7 @@ def _health_endpoint() -> str:
|
|
| 349 |
"transport": "Gradio Blocks + ZeroGPU",
|
| 350 |
"zero_gpu": True,
|
| 351 |
"database": database.config_info(),
|
|
|
|
| 352 |
"api": {"analyze": True, "health": True, "gpu_health": True, "classes": True},
|
| 353 |
}
|
| 354 |
)
|
|
|
|
| 21 |
)
|
| 22 |
logger = logging.getLogger("featurex-zerogpu-server")
|
| 23 |
|
| 24 |
+
SERVER_VERSION = "3.2.0-hf-zerogpu-v6.2-janus49"
|
| 25 |
MAX_UPLOAD = 15 * 1024 * 1024
|
| 26 |
|
| 27 |
+
def _runtime_info():
|
| 28 |
+
info = {}
|
| 29 |
+
try:
|
| 30 |
+
import torch
|
| 31 |
+
info["torch"] = torch.__version__
|
| 32 |
+
info["cuda_available"] = bool(torch.cuda.is_available())
|
| 33 |
+
info["cuda_device"] = torch.cuda.get_device_name(0) if torch.cuda.is_available() else None
|
| 34 |
+
except Exception as exc:
|
| 35 |
+
info["torch_error"] = str(exc)
|
| 36 |
+
try:
|
| 37 |
+
import transformers
|
| 38 |
+
info["transformers"] = transformers.__version__
|
| 39 |
+
except Exception as exc:
|
| 40 |
+
info["transformers_error"] = str(exc)
|
| 41 |
+
info["janus_enabled"] = os.environ.get("JANUS_ENABLED", "true")
|
| 42 |
+
info["janus_model_id"] = os.environ.get("JANUS_MODEL_ID", "ZrH42/Janus-Pro-CXR-Final")
|
| 43 |
+
info["model_path"] = os.environ.get("MODEL_PATH", os.path.join(os.path.dirname(os.path.abspath(__file__)), "best_efficientnet.pth"))
|
| 44 |
+
info["model_file_exists"] = os.path.isfile(info["model_path"])
|
| 45 |
+
return info
|
| 46 |
+
|
| 47 |
+
|
| 48 |
|
| 49 |
def _json(payload: Any) -> str:
|
| 50 |
return json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
|
|
|
|
| 323 |
"server_version": SERVER_VERSION,
|
| 324 |
"transport": "Gradio Server + ZeroGPU",
|
| 325 |
"database": database.config_info(),
|
| 326 |
+
"runtime": _runtime_info(),
|
| 327 |
"api": {"analyze": True, "health": True, "classes": True},
|
| 328 |
}
|
| 329 |
)
|
|
|
|
| 338 |
"transport": "Gradio Server + ZeroGPU",
|
| 339 |
"zero_gpu": True,
|
| 340 |
"database": database.config_info(),
|
| 341 |
+
"runtime": _runtime_info(),
|
| 342 |
}
|
| 343 |
)
|
| 344 |
return _json(info)
|
|
|
|
| 372 |
"transport": "Gradio Blocks + ZeroGPU",
|
| 373 |
"zero_gpu": True,
|
| 374 |
"database": database.config_info(),
|
| 375 |
+
"runtime": _runtime_info(),
|
| 376 |
"api": {"analyze": True, "health": True, "gpu_health": True, "classes": True},
|
| 377 |
}
|
| 378 |
)
|
database.py
CHANGED
|
@@ -15,7 +15,10 @@ logger = logging.getLogger("clinical-ai-db")
|
|
| 15 |
|
| 16 |
SUPABASE_URL = os.environ.get("SUPABASE_URL", "").rstrip("/")
|
| 17 |
SUPABASE_SERVICE_ROLE_KEY = os.environ.get("SUPABASE_SERVICE_ROLE_KEY", "")
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
| 19 |
PUBLIC_STORAGE = (
|
| 20 |
os.environ.get("SUPABASE_STORAGE_PUBLIC", "true").strip().lower() == "true"
|
| 21 |
)
|
|
@@ -41,35 +44,33 @@ def config_info() -> Dict[str, Any]:
|
|
| 41 |
|
| 42 |
|
| 43 |
def _ensure_bucket() -> None:
|
| 44 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
if not enabled():
|
| 46 |
raise RuntimeError("Supabase is not configured on the server.")
|
| 47 |
-
|
|
|
|
| 48 |
try:
|
| 49 |
-
response = requests.
|
| 50 |
url,
|
| 51 |
headers=_headers(),
|
| 52 |
-
json={"id": SUPABASE_BUCKET, "name": SUPABASE_BUCKET, "public": PUBLIC_STORAGE},
|
| 53 |
timeout=REQUEST_TIMEOUT,
|
| 54 |
)
|
| 55 |
-
if response.status_code in (200, 201, 409):
|
| 56 |
-
return
|
| 57 |
-
# Some projects already have the bucket but reject create; verify it exists.
|
| 58 |
-
if response.status_code >= 300:
|
| 59 |
-
check = requests.get(
|
| 60 |
-
f"{url}/{SUPABASE_BUCKET}",
|
| 61 |
-
headers=_headers(),
|
| 62 |
-
timeout=REQUEST_TIMEOUT,
|
| 63 |
-
)
|
| 64 |
-
if check.status_code == 200:
|
| 65 |
-
return
|
| 66 |
-
raise RuntimeError(
|
| 67 |
-
f"Storage bucket '{SUPABASE_BUCKET}' is unavailable: "
|
| 68 |
-
f"{response.status_code} {response.text[:800]}"
|
| 69 |
-
)
|
| 70 |
except requests.RequestException as exc:
|
| 71 |
raise RuntimeError(f"Storage bucket check failed: {exc}") from exc
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
def _headers(content_type: str = "application/json") -> Dict[str, str]:
|
| 75 |
if not enabled():
|
|
@@ -422,17 +423,16 @@ def save_analysis(
|
|
| 422 |
"threshold_url": visual_urls.get("threshold"),
|
| 423 |
"comparison_url": visual_urls.get("comparison"),
|
| 424 |
"medical_report_json": json.dumps(
|
| 425 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
ensure_ascii=False,
|
| 427 |
),
|
| 428 |
"report_ar": reports_ar,
|
| 429 |
"report_en": reports_en,
|
| 430 |
-
"janus_raw_text": (result.get("janus") or {}).get("raw_text") or selected_report.get("janus_raw_text") or "",
|
| 431 |
-
"janus_model": (result.get("janus") or {}).get("model") or selected_report.get("report_model") or "",
|
| 432 |
-
"report_source": selected_report.get("report_source") or "",
|
| 433 |
-
"translation_methods": (result.get("report_integrity") or {}).get("translation_methods") or [],
|
| 434 |
-
"report_integrity": result.get("report_integrity") or {},
|
| 435 |
-
"safety": result.get("safety") or {},
|
| 436 |
"patient_data": _localized_patient(patient),
|
| 437 |
"predictions_data": predictions,
|
| 438 |
"quality_metrics": quality_metrics,
|
|
@@ -440,7 +440,6 @@ def save_analysis(
|
|
| 440 |
"model_info": model_info,
|
| 441 |
"visualization_urls": visual_urls,
|
| 442 |
"language_requested": requested_language,
|
| 443 |
-
"language": requested_language,
|
| 444 |
"original_filename": original_filename,
|
| 445 |
"image_sha256": image_sha256,
|
| 446 |
"server_version": model_info.get("version", "unknown"),
|
|
|
|
| 15 |
|
| 16 |
SUPABASE_URL = os.environ.get("SUPABASE_URL", "").rstrip("/")
|
| 17 |
SUPABASE_SERVICE_ROLE_KEY = os.environ.get("SUPABASE_SERVICE_ROLE_KEY", "")
|
| 18 |
+
_RAW_BUCKET = os.environ.get("SUPABASE_STORAGE_BUCKET", "xray-images").strip()
|
| 19 |
+
# The deployed SQL and successful Kaggle server use the hyphenated bucket.
|
| 20 |
+
# Accept the old xray_images spelling to avoid breaking an existing secret.
|
| 21 |
+
SUPABASE_BUCKET = "xray-images" if _RAW_BUCKET == "xray_images" else (_RAW_BUCKET or "xray-images")
|
| 22 |
PUBLIC_STORAGE = (
|
| 23 |
os.environ.get("SUPABASE_STORAGE_PUBLIC", "true").strip().lower() == "true"
|
| 24 |
)
|
|
|
|
| 44 |
|
| 45 |
|
| 46 |
def _ensure_bucket() -> None:
|
| 47 |
+
"""Verify that the configured Storage bucket already exists.
|
| 48 |
+
|
| 49 |
+
The previous HF build tried to create the bucket on every request. On
|
| 50 |
+
Supabase projects with Storage RLS this can fail with:
|
| 51 |
+
"new row violates row-level security policy".
|
| 52 |
+
Bucket administration belongs in Supabase SQL/dashboard; the server only
|
| 53 |
+
verifies the bucket and uploads with the service-role key.
|
| 54 |
+
"""
|
| 55 |
if not enabled():
|
| 56 |
raise RuntimeError("Supabase is not configured on the server.")
|
| 57 |
+
|
| 58 |
+
url = f"{SUPABASE_URL}/storage/v1/bucket/{SUPABASE_BUCKET}"
|
| 59 |
try:
|
| 60 |
+
response = requests.get(
|
| 61 |
url,
|
| 62 |
headers=_headers(),
|
|
|
|
| 63 |
timeout=REQUEST_TIMEOUT,
|
| 64 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
except requests.RequestException as exc:
|
| 66 |
raise RuntimeError(f"Storage bucket check failed: {exc}") from exc
|
| 67 |
|
| 68 |
+
if response.status_code != 200:
|
| 69 |
+
body = _safe_response_text(response, 800)
|
| 70 |
+
raise RuntimeError(
|
| 71 |
+
f"Storage bucket '{SUPABASE_BUCKET}' is unavailable: "
|
| 72 |
+
f"{response.status_code} {body}"
|
| 73 |
+
)
|
| 74 |
|
| 75 |
def _headers(content_type: str = "application/json") -> Dict[str, str]:
|
| 76 |
if not enabled():
|
|
|
|
| 423 |
"threshold_url": visual_urls.get("threshold"),
|
| 424 |
"comparison_url": visual_urls.get("comparison"),
|
| 425 |
"medical_report_json": json.dumps(
|
| 426 |
+
{
|
| 427 |
+
**selected_report,
|
| 428 |
+
"report_integrity": result.get("report_integrity") or {},
|
| 429 |
+
"janus": result.get("janus") or {},
|
| 430 |
+
"safety": result.get("safety") or {},
|
| 431 |
+
},
|
| 432 |
ensure_ascii=False,
|
| 433 |
),
|
| 434 |
"report_ar": reports_ar,
|
| 435 |
"report_en": reports_en,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
"patient_data": _localized_patient(patient),
|
| 437 |
"predictions_data": predictions,
|
| 438 |
"quality_metrics": quality_metrics,
|
|
|
|
| 440 |
"model_info": model_info,
|
| 441 |
"visualization_urls": visual_urls,
|
| 442 |
"language_requested": requested_language,
|
|
|
|
| 443 |
"original_filename": original_filename,
|
| 444 |
"image_sha256": image_sha256,
|
| 445 |
"server_version": model_info.get("version", "unknown"),
|
janus_engine.py
CHANGED
|
@@ -10,13 +10,22 @@ from typing import Dict, List
|
|
| 10 |
|
| 11 |
import torch
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
logger = logging.getLogger("featurex-janus")
|
| 14 |
|
| 15 |
MODEL_ID = os.environ.get("JANUS_MODEL_ID", "ZrH42/Janus-Pro-CXR-Final")
|
| 16 |
REPO_URL = "https://github.com/ZrH42/Janus-Pro-CXR.git"
|
| 17 |
REPO_DIR = Path(os.environ.get("JANUS_REPO_DIR", "/tmp/Janus-Pro-CXR"))
|
| 18 |
JANUS_ENABLED = os.environ.get("JANUS_ENABLED", "true").strip().lower() in {"1", "true", "yes", "on"}
|
| 19 |
-
BILINGUAL = os.environ.get("JANUS_BILINGUAL", "
|
| 20 |
|
| 21 |
_PROCESSOR = None
|
| 22 |
_MODEL = None
|
|
@@ -59,11 +68,12 @@ def _initialize():
|
|
| 59 |
global _PROCESSOR, _MODEL, _INITIALIZED
|
| 60 |
if _INITIALIZED:
|
| 61 |
return
|
|
|
|
| 62 |
if not JANUS_ENABLED:
|
| 63 |
_INITIALIZED = True
|
| 64 |
return
|
| 65 |
if not torch.cuda.is_available():
|
| 66 |
-
raise RuntimeError("Janus requires CUDA
|
| 67 |
|
| 68 |
_clone_repo()
|
| 69 |
_patch_compatibility()
|
|
|
|
| 10 |
|
| 11 |
import torch
|
| 12 |
|
| 13 |
+
def _log_runtime_versions():
|
| 14 |
+
try:
|
| 15 |
+
import transformers
|
| 16 |
+
logger.info("Transformers version: %s", transformers.__version__)
|
| 17 |
+
except Exception as exc:
|
| 18 |
+
logger.warning("Could not read Transformers version: %s", exc)
|
| 19 |
+
logger.info("Torch version: %s | CUDA available: %s", torch.__version__, torch.cuda.is_available())
|
| 20 |
+
|
| 21 |
+
|
| 22 |
logger = logging.getLogger("featurex-janus")
|
| 23 |
|
| 24 |
MODEL_ID = os.environ.get("JANUS_MODEL_ID", "ZrH42/Janus-Pro-CXR-Final")
|
| 25 |
REPO_URL = "https://github.com/ZrH42/Janus-Pro-CXR.git"
|
| 26 |
REPO_DIR = Path(os.environ.get("JANUS_REPO_DIR", "/tmp/Janus-Pro-CXR"))
|
| 27 |
JANUS_ENABLED = os.environ.get("JANUS_ENABLED", "true").strip().lower() in {"1", "true", "yes", "on"}
|
| 28 |
+
BILINGUAL = os.environ.get("JANUS_BILINGUAL", "false").strip().lower() in {"1", "true", "yes", "on"}
|
| 29 |
|
| 30 |
_PROCESSOR = None
|
| 31 |
_MODEL = None
|
|
|
|
| 68 |
global _PROCESSOR, _MODEL, _INITIALIZED
|
| 69 |
if _INITIALIZED:
|
| 70 |
return
|
| 71 |
+
_log_runtime_versions()
|
| 72 |
if not JANUS_ENABLED:
|
| 73 |
_INITIALIZED = True
|
| 74 |
return
|
| 75 |
if not torch.cuda.is_available():
|
| 76 |
+
raise RuntimeError("Janus requires CUDA/ZeroGPU. The analyze function must execute on a ZeroGPU Space.")
|
| 77 |
|
| 78 |
_clone_repo()
|
| 79 |
_patch_compatibility()
|
requirements.txt
CHANGED
|
@@ -1,21 +1,19 @@
|
|
| 1 |
-
#
|
| 2 |
-
#
|
| 3 |
-
#
|
| 4 |
-
#
|
| 5 |
-
|
| 6 |
-
# used here as the compatibility bridge for the current Hub/Gradio stack.
|
| 7 |
-
# Janus-Pro-CXR is still loaded through its custom remote-code implementation.
|
| 8 |
-
|
| 9 |
torch==2.10.0
|
| 10 |
torchvision==0.25.0
|
| 11 |
-
transformers==
|
| 12 |
-
accelerate
|
|
|
|
|
|
|
| 13 |
attrdict==2.0.1
|
| 14 |
sentencepiece
|
| 15 |
einops
|
| 16 |
timm>=1.0.0
|
| 17 |
safetensors>=0.5.0
|
| 18 |
-
|
| 19 |
opencv-python-headless
|
| 20 |
Pillow
|
| 21 |
numpy==1.26.4
|
|
|
|
| 1 |
+
# FeatureX Clinical AI — Hugging Face ZeroGPU
|
| 2 |
+
# Gradio 5.29.1 is intentionally pinned because Janus-Pro-CXR was validated
|
| 3 |
+
# with the Transformers 4.x stack; Gradio 6 + Hub 1.x forced an incompatible
|
| 4 |
+
# Transformers 5.x bridge in the previous build.
|
| 5 |
+
gradio==5.29.1
|
|
|
|
|
|
|
|
|
|
| 6 |
torch==2.10.0
|
| 7 |
torchvision==0.25.0
|
| 8 |
+
transformers==4.49.0
|
| 9 |
+
accelerate==1.2.1
|
| 10 |
+
tokenizers==0.21.0
|
| 11 |
+
huggingface-hub<1.0
|
| 12 |
attrdict==2.0.1
|
| 13 |
sentencepiece
|
| 14 |
einops
|
| 15 |
timm>=1.0.0
|
| 16 |
safetensors>=0.5.0
|
|
|
|
| 17 |
opencv-python-headless
|
| 18 |
Pillow
|
| 19 |
numpy==1.26.4
|