File size: 16,987 Bytes
0eb5936
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92a6999
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0eb5936
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
"""
health_module.py - Dog Health Assessment Module
Uses pose detection and heuristics for health scoring
"""

import cv2
import numpy as np
from typing import List, Dict, Optional, Tuple
from dataclasses import dataclass
from collections import deque

@dataclass
class HealthScore:
    """Health assessment result"""
    score: float  # 0-10
    score_text: str  # "8.5/10"
    color: Tuple[int, int, int]  # BGR color for visualization
    status: str  # Turkish status: Sağlıklı, İyi, Dikkat, Kritik
    alerts: List[str]  # Health alerts
    confidence: float  # 0-1 confidence in assessment

class DogHealthAssessment:
    """
    Health assessment using pose keypoints and visual analysis
    Optimized for real-time processing on T4 GPU
    """
    
    def __init__(self):
        # Store history for temporal analysis
        self.pose_history = {}  # dog_id -> deque of keypoints
        self.movement_history = {}  # dog_id -> deque of positions
        self.health_history = {}  # dog_id -> deque of scores
        
        # Thresholds
        self.thresholds = {
            'head_low_ratio': 0.7,  # Head below 70% of body = concern
            'leg_asymmetry_ratio': 0.1,  # >10% difference = limping
            'body_condition_thin': 0.35,  # Width/height ratio
            'body_condition_obese': 0.65,
            'movement_inactive': 50,  # Pixels moved
            'movement_hyperactive': 500,
            'red_area_threshold': 0.05  # >5% red = possible wound
        }
        
        # Pose keypoint indices for dogs (17 keypoints)
        self.keypoints_map = {
            'nose': 0,
            'left_eye': 1,
            'right_eye': 2,
            'left_ear': 3,
            'right_ear': 4,
            'left_shoulder': 5,
            'right_shoulder': 6,
            'left_elbow': 7,
            'right_elbow': 8,
            'left_wrist': 9,
            'right_wrist': 10,
            'left_hip': 11,
            'right_hip': 12,
            'left_knee': 13,
            'right_knee': 14,
            'left_ankle': 15,
            'right_ankle': 16
        }
    
    def assess_from_pose(self, keypoints: np.ndarray, bbox: List[float]) -> Dict:
        """
        Analyze health from pose keypoints
        Returns: dict with scores for different aspects
        """
        scores = {
            'posture': 10.0,
            'gait_symmetry': 10.0,
            'head_position': 10.0
        }
        
        if keypoints is None or len(keypoints) < 17:
            return scores  # Return default if no pose data
        
        body_height = bbox[3] - bbox[1]
        body_width = bbox[2] - bbox[0]
        
        # 1. Head Position Analysis
        nose_kp = keypoints[self.keypoints_map['nose']]
        if nose_kp[0] > 0 and nose_kp[1] > 0:  # Valid keypoint
            head_relative_y = (nose_kp[1] - bbox[1]) / body_height
            
            if head_relative_y > self.thresholds['head_low_ratio']:
                # Head is too low - sign of illness or exhaustion
                scores['head_position'] -= 4.0
            elif head_relative_y > 0.5:
                # Head slightly low
                scores['head_position'] -= 2.0
        
        # 2. Leg Symmetry (detect limping)
        # Compare front legs
        left_shoulder = keypoints[self.keypoints_map['left_shoulder']]
        right_shoulder = keypoints[self.keypoints_map['right_shoulder']]
        left_wrist = keypoints[self.keypoints_map['left_wrist']]
        right_wrist = keypoints[self.keypoints_map['right_wrist']]
        
        if all(kp[1] > 0 for kp in [left_shoulder, right_shoulder, left_wrist, right_wrist]):
            left_leg_length = abs(left_wrist[1] - left_shoulder[1])
            right_leg_length = abs(right_wrist[1] - right_shoulder[1])
            
            if left_leg_length > 0 and right_leg_length > 0:
                asymmetry = abs(left_leg_length - right_leg_length) / max(left_leg_length, right_leg_length)
                if asymmetry > self.thresholds['leg_asymmetry_ratio']:
                    scores['gait_symmetry'] -= 3.0 * (asymmetry / self.thresholds['leg_asymmetry_ratio'])
        
        # 3. Back legs symmetry
        left_hip = keypoints[self.keypoints_map['left_hip']]
        right_hip = keypoints[self.keypoints_map['right_hip']]
        left_ankle = keypoints[self.keypoints_map['left_ankle']]
        right_ankle = keypoints[self.keypoints_map['right_ankle']]
        
        if all(kp[1] > 0 for kp in [left_hip, right_hip, left_ankle, right_ankle]):
            left_back_length = abs(left_ankle[1] - left_hip[1])
            right_back_length = abs(right_ankle[1] - right_hip[1])
            
            if left_back_length > 0 and right_back_length > 0:
                back_asymmetry = abs(left_back_length - right_back_length) / max(left_back_length, right_back_length)
                if back_asymmetry > self.thresholds['leg_asymmetry_ratio']:
                    scores['gait_symmetry'] -= 3.0 * (back_asymmetry / self.thresholds['leg_asymmetry_ratio'])
        
        # 4. Posture Analysis (spine alignment)
        if nose_kp[0] > 0 and left_hip[0] > 0 and right_hip[0] > 0:
            hip_center_x = (left_hip[0] + right_hip[0]) / 2
            spine_alignment = abs(nose_kp[0] - hip_center_x) / body_width
            
            if spine_alignment > 0.3:  # Spine not straight
                scores['posture'] -= 2.0
        
        # Ensure scores don't go below 0
        for key in scores:
            scores[key] = max(0, scores[key])
        
        return scores
    
    def assess_body_condition(self, bbox: List[float], dog_crop: np.ndarray) -> Dict:
        """
        Assess body condition from appearance
        Returns: dict with body condition scores
        """
        scores = {
            'weight': 10.0,
            'coat_quality': 10.0,
            'visible_issues': 10.0
        }
        
        # 1. Body Condition Score (weight assessment)
        width = bbox[2] - bbox[0]
        height = bbox[3] - bbox[1]
        
        if height > 0:
            aspect_ratio = width / height
            
            if aspect_ratio < self.thresholds['body_condition_thin']:
                # Too thin
                scores['weight'] = 3.0
            elif aspect_ratio < 0.45:
                # Slightly thin
                scores['weight'] = 6.0
            elif aspect_ratio > self.thresholds['body_condition_obese']:
                # Obese
                scores['weight'] = 4.0
            elif aspect_ratio > 0.55:
                # Overweight
                scores['weight'] = 7.0
            # else: ideal weight, keep at 10
        
        # 2. Coat Quality Assessment
        gray = cv2.cvtColor(dog_crop, cv2.COLOR_BGR2GRAY)
        
        # Texture analysis using standard deviation
        texture_score = np.std(gray)
        
        if texture_score < 15:
            # Very poor coat quality
            scores['coat_quality'] = 3.0
        elif texture_score < 25:
            # Poor coat quality
            scores['coat_quality'] = 6.0
        elif texture_score > 50:
            # Good texture
            scores['coat_quality'] = 10.0
        
        # Edge detection for matted fur
        edges = cv2.Canny(gray, 50, 150)
        edge_density = np.sum(edges > 0) / edges.size
        
        if edge_density < 0.02:
            # Too smooth, possible hair loss
            scores['coat_quality'] = min(scores['coat_quality'], 5.0)
        
        # 3. Visible Issues (wounds, skin problems)
        hsv = cv2.cvtColor(dog_crop, cv2.COLOR_BGR2HSV)
        
        # Check for red areas (possible wounds)
        lower_red1 = np.array([0, 50, 50])
        upper_red1 = np.array([10, 255, 255])
        lower_red2 = np.array([170, 50, 50])
        upper_red2 = np.array([180, 255, 255])
        
        mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
        mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
        red_mask = mask1 | mask2
        
        red_ratio = np.sum(red_mask > 0) / red_mask.size
        
        if red_ratio > self.thresholds['red_area_threshold']:
            # Significant red areas detected
            scores['visible_issues'] = 4.0
        elif red_ratio > 0.02:
            # Some red areas
            scores['visible_issues'] = 7.0
        
        return scores
    # database_health_update.py
    """Add health assessment fields to existing database"""
    
    def add_health_fields_to_database():
        """Add health-related fields to the database"""
        import sqlite3
        from pathlib import Path
        
        db_path = "dog_monitoring.db"
        
        # Only proceed if database exists
        if not Path(db_path).exists():
            return
        
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        
        # Add health fields to dogs table
        try:
            cursor.execute("ALTER TABLE dogs ADD COLUMN last_health_score REAL DEFAULT 5.0")
        except:
            pass  # Column already exists
        
        try:
            cursor.execute("ALTER TABLE dogs ADD COLUMN health_status TEXT DEFAULT 'Unknown'")
        except:
            pass  # Column already exists
        
        # Create health assessments table if not exists
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS health_assessments (
                assessment_id INTEGER PRIMARY KEY AUTOINCREMENT,
                dog_id INTEGER,
                timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                health_score REAL,
                status TEXT,
                posture_score REAL,
                gait_score REAL,
                body_condition_score REAL,
                activity_score REAL,
                alerts TEXT,
                recommendations TEXT,
                confidence REAL,
                video_source TEXT,
                frame_number INTEGER,
                FOREIGN KEY (dog_id) REFERENCES dogs(dog_id)
            )
        """)
        
        conn.commit()
        conn.close()
    
    def assess_movement(self, dog_id: int, current_pos: Tuple[float, float]) -> float:
        """
        Assess movement patterns and activity level
        Returns: activity score (0-10)
        """
        if dog_id not in self.movement_history:
            self.movement_history[dog_id] = deque(maxlen=30)
        
        self.movement_history[dog_id].append(current_pos)
        
        if len(self.movement_history[dog_id]) < 2:
            return 7.0  # Default neutral score
        
        # Calculate total movement
        positions = list(self.movement_history[dog_id])
        total_movement = 0
        
        for i in range(1, len(positions)):
            dx = positions[i][0] - positions[i-1][0]
            dy = positions[i][1] - positions[i-1][1]
            total_movement += np.sqrt(dx**2 + dy**2)
        
        # Normalize movement score
        if total_movement < self.thresholds['movement_inactive']:
            # Very inactive - could be sick or injured
            return 4.0
        elif total_movement > self.thresholds['movement_hyperactive']:
            # Very active - healthy
            return 10.0
        else:
            # Normal activity
            return 7.0 + (total_movement / self.thresholds['movement_hyperactive']) * 3.0
    
    def calculate_overall_health(self, dog_id: int, keypoints: Optional[np.ndarray],
                                dog_crop: np.ndarray, bbox: List[float],
                                current_pos: Optional[Tuple[float, float]] = None) -> HealthScore:
        """
        Calculate comprehensive health score
        Combines pose, appearance, and movement analysis
        """
        # Get individual assessments
        pose_scores = self.assess_from_pose(keypoints, bbox) if keypoints is not None else {
            'posture': 7.0, 'gait_symmetry': 7.0, 'head_position': 7.0
        }
        
        body_scores = self.assess_body_condition(bbox, dog_crop)
        
        movement_score = self.assess_movement(dog_id, current_pos) if current_pos else 7.0
        
        # Calculate weighted average
        weights = {
            'pose': 0.35,
            'body': 0.35,
            'movement': 0.30
        }
        
        # Average pose scores
        avg_pose = np.mean(list(pose_scores.values()))
        
        # Average body condition scores
        avg_body = np.mean(list(body_scores.values()))
        
        # Final score calculation
        final_score = (
            avg_pose * weights['pose'] +
            avg_body * weights['body'] +
            movement_score * weights['movement']
        )
        
        # Round to 1 decimal
        final_score = round(final_score, 1)
        
        # Determine status and color
        if final_score >= 8.0:
            status = "Sağlıklı"
            color = (0, 255, 0)  # Green
        elif final_score >= 6.0:
            status = "İyi"
            color = (0, 255, 255)  # Yellow
        elif final_score >= 4.0:
            status = "Dikkat"
            color = (0, 165, 255)  # Orange
        else:
            status = "Kritik"
            color = (0, 0, 255)  # Red
        
        # Generate alerts based on specific issues
        alerts = []
        
        if pose_scores['head_position'] < 6.0:
            alerts.append("Baş pozisyonu düşük")
        
        if pose_scores['gait_symmetry'] < 6.0:
            alerts.append("Yürüyüş bozukluğu")
        
        if body_scores['weight'] < 4.0:
            alerts.append("Çok zayıf")
        elif body_scores['weight'] < 7.0:
            alerts.append("Kilo problemi")
        
        if body_scores['coat_quality'] < 6.0:
            alerts.append("Tüy kalitesi düşük")
        
        if body_scores['visible_issues'] < 6.0:
            alerts.append("Görünür sağlık sorunu")
        
        if movement_score < 5.0:
            alerts.append("Hareketsiz")
        
        # Calculate confidence based on available data
        confidence = 0.5  # Base confidence
        
        if keypoints is not None:
            confidence += 0.25
        
        if dog_id in self.movement_history and len(self.movement_history[dog_id]) > 10:
            confidence += 0.15
        
        if dog_crop.size > 10000:  # Good quality image
            confidence += 0.10
        
        # Store in history
        if dog_id not in self.health_history:
            self.health_history[dog_id] = deque(maxlen=50)
        self.health_history[dog_id].append(final_score)
        
        return HealthScore(
            score=final_score,
            score_text=f"{final_score}/10",
            color=color,
            status=status,
            alerts=alerts,
            confidence=min(1.0, confidence)
        )
    
    def get_health_trend(self, dog_id: int) -> str:
        """
        Get health trend over time
        Returns: trend description
        """
        if dog_id not in self.health_history or len(self.health_history[dog_id]) < 5:
            return "Yetersiz veri"
        
        scores = list(self.health_history[dog_id])
        recent_avg = np.mean(scores[-5:])
        older_avg = np.mean(scores[-10:-5]) if len(scores) >= 10 else np.mean(scores[:5])
        
        if recent_avg > older_avg + 1:
            return "İyileşiyor ↑"
        elif recent_avg < older_avg - 1:
            return "Kötüleşiyor ↓"
        else:
            return "Stabil →"
    
    def get_recommendations(self, health_score: HealthScore) -> List[str]:
        """
        Get care recommendations based on health assessment
        """
        recommendations = []
        
        if health_score.score < 4.0:
            recommendations.append("🚨 Acil veteriner kontrolü")
            recommendations.append("🍖 Yüksek kaliteli beslenme")
            recommendations.append("💊 Tıbbi tedavi gerekebilir")
        elif health_score.score < 6.0:
            recommendations.append("🏥 Veteriner muayenesi önerilir")
            recommendations.append("🥫 Düzenli beslenme programı")
            recommendations.append("🔍 Yakın takip")
        elif health_score.score < 8.0:
            recommendations.append("📋 Rutin kontrol")
            recommendations.append("🥘 Dengeli beslenme")
        else:
            recommendations.append("✅ Mevcut bakım devam etsin")
            recommendations.append("📅 Periyodik kontroller")
        
        # Add specific recommendations based on alerts
        if "Çok zayıf" in health_score.alerts:
            recommendations.append("🍖 Protein takviyesi")
        
        if "Yürüyüş bozukluğu" in health_score.alerts:
            recommendations.append("🦴 Eklem kontrolü")
        
        if "Tüy kalitesi düşük" in health_score.alerts:
            recommendations.append("🧴 Parazit kontrolü")
            
        return recommendations