Spaces:
Sleeping
Sleeping
File size: 4,734 Bytes
344f0b4 ef29459 344f0b4 fd8bc35 ef29459 fd8bc35 ef29459 fd8bc35 ef29459 fd8bc35 ef29459 fd8bc35 ef29459 fd8bc35 ef29459 fd8bc35 344f0b4 ef29459 344f0b4 fd8bc35 ef29459 fd8bc35 344f0b4 ef29459 344f0b4 ef29459 fd8bc35 ef29459 fd8bc35 ef29459 fd8bc35 344f0b4 fd8bc35 344f0b4 ef29459 344f0b4 fd8bc35 344f0b4 fd8bc35 344f0b4 ef29459 344f0b4 ef29459 344f0b4 fd8bc35 344f0b4 fd8bc35 344f0b4 fd8bc35 344f0b4 fd8bc35 344f0b4 fd8bc35 344f0b4 fd8bc35 344f0b4 fd8bc35 344f0b4 |
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 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# app.py
import os
from typing import List, Any
import gradio as gr
from PIL import Image
from pipeline import SmartCBC
# -------------------------------------------------
# Initialize pipeline ONCE (cached in Spaces)
# -------------------------------------------------
cbc = SmartCBC() # loads YOLO + classifier once
# -------------------------------------------------
# Helper: convert uploaded files to PIL Images
# -------------------------------------------------
def files_to_pil_list(files: List[Any]) -> List[Image.Image]:
"""
Gradio Files (file_count='multiple') returns a list of file objects or dicts.
Each item commonly looks like:
- {"name": "/tmp/....png", "orig_name": "...", ...}
- or a file-like object with .name
This helper normalizes them into a list of RGB PIL Images.
"""
pil_list: List[Image.Image] = []
if files is None:
return pil_list
for f in files:
# Newer gradio often returns dicts with "name"
if isinstance(f, dict) and "name" in f:
path = f["name"]
# Older style: File object with .name
elif hasattr(f, "name"):
path = f.name
# Fallback: assume it's already a path-like
else:
path = str(f)
if not os.path.isfile(path):
raise FileNotFoundError(f"Uploaded file not found on disk: {path}")
img = Image.open(path).convert("RGB")
pil_list.append(img)
return pil_list
# -------------------------------------------------
# Core Gradio wrapper
# -------------------------------------------------
def analyze_images(files, age, gender, output_mode):
"""
Wrapper for SmartCBC.analyze().
- Accepts one or multiple images from a Gradio Files input.
- If a single image -> sends a single PIL.Image
If multiple -> sends a list[Image.Image] (SmartCBC can route to analyze_batch).
- Returns either a human-readable text report or full JSON.
"""
if files is None or len(files) == 0:
return "Please upload at least one image.", None
pil_images = files_to_pil_list(files)
if len(pil_images) == 1:
image_input = pil_images[0]
else:
image_input = pil_images
# Run SmartCBC pipeline
result = cbc.analyze(
image=image_input,
age=age,
gender=gender,
)
# Choose output mode
if output_mode == "Text Report":
return result.get("report_text", "No report generated."), None
else:
return None, result
# -------------------------------------------------
# Gradio UI Layout (compatible with Gradio 4.0.0)
# -------------------------------------------------
with gr.Blocks(title="SmartCBC - Multimodal Blood Analysis") as demo:
gr.Markdown(
"""
# 🩸 SmartCBC — Multimodal AI Blood Smear Analysis
Upload **one or multiple** peripheral smear FOV images and get:
- RBC / WBC / Platelet counts
- WBC subtype classification
- Aggregated multi-FOV differential
- Age-specific reference comparisons
- Clinical insights (non-diagnostic)
"""
)
with gr.Row():
# Use Files for multi-image upload (works on Gradio 4.0.0)
img_in = gr.Files(
label="Upload 1 or Multiple Blood Smear Images (FOVs)",
file_count="multiple",
file_types=["image"],
)
with gr.Column():
age_in = gr.Number(label="Age (years)", value=30)
gender_in = gr.Dropdown(
["", "M", "F"],
label="Gender (optional)",
value=""
)
output_mode = gr.Radio(
["Text Report", "Structured JSON"],
value="Text Report",
label="Output Format",
)
btn = gr.Button("Analyze")
# OUTPUT AREAS
txt_out = gr.Textbox(
label="Report (Human Readable)",
visible=True,
lines=30,
interactive=False,
)
json_out = gr.JSON(
label="Structured Output (JSON)",
visible=False,
)
# Toggle visibility based on output mode
def toggle_output(mode):
return (
gr.update(visible=(mode == "Text Report")),
gr.update(visible=(mode == "Structured JSON")),
)
output_mode.change(toggle_output, [output_mode], [txt_out, json_out])
# Button Binding
btn.click(
analyze_images,
inputs=[img_in, age_in, gender_in, output_mode],
outputs=[txt_out, json_out],
)
# -------------------------------------------------
# HF Spaces entrypoint
# -------------------------------------------------
if __name__ == "__main__":
demo.launch()
|