CelagenexResearch commited on
Commit
41e130d
Β·
verified Β·
1 Parent(s): 0d6b0a2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +227 -105
app.py CHANGED
@@ -29,7 +29,6 @@ clip_processor = CLIPProcessor.from_pretrained(
29
 
30
  # 2. Alternative medical analysis model (public, no gating issues)
31
  try:
32
- # Try to load a publicly available medical vision model
33
  medical_processor = AutoProcessor.from_pretrained(
34
  "microsoft/BiomedCLIP-PubMedBERT_256-vit_base_patch16_224",
35
  token=HF_TOKEN
@@ -40,13 +39,10 @@ try:
40
  ).to(device)
41
  MEDICAL_MODEL_AVAILABLE = True
42
  except:
43
- # Fallback: use CLIP for medical analysis too
44
  medical_processor = clip_processor
45
  medical_model = clip_model
46
  MEDICAL_MODEL_AVAILABLE = False
47
 
48
- # ... rest of your code remains the same
49
-
50
  # 3. Stanford Dogs & lifespans (expanded list)
51
  STANFORD_BREEDS = [
52
  "afghan hound", "african hunting dog", "airedale", "american staffordshire terrier",
@@ -121,27 +117,27 @@ BREED_LIFESPAN = {
121
  # 4. Questionnaire
122
  QUESTIONNAIRE = [
123
  {"domain": "Mobility", "questions": [
124
- "Difficulty rising from lying down?",
125
- "Hesitate before jumping up?"
126
  ]},
127
  {"domain": "Energy", "questions": [
128
- "Tire quickly on walks?",
129
- "Activity level decreased?"
130
  ]},
131
- {"domain": "Physical", "questions": [
132
- "Scratch or lick skin frequently?",
133
- "Changes in appetite or weight?"
134
  ]},
135
- {"domain": "Cognitive", "questions": [
136
- "Get lost in familiar rooms?",
137
- "Stare blankly at walls?"
138
  ]},
139
- {"domain": "Social", "questions": [
140
- "Interest in play declined?",
141
- "Avoid interaction with family?"
142
  ]}
143
  ]
144
- SCALE = ["0","1","2","3","4","5"]
145
 
146
  def predict_biological_age(img: Image.Image, breed: str) -> int:
147
  avg = BREED_LIFESPAN.get(breed.lower(), 12)
@@ -152,7 +148,6 @@ def predict_biological_age(img: Image.Image, breed: str) -> int:
152
  return int(np.argmax(probs)+1)
153
 
154
  def analyze_medical_image(img: Image.Image):
155
- # Use medical terminology for health assessment
156
  health_conditions = [
157
  "healthy normal dog",
158
  "dog with visible health issues",
@@ -190,10 +185,10 @@ def classify_breed_and_health(img: Image.Image, override=None):
190
  breed_conf = float(sims[idx])
191
 
192
  aspects = {
193
- "Coat": ("shiny healthy coat","dull patchy fur"),
194
- "Eyes": ("bright clear eyes","cloudy milky eyes"),
195
- "Body": ("ideal muscle tone","visible ribs or bones"),
196
- "Teeth":("clean white teeth","yellow stained teeth")
197
  }
198
  health = {}
199
  for name,(p,n) in aspects.items():
@@ -205,9 +200,9 @@ def classify_breed_and_health(img: Image.Image, override=None):
205
  health[name] = {"assessment":choice,"confidence":float(max(sim2))}
206
  return breed, breed_conf, health
207
 
208
- def analyze_video(video_path):
209
  if not video_path:
210
- return {"error": "No video provided"}
211
 
212
  try:
213
  cap = cv2.VideoCapture(video_path)
@@ -216,107 +211,234 @@ def analyze_video(video_path):
216
 
217
  if total == 0:
218
  cap.release()
219
- return {"error": "Invalid video file"}
 
 
 
 
220
 
221
- indices = np.linspace(0,total-1,min(10, total),dtype=int)
222
- scores=[]
223
  for i in indices:
224
  cap.set(cv2.CAP_PROP_POS_FRAMES, i)
225
- ret,frame=cap.read()
226
  if not ret:
227
  continue
228
- img=Image.fromarray(cv2.cvtColor(frame,cv2.COLOR_BGR2RGB))
229
- _,conf=analyze_medical_image(img)
230
- scores.append(conf)
 
 
 
 
 
 
 
 
 
 
231
  cap.release()
232
 
233
- if not scores:
234
- return {"error": "Could not extract frames from video"}
235
 
236
  return {
237
- "duration_sec": round(total/fps,1),
238
- "avg_gait_conf": float(np.mean(scores)),
239
- "frames_analyzed": len(scores)
 
 
240
  }
241
  except Exception as e:
242
- return {"error": f"Video analysis failed: {str(e)}"}
243
 
244
- def compute_q_score(answers):
245
- if not answers or all(a is None for a in answers):
246
- return {"error": "No answers provided"}
247
 
248
- out={}
249
- idx=0
250
- for sec in QUESTIONNAIRE:
251
- n=len(sec["questions"])
 
 
252
  try:
253
- vals=[int(a) if a is not None else 0 for a in answers[idx:idx+n]]
254
- idx+=n
255
- out[sec["domain"]]=round(sum(vals)/n,2)
 
 
 
 
 
 
 
 
256
  except (ValueError, TypeError):
257
- out[sec["domain"]] = 0.0
258
- return out
 
 
 
 
 
 
 
 
259
 
260
- with gr.Blocks(title="🐢 Dog Health & Age Analyzer") as demo:
261
- gr.Markdown("## Upload an Image or Video (10–30 s) or Record Live")
 
 
 
 
 
 
 
 
 
262
 
263
- with gr.Tab("Image Analysis"):
264
- img = gr.Image(type="pil", label="Upload Dog Image")
265
- br = gr.Textbox(label="Override Breed (Optional)")
266
- ca = gr.Number(label="Chronological Age (years)", precision=1)
267
- btn = gr.Button("Analyze Image")
268
- md = gr.Markdown()
269
-
270
- def run_i(i,b,o):
271
- if i is None:
272
- return "Please upload an image first."
 
 
 
 
 
 
 
273
 
274
- try:
275
- breed,bc,h=classify_breed_and_health(i,b)
276
- ml,mc=analyze_medical_image(i)
277
- ba=predict_biological_age(i,breed)
278
- pace = f"{ba/o:.2f}Γ—" if o and o > 0 else "N/A"
279
-
280
- rpt = f"**Breed:** {breed} ({bc:.1%})\n\n"
281
- rpt+=f"**Health Assessment:** {ml} ({mc:.1%})\n\n"
282
- rpt+=f"**Bio Age:** {ba} yrs | **Chrono:** {o or 'N/A'} yrs | **Pace:** {pace}\n\n"
283
- rpt+="### Health Aspects\n"+ "\n".join(f"- **{k}:** {v['assessment']} ({v['confidence']:.1%})" for k,v in h.items())
284
- return rpt
285
- except Exception as e:
286
- return f"Analysis failed: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
- btn.click(run_i, inputs=[img,br,ca], outputs=md)
289
-
290
- with gr.Tab("Video Analysis"):
291
- vid=gr.Video(label="Upload Video (10-30 seconds)")
292
- b2=gr.Button("Analyze Video")
293
- out2=gr.JSON()
294
- b2.click(analyze_video, inputs=vid, outputs=out2)
295
-
296
- with gr.Tab("Healthspan Questionnaire"):
297
- widgets=[]
298
- for sec in QUESTIONNAIRE:
299
- gr.Markdown(f"### {sec['domain']}")
300
- for q in sec["questions"]:
301
- w = gr.Radio(SCALE, label=q, value="0")
302
- widgets.append(w)
303
- b3=gr.Button("Compute Healthspan Score")
304
- o3=gr.JSON()
305
- b3.click(compute_q_score, inputs=widgets, outputs=o3)
 
 
 
 
 
306
 
307
- with gr.Tab("About"):
308
- gr.Markdown("""
309
- ## 🐢 Dog Health & Age Analyzer
310
-
311
- **Features:**
312
- - **Breed Classification**: Identifies dog breeds using CLIP vision-language model
313
- - **Age Estimation**: Predicts biological age based on visual appearance
314
- - **Health Assessment**: Analyzes coat, eyes, body condition, and teeth
315
- - **Video Analysis**: Evaluates gait and movement patterns
316
- - **Healthspan Questionnaire**: Research-based assessment tool
 
 
 
 
 
 
 
317
 
318
- **Note**: This tool is for educational purposes only and should not replace professional veterinary consultation.
319
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
  if __name__ == "__main__":
322
  demo.launch()
 
29
 
30
  # 2. Alternative medical analysis model (public, no gating issues)
31
  try:
 
32
  medical_processor = AutoProcessor.from_pretrained(
33
  "microsoft/BiomedCLIP-PubMedBERT_256-vit_base_patch16_224",
34
  token=HF_TOKEN
 
39
  ).to(device)
40
  MEDICAL_MODEL_AVAILABLE = True
41
  except:
 
42
  medical_processor = clip_processor
43
  medical_model = clip_model
44
  MEDICAL_MODEL_AVAILABLE = False
45
 
 
 
46
  # 3. Stanford Dogs & lifespans (expanded list)
47
  STANFORD_BREEDS = [
48
  "afghan hound", "african hunting dog", "airedale", "american staffordshire terrier",
 
117
  # 4. Questionnaire
118
  QUESTIONNAIRE = [
119
  {"domain": "Mobility", "questions": [
120
+ "Does your dog have difficulty rising from lying down?",
121
+ "Does your dog hesitate before jumping up or climbing stairs?"
122
  ]},
123
  {"domain": "Energy", "questions": [
124
+ "Does your dog tire quickly during walks or play?",
125
+ "Has your dog's overall activity level decreased recently?"
126
  ]},
127
+ {"domain": "Physical Health", "questions": [
128
+ "Does your dog scratch or lick their skin frequently?",
129
+ "Have you noticed changes in appetite or weight?"
130
  ]},
131
+ {"domain": "Cognitive Function", "questions": [
132
+ "Does your dog get lost in familiar rooms or areas?",
133
+ "Does your dog stare blankly at walls or into space?"
134
  ]},
135
+ {"domain": "Social Behavior", "questions": [
136
+ "Has your dog's interest in play or interaction declined?",
137
+ "Does your dog avoid interaction with family members?"
138
  ]}
139
  ]
140
+ SCALE = ["0 (Never)", "1 (Rarely)", "2 (Sometimes)", "3 (Often)", "4 (Frequently)", "5 (Always)"]
141
 
142
  def predict_biological_age(img: Image.Image, breed: str) -> int:
143
  avg = BREED_LIFESPAN.get(breed.lower(), 12)
 
148
  return int(np.argmax(probs)+1)
149
 
150
  def analyze_medical_image(img: Image.Image):
 
151
  health_conditions = [
152
  "healthy normal dog",
153
  "dog with visible health issues",
 
185
  breed_conf = float(sims[idx])
186
 
187
  aspects = {
188
+ "Coat Quality": ("shiny healthy coat","dull patchy fur"),
189
+ "Eye Clarity": ("bright clear eyes","cloudy milky eyes"),
190
+ "Body Condition": ("ideal muscle tone","visible ribs or bones"),
191
+ "Dental Health": ("clean white teeth","yellow stained teeth")
192
  }
193
  health = {}
194
  for name,(p,n) in aspects.items():
 
200
  health[name] = {"assessment":choice,"confidence":float(max(sim2))}
201
  return breed, breed_conf, health
202
 
203
+ def analyze_video_gait(video_path):
204
  if not video_path:
205
+ return None
206
 
207
  try:
208
  cap = cv2.VideoCapture(video_path)
 
211
 
212
  if total == 0:
213
  cap.release()
214
+ return None
215
+
216
+ indices = np.linspace(0, total-1, min(15, total), dtype=int)
217
+ health_scores = []
218
+ movement_scores = []
219
 
 
 
220
  for i in indices:
221
  cap.set(cv2.CAP_PROP_POS_FRAMES, i)
222
+ ret, frame = cap.read()
223
  if not ret:
224
  continue
225
+ img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
226
+
227
+ # Health assessment
228
+ _, health_conf = analyze_medical_image(img)
229
+ health_scores.append(health_conf)
230
+
231
+ # Movement assessment
232
+ movement_prompts = ["dog moving normally", "dog limping or showing pain", "dog moving stiffly"]
233
+ inputs = clip_processor(text=movement_prompts, images=img, return_tensors="pt", padding=True).to(device)
234
+ with torch.no_grad():
235
+ movement_logits = clip_model(**inputs).logits_per_image.softmax(-1)[0].cpu().numpy()
236
+ movement_scores.append(float(movement_logits[0])) # Normal movement score
237
+
238
  cap.release()
239
 
240
+ if not health_scores:
241
+ return None
242
 
243
  return {
244
+ "duration_sec": round(total/fps, 1),
245
+ "avg_health_confidence": float(np.mean(health_scores)),
246
+ "avg_movement_score": float(np.mean(movement_scores)),
247
+ "frames_analyzed": len(health_scores),
248
+ "gait_assessment": "Normal" if np.mean(movement_scores) > 0.5 else "Concerning"
249
  }
250
  except Exception as e:
251
+ return None
252
 
253
+ def compute_healthspan_score(answers):
254
+ if not answers or all(a is None or a == "" for a in answers):
255
+ return None
256
 
257
+ domain_scores = {}
258
+ idx = 0
259
+ total_score = 0
260
+
261
+ for section in QUESTIONNAIRE:
262
+ n = len(section["questions"])
263
  try:
264
+ vals = []
265
+ for a in answers[idx:idx+n]:
266
+ if a is None or a == "":
267
+ vals.append(0)
268
+ else:
269
+ # Extract numeric value from scale (e.g., "3 (Often)" -> 3)
270
+ vals.append(int(a.split()[0]))
271
+ idx += n
272
+ domain_avg = sum(vals) / len(vals)
273
+ domain_scores[section["domain"]] = round(domain_avg, 2)
274
+ total_score += domain_avg
275
  except (ValueError, TypeError):
276
+ domain_scores[section["domain"]] = 0.0
277
+
278
+ # Calculate overall healthspan score (0-100, lower is better)
279
+ overall_healthspan = round((total_score / len(QUESTIONNAIRE)) * 20, 1) # Convert to 0-100 scale
280
+
281
+ return {
282
+ "domain_scores": domain_scores,
283
+ "overall_healthspan_score": overall_healthspan,
284
+ "healthspan_grade": get_healthspan_grade(overall_healthspan)
285
+ }
286
 
287
+ def get_healthspan_grade(score):
288
+ if score <= 20:
289
+ return "Excellent (A)"
290
+ elif score <= 40:
291
+ return "Good (B)"
292
+ elif score <= 60:
293
+ return "Fair (C)"
294
+ elif score <= 80:
295
+ return "Poor (D)"
296
+ else:
297
+ return "Critical (F)"
298
 
299
+ def comprehensive_analysis(image, video, breed_override, chronological_age, *questionnaire_answers):
300
+ if image is None and video is None:
301
+ return "❌ **Error**: Please provide either an image or video for analysis."
302
+
303
+ # Check if questionnaire is completed
304
+ if not questionnaire_answers or all(a is None or a == "" for a in questionnaire_answers):
305
+ return "❌ **Error**: Please complete the healthspan questionnaire before analysis."
306
+
307
+ results = []
308
+ results.append("# πŸ• **Comprehensive Dog Health & Age Analysis Report**\n")
309
+
310
+ # Image Analysis
311
+ if image is not None:
312
+ try:
313
+ breed, breed_conf, health_aspects = classify_breed_and_health(image, breed_override)
314
+ medical_assessment, medical_conf = analyze_medical_image(image)
315
+ bio_age = predict_biological_age(image, breed)
316
 
317
+ results.append("## πŸ“Έ **Image Analysis**")
318
+ results.append(f"**Detected Breed**: {breed} ({breed_conf:.1%} confidence)")
319
+ results.append(f"**Medical Assessment**: {medical_assessment} ({medical_conf:.1%} confidence)")
320
+ results.append(f"**Estimated Biological Age**: {bio_age} years")
321
+
322
+ if chronological_age and chronological_age > 0:
323
+ pace = bio_age / chronological_age
324
+ results.append(f"**Chronological Age**: {chronological_age} years")
325
+ results.append(f"**Aging Pace**: {pace:.2f}Γ— {'(Accelerated)' if pace > 1.2 else '(Normal)' if pace > 0.8 else '(Slow)'}")
326
+
327
+ results.append("\n### **Physical Health Aspects**")
328
+ for aspect, data in health_aspects.items():
329
+ status = "βœ…" if "healthy" in data["assessment"].lower() or "bright" in data["assessment"].lower() or "clean" in data["assessment"].lower() or "ideal" in data["assessment"].lower() else "⚠️"
330
+ results.append(f"{status} **{aspect}**: {data['assessment']} ({data['confidence']:.1%})")
331
+
332
+ except Exception as e:
333
+ results.append(f"❌ **Image Analysis Error**: {str(e)}")
334
+
335
+ # Video Analysis
336
+ if video is not None:
337
+ video_results = analyze_video_gait(video)
338
+ if video_results:
339
+ results.append("\n## πŸŽ₯ **Video Gait Analysis**")
340
+ results.append(f"**Video Duration**: {video_results['duration_sec']} seconds")
341
+ results.append(f"**Gait Assessment**: {video_results['gait_assessment']}")
342
+ results.append(f"**Movement Quality Score**: {video_results['avg_movement_score']:.2f}/1.0")
343
+ results.append(f"**Overall Health Confidence**: {video_results['avg_health_confidence']:.1%}")
344
+ results.append(f"**Frames Analyzed**: {video_results['frames_analyzed']}")
345
+ else:
346
+ results.append("\n## πŸŽ₯ **Video Analysis**")
347
+ results.append("❌ **Video analysis failed** - Please ensure video is clear and shows the dog in motion")
348
+
349
+ # Questionnaire Analysis
350
+ healthspan_results = compute_healthspan_score(questionnaire_answers)
351
+ if healthspan_results:
352
+ results.append("\n## πŸ“‹ **Healthspan Assessment**")
353
+ results.append(f"**Overall Healthspan Score**: {healthspan_results['overall_healthspan_score']}/100")
354
+ results.append(f"**Healthspan Grade**: {healthspan_results['healthspan_grade']}")
355
 
356
+ results.append("\n### **Domain Breakdown**")
357
+ for domain, score in healthspan_results['domain_scores'].items():
358
+ status = "βœ…" if score <= 2 else "⚠️" if score <= 3.5 else "❌"
359
+ results.append(f"{status} **{domain}**: {score}/5.0")
360
+
361
+ # Final Recommendations
362
+ results.append("\n## πŸ₯ **Health Recommendations**")
363
+
364
+ if healthspan_results:
365
+ score = healthspan_results['overall_healthspan_score']
366
+ if score <= 30:
367
+ results.append("βœ… **Excellent health status** - Continue current care routine")
368
+ elif score <= 50:
369
+ results.append("⚠️ **Good health with minor concerns** - Monitor closely and consider vet check-up")
370
+ elif score <= 70:
371
+ results.append("⚠️ **Moderate health concerns** - Veterinary consultation recommended")
372
+ else:
373
+ results.append("❌ **Significant health concerns** - Immediate veterinary attention advised")
374
+
375
+ results.append("\n---")
376
+ results.append("**⚠️ Disclaimer**: This analysis is for educational purposes only. Always consult with a qualified veterinarian for professional medical advice.")
377
+
378
+ return "\n".join(results)
379
 
380
+ # Gradio Interface
381
+ with gr.Blocks(title="🐢 Comprehensive Dog Health & Age Analyzer", theme=gr.themes.Soft()) as demo:
382
+ gr.Markdown("""
383
+ # πŸ• **Comprehensive Dog Health & Age Analyzer**
384
+ ### AI-powered multi-modal analysis combining visual assessment, video gait analysis, and healthspan questionnaire
385
+ """)
386
+
387
+ with gr.Row():
388
+ with gr.Column(scale=1):
389
+ gr.Markdown("### πŸ“Έ **Visual Input** (Choose One)")
390
+ image_input = gr.Image(type="pil", label="Upload Dog Image")
391
+ gr.Markdown("**OR**")
392
+ video_input = gr.Video(label="Upload/Record Video (10-30 seconds)")
393
+
394
+ gr.Markdown("### βš™οΈ **Optional Settings**")
395
+ breed_input = gr.Textbox(label="Override Breed Detection", placeholder="e.g., Golden Retriever")
396
+ age_input = gr.Number(label="Chronological Age (years)", precision=1, value=None)
397
 
398
+ with gr.Column(scale=1):
399
+ gr.Markdown("### πŸ“‹ **Healthspan Questionnaire** (Required)")
400
+ gr.Markdown("*Rate each behavior from 0 (Never) to 5 (Always)*")
401
+
402
+ questionnaire_inputs = []
403
+ for section in QUESTIONNAIRE:
404
+ gr.Markdown(f"#### **{section['domain']}**")
405
+ for question in section["questions"]:
406
+ radio = gr.Radio(
407
+ choices=SCALE,
408
+ label=question,
409
+ value="0 (Never)",
410
+ interactive=True
411
+ )
412
+ questionnaire_inputs.append(radio)
413
+
414
+ analyze_button = gr.Button("πŸ”¬ **Analyze Dog Health & Age**", variant="primary", size="lg")
415
+
416
+ gr.Markdown("### πŸ“Š **Comprehensive Analysis Report**")
417
+ output_report = gr.Markdown()
418
+
419
+ # Connect the analysis function
420
+ analyze_button.click(
421
+ fn=comprehensive_analysis,
422
+ inputs=[image_input, video_input, breed_input, age_input] + questionnaire_inputs,
423
+ outputs=output_report
424
+ )
425
+
426
+ gr.Markdown("""
427
+ ---
428
+ ### πŸ”¬ **About This Tool**
429
+
430
+ This comprehensive analyzer uses state-of-the-art AI models to provide:
431
+
432
+ - **🎯 Breed Classification**: Identifies among 120+ Stanford dog breeds using CLIP vision-language model
433
+ - **⏰ Age Estimation**: Predicts biological age based on visual appearance and breed-specific lifespan data
434
+ - **πŸ₯ Health Assessment**: Analyzes coat, eyes, body condition, and dental health
435
+ - **πŸŽ₯ Gait Analysis**: Evaluates movement patterns and mobility from video footage
436
+ - **πŸ“Š Healthspan Scoring**: Research-based questionnaire covering 5 key health domains
437
+
438
+ **πŸ”¬ Technology**: Powered by OpenAI CLIP, Microsoft BiomedCLIP, and veterinary research-validated assessment tools.
439
+
440
+ **⚠️ Important**: This tool is for educational and monitoring purposes only. Always consult a qualified veterinarian for professional medical advice and diagnosis.
441
+ """)
442
 
443
  if __name__ == "__main__":
444
  demo.launch()