coralnet-toolbox 0.0.74__py2.py3-none-any.whl → 0.0.75__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. coralnet_toolbox/Explorer/QtDataItem.py +52 -22
  2. coralnet_toolbox/Explorer/QtExplorer.py +277 -1600
  3. coralnet_toolbox/Explorer/QtSettingsWidgets.py +101 -15
  4. coralnet_toolbox/Explorer/QtViewers.py +1568 -0
  5. coralnet_toolbox/Explorer/transformer_models.py +59 -0
  6. coralnet_toolbox/Explorer/yolo_models.py +112 -0
  7. coralnet_toolbox/MachineLearning/ImportDataset/QtBase.py +239 -147
  8. coralnet_toolbox/MachineLearning/VideoInference/YOLO3D/run.py +102 -16
  9. coralnet_toolbox/QtAnnotationWindow.py +16 -10
  10. coralnet_toolbox/QtImageWindow.py +3 -7
  11. coralnet_toolbox/Rasters/RasterTableModel.py +20 -0
  12. coralnet_toolbox/SAM/QtDeployGenerator.py +1 -4
  13. coralnet_toolbox/SAM/QtDeployPredictor.py +1 -3
  14. coralnet_toolbox/SeeAnything/QtDeployGenerator.py +131 -106
  15. coralnet_toolbox/SeeAnything/QtDeployPredictor.py +45 -3
  16. coralnet_toolbox/Tools/QtPolygonTool.py +42 -3
  17. coralnet_toolbox/Tools/QtRectangleTool.py +30 -0
  18. coralnet_toolbox/__init__.py +1 -1
  19. coralnet_toolbox/utilities.py +21 -0
  20. {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.75.dist-info}/METADATA +6 -3
  21. {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.75.dist-info}/RECORD +25 -22
  22. {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.75.dist-info}/WHEEL +0 -0
  23. {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.75.dist-info}/entry_points.txt +0 -0
  24. {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.75.dist-info}/licenses/LICENSE.txt +0 -0
  25. {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.75.dist-info}/top_level.txt +0 -0
@@ -189,6 +189,21 @@ Examples:
189
189
  help='Filter by specific class IDs (e.g., --classes 0 1 2 for persons, bicycles, cars)'
190
190
  )
191
191
 
192
+ # Frame range parameters
193
+ parser.add_argument(
194
+ '--start_at',
195
+ type=int,
196
+ default=0,
197
+ help='Start processing at this frame number (0-based)'
198
+ )
199
+ parser.add_argument(
200
+ '--end_at',
201
+ type=int,
202
+ default=None,
203
+ help='End processing at this frame number (inclusive, 0-based). If not provided, '
204
+ 'process until the end of the video.'
205
+ )
206
+
192
207
  # Device settings
193
208
  parser.add_argument(
194
209
  '--device',
@@ -222,6 +237,8 @@ Examples:
222
237
  action='store_true',
223
238
  help='Show all visualization windows (result, depth, detection). By default, only the result frame is shown.'
224
239
  )
240
+
241
+
225
242
  return parser.parse_args()
226
243
 
227
244
 
@@ -271,19 +288,25 @@ def main():
271
288
  enable_bev = not args.no_bev
272
289
  enable_display = not args.no_display
273
290
  show_all_frames = args.yes_display
291
+
292
+ # Frame range parameters
293
+ start_frame = args.start_at
294
+ end_frame = args.end_at
295
+
274
296
  # Camera parameters - simplified approach
275
297
  camera_params_file = None # Path to camera parameters file (None to use default parameters)
276
298
  # ===============================================
277
- print(f"\nConfiguration:")
299
+ print("\nConfiguration:")
278
300
  print(f"Input source: {source}")
279
301
  print(f"Output path: {output_path}")
280
302
  if target_size is not None:
281
303
  print(f"Target size: {target_size}px (longest edge)")
282
304
  else:
283
- print(f"Using original resolution (no scaling)")
305
+ print("Using original resolution (no scaling)")
284
306
  print(f"YOLO model: {'Custom path: ' + yolo_model_path if yolo_model_path else 'Size: ' + yolo_model_size}")
285
307
  print(f"Depth model size: {depth_model_size}")
286
308
  print(f"Device: {device}")
309
+ print(f"Frame range: {start_frame} to {end_frame if end_frame is not None else 'end'}")
287
310
  print(f"Tracking: {'enabled' if enable_tracking else 'disabled'}")
288
311
  print(f"Bird's Eye View: {'enabled' if enable_bev else 'disabled'}")
289
312
  print(f"Display: {'enabled' if enable_display else 'disabled'}")
@@ -382,9 +405,52 @@ def main():
382
405
  # Use a scale that works well for the 1-5 meter range
383
406
  bev = BirdEyeView(image_shape=(width, height), scale=100) # Increased scale to spread objects out
384
407
 
385
- # Initialize video writer
386
- fourcc = cv2.VideoWriter_fourcc(*'mp4v')
387
- out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
408
+ # Initialize video writer - use a more reliable codec and check output path
409
+ output_dir = os.path.dirname(output_path)
410
+ if output_dir and not os.path.exists(output_dir):
411
+ os.makedirs(output_dir)
412
+
413
+ # On Windows, try different codec options
414
+ if sys.platform == 'win32':
415
+ try:
416
+ # First try H264 codec
417
+ fourcc = cv2.VideoWriter_fourcc(*'H264')
418
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
419
+
420
+ # Check if writer was successfully initialized
421
+ if not out.isOpened():
422
+ # Fallback to XVID codec
423
+ fourcc = cv2.VideoWriter_fourcc(*'XVID')
424
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
425
+
426
+ if not out.isOpened():
427
+ # Last resort, try MJPG
428
+ fourcc = cv2.VideoWriter_fourcc(*'MJPG')
429
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
430
+
431
+ if not out.isOpened():
432
+ print(f"Warning: Could not create output video with standard codecs. Trying AVI format.")
433
+ # Try changing extension to .avi
434
+ output_path = os.path.splitext(output_path)[0] + '.avi'
435
+ fourcc = cv2.VideoWriter_fourcc(*'XVID')
436
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
437
+ except Exception as e:
438
+ print(f"Error initializing video writer: {e}")
439
+ # Last fallback to MP4V
440
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
441
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
442
+ else:
443
+ # For other platforms, use mp4v codec
444
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
445
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
446
+
447
+ # Check if writer was successfully initialized
448
+ if not out.isOpened():
449
+ print(f"Error: Could not create output video file at {output_path}")
450
+ print("Continuing without saving output video.")
451
+ out = None
452
+ else:
453
+ print(f"Successfully opened output video file: {output_path}")
388
454
 
389
455
  # Initialize variables for FPS calculation
390
456
  frame_count = 0
@@ -394,6 +460,7 @@ def main():
394
460
  print("Starting processing...")
395
461
 
396
462
  # Main loop
463
+ current_frame = 0
397
464
  while True:
398
465
  # Check for key press at the beginning of each loop
399
466
  if enable_display:
@@ -401,11 +468,22 @@ def main():
401
468
  if key == ord('q') or key == 27 or (key & 0xFF) == ord('q') or (key & 0xFF) == 27:
402
469
  print("Exiting program...")
403
470
  break
404
- try: # Read frame
471
+ try:
472
+ # Read frame
405
473
  ret, frame = cap.read()
406
474
  if not ret:
407
475
  break
408
476
 
477
+ # Skip frames before start_frame
478
+ if current_frame < start_frame:
479
+ current_frame += 1
480
+ continue
481
+
482
+ # Stop if we've processed the end_frame
483
+ if end_frame is not None and current_frame > end_frame:
484
+ print(f"Reached end frame {end_frame}, stopping processing.")
485
+ break
486
+
409
487
  # Apply resizing if needed
410
488
  if target_size is not None:
411
489
  frame = cv2.resize(frame, (width, height))
@@ -543,7 +621,7 @@ def main():
543
621
  fps_display = f"FPS: {fps_value:.1f}"
544
622
 
545
623
  # Add FPS and device info to the result frame (top-right corner)
546
- text = f"{fps_display} | Device: {device}"
624
+ text = f"{fps_display} | Device: {device} | Frame: {current_frame}"
547
625
 
548
626
  # Calculate text size for right alignment
549
627
  (text_width, text_height), _ = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2)
@@ -584,8 +662,9 @@ def main():
584
662
  except Exception as e:
585
663
  print(f"Error adding depth map to result: {e}")
586
664
 
587
- # Write frame to output video
588
- out.write(result_frame)
665
+ # Write frame to output video (only if writer is valid)
666
+ if out is not None and out.isOpened():
667
+ out.write(result_frame)
589
668
 
590
669
  # Display frames only if display is enabled
591
670
  if enable_display:
@@ -594,12 +673,15 @@ def main():
594
673
  cv2.imshow("Depth Map", depth_colored)
595
674
  cv2.imshow("Object Detection", detection_frame)
596
675
 
597
- # Check for key press again at the end of the loop
598
- key = cv2.waitKey(1)
599
- if key == ord('q') or key == 27 or (key & 0xFF) == ord('q') or (key & 0xFF) == 27:
600
- print("Exiting program...")
601
- break
602
-
676
+ # Check for key press again at the end of the loop
677
+ key = cv2.waitKey(1)
678
+ if key == ord('q') or key == 27 or (key & 0xFF) == ord('q') or (key & 0xFF) == 27:
679
+ print("Exiting program...")
680
+ break
681
+
682
+ # Increment frame counter
683
+ current_frame += 1
684
+
603
685
  except Exception as e:
604
686
  print(f"Error processing frame: {e}")
605
687
  # Also check for key press during exception handling
@@ -608,12 +690,16 @@ def main():
608
690
  if key == ord('q') or key == 27 or (key & 0xFF) == ord('q') or (key & 0xFF) == 27:
609
691
  print("Exiting program...")
610
692
  break
693
+
694
+ # Still increment frame counter even if there was an error
695
+ current_frame += 1
611
696
  continue
612
697
 
613
698
  # Clean up
614
699
  print("Cleaning up resources...")
615
700
  cap.release()
616
- out.release()
701
+ if out is not None:
702
+ out.release()
617
703
  cv2.destroyAllWindows()
618
704
 
619
705
  print(f"Processing complete. Output saved to {output_path}")
@@ -492,9 +492,6 @@ class AnnotationWindow(QGraphicsView):
492
492
  relative_area = 1.0 # fallback, treat as full image
493
493
 
494
494
  # Step 3: Map ratio to padding factor (smaller annotation = more padding)
495
- # Example: padding_factor = clamp(0.5 * (1/relative_area)**0.5, 0.1, 0.5)
496
- # - For very small annotations, padding approaches 0.5 (50%)
497
- # - For large annotations, padding approaches 0.1 (10%)
498
495
  import math
499
496
  min_padding = 0.1 # 10%
500
497
  max_padding = 0.5 # 50%
@@ -503,23 +500,32 @@ class AnnotationWindow(QGraphicsView):
503
500
  else:
504
501
  padding_factor = min_padding
505
502
 
506
- # Step 4: Apply dynamic padding
507
- padding_x = annotation_rect.width() * padding_factor
508
- padding_y = annotation_rect.height() * padding_factor
503
+ # Step 4: Apply dynamic padding with minimum values to prevent zero width/height
504
+ min_padding_absolute = 1.0 # Minimum padding in pixels
505
+ padding_x = max(annotation_rect.width() * padding_factor, min_padding_absolute)
506
+ padding_y = max(annotation_rect.height() * padding_factor, min_padding_absolute)
509
507
  padded_rect = annotation_rect.adjusted(-padding_x, -padding_y, padding_x, padding_y)
510
508
 
511
509
  # Fit the padded annotation rect in the view
512
510
  self.fitInView(padded_rect, Qt.KeepAspectRatio)
513
511
 
514
- # Update the zoom factor based on the new view transformation
512
+ # Update the zoom factor based on the new view transformation with safety checks
515
513
  view_rect = self.viewport().rect()
516
- zoom_x = view_rect.width() / padded_rect.width()
517
- zoom_y = view_rect.height() / padded_rect.height()
514
+ if padded_rect.width() > 0:
515
+ zoom_x = view_rect.width() / padded_rect.width()
516
+ else:
517
+ zoom_x = 1.0 # Default zoom if width is zero
518
+
519
+ if padded_rect.height() > 0:
520
+ zoom_y = view_rect.height() / padded_rect.height()
521
+ else:
522
+ zoom_y = 1.0 # Default zoom if height is zero
523
+
518
524
  self.zoom_factor = min(zoom_x, zoom_y)
519
525
 
520
526
  # Signal that the view has changed
521
527
  self.viewChanged.emit(*self.get_image_dimensions())
522
-
528
+
523
529
  def cycle_annotations(self, direction):
524
530
  """Cycle through annotations in the specified direction."""
525
531
  # Get the annotations for the current image
@@ -578,13 +578,9 @@ class ImageWindow(QWidget):
578
578
 
579
579
  # Immediately update filtered paths to include the new image
580
580
  # This ensures the image will be visible in the table right away
581
- if self.table_model.filtered_paths == self.raster_manager.image_paths[:-1]:
582
- # No filters are active, so just add the new path
583
- self.table_model.filtered_paths.append(image_path)
584
- self.table_model.dataChanged.emit(
585
- self.table_model.index(0, 0),
586
- self.table_model.index(len(self.table_model.filtered_paths) - 1,
587
- self.table_model.columnCount() - 1))
581
+ if len(self.table_model.filtered_paths) == len(self.raster_manager.image_paths) - 1:
582
+ # No filters are active, so efficiently add the new path to the model
583
+ self.table_model.add_path(image_path)
588
584
  else:
589
585
  # Filters are active, so run filtering again
590
586
  self.filter_images()
@@ -149,6 +149,26 @@ class RasterTableModel(QAbstractTableModel):
149
149
  return Qt.NoItemFlags
150
150
 
151
151
  return Qt.ItemIsEnabled | Qt.ItemIsSelectable
152
+
153
+ def add_path(self, path: str):
154
+ """
155
+ Efficiently add a single path by signaling a row insertion.
156
+
157
+ Args:
158
+ path (str): The image path to add.
159
+ """
160
+ if path in self.raster_manager.image_paths and path not in self.filtered_paths:
161
+ # The position for the new row is at the end of the current list
162
+ row_position = len(self.filtered_paths)
163
+
164
+ # Signal that we are about to insert one row at this position
165
+ self.beginInsertRows(QModelIndex(), row_position, row_position)
166
+
167
+ # Add the data
168
+ self.filtered_paths.append(path)
169
+
170
+ # Signal that the insertion is complete
171
+ self.endInsertRows()
152
172
 
153
173
  def highlight_path(self, path: str, highlighted: bool = True):
154
174
  """
@@ -168,7 +168,7 @@ class DeployGeneratorDialog(QDialog):
168
168
 
169
169
  # Image size control
170
170
  self.imgsz_spinbox = QSpinBox()
171
- self.imgsz_spinbox.setRange(512, 65536)
171
+ self.imgsz_spinbox.setRange(1024, 65536)
172
172
  self.imgsz_spinbox.setSingleStep(1024)
173
173
  self.imgsz_spinbox.setValue(self.imgsz)
174
174
  layout.addRow("Image Size (imgsz):", self.imgsz_spinbox)
@@ -460,9 +460,6 @@ class DeployGeneratorDialog(QDialog):
460
460
  progress_bar.stop_progress()
461
461
  progress_bar.close()
462
462
 
463
- # Exit the dialog box
464
- self.accept()
465
-
466
463
  def get_imgsz(self):
467
464
  """Get the image size for the model."""
468
465
  self.imgsz = self.imgsz_spinbox.value()
@@ -158,7 +158,7 @@ class DeployPredictorDialog(QDialog):
158
158
 
159
159
  # Image size control
160
160
  self.imgsz_spinbox = QSpinBox()
161
- self.imgsz_spinbox.setRange(512, 65536)
161
+ self.imgsz_spinbox.setRange(1024, 65536)
162
162
  self.imgsz_spinbox.setSingleStep(1024)
163
163
  self.imgsz_spinbox.setValue(self.imgsz)
164
164
  layout.addRow("Image Size (imgsz):", self.imgsz_spinbox)
@@ -358,8 +358,6 @@ class DeployPredictorDialog(QDialog):
358
358
  progress_bar.stop_progress()
359
359
  progress_bar.close()
360
360
 
361
- self.accept()
362
-
363
361
  def resize_image(self, image):
364
362
  """
365
363
  Resize the image to the specified size.