megadetector 5.0.28__py3-none-any.whl → 5.0.29__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.

Files changed (176) hide show
  1. megadetector/api/batch_processing/api_core/batch_service/score.py +4 -5
  2. megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +1 -1
  3. megadetector/api/batch_processing/api_support/summarize_daily_activity.py +1 -1
  4. megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +2 -2
  5. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +1 -1
  6. megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +1 -1
  7. megadetector/api/synchronous/api_core/tests/load_test.py +2 -3
  8. megadetector/classification/aggregate_classifier_probs.py +3 -3
  9. megadetector/classification/analyze_failed_images.py +5 -5
  10. megadetector/classification/cache_batchapi_outputs.py +5 -5
  11. megadetector/classification/create_classification_dataset.py +11 -12
  12. megadetector/classification/crop_detections.py +10 -10
  13. megadetector/classification/csv_to_json.py +8 -8
  14. megadetector/classification/detect_and_crop.py +13 -15
  15. megadetector/classification/evaluate_model.py +7 -7
  16. megadetector/classification/identify_mislabeled_candidates.py +6 -6
  17. megadetector/classification/json_to_azcopy_list.py +1 -1
  18. megadetector/classification/json_validator.py +29 -32
  19. megadetector/classification/map_classification_categories.py +9 -9
  20. megadetector/classification/merge_classification_detection_output.py +12 -9
  21. megadetector/classification/prepare_classification_script.py +19 -19
  22. megadetector/classification/prepare_classification_script_mc.py +23 -23
  23. megadetector/classification/run_classifier.py +4 -4
  24. megadetector/classification/save_mislabeled.py +6 -6
  25. megadetector/classification/train_classifier.py +1 -1
  26. megadetector/classification/train_classifier_tf.py +9 -9
  27. megadetector/classification/train_utils.py +10 -10
  28. megadetector/data_management/annotations/annotation_constants.py +1 -1
  29. megadetector/data_management/camtrap_dp_to_coco.py +45 -45
  30. megadetector/data_management/cct_json_utils.py +101 -101
  31. megadetector/data_management/cct_to_md.py +49 -49
  32. megadetector/data_management/cct_to_wi.py +33 -33
  33. megadetector/data_management/coco_to_labelme.py +75 -75
  34. megadetector/data_management/coco_to_yolo.py +189 -189
  35. megadetector/data_management/databases/add_width_and_height_to_db.py +3 -2
  36. megadetector/data_management/databases/combine_coco_camera_traps_files.py +38 -38
  37. megadetector/data_management/databases/integrity_check_json_db.py +202 -188
  38. megadetector/data_management/databases/subset_json_db.py +33 -33
  39. megadetector/data_management/generate_crops_from_cct.py +38 -38
  40. megadetector/data_management/get_image_sizes.py +54 -49
  41. megadetector/data_management/labelme_to_coco.py +130 -124
  42. megadetector/data_management/labelme_to_yolo.py +78 -72
  43. megadetector/data_management/lila/create_lila_blank_set.py +81 -83
  44. megadetector/data_management/lila/create_lila_test_set.py +32 -31
  45. megadetector/data_management/lila/create_links_to_md_results_files.py +18 -18
  46. megadetector/data_management/lila/download_lila_subset.py +21 -24
  47. megadetector/data_management/lila/generate_lila_per_image_labels.py +91 -91
  48. megadetector/data_management/lila/get_lila_annotation_counts.py +30 -30
  49. megadetector/data_management/lila/get_lila_image_counts.py +22 -22
  50. megadetector/data_management/lila/lila_common.py +70 -70
  51. megadetector/data_management/lila/test_lila_metadata_urls.py +13 -14
  52. megadetector/data_management/mewc_to_md.py +339 -340
  53. megadetector/data_management/ocr_tools.py +258 -252
  54. megadetector/data_management/read_exif.py +231 -224
  55. megadetector/data_management/remap_coco_categories.py +26 -26
  56. megadetector/data_management/remove_exif.py +31 -20
  57. megadetector/data_management/rename_images.py +187 -187
  58. megadetector/data_management/resize_coco_dataset.py +41 -41
  59. megadetector/data_management/speciesnet_to_md.py +41 -41
  60. megadetector/data_management/wi_download_csv_to_coco.py +55 -55
  61. megadetector/data_management/yolo_output_to_md_output.py +117 -120
  62. megadetector/data_management/yolo_to_coco.py +195 -188
  63. megadetector/detection/change_detection.py +831 -0
  64. megadetector/detection/process_video.py +340 -337
  65. megadetector/detection/pytorch_detector.py +304 -262
  66. megadetector/detection/run_detector.py +177 -164
  67. megadetector/detection/run_detector_batch.py +364 -363
  68. megadetector/detection/run_inference_with_yolov5_val.py +328 -325
  69. megadetector/detection/run_tiled_inference.py +256 -249
  70. megadetector/detection/tf_detector.py +24 -24
  71. megadetector/detection/video_utils.py +290 -282
  72. megadetector/postprocessing/add_max_conf.py +15 -11
  73. megadetector/postprocessing/categorize_detections_by_size.py +44 -44
  74. megadetector/postprocessing/classification_postprocessing.py +415 -415
  75. megadetector/postprocessing/combine_batch_outputs.py +20 -21
  76. megadetector/postprocessing/compare_batch_results.py +528 -517
  77. megadetector/postprocessing/convert_output_format.py +97 -97
  78. megadetector/postprocessing/create_crop_folder.py +219 -146
  79. megadetector/postprocessing/detector_calibration.py +173 -168
  80. megadetector/postprocessing/generate_csv_report.py +508 -499
  81. megadetector/postprocessing/load_api_results.py +23 -20
  82. megadetector/postprocessing/md_to_coco.py +129 -98
  83. megadetector/postprocessing/md_to_labelme.py +89 -83
  84. megadetector/postprocessing/md_to_wi.py +40 -40
  85. megadetector/postprocessing/merge_detections.py +87 -114
  86. megadetector/postprocessing/postprocess_batch_results.py +313 -298
  87. megadetector/postprocessing/remap_detection_categories.py +36 -36
  88. megadetector/postprocessing/render_detection_confusion_matrix.py +205 -199
  89. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +57 -57
  90. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +27 -28
  91. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +702 -677
  92. megadetector/postprocessing/separate_detections_into_folders.py +226 -211
  93. megadetector/postprocessing/subset_json_detector_output.py +265 -262
  94. megadetector/postprocessing/top_folders_to_bottom.py +45 -45
  95. megadetector/postprocessing/validate_batch_results.py +70 -70
  96. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +52 -52
  97. megadetector/taxonomy_mapping/map_new_lila_datasets.py +15 -15
  98. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +14 -14
  99. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +66 -66
  100. megadetector/taxonomy_mapping/retrieve_sample_image.py +16 -16
  101. megadetector/taxonomy_mapping/simple_image_download.py +8 -8
  102. megadetector/taxonomy_mapping/species_lookup.py +33 -33
  103. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +14 -14
  104. megadetector/taxonomy_mapping/taxonomy_graph.py +10 -10
  105. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +13 -13
  106. megadetector/utils/azure_utils.py +22 -22
  107. megadetector/utils/ct_utils.py +1018 -200
  108. megadetector/utils/directory_listing.py +21 -77
  109. megadetector/utils/gpu_test.py +22 -22
  110. megadetector/utils/md_tests.py +541 -518
  111. megadetector/utils/path_utils.py +1457 -398
  112. megadetector/utils/process_utils.py +41 -41
  113. megadetector/utils/sas_blob_utils.py +53 -49
  114. megadetector/utils/split_locations_into_train_val.py +61 -61
  115. megadetector/utils/string_utils.py +147 -26
  116. megadetector/utils/url_utils.py +463 -173
  117. megadetector/utils/wi_utils.py +2629 -2526
  118. megadetector/utils/write_html_image_list.py +137 -137
  119. megadetector/visualization/plot_utils.py +21 -21
  120. megadetector/visualization/render_images_with_thumbnails.py +37 -73
  121. megadetector/visualization/visualization_utils.py +401 -397
  122. megadetector/visualization/visualize_db.py +197 -190
  123. megadetector/visualization/visualize_detector_output.py +79 -73
  124. {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/METADATA +135 -132
  125. megadetector-5.0.29.dist-info/RECORD +163 -0
  126. {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/WHEEL +1 -1
  127. {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/licenses/LICENSE +0 -0
  128. {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/top_level.txt +0 -0
  129. megadetector/data_management/importers/add_nacti_sizes.py +0 -52
  130. megadetector/data_management/importers/add_timestamps_to_icct.py +0 -79
  131. megadetector/data_management/importers/animl_results_to_md_results.py +0 -158
  132. megadetector/data_management/importers/auckland_doc_test_to_json.py +0 -373
  133. megadetector/data_management/importers/auckland_doc_to_json.py +0 -201
  134. megadetector/data_management/importers/awc_to_json.py +0 -191
  135. megadetector/data_management/importers/bellevue_to_json.py +0 -272
  136. megadetector/data_management/importers/cacophony-thermal-importer.py +0 -793
  137. megadetector/data_management/importers/carrizo_shrubfree_2018.py +0 -269
  138. megadetector/data_management/importers/carrizo_trail_cam_2017.py +0 -289
  139. megadetector/data_management/importers/cct_field_adjustments.py +0 -58
  140. megadetector/data_management/importers/channel_islands_to_cct.py +0 -913
  141. megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
  142. megadetector/data_management/importers/eMammal/eMammal_helpers.py +0 -249
  143. megadetector/data_management/importers/eMammal/make_eMammal_json.py +0 -223
  144. megadetector/data_management/importers/ena24_to_json.py +0 -276
  145. megadetector/data_management/importers/filenames_to_json.py +0 -386
  146. megadetector/data_management/importers/helena_to_cct.py +0 -283
  147. megadetector/data_management/importers/idaho-camera-traps.py +0 -1407
  148. megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
  149. megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +0 -387
  150. megadetector/data_management/importers/jb_csv_to_json.py +0 -150
  151. megadetector/data_management/importers/mcgill_to_json.py +0 -250
  152. megadetector/data_management/importers/missouri_to_json.py +0 -490
  153. megadetector/data_management/importers/nacti_fieldname_adjustments.py +0 -79
  154. megadetector/data_management/importers/noaa_seals_2019.py +0 -181
  155. megadetector/data_management/importers/osu-small-animals-to-json.py +0 -364
  156. megadetector/data_management/importers/pc_to_json.py +0 -365
  157. megadetector/data_management/importers/plot_wni_giraffes.py +0 -123
  158. megadetector/data_management/importers/prepare_zsl_imerit.py +0 -131
  159. megadetector/data_management/importers/raic_csv_to_md_results.py +0 -416
  160. megadetector/data_management/importers/rspb_to_json.py +0 -356
  161. megadetector/data_management/importers/save_the_elephants_survey_A.py +0 -320
  162. megadetector/data_management/importers/save_the_elephants_survey_B.py +0 -329
  163. megadetector/data_management/importers/snapshot_safari_importer.py +0 -758
  164. megadetector/data_management/importers/snapshot_serengeti_lila.py +0 -1067
  165. megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
  166. megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
  167. megadetector/data_management/importers/sulross_get_exif.py +0 -65
  168. megadetector/data_management/importers/timelapse_csv_set_to_json.py +0 -490
  169. megadetector/data_management/importers/ubc_to_json.py +0 -399
  170. megadetector/data_management/importers/umn_to_json.py +0 -507
  171. megadetector/data_management/importers/wellington_to_json.py +0 -263
  172. megadetector/data_management/importers/wi_to_json.py +0 -442
  173. megadetector/data_management/importers/zamba_results_to_md_results.py +0 -180
  174. megadetector/data_management/lila/add_locations_to_island_camera_traps.py +0 -101
  175. megadetector/data_management/lila/add_locations_to_nacti.py +0 -151
  176. megadetector-5.0.28.dist-info/RECORD +0 -209
@@ -48,140 +48,140 @@ class ProcessVideoOptions:
48
48
  """
49
49
  Options controlling the behavior of process_video()
50
50
  """
51
-
51
+
52
52
  def __init__(self):
53
-
53
+
54
54
  #: Can be a model filename (.pt or .pb) or a model name (e.g. "MDV5A")
55
- #:
55
+ #:
56
56
  #: Use the string "no_detection" to indicate that you only want to extract frames,
57
- #: not run a model. If you do this, you almost definitely want to set
57
+ #: not run a model. If you do this, you almost definitely want to set
58
58
  #: keep_extracted_frames to "True", otherwise everything in this module is a no-op.
59
59
  #: I.e., there's no reason to extract frames, do nothing with them, then delete them.
60
60
  self.model_file = 'MDV5A'
61
-
61
+
62
62
  #: Video (of folder of videos) to process
63
63
  self.input_video_file = ''
64
-
64
+
65
65
  #: .json file to which we should write results
66
66
  self.output_json_file = None
67
-
68
- #: File to which we should write a video with boxes, only relevant if
67
+
68
+ #: File to which we should write a video with boxes, only relevant if
69
69
  #: render_output_video is True
70
70
  self.output_video_file = None
71
-
71
+
72
72
  #: Folder to use for extracted frames; will use a folder in system temp space
73
73
  #: if this is None
74
74
  self.frame_folder = None
75
-
76
- #: Folder to use for rendered frames (if rendering output video); will use a folder
75
+
76
+ #: Folder to use for rendered frames (if rendering output video); will use a folder
77
77
  #: in system temp space if this is None
78
78
  self.frame_rendering_folder = None
79
-
79
+
80
80
  #: Should we render a video with detection boxes?
81
81
  #:
82
82
  #: If processing a folder, this renders each input video to a separate
83
83
  #: video with detection boxes.
84
84
  self.render_output_video = False
85
-
85
+
86
86
  #: If we are rendering boxes to a new video, should we keep the temporary
87
87
  #: rendered frames?
88
88
  self.keep_rendered_frames = False
89
-
89
+
90
90
  #: Should we keep the extracted frames?
91
91
  self.keep_extracted_frames = False
92
-
92
+
93
93
  #: Should we delete the entire folder the extracted frames are written to?
94
94
  #:
95
- #: By default, we delete the frame files but leave the (probably-empty) folder in place,
95
+ #: By default, we delete the frame files but leave the (probably-empty) folder in place,
96
96
  #: for no reason other than being paranoid about deleting folders.
97
97
  self.force_extracted_frame_folder_deletion = False
98
-
98
+
99
99
  #: Should we delete the entire folder the rendered frames are written to?
100
100
  #:
101
101
  #: By default, we delete the frame files but leave the (probably-empty) folder in place,
102
102
  #: for no reason other than being paranoid about deleting folders.
103
103
  self.force_rendered_frame_folder_deletion = False
104
-
105
- #: If we've already run MegaDetector on this video or folder of videos, i.e. if we
104
+
105
+ #: If we've already run MegaDetector on this video or folder of videos, i.e. if we
106
106
  #: find a corresponding MD results file, should we re-use it? Defaults to reprocessing.
107
107
  self.reuse_results_if_available = False
108
-
109
- #: If we've already split this video or folder of videos into frames, should we
108
+
109
+ #: If we've already split this video or folder of videos into frames, should we
110
110
  #: we re-use those extracted frames? Defaults to reprocessing.
111
111
  self.reuse_frames_if_available = False
112
-
112
+
113
113
  #: If [input_video_file] is a folder, should we search for videos recursively?
114
- self.recursive = False
115
-
114
+ self.recursive = False
115
+
116
116
  #: Enable additional debug console output
117
117
  self.verbose = False
118
-
118
+
119
119
  #: fourcc code to use for writing videos; only relevant if render_output_video is True
120
120
  self.fourcc = None
121
-
122
- #: force a specific frame rate for output videos; only relevant if render_output_video
121
+
122
+ #: force a specific frame rate for output videos; only relevant if render_output_video
123
123
  #: is True
124
124
  self.rendering_fs = None
125
-
125
+
126
126
  #: Confidence threshold to use for writing videos with boxes, only relevant if
127
127
  #: if render_output_video is True. Defaults to choosing a reasonable threshold
128
128
  #: based on the model version.
129
129
  self.rendering_confidence_threshold = None
130
-
130
+
131
131
  #: Detections below this threshold will not be included in the output file.
132
132
  self.json_confidence_threshold = 0.005
133
-
133
+
134
134
  #: Sample every Nth frame; set to None (default) or 1 to sample every frame. Typically
135
- #: we sample down to around 3 fps, so for typical 30 fps videos, frame_sample=10 is a
135
+ #: we sample down to around 3 fps, so for typical 30 fps videos, frame_sample=10 is a
136
136
  #: typical value. Mutually exclusive with [frames_to_extract] and [time_sample].
137
137
  self.frame_sample = None
138
-
138
+
139
139
  #: Extract a specific set of frames (list of ints, or a single int). Mutually exclusive with
140
140
  #: [frame_sample] and [time_sample].
141
141
  self.frames_to_extract = None
142
-
142
+
143
143
  # Sample frames every N seconds. Mutually exclusive with [frame_sample] and [frames_to_extract].
144
144
  self.time_sample = None
145
-
145
+
146
146
  #: Number of workers to use for parallelization; set to <= 1 to disable parallelization
147
147
  self.n_cores = 1
148
-
148
+
149
149
  #: For debugging only, stop processing after a certain number of frames.
150
150
  self.debug_max_frames = -1
151
-
151
+
152
152
  #: For debugging only, force on-disk frame extraction, even if it wouldn't otherwise be
153
153
  #: necessary
154
154
  self.force_on_disk_frame_extraction = False
155
-
155
+
156
156
  #: File containing non-standard categories, typically only used if you're running a non-MD
157
157
  #: detector.
158
158
  self.class_mapping_filename = None
159
-
159
+
160
160
  #: JPEG quality for frame output, from 0-100. Use None or -1 to let opencv decide.
161
161
  self.quality = 90
162
-
162
+
163
163
  #: Resize frames so they're at most this wide
164
164
  self.max_width = None
165
-
165
+
166
166
  #: Run the model at this image size (don't mess with this unless you know what you're
167
- #: getting into)... if you just want to pass smaller frames to MD, use max_width
167
+ #: getting into)... if you just want to pass smaller frames to MD, use max_width
168
168
  self.image_size = None
169
-
169
+
170
170
  #: Enable image augmentation
171
171
  self.augment = False
172
-
172
+
173
173
  #: By default, a video with no frames (or no frames retrievable with the current parameters)
174
174
  #: is an error, this makes it a warning. This would apply if you request, e.g., the 100th
175
175
  #: frame from each video, but a video only has 50 frames.
176
176
  self.allow_empty_videos = False
177
-
178
- #: When processing a folder of videos, should we include just a single representative
177
+
178
+ #: When processing a folder of videos, should we include just a single representative
179
179
  #: frame result for each video (default), or every frame that was processed?
180
180
  self.include_all_processed_frames = False
181
-
181
+
182
182
  #: Detector-specific options
183
183
  self.detector_options = None
184
-
184
+
185
185
  # ...class ProcessVideoOptions
186
186
 
187
187
 
@@ -191,7 +191,7 @@ def _validate_video_options(options):
191
191
  """
192
192
  Consistency checking for ProcessVideoOptions objects.
193
193
  """
194
-
194
+
195
195
  n_sampling_options_configured = 0
196
196
  if options.frame_sample is not None:
197
197
  n_sampling_options_configured += 1
@@ -199,45 +199,45 @@ def _validate_video_options(options):
199
199
  n_sampling_options_configured += 1
200
200
  if options.frames_to_extract is not None:
201
201
  n_sampling_options_configured += 1
202
-
202
+
203
203
  if n_sampling_options_configured > 1:
204
204
  raise ValueError('frame_sample, time_sample, and frames_to_extract are mutually exclusive')
205
-
205
+
206
206
  return True
207
-
208
-
207
+
208
+
209
209
  def _select_temporary_output_folders(options):
210
210
  """
211
211
  Choose folders in system temp space for writing temporary frames. Does not create folders,
212
212
  just defines them.
213
213
  """
214
-
214
+
215
215
  tempdir = os.path.join(tempfile.gettempdir(), 'process_camera_trap_video')
216
-
216
+
217
217
  # If we create a folder like "process_camera_trap_video" in the system temp dir, it may
218
218
  # be the case that no one else can write to it, even to create user-specific subfolders.
219
- # If we create a uuid-named folder in the system temp dir, we make a mess.
219
+ # If we create a uuid-named folder in the system temp dir, we make a mess.
220
220
  #
221
221
  # Compromise with "process_camera_trap_video-[user]".
222
222
  user_tempdir = tempdir + '-' + getpass.getuser()
223
-
223
+
224
224
  # I don't know whether it's possible for a username to contain characters that are
225
225
  # not valid filename characters, but just to be sure...
226
226
  user_tempdir = clean_path(user_tempdir)
227
-
227
+
228
228
  frame_output_folder = os.path.join(
229
229
  user_tempdir, os.path.basename(options.input_video_file) + '_frames_' + str(uuid1()))
230
-
230
+
231
231
  rendering_output_folder = os.path.join(
232
232
  tempdir, os.path.basename(options.input_video_file) + '_detections_' + str(uuid1()))
233
-
233
+
234
234
  temporary_folder_info = \
235
235
  {
236
236
  'temp_folder_base':user_tempdir,
237
237
  'frame_output_folder':frame_output_folder,
238
238
  'rendering_output_folder':rendering_output_folder
239
239
  }
240
-
240
+
241
241
  return temporary_folder_info
242
242
 
243
243
  # ...def _create_frame_output_folders(...)
@@ -247,50 +247,50 @@ def _clean_up_rendered_frames(options,rendering_output_folder,detected_frame_fil
247
247
  """
248
248
  If necessary, delete rendered frames and/or the entire rendering output folder.
249
249
  """
250
-
250
+
251
251
  if rendering_output_folder is None:
252
252
  return
253
-
253
+
254
254
  caller_provided_rendering_output_folder = (options.frame_rendering_folder is not None)
255
-
255
+
256
256
  # (Optionally) delete the temporary directory we used for rendered detection images
257
257
  if not options.keep_rendered_frames:
258
-
258
+
259
259
  try:
260
-
260
+
261
261
  # If (a) we're supposed to delete the temporary rendering folder no
262
- # matter where it is and (b) we created it in temp space, delete the
262
+ # matter where it is and (b) we created it in temp space, delete the
263
263
  # whole tree
264
264
  if options.force_rendered_frame_folder_deletion and \
265
265
  (not caller_provided_rendering_output_folder):
266
-
266
+
267
267
  if options.verbose:
268
268
  print('Recursively deleting rendered frame folder {}'.format(
269
269
  rendering_output_folder))
270
-
270
+
271
271
  shutil.rmtree(rendering_output_folder)
272
-
272
+
273
273
  # ...otherwise just delete the frames, but leave the folder in place
274
274
  else:
275
-
275
+
276
276
  if options.force_rendered_frame_folder_deletion:
277
277
  assert caller_provided_rendering_output_folder
278
278
  print('Warning: force_rendered_frame_folder_deletion supplied with a ' + \
279
279
  'user-provided folder, only removing frames')
280
-
280
+
281
281
  for rendered_frame_fn in detected_frame_files:
282
282
  os.remove(rendered_frame_fn)
283
-
283
+
284
284
  except Exception as e:
285
285
  print('Warning: error deleting rendered frames from folder {}:\n{}'.format(
286
286
  rendering_output_folder,str(e)))
287
287
  pass
288
288
 
289
289
  elif options.force_rendered_frame_folder_deletion:
290
-
290
+
291
291
  print('Warning: keep_rendered_frames and force_rendered_frame_folder_deletion both ' + \
292
292
  'specified, not deleting')
293
-
293
+
294
294
  # ...def _clean_up_rendered_frames(...)
295
295
 
296
296
 
@@ -298,48 +298,48 @@ def _clean_up_extracted_frames(options,frame_output_folder,frame_filenames):
298
298
  """
299
299
  If necessary, delete extracted frames and/or the entire temporary frame folder.
300
300
  """
301
-
301
+
302
302
  if frame_output_folder is None:
303
303
  return
304
-
304
+
305
305
  caller_provided_frame_output_folder = (options.frame_folder is not None)
306
-
306
+
307
307
  if not options.keep_extracted_frames:
308
-
308
+
309
309
  try:
310
-
310
+
311
311
  # If (a) we're supposed to delete the temporary frame folder no
312
- # matter where it is and (b) we created it in temp space, delete the
312
+ # matter where it is and (b) we created it in temp space, delete the
313
313
  # whole tree.
314
314
  if options.force_extracted_frame_folder_deletion and \
315
315
  (not caller_provided_frame_output_folder):
316
-
316
+
317
317
  if options.verbose:
318
318
  print('Recursively deleting frame output folder {}'.format(frame_output_folder))
319
-
319
+
320
320
  shutil.rmtree(frame_output_folder)
321
-
321
+
322
322
  # ...otherwise just delete the frames, but leave the folder in place
323
323
  else:
324
-
324
+
325
325
  if frame_filenames is None:
326
326
  return
327
-
327
+
328
328
  if options.force_extracted_frame_folder_deletion:
329
329
  assert caller_provided_frame_output_folder
330
330
  print('Warning: force_extracted_frame_folder_deletion supplied with a ' + \
331
331
  'user-provided folder, only removing frames')
332
-
332
+
333
333
  for extracted_frame_fn in frame_filenames:
334
334
  os.remove(extracted_frame_fn)
335
-
335
+
336
336
  except Exception as e:
337
337
  print('Warning: error removing extracted frames from folder {}:\n{}'.format(
338
338
  frame_output_folder,str(e)))
339
339
  pass
340
-
340
+
341
341
  elif options.force_extracted_frame_folder_deletion:
342
-
342
+
343
343
  print('Warning: keep_extracted_frames and force_extracted_frame_folder_deletion both ' + \
344
344
  'specified, not deleting')
345
345
 
@@ -350,18 +350,18 @@ def process_video(options):
350
350
  """
351
351
  Process a single video through MD, optionally writing a new video with boxes.
352
352
  Can also be used just to split a video into frames, without running a model.
353
-
354
- Args:
353
+
354
+ Args:
355
355
  options (ProcessVideoOptions): all the parameters used to control this process,
356
356
  including filenames; see ProcessVideoOptions for details
357
-
357
+
358
358
  Returns:
359
359
  dict: frame-level MegaDetector results, identical to what's in the output .json file
360
360
  """
361
361
 
362
362
  # Check for incompatible options
363
363
  _validate_video_options(options)
364
-
364
+
365
365
  if options.output_json_file is None:
366
366
  options.output_json_file = options.input_video_file + '.json'
367
367
 
@@ -371,59 +371,59 @@ def process_video(options):
371
371
  if options.time_sample is not None:
372
372
  raise ValueError('Time-based sampling is not supported when processing a single video; ' + \
373
373
  'consider processing a folder, or using frame_sample')
374
-
374
+
375
375
  if options.model_file == 'no_detection' and not options.keep_extracted_frames:
376
376
  print('Warning: you asked for no detection, but did not specify keep_extracted_frames, this is a no-op')
377
377
  return
378
-
378
+
379
379
  # Track whether frame and rendering folders were created by this script
380
380
  caller_provided_frame_output_folder = (options.frame_folder is not None)
381
381
  caller_provided_rendering_output_folder = (options.frame_rendering_folder is not None)
382
-
382
+
383
383
  frame_output_folder = None
384
384
  frame_filenames = None
385
-
385
+
386
386
  # If we should re-use existing results, and the output file exists, don't bother running MD
387
387
  if (options.reuse_results_if_available and os.path.isfile(options.output_json_file)):
388
-
388
+
389
389
  print('Loading results from {}'.format(options.output_json_file))
390
390
  with open(options.output_json_file,'r') as f:
391
391
  results = json.load(f)
392
-
392
+
393
393
  # Run MD in memory if we don't need to generate frames
394
394
  #
395
395
  # Currently if we're generating an output video, we need to generate frames on disk first.
396
396
  elif (not options.keep_extracted_frames and \
397
397
  not options.render_output_video and \
398
398
  not options.force_on_disk_frame_extraction):
399
-
399
+
400
400
  # Run MegaDetector in memory
401
-
401
+
402
402
  if options.verbose:
403
403
  print('Running MegaDetector in memory for {}'.format(options.input_video_file))
404
-
404
+
405
405
  if options.frame_folder is not None:
406
406
  print('Warning: frame_folder specified, but keep_extracted_frames is ' + \
407
407
  'not; no raw frames will be written')
408
-
408
+
409
409
  detector = load_detector(options.model_file,detector_options=options.detector_options)
410
-
410
+
411
411
  def frame_callback(image_np,image_id):
412
412
  return detector.generate_detections_one_image(image_np,
413
413
  image_id,
414
414
  detection_threshold=options.json_confidence_threshold,
415
415
  augment=options.augment)
416
-
417
- frame_results = run_callback_on_frames(options.input_video_file,
416
+
417
+ frame_results = run_callback_on_frames(options.input_video_file,
418
418
  frame_callback,
419
- every_n_frames=options.frame_sample,
420
- verbose=options.verbose,
419
+ every_n_frames=options.frame_sample,
420
+ verbose=options.verbose,
421
421
  frames_to_process=options.frames_to_extract)
422
-
422
+
423
423
  frame_results['results'] = _add_frame_numbers_to_results(frame_results['results'])
424
-
424
+
425
425
  run_detector_batch.write_results_to_file(
426
- frame_results['results'],
426
+ frame_results['results'],
427
427
  options.output_json_file,
428
428
  relative_path_base=None,
429
429
  detector_file=options.model_file,
@@ -431,48 +431,48 @@ def process_video(options):
431
431
 
432
432
  # Extract frames and optionally run MegaDetector on those frames
433
433
  else:
434
-
434
+
435
435
  if options.verbose:
436
436
  print('Extracting frames for {}'.format(options.input_video_file))
437
-
438
- # This does not create any folders, just defines temporary folder names in
437
+
438
+ # This does not create any folders, just defines temporary folder names in
439
439
  # case we need them.
440
440
  temporary_folder_info = _select_temporary_output_folders(options)
441
-
441
+
442
442
  if (caller_provided_frame_output_folder):
443
443
  frame_output_folder = options.frame_folder
444
444
  else:
445
445
  frame_output_folder = temporary_folder_info['frame_output_folder']
446
-
446
+
447
447
  os.makedirs(frame_output_folder, exist_ok=True)
448
-
449
-
448
+
449
+
450
450
  ## Extract frames
451
-
452
- frame_filenames, Fs = video_to_frames(
453
- options.input_video_file,
454
- frame_output_folder,
455
- every_n_frames=options.frame_sample,
451
+
452
+ frame_filenames, fs = video_to_frames(
453
+ options.input_video_file,
454
+ frame_output_folder,
455
+ every_n_frames=options.frame_sample,
456
456
  overwrite=(not options.reuse_frames_if_available),
457
- quality=options.quality,
458
- max_width=options.max_width,
457
+ quality=options.quality,
458
+ max_width=options.max_width,
459
459
  verbose=options.verbose,
460
460
  frames_to_extract=options.frames_to_extract,
461
461
  allow_empty_videos=options.allow_empty_videos)
462
-
462
+
463
463
  image_file_names = frame_filenames
464
464
  if options.debug_max_frames > 0:
465
465
  image_file_names = image_file_names[0:options.debug_max_frames]
466
-
466
+
467
467
  ## Run MegaDetector on those frames
468
-
468
+
469
469
  if options.model_file != 'no_detection':
470
-
470
+
471
471
  if options.verbose:
472
472
  print('Running MD for {}'.format(options.input_video_file))
473
-
473
+
474
474
  results = run_detector_batch.load_and_run_detector_batch(
475
- options.model_file,
475
+ options.model_file,
476
476
  image_file_names,
477
477
  confidence_threshold=options.json_confidence_threshold,
478
478
  n_cores=options.n_cores,
@@ -481,73 +481,73 @@ def process_video(options):
481
481
  augment=options.augment,
482
482
  image_size=options.image_size,
483
483
  detector_options=options.detector_options)
484
-
484
+
485
485
  results = _add_frame_numbers_to_results(results)
486
-
486
+
487
487
  run_detector_batch.write_results_to_file(
488
- results,
488
+ results,
489
489
  options.output_json_file,
490
490
  relative_path_base=frame_output_folder,
491
491
  detector_file=options.model_file,
492
- custom_metadata={'video_frame_rate':Fs})
493
-
492
+ custom_metadata={'video_frame_rate':fs})
493
+
494
494
  # ...if we are/aren't keeping raw frames on disk
495
-
496
-
495
+
496
+
497
497
  ## (Optionally) render output video
498
-
498
+
499
499
  if options.render_output_video:
500
-
500
+
501
501
  ## Render detections to images
502
-
502
+
503
503
  if (caller_provided_rendering_output_folder):
504
504
  rendering_output_dir = options.frame_rendering_folder
505
505
  else:
506
506
  rendering_output_dir = temporary_folder_info['rendering_output_folder']
507
-
507
+
508
508
  os.makedirs(rendering_output_dir,exist_ok=True)
509
-
509
+
510
510
  detected_frame_files = visualize_detector_output.visualize_detector_output(
511
511
  detector_output_path=options.output_json_file,
512
512
  out_dir=rendering_output_dir,
513
513
  images_dir=frame_output_folder,
514
514
  confidence_threshold=options.rendering_confidence_threshold)
515
515
 
516
-
516
+
517
517
  ## Choose the frame rate at which we should render the output video
518
-
518
+
519
519
  if options.rendering_fs is not None:
520
520
  rendering_fs = options.rendering_fs
521
521
  elif options.frame_sample is None and options.time_sample is None:
522
- rendering_fs = Fs
522
+ rendering_fs = fs
523
523
  elif options.frame_sample is not None:
524
524
  assert options.time_sample is None
525
- # If the original video was 30fps and we sampled every 10th frame,
525
+ # If the original video was 30fps and we sampled every 10th frame,
526
526
  # render at 3fps
527
- rendering_fs = Fs / options.frame_sample
527
+ rendering_fs = fs / options.frame_sample
528
528
  elif options.time_sample is not None:
529
529
  rendering_fs = options.time_sample
530
-
531
-
530
+
531
+
532
532
  ## Render the output video
533
-
533
+
534
534
  print('Rendering {} frames to {} at {} fps (original video {} fps)'.format(
535
- len(detected_frame_files), options.output_video_file,rendering_fs,Fs))
536
- frames_to_video(detected_frame_files,
537
- rendering_fs,
538
- options.output_video_file,
535
+ len(detected_frame_files), options.output_video_file,rendering_fs,fs))
536
+ frames_to_video(detected_frame_files,
537
+ rendering_fs,
538
+ options.output_video_file,
539
539
  codec_spec=options.fourcc)
540
-
540
+
541
541
  # Possibly clean up rendered frames
542
542
  _clean_up_rendered_frames(options,rendering_output_dir,detected_frame_files)
543
-
543
+
544
544
  # ...if we're rendering video
545
-
546
-
545
+
546
+
547
547
  ## (Optionally) delete the extracted frames
548
-
548
+
549
549
  _clean_up_extracted_frames(options, frame_output_folder, frame_filenames)
550
-
550
+
551
551
  # ...process_video()
552
552
 
553
553
 
@@ -555,119 +555,119 @@ def process_video_folder(options):
555
555
  """
556
556
  Process a folder of videos through MD. Can also be used just to split a folder of
557
557
  videos into frames, without running a model.
558
-
559
- When this function is used to run MD, two .json files will get written, one with
558
+
559
+ When this function is used to run MD, two .json files will get written, one with
560
560
  an entry for each *frame* (identical to what's created by process_video()), and
561
561
  one with an entry for each *video* (which is more suitable for, e.g., reading into
562
562
  Timelapse).
563
-
564
- Args:
563
+
564
+ Args:
565
565
  options (ProcessVideoOptions): all the parameters used to control this process,
566
- including filenames; see ProcessVideoOptions for details
566
+ including filenames; see ProcessVideoOptions for details
567
567
  """
568
-
568
+
569
569
  ## Validate options
570
570
 
571
571
  # Check for incompatible options
572
572
  _validate_video_options(options)
573
-
573
+
574
574
  assert os.path.isdir(options.input_video_file), \
575
575
  '{} is not a folder'.format(options.input_video_file)
576
-
576
+
577
577
  if options.model_file == 'no_detection' and not options.keep_extracted_frames:
578
578
  print('Warning: you asked for no detection, but did not specify keep_extracted_frames, this is a no-op')
579
579
  return
580
-
580
+
581
581
  if options.model_file != 'no_detection':
582
582
  assert options.output_json_file is not None, \
583
- 'When processing a folder, you must specify an output .json file'
583
+ 'When processing a folder, you must specify an output .json file'
584
584
  assert options.output_json_file.endswith('.json')
585
585
  video_json = options.output_json_file
586
586
  frames_json = options.output_json_file.replace('.json','.frames.json')
587
587
  os.makedirs(os.path.dirname(video_json),exist_ok=True)
588
-
588
+
589
589
  # Track whether frame and rendering folders were created by this script
590
590
  caller_provided_frame_output_folder = (options.frame_folder is not None)
591
591
  caller_provided_rendering_output_folder = (options.frame_rendering_folder is not None)
592
-
593
- # This does not create any folders, just defines temporary folder names in
592
+
593
+ # This does not create any folders, just defines temporary folder names in
594
594
  # case we need them.
595
595
  temporary_folder_info = _select_temporary_output_folders(options)
596
-
596
+
597
597
  frame_output_folder = None
598
598
  image_file_names = None
599
599
  video_filename_to_fs = {}
600
-
600
+
601
601
  if options.time_sample is not None:
602
602
  every_n_frames_param = -1 * options.time_sample
603
603
  else:
604
604
  every_n_frames_param = options.frame_sample
605
-
605
+
606
606
  # Run MD in memory if we don't need to generate frames
607
607
  #
608
608
  # Currently if we're generating an output video, we need to generate frames on disk first.
609
609
  if (not options.keep_extracted_frames and \
610
610
  not options.render_output_video and \
611
611
  not options.force_on_disk_frame_extraction):
612
-
612
+
613
613
  if options.verbose:
614
614
  print('Running MegaDetector in memory for folder {}'.format(options.input_video_file))
615
-
615
+
616
616
  if options.frame_folder is not None:
617
617
  print('Warning: frame_folder specified, but keep_extracted_frames is ' + \
618
618
  'not; no raw frames will be written')
619
-
619
+
620
620
  detector = load_detector(options.model_file,detector_options=options.detector_options)
621
-
621
+
622
622
  def frame_callback(image_np,image_id):
623
623
  return detector.generate_detections_one_image(image_np,
624
624
  image_id,
625
625
  detection_threshold=options.json_confidence_threshold,
626
626
  augment=options.augment)
627
-
628
- md_results = run_callback_on_frames_for_folder(input_video_folder=options.input_video_file,
627
+
628
+ md_results = run_callback_on_frames_for_folder(input_video_folder=options.input_video_file,
629
629
  frame_callback=frame_callback,
630
630
  every_n_frames=every_n_frames_param,
631
631
  verbose=options.verbose)
632
-
632
+
633
633
  video_results = md_results['results']
634
-
634
+
635
635
  for i_video,video_filename in enumerate(md_results['video_filenames']):
636
636
  video_filename = video_filename.replace('\\','/')
637
637
  assert video_filename not in video_filename_to_fs
638
638
  video_filename_to_fs[video_filename] = md_results['frame_rates'][i_video]
639
-
639
+
640
640
  all_frame_results = []
641
-
641
+
642
642
  # r = video_results[0]
643
643
  for frame_results in video_results:
644
644
  _add_frame_numbers_to_results(frame_results)
645
645
  all_frame_results.extend(frame_results)
646
-
646
+
647
647
  run_detector_batch.write_results_to_file(
648
- all_frame_results,
648
+ all_frame_results,
649
649
  frames_json,
650
650
  relative_path_base=None,
651
651
  detector_file=options.model_file)
652
-
652
+
653
653
  else:
654
-
654
+
655
655
  ## Split every video into frames
656
-
656
+
657
657
  if options.verbose:
658
658
  print('Extracting frames for folder {}'.format(options.input_video_file))
659
-
659
+
660
660
  if caller_provided_frame_output_folder:
661
661
  frame_output_folder = options.frame_folder
662
662
  else:
663
663
  frame_output_folder = temporary_folder_info['frame_output_folder']
664
-
664
+
665
665
  os.makedirs(frame_output_folder, exist_ok=True)
666
-
667
- frame_filenames, Fs, video_filenames = \
666
+
667
+ frame_filenames, fs, video_filenames = \
668
668
  video_folder_to_frames(input_folder=options.input_video_file,
669
- output_folder_base=frame_output_folder,
670
- recursive=options.recursive,
669
+ output_folder_base=frame_output_folder,
670
+ recursive=options.recursive,
671
671
  overwrite=(not options.reuse_frames_if_available),
672
672
  n_threads=options.n_cores,
673
673
  every_n_frames=every_n_frames_param,
@@ -676,16 +676,16 @@ def process_video_folder(options):
676
676
  max_width=options.max_width,
677
677
  frames_to_extract=options.frames_to_extract,
678
678
  allow_empty_videos=options.allow_empty_videos)
679
-
679
+
680
680
  for i_video,video_filename_abs in enumerate(video_filenames):
681
681
  video_filename_relative = os.path.relpath(video_filename_abs,options.input_video_file)
682
682
  video_filename_relative = video_filename_relative.replace('\\','/')
683
683
  assert video_filename_relative not in video_filename_to_fs
684
- video_filename_to_fs[video_filename_relative] = Fs[i_video]
685
-
684
+ video_filename_to_fs[video_filename_relative] = fs[i_video]
685
+
686
686
  print('Extracted frames for {} videos'.format(len(set(video_filenames))))
687
687
  image_file_names = list(itertools.chain.from_iterable(frame_filenames))
688
-
688
+
689
689
  if len(image_file_names) == 0:
690
690
  if len(video_filenames) == 0:
691
691
  print('No videos found in folder {}'.format(options.input_video_file))
@@ -693,31 +693,31 @@ def process_video_folder(options):
693
693
  print('No frames extracted from folder {}, this may be due to an '\
694
694
  'unsupported video codec'.format(options.input_video_file))
695
695
  return
696
-
696
+
697
697
  if options.debug_max_frames is not None and options.debug_max_frames > 0:
698
698
  image_file_names = image_file_names[0:options.debug_max_frames]
699
-
699
+
700
700
  if options.model_file == 'no_detection':
701
701
  assert options.keep_extracted_frames, \
702
702
  'Internal error: keep_extracted_frames not set, but no model specified'
703
703
  return
704
-
705
-
704
+
705
+
706
706
  ## Run MegaDetector on the extracted frames
707
-
707
+
708
708
  if options.reuse_results_if_available and \
709
709
  os.path.isfile(frames_json):
710
-
710
+
711
711
  print('Bypassing inference, loading results from {}'.format(frames_json))
712
712
  with open(frames_json,'r') as f:
713
713
  results = json.load(f)
714
-
714
+
715
715
  else:
716
-
716
+
717
717
  print('Running MegaDetector')
718
-
718
+
719
719
  results = run_detector_batch.load_and_run_detector_batch(
720
- options.model_file,
720
+ options.model_file,
721
721
  image_file_names,
722
722
  confidence_threshold=options.json_confidence_threshold,
723
723
  n_cores=options.n_cores,
@@ -726,24 +726,24 @@ def process_video_folder(options):
726
726
  augment=options.augment,
727
727
  image_size=options.image_size,
728
728
  detector_options=options.detector_options)
729
-
729
+
730
730
  _add_frame_numbers_to_results(results)
731
-
731
+
732
732
  run_detector_batch.write_results_to_file(
733
- results,
733
+ results,
734
734
  frames_json,
735
735
  relative_path_base=frame_output_folder,
736
736
  detector_file=options.model_file)
737
-
737
+
738
738
  # ...if we're re-using existing results / running MD
739
-
739
+
740
740
  # ...if we're running MD on in-memory frames vs. extracting frames to disk
741
-
741
+
742
742
  ## Convert frame-level results to video-level results
743
743
 
744
744
  frame_to_video_options = FrameToVideoOptions()
745
745
  frame_to_video_options.include_all_processed_frames = options.include_all_processed_frames
746
-
746
+
747
747
  print('Converting frame-level results to video-level results')
748
748
  frame_results_to_video_results(frames_json,
749
749
  video_json,
@@ -752,17 +752,17 @@ def process_video_folder(options):
752
752
 
753
753
 
754
754
  ## (Optionally) render output videos
755
-
755
+
756
756
  if options.render_output_video:
757
-
757
+
758
758
  # Render detections to images
759
759
  if (caller_provided_rendering_output_folder):
760
760
  rendering_output_dir = options.frame_rendering_folder
761
761
  else:
762
762
  rendering_output_dir = temporary_folder_info['rendering_output_folder']
763
-
763
+
764
764
  os.makedirs(rendering_output_dir,exist_ok=True)
765
-
765
+
766
766
  detected_frame_files = visualize_detector_output.visualize_detector_output(
767
767
  detector_output_path=frames_json,
768
768
  out_dir=rendering_output_dir,
@@ -771,7 +771,7 @@ def process_video_folder(options):
771
771
  preserve_path_structure=True,
772
772
  output_image_width=-1)
773
773
  detected_frame_files = [s.replace('\\','/') for s in detected_frame_files]
774
-
774
+
775
775
  # Choose an output folder
776
776
  output_folder_is_input_folder = False
777
777
  if options.output_video_file is not None:
@@ -786,62 +786,62 @@ def process_video_folder(options):
786
786
  else:
787
787
  output_folder_is_input_folder = True
788
788
  output_video_folder = options.input_video_file
789
-
789
+
790
790
  # For each video
791
791
  #
792
792
  # TODO: parallelize this loop
793
793
  #
794
794
  # i_video=0; input_video_file_abs = video_filenames[i_video]
795
795
  for i_video,input_video_file_abs in enumerate(video_filenames):
796
-
797
- video_fs = Fs[i_video]
798
-
796
+
797
+ video_fs = fs[i_video]
798
+
799
799
  if options.rendering_fs is not None:
800
800
  rendering_fs = options.rendering_fs
801
- elif options.frame_sample is None:
801
+ elif options.frame_sample is None:
802
802
  rendering_fs = video_fs
803
803
  else:
804
- # If the original video was 30fps and we sampled every 10th frame,
805
- # render at 3fps
804
+ # If the original video was 30fps and we sampled every 10th frame,
805
+ # render at 3fps
806
806
  rendering_fs = video_fs / options.frame_sample
807
-
807
+
808
808
  input_video_file_relative = os.path.relpath(input_video_file_abs,options.input_video_file)
809
809
  video_frame_output_folder = os.path.join(rendering_output_dir,input_video_file_relative)
810
-
810
+
811
811
  video_frame_output_folder = video_frame_output_folder.replace('\\','/')
812
812
  assert os.path.isdir(video_frame_output_folder), \
813
813
  'Could not find frame folder for video {}'.format(input_video_file_relative)
814
-
814
+
815
815
  # Find the corresponding rendered frame folder
816
816
  video_frame_files = [fn for fn in detected_frame_files if \
817
817
  fn.startswith(video_frame_output_folder)]
818
818
  assert len(video_frame_files) > 0, 'Could not find rendered frames for video {}'.format(
819
819
  input_video_file_relative)
820
-
820
+
821
821
  # Select the output filename for the rendered video
822
822
  if output_folder_is_input_folder:
823
823
  video_output_file = insert_before_extension(input_video_file_abs,'annotated','_')
824
824
  else:
825
825
  video_output_file = os.path.join(output_video_folder,input_video_file_relative)
826
-
826
+
827
827
  os.makedirs(os.path.dirname(video_output_file),exist_ok=True)
828
-
829
- # Create the output video
828
+
829
+ # Create the output video
830
830
  print('Rendering detections for video {} to {} at {} fps (original video {} fps)'.format(
831
831
  input_video_file_relative,video_output_file,rendering_fs,video_fs))
832
- frames_to_video(video_frame_files,
833
- rendering_fs,
834
- video_output_file,
832
+ frames_to_video(video_frame_files,
833
+ rendering_fs,
834
+ video_output_file,
835
835
  codec_spec=options.fourcc)
836
-
836
+
837
837
  # ...for each video
838
-
838
+
839
839
  # Possibly clean up rendered frames
840
- _clean_up_rendered_frames(options,rendering_output_dir,detected_frame_files)
841
-
840
+ _clean_up_rendered_frames(options,rendering_output_dir,detected_frame_files)
841
+
842
842
  # ...if we're rendering video
843
-
844
-
843
+
844
+
845
845
  ## (Optionally) delete the extracted frames
846
846
  _clean_up_extracted_frames(options, frame_output_folder, image_file_names)
847
847
 
@@ -851,25 +851,25 @@ def process_video_folder(options):
851
851
  def options_to_command(options):
852
852
  """
853
853
  Convert a ProcessVideoOptions object to a corresponding command line.
854
-
854
+
855
855
  Args:
856
856
  options (ProcessVideoOptions): the options set to render as a command line
857
-
857
+
858
858
  Returns:
859
859
  str: the command line corresponding to [options]
860
-
860
+
861
861
  :meta private:
862
862
  """
863
863
  cmd = 'python process_video.py'
864
864
  cmd += ' "' + options.model_file + '"'
865
865
  cmd += ' "' + options.input_video_file + '"'
866
-
866
+
867
867
  if options.recursive:
868
868
  cmd += ' --recursive'
869
869
  if options.frame_folder is not None:
870
870
  cmd += ' --frame_folder' + ' "' + options.frame_folder + '"'
871
871
  if options.frame_rendering_folder is not None:
872
- cmd += ' --frame_rendering_folder' + ' "' + options.frame_rendering_folder + '"'
872
+ cmd += ' --frame_rendering_folder' + ' "' + options.frame_rendering_folder + '"'
873
873
  if options.output_json_file is not None:
874
874
  cmd += ' --output_json_file' + ' "' + options.output_json_file + '"'
875
875
  if options.output_video_file is not None:
@@ -877,13 +877,13 @@ def options_to_command(options):
877
877
  if options.keep_extracted_frames:
878
878
  cmd += ' --keep_extracted_frames'
879
879
  if options.reuse_results_if_available:
880
- cmd += ' --reuse_results_if_available'
880
+ cmd += ' --reuse_results_if_available'
881
881
  if options.reuse_frames_if_available:
882
882
  cmd += ' --reuse_frames_if_available'
883
883
  if options.render_output_video:
884
884
  cmd += ' --render_output_video'
885
885
  if options.keep_rendered_frames:
886
- cmd += ' --keep_rendered_frames'
886
+ cmd += ' --keep_rendered_frames'
887
887
  if options.rendering_confidence_threshold is not None:
888
888
  cmd += ' --rendering_confidence_threshold ' + str(options.rendering_confidence_threshold)
889
889
  if options.json_confidence_threshold is not None:
@@ -917,37 +917,37 @@ def options_to_command(options):
917
917
  if options.force_rendered_frame_folder_deletion:
918
918
  cmd += ' --force_rendered_frame_folder_deletion'
919
919
  if options.detector_options is not None and len(options.detector_options) > 0:
920
- cmd += '--detector_options {}'.format(dict_to_kvp_list(options.detector_options))
920
+ cmd += '--detector_options {}'.format(dict_to_kvp_list(options.detector_options))
921
921
 
922
922
  return cmd
923
923
 
924
-
924
+
925
925
  #%% Interactive driver
926
926
 
927
- if False:
928
-
927
+ if False:
928
+
929
929
  pass
930
930
 
931
931
  #%% Process a folder of videos
932
-
932
+
933
933
  model_file = 'MDV5A'
934
934
  # input_dir = r'g:\temp\test-videos'
935
935
  # input_dir = r'G:\temp\md-test-package\md-test-images\video-samples'
936
936
  input_dir = os.path.expanduser('~/AppData/Local/Temp/md-tests/md-test-images/video-samples')
937
937
  assert os.path.isdir(input_dir)
938
-
938
+
939
939
  output_base = r'g:\temp\video_test'
940
940
  os.makedirs(output_base,exist_ok=True)
941
-
941
+
942
942
  frame_folder = os.path.join(output_base,'frames')
943
943
  rendering_folder = os.path.join(output_base,'rendered-frames')
944
944
  output_json_file = os.path.join(output_base,'video-test.json')
945
945
  output_video_folder = os.path.join(output_base,'output_videos')
946
-
947
-
946
+
947
+
948
948
  print('Processing folder {}'.format(input_dir))
949
-
950
- options = ProcessVideoOptions()
949
+
950
+ options = ProcessVideoOptions()
951
951
  options.model_file = model_file
952
952
  options.input_video_file = input_dir
953
953
  options.output_video_file = output_video_folder
@@ -960,9 +960,9 @@ if False:
960
960
  options.max_width = None # 1280
961
961
  options.n_cores = 4
962
962
  options.verbose = True
963
- options.render_output_video = False
963
+ options.render_output_video = False
964
964
  options.frame_folder = frame_folder
965
- options.frame_rendering_folder = rendering_folder
965
+ options.frame_rendering_folder = rendering_folder
966
966
  options.keep_extracted_frames = False
967
967
  options.keep_rendered_frames = False
968
968
  options.force_extracted_frame_folder_deletion = False
@@ -970,113 +970,113 @@ if False:
970
970
  options.fourcc = 'mp4v'
971
971
  options.force_on_disk_frame_extraction = False
972
972
  # options.rendering_confidence_threshold = 0.15
973
-
973
+
974
974
  cmd = options_to_command(options); print(cmd)
975
-
975
+
976
976
  # import clipboard; clipboard.copy(cmd)
977
977
  process_video_folder(options)
978
-
979
-
978
+
979
+
980
980
  #%% Process a single video
981
981
 
982
982
  fn = r'g:\temp\test-videos\person_and_dog\DSCF0056.AVI'
983
983
  assert os.path.isfile(fn)
984
984
  model_file = 'MDV5A'
985
985
  input_video_file = fn
986
-
986
+
987
987
  output_base = r'g:\temp\video_test'
988
988
  frame_folder = os.path.join(output_base,'frames')
989
989
  rendering_folder = os.path.join(output_base,'rendered-frames')
990
990
  output_json_file = os.path.join(output_base,'video-test.json')
991
991
  output_video_file = os.path.join(output_base,'output_video.mp4')
992
-
992
+
993
993
  options = ProcessVideoOptions()
994
994
  options.model_file = model_file
995
995
  options.input_video_file = input_video_file
996
996
  options.render_output_video = True
997
997
  options.output_video_file = output_video_file
998
- options.output_json_file = output_json_file
999
- options.verbose = True
998
+ options.output_json_file = output_json_file
999
+ options.verbose = True
1000
1000
  options.quality = 75
1001
1001
  options.frame_sample = 10
1002
- options.max_width = 1600
1002
+ options.max_width = 1600
1003
1003
  options.frame_folder = frame_folder
1004
- options.frame_rendering_folder = rendering_folder
1004
+ options.frame_rendering_folder = rendering_folder
1005
1005
  options.keep_extracted_frames = False
1006
1006
  options.keep_rendered_frames = False
1007
1007
  options.force_extracted_frame_folder_deletion = True
1008
- options.force_rendered_frame_folder_deletion = True
1008
+ options.force_rendered_frame_folder_deletion = True
1009
1009
  options.fourcc = 'mp4v'
1010
1010
  # options.rendering_confidence_threshold = 0.15
1011
-
1011
+
1012
1012
  cmd = options_to_command(options); print(cmd)
1013
-
1014
- # import clipboard; clipboard.copy(cmd)
1013
+
1014
+ # import clipboard; clipboard.copy(cmd)
1015
1015
  process_video(options)
1016
-
1017
-
1016
+
1017
+
1018
1018
  #%% Extract specific frames from a single video, no detection
1019
1019
 
1020
1020
  fn = r'g:\temp\test-videos\person_and_dog\DSCF0064.AVI'
1021
1021
  assert os.path.isfile(fn)
1022
1022
  model_file = 'no_detection'
1023
1023
  input_video_file = fn
1024
-
1024
+
1025
1025
  output_base = r'g:\temp\video_test'
1026
1026
  frame_folder = os.path.join(output_base,'frames')
1027
1027
  output_video_file = os.path.join(output_base,'output_videos.mp4')
1028
-
1028
+
1029
1029
  options = ProcessVideoOptions()
1030
1030
  options.model_file = model_file
1031
- options.input_video_file = input_video_file
1032
- options.verbose = True
1031
+ options.input_video_file = input_video_file
1032
+ options.verbose = True
1033
1033
  options.quality = 90
1034
1034
  options.frame_sample = None
1035
1035
  options.frames_to_extract = [0,100]
1036
- options.max_width = None
1036
+ options.max_width = None
1037
1037
  options.frame_folder = frame_folder
1038
1038
  options.keep_extracted_frames = True
1039
-
1039
+
1040
1040
  cmd = options_to_command(options); print(cmd)
1041
-
1041
+
1042
1042
  # import clipboard; clipboard.copy(cmd)
1043
- process_video(options)
1044
-
1045
-
1043
+ process_video(options)
1044
+
1045
+
1046
1046
  #%% Extract specific frames from a folder, no detection
1047
1047
 
1048
1048
  fn = r'g:\temp\test-videos\person_and_dog'
1049
1049
  assert os.path.isdir(fn)
1050
1050
  model_file = 'no_detection'
1051
1051
  input_video_file = fn
1052
-
1052
+
1053
1053
  output_base = r'g:\temp\video_test'
1054
1054
  frame_folder = os.path.join(output_base,'frames')
1055
1055
  output_video_file = os.path.join(output_base,'output_videos.mp4')
1056
-
1056
+
1057
1057
  options = ProcessVideoOptions()
1058
1058
  options.model_file = model_file
1059
- options.input_video_file = input_video_file
1060
- options.verbose = True
1059
+ options.input_video_file = input_video_file
1060
+ options.verbose = True
1061
1061
  options.quality = 90
1062
1062
  options.frame_sample = None
1063
1063
  options.frames_to_extract = [0,100]
1064
- options.max_width = None
1064
+ options.max_width = None
1065
1065
  options.frame_folder = frame_folder
1066
1066
  options.keep_extracted_frames = True
1067
-
1067
+
1068
1068
  cmd = options_to_command(options); print(cmd)
1069
-
1069
+
1070
1070
  # import clipboard; clipboard.copy(cmd)
1071
- process_video(options)
1071
+ process_video(options)
1072
+
1072
1073
 
1073
-
1074
1074
  #%% Command-line driver
1075
1075
 
1076
- def main():
1076
+ def main(): # noqa
1077
1077
 
1078
1078
  default_options = ProcessVideoOptions()
1079
-
1079
+
1080
1080
  parser = argparse.ArgumentParser(description=(
1081
1081
  'Run MegaDetector on each frame (or every Nth frame) in a video (or folder of videos), optionally '\
1082
1082
  'producing a new video with detections annotated'))
@@ -1091,15 +1091,15 @@ def main():
1091
1091
  parser.add_argument('--recursive', action='store_true',
1092
1092
  help='recurse into [input_video_file]; only meaningful if a folder '\
1093
1093
  'is specified as input')
1094
-
1094
+
1095
1095
  parser.add_argument('--frame_folder', type=str, default=None,
1096
1096
  help='folder to use for intermediate frame storage, defaults to a folder '\
1097
1097
  'in the system temporary folder')
1098
-
1098
+
1099
1099
  parser.add_argument('--frame_rendering_folder', type=str, default=None,
1100
1100
  help='folder to use for rendered frame storage, defaults to a folder in '\
1101
1101
  'the system temporary folder')
1102
-
1102
+
1103
1103
  parser.add_argument('--output_json_file', type=str,
1104
1104
  default=None, help='.json output file, defaults to [video file].json')
1105
1105
 
@@ -1109,21 +1109,23 @@ def main():
1109
1109
 
1110
1110
  parser.add_argument('--keep_extracted_frames',
1111
1111
  action='store_true', help='Disable the deletion of extracted frames')
1112
-
1112
+
1113
1113
  parser.add_argument('--reuse_frames_if_available',
1114
- action='store_true', help="Don't extract frames that are already available in the frame extraction folder")
1115
-
1114
+ action='store_true',
1115
+ help="Don't extract frames that are already available in the frame extraction folder")
1116
+
1116
1117
  parser.add_argument('--reuse_results_if_available',
1117
- action='store_true', help='If the output .json files exists, and this flag is set,'\
1118
+ action='store_true',
1119
+ help='If the output .json files exists, and this flag is set,'\
1118
1120
  'we\'ll skip running MegaDetector')
1119
-
1121
+
1120
1122
  parser.add_argument('--render_output_video', action='store_true',
1121
1123
  help='enable video output rendering (not rendered by default)')
1122
1124
 
1123
1125
  parser.add_argument('--fourcc', default=default_fourcc,
1124
- help='fourcc code to use for video encoding (default {}), only used if render_output_video is True'.format(
1125
- default_fourcc))
1126
-
1126
+ help=f'fourcc code to use for video encoding (default {default_fourcc}), ' + \
1127
+ 'only used if render_output_video is True')
1128
+
1127
1129
  parser.add_argument('--keep_rendered_frames',
1128
1130
  action='store_true', help='Disable the deletion of rendered (w/boxes) frames')
1129
1131
 
@@ -1132,24 +1134,25 @@ def main():
1132
1134
  'delete the frames, but leave the (probably-empty) folder in place. This option '\
1133
1135
  'forces deletion of the folder as well. Use at your own risk; does not check '\
1134
1136
  'whether other files were present in the folder.')
1135
-
1137
+
1136
1138
  parser.add_argument('--force_rendered_frame_folder_deletion',
1137
1139
  action='store_true', help='By default, when keep_rendered_frames is False, we '\
1138
1140
  'delete the frames, but leave the (probably-empty) folder in place. This option '\
1139
1141
  'forces deletion of the folder as well. Use at your own risk; does not check '\
1140
1142
  'whether other files were present in the folder.')
1141
-
1143
+
1142
1144
  parser.add_argument('--rendering_confidence_threshold', type=float,
1143
- default=None,
1144
- help="don't render boxes with confidence below this threshold (defaults to choosing based on the MD version)")
1145
+ default=None,
1146
+ help="don't render boxes with confidence below this threshold " + \
1147
+ "(defaults to choosing based on the MD version)")
1145
1148
 
1146
1149
  parser.add_argument('--rendering_fs', type=float,
1147
- default=None,
1150
+ default=None,
1148
1151
  help='force a specific frame rate for output videos (only relevant when using '\
1149
1152
  '--render_output_video) (defaults to the original frame rate)')
1150
1153
 
1151
1154
  parser.add_argument('--json_confidence_threshold', type=float,
1152
- default=default_options.json_confidence_threshold,
1155
+ default=default_options.json_confidence_threshold,
1153
1156
  help="don't include boxes in the .json file with confidence "\
1154
1157
  'below this threshold (default {})'.format(
1155
1158
  default_options.json_confidence_threshold))
@@ -1168,26 +1171,26 @@ def main():
1168
1171
  parser.add_argument('--frames_to_extract', nargs='+', type=int,
1169
1172
  default=None, help='extract specific frames (one or more ints), mutually exclusive '\
1170
1173
  'with --frame_sample and --time_sample.')
1171
-
1174
+
1172
1175
  parser.add_argument('--time_sample', type=float,
1173
1176
  default=None, help='process frames every N seconds; this is converted to a '\
1174
1177
  'frame sampling rate, so it may not be exactly the requested interval in seconds. '\
1175
1178
  'mutually exclusive with --frame_sample and --frames_to_extract.')
1176
1179
 
1177
1180
  parser.add_argument('--quality', type=int,
1178
- default=default_options.quality,
1179
- help='JPEG quality for extracted frames (defaults to {}), use -1 to force no quality setting'.format(
1180
- default_options.quality))
1181
+ default=default_options.quality,
1182
+ help=f'JPEG quality for extracted frames (defaults to {default_options.quality}), ' + \
1183
+ 'use -1 to force no quality setting')
1181
1184
 
1182
1185
  parser.add_argument('--max_width', type=int,
1183
- default=default_options.max_width,
1186
+ default=default_options.max_width,
1184
1187
  help='Resize frames larger than this before writing (defaults to {})'.format(
1185
1188
  default_options.max_width))
1186
1189
 
1187
1190
  parser.add_argument('--debug_max_frames', type=int,
1188
1191
  default=-1, help='Trim to N frames for debugging (impacts model execution, '\
1189
1192
  'not frame rendering)')
1190
-
1193
+
1191
1194
  parser.add_argument('--class_mapping_filename',
1192
1195
  type=str,
1193
1196
  default=None, help='Use a non-default class mapping, supplied in a .json file '\
@@ -1197,42 +1200,42 @@ def main():
1197
1200
 
1198
1201
  parser.add_argument('--verbose', action='store_true',
1199
1202
  help='Enable additional debug output')
1200
-
1203
+
1201
1204
  parser.add_argument('--image_size',
1202
1205
  type=int,
1203
1206
  default=None,
1204
1207
  help=('Force image resizing to a specific integer size on the long '\
1205
- 'axis (not recommended to change this)'))
1206
-
1208
+ 'axis (not recommended to change this)'))
1209
+
1207
1210
  parser.add_argument('--augment',
1208
1211
  action='store_true',
1209
1212
  help='Enable image augmentation')
1210
-
1213
+
1211
1214
  parser.add_argument('--include_all_processed_frames',
1212
1215
  action='store_true',
1213
1216
  help='When processing a folder of videos, this flag indicates that the output '\
1214
1217
  'should include results for every frame that was processed, rather than just '\
1215
1218
  'one representative frame for each detection category per video.')
1216
-
1219
+
1217
1220
  parser.add_argument('--allow_empty_videos',
1218
1221
  action='store_true',
1219
1222
  help='By default, videos with no retrievable frames cause an error, this makes it a warning')
1220
-
1223
+
1221
1224
  parser.add_argument(
1222
1225
  '--detector_options',
1223
1226
  nargs='*',
1224
1227
  metavar='KEY=VALUE',
1225
1228
  default='',
1226
1229
  help='Detector-specific options, as a space-separated list of key-value pairs')
1227
-
1230
+
1228
1231
  if len(sys.argv[1:]) == 0:
1229
1232
  parser.print_help()
1230
1233
  parser.exit()
1231
-
1234
+
1232
1235
  args = parser.parse_args()
1233
- options = ProcessVideoOptions()
1236
+ options = ProcessVideoOptions()
1234
1237
  args_to_object(args,options)
1235
-
1238
+
1236
1239
  options.detector_options = parse_kvp_list(args.detector_options)
1237
1240
 
1238
1241
  if os.path.isdir(options.input_video_file):