Spaces:
Sleeping
Sleeping
File size: 5,705 Bytes
698a40b 3fe9172 356d0f9 698a40b 356d0f9 3fe9172 356d0f9 cda4955 356d0f9 cda4955 356d0f9 754d54a 356d0f9 754d54a 356d0f9 f8f0fd8 356d0f9 f8f0fd8 754d54a f8f0fd8 754d54a f8f0fd8 356d0f9 754d54a 356d0f9 f8f0fd8 356d0f9 cda4955 356d0f9 cda4955 356d0f9 c97cde3 356d0f9 cda4955 c97cde3 356d0f9 1a222d7 c97cde3 356d0f9 c97cde3 cda4955 c97cde3 356d0f9 f8f0fd8 356d0f9 b43fb26 3fe9172 754d54a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | import os
import spaces
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
os.environ["OMP_NUM_THREADS"] = "1"
import sys
import torch
torch.set_num_threads(1)
import tempfile
from pathlib import Path
import numpy as np
from PIL import Image
try:
import gradio as gr
HAS_GRADIO = True
except ImportError:
gr = None
HAS_GRADIO = False
IS_HF_SPACE = os.getenv("SPACE_ID") is not None or os.getenv("SPACES_ZERO_GPU") is not None
# Add workspace root / parent paths to sys.path
WORKSPACE_ROOT = Path(__file__).resolve().parent.parent.parent
if str(WORKSPACE_ROOT) not in sys.path:
sys.path.insert(0, str(WORKSPACE_ROOT))
# Also add current directory
CURRENT_DIR = Path(__file__).resolve().parent
if str(CURRENT_DIR) not in sys.path:
sys.path.insert(0, str(CURRENT_DIR))
try:
from backend.core_ml.classification.scripts.inference_pipeline import OCTInferencePipeline
except ImportError:
from scripts.inference_pipeline import OCTInferencePipeline
print("=====================================================================")
print("Initializing ZeroGPU Multi-Head ConvNeXt V2 Classification Space")
print(f"IS_HF_SPACE: {IS_HF_SPACE}")
print("=====================================================================")
# Cold Start Penalty Mitigation (ZeroGPU Rule #2):
# Model weights are loaded globally onto CPU memory during Space startup phase.
print("1. Loading ConvNeXt V2 Multi-Head Model onto CPU memory...", flush=True)
pipeline = OCTInferencePipeline(device="cpu")
print("✅ ConvNeXt V2 Model loaded into CPU memory!", flush=True)
# Inference function wrapped in ZeroGPU decorator
def _run_classification(image_input, generate_gradcam=True):
if image_input is None:
return {"error": "Please upload a valid OCT scan image."}
img_path = None
if isinstance(image_input, str):
# Plain filepath string — the expected case for gr.Api and legacy clients
img_path = image_input
elif isinstance(image_input, dict):
# Gradio 4+ FileData serialised as a dict: {"path": ..., "url": ..., "orig_name": ...}
img_path = image_input.get("path") or image_input.get("name") or image_input.get("url")
elif hasattr(image_input, "path") and image_input.path and isinstance(image_input.path, str):
# Gradio 4 FileData Pydantic model — access .path attribute directly
img_path = image_input.path
elif hasattr(image_input, "name") and isinstance(getattr(image_input, "name", None), str):
img_path = image_input.name
elif hasattr(image_input, "save"):
# PIL Image object
temp_dir = Path(tempfile.gettempdir())
img_path = str(temp_dir / "temp_input_scan.png")
image_input.save(img_path)
else:
try:
temp_dir = Path(tempfile.gettempdir())
img_path = str(temp_dir / "temp_input_scan.png")
Image.fromarray(np.array(image_input)).save(img_path)
except Exception as err:
return {"error": f"Invalid image format received: {type(image_input)} - {err}"}
if not img_path:
return {"error": f"Could not extract a file path from input type: {type(image_input)}"}
# Dynamic CUDA transfer inside GPU context
if torch.cuda.is_available():
pipeline.model.to("cuda")
pipeline.device = torch.device("cuda")
# Sub-60s Execution (Forward Pass + Grad-CAM)
result = pipeline.predict(img_path, gradcam=generate_gradcam)
return result
HAS_ZEROGPU = os.getenv("SPACES_ZERO_GPU") is not None or os.getenv("ZERO_GPU") is not None
if HAS_ZEROGPU and IS_HF_SPACE and spaces is not None:
@spaces.GPU
def predict_multi_head(image, gradcam=True):
return _run_classification(image, generate_gradcam=gradcam)
else:
def predict_multi_head(image, gradcam=True):
return _run_classification(image, generate_gradcam=gradcam)
if HAS_GRADIO:
with gr.Blocks(title="ConvNeXt V2 Multi-Head OCT Classifier (ZeroGPU)") as demo:
gr.Markdown("# 👁️ ConvNeXt V2 Multi-Head OCT Pathology Classifier")
gr.Markdown("ZeroGPU-accelerated hierarchical disease classification (15 pathology classes) with Grad-CAM explainability.")
with gr.Row():
with gr.Column():
inp_img = gr.Image(type="filepath", label="Input OCT Scan")
chk_gradcam = gr.Checkbox(value=True, label="Generate Grad-CAM Heatmaps")
btn_run = gr.Button("Classify Scan", variant="primary")
with gr.Column():
out_json = gr.JSON(label="Hierarchical Diagnosis Result")
out_cam = gr.Image(type="pil", label="Grad-CAM Pathology Attention Overlay")
def gradio_adapter(img, use_cam):
res = predict_multi_head(img, gradcam=use_cam)
if isinstance(res, dict) and "error" in res:
return res, None
cam_img = None
if use_cam and isinstance(res, dict) and "gradcams" in res:
cam_data = res["gradcams"].get("L2") or res["gradcams"].get("L1")
if cam_data and isinstance(cam_data, str) and cam_data.startswith("data:image"):
import base64, io
base64_data = cam_data.split(",")[1]
cam_bytes = base64.b64decode(base64_data)
cam_img = Image.open(io.BytesIO(cam_bytes))
return res, cam_img
# UI route — for browser usage via the Gradio web interface
btn_run.click(gradio_adapter, inputs=[inp_img, chk_gradcam], outputs=[out_json, out_cam], api_name="predict_multi_head")
demo.queue()
if __name__ == "__main__":
if HAS_GRADIO:
demo.launch(show_error=True)
|