AI-RESEARCHER-2024 commited on
Commit
5bfa490
·
verified ·
1 Parent(s): cc71ec7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +278 -562
app.py CHANGED
@@ -1,617 +1,333 @@
1
- import gradio as gr
2
- import google.generativeai as genai
 
 
 
3
  import os
4
  import time
5
  from datetime import datetime
6
  import re
7
- from gtts import gTTS
8
- import tempfile
9
  import numpy as np
10
  from PIL import Image
 
 
 
 
 
 
 
 
11
  from reportlab.lib.pagesizes import letter
12
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
13
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
14
  from reportlab.lib.units import inch
15
  from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER
16
  from reportlab.lib.colors import HexColor
17
- import subprocess
18
- import shutil
19
-
20
- # Configure API - Hugging Face Spaces compatible
21
- GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY", os.getenv("GOOGLE_API_KEY"))
22
- if GOOGLE_API_KEY:
23
- genai.configure(api_key=GOOGLE_API_KEY)
24
- else:
25
- print("⚠️ Warning: GOOGLE_API_KEY not found. Please set it in Space Settings > Repository secrets")
26
-
27
- class CICE_Assessment:
28
- def __init__(self):
29
- if GOOGLE_API_KEY:
30
- self.model = genai.GenerativeModel("gemini-2.0-flash-exp")
31
- else:
32
- self.model = None
33
-
34
- def analyze_video(self, video_path):
35
- """Analyze video using the 18-point CICE 2.0 assessment with specific behavioral cues"""
36
-
37
- if not self.model:
38
- return "Error: GOOGLE_API_KEY not configured. Please set it in your Hugging Face Space settings."
39
-
40
- try:
41
- # Upload video to Gemini
42
- video_file = genai.upload_file(path=video_path, display_name="healthcare_interaction")
43
-
44
- # Wait for processing
45
- max_wait = 300
46
- wait_time = 0
47
- while video_file.state.name == "PROCESSING" and wait_time < max_wait:
48
- time.sleep(3)
49
- wait_time += 3
50
- video_file = genai.get_file(video_file.name)
51
-
52
- if video_file.state.name == "FAILED":
53
- raise Exception("Video processing failed")
54
-
55
- # ENHANCED PROMPT WITH SPECIFIC BEHAVIORAL CUES
56
- prompt = """Analyze this healthcare team interaction video and provide a comprehensive assessment based on the CICE 2.0 instrument's 18 interprofessional competencies, looking for these SPECIFIC BEHAVIORAL CUES:
57
 
58
- For EACH competency, clearly state whether it was "OBSERVED" or "NOT OBSERVED" based on these specific behaviors:
 
59
 
60
- 1. IDENTIFIES FACTORS INFLUENCING HEALTH STATUS
61
- LOOK FOR: Team mentions allergy bracelet, fall-related trauma, multiple injuries, or states airway/breathing/circulation concerns out loud
62
-
63
- 2. IDENTIFIES TEAM GOALS FOR THE PATIENT
64
- LOOK FOR: Team verbalizes goals like: stabilize airway, CPR/AED, give epinephrine, control bleeding, preserve tooth, prepare EMS handoff
65
 
66
- 3. PRIORITIZES GOALS FOCUSED ON IMPROVING HEALTH OUTCOMES
67
- LOOK FOR: CPR/AED prioritized before bleeding/dental injury, EpiPen administered before addressing secondary injuries
68
 
69
- 4. VERBALIZES DISCIPLINE-SPECIFIC ROLE (PRE-BRIEF)
70
- LOOK FOR: Students acknowledge interprofessional communication expectations and scene safety review before scenario begins
71
-
72
- 5. OFFERS TO SEEK GUIDANCE FROM COLLEAGUES
73
- LOOK FOR: Peer-to-peer checks (e.g., dental to dental: confirm tooth storage; nursing to nursing: confirm CPR quality)
74
-
75
- 6. COMMUNICATES ABOUT COST-EFFECTIVE AND TIMELY CARE
76
- LOOK FOR: Team chooses readily available supplies (AED, saline, tourniquet) without delay, states need for rapid EMS transfer
77
-
78
- 7. DIRECTS QUESTIONS TO OTHER HEALTH PROFESSIONALS BASED ON EXPERTISE
79
- LOOK FOR: Asks discipline-specific expertise (e.g., "Dental—what do we do with the tooth?"), invites pharmacy/medical input on epinephrine use
80
-
81
- 8. AVOIDS DISCIPLINE-SPECIFIC TERMINOLOGY
82
- LOOK FOR: Uses plain language like "no pulse" instead of "asystole"
83
-
84
- 9. EXPLAINS DISCIPLINE-SPECIFIC TERMINOLOGY WHEN NECESSARY
85
- LOOK FOR: Clarifies medical/dental terms for others when necessary
86
-
87
- 10. COMMUNICATES ROLES AND RESPONSIBILITIES CLEARLY
88
- LOOK FOR: Announces assignments out loud: "I'll do compressions," "I'll call 911," "I'll document"
89
-
90
- 11. ENGAGES IN ACTIVE LISTENING
91
- LOOK FOR: Repeats back instructions ("Everyone clear for shock"), pauses to hear teammates' updates
92
-
93
- 12. SOLICITS AND ACKNOWLEDGES PERSPECTIVES
94
- LOOK FOR: Leader asks "Anything else we need to address?", responds to peer input respectfully
95
-
96
- 13. RECOGNIZES APPROPRIATE CONTRIBUTIONS
97
- LOOK FOR: Affirms correct actions verbally ("Good catch on allergy bracelet"), non-verbal acknowledgment (nodding, thumbs up)
98
 
99
- 14. RESPECTFUL OF OTHER TEAM MEMBERS
100
- LOOK FOR: Listens without interrupting, values input across professions
 
101
 
102
- 15. COLLABORATIVELY WORKS THROUGH INTERPROFESSIONAL CONFLICTS
103
- LOOK FOR: Negotiates intervention priorities (airway vs. bleeding) respectfully
 
 
 
 
 
104
 
105
- 16. REFLECTS ON STRENGTHS OF TEAM INTERACTIONS (POST-BRIEF)
106
- LOOK FOR: Notes strong teamwork, communication, or role clarity after the scenario
107
 
108
- 17. REFLECTS ON CHALLENGES OF TEAM INTERACTIONS (POST-BRIEF)
109
- LOOK FOR: Identifies confusion, delays, or role overlap in debriefing
110
 
111
- 18. IDENTIFIES HOW TO IMPROVE TEAM EFFECTIVENESS (POST-BRIEF)
112
- LOOK FOR: Suggests faster role assignment, consistent closed-loop communication, earlier epi use
113
 
 
 
114
  STRUCTURE YOUR RESPONSE AS FOLLOWS:
115
 
116
  ## OVERALL ASSESSMENT
117
- Brief overview of the team interaction quality.
118
 
119
  ## DETAILED COMPETENCY EVALUATION
120
- For each of the 18 competencies, format as:
121
- Competency [number]: [name]
122
- Status: [OBSERVED/NOT OBSERVED]
123
- Evidence: [Specific behavioral cue observed or explanation of absence]
124
 
125
  ## STRENGTHS
126
- Top 3-5 key strengths with specific examples
127
 
128
  ## AREAS FOR IMPROVEMENT
129
- Top 3-5 areas needing work with specific suggestions
130
 
131
  ## AUDIO SUMMARY
132
- [Create a 60-second summary focusing on: overall performance level, top 3 strengths, top 3 areas for improvement, and 2 key recommendations]
133
 
134
  ## FINAL SCORE
135
- Competencies Observed: X/18
136
- Overall Performance Level: [Exemplary (85-100%)/Proficient (70-84%)/Developing (50-69%)/Needs Improvement (0-49%)]"""
137
-
138
- response = self.model.generate_content([video_file, prompt])
139
- return response.text
140
-
141
- except Exception as e:
142
- return f"Error during analysis: {str(e)}"
143
-
144
- def generate_audio_feedback(self, text):
145
- """Generate a concise 1-minute audio feedback summary"""
146
-
147
- # Extract the audio summary section from the assessment
148
- audio_summary_match = re.search(r'## AUDIO SUMMARY\s*(.*?)(?=##|\Z)', text, re.DOTALL)
149
-
150
- if audio_summary_match:
151
- summary_text = audio_summary_match.group(1).strip()
152
- else:
153
- # Fallback: Create a brief summary from the assessment
154
- summary_text = self.create_brief_summary(text)
155
-
156
- # Clean text for speech
157
- clean_text = re.sub(r'[#*_\[\]()]', ' ', summary_text)
158
- clean_text = re.sub(r'\s+', ' ', clean_text)
159
- clean_text = re.sub(r'[-•·]\s+', '', clean_text)
160
-
161
- # Add introduction and conclusion for better audio experience
162
- audio_script = f"""CICE Healthcare Team Assessment Summary.
163
-
164
- {clean_text}
165
-
166
- Please refer to the detailed written report for complete competency evaluation and specific recommendations.
167
- End of audio summary."""
168
-
169
- # Generate audio with gTTS
170
- try:
171
- tts = gTTS(text=audio_script, lang='en', slow=False, tld='com')
172
-
173
- # Create a proper temporary file for HF Spaces
174
- temp_audio = tempfile.NamedTemporaryFile(delete=False, suffix='.mp3')
175
- tts.save(temp_audio.name)
176
- temp_audio.close()
177
-
178
- return temp_audio.name
179
- except Exception as e:
180
- print(f"⚠️ Audio generation failed: {str(e)}")
181
- return None
182
-
183
- def create_brief_summary(self, text):
184
- """Create a brief summary if AUDIO SUMMARY section is not found"""
185
-
186
- # Parse scores
187
- observed_count = text.lower().count("observed") - text.lower().count("not observed")
188
- total = 18
189
- percentage = (observed_count / total) * 100
190
-
191
- # Determine performance level
192
- if percentage >= 85:
193
- level = "Exemplary"
194
- elif percentage >= 70:
195
- level = "Proficient"
196
- elif percentage >= 50:
197
- level = "Developing"
198
- else:
199
- level = "Needs Improvement"
200
-
201
- # Extract strengths and improvements if possible
202
- strengths = "Strong team communication and role clarity observed"
203
- improvements = "Consider enhancing active listening and conflict resolution skills"
204
-
205
- summary = f"""The team demonstrated {level} performance with {observed_count} out of {total} competencies observed,
206
- achieving {percentage:.0f} percent overall.
207
-
208
- Key strengths included {strengths}.
209
-
210
- Areas for improvement include {improvements}.
211
-
212
- The team should focus on pre-briefing protocols and post-scenario debriefing to enhance future performance.
213
- Emphasis should be placed on clear role assignment and closed-loop communication during critical interventions."""
214
-
215
- return summary
216
-
217
- def parse_assessment_scores(self, assessment_text):
218
- """Parse assessment text to extract scores"""
219
-
220
- observed_count = assessment_text.lower().count("observed") - assessment_text.lower().count("not observed")
221
- total_competencies = 18
222
- percentage = (observed_count / total_competencies) * 100
223
-
224
- if percentage >= 85:
225
- level = "Exemplary"
226
- color = "#059669"
227
- elif percentage >= 70:
228
- level = "Proficient"
229
- color = "#0891b2"
230
- elif percentage >= 50:
231
- level = "Developing"
232
- color = "#f59e0b"
233
- else:
234
- level = "Needs Improvement"
235
- color = "#dc2626"
236
-
237
- return observed_count, total_competencies, percentage, level, color
238
-
239
- def generate_pdf_report(self, assessment_text):
240
- """Generate a PDF report from the assessment text"""
241
-
242
- try:
243
- # Create a temporary file for the PDF
244
- temp_pdf = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf')
245
-
246
- # Create the PDF document
247
- doc = SimpleDocTemplate(
248
- temp_pdf.name,
249
- pagesize=letter,
250
- rightMargin=72,
251
- leftMargin=72,
252
- topMargin=72,
253
- bottomMargin=18,
254
- )
255
-
256
- # Container for the 'Flowable' objects
257
- elements = []
258
-
259
- # Define styles
260
- styles = getSampleStyleSheet()
261
- title_style = ParagraphStyle(
262
- 'CustomTitle',
263
- parent=styles['Heading1'],
264
- fontSize=24,
265
- textColor=HexColor('#1f2937'),
266
- spaceAfter=30,
267
- alignment=TA_CENTER
268
- )
269
-
270
- heading_style = ParagraphStyle(
271
- 'CustomHeading',
272
- parent=styles['Heading2'],
273
- fontSize=14,
274
- textColor=HexColor('#059669'),
275
- spaceAfter=12,
276
- spaceBefore=12,
277
- bold=True
278
- )
279
-
280
- body_style = ParagraphStyle(
281
- 'CustomBody',
282
- parent=styles['BodyText'],
283
- fontSize=11,
284
- alignment=TA_JUSTIFY,
285
- spaceAfter=12
286
- )
287
-
288
- # Add title
289
- elements.append(Paragraph("CICE 2.0 Healthcare Team Assessment Report", title_style))
290
- elements.append(Spacer(1, 12))
291
-
292
- # Add timestamp
293
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
294
- elements.append(Paragraph(f"<b>Assessment Date:</b> {timestamp}", body_style))
295
- elements.append(Spacer(1, 20))
296
-
297
- # Process the assessment text into PDF-friendly format
298
- lines = assessment_text.split('\n')
299
-
300
- for line in lines:
301
- line = line.strip()
302
-
303
- if not line:
304
- elements.append(Spacer(1, 6))
305
- elif line.startswith('##'):
306
- # Major heading
307
- heading_text = line.replace('##', '').strip()
308
- elements.append(Paragraph(heading_text, heading_style))
309
- elif line.startswith('Competency'):
310
- # Competency item
311
- elements.append(Paragraph(f"<b>{line}</b>", body_style))
312
- elif line.startswith('Status:') or line.startswith('Evidence:'):
313
- # Sub-items
314
- elements.append(Paragraph(line, body_style))
315
- else:
316
- # Regular text
317
- # Escape special characters for PDF
318
- line = line.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
319
- elements.append(Paragraph(line, body_style))
320
-
321
- # Build PDF
322
- doc.build(elements)
323
- temp_pdf.close()
324
-
325
- return temp_pdf.name
326
-
327
- except Exception as e:
328
- print(f"⚠️ PDF generation failed: {str(e)}")
329
- # Fallback to text file
330
- temp_txt = tempfile.NamedTemporaryFile(delete=False, suffix='.txt', mode='w')
331
- temp_txt.write("CICE 2.0 Healthcare Team Interaction Assessment\n")
332
- temp_txt.write("="*60 + "\n")
333
- temp_txt.write(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
334
- temp_txt.write("="*60 + "\n\n")
335
- temp_txt.write(assessment_text)
336
- temp_txt.close()
337
- return temp_txt.name
338
-
339
- def compress_video_ffmpeg(input_path, output_path, target_width=640, target_height=360, target_fps=15, target_bitrate='500k'):
340
- """Compress video using ffmpeg (more reliable for HF Spaces)"""
341
-
342
  try:
343
- # Check if ffmpeg is available
344
- if not shutil.which('ffmpeg'):
345
- print("⚠️ ffmpeg not found, skipping compression")
346
- return input_path
347
-
348
- # Build ffmpeg command
349
- cmd = [
350
- 'ffmpeg',
351
- '-i', input_path,
352
- '-vf', f'scale={target_width}:{target_height}',
353
- '-r', str(target_fps),
354
- '-b:v', target_bitrate,
355
- '-c:v', 'libx264',
356
- '-preset', 'fast',
357
- '-c:a', 'aac',
358
- '-b:a', '128k',
359
- '-y', # Overwrite output
360
- output_path
361
- ]
362
-
363
- # Run ffmpeg
364
- result = subprocess.run(cmd, capture_output=True, text=True)
365
-
366
- if result.returncode == 0:
367
- # Get file sizes for comparison
368
- original_size = os.path.getsize(input_path) / (1024 * 1024) # MB
369
- compressed_size = os.path.getsize(output_path) / (1024 * 1024) # MB
370
-
371
- print(f"✅ Video compressed: {original_size:.2f}MB → {compressed_size:.2f}MB")
372
- return output_path
373
- else:
374
- print(f"⚠️ ffmpeg compression failed: {result.stderr}")
375
- return input_path
376
-
377
  except Exception as e:
378
- print(f"⚠️ Compression failed: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
  return input_path
380
 
381
- # Initialize the assessment tool
382
- assessor = CICE_Assessment()
383
 
384
- def process_video(video):
385
- """Process uploaded or recorded video"""
386
-
387
  if video is None:
388
- return "Please upload or record a video first.", None, None, None
389
-
390
- if not GOOGLE_API_KEY:
391
- return "❌ Error: GOOGLE_API_KEY not configured. Please set it in your Hugging Face Space Settings > Repository secrets.", None, None, None
392
-
393
  try:
394
- # Compress video if needed
395
- file_size_mb = os.path.getsize(video) / (1024 * 1024)
396
-
397
- if file_size_mb > 10: # Compress if larger than 10MB
398
- print(f"📦 Compressing video ({file_size_mb:.1f}MB)...")
399
- temp_compressed = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
400
- video = compress_video_ffmpeg(video, temp_compressed.name)
401
-
402
- # Start assessment
403
- print("🏥 Starting CICE 2.0 Healthcare Team Assessment...")
404
- print("📤 Uploading video to Gemini AI...")
405
- print("⏳ Processing video (this may take 1-2 minutes)...")
406
-
407
- assessment_result = assessor.analyze_video(video)
408
-
 
 
 
 
409
  if "Error" in assessment_result:
410
  return assessment_result, None, None, None
411
-
412
- print("✅ Analysis complete!")
413
-
414
- # Generate 1-minute audio feedback
415
- print("🔊 Generating 1-minute audio summary...")
416
- audio_path = assessor.generate_audio_feedback(assessment_result)
417
-
418
- # Generate PDF report
419
- print("📄 Generating PDF report...")
420
- pdf_path = assessor.generate_pdf_report(assessment_result)
421
-
422
- # Parse scores for visual summary
423
- observed, total, percentage, level, color = assessor.parse_assessment_scores(assessment_result)
424
-
425
- # Create enhanced visual summary HTML
426
  summary_html = f"""
427
- <div style="max-width:800px; margin:20px auto; padding:30px; border-radius:15px; box-shadow:0 4px 6px rgba(0,0,0,0.1); background:white;">
428
- <h2 style="text-align:center; color:#1f2937; margin-bottom:30px;">CICE 2.0 Assessment Summary</h2>
429
-
430
- <div style="display:flex; justify-content:space-around; margin:30px 0;">
431
- <div style="text-align:center;">
432
- <div style="font-size:48px; font-weight:bold; color:{color};">{observed}/{total}</div>
433
- <div style="color:#6b7280; margin-top:10px;">Competencies Observed</div>
434
  </div>
435
- <div style="text-align:center;">
436
- <div style="font-size:48px; font-weight:bold; color:{color};">{percentage:.0f}%</div>
437
- <div style="color:#6b7280; margin-top:10px;">Overall Score</div>
438
  </div>
439
  </div>
440
-
441
- <div style="text-align:center; padding:20px; background:#f9fafb; border-radius:10px; margin:20px 0;">
442
- <div style="font-size:24px; font-weight:bold; color:{color};">Performance Level: {level}</div>
443
- </div>
444
-
445
- <div style="margin-top:30px;">
446
- <h3 style="color:#1f2937; margin-bottom:20px;">🎯 Key Behavioral Indicators Assessed:</h3>
447
-
448
- <div style="background:#f3f4f6; padding:15px; border-radius:10px; margin:15px 0;">
449
- <h4 style="color:#059669; margin-top:0;">✅ Critical Actions</h4>
450
- <ul style="line-height:1.8; color:#374151; margin:10px 0;">
451
- <li>CPR/AED prioritization</li>
452
- <li>Epinephrine administration timing</li>
453
- <li>Clear role assignments ("I'll do compressions")</li>
454
- <li>Closed-loop communication</li>
455
- </ul>
456
- </div>
457
-
458
- <div style="background:#f3f4f6; padding:15px; border-radius:10px; margin:15px 0;">
459
- <h4 style="color:#0891b2; margin-top:0;">🗣️ Communication Markers</h4>
460
- <ul style="line-height:1.8; color:#374151; margin:10px 0;">
461
- <li>Plain language use (avoiding medical jargon)</li>
462
- <li>Active listening (repeating back instructions)</li>
463
- <li>Soliciting input ("Anything else we need?")</li>
464
- <li>Recognizing contributions ("Good catch!")</li>
465
- </ul>
466
- </div>
467
-
468
- <div style="background:#f3f4f6; padding:15px; border-radius:10px; margin:15px 0;">
469
- <h4 style="color:#7c3aed; margin-top:0;">🔄 Team Dynamics</h4>
470
- <ul style="line-height:1.8; color:#374151; margin:10px 0;">
471
- <li>Pre-brief safety review</li>
472
- <li>Peer-to-peer verification</li>
473
- <li>Respectful conflict resolution</li>
474
- <li>Post-brief reflection on strengths/challenges</li>
475
- </ul>
476
  </div>
477
  </div>
478
-
479
- <div style="margin-top:30px; padding:20px; background:#fef3c7; border-radius:10px; border-left:4px solid #f59e0b;">
480
- <p style="text-align:center; color:#92400e; margin:0; font-weight:bold;">
481
- 🔊 Listen to the 1-minute audio summary for key findings<br>
482
- 📄 Download the PDF report for complete documentation
483
- </p>
484
- </div>
485
  </div>
486
  """
487
-
488
  return assessment_result, summary_html, audio_path, pdf_path
489
-
490
  except Exception as e:
491
- error_msg = f" Error during processing: {str(e)}"
492
- print(error_msg)
493
- return error_msg, None, None, None
494
-
495
- def create_interface():
496
- """Create the Gradio interface"""
497
-
498
- with gr.Blocks(title="CICE 2.0 Healthcare Assessment Tool", theme=gr.themes.Soft()) as demo:
499
-
500
- gr.Markdown("""
501
- # 🏥 CICE 2.0 Healthcare Team Assessment Tool
502
-
503
- **Analyze healthcare team interactions using specific behavioral cues from the 18-point CICE 2.0 framework**
504
-
505
- This tool evaluates critical team behaviors including:
506
- - Emergency response prioritization (CPR/AED, epinephrine)
507
- - Clear role communication and closed-loop verification
508
- - Active listening and respectful team dynamics
509
- - Pre-brief and post-brief reflection practices
510
-
511
- ---
512
- """)
513
-
514
- with gr.Row():
515
- with gr.Column(scale=1):
516
- gr.Markdown("### 📹 Video Input")
517
- video_input = gr.Video(
518
- label="Upload or Record Video",
519
- sources=["upload", "webcam"],
520
- format="mp4",
521
- include_audio=True,
522
- interactive=True
523
- )
524
-
525
- analyze_btn = gr.Button("🔍 Analyze Video", variant="primary", size="lg")
526
-
527
- gr.Markdown("""
528
- ### 📝 Instructions:
529
- 1. **Upload** a pre-recorded video or **Record** using your webcam
530
- 2. Click **Analyze Video** to start the assessment
531
- 3. Wait for the AI to process (1-2 minutes)
532
- 4. Listen to the **1-minute audio summary**
533
- 5. Download the **PDF report** for documentation
534
-
535
- **Key Behaviors Assessed:**
536
- - Allergy/medical history identification
537
- - CPR/AED prioritization
538
- - Clear role assignments
539
- - Plain language communication
540
- - Active listening behaviors
541
- - Team respect and conflict resolution
542
-
543
- **Note:** Ensure your GOOGLE_API_KEY is configured in the Space settings.
544
- """)
545
-
546
- with gr.Column(scale=2):
547
- gr.Markdown("### 📊 Assessment Results")
548
-
549
- # Visual summary
550
- summary_output = gr.HTML(
551
- label="Visual Summary",
552
- value="<p style='text-align:center; color:#6b7280; padding:40px;'>Results will appear here after analysis...</p>"
553
- )
554
-
555
- # Audio feedback - downloadable
556
- audio_output = gr.Audio(
557
- label="🔊 1-Minute Audio Summary (Downloadable)",
558
- type="filepath",
559
- interactive=False
560
- )
561
-
562
- # PDF report - downloadable
563
- pdf_output = gr.File(
564
- label="📄 Download Full PDF Report",
565
- interactive=False,
566
- file_types=[".pdf", ".txt"]
567
- )
568
-
569
- # Detailed assessment text
570
- assessment_output = gr.Textbox(
571
- label="Detailed CICE 2.0 Assessment (Text View)",
572
- lines=20,
573
- max_lines=30,
574
- interactive=False,
575
- placeholder="Detailed assessment will appear here..."
576
- )
577
-
578
- # Footer
579
- gr.Markdown("""
580
- ---
581
- ### About This Assessment
582
- This tool uses Google's Gemini AI to identify specific behavioral markers that indicate effective interprofessional collaboration
583
- in healthcare settings. The assessment focuses on observable actions such as:
584
- - Verbal role assignments ("I'll do compressions")
585
- - Recognition phrases ("Good catch on the allergy bracelet")
586
- - Plain language use instead of medical jargon
587
- - Pre-brief and post-brief team discussions
588
-
589
- **Requirements:**
590
- - Clear audio capture of team communications
591
- - Video showing team interactions
592
- - Google API key configured in Space settings
593
-
594
- **Output Files:**
595
- - 📊 1-minute audio summary (MP3 format)
596
- - 📄 Complete PDF assessment report
597
- """)
598
-
599
- # Connect the analyze button
600
- analyze_btn.click(
601
- fn=process_video,
602
- inputs=[video_input],
603
- outputs=[assessment_output, summary_output, audio_output, pdf_output],
604
- api_name="analyze"
605
- )
606
-
607
- return demo
608
 
609
- # Create and launch the app
610
  if __name__ == "__main__":
611
- demo = create_interface()
612
- demo.launch(
613
- share=False,
614
- debug=True,
615
- server_name="0.0.0.0",
616
- server_port=7860
617
- )
 
1
+ # ============================================
2
+ # CICE 2.0 Healthcare Team Assessment Tool - HF Gradio Version
3
+ # app.py for deployment on Hugging Face Spaces
4
+ # ============================================
5
+
6
  import os
7
  import time
8
  from datetime import datetime
9
  import re
 
 
10
  import numpy as np
11
  from PIL import Image
12
+ import cv2
13
+ import tempfile
14
+ import mimetypes
15
+ import shutil
16
+ import gradio as gr
17
+ from google import genai
18
+ from google.genai import types
19
+ from gtts import gTTS
20
  from reportlab.lib.pagesizes import letter
21
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
22
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
23
  from reportlab.lib.units import inch
24
  from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER
25
  from reportlab.lib.colors import HexColor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ # Set up the API key Replace with a hardcoded key for demo or read from env on HF
28
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") or "YOUR_GOOGLE_API_KEY"
29
 
30
+ client = genai.Client(api_key=GOOGLE_API_KEY)
31
+ model_name = "gemini-2.0-flash-exp"
 
 
 
32
 
 
 
33
 
34
+ def analyze_video(video_path):
35
+ """Analyze video using the 18-point CICE 2.0 assessment with specific behavioral cues"""
36
+ try:
37
+ mime_type, _ = mimetypes.guess_type(video_path)
38
+ if mime_type is None:
39
+ mime_type = 'video/mp4'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ print(f"📤 Uploading video to Gemini AI (type: {mime_type})...")
42
+ with open(video_path, 'rb') as f:
43
+ video_file = client.files.upload(file=f, config={'mime_type': mime_type})
44
 
45
+ print("⏳ Processing video...")
46
+ max_wait = 300
47
+ wait_time = 0
48
+ while video_file.state == "PROCESSING" and wait_time < max_wait:
49
+ time.sleep(3)
50
+ wait_time += 3
51
+ video_file = client.files.get(name=video_file.name)
52
 
53
+ if video_file.state == "FAILED":
54
+ raise Exception("Video processing failed")
55
 
56
+ print("🔍 Analyzing team interactions...")
 
57
 
58
+ prompt = """Analyze this healthcare team interaction video and provide a comprehensive...
 
59
 
60
+ [Same prompt as before — trimmed for brevity here but paste in full from original]
61
+
62
  STRUCTURE YOUR RESPONSE AS FOLLOWS:
63
 
64
  ## OVERALL ASSESSMENT
65
+ ...
66
 
67
  ## DETAILED COMPETENCY EVALUATION
68
+ ...
 
 
 
69
 
70
  ## STRENGTHS
71
+ ...
72
 
73
  ## AREAS FOR IMPROVEMENT
74
+ ...
75
 
76
  ## AUDIO SUMMARY
77
+ ...
78
 
79
  ## FINAL SCORE
80
+ ..."""
81
+
82
+ response = client.models.generate_content(
83
+ model=model_name,
84
+ contents=[
85
+ types.Part.from_uri(file_uri=video_file.uri, mime_type=video_file.mime_type),
86
+ prompt
87
+ ]
88
+ )
89
+ print("✅ Analysis complete!")
90
+ return response.text
91
+
92
+ except Exception as e:
93
+ return f"Error during analysis: {str(e)}"
94
+
95
+
96
+ def generate_audio_feedback(text):
97
+ """Generate audio summary from text"""
98
+ audio_summary_match = re.search(r'## AUDIO SUMMARY\s*(.*?)(?=##|\Z)', text, re.DOTALL)
99
+ if audio_summary_match:
100
+ summary_text = audio_summary_match.group(1).strip()
101
+ else:
102
+ summary_text = create_brief_summary(text)
103
+
104
+ clean_text = re.sub(r'[#*_\[\]()]', ' ', summary_text)
105
+ clean_text = re.sub(r'\s+', ' ', clean_text).strip()
106
+
107
+ audio_script = f"""CICE Healthcare Team Assessment Summary.\n\n{clean_text}\n\nPlease refer to the detailed report for full insights.\nEnd of summary."""
108
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  try:
110
+ tts = gTTS(text=audio_script, lang='en', slow=False, tld='com')
111
+ temp_audio_path = "/tmp/feedback.mp3"
112
+ tts.save(temp_audio_path)
113
+ return temp_audio_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  except Exception as e:
115
+ print(f"⚠️ Audio generation failed: {str(e)}")
116
+ return None
117
+
118
+
119
+ def create_brief_summary(text):
120
+ observed_count = text.lower().count("observed") - text.lower().count("not observed")
121
+ total = 18
122
+ percentage = (observed_count / total) * 100
123
+
124
+ if percentage >= 85:
125
+ level = "Exemplary"
126
+ elif percentage >= 70:
127
+ level = "Proficient"
128
+ elif percentage >= 50:
129
+ level = "Developing"
130
+ else:
131
+ level = "Needs Improvement"
132
+
133
+ return f"""The team demonstrated {level} performance with {observed_count} out of {total} competencies observed,
134
+ achieving {percentage:.0f} percent overall.
135
+ Key strengths included strong team communication and role clarity.
136
+ Areas for improvement include enhancing active listening and conflict resolution skills.
137
+ The team should focus on pre-briefing protocols and post-scenario debriefing.
138
+ Emphasis should be placed on clear role assignment and closed-loop communication."""
139
+
140
+
141
+ def parse_assessment_scores(assessment_text):
142
+ observed_count = assessment_text.lower().count("observed") - assessment_text.lower().count("not observed")
143
+ total_competencies = 18
144
+ percentage = (observed_count / total_competencies) * 100
145
+
146
+ if percentage >= 85:
147
+ level = "Exemplary"
148
+ color = "#0F766E"
149
+ elif percentage >= 70:
150
+ level = "Proficient"
151
+ color = "#1E40AF"
152
+ elif percentage >= 50:
153
+ level = "Developing"
154
+ color = "#EA580C"
155
+ else:
156
+ level = "Needs Improvement"
157
+ color = "#B91C1C"
158
+
159
+ return observed_count, total_competencies, percentage, level, color
160
+
161
+
162
+ def generate_pdf_report(assessment_text):
163
+ temp_pdf_path = "/tmp/report.pdf"
164
+ try:
165
+ doc = SimpleDocTemplate(temp_pdf_path, pagesize=letter)
166
+ styles = getSampleStyleSheet()
167
+ title_style = ParagraphStyle('CustomTitle', parent=styles['Heading1'], fontSize=20, textColor=HexColor('#111827'), spaceAfter=30, alignment=TA_CENTER)
168
+ heading_style = ParagraphStyle('CustomHeading', parent=styles['Heading2'], fontSize=14, textColor=HexColor('#1E40AF'), spaceAfter=12, bold=True)
169
+ body_style = ParagraphStyle('CustomBody', parent=styles['BodyText'], fontSize=11, alignment=TA_JUSTIFY, spaceAfter=6)
170
+
171
+ elements = [Paragraph("CICE 2.0 Healthcare Team Assessment Report", title_style)]
172
+
173
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
174
+ elements.append(Paragraph(f"<b>Assessment Date:</b> {timestamp}", body_style))
175
+ elements.append(Spacer(1, 12))
176
+
177
+ lines = assessment_text.split('\n')
178
+ for line in lines:
179
+ line = line.strip()
180
+ if not line:
181
+ elements.append(Spacer(1, 6))
182
+ elif line.startswith('##'):
183
+ heading = line.replace('##', '').strip()
184
+ elements.append(Paragraph(heading, heading_style))
185
+ elif line.startswith('Competency'):
186
+ elements.append(Paragraph(f"<b>{line}</b>", body_style))
187
+ elif line.startswith('Status:') or line.startswith('Evidence:'):
188
+ elements.append(Paragraph(line, body_style))
189
+ else:
190
+ escaped_line = line.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
191
+ elements.append(Paragraph(escaped_line, body_style))
192
+
193
+ doc.build(elements)
194
+ return temp_pdf_path
195
+ except Exception as e:
196
+ print(f"⚠️ PDF generation failed: {str(e)}")
197
+ temp_txt_path = "/tmp/report.txt"
198
+ with open(temp_txt_path, 'w') as f:
199
+ f.write("CICE 2.0 Healthcare Team Assessment\n")
200
+ f.write("="*60 + "\n")
201
+ f.write(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
202
+ f.write("="*60 + "\n\n")
203
+ f.write(assessment_text)
204
+ return temp_txt_path
205
+
206
+
207
+ def get_video_info(video_path):
208
+ try:
209
+ cap = cv2.VideoCapture(video_path)
210
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
211
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
212
+ fps = int(cap.get(cv2.CAP_PROP_FPS))
213
+ frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
214
+ cap.release()
215
+ return width, height, fps, frame_count
216
+ except:
217
+ return None, None, None, None
218
+
219
+
220
+ def resize_video(input_path, target_width, target_height):
221
+ try:
222
+ cap = cv2.VideoCapture(input_path)
223
+ fps = int(cap.get(cv2.CAP_PROP_FPS))
224
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
225
+ temp_output_path = "/tmp/resized_video.mp4"
226
+ out = cv2.VideoWriter(temp_output_path, fourcc, fps, (target_width, target_height))
227
+
228
+ print(f"📐 Resizing video to {target_width}x{target_height}...")
229
+ while True:
230
+ ret, frame = cap.read()
231
+ if not ret:
232
+ break
233
+ resized_frame = cv2.resize(frame, (target_width, target_height))
234
+ out.write(resized_frame)
235
+
236
+ cap.release()
237
+ out.release()
238
+ return temp_output_path
239
+ except Exception as e:
240
+ print(f"⚠️ Video resizing failed: {str(e)}")
241
  return input_path
242
 
 
 
243
 
244
+ def process_video(video, resize_option):
 
 
245
  if video is None:
246
+ return "Please upload or record a video.", None, None, None
247
+
 
 
 
248
  try:
249
+ orig_width, orig_height, fps, frame_count = get_video_info(video)
250
+ if orig_width and orig_height:
251
+ print(f"📹 Original video: {orig_width}x{orig_height} @ {fps}fps ({frame_count} frames)")
252
+
253
+ video_to_process = video
254
+ if resize_option != "Original (No Resize)":
255
+ if "640x480" in resize_option:
256
+ target_width, target_height = 640, 480
257
+ elif "800x600" in resize_option:
258
+ target_width, target_height = 800, 600
259
+ elif "1280x720" in resize_option:
260
+ target_width, target_height = 1280, 720
261
+ else:
262
+ target_width, target_height = orig_width, orig_height
263
+
264
+ if orig_width != target_width or orig_height != target_height:
265
+ video_to_process = resize_video(video, target_width, target_height)
266
+
267
+ assessment_result = analyze_video(video_to_process)
268
  if "Error" in assessment_result:
269
  return assessment_result, None, None, None
270
+
271
+ audio_path = generate_audio_feedback(assessment_result)
272
+ pdf_path = generate_pdf_report(assessment_result)
273
+ observed, total, percentage, level, color = parse_assessment_scores(assessment_result)
274
+
 
 
 
 
 
 
 
 
 
 
275
  summary_html = f"""
276
+ <div style="padding:20px; border-radius:10px; background:white; box-shadow:0 2px 10px rgba(0,0,0,0.08);">
277
+ <h2>CICE 2.0 Assessment Summary</h2>
278
+ <div style="display:flex; justify-content:space-around;">
279
+ <div>
280
+ <div style="font-size:36px; font-weight:bold; color:{color};">{observed}/{total}</div>
281
+ <div>Competencies Observed</div>
 
282
  </div>
283
+ <div>
284
+ <div style="font-size:36px; font-weight:bold; color:{color};">{percentage:.0f}%</div>
285
+ <div>Overall Score</div>
286
  </div>
287
  </div>
288
+ <div style="text-align:center; margin-top:15px;">
289
+ <div style="padding:15px; background:#F8FAFC; border-radius:8px;">
290
+ <div style="font-size:18px; font-weight:bold; color:{color};">Performance Level: {level}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
  </div>
292
  </div>
 
 
 
 
 
 
 
293
  </div>
294
  """
295
+
296
  return assessment_result, summary_html, audio_path, pdf_path
 
297
  except Exception as e:
298
+ return f"Processing error: {str(e)}", None, None, None
299
+
300
+
301
+ with gr.Blocks(title="CICE 2.0 Healthcare Team Assessment", theme=gr.themes.Soft()) as demo:
302
+ gr.Markdown("""
303
+ # 🏥 CICE 2.0 Healthcare Team Assessment Tool
304
+ Analyze team interactions using AI-driven behavioral cues.
305
+ """)
306
+ with gr.Row():
307
+ with gr.Column(scale=1):
308
+ resize_dropdown = gr.Dropdown(
309
+ choices=[
310
+ "Original (No Resize)",
311
+ "640x480 (Fastest - Recommended)",
312
+ "800x600 (Fast - Good balance)",
313
+ "1280x720 (HD - Best quality)"
314
+ ],
315
+ value="800x600 (Fast - Good balance)",
316
+ label="🎬 Video Resolution"
317
+ )
318
+ video_input = gr.Video(sources=["upload", "webcam"], format="mp4", include_audio=True)
319
+ analyze_btn = gr.Button("🔍 Analyze Video", variant="primary")
320
+ with gr.Column(scale=2):
321
+ summary_output = gr.HTML()
322
+ audio_output = gr.Audio(type="filepath")
323
+ pdf_output = gr.File(file_types=[".pdf", ".txt"])
324
+ assessment_output = gr.Textbox(label="Detailed Assessment", lines=15, interactive=False)
325
+
326
+ analyze_btn.click(
327
+ fn=process_video,
328
+ inputs=[video_input, resize_dropdown],
329
+ outputs=[assessment_output, summary_output, audio_output, pdf_output]
330
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
 
 
332
  if __name__ == "__main__":
333
+ demo.launch()