mustafa2ak commited on
Commit
d49c095
Β·
verified Β·
1 Parent(s): dc99cf1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +415 -218
app.py CHANGED
@@ -118,12 +118,6 @@ class SmartImageSelector:
118
  if len(dog_data) <= max_images:
119
  return dog_data
120
 
121
- # Calculate quality scores
122
- for item in dog_data:
123
- item['quality_score'] = self.quality_analyzer.calculate_overall_quality(
124
- item['crop'], item['bbox'], item['frame'].shape
125
- )
126
-
127
  # Sort by quality
128
  dog_data.sort(key=lambda x: x['quality_score'], reverse=True)
129
 
@@ -235,7 +229,7 @@ class AdvancedHeadExtractor:
235
  edges = cv2.Canny(gray, 50, 150)
236
 
237
  # Find feature concentration (likely head area)
238
- kernel_size = h // 10
239
  kernel = np.ones((kernel_size, kernel_size), np.float32)
240
  edge_density = cv2.filter2D(edges, -1, kernel)
241
 
@@ -276,6 +270,7 @@ class ResNetDatasetCreator:
276
  def __init__(self):
277
  self.temp_dir = Path("temp_dataset")
278
  self.final_dir = Path("resnet_finetune_dataset")
 
279
 
280
  # Components
281
  self.detector = DogDetector(device='cuda' if torch.cuda.is_available() else 'cpu')
@@ -291,6 +286,47 @@ class ResNetDatasetCreator:
291
  # Create directories
292
  self.temp_dir.mkdir(exist_ok=True)
293
  self.final_dir.mkdir(exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
  def process_video(self, video_path: str, reid_threshold: float,
296
  max_images_per_dog: int, sample_rate: int) -> Dict:
@@ -302,7 +338,7 @@ class ResNetDatasetCreator:
302
  max_images_per_dog: Maximum images to extract per dog
303
  sample_rate: Process every Nth frame
304
  """
305
- # Clear temp directory
306
  if self.temp_dir.exists():
307
  shutil.rmtree(self.temp_dir)
308
  self.temp_dir.mkdir()
@@ -344,7 +380,7 @@ class ResNetDatasetCreator:
344
  dog_id = results['ResNet50']['dog_id']
345
  confidence = results['ResNet50']['confidence']
346
 
347
- if dog_id > 0 and confidence > 0.5:
348
  # Get best detection
349
  detection = None
350
  for det in reversed(track.detections):
@@ -381,6 +417,8 @@ class ResNetDatasetCreator:
381
 
382
  # Select best images for each dog
383
  total_images = 0
 
 
384
  for dog_id, images in dog_data.items():
385
  # Use smart selector
386
  selected = self.image_selector.select_best_images(
@@ -410,20 +448,23 @@ class ResNetDatasetCreator:
410
  total_images += saved_count
411
 
412
  # Store metadata
413
- self.processed_dogs[dog_id] = {
414
  'num_images': saved_count,
415
  'avg_confidence': np.mean([d['reid_confidence'] for d in selected]),
416
  'quality_scores': [d['quality_score'] for d in selected]
417
  }
418
 
 
 
 
419
  # Save session info
420
  self.current_session = {
421
  'video': video_path,
422
  'timestamp': datetime.now().isoformat(),
423
- 'num_dogs': len(dog_data),
424
  'total_images': total_images,
425
  'reid_threshold': reid_threshold,
426
- 'dogs': {str(k): v for k, v in self.processed_dogs.items()}
427
  }
428
 
429
  # Save metadata
@@ -434,42 +475,65 @@ class ResNetDatasetCreator:
434
 
435
  def get_dog_images(self, dog_id: int) -> List:
436
  """Get images for verification interface"""
 
437
  dog_dir = self.temp_dir / f"dog_{dog_id:03d}"
438
- full_dir = dog_dir / 'full'
 
439
 
 
440
  if not full_dir.exists():
441
  return []
442
 
443
  images = []
444
  for img_path in sorted(full_dir.glob("*.jpg"))[:12]:
445
  img = cv2.imread(str(img_path))
446
- img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
447
- images.append(img_rgb)
 
448
 
449
  return images
450
 
451
  def remove_images(self, dog_id: int, image_indices: List[int]):
452
  """Remove specific images from a dog folder"""
453
- dog_dir = self.temp_dir / f"dog_{dog_id:03d}"
454
- full_dir = dog_dir / 'full'
455
- head_dir = dog_dir / 'head'
456
-
457
- image_files = sorted(list(full_dir.glob("*.jpg")))
458
-
459
- for idx in image_indices:
460
- if idx < len(image_files):
461
- # Remove full image
462
- image_files[idx].unlink()
463
- # Remove corresponding head
464
- head_file = head_dir / image_files[idx].name
465
- if head_file.exists():
466
- head_file.unlink()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
 
468
  def delete_dog(self, dog_id: int):
469
- """Delete entire dog folder"""
470
- dog_dir = self.temp_dir / f"dog_{dog_id:03d}"
471
- if dog_dir.exists():
472
- shutil.rmtree(dog_dir)
 
 
 
 
 
473
 
474
  def save_final_dataset(self, format_type: str = 'folder') -> str:
475
  """
@@ -483,12 +547,24 @@ class ResNetDatasetCreator:
483
  shutil.rmtree(self.final_dir)
484
  self.final_dir.mkdir()
485
 
486
- # Copy verified dogs
487
- dog_dirs = sorted([d for d in self.temp_dir.iterdir() if d.is_dir()])
 
 
 
 
 
 
 
 
 
 
 
 
488
  data_entries = []
489
  final_id = 1
490
 
491
- for dog_dir in dog_dirs:
492
  if not (dog_dir / 'full').exists():
493
  continue
494
 
@@ -512,22 +588,30 @@ class ResNetDatasetCreator:
512
  # Create train/val split
513
  df = pd.DataFrame(data_entries)
514
 
515
- # Stratified split by dog_id
516
- from sklearn.model_selection import train_test_split
517
- train_df, val_df = train_test_split(
518
- df, test_size=0.2, stratify=df['dog_id'], random_state=42
519
- )
520
-
521
- # Save CSV files
522
- train_df.to_csv(self.final_dir / 'train.csv', index=False)
523
- val_df.to_csv(self.final_dir / 'val.csv', index=False)
 
 
 
 
 
 
 
 
524
 
525
  # Create metadata
526
  metadata = {
527
  'total_dogs': final_id - 1,
528
  'total_images': len(data_entries),
529
- 'train_images': len(train_df) if format_type in ['csv', 'both'] else 0,
530
- 'val_images': len(val_df) if format_type in ['csv', 'both'] else 0,
531
  'format': format_type,
532
  'created': datetime.now().isoformat()
533
  }
@@ -556,8 +640,6 @@ class ResNetDatasetCreator:
556
 
557
  # State to store processing results
558
  processing_state = gr.State(None)
559
- # State for tab navigation
560
- selected_tab = gr.State(0)
561
 
562
  # Step 1: Process Video
563
  with gr.Tabs() as tabs:
@@ -585,7 +667,7 @@ class ResNetDatasetCreator:
585
  # Results display in formatted table
586
  with gr.Column():
587
  progress_bar = gr.Textbox(label="Progress", interactive=False)
588
- results_display = gr.HTML(label="Processing Results")
589
  save_status = gr.Textbox(label="Save Status", interactive=False, visible=False)
590
 
591
  with gr.Row():
@@ -600,154 +682,152 @@ class ResNetDatasetCreator:
600
  variant="secondary",
601
  visible=False
602
  )
603
-
604
- def format_results_table(session_data):
605
- """Format session data as HTML table"""
606
- if not session_data:
607
- return ""
608
-
609
- html = """
610
- <div style="padding: 20px; background-color: #f8f9fa; border-radius: 10px;">
611
- <h3 style="color: #2c3e50;">πŸ“Š Processing Results</h3>
612
- <table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
613
- <tr style="background-color: #3498db; color: white;">
614
- <td style="padding: 10px; border: 1px solid #ddd;"><b>Metric</b></td>
615
- <td style="padding: 10px; border: 1px solid #ddd;"><b>Value</b></td>
 
 
 
 
 
 
 
 
616
  </tr>
617
- """
618
-
619
- # Basic info
620
- html += f"""
621
- <tr style="background-color: #ecf0f1;">
622
- <td style="padding: 10px; border: 1px solid #ddd;">Video File</td>
623
- <td style="padding: 10px; border: 1px solid #ddd;">{session_data['video'].split('/')[-1]}</td>
624
- </tr>
625
- <tr>
626
- <td style="padding: 10px; border: 1px solid #ddd;">Processing Time</td>
627
- <td style="padding: 10px; border: 1px solid #ddd;">{session_data['timestamp'].split('T')[1].split('.')[0]}</td>
628
- </tr>
629
- <tr style="background-color: #ecf0f1;">
630
- <td style="padding: 10px; border: 1px solid #ddd;">Number of Dogs Detected</td>
631
- <td style="padding: 10px; border: 1px solid #ddd;"><b>{session_data['num_dogs']}</b></td>
632
- </tr>
633
- <tr>
634
- <td style="padding: 10px; border: 1px solid #ddd;">Total Images Extracted</td>
635
- <td style="padding: 10px; border: 1px solid #ddd;"><b>{session_data['total_images']}</b></td>
636
- </tr>
637
- <tr style="background-color: #ecf0f1;">
638
- <td style="padding: 10px; border: 1px solid #ddd;">ReID Threshold Used</td>
639
- <td style="padding: 10px; border: 1px solid #ddd;">{session_data['reid_threshold']:.2f}</td>
640
- </tr>
641
- </table>
642
- """
643
-
644
- # Dog-specific details
645
- if session_data['dogs']:
646
- html += """
647
- <h4 style="color: #2c3e50; margin-top: 20px;">πŸ• Dog Details</h4>
648
- <table style="width: 100%; border-collapse: collapse; margin: 10px 0;">
649
- <tr style="background-color: #27ae60; color: white;">
650
- <td style="padding: 10px; border: 1px solid #ddd;"><b>Dog ID</b></td>
651
- <td style="padding: 10px; border: 1px solid #ddd;"><b>Images</b></td>
652
- <td style="padding: 10px; border: 1px solid #ddd;"><b>Avg Confidence</b></td>
653
- <td style="padding: 10px; border: 1px solid #ddd;"><b>Avg Quality</b></td>
654
- <td style="padding: 10px; border: 1px solid #ddd;"><b>Quality Range</b></td>
655
  </tr>
 
 
 
 
 
656
  """
657
 
658
- for dog_id, dog_info in session_data['dogs'].items():
659
- avg_quality = np.mean(dog_info['quality_scores'])
660
- min_quality = min(dog_info['quality_scores'])
661
- max_quality = max(dog_info['quality_scores'])
662
-
663
- row_style = "background-color: #ecf0f1;" if int(dog_id) % 2 == 0 else ""
664
- html += f"""
665
- <tr style="{row_style}">
666
- <td style="padding: 10px; border: 1px solid #ddd;">Dog {dog_id}</td>
667
- <td style="padding: 10px; border: 1px solid #ddd;">{dog_info['num_images']}</td>
668
- <td style="padding: 10px; border: 1px solid #ddd;">{dog_info['avg_confidence']:.2%}</td>
669
- <td style="padding: 10px; border: 1px solid #ddd;">{avg_quality:.1f}</td>
670
- <td style="padding: 10px; border: 1px solid #ddd;">{min_quality:.1f} - {max_quality:.1f}</td>
671
- </tr>
672
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
673
 
674
- html += "</table>"
675
-
676
- html += """
677
- <div style="margin-top: 20px; padding: 10px; background-color: #d4edda; border-radius: 5px;">
678
- <p style="margin: 0; color: #155724;">
679
- βœ… <b>Processing Complete!</b> Click "Save Results & Proceed" to continue to verification step.
680
- </p>
681
  </div>
682
- </div>
683
- """
684
-
685
- return html
686
-
687
- def process_wrapper(video, threshold, max_img, sample):
688
- """Process video and format results"""
689
- if not video:
690
- return None, "", "Please upload a video", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
691
-
692
- # Clear previous session
693
- self.current_session = None
694
- self.processed_dogs = {}
695
-
696
- # Process video
697
- for update in self.process_video(video, threshold, int(max_img), int(sample)):
698
- if 'progress' in update:
699
- yield None, "", update['status'], gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
700
- else:
701
- # Store session data
702
- self.current_session = update['session']
703
- # Format results as table
704
- formatted_table = format_results_table(update['session'])
705
- yield update['session'], formatted_table, "Complete! βœ…", gr.update(visible=False), gr.update(visible=True), gr.update(visible=True)
706
-
707
- def save_and_proceed():
708
- """Save current results and notify user"""
709
- if self.current_session and self.processed_dogs:
710
- # Debug info
711
- dog_count = len(self.processed_dogs)
712
- img_count = sum(d['num_images'] for d in self.processed_dogs.values())
713
-
714
- message = f"""βœ… Results saved successfully!
715
-
716
- πŸ“Š Summary:
717
- - Dogs saved: {dog_count}
718
- - Total images: {img_count}
719
- - Data location: {self.temp_dir}
720
 
721
- You can now proceed to Step 2: Verify & Clean
722
- Click the 'Refresh List' button in Step 2 to load the dogs."""
 
 
 
 
723
 
724
- return message, gr.update(visible=True)
725
- return "❌ No results to save. Please process a video first.", gr.update(visible=False)
726
-
727
- def clear_results():
728
- """Clear current processing results"""
729
- self.current_session = None
730
- self.processed_dogs = {}
731
- if self.temp_dir.exists():
732
- shutil.rmtree(self.temp_dir)
733
- self.temp_dir.mkdir()
734
- return None, "", "", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
735
-
736
- process_btn.click(
737
- process_wrapper,
738
- inputs=[video_input, reid_threshold, max_images, sample_rate],
739
- outputs=[processing_state, results_display, progress_bar, save_status, save_proceed_btn, clear_btn]
740
- )
741
-
742
- save_proceed_btn.click(
743
- save_and_proceed,
744
- outputs=[save_status, save_status] # Use save_status for both message and visibility
745
- )
746
-
747
- clear_btn.click(
748
- clear_results,
749
- outputs=[processing_state, results_display, progress_bar, save_status, save_proceed_btn, clear_btn]
750
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
 
752
  # Step 2: Verify & Clean
753
  with gr.Tab("βœ… Step 2: Verify & Clean", id=1):
@@ -759,7 +839,14 @@ class ResNetDatasetCreator:
759
  choices=[],
760
  interactive=True
761
  )
 
 
 
762
  refresh_btn = gr.Button("πŸ”„ Refresh List")
 
 
 
 
763
 
764
  image_gallery = gr.Gallery(
765
  label="Dog Images (Click to select for removal)",
@@ -769,18 +856,27 @@ class ResNetDatasetCreator:
769
  rows=3,
770
  object_fit="contain",
771
  height="auto",
772
- interactive=True
 
773
  )
774
 
775
  with gr.Row():
 
 
 
 
 
776
  remove_selected_btn = gr.Button("πŸ—‘ Remove Selected Images", variant="secondary")
777
  delete_dog_btn = gr.Button("❌ Delete Entire Dog", variant="stop")
778
 
779
  status_text = gr.Textbox(label="Status", interactive=False)
780
 
781
  def refresh_dogs():
782
- """Refresh the dog list from processed data"""
783
- if not self.current_session or not self.processed_dogs:
 
 
 
784
  return gr.update(choices=[], value=None)
785
 
786
  choices = [f"Dog {dog_id}" for dog_id in sorted(self.processed_dogs.keys())]
@@ -788,6 +884,51 @@ class ResNetDatasetCreator:
788
  return gr.update(choices=choices, value=choices[0])
789
  return gr.update(choices=[], value=None)
790
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
791
  def show_dog_images(dog_selection):
792
  """Display images for selected dog"""
793
  if not dog_selection:
@@ -803,31 +944,59 @@ class ResNetDatasetCreator:
803
  print(f"Error loading images: {e}")
804
  return []
805
 
806
- def remove_selected(dog_selection, selected_indices):
807
- if not dog_selection or not selected_indices:
808
- return "No images selected", gr.update()
 
809
 
810
- dog_id = int(dog_selection.split()[1])
811
- self.remove_images(dog_id, selected_indices)
812
- return f"Removed {len(selected_indices)} images", self.get_dog_images(dog_id)
 
 
 
 
 
 
 
 
 
 
813
 
814
  def delete_dog(dog_selection):
815
  if not dog_selection:
816
- return "No dog selected"
817
 
818
  dog_id = int(dog_selection.split()[1])
819
  self.delete_dog(dog_id)
820
- del self.processed_dogs[dog_id]
821
- return f"Deleted Dog {dog_id}"
 
 
 
 
 
 
 
 
822
 
823
  refresh_btn.click(refresh_dogs, outputs=dog_selector)
 
824
  dog_selector.change(show_dog_images, inputs=dog_selector, outputs=image_gallery)
825
  remove_selected_btn.click(
826
  remove_selected,
827
- inputs=[dog_selector, image_gallery],
828
  outputs=[status_text, image_gallery]
829
  )
830
- delete_dog_btn.click(delete_dog, inputs=dog_selector, outputs=status_text)
 
 
 
 
 
 
 
 
831
 
832
  # Step 3: Export Dataset
833
  with gr.Tab("πŸ’Ύ Step 3: Export Dataset", id=2):
@@ -845,36 +1014,64 @@ class ResNetDatasetCreator:
845
  label="Export Format"
846
  )
847
 
848
- export_btn = gr.Button("πŸ“¦ Export Final Dataset", variant="primary", size="lg")
 
 
 
849
  export_output = gr.Textbox(label="Export Path", interactive=False)
850
  download_file = gr.File(label="Download Dataset", interactive=False)
851
  stats_display = gr.Markdown()
852
 
853
  def export_dataset(format_type):
854
- zip_path = self.save_final_dataset(format_type)
855
-
856
- # Get statistics
857
- with open(self.final_dir / 'metadata.json', 'r') as f:
858
- metadata = json.load(f)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
859
 
860
- stats = f"""
861
- ### βœ… Dataset Exported Successfully!
862
 
863
- - **Total Dogs**: {metadata['total_dogs']}
864
- - **Total Images**: {metadata['total_images']}
865
- - **Training Images**: {metadata.get('train_images', 'N/A')}
866
- - **Validation Images**: {metadata.get('val_images', 'N/A')}
867
 
868
- Dataset is ready for ResNet fine-tuning!
869
  """
870
-
871
- return zip_path, zip_path, stats
872
 
873
  export_btn.click(
874
  export_dataset,
875
  inputs=format_selector,
876
  outputs=[export_output, download_file, stats_display]
877
  )
 
 
 
 
 
878
 
879
  return app
880
 
 
118
  if len(dog_data) <= max_images:
119
  return dog_data
120
 
 
 
 
 
 
 
121
  # Sort by quality
122
  dog_data.sort(key=lambda x: x['quality_score'], reverse=True)
123
 
 
229
  edges = cv2.Canny(gray, 50, 150)
230
 
231
  # Find feature concentration (likely head area)
232
+ kernel_size = max(1, h // 10)
233
  kernel = np.ones((kernel_size, kernel_size), np.float32)
234
  edge_density = cv2.filter2D(edges, -1, kernel)
235
 
 
270
  def __init__(self):
271
  self.temp_dir = Path("temp_dataset")
272
  self.final_dir = Path("resnet_finetune_dataset")
273
+ self.database_dir = Path("permanent_database")
274
 
275
  # Components
276
  self.detector = DogDetector(device='cuda' if torch.cuda.is_available() else 'cpu')
 
286
  # Create directories
287
  self.temp_dir.mkdir(exist_ok=True)
288
  self.final_dir.mkdir(exist_ok=True)
289
+ self.database_dir.mkdir(exist_ok=True)
290
+
291
+ # Load existing database if exists
292
+ self.load_database()
293
+
294
+ def load_database(self):
295
+ """Load existing permanent database"""
296
+ db_file = self.database_dir / "database.json"
297
+ if db_file.exists():
298
+ with open(db_file, 'r') as f:
299
+ data = json.load(f)
300
+ self.processed_dogs = {int(k): v for k, v in data.get('dogs', {}).items()}
301
+ print(f"Loaded {len(self.processed_dogs)} dogs from database")
302
+
303
+ def save_to_database(self):
304
+ """Save current processed dogs to permanent database"""
305
+ db_file = self.database_dir / "database.json"
306
+ data = {
307
+ 'dogs': {str(k): v for k, v in self.processed_dogs.items()},
308
+ 'last_updated': datetime.now().isoformat()
309
+ }
310
+ with open(db_file, 'w') as f:
311
+ json.dump(data, f, indent=2)
312
+
313
+ # Also save images to permanent location
314
+ for dog_id in self.processed_dogs:
315
+ src_dir = self.temp_dir / f"dog_{dog_id:03d}"
316
+ dst_dir = self.database_dir / f"dog_{dog_id:03d}"
317
+ if src_dir.exists():
318
+ if dst_dir.exists():
319
+ shutil.rmtree(dst_dir)
320
+ shutil.copytree(src_dir, dst_dir)
321
+
322
+ def clear_database(self):
323
+ """Clear all permanent database"""
324
+ if self.database_dir.exists():
325
+ shutil.rmtree(self.database_dir)
326
+ self.database_dir.mkdir(exist_ok=True)
327
+ self.processed_dogs = {}
328
+ self.current_session = None
329
+ print("Database cleared")
330
 
331
  def process_video(self, video_path: str, reid_threshold: float,
332
  max_images_per_dog: int, sample_rate: int) -> Dict:
 
338
  max_images_per_dog: Maximum images to extract per dog
339
  sample_rate: Process every Nth frame
340
  """
341
+ # Clear temp directory for new processing
342
  if self.temp_dir.exists():
343
  shutil.rmtree(self.temp_dir)
344
  self.temp_dir.mkdir()
 
380
  dog_id = results['ResNet50']['dog_id']
381
  confidence = results['ResNet50']['confidence']
382
 
383
+ if dog_id > 0 and confidence > 0.3: # Lower threshold for detection
384
  # Get best detection
385
  detection = None
386
  for det in reversed(track.detections):
 
417
 
418
  # Select best images for each dog
419
  total_images = 0
420
+ new_dogs = {}
421
+
422
  for dog_id, images in dog_data.items():
423
  # Use smart selector
424
  selected = self.image_selector.select_best_images(
 
448
  total_images += saved_count
449
 
450
  # Store metadata
451
+ new_dogs[dog_id] = {
452
  'num_images': saved_count,
453
  'avg_confidence': np.mean([d['reid_confidence'] for d in selected]),
454
  'quality_scores': [d['quality_score'] for d in selected]
455
  }
456
 
457
+ # Update processed dogs (append, don't replace)
458
+ self.processed_dogs.update(new_dogs)
459
+
460
  # Save session info
461
  self.current_session = {
462
  'video': video_path,
463
  'timestamp': datetime.now().isoformat(),
464
+ 'num_dogs': len(new_dogs),
465
  'total_images': total_images,
466
  'reid_threshold': reid_threshold,
467
+ 'dogs': {str(k): v for k, v in new_dogs.items()}
468
  }
469
 
470
  # Save metadata
 
475
 
476
  def get_dog_images(self, dog_id: int) -> List:
477
  """Get images for verification interface"""
478
+ # Try temp dir first, then database dir
479
  dog_dir = self.temp_dir / f"dog_{dog_id:03d}"
480
+ if not dog_dir.exists():
481
+ dog_dir = self.database_dir / f"dog_{dog_id:03d}"
482
 
483
+ full_dir = dog_dir / 'full'
484
  if not full_dir.exists():
485
  return []
486
 
487
  images = []
488
  for img_path in sorted(full_dir.glob("*.jpg"))[:12]:
489
  img = cv2.imread(str(img_path))
490
+ if img is not None:
491
+ img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
492
+ images.append(img_rgb)
493
 
494
  return images
495
 
496
  def remove_images(self, dog_id: int, image_indices: List[int]):
497
  """Remove specific images from a dog folder"""
498
+ # Handle both temp and database directories
499
+ for base_dir in [self.temp_dir, self.database_dir]:
500
+ dog_dir = base_dir / f"dog_{dog_id:03d}"
501
+ if not dog_dir.exists():
502
+ continue
503
+
504
+ full_dir = dog_dir / 'full'
505
+ head_dir = dog_dir / 'head'
506
+
507
+ image_files = sorted(list(full_dir.glob("*.jpg")))
508
+
509
+ # Extract actual indices from gallery selection
510
+ indices_to_remove = []
511
+ if isinstance(image_indices, list):
512
+ for item in image_indices:
513
+ if isinstance(item, (list, tuple)) and len(item) > 0:
514
+ indices_to_remove.append(item[0])
515
+ elif isinstance(item, int):
516
+ indices_to_remove.append(item)
517
+
518
+ for idx in indices_to_remove:
519
+ if 0 <= idx < len(image_files):
520
+ # Remove full image
521
+ image_files[idx].unlink(missing_ok=True)
522
+ # Remove corresponding head
523
+ head_file = head_dir / image_files[idx].name
524
+ if head_file.exists():
525
+ head_file.unlink()
526
 
527
  def delete_dog(self, dog_id: int):
528
+ """Delete entire dog folder from both temp and database"""
529
+ for base_dir in [self.temp_dir, self.database_dir]:
530
+ dog_dir = base_dir / f"dog_{dog_id:03d}"
531
+ if dog_dir.exists():
532
+ shutil.rmtree(dog_dir)
533
+
534
+ # Remove from processed dogs
535
+ if dog_id in self.processed_dogs:
536
+ del self.processed_dogs[dog_id]
537
 
538
  def save_final_dataset(self, format_type: str = 'folder') -> str:
539
  """
 
547
  shutil.rmtree(self.final_dir)
548
  self.final_dir.mkdir()
549
 
550
+ # Copy all dogs from both temp and database
551
+ all_dog_dirs = []
552
+
553
+ # Get dogs from temp
554
+ for d in self.temp_dir.iterdir():
555
+ if d.is_dir() and d.name.startswith('dog_'):
556
+ all_dog_dirs.append(d)
557
+
558
+ # Get dogs from database (if not already in temp)
559
+ temp_dogs = {d.name for d in all_dog_dirs}
560
+ for d in self.database_dir.iterdir():
561
+ if d.is_dir() and d.name.startswith('dog_') and d.name not in temp_dogs:
562
+ all_dog_dirs.append(d)
563
+
564
  data_entries = []
565
  final_id = 1
566
 
567
+ for dog_dir in sorted(all_dog_dirs):
568
  if not (dog_dir / 'full').exists():
569
  continue
570
 
 
588
  # Create train/val split
589
  df = pd.DataFrame(data_entries)
590
 
591
+ if len(df) > 0:
592
+ # Stratified split by dog_id
593
+ from sklearn.model_selection import train_test_split
594
+
595
+ # Only split if we have enough samples
596
+ if len(df) > 5:
597
+ train_df, val_df = train_test_split(
598
+ df, test_size=0.2, stratify=df['dog_id'], random_state=42
599
+ )
600
+ else:
601
+ train_df = df
602
+ val_df = pd.DataFrame()
603
+
604
+ # Save CSV files
605
+ train_df.to_csv(self.final_dir / 'train.csv', index=False)
606
+ if len(val_df) > 0:
607
+ val_df.to_csv(self.final_dir / 'val.csv', index=False)
608
 
609
  # Create metadata
610
  metadata = {
611
  'total_dogs': final_id - 1,
612
  'total_images': len(data_entries),
613
+ 'train_images': len(train_df) if format_type in ['csv', 'both'] and 'train_df' in locals() else len(data_entries),
614
+ 'val_images': len(val_df) if format_type in ['csv', 'both'] and 'val_df' in locals() else 0,
615
  'format': format_type,
616
  'created': datetime.now().isoformat()
617
  }
 
640
 
641
  # State to store processing results
642
  processing_state = gr.State(None)
 
 
643
 
644
  # Step 1: Process Video
645
  with gr.Tabs() as tabs:
 
667
  # Results display in formatted table
668
  with gr.Column():
669
  progress_bar = gr.Textbox(label="Progress", interactive=False)
670
+ results_display = gr.HTML(label="Processing Results", value="")
671
  save_status = gr.Textbox(label="Save Status", interactive=False, visible=False)
672
 
673
  with gr.Row():
 
682
  variant="secondary",
683
  visible=False
684
  )
685
+
686
+ def format_results_table(session_data):
687
+ """Format session data as HTML table"""
688
+ if not session_data:
689
+ return ""
690
+
691
+ html = """
692
+ <div style="padding: 20px; background-color: #f8f9fa; border-radius: 10px;">
693
+ <h3 style="color: #2c3e50;">πŸ“Š Processing Results</h3>
694
+ <table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
695
+ <tr style="background-color: #3498db; color: white;">
696
+ <td style="padding: 10px; border: 1px solid #ddd;"><b>Metric</b></td>
697
+ <td style="padding: 10px; border: 1px solid #ddd;"><b>Value</b></td>
698
+ </tr>
699
+ """
700
+
701
+ # Basic info
702
+ html += f"""
703
+ <tr style="background-color: #ecf0f1;">
704
+ <td style="padding: 10px; border: 1px solid #ddd;">Video File</td>
705
+ <td style="padding: 10px; border: 1px solid #ddd;">{session_data['video'].split('/')[-1]}</td>
706
  </tr>
707
+ <tr>
708
+ <td style="padding: 10px; border: 1px solid #ddd;">Processing Time</td>
709
+ <td style="padding: 10px; border: 1px solid #ddd;">{session_data['timestamp'].split('T')[1].split('.')[0]}</td>
710
+ </tr>
711
+ <tr style="background-color: #ecf0f1;">
712
+ <td style="padding: 10px; border: 1px solid #ddd;">Number of Dogs Detected</td>
713
+ <td style="padding: 10px; border: 1px solid #ddd;"><b>{session_data['num_dogs']}</b></td>
714
+ </tr>
715
+ <tr>
716
+ <td style="padding: 10px; border: 1px solid #ddd;">Total Images Extracted</td>
717
+ <td style="padding: 10px; border: 1px solid #ddd;"><b>{session_data['total_images']}</b></td>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
718
  </tr>
719
+ <tr style="background-color: #ecf0f1;">
720
+ <td style="padding: 10px; border: 1px solid #ddd;">ReID Threshold Used</td>
721
+ <td style="padding: 10px; border: 1px solid #ddd;">{session_data['reid_threshold']:.2f}</td>
722
+ </tr>
723
+ </table>
724
  """
725
 
726
+ # Dog-specific details
727
+ if session_data['dogs']:
728
+ html += """
729
+ <h4 style="color: #2c3e50; margin-top: 20px;">πŸ• Dog Details</h4>
730
+ <table style="width: 100%; border-collapse: collapse; margin: 10px 0;">
731
+ <tr style="background-color: #27ae60; color: white;">
732
+ <td style="padding: 10px; border: 1px solid #ddd;"><b>Dog ID</b></td>
733
+ <td style="padding: 10px; border: 1px solid #ddd;"><b>Images</b></td>
734
+ <td style="padding: 10px; border: 1px solid #ddd;"><b>Avg Confidence</b></td>
735
+ <td style="padding: 10px; border: 1px solid #ddd;"><b>Avg Quality</b></td>
736
+ <td style="padding: 10px; border: 1px solid #ddd;"><b>Quality Range</b></td>
737
+ </tr>
 
 
738
  """
739
+
740
+ for dog_id, dog_info in session_data['dogs'].items():
741
+ avg_quality = np.mean(dog_info['quality_scores'])
742
+ min_quality = min(dog_info['quality_scores'])
743
+ max_quality = max(dog_info['quality_scores'])
744
+
745
+ row_style = "background-color: #ecf0f1;" if int(dog_id) % 2 == 0 else ""
746
+ html += f"""
747
+ <tr style="{row_style}">
748
+ <td style="padding: 10px; border: 1px solid #ddd;">Dog {dog_id}</td>
749
+ <td style="padding: 10px; border: 1px solid #ddd;">{dog_info['num_images']}</td>
750
+ <td style="padding: 10px; border: 1px solid #ddd;">{dog_info['avg_confidence']:.2%}</td>
751
+ <td style="padding: 10px; border: 1px solid #ddd;">{avg_quality:.1f}</td>
752
+ <td style="padding: 10px; border: 1px solid #ddd;">{min_quality:.1f} - {max_quality:.1f}</td>
753
+ </tr>
754
+ """
755
+
756
+ html += "</table>"
757
 
758
+ html += """
759
+ <div style="margin-top: 20px; padding: 10px; background-color: #d4edda; border-radius: 5px;">
760
+ <p style="margin: 0; color: #155724;">
761
+ βœ… <b>Processing Complete!</b> Click "Save Results & Proceed" to continue to verification step.
762
+ </p>
763
+ </div>
 
764
  </div>
765
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
766
 
767
+ return html
768
+
769
+ def process_wrapper(video, threshold, max_img, sample):
770
+ """Process video and format results"""
771
+ if not video:
772
+ return None, "", "Please upload a video", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
773
 
774
+ # Process video
775
+ for update in self.process_video(video, threshold, int(max_img), int(sample)):
776
+ if 'progress' in update:
777
+ yield None, "", update['status'], gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
778
+ else:
779
+ # Store session data
780
+ self.current_session = update['session']
781
+ # Format results as table
782
+ formatted_table = format_results_table(update['session'])
783
+ yield update['session'], formatted_table, "Complete! βœ…", gr.update(visible=False), gr.update(visible=True), gr.update(visible=True)
784
+
785
+ def save_and_proceed():
786
+ """Save current results and notify user"""
787
+ if self.current_session and self.processed_dogs:
788
+ # Save to permanent database
789
+ self.save_to_database()
790
+
791
+ # Debug info
792
+ dog_count = len(self.processed_dogs)
793
+ img_count = sum(d.get('num_images', 0) for d in self.processed_dogs.values())
794
+
795
+ message = f"""βœ… Results saved successfully to database!
796
+
797
+ πŸ“Š Summary:
798
+ - Total dogs in database: {dog_count}
799
+ - Total images: {img_count}
800
+ - Data location: {self.database_dir}
801
+
802
+ You can now proceed to Step 2: Verify & Clean
803
+ Click the 'Refresh List' button in Step 2 to load all dogs."""
804
+
805
+ return message, gr.update(visible=True)
806
+ return "❌ No results to save. Please process a video first.", gr.update(visible=False)
807
+
808
+ def clear_results():
809
+ """Clear current processing results (not database)"""
810
+ self.current_session = None
811
+ if self.temp_dir.exists():
812
+ shutil.rmtree(self.temp_dir)
813
+ self.temp_dir.mkdir()
814
+ return None, "", "", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
815
+
816
+ process_btn.click(
817
+ process_wrapper,
818
+ inputs=[video_input, reid_threshold, max_images, sample_rate],
819
+ outputs=[processing_state, results_display, progress_bar, save_status, save_proceed_btn, clear_btn]
820
+ )
821
+
822
+ save_proceed_btn.click(
823
+ save_and_proceed,
824
+ outputs=[save_status, save_status]
825
+ )
826
+
827
+ clear_btn.click(
828
+ clear_results,
829
+ outputs=[processing_state, results_display, progress_bar, save_status, save_proceed_btn, clear_btn]
830
+ )
831
 
832
  # Step 2: Verify & Clean
833
  with gr.Tab("βœ… Step 2: Verify & Clean", id=1):
 
839
  choices=[],
840
  interactive=True
841
  )
842
+
843
+ # Add diagnostic and management buttons
844
+ with gr.Row():
845
  refresh_btn = gr.Button("πŸ”„ Refresh List")
846
+ diagnose_btn = gr.Button("πŸ” Diagnose Data", variant="secondary")
847
+ clear_db_btn = gr.Button("⚠️ Clear All Database", variant="stop")
848
+
849
+ diagnostic_output = gr.Textbox(label="Diagnostic Info", visible=False)
850
 
851
  image_gallery = gr.Gallery(
852
  label="Dog Images (Click to select for removal)",
 
856
  rows=3,
857
  object_fit="contain",
858
  height="auto",
859
+ type="numpy",
860
+ interactive=False
861
  )
862
 
863
  with gr.Row():
864
+ selected_images = gr.Textbox(
865
+ label="Selected Image Indices (comma-separated)",
866
+ placeholder="e.g., 0,2,5",
867
+ interactive=True
868
+ )
869
  remove_selected_btn = gr.Button("πŸ—‘ Remove Selected Images", variant="secondary")
870
  delete_dog_btn = gr.Button("❌ Delete Entire Dog", variant="stop")
871
 
872
  status_text = gr.Textbox(label="Status", interactive=False)
873
 
874
  def refresh_dogs():
875
+ """Refresh the dog list from all available data"""
876
+ # Load from database
877
+ self.load_database()
878
+
879
+ if not self.processed_dogs:
880
  return gr.update(choices=[], value=None)
881
 
882
  choices = [f"Dog {dog_id}" for dog_id in sorted(self.processed_dogs.keys())]
 
884
  return gr.update(choices=choices, value=choices[0])
885
  return gr.update(choices=[], value=None)
886
 
887
+ def diagnose_data():
888
+ """Show diagnostic information about saved data"""
889
+ info = []
890
+ info.append("=== DIAGNOSTIC INFORMATION ===\n")
891
+
892
+ # Check session
893
+ if self.current_session:
894
+ info.append(f"βœ… Session exists: {self.current_session['num_dogs']} dogs, {self.current_session['total_images']} images")
895
+ else:
896
+ info.append("❌ No current session data")
897
+
898
+ # Check processed dogs
899
+ if self.processed_dogs:
900
+ info.append(f"βœ… Processed dogs dict: {len(self.processed_dogs)} dogs")
901
+ for dog_id, data in self.processed_dogs.items():
902
+ info.append(f" - Dog {dog_id}: {data.get('num_images', 0)} images, conf={data.get('avg_confidence', 0):.2f}")
903
+ else:
904
+ info.append("❌ No processed dogs data")
905
+
906
+ # Check temp directory
907
+ if self.temp_dir.exists():
908
+ info.append(f"βœ… Temp directory exists: {self.temp_dir}")
909
+ dog_dirs = list(self.temp_dir.glob("dog_*"))
910
+ info.append(f" - Found {len(dog_dirs)} dog directories")
911
+ for dog_dir in sorted(dog_dirs):
912
+ if (dog_dir / 'full').exists():
913
+ img_count = len(list((dog_dir / 'full').glob("*.jpg")))
914
+ info.append(f" β€’ {dog_dir.name}: {img_count} full images")
915
+ else:
916
+ info.append("❌ Temp directory not found")
917
+
918
+ # Check database directory
919
+ if self.database_dir.exists():
920
+ info.append(f"βœ… Database directory exists: {self.database_dir}")
921
+ dog_dirs = list(self.database_dir.glob("dog_*"))
922
+ info.append(f" - Found {len(dog_dirs)} dog directories")
923
+ for dog_dir in sorted(dog_dirs):
924
+ if (dog_dir / 'full').exists():
925
+ img_count = len(list((dog_dir / 'full').glob("*.jpg")))
926
+ info.append(f" β€’ {dog_dir.name}: {img_count} full images")
927
+ else:
928
+ info.append("❌ Database directory not found")
929
+
930
+ return "\n".join(info), gr.update(visible=True)
931
+
932
  def show_dog_images(dog_selection):
933
  """Display images for selected dog"""
934
  if not dog_selection:
 
944
  print(f"Error loading images: {e}")
945
  return []
946
 
947
+ def remove_selected(dog_selection, indices_str):
948
+ """Remove selected images based on text input"""
949
+ if not dog_selection or not indices_str:
950
+ return "No images selected", []
951
 
952
+ try:
953
+ # Parse comma-separated indices
954
+ indices = [int(i.strip()) for i in indices_str.split(',')]
955
+ dog_id = int(dog_selection.split()[1])
956
+
957
+ self.remove_images(dog_id, indices)
958
+
959
+ # Update database
960
+ self.save_to_database()
961
+
962
+ return f"Removed {len(indices)} images", self.get_dog_images(dog_id)
963
+ except Exception as e:
964
+ return f"Error: {str(e)}", []
965
 
966
  def delete_dog(dog_selection):
967
  if not dog_selection:
968
+ return "No dog selected", []
969
 
970
  dog_id = int(dog_selection.split()[1])
971
  self.delete_dog(dog_id)
972
+
973
+ # Update database
974
+ self.save_to_database()
975
+
976
+ return f"Deleted Dog {dog_id}", []
977
+
978
+ def clear_all_database():
979
+ """Clear entire database"""
980
+ self.clear_database()
981
+ return "Database cleared successfully", gr.update(choices=[], value=None), []
982
 
983
  refresh_btn.click(refresh_dogs, outputs=dog_selector)
984
+ diagnose_btn.click(diagnose_data, outputs=[diagnostic_output, diagnostic_output])
985
  dog_selector.change(show_dog_images, inputs=dog_selector, outputs=image_gallery)
986
  remove_selected_btn.click(
987
  remove_selected,
988
+ inputs=[dog_selector, selected_images],
989
  outputs=[status_text, image_gallery]
990
  )
991
+ delete_dog_btn.click(
992
+ delete_dog,
993
+ inputs=dog_selector,
994
+ outputs=[status_text, image_gallery]
995
+ )
996
+ clear_db_btn.click(
997
+ clear_all_database,
998
+ outputs=[status_text, dog_selector, image_gallery]
999
+ )
1000
 
1001
  # Step 3: Export Dataset
1002
  with gr.Tab("πŸ’Ύ Step 3: Export Dataset", id=2):
 
1014
  label="Export Format"
1015
  )
1016
 
1017
+ with gr.Row():
1018
+ export_btn = gr.Button("πŸ“¦ Export Final Dataset", variant="primary", size="lg")
1019
+ export_status = gr.Button("πŸ“Š Check Export Status", variant="secondary")
1020
+
1021
  export_output = gr.Textbox(label="Export Path", interactive=False)
1022
  download_file = gr.File(label="Download Dataset", interactive=False)
1023
  stats_display = gr.Markdown()
1024
 
1025
  def export_dataset(format_type):
1026
+ try:
1027
+ zip_path = self.save_final_dataset(format_type)
1028
+
1029
+ # Get statistics
1030
+ with open(self.final_dir / 'metadata.json', 'r') as f:
1031
+ metadata = json.load(f)
1032
+
1033
+ stats = f"""
1034
+ ### βœ… Dataset Exported Successfully!
1035
+
1036
+ - **Total Dogs**: {metadata['total_dogs']}
1037
+ - **Total Images**: {metadata['total_images']}
1038
+ - **Training Images**: {metadata.get('train_images', 'N/A')}
1039
+ - **Validation Images**: {metadata.get('val_images', 'N/A')}
1040
+
1041
+ Dataset is ready for ResNet fine-tuning!
1042
+ Download the ZIP file below.
1043
+ """
1044
+
1045
+ return zip_path, zip_path, stats
1046
+ except Exception as e:
1047
+ return "", None, f"### ❌ Export Error\n{str(e)}"
1048
+
1049
+ def check_export_status():
1050
+ """Check what data is available for export"""
1051
+ total_dogs = len(self.processed_dogs)
1052
+ total_images = sum(d.get('num_images', 0) for d in self.processed_dogs.values())
1053
 
1054
+ status = f"""
1055
+ ### πŸ“Š Export Status
1056
 
1057
+ **Available Data:**
1058
+ - Dogs in database: {total_dogs}
1059
+ - Total images: {total_images}
 
1060
 
1061
+ {'βœ… Ready to export!' if total_dogs > 0 else '❌ No data available. Process videos first.'}
1062
  """
1063
+ return status
 
1064
 
1065
  export_btn.click(
1066
  export_dataset,
1067
  inputs=format_selector,
1068
  outputs=[export_output, download_file, stats_display]
1069
  )
1070
+
1071
+ export_status.click(
1072
+ check_export_status,
1073
+ outputs=stats_display
1074
+ )
1075
 
1076
  return app
1077