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.
- coralnet_toolbox/Explorer/QtDataItem.py +52 -22
- coralnet_toolbox/Explorer/QtExplorer.py +277 -1600
- coralnet_toolbox/Explorer/QtSettingsWidgets.py +101 -15
- coralnet_toolbox/Explorer/QtViewers.py +1568 -0
- coralnet_toolbox/Explorer/transformer_models.py +59 -0
- coralnet_toolbox/Explorer/yolo_models.py +112 -0
- coralnet_toolbox/MachineLearning/ImportDataset/QtBase.py +239 -147
- coralnet_toolbox/MachineLearning/VideoInference/YOLO3D/run.py +102 -16
- coralnet_toolbox/QtAnnotationWindow.py +16 -10
- coralnet_toolbox/QtImageWindow.py +3 -7
- coralnet_toolbox/Rasters/RasterTableModel.py +20 -0
- coralnet_toolbox/SAM/QtDeployGenerator.py +1 -4
- coralnet_toolbox/SAM/QtDeployPredictor.py +1 -3
- coralnet_toolbox/SeeAnything/QtDeployGenerator.py +131 -106
- coralnet_toolbox/SeeAnything/QtDeployPredictor.py +45 -3
- coralnet_toolbox/Tools/QtPolygonTool.py +42 -3
- coralnet_toolbox/Tools/QtRectangleTool.py +30 -0
- coralnet_toolbox/__init__.py +1 -1
- coralnet_toolbox/utilities.py +21 -0
- {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.75.dist-info}/METADATA +6 -3
- {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.75.dist-info}/RECORD +25 -22
- {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.75.dist-info}/WHEEL +0 -0
- {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.75.dist-info}/entry_points.txt +0 -0
- {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.75.dist-info}/licenses/LICENSE.txt +0 -0
- {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(
|
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(
|
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
|
-
|
387
|
-
|
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:
|
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.
|
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
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
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
|
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
|
-
|
508
|
-
|
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
|
-
|
517
|
-
|
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
|
582
|
-
# No filters are active, so
|
583
|
-
self.table_model.
|
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(
|
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(
|
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.
|