Spaces:
Sleeping
Sleeping
File size: 28,381 Bytes
ed48c92 7403c04 f5377fa 204fca4 7403c04 9f5b856 7403c04 ed48c92 c0531db ed48c92 204fca4 7403c04 ed48c92 7403c04 c0531db 7403c04 c0531db 7403c04 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 |
# -*- 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*
"""
|