mustafa2ak commited on
Commit
d4d1ce1
·
verified ·
1 Parent(s): 0eb5936

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +495 -488
app.py CHANGED
@@ -1,51 +1,50 @@
1
  """
2
- sokak_dostlari_app.py - Sokak Dostları AI Tanıma Sistemi
3
- Hugging Face Spaces T4 GPU için tam Türkçe versiyon
4
  """
 
5
  import gradio as gr
6
  import cv2
7
  import numpy as np
8
  import pandas as pd
9
- import random
10
  import json
11
  import time
12
- import folium
13
  from pathlib import Path
14
  from typing import List, Dict, Optional, Tuple
15
- import sqlite3
16
- import base64
17
- import pickle
18
  from datetime import datetime
19
- from io import BytesIO
20
- from PIL import Image
21
 
22
  # Import core modules
23
  from detection import DogDetector
24
  from tracking import SimpleTracker
25
  from reid import SimplifiedDogReID
 
 
26
 
27
- # ========== TURKISH DOG NAMES ==========
28
  class TurkishDogNames:
29
- """Türkçe köpek isimleri üretici"""
30
 
31
  NAMES = [
32
- "Karabaş", "Pamuk", "Boncuk", "Çomar", "Duman", "Paşa",
33
- "Aslan", "Kömür", "Bal", "Fındık", "Zeytin", "Tarçın",
34
- "Minnoş", "Cesur", "Kara", "Beyaz", "Benekli", "Sarı",
35
- "Kuyruk", "Pati", "Şanslı", "Dostum", "Kont", "Prenses",
36
- "Sultan", "Reis", "Kaptan", "Yıldız", "Ay", "Güneş",
37
- "Bulut", "Rüzgar", "Fırtına", "Şimşek", "Kar", "Buz"
 
38
  ]
39
 
40
  def __init__(self):
41
  self.used_names = set()
42
- self.name_counter = {}
43
-
44
  def get_name(self, dog_id: int) -> str:
45
- """ID'ye göre benzersiz isim döndür"""
46
- if dog_id in self.name_counter:
47
- return self.name_counter[dog_id]
48
-
49
  base_name = self.NAMES[dog_id % len(self.NAMES)]
50
 
51
  if base_name in self.used_names:
@@ -55,264 +54,215 @@ class TurkishDogNames:
55
  name = f"{base_name} {counter}"
56
  else:
57
  name = base_name
58
-
59
  self.used_names.add(name)
60
- self.name_counter[dog_id] = name
61
  return name
62
 
63
  # ========== BODRUM LOCATIONS ==========
64
  class BodrumLocations:
65
- """Bodrum lokasyonları"""
66
 
67
  LOCATIONS = [
68
  ("Marina Sokak", "Bodrum Marina", 37.0318, 27.4305),
69
- ("Kale Meydanı", "Bodrum Kalesi", 37.0325, 27.4298),
70
- ("Çarşı İçi", "Merkez Çarşı", 37.0334, 27.4312),
71
  ("Neyzen Tevfik Caddesi", "Sahil", 37.0341, 27.4289),
72
  ("Cumhuriyet Caddesi", "Merkez", 37.0357, 27.4301),
73
  ("Dr. Alim Bey Caddesi", "Hastane Yolu", 37.0365, 27.4276),
74
- ("Gümbet Mahallesi", "Gümbet Plajı", 37.0298, 27.4088),
75
- ("Konacık Merkez", "Konacık", 37.0445, 27.4189),
76
- ("Ortakent Yolu", "Ortakent", 37.0521, 27.3412),
77
- ("Yalıkavak Marina", "Yalıkavak", 37.0983, 27.2891)
78
  ]
79
 
80
  @classmethod
81
- def get_random_location(cls) -> Tuple[str, str, float, float]:
82
- """Rastgele lokasyon seç"""
83
  return random.choice(cls.LOCATIONS)
84
 
85
- # ========== IMAGE QUALITY SCORER ==========
86
- class ImageQualityScorer:
87
- """Görüntü kalitesi skorlayıcı"""
88
 
89
- @staticmethod
90
- def score_image_with_head(image: np.ndarray) -> float:
91
- """Baş görünürlüğü ile birlikte görüntü kalitesi"""
92
- if image is None or image.size == 0:
93
- return 0
94
 
95
- gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- # Bulanıklık (Laplacian)
98
- sharpness = cv2.Laplacian(gray, cv2.CV_64F).var()
 
 
99
 
100
- # Baş bölgesi (üst %40)
101
- h, w = image.shape[:2]
102
- head_region = gray[:int(h * 0.4), :]
103
- head_detail = np.std(head_region) if head_region.size > 0 else 0
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
- # Parlaklık
106
- brightness = np.mean(gray)
107
- good_brightness = 1.0 if 50 < brightness < 200 else 0.5
108
 
109
- # Toplam skor
110
- total = (sharpness * 0.4 + head_detail * 0.4 + good_brightness * 0.2)
111
- return total
112
-
113
- @staticmethod
114
- def select_best_image(images: List[np.ndarray]) -> Tuple[np.ndarray, int]:
115
- """En iyi kaliteli görüntüyü seç"""
116
- if not images:
117
- return None, -1
118
 
119
- best_score = -1
120
- best_idx = 0
121
-
122
- for idx, img in enumerate(images):
123
- score = ImageQualityScorer.score_image_with_head(img)
124
- if score > best_score:
125
- best_score = score
126
- best_idx = idx
127
-
128
- return images[best_idx], best_idx
129
-
130
- # ========== DATABASE MANAGER ==========
131
- class DogDatabase:
132
- """Basitleştirilmiş veritabanı"""
133
-
134
- def __init__(self, db_path: str = "sokak_dostlari.db"):
135
- self.conn = sqlite3.connect(db_path, check_same_thread=False)
136
- self.conn.row_factory = sqlite3.Row
137
- self.cursor = self.conn.cursor()
138
- self._create_tables()
139
-
140
- def _create_tables(self):
141
- """Tabloları oluştur"""
142
- self.cursor.execute("""
143
- CREATE TABLE IF NOT EXISTS dogs (
144
- dog_id INTEGER PRIMARY KEY,
145
- name TEXT,
146
- profile_image BLOB,
147
- best_head_image BLOB,
148
- first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
149
- total_sightings INTEGER DEFAULT 1,
150
- features BLOB,
151
- current_location TEXT,
152
- current_place TEXT
153
- )
154
- """)
155
-
156
- self.cursor.execute("""
157
- CREATE TABLE IF NOT EXISTS sightings (
158
- sighting_id INTEGER PRIMARY KEY AUTOINCREMENT,
159
- dog_id INTEGER,
160
- timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
161
- location TEXT,
162
- place TEXT,
163
- lat REAL,
164
- lon REAL,
165
- confidence REAL,
166
- FOREIGN KEY (dog_id) REFERENCES dogs(dog_id)
167
- )
168
- """)
169
-
170
- self.conn.commit()
171
-
172
- def save_or_update_dog(self, dog_id: int, name: str, image: np.ndarray,
173
- features: np.ndarray, location: str, place: str) -> Dict:
174
- """Köpek kaydet veya güncelle"""
175
- # Mevcut köpeği kontrol et
176
- self.cursor.execute("SELECT * FROM dogs WHERE dog_id = ?", (dog_id,))
177
- existing = self.cursor.fetchone()
178
-
179
- # Görüntüyü encode et
180
- _, img_buffer = cv2.imencode('.jpg', image, [cv2.IMWRITE_JPEG_QUALITY, 85])
181
- img_data = base64.b64encode(img_buffer).decode('utf-8')
182
-
183
- # Baş görüntüsü
184
- h, w = image.shape[:2]
185
- head_img = image[:int(h * 0.4), :]
186
- _, head_buffer = cv2.imencode('.jpg', head_img, [cv2.IMWRITE_JPEG_QUALITY, 85])
187
- head_data = base64.b64encode(head_buffer).decode('utf-8')
188
-
189
- features_blob = pickle.dumps(features)
190
-
191
- if existing:
192
- # Güncelle
193
- self.cursor.execute("""
194
- UPDATE dogs SET
195
- total_sightings = total_sightings + 1,
196
- current_location = ?,
197
- current_place = ?
198
- WHERE dog_id = ?
199
- """, (location, place, dog_id))
200
 
201
- # Profil resmini decode et
202
- profile_bytes = base64.b64decode(existing['profile_image'])
203
- nparr = np.frombuffer(profile_bytes, np.uint8)
204
- profile_img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
205
 
206
- result = {
207
- 'is_new': False,
208
- 'name': existing['name'],
209
- 'profile_image': profile_img,
210
- 'total_sightings': existing['total_sightings'] + 1
211
- }
212
- else:
213
- # Yeni köpek
214
- self.cursor.execute("""
215
- INSERT INTO dogs (dog_id, name, profile_image, best_head_image,
216
- features, current_location, current_place)
217
- VALUES (?, ?, ?, ?, ?, ?, ?)
218
- """, (dog_id, name, img_data, head_data, features_blob, location, place))
219
 
220
- result = {
221
- 'is_new': True,
222
- 'name': name,
223
- 'profile_image': image,
224
- 'total_sightings': 1
225
- }
226
 
227
- self.conn.commit()
228
- return result
229
 
230
- def get_all_dogs_for_table(self) -> pd.DataFrame:
231
- """Tablo için köpek verisi"""
232
- self.cursor.execute("""
233
- SELECT dog_id, name, current_location, current_place, total_sightings, profile_image
234
- FROM dogs ORDER BY dog_id DESC
235
- """)
236
-
237
- data = []
238
- for row in self.cursor.fetchall():
239
- # Mini thumbnail oluştur
240
- img_bytes = base64.b64decode(row['profile_image'])
241
- nparr = np.frombuffer(img_bytes, np.uint8)
242
- img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
243
- img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
244
- img_small = cv2.resize(img_rgb, (60, 60))
245
-
246
- data.append({
247
- 'Fotoğraf': img_small,
248
- 'Adı': f"Adı: {row['name']}",
249
- 'Bulunduğu Yer': f"{row['current_location']}, {row['current_place']}",
250
- 'Görülme Sayısı': row['total_sightings']
251
- })
252
 
253
- return pd.DataFrame(data) if data else pd.DataFrame(columns=['Fotoğraf', 'Adı', 'Bulunduğu Yer', 'Görülme Sayısı'])
254
-
255
- # ========== MAIN APPLICATION ==========
256
- class SokakDostlariApp:
257
- """Ana uygulama - T4 GPU optimize"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
- def __init__(self):
260
- # Core components
261
- self.detector = DogDetector(device='cuda', confidence_threshold=0.4)
262
- self.tracker = SimpleTracker()
263
- self.reid = SimplifiedDogReID(device='cuda', similarity_threshold=0.65)
264
 
265
- # Helpers
266
- self.db = DogDatabase()
267
- self.name_gen = TurkishDogNames()
268
- self.quality_scorer = ImageQualityScorer()
269
 
270
- # Session state
271
- self.current_dogs = {}
272
- self.reid_threshold = 0.65
273
- self.current_video_location = None
274
- self.current_video_place = None
275
- self.current_lat = None
276
- self.current_lon = None
277
- self.processing_active = False
278
- self.live_detections = []
279
 
 
 
 
 
 
 
 
 
 
280
  def process_video_with_live_display(self, video_path: str, mirror_fix: bool = False):
281
- """Process video with live display of detection"""
282
  if not video_path:
283
- yield None, None, pd.DataFrame(), "Video yüklenmedi"
284
  return
285
 
286
- # Reset
287
  self.tracker = SimpleTracker()
288
  self.reid.reset()
289
  self.current_dogs = {}
290
- self.live_detections = []
291
  self.processing_active = True
292
 
293
- # Choose single location for video
294
  street, place, lat, lon = BodrumLocations.get_random_location()
295
- self.current_video_location = street
296
- self.current_video_place = place
297
- self.current_lat = lat
298
- self.current_lon = lon
299
 
300
  # Open video
301
- cap = cv2.VideoCapture(video_path)
 
 
302
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
303
  fps = cap.get(cv2.CAP_PROP_FPS)
304
-
305
- # Prepare output
306
  width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
307
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
308
- output_path = "processed_output.mp4"
 
 
309
  fourcc = cv2.VideoWriter_fourcc(*'mp4v')
310
  out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
311
 
312
  frame_count = 0
313
- detected_names = []
314
 
315
- while self.processing_active:
316
  ret, frame = cap.read()
317
  if not ret:
318
  break
@@ -321,347 +271,404 @@ class SokakDostlariApp:
321
  frame = cv2.flip(frame, 1)
322
 
323
  frame_count += 1
324
- display_frame = frame.copy()
325
 
326
- # Process every 3rd frame
327
- if frame_count % 3 == 0:
 
328
  detections = self.detector.detect(frame)
 
 
329
  tracks = self.tracker.update(detections)
330
 
331
  for track in tracks:
332
- # Draw YOLO box immediately
333
  bbox = track.bbox
334
  x1, y1, x2, y2 = map(int, bbox)
335
- cv2.rectangle(display_frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
336
 
337
- # Get best image for ReID
338
- images = []
339
- for det in track.detections[-5:]:
340
  if det.image_crop is not None:
341
- images.append(det.image_crop)
 
342
 
343
- if images:
344
- best_img, _ = self.quality_scorer.select_best_image(images)
 
 
 
 
 
 
 
345
 
346
- # ReID
347
- dog_id, confidence = self.reid.match_or_register(track)
 
 
 
 
 
 
 
 
 
 
348
 
349
- if dog_id > 0:
350
- name = self.name_gen.get_name(dog_id)
351
-
352
- # Show name being assigned with animation
353
- cv2.putText(display_frame, f">>> {name}", (x1, y1-10),
354
- cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 2)
355
-
356
- # Save to database
357
- result = self.db.save_or_update_dog(
358
- dog_id, name, best_img,
359
- np.zeros(2048),
360
- self.current_video_location,
361
- self.current_video_place
362
- )
363
-
364
- # Add to current dogs
365
- if result['is_new']:
366
- self.live_detections.append(f"🟢 Yeni: {name}")
367
- detected_names.append(name)
368
- color = (0, 255, 255) # Yellow for new
369
- else:
370
- self.live_detections.append(f"🔄 Tanıdık: {name} (#{result['total_sightings']})")
371
- color = (0, 255, 0) # Green for known
372
-
373
- # Show mini profile
374
- if result['profile_image'] is not None:
375
- try:
376
- mini = cv2.resize(result['profile_image'], (50, 50))
377
- if y1 > 50:
378
- display_frame[y1-50:y1, x1:x1+50] = mini
379
- except:
380
- pass
381
-
382
- # Draw final box with color
383
- cv2.rectangle(display_frame, (x1, y1), (x2, y2), color, 3)
384
-
385
- self.current_dogs[dog_id] = result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
 
387
- # Add overlay
388
- self._add_live_overlay(display_frame, frame_count, len(self.current_dogs), self.live_detections[-3:])
 
 
389
 
390
- # Convert for display
391
- frame_rgb = cv2.cvtColor(display_frame, cv2.COLOR_BGR2RGB)
 
392
 
393
- # Write original frame to video
394
- self._add_live_overlay(frame, frame_count, len(self.current_dogs), self.live_detections[-3:])
 
 
395
  out.write(frame)
396
 
397
- # Yield frame for live display
398
- yield frame_rgb, None, pd.DataFrame(), f"İşleniyor: {frame_count}/{total_frames} | Bulunan: {len(self.current_dogs)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
 
400
  # Cleanup
401
  cap.release()
402
  out.release()
403
 
404
- # Prepare final results
405
- df = self.db.get_all_dogs_for_table()
406
- map_html = self._create_map()
407
- status = f"✅ {len(self.current_dogs)} köpek bulundu\n📍 Konum: {self.current_video_location}, {self.current_video_place}"
 
 
 
 
 
 
 
 
 
408
 
409
- # Final yield with complete results
410
- yield None, output_path, df, map_html, status
411
 
412
- def _draw_detection_with_info(self, frame, track, dog_info, name):
413
- """Tespit kutusunu çiz"""
414
- bbox = track.bbox
415
- x1, y1, x2, y2 = map(int, bbox)
416
-
417
- if dog_info['is_new']:
418
- # Yeni köpek - turuncu
419
- color = (255, 165, 0)
420
- label = f"Yeni: {name}"
 
421
  else:
422
- # Tanıdık - yeşil
423
- color = (0, 255, 0)
424
- label = f"Tanıdık: {name} (#{dog_info['total_sightings']})"
425
-
426
- # Mini profil göster
427
- if dog_info['profile_image'] is not None:
428
- try:
429
- mini = cv2.resize(dog_info['profile_image'], (50, 50))
430
- if y1 > 50:
431
- frame[y1-50:y1, x1:x1+50] = mini
432
- except:
433
- pass
434
-
435
- # Kutu ve etiket
436
- cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
437
-
438
- # Etiket arka planı
439
- label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)[0]
440
- cv2.rectangle(frame, (x1, y1 - label_size[1] - 10),
441
- (x1 + label_size[0], y1), color, -1)
442
- cv2.putText(frame, label, (x1, y1 - 5),
443
- cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
444
-
445
- def _add_live_overlay(self, frame, frame_count, dog_count, recent_detections):
446
- """Canlı bilgi overlay"""
447
- h, w = frame.shape[:2]
448
-
449
- # Üst bilgi çubuğu
450
- overlay = frame.copy()
451
- cv2.rectangle(overlay, (0, 0), (w, 80), (0, 0, 0), -1)
452
- frame[:80, :] = cv2.addWeighted(overlay[:80, :], 0.4, frame[:80, :], 0.6, 0)
453
-
454
- # Ana bilgi
455
- cv2.putText(frame, f"Kare: {frame_count} | Bulunan: {dog_count} kopek",
456
- (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
457
-
458
- # Son tespitler
459
- y_pos = 50
460
- for detection in recent_detections[-2:]:
461
- cv2.putText(frame, detection, (10, y_pos),
462
- cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1)
463
- y_pos += 20
464
-
465
- def _create_map(self):
466
- """Harita oluştur"""
467
- if not self.current_lat:
468
- return "<p>Harita yüklenemedi</p>"
469
-
470
- # Folium harita
471
- m = folium.Map(location=[self.current_lat, self.current_lon], zoom_start=16)
472
-
473
- # Köpek isimleri
474
- dog_names = [dog['name'] for dog in self.current_dogs.values()]
475
- popup_text = f"""
476
- <b>📍 {self.current_video_location}</b><br>
477
- {self.current_video_place}<br><br>
478
- <b>Bu konumdaki köpekler:</b><br>
479
- {'<br>'.join([f'• {name}' for name in dog_names])}
480
  """
481
 
482
- # Marker ekle
 
 
 
 
 
 
483
  folium.Marker(
484
- [self.current_lat, self.current_lon],
485
- popup=folium.Popup(popup_text, max_width=300),
486
  icon=folium.Icon(color='blue', icon='paw', prefix='fa')
487
  ).add_to(m)
488
 
489
  return m._repr_html_()
490
 
491
- def update_threshold(self, value):
492
- """Eşik değerini güncelle"""
493
- self.reid_threshold = value
494
- self.reid.set_threshold(value)
495
-
496
- # Tahmini köpek sayısı
497
- if value < 0.5:
498
- estimate = "~2-4 köpek (benzerler birleşir)"
499
- elif value < 0.7:
500
- estimate = "~4-6 köpek (orta hassasiyet)"
501
- else:
502
- estimate = "~6+ köpek (yüksek hassasiyet)"
503
-
504
- return estimate
505
-
506
- def save_session(self):
507
- """Oturumu kaydet"""
508
- count = len(self.current_dogs)
509
- if count > 0:
510
- return f"✅ {count} köpek veritabanına kaydedildi!"
511
- return "❌ Kaydedilecek köpek bulunamadı"
512
-
513
- def search_dogs(self, search_term):
514
- """Köpek ara"""
515
- df = self.db.get_all_dogs_for_table()
516
- if search_term:
517
- df = df[df['Adı'].str.contains(search_term, case=False, na=False)]
518
- return df
519
-
520
  def create_interface(self):
521
- """Gradio arayüzü"""
 
522
  with gr.Blocks(
523
- title="Sokak Dostları AI",
524
  theme=gr.themes.Soft(),
525
  css="""
526
- .gradio-container {font-family: 'Segoe UI', Tahoma, sans-serif;}
 
 
 
527
  @media (max-width: 768px) {
528
- .gr-button {min-height: 50px !important; font-size: 16px !important;}
529
- .gr-slider {padding: 20px 0 !important;}
 
 
 
 
 
 
530
  }
531
  """
532
  ) as app:
533
 
534
  # Header
535
  gr.Markdown("""
536
- # 🐕 Sokak Dostları AI Tanıma Sistemi
537
 
538
- **Yapay Zeka** ile sokak köpeklerini takip edin:
539
 
540
- - 📹 Videodaki köpekleri otomatik tespit eder
541
- - 🆔 Aynı köpeği farklı videolarda tanır
542
- - 🖼️ Her köpek için profil oluşturur
543
- - 🗺️ Bodrum haritasında yerlerini gösterir
544
- - 📊 Uzun süreli takip için veri saklar
545
  """)
546
 
547
- with gr.Tabs() as tabs:
548
- # Video Tab
549
- with gr.Tab("📹 Video İşle"):
550
- with gr.Accordion("⚡ Hızlı Kullanım", open=False):
551
- gr.Markdown("""
552
- 1. Köpek videosu yükleyin
553
- 2. Tanıma hassasiyetini ayarlayın
554
- 3. İşlenmiş videoyu inceleyin
555
- 4. Veritabanına kaydedin
556
- 5. Haritada konumu görün
 
557
 
558
- **ℹ️ Önemli Notlar:**
559
- - Genel AI modeli kullanılmaktadır, yerel köpek verisi yoktur → doğruluk değişebilir
560
- - Harita konumları **demo için simüle edilmiştir**
561
- - Bazı manuel düzeltmeler gerekebilir
562
 
563
- Bu demo, AI'nın **belediyeler ve STK'lar** için sokak köpeklerini daha verimli takip edebileceğini göstermektedir.
564
- """)
565
-
566
- with gr.Row():
567
- with gr.Column(scale=1):
568
- video_input = gr.Video(label="Video Yükle")
569
-
570
- mirror_fix = gr.Checkbox(label="Kamera ters mi? (Düzelt)", value=False)
571
-
572
- # Mobile-friendly layout
573
- find_btn = gr.Button("🔍 Dostları Bul", variant="primary", size="lg")
574
-
575
- gr.Markdown("### Kaç köpek var?")
576
- threshold_slider = gr.Slider(
577
- 0.4, 0.9, 0.65, step=0.05,
578
- label="Düşük: Benzerler birleşir | Yüksek: Ayrı sayılır",
579
- interactive=True
580
- )
581
- threshold_info = gr.Textbox(
582
- value="~4-6 köpek (orta hassasiyet)",
583
- label="Tahmini köpek sayısı",
584
- interactive=False
585
  )
586
 
587
- save_btn = gr.Button("💾 Kaydet", size="lg")
588
- status = gr.Textbox(label="Durum", lines=2)
 
 
589
 
590
- with gr.Column(scale=2):
591
- live_frame = gr.Image(label="Canlı İşleme", type="numpy")
592
- video_output = gr.Video(label="Final Video", visible=False)
593
- map_display = gr.HTML(label="Konum")
 
 
 
 
 
 
 
 
594
 
595
- # Dog List Tab
596
- with gr.Tab("🐶 Köpek Listesi"):
597
- search_box = gr.Textbox(
598
- label="",
599
- placeholder="Köpek ara...",
600
- interactive=True
601
  )
602
 
603
- dogs_table = gr.Dataframe(
604
- headers=["Fotoğraf", "Adı", "Bulunduğu Yer", "Görülme Sayısı"],
605
- datatype=["image", "str", "str", "number"],
606
- interactive=False,
607
- label="Kayıtlı Köpekler"
 
 
608
  )
609
 
610
- refresh_btn = gr.Button("🔄 Listeyi Yenile")
611
 
612
- # Map Tab
613
- with gr.Tab("🗺Harita"):
614
- map_html = gr.HTML(label="Bodrum Haritası")
615
  gr.Markdown("""
616
- 📍 **Not:** Harita konumları demo için simüle edilmiştir.
617
- Gerçek uygulamada GPS veya sabit kamera konumları kullanılacaktır.
 
 
 
 
 
 
 
 
 
 
618
  """)
619
 
620
- # Event handlers
621
- find_btn.click(
622
- self.process_video_with_live_display,
623
- inputs=[video_input, mirror_fix],
624
- outputs=[live_frame, video_output, dogs_table, status],
625
- show_progress=True
626
- )
 
 
 
 
 
 
 
 
 
627
 
 
 
 
 
 
 
 
 
 
 
 
628
 
629
- threshold_slider.change(
630
- self.update_threshold,
631
- inputs=[threshold_slider],
632
- outputs=[threshold_info]
 
633
  )
634
 
635
- save_btn.click(
636
- self.save_session,
637
- outputs=[status]
 
638
  )
639
 
640
- search_box.submit(
641
- self.search_dogs,
642
- inputs=[search_box],
643
- outputs=[dogs_table]
644
  )
645
 
646
- refresh_btn.click(
647
- lambda: self.db.get_all_dogs_for_table(),
648
- outputs=[dogs_table]
649
  )
650
 
651
- # Auto-refresh map on tab change
652
- tabs.select(
653
- lambda: self._create_map() if self.current_lat else "<p>Henüz video işlenmedi</p>",
654
- outputs=[map_html]
655
  )
656
 
657
  return app
658
 
659
  # ========== LAUNCH ==========
660
  def main():
661
- app = SokakDostlariApp()
 
 
 
 
 
 
 
 
 
 
 
662
  interface = app.create_interface()
663
 
664
- # Hugging Face Spaces settings
665
  interface.launch(
666
  server_name="0.0.0.0",
667
  server_port=7860,
 
1
  """
2
+ sokak_dostlari_app.py - Complete Integrated System
3
+ With pose detection, health assessment, and optimizations
4
  """
5
+
6
  import gradio as gr
7
  import cv2
8
  import numpy as np
9
  import pandas as pd
10
+ import folium
11
  import json
12
  import time
 
13
  from pathlib import Path
14
  from typing import List, Dict, Optional, Tuple
 
 
 
15
  from datetime import datetime
16
+ from ultralytics import YOLO
17
+ import base64
18
 
19
  # Import core modules
20
  from detection import DogDetector
21
  from tracking import SimpleTracker
22
  from reid import SimplifiedDogReID
23
+ from database import DogDatabase
24
+ from health_module import DogHealthAssessment, HealthScore
25
 
26
+ # ========== TURKISH NAMES WITH ENGLISH ALPHABET ==========
27
  class TurkishDogNames:
28
+ """Turkish dog names with English alphabet"""
29
 
30
  NAMES = [
31
+ "Karabas", "Pamuk", "Boncuk", "Comar", "Duman", "Pasa",
32
+ "Aslan", "Komur", "Bal", "Findik", "Zeytin", "Tarcin",
33
+ "Minnos", "Cesur", "Kara", "Beyaz", "Benekli", "Sari",
34
+ "Kuyruk", "Pati", "Sansli", "Dostum", "Kont", "Prenses",
35
+ "Sultan", "Reis", "Kaptan", "Yildiz", "Ay", "Gunes",
36
+ "Bulut", "Ruzgar", "Firtina", "Simsek", "Kar", "Buz",
37
+ "Tatli", "Seker", "Lokum", "Badem", "Ceviz", "Kestane"
38
  ]
39
 
40
  def __init__(self):
41
  self.used_names = set()
42
+ self.name_mapping = {}
43
+
44
  def get_name(self, dog_id: int) -> str:
45
+ if dog_id in self.name_mapping:
46
+ return self.name_mapping[dog_id]
47
+
 
48
  base_name = self.NAMES[dog_id % len(self.NAMES)]
49
 
50
  if base_name in self.used_names:
 
54
  name = f"{base_name} {counter}"
55
  else:
56
  name = base_name
57
+
58
  self.used_names.add(name)
59
+ self.name_mapping[dog_id] = name
60
  return name
61
 
62
  # ========== BODRUM LOCATIONS ==========
63
  class BodrumLocations:
64
+ """Fixed Bodrum locations for demo"""
65
 
66
  LOCATIONS = [
67
  ("Marina Sokak", "Bodrum Marina", 37.0318, 27.4305),
68
+ ("Kale Meydani", "Bodrum Kalesi", 37.0325, 27.4298),
69
+ ("Carsi Ici", "Merkez Carsi", 37.0334, 27.4312),
70
  ("Neyzen Tevfik Caddesi", "Sahil", 37.0341, 27.4289),
71
  ("Cumhuriyet Caddesi", "Merkez", 37.0357, 27.4301),
72
  ("Dr. Alim Bey Caddesi", "Hastane Yolu", 37.0365, 27.4276),
73
+ ("Gumbet Mahallesi", "Gumbet Plaji", 37.0298, 27.4088),
74
+ ("Konacik Merkez", "Konacik", 37.0445, 27.4189)
 
 
75
  ]
76
 
77
  @classmethod
78
+ def get_random_location(cls):
79
+ import random
80
  return random.choice(cls.LOCATIONS)
81
 
82
+ # ========== MAIN APPLICATION ==========
83
+ class SokakDostlariAI:
84
+ """Complete Sokak Dostları AI System"""
85
 
86
+ def __init__(self):
87
+ # Core AI components
88
+ self.detector = DogDetector(device='cuda', confidence_threshold=0.4)
89
+ self.tracker = SimpleTracker()
90
+ self.reid = SimplifiedDogReID(device='cuda', similarity_threshold=0.65)
91
 
92
+ # Load pose model
93
+ try:
94
+ self.pose_model = YOLO('dog-pose-trained.pt')
95
+ self.pose_available = True
96
+ print("Dog pose model loaded")
97
+ except:
98
+ try:
99
+ self.pose_model = YOLO('yolov8m-pose.pt')
100
+ self.pose_available = True
101
+ print("Using default pose model")
102
+ except:
103
+ self.pose_available = False
104
+ print("Pose model not available")
105
+
106
+ if self.pose_available:
107
+ self.pose_model.to('cuda')
108
+
109
+ # Helper modules
110
+ self.db = DogDatabase()
111
+ self.health_module = DogHealthAssessment()
112
+ self.name_gen = TurkishDogNames()
113
 
114
+ # Session state
115
+ self.current_dogs = {}
116
+ self.current_location = None
117
+ self.processing_active = False
118
 
119
+ # Dog pose skeleton connections
120
+ self.dog_skeleton = [
121
+ [0, 1], [1, 2], [2, 3], # Head
122
+ [3, 4], [4, 5], [5, 6], # Spine
123
+ [5, 7], [7, 9], # Front left
124
+ [6, 8], [8, 10], # Front right
125
+ [11, 13], [13, 15], # Back left
126
+ [12, 14], [14, 16] # Back right
127
+ ]
128
+
129
+ # Performance settings
130
+ self.process_every_n = 10 # Process every 10th frame
131
+ self.pose_every_n = 15 # Run pose every 15th frame
132
+
133
+ def optimize_video(self, video_path: str) -> str:
134
+ """Optimize video for faster processing"""
135
+ cap = cv2.VideoCapture(video_path)
136
 
137
+ fps = int(cap.get(cv2.CAP_PROP_FPS))
138
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
139
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
140
 
141
+ # Target: 854x480 @ 15fps
142
+ if width > 854 or height > 480 or fps > 15:
143
+ scale = min(854/width, 480/height, 1.0)
144
+ new_width = int(width * scale)
145
+ new_height = int(height * scale)
146
+ new_fps = min(fps, 15)
 
 
 
147
 
148
+ output_path = "optimized_temp.mp4"
149
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
150
+ out = cv2.VideoWriter(output_path, fourcc, new_fps, (new_width, new_height))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
+ frame_skip = max(1, fps // new_fps)
153
+ frame_count = 0
 
 
154
 
155
+ while True:
156
+ ret, frame = cap.read()
157
+ if not ret:
158
+ break
159
+
160
+ if frame_count % frame_skip == 0:
161
+ resized = cv2.resize(frame, (new_width, new_height))
162
+ out.write(resized)
163
+
164
+ frame_count += 1
 
 
 
165
 
166
+ cap.release()
167
+ out.release()
168
+ return output_path
 
 
 
169
 
170
+ cap.release()
171
+ return video_path
172
 
173
+ def draw_pose_skeleton(self, frame: np.ndarray, keypoints: np.ndarray,
174
+ bbox: List[float], color: Tuple = None):
175
+ """Draw pose skeleton on frame"""
176
+ if keypoints is None or len(keypoints) < 17:
177
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
+ x1, y1 = int(bbox[0]), int(bbox[1])
180
+
181
+ # Use health color if not specified
182
+ if color is None:
183
+ color = (255, 0, 255) # Default magenta
184
+
185
+ # Draw connections
186
+ for connection in self.dog_skeleton:
187
+ if connection[0] < len(keypoints) and connection[1] < len(keypoints):
188
+ kp1 = keypoints[connection[0]]
189
+ kp2 = keypoints[connection[1]]
190
+
191
+ if kp1[0] > 0 and kp1[1] > 0 and kp2[0] > 0 and kp2[1] > 0:
192
+ pt1 = (int(kp1[0] + x1), int(kp1[1] + y1))
193
+ pt2 = (int(kp2[0] + x1), int(kp2[1] + y1))
194
+ cv2.line(frame, pt1, pt2, color, 1)
195
+
196
+ # Draw keypoints
197
+ for kp in keypoints:
198
+ if kp[0] > 0 and kp[1] > 0:
199
+ cv2.circle(frame, (int(kp[0] + x1), int(kp[1] + y1)), 2, color, -1)
200
 
201
+ def draw_detection_info(self, frame: np.ndarray, bbox: List[float],
202
+ name: str, health_score: HealthScore,
203
+ is_new: bool = False):
204
+ """Draw bounding box with dog info"""
205
+ x1, y1, x2, y2 = map(int, bbox)
206
 
207
+ # Draw bounding box with health color
208
+ cv2.rectangle(frame, (x1, y1), (x2, y2), health_score.color, 2)
 
 
209
 
210
+ # Prepare text
211
+ name_text = f"Adi: {name}"
212
+ health_text = f"Saglik: {health_score.score_text}"
213
+
214
+ # Draw name
215
+ name_size = cv2.getTextSize(name_text, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2)[0]
216
+ cv2.rectangle(frame, (x1, y1 - 35), (x1 + name_size[0] + 10, y1), (0, 0, 0), -1)
217
+ cv2.putText(frame, name_text, (x1 + 5, y1 - 15),
218
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
219
 
220
+ # Draw health score
221
+ cv2.putText(frame, health_text, (x1 + 5, y1 - 5),
222
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, health_score.color, 2)
223
+
224
+ # Add "Yeni" tag if new dog
225
+ if is_new:
226
+ cv2.putText(frame, "YENI", (x2 - 50, y1 + 20),
227
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
228
+
229
  def process_video_with_live_display(self, video_path: str, mirror_fix: bool = False):
230
+ """Process video with live display of detections"""
231
  if not video_path:
232
+ yield None, "Video yuklenmedi", pd.DataFrame()
233
  return
234
 
235
+ # Reset state
236
  self.tracker = SimpleTracker()
237
  self.reid.reset()
238
  self.current_dogs = {}
 
239
  self.processing_active = True
240
 
241
+ # Choose single location for entire video
242
  street, place, lat, lon = BodrumLocations.get_random_location()
243
+ self.current_location = f"{street}, {place}"
244
+
245
+ # Optimize video
246
+ optimized_path = self.optimize_video(video_path)
247
 
248
  # Open video
249
+ cap = cv2.VideoCapture(optimized_path)
250
+ cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
251
+
252
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
253
  fps = cap.get(cv2.CAP_PROP_FPS)
 
 
254
  width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
255
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
256
+
257
+ # Output video
258
+ output_path = "output_processed.mp4"
259
  fourcc = cv2.VideoWriter_fourcc(*'mp4v')
260
  out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
261
 
262
  frame_count = 0
263
+ detected_count = 0
264
 
265
+ while self.processing_active and frame_count < total_frames:
266
  ret, frame = cap.read()
267
  if not ret:
268
  break
 
271
  frame = cv2.flip(frame, 1)
272
 
273
  frame_count += 1
 
274
 
275
+ # Process every Nth frame
276
+ if frame_count % self.process_every_n == 0:
277
+ # Detect dogs
278
  detections = self.detector.detect(frame)
279
+
280
+ # Update tracking
281
  tracks = self.tracker.update(detections)
282
 
283
  for track in tracks:
 
284
  bbox = track.bbox
285
  x1, y1, x2, y2 = map(int, bbox)
 
286
 
287
+ # Get best detection with image
288
+ detection = None
289
+ for det in reversed(track.detections):
290
  if det.image_crop is not None:
291
+ detection = det
292
+ break
293
 
294
+ if not detection:
295
+ continue
296
+
297
+ # ReID
298
+ dog_id, confidence = self.reid.match_or_register(track)
299
+
300
+ if dog_id > 0:
301
+ # Get or assign name
302
+ name = self.name_gen.get_name(dog_id)
303
 
304
+ # Pose detection (if available and on pose frame)
305
+ keypoints = None
306
+ if self.pose_available and frame_count % self.pose_every_n == 0:
307
+ try:
308
+ pose_results = self.pose_model(detection.image_crop)
309
+ if pose_results and len(pose_results) > 0:
310
+ if hasattr(pose_results[0], 'keypoints'):
311
+ kpts = pose_results[0].keypoints
312
+ if kpts is not None:
313
+ keypoints = kpts.xy[0].cpu().numpy()
314
+ except:
315
+ pass
316
 
317
+ # Health assessment
318
+ center_x = (bbox[0] + bbox[2]) / 2
319
+ center_y = (bbox[1] + bbox[3]) / 2
320
+
321
+ health_score = self.health_module.calculate_overall_health(
322
+ dog_id=dog_id,
323
+ keypoints=keypoints,
324
+ dog_crop=detection.image_crop,
325
+ bbox=bbox,
326
+ current_pos=(center_x, center_y)
327
+ )
328
+
329
+ # Check if new dog
330
+ is_new = dog_id not in self.current_dogs
331
+
332
+ if is_new:
333
+ detected_count += 1
334
+ # Add to database
335
+ self.db.add_dog(dog_id, name)
336
+
337
+ # Update sighting
338
+ self.db.update_dog_sighting(dog_id)
339
+
340
+ # Save image
341
+ image_id = self.db.save_image(
342
+ dog_id=dog_id,
343
+ image=detection.image_crop,
344
+ frame_number=frame_count,
345
+ video_source=video_path,
346
+ bbox=bbox,
347
+ confidence=confidence,
348
+ pose_keypoints=keypoints.tolist() if keypoints is not None else None
349
+ )
350
+
351
+ # Save features
352
+ if hasattr(self.reid, 'dog_database') and dog_id in self.reid.dog_database:
353
+ features = self.reid.dog_database[dog_id][-1]
354
+ if hasattr(features, 'full_features'):
355
+ self.db.save_features(
356
+ dog_id=dog_id,
357
+ resnet_features=features.full_features,
358
+ color_histogram=features.color_histogram if hasattr(features, 'color_histogram') else np.zeros(170),
359
+ confidence=confidence
360
+ )
361
+
362
+ # Add sighting
363
+ self.db.add_sighting(
364
+ dog_id=dog_id,
365
+ position=(center_x, center_y),
366
+ video_source=video_path,
367
+ frame_number=frame_count,
368
+ confidence=confidence
369
+ )
370
+
371
+ # Draw on frame
372
+ self.draw_detection_info(frame, bbox, name, health_score, is_new)
373
+
374
+ # Draw pose if available
375
+ if keypoints is not None:
376
+ self.draw_pose_skeleton(frame, keypoints, bbox, health_score.color)
377
+
378
+ # Store in current dogs
379
+ self.current_dogs[dog_id] = {
380
+ 'name': name,
381
+ 'health_score': health_score.score,
382
+ 'health_status': health_score.status,
383
+ 'location': self.current_location
384
+ }
385
+
386
+ # Add overlay with statistics
387
+ overlay_height = 60
388
+ overlay = np.zeros((overlay_height, width, 3), dtype=np.uint8)
389
 
390
+ # Text info
391
+ info_text = f"Kopek Sayisi: {len(self.current_dogs)} | Konum: {self.current_location}"
392
+ cv2.putText(overlay, info_text, (10, 25),
393
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
394
 
395
+ progress_text = f"Kare: {frame_count}/{total_frames}"
396
+ cv2.putText(overlay, progress_text, (10, 45),
397
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 200), 1)
398
 
399
+ # Add overlay to frame top
400
+ frame[:overlay_height, :] = cv2.addWeighted(frame[:overlay_height, :], 0.3, overlay, 0.7, 0)
401
+
402
+ # Write to output video
403
  out.write(frame)
404
 
405
+ # Yield for live display (every 5 frames)
406
+ if frame_count % 5 == 0:
407
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
408
+ status = f"Isleniyor: %{int(frame_count/total_frames*100)} | Bulunan: {len(self.current_dogs)} kopek"
409
+
410
+ # Create simple dataframe
411
+ dogs_data = []
412
+ for dog_id, info in self.current_dogs.items():
413
+ dogs_data.append({
414
+ 'Adi': info['name'],
415
+ 'Saglik': f"{info['health_score']}/10",
416
+ 'Durum': info['health_status'],
417
+ 'Konum': info['location']
418
+ })
419
+ df = pd.DataFrame(dogs_data) if dogs_data else pd.DataFrame()
420
+
421
+ yield frame_rgb, status, df
422
 
423
  # Cleanup
424
  cap.release()
425
  out.release()
426
 
427
+ # Final status
428
+ final_status = f"✅ Tamamlandi! {len(self.current_dogs)} kopek bulundu | Konum: {self.current_location}"
429
+
430
+ # Create final dataframe
431
+ final_dogs_data = []
432
+ for dog_id, info in self.current_dogs.items():
433
+ final_dogs_data.append({
434
+ 'Adi': info['name'],
435
+ 'Saglik': f"{info['health_score']}/10",
436
+ 'Durum': info['health_status'],
437
+ 'Konum': info['location']
438
+ })
439
+ final_df = pd.DataFrame(final_dogs_data) if final_dogs_data else pd.DataFrame()
440
 
441
+ # Loop the processed video
442
+ yield output_path, final_status, final_df
443
 
444
+ def create_map_html(self) -> str:
445
+ """Create HTML map with dog locations"""
446
+ if not self.current_location or not self.current_dogs:
447
+ return "<p>Henuz veri yok</p>"
448
+
449
+ # Get location coordinates
450
+ for loc in BodrumLocations.LOCATIONS:
451
+ if loc[0] in self.current_location:
452
+ lat, lon = loc[2], loc[3]
453
+ break
454
  else:
455
+ lat, lon = 37.0334, 27.4312 # Default center
456
+
457
+ # Create map
458
+ m = folium.Map(location=[lat, lon], zoom_start=16)
459
+
460
+ # Create popup content
461
+ popup_html = f"""
462
+ <div style="width: 200px;">
463
+ <h4>{self.current_location}</h4>
464
+ <p><b>Bulunan Kopekler ({len(self.current_dogs)}):</b></p>
465
+ <ul>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
  """
467
 
468
+ for dog_id, info in self.current_dogs.items():
469
+ color = "🟢" if info['health_score'] >= 7 else "🟡" if info['health_score'] >= 5 else "🔴"
470
+ popup_html += f"<li>{info['name']} {color} ({info['health_score']}/10)</li>"
471
+
472
+ popup_html += "</ul></div>"
473
+
474
+ # Add marker
475
  folium.Marker(
476
+ [lat, lon],
477
+ popup=folium.Popup(popup_html, max_width=300),
478
  icon=folium.Icon(color='blue', icon='paw', prefix='fa')
479
  ).add_to(m)
480
 
481
  return m._repr_html_()
482
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  def create_interface(self):
484
+ """Create Gradio interface optimized for mobile"""
485
+
486
  with gr.Blocks(
487
+ title="Sokak Dostlari AI",
488
  theme=gr.themes.Soft(),
489
  css="""
490
+ .gradio-container {
491
+ max-width: 100% !important;
492
+ padding: 10px !important;
493
+ }
494
  @media (max-width: 768px) {
495
+ .gr-button {
496
+ min-height: 50px !important;
497
+ font-size: 16px !important;
498
+ margin: 5px 0 !important;
499
+ }
500
+ #single_video {
501
+ max-height: 50vh !important;
502
+ }
503
  }
504
  """
505
  ) as app:
506
 
507
  # Header
508
  gr.Markdown("""
509
+ # 🐕 Sokak Dostlari AI Tanima Sistemi
510
 
511
+ **Yapay Zeka** ile sokak kopeklerini takip edin:
512
 
513
+ - 📹 Videodaki kopekleri otomatik tespit eder
514
+ - 🆔 Ayni kopegi farkli videolarda tanir
515
+ - 🏥 Saglik durumunu degerlendirir
516
+ - 🗺️ Bodrum haritasinda yerlerini gosterir
 
517
  """)
518
 
519
+ with gr.Tabs():
520
+ # Video Processing Tab
521
+ with gr.Tab("📹 Video Isle"):
522
+ with gr.Column():
523
+ # Single video component
524
+ video_display = gr.Video(
525
+ label="Video",
526
+ elem_id="single_video",
527
+ autoplay=True,
528
+ loop=True
529
+ )
530
 
531
+ status_text = gr.Textbox(
532
+ label="Durum",
533
+ value="Video yukleyin"
534
+ )
535
 
536
+ # Controls
537
+ with gr.Row():
538
+ upload_btn = gr.UploadButton(
539
+ "📁 Video Yukle",
540
+ file_types=["video"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  )
542
 
543
+ mirror_checkbox = gr.Checkbox(
544
+ label="Ters Duzelt",
545
+ value=False
546
+ )
547
 
548
+ find_btn = gr.Button(
549
+ "🔍 Dostlari Bul",
550
+ variant="primary",
551
+ size="lg"
552
+ )
553
+
554
+ # Dog table
555
+ with gr.Accordion("Bulunan Kopekler", open=False):
556
+ dogs_table = gr.Dataframe(
557
+ headers=["Adi", "Saglik", "Durum", "Konum"],
558
+ datatype=["str", "str", "str", "str"]
559
+ )
560
 
561
+ # Map Tab
562
+ with gr.Tab("🗺️ Harita"):
563
+ map_html = gr.HTML(
564
+ label="Kopek Konumlari"
 
 
565
  )
566
 
567
+ refresh_map_btn = gr.Button("🔄 Haritayi Guncelle")
568
+
569
+ # Statistics Tab
570
+ with gr.Tab("📊 Istatistikler"):
571
+ stats_text = gr.Textbox(
572
+ label="Veritabani Istatistikleri",
573
+ lines=10
574
  )
575
 
576
+ refresh_stats_btn = gr.Button("🔄 Istatistikleri Guncelle")
577
 
578
+ # Help Tab
579
+ with gr.Tab("Yardim"):
 
580
  gr.Markdown("""
581
+ ### Hizli Kullanim
582
+ 1. Kopek videosu yukleyin
583
+ 2. "Dostlari Bul" butonuna tiklayin
584
+ 3. Islenirken kopeklerin tespit edilisini izleyin
585
+ 4. Harita sekmesinde konumlari gorun
586
+
587
+ ### 📝 Onemli Notlar
588
+ - Genel AI modeli kullanilmaktadir
589
+ - Harita konumlari demo icin simuledir
590
+ - Saglik degerlendirmesi tahminidir
591
+
592
+ ✅ Bu demo, belediyelerin sokak kopeklerini daha verimli takip edebilecegini gostermektedir.
593
  """)
594
 
595
+ # Event Handlers
596
+ def handle_upload(file):
597
+ if file:
598
+ return file.name, "Video yuklendi, 'Dostlari Bul' tiklayin"
599
+ return None, "Video yukleyin"
600
+
601
+ def process_video(video_path, mirror_fix):
602
+ if not video_path:
603
+ yield None, "Lutfen once video yukleyin", pd.DataFrame()
604
+ return
605
+
606
+ for result in self.process_video_with_live_display(video_path, mirror_fix):
607
+ if isinstance(result[0], str): # Final result with video path
608
+ yield result[0], result[1], result[2]
609
+ else: # Live frame
610
+ yield result[0], result[1], result[2]
611
 
612
+ def get_stats():
613
+ stats = self.db.get_dog_statistics()
614
+ return f"""
615
+ 📊 Veritabani Istatistikleri:
616
+
617
+ Toplam Aktif Kopek: {stats.get('total_active_dogs', 0)}
618
+ Toplam Goruntu: {stats.get('total_images', 0)}
619
+ Toplam Goruntulenme: {stats.get('total_sightings', 0)}
620
+
621
+ En Cok Gorulen: {stats.get('most_seen_dog', {}).get('name', 'Yok')}
622
+ """
623
 
624
+ # Connect events
625
+ upload_btn.upload(
626
+ handle_upload,
627
+ inputs=[upload_btn],
628
+ outputs=[video_display, status_text]
629
  )
630
 
631
+ find_btn.click(
632
+ process_video,
633
+ inputs=[video_display, mirror_checkbox],
634
+ outputs=[video_display, status_text, dogs_table]
635
  )
636
 
637
+ refresh_map_btn.click(
638
+ self.create_map_html,
639
+ outputs=[map_html]
 
640
  )
641
 
642
+ refresh_stats_btn.click(
643
+ get_stats,
644
+ outputs=[stats_text]
645
  )
646
 
647
+ # Load initial stats
648
+ app.load(
649
+ get_stats,
650
+ outputs=[stats_text]
651
  )
652
 
653
  return app
654
 
655
  # ========== LAUNCH ==========
656
  def main():
657
+ """Main entry point"""
658
+
659
+ # First, update database with health fields (run once)
660
+ try:
661
+ from database_health_update import add_health_fields_to_database
662
+ add_health_fields_to_database()
663
+ print("Database updated with health fields")
664
+ except Exception as e:
665
+ print(f"Database update info: {e}")
666
+
667
+ # Create and launch app
668
+ app = SokakDostlariAI()
669
  interface = app.create_interface()
670
 
671
+ # Launch settings optimized for Hugging Face Spaces
672
  interface.launch(
673
  server_name="0.0.0.0",
674
  server_port=7860,