Spaces:
Running
Running
import streamlit as st | |
from PIL import Image | |
import os | |
import base64 | |
import io | |
from dotenv import load_dotenv | |
from groq import Groq | |
from reportlab.lib.pagesizes import letter | |
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as ReportLabImage | |
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle | |
from reportlab.lib.units import inch | |
from datetime import datetime | |
import re | |
from reportlab.lib import colors | |
import random | |
import streamlit.components.v1 as components | |
# ====================== | |
# CONFIGURATION SETTINGS | |
# ====================== | |
PAGE_CONFIG = { | |
"page_title": "Radiology Analyzer", | |
"page_icon": "๐ฉบ", | |
"layout": "wide", | |
"initial_sidebar_state": "expanded" | |
} | |
ALLOWED_FILE_TYPES = ['png', 'jpg', 'jpeg'] | |
CSS_STYLES = """ | |
<style> | |
/* Main background and text colors */ | |
.main { | |
background-color: #0e1117; | |
color: #ffffff; | |
} | |
.sidebar .sidebar-content { | |
background-color: #1a1d24; | |
color: #ffffff; | |
} | |
/* Custom title styling */ | |
.main-title { | |
font-size: 2.8rem; | |
font-weight: 700; | |
color: #ffffff; | |
margin-bottom: 0.2rem; | |
text-align: center; | |
} | |
.sub-title { | |
font-size: 1.5rem; | |
color: #9ca3af; | |
margin-top: 0.2rem; | |
text-align: center; | |
margin-bottom: 2rem; | |
} | |
/* Button styling */ | |
.stButton>button { | |
background-color: #21b9e1 !important; | |
color: white !important; | |
border-radius: 8px !important; | |
padding: 0.5rem 1rem !important; | |
border: none !important; | |
transition: all 0.3s ease !important; | |
} | |
.stButton>button:hover { | |
background-color: #17a2b8 !important; | |
transform: translateY(-2px); | |
box-shadow: 0 4px 12px rgba(0, 200, 225, 0.3); | |
} | |
/* Report container */ | |
.report-container { | |
background-color: #1a1d24; | |
border-radius: 10px; | |
padding: 25px; | |
margin-top: 20px; | |
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); | |
border-left: 5px solid #21b9e1; | |
} | |
.report-text { | |
font-family: 'Inter', sans-serif; | |
font-size: 14px; | |
line-height: 1.6; | |
color: #e2e8f0; | |
} | |
/* File uploader */ | |
.uploadedFile { | |
background-color: #1a1d24 !important; | |
border-radius: 10px !important; | |
padding: 10px !important; | |
border: 2px dashed #21b9e1 !important; | |
} | |
/* Sidebar items */ | |
.sidebar-item { | |
padding: 10px 0; | |
border-bottom: 1px solid #2d3748; | |
} | |
.sidebar-title { | |
font-weight: bold; | |
color: #21b9e1; | |
margin-bottom: 10px; | |
} | |
/* Logo container */ | |
.logo-container { | |
display: flex; | |
justify-content: center; | |
margin-bottom: 20px; | |
} | |
.logo-pulse { | |
width: 100px; | |
height: 100px; | |
border-radius: 50%; | |
animation: pulse 2s infinite; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
background-color: rgba(33, 185, 225, 0.1); | |
} | |
@keyframes pulse { | |
0% { | |
box-shadow: 0 0 0 0 rgba(33, 185, 225, 0.4); | |
} | |
70% { | |
box-shadow: 0 0 0 20px rgba(33, 185, 225, 0); | |
} | |
100% { | |
box-shadow: 0 0 0 0 rgba(33, 185, 225, 0); | |
} | |
} | |
/* Progress bar */ | |
.stProgress > div > div { | |
background-color: #21b9e1 !important; | |
} | |
/* Analysis status indicator */ | |
.analysis-complete { | |
display: inline-flex; | |
align-items: center; | |
background-color: rgba(33, 225, 185, 0.2); | |
color: #21e1b9; | |
padding: 8px 16px; | |
border-radius: 20px; | |
font-weight: 600; | |
margin-bottom: 20px; | |
} | |
.analysis-complete svg { | |
margin-right: 8px; | |
} | |
/* Drop zone */ | |
.drop-zone { | |
background-color: #1a1d24; | |
border: 2px dashed #21b9e1; | |
border-radius: 10px; | |
padding: 40px 20px; | |
text-align: center; | |
transition: all 0.3s ease; | |
margin-bottom: 20px; | |
} | |
.drop-zone:hover { | |
border-color: #17a2b8; | |
background-color: #242830; | |
} | |
.drop-icon { | |
font-size: 3rem; | |
color: #21b9e1; | |
margin-bottom: 10px; | |
} | |
/* Markdown adjustments */ | |
.markdown-text-container p { | |
color: #e2e8f0 !important; | |
} | |
/* Image styling */ | |
.stImage img { | |
border-radius: 10px; | |
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); | |
border: 3px solid #1a1d24; | |
} | |
/* Card styles */ | |
.card { | |
background-color: #1a1d24; | |
border-radius: 10px; | |
padding: 20px; | |
margin-bottom: 20px; | |
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | |
transition: all 0.3s ease; | |
} | |
.card:hover { | |
transform: translateY(-5px); | |
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); | |
} | |
.card-title { | |
color: #21b9e1; | |
font-size: 1.2rem; | |
font-weight: 600; | |
margin-bottom: 10px; | |
} | |
/* Tooltip */ | |
.tooltip { | |
position: relative; | |
display: inline-block; | |
cursor: pointer; | |
} | |
.tooltip .tooltiptext { | |
visibility: hidden; | |
width: 200px; | |
background-color: #2d3748; | |
color: #fff; | |
text-align: center; | |
border-radius: 6px; | |
padding: 10px; | |
position: absolute; | |
z-index: 1; | |
bottom: 125%; | |
left: 50%; | |
margin-left: -100px; | |
opacity: 0; | |
transition: opacity 0.3s; | |
} | |
.tooltip:hover .tooltiptext { | |
visibility: visible; | |
opacity: 1; | |
} | |
/* API selector styling */ | |
.api-selector { | |
background-color: #1a1d24; | |
border-radius: 10px; | |
padding: 15px; | |
margin-bottom: 20px; | |
border-left: 3px solid #21b9e1; | |
} | |
.api-selector-title { | |
color: #21b9e1; | |
font-weight: bold; | |
margin-bottom: 10px; | |
} | |
</style> | |
""" | |
# ====================== | |
# CORE FUNCTIONS | |
# ====================== | |
def configure_application(): | |
"""Initialize application settings and styling""" | |
st.set_page_config(**PAGE_CONFIG) | |
st.markdown(CSS_STYLES, unsafe_allow_html=True) | |
def initialize_groq_client(): | |
"""Create and validate Groq API client""" | |
load_dotenv() | |
api_key = os.getenv("GROQ_API_KEY") | |
if not api_key: | |
st.error("Groq API key not found. Please provide an API key.") | |
return None | |
return Groq(api_key=api_key) | |
def encode_logo(image_path): | |
"""Encode logo image to base64""" | |
try: | |
with open(image_path, "rb") as img_file: | |
return base64.b64encode(img_file.read()).decode("utf-8") | |
except FileNotFoundError: | |
# Return a placeholder image (blue medical technology icon) encoded as base64 | |
return "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFLUlEQVR4nO2cfYhUVRTGf+uqlWmpZX6kaZpppX1YWpllEQVFEQVBEVFUpBVF0QdFRlEUVERBRNAfkRVZ9GVFpaXlRwpqlGlqpmZqaZa67q7r6qrr9kf30dxh9u7MnTvz5t6Z+8DLMDvv3nPOuWfOPe++e+6CEEIIIYQQ1cCVwBpgl8/2AnCa7UBjZDjwIbDXR4FUsjbg99zV7AZGpBJoZlwDfK+EeOAHYJTNIPuVkMAca2PghBs/AEcZMpdKSGD20IKJQCuvQSoYLSWjpJRiYzLCHgCeB5YDzTZicYGrgF+V7T10AG+kGcRLwPGWYnGFicA/SmgfeoAZaQ6yFhjmMxzXGAV8q6T24WdggB/DTcDFluJxhYHAFiW1DyuDGG9OIIyrgJ+U2N+4z69xNzxcXZ1L3FdDqoVoXucjgdXkQSz6nKzw5BkFbK/xAV3jUsBpoUEJKYmNQD0Pnw+xtgRCeNdg6WO7wvVuVEkMtiWE8K5SrZ1L+pwPAusV+n7Zk8ZHv35FaWiUEOPcC3xRo8JbV41i+3K8RgVX8aysMbGVnWHVKLYvR2tEcE9Wq9iaiMYaEdzjNSq2JqK+RgR3by2KrQYmdG0MCu6yahdbja16wDe1KLYaW/XA6BoUWxPRWWWCuxw4XKNiq5NVD1QjP/oVW5PCrFbmVrW27Krpz8AVZZrWrBaRvQcsAb4CfitKbEtpgbWxAzg3zKSSEyLBjXIVtzf3vLZYqaUQhRQGRWoTmCcHjvDpX41UKcVGZLqPQPdoYkqTxQEW+DTWC7yfVBDWspmQkFHmlWXMfkwRInoMeKyAn+F6Zw2E4e1c9F7KPsMxdxdwfJQVNTfwU9U7bGBfcYLXmlKp5xUL4WUt/Jvgxp5QUaMd3MbA58XMRlqgBxiYVAw3Ay8m8PFNQC+EQVWXlD8XVVIQVwCtZQgiHwaYMpwwiFuThpn+ZFFBzA7gIzCVnLRNAjodF87GIhdPDzArJmxsJxxicYZvFrnwfUBnwrDG5ApjH5cQHnVFhYsB1gEdMWGjtwwXeZnWZJ1fZtjtucHSUuSY/WWGpXLREcWQeBjYFHGRzeUI4nKgiPWAKbmw0NJC2Hhc6Fx6G3SLe76Wc7s17j7BTOA94IKIY7g8I+PrDfZvZWX/gojj/QtMB74vOcwW/qPQnUZ23FHGZNlX5KLFQgMjciOH+W9nngG25MQwLpXvxcDCnO97YubXDGqLEUZTbsvKA8BDwNWGPvuNsTKKKXEzdKNwbg6mxMnlGcZ7iu1BYOmJy4U1phZpgEJhw4tEGZq8DQR2W57QdUPnTIrA1nZaGLEdJLt3p/uD2aCHo+5GFKt4zYWnxs2Qgej1cVZrJJIcYnoX+MiXdW/ywOYtztEp+Jhjd//5YMaGJ5iHrq0GVtB7X5ZY88p2gTBIlRDjiQ3AAVH2K0oIZTG5hP7ESCWkTzUoFJ3AEVlHYotJWQx0TYwNSoj5w2chKP3DibadnWk7SMepVkKMJ9qBWPocB2QdgG1eL0r7gQxwmBJSXvOeRJaXUcY/sMWExm4lxHhiPtBWRuhryjXeRQkJ1MWfVsb2HVZfpjrFbZ1aAMzJNaMLn3+35LbXwXJZR0IIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYRD/A9AH1PHpL17/AAAAABJRU5ErkJggg==" | |
def process_image_data(uploaded_file): | |
"""Convert image to base64 encoded string""" | |
try: | |
image = Image.open(uploaded_file) | |
buffer = io.BytesIO() | |
image.save(buffer, format=image.format) | |
return base64.b64encode(buffer.getvalue()).decode('utf-8'), image.format | |
except Exception as e: | |
st.error(f"Image processing error: {str(e)}") | |
return None, None | |
def generate_pdf_report(report_text, uploaded_file): | |
"""Generate professionally formatted PDF report with bold headers.""" | |
buffer = io.BytesIO() | |
doc = SimpleDocTemplate(buffer, pagesize=letter, | |
rightMargin=72, leftMargin=72, | |
topMargin=72, bottomMargin=72) | |
# Create custom styles for different parts of the report | |
styles = getSampleStyleSheet() | |
# Custom styles for better formatting | |
title_style = ParagraphStyle( | |
'ReportTitle', | |
parent=styles['Title'], | |
fontSize=16, | |
alignment=1, # Center aligned | |
spaceAfter=12 | |
) | |
header_style = ParagraphStyle( | |
'SectionHeader', | |
parent=styles['Heading2'], | |
fontSize=12, | |
fontName='Helvetica-Bold', | |
textColor=colors.black, | |
spaceBefore=12, | |
spaceAfter=6 | |
) | |
normal_style = ParagraphStyle( | |
'NormalText', | |
parent=styles['BodyText'], | |
fontSize=11, | |
leading=14, | |
spaceAfter=8 | |
) | |
abnormal_style = ParagraphStyle( | |
'AbnormalText', | |
parent=styles['BodyText'], | |
fontSize=11, | |
leading=14, | |
textColor=colors.red, | |
backColor=colors.lightgrey, | |
borderPadding=5, | |
spaceAfter=8 | |
) | |
footer_style = ParagraphStyle( | |
'FooterText', | |
parent=styles['Italic'], | |
fontSize=9, | |
alignment=1 # Center aligned | |
) | |
# Begin building the report | |
story = [] | |
# Hospital/Institution Header | |
header_text = "RADIOLOGY DEPARTMENT" | |
header = Paragraph(header_text, title_style) | |
story.append(header) | |
# Report Title | |
report_title = "RADIOLOGICAL EXAMINATION REPORT" | |
title = Paragraph(report_title, title_style) | |
story.append(title) | |
# Add date and report ID | |
date_text = f"Date: {datetime.now().strftime('%B %d, %Y')}" | |
report_id = f"Report ID: RAD-{datetime.now().strftime('%Y%m%d')}-{random.randint(1000, 9999)}" | |
date_para = Paragraph(date_text, normal_style) | |
id_para = Paragraph(report_id, normal_style) | |
story.append(date_para) | |
story.append(id_para) | |
story.append(Spacer(1, 12)) | |
# Add the image to the PDF | |
if uploaded_file: | |
try: | |
uploaded_file.seek(0) # Reset file pointer to beginning | |
pil_image = Image.open(uploaded_file) | |
img_width = 5 * inch | |
aspect = float(pil_image.height) / float(pil_image.width) | |
img_height = img_width * aspect | |
img_temp = io.BytesIO() | |
pil_image.save(img_temp, format=pil_image.format if pil_image.format else 'JPEG') | |
img_temp.seek(0) | |
img = ReportLabImage(img_temp, width=img_width, height=img_height) | |
story.append(img) | |
story.append(Spacer(1, 12)) | |
# Add image caption | |
caption = Paragraph("Figure 1: Radiological Image for Analysis", normal_style) | |
story.append(caption) | |
story.append(Spacer(1, 12)) | |
except Exception as e: | |
error_text = Paragraph(f"Image processing error: {str(e)}", normal_style) | |
story.append(error_text) | |
story.append(Spacer(1, 12)) | |
# Clean the report text (remove markdown-style formatting and unwanted characters) | |
cleaned_text = report_text.replace('**', '').replace('##', '').replace('*', '-') | |
# Define section headers to identify | |
section_headers = [ | |
"DIAGNOSIS", | |
"ETIOLOGY", | |
"RISK FACTORS", | |
"PATHOPHYSIOLOGY", | |
"CLINICAL FEATURES", | |
"SIGNS AND SYMPTOMS", | |
"INVESTIGATIONS", | |
"MANAGEMENT", | |
"INITIAL STABILIZATION", | |
"MEDICAL MANAGEMENT", | |
"SURGICAL MANAGEMENT", | |
"PROGNOSIS" | |
] | |
# Split into lines for more precise processing | |
lines = cleaned_text.split('\n') | |
current_section = "" | |
section_content = "" | |
for i, line in enumerate(lines): | |
line = line.strip() | |
if not line: | |
continue | |
# Remove any "Step X:" prefixes | |
line = re.sub(r'^Step \d+:\s*', '', line) | |
# Check if this is a section header | |
is_header = False | |
for header in section_headers: | |
if line.upper().startswith(header) or line.upper() == header + ":": | |
is_header = True | |
break | |
# Also check if it's a short line ending with a colon (likely a header) | |
if not is_header and len(line) < 60 and line.endswith(':'): | |
is_header = True | |
# If we found a header | |
if is_header: | |
# First add any accumulated content from previous section | |
if section_content.strip(): | |
# Check for severe abnormalities to highlight | |
severe_abnormal_keywords = [ | |
'severe', 'critical', 'urgent', 'emergency', 'life-threatening', | |
'malignant', 'neoplasm', 'carcinoma', 'metastasis', 'hemorrhage', | |
'fracture', 'rupture', 'perforation', | |
] | |
has_severe_issue = any(keyword in section_content.lower() for keyword in severe_abnormal_keywords) | |
if current_section.upper().startswith("DIAGNOSIS") or current_section.upper().startswith("ABNORMAL"): | |
# This is a diagnosis section - highlight abnormalities | |
p = Paragraph(section_content, abnormal_style if has_severe_issue else normal_style) | |
else: | |
p = Paragraph(section_content, normal_style) | |
story.append(p) | |
story.append(Spacer(1, 6)) | |
section_content = "" | |
# Add the new section header - remove any trailing colon for cleaner look | |
clean_header = line.strip() | |
if clean_header.endswith(':'): | |
clean_header = clean_header[:-1] | |
current_section = clean_header | |
p = Paragraph(f"<b>{clean_header}</b>", header_style) # Bold the header | |
story.append(p) | |
else: | |
# This is content - append to the current section content | |
if section_content: | |
section_content += "<br/>" + line | |
else: | |
section_content = line | |
# Add any remaining content | |
if section_content.strip(): | |
p = Paragraph(section_content, normal_style) | |
story.append(p) | |
# Add conclusion if not present | |
if not any("PROGNOSIS" in line.upper() for line in lines): | |
conclusion_header = Paragraph("<b>PROGNOSIS</b>", header_style) | |
story.append(conclusion_header) | |
story.append(Spacer(1, 6)) | |
conclusion_text = "Prognosis varies based on the extent and location of findings. Clinical correlation with the patient's symptoms and medical history is recommended." | |
conclusion_para = Paragraph(conclusion_text, normal_style) | |
story.append(conclusion_para) | |
# Add footer with disclaimer | |
story.append(Spacer(1, 24)) | |
disclaimer = "This report was generated with AI assistance and should be reviewed by a qualified healthcare professional." | |
footer = Paragraph(disclaimer, footer_style) | |
story.append(footer) | |
# Build PDF | |
doc.build(story) | |
buffer.seek(0) | |
return buffer | |
def generate_radiology_report_groq(uploaded_file, client): | |
"""Generate AI-powered radiology analysis using Groq API""" | |
base64_image, img_format = process_image_data(uploaded_file) | |
if not base64_image: | |
return None | |
image_url = f"data:image/{img_format.lower()};base64,{base64_image}" | |
try: | |
with st.spinner("Analyzing image..."): | |
# Add progress bar for visual feedback | |
progress_bar = st.progress(0) | |
for i in range(100): | |
# Update progress bar | |
progress_bar.progress(i + 1) | |
import time | |
time.sleep(0.025) # Simulate processing time | |
# Updated prompt to request the detailed, structured format | |
response = client.chat.completions.create( | |
model="meta-llama/llama-4-maverick-17b-128e-instruct", # Use Groq's model | |
messages=[{ | |
"role": "user", | |
"content": [ | |
{"type": "text", "text": ( | |
"""As a radiologist, analyze the following Medical report and provide a comprehensive report structured as follows: | |
1. **DIAGNOSIS**: Clearly state the primary diagnosis, including dimensions in mmIf where applicable (e.g., if a tumor is present). Use specific anatomical terms relevant to the body part being examined.If it is chest xray also mention if pneumonia is present or not. | |
2. **FINDINGS**: | |
- Provide detailed observations from the Medical report, including: | |
- The size, shape, and location of any lesions or abnormalities if applicable. | |
- Description of the surrounding tissues and structures if applicable. | |
- Any noted changes in signal intensity on various sequences (e.g., T1W, T2W, FLAIR) if applicable. | |
- Mention of any associated findings, such as edema, mass effect, or midline shift if applicble. | |
- Specific comments on vascular structures, if applicable. | |
3. **PATHOPHYSIOLOGY**: Briefly explain the disease mechanism related to the diagnosis, focusing on how it affects the specific body part. | |
4. **CLINICAL FEATURES**: Provide an overview of typical clinical presentations associated with this diagnosis, emphasizing symptoms that may arise from abnormalities in the specified anatomical area. | |
5. **SIGNS AND SYMPTOMS**: List common signs and symptoms relevant to the findings in the MRI report. Tailor this section to align with the specific anatomy being assessed. | |
6. **INVESTIGATIONS**: Mention diagnostic tests typically used for confirmation of the diagnosis, including imaging studies or laboratory tests pertinent to the body part. | |
7. **MANAGEMENT**: Outline the management plans in three parts: | |
- Initial Stabilization: Describe immediate steps for patient care. | |
- Medical Management: Outline pharmacological treatments and monitoring. | |
- Surgical Management (if applicable): Discuss any surgical interventions specific to the diagnosis and body part. | |
8. **PROGNOSIS**: Describe expected outcomes and factors that may affect prognosis based on the diagnosis. Include considerations specific to the anatomical region and associated complications. | |
Please ensure to focus on the following findings from the report: | |
- Mention specific abnormalities based on the region (e.g., "T2/FLAIR hyperintensities in the right fronto-parietal region" for brain MRI). | |
- Highlight any significant lesions or deviations from the norm. | |
- Include any other abnormal findings noted in the report that are relevant to the specific anatomy. | |
Format each section with appropriate headings and use bullet points for lists. Base your analysis on the provided MRI report details.""" | |
)}, | |
{"type": "image_url", "image_url": {"url": image_url}}, | |
] | |
}], | |
temperature=0.1, | |
max_tokens=3000, # Increased token limit for more detailed response | |
top_p=0.3 | |
) | |
return response.choices[0].message.content | |
except Exception as e: | |
st.error(f"Groq API error: {str(e)}") | |
return None | |
def generate_radiology_report(uploaded_file, api_choice='groq'): | |
client = initialize_groq_client() | |
if client: | |
return generate_radiology_report_groq(uploaded_file, client) | |
else: | |
st.error("Failed to initialize Groq client. Please check your API key.") | |
return None | |
# ====================== | |
# UI COMPONENTS | |
# ====================== | |
def display_animated_logo(): | |
"""Display an animated medical logo""" | |
logo_b64 = encode_logo("src/Round_image_depicting_a_futuristic_medical_image_a-1742282117033-photoaidcom-cropped.png") | |
# If logo file doesn't exist, use the placeholder from encode_logo | |
st.markdown( | |
f""" | |
<div class="logo-container"> | |
<div class="logo-pulse"> | |
<img src="data:image/png;base64,{logo_b64}" width="200"> | |
</div> | |
</div> | |
""", | |
unsafe_allow_html=True | |
) | |
def display_main_interface(): | |
"""Render primary application interface""" | |
# Display animated logo and titles | |
display_animated_logo() | |
st.markdown('<h1 class="main-title">Radiology Analyzer</h1>', unsafe_allow_html=True) | |
st.markdown('<p class="sub-title">Advanced Medical Imaging Analysis</p>', unsafe_allow_html=True) | |
# Action buttons | |
if st.session_state.get('analysis_result'): | |
st.markdown( | |
""" | |
<div class="analysis-complete"> | |
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> | |
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/> | |
</svg> | |
Analysis Complete | |
</div> | |
""", | |
unsafe_allow_html=True | |
) | |
col1, col2 = st.columns([1, 1]) | |
with col1: | |
pdf_report = generate_pdf_report(st.session_state.analysis_result, st.session_state.uploaded_file) | |
st.download_button( | |
label="๐ Download PDF Report", | |
data=pdf_report, | |
file_name="radiology_report.pdf", | |
mime="application/pdf", | |
use_container_width=True, | |
help="Download formal PDF version of the report" | |
) | |
with col2: | |
if st.button("Clear Analysis ๐๏ธ", use_container_width=True, help="Remove current results"): | |
st.session_state.pop('analysis_result', None) | |
st.session_state.pop('uploaded_file', None) | |
st.rerun() | |
# Display analysis results in a styled container | |
st.markdown("### ๐ฏ Radiological Findings Report") | |
st.markdown( | |
f'<div class="report-container"><div class="report-text">{st.session_state.analysis_result}</div></div>', | |
unsafe_allow_html=True | |
) | |
else: | |
# Show a centered placeholder message | |
st.markdown( | |
""" | |
<div style="text-align: center; margin-top: 50px; color: #9ca3af; padding: 100px 0;"> | |
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="currentColor" viewBox="0 0 16 16" style="margin-bottom: 20px;"> | |
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0ZM1.5 8a6.5 6.5 0 1 1 13 0 6.5 6.5 0 0 1-13 0Zm4.879-2.773 4.264 2.559a.25.25 0 0 1 0 .428l-4.264 2.559A.25.25 0 0 1 6 10.559V5.442a.25.25 0 0 1 .379-.215Z"/> | |
</svg> | |
<p style="font-size: 1.2rem;">Upload a medical image to begin analysis</p> | |
</div> | |
""", | |
unsafe_allow_html=True | |
) | |
def render_sidebar(): | |
"""Create sidebar interface elements""" | |
with st.sidebar: | |
st.markdown('<div class="sidebar-item">', unsafe_allow_html=True) | |
st.markdown('<div class="sidebar-title">Diagnostic Capabilities</div>', unsafe_allow_html=True) | |
st.markdown(""" | |
- **Multi-Modality Analysis:** X-ray, MRI, CT, Ultrasound | |
- **Pathology Detection:** Fractures, tumors, infections | |
- **Comparative Analysis:** Track disease progression | |
- **Structured Reporting:** Standardized output format | |
- **Clinical Correlation:** Suggested next steps | |
""") | |
st.markdown(""" | |
<div class="tooltip"> | |
<strong>Disclaimer:</strong> This service does not provide medical advice. | |
<span class="tooltiptext">Always consult with a qualified healthcare professional for diagnosis and treatment.</span> | |
</div> | |
""", unsafe_allow_html=True) | |
st.markdown('</div>', unsafe_allow_html=True) | |
st.markdown('</div>', unsafe_allow_html=True) | |
# Image Upload Section | |
st.markdown('<div class="sidebar-item">', unsafe_allow_html=True) | |
st.markdown('<div class="sidebar-title">Image Upload Section</div>', unsafe_allow_html=True) | |
uploaded_file = st.file_uploader( | |
"Select Medical Image", | |
type=ALLOWED_FILE_TYPES, | |
label_visibility="collapsed", | |
help="Supported formats: PNG, JPG, JPEG" | |
) | |
if uploaded_file: | |
st.session_state.uploaded_file = uploaded_file # Store uploaded file in session state | |
# Display image with a styled container | |
# st.markdown('<div class="card">', unsafe_allow_html=True) | |
st.image(Image.open(uploaded_file), | |
caption="Uploaded Medical Image", | |
use_container_width=True) | |
st.markdown('</div>', unsafe_allow_html=True) | |
if st.button("โถ๏ธ Initiate Analysis", use_container_width=True): | |
# Use the selected API provider | |
api_choice = 'groq' | |
report = generate_radiology_report(uploaded_file, api_choice) | |
if report: | |
st.session_state.analysis_result = report | |
st.rerun() | |
st.markdown('</div>', unsafe_allow_html=True) | |
# ====================== | |
# APPLICATION ENTRYPOINT | |
# ====================== | |
def main(): | |
"""Primary application controller""" | |
# Check if dark mode is in session state, default to true | |
if 'dark_mode' not in st.session_state: | |
st.session_state.dark_mode = True | |
configure_application() | |
render_sidebar() | |
display_main_interface() | |
if __name__ == "__main__": | |
main() | |