megadetector 10.0.5__py3-none-any.whl → 10.0.6__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.
Potentially problematic release.
This version of megadetector might be problematic. Click here for more details.
- megadetector/detection/process_video.py +15 -6
- megadetector/detection/video_utils.py +132 -21
- {megadetector-10.0.5.dist-info → megadetector-10.0.6.dist-info}/METADATA +1 -1
- {megadetector-10.0.5.dist-info → megadetector-10.0.6.dist-info}/RECORD +7 -7
- {megadetector-10.0.5.dist-info → megadetector-10.0.6.dist-info}/WHEEL +0 -0
- {megadetector-10.0.5.dist-info → megadetector-10.0.6.dist-info}/licenses/LICENSE +0 -0
- {megadetector-10.0.5.dist-info → megadetector-10.0.6.dist-info}/top_level.txt +0 -0
|
@@ -81,8 +81,8 @@ class ProcessVideoOptions:
|
|
|
81
81
|
self.augment = False
|
|
82
82
|
|
|
83
83
|
#: By default, a video with no frames (or no frames retrievable with the current parameters)
|
|
84
|
-
#: is
|
|
85
|
-
self.
|
|
84
|
+
#: is silently stored as a failure; this causes it to halt execution.
|
|
85
|
+
self.exit_on_empty_video = False
|
|
86
86
|
|
|
87
87
|
#: Detector-specific options
|
|
88
88
|
self.detector_options = None
|
|
@@ -134,6 +134,14 @@ def process_videos(options):
|
|
|
134
134
|
# Check for incompatible options
|
|
135
135
|
_validate_video_options(options)
|
|
136
136
|
|
|
137
|
+
if options.output_json_file is None:
|
|
138
|
+
video_file = options.input_video_file.replace('\\','/')
|
|
139
|
+
if video_file.endswith('/'):
|
|
140
|
+
video_file = video_file[:-1]
|
|
141
|
+
options.output_json_file = video_file + '.json'
|
|
142
|
+
print('Output file not specified, defaulting to {}'.format(
|
|
143
|
+
options.output_json_file))
|
|
144
|
+
|
|
137
145
|
assert options.output_json_file.endswith('.json'), \
|
|
138
146
|
'Illegal output file {}'.format(options.output_json_file)
|
|
139
147
|
|
|
@@ -173,7 +181,7 @@ def process_videos(options):
|
|
|
173
181
|
every_n_frames=every_n_frames_param,
|
|
174
182
|
verbose=options.verbose,
|
|
175
183
|
files_to_process_relative=[video_bn],
|
|
176
|
-
|
|
184
|
+
error_on_empty_video=options.exit_on_empty_video)
|
|
177
185
|
|
|
178
186
|
else:
|
|
179
187
|
|
|
@@ -187,7 +195,7 @@ def process_videos(options):
|
|
|
187
195
|
every_n_frames=every_n_frames_param,
|
|
188
196
|
verbose=options.verbose,
|
|
189
197
|
recursive=options.recursive,
|
|
190
|
-
|
|
198
|
+
error_on_empty_video=options.exit_on_empty_video)
|
|
191
199
|
|
|
192
200
|
# ...whether we're processing a file or a folder
|
|
193
201
|
|
|
@@ -414,9 +422,10 @@ def main(): # noqa
|
|
|
414
422
|
action='store_true',
|
|
415
423
|
help='Enable image augmentation')
|
|
416
424
|
|
|
417
|
-
parser.add_argument('--
|
|
425
|
+
parser.add_argument('--exit_on_empty_video',
|
|
418
426
|
action='store_true',
|
|
419
|
-
help='By default, videos with no retrievable frames
|
|
427
|
+
help=('By default, videos with no retrievable frames are stored as failures; this' \
|
|
428
|
+
'causes them to halt execution'))
|
|
420
429
|
|
|
421
430
|
parser.add_argument(
|
|
422
431
|
'--detector_options',
|
|
@@ -101,6 +101,99 @@ def find_videos(dirname,
|
|
|
101
101
|
return find_video_strings(files)
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
#%% Shared function for opening videos
|
|
105
|
+
|
|
106
|
+
DEFAULT_BACKEND = -1
|
|
107
|
+
|
|
108
|
+
# This is the order in which we'll try to open backends.
|
|
109
|
+
#
|
|
110
|
+
# In general, the defaults are as follows, though they vary depending
|
|
111
|
+
# on what's installed:
|
|
112
|
+
#
|
|
113
|
+
# Windows: CAP_DSHOW or CAP_MSMF
|
|
114
|
+
# Linux: CAP_FFMPEG
|
|
115
|
+
# macOS: CAP_AVFOUNDATION
|
|
116
|
+
#
|
|
117
|
+
# Technically if the default fails, we may try the same backend again, but this
|
|
118
|
+
# is rare, and it's not worth the complexity of figuring out what the system
|
|
119
|
+
# default is.
|
|
120
|
+
backend_id_to_name = {
|
|
121
|
+
DEFAULT_BACKEND:'default',
|
|
122
|
+
cv2.CAP_FFMPEG: 'CAP_FFMPEG',
|
|
123
|
+
cv2.CAP_DSHOW: 'CAP_DSHOW',
|
|
124
|
+
cv2.CAP_MSMF: 'CAP_MSMF',
|
|
125
|
+
cv2.CAP_AVFOUNDATION: 'CAP_AVFOUNDATION',
|
|
126
|
+
cv2.CAP_GSTREAMER: 'CAP_GSTREAMER'
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
def open_video(video_path,verbose=False):
|
|
130
|
+
"""
|
|
131
|
+
Open the video at [video_path], trying multiple OpenCV backends if necessary.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
video_path (str): the file to open
|
|
135
|
+
verbose (bool, optional): enable additional debug output
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
(cv2.VideoCapture,image): a tuple containing (a) the open video capture device
|
|
139
|
+
(or None if no backends succeeded) and (b) the first frame of the video (or None)
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
if not os.path.isfile(video_path):
|
|
143
|
+
print('Video file {} not found'.format(video_path))
|
|
144
|
+
return None,None
|
|
145
|
+
|
|
146
|
+
backend_ids = backend_id_to_name.keys()
|
|
147
|
+
|
|
148
|
+
for backend_id in backend_ids:
|
|
149
|
+
|
|
150
|
+
backend_name = backend_id_to_name[backend_id]
|
|
151
|
+
if verbose:
|
|
152
|
+
print('Trying backend {}'.format(backend_name))
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
if backend_id == DEFAULT_BACKEND:
|
|
156
|
+
vidcap = cv2.VideoCapture(video_path)
|
|
157
|
+
else:
|
|
158
|
+
vidcap = cv2.VideoCapture(video_path, backend_id)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
if verbose:
|
|
161
|
+
print('Warning: error opening {} with backend {}: {}'.format(
|
|
162
|
+
video_path,backend_name,str(e)))
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
if not vidcap.isOpened():
|
|
166
|
+
if verbose:
|
|
167
|
+
print('Warning: isOpened() is False for {} with backend {}'.format(
|
|
168
|
+
video_path,backend_name))
|
|
169
|
+
try:
|
|
170
|
+
vidcap.release()
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
success, image = vidcap.read()
|
|
176
|
+
if success and (image is not None):
|
|
177
|
+
if verbose:
|
|
178
|
+
print('Successfully opened {} with backend: {}'.format(
|
|
179
|
+
video_path,backend_name))
|
|
180
|
+
return vidcap,image
|
|
181
|
+
|
|
182
|
+
print('Warning: failed to open {} with backend {}'.format(
|
|
183
|
+
video_path,backend_name))
|
|
184
|
+
try:
|
|
185
|
+
vidcap.release()
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
# ...for each backend
|
|
190
|
+
|
|
191
|
+
print('Error: failed to open {} with any backend'.format(video_path))
|
|
192
|
+
return None,None
|
|
193
|
+
|
|
194
|
+
# ...def open_video(...)
|
|
195
|
+
|
|
196
|
+
|
|
104
197
|
#%% Functions for rendering frames to video and vice-versa
|
|
105
198
|
|
|
106
199
|
# http://tsaith.github.io/combine-images-into-a-video-with-python-3-and-opencv-3.html
|
|
@@ -146,21 +239,32 @@ def frames_to_video(images, fs, output_file_name, codec_spec=default_fourcc):
|
|
|
146
239
|
cv2.destroyAllWindows()
|
|
147
240
|
|
|
148
241
|
|
|
149
|
-
def get_video_fs(input_video_file):
|
|
242
|
+
def get_video_fs(input_video_file,verbose=False):
|
|
150
243
|
"""
|
|
151
244
|
Retrieves the frame rate of [input_video_file].
|
|
152
245
|
|
|
153
246
|
Args:
|
|
154
247
|
input_video_file (str): video file for which we want the frame rate
|
|
248
|
+
verbose (bool, optional): enable additional debug output
|
|
155
249
|
|
|
156
250
|
Returns:
|
|
157
|
-
float: the frame rate of [input_video_file]
|
|
251
|
+
float: the frame rate of [input_video_file], or None if no frame
|
|
252
|
+
rate could be extracted
|
|
158
253
|
"""
|
|
159
254
|
|
|
160
|
-
assert os.path.isfile(input_video_file),
|
|
161
|
-
|
|
255
|
+
assert os.path.isfile(input_video_file), \
|
|
256
|
+
'File {} not found'.format(input_video_file)
|
|
257
|
+
vidcap,_ = open_video(input_video_file,verbose=verbose)
|
|
258
|
+
if vidcap is None:
|
|
259
|
+
if verbose:
|
|
260
|
+
print('Failed to get frame rate for {}'.format(input_video_file))
|
|
261
|
+
return None
|
|
162
262
|
fs = vidcap.get(cv2.CAP_PROP_FPS)
|
|
163
|
-
|
|
263
|
+
try:
|
|
264
|
+
vidcap.release()
|
|
265
|
+
except Exception as e:
|
|
266
|
+
print('Warning: error closing video handle for {}: {}'.format(
|
|
267
|
+
input_video_file,str(e)))
|
|
164
268
|
return fs
|
|
165
269
|
|
|
166
270
|
|
|
@@ -249,7 +353,7 @@ def run_callback_on_frames(input_video_file,
|
|
|
249
353
|
of the video, no frames are extracted. Can also be a single int, specifying
|
|
250
354
|
a single frame number.
|
|
251
355
|
allow_empty_videos (bool, optional): Just print a warning if a video appears to have no
|
|
252
|
-
frames (by default, this
|
|
356
|
+
frames (by default, this raises an Exception).
|
|
253
357
|
|
|
254
358
|
Returns:
|
|
255
359
|
dict: dict with keys 'frame_filenames' (list), 'frame_rate' (float), 'results' (list).
|
|
@@ -271,7 +375,7 @@ def run_callback_on_frames(input_video_file,
|
|
|
271
375
|
|
|
272
376
|
try:
|
|
273
377
|
|
|
274
|
-
vidcap =
|
|
378
|
+
vidcap,image = open_video(input_video_file,verbose=verbose)
|
|
275
379
|
n_frames = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
276
380
|
frame_rate = vidcap.get(cv2.CAP_PROP_FPS)
|
|
277
381
|
|
|
@@ -300,7 +404,11 @@ def run_callback_on_frames(input_video_file,
|
|
|
300
404
|
# frame_number = 0
|
|
301
405
|
for frame_number in range(0,n_frames):
|
|
302
406
|
|
|
303
|
-
|
|
407
|
+
# We've already read the first frame, when we opened the video
|
|
408
|
+
if frame_number != 0:
|
|
409
|
+
success,image = vidcap.read()
|
|
410
|
+
else:
|
|
411
|
+
success = True
|
|
304
412
|
|
|
305
413
|
if not success:
|
|
306
414
|
assert image is None
|
|
@@ -363,9 +471,9 @@ def run_callback_on_frames_for_folder(input_video_folder,
|
|
|
363
471
|
frame_callback,
|
|
364
472
|
every_n_frames=None,
|
|
365
473
|
verbose=False,
|
|
366
|
-
allow_empty_videos=False,
|
|
367
474
|
recursive=True,
|
|
368
|
-
files_to_process_relative=None
|
|
475
|
+
files_to_process_relative=None,
|
|
476
|
+
error_on_empty_video=False):
|
|
369
477
|
"""
|
|
370
478
|
Calls the function frame_callback(np.array,image_id) on all (or selected) frames in
|
|
371
479
|
all videos in [input_video_folder].
|
|
@@ -382,10 +490,10 @@ def run_callback_on_frames_for_folder(input_video_folder,
|
|
|
382
490
|
interpreted as a sampling rate in seconds, which is rounded to the nearest frame
|
|
383
491
|
sampling rate.
|
|
384
492
|
verbose (bool, optional): enable additional debug console output
|
|
385
|
-
allow_empty_videos (bool, optional): Just print a warning if a video appears to have no
|
|
386
|
-
frames (by default, this is an error).
|
|
387
493
|
recursive (bool, optional): recurse into [input_video_folder]
|
|
388
494
|
files_to_process_relative (list, optional): only process specific relative paths
|
|
495
|
+
error_on_empty_video (bool, optional): by default, videos with errors or no valid frames
|
|
496
|
+
are silently stored as failures; this turns them into exceptions
|
|
389
497
|
|
|
390
498
|
Returns:
|
|
391
499
|
dict: dict with keys 'video_filenames' (list of str), 'frame_rates' (list of floats),
|
|
@@ -440,18 +548,21 @@ def run_callback_on_frames_for_folder(input_video_folder,
|
|
|
440
548
|
every_n_frames=every_n_frames,
|
|
441
549
|
verbose=verbose,
|
|
442
550
|
frames_to_process=None,
|
|
443
|
-
allow_empty_videos=
|
|
551
|
+
allow_empty_videos=False)
|
|
444
552
|
|
|
445
553
|
except Exception as e:
|
|
446
554
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
555
|
+
if (not error_on_empty_video):
|
|
556
|
+
print('Warning: error processing video {}: {}'.format(
|
|
557
|
+
video_fn_abs,str(e)
|
|
558
|
+
))
|
|
559
|
+
to_return['frame_rates'].append(-1.0)
|
|
560
|
+
failure_result = {}
|
|
561
|
+
failure_result['failure'] = 'Failure processing video: {}'.format(str(e))
|
|
562
|
+
to_return['results'].append(failure_result)
|
|
563
|
+
continue
|
|
564
|
+
else:
|
|
565
|
+
raise
|
|
455
566
|
|
|
456
567
|
# ...try/except
|
|
457
568
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: megadetector
|
|
3
|
-
Version: 10.0.
|
|
3
|
+
Version: 10.0.6
|
|
4
4
|
Summary: MegaDetector is an AI model that helps conservation folks spend less time doing boring things with camera trap images.
|
|
5
5
|
Author-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
|
|
6
6
|
Maintainer-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
|
|
@@ -72,7 +72,7 @@ megadetector/data_management/lila/lila_common.py,sha256=IRWs46TrxcjckLidDwXPmb5O
|
|
|
72
72
|
megadetector/data_management/lila/test_lila_metadata_urls.py,sha256=ThU78Ks5V3rFyJSKStFcM5M2yTlhR_pgMTa6_KuF5Hs,5256
|
|
73
73
|
megadetector/detection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
74
74
|
megadetector/detection/change_detection.py,sha256=Ne3GajbH_0KPBU8ruHp4Rkr0uKd5oKAMQ3CQTRKRHgQ,28659
|
|
75
|
-
megadetector/detection/process_video.py,sha256=
|
|
75
|
+
megadetector/detection/process_video.py,sha256=kuQHrpOC3LQo9ecqJPpzkds9fZVnoLmrfJw_yh-oxi8,17890
|
|
76
76
|
megadetector/detection/pytorch_detector.py,sha256=ixU-2_AnCwEP33FaaqTZRoi1WxIUeM4x_ksbNT-tezA,59817
|
|
77
77
|
megadetector/detection/run_detector.py,sha256=TTX29zxDN_O7ja61sOmMIVewUz3yRvKg1D1AAYhVEkc,46851
|
|
78
78
|
megadetector/detection/run_detector_batch.py,sha256=aZgiywL6arrdQ_l3jzlHctlccqL537lwVStjhi1hIWw,89823
|
|
@@ -80,7 +80,7 @@ megadetector/detection/run_inference_with_yolov5_val.py,sha256=A-AQuARVVy7oR9Wte
|
|
|
80
80
|
megadetector/detection/run_md_and_speciesnet.py,sha256=Dp_SpJZp0pX9jzFtxM6zPCyBNq49uyQpMDAdNDLVorM,50280
|
|
81
81
|
megadetector/detection/run_tiled_inference.py,sha256=wrQkKIloHBO9v2i0nZ1_Tt75iFtVrnco3Y4FafoVxdw,39382
|
|
82
82
|
megadetector/detection/tf_detector.py,sha256=3b2MiqgMw8KBDzHQliUSDXWrmKpa9iZnfe6EgYpMcYo,8398
|
|
83
|
-
megadetector/detection/video_utils.py,sha256=
|
|
83
|
+
megadetector/detection/video_utils.py,sha256=AlmNJ5n7qmv3Z65HcjI1ALAxXMmyTG3pUiO7oJm-8rs,53363
|
|
84
84
|
megadetector/postprocessing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
85
85
|
megadetector/postprocessing/add_max_conf.py,sha256=9MYtsH2mwkiaZb7Qcor5J_HskfAj7d9srp8G_Qldpk0,1722
|
|
86
86
|
megadetector/postprocessing/categorize_detections_by_size.py,sha256=DpZpRNFlyeOfWuOc6ICuENgIWDCEtiErJ_frBZp9lYM,5382
|
|
@@ -140,8 +140,8 @@ megadetector/visualization/visualization_utils.py,sha256=E5uvysS3F1S_yiPFxZty3U2
|
|
|
140
140
|
megadetector/visualization/visualize_db.py,sha256=8YDWSR0eMehXYdPtak9z8UUw35xV7hu-0eCuzgSLjWc,25558
|
|
141
141
|
megadetector/visualization/visualize_detector_output.py,sha256=HpWh7ugwo51YBHsFi40iAp9G-uRAMMjgsm8H_uBolBs,20295
|
|
142
142
|
megadetector/visualization/visualize_video_output.py,sha256=4A5uit_JVV46kZCsO6j0bZ5-o6ZTAlXKuVvvR_xWpho,20266
|
|
143
|
-
megadetector-10.0.
|
|
144
|
-
megadetector-10.0.
|
|
145
|
-
megadetector-10.0.
|
|
146
|
-
megadetector-10.0.
|
|
147
|
-
megadetector-10.0.
|
|
143
|
+
megadetector-10.0.6.dist-info/licenses/LICENSE,sha256=RMa3qq-7Cyk7DdtqRj_bP1oInGFgjyHn9-PZ3PcrqIs,1100
|
|
144
|
+
megadetector-10.0.6.dist-info/METADATA,sha256=KjAHAEEE_ELiV6BtcFx_bfjKJg9fxFFEOF_0uHtbp3s,6352
|
|
145
|
+
megadetector-10.0.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
146
|
+
megadetector-10.0.6.dist-info/top_level.txt,sha256=wf9DXa8EwiOSZ4G5IPjakSxBPxTDjhYYnqWRfR-zS4M,13
|
|
147
|
+
megadetector-10.0.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|