Bonosa2 commited on
Commit
7403c04
Β·
verified Β·
1 Parent(s): 350c7cd

Upload 2 files

Browse files
Files changed (2) hide show
  1. README.md +14 -0
  2. app.py +706 -0
README.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Scribbled Docs Notes
3
+ emoji: 🐨
4
+ colorFrom: pink
5
+ colorTo: yellow
6
+ sdk: gradio
7
+ sdk_version: 5.36.2
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ short_description: An app to convert doc notes to SOAP
12
+ ---
13
+
14
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,706 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """gemma_3n_colab.ipynb
3
+
4
+ Automatically generated by Colab.
5
+
6
+ Original file is located at
7
+ https://colab.research.google.com/drive/1U5pbaYG8qD7HFANwU7PI1jbLGOPvArnP
8
+
9
+ # πŸ₯ Gemma 3N SOAP Note Generator
10
+ ## Interactive Medical Documentation Assistant
11
+
12
+ This notebook provides a complete interface for generating SOAP notes from medical text using the Gemma 3N model.
13
+ """
14
+
15
+ # Install required packages
16
+ !pip install -q transformers torch torchvision torchaudio timm accelerate
17
+ !pip install -q ipywidgets gradio
18
+ !pip install -q --upgrade huggingface_hub
19
+ !pip install GPUtil
20
+
21
+ # Enable widgets
22
+ from IPython.display import display, HTML
23
+ display(HTML("<script src='https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.10/require.min.js'></script>"))
24
+
25
+ # Import libraries and authenticate
26
+ import torch
27
+ from transformers import AutoProcessor, AutoModelForImageTextToText
28
+ import gradio as gr
29
+ import ipywidgets as widgets
30
+ from IPython.display import display, clear_output
31
+ import io
32
+ import base64
33
+ from datetime import datetime
34
+ from huggingface_hub import login
35
+ import getpass
36
+
37
+ # Authenticate with HuggingFace
38
+ print("πŸ” HuggingFace Authentication Required")
39
+ print("Please enter your HuggingFace token (it will be hidden):")
40
+ hf_token = getpass.getpass("HF Token: ")
41
+
42
+ try:
43
+ login(token=hf_token)
44
+ print("βœ… Successfully authenticated with HuggingFace!")
45
+ except Exception as e:
46
+ print(f"❌ Authentication failed: {e}")
47
+ print("Please check your token and try again.")
48
+
49
+ # Check GPU availability
50
+ device = "cuda" if torch.cuda.is_available() else "cpu"
51
+ print(f"πŸ–₯️ Using device: {device}")
52
+ if torch.cuda.is_available():
53
+ print(f"πŸš€ GPU: {torch.cuda.get_device_name(0)}")
54
+ print(f"πŸ’Ύ GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")
55
+ else:
56
+ print("⚠️ Running on CPU - this will be slower")
57
+
58
+ # Load Gemma 3N model
59
+ print("πŸ“‘ Loading Gemma 3N model...")
60
+ model_id = "google/gemma-3n-e2b-it"
61
+
62
+ print("πŸ”§ Loading processor...")
63
+ processor = AutoProcessor.from_pretrained(model_id)
64
+
65
+ print("πŸ€– Loading Gemma 3N model (this may take a few minutes)...")
66
+ model = AutoModelForImageTextToText.from_pretrained(
67
+ model_id,
68
+ torch_dtype=torch.float16 if device == "cuda" else torch.float32,
69
+ low_cpu_mem_usage=True,
70
+ ).to(device)
71
+
72
+ print("βœ… Gemma 3N model loaded successfully!")
73
+ print(f"πŸ“Š Model size: ~2.9GB")
74
+ print(f"🎯 Ready for SOAP note generation!")
75
+
76
+ # SOAP Note Generation Function
77
+ def generate_soap_note(doctor_notes, include_timestamp=True):
78
+ """
79
+ Generate a SOAP note from unstructured doctor's notes
80
+ """
81
+ if not doctor_notes.strip():
82
+ return "❌ Please enter some medical notes to process."
83
+
84
+ prompt = f"""You are a medical AI assistant. Convert the following unstructured doctor's notes into a professional SOAP note format.
85
+
86
+ Doctor's Notes:
87
+ {doctor_notes}
88
+
89
+ Please generate a structured SOAP note with the following sections:
90
+ - SUBJECTIVE: Patient's reported symptoms and history
91
+ - OBJECTIVE: Physical examination findings, vital signs, and test results
92
+ - ASSESSMENT: Clinical diagnosis and reasoning
93
+ - PLAN: Treatment plan, medications, and follow-up
94
+
95
+ Format your response as a proper medical SOAP note with specific details extracted from the notes."""
96
+
97
+ try:
98
+ # Process input
99
+ inputs = processor(text=prompt, return_tensors="pt").to(device)
100
+
101
+ # Generate response
102
+ print("πŸ”„ Generating SOAP note with Gemma 3N...")
103
+ with torch.no_grad():
104
+ outputs = model.generate(
105
+ **inputs,
106
+ max_new_tokens=512,
107
+ temperature=0.3, # Lower temperature for medical precision
108
+ do_sample=True,
109
+ pad_token_id=processor.tokenizer.eos_token_id
110
+ )
111
+
112
+ # Decode response
113
+ generated_text = processor.decode(outputs[0], skip_special_tokens=True)
114
+
115
+ # Extract only the generated part (remove the prompt)
116
+ soap_response = generated_text[len(prompt):].strip()
117
+
118
+ # Add header if requested
119
+ if include_timestamp:
120
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
121
+ header = f"""πŸ“‹ SOAP NOTE - Generated by Gemma 3N
122
+ πŸ• Timestamp: {timestamp}
123
+ πŸ€– Model: google/gemma-3n-e2b-it
124
+ πŸ”’ Processed locally on device: {device.upper()}
125
+
126
+ {'='*60}
127
+ """
128
+ return header + soap_response
129
+
130
+ return soap_response
131
+
132
+ except Exception as e:
133
+ return f"❌ Error generating SOAP note: {str(e)}"
134
+
135
+ print("βœ… SOAP generation function ready!")
136
+
137
+ """## πŸ“ Interactive SOAP Note Generator
138
+ ### Enter medical notes below and generate professional SOAP documentation
139
+ """
140
+
141
+ # Create interactive widgets
142
+ print("🎨 Creating interactive interface...")
143
+
144
+ # Text input area
145
+ notes_input = widgets.Textarea(
146
+ value='',
147
+ 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...',
148
+ description='Medical Notes:',
149
+ layout=widgets.Layout(width='100%', height='200px')
150
+ )
151
+
152
+ # File upload widget
153
+ file_upload = widgets.FileUpload(
154
+ accept='.txt,.doc,.docx,.pdf',
155
+ multiple=False,
156
+ description='Or upload file:',
157
+ layout=widgets.Layout(width='300px')
158
+ )
159
+
160
+ # Generate button
161
+ generate_btn = widgets.Button(
162
+ description='πŸ€– Generate SOAP Note',
163
+ button_style='primary',
164
+ layout=widgets.Layout(width='200px', height='40px')
165
+ )
166
+
167
+ # Output area
168
+ output_area = widgets.HTML(
169
+ value='<p style="color: #666;">πŸ“‹ Ready to generate SOAP notes! Enter medical notes above or upload a file.</p>',
170
+ layout=widgets.Layout(width='100%', height='400px', overflow='auto', border='1px solid #ddd', padding='10px')
171
+ )
172
+
173
+ # Example buttons
174
+ example1_btn = widgets.Button(description='πŸ“‹ Chest Pain Example', button_style='info', layout=widgets.Layout(width='180px'))
175
+ example2_btn = widgets.Button(description='🩺 Diabetes Follow-up', button_style='info', layout=widgets.Layout(width='180px'))
176
+ example3_btn = widgets.Button(description='πŸ‘Ά Pediatric Visit', button_style='info', layout=widgets.Layout(width='180px'))
177
+
178
+ # Clear button
179
+ clear_btn = widgets.Button(description='πŸ—‘οΈ Clear', button_style='warning', layout=widgets.Layout(width='100px'))
180
+
181
+ print("βœ… Interface widgets created!")
182
+
183
+ # Example medical notes
184
+ examples = {
185
+ '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.""",
186
+
187
+ '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.""",
188
+
189
+ '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."""
190
+ }
191
+
192
+ # Event handlers
193
+ def on_generate_click(b):
194
+ with output_area:
195
+ output_area.value = '<p style="color: #007bff;">πŸ”„ Processing with Gemma 3N... Please wait...</p>'
196
+
197
+ # Get text from input or uploaded file
198
+ text_to_process = notes_input.value
199
+
200
+ # Check if file was uploaded
201
+ if file_upload.value and len(file_upload.value) > 0:
202
+ try:
203
+ uploaded_file = list(file_upload.value.values())[0]
204
+ file_content = uploaded_file['content'].decode('utf-8')
205
+ text_to_process = file_content
206
+ notes_input.value = file_content # Show in text area
207
+ except Exception as e:
208
+ output_area.value = f'<p style="color: #dc3545;">❌ Error reading file: {str(e)}</p>'
209
+ return
210
+
211
+ if not text_to_process.strip():
212
+ output_area.value = '<p style="color: #dc3545;">❌ Please enter medical notes or upload a file!</p>'
213
+ return
214
+
215
+ # Generate SOAP note
216
+ soap_note = generate_soap_note(text_to_process)
217
+
218
+ # Format output as HTML
219
+ formatted_output = f'<pre style="font-family: monospace; font-size: 12px; line-height: 1.4; white-space: pre-wrap;">{soap_note}</pre>'
220
+ output_area.value = formatted_output
221
+
222
+ def on_example1_click(b):
223
+ notes_input.value = examples['chest_pain']
224
+ output_area.value = '<p style="color: #28a745;">βœ… Chest pain example loaded! Click "Generate SOAP Note" to process.</p>'
225
+
226
+ def on_example2_click(b):
227
+ notes_input.value = examples['diabetes']
228
+ output_area.value = '<p style="color: #28a745;">βœ… Diabetes follow-up example loaded! Click "Generate SOAP Note" to process.</p>'
229
+
230
+ def on_example3_click(b):
231
+ notes_input.value = examples['pediatric']
232
+ output_area.value = '<p style="color: #28a745;">βœ… Pediatric example loaded! Click "Generate SOAP Note" to process.</p>'
233
+
234
+ def on_clear_click(b):
235
+ notes_input.value = ''
236
+ file_upload.value = ()
237
+ output_area.value = '<p style="color: #666;">πŸ“‹ Ready to generate SOAP notes! Enter medical notes above or upload a file.</p>'
238
+
239
+ # Bind event handlers
240
+ generate_btn.on_click(on_generate_click)
241
+ example1_btn.on_click(on_example1_click)
242
+ example2_btn.on_click(on_example2_click)
243
+ example3_btn.on_click(on_example3_click)
244
+ clear_btn.on_click(on_clear_click)
245
+
246
+ print("βœ… Event handlers configured!")
247
+
248
+ # Define example medical notes first
249
+ example_notes_1 = """
250
+ Patient: John Smith, 45-year-old male
251
+ Chief Complaint: Chest pain for 2 hours
252
+ 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.
253
+ 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.
254
+ Assessment: Acute chest pain, rule out myocardial infarction
255
+ Plan: EKG, cardiac enzymes, chest X-ray, aspirin 325mg, continuous cardiac monitoring
256
+ """
257
+
258
+ example_notes_2 = """
259
+ Patient: Sarah Johnson, 28-year-old female
260
+ Chief Complaint: Severe headache and fever
261
+ 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.
262
+ 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.
263
+ Assessment: Suspected meningitis
264
+ Plan: Lumbar puncture, blood cultures, empiric antibiotics, supportive care
265
+ """
266
+
267
+ example_notes_3 = """
268
+ Patient: Robert Davis, 62-year-old male
269
+ Chief Complaint: Shortness of breath and leg swelling
270
+ 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.
271
+ 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.
272
+ Assessment: Congestive heart failure exacerbation
273
+ Plan: Chest X-ray, BNP, echocardiogram, furosemide, ACE inhibitor, daily weights
274
+ """
275
+
276
+ # Event handlers
277
+ def on_generate_click(b):
278
+ try:
279
+ # Update the HTML widget directly
280
+ output_area.value = '<p style="color: #007bff;">πŸ”„ Processing with Gemma 3N... Please wait...</p>'
281
+
282
+ # Get input text
283
+ input_text = notes_input.value.strip()
284
+
285
+ # Check if file was uploaded
286
+ if file_upload.value:
287
+ try:
288
+ # Process uploaded file
289
+ uploaded_file = list(file_upload.value.values())[0]
290
+ file_content = uploaded_file['content'].decode('utf-8')
291
+ input_text = file_content
292
+ except Exception as upload_error:
293
+ output_area.value = f'<p style="color: #ff6b6b;">❌ File upload error: {str(upload_error)}</p>'
294
+ return
295
+
296
+ if not input_text:
297
+ output_area.value = '<p style="color: #ff6b6b;">⚠️ Please enter medical notes or upload a file first!</p>'
298
+ return
299
+
300
+ # Check if generate_soap_note function exists
301
+ if 'generate_soap_note' not in globals():
302
+ output_area.value = '<p style="color: #ff6b6b;">❌ Error: generate_soap_note function not found. Please define it first.</p>'
303
+ return
304
+
305
+ # Generate SOAP note using Gemma
306
+ soap_note = generate_soap_note(input_text)
307
+
308
+ # Escape HTML in soap_note to prevent rendering issues
309
+ import html
310
+ escaped_soap_note = html.escape(soap_note)
311
+
312
+ # Display result
313
+ output_area.value = f'''
314
+ <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid #28a745;">
315
+ <h4 style="color: #28a745; margin-top: 0;">βœ… Generated SOAP Note:</h4>
316
+ <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>
317
+ </div>
318
+ '''
319
+
320
+ except Exception as e:
321
+ import traceback
322
+ error_details = traceback.format_exc()
323
+ output_area.value = f'''
324
+ <div style="color: #ff6b6b; background: #ffe6e6; padding: 15px; border-radius: 5px;">
325
+ <h4>❌ Error Details:</h4>
326
+ <p><strong>Error:</strong> {str(e)}</p>
327
+ <details>
328
+ <summary>Click for full traceback</summary>
329
+ <pre style="font-size: 12px; background: #fff; padding: 10px; border-radius: 3px; margin-top: 10px;">{error_details}</pre>
330
+ </details>
331
+ </div>
332
+ '''
333
+
334
+ def on_clear_click(b):
335
+ try:
336
+ notes_input.value = ""
337
+ file_upload.value = ()
338
+ output_area.value = '<p>πŸ“‹ Ready to generate SOAP notes! Enter medical notes above or upload a file.</p>'
339
+ except Exception as e:
340
+ output_area.value = f'<p style="color: #ff6b6b;">❌ Clear error: {str(e)}</p>'
341
+
342
+ def on_example_click(example_text):
343
+ def handler(b):
344
+ try:
345
+ notes_input.value = example_text
346
+ output_area.value = '<p style="color: #28a745;">πŸ“‹ Example loaded! Click "Generate SOAP Note" to process.</p>'
347
+ except Exception as e:
348
+ output_area.value = f'<p style="color: #ff6b6b;">❌ Example load error: {str(e)}</p>'
349
+ return handler
350
+
351
+ # Connect event handlers to buttons
352
+ try:
353
+ generate_btn.on_click(on_generate_click)
354
+ clear_btn.on_click(on_clear_click)
355
+ example1_btn.on_click(on_example_click(example_notes_1))
356
+ example2_btn.on_click(on_example_click(example_notes_2))
357
+ example3_btn.on_click(on_example_click(example_notes_3))
358
+
359
+ print("βœ… Event handlers connected successfully!")
360
+ print("πŸ“‹ Example notes loaded:")
361
+ print(" - Example 1: Chest pain case")
362
+ print(" - Example 2: Suspected meningitis")
363
+ print(" - Example 3: Heart failure")
364
+
365
+ except Exception as e:
366
+ print(f"❌ Error connecting event handlers: {str(e)}")
367
+ import traceback
368
+ traceback.print_exc()
369
+
370
+ """## 🌐 Alternative: Gradio Web Interface
371
+ ### Run this cell for a shareable web interface
372
+ """
373
+
374
+ # Install required packages for image processing and OCR
375
+ !pip install -q pytesseract opencv-python pillow easyocr
376
+ !apt-get install -q tesseract-ocr
377
+
378
+ import gradio as gr
379
+ import torch
380
+ from PIL import Image
381
+ import pytesseract
382
+ import cv2
383
+ import numpy as np
384
+ import easyocr
385
+ import io
386
+
387
+ # First, make sure you have the examples dictionary defined
388
+ examples = {
389
+ 'chest_pain': """Patient: John Smith, 45-year-old male
390
+ Chief Complaint: Chest pain for 2 hours
391
+ 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.
392
+ 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.
393
+ Assessment: Acute chest pain, rule out myocardial infarction
394
+ Plan: EKG, cardiac enzymes, chest X-ray, aspirin 325mg, continuous cardiac monitoring""",
395
+
396
+ 'diabetes': """Patient: Maria Garcia, 52-year-old female
397
+ Chief Complaint: Increased thirst and frequent urination for 3 weeks
398
+ 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.
399
+ 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.
400
+ Assessment: New onset diabetes mellitus, likely Type 2
401
+ Plan: HbA1c, fasting glucose, comprehensive metabolic panel, diabetic education, metformin initiation""",
402
+
403
+ 'pediatric': """Patient: Emma Thompson, 8-year-old female
404
+ Chief Complaint: Fever and sore throat for 2 days
405
+ 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.
406
+ 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.
407
+ Assessment: Streptococcal pharyngitis (probable)
408
+ Plan: Rapid strep test, throat culture, amoxicillin if positive, supportive care, return if worsening"""
409
+ }
410
+
411
+ # Initialize EasyOCR reader (better for handwritten text)
412
+ try:
413
+ ocr_reader = easyocr.Reader(['en'])
414
+ print("βœ… EasyOCR initialized successfully")
415
+ except:
416
+ ocr_reader = None
417
+ print("⚠️ EasyOCR not available, using Tesseract only")
418
+
419
+ def preprocess_image_for_ocr(image):
420
+ """
421
+ Preprocess image to improve OCR accuracy
422
+ """
423
+ # Convert PIL Image to numpy array
424
+ img_array = np.array(image)
425
+
426
+ # Convert to grayscale if needed
427
+ if len(img_array.shape) == 3:
428
+ gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
429
+ else:
430
+ gray = img_array
431
+
432
+ # Apply image preprocessing for better OCR
433
+ # 1. Resize image if too small
434
+ height, width = gray.shape
435
+ if height < 300 or width < 300:
436
+ scale_factor = max(300/height, 300/width)
437
+ new_width = int(width * scale_factor)
438
+ new_height = int(height * scale_factor)
439
+ gray = cv2.resize(gray, (new_width, new_height), interpolation=cv2.INTER_CUBIC)
440
+
441
+ # 2. Noise removal
442
+ denoised = cv2.medianBlur(gray, 3)
443
+
444
+ # 3. Contrast enhancement
445
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
446
+ enhanced = clahe.apply(denoised)
447
+
448
+ # 4. Thresholding
449
+ _, thresh = cv2.threshold(enhanced, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
450
+
451
+ return thresh
452
+
453
+ def extract_text_from_image(image):
454
+ """
455
+ Extract text from image using multiple OCR methods
456
+ """
457
+ if image is None:
458
+ return "❌ No image provided"
459
+
460
+ try:
461
+ # Preprocess image
462
+ processed_img = preprocess_image_for_ocr(image)
463
+
464
+ # Method 1: Try EasyOCR (better for handwritten text)
465
+ if ocr_reader is not None:
466
+ try:
467
+ # Convert back to PIL Image for EasyOCR
468
+ pil_img = Image.fromarray(processed_img)
469
+ results = ocr_reader.readtext(np.array(pil_img))
470
+
471
+ # Extract text from EasyOCR results
472
+ easyocr_text = ' '.join([result[1] for result in results])
473
+
474
+ if len(easyocr_text.strip()) > 20: # If we got good results
475
+ return clean_extracted_text(easyocr_text)
476
+
477
+ except Exception as e:
478
+ print(f"EasyOCR failed: {e}")
479
+
480
+ # Method 2: Tesseract OCR (fallback)
481
+ try:
482
+ # Configure Tesseract for medical text
483
+ custom_config = r'--oem 3 --psm 6'
484
+ tesseract_text = pytesseract.image_to_string(processed_img, config=custom_config)
485
+
486
+ if len(tesseract_text.strip()) > 10:
487
+ return clean_extracted_text(tesseract_text)
488
+
489
+ except Exception as e:
490
+ print(f"Tesseract failed: {e}")
491
+
492
+ return "❌ Could not extract text from image. Please try a clearer image or enter text manually."
493
+
494
+ except Exception as e:
495
+ return f"❌ Error processing image: {str(e)}"
496
+
497
+ def clean_extracted_text(text):
498
+ """
499
+ Clean up extracted text
500
+ """
501
+ # Remove excessive whitespace and empty lines
502
+ lines = [line.strip() for line in text.split('\n') if line.strip()]
503
+ cleaned_text = '\n'.join(lines)
504
+
505
+ # Remove special characters that might interfere
506
+ cleaned_text = cleaned_text.replace('|', '').replace('_', ' ')
507
+
508
+ return cleaned_text.strip()
509
+
510
+ def gradio_generate_soap(medical_notes, uploaded_image):
511
+ """
512
+ Modified Gradio interface function for SOAP generation from images
513
+ """
514
+ text_to_process = medical_notes.strip() if medical_notes else ""
515
+
516
+ # If image is uploaded, extract text using OCR
517
+ if uploaded_image is not None:
518
+ try:
519
+ print("πŸ” Extracting text from uploaded image...")
520
+ extracted_text = extract_text_from_image(uploaded_image)
521
+
522
+ # Check if OCR was successful
523
+ if extracted_text.startswith("❌"):
524
+ return extracted_text
525
+
526
+ # Use extracted text if manual text is empty or append to manual text
527
+ if not text_to_process:
528
+ text_to_process = extracted_text
529
+ else:
530
+ text_to_process = f"{text_to_process}\n\n--- Extracted from image ---\n{extracted_text}"
531
+
532
+ except Exception as e:
533
+ return f"❌ Error processing image: {str(e)}"
534
+
535
+ if not text_to_process:
536
+ return "❌ Please enter medical notes manually or upload a PNG/JPG image with medical text"
537
+
538
+ # Check if generate_soap_note function exists
539
+ if 'generate_soap_note' not in globals():
540
+ return "❌ Error: generate_soap_note function not found. Please define it first."
541
+
542
+ try:
543
+ return generate_soap_note(text_to_process)
544
+ except Exception as e:
545
+ return f"❌ Error generating SOAP note: {str(e)}"
546
+
547
+ # Create example images (you can replace these with actual medical note images)
548
+ def create_example_image(text, filename):
549
+ """
550
+ Create example images from text (for demonstration)
551
+ """
552
+ from PIL import Image, ImageDraw, ImageFont
553
+
554
+ # Create a white image
555
+ img = Image.new('RGB', (800, 600), color='white')
556
+ draw = ImageDraw.Draw(img)
557
+
558
+ try:
559
+ # Try to use a default font
560
+ font = ImageFont.load_default()
561
+ except:
562
+ font = None
563
+
564
+ # Add text to image
565
+ lines = text.split('\n')
566
+ y_offset = 20
567
+ for line in lines[:15]: # Limit to first 15 lines
568
+ draw.text((20, y_offset), line, fill='black', font=font)
569
+ y_offset += 25
570
+
571
+ return img
572
+
573
+ # Create Gradio interface
574
+ gradio_interface = gr.Interface(
575
+ fn=gradio_generate_soap,
576
+ inputs=[
577
+ gr.Textbox(
578
+ lines=6,
579
+ placeholder="Enter medical notes manually (optional)...\n\nOr upload an image below and text will be extracted automatically.",
580
+ label="πŸ“ Medical Notes (Manual Entry)"
581
+ ),
582
+ gr.Image(
583
+ type="pil",
584
+ label="πŸ“· Upload Medical Image (PNG/JPG only)",
585
+ sources=["upload", "webcam"], # FIXED: Changed "camera" to "webcam"
586
+ image_mode="RGB"
587
+ )
588
+ ],
589
+ outputs=[
590
+ gr.Textbox(
591
+ lines=15,
592
+ label="πŸ“‹ Generated SOAP Note",
593
+ show_copy_button=True
594
+ )
595
+ ],
596
+ title="πŸ₯ Medical Image SOAP Note Generator",
597
+ description="""
598
+ Transform medical images (PNG/JPG) into professional SOAP documentation using OCR + Gemma 3N model.
599
+
600
+ πŸ“Έ **How to use:**
601
+ 1. Upload a PNG or JPG image of medical notes (typed or handwritten)
602
+ 2. Or enter text manually in the text box above
603
+ 3. The system will extract text from images using OCR
604
+ 4. Generate structured SOAP notes automatically
605
+
606
+ πŸ’‘ **Tips for better OCR results:**
607
+ - Use clear, high-resolution images
608
+ - Ensure good lighting and contrast
609
+ - Keep text horizontal (not tilted)
610
+ - Handwritten text works best when clearly written
611
+ """,
612
+ examples=[
613
+ [examples['chest_pain'], None],
614
+ [examples['diabetes'], None],
615
+ [examples['pediatric'], None]
616
+ ],
617
+ theme=gr.themes.Soft(),
618
+ flagging_mode="never"
619
+ )
620
+
621
+ # Launch Gradio interface with flexible port selection
622
+ print("πŸš€ Launching Medical Image SOAP Generator...")
623
+
624
+ try:
625
+ # Try different ports if 7860 is busy
626
+ for port in [7860, 7861, 7862, 7863, 7864]:
627
+ try:
628
+ gradio_interface.launch(
629
+ share=True, # Creates a public shareable link
630
+ server_port=port,
631
+ show_error=True,
632
+ quiet=False
633
+ )
634
+ print(f"βœ… Interface launched successfully on port {port}")
635
+ break
636
+ except OSError as port_error:
637
+ print(f"⚠️ Port {port} is busy, trying next port...")
638
+ continue
639
+ else:
640
+ # If all ports are busy, let Gradio choose automatically
641
+ print("πŸ”„ All preferred ports busy, letting Gradio choose automatically...")
642
+ gradio_interface.launch(
643
+ share=True,
644
+ show_error=True,
645
+ quiet=False
646
+ )
647
+
648
+ except Exception as e:
649
+ print(f"❌ Error launching Gradio interface: {str(e)}")
650
+ print("πŸ’‘ Alternative: Try running without share=True:")
651
+ print("gradio_interface.launch(show_error=True)")
652
+
653
+ print("🎯 Medical Image SOAP Generator ready!")
654
+ print("πŸ“Έ Upload PNG/JPG images of medical notes for automatic text extraction and SOAP generation")
655
+
656
+ """## πŸ“Š Usage Statistics & Model Info"""
657
+
658
+ # Display model and system information
659
+ import psutil
660
+ import GPUtil
661
+
662
+ def show_system_info():
663
+ print("πŸ”§ SYSTEM INFORMATION")
664
+ print("="*50)
665
+ print(f"πŸ–₯️ Device: {device.upper()}")
666
+ print(f"🧠 CPU Usage: {psutil.cpu_percent(interval=1):.1f}%")
667
+ print(f"πŸ’Ύ RAM Usage: {psutil.virtual_memory().percent:.1f}%")
668
+
669
+ if torch.cuda.is_available():
670
+ try:
671
+ gpus = GPUtil.getGPUs()
672
+ if gpus:
673
+ gpu = gpus[0]
674
+ print(f"πŸš€ GPU: {gpu.name}")
675
+ print(f"πŸ“Š GPU Usage: {gpu.load*100:.1f}%")
676
+ print(f"πŸ”₯ GPU Memory: {gpu.memoryUsed}/{gpu.memoryTotal} MB ({gpu.memoryPercent:.1f}%)")
677
+ print(f"🌑️ GPU Temp: {gpu.temperature}°C")
678
+ except:
679
+ print(f"πŸš€ GPU Memory: {torch.cuda.memory_allocated()/1e9:.1f}GB / {torch.cuda.memory_reserved()/1e9:.1f}GB")
680
+
681
+ print("\nπŸ€– MODEL INFORMATION")
682
+ print("="*50)
683
+ print(f"πŸ“‘ Model ID: {model_id}")
684
+ print(f"🎯 Model Type: Multimodal (Text, Image, Audio)")
685
+ print(f"πŸ“Š Model Size: ~2.9GB")
686
+ print(f"πŸ”’ Parameters: ~2.9B")
687
+ print(f"🌍 Languages: 140 text + 35 multimodal")
688
+ print(f"πŸ’½ Precision: {model.dtype}")
689
+
690
+ print("\nβœ… Ready for SOAP note generation!")
691
+
692
+ show_system_info()
693
+
694
+ """---
695
+ ## πŸ“‹ SOAP Note Format Reference
696
+
697
+ **S - SUBJECTIVE**: Patient's reported symptoms and history
698
+ **O - OBJECTIVE**: Observable clinical findings
699
+ **A - ASSESSMENT**: Clinical diagnosis/impression
700
+ **P - PLAN**: Treatment and follow-up plan
701
+
702
+ ---
703
+ *πŸ€– Powered by Google's Gemma 3N Model | πŸ”’ All processing performed locally*
704
+ """
705
+
706
+ !gradio deploy