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,479 @@
1
+ """
2
+
3
+ visualize_detector_output.py
4
+
5
+ Render images with bounding boxes annotated on them to a folder, based on a
6
+ detector output result file (.json), optionally writing an HTML index file.
7
+
8
+ """
9
+
10
+ #%% Imports
11
+
12
+ import argparse
13
+ import os
14
+ import random
15
+ import sys
16
+
17
+ from multiprocessing.pool import ThreadPool
18
+ from multiprocessing.pool import Pool
19
+ from functools import partial
20
+ from tqdm import tqdm
21
+
22
+ from megadetector.data_management.annotations.annotation_constants import detector_bbox_category_id_to_name
23
+ from megadetector.detection.run_detector import get_typical_confidence_threshold_from_results
24
+ from megadetector.utils.ct_utils import get_max_conf
25
+ from megadetector.utils import write_html_image_list
26
+ from megadetector.utils.path_utils import path_is_abs
27
+ from megadetector.utils.path_utils import open_file
28
+ from megadetector.utils.wi_taxonomy_utils import load_md_or_speciesnet_file
29
+ from megadetector.visualization import visualization_utils as vis_utils
30
+ from megadetector.visualization.visualization_utils import blur_detections
31
+
32
+ default_box_sort_order = 'confidence'
33
+
34
+
35
+ #%% Constants
36
+
37
+ # This will only be used if a category mapping is not available in the results file.
38
+ DEFAULT_DETECTOR_LABEL_MAP = {
39
+ str(k): v for k, v in detector_bbox_category_id_to_name.items()
40
+ }
41
+
42
+
43
+ #%% Support functions
44
+
45
+ def _render_image(entry,
46
+ detector_label_map,
47
+ classification_label_map,
48
+ confidence_threshold,
49
+ classification_confidence_threshold,
50
+ render_detections_only,
51
+ preserve_path_structure,
52
+ out_dir,
53
+ images_dir,
54
+ output_image_width,
55
+ box_sort_order=default_box_sort_order,
56
+ category_names_to_blur=None):
57
+ """
58
+ Internal function for rendering a single image.
59
+ """
60
+
61
+ rendering_result = {'failed_image':False,'missing_image':False,
62
+ 'skipped_image':False,'annotated_image_path':None,
63
+ 'max_conf':None,'file':entry['file']}
64
+
65
+ image_id = entry['file']
66
+
67
+ if 'failure' in entry and entry['failure'] is not None:
68
+ rendering_result['failed_image'] = True
69
+ return rendering_result
70
+
71
+ assert 'detections' in entry and entry['detections'] is not None
72
+
73
+ max_conf = get_max_conf(entry)
74
+ rendering_result['max_conf'] = max_conf
75
+
76
+ if (max_conf < confidence_threshold) and render_detections_only:
77
+ rendering_result['skipped_image'] = True
78
+ return rendering_result
79
+
80
+ if images_dir is None:
81
+ image_filename_in_abs = image_id
82
+ assert path_is_abs(image_filename_in_abs), \
83
+ 'Absolute paths are required when no image base dir is supplied'
84
+ else:
85
+ assert not path_is_abs(image_id), \
86
+ 'Relative paths are required when an image base dir is supplied'
87
+ image_filename_in_abs = os.path.join(images_dir, image_id)
88
+ if not os.path.exists(image_filename_in_abs):
89
+ print(f'Image {image_id} not found')
90
+ rendering_result['missing_image'] = True
91
+ return rendering_result
92
+
93
+ # Load the image
94
+ image = vis_utils.open_image(image_filename_in_abs)
95
+
96
+ # Find categories we're supposed to blur
97
+ category_ids_to_blur = []
98
+ if category_names_to_blur is not None:
99
+ if isinstance(category_names_to_blur,str):
100
+ category_names_to_blur = [category_names_to_blur]
101
+ for category_id in detector_label_map:
102
+ if detector_label_map[category_id] in category_names_to_blur:
103
+ category_ids_to_blur.append(category_id)
104
+
105
+ detections_to_blur = []
106
+ for d in entry['detections']:
107
+ if d['conf'] >= confidence_threshold and d['category'] in category_ids_to_blur:
108
+ detections_to_blur.append(d)
109
+ if len(detections_to_blur) > 0:
110
+ blur_detections(image,detections_to_blur)
111
+
112
+ # Resize if necessary
113
+ #
114
+ # If output_image_width is -1 or None, this will just return the original image
115
+ image = vis_utils.resize_image(image, output_image_width)
116
+
117
+ vis_utils.render_detection_bounding_boxes(
118
+ entry['detections'], image,
119
+ label_map=detector_label_map,
120
+ classification_label_map=classification_label_map,
121
+ confidence_threshold=confidence_threshold,
122
+ classification_confidence_threshold=classification_confidence_threshold,
123
+ box_sort_order=box_sort_order)
124
+
125
+ if not preserve_path_structure:
126
+ for char in ['/', '\\', ':']:
127
+ image_id = image_id.replace(char, '~')
128
+ annotated_img_path = os.path.join(out_dir, f'anno_{image_id}')
129
+ else:
130
+ assert not os.path.isabs(image_id), "Can't preserve paths when operating on absolute paths"
131
+ annotated_img_path = os.path.join(out_dir, image_id)
132
+ os.makedirs(os.path.dirname(annotated_img_path),exist_ok=True)
133
+
134
+ image.save(annotated_img_path)
135
+ rendering_result['annotated_image_path'] = annotated_img_path
136
+
137
+ return rendering_result
138
+
139
+ # ...def _render_image(...)
140
+
141
+
142
+ #%% Main function
143
+
144
+ def visualize_detector_output(detector_output_path,
145
+ out_dir,
146
+ images_dir=None,
147
+ confidence_threshold=0.15,
148
+ sample=-1,
149
+ output_image_width=700,
150
+ random_seed=None,
151
+ render_detections_only=False,
152
+ classification_confidence_threshold=0.1,
153
+ html_output_file=None,
154
+ html_output_options=None,
155
+ preserve_path_structure=False,
156
+ parallelize_rendering=False,
157
+ parallelize_rendering_n_cores=10,
158
+ parallelize_rendering_with_threads=True,
159
+ box_sort_order=default_box_sort_order,
160
+ category_names_to_blur=None):
161
+ """
162
+ Draws bounding boxes on images given the output of a detector.
163
+
164
+ Args:
165
+ detector_output_path (str): path to detector output .json file
166
+ out_dir (str): path to directory for saving annotated images
167
+ images_dir (str, optional): folder where the images live; filenames in
168
+ [detector_output_path] should be relative to [image_dir]. Can be None if paths are
169
+ absolute.
170
+ confidence_threshold (float, optional): threshold above which detections will be rendered
171
+ sample (int, optional): maximum number of images to render, -1 for all
172
+ output_image_width (int, optional): width in pixels to resize images for display,
173
+ preserving aspect ration; set to -1 to use original image width
174
+ random_seed (int, optional): seed to use for choosing images when sample != -1
175
+ render_detections_only (bool, optional): only render images with above-threshold detections.
176
+ Empty images are discarded after sampling, so if you want to see, e.g., 1000 non-empty
177
+ images, you can set [render_detections_only], but you need to sample more than 1000 images.
178
+ classification_confidence_threshold (float, optional): only show classifications
179
+ above this threshold; does not impact whether images are rendered, only whether
180
+ classification labels (not detection categories) are displayed
181
+ html_output_file (str, optional): output path for an HTML index file (not written
182
+ if None)
183
+ html_output_options (dict, optional): HTML formatting options; see write_html_image_list
184
+ for details. The most common option you may want to supply here is
185
+ 'maxFiguresPerHtmlFile'.
186
+ preserve_path_structure (bool, optional): if False (default), writes images to unique
187
+ names in a flat structure in the output folder; if True, preserves relative paths
188
+ within the output folder
189
+ parallelize_rendering (bool, optional): whether to use concurrent workers for rendering
190
+ parallelize_rendering_n_cores (int, optional): number of concurrent workers to use
191
+ (ignored if parallelize_rendering is False)
192
+ parallelize_rendering_with_threads (bool, optional): determines whether we use
193
+ threads (True) or processes (False) for parallelization (ignored if parallelize_rendering
194
+ is False)
195
+ box_sort_order (str, optional): sorting scheme for detection boxes, can be None, "confidence", or
196
+ "reverse_confidence"
197
+ category_names_to_blur (list of str, optional): category names for which we should blur detections,
198
+ most commonly ['person']
199
+
200
+ Returns:
201
+ list: list of paths to annotated images
202
+ """
203
+
204
+ assert os.path.exists(detector_output_path), \
205
+ 'Detector output file does not exist at {}'.format(detector_output_path)
206
+
207
+ if images_dir is not None:
208
+ assert os.path.isdir(images_dir), \
209
+ 'Image folder {} is not available'.format(images_dir)
210
+
211
+ os.makedirs(out_dir, exist_ok=True)
212
+
213
+
214
+ ##%% Load detector output
215
+
216
+ detector_output = load_md_or_speciesnet_file(detector_output_path)
217
+
218
+ images = detector_output['images']
219
+
220
+ if confidence_threshold is None:
221
+ confidence_threshold = get_typical_confidence_threshold_from_results(detector_output)
222
+
223
+ assert confidence_threshold >= 0 and confidence_threshold <= 1, \
224
+ f'Confidence threshold {confidence_threshold} is invalid, must be in (0, 1).'
225
+
226
+ if 'detection_categories' in detector_output:
227
+ detector_label_map = detector_output['detection_categories']
228
+ else:
229
+ detector_label_map = DEFAULT_DETECTOR_LABEL_MAP
230
+
231
+ num_images = len(images)
232
+ print(f'Detector output file contains {num_images} entries.')
233
+
234
+ if (sample is not None) and (sample > 0) and (num_images > sample):
235
+
236
+ if random_seed is not None:
237
+ images = sorted(images, key=lambda x: x['file'])
238
+ random.seed(random_seed)
239
+
240
+ random.shuffle(images)
241
+ images = sorted(images[:sample], key=lambda x: x['file'])
242
+ print(f'Sampled {len(images)} entries from the detector output file.')
243
+
244
+
245
+ ##%% Load images, annotate them and save
246
+
247
+ print('Rendering detections above a confidence threshold of {}'.format(
248
+ confidence_threshold))
249
+
250
+ classification_label_map = None
251
+
252
+ if 'classification_categories' in detector_output:
253
+ classification_label_map = detector_output['classification_categories']
254
+
255
+ rendering_results = []
256
+
257
+ if parallelize_rendering:
258
+
259
+ if parallelize_rendering_with_threads:
260
+ worker_string = 'threads'
261
+ else:
262
+ worker_string = 'processes'
263
+
264
+ pool = None
265
+ try:
266
+ if parallelize_rendering_n_cores is None:
267
+ if parallelize_rendering_with_threads:
268
+ pool = ThreadPool()
269
+ else:
270
+ pool = Pool()
271
+ else:
272
+ if parallelize_rendering_with_threads:
273
+ pool = ThreadPool(parallelize_rendering_n_cores)
274
+ else:
275
+ pool = Pool(parallelize_rendering_n_cores)
276
+ print('Rendering images with {} {}'.format(parallelize_rendering_n_cores,
277
+ worker_string))
278
+ rendering_results = list(tqdm(pool.imap(
279
+ partial(_render_image,detector_label_map=detector_label_map,
280
+ classification_label_map=classification_label_map,
281
+ confidence_threshold=confidence_threshold,
282
+ classification_confidence_threshold=classification_confidence_threshold,
283
+ render_detections_only=render_detections_only,
284
+ preserve_path_structure=preserve_path_structure,
285
+ out_dir=out_dir,
286
+ images_dir=images_dir,
287
+ output_image_width=output_image_width,
288
+ box_sort_order=box_sort_order,
289
+ category_names_to_blur=category_names_to_blur),
290
+ images), total=len(images)))
291
+ finally:
292
+ if pool is not None:
293
+ pool.close()
294
+ pool.join()
295
+ print('Pool closed and joined for detector output visualization')
296
+
297
+ else:
298
+
299
+ for entry in tqdm(images):
300
+
301
+ rendering_result = _render_image(entry,detector_label_map,classification_label_map,
302
+ confidence_threshold,classification_confidence_threshold,
303
+ render_detections_only,preserve_path_structure,out_dir,
304
+ images_dir,output_image_width,box_sort_order,
305
+ category_names_to_blur=category_names_to_blur)
306
+ rendering_results.append(rendering_result)
307
+
308
+ # ...for each image
309
+
310
+ failed_images = [r for r in rendering_results if r['failed_image']]
311
+ missing_images = [r for r in rendering_results if r['missing_image']]
312
+ skipped_images = [r for r in rendering_results if r['skipped_image']]
313
+
314
+ print('Skipped {} failed images (of {})'.format(len(failed_images),len(images)))
315
+ print('Skipped {} missing images (of {})'.format(len(missing_images),len(images)))
316
+ print('Skipped {} below-threshold images (of {})'.format(len(skipped_images),len(images)))
317
+
318
+ print(f'Rendered detection results to {out_dir}')
319
+
320
+ annotated_image_paths = [r['annotated_image_path'] for r in rendering_results if \
321
+ r['annotated_image_path'] is not None]
322
+
323
+ if html_output_file is not None:
324
+
325
+ html_dir = os.path.dirname(html_output_file)
326
+
327
+ html_image_info = []
328
+
329
+ for r in rendering_results:
330
+ d = {}
331
+ if r['annotated_image_path'] is None:
332
+ assert r['failed_image'] or r['missing_image'] or r['skipped_image']
333
+ continue
334
+ annotated_image_path_relative = os.path.relpath(r['annotated_image_path'],html_dir)
335
+ d['filename'] = annotated_image_path_relative
336
+ d['textStyle'] = \
337
+ 'font-family:verdana,arial,calibri;font-size:80%;' + \
338
+ 'text-align:left;margin-top:20;margin-bottom:5'
339
+ d['title'] = '{} (max conf: {})'.format(r['file'],r['max_conf'])
340
+ html_image_info.append(d)
341
+
342
+ _ = write_html_image_list.write_html_image_list(html_output_file,html_image_info,
343
+ options=html_output_options)
344
+
345
+ return annotated_image_paths
346
+
347
+ # ...def visualize_detector_output(...)
348
+
349
+
350
+ #%% Command-line driver
351
+
352
+ def main(): # noqa
353
+
354
+ parser = argparse.ArgumentParser(
355
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
356
+ description='Annotate the bounding boxes predicted by a detector above '
357
+ 'some confidence threshold, and save the annotated images.')
358
+ parser.add_argument(
359
+ 'detector_output_path', type=str,
360
+ help='Path to json output file of the detector')
361
+ parser.add_argument(
362
+ 'out_dir', type=str,
363
+ help='Path to directory where the annotated images will be saved. '
364
+ 'The directory will be created if it does not exist.')
365
+ parser.add_argument(
366
+ '--confidence', type=float, default=0.15,
367
+ help='Value between 0 and 1, indicating the confidence threshold '
368
+ 'above which to visualize bounding boxes')
369
+ parser.add_argument(
370
+ '--images_dir', type=str, default=None,
371
+ help='Path to a local directory where images are stored. This '
372
+ 'serves as the root directory for image paths in '
373
+ 'detector_output_path. Omit if image paths are absolute.')
374
+ parser.add_argument(
375
+ '--sample', type=int, default=-1,
376
+ help='Number of images to be annotated and rendered. Set to -1 '
377
+ '(default) to annotate all images in the detector output file. '
378
+ 'There may be fewer images if some are not found in images_dir.')
379
+ parser.add_argument(
380
+ '--output_image_width', type=int, default=700,
381
+ help='Integer, desired width in pixels of the output annotated images. '
382
+ 'Use -1 to not resize. Default: 700.')
383
+ parser.add_argument(
384
+ '--random_seed', type=int, default=None,
385
+ help='Integer, for deterministic order of image sampling')
386
+ parser.add_argument(
387
+ '--html_output_file', type=str, default=None,
388
+ help='Filename to which we should write an HTML image index (off by default)')
389
+ parser.add_argument(
390
+ '--open_html_output_file', action='store_true',
391
+ help='Open the .html output file when done')
392
+ parser.add_argument(
393
+ '--detections_only', action='store_true',
394
+ help='Only render images with above-threshold detections (by default, '
395
+ 'both empty and non-empty images are rendered).')
396
+ parser.add_argument(
397
+ '--preserve_path_structure', action='store_true',
398
+ help='Preserve relative image paths (otherwise flattens and assigns unique file names)')
399
+ parser.add_argument(
400
+ '--category_names_to_blur', default=None, type=str,
401
+ help='Comma-separated list of category names to blur (or a single category name, typically "person")')
402
+ parser.add_argument(
403
+ '--classification_confidence', type=float, default=0.3,
404
+ help='If classification results are present, render results above this threshold')
405
+
406
+ if len(sys.argv[1:]) == 0:
407
+ parser.print_help()
408
+ parser.exit()
409
+
410
+ args = parser.parse_args()
411
+
412
+ category_names_to_blur = args.category_names_to_blur
413
+ if category_names_to_blur is not None:
414
+ category_names_to_blur = category_names_to_blur.split(',')
415
+
416
+ visualize_detector_output(
417
+ detector_output_path=args.detector_output_path,
418
+ out_dir=args.out_dir,
419
+ confidence_threshold=args.confidence,
420
+ images_dir=args.images_dir,
421
+ sample=args.sample,
422
+ output_image_width=args.output_image_width,
423
+ random_seed=args.random_seed,
424
+ render_detections_only=args.detections_only,
425
+ classification_confidence_threshold=args.classification_confidence,
426
+ preserve_path_structure=args.preserve_path_structure,
427
+ html_output_file=args.html_output_file,
428
+ category_names_to_blur=category_names_to_blur)
429
+
430
+ if (args.html_output_file is not None) and args.open_html_output_file:
431
+ print('Opening output file {}'.format(args.html_output_file))
432
+ open_file(args.html_output_file)
433
+
434
+ if __name__ == '__main__':
435
+ main()
436
+
437
+
438
+ #%% Interactive driver
439
+
440
+ if False:
441
+
442
+ pass
443
+
444
+ #%%
445
+
446
+ detector_output_path = os.path.expanduser('detections.json')
447
+ out_dir = r'g:\temp\preview'
448
+ images_dir = r'g:\camera_traps\camera_trap_images'
449
+ confidence_threshold = 0.15
450
+ sample = 50
451
+ output_image_width = 700
452
+ random_seed = 1
453
+ render_detections_only = True
454
+ classification_confidence_threshold = 0.1
455
+ html_output_file = os.path.join(out_dir,'index.html')
456
+ html_output_options = None
457
+ preserve_path_structure = False
458
+ parallelize_rendering = True
459
+ parallelize_rendering_n_cores = 10
460
+ parallelize_rendering_with_threads = False
461
+
462
+ _ = visualize_detector_output(detector_output_path,
463
+ out_dir,
464
+ images_dir,
465
+ confidence_threshold,
466
+ sample,
467
+ output_image_width,
468
+ random_seed,
469
+ render_detections_only,
470
+ classification_confidence_threshold,
471
+ html_output_file,
472
+ html_output_options,
473
+ preserve_path_structure,
474
+ parallelize_rendering,
475
+ parallelize_rendering_n_cores,
476
+ parallelize_rendering_with_threads)
477
+
478
+ from megadetector.utils.path_utils import open_file
479
+ open_file(html_output_file)