Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -2,8 +2,6 @@
|
|
| 2 |
|
| 3 |
import gradio as gr
|
| 4 |
from PIL import Image
|
| 5 |
-
import numpy as np
|
| 6 |
-
|
| 7 |
|
| 8 |
from pipeline import SmartCBC
|
| 9 |
|
|
@@ -13,44 +11,70 @@ from pipeline import SmartCBC
|
|
| 13 |
cbc = SmartCBC() # loads YOLO + classifier once
|
| 14 |
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
# -------------------------------------------------
|
| 17 |
# Core Gradio wrapper
|
| 18 |
# -------------------------------------------------
|
| 19 |
def analyze_images(images, age, gender, output_mode):
|
| 20 |
"""
|
| 21 |
-
Wrapper for SmartCBC.analyze()
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
"""
|
|
|
|
| 24 |
if images is None or len(images) == 0:
|
| 25 |
return "Please upload at least one image.", None
|
| 26 |
|
| 27 |
-
#
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
elif isinstance(media, Image.Image):
|
| 43 |
-
pil = media.convert("RGB")
|
| 44 |
-
# numpy array
|
| 45 |
-
elif isinstance(media, np.ndarray):
|
| 46 |
-
pil = Image.fromarray(media).convert("RGB")
|
| 47 |
-
else:
|
| 48 |
-
raise ValueError(f"Unsupported gallery item type: {type(media)}")
|
| 49 |
-
|
| 50 |
-
pil_list.append(pil)
|
| 51 |
-
|
| 52 |
-
return pil_list
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
|
| 56 |
# -------------------------------------------------
|
|
@@ -58,35 +82,40 @@ def analyze_images(images, age, gender, output_mode):
|
|
| 58 |
# -------------------------------------------------
|
| 59 |
with gr.Blocks(title="SmartCBC - Multimodal Blood Analysis") as demo:
|
| 60 |
|
| 61 |
-
gr.Markdown(
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
| 64 |
- RBC / WBC / Platelet counts
|
| 65 |
- WBC subtype classification
|
| 66 |
- Aggregated multi-FOV differential
|
| 67 |
- Age-specific reference comparisons
|
| 68 |
- Clinical insights (non-diagnostic)
|
| 69 |
-
"""
|
|
|
|
| 70 |
|
| 71 |
with gr.Row():
|
| 72 |
img_in = gr.Gallery(
|
| 73 |
label="Upload 1 or Multiple Blood Smear Images (FOVs)",
|
| 74 |
columns=3,
|
| 75 |
height="auto",
|
| 76 |
-
allow_preview=True
|
|
|
|
|
|
|
| 77 |
)
|
| 78 |
|
| 79 |
with gr.Column():
|
| 80 |
age_in = gr.Number(label="Age (years)", value=30)
|
| 81 |
gender_in = gr.Dropdown(
|
| 82 |
-
["
|
| 83 |
label="Gender (optional)",
|
| 84 |
value=""
|
| 85 |
)
|
| 86 |
output_mode = gr.Radio(
|
| 87 |
["Text Report", "Structured JSON"],
|
| 88 |
value="Text Report",
|
| 89 |
-
label="Output Format"
|
| 90 |
)
|
| 91 |
btn = gr.Button("Analyze")
|
| 92 |
|
|
@@ -95,19 +124,19 @@ with gr.Blocks(title="SmartCBC - Multimodal Blood Analysis") as demo:
|
|
| 95 |
label="Report (Human Readable)",
|
| 96 |
visible=True,
|
| 97 |
lines=30,
|
| 98 |
-
interactive=False
|
| 99 |
)
|
| 100 |
|
| 101 |
json_out = gr.JSON(
|
| 102 |
label="Structured Output (JSON)",
|
| 103 |
-
visible=False
|
| 104 |
)
|
| 105 |
|
| 106 |
-
# Toggle visibility
|
| 107 |
def toggle_output(mode):
|
| 108 |
return (
|
| 109 |
gr.update(visible=(mode == "Text Report")),
|
| 110 |
-
gr.update(visible=(mode == "Structured JSON"))
|
| 111 |
)
|
| 112 |
|
| 113 |
output_mode.change(toggle_output, [output_mode], [txt_out, json_out])
|
|
@@ -116,7 +145,7 @@ with gr.Blocks(title="SmartCBC - Multimodal Blood Analysis") as demo:
|
|
| 116 |
btn.click(
|
| 117 |
analyze_images,
|
| 118 |
inputs=[img_in, age_in, gender_in, output_mode],
|
| 119 |
-
outputs=[txt_out, json_out]
|
| 120 |
)
|
| 121 |
|
| 122 |
|
|
|
|
| 2 |
|
| 3 |
import gradio as gr
|
| 4 |
from PIL import Image
|
|
|
|
|
|
|
| 5 |
|
| 6 |
from pipeline import SmartCBC
|
| 7 |
|
|
|
|
| 11 |
cbc = SmartCBC() # loads YOLO + classifier once
|
| 12 |
|
| 13 |
|
| 14 |
+
# -------------------------------------------------
|
| 15 |
+
# Helper to normalize Gallery input
|
| 16 |
+
# -------------------------------------------------
|
| 17 |
+
def _normalize_gallery_images(images):
|
| 18 |
+
"""
|
| 19 |
+
Gradio Gallery (type='pil') can return:
|
| 20 |
+
- list[PIL.Image]
|
| 21 |
+
- or list[(PIL.Image, metadata_dict)]
|
| 22 |
+
|
| 23 |
+
This function strips metadata if present and returns a flat list of images.
|
| 24 |
+
"""
|
| 25 |
+
if images is None:
|
| 26 |
+
return []
|
| 27 |
+
|
| 28 |
+
normalized = []
|
| 29 |
+
for item in images:
|
| 30 |
+
# Sometimes (media, metadata), sometimes media only
|
| 31 |
+
if isinstance(item, tuple):
|
| 32 |
+
media = item[0]
|
| 33 |
+
else:
|
| 34 |
+
media = item
|
| 35 |
+
normalized.append(media)
|
| 36 |
+
return normalized
|
| 37 |
+
|
| 38 |
+
|
| 39 |
# -------------------------------------------------
|
| 40 |
# Core Gradio wrapper
|
| 41 |
# -------------------------------------------------
|
| 42 |
def analyze_images(images, age, gender, output_mode):
|
| 43 |
"""
|
| 44 |
+
Wrapper for SmartCBC.analyze().
|
| 45 |
+
|
| 46 |
+
- Accepts one or multiple images from a Gradio Gallery.
|
| 47 |
+
- Delegates to SmartCBC.analyze(), which auto-routes to analyze_batch()
|
| 48 |
+
if a list of images is provided.
|
| 49 |
+
- Returns either a human-readable text report or full JSON.
|
| 50 |
"""
|
| 51 |
+
# No images
|
| 52 |
if images is None or len(images) == 0:
|
| 53 |
return "Please upload at least one image.", None
|
| 54 |
|
| 55 |
+
# Normalize gallery output (strip metadata if any)
|
| 56 |
+
img_list = _normalize_gallery_images(images)
|
| 57 |
+
|
| 58 |
+
# If single image, send just that; if multiple, send list → analyze_batch
|
| 59 |
+
if len(img_list) == 1:
|
| 60 |
+
image_input = img_list[0]
|
| 61 |
+
else:
|
| 62 |
+
image_input = img_list
|
| 63 |
+
|
| 64 |
+
# Run SmartCBC pipeline
|
| 65 |
+
result = cbc.analyze(
|
| 66 |
+
image=image_input,
|
| 67 |
+
age=age,
|
| 68 |
+
gender=gender,
|
| 69 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
+
# Choose output mode
|
| 72 |
+
if output_mode == "Text Report":
|
| 73 |
+
# result["report_text"] is produced by build_api_response()
|
| 74 |
+
return result.get("report_text", "No report generated."), None
|
| 75 |
+
else:
|
| 76 |
+
# Show full structured JSON
|
| 77 |
+
return None, result
|
| 78 |
|
| 79 |
|
| 80 |
# -------------------------------------------------
|
|
|
|
| 82 |
# -------------------------------------------------
|
| 83 |
with gr.Blocks(title="SmartCBC - Multimodal Blood Analysis") as demo:
|
| 84 |
|
| 85 |
+
gr.Markdown(
|
| 86 |
+
"""
|
| 87 |
+
# 🩸 SmartCBC — Multimodal AI Blood Smear Analysis
|
| 88 |
+
|
| 89 |
+
Upload **one or multiple** peripheral smear FOV images and get:
|
| 90 |
- RBC / WBC / Platelet counts
|
| 91 |
- WBC subtype classification
|
| 92 |
- Aggregated multi-FOV differential
|
| 93 |
- Age-specific reference comparisons
|
| 94 |
- Clinical insights (non-diagnostic)
|
| 95 |
+
"""
|
| 96 |
+
)
|
| 97 |
|
| 98 |
with gr.Row():
|
| 99 |
img_in = gr.Gallery(
|
| 100 |
label="Upload 1 or Multiple Blood Smear Images (FOVs)",
|
| 101 |
columns=3,
|
| 102 |
height="auto",
|
| 103 |
+
allow_preview=True,
|
| 104 |
+
type="pil", # works with gradio==4.44.1
|
| 105 |
+
file_types=["image"] # ensure only images are selectable
|
| 106 |
)
|
| 107 |
|
| 108 |
with gr.Column():
|
| 109 |
age_in = gr.Number(label="Age (years)", value=30)
|
| 110 |
gender_in = gr.Dropdown(
|
| 111 |
+
["", "M", "F"],
|
| 112 |
label="Gender (optional)",
|
| 113 |
value=""
|
| 114 |
)
|
| 115 |
output_mode = gr.Radio(
|
| 116 |
["Text Report", "Structured JSON"],
|
| 117 |
value="Text Report",
|
| 118 |
+
label="Output Format",
|
| 119 |
)
|
| 120 |
btn = gr.Button("Analyze")
|
| 121 |
|
|
|
|
| 124 |
label="Report (Human Readable)",
|
| 125 |
visible=True,
|
| 126 |
lines=30,
|
| 127 |
+
interactive=False,
|
| 128 |
)
|
| 129 |
|
| 130 |
json_out = gr.JSON(
|
| 131 |
label="Structured Output (JSON)",
|
| 132 |
+
visible=False,
|
| 133 |
)
|
| 134 |
|
| 135 |
+
# Toggle visibility based on output mode
|
| 136 |
def toggle_output(mode):
|
| 137 |
return (
|
| 138 |
gr.update(visible=(mode == "Text Report")),
|
| 139 |
+
gr.update(visible=(mode == "Structured JSON")),
|
| 140 |
)
|
| 141 |
|
| 142 |
output_mode.change(toggle_output, [output_mode], [txt_out, json_out])
|
|
|
|
| 145 |
btn.click(
|
| 146 |
analyze_images,
|
| 147 |
inputs=[img_in, age_in, gender_in, output_mode],
|
| 148 |
+
outputs=[txt_out, json_out],
|
| 149 |
)
|
| 150 |
|
| 151 |
|