""" app.py - Sokak Köpeği İzleme Sistemi Yapay zeka ile sokak köpeği takibi, sağlık değerlendirmesi ve harita görselleştirme """ import os os.environ["OMP_NUM_THREADS"] = "1" import zipfile import tempfile import gradio as gr import cv2 import numpy as np import torch from typing import Dict, List, Optional import gc import base64 from io import BytesIO from PIL import Image from pathlib import Path import json from datetime import datetime import threading from detection import DogDetector from pose_detection import DogPoseDetector from tracking import DeepSORTTracker from reid import SimplifiedReID from health_assessment import DogHealthAssessor from database import DogDatabase from map_visualization import DogMapVisualizer from config import Config class DogMonitoringApp: """Sokak köpeği izleme ve sağlık değerlendirme uygulaması""" def __init__(self): self.device = 'cuda' if torch.cuda.is_available() else 'cpu' print("="*60) print("SOKAK KÖPEĞİ İZLEME SİSTEMİ BAŞLATILIYOR") print("="*60) # Config sistemi self.config = Config() detection_cfg = self.config.get_detection_config() pose_cfg = self.config.get_pose_config() tracking_cfg = self.config.get_tracking_config() reid_cfg = self.config.get_reid_config() health_cfg = self.config.get_health_config() map_cfg = self.config.get_map_config() db_cfg = self.config.get_database_config() # Modülleri başlat print("\n1. Tespit modülü yükleniyor...") self.detector = DogDetector( confidence_threshold=detection_cfg['confidence_threshold'], device=self.device ) print("\n2. Poz algılama modülü yükleniyor...") self.pose_detector = DogPoseDetector( model_path=pose_cfg['model'], device=self.device ) print("\n3. Takip modülü yükleniyor...") self.tracker = DeepSORTTracker( max_iou_distance=tracking_cfg['max_iou_distance'], max_age=tracking_cfg['max_age'], n_init=tracking_cfg['n_init'], use_appearance=tracking_cfg['use_appearance'] ) print("\n4. ReID modülü yükleniyor...") self.reid = SimplifiedReID(device=self.device) self.reid.set_threshold(reid_cfg['threshold']) print("\n5. Sağlık değerlendirme modülü yükleniyor...") self.health_assessor = DogHealthAssessor( temporal_window=health_cfg['temporal_window'] ) print("\n6. Veritabanı modülü yükleniyor...") self.db = DogDatabase(db_cfg['path']) print("\n7. Harita görselleştirme modülü yükleniyor...") self.map_visualizer = DogMapVisualizer( default_location=map_cfg['default_location'] ) # Oturum verileri self.temp_session = {} self.current_video_path = None self.is_processing = threading.Event() self.processing_lock = threading.Lock() self.validation_data = {} print("\n" + "="*60) print("✅ SİSTEM BAŞARIYLA BAŞLATILDI") print(f" Veritabanı: {len(self.db.get_all_dogs())} köpek") print(f" Cihaz: {self.device}") print("="*60 + "\n") def process_video(self, video_path: str, reid_threshold: float, sample_rate: int, create_viz_video: bool = True): """Video işle ve geçici oturuma kaydet""" if not video_path: return None, "Lütfen bir video yükleyin", "", gr.update(visible=False), None if not self.processing_lock.acquire(blocking=False): return None, "İşlem zaten devam ediyor", "", gr.update(visible=False), None try: self.is_processing.set() self.current_video_path = video_path self.temp_session.clear() self.validation_data = {} self.reid.set_threshold(reid_threshold) self.reid.set_video_source(video_path) self.tracker.reset() self.reid.reset_session() cap = cv2.VideoCapture(video_path) if not cap.isOpened(): return None, "Video açılamıyor", "", gr.update(visible=False), None total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) fps = cap.get(cv2.CAP_PROP_FPS) or 30 frame_num = 0 processed = 0 temp_dogs = {} dataset_cfg = self.config.get_dataset_config() health_cfg = self.config.get_health_config() min_frame_gap = max(dataset_cfg['min_frame_gap'], total_frames // 45) blur_threshold = health_cfg['blur_threshold'] blur_rejected = 0 pose_failed = 0 health_assessed = 0 print(f"\n{'='*60}") print(f"VİDEO İŞLENİYOR") print(f"{'='*60}") print(f"Toplam kare: {total_frames}") print(f"FPS: {fps}") print(f"Örnekleme hızı: {sample_rate}") print(f"Min kare aralığı: {min_frame_gap}") print(f"ReID eşiği: {reid_threshold:.2f}") print(f"{'='*60}\n") while cap.isOpened() and self.is_processing.is_set(): ret, frame = cap.read() if not ret: break if frame_num % sample_rate == 0: detections = self.detector.detect(frame) tracks = self.tracker.update(detections) for track in tracks: if not self.is_processing.is_set(): break result = self.reid.match_or_register(track) temp_id = result['temp_id'] if temp_id == 0: continue if temp_id not in temp_dogs: temp_dogs[temp_id] = { 'images': [], 'keypoints_24': [], 'keypoints_8': [], 'health_scores': [], 'timestamps': [], 'confidences': [], 'bboxes': [], 'frame_numbers': [], 'last_captured_frame': -1 } if len(temp_dogs[temp_id]['images']) >= dataset_cfg['max_images_per_dog']: continue frames_since_last = frame_num - temp_dogs[temp_id]['last_captured_frame'] if frames_since_last < min_frame_gap: continue image_crop = None for det in reversed(track.detections[-3:]): if det.image_crop is not None: image_crop = det.image_crop break if image_crop is None: continue # Bulanıklık kontrolü gray = cv2.cvtColor(image_crop, cv2.COLOR_BGR2GRAY) laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var() if laplacian_var < blur_threshold: blur_rejected += 1 continue # Poz algılama keypoints_24 = self.pose_detector.detect_pose(image_crop) if keypoints_24 is None: pose_failed += 1 continue # 8 nokta iskelet keypoints_8 = self.pose_detector.extract_8_keypoints(keypoints_24) # Sağlık değerlendirmesi health_assessment = self.health_assessor.assess_health(keypoints_24) if health_assessment is not None: health_assessed += 1 # Kaydet temp_dogs[temp_id]['images'].append(image_crop.copy()) temp_dogs[temp_id]['keypoints_24'].append(keypoints_24) temp_dogs[temp_id]['keypoints_8'].append(keypoints_8) temp_dogs[temp_id]['health_scores'].append(health_assessment) temp_dogs[temp_id]['timestamps'].append(frame_num / fps) det = track.detections[-1] temp_dogs[temp_id]['confidences'].append(det.confidence) temp_dogs[temp_id]['bboxes'].append(det.bbox) temp_dogs[temp_id]['frame_numbers'].append(frame_num) temp_dogs[temp_id]['last_captured_frame'] = frame_num processed += 1 frame_num += 1 if frame_num % 30 == 0: progress = int((frame_num / total_frames) * 100) print(f"İlerleme: {progress}% ({frame_num}/{total_frames} kare)") cap.release() # Temizlik for temp_id in list(temp_dogs.keys()): if 'last_captured_frame' in temp_dogs[temp_id]: del temp_dogs[temp_id]['last_captured_frame'] # Filtreleme original_count = len(temp_dogs) discarded_ids = [] for temp_id in list(temp_dogs.keys()): if len(temp_dogs[temp_id]['images']) < dataset_cfg['min_images_per_dog']: discarded_ids.append(temp_id) del temp_dogs[temp_id] discarded_count = len(discarded_ids) self.temp_session = temp_dogs # Checkbox durumları başlat for temp_id in temp_dogs.keys(): self.validation_data[temp_id] = [True] * len(temp_dogs[temp_id]['images']) # Özet summary = f"✅ İşlem tamamlandı!\n\n" summary += f"📊 Tespit özeti:\n" summary += f" Başlangıçta tespit: {original_count} köpek\n" if discarded_count > 0: summary += f" İptal edildi (< {dataset_cfg['min_images_per_dog']} resim): {discarded_count} köpek\n" summary += f" ID'ler: {discarded_ids}\n" summary += f" Tutuldu ({dataset_cfg['min_images_per_dog']}+ resim): {len(temp_dogs)} köpek\n\n" summary += f"📈 İşlem istatistikleri:\n" summary += f" İşlenen kare: {processed}\n" summary += f" Kare aralığı: {min_frame_gap} minimum\n" summary += f" Bulanık reddedilen: {blur_rejected}\n" summary += f" Poz algılama başarısız: {pose_failed}\n" summary += f" Sağlık değerlendirmesi: {health_assessed}\n\n" if len(temp_dogs) == 0: summary += "❌ Hiçbir köpek minimum resim gereksinimini karşılamadı.\n" summary += "ReID eşiğini ayarlamayı veya daha uzun video kullanmayı deneyin." show_validation = False else: summary += "✅ Sonuçlar GEÇİCİ oturumda saklandı\n" summary += "Resimleri incelemek ve kaydetmek için Sekme 2'ye gidin" show_validation = True gallery_html = self._create_temp_gallery() # Görselleştirme videosu oluştur viz_video_path = None if create_viz_video and len(temp_dogs) > 0: print("\n" + "="*60) print("GÖRSELLEŞTİRME VİDEOSU OLUŞTURULUYOR") print("="*60) viz_video_path = self._create_visualization_video(video_path, sample_rate) if viz_video_path: summary += f"\n\n🎬 Görselleştirme videosu oluşturuldu!" print(f"✅ Görselleştirme kaydedildi: {viz_video_path}") print("="*60 + "\n") gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() status = "Doğrulama için hazır" if len(temp_dogs) > 0 else "Geçerli köpek yok" return ( gallery_html, summary, status, gr.update(visible=show_validation), viz_video_path ) except Exception as e: import traceback error = f"❌ Hata: {str(e)}\n\n{traceback.format_exc()}" print(f"\n{'='*60}") print("İŞLEM SIRASINDA HATA") print("="*60) print(error) print("="*60 + "\n") return None, error, "", gr.update(visible=False), None finally: self.is_processing.clear() self.processing_lock.release() def _create_visualization_video(self, video_path: str, sample_rate: int) -> Optional[str]: """Robust visualization: Detects and tracks LIVE to ensure perfect sync""" try: cap = cv2.VideoCapture(video_path) if not cap.isOpened(): print("Error: Cannot open video") return None fps = cap.get(cv2.CAP_PROP_FPS) or 30 width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # Use absolute path for output to avoid Gradio finding issues output_path = os.path.abspath(f"viz_{datetime.now().strftime('%H%M%S')}.mp4") # Try avc1 (H.264) for browser compatibility, fallback to mp4v fourcc = cv2.VideoWriter_fourcc(*'avc1') out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) if not out.isOpened(): fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) # Create a FRESH tracker for visualization to avoid state conflicts viz_tracker = DeepSORTTracker( max_iou_distance=0.7, max_age=30, n_init=1, # Lower init to show tracks immediately use_appearance=False # Faster for viz ) frame_num = 0 colors = [ (0, 255, 0), (255, 0, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255), (0, 255, 255), (128, 0, 128), (255, 128, 0) ] print(f"Generating visualization video (Live Mode)...") while cap.isOpened(): ret, frame = cap.read() if not ret: break # Only process frames according to sample rate if frame_num % sample_rate == 0: # 1. Detect LIVE detections = self.detector.detect(frame) # 2. Track LIVE tracks = viz_tracker.update(detections) # 3. Draw LIVE for idx, track in enumerate(tracks): # Get ReID temp_id result = self.reid.match_or_register(track) temp_id = result['temp_id'] color = colors[temp_id % len(colors)] # ← Use temp_id for color # Draw BBox x1, y1, x2, y2 = map(int, track.bbox) cv2.rectangle(frame, (x1, y1), (x2, y2), color, 6) # Draw ID cv2.putText(frame, f"ID: {temp_id}", (x1, y1-10), # ← Use temp_id for label cv2.FONT_HERSHEY_SIMPLEX, 1.5, color, 5) # Draw Skeleton # Find the detection corresponding to this track detection = None # Search recent detections for the one matching this track for det in reversed(track.detections[-5:]): if det.image_crop is not None: detection = det break if detection and detection.image_crop is not None: # Re-run pose on the specific crop to ensure alignment keypoints_24 = self.pose_detector.detect_pose(detection.image_crop) if keypoints_24 is not None: keypoints_8 = self.pose_detector.extract_8_keypoints(keypoints_24) # Scale back to full frame keypoints_8_scaled = keypoints_8.copy() keypoints_8_scaled[:, 0] += x1 keypoints_8_scaled[:, 1] += y1 self.pose_detector.visualize_8_keypoints( frame, keypoints_8_scaled, draw_skeleton=True, draw_points=True ) out.write(frame) frame_num += 1 if frame_num % 30 == 0: print(f"Viz Progress: {frame_num}/{total_frames}") cap.release() out.release() if os.path.exists(output_path) and os.path.getsize(output_path) > 1000: return output_path return None except Exception as e: print(f"Visualization Error: {e}") import traceback traceback.print_exc() return None def stop_processing(self): """Video işlemeyi durdur""" if self.is_processing.is_set(): self.is_processing.clear() return "İşlem kullanıcı tarafından durduruldu", "Durduruldu", None else: return "Durduralacak işlem yok", "İşlem yapılmıyor", None def clear_reset(self): """Tüm geçici verileri temizle ve UI'yi sıfırla""" self.temp_session.clear() self.tracker.reset() self.reid.reset_session() self.current_video_path = None self.validation_data = {} gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() return ( None, # video_display "

Oturum temizlendi. Başlamak için yeni bir video yükleyin.

", # gallery "", # summary "", # status gr.update(visible=False) # validation container ) def discard_session(self): """Geçici oturumu tamamen iptal et""" count = len(self.temp_session) self.temp_session.clear() self.tracker.reset() self.reid.reset_session() self.validation_data = {} gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() return ( gr.update(visible=False), # validation container f"{count} geçici köpek iptal edildi. Farklı bir eşik deneyin.", # summary gr.update(visible=False) # database display ) def _create_temp_gallery(self) -> str: """Geçici oturumdan galeri oluştur""" if not self.temp_session: return "

Henüz köpek tespit edilmedi

" html = "
" html += "

GEÇİCİ OTURUM - Henüz Kaydedilmedi

" html += f"

Tespit edilen köpekler: {len(self.temp_session)}

" for temp_id, data in sorted(self.temp_session.items()): num_images = len(data['images']) avg_conf = np.mean(data['confidences']) if data['confidences'] else 0 # Sağlık durumu valid_health = [h for h in data['health_scores'] if h is not None] if valid_health: avg_health = np.mean([h['overall'] for h in valid_health]) health_status = valid_health[-1]['status'] status_colors = { 'Healthy': '#51cf66', 'Sağlıklı': '#51cf66', 'Monitor': '#ffd43b', 'İzlenmeli': '#ffd43b', 'Concern': '#ff6b6b', 'Risk altında': '#ff6b6b' } status_color = status_colors.get(health_status, '#868e96') # Türkçe durum status_turkish = { 'Healthy': 'Sağlıklı', 'Monitor': 'İzlenmeli', 'Concern': 'Risk altında' }.get(health_status, health_status) else: avg_health = None status_turkish = "Bilinmiyor" status_color = '#868e96' html += f"""

Geçici Köpek #{temp_id}

""" # İlk 12 resmi göster for idx, img in enumerate(data['images'][:12]): img_base64 = self._image_to_base64(img) html += f"""
#{idx+1}
""" html += f"""

Toplam resim: {num_images}

Ortalama güven: {avg_conf:.2f}

""" if avg_health is not None: html += f"

Sağlık puanı: {avg_health:.1f}/10

" html += f"

Durum: {status_turkish}

" html += """
""" html += "
" return html def _image_to_base64(self, image: np.ndarray) -> str: """Resmi base64 string'e çevir""" _, buffer = cv2.imencode('.jpg', image, [cv2.IMWRITE_JPEG_QUALITY, 85]) img_base64 = base64.b64encode(buffer).decode('utf-8') return img_base64 def save_validated_to_database(self, *checkbox_states): """Doğrulanmış resimleri kalıcı veritabanına kaydet""" if not self.temp_session: return "Kaydedilecek geçici oturum yok", gr.update() try: saved_count = 0 updated_count = 0 total_images_saved = 0 print(f"\n{'='*60}") print("VERİTABANINA KAYDEDİLİYOR") print(f"{'='*60}\n") checkbox_idx = 0 for temp_id in sorted(self.temp_session.keys()): dog_data = self.temp_session[temp_id] num_images = len(dog_data['images']) # Bu köpek için seçili resimleri bul selected_indices = [] for i in range(num_images): if checkbox_idx < len(checkbox_states) and checkbox_states[checkbox_idx]: selected_indices.append(i) checkbox_idx += 1 if not selected_indices: print(f" Köpek #{temp_id} atlandı: Resim seçilmedi") continue # Seçili verileri al images = [dog_data['images'][i] for i in selected_indices] keypoints_24 = [dog_data['keypoints_24'][i] for i in selected_indices] keypoints_8 = [dog_data['keypoints_8'][i] for i in selected_indices] health_scores = [dog_data['health_scores'][i] for i in selected_indices] confidences = [dog_data['confidences'][i] for i in selected_indices] frame_numbers = [dog_data['frame_numbers'][i] for i in selected_indices] # Ortalama özellikler avg_features = self.reid.get_temp_id_features(temp_id) if avg_features is None: print(f" Köpek #{temp_id} atlandı: Özellikler yok") continue # Kalıcı veritabanı ile eşleştir match_result = self.reid.compare_with_permanent_database(temp_id, self.db) if match_result and match_result['matched']: # Mevcut köpeği güncelle dog_id = match_result['dog_id'] confidence = match_result['confidence'] print(f" Köpek #{temp_id} mevcut köpek #{dog_id} ile eşleşti (güven: {confidence:.2f})") self.db.update_dog_sighting( dog_id=dog_id, new_images=images[:5], new_features=avg_features, health_score=health_scores[-1] if health_scores else None ) updated_count += 1 total_images_saved += len(images[:5]) else: # Yeni köpek oluştur print(f" Köpek #{temp_id} için yeni kayıt oluşturuluyor") dog_name = f"Kopek_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{temp_id}" dog_id = self.db.save_dog( name=dog_name, images=images, features=avg_features, keypoints_24=keypoints_24, keypoints_8=keypoints_8, health_scores=health_scores, video_source=self.current_video_path or "bilinmiyor", frame_numbers=frame_numbers, confidences=confidences ) saved_count += 1 total_images_saved += len(images) # Geçici oturumu temizle self.temp_session.clear() self.validation_data = {} print(f"\n{'='*60}") print(f"KAYIT TAMAMLANDI") print(f" Yeni köpekler: {saved_count}") print(f" Güncellenen köpekler: {updated_count}") print(f" Toplam resim: {total_images_saved}") print(f"{'='*60}\n") db_html = self._show_database() summary = f"✅ Kalıcı veritabanına başarıyla kaydedildi!\n\n" summary += f"📊 Özet:\n" summary += f" Oluşturulan yeni köpekler: {saved_count}\n" summary += f" Güncellenen mevcut köpekler: {updated_count}\n" summary += f" Kaydedilen toplam resim: {total_images_saved}\n" summary += f" İşlenen toplam: {saved_count + updated_count}\n\n" summary += f"💾 Veritabanında şimdi {len(self.db.get_all_dogs())} köpek var" gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() return summary, db_html except Exception as e: import traceback error = f"❌ Kayıt hatası: {str(e)}\n\n{traceback.format_exc()}" print(error) return error, gr.update() def _show_database(self) -> str: """Veritabanı içeriğini göster""" dogs_df = self.db.get_all_dogs() if dogs_df.empty: return "

Veritabanında henüz köpek yok

" html = "
" html += f"

Kalıcı Veritabanı ({len(dogs_df)} köpek)

" for _, dog in dogs_df.iterrows(): dog_id = dog['dog_id'] images = self.db.get_dog_images(dog_id, limit=6) # Sağlık durumu rengi status_colors = { 'Healthy': '#51cf66', 'Sağlıklı': '#51cf66', 'Monitor': '#ffd43b', 'İzlenmeli': '#ffd43b', 'Concern': '#ff6b6b', 'Risk altında': '#ff6b6b' } last_status = dog.get('last_health_status', '') status_color = status_colors.get(last_status, '#868e96') # Türkçe durum status_turkish = { 'Healthy': 'Sağlıklı', 'Monitor': 'İzlenmeli', 'Concern': 'Risk altında' }.get(last_status, last_status) if last_status else 'Bilinmiyor' html += f"""

{dog['name']} (ID: {dog_id})

""" for img in images: img_base64 = self._image_to_base64(img) html += f""" """ html += f"""

İlk görülme: {dog['first_seen']}

Son görülme: {dog['last_seen']}

Toplam görüntülenme: {dog['total_sightings']}

""" if dog['avg_health_score'] is not None: html += f"

Ortalama sağlık puanı: {dog['avg_health_score']:.1f}/10

" if last_status: html += f"

Sağlık durumu: {status_turkish}

" html += """
""" html += "
" return html def show_map(self): """Harita görselleştirmesi göster""" dogs_data = self.db.get_dogs_for_map() map_html = self.map_visualizer.create_map(dogs_data) return map_html def export_to_csv(self): """Veritabanını CSV olarak dışa aktar""" try: dogs_df = self.db.get_all_dogs() if dogs_df.empty: return "Veritabanında dışa aktarılacak köpek yok", None export_path = self.db.export_to_csv("kopekler_disa_aktarim.csv") return f"✅ {len(dogs_df)} köpek CSV'ye aktarıldı", export_path except Exception as e: return f"❌ Dışa aktarma başarısız: {str(e)}", None def export_dataset_zip(self): """Veri setini ZIP olarak indir""" try: dogs_df = self.db.get_all_dogs() if dogs_df.empty: return "Veritabanında dışa aktarılacak köpek yok", None zip_buffer = BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf: total_images = 0 export_info = [] for _, dog in dogs_df.iterrows(): dog_id = dog['dog_id'] dog_name = dog['name'] or f"kopek_{dog_id}" safe_name = "".join(c if c.isalnum() or c in ('_', '-') else '_' for c in dog_name) images = self.db.get_dog_images(dog_id, limit=None) if not images: continue for idx, img in enumerate(images): img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) pil_image = Image.fromarray(img_rgb) img_buffer = BytesIO() pil_image.save(img_buffer, format='JPEG', quality=95) img_bytes = img_buffer.getvalue() filename = f"egitim_veri_seti/{safe_name}/resim_{idx+1:04d}.jpg" zipf.writestr(filename, img_bytes) total_images += 1 export_info.append({ 'dog_id': int(dog_id), 'name': dog_name, 'image_count': len(images) }) print(f"{dog_name} için {len(images)} resim aktarıldı") # Metadata metadata = { 'export_date': datetime.now().isoformat(), 'total_dogs': len(dogs_df), 'total_images': total_images, 'dogs': export_info } zipf.writestr('egitim_veri_seti/metadata.json', json.dumps(metadata, indent=2, ensure_ascii=False)) zip_buffer.seek(0) temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip', prefix='kopek_veri_seti_') temp_file.write(zip_buffer.getvalue()) temp_file.close() summary = f"✅ Veri seti başarıyla dışa aktarıldı!\n\n" summary += f"📦 Toplam köpek: {len(dogs_df)}\n" summary += f"🖼️ Toplam resim: {total_images}\n\n" summary += "Bilgisayarınıza kaydetmek için aşağıdaki indirme düğmesine tıklayın." return summary, temp_file.name except Exception as e: import traceback error = f"❌ Dışa aktarma hatası: {str(e)}\n\n{traceback.format_exc()}" return error, None def get_statistics(self): """Veritabanı istatistiklerini göster""" stats = self.db.get_statistics() stats_text = f""" 📊 Veritabanı İstatistikleri 🐕 Toplam köpek: {stats['total_dogs']} 📸 Toplam resim: {stats['total_images']} 🏥 Sağlık değerlendirmeleri: {stats['total_assessments']} """ if stats['average_health_score']: stats_text += f"💚 Ortalama sağlık puanı: {stats['average_health_score']:.1f}/10" return stats_text def create_gradio_interface(): """Gradio arayüzü oluştur""" app = DogMonitoringApp() with gr.Blocks(title="Sokak Köpeği İzleme Sistemi", theme=gr.themes.Soft()) as demo: # HEADER - Turkish 4-stage explanation gr.Markdown(""" ### 🐕 Yapay zeka ile sokak köpeği izlemesi uygulaması ne yapiyor? ✅ Sokak köpekleri görsellerinden her köpeğe özel kimlik bilgisi tanımlıyor ✅ Vücut yapısı ve hareketlerinden genel sağlık durumu bilgisi oluşturuyor ✅ Harita üzerinde köpek konumları ve hareketlerinin izlendiği coğrafi bilgi sistemi oluşturuyor ✅ Yapay zekânın modelinin doğruluğunu artırılması ve eğitimi için veri topluyor. --- """) with gr.Tabs(): # TAB 1: Video yükle ve analiz et with gr.Tab("1️⃣ Video yükle ve analiz et"): gr.Markdown("### Video yükleyin ve köpekleri tespit edin") with gr.Row(): with gr.Column(): # SINGLE video player - "flips" from upload to visualization video_display = gr.Video( label="📹 Video yükle veya sonuçları izle", sources=["upload"], autoplay=True, loop=True ) with gr.Row(): reid_threshold = gr.Slider( minimum=0.1, maximum=0.9, value=0.40, step=0.05, label="🎯 Köpek tanıma hassasiyeti", info="Düşük = daha katı eşleştirme" ) sample_rate = gr.Slider( minimum=1, maximum=5, value=1, step=1, label="⏱️ Saniyede kaç kare işlensin?", info="Her N. kareyi işle" ) create_viz = gr.Checkbox( value=True, label="Görselleştirme videosu oluştur", info="Takip ve poz overlay göster" ) # THREE BUTTONS in a row with gr.Row(): process_btn = gr.Button("⚙️ Analiz et", variant="primary", size="lg") stop_btn = gr.Button("🛑 İşlemi durdur", variant="stop", size="lg") restart_btn = gr.Button("🔄 Yeniden başla", variant="secondary", size="lg") status_text = gr.Textbox(label="⏳ İşlem durumu", lines=1, interactive=False) summary_text = gr.Textbox( label="📊 Detaylı bilgi", lines=12, interactive=False ) with gr.Column(): gallery_output = gr.HTML(label="🐕 Tespit edilen köpekler") # Discard button discard_btn = gr.Button( "↩️ Farklı ayarlarla tekrar dene", variant="secondary", visible=True ) validation_container = gr.Column(visible=False) # TAB 2: Fotoğrafları incele ve kaydet with gr.Tab("2️⃣ Fotoğrafları incele ve kaydet"): gr.Markdown("### Tespit edilen köpeklerin fotoğraflarını inceleyin ve kaydetmek istediklerinizi seçin") validation_status = gr.Textbox(label="📋 Durum", lines=2, interactive=False) load_validation_btn = gr.Button( "✅ Fotoğrafları incele ve seç", variant="primary", size="lg" ) # Dynamic validation interface with checkboxes @gr.render(inputs=[], triggers=[load_validation_btn.click]) def render_validation_interface(): if not app.temp_session: gr.Markdown("⚠️ Geçici oturum yok. Önce bir video işleyin.") return checkboxes = [] for temp_id in sorted(app.temp_session.keys()): dog_data = app.temp_session[temp_id] images = dog_data['images'] # Health info valid_health = [h for h in dog_data['health_scores'] if h is not None] if valid_health: avg_health = np.mean([h['overall'] for h in valid_health]) health_status = valid_health[-1]['status'] status_turkish = { 'Healthy': 'Sağlıklı', 'Monitor': 'İzlenmeli', 'Concern': 'Risk altında' }.get(health_status, health_status) status_colors = { 'Sağlıklı': '#51cf66', 'İzlenmeli': '#ffd43b', 'Risk altında': '#ff6b6b' } status_color = status_colors.get(status_turkish, '#868e96') else: avg_health = None status_turkish = "Bilinmiyor" status_color = '#868e96' with gr.Group(): # Dog header with health info header_html = f"""

🐕 Köpek #{temp_id} - {len(images)} resim

""" if avg_health is not None: header_html += f"""

Sağlık puanı: {avg_health:.1f}/10

Sağlık durumu: {status_turkish}

""" header_html += "
" gr.HTML(header_html) # Images with checkboxes (6 per row) for i in range(0, len(images), 6): with gr.Row(): for j in range(6): if i + j < len(images): img = images[i + j] img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) with gr.Column(scale=1, min_width=120): gr.Image( value=img_rgb, label=f"#{i+j+1}", interactive=False, height=150, show_download_button=False ) cb = gr.Checkbox( label="Tut", value=True, elem_id=f"cb_{temp_id}_{i+j}" ) checkboxes.append(cb) # Save button save_btn = gr.Button( "💾 Seçili fotoğrafları veritabanına kaydet", variant="primary", size="lg" ) save_summary = gr.Textbox(label="📋 Kayıt durumu", lines=8, interactive=False) saved_db_display = gr.HTML(visible=False) # Save button click save_btn.click( fn=app.save_validated_to_database, inputs=checkboxes, outputs=[save_summary, saved_db_display] ) # TAB 3: Veritabanı with gr.Tab("3️⃣ Veritabanı"): gr.Markdown("### Kaydedilmiş köpekleri görüntüleyin ve veri setini indirin") with gr.Row(): refresh_db_btn = gr.Button("🔄 Yenile", size="sm") stats_btn = gr.Button("📊 İstatistikler", size="sm") csv_btn = gr.Button("📄 CSV indir", size="sm") zip_btn = gr.Button("📦 ZIP indir", size="sm") database_display = gr.HTML(visible=False) stats_output = gr.Textbox(label="İstatistikler", lines=8, visible=False) csv_status = gr.Textbox(label="CSV durumu", lines=2, visible=False) csv_file = gr.File(label="CSV dosyasını indir", visible=False) zip_status = gr.Textbox(label="ZIP durumu", lines=5, visible=False) zip_file = gr.File(label="ZIP dosyasını indir", visible=False) # TAB 4: Harita with gr.Tab("4️⃣ Harita"): gr.Markdown("### Köpek konumları ve sağlık durumları") refresh_map_btn = gr.Button("🔄 Haritayı yenile", variant="primary") map_display = gr.HTML() # EVENT HANDLERS # Process video - outputs to SAME video player (flip!) process_btn.click( fn=app.process_video, inputs=[video_display, reid_threshold, sample_rate, create_viz], outputs=[ gallery_output, summary_text, status_text, validation_container, video_display # ← Flips to visualization in SAME player! ] ) # Stop processing stop_btn.click( fn=app.stop_processing, outputs=[summary_text, status_text, gallery_output] ) # Restart/Reset restart_btn.click( fn=app.clear_reset, outputs=[ video_display, gallery_output, summary_text, status_text, validation_container ] ) # Discard session discard_btn.click( fn=app.discard_session, outputs=[validation_container, summary_text, database_display] ) # Load validation interface load_validation_btn.click( fn=lambda: "Fotoğraflar yüklendi. Aşağıdan tutmak istediğiniz resimleri seçin.", outputs=[validation_status] ) # Database refresh refresh_db_btn.click( fn=lambda: gr.update(value=app._show_database(), visible=True), outputs=[database_display] ) # Statistics stats_btn.click( fn=app.get_statistics, outputs=stats_output ).then( fn=lambda: gr.update(visible=True), outputs=stats_output ) # CSV export csv_btn.click( fn=app.export_to_csv, outputs=[csv_status, csv_file] ).then( fn=lambda: (gr.update(visible=True), gr.update(visible=True)), outputs=[csv_status, csv_file] ) # ZIP export zip_btn.click( fn=app.export_dataset_zip, outputs=[zip_status, zip_file] ).then( fn=lambda: (gr.update(visible=True), gr.update(visible=True)), outputs=[zip_status, zip_file] ) # Map refresh refresh_map_btn.click( fn=app.show_map, outputs=map_display ) # Load database on startup demo.load( fn=lambda: gr.update(value=app._show_database(), visible=True), outputs=[database_display] ) return demo if __name__ == "__main__": demo = create_gradio_interface() demo.launch(share=False, server_name="0.0.0.0", server_port=7860)