tanisha-ms commited on
Commit
e3871c2
·
verified ·
1 Parent(s): 68838c3

Upload 5 files

Browse files

added project files of HEALIO

app.py ADDED
@@ -0,0 +1,561 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ load_dotenv()
3
+
4
+ import os
5
+ import gradio as gr
6
+
7
+ from brain_of_the_doctor import encode_image, analyze_image_with_query
8
+ from voice_of_the_patient import transcribe_with_groq
9
+ from voice_of_the_doctor import text_to_speech_with_gtts, text_to_speech_with_elevenlabs
10
+
11
+ system_prompt = """You have to act as a professional doctor, i know you are not but this is for learning purpose.
12
+ What's in this image?. Do you find anything wrong with it medically?
13
+ If you make a differential, suggest some remedies for them. Donot add any numbers or special characters in
14
+ your response. Your response should be in one long paragraph. Also always answer as if you are answering to a real person.
15
+ Donot say 'In the image I see' but say 'With what I see, I think you have ....'
16
+ Dont respond as an AI model in markdown, your answer should mimic that of an actual doctor not an AI bot,
17
+ Keep your answer concise (max 2 sentences). No preamble, start your answer right away please"""
18
+
19
+
20
+ def process_inputs(audio_filepath, image_filepath):
21
+ speech_to_text_output = ""
22
+ if audio_filepath:
23
+ try:
24
+ speech_to_text_output = transcribe_with_groq(
25
+ GROQ_API_KEY=os.environ.get("GROQ_API_KEY"),
26
+ audio_filepath=audio_filepath,
27
+ stt_model="whisper-large-v3"
28
+ )
29
+ except Exception as e:
30
+ speech_to_text_output = f"Error transcribing audio: {str(e)}"
31
+
32
+ if image_filepath:
33
+ try:
34
+ doctor_response = analyze_image_with_query(
35
+ query=system_prompt + " " + speech_to_text_output,
36
+ encoded_image=encode_image(image_filepath),
37
+ model="meta-llama/llama-4-scout-17b-16e-instruct"
38
+ )
39
+ except Exception as e:
40
+ doctor_response = f"Error analyzing image: {str(e)}"
41
+ else:
42
+ doctor_response = "No image provided for me to analyze."
43
+
44
+ output_filepath = "final.mp3"
45
+ try:
46
+ elevenlabs_key = os.environ.get("ELEVENLABS_API_KEY")
47
+ if elevenlabs_key:
48
+ text_to_speech_with_elevenlabs(input_text=doctor_response, output_filepath=output_filepath)
49
+ else:
50
+ text_to_speech_with_gtts(input_text=doctor_response, output_filepath=output_filepath)
51
+ except Exception as e:
52
+ print(f"ElevenLabs TTS failed, falling back to gTTS: {e}")
53
+ try:
54
+ text_to_speech_with_gtts(input_text=doctor_response, output_filepath=output_filepath)
55
+ except Exception as e2:
56
+ print(f"gTTS also failed: {e2}")
57
+ output_filepath = None
58
+
59
+ return speech_to_text_output, doctor_response, output_filepath
60
+
61
+
62
+
63
+ # white/grey backgrounds from every Gradio wrapper div.
64
+ # ─────────────────────────────────────────────────────────────────────────────
65
+ ANIMATED_BG = """
66
+ <style>
67
+ /* ── Step 1: force dark navy on root elements ── */
68
+ html, body {
69
+ background-color: #07111e !important;
70
+ background: #07111e !important;
71
+ margin: 0; padding: 0;
72
+ }
73
+
74
+ /* ── Step 2: make every Gradio shell transparent ── */
75
+ gradio-app,
76
+ gradio-app > div,
77
+ .gradio-container,
78
+ .gradio-container > div,
79
+ .main, .contain, .app,
80
+ #component-0, .tabs, .tabitem,
81
+ .form, .wrap, .gap, .svelte-1gfkn6j {
82
+ background: transparent !important;
83
+ background-color: transparent !important;
84
+ }
85
+
86
+ /* ── Step 3: pin canvas behind everything ── */
87
+ #med-canvas {
88
+ position: fixed;
89
+ top: 0; left: 0;
90
+ width: 100vw; height: 100vh;
91
+ z-index: 0;
92
+ pointer-events: none;
93
+ }
94
+
95
+ /* ── Step 4: lift Gradio content above canvas ── */
96
+ gradio-app {
97
+ position: relative;
98
+ z-index: 1;
99
+ }
100
+ </style>
101
+
102
+ <canvas id="med-canvas"></canvas>
103
+
104
+ <script>
105
+ (function () {
106
+ const canvas = document.getElementById('med-canvas');
107
+ const ctx = canvas.getContext('2d');
108
+
109
+ function resize() {
110
+ canvas.width = window.innerWidth;
111
+ canvas.height = window.innerHeight;
112
+ }
113
+ resize();
114
+ window.addEventListener('resize', resize);
115
+
116
+ /* ---------- Background gradient ---------- */
117
+ function drawBg() {
118
+ const g = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
119
+ g.addColorStop(0, '#07111e');
120
+ g.addColorStop(0.5, '#0c2040');
121
+ g.addColorStop(1, '#061520');
122
+ ctx.fillStyle = g;
123
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
124
+ }
125
+
126
+ /* ---------- Neural network ---------- */
127
+ const nodes = Array.from({ length: 44 }, () => ({
128
+ x: Math.random() * window.innerWidth,
129
+ y: Math.random() * window.innerHeight,
130
+ vx: (Math.random() - 0.5) * 0.28,
131
+ vy: (Math.random() - 0.5) * 0.28,
132
+ r: 1.6 + Math.random() * 2.4,
133
+ t: Math.random() * Math.PI * 2,
134
+ }));
135
+
136
+ function stepNodes() {
137
+ nodes.forEach(n => {
138
+ n.x += n.vx; n.y += n.vy; n.t += 0.015;
139
+ if (n.x < 0 || n.x > canvas.width) n.vx *= -1;
140
+ if (n.y < 0 || n.y > canvas.height) n.vy *= -1;
141
+ });
142
+ }
143
+
144
+ function drawNetwork() {
145
+ for (let i = 0; i < nodes.length; i++) {
146
+ for (let j = i + 1; j < nodes.length; j++) {
147
+ const dx = nodes[i].x - nodes[j].x;
148
+ const dy = nodes[i].y - nodes[j].y;
149
+ const d = Math.hypot(dx, dy);
150
+ if (d < 155) {
151
+ ctx.beginPath();
152
+ ctx.moveTo(nodes[i].x, nodes[i].y);
153
+ ctx.lineTo(nodes[j].x, nodes[j].y);
154
+ ctx.strokeStyle = `rgba(65,160,205,${0.10 * (1 - d / 155)})`;
155
+ ctx.lineWidth = 0.7;
156
+ ctx.stroke();
157
+ }
158
+ }
159
+ }
160
+ nodes.forEach(n => {
161
+ const a = 0.38 + 0.24 * Math.sin(n.t);
162
+ ctx.beginPath();
163
+ ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
164
+ ctx.fillStyle = `rgba(88,182,222,${a})`;
165
+ ctx.fill();
166
+ });
167
+ }
168
+
169
+ /* ---------- Floating medical crosses ---------- */
170
+ const crosses = Array.from({ length: 9 }, () => ({
171
+ x: Math.random() * window.innerWidth,
172
+ y: Math.random() * window.innerHeight,
173
+ size: 10 + Math.random() * 18,
174
+ vy: 0.14 + Math.random() * 0.18,
175
+ alpha: 0.07 + Math.random() * 0.10,
176
+ angle: Math.random() * Math.PI * 2,
177
+ da: (Math.random() - 0.5) * 0.003,
178
+ }));
179
+
180
+ function drawCrosses() {
181
+ crosses.forEach(c => {
182
+ c.y -= c.vy; c.angle += c.da;
183
+ if (c.y + c.size < 0) {
184
+ c.y = canvas.height + c.size;
185
+ c.x = Math.random() * canvas.width;
186
+ }
187
+ ctx.save();
188
+ ctx.translate(c.x, c.y);
189
+ ctx.rotate(c.angle);
190
+ ctx.strokeStyle = `rgba(100,188,226,${c.alpha})`;
191
+ ctx.lineWidth = c.size * 0.20;
192
+ ctx.lineCap = 'round';
193
+ ctx.beginPath(); ctx.moveTo(-c.size/2, 0); ctx.lineTo(c.size/2, 0); ctx.stroke();
194
+ ctx.beginPath(); ctx.moveTo(0, -c.size/2); ctx.lineTo(0, c.size/2); ctx.stroke();
195
+ ctx.restore();
196
+ });
197
+ }
198
+
199
+ /* ---------- DNA helices ---------- */
200
+ let dnaT = 0;
201
+ function drawDNA(cx) {
202
+ const amp = 26, steps = 22;
203
+ for (let i = 0; i < steps; i++) {
204
+ const y = (i / steps) * canvas.height;
205
+ const ph = dnaT + i * 0.52;
206
+ const x1 = cx + amp * Math.sin(ph);
207
+ const x2 = cx - amp * Math.sin(ph);
208
+ ctx.beginPath(); ctx.arc(x1, y, 2.1, 0, Math.PI*2);
209
+ ctx.fillStyle = 'rgba(82,178,218,0.26)'; ctx.fill();
210
+ ctx.beginPath(); ctx.arc(x2, y, 2.1, 0, Math.PI*2);
211
+ ctx.fillStyle = 'rgba(82,178,218,0.26)'; ctx.fill();
212
+ if (i % 2 === 0) {
213
+ ctx.beginPath(); ctx.moveTo(x1, y); ctx.lineTo(x2, y);
214
+ ctx.strokeStyle = 'rgba(82,178,218,0.11)'; ctx.lineWidth = 1; ctx.stroke();
215
+ }
216
+ }
217
+ dnaT += 0.009;
218
+ }
219
+
220
+ /* ---------- ECG heartbeat line ---------- */
221
+ let ecgOff = 0;
222
+ function ecgY(x) {
223
+ const t = (x + ecgOff) * 0.040;
224
+ const base = Math.sin(t * 0.60) * 5;
225
+ const mod = x % 128;
226
+ const spike = mod < 8 ? (mod < 4 ? mod * 7 : (8 - mod) * 7) - 4 : 0;
227
+ return base + spike * 2.8;
228
+ }
229
+ function drawECG() {
230
+ ecgOff += 1.0;
231
+ const y0 = canvas.height * 0.91;
232
+ ctx.beginPath();
233
+ for (let x = 0; x <= canvas.width; x++) {
234
+ const y = y0 + ecgY(x);
235
+ x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
236
+ }
237
+ ctx.strokeStyle = 'rgba(100,196,232,0.26)';
238
+ ctx.lineWidth = 1.4;
239
+ ctx.stroke();
240
+ }
241
+
242
+ /* ---------- Expanding pulse rings ---------- */
243
+ const rings = [0, 0.33, 0.66].map(p => ({ p }));
244
+ function drawRings() {
245
+ const cx = canvas.width * 0.5, cy = canvas.height * 0.44;
246
+ rings.forEach(r => {
247
+ r.p = (r.p + 0.0033) % 1;
248
+ ctx.beginPath();
249
+ ctx.arc(cx, cy, 70 + 265 * r.p, 0, Math.PI * 2);
250
+ ctx.strokeStyle = `rgba(92,188,226,${0.046 * (1 - r.p)})`;
251
+ ctx.lineWidth = 1.1;
252
+ ctx.stroke();
253
+ });
254
+ }
255
+
256
+ /* ---------- Main loop ---------- */
257
+ function loop() {
258
+ drawBg();
259
+ drawRings();
260
+ drawDNA(46);
261
+ drawDNA(canvas.width - 46);
262
+ stepNodes();
263
+ drawNetwork();
264
+ drawCrosses();
265
+ drawECG();
266
+ requestAnimationFrame(loop);
267
+ }
268
+
269
+ // Small delay lets Gradio finish mounting before we start
270
+ setTimeout(loop, 150);
271
+ })();
272
+ </script>
273
+ """
274
+
275
+ # ── CSS ───────────────────────────────────────────────────────────────────────
276
+ custom_css = """
277
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700&family=Exo+2:wght@300;400;500;600&display=swap');
278
+
279
+ :root {
280
+ --panel-bg: rgba(8, 26, 52, 0.74);
281
+ --border-soft: rgba(90, 180, 220, 0.22);
282
+ --text-main: #ddf0fa;
283
+ --text-muted: #7ab8d4;
284
+ }
285
+
286
+ /* Ensure dark base at CSS level too */
287
+ html { background: #07111e !important; }
288
+ body { background: #07111e !important; }
289
+
290
+ gradio-app, gradio-app > div,
291
+ .gradio-container, .gradio-container > div,
292
+ .main, .contain, .app, #component-0,
293
+ .tabs, .tabitem, .form, .wrap, .gap {
294
+ background: transparent !important;
295
+ background-color: transparent !important;
296
+ }
297
+
298
+ body, gradio-app {
299
+ font-family: 'Exo 2', sans-serif !important;
300
+ color: var(--text-main) !important;
301
+ }
302
+
303
+ /* Title */
304
+ h1 {
305
+ font-family: 'Orbitron', monospace !important;
306
+ font-weight: 700 !important;
307
+ font-size: clamp(1.3rem, 2.6vw, 2rem) !important;
308
+ letter-spacing: 0.10em !important;
309
+ text-align: center !important;
310
+ color: #8dd8f0 !important;
311
+ padding: 1.4rem 0 0.2rem !important;
312
+ }
313
+ h1::after {
314
+ content: '';
315
+ display: block;
316
+ margin: 0.45rem auto 0;
317
+ width: 200px; height: 1.5px;
318
+ background: linear-gradient(90deg, transparent, #5ab4dc, #4ecdc4, transparent);
319
+ border-radius: 2px;
320
+ }
321
+
322
+ /* Cards */
323
+ .block, .gr-block, .gr-box, .gr-panel, .panel {
324
+ background: var(--panel-bg) !important;
325
+ border: 1px solid var(--border-soft) !important;
326
+ border-radius: 12px !important;
327
+ backdrop-filter: blur(20px) saturate(1.4) !important;
328
+ -webkit-backdrop-filter: blur(20px) saturate(1.4) !important;
329
+ box-shadow: 0 4px 28px rgba(0,0,0,0.55) !important;
330
+ transition: border-color 0.3s !important;
331
+ }
332
+ .block:hover { border-color: rgba(90,180,220,0.42) !important; }
333
+
334
+ /* Labels */
335
+ label span, .gr-label, label {
336
+ font-family: 'Exo 2', sans-serif !important;
337
+ font-size: 0.76rem !important;
338
+ font-weight: 600 !important;
339
+ letter-spacing: 0.08em !important;
340
+ color: #7ecfe8 !important;
341
+ text-transform: uppercase !important;
342
+ }
343
+
344
+ /* Inputs */
345
+ textarea, input[type="text"] {
346
+ background: rgba(4,16,36,0.82) !important;
347
+ border: 1px solid rgba(90,180,220,0.20) !important;
348
+ border-radius: 8px !important;
349
+ color: var(--text-main) !important;
350
+ font-family: 'Exo 2', sans-serif !important;
351
+ font-size: 0.90rem !important;
352
+ transition: border-color 0.2s !important;
353
+ }
354
+ textarea:focus, input[type="text"]:focus {
355
+ border-color: rgba(90,180,220,0.50) !important;
356
+ outline: none !important;
357
+ }
358
+
359
+ /* Primary button */
360
+ button.primary, .gr-button-primary, button[variant="primary"] {
361
+ font-family: 'Orbitron', monospace !important;
362
+ font-weight: 600 !important;
363
+ font-size: 0.73rem !important;
364
+ letter-spacing: 0.09em !important;
365
+ text-transform: uppercase !important;
366
+ background: linear-gradient(135deg, #0d4a6e 0%, #1a7aab 100%) !important;
367
+ color: #ddf0fa !important;
368
+ border: 1px solid rgba(90,180,220,0.36) !important;
369
+ border-radius: 8px !important;
370
+ padding: 0.6rem 1.5rem !important;
371
+ transition: background 0.25s, transform 0.15s !important;
372
+ }
373
+ button.primary:hover {
374
+ background: linear-gradient(135deg, #115880, #2192cc) !important;
375
+ transform: translateY(-1px) !important;
376
+ }
377
+ button.secondary, .gr-button-secondary {
378
+ font-family: 'Exo 2', sans-serif !important;
379
+ font-size: 0.77rem !important;
380
+ background: rgba(90,180,220,0.07) !important;
381
+ color: #7ecfe8 !important;
382
+ border: 1px solid rgba(90,180,220,0.22) !important;
383
+ border-radius: 8px !important;
384
+ transition: background 0.2s !important;
385
+ }
386
+ button.secondary:hover { background: rgba(90,180,220,0.16) !important; }
387
+
388
+ /* Audio */
389
+ .waveform-container, audio {
390
+ background: rgba(4,16,36,0.65) !important;
391
+ border-radius: 10px !important;
392
+ border: 1px solid rgba(90,180,220,0.16) !important;
393
+ }
394
+
395
+ /* Image upload */
396
+ .image-container, .upload-container {
397
+ border: 1.5px dashed rgba(90,180,220,0.25) !important;
398
+ border-radius: 10px !important;
399
+ background: rgba(4,16,36,0.50) !important;
400
+ transition: border-color 0.3s !important;
401
+ }
402
+ .image-container:hover { border-color: rgba(90,180,220,0.50) !important; }
403
+
404
+ ::-webkit-scrollbar { width: 5px; }
405
+ ::-webkit-scrollbar-track { background: rgba(0,16,32,0.4); }
406
+ ::-webkit-scrollbar-thumb { background: #2a6e96; border-radius: 3px; }
407
+
408
+ footer { display: none !important; }
409
+ """
410
+
411
+ # ── Gradio theme ──────────────────────────────────────────────────────────────
412
+ theme = gr.themes.Base(
413
+ primary_hue=gr.themes.colors.cyan,
414
+ secondary_hue=gr.themes.colors.blue,
415
+ neutral_hue=gr.themes.colors.slate,
416
+ font=[gr.themes.GoogleFont("Exo 2"), "sans-serif"],
417
+ font_mono=[gr.themes.GoogleFont("Orbitron"), "monospace"],
418
+ ).set(
419
+ body_background_fill="transparent",
420
+ body_background_fill_dark="transparent",
421
+ block_background_fill="rgba(8,26,52,0.74)",
422
+ block_background_fill_dark="rgba(8,26,52,0.74)",
423
+ block_border_color="rgba(90,180,220,0.22)",
424
+ block_border_color_dark="rgba(90,180,220,0.22)",
425
+ block_label_text_color="#7ecfe8",
426
+ block_label_text_color_dark="#7ecfe8",
427
+ input_background_fill="rgba(4,16,36,0.82)",
428
+ input_background_fill_dark="rgba(4,16,36,0.82)",
429
+ input_border_color="rgba(90,180,220,0.20)",
430
+ input_border_color_dark="rgba(90,180,220,0.20)",
431
+ button_primary_background_fill="linear-gradient(135deg,#0d4a6e,#1a7aab)",
432
+ button_primary_background_fill_dark="linear-gradient(135deg,#0d4a6e,#1a7aab)",
433
+ button_primary_text_color="#ddf0fa",
434
+ button_primary_text_color_dark="#ddf0fa",
435
+ )
436
+
437
+ # ── UI ────────────────────────────────────────────────────────────────────────
438
+ with gr.Blocks(theme=theme, css=custom_css, title="AI Doctor — Vision & Voice") as iface:
439
+
440
+ gr.HTML(ANIMATED_BG)
441
+
442
+ gr.Markdown("""
443
+ <h1>⚕ Healio-AI Doctor Vision &amp; Voice</h1>
444
+ <p style='text-align:center;color:#7ab8d4;font-family:Exo 2,sans-serif;
445
+ font-size:0.91rem;letter-spacing:0.03em;margin-bottom:0.6rem;'>
446
+ Upload a medical image and describe your symptoms via microphone.<br>
447
+ The AI doctor will analyze and respond with a diagnosis.
448
+ </p>
449
+ """)
450
+
451
+ # Cute animated AI doctor robot in the bottom-right corner
452
+ gr.HTML("""
453
+ <style>
454
+ #ai-doc-robot {
455
+ position: fixed;
456
+ bottom: 18px;
457
+ right: 22px;
458
+ z-index: 999;
459
+ width: 80px;
460
+ cursor: pointer;
461
+ filter: drop-shadow(0 4px 12px rgba(0,180,220,0.35));
462
+ animation: robot-float 3.2s ease-in-out infinite;
463
+ }
464
+ @keyframes robot-float {
465
+ 0%,100% { transform: translateY(0px); }
466
+ 50% { transform: translateY(-10px); }
467
+ }
468
+ #ai-doc-robot:hover { animation: robot-wiggle 0.5s ease-in-out; }
469
+ @keyframes robot-wiggle {
470
+ 0%,100% { transform: rotate(0deg); }
471
+ 25% { transform: rotate(-8deg); }
472
+ 75% { transform: rotate(8deg); }
473
+ }
474
+ </style>
475
+ <svg id="ai-doc-robot" viewBox="0 0 80 100" xmlns="http://www.w3.org/2000/svg">
476
+ <!-- Antenna -->
477
+ <line x1="40" y1="8" x2="40" y2="18" stroke="#5ab4dc" stroke-width="2.5" stroke-linecap="round"/>
478
+ <circle cx="40" cy="6" r="4" fill="#4ecdc4" opacity="0.9">
479
+ <animate attributeName="opacity" values="0.9;0.4;0.9" dur="1.4s" repeatCount="indefinite"/>
480
+ <animate attributeName="r" values="4;5.5;4" dur="1.4s" repeatCount="indefinite"/>
481
+ </circle>
482
+ <!-- Head -->
483
+ <rect x="20" y="18" width="40" height="32" rx="9" fill="#0d3a58" stroke="#5ab4dc" stroke-width="1.5"/>
484
+ <!-- Eyes -->
485
+ <ellipse cx="31" cy="31" rx="6" ry="6" fill="#07111e"/>
486
+ <ellipse cx="49" cy="31" rx="6" ry="6" fill="#07111e"/>
487
+ <circle cx="31" cy="31" r="3.5" fill="#4ecdc4">
488
+ <animate attributeName="r" values="3.5;2;3.5" dur="3s" repeatCount="indefinite"/>
489
+ </circle>
490
+ <circle cx="49" cy="31" r="3.5" fill="#4ecdc4">
491
+ <animate attributeName="r" values="3.5;2;3.5" dur="3s" begin="0.3s" repeatCount="indefinite"/>
492
+ </circle>
493
+ <!-- Eye shine -->
494
+ <circle cx="33" cy="29" r="1.2" fill="white" opacity="0.7"/>
495
+ <circle cx="51" cy="29" r="1.2" fill="white" opacity="0.7"/>
496
+ <!-- Mouth / display bar -->
497
+ <rect x="27" y="41" width="26" height="5" rx="2.5" fill="#07111e" stroke="#5ab4dc" stroke-width="0.8"/>
498
+ <rect x="29" y="42.5" width="6" height="2" rx="1" fill="#4ecdc4">
499
+ <animate attributeName="width" values="6;14;6" dur="1.2s" repeatCount="indefinite"/>
500
+ </rect>
501
+ <!-- Neck -->
502
+ <rect x="36" y="50" width="8" height="6" rx="2" fill="#0a2e48"/>
503
+ <!-- Body -->
504
+ <rect x="16" y="56" width="48" height="34" rx="10" fill="#0d3a58" stroke="#5ab4dc" stroke-width="1.5"/>
505
+ <!-- Chest cross / medical -->
506
+ <rect x="37" y="63" width="6" height="14" rx="2" fill="#4ecdc4" opacity="0.85"/>
507
+ <rect x="33" y="67" width="14" height="6" rx="2" fill="#4ecdc4" opacity="0.85"/>
508
+ <!-- Side panel dots -->
509
+ <circle cx="23" cy="67" r="2.5" fill="#5ab4dc" opacity="0.6">
510
+ <animate attributeName="opacity" values="0.6;1;0.6" dur="1.8s" repeatCount="indefinite"/>
511
+ </circle>
512
+ <circle cx="23" cy="75" r="2.5" fill="#5ab4dc" opacity="0.6">
513
+ <animate attributeName="opacity" values="0.6;1;0.6" dur="1.8s" begin="0.6s" repeatCount="indefinite"/>
514
+ </circle>
515
+ <circle cx="57" cy="67" r="2.5" fill="#5ab4dc" opacity="0.6">
516
+ <animate attributeName="opacity" values="0.6;1;0.6" dur="1.8s" begin="0.3s" repeatCount="indefinite"/>
517
+ </circle>
518
+ <circle cx="57" cy="75" r="2.5" fill="#5ab4dc" opacity="0.6">
519
+ <animate attributeName="opacity" values="0.6;1;0.6" dur="1.8s" begin="0.9s" repeatCount="indefinite"/>
520
+ </circle>
521
+ <!-- Arms -->
522
+ <rect x="2" y="58" width="14" height="8" rx="4" fill="#0d3a58" stroke="#5ab4dc" stroke-width="1.2">
523
+ <animateTransform attributeName="transform" type="rotate" values="0 9 62; 15 9 62; 0 9 62" dur="3.2s" repeatCount="indefinite"/>
524
+ </rect>
525
+ <rect x="64" y="58" width="14" height="8" rx="4" fill="#0d3a58" stroke="#5ab4dc" stroke-width="1.2">
526
+ <animateTransform attributeName="transform" type="rotate" values="0 71 62; -15 71 62; 0 71 62" dur="3.2s" repeatCount="indefinite"/>
527
+ </rect>
528
+ <!-- Legs -->
529
+ <rect x="25" y="90" width="12" height="8" rx="4" fill="#0a2e48" stroke="#5ab4dc" stroke-width="1"/>
530
+ <rect x="43" y="90" width="12" height="8" rx="4" fill="#0a2e48" stroke="#5ab4dc" stroke-width="1"/>
531
+ </svg>
532
+ """)
533
+
534
+ with gr.Row():
535
+ with gr.Column(scale=1):
536
+ audio_input = gr.Audio(sources=["microphone"], type="filepath",
537
+ label="🎙 Patient Audio Input")
538
+ image_input = gr.Image(type="filepath",
539
+ label="🩻 Medical Image Upload")
540
+ with gr.Row():
541
+ submit_btn = gr.Button("🩺 Analyze", variant="primary")
542
+ clear_btn = gr.Button("🗑 Clear", variant="secondary")
543
+
544
+ with gr.Column(scale=1):
545
+ text_out = gr.Textbox(label="📝 Speech Transcription", lines=2)
546
+ doc_out = gr.Textbox(label="🩺 Doctor's Diagnosis", lines=5)
547
+ audio_out = gr.Audio(label="🔊 Doctor's Voice Response", type="filepath")
548
+
549
+ submit_btn.click(
550
+ fn=process_inputs,
551
+ inputs=[audio_input, image_input],
552
+ outputs=[text_out, doc_out, audio_out],
553
+ )
554
+
555
+ clear_btn.click(
556
+ fn=lambda: (None, None, "", "", None),
557
+ inputs=[],
558
+ outputs=[audio_input, image_input, text_out, doc_out, audio_out],
559
+ )
560
+
561
+ iface.launch(server_name="0.0.0.0",server_port=7860, allowed_paths=["."])
brain_of_the_doctor.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ load_dotenv()
3
+
4
+ import os
5
+ import base64
6
+ from groq import Groq
7
+
8
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
9
+
10
+
11
+ def encode_image(image_path):
12
+ with open(image_path, "rb") as image_file:
13
+ return base64.b64encode(image_file.read()).decode('utf-8')
14
+
15
+
16
+ def analyze_image_with_query(query, model, encoded_image):
17
+ client = Groq(api_key=GROQ_API_KEY)
18
+ messages = [
19
+ {
20
+ "role": "user",
21
+ "content": [
22
+ {
23
+ "type": "text",
24
+ "text": query
25
+ },
26
+ {
27
+ "type": "image_url",
28
+ "image_url": {
29
+ "url": f"data:image/jpeg;base64,{encoded_image}",
30
+ },
31
+ },
32
+ ],
33
+ }
34
+ ]
35
+ chat_completion = client.chat.completions.create(
36
+ messages=messages,
37
+ model=model
38
+ )
39
+ return chat_completion.choices[0].message.content
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==5.12.0
2
+ groq==0.15.0
3
+ elevenlabs==1.50.3
4
+ gtts==2.5.4
5
+ speechrecognition==3.13.0
6
+ pydub==0.25.1
7
+ pillow==11.1.0
8
+ requests==2.32.3
9
+ python-dotenv
10
+ httpx==0.28.1
voice_of_the_doctor.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ load_dotenv()
3
+
4
+ import os
5
+ import subprocess
6
+ import platform
7
+ from gtts import gTTS
8
+ from elevenlabs.client import ElevenLabs
9
+ from elevenlabs import save
10
+
11
+ ELEVENLABS_API_KEY = os.environ.get("ELEVENLABS_API_KEY")
12
+
13
+
14
+ def text_to_speech_with_gtts(input_text, output_filepath):
15
+ """Convert text to speech using gTTS and save to file."""
16
+ language = "en"
17
+ audioobj = gTTS(
18
+ text=input_text,
19
+ lang=language,
20
+ slow=False
21
+ )
22
+ audioobj.save(output_filepath)
23
+
24
+ os_name = platform.system()
25
+ try:
26
+ if os_name == "Darwin": # macOS
27
+ subprocess.run(['afplay', output_filepath])
28
+ elif os_name == "Windows": # Windows
29
+ subprocess.run(
30
+ ['ffplay', '-nodisp', '-autoexit', output_filepath],
31
+ stdout=subprocess.DEVNULL,
32
+ stderr=subprocess.DEVNULL
33
+ )
34
+ elif os_name == "Linux": # Linux
35
+ subprocess.run(['aplay', output_filepath])
36
+ else:
37
+ raise OSError("Unsupported operating system")
38
+ except Exception as e:
39
+ print(f"An error occurred while trying to play the audio: {e}")
40
+
41
+
42
+ def text_to_speech_with_elevenlabs(input_text, output_filepath):
43
+ """Convert text to speech using ElevenLabs and save to file."""
44
+ client = ElevenLabs(api_key=ELEVENLABS_API_KEY)
45
+
46
+ # generate() returns a generator; collect all chunks before saving
47
+ audio_generator = client.text_to_speech.convert(
48
+ text=input_text,
49
+ voice_id="EXAVITQu4vr4xnSDxMaL", # "Aria" voice ID
50
+ output_format="mp3_22050_32",
51
+ model_id="eleven_turbo_v2"
52
+ )
53
+
54
+ # Consume the generator and write bytes to file
55
+ with open(output_filepath, "wb") as f:
56
+ for chunk in audio_generator:
57
+ if chunk:
58
+ f.write(chunk)
59
+
60
+ os_name = platform.system()
61
+ try:
62
+ if os_name == "Darwin": # macOS
63
+ subprocess.run(['afplay', output_filepath])
64
+ elif os_name == "Windows": # Windows
65
+ subprocess.run(
66
+ ['ffplay', '-nodisp', '-autoexit', output_filepath],
67
+ stdout=subprocess.DEVNULL,
68
+ stderr=subprocess.DEVNULL
69
+ )
70
+ elif os_name == "Linux": # Linux
71
+ subprocess.run(['aplay', output_filepath])
72
+ else:
73
+ raise OSError("Unsupported operating system")
74
+ except Exception as e:
75
+ print(f"An error occurred while trying to play the audio: {e}")
voice_of_the_patient.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ load_dotenv()
3
+
4
+ import os
5
+ import logging
6
+ import speech_recognition as sr
7
+ from pydub import AudioSegment
8
+ from io import BytesIO
9
+ from groq import Groq
10
+
11
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
12
+
13
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
14
+
15
+
16
+ def record_audio(file_path, timeout=20, phrase_time_limit=None):
17
+ """Record audio from microphone and save as MP3."""
18
+ recognizer = sr.Recognizer()
19
+
20
+ try:
21
+ with sr.Microphone() as source:
22
+ logging.info("Adjusting for ambient noise...")
23
+ recognizer.adjust_for_ambient_noise(source, duration=1)
24
+ logging.info("Start speaking now...")
25
+
26
+ audio_data = recognizer.listen(source, timeout=timeout, phrase_time_limit=phrase_time_limit)
27
+ logging.info("Recording complete.")
28
+
29
+ wav_data = audio_data.get_wav_data()
30
+ audio_segment = AudioSegment.from_wav(BytesIO(wav_data))
31
+ audio_segment.export(file_path, format="mp3", bitrate="128k")
32
+
33
+ logging.info(f"Audio saved to {file_path}")
34
+
35
+ except Exception as e:
36
+ logging.error(f"An error occurred: {e}")
37
+
38
+
39
+ def transcribe_with_groq(stt_model, audio_filepath, GROQ_API_KEY):
40
+ """Transcribe audio file using Groq Whisper."""
41
+ client = Groq(api_key=GROQ_API_KEY)
42
+
43
+ with open(audio_filepath, "rb") as audio_file:
44
+ transcription = client.audio.transcriptions.create(
45
+ model=stt_model,
46
+ file=audio_file,
47
+ language="en"
48
+ )
49
+ return transcription.text