Update app.py
Browse filesUI/UX Refactoring and Deployment Optimization
1. Localization & Medical Terminology
Full English Translation: Translated the entire Gradio interface into English to align with international academic and clinical standards.
Professional Terminology: Upgraded standard variable names and outputs to professional medical terminology (e.g., Clinical Assessment, Anatomical Landmarks, Acetabular Index).
2. UI/UX Enhancements & Layout
Aesthetic Theme: Applied the gr.themes.Soft() theme to provide a clean, calming, and professional visual experience suitable for a healthcare application.
Layout Restructuring: Redesigned the interface into a logical two-column layout using gr.Row() and gr.Column(). The left column acts as the "Input Panel," and the right column serves as the "Diagnostic Output," significantly improving the user workflow.
Progressive Disclosure: Encapsulated advanced model parameters (like the Confidence Threshold slider) within an interactive gr.Accordion. This keeps the primary interface uncluttered for medical end-users while remaining accessible for engineers.
3. Enhanced Result Formatting
Markdown Integration: Upgraded the diagnostic output from plain text to rich Markdown format.
Visual Hierarchy: Clinical metrics (such as exact angles and severity levels) are now highlighted in bold, and visual indicators (emojis) were introduced to allow physicians to quickly assess the severity of the dysplasia at a glance.
4. Cloud Deployment Fixes
Hugging Face Compatibility: Modified the demo.launch() execution block by defining explicit server bindings (server_name="0.0.0.0", server_port=7860). This resolves the local-host access error and ensures seamless, stable execution on Hugging Face Spaces.
|
@@ -1,92 +1,121 @@
|
|
| 1 |
-
import gradio as gr
|
| 2 |
-
import cv2
|
| 3 |
-
import numpy as np
|
| 4 |
-
import math
|
| 5 |
-
from ultralytics import YOLO
|
| 6 |
-
|
| 7 |
-
#
|
| 8 |
-
MODEL_PATH = "best.pt"
|
| 9 |
-
model = YOLO(MODEL_PATH, task="pose")
|
| 10 |
-
|
| 11 |
-
def get_hip_points(image_crop, model, conf=0.25):
|
| 12 |
-
results = model(image_crop, conf=conf, verbose=False)
|
| 13 |
-
for r in results:
|
| 14 |
-
if r.keypoints and r.keypoints.shape[1] >= 2:
|
| 15 |
-
kpts = r.keypoints.xy[0].cpu().numpy().astype(int)
|
| 16 |
-
valid_kpts = [p for p in kpts if p[0] != 0 and p[1] != 0]
|
| 17 |
-
if len(valid_kpts) >= 2:
|
| 18 |
-
valid_kpts.sort(key=lambda p: p[1])
|
| 19 |
-
return valid_kpts[-1], valid_kpts[0]
|
| 20 |
-
return None, None
|
| 21 |
-
|
| 22 |
-
def calculate_slope_angle(center, rim, other_center):
|
| 23 |
-
try:
|
| 24 |
-
if other_center[0] - center[0] == 0: m_h = 0
|
| 25 |
-
else: m_h = (other_center[1] - center[1]) / (other_center[0] - center[0])
|
| 26 |
-
if rim[0] - center[0] == 0: m_r = 1e9
|
| 27 |
-
else: m_r = (rim[1] - center[1]) / (rim[0] - center[0])
|
| 28 |
-
tan_theta = abs((m_r - m_h) / (1 + m_h * m_r))
|
| 29 |
-
return math.degrees(math.atan(tan_theta))
|
| 30 |
-
except: return 0.0
|
| 31 |
-
|
| 32 |
-
def get_diagnosis_style(angle):
|
| 33 |
-
|
| 34 |
-
if angle >=
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
image =
|
| 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 |
-
result_text = f"
|
| 67 |
-
result_text += f"**
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
cv2.line(image, tuple(
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
#
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import cv2
|
| 3 |
+
import numpy as np
|
| 4 |
+
import math
|
| 5 |
+
from ultralytics import YOLO
|
| 6 |
+
|
| 7 |
+
# Load Model
|
| 8 |
+
MODEL_PATH = "best.pt"
|
| 9 |
+
model = YOLO(MODEL_PATH, task="pose")
|
| 10 |
+
|
| 11 |
+
def get_hip_points(image_crop, model, conf=0.25):
|
| 12 |
+
results = model(image_crop, conf=conf, verbose=False)
|
| 13 |
+
for r in results:
|
| 14 |
+
if r.keypoints and r.keypoints.shape[1] >= 2:
|
| 15 |
+
kpts = r.keypoints.xy[0].cpu().numpy().astype(int)
|
| 16 |
+
valid_kpts = [p for p in kpts if p[0] != 0 and p[1] != 0]
|
| 17 |
+
if len(valid_kpts) >= 2:
|
| 18 |
+
valid_kpts.sort(key=lambda p: p[1])
|
| 19 |
+
return valid_kpts[-1], valid_kpts[0]
|
| 20 |
+
return None, None
|
| 21 |
+
|
| 22 |
+
def calculate_slope_angle(center, rim, other_center):
|
| 23 |
+
try:
|
| 24 |
+
if other_center[0] - center[0] == 0: m_h = 0
|
| 25 |
+
else: m_h = (other_center[1] - center[1]) / (other_center[0] - center[0])
|
| 26 |
+
if rim[0] - center[0] == 0: m_r = 1e9
|
| 27 |
+
else: m_r = (rim[1] - center[1]) / (rim[0] - center[0])
|
| 28 |
+
tan_theta = abs((m_r - m_h) / (1 + m_h * m_r))
|
| 29 |
+
return math.degrees(math.atan(tan_theta))
|
| 30 |
+
except: return 0.0
|
| 31 |
+
|
| 32 |
+
def get_diagnosis_style(angle):
|
| 33 |
+
if angle >= 30: return "Dysplasia (High Severity)", "#FF4B4B"
|
| 34 |
+
if angle >= 25: return "Borderline / Mild", "#FFA500"
|
| 35 |
+
return "Normal", "#09AB3B"
|
| 36 |
+
|
| 37 |
+
def analyze_xray(input_img, conf):
|
| 38 |
+
image = np.array(input_img)
|
| 39 |
+
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
| 40 |
+
h, w, _ = image.shape
|
| 41 |
+
mid_x = w // 2
|
| 42 |
+
|
| 43 |
+
right_hip_img = image[:, :mid_x]
|
| 44 |
+
left_hip_img = image[:, mid_x:]
|
| 45 |
+
|
| 46 |
+
r_tri, r_rim = get_hip_points(right_hip_img, model, conf)
|
| 47 |
+
l_tri_local, l_rim_local = get_hip_points(left_hip_img, model, conf)
|
| 48 |
+
|
| 49 |
+
if r_tri is None or l_tri_local is None:
|
| 50 |
+
return image, "⚠️ **Error:** Could not detect anatomical landmarks. Try adjusting the confidence threshold."
|
| 51 |
+
|
| 52 |
+
l_tri_global = (l_tri_local[0] + mid_x, l_tri_local[1])
|
| 53 |
+
l_rim_global = (l_rim_local[0] + mid_x, l_rim_local[1])
|
| 54 |
+
|
| 55 |
+
# Draw Hilgenreiner's line (Base Line)
|
| 56 |
+
cv2.line(image, tuple(r_tri), tuple(l_tri_global), (0, 255, 255), 3)
|
| 57 |
+
|
| 58 |
+
ang_r = calculate_slope_angle(r_tri, r_rim, l_tri_global)
|
| 59 |
+
diag_r, color_r = get_diagnosis_style(ang_r)
|
| 60 |
+
|
| 61 |
+
ang_l = calculate_slope_angle(l_tri_global, l_rim_global, r_tri)
|
| 62 |
+
diag_l, color_l = get_diagnosis_style(ang_l)
|
| 63 |
+
|
| 64 |
+
# Formatting the output text with Markdown
|
| 65 |
+
result_text = f"### 🩺 Clinical Assessment Results\n\n"
|
| 66 |
+
result_text += f"**Right Hip AI:** `{ang_r:.1f}°` ➔ **{diag_r}**\n\n"
|
| 67 |
+
result_text += f"**Left Hip AI:** `{ang_l:.1f}°` ➔ **{diag_l}**"
|
| 68 |
+
|
| 69 |
+
# Draw Roof Lines
|
| 70 |
+
cv2.line(image, tuple(r_tri), tuple(r_rim), (0, 0, 255), 3)
|
| 71 |
+
cv2.line(image, tuple(l_tri_global), tuple(l_rim_global), (0, 0, 255), 3)
|
| 72 |
+
|
| 73 |
+
return cv2.cvtColor(image, cv2.COLOR_BGR2RGB), result_text
|
| 74 |
+
|
| 75 |
+
# ==========================================
|
| 76 |
+
# Gradio UI Design (Enhanced & English)
|
| 77 |
+
# ==========================================
|
| 78 |
+
custom_theme = gr.themes.Soft(
|
| 79 |
+
primary_hue="blue",
|
| 80 |
+
secondary_hue="slate",
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
with gr.Blocks(title="DislocPred: DDH System", theme=custom_theme) as demo:
|
| 84 |
+
|
| 85 |
+
# Header Section
|
| 86 |
+
with gr.Row():
|
| 87 |
+
gr.Markdown(
|
| 88 |
+
"""
|
| 89 |
+
# 🏥 DislocPred: AI Diagnostic Assistant
|
| 90 |
+
**Automated Detection & Grading for Developmental Dysplasia of the Hip (DDH)** Upload a pediatric pelvic X-ray (AP view). The system will analyze the anatomical landmarks, calculate the Acetabular Index (AI), and provide a clinical evaluation.
|
| 91 |
+
"""
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
# Main Application Area
|
| 95 |
+
with gr.Row():
|
| 96 |
+
# Left Column: Inputs
|
| 97 |
+
with gr.Column(scale=1):
|
| 98 |
+
gr.Markdown("### 📥 Input Panel")
|
| 99 |
+
input_img = gr.Image(type="pil", label="Upload X-ray Image")
|
| 100 |
+
|
| 101 |
+
with gr.Accordion("⚙️ Advanced Model Settings", open=False):
|
| 102 |
+
conf_slider = gr.Slider(0.1, 1.0, value=0.25, step=0.05, label="Detection Confidence Threshold")
|
| 103 |
+
|
| 104 |
+
btn = gr.Button("🔍 Run AI Analysis", variant="primary")
|
| 105 |
+
|
| 106 |
+
# Right Column: Outputs
|
| 107 |
+
with gr.Column(scale=1):
|
| 108 |
+
gr.Markdown("### 📤 Diagnostic Output")
|
| 109 |
+
output_img = gr.Image(label="Annotated Radiograph")
|
| 110 |
+
output_text = gr.Markdown(label="Results")
|
| 111 |
+
|
| 112 |
+
# Connect logic to UI
|
| 113 |
+
btn.click(
|
| 114 |
+
fn=analyze_xray,
|
| 115 |
+
inputs=[input_img, conf_slider],
|
| 116 |
+
outputs=[output_img, output_text]
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
# Launch with proper Hugging Face settings
|
| 120 |
+
if __name__ == "__main__":
|
| 121 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|