megadetector 10.0.13__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 (147) hide show
  1. megadetector/__init__.py +0 -0
  2. megadetector/api/__init__.py +0 -0
  3. megadetector/api/batch_processing/integration/digiKam/setup.py +6 -0
  4. megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +465 -0
  5. megadetector/api/batch_processing/integration/eMammal/test_scripts/config_template.py +5 -0
  6. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +125 -0
  7. megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +55 -0
  8. megadetector/classification/__init__.py +0 -0
  9. megadetector/classification/aggregate_classifier_probs.py +108 -0
  10. megadetector/classification/analyze_failed_images.py +227 -0
  11. megadetector/classification/cache_batchapi_outputs.py +198 -0
  12. megadetector/classification/create_classification_dataset.py +626 -0
  13. megadetector/classification/crop_detections.py +516 -0
  14. megadetector/classification/csv_to_json.py +226 -0
  15. megadetector/classification/detect_and_crop.py +853 -0
  16. megadetector/classification/efficientnet/__init__.py +9 -0
  17. megadetector/classification/efficientnet/model.py +415 -0
  18. megadetector/classification/efficientnet/utils.py +608 -0
  19. megadetector/classification/evaluate_model.py +520 -0
  20. megadetector/classification/identify_mislabeled_candidates.py +152 -0
  21. megadetector/classification/json_to_azcopy_list.py +63 -0
  22. megadetector/classification/json_validator.py +696 -0
  23. megadetector/classification/map_classification_categories.py +276 -0
  24. megadetector/classification/merge_classification_detection_output.py +509 -0
  25. megadetector/classification/prepare_classification_script.py +194 -0
  26. megadetector/classification/prepare_classification_script_mc.py +228 -0
  27. megadetector/classification/run_classifier.py +287 -0
  28. megadetector/classification/save_mislabeled.py +110 -0
  29. megadetector/classification/train_classifier.py +827 -0
  30. megadetector/classification/train_classifier_tf.py +725 -0
  31. megadetector/classification/train_utils.py +323 -0
  32. megadetector/data_management/__init__.py +0 -0
  33. megadetector/data_management/animl_to_md.py +161 -0
  34. megadetector/data_management/annotations/__init__.py +0 -0
  35. megadetector/data_management/annotations/annotation_constants.py +33 -0
  36. megadetector/data_management/camtrap_dp_to_coco.py +270 -0
  37. megadetector/data_management/cct_json_utils.py +566 -0
  38. megadetector/data_management/cct_to_md.py +184 -0
  39. megadetector/data_management/cct_to_wi.py +293 -0
  40. megadetector/data_management/coco_to_labelme.py +284 -0
  41. megadetector/data_management/coco_to_yolo.py +702 -0
  42. megadetector/data_management/databases/__init__.py +0 -0
  43. megadetector/data_management/databases/add_width_and_height_to_db.py +107 -0
  44. megadetector/data_management/databases/combine_coco_camera_traps_files.py +210 -0
  45. megadetector/data_management/databases/integrity_check_json_db.py +528 -0
  46. megadetector/data_management/databases/subset_json_db.py +195 -0
  47. megadetector/data_management/generate_crops_from_cct.py +200 -0
  48. megadetector/data_management/get_image_sizes.py +164 -0
  49. megadetector/data_management/labelme_to_coco.py +559 -0
  50. megadetector/data_management/labelme_to_yolo.py +349 -0
  51. megadetector/data_management/lila/__init__.py +0 -0
  52. megadetector/data_management/lila/create_lila_blank_set.py +556 -0
  53. megadetector/data_management/lila/create_lila_test_set.py +187 -0
  54. megadetector/data_management/lila/create_links_to_md_results_files.py +106 -0
  55. megadetector/data_management/lila/download_lila_subset.py +182 -0
  56. megadetector/data_management/lila/generate_lila_per_image_labels.py +777 -0
  57. megadetector/data_management/lila/get_lila_annotation_counts.py +174 -0
  58. megadetector/data_management/lila/get_lila_image_counts.py +112 -0
  59. megadetector/data_management/lila/lila_common.py +319 -0
  60. megadetector/data_management/lila/test_lila_metadata_urls.py +164 -0
  61. megadetector/data_management/mewc_to_md.py +344 -0
  62. megadetector/data_management/ocr_tools.py +873 -0
  63. megadetector/data_management/read_exif.py +964 -0
  64. megadetector/data_management/remap_coco_categories.py +195 -0
  65. megadetector/data_management/remove_exif.py +156 -0
  66. megadetector/data_management/rename_images.py +194 -0
  67. megadetector/data_management/resize_coco_dataset.py +663 -0
  68. megadetector/data_management/speciesnet_to_md.py +41 -0
  69. megadetector/data_management/wi_download_csv_to_coco.py +247 -0
  70. megadetector/data_management/yolo_output_to_md_output.py +594 -0
  71. megadetector/data_management/yolo_to_coco.py +876 -0
  72. megadetector/data_management/zamba_to_md.py +188 -0
  73. megadetector/detection/__init__.py +0 -0
  74. megadetector/detection/change_detection.py +840 -0
  75. megadetector/detection/process_video.py +479 -0
  76. megadetector/detection/pytorch_detector.py +1451 -0
  77. megadetector/detection/run_detector.py +1267 -0
  78. megadetector/detection/run_detector_batch.py +2159 -0
  79. megadetector/detection/run_inference_with_yolov5_val.py +1314 -0
  80. megadetector/detection/run_md_and_speciesnet.py +1494 -0
  81. megadetector/detection/run_tiled_inference.py +1038 -0
  82. megadetector/detection/tf_detector.py +209 -0
  83. megadetector/detection/video_utils.py +1379 -0
  84. megadetector/postprocessing/__init__.py +0 -0
  85. megadetector/postprocessing/add_max_conf.py +72 -0
  86. megadetector/postprocessing/categorize_detections_by_size.py +166 -0
  87. megadetector/postprocessing/classification_postprocessing.py +1752 -0
  88. megadetector/postprocessing/combine_batch_outputs.py +249 -0
  89. megadetector/postprocessing/compare_batch_results.py +2110 -0
  90. megadetector/postprocessing/convert_output_format.py +403 -0
  91. megadetector/postprocessing/create_crop_folder.py +629 -0
  92. megadetector/postprocessing/detector_calibration.py +570 -0
  93. megadetector/postprocessing/generate_csv_report.py +522 -0
  94. megadetector/postprocessing/load_api_results.py +223 -0
  95. megadetector/postprocessing/md_to_coco.py +428 -0
  96. megadetector/postprocessing/md_to_labelme.py +351 -0
  97. megadetector/postprocessing/md_to_wi.py +41 -0
  98. megadetector/postprocessing/merge_detections.py +392 -0
  99. megadetector/postprocessing/postprocess_batch_results.py +2077 -0
  100. megadetector/postprocessing/remap_detection_categories.py +226 -0
  101. megadetector/postprocessing/render_detection_confusion_matrix.py +677 -0
  102. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +206 -0
  103. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +82 -0
  104. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1665 -0
  105. megadetector/postprocessing/separate_detections_into_folders.py +795 -0
  106. megadetector/postprocessing/subset_json_detector_output.py +964 -0
  107. megadetector/postprocessing/top_folders_to_bottom.py +238 -0
  108. megadetector/postprocessing/validate_batch_results.py +332 -0
  109. megadetector/taxonomy_mapping/__init__.py +0 -0
  110. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +491 -0
  111. megadetector/taxonomy_mapping/map_new_lila_datasets.py +213 -0
  112. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +165 -0
  113. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +543 -0
  114. megadetector/taxonomy_mapping/retrieve_sample_image.py +71 -0
  115. megadetector/taxonomy_mapping/simple_image_download.py +224 -0
  116. megadetector/taxonomy_mapping/species_lookup.py +1008 -0
  117. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +159 -0
  118. megadetector/taxonomy_mapping/taxonomy_graph.py +346 -0
  119. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +83 -0
  120. megadetector/tests/__init__.py +0 -0
  121. megadetector/tests/test_nms_synthetic.py +335 -0
  122. megadetector/utils/__init__.py +0 -0
  123. megadetector/utils/ct_utils.py +1857 -0
  124. megadetector/utils/directory_listing.py +199 -0
  125. megadetector/utils/extract_frames_from_video.py +307 -0
  126. megadetector/utils/gpu_test.py +125 -0
  127. megadetector/utils/md_tests.py +2072 -0
  128. megadetector/utils/path_utils.py +2832 -0
  129. megadetector/utils/process_utils.py +172 -0
  130. megadetector/utils/split_locations_into_train_val.py +237 -0
  131. megadetector/utils/string_utils.py +234 -0
  132. megadetector/utils/url_utils.py +825 -0
  133. megadetector/utils/wi_platform_utils.py +968 -0
  134. megadetector/utils/wi_taxonomy_utils.py +1759 -0
  135. megadetector/utils/write_html_image_list.py +239 -0
  136. megadetector/visualization/__init__.py +0 -0
  137. megadetector/visualization/plot_utils.py +309 -0
  138. megadetector/visualization/render_images_with_thumbnails.py +243 -0
  139. megadetector/visualization/visualization_utils.py +1940 -0
  140. megadetector/visualization/visualize_db.py +630 -0
  141. megadetector/visualization/visualize_detector_output.py +479 -0
  142. megadetector/visualization/visualize_video_output.py +705 -0
  143. megadetector-10.0.13.dist-info/METADATA +134 -0
  144. megadetector-10.0.13.dist-info/RECORD +147 -0
  145. megadetector-10.0.13.dist-info/WHEEL +5 -0
  146. megadetector-10.0.13.dist-info/licenses/LICENSE +19 -0
  147. megadetector-10.0.13.dist-info/top_level.txt +1 -0
@@ -0,0 +1,199 @@
1
+ """
2
+
3
+ directory_listing.py
4
+
5
+ Script for creating Apache-style HTML directory listings for a local directory
6
+ and all its subdirectories.
7
+
8
+ Also includes a preview of a jpg file (the first in an alphabetical list),
9
+ if present.
10
+
11
+ """
12
+
13
+ #%% Imports
14
+
15
+ import os
16
+ import sys
17
+ import argparse
18
+
19
+ from megadetector.utils.path_utils import is_image_file
20
+
21
+
22
+ #%% Directory enumeration functions
23
+
24
+ def _create_plain_index(root, dirs, files, dirname=None):
25
+ """
26
+ Creates the fairly plain HTML folder index including a preview of a single image file,
27
+ if any is present.
28
+
29
+ Args:
30
+ root (str): path to the root directory, all paths in [dirs] and
31
+ [files] are relative to this root folder
32
+ dirs (list): list of strings, the directories in [root]
33
+ files (list): list of strings, the files in [root]
34
+ dirname (str, optional): name to print in the html,
35
+ which may be different than [root]
36
+
37
+ Returns:
38
+ str: HTML source of the directory listing
39
+ """
40
+
41
+ if dirname is None:
42
+ dirname = root or '/'
43
+ dirname = dirname.replace('\\','/')
44
+
45
+ html = "<!DOCTYPE html>\n"
46
+ html += "<html lang='en'><head>"
47
+ html += "<title>Index of {}</title>\n".format(dirname)
48
+ html += "<meta charset='UTF-8'>\n"
49
+ html += "<style>\n"
50
+ html += "body { font-family: Segoe UI, Helvetica, Arial, sans-serif; }\n"
51
+ html += "a { text-decoration:none; }\n"
52
+ html += "</style>\n"
53
+ html += "</head><body>\n"
54
+
55
+ html += "<h1>Index of {}</h1>\n".format(dirname)
56
+
57
+ # Insert preview image
58
+ jpg_files = [f for f in files if is_image_file(f)]
59
+
60
+ if len(jpg_files) > 0:
61
+
62
+ # This is slow, so defaulting to just the first image:
63
+ #
64
+ # Use the largest image file as this is most likely to contain
65
+ # useful content.
66
+ #
67
+ # jpg_file_sizes = [os.path.getsize(f) for f in jpg_files]
68
+ # largest_file_index = max(range(len(jpg_files)), key=lambda x: jpg_file_sizes[x])
69
+
70
+ html += "<a href='{0}'><img style='height:200px; float:right;' src='{0}' alt='Preview image'></a>\n".\
71
+ format(jpg_files[0])
72
+ else:
73
+ html += "\n"
74
+ # html += "<p style='width:15em; float:right; margin:0;'>[No preview available]</p>\n"
75
+
76
+ if root:
77
+ html += "<p><a href='../index.html'>To parent directory</a></p>\n"
78
+ else:
79
+ html += "\n"
80
+ # html += "<p>This is the root directory.</p>\n"
81
+
82
+ html += "<h2>Folders</h2>\n"
83
+ if len(dirs) > 0:
84
+ html += "<ul style='list-style-type: none; padding-left:1em;'>\n"
85
+ for dir in sorted(dirs):
86
+ html += "<li>&#128193; <a href='{0}/index.html'>{0}</a></li>\n".format(dir)
87
+ html += "</ul>\n"
88
+ else:
89
+ html += "<p style='padding-left:1em;'>No folders</p>\n"
90
+
91
+ html += "<h2>Files</h2>\n"
92
+ if len(files) > 0:
93
+ html += "<ul style='list-style-type: none; padding-left:1em;'>\n"
94
+ for fname in sorted(files):
95
+ if is_image_file(fname):
96
+ html += "<li>&#128443; <a href='{0}'>{0}</a></li>\n".format(fname)
97
+ else:
98
+ html += "<li>&#128442; <a href='{0}'>{0}</a></li>\n".format(fname)
99
+ html += "</ul>\n"
100
+ else:
101
+ html += "<p style = 'padding-left:1em;'>No files</p>\n"
102
+
103
+ # Add some space at the bottom because the browser's status bar might hide stuff
104
+ html += "<p style='margin:2em;'>&nbsp;</p>\n"
105
+ html += "</body></html>\n"
106
+ return html
107
+
108
+ # ...def _create_plain_index(...)
109
+
110
+
111
+ def create_html_index(dir,
112
+ overwrite=False,
113
+ template_fun=_create_plain_index,
114
+ basepath=None,
115
+ recursive=True):
116
+ """
117
+ Recursively traverses the local directory [dir] and generates a index
118
+ file for each folder using [template_fun] to generate the HTML output.
119
+ Excludes hidden files.
120
+
121
+ Args:
122
+ dir (str): directory to process
123
+ overwrite (bool, optional): whether to over-write existing index file
124
+ template_fun (func, optional): function taking three arguments (string,
125
+ list of string, list of string) representing the current root, the list of folders,
126
+ and the list of files. Should return the HTML source of the index file.
127
+ basepath (str, optional): if not None, the name used for each subfolder in [dir]
128
+ in the output files will be relative to [basepath]
129
+ recursive (bool, optional): recurse into subfolders
130
+ """
131
+
132
+ if template_fun is None:
133
+ template_fun = _create_plain_index
134
+
135
+ print('Traversing {}'.format(dir))
136
+
137
+ # Make sure we remove the trailing /
138
+ dir = os.path.normpath(dir)
139
+
140
+ # Traverse directory and all sub directories, excluding hidden files
141
+ for root, dirs, files in os.walk(dir):
142
+
143
+ # Exclude files and folders that are hidden
144
+ files = [f for f in files if not f[0] == '.']
145
+ dirs[:] = [d for d in dirs if not d[0] == '.']
146
+
147
+ # Output is written to file *root*/index.html
148
+ output_file = os.path.join(root, "index.html")
149
+
150
+ if (not overwrite) and os.path.isfile(output_file):
151
+ print('Skipping {}, file exists'.format(output_file))
152
+ continue
153
+
154
+ print("Generating {}".format(output_file))
155
+
156
+ # Generate HTML with template function
157
+ dirname = None
158
+ if basepath is not None:
159
+ dirname = os.path.relpath(root,basepath)
160
+ html = template_fun(root[len(dir):], dirs, files, dirname)
161
+
162
+ # Write to file
163
+ with open(output_file, 'wt') as fi:
164
+ fi.write(html)
165
+
166
+ if not recursive:
167
+ break
168
+
169
+ # ...def create_html_index(...)
170
+
171
+
172
+ #%% Command-line driver
173
+
174
+ def main(): # noqa
175
+
176
+ parser = argparse.ArgumentParser()
177
+
178
+ parser.add_argument("directory", type=str,
179
+ help='Path to directory which should be traversed.')
180
+ parser.add_argument("--basepath", type=str,
181
+ help='Folder names will be printed relative to basepath, if specified',
182
+ default=None)
183
+ parser.add_argument("--overwrite", action='store_true', default=False,
184
+ help='If set, the script will overwrite existing index.html files.')
185
+
186
+ if len(sys.argv[1:]) == 0:
187
+ parser.print_help()
188
+ parser.exit()
189
+
190
+ args = parser.parse_args()
191
+
192
+ assert os.path.isdir(args.directory), "{} is not a valid directory".format(args.directory)
193
+
194
+ create_html_index(args.directory,
195
+ overwrite=args.overwrite,
196
+ basepath=args.basepath)
197
+
198
+ if __name__ == '__main__':
199
+ main()
@@ -0,0 +1,307 @@
1
+ """
2
+
3
+ extract_frames_from_video.py
4
+
5
+ Extracts frames from a source video or folder of videos and writes those frames to jpeg files.
6
+ For single videos, writes frame images to the destination folder. For folders of videos, creates
7
+ subfolders in the destination folder (one per video) and writes frame images to those subfolders.
8
+
9
+ """
10
+
11
+ #%% Constants and imports
12
+
13
+ import argparse
14
+ import inspect
15
+ import json
16
+ import os
17
+ import sys
18
+
19
+ from megadetector.detection.video_utils import \
20
+ video_to_frames, video_folder_to_frames, is_video_file
21
+
22
+
23
+ #%% Options class
24
+
25
+ class FrameExtractionOptions:
26
+ """
27
+ Parameters controlling the behavior of extract_frames().
28
+ """
29
+
30
+ def __init__(self):
31
+
32
+ #: Number of workers to use for parallel processing
33
+ self.n_workers = 1
34
+
35
+ #: Use threads for parallel processing
36
+ self.parallelize_with_threads = False
37
+
38
+ #: JPEG quality for extracted frames
39
+ self.quality = 80
40
+
41
+ #: Maximum width for extracted frames (defaults to None)
42
+ self.max_width = None
43
+
44
+ #: Enable additional debug output
45
+ self.verbose = False
46
+
47
+ #: Sample every Nth frame starting from the first frame; if this is None
48
+ #: or 1, every frame is extracted. If this is a negative value, it's interpreted
49
+ #: as a sampling rate in seconds, which is rounded to the nearest frame sampling
50
+ #: rate. Mutually exclusive with detector_output_file.
51
+ self.frame_sample = None
52
+
53
+ #: Path to MegaDetector .json output file. When specified, extracts frames
54
+ #: referenced in this file. Mutually exclusive with frame_sample. [source]
55
+ #: must be a folder when this is specified.
56
+ self.detector_output_file = None
57
+
58
+ # ...def __init__(...)
59
+
60
+ # ...class FrameExtractionOptions
61
+
62
+
63
+ #%% Core functions
64
+
65
+ def extract_frames(source, destination, options=None):
66
+ """
67
+ Extracts frames from a video or folder of videos.
68
+
69
+ Args:
70
+ source (str): path to a single video file or folder of videos
71
+ destination (str): folder to write frame images to (will be created if it doesn't exist)
72
+ options (FrameExtractionOptions, optional): parameters controlling frame extraction
73
+
74
+ Returns:
75
+ tuple: for single videos, returns (list of frame filenames, frame rate).
76
+ for folders, returns (list of lists of frame filenames, list of frame rates, list
77
+ of video filenames)
78
+ """
79
+
80
+ if options is None:
81
+ options = FrameExtractionOptions()
82
+
83
+ # Validate inputs
84
+ if not os.path.exists(source):
85
+ raise ValueError('Source path {} does not exist'.format(source))
86
+
87
+ if os.path.abspath(source) == os.path.abspath(destination):
88
+ raise ValueError('Source and destination cannot be the same')
89
+
90
+ # Create destination folder if it doesn't exist
91
+ os.makedirs(destination, exist_ok=True)
92
+
93
+ # Determine whether source is a file or folder
94
+ source_is_file = os.path.isfile(source)
95
+
96
+ if source_is_file:
97
+
98
+ # Validate that source is a video file
99
+ if not is_video_file(source):
100
+ raise ValueError('Source file {} is not a video file'.format(source))
101
+
102
+ # detector_output_file requires source to be a folder
103
+ if options.detector_output_file is not None:
104
+ raise ValueError('detector_output_file option requires source to be a folder, not a file')
105
+
106
+ # Extract frames from single video
107
+ return video_to_frames(input_video_file=source,
108
+ output_folder=destination,
109
+ overwrite=True,
110
+ every_n_frames=options.frame_sample,
111
+ verbose=options.verbose,
112
+ quality=options.quality,
113
+ max_width=options.max_width,
114
+ allow_empty_videos=True)
115
+
116
+ else:
117
+
118
+ frames_to_extract = None
119
+ relative_paths_to_process = None
120
+
121
+ # Handle detector output file
122
+ if options.detector_output_file is not None:
123
+ frames_to_extract, relative_paths_to_process = _parse_detector_output(
124
+ options.detector_output_file, source, options.verbose)
125
+ options.frame_sample = None
126
+
127
+ return video_folder_to_frames(input_folder=source,
128
+ output_folder_base=destination,
129
+ recursive=True,
130
+ overwrite=True,
131
+ n_threads=options.n_workers,
132
+ every_n_frames=options.frame_sample,
133
+ verbose=options.verbose,
134
+ parallelization_uses_threads=options.parallelize_with_threads,
135
+ quality=options.quality,
136
+ max_width=options.max_width,
137
+ frames_to_extract=frames_to_extract,
138
+ relative_paths_to_process=relative_paths_to_process,
139
+ allow_empty_videos=True)
140
+
141
+ # ...def extract_frames(...)
142
+
143
+
144
+ def _parse_detector_output(detector_output_file, source_folder, verbose=False):
145
+ """
146
+ Parses a MegaDetector .json output file and returns frame extraction information.
147
+
148
+ Args:
149
+ detector_output_file (str): path to MegaDetector .json output file
150
+ source_folder (str): folder containing the source videos
151
+ verbose (bool, optional): enable additional debug output
152
+
153
+ Returns:
154
+ tuple: (frames_to_extract_dict, relative_paths_to_process) where:
155
+ - frames_to_extract_dict maps relative video paths to lists of frame numbers
156
+ - relative_paths_to_process is a list of relative video paths to process
157
+ """
158
+
159
+ print('Parsing detector output file: {}'.format(detector_output_file))
160
+
161
+ # Load the detector results
162
+ with open(detector_output_file, 'r') as f:
163
+ detector_results = json.load(f)
164
+
165
+ if 'images' not in detector_results:
166
+ raise ValueError('Detector output file does not contain "images" field')
167
+
168
+ images = detector_results['images']
169
+ frames_to_extract_dict = {}
170
+ video_files_in_results = set()
171
+
172
+ for image_entry in images:
173
+
174
+ file_path = image_entry['file']
175
+
176
+ # Skip non-video files
177
+ if not is_video_file(file_path):
178
+ if verbose:
179
+ print('Skipping non-video file {}'.format(file_path))
180
+ continue
181
+
182
+ # Check whether video file exists in source folder
183
+ full_video_path = os.path.join(source_folder, file_path)
184
+ if not os.path.isfile(full_video_path):
185
+ print('Warning: video file {} not found in source folder, skipping'.format(file_path))
186
+ continue
187
+
188
+ video_files_in_results.add(file_path)
189
+
190
+ # Determine which frames to extract for this video
191
+ frames_for_this_video = []
192
+
193
+ if 'frames_processed' in image_entry:
194
+ # Use the frames_processed field if available
195
+ frames_for_this_video = image_entry['frames_processed']
196
+ if verbose:
197
+ print('Video {}: using frames_processed field with {} frames'.format(
198
+ file_path, len(frames_for_this_video)))
199
+ else:
200
+ # Extract frames from detections
201
+ if ('detections' in image_entry) and (image_entry['detections'] is not None):
202
+ frame_numbers = set()
203
+ for detection in image_entry['detections']:
204
+ if 'frame_number' in detection:
205
+ frame_numbers.add(detection['frame_number'])
206
+ frames_for_this_video = sorted(list(frame_numbers))
207
+ if verbose:
208
+ print('Video {}: extracted {} unique frame numbers from detections'.format(
209
+ file_path, len(frames_for_this_video)))
210
+
211
+ if len(frames_for_this_video) > 0:
212
+ frames_to_extract_dict[file_path] = frames_for_this_video
213
+
214
+ # ...for each image/video in this file
215
+
216
+ relative_paths_to_process = sorted(list(video_files_in_results))
217
+
218
+ print('Found {} videos with frames to extract'.format(len(frames_to_extract_dict)))
219
+
220
+ return frames_to_extract_dict, relative_paths_to_process
221
+
222
+ # ...def _parse_detector_output(...)
223
+
224
+
225
+ #%% Command-line driver
226
+
227
+ def _args_to_object(args, obj):
228
+ """
229
+ Copy all fields from a Namespace (i.e., the output from parse_args) to an object.
230
+ Skips fields starting with _. Does not check existence in the target object.
231
+ """
232
+
233
+ for n, v in inspect.getmembers(args):
234
+ if not n.startswith('_'):
235
+ setattr(obj, n, v)
236
+
237
+
238
+ def main():
239
+ """
240
+ Command-line driver for extract_frames_from_video
241
+ """
242
+
243
+ parser = argparse.ArgumentParser(
244
+ description='Extract frames from videos and save as JPEG files')
245
+
246
+ parser.add_argument('source', type=str,
247
+ help='Path to a single video file or folder containing videos')
248
+ parser.add_argument('destination', type=str,
249
+ help='Output folder for extracted frames (will be created if it does not exist)')
250
+
251
+ parser.add_argument('--n_workers', type=int, default=1,
252
+ help='Number of workers to use for parallel processing (default: %(default)s)')
253
+ parser.add_argument('--parallelize_with_threads', action='store_true',
254
+ help='Use threads for parallel processing (default: use processes)')
255
+ parser.add_argument('--quality', type=int, default=80,
256
+ help='JPEG quality for extracted frames (default: %(default)s)')
257
+ parser.add_argument('--max_width', type=int, default=None,
258
+ help='Maximum width for extracted frames (default: no resizing)')
259
+ parser.add_argument('--verbose', action='store_true',
260
+ help='Enable additional debug output')
261
+
262
+ # Mutually exclusive group for frame sampling options
263
+ frame_group = parser.add_mutually_exclusive_group()
264
+ frame_group.add_argument('--frame_sample', type=float, default=None,
265
+ help='Sample every Nth frame starting from the first frame; if this is None or 1, ' +
266
+ 'every frame is extracted. If this is a negative value, it\'s interpreted as a ' +
267
+ 'sampling rate in seconds, which is rounded to the nearest frame sampling rate')
268
+ frame_group.add_argument('--detector_output_file', type=str, default=None,
269
+ help='Path to MegaDetector .json output file. When specified, extracts frames ' +
270
+ 'referenced in this file. Source must be a folder when this is specified.')
271
+
272
+ if len(sys.argv[1:]) == 0:
273
+ parser.print_help()
274
+ parser.exit()
275
+
276
+ args = parser.parse_args()
277
+
278
+ # Convert to an options object
279
+ options = FrameExtractionOptions()
280
+ _args_to_object(args, options)
281
+
282
+ # Additional validation
283
+ if options.detector_output_file is not None:
284
+ if not os.path.isfile(options.detector_output_file):
285
+ print('Error: detector_output_file {} does not exist'.format(options.detector_output_file))
286
+ sys.exit(1)
287
+
288
+ try:
289
+ result = extract_frames(args.source, args.destination, options)
290
+
291
+ if os.path.isfile(args.source):
292
+ frame_filenames, frame_rate = result
293
+ print('Extracted {} frames from {} (frame rate: {:.2f} fps)'.format(
294
+ len(frame_filenames), args.source, frame_rate))
295
+ else:
296
+ frame_filenames_by_video, fs_by_video, video_filenames = result
297
+ total_frames = sum(len(frames) for frames in frame_filenames_by_video)
298
+ print('Processed {} videos, extracted {} total frames'.format(
299
+ len(video_filenames), total_frames))
300
+
301
+ except Exception as e:
302
+ print('Error: {}'.format(str(e)))
303
+ sys.exit(1)
304
+
305
+
306
+ if __name__ == '__main__':
307
+ main()
@@ -0,0 +1,125 @@
1
+ """
2
+
3
+ gpu_test.py
4
+
5
+ Simple script to verify CUDA availability, used to verify a CUDA environment
6
+ for TF or PyTorch
7
+
8
+ """
9
+
10
+ # Minimize TF printouts
11
+ import os
12
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1'
13
+
14
+ try:
15
+ import logging
16
+ logging.getLogger('tensorflow').setLevel(logging.ERROR)
17
+ except Exception:
18
+ pass
19
+
20
+
21
+ #%% Torch/TF test functions
22
+
23
+ def torch_test():
24
+ """
25
+ Print diagnostic information about Torch/CUDA status, including Torch/CUDA versions
26
+ and all available CUDA device names.
27
+
28
+ Returns:
29
+ int: The number of CUDA devices reported by PyTorch.
30
+ """
31
+
32
+ try:
33
+ import torch
34
+ except Exception as e: #noqa
35
+ print('PyTorch unavailable, not running PyTorch tests. PyTorch import error was:\n{}'.format(
36
+ str(e)))
37
+ return 0
38
+
39
+ print('Torch version: {}'.format(str(torch.__version__)))
40
+ print('CUDA available (according to PyTorch): {}'.format(torch.cuda.is_available()))
41
+ if torch.cuda.is_available():
42
+ print('CUDA version (according to PyTorch): {}'.format(torch.version.cuda))
43
+ print('CuDNN version (according to PyTorch): {}'.format(torch.backends.cudnn.version()))
44
+
45
+ device_ids = list(range(torch.cuda.device_count()))
46
+
47
+ if len(device_ids) > 0:
48
+ cuda_str = 'Found {} CUDA devices:'.format(len(device_ids))
49
+ print(cuda_str)
50
+
51
+ for device_id in device_ids:
52
+ device_name = 'unknown'
53
+ try:
54
+ device_name = torch.cuda.get_device_name(device=device_id)
55
+ except Exception as e: #noqa
56
+ pass
57
+ print('{}: {}'.format(device_id,device_name))
58
+ else:
59
+ print('No GPUs reported by PyTorch')
60
+
61
+ try:
62
+ if torch.backends.mps.is_built and torch.backends.mps.is_available():
63
+ print('PyTorch reports that Metal Performance Shaders are available')
64
+ except Exception:
65
+ pass
66
+ return len(device_ids)
67
+
68
+
69
+ def tf_test():
70
+ """
71
+ Print diagnostic information about TF/CUDA status.
72
+
73
+ Returns:
74
+ int: The number of CUDA devices reported by TensorFlow.
75
+ """
76
+
77
+ try:
78
+ import tensorflow as tf # type: ignore
79
+ except Exception as e: #noqa
80
+ print('TensorFlow unavailable, not running TF tests. TF import error was:\n{}'.format(
81
+ str(e)))
82
+ return 0
83
+
84
+ from tensorflow.python.platform import build_info as build # type: ignore
85
+ print(f"TF version: {tf.__version__}")
86
+
87
+ if 'cuda_version' not in build.build_info:
88
+ print('TF does not appear to be built with CUDA')
89
+ else:
90
+ print(f"CUDA build version reported by TensorFlow: {build.build_info['cuda_version']}")
91
+ if 'cudnn_version' not in build.build_info:
92
+ print('TF does not appear to be built with CuDNN')
93
+ else:
94
+ print(f"CuDNN build version reported by TensorFlow: {build.build_info['cudnn_version']}")
95
+
96
+ try:
97
+ from tensorflow.python.compiler.tensorrt import trt_convert as trt # type: ignore
98
+ print("Linked TensorRT version: {}".format(trt.trt_utils._pywrap_py_utils.get_linked_tensorrt_version()))
99
+ except Exception:
100
+ print('Could not probe TensorRT version')
101
+
102
+ gpus = tf.config.list_physical_devices('GPU')
103
+ if gpus is None:
104
+ gpus = []
105
+
106
+ if len(gpus) > 0:
107
+ print('TensorFlow found the following GPUs:')
108
+ for gpu in gpus:
109
+ print(gpu.name)
110
+
111
+ else:
112
+ print('No GPUs reported by TensorFlow')
113
+
114
+ return len(gpus)
115
+
116
+
117
+ #%% Command-line driver
118
+
119
+ if __name__ == '__main__':
120
+
121
+ print('*** Running Torch tests ***\n')
122
+ torch_test()
123
+
124
+ print('\n*** Running TF tests ***\n')
125
+ tf_test()