|
|
import gradio as gr |
|
|
import base64 |
|
|
import os |
|
|
from pathlib import Path |
|
|
import tempfile |
|
|
import time |
|
|
from groq import Groq |
|
|
import requests |
|
|
|
|
|
|
|
|
GROQ_CLIENT = Groq(api_key=os.environ.get('groq')) |
|
|
HYPERBOLIC_KEY = os.environ.get('h_api_key') |
|
|
|
|
|
class MediClearBackend: |
|
|
"""Handles the API logic to keep the UI code clean.""" |
|
|
|
|
|
@staticmethod |
|
|
def encode_image(image_path): |
|
|
with open(image_path, "rb") as image_file: |
|
|
return base64.b64encode(image_file.read()).decode('utf-8') |
|
|
|
|
|
@staticmethod |
|
|
def analyze_medical_image(image_path): |
|
|
"""Step 1: Extract technical info from a single image""" |
|
|
try: |
|
|
base64_image = MediClearBackend.encode_image(image_path) |
|
|
|
|
|
messages = [ |
|
|
{ |
|
|
"role": "user", |
|
|
"content": [ |
|
|
{"type": "text", "text": "You are a highly experienced medical doctor. Extract every piece of information from this medical image. Provide detailed observations about any findings, measurements, abnormalities, or notable features."}, |
|
|
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}} |
|
|
] |
|
|
} |
|
|
] |
|
|
|
|
|
completion = GROQ_CLIENT.chat.completions.create( |
|
|
model="meta-llama/llama-4-scout-17b-16e-instruct", |
|
|
messages=messages, |
|
|
temperature=0.1, |
|
|
max_completion_tokens=4096, |
|
|
top_p=0.9, |
|
|
stream=True, |
|
|
stop=None |
|
|
) |
|
|
|
|
|
technical_data = "" |
|
|
for chunk in completion: |
|
|
if chunk.choices[0].delta.content: |
|
|
technical_data += chunk.choices[0].delta.content |
|
|
|
|
|
return technical_data |
|
|
except Exception as e: |
|
|
raise Exception(f"Image Analysis Error: {str(e)}") |
|
|
|
|
|
@staticmethod |
|
|
def extract_text_from_multiple_images(image_files): |
|
|
"""Extract and combine text from multiple medical images""" |
|
|
combined_text = "COMBINED MEDICAL REPORTS ANALYSIS:\n\n" |
|
|
|
|
|
for index, image_file in enumerate(image_files, 1): |
|
|
try: |
|
|
filename = Path(image_file.name).name |
|
|
combined_text += f"=== REPORT {index}: {filename} ===\n" |
|
|
|
|
|
|
|
|
image_text = MediClearBackend.analyze_medical_image(image_file.name) |
|
|
combined_text += f"{image_text}\n\n" |
|
|
combined_text += "=" * 50 + "\n\n" |
|
|
|
|
|
except Exception as e: |
|
|
combined_text += f"β Failed to process {filename}: {str(e)}\n\n" |
|
|
combined_text += "=" * 50 + "\n\n" |
|
|
|
|
|
return combined_text |
|
|
|
|
|
@staticmethod |
|
|
def summarize_combined_reports(combined_text): |
|
|
"""Step 2: Convert combined technical info to patient-friendly summary""" |
|
|
try: |
|
|
if not HYPERBOLIC_KEY: |
|
|
raise Exception("Hyperbolic API key not configured") |
|
|
|
|
|
url = "https://api.hyperbolic.xyz/v1/chat/completions" |
|
|
headers = { |
|
|
"Content-Type": "application/json", |
|
|
"Authorization": f"Bearer {HYPERBOLIC_KEY}" |
|
|
} |
|
|
|
|
|
system_prompt = """You are a senior medical doctor with 50 years of experience. |
|
|
Your role is to analyze multiple medical reports and provide a comprehensive, unified analysis to patients in simple, reassuring, everyday language. |
|
|
Never mention the doctors name whatsoever and never ask any counter questions. |
|
|
|
|
|
You will receive multiple medical reports combined together. Analyze them as a complete patient case and provide: |
|
|
|
|
|
Structure: |
|
|
1. Warm greeting acknowledging this is a comprehensive analysis of multiple reports. |
|
|
2. Overall unified assessment of the patient's condition across all reports. |
|
|
3. Key findings from all reports, highlighting patterns, consistencies, or important observations. |
|
|
4. Comprehensive recommendations based on the complete picture. |
|
|
5. Reassuring closing. |
|
|
|
|
|
Speak directly to the patient and provide a holistic view of their medical situation.""" |
|
|
|
|
|
data = { |
|
|
"messages": [ |
|
|
{"role": "system", "content": system_prompt}, |
|
|
{"role": "user", "content": combined_text} |
|
|
], |
|
|
"model": "moonshotai/Kimi-K2-Instruct", |
|
|
"max_tokens": 4096, |
|
|
"temperature": 0.1, |
|
|
"top_p": 0.9 |
|
|
} |
|
|
|
|
|
response = requests.post(url, headers=headers, json=data) |
|
|
response.raise_for_status() |
|
|
return response.json()['choices'][0]['message']['content'] |
|
|
except Exception as e: |
|
|
raise Exception(f"Summary Error (Kimi K2): {str(e)}") |
|
|
|
|
|
def process_single_image(image_file): |
|
|
"""Process a single medical image and return the analysis""" |
|
|
backend = MediClearBackend() |
|
|
|
|
|
if image_file is None: |
|
|
return "β Please upload a medical image first." |
|
|
|
|
|
try: |
|
|
|
|
|
technical_data = backend.analyze_medical_image(image_file.name) |
|
|
|
|
|
|
|
|
final_report = backend.summarize_combined_reports(technical_data) |
|
|
|
|
|
|
|
|
filename = Path(image_file.name).name |
|
|
formatted_report = f""" |
|
|
# π©Ί MEDICAL ANALYSIS REPORT: {filename} |
|
|
--- |
|
|
## π¬ Technical Analysis |
|
|
*Medical image processing complete* |
|
|
## π Patient Report Summary |
|
|
{final_report} |
|
|
--- |
|
|
> β
**Analysis Complete** - This tool provides AI-powered insights and is not a substitute for professional medical diagnosis. |
|
|
""" |
|
|
return formatted_report |
|
|
|
|
|
except Exception as e: |
|
|
return f"## β Error\n**Error processing image:** {str(e)}" |
|
|
|
|
|
def process_multiple_images(image_files): |
|
|
"""Process multiple medical images and return a SINGLE combined analysis""" |
|
|
backend = MediClearBackend() |
|
|
|
|
|
if not image_files: |
|
|
return "β Please upload at least one medical image." |
|
|
|
|
|
try: |
|
|
|
|
|
combined_text = backend.extract_text_from_multiple_images(image_files) |
|
|
|
|
|
|
|
|
comprehensive_summary = backend.summarize_combined_reports(combined_text) |
|
|
|
|
|
|
|
|
full_report = f"""# π©Ί COMPREHENSIVE MEDICAL ANALYSIS |
|
|
|
|
|
## π Analysis of {len(image_files)} Medical Reports |
|
|
|
|
|
**Combined insights from all uploaded medical images:** |
|
|
|
|
|
--- |
|
|
|
|
|
## π Comprehensive Patient Summary |
|
|
|
|
|
{comprehensive_summary} |
|
|
|
|
|
--- |
|
|
|
|
|
## π Reports Analyzed: |
|
|
""" |
|
|
|
|
|
for index, image_file in enumerate(image_files, 1): |
|
|
filename = Path(image_file.name).name |
|
|
full_report += f"- **Report {index}:** {filename}\n" |
|
|
|
|
|
full_report += """ |
|
|
--- |
|
|
> π **Comprehensive Analysis Complete** - This unified analysis provides insights across all your medical reports. Remember: This tool provides AI-powered insights and is not a substitute for professional medical diagnosis. |
|
|
""" |
|
|
return full_report |
|
|
|
|
|
except Exception as e: |
|
|
return f"## β Processing Error\n**Failed to process multiple images:** {str(e)}" |
|
|
|
|
|
def create_gradio_interface(): |
|
|
"""Create the Gradio interface""" |
|
|
|
|
|
|
|
|
custom_css = """ |
|
|
.gradio-container { |
|
|
background: linear-gradient(135deg, #1A1A1A 0%, #2D2D2D 100%); |
|
|
font-family: 'Segoe UI', system-ui, sans-serif; |
|
|
} |
|
|
.dark { |
|
|
background: transparent !important; |
|
|
} |
|
|
.panel { |
|
|
background: #252525 !important; |
|
|
border-radius: 12px !important; |
|
|
border: 1px solid #404040 !important; |
|
|
padding: 20px !important; |
|
|
} |
|
|
.header { |
|
|
background: linear-gradient(135deg, #1A1A1A 0%, #252525 100%) !important; |
|
|
border-radius: 12px !important; |
|
|
padding: 20px !important; |
|
|
margin-bottom: 20px !important; |
|
|
border: 1px solid #404040 !important; |
|
|
} |
|
|
.warning { |
|
|
background: #FF980020 !important; |
|
|
border: 1px solid #FF9800 !important; |
|
|
border-radius: 8px !important; |
|
|
padding: 15px !important; |
|
|
margin: 10px 0 !important; |
|
|
} |
|
|
.success { |
|
|
background: #4CAF5020 !important; |
|
|
border: 1px solid #4CAF50 !important; |
|
|
border-radius: 8px !important; |
|
|
padding: 15px !important; |
|
|
margin: 10x 0 !important; |
|
|
} |
|
|
.processing { |
|
|
background: #FFD60A20 !important; |
|
|
border: 1px solid #FFD60A !important; |
|
|
border-radius: 8px !important; |
|
|
padding: 15px !important; |
|
|
margin: 10px 0 !important; |
|
|
} |
|
|
.markdown-output { |
|
|
background: #1a1a1a !important; |
|
|
border-radius: 8px !important; |
|
|
padding: 20px !important; |
|
|
} |
|
|
.markdown-output h1, .markdown-output h2, .markdown-output h3 { |
|
|
color: #FFD60A !important; |
|
|
margin-top: 1em !important; |
|
|
margin-bottom: 0.5em !important; |
|
|
} |
|
|
.markdown-output p { |
|
|
color: #E8E8E8 !important; |
|
|
line-height: 1.6 !important; |
|
|
} |
|
|
.markdown-output ul, .markdown-output ol { |
|
|
color: #E8E8E8 !important; |
|
|
margin-left: 20px !important; |
|
|
} |
|
|
.markdown-output blockquote { |
|
|
border-left: 4px solid #FFD60A !important; |
|
|
margin: 1em 0 !important; |
|
|
padding-left: 1em !important; |
|
|
color: #B0B0B0 !important; |
|
|
font-style: italic !important; |
|
|
} |
|
|
.markdown-output code { |
|
|
background: #404040 !important; |
|
|
padding: 2px 6px !important; |
|
|
border-radius: 4px !important; |
|
|
color: #E8E8E8 !important; |
|
|
} |
|
|
.markdown-output pre { |
|
|
background: #404040 !important; |
|
|
padding: 15px !important; |
|
|
border-radius: 8px !important; |
|
|
overflow-x: auto !important; |
|
|
} |
|
|
.markdown-output hr { |
|
|
border: none !important; |
|
|
border-top: 2px solid #404040 !important; |
|
|
margin: 2em 0 !important; |
|
|
} |
|
|
""" |
|
|
|
|
|
with gr.Blocks( |
|
|
title="SehatScan - Koshur AI", |
|
|
css=custom_css, |
|
|
theme=gr.themes.Soft( |
|
|
primary_hue="yellow", |
|
|
neutral_hue="slate" |
|
|
) |
|
|
) as demo: |
|
|
|
|
|
|
|
|
with gr.Column(elem_classes="header"): |
|
|
gr.Markdown( |
|
|
""" |
|
|
# π©Ί SehatScan - Koshur AI |
|
|
### Professional Medical Image Analysis |
|
|
|
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
with gr.Column(scale=1, min_width=400): |
|
|
with gr.Group(elem_classes="panel"): |
|
|
gr.Markdown("### π Upload Medical Images") |
|
|
|
|
|
|
|
|
single_image = gr.File( |
|
|
label="Single Image Analysis", |
|
|
file_types=[".jpg", ".jpeg", ".png", ".bmp"], |
|
|
file_count="single", |
|
|
type="filepath" |
|
|
) |
|
|
|
|
|
|
|
|
multiple_images = gr.File( |
|
|
label="Batch Image Analysis (Multiple) - Combined Report", |
|
|
file_types=[".jpg", ".jpeg", ".png", ".bmp"], |
|
|
file_count="multiple", |
|
|
type="filepath" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
analyze_single_btn = gr.Button( |
|
|
"π Analyze Single Image", |
|
|
variant="primary", |
|
|
size="lg" |
|
|
) |
|
|
analyze_batch_btn = gr.Button( |
|
|
"π Analyze Multiple Images", |
|
|
variant="secondary", |
|
|
size="lg" |
|
|
) |
|
|
|
|
|
|
|
|
clear_btn = gr.Button("ποΈ Clear All", variant="stop") |
|
|
|
|
|
|
|
|
with gr.Group(elem_classes="processing"): |
|
|
gr.Markdown( |
|
|
""" |
|
|
### π Processing Information |
|
|
**Single Image:** Individual analysis |
|
|
**Multiple Images:** Combined comprehensive analysis from all reports |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Group(elem_classes="warning"): |
|
|
gr.Markdown( |
|
|
""" |
|
|
### β οΈ Medical Disclaimer |
|
|
This tool provides AI-powered insights and is not a substitute for professional medical diagnosis, |
|
|
treatment, or medical advice. Always consult with qualified healthcare providers for medical decisions. |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Column(scale=2, min_width=600): |
|
|
with gr.Group(elem_classes="panel"): |
|
|
gr.Markdown("### π Doctor's Analysis Report") |
|
|
|
|
|
|
|
|
output_markdown = gr.Markdown( |
|
|
label="Analysis Results", |
|
|
value="*Upload a medical image and click analyze to see the results here...*", |
|
|
elem_classes="markdown-output" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
copy_btn = gr.Button("π Copy to Clipboard", size="sm") |
|
|
export_btn = gr.Button("πΎ Export Report", size="sm") |
|
|
|
|
|
|
|
|
with gr.Column(elem_classes="panel"): |
|
|
with gr.Row(): |
|
|
gr.Markdown( |
|
|
""" |
|
|
**β€οΈ Built by Koshur AI β’ An initiative for Kashmir** |
|
|
[Visit our LinkedIn](https://www.linkedin.com/in/koshurai/) |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
analyze_single_btn.click( |
|
|
fn=process_single_image, |
|
|
inputs=[single_image], |
|
|
outputs=[output_markdown], |
|
|
api_name="analyze_single" |
|
|
) |
|
|
|
|
|
analyze_batch_btn.click( |
|
|
fn=process_multiple_images, |
|
|
inputs=[multiple_images], |
|
|
outputs=[output_markdown], |
|
|
api_name="analyze_batch" |
|
|
) |
|
|
|
|
|
|
|
|
def clear_all(): |
|
|
return None, None, "*Upload a medical image and click analyze to see the results here...*" |
|
|
|
|
|
clear_btn.click( |
|
|
fn=clear_all, |
|
|
outputs=[single_image, multiple_images, output_markdown] |
|
|
) |
|
|
|
|
|
|
|
|
copy_btn.click( |
|
|
fn=lambda: gr.Info("Use the copy button in the Markdown output or select and copy the text directly"), |
|
|
outputs=None |
|
|
) |
|
|
|
|
|
|
|
|
def export_report(): |
|
|
return gr.Info("To export, please select and copy the Markdown content above, then paste it into a text file.") |
|
|
|
|
|
export_btn.click( |
|
|
fn=export_report, |
|
|
outputs=None |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown("### π― How to use:") |
|
|
gr.Markdown(""" |
|
|
1. **Upload** a medical report image |
|
|
2. **Click Analyze** to get AI-powered medical insights |
|
|
3. **For multiple images:** Get a single combined analysis of all reports |
|
|
4. **Always consult** with healthcare professionals for medical decisions |
|
|
""") |
|
|
|
|
|
return demo |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo = create_gradio_interface() |
|
|
|
|
|
|
|
|
demo.launch( |
|
|
server_name="0.0.0.0" if os.getenv("SPACE_ID") else None, |
|
|
share=False, |
|
|
show_error=True, |
|
|
debug=False |
|
|
) |