Spaces:
Sleeping
Sleeping
| 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() | |