megadetector 5.0.27__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 +232 -223
  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 +341 -338
  65. megadetector/detection/pytorch_detector.py +308 -266
  66. megadetector/detection/run_detector.py +186 -166
  67. megadetector/detection/run_detector_batch.py +366 -364
  68. megadetector/detection/run_inference_with_yolov5_val.py +328 -325
  69. megadetector/detection/run_tiled_inference.py +312 -253
  70. megadetector/detection/tf_detector.py +24 -24
  71. megadetector/detection/video_utils.py +291 -283
  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 +808 -311
  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 +220 -147
  79. megadetector/postprocessing/detector_calibration.py +173 -168
  80. megadetector/postprocessing/generate_csv_report.py +508 -0
  81. megadetector/postprocessing/load_api_results.py +25 -22
  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 +319 -302
  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 -69
  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 +11 -11
  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 +1019 -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 +1511 -406
  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 +73 -60
  115. megadetector/utils/string_utils.py +147 -26
  116. megadetector/utils/url_utils.py +463 -173
  117. megadetector/utils/wi_utils.py +2629 -2868
  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 +424 -404
  122. megadetector/visualization/visualize_db.py +197 -190
  123. megadetector/visualization/visualize_detector_output.py +126 -98
  124. {megadetector-5.0.27.dist-info → megadetector-5.0.29.dist-info}/METADATA +6 -3
  125. megadetector-5.0.29.dist-info/RECORD +163 -0
  126. {megadetector-5.0.27.dist-info → megadetector-5.0.29.dist-info}/WHEEL +1 -1
  127. megadetector/data_management/importers/add_nacti_sizes.py +0 -52
  128. megadetector/data_management/importers/add_timestamps_to_icct.py +0 -79
  129. megadetector/data_management/importers/animl_results_to_md_results.py +0 -158
  130. megadetector/data_management/importers/auckland_doc_test_to_json.py +0 -373
  131. megadetector/data_management/importers/auckland_doc_to_json.py +0 -201
  132. megadetector/data_management/importers/awc_to_json.py +0 -191
  133. megadetector/data_management/importers/bellevue_to_json.py +0 -272
  134. megadetector/data_management/importers/cacophony-thermal-importer.py +0 -793
  135. megadetector/data_management/importers/carrizo_shrubfree_2018.py +0 -269
  136. megadetector/data_management/importers/carrizo_trail_cam_2017.py +0 -289
  137. megadetector/data_management/importers/cct_field_adjustments.py +0 -58
  138. megadetector/data_management/importers/channel_islands_to_cct.py +0 -913
  139. megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
  140. megadetector/data_management/importers/eMammal/eMammal_helpers.py +0 -249
  141. megadetector/data_management/importers/eMammal/make_eMammal_json.py +0 -223
  142. megadetector/data_management/importers/ena24_to_json.py +0 -276
  143. megadetector/data_management/importers/filenames_to_json.py +0 -386
  144. megadetector/data_management/importers/helena_to_cct.py +0 -283
  145. megadetector/data_management/importers/idaho-camera-traps.py +0 -1407
  146. megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
  147. megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +0 -387
  148. megadetector/data_management/importers/jb_csv_to_json.py +0 -150
  149. megadetector/data_management/importers/mcgill_to_json.py +0 -250
  150. megadetector/data_management/importers/missouri_to_json.py +0 -490
  151. megadetector/data_management/importers/nacti_fieldname_adjustments.py +0 -79
  152. megadetector/data_management/importers/noaa_seals_2019.py +0 -181
  153. megadetector/data_management/importers/osu-small-animals-to-json.py +0 -364
  154. megadetector/data_management/importers/pc_to_json.py +0 -365
  155. megadetector/data_management/importers/plot_wni_giraffes.py +0 -123
  156. megadetector/data_management/importers/prepare_zsl_imerit.py +0 -131
  157. megadetector/data_management/importers/raic_csv_to_md_results.py +0 -416
  158. megadetector/data_management/importers/rspb_to_json.py +0 -356
  159. megadetector/data_management/importers/save_the_elephants_survey_A.py +0 -320
  160. megadetector/data_management/importers/save_the_elephants_survey_B.py +0 -329
  161. megadetector/data_management/importers/snapshot_safari_importer.py +0 -758
  162. megadetector/data_management/importers/snapshot_serengeti_lila.py +0 -1067
  163. megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
  164. megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
  165. megadetector/data_management/importers/sulross_get_exif.py +0 -65
  166. megadetector/data_management/importers/timelapse_csv_set_to_json.py +0 -490
  167. megadetector/data_management/importers/ubc_to_json.py +0 -399
  168. megadetector/data_management/importers/umn_to_json.py +0 -507
  169. megadetector/data_management/importers/wellington_to_json.py +0 -263
  170. megadetector/data_management/importers/wi_to_json.py +0 -442
  171. megadetector/data_management/importers/zamba_results_to_md_results.py +0 -180
  172. megadetector/data_management/lila/add_locations_to_island_camera_traps.py +0 -101
  173. megadetector/data_management/lila/add_locations_to_nacti.py +0 -151
  174. megadetector-5.0.27.dist-info/RECORD +0 -208
  175. {megadetector-5.0.27.dist-info → megadetector-5.0.29.dist-info}/licenses/LICENSE +0 -0
  176. {megadetector-5.0.27.dist-info → megadetector-5.0.29.dist-info}/top_level.txt +0 -0
@@ -10,7 +10,6 @@ detector output result file (.json), optionally writing an HTML index file.
10
10
  #%% Imports
11
11
 
12
12
  import argparse
13
- import json
14
13
  import os
15
14
  import random
16
15
  import sys
@@ -21,11 +20,13 @@ from functools import partial
21
20
  from tqdm import tqdm
22
21
 
23
22
  from megadetector.data_management.annotations.annotation_constants import detector_bbox_category_id_to_name
24
- from megadetector.visualization import visualization_utils as vis_utils
25
- from megadetector.visualization.visualization_utils import blur_detections
23
+ from megadetector.detection.run_detector import get_typical_confidence_threshold_from_results
26
24
  from megadetector.utils.ct_utils import get_max_conf
27
25
  from megadetector.utils import write_html_image_list
28
- from megadetector.detection.run_detector import get_typical_confidence_threshold_from_results
26
+ from megadetector.utils.path_utils import path_is_abs
27
+ from megadetector.utils.wi_utils import load_md_or_speciesnet_file
28
+ from megadetector.visualization import visualization_utils as vis_utils
29
+ from megadetector.visualization.visualization_utils import blur_detections
29
30
 
30
31
 
31
32
  #%% Constants
@@ -53,11 +54,11 @@ def _render_image(entry,
53
54
  """
54
55
  Internal function for rendering a single image.
55
56
  """
56
-
57
+
57
58
  rendering_result = {'failed_image':False,'missing_image':False,
58
59
  'skipped_image':False,'annotated_image_path':None,
59
60
  'max_conf':None,'file':entry['file']}
60
-
61
+
61
62
  image_id = entry['file']
62
63
 
63
64
  if 'failure' in entry and entry['failure'] is not None:
@@ -65,23 +66,30 @@ def _render_image(entry,
65
66
  return rendering_result
66
67
 
67
68
  assert 'detections' in entry and entry['detections'] is not None
68
-
69
+
69
70
  max_conf = get_max_conf(entry)
70
71
  rendering_result['max_conf'] = max_conf
71
-
72
+
72
73
  if (max_conf < confidence_threshold) and render_detections_only:
73
74
  rendering_result['skipped_image'] = True
74
75
  return rendering_result
75
-
76
- image_filename_in_abs = os.path.join(images_dir, image_id)
76
+
77
+ if images_dir is None:
78
+ image_filename_in_abs = image_id
79
+ assert path_is_abs(image_filename_in_abs), \
80
+ 'Absolute paths are required when no image base dir is supplied'
81
+ else:
82
+ assert not path_is_abs(image_id), \
83
+ 'Relative paths are required when an image base dir is supplied'
84
+ image_filename_in_abs = os.path.join(images_dir, image_id)
77
85
  if not os.path.exists(image_filename_in_abs):
78
- print(f'Image {image_id} not found in images_dir')
86
+ print(f'Image {image_id} not found')
79
87
  rendering_result['missing_image'] = True
80
88
  return rendering_result
81
89
 
82
90
  # Load the image
83
91
  image = vis_utils.open_image(image_filename_in_abs)
84
-
92
+
85
93
  # Find categories we're supposed to blur
86
94
  category_ids_to_blur = []
87
95
  if category_names_to_blur is not None:
@@ -90,21 +98,21 @@ def _render_image(entry,
90
98
  for category_id in detector_label_map:
91
99
  if detector_label_map[category_id] in category_names_to_blur:
92
100
  category_ids_to_blur.append(category_id)
93
-
101
+
94
102
  detections_to_blur = []
95
103
  for d in entry['detections']:
96
104
  if d['conf'] >= confidence_threshold and d['category'] in category_ids_to_blur:
97
105
  detections_to_blur.append(d)
98
106
  if len(detections_to_blur) > 0:
99
107
  blur_detections(image,detections_to_blur)
100
-
108
+
101
109
  # Resize if necessary
102
110
  #
103
111
  # If output_image_width is -1 or None, this will just return the original image
104
112
  image = vis_utils.resize_image(image, output_image_width)
105
113
 
106
114
  vis_utils.render_detection_bounding_boxes(
107
- entry['detections'], image,
115
+ entry['detections'], image,
108
116
  label_map=detector_label_map,
109
117
  classification_label_map=classification_label_map,
110
118
  confidence_threshold=confidence_threshold,
@@ -119,18 +127,20 @@ def _render_image(entry,
119
127
  assert not os.path.isabs(image_id), "Can't preserve paths when operating on absolute paths"
120
128
  annotated_img_path = os.path.join(out_dir, image_id)
121
129
  os.makedirs(os.path.dirname(annotated_img_path),exist_ok=True)
122
-
130
+
123
131
  image.save(annotated_img_path)
124
- rendering_result['annotated_image_path'] = annotated_img_path
132
+ rendering_result['annotated_image_path'] = annotated_img_path
125
133
 
126
134
  return rendering_result
127
135
 
136
+ # ...def _render_image(...)
137
+
128
138
 
129
139
  #%% Main function
130
140
 
131
141
  def visualize_detector_output(detector_output_path,
132
142
  out_dir,
133
- images_dir,
143
+ images_dir=None,
134
144
  confidence_threshold=0.15,
135
145
  sample=-1,
136
146
  output_image_width=700,
@@ -145,36 +155,39 @@ def visualize_detector_output(detector_output_path,
145
155
  parallelize_rendering_with_threads=True,
146
156
  box_sort_order=None,
147
157
  category_names_to_blur=None):
148
-
149
158
  """
150
159
  Draws bounding boxes on images given the output of a detector.
151
160
 
152
161
  Args:
153
162
  detector_output_path (str): path to detector output .json file
154
163
  out_dir (str): path to directory for saving annotated images
155
- images_dir (str): folder where the images live; filenames in
156
- [detector_output_path] should be relative to [image_dir]
164
+ images_dir (str): folder where the images live; filenames in
165
+ [detector_output_path] should be relative to [image_dir]. Can be None if paths are
166
+ absolute.
157
167
  confidence_threshold (float, optional): threshold above which detections will be rendered
158
168
  sample (int, optional): maximum number of images to render, -1 for all
159
169
  output_image_width (int, optional): width in pixels to resize images for display,
160
170
  preserving aspect ration; set to -1 to use original image width
161
171
  random_seed (int, optional): seed to use for choosing images when sample != -1
162
- render_detections_only (bool): only render images with above-threshold detections
172
+ render_detections_only (bool): only render images with above-threshold detections. Empty
173
+ images are discarded after sampling, so if you want to see, e.g., 1000 non-empty images,
174
+ you can set [render_detections_only], but you need to sample more than 1000 images.
163
175
  classification_confidence_threshold (float, optional): only show classifications
164
176
  above this threshold; does not impact whether images are rendered, only whether
165
177
  classification labels (not detection categories) are displayed
166
178
  html_output_file (str, optional): output path for an HTML index file (not written
167
179
  if None)
168
- html_output_options (dict, optional): HTML formatting options; see write_html_image_list
169
- for details
180
+ html_output_options (dict, optional): HTML formatting options; see write_html_image_list
181
+ for details. The most common option you may want to supply here is
182
+ 'maxFiguresPerHtmlFile'.
170
183
  preserve_path_structure (bool, optional): if False (default), writes images to unique
171
184
  names in a flat structure in the output folder; if True, preserves relative paths
172
185
  within the output folder
173
186
  parallelize_rendering (bool, optional): whether to use concurrent workers for rendering
174
- parallelize_rendering_n_cores (int, optional): number of concurrent workers to use
187
+ parallelize_rendering_n_cores (int, optional): number of concurrent workers to use
175
188
  (ignored if parallelize_rendering is False)
176
189
  parallelize_rendering_with_threads (bool, optional): determines whether we use
177
- threads (True) or processes (False) for parallelization (ignored if parallelize_rendering
190
+ threads (True) or processes (False) for parallelization (ignored if parallelize_rendering
178
191
  is False)
179
192
  box_sort_order (str, optional): sorting scheme for detection boxes, can be None, "confidence", or
180
193
  "reverse_confidence"
@@ -184,30 +197,29 @@ def visualize_detector_output(detector_output_path,
184
197
  Returns:
185
198
  list: list of paths to annotated images
186
199
  """
187
-
200
+
188
201
  assert os.path.exists(detector_output_path), \
189
202
  'Detector output file does not exist at {}'.format(detector_output_path)
190
203
 
191
- assert os.path.isdir(images_dir), \
192
- 'Image folder {} is not available'.format(images_dir)
204
+ if images_dir is not None:
205
+ assert os.path.isdir(images_dir), \
206
+ 'Image folder {} is not available'.format(images_dir)
193
207
 
194
208
  os.makedirs(out_dir, exist_ok=True)
195
209
 
196
210
 
197
211
  ##%% Load detector output
198
212
 
199
- with open(detector_output_path) as f:
200
- detector_output = json.load(f)
201
- assert 'images' in detector_output, (
202
- 'Detector output file should be a json with an "images" field.')
213
+ detector_output = load_md_or_speciesnet_file(detector_output_path)
214
+
203
215
  images = detector_output['images']
204
-
216
+
205
217
  if confidence_threshold is None:
206
218
  confidence_threshold = get_typical_confidence_threshold_from_results(detector_output)
207
-
208
- assert confidence_threshold >= 0 and confidence_threshold <= 1, (
209
- f'Confidence threshold {confidence_threshold} is invalid, must be in (0, 1).')
210
-
219
+
220
+ assert confidence_threshold >= 0 and confidence_threshold <= 1, \
221
+ f'Confidence threshold {confidence_threshold} is invalid, must be in (0, 1).'
222
+
211
223
  if 'detection_categories' in detector_output:
212
224
  detector_label_map = detector_output['detection_categories']
213
225
  else:
@@ -231,81 +243,91 @@ def visualize_detector_output(detector_output_path,
231
243
 
232
244
  print('Rendering detections above a confidence threshold of {}'.format(
233
245
  confidence_threshold))
234
-
246
+
235
247
  classification_label_map = None
236
-
248
+
237
249
  if 'classification_categories' in detector_output:
238
250
  classification_label_map = detector_output['classification_categories']
239
-
251
+
240
252
  rendering_results = []
241
-
253
+
242
254
  if parallelize_rendering:
243
-
255
+
244
256
  if parallelize_rendering_with_threads:
245
257
  worker_string = 'threads'
246
258
  else:
247
259
  worker_string = 'processes'
248
-
249
- if parallelize_rendering_n_cores is None:
250
- if parallelize_rendering_with_threads:
251
- pool = ThreadPool()
252
- else:
253
- pool = Pool()
254
- else:
255
- if parallelize_rendering_with_threads:
256
- pool = ThreadPool(parallelize_rendering_n_cores)
260
+
261
+ pool = None
262
+ try:
263
+ if parallelize_rendering_n_cores is None:
264
+ if parallelize_rendering_with_threads:
265
+ pool = ThreadPool()
266
+ else:
267
+ pool = Pool()
257
268
  else:
258
- pool = Pool(parallelize_rendering_n_cores)
259
- print('Rendering images with {} {}'.format(parallelize_rendering_n_cores,
260
- worker_string))
261
- rendering_results = list(tqdm(pool.imap(
262
- partial(_render_image,detector_label_map=detector_label_map,
263
- classification_label_map=classification_label_map,
264
- confidence_threshold=confidence_threshold,
265
- classification_confidence_threshold=classification_confidence_threshold,
266
- render_detections_only=render_detections_only,
267
- preserve_path_structure=preserve_path_structure,
268
- out_dir=out_dir,
269
- images_dir=images_dir,
270
- output_image_width=output_image_width,
271
- box_sort_order=box_sort_order,
272
- category_names_to_blur=category_names_to_blur),
273
- images), total=len(images)))
274
-
269
+ if parallelize_rendering_with_threads:
270
+ pool = ThreadPool(parallelize_rendering_n_cores)
271
+ else:
272
+ pool = Pool(parallelize_rendering_n_cores)
273
+ print('Rendering images with {} {}'.format(parallelize_rendering_n_cores,
274
+ worker_string))
275
+ rendering_results = list(tqdm(pool.imap(
276
+ partial(_render_image,detector_label_map=detector_label_map,
277
+ classification_label_map=classification_label_map,
278
+ confidence_threshold=confidence_threshold,
279
+ classification_confidence_threshold=classification_confidence_threshold,
280
+ render_detections_only=render_detections_only,
281
+ preserve_path_structure=preserve_path_structure,
282
+ out_dir=out_dir,
283
+ images_dir=images_dir,
284
+ output_image_width=output_image_width,
285
+ box_sort_order=box_sort_order,
286
+ category_names_to_blur=category_names_to_blur),
287
+ images), total=len(images)))
288
+ finally:
289
+ if pool is not None:
290
+ pool.close()
291
+ pool.join()
292
+ print("Pool closed and joined for detector output visualization")
293
+
275
294
  else:
276
-
295
+
277
296
  for entry in tqdm(images):
278
-
297
+
279
298
  rendering_result = _render_image(entry,detector_label_map,classification_label_map,
280
299
  confidence_threshold,classification_confidence_threshold,
281
300
  render_detections_only,preserve_path_structure,out_dir,
282
301
  images_dir,output_image_width,box_sort_order,
283
302
  category_names_to_blur=category_names_to_blur)
284
303
  rendering_results.append(rendering_result)
285
-
304
+
286
305
  # ...for each image
287
-
306
+
288
307
  failed_images = [r for r in rendering_results if r['failed_image']]
289
308
  missing_images = [r for r in rendering_results if r['missing_image']]
290
309
  skipped_images = [r for r in rendering_results if r['skipped_image']]
291
-
310
+
292
311
  print('Skipped {} failed images (of {})'.format(len(failed_images),len(images)))
293
312
  print('Skipped {} missing images (of {})'.format(len(missing_images),len(images)))
294
313
  print('Skipped {} below-threshold images (of {})'.format(len(skipped_images),len(images)))
295
-
314
+
296
315
  print(f'Rendered detection results to {out_dir}')
297
316
 
298
317
  annotated_image_paths = [r['annotated_image_path'] for r in rendering_results if \
299
318
  r['annotated_image_path'] is not None]
300
-
319
+
301
320
  if html_output_file is not None:
302
-
321
+
303
322
  html_dir = os.path.dirname(html_output_file)
304
-
323
+
305
324
  html_image_info = []
306
-
325
+
307
326
  for r in rendering_results:
308
327
  d = {}
328
+ if r['annotated_image_path'] is None:
329
+ assert r['failed_image'] or r['missing_image'] or r['skipped_image']
330
+ continue
309
331
  annotated_image_path_relative = os.path.relpath(r['annotated_image_path'],html_dir)
310
332
  d['filename'] = annotated_image_path_relative
311
333
  d['textStyle'] = \
@@ -313,17 +335,19 @@ def visualize_detector_output(detector_output_path,
313
335
  'text-align:left;margin-top:20;margin-bottom:5'
314
336
  d['title'] = '{} (max conf: {})'.format(r['file'],r['max_conf'])
315
337
  html_image_info.append(d)
316
-
338
+
317
339
  _ = write_html_image_list.write_html_image_list(html_output_file,html_image_info,
318
340
  options=html_output_options)
319
-
341
+
320
342
  return annotated_image_paths
321
343
 
344
+ # ...def visualize_detector_output(...)
345
+
322
346
 
323
347
  #%% Command-line driver
324
348
 
325
- def main():
326
-
349
+ def main(): # noqa
350
+
327
351
  parser = argparse.ArgumentParser(
328
352
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
329
353
  description='Annotate the bounding boxes predicted by a detector above '
@@ -336,53 +360,56 @@ def main():
336
360
  help='Path to directory where the annotated images will be saved. '
337
361
  'The directory will be created if it does not exist.')
338
362
  parser.add_argument(
339
- '-c', '--confidence', type=float, default=0.15,
363
+ '--confidence', type=float, default=0.15,
340
364
  help='Value between 0 and 1, indicating the confidence threshold '
341
365
  'above which to visualize bounding boxes')
342
366
  parser.add_argument(
343
- '-i', '--images_dir', type=str, default=None,
367
+ '--images_dir', type=str, default=None,
344
368
  help='Path to a local directory where images are stored. This '
345
369
  'serves as the root directory for image paths in '
346
- 'detector_output_path.')
370
+ 'detector_output_path. Omit if image paths are absolute.')
347
371
  parser.add_argument(
348
- '-n', '--sample', type=int, default=-1,
372
+ '--sample', type=int, default=-1,
349
373
  help='Number of images to be annotated and rendered. Set to -1 '
350
374
  '(default) to annotate all images in the detector output file. '
351
375
  'There may be fewer images if some are not found in images_dir.')
352
376
  parser.add_argument(
353
- '-w', '--output_image_width', type=int, default=700,
377
+ '--output_image_width', type=int, default=700,
354
378
  help='Integer, desired width in pixels of the output annotated images. '
355
379
  'Use -1 to not resize. Default: 700.')
356
380
  parser.add_argument(
357
- '-r', '--random_seed', type=int, default=None,
381
+ '--random_seed', type=int, default=None,
358
382
  help='Integer, for deterministic order of image sampling')
359
383
  parser.add_argument(
360
- '-html', '--html_output_file', type=str, default=None,
384
+ '--html_output_file', type=str, default=None,
361
385
  help='Filename to which we should write an HTML image index (off by default)')
362
386
  parser.add_argument(
363
387
  '--open_html_output_file', action='store_true',
364
388
  help='Open the .html output file when done')
365
389
  parser.add_argument(
366
- '-do', '--detections_only', action='store_true',
390
+ '--detections_only', action='store_true',
367
391
  help='Only render images with above-threshold detections (by default, '
368
392
  'both empty and non-empty images are rendered).')
369
393
  parser.add_argument(
370
- '-pps', '--preserve_path_structure', action='store_true',
394
+ '--preserve_path_structure', action='store_true',
371
395
  help='Preserve relative image paths (otherwise flattens and assigns unique file names)')
372
396
  parser.add_argument(
373
397
  '--category_names_to_blur', default=None, type=str,
374
398
  help='Comma-separated list of category names to blur (or a single category name, typically "person")')
399
+ parser.add_argument(
400
+ '--classification_confidence', type=float, default=0.1,
401
+ help='If classification results are present, render results above this threshold')
375
402
 
376
403
  if len(sys.argv[1:]) == 0:
377
404
  parser.print_help()
378
405
  parser.exit()
379
406
 
380
407
  args = parser.parse_args()
381
-
408
+
382
409
  category_names_to_blur = args.category_names_to_blur
383
410
  if category_names_to_blur is not None:
384
411
  category_names_to_blur = category_names_to_blur.split(',')
385
-
412
+
386
413
  visualize_detector_output(
387
414
  detector_output_path=args.detector_output_path,
388
415
  out_dir=args.out_dir,
@@ -392,6 +419,7 @@ def main():
392
419
  output_image_width=args.output_image_width,
393
420
  random_seed=args.random_seed,
394
421
  render_detections_only=args.detections_only,
422
+ classification_confidence_threshold=args.classification_confidence,
395
423
  preserve_path_structure=args.preserve_path_structure,
396
424
  html_output_file=args.html_output_file,
397
425
  category_names_to_blur=category_names_to_blur)
@@ -407,12 +435,12 @@ if __name__ == '__main__':
407
435
  #%% Interactive driver
408
436
 
409
437
  if False:
410
-
438
+
411
439
  pass
412
440
 
413
441
  #%%
414
-
415
- detector_output_path = os.path.expanduser('~/postprocessing/bellevue-camera-traps/bellevue-camera-traps-2023-12-05-v5a.0.0/combined_api_outputs/bellevue-camera-traps-2023-12-05-v5a.0.0_detections.json')
442
+
443
+ detector_output_path = os.path.expanduser('detections.json')
416
444
  out_dir = r'g:\temp\preview'
417
445
  images_dir = r'g:\camera_traps\camera_trap_images'
418
446
  confidence_threshold = 0.15
@@ -443,6 +471,6 @@ if False:
443
471
  parallelize_rendering,
444
472
  parallelize_rendering_n_cores,
445
473
  parallelize_rendering_with_threads)
446
-
474
+
447
475
  from megadetector.utils.path_utils import open_file
448
476
  open_file(html_output_file)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: megadetector
3
- Version: 5.0.27
3
+ Version: 5.0.29
4
4
  Summary: MegaDetector is an AI model that helps conservation folks spend less time doing boring things with camera trap images.
5
5
  Author-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
6
6
  Maintainer-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
@@ -34,7 +34,7 @@ Requires-Python: <3.14,>=3.9
34
34
  Description-Content-Type: text/markdown
35
35
  License-File: LICENSE
36
36
  Requires-Dist: mkl==2024.0; sys_platform != "darwin"
37
- Requires-Dist: numpy<2.0,>=1.26.4
37
+ Requires-Dist: numpy>=1.26.4
38
38
  Requires-Dist: Pillow>=9.5
39
39
  Requires-Dist: tqdm>=4.64.0
40
40
  Requires-Dist: jsonpickle>=3.0.2
@@ -47,10 +47,13 @@ Requires-Dist: scikit-learn>=1.3.1
47
47
  Requires-Dist: pandas>=2.1.1
48
48
  Requires-Dist: python-dateutil
49
49
  Requires-Dist: send2trash
50
+ Requires-Dist: python-dateutil
51
+ Requires-Dist: clipboard
50
52
  Requires-Dist: dill
53
+ Requires-Dist: ruff
54
+ Requires-Dist: pytest
51
55
  Requires-Dist: ultralytics-yolov5==0.1.1
52
56
  Requires-Dist: yolov9pip==0.0.4
53
- Requires-Dist: python-dateutil
54
57
  Dynamic: license-file
55
58
 
56
59
  # MegaDetector