Spaces:
Sleeping
Sleeping
| # -*- coding: utf-8 | |
| # π₯ Gemma 3N SOAP Note Generator | |
| # This decorator tells HF Spaces to use GPU | |
| # Enable widgets | |
| # 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 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 | |
| # 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 | |
| device = "cuda" if torch.cuda.is_available() else "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* | |
| """ | |