mustafa2ak commited on
Commit
67fefa1
·
verified ·
1 Parent(s): 11582fb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +882 -106
app.py CHANGED
@@ -1,123 +1,899 @@
1
- def create_visualization_video(self, video_path: str, sample_rate: int) -> str:
2
- """Create visualization video with colored tracking boxes and IDs"""
3
- try:
4
- cap = cv2.VideoCapture(video_path)
5
- if not cap.isOpened():
6
- print("ERROR: Cannot open input video")
7
- return None
8
-
9
- fps = cap.get(cv2.CAP_PROP_FPS) or 30
10
- width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
11
- height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
12
-
13
- output_path = f"visualization_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
14
-
15
- # Try multiple codecs in order of preference
16
- codecs = ['mp4v', 'XVID', 'MJPG']
17
- out = None
18
-
19
- for codec in codecs:
20
- fourcc = cv2.VideoWriter_fourcc(*codec)
21
- out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
22
-
23
- if out.isOpened():
24
- print(f"Using codec: {codec}")
25
- break
26
- else:
27
- out.release()
28
- out = None
29
-
30
- # If no codec worked, return None
31
- if out is None or not out.isOpened():
32
- print("ERROR: Could not initialize VideoWriter with any codec")
33
- cap.release()
34
- return None
35
-
36
- viz_tracker = DeepSORTTracker(
 
 
 
 
 
 
 
 
 
37
  max_iou_distance=0.5,
38
  max_age=90,
39
  n_init=1,
40
- use_appearance=False
41
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
- track_colors = {}
44
- frame_num = 0
45
 
46
- print("\nCreating visualization video...")
 
 
47
 
48
- while cap.isOpened():
49
- ret, frame = cap.read()
50
- if not ret:
51
- break
52
-
53
- if frame_num % sample_rate == 0:
54
- detections = self.detector.detect(frame)
55
- tracks = viz_tracker.update(detections)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- for track in tracks:
58
- if track.track_id not in track_colors:
59
- track_colors[track.track_id] = (
60
- np.random.randint(50, 255),
61
- np.random.randint(50, 255),
62
- np.random.randint(50, 255)
63
- )
64
 
65
- x1, y1, x2, y2 = map(int, track.bbox)
66
- color = track_colors[track.track_id]
 
 
 
67
 
68
- # Bold box
69
- cv2.rectangle(frame, (x1, y1), (x2, y2), color, 6)
70
 
71
- # Add ID label
72
- label = f"ID: {track.track_id}"
73
- font = cv2.FONT_HERSHEY_SIMPLEX
74
- font_scale = 1.2
75
- font_thickness = 3
 
 
 
 
 
 
 
 
76
 
77
- (text_width, text_height), baseline = cv2.getTextSize(
78
- label, font, font_scale, font_thickness
79
- )
 
 
80
 
81
- # Background rectangle for text
82
- cv2.rectangle(
83
- frame,
84
- (x1, y1 - text_height - 10),
85
- (x1 + text_width + 10, y1),
86
- color,
87
- -1
88
- )
89
-
90
- # White text
91
- cv2.putText(
92
- frame,
93
- label,
94
- (x1 + 5, y1 - 5),
95
- font,
96
- font_scale,
97
- (255, 255, 255),
98
- font_thickness,
99
- cv2.LINE_AA
100
- )
101
 
102
- out.write(frame)
103
- frame_num += 1
 
 
104
 
105
- if frame_num % 30 == 0:
106
- print(f"Visualization progress: {frame_num} frames")
107
-
108
- cap.release()
109
- out.release()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- # Verify file was created and has content
112
- if os.path.exists(output_path) and os.path.getsize(output_path) > 1000:
113
- print(f"Visualization video saved: {output_path}")
114
- return output_path
115
- else:
116
- print("ERROR: Video file not created or is empty")
117
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
- except Exception as e:
120
- print(f"Visualization video error: {e}")
121
- import traceback
122
- traceback.print_exc()
123
- return None
 
 
 
 
 
 
 
1
+ """
2
+ Simplified Dog Tracking for Training Dataset Collection
3
+ - Process video with adjustable threshold
4
+ - Temporary storage with discard option
5
+ - Manual validation with checkbox selection per image
6
+ - Export to folder structure for fine-tuning
7
+ - Download to laptop as ZIP
8
+ - Automatic HuggingFace backup/restore
9
+ - Visualization video with colored tracking boxes
10
+ """
11
+ import os
12
+ os.environ["OMP_NUM_THREADS"] = "1"
13
+
14
+ import zipfile
15
+ import tempfile
16
+ import gradio as gr
17
+ import cv2
18
+ import numpy as np
19
+ import torch
20
+ from typing import Dict, List
21
+ import gc
22
+ import base64
23
+ from io import BytesIO
24
+ from PIL import Image
25
+ from pathlib import Path
26
+ import json
27
+ from datetime import datetime
28
+
29
+ from detection import DogDetector
30
+ from tracking import DeepSORTTracker
31
+ from reid import SimplifiedReID
32
+ from database import DogDatabase
33
+
34
+
35
+ class DatasetCollectionApp:
36
+ """Simplified app for collecting training datasets"""
37
+
38
+ def __init__(self):
39
+ device = 'cuda' if torch.cuda.is_available() else 'cpu'
40
+
41
+ # Restore database before initializing
42
+ self._restore_database()
43
+
44
+ self.detector = DogDetector(device=device)
45
+ self.tracker = DeepSORTTracker(
46
  max_iou_distance=0.5,
47
  max_age=90,
48
  n_init=1,
49
+ use_appearance=True
50
  )
51
+ self.reid = SimplifiedReID(device=device)
52
+ self.db = DogDatabase('dog_monitoring.db')
53
+
54
+ # Temporary session storage (in-memory)
55
+ self.temp_session = {}
56
+ self.current_video_path = None
57
+ self.is_processing = False
58
+
59
+ # Validation state: stores checkbox states for each temp_id
60
+ self.validation_data = {} # {temp_id: [bool, bool, ...]}
61
+
62
+ print("Dataset Collection App initialized")
63
+ print(f"Database has {len(self.db.get_all_dogs())} dogs")
64
+
65
+ def create_visualization_video(self, video_path: str, sample_rate: int) -> str:
66
+ """Create visualization video with colored tracking boxes and IDs"""
67
+ try:
68
+ cap = cv2.VideoCapture(video_path)
69
+ if not cap.isOpened():
70
+ print("ERROR: Cannot open input video")
71
+ return None
72
+
73
+ fps = cap.get(cv2.CAP_PROP_FPS) or 30
74
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
75
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
76
+
77
+ output_path = f"visualization_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
78
+
79
+ # Try multiple codecs in order of preference
80
+ codecs = ['mp4v', 'XVID', 'MJPG']
81
+ out = None
82
+
83
+ for codec in codecs:
84
+ fourcc = cv2.VideoWriter_fourcc(*codec)
85
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
86
+
87
+ if out.isOpened():
88
+ print(f"Using codec: {codec}")
89
+ break
90
+ else:
91
+ out.release()
92
+ out = None
93
+
94
+ # If no codec worked, return None
95
+ if out is None or not out.isOpened():
96
+ print("ERROR: Could not initialize VideoWriter with any codec")
97
+ cap.release()
98
+ return None
99
+
100
+ viz_tracker = DeepSORTTracker(
101
+ max_iou_distance=0.5,
102
+ max_age=90,
103
+ n_init=1,
104
+ use_appearance=False
105
+ )
106
+
107
+ track_colors = {}
108
+ frame_num = 0
109
+
110
+ print("\nCreating visualization video...")
111
+
112
+ while cap.isOpened():
113
+ ret, frame = cap.read()
114
+ if not ret:
115
+ break
116
+
117
+ if frame_num % sample_rate == 0:
118
+ detections = self.detector.detect(frame)
119
+ tracks = viz_tracker.update(detections)
120
+
121
+ for track in tracks:
122
+ if track.track_id not in track_colors:
123
+ track_colors[track.track_id] = (
124
+ np.random.randint(50, 255),
125
+ np.random.randint(50, 255),
126
+ np.random.randint(50, 255)
127
+ )
128
+
129
+ x1, y1, x2, y2 = map(int, track.bbox)
130
+ color = track_colors[track.track_id]
131
+
132
+ # Bold box (thickness = 6)
133
+ cv2.rectangle(frame, (x1, y1), (x2, y2), color, 6)
134
+
135
+ # Add ID label
136
+ label = f"ID: {track.track_id}"
137
+ font = cv2.FONT_HERSHEY_SIMPLEX
138
+ font_scale = 1.2
139
+ font_thickness = 3
140
+
141
+ (text_width, text_height), baseline = cv2.getTextSize(
142
+ label, font, font_scale, font_thickness
143
+ )
144
+
145
+ # Background rectangle for text
146
+ cv2.rectangle(
147
+ frame,
148
+ (x1, y1 - text_height - 10),
149
+ (x1 + text_width + 10, y1),
150
+ color,
151
+ -1
152
+ )
153
+
154
+ # White text
155
+ cv2.putText(
156
+ frame,
157
+ label,
158
+ (x1 + 5, y1 - 5),
159
+ font,
160
+ font_scale,
161
+ (255, 255, 255),
162
+ font_thickness,
163
+ cv2.LINE_AA
164
+ )
165
+
166
+ out.write(frame)
167
+ frame_num += 1
168
+
169
+ if frame_num % 30 == 0:
170
+ print(f"Visualization progress: {frame_num} frames")
171
+
172
+ cap.release()
173
+ out.release()
174
+
175
+ # Verify file was created and has content
176
+ if os.path.exists(output_path) and os.path.getsize(output_path) > 1000:
177
+ print(f"Visualization video saved: {output_path}")
178
+ return output_path
179
+ else:
180
+ print("ERROR: Video file not created or is empty")
181
+ return None
182
+
183
+ except Exception as e:
184
+ print(f"Visualization video error: {e}")
185
+ import traceback
186
+ traceback.print_exc()
187
+ return None
188
+
189
+ def stop_processing(self):
190
+ """Stop video processing"""
191
+ if self.is_processing:
192
+ self.is_processing = False
193
+ return "İşlem kullanıcı tarafından durduruldu", "Durduruldu", None
194
+ else:
195
+ return "Durdurulaack işlem yok", "İşlem yapılmıyor", None
196
+
197
+ def clear_reset(self):
198
+ """Clear all temporary data and reset UI"""
199
+ self.temp_session.clear()
200
+ self.tracker.reset()
201
+ self.reid.reset_session()
202
+ self.current_video_path = None
203
+ self.validation_data = {}
204
+
205
+ gc.collect()
206
+ if torch.cuda.is_available():
207
+ torch.cuda.empty_cache()
208
+
209
+ return (
210
+ None, # Clear video
211
+ "<p style='text-align:center; color:#868e96;'>Oturum temizlendi. Başlamak için yeni bir video yükleyin.</p>",
212
+ "",
213
+ "",
214
+ gr.update(visible=False)
215
+ )
216
+
217
+ def discard_session(self):
218
+ """Discard temporary session completely"""
219
+ count = len(self.temp_session)
220
+ self.temp_session.clear()
221
+ self.tracker.reset()
222
+ self.reid.reset_session()
223
+ self.validation_data = {}
224
+
225
+ gc.collect()
226
+ if torch.cuda.is_available():
227
+ torch.cuda.empty_cache()
228
+
229
+ return (
230
+ gr.update(visible=False),
231
+ f"{count} geçici köpek iptal edildi. Farklı bir eşik deneyin.",
232
+ gr.update(visible=False)
233
+ )
234
+
235
+ def process_video(self, video_path: str, reid_threshold: float, sample_rate: int):
236
+ """Process video and store in temporary session"""
237
+
238
+ if not video_path:
239
+ return None, "Lütfen bir video yükleyin", "", gr.update(visible=False), None
240
+
241
+ self.is_processing = True
242
+ self.current_video_path = video_path
243
+ self.temp_session.clear()
244
+ self.validation_data = {}
245
+
246
+ # Set threshold
247
+ self.reid.set_threshold(reid_threshold)
248
+ self.reid.set_video_source(video_path)
249
+
250
+ # Reset tracker and reid
251
+ self.tracker.reset()
252
+ self.reid.reset_session()
253
+
254
+ try:
255
+ cap = cv2.VideoCapture(video_path)
256
+ if not cap.isOpened():
257
+ return None, "Video açılamıyor", "", gr.update(visible=False), None
258
+
259
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
260
+ fps = cap.get(cv2.CAP_PROP_FPS) or 30
261
+
262
+ frame_num = 0
263
+ processed = 0
264
+
265
+ # Temporary storage
266
+ temp_dogs = {}
267
+
268
+ # Calculate minimum frame gap for diversity
269
+ min_frame_gap = max(15, total_frames // 45)
270
+
271
+ # Quality tracking
272
+ blur_rejected = 0
273
+
274
+ print(f"\nProcessing video: {total_frames} frames, {fps} fps")
275
+ print(f"Minimum frame gap: {min_frame_gap} frames")
276
+
277
+ while cap.isOpened() and self.is_processing:
278
+ ret, frame = cap.read()
279
+ if not ret:
280
+ break
281
+
282
+ if frame_num % sample_rate == 0:
283
+ # Detect
284
+ detections = self.detector.detect(frame)
285
+
286
+ # Track
287
+ tracks = self.tracker.update(detections)
288
+
289
+ # ReID
290
+ for track in tracks:
291
+ if not self.is_processing:
292
+ break
293
+
294
+ result = self.reid.match_or_register(track)
295
+ temp_id = result['temp_id']
296
+
297
+ if temp_id == 0:
298
+ continue
299
+
300
+ # Initialize temp dog if new
301
+ if temp_id not in temp_dogs:
302
+ temp_dogs[temp_id] = {
303
+ 'images': [],
304
+ 'timestamps': [],
305
+ 'confidences': [],
306
+ 'bboxes': [],
307
+ 'frame_numbers': [],
308
+ 'last_captured_frame': -1
309
+ }
310
+
311
+ # CHECK 1: Already have 30 images?
312
+ if len(temp_dogs[temp_id]['images']) >= 30:
313
+ continue
314
+
315
+ # CHECK 2: Has enough frames passed since last capture?
316
+ frames_since_last = frame_num - temp_dogs[temp_id]['last_captured_frame']
317
+ if frames_since_last < min_frame_gap:
318
+ continue
319
+
320
+ # Get image from detection
321
+ image_crop = None
322
+ for det in reversed(track.detections[-3:]):
323
+ if det.image_crop is not None:
324
+ image_crop = det.image_crop
325
+ break
326
+
327
+ if image_crop is None:
328
+ continue
329
+
330
+ # CHECK 3: Blur detection
331
+ gray = cv2.cvtColor(image_crop, cv2.COLOR_BGR2GRAY)
332
+ laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
333
+
334
+ if laplacian_var < 75:
335
+ blur_rejected += 1
336
+ continue
337
+
338
+ # All checks passed - store image
339
+ temp_dogs[temp_id]['images'].append(image_crop.copy())
340
+ temp_dogs[temp_id]['timestamps'].append(frame_num / fps)
341
+ temp_dogs[temp_id]['confidences'].append(det.confidence)
342
+ temp_dogs[temp_id]['bboxes'].append(det.bbox)
343
+ temp_dogs[temp_id]['frame_numbers'].append(frame_num)
344
+ temp_dogs[temp_id]['last_captured_frame'] = frame_num
345
+
346
+ processed += 1
347
+
348
+ frame_num += 1
349
+
350
+ if frame_num % 30 == 0:
351
+ progress = int((frame_num / total_frames) * 100)
352
+ print(f"Progress: {progress}%")
353
+
354
+ cap.release()
355
+
356
+ # Clean up temporary tracking data
357
+ for temp_id in list(temp_dogs.keys()):
358
+ if 'last_captured_frame' in temp_dogs[temp_id]:
359
+ del temp_dogs[temp_id]['last_captured_frame']
360
+
361
+ # FILTER: Remove dogs with fewer than 14 images
362
+ original_count = len(temp_dogs)
363
+ discarded_ids = []
364
+
365
+ for temp_id in list(temp_dogs.keys()):
366
+ if len(temp_dogs[temp_id]['images']) < 14:
367
+ discarded_ids.append(temp_id)
368
+ del temp_dogs[temp_id]
369
+
370
+ discarded_count = len(discarded_ids)
371
+
372
+ # Store in temp session
373
+ self.temp_session = temp_dogs
374
+
375
+ # Initialize validation data (all images selected by default)
376
+ for temp_id in temp_dogs.keys():
377
+ self.validation_data[temp_id] = [True] * len(temp_dogs[temp_id]['images'])
378
+
379
+ # Generate summary
380
+ summary = f"İşlem tamamlandı!\n"
381
+ summary += f"Başlangıçta {original_count} köpek tespit edildi\n"
382
+ if discarded_count > 0:
383
+ summary += f"14'ten az resimli {discarded_count} köpek iptal edildi (ID'ler: {discarded_ids})\n"
384
+ summary += f"14+ resimli {len(temp_dogs)} köpek tutuldu\n"
385
+ summary += f"{processed} kare işlendi\n"
386
+ summary += f"Kare aralığı: {min_frame_gap} kare\n"
387
+ summary += f"Bulanık resimler reddedildi: {blur_rejected}\n\n"
388
+
389
+ if len(temp_dogs) == 0:
390
+ summary += "Hiçbir köpek minimum 14 resim gereksinimini karşılamadı.\n"
391
+ summary += "ReID eşiğini ayarlamayı veya daha uzun bir video kullanmayı deneyin."
392
+ show_validation = False
393
+ else:
394
+ summary += "Sonuçlar GEÇİCİ oturumda saklandı\n"
395
+ summary += "Kaydetmeden önce resimleri incelemek ve seçmek için Sekme 2'ye gidin"
396
+ show_validation = True
397
+
398
+ gallery_html = self._create_temp_gallery()
399
+
400
+ # Create visualization video
401
+ viz_video_path = self.create_visualization_video(video_path, sample_rate)
402
+
403
+ gc.collect()
404
+ if torch.cuda.is_available():
405
+ torch.cuda.empty_cache()
406
+
407
+ return (
408
+ gallery_html,
409
+ summary,
410
+ "Doğrulama için hazır" if len(temp_dogs) > 0 else "Geçerli köpek yok",
411
+ gr.update(visible=show_validation),
412
+ viz_video_path
413
+ )
414
+
415
+ except Exception as e:
416
+ import traceback
417
+ error = f"Hata: {str(e)}\n{traceback.format_exc()}"
418
+ return None, error, "", gr.update(visible=False), None
419
+ finally:
420
+ self.is_processing = False
421
+
422
+ def _create_temp_gallery(self) -> str:
423
+ """Create gallery from temporary session"""
424
+ if not self.temp_session:
425
+ return "<p>Geçici oturumda köpek yok</p>"
426
+
427
+ html = "<div style='padding: 20px;'>"
428
+ html += "<h2 style='text-align:center; color:#ff6b6b;'>GEÇİCİ OTURUM - Henüz Kaydedilmedi</h2>"
429
+ html += f"<p style='text-align:center;'>Tespit edilen köpekler: {len(self.temp_session)}</p>"
430
+ html += "<div style='display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 20px;'>"
431
+
432
+ for temp_id in sorted(self.temp_session.keys()):
433
+ dog_data = self.temp_session[temp_id]
434
+ images = dog_data['images']
435
+ display_images = images[:10]
436
+
437
+ html += f"""
438
+ <div style='border: 3px solid #ff6b6b; border-radius: 10px;
439
+ padding: 15px; background: #fff5f5;'>
440
+ <h3 style='margin: 0 0 10px 0; color:#c92a2a;'>
441
+ Geçici Köpek #{temp_id} (GEÇİCİ)
442
+ </h3>
443
+ <p style='color: #666;'>Toplam resim: {len(images)}</p>
444
+ <p style='color: #666; font-size:12px;'>
445
+ İlk {len(display_images)} resim gösteriliyor
446
+ </p>
447
+ <div style='display: grid; grid-template-columns: repeat(5, 1fr); gap: 5px;'>
448
+ """
449
+
450
+ for img in display_images:
451
+ img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
452
+ img_base64 = self._img_to_base64(img_rgb)
453
+ html += f"""
454
+ <img src='data:image/jpeg;base64,{img_base64}'
455
+ style='width: 100%; aspect-ratio: 1; object-fit: cover;
456
+ border-radius: 5px;'>
457
+ """
458
+
459
+ html += "</div></div>"
460
+
461
+ html += "</div></div>"
462
+ return html
463
+
464
+ def load_validation_interface(self):
465
+ """Load validation interface with checkbox selection"""
466
+ if not self.temp_session:
467
+ return (
468
+ gr.update(visible=False),
469
+ "Doğrulanacak geçici oturum yok. Önce bir video işleyin.",
470
+ ""
471
+ )
472
+
473
+ html = "<div style='padding: 20px;'>"
474
+ html += "<h2 style='text-align:center;'>Resimleri İnceleyin ve Seçin</h2>"
475
+ 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>"
476
+ html += "</div>"
477
+
478
+ status = f"{len(self.temp_session)} köpek doğrulama için yüklendi. İnceleyin ve hazır olduğunuzda 'Seçilenleri Veritabanına Kaydet' düğmesine tıklayın."
479
+
480
+ return (
481
+ gr.update(visible=True),
482
+ status,
483
+ html
484
+ )
485
+
486
+ def save_validated_to_database(self, *checkbox_states):
487
+ """Save validated images to permanent database"""
488
+ if not self.temp_session:
489
+ return "Kaydedilecek geçici oturum yok", gr.update()
490
+
491
+ try:
492
+ saved_count = 0
493
+ total_images_saved = 0
494
+
495
+ checkbox_idx = 0
496
+
497
+ for temp_id in sorted(self.temp_session.keys()):
498
+ dog_data = self.temp_session[temp_id]
499
+ num_images = len(dog_data['images'])
500
+
501
+ selected_indices = []
502
+ for i in range(num_images):
503
+ if checkbox_idx < len(checkbox_states) and checkbox_states[checkbox_idx]:
504
+ selected_indices.append(i)
505
+ checkbox_idx += 1
506
+
507
+ if not selected_indices:
508
+ continue
509
+
510
+ dog_id = self.db.add_dog(
511
+ name=f"Kopek_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{temp_id}"
512
+ )
513
+
514
+ for idx in selected_indices:
515
+ self.db.add_dog_image(
516
+ dog_id=dog_id,
517
+ image=dog_data['images'][idx],
518
+ timestamp=dog_data['timestamps'][idx],
519
+ confidence=dog_data['confidences'][idx],
520
+ bbox=dog_data['bboxes'][idx]
521
+ )
522
+ total_images_saved += 1
523
+
524
+ saved_count += 1
525
+
526
+ self.temp_session.clear()
527
+ self.validation_data = {}
528
+
529
+ self._backup_database()
530
+
531
+ db_html = self._show_database()
532
+
533
+ summary = f"✅ {saved_count} köpek ve {total_images_saved} seçili resim başarıyla kalıcı veritabanına kaydedildi!"
534
+
535
+ gc.collect()
536
+ if torch.cuda.is_available():
537
+ torch.cuda.empty_cache()
538
+
539
+ return summary, gr.update(value=db_html, visible=True)
540
+
541
+ except Exception as e:
542
+ import traceback
543
+ error = f"Kayıt hatası: {str(e)}\n{traceback.format_exc()}"
544
+ return error, gr.update()
545
+
546
+ def _backup_database(self):
547
+ """Backup database to HuggingFace"""
548
+ try:
549
+ from huggingface_hub import HfApi
550
+
551
+ hf_token = os.getenv('HF_TOKEN')
552
+ if not hf_token:
553
+ print("Uyarı: HF_TOKEN bulunamadı, yedekleme atlanıyor")
554
+ return
555
+
556
+ api = HfApi()
557
+ repo_id = "mustafa2ak/dog-dataset-backup"
558
+
559
+ api.upload_file(
560
+ path_or_fileobj='dog_monitoring.db',
561
+ path_in_repo='dog_monitoring.db',
562
+ repo_id=repo_id,
563
+ repo_type='dataset',
564
+ token=hf_token
565
+ )
566
+
567
+ print(f"✅ Veritabanı {repo_id} adresine yedeklendi")
568
+
569
+ except Exception as e:
570
+ print(f"Yedekleme başarısız: {str(e)}")
571
+
572
+ def _restore_database(self):
573
+ """Restore database from HuggingFace"""
574
+ try:
575
+ from huggingface_hub import hf_hub_download
576
+
577
+ hf_token = os.getenv('HF_TOKEN')
578
+ if not hf_token:
579
+ print("HF_TOKEN bulunamadı, yeni veritabanı ile başlanıyor")
580
+ return
581
+
582
+ repo_id = "mustafa2ak/dog-dataset-backup"
583
+
584
+ db_path = hf_hub_download(
585
+ repo_id=repo_id,
586
+ filename='dog_monitoring.db',
587
+ repo_type='dataset',
588
+ token=hf_token
589
+ )
590
+
591
+ import shutil
592
+ shutil.copy(db_path, 'dog_monitoring.db')
593
+
594
+ print(f"✅ Veritabanı {repo_id} adresinden geri yüklendi")
595
+
596
+ except Exception as e:
597
+ print(f"Yedek bulunamadı veya geri yükleme başarısız: {str(e)}")
598
+
599
+ def _show_database(self) -> str:
600
+ """Show current database contents"""
601
+ dogs = self.db.get_all_dogs()
602
 
603
+ if dogs.empty:
604
+ return "<p style='text-align:center; color:#868e96;'>Veritabanında henüz köpek yok</p>"
605
 
606
+ html = "<div style='padding: 20px;'>"
607
+ html += f"<h2 style='text-align:center; color:#228be6;'>Kalıcı Veritabanı ({len(dogs)} köpek)</h2>"
608
+ html += "<div style='display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px;'>"
609
 
610
+ for _, dog in dogs.iterrows():
611
+ images = self.db.get_dog_images(dog['dog_id'])
612
+ display_count = min(6, len(images))
613
+
614
+ html += f"""
615
+ <div style='border: 2px solid #228be6; border-radius: 10px;
616
+ padding: 15px; background: #e7f5ff;'>
617
+ <h3 style='margin: 0 0 10px 0; color:#1971c2;'>{dog['name']}</h3>
618
+ <p style='color: #666; margin: 5px 0;'>ID: {dog['dog_id']}</p>
619
+ <p style='color: #666; margin: 5px 0;'>Resimler: {len(images)}</p>
620
+ <p style='color: #666; margin: 5px 0; font-size: 12px;'>
621
+ İlk görülme: {dog['first_seen']}
622
+ </p>
623
+ <div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-top: 10px;'>
624
+ """
625
+
626
+ for img_data in images[:display_count]:
627
+ img = img_data['image']
628
+ img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
629
+ img_base64 = self._img_to_base64(img_rgb)
630
+ html += f"""
631
+ <img src='data:image/jpeg;base64,{img_base64}'
632
+ style='width: 100%; aspect-ratio: 1; object-fit: cover;
633
+ border-radius: 5px;'>
634
+ """
635
+
636
+ html += "</div></div>"
637
+
638
+ html += "</div></div>"
639
+ return html
640
+
641
+ def export_dataset(self):
642
+ """Export dataset as downloadable ZIP file"""
643
+ try:
644
+ dogs = self.db.get_all_dogs()
645
+
646
+ if dogs.empty:
647
+ return "Veritabanında dışa aktarılacak köpek yok", None
648
+
649
+ zip_buffer = BytesIO()
650
+
651
+ with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
652
+ total_images = 0
653
+ export_info = []
654
 
655
+ for _, dog in dogs.iterrows():
656
+ dog_id = dog['dog_id']
657
+ dog_name = dog['name'] or f"kopek_{dog_id}"
658
+ safe_name = "".join(c if c.isalnum() or c in ('_', '-') else '_' for c in dog_name)
 
 
 
659
 
660
+ images = self.db.get_dog_images(
661
+ dog_id=dog_id,
662
+ validated_only=False,
663
+ include_discarded=False
664
+ )
665
 
666
+ if not images:
667
+ continue
668
 
669
+ for idx, img_data in enumerate(images):
670
+ image = img_data['image']
671
+
672
+ img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
673
+ pil_image = Image.fromarray(img_rgb)
674
+
675
+ img_buffer = BytesIO()
676
+ pil_image.save(img_buffer, format='JPEG', quality=95)
677
+ img_bytes = img_buffer.getvalue()
678
+
679
+ filename = f"egitim_veri_seti/{safe_name}/resim_{idx+1:04d}.jpg"
680
+ zipf.writestr(filename, img_bytes)
681
+ total_images += 1
682
 
683
+ export_info.append({
684
+ 'dog_id': int(dog_id),
685
+ 'name': dog_name,
686
+ 'image_count': len(images)
687
+ })
688
 
689
+ print(f"Exported {len(images)} images for {dog_name}")
690
+
691
+ metadata = {
692
+ 'export_date': datetime.now().isoformat(),
693
+ 'total_dogs': len(dogs),
694
+ 'total_images': total_images,
695
+ 'dogs': export_info
696
+ }
697
+
698
+ zipf.writestr('egitim_veri_seti/metadata.json', json.dumps(metadata, indent=2, ensure_ascii=False))
 
 
 
 
 
 
 
 
 
 
699
 
700
+ zip_buffer.seek(0)
701
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip', prefix='kopek_veri_seti_')
702
+ temp_file.write(zip_buffer.getvalue())
703
+ temp_file.close()
704
 
705
+ summary = f"✅ Veri seti başarıyla dışa aktarıldı!\n\n"
706
+ summary += f"📦 Toplam köpek: {len(dogs)}\n"
707
+ summary += f"🖼️ Toplam resim: {total_images}\n\n"
708
+ summary += "Bilgisayarınıza kaydetmek için aşağıdaki indirme düğmesine tıklayın."
709
+
710
+ return summary, temp_file.name
711
+
712
+ except Exception as e:
713
+ import traceback
714
+ error = f"Dışa aktarma hatası: {str(e)}\n{traceback.format_exc()}"
715
+ return error, None
716
+
717
+ def _img_to_base64(self, img_array: np.ndarray) -> str:
718
+ """Convert image array to base64 string"""
719
+ img_pil = Image.fromarray(img_array)
720
+ buffered = BytesIO()
721
+ img_pil.save(buffered, format="JPEG", quality=85)
722
+ return base64.b64encode(buffered.getvalue()).decode()
723
+
724
+ def create_interface(self):
725
+ """Create Gradio interface with single video component"""
726
 
727
+ with gr.Blocks(title="Köpek Veri Seti Toplama", theme=gr.themes.Soft()) as app:
728
+ gr.Markdown("""
729
+ # 🐕 Köpek Eğitim Veri Seti Toplama
730
+ **İşle → Doğrula → Kaydet → Dışa Aktar**
731
+ """)
732
+
733
+ with gr.Tabs():
734
+ # TAB 1: Process Video
735
+ with gr.Tab("1. Videoyu İşle"):
736
+ gr.Markdown("### Köpekleri tespit etmek için video yükleyin ve işleyin")
737
+
738
+ with gr.Row():
739
+ with gr.Column():
740
+ # Single video component for both upload and display
741
+ video_display = gr.Video(
742
+ label="Video Yükle / İşlenmiş Video",
743
+ sources=["upload"],
744
+ autoplay=True,
745
+ loop=True
746
+ )
747
+
748
+ with gr.Row():
749
+ reid_threshold = gr.Slider(
750
+ minimum=0.1, maximum=0.9, value=0.3, step=0.05,
751
+ label="ReID Eşiği (düşük = daha fazla köpek)"
752
+ )
753
+ sample_rate = gr.Slider(
754
+ minimum=1, maximum=10, value=3, step=1,
755
+ label="Kare Örnekleme Hızı"
756
+ )
757
+
758
+ with gr.Row():
759
+ process_btn = gr.Button("🎬 Videoyu İşle", variant="primary", size="lg")
760
+ stop_btn = gr.Button("⏹️ Durdur", variant="stop")
761
+ clear_btn = gr.Button("🗑️ Temizle ve Sıfırla")
762
+
763
+ progress_text = gr.Textbox(label="İlerleme", lines=1)
764
+ status_text = gr.Textbox(label="Durum", lines=8)
765
+
766
+ with gr.Column():
767
+ gallery_output = gr.HTML(label="Tespit Sonuçları")
768
+
769
+ with gr.Row():
770
+ discard_btn = gr.Button("❌ İptal Et ve Farklı Eşikle Tekrar Dene", variant="secondary")
771
+
772
+ # TAB 2: Validate & Save
773
+ with gr.Tab("2. Doğrula ve Kaydet"):
774
+ gr.Markdown("### Tespit edilen köpekleri inceleyin ve tutulacak resimleri seçin")
775
+
776
+ with gr.Column(visible=False) as validation_container:
777
+ validation_status = gr.Textbox(label="Durum", lines=2)
778
+
779
+ load_btn = gr.Button("📋 Doğrulama Arayüzünü Yükle", variant="primary", size="lg")
780
+
781
+ @gr.render(inputs=[], triggers=[load_btn.click])
782
+ def render_validation():
783
+ if not self.temp_session:
784
+ gr.Markdown("Geçici oturum yok. Önce bir video işleyin.")
785
+ return
786
+
787
+ checkboxes = []
788
+
789
+ for temp_id in sorted(self.temp_session.keys()):
790
+ dog_data = self.temp_session[temp_id]
791
+ images = dog_data['images']
792
+
793
+ with gr.Group():
794
+ gr.Markdown(f"### 🐕 Köpek #{temp_id} - {len(images)} resim")
795
+
796
+ for i in range(0, len(images), 6):
797
+ with gr.Row():
798
+ for j in range(6):
799
+ if i + j < len(images):
800
+ img = images[i + j]
801
+ img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
802
+
803
+ with gr.Column(scale=1, min_width=120):
804
+ gr.Image(
805
+ value=img_rgb,
806
+ label=f"#{i+j+1}",
807
+ interactive=False,
808
+ height=150,
809
+ show_download_button=False
810
+ )
811
+ cb = gr.Checkbox(
812
+ label="Tut",
813
+ value=True,
814
+ elem_id=f"cb_{temp_id}_{i+j}"
815
+ )
816
+ checkboxes.append(cb)
817
+
818
+ save_btn = gr.Button("💾 Seçilenleri Veritabanına Kaydet", variant="primary", size="lg")
819
+ save_status = gr.Textbox(label="Kayıt Durumu", lines=3)
820
+
821
+ save_btn.click(
822
+ fn=self.save_validated_to_database,
823
+ inputs=checkboxes,
824
+ outputs=[save_status, validation_container]
825
+ )
826
+
827
+ # TAB 3: Database & Export
828
+ with gr.Tab("3. Veritabanı ve Dışa Aktarım"):
829
+ gr.Markdown("### Veritabanını görüntüleyin ve ince ayar için dışa aktarın")
830
+
831
+ refresh_db_btn = gr.Button("🔄 Veritabanını Yenile", variant="secondary")
832
+ database_display = gr.HTML(label="Veritabanı İçeriği", visible=False)
833
+
834
+ gr.Markdown("---")
835
+
836
+ export_btn = gr.Button("📦 Veri Setini Dışa Aktar", variant="primary", size="lg")
837
+ export_status = gr.Textbox(label="Dışa Aktarım Durumu", lines=5)
838
+ download_btn = gr.File(label="Dışa Aktarılan Veri Setini İndir", interactive=False)
839
+
840
+ # Event handlers
841
+ process_btn.click(
842
+ fn=self.process_video,
843
+ inputs=[video_display, reid_threshold, sample_rate],
844
+ outputs=[
845
+ gallery_output,
846
+ status_text,
847
+ progress_text,
848
+ validation_container,
849
+ video_display # Processed video replaces upload
850
+ ]
851
+ )
852
+
853
+ stop_btn.click(
854
+ fn=self.stop_processing,
855
+ outputs=[status_text, progress_text, gallery_output]
856
+ )
857
+
858
+ clear_btn.click(
859
+ fn=self.clear_reset,
860
+ outputs=[
861
+ video_display, # Clear video
862
+ gallery_output,
863
+ status_text,
864
+ progress_text,
865
+ validation_container
866
+ ]
867
+ )
868
+
869
+ discard_btn.click(
870
+ fn=self.discard_session,
871
+ outputs=[validation_container, status_text, database_display]
872
+ )
873
+
874
+ load_btn.click(
875
+ fn=self.load_validation_interface,
876
+ outputs=[validation_container, validation_status, gr.HTML()]
877
+ )
878
+
879
+ refresh_db_btn.click(
880
+ fn=lambda: gr.update(value=self._show_database(), visible=True),
881
+ outputs=[database_display]
882
+ )
883
+
884
+ export_btn.click(
885
+ fn=self.export_dataset,
886
+ outputs=[export_status, download_btn]
887
+ )
888
 
889
+ return app
890
+
891
+ def launch(self):
892
+ """Launch the Gradio app"""
893
+ app = self.create_interface()
894
+ app.launch(share=False, server_name="0.0.0.0", server_port=7860)
895
+
896
+
897
+ if __name__ == "__main__":
898
+ app = DatasetCollectionApp()
899
+ app.launch()