Spaces:
Sleeping
Sleeping
| """ | |
| Simplified Dog Tracking for Training Dataset Collection | |
| - Process video with adjustable threshold | |
| - Temporary storage with discard option | |
| - Manual validation with checkbox selection per image | |
| - Export to folder structure for fine-tuning | |
| - Download to laptop as ZIP | |
| - Automatic HuggingFace backup/restore | |
| - Visualization video with colored tracking boxes | |
| """ | |
| 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 | |
| import gc | |
| import base64 | |
| from io import BytesIO | |
| from PIL import Image | |
| from pathlib import Path | |
| import json | |
| from datetime import datetime | |
| from detection import DogDetector | |
| from tracking import DeepSORTTracker | |
| from reid import SimplifiedReID | |
| from database import DogDatabase | |
| class DatasetCollectionApp: | |
| """Simplified app for collecting training datasets""" | |
| def __init__(self): | |
| self.device = 'cuda' if torch.cuda.is_available() else 'cpu' | |
| # Restore database before initializing | |
| self._restore_database() | |
| self.detector = DogDetector(device=self.device) | |
| self.tracker = DeepSORTTracker( | |
| max_iou_distance=0.5, | |
| max_age=90, | |
| n_init=1, | |
| use_appearance=True | |
| ) | |
| self.reid = SimplifiedReID(device=self.device) | |
| self.db = DogDatabase('dog_monitoring.db') | |
| # Temporary session storage (in-memory) | |
| self.temp_session = {} | |
| self.current_video_path = None | |
| self.is_processing = False | |
| # Validation state: stores checkbox states for each temp_id | |
| self.validation_data = {} # {temp_id: [bool, bool, ...]} | |
| print("Dataset Collection App initialized") | |
| print(f"Database has {len(self.db.get_all_dogs())} dogs") | |
| def create_visualization_video(self, video_path: str, sample_rate: int) -> str: | |
| """Create visualization video with bold boxes from tracking.py only (no ReID, no labels)""" | |
| try: | |
| cap = cv2.VideoCapture(video_path) | |
| if not cap.isOpened(): | |
| print("ERROR: Cannot open input 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)) | |
| output_path = f"visualization_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4" | |
| # Try codecs | |
| codecs = ['mp4v', 'XVID', 'MJPG'] | |
| out = None | |
| for codec in codecs: | |
| fourcc = cv2.VideoWriter_fourcc(*codec) | |
| out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) | |
| if out.isOpened(): | |
| print(f"Using codec: {codec}") | |
| break | |
| else: | |
| out.release() | |
| out = None | |
| if out is None or not out.isOpened(): | |
| print("ERROR: Could not initialize VideoWriter with any codec") | |
| cap.release() | |
| return None | |
| # Tracker only (no ReID) | |
| viz_tracker = DeepSORTTracker( | |
| max_iou_distance=0.5, | |
| max_age=90, | |
| n_init=1, | |
| use_appearance=False | |
| ) | |
| track_colors = {} | |
| frame_num = 0 | |
| print("\nCreating visualization video (tracking IDs only)...") | |
| while cap.isOpened(): | |
| ret, frame = cap.read() | |
| if not ret: | |
| break | |
| if frame_num % sample_rate == 0: | |
| detections = self.detector.detect(frame) | |
| tracks = viz_tracker.update(detections) | |
| for track in tracks: | |
| tid = track.track_id | |
| if tid not in track_colors: | |
| track_colors[tid] = ( | |
| np.random.randint(50, 255), | |
| np.random.randint(50, 255), | |
| np.random.randint(50, 255) | |
| ) | |
| color = track_colors[tid] | |
| x1, y1, x2, y2 = map(int, track.bbox) | |
| # Bold box only | |
| cv2.rectangle(frame, (x1, y1), (x2, y2), color, 8) | |
| out.write(frame) | |
| frame_num += 1 | |
| if frame_num % 30 == 0: | |
| print(f"Visualization progress: {frame_num} frames") | |
| cap.release() | |
| out.release() | |
| if os.path.exists(output_path) and os.path.getsize(output_path) > 1000: | |
| print(f"Visualization video saved: {output_path}") | |
| return output_path | |
| else: | |
| print("ERROR: Video file not created or is empty") | |
| return None | |
| except Exception as e: | |
| print(f"Visualization video error: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return None | |
| def stop_processing(self): | |
| """Stop video processing""" | |
| if self.is_processing: | |
| self.is_processing = False | |
| return "İşlem kullanıcı tarafından durduruldu", "Durduruldu", None | |
| else: | |
| return "Durdurulaack işlem yok", "İşlem yapılmıyor", None | |
| def clear_reset(self): | |
| """Clear all temporary data and reset UI""" | |
| 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, | |
| "<p style='text-align:center; color:#868e96;'>Oturum temizlendi. Başlamak için yeni bir video yükleyin.</p>", | |
| "", | |
| "", | |
| gr.update(visible=False) | |
| ) | |
| def discard_session(self): | |
| """Discard temporary session completely""" | |
| 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), | |
| f"{count} geçici köpek iptal edildi. Farklı bir eşik deneyin.", | |
| gr.update(visible=False) | |
| ) | |
| def process_video(self, video_path: str, reid_threshold: float, sample_rate: int): | |
| """Process video and store in temporary session""" | |
| if not video_path: | |
| return None, "Lütfen bir video yükleyin", "", gr.update(visible=False), None | |
| self.is_processing = True | |
| 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() | |
| try: | |
| 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 = {} | |
| min_frame_gap = max(15, total_frames // 45) | |
| blur_rejected = 0 | |
| print(f"\nProcessing video: {total_frames} frames, {fps} fps") | |
| print(f"Minimum frame gap: {min_frame_gap} frames") | |
| while cap.isOpened() and self.is_processing: | |
| 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: | |
| 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': [], | |
| 'timestamps': [], | |
| 'confidences': [], | |
| 'bboxes': [], | |
| 'frame_numbers': [], | |
| 'last_captured_frame': -1 | |
| } | |
| if len(temp_dogs[temp_id]['images']) >= 30: | |
| 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 | |
| gray = cv2.cvtColor(image_crop, cv2.COLOR_BGR2GRAY) | |
| laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var() | |
| if laplacian_var < 75: | |
| blur_rejected += 1 | |
| continue | |
| temp_dogs[temp_id]['images'].append(image_crop.copy()) | |
| temp_dogs[temp_id]['timestamps'].append(frame_num / fps) | |
| 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"Progress: {progress}%") | |
| cap.release() | |
| 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'] | |
| original_count = len(temp_dogs) | |
| discarded_ids = [] | |
| for temp_id in list(temp_dogs.keys()): | |
| if len(temp_dogs[temp_id]['images']) < 14: | |
| discarded_ids.append(temp_id) | |
| del temp_dogs[temp_id] | |
| discarded_count = len(discarded_ids) | |
| self.temp_session = temp_dogs | |
| for temp_id in temp_dogs.keys(): | |
| self.validation_data[temp_id] = [True] * len(temp_dogs[temp_id]['images']) | |
| summary = f"İşlem tamamlandı!\n" | |
| summary += f"Başlangıçta {original_count} köpek tespit edildi\n" | |
| if discarded_count > 0: | |
| summary += f"14'ten az resimli {discarded_count} köpek iptal edildi (ID'ler: {discarded_ids})\n" | |
| summary += f"14+ resimli {len(temp_dogs)} köpek tutuldu\n" | |
| summary += f"{processed} kare işlendi\n" | |
| summary += f"Kare aralığı: {min_frame_gap} kare\n" | |
| summary += f"Bulanık resimler reddedildi: {blur_rejected}\n\n" | |
| if len(temp_dogs) == 0: | |
| summary += "Hiçbir köpek minimum 14 resim gereksinimini karşılamadı.\n" | |
| summary += "ReID eşiğini ayarlamayı veya daha uzun bir video kullanmayı deneyin." | |
| show_validation = False | |
| else: | |
| summary += "Sonuçlar GEÇİCİ oturumda saklandı\n" | |
| summary += "Kaydetmeden önce resimleri incelemek ve seçmek için Sekme 2'ye gidin" | |
| show_validation = True | |
| gallery_html = self._create_temp_gallery() | |
| viz_video_path = self.create_visualization_video(video_path, sample_rate) | |
| gc.collect() | |
| if torch.cuda.is_available(): | |
| torch.cuda.empty_cache() | |
| return ( | |
| gallery_html, | |
| summary, | |
| "Doğrulama için hazır" if len(temp_dogs) > 0 else "Geçerli köpek yok", | |
| gr.update(visible=show_validation), | |
| viz_video_path | |
| ) | |
| except Exception as e: | |
| import traceback | |
| error = f"Hata: {str(e)}\n{traceback.format_exc()}" | |
| return None, error, "", gr.update(visible=False), None | |
| finally: | |
| self.is_processing = False | |
| def _create_temp_gallery(self) -> str: | |
| """Create gallery from temporary session""" | |
| if not self.temp_session: | |
| return "<p>Geçici oturumda köpek yok</p>" | |
| html = "<div style='padding: 20px;'>" | |
| html += "<h2 style='text-align:center; color:#ff6b6b;'>GEÇİCİ OTURUM - Henüz Kaydedilmedi</h2>" | |
| html += f"<p style='text-align:center;'>Tespit edilen köpekler: {len(self.temp_session)}</p>" | |
| html += "<div style='display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 20px;'>" | |
| for temp_id in sorted(self.temp_session.keys()): | |
| dog_data = self.temp_session[temp_id] | |
| images = dog_data['images'] | |
| display_images = images[:10] | |
| html += f""" | |
| <div style='border: 3px solid #ff6b6b; border-radius: 10px; | |
| padding: 15px; background: #fff5f5;'> | |
| <h3 style='margin: 0 0 10px 0; color:#c92a2a;'> | |
| Geçici Köpek #{temp_id} (GEÇİCİ) | |
| </h3> | |
| <p style='color: #666;'>Toplam resim: {len(images)}</p> | |
| <p style='color: #666; font-size:12px;'> | |
| İlk {len(display_images)} resim gösteriliyor | |
| </p> | |
| <div style='display: grid; grid-template-columns: repeat(5, 1fr); gap: 5px;'> | |
| """ | |
| for img in display_images: | |
| img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| img_base64 = self._img_to_base64(img_rgb) | |
| html += f""" | |
| <img src='data:image/jpeg;base64,{img_base64}' | |
| style='width: 100%; aspect-ratio: 1; object-fit: cover; | |
| border-radius: 5px;'> | |
| """ | |
| html += "</div></div>" | |
| html += "</div></div>" | |
| return html | |
| def load_validation_interface(self): | |
| """Load validation interface with checkbox selection""" | |
| if not self.temp_session: | |
| return ( | |
| gr.update(visible=False), | |
| "Doğrulanacak geçici oturum yok. Önce bir video işleyin.", | |
| "" | |
| ) | |
| html = "<div style='padding: 20px;'>" | |
| html += "<h2 style='text-align:center;'>Resimleri İnceleyin ve Seçin</h2>" | |
| html += "<p style='text-align:center; color:#666;'>Tutmak/atmak için resimleri işaretleyin/işaretini kaldırın. Hepsi varsayılan olarak seçilidir.</p>" | |
| html += "</div>" | |
| status = f"{len(self.temp_session)} köpek doğrulama için yüklendi. İnceleyin ve hazır olduğunuzda 'Seçili Fotoğrafları Kaydet' düğmesine tıklayın." | |
| return ( | |
| gr.update(visible=True), | |
| status, | |
| html | |
| ) | |
| def save_validated_to_database(self, *checkbox_states): | |
| """Save validated images to permanent database""" | |
| if not self.temp_session: | |
| return "Kaydedilecek geçici oturum yok", gr.update() | |
| try: | |
| saved_count = 0 | |
| total_images_saved = 0 | |
| 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']) | |
| 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: | |
| continue | |
| dog_id = self.db.add_dog( | |
| name=f"Kopek_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{temp_id}" | |
| ) | |
| for idx in selected_indices: | |
| self.db.add_dog_image( | |
| dog_id=dog_id, | |
| image=dog_data['images'][idx], | |
| timestamp=dog_data['timestamps'][idx], | |
| confidence=dog_data['confidences'][idx], | |
| bbox=dog_data['bboxes'][idx] | |
| ) | |
| total_images_saved += 1 | |
| saved_count += 1 | |
| self.temp_session.clear() | |
| self.validation_data = {} | |
| self._backup_database() | |
| db_html = self._show_database() | |
| summary = f"✅ {saved_count} köpek ve {total_images_saved} seçili resim başarıyla kalıcı veritabanına kaydedildi!" | |
| gc.collect() | |
| if torch.cuda.is_available(): | |
| torch.cuda.empty_cache() | |
| return summary, gr.update(value=db_html, visible=True) | |
| except Exception as e: | |
| import traceback | |
| error = f"Kayıt hatası: {str(e)}\n{traceback.format_exc()}" | |
| return error, gr.update() | |
| def _backup_database(self): | |
| """Backup database to HuggingFace""" | |
| try: | |
| from huggingface_hub import HfApi | |
| hf_token = os.getenv('HF_TOKEN') | |
| if not hf_token: | |
| print("Uyarı: HF_TOKEN bulunamadı, yedekleme atlanıyor") | |
| return | |
| api = HfApi() | |
| repo_id = "mustafa2ak/dog-dataset-backup" | |
| api.upload_file( | |
| path_or_fileobj='dog_monitoring.db', | |
| path_in_repo='dog_monitoring.db', | |
| repo_id=repo_id, | |
| repo_type='dataset', | |
| token=hf_token | |
| ) | |
| print(f"✅ Veritabanı {repo_id} adresine yedeklendi") | |
| except Exception as e: | |
| print(f"Yedekleme başarısız: {str(e)}") | |
| def _restore_database(self): | |
| """Restore database from HuggingFace""" | |
| try: | |
| from huggingface_hub import hf_hub_download | |
| hf_token = os.getenv('HF_TOKEN') | |
| if not hf_token: | |
| print("HF_TOKEN bulunamadı, yeni veritabanı ile başlanıyor") | |
| return | |
| repo_id = "mustafa2ak/dog-dataset-backup" | |
| db_path = hf_hub_download( | |
| repo_id=repo_id, | |
| filename='dog_monitoring.db', | |
| repo_type='dataset', | |
| token=hf_token | |
| ) | |
| import shutil | |
| shutil.copy(db_path, 'dog_monitoring.db') | |
| print(f"✅ Veritabanı {repo_id} adresinden geri yüklendi") | |
| except Exception as e: | |
| print(f"Yedek bulunamadı veya geri yükleme başarısız: {str(e)}") | |
| def _show_database(self) -> str: | |
| """Show current database contents""" | |
| dogs = self.db.get_all_dogs() | |
| if dogs.empty: | |
| return "<p style='text-align:center; color:#868e96;'>Veritabanında henüz köpek yok</p>" | |
| html = "<div style='padding: 20px;'>" | |
| html += f"<h2 style='text-align:center; color:#228be6;'>Kalıcı Veritabanı ({len(dogs)} köpek)</h2>" | |
| html += "<div style='display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px;'>" | |
| for _, dog in dogs.iterrows(): | |
| images = self.db.get_dog_images(dog['dog_id']) | |
| display_count = min(6, len(images)) | |
| html += f""" | |
| <div style='border: 2px solid #228be6; border-radius: 10px; | |
| padding: 15px; background: #e7f5ff;'> | |
| <h3 style='margin: 0 0 10px 0; color:#1971c2;'>{dog['name']}</h3> | |
| <p style='color: #666; margin: 5px 0;'>ID: {dog['dog_id']}</p> | |
| <p style='color: #666; margin: 5px 0;'>Resimler: {len(images)}</p> | |
| <p style='color: #666; margin: 5px 0; font-size: 12px;'> | |
| İlk görülme: {dog['first_seen']} | |
| </p> | |
| <div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-top: 10px;'> | |
| """ | |
| for img_data in images[:display_count]: | |
| img = img_data['image'] | |
| img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| img_base64 = self._img_to_base64(img_rgb) | |
| html += f""" | |
| <img src='data:image/jpeg;base64,{img_base64}' | |
| style='width: 100%; aspect-ratio: 1; object-fit: cover; | |
| border-radius: 5px;'> | |
| """ | |
| html += "</div></div>" | |
| html += "</div></div>" | |
| return html | |
| def export_dataset(self): | |
| """Export dataset as downloadable ZIP file""" | |
| try: | |
| dogs = self.db.get_all_dogs() | |
| if dogs.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.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=dog_id, | |
| validated_only=False, | |
| include_discarded=False | |
| ) | |
| if not images: | |
| continue | |
| for idx, img_data in enumerate(images): | |
| image = img_data['image'] | |
| img_rgb = cv2.cvtColor(image, 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"Exported {len(images)} images for {dog_name}") | |
| metadata = { | |
| 'export_date': datetime.now().isoformat(), | |
| 'total_dogs': len(dogs), | |
| '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)}\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{traceback.format_exc()}" | |
| return error, None | |
| def _img_to_base64(self, img_array: np.ndarray) -> str: | |
| """Convert image array to base64 string""" | |
| img_pil = Image.fromarray(img_array) | |
| buffered = BytesIO() | |
| img_pil.save(buffered, format="JPEG", quality=85) | |
| return base64.b64encode(buffered.getvalue()).decode() | |
| def create_interface(self): | |
| """Create Gradio interface with improved Turkish labels""" | |
| with gr.Blocks(title="Köpek Veri Seti Toplama", theme=gr.themes.Soft()) as app: | |
| gr.Markdown(""" | |
| # Yapay zeka ile sokak köpeği takibi | |
| Üç aşamada tamamlanacak. | |
| **📊 1. aşama – veri toplama (şu anki aşama)** | |
| Yapay zeka modeli eğitimi için temiz ve etiketli veri seti oluşturuluyor. | |
| **🤖 2. aşama – yapay zeka modeli eğitimi** | |
| Sistem %90 başarı oranıyla her köpeği bireysel olarak ayırt edebilecek. | |
| **📍 3. aşama – sahada kullanım** | |
| Şehir genelinde kalıcı, otomatik köpek takip sistemi ve veritabanı oluşturulacak. | |
| --- | |
| """) | |
| with gr.Tabs(): | |
| # TAB 1 | |
| with gr.Tab("1️⃣ Video yükle ve analiz et"): | |
| gr.Markdown("### Köpekleri tespit etmek için video yükleyin ve analiz edin") | |
| with gr.Row(): | |
| with gr.Column(): | |
| 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.3, step=0.05, | |
| label="🎯 Köpek tanıma hassasiyeti" | |
| ) | |
| sample_rate = gr.Slider( | |
| minimum=1, maximum=10, value=3, step=1, | |
| label="⏱️ Saniyede kaç kare işlensin?" | |
| ) | |
| with gr.Row(): | |
| process_btn = gr.Button("⚙️ Videoyu analiz et", variant="primary", size="lg") | |
| stop_btn = gr.Button("🛑 İşlemi durdur", variant="stop") | |
| clear_btn = gr.Button("🔄 Yeniden başla") | |
| progress_text = gr.Textbox(label="⏳ İşlem durumu", lines=1) | |
| status_text = gr.Textbox(label="📊 Detaylı bilgi", lines=8) | |
| with gr.Column(): | |
| gallery_output = gr.HTML(label="🐕 Bulunan köpekler") | |
| with gr.Row(): | |
| discard_btn = gr.Button("↩️ Farklı ayarlarla tekrar dene", variant="secondary") | |
| # TAB 2 | |
| with gr.Tab("2️⃣ Fotoğrafları incele ve kaydet"): | |
| gr.Markdown("### Tespit edilen köpeklerin fotoğraflarını inceleyin ve kaydetmek istediklerinizi seçin") | |
| with gr.Column(visible=False) as validation_container: | |
| validation_status = gr.Textbox(label="📋 Durum", lines=2) | |
| load_btn = gr.Button("✅ Fotoğrafları incele ve seç", variant="primary", size="lg") | |
| def render_validation(): | |
| if not self.temp_session: | |
| gr.Markdown("Geçici oturum yok. Önce bir video işleyin.") | |
| return | |
| checkboxes = [] | |
| for temp_id in sorted(self.temp_session.keys()): | |
| dog_data = self.temp_session[temp_id] | |
| images = dog_data['images'] | |
| with gr.Group(): | |
| gr.Markdown(f"### 🐕 Köpek #{temp_id} - {len(images)} resim") | |
| 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_btn = gr.Button("💾 Seçili fotoğrafları kaydet", variant="primary", size="lg") | |
| save_status = gr.Textbox(label="Kayıt durumu", lines=3) | |
| save_btn.click( | |
| fn=self.save_validated_to_database, | |
| inputs=checkboxes, | |
| outputs=[save_status, validation_container] | |
| ) | |
| # TAB 3 | |
| with gr.Tab("3️⃣ Kayıtları gör ve indir"): | |
| gr.Markdown("### Kaydedilmiş köpekleri görüntüleyin ve veri setini bilgisayarınıza indirin") | |
| refresh_db_btn = gr.Button("🔄 Kaydedilenleri göster", variant="secondary") | |
| database_display = gr.HTML(label="Veritabanı içeriği", visible=False) | |
| gr.Markdown("---") | |
| export_btn = gr.Button("📦 Veri setini indir (ZIP)", variant="primary", size="lg") | |
| export_status = gr.Textbox(label="İndirme durumu", lines=5) | |
| download_btn = gr.File(label="ZIP dosyasını bilgisayara kaydet", interactive=False) | |
| # Event handlers | |
| process_btn.click( | |
| fn=self.process_video, | |
| inputs=[video_display, reid_threshold, sample_rate], | |
| outputs=[ | |
| gallery_output, | |
| status_text, | |
| progress_text, | |
| validation_container, | |
| video_display | |
| ] | |
| ) | |
| stop_btn.click( | |
| fn=self.stop_processing, | |
| outputs=[status_text, progress_text, gallery_output] | |
| ) | |
| clear_btn.click( | |
| fn=self.clear_reset, | |
| outputs=[ | |
| video_display, | |
| gallery_output, | |
| status_text, | |
| progress_text, | |
| validation_container | |
| ] | |
| ) | |
| discard_btn.click( | |
| fn=self.discard_session, | |
| outputs=[validation_container, status_text, database_display] | |
| ) | |
| load_btn.click( | |
| fn=self.load_validation_interface, | |
| outputs=[validation_container, validation_status, gr.HTML()] | |
| ) | |
| refresh_db_btn.click( | |
| fn=lambda: gr.update(value=self._show_database(), visible=True), | |
| outputs=[database_display] | |
| ) | |
| export_btn.click( | |
| fn=self.export_dataset, | |
| outputs=[export_status, download_btn] | |
| ) | |
| return app | |
| def launch(self): | |
| """Launch the Gradio app""" | |
| app = self.create_interface() | |
| app.launch(share=False, server_name="0.0.0.0", server_port=7860) | |
| if __name__ == "__main__": | |
| app = DatasetCollectionApp() | |
| app.launch() |