Spaces:
Sleeping
Sleeping
# -*- coding: utf-8 | |
# π₯ Gemma 3N SOAP Note Generator | |
#-*- coding: utf-8 | |
# π₯ Gemma 3N SOAP Note Generator | |
# Enable widgets | |
# Enable widgets | |
import torch | |
from transformers import AutoProcessor, AutoModelForImageTextToText | |
import gradio as gr | |
import ipywidgets as widgets | |
from IPython.display import display, clear_output | |
import io | |
import base64 | |
from datetime import datetime | |
from huggingface_hub import login | |
import getpass | |
# Authenticate with HuggingFace | |
# Replace the authentication section (lines around the getpass part) with this: | |
# Import libraries and authenticate | |
import torch | |
from transformers import AutoProcessor, AutoModelForImageTextToText | |
import gradio as gr | |
import ipywidgets as widgets | |
from IPython.display import display, clear_output | |
import io | |
import base64 | |
from datetime import datetime | |
from huggingface_hub import login | |
import os | |
import easyocr | |
from PIL import Image | |
# Authenticate with HuggingFace | |
print("π HuggingFace Authentication Required") | |
# Try to get token from environment variable first (for production/HF Spaces) | |
hf_token = os.environ.get('HF_TOKEN') or os.environ.get('HUGGINGFACE_TOKEN') | |
if hf_token: | |
print("β Found HF token in environment variables") | |
try: | |
login(token=hf_token) | |
print("β Successfully authenticated with HuggingFace!") | |
except Exception as e: | |
print(f"β Authentication failed: {e}") | |
print("Please check your token and try again.") | |
# Check GPU availability | |
import os | |
os.environ["CUDA_VISIBLE_DEVICES"] = "" # Hide all GPUs from PyTorch | |
device = "cuda" if torch.cuda.is_available() else "cpu" | |
device = "cpu" | |
print(f"π₯οΈ Using device: {device}") | |
if torch.cuda.is_available(): | |
print(f"π GPU: {torch.cuda.get_device_name(0)}") | |
print(f"πΎ GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB") | |
else: | |
print("β οΈ Running on CPU - this will be slower") | |
# Load Gemma 3N model | |
print("π‘ Loading Gemma 3N model...") | |
model_id = "google/gemma-3n-e2b-it" | |
print("π§ Loading processor...") | |
processor = AutoProcessor.from_pretrained(model_id) | |
print("π€ Loading Gemma 3N model (this may take a few minutes)...") | |
model = AutoModelForImageTextToText.from_pretrained( | |
model_id, | |
torch_dtype=torch.float16 if device == "cuda" else torch.float32, | |
low_cpu_mem_usage=True, | |
).to(device) | |
print("β Gemma 3N model loaded successfully!") | |
print(f"π Model size: ~2.9GB") | |
print(f"π― Ready for SOAP note generation!") | |
# SOAP Note Generation Function | |
def generate_soap_note(doctor_notes, include_timestamp=True): | |
""" | |
Generate a SOAP note from unstructured doctor's notes | |
""" | |
if not doctor_notes.strip(): | |
return "β Please enter some medical notes to process." | |
prompt = f"""You are a medical AI assistant. Convert the following unstructured doctor's notes into a professional SOAP note format. | |
Doctor's Notes: | |
{doctor_notes} | |
Please generate a structured SOAP note with the following sections: | |
- SUBJECTIVE: Patient's reported symptoms and history | |
- OBJECTIVE: Physical examination findings, vital signs, and test results | |
- ASSESSMENT: Clinical diagnosis and reasoning | |
- PLAN: Treatment plan, medications, and follow-up | |
Format your response as a proper medical SOAP note with specific details extracted from the notes.""" | |
try: | |
# Process input | |
inputs = processor(text=prompt, return_tensors="pt").to(device) | |
# Generate response | |
print("π Generating SOAP note with Gemma 3N...") | |
with torch.no_grad(): | |
outputs = model.generate( | |
**inputs, | |
max_new_tokens=512, | |
temperature=0.3, # Lower temperature for medical precision | |
do_sample=True, | |
pad_token_id=processor.tokenizer.eos_token_id | |
) | |
# Decode response | |
generated_text = processor.decode(outputs[0], skip_special_tokens=True) | |
# Extract only the generated part (remove the prompt) | |
soap_response = generated_text[len(prompt):].strip() | |
# Add header if requested | |
if include_timestamp: | |
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
header = f"""π SOAP NOTE - Generated by Gemma 3N | |
π Timestamp: {timestamp} | |
π€ Model: google/gemma-3n-e2b-it | |
π Processed locally on device: {device.upper()} | |
{'='*60} | |
""" | |
return header + soap_response | |
return soap_response | |
except Exception as e: | |
return f"β Error generating SOAP note: {str(e)}" | |
print("β SOAP generation function ready!") | |
"""## π Interactive SOAP Note Generator | |
### Enter medical notes below and generate professional SOAP documentation | |
""" | |
# Create interactive widgets | |
print("π¨ Creating interactive interface...") | |
# Text input area | |
notes_input = widgets.Textarea( | |
value='', | |
placeholder='Enter unstructured doctor notes here...\n\nExample:\nPatient John Smith, 45yo male, came in complaining of chest pain for 2 days. Pain is sharp, 7/10 intensity, worse with movement. Vital signs: BP 140/90, HR 88, Temp 98.6F...', | |
description='Medical Notes:', | |
layout=widgets.Layout(width='100%', height='200px') | |
) | |
# File upload widget | |
file_upload = widgets.FileUpload( | |
accept='.txt,.doc,.docx,.pdf', | |
multiple=False, | |
description='Or upload file:', | |
layout=widgets.Layout(width='300px') | |
) | |
# Generate button | |
generate_btn = widgets.Button( | |
description='π€ Generate SOAP Note', | |
button_style='primary', | |
layout=widgets.Layout(width='200px', height='40px') | |
) | |
# Output area | |
output_area = widgets.HTML( | |
value='<p style="color: #666;">π Ready to generate SOAP notes! Enter medical notes above or upload a file.</p>', | |
layout=widgets.Layout(width='100%', height='400px', overflow='auto', border='1px solid #ddd', padding='10px') | |
) | |
# Example buttons | |
example1_btn = widgets.Button(description='π Chest Pain Example', button_style='info', layout=widgets.Layout(width='180px')) | |
example2_btn = widgets.Button(description='π©Ί Diabetes Follow-up', button_style='info', layout=widgets.Layout(width='180px')) | |
example3_btn = widgets.Button(description='πΆ Pediatric Visit', button_style='info', layout=widgets.Layout(width='180px')) | |
# Clear button | |
clear_btn = widgets.Button(description='ποΈ Clear', button_style='warning', layout=widgets.Layout(width='100px')) | |
print("β Interface widgets created!") | |
# Example medical notes | |
examples = { | |
'chest_pain': """Patient John Smith, 45yo male, came in complaining of chest pain for 2 days. Pain is sharp, 7/10 intensity, worse with movement. No radiation to arms. Vital signs: BP 140/90, HR 88, Temp 98.6F, RR 16, O2 sat 98%. Physical exam shows tenderness over left chest wall, no murmurs. EKG normal sinus rhythm. Chest X-ray clear. Diagnosed with costochondritis. Prescribed ibuprofen 600mg TID and advised rest. Follow up in 1 week if symptoms persist.""", | |
'diabetes': """Sarah Johnson, 62yo female with Type 2 diabetes, here for routine follow-up. Says blood sugars have been running high lately, 180-220 mg/dL. Taking metformin 1000mg BID. Diet has been poor due to holiday stress. Weight increased 5 lbs since last visit. BP 150/85, BMI 32. HbA1c 8.2% (was 7.1% 3 months ago). Feet exam normal, no neuropathy. Plan to increase metformin to 1000mg TID, refer to nutritionist, recheck labs in 3 months.""", | |
'pediatric': """Tommy Rodriguez, 8yo male, brought by mother for fever and cough x3 days. Fever up to 102F, productive cough with yellow sputum. Decreased appetite, no vomiting or diarrhea. Vital signs: Temp 101.2F, HR 110, RR 24, BP 95/60. Exam shows bilateral crackles in lower lobes, no wheeze. Throat clear. Diagnosed with bacterial pneumonia. Prescribed amoxicillin 500mg BID x10 days. Return if fever persists >48 hours on antibiotics.""" | |
} | |
# Event handlers | |
def on_generate_click(b): | |
with output_area: | |
output_area.value = '<p style="color: #007bff;">π Processing with Gemma 3N... Please wait...</p>' | |
# Get text from input or uploaded file | |
text_to_process = notes_input.value | |
# Check if file was uploaded | |
if file_upload.value and len(file_upload.value) > 0: | |
try: | |
uploaded_file = list(file_upload.value.values())[0] | |
file_content = uploaded_file['content'].decode('utf-8') | |
text_to_process = file_content | |
notes_input.value = file_content # Show in text area | |
except Exception as e: | |
output_area.value = f'<p style="color: #dc3545;">β Error reading file: {str(e)}</p>' | |
return | |
if not text_to_process.strip(): | |
output_area.value = '<p style="color: #dc3545;">β Please enter medical notes or upload a file!</p>' | |
return | |
# Generate SOAP note | |
soap_note = generate_soap_note(text_to_process) | |
# Format output as HTML | |
formatted_output = f'<pre style="font-family: monospace; font-size: 12px; line-height: 1.4; white-space: pre-wrap;">{soap_note}</pre>' | |
output_area.value = formatted_output | |
def on_example1_click(b): | |
notes_input.value = examples['chest_pain'] | |
output_area.value = '<p style="color: #28a745;">β Chest pain example loaded! Click "Generate SOAP Note" to process.</p>' | |
def on_example2_click(b): | |
notes_input.value = examples['diabetes'] | |
output_area.value = '<p style="color: #28a745;">β Diabetes follow-up example loaded! Click "Generate SOAP Note" to process.</p>' | |
def on_example3_click(b): | |
notes_input.value = examples['pediatric'] | |
output_area.value = '<p style="color: #28a745;">β Pediatric example loaded! Click "Generate SOAP Note" to process.</p>' | |
def on_clear_click(b): | |
notes_input.value = '' | |
file_upload.value = () | |
output_area.value = '<p style="color: #666;">π Ready to generate SOAP notes! Enter medical notes above or upload a file.</p>' | |
# Bind event handlers | |
generate_btn.on_click(on_generate_click) | |
example1_btn.on_click(on_example1_click) | |
example2_btn.on_click(on_example2_click) | |
example3_btn.on_click(on_example3_click) | |
clear_btn.on_click(on_clear_click) | |
print("β Event handlers configured!") | |
# Define example medical notes first | |
example_notes_1 = """ | |
Patient: John Smith, 45-year-old male | |
Chief Complaint: Chest pain for 2 hours | |
History: Patient reports sudden onset of sharp chest pain while at work. Pain is 7/10 intensity, located substernal, radiating to left arm. Associated with shortness of breath and diaphoresis. No previous cardiac history. Denies nausea or vomiting. | |
Physical Exam: VS: BP 150/90, HR 110, RR 22, O2 Sat 96% on RA. Patient appears anxious and diaphoretic. Heart: Regular rhythm, no murmurs. Lungs: Clear bilaterally. Extremities: No edema. | |
Assessment: Acute chest pain, rule out myocardial infarction | |
Plan: EKG, cardiac enzymes, chest X-ray, aspirin 325mg, continuous cardiac monitoring | |
""" | |
example_notes_2 = """ | |
Patient: Sarah Johnson, 28-year-old female | |
Chief Complaint: Severe headache and fever | |
History: 3-day history of progressive headache, fever up to 101.5Β°F, photophobia, and neck stiffness. Patient reports this is the worst headache of her life. No recent travel or sick contacts. No rash noted. | |
Physical Exam: VS: T 101.2Β°F, BP 130/80, HR 95, RR 18. Patient appears ill and photophobic. HEENT: Pupils equal and reactive. Neck: Stiff with positive Kernig's sign. Neurologic: Alert and oriented x3, no focal deficits. | |
Assessment: Suspected meningitis | |
Plan: Lumbar puncture, blood cultures, empiric antibiotics, supportive care | |
""" | |
example_notes_3 = """ | |
Patient: Robert Davis, 62-year-old male | |
Chief Complaint: Shortness of breath and leg swelling | |
History: 2-week history of progressive dyspnea on exertion, orthopnea, and bilateral lower extremity edema. Patient has history of hypertension and diabetes. Reports sleeping on 3 pillows due to breathing difficulty. | |
Physical Exam: VS: BP 140/85, HR 88, RR 24, O2 Sat 92% on RA. Heart: S3 gallop present, JVD elevated. Lungs: Bilateral rales in lower fields. Extremities: 2+ pitting edema bilaterally. | |
Assessment: Congestive heart failure exacerbation | |
Plan: Chest X-ray, BNP, echocardiogram, furosemide, ACE inhibitor, daily weights | |
""" | |
# Event handlers | |
def on_generate_click(b): | |
try: | |
# Update the HTML widget directly | |
output_area.value = '<p style="color: #007bff;">π Processing with Gemma 3N... Please wait...</p>' | |
# Get input text | |
input_text = notes_input.value.strip() | |
# Check if file was uploaded | |
if file_upload.value: | |
try: | |
# Process uploaded file | |
uploaded_file = list(file_upload.value.values())[0] | |
file_content = uploaded_file['content'].decode('utf-8') | |
input_text = file_content | |
except Exception as upload_error: | |
output_area.value = f'<p style="color: #ff6b6b;">β File upload error: {str(upload_error)}</p>' | |
return | |
if not input_text: | |
output_area.value = '<p style="color: #ff6b6b;">β οΈ Please enter medical notes or upload a file first!</p>' | |
return | |
# Check if generate_soap_note function exists | |
if 'generate_soap_note' not in globals(): | |
output_area.value = '<p style="color: #ff6b6b;">β Error: generate_soap_note function not found. Please define it first.</p>' | |
return | |
# Generate SOAP note using Gemma | |
soap_note = generate_soap_note(input_text) | |
# Escape HTML in soap_note to prevent rendering issues | |
import html | |
escaped_soap_note = html.escape(soap_note) | |
# Display result | |
output_area.value = f''' | |
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid #28a745;"> | |
<h4 style="color: #28a745; margin-top: 0;">β Generated SOAP Note:</h4> | |
<pre style="white-space: pre-wrap; font-family: 'Courier New', monospace; background: white; padding: 15px; border-radius: 5px; border: 1px solid #ddd;">{escaped_soap_note}</pre> | |
</div> | |
''' | |
except Exception as e: | |
import traceback | |
error_details = traceback.format_exc() | |
output_area.value = f''' | |
<div style="color: #ff6b6b; background: #ffe6e6; padding: 15px; border-radius: 5px;"> | |
<h4>β Error Details:</h4> | |
<p><strong>Error:</strong> {str(e)}</p> | |
<details> | |
<summary>Click for full traceback</summary> | |
<pre style="font-size: 12px; background: #fff; padding: 10px; border-radius: 3px; margin-top: 10px;">{error_details}</pre> | |
</details> | |
</div> | |
''' | |
def on_clear_click(b): | |
try: | |
notes_input.value = "" | |
file_upload.value = () | |
output_area.value = '<p>π Ready to generate SOAP notes! Enter medical notes above or upload a file.</p>' | |
except Exception as e: | |
output_area.value = f'<p style="color: #ff6b6b;">β Clear error: {str(e)}</p>' | |
def on_example_click(example_text): | |
def handler(b): | |
try: | |
notes_input.value = example_text | |
output_area.value = '<p style="color: #28a745;">π Example loaded! Click "Generate SOAP Note" to process.</p>' | |
except Exception as e: | |
output_area.value = f'<p style="color: #ff6b6b;">β Example load error: {str(e)}</p>' | |
return handler | |
# Connect event handlers to buttons | |
try: | |
generate_btn.on_click(on_generate_click) | |
clear_btn.on_click(on_clear_click) | |
example1_btn.on_click(on_example_click(example_notes_1)) | |
example2_btn.on_click(on_example_click(example_notes_2)) | |
example3_btn.on_click(on_example_click(example_notes_3)) | |
print("β Event handlers connected successfully!") | |
print("π Example notes loaded:") | |
print(" - Example 1: Chest pain case") | |
print(" - Example 2: Suspected meningitis") | |
print(" - Example 3: Heart failure") | |
except Exception as e: | |
print(f"β Error connecting event handlers: {str(e)}") | |
import traceback | |
traceback.print_exc() | |
"""## π Alternative: Gradio Web Interface | |
### Run this cell for a shareable web interface | |
""" | |
# Install required packages for image processing and OCR | |
import gradio as gr | |
import torch | |
from PIL import Image | |
import pytesseract | |
import cv2 | |
import numpy as np | |
import easyocr | |
import io | |
# First, make sure you have the examples dictionary defined | |
examples = { | |
'chest_pain': """Patient: John Smith, 45-year-old male | |
Chief Complaint: Chest pain for 2 hours | |
History: Patient reports sudden onset of sharp chest pain while at work. Pain is 7/10 intensity, located substernal, radiating to left arm. Associated with shortness of breath and diaphoresis. No previous cardiac history. Denies nausea or vomiting. | |
Physical Exam: VS: BP 150/90, HR 110, RR 22, O2 Sat 96% on RA. Patient appears anxious and diaphoretic. Heart: Regular rhythm, no murmurs. Lungs: Clear bilaterally. Extremities: No edema. | |
Assessment: Acute chest pain, rule out myocardial infarction | |
Plan: EKG, cardiac enzymes, chest X-ray, aspirin 325mg, continuous cardiac monitoring""", | |
'diabetes': """Patient: Maria Garcia, 52-year-old female | |
Chief Complaint: Increased thirst and frequent urination for 3 weeks | |
History: Patient reports polyuria, polydipsia, and unintentional weight loss of 10 lbs over past month. Family history of diabetes. Denies fever, abdominal pain, or vision changes. | |
Physical Exam: VS: BP 140/85, HR 88, RR 16, BMI 28. Patient appears well but slightly dehydrated. HEENT: Dry mucous membranes. Cardiovascular: Regular rate and rhythm. Extremities: No diabetic foot changes noted. | |
Assessment: New onset diabetes mellitus, likely Type 2 | |
Plan: HbA1c, fasting glucose, comprehensive metabolic panel, diabetic education, metformin initiation""", | |
'pediatric': """Patient: Emma Thompson, 8-year-old female | |
Chief Complaint: Fever and sore throat for 2 days | |
History: Mother reports fever up to 102Β°F, sore throat, difficulty swallowing, and decreased appetite. No cough or runny nose. Several classmates have been sick with similar symptoms. | |
Physical Exam: VS: T 101.8Β°F, HR 110, RR 20, O2 Sat 99%. Patient appears mildly ill but alert. HEENT: Throat erythematous with tonsillar exudate, anterior cervical lymphadenopathy. Heart and lungs: Normal. | |
Assessment: Streptococcal pharyngitis (probable) | |
Plan: Rapid strep test, throat culture, amoxicillin if positive, supportive care, return if worsening""" | |
} | |
# Initialize EasyOCR reader (better for handwritten text) | |
try: | |
ocr_reader = easyocr.Reader(['en']) | |
print("β EasyOCR initialized successfully") | |
except: | |
ocr_reader = None | |
print("β οΈ EasyOCR not available, using Tesseract only") | |
def preprocess_image_for_ocr(image): | |
""" | |
Preprocess image to improve OCR accuracy | |
""" | |
# Convert PIL Image to numpy array | |
img_array = np.array(image) | |
# Convert to grayscale if needed | |
if len(img_array.shape) == 3: | |
gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY) | |
else: | |
gray = img_array | |
# Apply image preprocessing for better OCR | |
# 1. Resize image if too small | |
height, width = gray.shape | |
if height < 300 or width < 300: | |
scale_factor = max(300/height, 300/width) | |
new_width = int(width * scale_factor) | |
new_height = int(height * scale_factor) | |
gray = cv2.resize(gray, (new_width, new_height), interpolation=cv2.INTER_CUBIC) | |
# 2. Noise removal | |
denoised = cv2.medianBlur(gray, 3) | |
# 3. Contrast enhancement | |
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) | |
enhanced = clahe.apply(denoised) | |
# 4. Thresholding | |
_, thresh = cv2.threshold(enhanced, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) | |
return thresh | |
def extract_text_from_image(image): | |
""" | |
Extract text from image using multiple OCR methods | |
""" | |
if image is None: | |
return "β No image provided" | |
try: | |
# Preprocess image | |
processed_img = preprocess_image_for_ocr(image) | |
# Method 1: Try EasyOCR (better for handwritten text) | |
if ocr_reader is not None: | |
try: | |
# Convert back to PIL Image for EasyOCR | |
pil_img = Image.fromarray(processed_img) | |
results = ocr_reader.readtext(np.array(pil_img)) | |
# Extract text from EasyOCR results | |
easyocr_text = ' '.join([result[1] for result in results]) | |
if len(easyocr_text.strip()) > 20: # If we got good results | |
return clean_extracted_text(easyocr_text) | |
except Exception as e: | |
print(f"EasyOCR failed: {e}") | |
# Method 2: Tesseract OCR (fallback) | |
try: | |
# Configure Tesseract for medical text | |
custom_config = r'--oem 3 --psm 6' | |
tesseract_text = pytesseract.image_to_string(processed_img, config=custom_config) | |
if len(tesseract_text.strip()) > 10: | |
return clean_extracted_text(tesseract_text) | |
except Exception as e: | |
print(f"Tesseract failed: {e}") | |
return "β Could not extract text from image. Please try a clearer image or enter text manually." | |
except Exception as e: | |
return f"β Error processing image: {str(e)}" | |
def clean_extracted_text(text): | |
""" | |
Clean up extracted text | |
""" | |
# Remove excessive whitespace and empty lines | |
lines = [line.strip() for line in text.split('\n') if line.strip()] | |
cleaned_text = '\n'.join(lines) | |
# Remove special characters that might interfere | |
cleaned_text = cleaned_text.replace('|', '').replace('_', ' ') | |
return cleaned_text.strip() | |
def gradio_generate_soap(medical_notes, uploaded_image): | |
""" | |
Modified Gradio interface function for SOAP generation from images | |
""" | |
text_to_process = medical_notes.strip() if medical_notes else "" | |
# If image is uploaded, extract text using OCR | |
if uploaded_image is not None: | |
try: | |
print("π Extracting text from uploaded image...") | |
extracted_text = extract_text_from_image(uploaded_image) | |
# Check if OCR was successful | |
if extracted_text.startswith("β"): | |
return extracted_text | |
# Use extracted text if manual text is empty or append to manual text | |
if not text_to_process: | |
text_to_process = extracted_text | |
else: | |
text_to_process = f"{text_to_process}\n\n--- Extracted from image ---\n{extracted_text}" | |
except Exception as e: | |
return f"β Error processing image: {str(e)}" | |
if not text_to_process: | |
return "β Please enter medical notes manually or upload a PNG/JPG image with medical text" | |
# Check if generate_soap_note function exists | |
if 'generate_soap_note' not in globals(): | |
return "β Error: generate_soap_note function not found. Please define it first." | |
try: | |
return generate_soap_note(text_to_process) | |
except Exception as e: | |
return f"β Error generating SOAP note: {str(e)}" | |
# Create example images (you can replace these with actual medical note images) | |
def create_example_image(text, filename): | |
""" | |
Create example images from text (for demonstration) | |
""" | |
from PIL import Image, ImageDraw, ImageFont | |
# Create a white image | |
img = Image.new('RGB', (800, 600), color='white') | |
draw = ImageDraw.Draw(img) | |
try: | |
# Try to use a default font | |
font = ImageFont.load_default() | |
except: | |
font = None | |
# Add text to image | |
lines = text.split('\n') | |
y_offset = 20 | |
for line in lines[:15]: # Limit to first 15 lines | |
draw.text((20, y_offset), line, fill='black', font=font) | |
y_offset += 25 | |
return img | |
# Create Gradio interface | |
gradio_interface = gr.Interface( | |
fn=gradio_generate_soap, | |
inputs=[ | |
gr.Textbox( | |
lines=6, | |
placeholder="Enter medical notes manually (optional)...\n\nOr upload an image below and text will be extracted automatically.", | |
label="π Medical Notes (Manual Entry)" | |
), | |
gr.Image( | |
type="pil", | |
label="π· Upload Medical Image (PNG/JPG only)", | |
sources=["upload", "webcam"], # FIXED: Changed "camera" to "webcam" | |
image_mode="RGB" | |
) | |
], | |
outputs=[ | |
gr.Textbox( | |
lines=15, | |
label="π Generated SOAP Note", | |
show_copy_button=True | |
) | |
], | |
title="π₯ Medical Image SOAP Note Generator", | |
description=""" | |
Transform medical images (PNG/JPG) into professional SOAP documentation using OCR + Gemma 3N model. | |
πΈ **How to use:** | |
1. Upload a PNG or JPG image of medical notes (typed or handwritten) | |
2. Or enter text manually in the text box above | |
3. The system will extract text from images using OCR | |
4. Generate structured SOAP notes automatically | |
π‘ **Tips for better OCR results:** | |
- Use clear, high-resolution images | |
- Ensure good lighting and contrast | |
- Keep text horizontal (not tilted) | |
- Handwritten text works best when clearly written | |
""", | |
examples=[ | |
[examples['chest_pain'], None], | |
[examples['diabetes'], None], | |
[examples['pediatric'], None] | |
], | |
theme=gr.themes.Soft(), | |
flagging_mode="never" | |
) | |
# Launch Gradio interface with flexible port selection | |
print("π Launching Medical Image SOAP Generator...") | |
try: | |
# Try different ports if 7860 is busy | |
for port in [7860, 7861, 7862, 7863, 7864]: | |
try: | |
gradio_interface.launch( | |
share=True, # Creates a public shareable link | |
server_port=port, | |
show_error=True, | |
quiet=False | |
) | |
print(f"β Interface launched successfully on port {port}") | |
break | |
except OSError as port_error: | |
print(f"β οΈ Port {port} is busy, trying next port...") | |
continue | |
else: | |
# If all ports are busy, let Gradio choose automatically | |
print("π All preferred ports busy, letting Gradio choose automatically...") | |
gradio_interface.launch( | |
share=True, | |
show_error=True, | |
quiet=False | |
) | |
except Exception as e: | |
print(f"β Error launching Gradio interface: {str(e)}") | |
print("π‘ Alternative: Try running without share=True:") | |
print("gradio_interface.launch(show_error=True)") | |
print("π― Medical Image SOAP Generator ready!") | |
print("πΈ Upload PNG/JPG images of medical notes for automatic text extraction and SOAP generation") | |
"""## π Usage Statistics & Model Info""" | |
# Display model and system information | |
import psutil | |
import GPUtil | |
def show_system_info(): | |
print("π§ SYSTEM INFORMATION") | |
print("="*50) | |
print(f"π₯οΈ Device: {device.upper()}") | |
print(f"π§ CPU Usage: {psutil.cpu_percent(interval=1):.1f}%") | |
print(f"πΎ RAM Usage: {psutil.virtual_memory().percent:.1f}%") | |
if torch.cuda.is_available(): | |
try: | |
gpus = GPUtil.getGPUs() | |
if gpus: | |
gpu = gpus[0] | |
print(f"π GPU: {gpu.name}") | |
print(f"π GPU Usage: {gpu.load*100:.1f}%") | |
print(f"π₯ GPU Memory: {gpu.memoryUsed}/{gpu.memoryTotal} MB ({gpu.memoryPercent:.1f}%)") | |
print(f"π‘οΈ GPU Temp: {gpu.temperature}Β°C") | |
except: | |
print(f"π GPU Memory: {torch.cuda.memory_allocated()/1e9:.1f}GB / {torch.cuda.memory_reserved()/1e9:.1f}GB") | |
print("\nπ€ MODEL INFORMATION") | |
print("="*50) | |
print(f"π‘ Model ID: {model_id}") | |
print(f"π― Model Type: Multimodal (Text, Image, Audio)") | |
print(f"π Model Size: ~2.9GB") | |
print(f"π’ Parameters: ~2.9B") | |
print(f"π Languages: 140 text + 35 multimodal") | |
print(f"π½ Precision: {model.dtype}") | |
print("\nβ Ready for SOAP note generation!") | |
show_system_info() | |
"""--- | |
## π SOAP Note Format Reference | |
**S - SUBJECTIVE**: Patient's reported symptoms and history | |
**O - OBJECTIVE**: Observable clinical findings | |
**A - ASSESSMENT**: Clinical diagnosis/impression | |
**P - PLAN**: Treatment and follow-up plan | |
--- | |
*π€ Powered by Google's Gemma 3N Model | π All processing performed locally* | |
""" | |