megadetector 5.0.11__py3-none-any.whl → 5.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 (203) hide show
  1. megadetector/api/__init__.py +0 -0
  2. megadetector/api/batch_processing/__init__.py +0 -0
  3. megadetector/api/batch_processing/api_core/__init__.py +0 -0
  4. megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
  5. megadetector/api/batch_processing/api_core/batch_service/score.py +439 -0
  6. megadetector/api/batch_processing/api_core/server.py +294 -0
  7. megadetector/api/batch_processing/api_core/server_api_config.py +97 -0
  8. megadetector/api/batch_processing/api_core/server_app_config.py +55 -0
  9. megadetector/api/batch_processing/api_core/server_batch_job_manager.py +220 -0
  10. megadetector/api/batch_processing/api_core/server_job_status_table.py +149 -0
  11. megadetector/api/batch_processing/api_core/server_orchestration.py +360 -0
  12. megadetector/api/batch_processing/api_core/server_utils.py +88 -0
  13. megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
  14. megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +46 -0
  15. megadetector/api/batch_processing/api_support/__init__.py +0 -0
  16. megadetector/api/batch_processing/api_support/summarize_daily_activity.py +152 -0
  17. megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
  18. megadetector/api/batch_processing/integration/digiKam/setup.py +6 -0
  19. megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +465 -0
  20. megadetector/api/batch_processing/integration/eMammal/test_scripts/config_template.py +5 -0
  21. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +125 -0
  22. megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +55 -0
  23. megadetector/api/synchronous/__init__.py +0 -0
  24. megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  25. megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +152 -0
  26. megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +263 -0
  27. megadetector/api/synchronous/api_core/animal_detection_api/config.py +35 -0
  28. megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
  29. megadetector/api/synchronous/api_core/tests/load_test.py +110 -0
  30. megadetector/classification/__init__.py +0 -0
  31. megadetector/classification/aggregate_classifier_probs.py +108 -0
  32. megadetector/classification/analyze_failed_images.py +227 -0
  33. megadetector/classification/cache_batchapi_outputs.py +198 -0
  34. megadetector/classification/create_classification_dataset.py +627 -0
  35. megadetector/classification/crop_detections.py +516 -0
  36. megadetector/classification/csv_to_json.py +226 -0
  37. megadetector/classification/detect_and_crop.py +855 -0
  38. megadetector/classification/efficientnet/__init__.py +9 -0
  39. megadetector/classification/efficientnet/model.py +415 -0
  40. megadetector/classification/efficientnet/utils.py +607 -0
  41. megadetector/classification/evaluate_model.py +520 -0
  42. megadetector/classification/identify_mislabeled_candidates.py +152 -0
  43. megadetector/classification/json_to_azcopy_list.py +63 -0
  44. megadetector/classification/json_validator.py +699 -0
  45. megadetector/classification/map_classification_categories.py +276 -0
  46. megadetector/classification/merge_classification_detection_output.py +506 -0
  47. megadetector/classification/prepare_classification_script.py +194 -0
  48. megadetector/classification/prepare_classification_script_mc.py +228 -0
  49. megadetector/classification/run_classifier.py +287 -0
  50. megadetector/classification/save_mislabeled.py +110 -0
  51. megadetector/classification/train_classifier.py +827 -0
  52. megadetector/classification/train_classifier_tf.py +725 -0
  53. megadetector/classification/train_utils.py +323 -0
  54. megadetector/data_management/__init__.py +0 -0
  55. megadetector/data_management/annotations/__init__.py +0 -0
  56. megadetector/data_management/annotations/annotation_constants.py +34 -0
  57. megadetector/data_management/camtrap_dp_to_coco.py +237 -0
  58. megadetector/data_management/cct_json_utils.py +404 -0
  59. megadetector/data_management/cct_to_md.py +176 -0
  60. megadetector/data_management/cct_to_wi.py +289 -0
  61. megadetector/data_management/coco_to_labelme.py +283 -0
  62. megadetector/data_management/coco_to_yolo.py +662 -0
  63. megadetector/data_management/databases/__init__.py +0 -0
  64. megadetector/data_management/databases/add_width_and_height_to_db.py +33 -0
  65. megadetector/data_management/databases/combine_coco_camera_traps_files.py +206 -0
  66. megadetector/data_management/databases/integrity_check_json_db.py +493 -0
  67. megadetector/data_management/databases/subset_json_db.py +115 -0
  68. megadetector/data_management/generate_crops_from_cct.py +149 -0
  69. megadetector/data_management/get_image_sizes.py +189 -0
  70. megadetector/data_management/importers/add_nacti_sizes.py +52 -0
  71. megadetector/data_management/importers/add_timestamps_to_icct.py +79 -0
  72. megadetector/data_management/importers/animl_results_to_md_results.py +158 -0
  73. megadetector/data_management/importers/auckland_doc_test_to_json.py +373 -0
  74. megadetector/data_management/importers/auckland_doc_to_json.py +201 -0
  75. megadetector/data_management/importers/awc_to_json.py +191 -0
  76. megadetector/data_management/importers/bellevue_to_json.py +273 -0
  77. megadetector/data_management/importers/cacophony-thermal-importer.py +793 -0
  78. megadetector/data_management/importers/carrizo_shrubfree_2018.py +269 -0
  79. megadetector/data_management/importers/carrizo_trail_cam_2017.py +289 -0
  80. megadetector/data_management/importers/cct_field_adjustments.py +58 -0
  81. megadetector/data_management/importers/channel_islands_to_cct.py +913 -0
  82. megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +180 -0
  83. megadetector/data_management/importers/eMammal/eMammal_helpers.py +249 -0
  84. megadetector/data_management/importers/eMammal/make_eMammal_json.py +223 -0
  85. megadetector/data_management/importers/ena24_to_json.py +276 -0
  86. megadetector/data_management/importers/filenames_to_json.py +386 -0
  87. megadetector/data_management/importers/helena_to_cct.py +283 -0
  88. megadetector/data_management/importers/idaho-camera-traps.py +1407 -0
  89. megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +294 -0
  90. megadetector/data_management/importers/jb_csv_to_json.py +150 -0
  91. megadetector/data_management/importers/mcgill_to_json.py +250 -0
  92. megadetector/data_management/importers/missouri_to_json.py +490 -0
  93. megadetector/data_management/importers/nacti_fieldname_adjustments.py +79 -0
  94. megadetector/data_management/importers/noaa_seals_2019.py +181 -0
  95. megadetector/data_management/importers/pc_to_json.py +365 -0
  96. megadetector/data_management/importers/plot_wni_giraffes.py +123 -0
  97. megadetector/data_management/importers/prepare-noaa-fish-data-for-lila.py +359 -0
  98. megadetector/data_management/importers/prepare_zsl_imerit.py +131 -0
  99. megadetector/data_management/importers/rspb_to_json.py +356 -0
  100. megadetector/data_management/importers/save_the_elephants_survey_A.py +320 -0
  101. megadetector/data_management/importers/save_the_elephants_survey_B.py +329 -0
  102. megadetector/data_management/importers/snapshot_safari_importer.py +758 -0
  103. megadetector/data_management/importers/snapshot_safari_importer_reprise.py +665 -0
  104. megadetector/data_management/importers/snapshot_serengeti_lila.py +1067 -0
  105. megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +150 -0
  106. megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +153 -0
  107. megadetector/data_management/importers/sulross_get_exif.py +65 -0
  108. megadetector/data_management/importers/timelapse_csv_set_to_json.py +490 -0
  109. megadetector/data_management/importers/ubc_to_json.py +399 -0
  110. megadetector/data_management/importers/umn_to_json.py +507 -0
  111. megadetector/data_management/importers/wellington_to_json.py +263 -0
  112. megadetector/data_management/importers/wi_to_json.py +442 -0
  113. megadetector/data_management/importers/zamba_results_to_md_results.py +181 -0
  114. megadetector/data_management/labelme_to_coco.py +547 -0
  115. megadetector/data_management/labelme_to_yolo.py +272 -0
  116. megadetector/data_management/lila/__init__.py +0 -0
  117. megadetector/data_management/lila/add_locations_to_island_camera_traps.py +97 -0
  118. megadetector/data_management/lila/add_locations_to_nacti.py +147 -0
  119. megadetector/data_management/lila/create_lila_blank_set.py +558 -0
  120. megadetector/data_management/lila/create_lila_test_set.py +152 -0
  121. megadetector/data_management/lila/create_links_to_md_results_files.py +106 -0
  122. megadetector/data_management/lila/download_lila_subset.py +178 -0
  123. megadetector/data_management/lila/generate_lila_per_image_labels.py +516 -0
  124. megadetector/data_management/lila/get_lila_annotation_counts.py +170 -0
  125. megadetector/data_management/lila/get_lila_image_counts.py +112 -0
  126. megadetector/data_management/lila/lila_common.py +300 -0
  127. megadetector/data_management/lila/test_lila_metadata_urls.py +132 -0
  128. megadetector/data_management/ocr_tools.py +870 -0
  129. megadetector/data_management/read_exif.py +809 -0
  130. megadetector/data_management/remap_coco_categories.py +84 -0
  131. megadetector/data_management/remove_exif.py +66 -0
  132. megadetector/data_management/rename_images.py +187 -0
  133. megadetector/data_management/resize_coco_dataset.py +189 -0
  134. megadetector/data_management/wi_download_csv_to_coco.py +247 -0
  135. megadetector/data_management/yolo_output_to_md_output.py +446 -0
  136. megadetector/data_management/yolo_to_coco.py +676 -0
  137. megadetector/detection/__init__.py +0 -0
  138. megadetector/detection/detector_training/__init__.py +0 -0
  139. megadetector/detection/detector_training/model_main_tf2.py +114 -0
  140. megadetector/detection/process_video.py +846 -0
  141. megadetector/detection/pytorch_detector.py +355 -0
  142. megadetector/detection/run_detector.py +779 -0
  143. megadetector/detection/run_detector_batch.py +1219 -0
  144. megadetector/detection/run_inference_with_yolov5_val.py +1087 -0
  145. megadetector/detection/run_tiled_inference.py +934 -0
  146. megadetector/detection/tf_detector.py +192 -0
  147. megadetector/detection/video_utils.py +698 -0
  148. megadetector/postprocessing/__init__.py +0 -0
  149. megadetector/postprocessing/add_max_conf.py +64 -0
  150. megadetector/postprocessing/categorize_detections_by_size.py +165 -0
  151. megadetector/postprocessing/classification_postprocessing.py +716 -0
  152. megadetector/postprocessing/combine_api_outputs.py +249 -0
  153. megadetector/postprocessing/compare_batch_results.py +966 -0
  154. megadetector/postprocessing/convert_output_format.py +396 -0
  155. megadetector/postprocessing/load_api_results.py +195 -0
  156. megadetector/postprocessing/md_to_coco.py +310 -0
  157. megadetector/postprocessing/md_to_labelme.py +330 -0
  158. megadetector/postprocessing/merge_detections.py +412 -0
  159. megadetector/postprocessing/postprocess_batch_results.py +1908 -0
  160. megadetector/postprocessing/remap_detection_categories.py +170 -0
  161. megadetector/postprocessing/render_detection_confusion_matrix.py +660 -0
  162. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +211 -0
  163. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +83 -0
  164. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1635 -0
  165. megadetector/postprocessing/separate_detections_into_folders.py +730 -0
  166. megadetector/postprocessing/subset_json_detector_output.py +700 -0
  167. megadetector/postprocessing/top_folders_to_bottom.py +223 -0
  168. megadetector/taxonomy_mapping/__init__.py +0 -0
  169. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +491 -0
  170. megadetector/taxonomy_mapping/map_new_lila_datasets.py +150 -0
  171. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -0
  172. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +588 -0
  173. megadetector/taxonomy_mapping/retrieve_sample_image.py +71 -0
  174. megadetector/taxonomy_mapping/simple_image_download.py +219 -0
  175. megadetector/taxonomy_mapping/species_lookup.py +834 -0
  176. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +159 -0
  177. megadetector/taxonomy_mapping/taxonomy_graph.py +346 -0
  178. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +83 -0
  179. megadetector/utils/__init__.py +0 -0
  180. megadetector/utils/azure_utils.py +178 -0
  181. megadetector/utils/ct_utils.py +613 -0
  182. megadetector/utils/directory_listing.py +246 -0
  183. megadetector/utils/md_tests.py +1164 -0
  184. megadetector/utils/path_utils.py +1045 -0
  185. megadetector/utils/process_utils.py +160 -0
  186. megadetector/utils/sas_blob_utils.py +509 -0
  187. megadetector/utils/split_locations_into_train_val.py +228 -0
  188. megadetector/utils/string_utils.py +92 -0
  189. megadetector/utils/url_utils.py +323 -0
  190. megadetector/utils/write_html_image_list.py +225 -0
  191. megadetector/visualization/__init__.py +0 -0
  192. megadetector/visualization/plot_utils.py +293 -0
  193. megadetector/visualization/render_images_with_thumbnails.py +275 -0
  194. megadetector/visualization/visualization_utils.py +1536 -0
  195. megadetector/visualization/visualize_db.py +552 -0
  196. megadetector/visualization/visualize_detector_output.py +405 -0
  197. {megadetector-5.0.11.dist-info → megadetector-5.0.13.dist-info}/LICENSE +0 -0
  198. {megadetector-5.0.11.dist-info → megadetector-5.0.13.dist-info}/METADATA +2 -2
  199. megadetector-5.0.13.dist-info/RECORD +201 -0
  200. megadetector-5.0.13.dist-info/top_level.txt +1 -0
  201. megadetector-5.0.11.dist-info/RECORD +0 -5
  202. megadetector-5.0.11.dist-info/top_level.txt +0 -1
  203. {megadetector-5.0.11.dist-info → megadetector-5.0.13.dist-info}/WHEEL +0 -0
@@ -0,0 +1,613 @@
1
+ """
2
+
3
+ ct_utils.py
4
+
5
+ Numeric/geometry/array utility functions.
6
+
7
+ """
8
+
9
+ #%% Imports and constants
10
+
11
+ import inspect
12
+ import json
13
+ import math
14
+ import os
15
+
16
+ import jsonpickle
17
+ import numpy as np
18
+
19
+ # List of file extensions we'll consider images; comparisons will be case-insensitive
20
+ # (i.e., no need to include both .jpg and .JPG on this list).
21
+ image_extensions = ['.jpg', '.jpeg', '.gif', '.png']
22
+
23
+
24
+ #%% Functions
25
+
26
+ def truncate_float_array(xs, precision=3):
27
+ """
28
+ Vectorized version of truncate_float(...), truncates the fractional portion of each
29
+ floating-point value to a specific number of floating-point digits.
30
+
31
+ Args:
32
+ xs (list): list of floats to truncate
33
+ precision (int, optional): the number of significant digits to preserve, should be >= 1
34
+
35
+ Returns:
36
+ list: list of truncated floats
37
+ """
38
+
39
+ return [truncate_float(x, precision=precision) for x in xs]
40
+
41
+
42
+ def truncate_float(x, precision=3):
43
+ """
44
+ Truncates the fractional portion of a floating-point value to a specific number of
45
+ floating-point digits.
46
+
47
+ For example:
48
+
49
+ truncate_float(0.0003214884) --> 0.000321
50
+ truncate_float(1.0003214884) --> 1.000321
51
+
52
+ This function is primarily used to achieve a certain float representation
53
+ before exporting to JSON.
54
+
55
+ Args:
56
+ x (float): scalar to truncate
57
+ precision (int, optional): the number of significant digits to preserve, should be >= 1
58
+
59
+ Returns:
60
+ float: truncated version of [x]
61
+ """
62
+
63
+ assert precision > 0
64
+
65
+ if np.isclose(x, 0):
66
+
67
+ return 0
68
+
69
+ elif (x > 1):
70
+
71
+ fractional_component = x - 1.0
72
+ return 1 + truncate_float(fractional_component)
73
+
74
+ else:
75
+
76
+ # Determine the factor, which shifts the decimal point of x
77
+ # just behind the last significant digit.
78
+ factor = math.pow(10, precision - 1 - math.floor(math.log10(abs(x))))
79
+
80
+ # Shift decimal point by multiplication with factor, flooring, and
81
+ # division by factor.
82
+ return math.floor(x * factor)/factor
83
+
84
+
85
+ def args_to_object(args, obj):
86
+ """
87
+ Copies all fields from a Namespace (typically the output from parse_args) to an
88
+ object. Skips fields starting with _. Does not check existence in the target
89
+ object.
90
+
91
+ Args:
92
+ args (argparse.Namespace): the namespace to convert to an object
93
+ obj (object): object whose whose attributes will be updated
94
+
95
+ Returns:
96
+ object: the modified object (modified in place, but also returned)
97
+ """
98
+
99
+ for n, v in inspect.getmembers(args):
100
+ if not n.startswith('_'):
101
+ setattr(obj, n, v)
102
+
103
+ return obj
104
+
105
+
106
+ def pretty_print_object(obj, b_print=True):
107
+ """
108
+ Converts an arbitrary object to .json, optionally printing the .json representation.
109
+
110
+ Args:
111
+ obj (object): object to print
112
+ b_print (bool, optional): whether to print the object
113
+
114
+ Returns:
115
+ str: .json reprepresentation of [obj]
116
+ """
117
+
118
+ # _ = pretty_print_object(obj)
119
+
120
+ # TODO: it's sloppy that I'm making a module-wide change here, consider at least
121
+ # recording these operations and re-setting them at the end of this function.
122
+ jsonpickle.set_encoder_options('json', sort_keys=True, indent=2)
123
+ a = jsonpickle.encode(obj)
124
+ s = '{}'.format(a)
125
+ if b_print:
126
+ print(s)
127
+ return s
128
+
129
+
130
+ def is_list_sorted(L, reverse=False):
131
+ """
132
+ Returns True if the list L appears to be sorted, otherwise False.
133
+
134
+ Calling is_list_sorted(L,reverse=True) is the same as calling
135
+ is_list_sorted(L.reverse(),reverse=False).
136
+
137
+ Args:
138
+ L (list): list to evaluate
139
+ reverse (bool, optional): whether to reverse the list before evaluating sort status
140
+
141
+ Returns:
142
+ bool: True if the list L appears to be sorted, otherwise False
143
+ """
144
+
145
+ if reverse:
146
+ return all(L[i] >= L[i + 1] for i in range(len(L)-1))
147
+ else:
148
+ return all(L[i] <= L[i + 1] for i in range(len(L)-1))
149
+
150
+
151
+ def write_json(path, content, indent=1):
152
+ """
153
+ Standardized wrapper for json.dump().
154
+
155
+ Args:
156
+ path (str): filename to write to
157
+ content (object): object to dump
158
+ indent (int, optional): indentation depth passed to json.dump
159
+ """
160
+
161
+ with open(path, 'w') as f:
162
+ json.dump(content, f, indent=indent)
163
+
164
+
165
+ def convert_yolo_to_xywh(yolo_box):
166
+ """
167
+ Converts a YOLO format bounding box to [x_min, y_min, width_of_box, height_of_box].
168
+
169
+ Args:
170
+ yolo_box (list): bounding box of format [x_center, y_center, width_of_box, height_of_box]
171
+
172
+ Returns:
173
+ list: bbox with coordinates represented as [x_min, y_min, width_of_box, height_of_box]
174
+ """
175
+
176
+ x_center, y_center, width_of_box, height_of_box = yolo_box
177
+ x_min = x_center - width_of_box / 2.0
178
+ y_min = y_center - height_of_box / 2.0
179
+ return [x_min, y_min, width_of_box, height_of_box]
180
+
181
+
182
+ def convert_xywh_to_tf(api_box):
183
+ """
184
+ Converts an xywh bounding box (the format used in MD output) to the [y_min, x_min, y_max, x_max]
185
+ format that the TensorFlow Object Detection API uses.
186
+
187
+ Args:
188
+ api_box: bbox output by the batch processing API [x_min, y_min, width_of_box, height_of_box]
189
+
190
+ Returns:
191
+ list: bbox with coordinates represented as [y_min, x_min, y_max, x_max]
192
+ """
193
+
194
+ x_min, y_min, width_of_box, height_of_box = api_box
195
+ x_max = x_min + width_of_box
196
+ y_max = y_min + height_of_box
197
+ return [y_min, x_min, y_max, x_max]
198
+
199
+
200
+ def convert_xywh_to_xyxy(api_bbox):
201
+ """
202
+ Converts an xywh bounding box (the MD output format) to an xyxy bounding box.
203
+
204
+ Args:
205
+ api_bbox (list): bbox formatted as [x_min, y_min, width_of_box, height_of_box]
206
+
207
+ Returns:
208
+ list: bbox formatted as [x_min, y_min, x_max, y_max]
209
+ """
210
+
211
+ x_min, y_min, width_of_box, height_of_box = api_bbox
212
+ x_max, y_max = x_min + width_of_box, y_min + height_of_box
213
+ return [x_min, y_min, x_max, y_max]
214
+
215
+
216
+ def get_iou(bb1, bb2):
217
+ """
218
+ Calculates the intersection over union (IoU) of two bounding boxes.
219
+
220
+ Adapted from:
221
+
222
+ https://stackoverflow.com/questions/25349178/calculating-percentage-of-bounding-box-overlap-for-image-detector-evaluation
223
+
224
+ Args:
225
+ bb1 (list): [x_min, y_min, width_of_box, height_of_box]
226
+ bb2 (list): [x_min, y_min, width_of_box, height_of_box]
227
+
228
+ Returns:
229
+ float: intersection_over_union, a float in [0, 1]
230
+ """
231
+
232
+ bb1 = convert_xywh_to_xyxy(bb1)
233
+ bb2 = convert_xywh_to_xyxy(bb2)
234
+
235
+ assert bb1[0] < bb1[2], 'Malformed bounding box (x2 >= x1)'
236
+ assert bb1[1] < bb1[3], 'Malformed bounding box (y2 >= y1)'
237
+
238
+ assert bb2[0] < bb2[2], 'Malformed bounding box (x2 >= x1)'
239
+ assert bb2[1] < bb2[3], 'Malformed bounding box (y2 >= y1)'
240
+
241
+ # Determine the coordinates of the intersection rectangle
242
+ x_left = max(bb1[0], bb2[0])
243
+ y_top = max(bb1[1], bb2[1])
244
+ x_right = min(bb1[2], bb2[2])
245
+ y_bottom = min(bb1[3], bb2[3])
246
+
247
+ if x_right < x_left or y_bottom < y_top:
248
+ return 0.0
249
+
250
+ # The intersection of two axis-aligned bounding boxes is always an
251
+ # axis-aligned bounding box
252
+ intersection_area = (x_right - x_left) * (y_bottom - y_top)
253
+
254
+ # Compute the area of both AABBs
255
+ bb1_area = (bb1[2] - bb1[0]) * (bb1[3] - bb1[1])
256
+ bb2_area = (bb2[2] - bb2[0]) * (bb2[3] - bb2[1])
257
+
258
+ # Compute the intersection over union by taking the intersection
259
+ # area and dividing it by the sum of prediction + ground-truth
260
+ # areas - the intersection area.
261
+ iou = intersection_area / float(bb1_area + bb2_area - intersection_area)
262
+ assert iou >= 0.0, 'Illegal IOU < 0'
263
+ assert iou <= 1.0, 'Illegal IOU > 1'
264
+ return iou
265
+
266
+
267
+ def _get_max_conf_from_detections(detections):
268
+ """
269
+ Internal function used by get_max_conf(); don't call this directly.
270
+ """
271
+
272
+ max_conf = 0.0
273
+ if detections is not None and len(detections) > 0:
274
+ confidences = [det['conf'] for det in detections]
275
+ max_conf = max(confidences)
276
+ return max_conf
277
+
278
+
279
+ def get_max_conf(im):
280
+ """
281
+ Given an image dict in the MD output format, computes the maximum detection confidence for any
282
+ class. Returns 0.0 (rather than None) if there was a failure or 'detections' isn't present.
283
+
284
+ Args:
285
+ im (dict): image dictionary in the MD output format (with a 'detections' field)
286
+
287
+ Returns:
288
+ float: the maximum detection confidence across all classes
289
+ """
290
+
291
+ max_conf = 0.0
292
+ if 'detections' in im and im['detections'] is not None and len(im['detections']) > 0:
293
+ max_conf = _get_max_conf_from_detections(im['detections'])
294
+ return max_conf
295
+
296
+
297
+ def point_dist(p1,p2):
298
+ """
299
+ Computes the distance between two points, represented as length-two tuples.
300
+
301
+ Args:
302
+ p1: point, formatted as (x,y)
303
+ p2: point, formatted as (x,y)
304
+
305
+ Returns:
306
+ float: the Euclidean distance between p1 and p2
307
+ """
308
+
309
+ return math.sqrt( ((p1[0]-p2[0])**2) + ((p1[1]-p2[1])**2) )
310
+
311
+
312
+ def rect_distance(r1, r2, format='x0y0x1y1'):
313
+ """
314
+ Computes the minimum distance between two axis-aligned rectangles, each represented as
315
+ (x0,y0,x1,y1) by default.
316
+
317
+ Can also specify "format" as x0y0wh for MD-style bbox formatting (x0,y0,w,h).
318
+
319
+ Args:
320
+ r1: rectangle, formatted as (x0,y0,x1,y1) or (x0,y0,xy,y1)
321
+ r2: rectangle, formatted as (x0,y0,x1,y1) or (x0,y0,xy,y1)
322
+ format (str, optional): whether the boxes are formatted as 'x0y0x1y1' (default) or 'x0y0wh'
323
+
324
+ Returns:
325
+ float: the minimum distance between r1 and r2
326
+ """
327
+
328
+ assert format in ('x0y0x1y1','x0y0wh'), 'Illegal rectangle format {}'.format(format)
329
+
330
+ if format == 'x0y0wh':
331
+ # Convert to x0y0x1y1 without modifying the original rectangles
332
+ r1 = [r1[0],r1[1],r1[0]+r1[2],r1[1]+r1[3]]
333
+ r2 = [r2[0],r2[1],r2[0]+r2[2],r2[1]+r2[3]]
334
+
335
+ # https://stackoverflow.com/a/26178015
336
+ x1, y1, x1b, y1b = r1
337
+ x2, y2, x2b, y2b = r2
338
+ left = x2b < x1
339
+ right = x1b < x2
340
+ bottom = y2b < y1
341
+ top = y1b < y2
342
+ if top and left:
343
+ return point_dist((x1, y1b), (x2b, y2))
344
+ elif left and bottom:
345
+ return point_dist((x1, y1), (x2b, y2b))
346
+ elif bottom and right:
347
+ return point_dist((x1b, y1), (x2, y2b))
348
+ elif right and top:
349
+ return point_dist((x1b, y1b), (x2, y2))
350
+ elif left:
351
+ return x1 - x2b
352
+ elif right:
353
+ return x2 - x1b
354
+ elif bottom:
355
+ return y1 - y2b
356
+ elif top:
357
+ return y2 - y1b
358
+ else:
359
+ return 0.0
360
+
361
+
362
+ def split_list_into_fixed_size_chunks(L,n):
363
+ """
364
+ Split the list or tuple L into chunks of size n (allowing at most one chunk with size
365
+ less than N, i.e. len(L) does not have to be a multiple of n).
366
+
367
+ Args:
368
+ L (list): list to split into chunks
369
+ n (int): preferred chunk size
370
+
371
+ Returns:
372
+ list: list of chunks, where each chunk is a list of length n or n-1
373
+ """
374
+
375
+ return [L[i * n:(i + 1) * n] for i in range((len(L) + n - 1) // n )]
376
+
377
+
378
+ def split_list_into_n_chunks(L, n, chunk_strategy='greedy'):
379
+ """
380
+ Splits the list or tuple L into n equally-sized chunks (some chunks may be one
381
+ element smaller than others, i.e. len(L) does not have to be a multiple of n).
382
+
383
+ chunk_strategy can be "greedy" (default, if there are k samples per chunk, the first
384
+ k go into the first chunk) or "balanced" (alternate between chunks when pulling
385
+ items from the list).
386
+
387
+ Args:
388
+ L (list): list to split into chunks
389
+ n (int): number of chunks
390
+ chunk_strategy (str, optiopnal): "greedy" or "balanced"; see above
391
+
392
+ Returns:
393
+ list: list of chunks, each of which is a list
394
+ """
395
+
396
+ if chunk_strategy == 'greedy':
397
+ k, m = divmod(len(L), n)
398
+ return list(L[i*k+min(i, m):(i+1)*k+min(i+1, m)] for i in range(n))
399
+ elif chunk_strategy == 'balanced':
400
+ chunks = [ [] for _ in range(n) ]
401
+ for i_item,item in enumerate(L):
402
+ i_chunk = i_item % n
403
+ chunks[i_chunk].append(item)
404
+ return chunks
405
+ else:
406
+ raise ValueError('Invalid chunk strategy: {}'.format(chunk_strategy))
407
+
408
+
409
+ def sort_dictionary_by_key(d,reverse=False):
410
+ """
411
+ Sorts the dictionary [d] by key.
412
+
413
+ Args:
414
+ d (dict): dictionary to sort
415
+ reverse (bool, optional): whether to sort in reverse (descending) order
416
+
417
+ Returns:
418
+ dict: sorted copy of [d]
419
+ """
420
+
421
+ d = dict(sorted(d.items(),reverse=reverse))
422
+ return d
423
+
424
+
425
+ def sort_dictionary_by_value(d,sort_values=None,reverse=False):
426
+ """
427
+ Sorts the dictionary [d] by value. If sort_values is None, uses d.values(),
428
+ otherwise uses the dictionary sort_values as the sorting criterion.
429
+
430
+ Args:
431
+ d (dict): dictionary to sort
432
+ sort_values (dict, optional): dictionary mapping keys in [d] to sort values (defaults
433
+ to None, uses [d] itself for sorting)
434
+ reverse (bool, optional): whether to sort in reverse (descending) order
435
+
436
+ Returns:
437
+ dict: sorted copy of [d]
438
+ """
439
+
440
+ if sort_values is None:
441
+ d = {k: v for k, v in sorted(d.items(), key=lambda item: item[1], reverse=reverse)}
442
+ else:
443
+ d = {k: v for k, v in sorted(d.items(), key=lambda item: sort_values[item[0]], reverse=reverse)}
444
+ return d
445
+
446
+
447
+ def invert_dictionary(d):
448
+ """
449
+ Creates a new dictionary that maps d.values() to d.keys(). Does not check
450
+ uniqueness.
451
+
452
+ Args:
453
+ d (dict): dictionary to invert
454
+
455
+ Returns:
456
+ dict: inverted copy of [d]
457
+ """
458
+
459
+ return {v: k for k, v in d.items()}
460
+
461
+
462
+ def image_file_to_camera_folder(image_fn):
463
+ r"""
464
+ Removes common overflow folders (e.g. RECNX101, RECNX102) from paths, i.e. turn:
465
+
466
+ a\b\c\RECNX101\image001.jpg
467
+
468
+ ...into:
469
+
470
+ a\b\c
471
+
472
+ Returns the same thing as os.dirname() (i.e., just the folder name) if no overflow folders are
473
+ present.
474
+
475
+ Always converts backslashes to slashes.
476
+
477
+ Args:
478
+ image_fn (str): the image filename from which we should remove overflow folders
479
+
480
+ Returns:
481
+ str: a version of [image_fn] from which camera overflow folders have been removed
482
+ """
483
+
484
+ import re
485
+
486
+ # 100RECNX is the overflow folder style for Reconyx cameras
487
+ # 100EK113 is (for some reason) the overflow folder style for Bushnell cameras
488
+ # 100_BTCF is the overflow folder style for Browning cameras
489
+ # 100MEDIA is the overflow folder style used on a number of consumer-grade cameras
490
+ patterns = ['\/\d+RECNX\/','\/\d+EK\d+\/','\/\d+_BTCF\/','\/\d+MEDIA\/']
491
+
492
+ image_fn = image_fn.replace('\\','/')
493
+ for pat in patterns:
494
+ image_fn = re.sub(pat,'/',image_fn)
495
+ camera_folder = os.path.dirname(image_fn)
496
+
497
+ return camera_folder
498
+
499
+
500
+ def is_float(v):
501
+ """
502
+ Determines whether v is either a float or a string representation of a float.
503
+
504
+ Args:
505
+ v (object): object to evaluate
506
+
507
+ Returns:
508
+ bool: True if [v] is a float or a string representation of a float, otherwise False
509
+ """
510
+
511
+ try:
512
+ _ = float(v)
513
+ return True
514
+ except ValueError:
515
+ return False
516
+
517
+
518
+ def is_iterable(x):
519
+ """
520
+ Uses duck typing to assess whether [x] is iterable (list, set, dict, etc.).
521
+
522
+ Args:
523
+ x (object): the object to test
524
+
525
+ Returns:
526
+ bool: True if [x] appears to be iterable, otherwise False
527
+ """
528
+
529
+ try:
530
+ _ = iter(x)
531
+ except:
532
+ return False
533
+ return True
534
+
535
+
536
+ def is_empty(v):
537
+ """
538
+ A common definition of "empty" used throughout the repo, particularly when loading
539
+ data from .csv files. "empty" includes None, '', and NaN.
540
+
541
+ Args:
542
+ v: the object to evaluate for emptiness
543
+
544
+ Returns:
545
+ bool: True if [v] is None, '', or NaN, otherwise False
546
+ """
547
+ if v is None:
548
+ return True
549
+ if isinstance(v,str) and v == '':
550
+ return True
551
+ if isinstance(v,float) and np.isnan(v):
552
+ return True
553
+ return False
554
+
555
+
556
+ def isnan(v):
557
+ """
558
+ Returns True if v is a nan-valued float, otherwise returns False.
559
+
560
+ Args:
561
+ v: the object to evaluate for nan-ness
562
+
563
+ Returns:
564
+ bool: True if v is a nan-valued float, otherwise False
565
+ """
566
+
567
+ try:
568
+ return np.isnan(v)
569
+ except Exception:
570
+ return False
571
+
572
+
573
+ def sets_overlap(set1, set2):
574
+ """
575
+ Determines whether two sets overlap.
576
+
577
+ Args:
578
+ set1 (set): the first set to compare (converted to a set if it's not already)
579
+ set2 (set): the second set to compare (converted to a set if it's not already)
580
+
581
+ Returns:
582
+ bool: True if any elements are shared between set1 and set2
583
+ """
584
+
585
+ return not set(set1).isdisjoint(set(set2))
586
+
587
+
588
+
589
+ #%% Test drivers
590
+
591
+ if False:
592
+
593
+ pass
594
+
595
+ #%% Test image_file_to_camera_folder()
596
+
597
+ relative_path = 'a/b/c/d/100EK113/blah.jpg'
598
+ print(image_file_to_camera_folder(relative_path))
599
+
600
+ relative_path = 'a/b/c/d/100RECNX/blah.jpg'
601
+ print(image_file_to_camera_folder(relative_path))
602
+
603
+
604
+ #%% Test a few rectangle distances
605
+
606
+ r1 = [0,0,1,1]; r2 = [0,0,1,1]; assert rect_distance(r1,r2)==0
607
+ r1 = [0,0,1,1]; r2 = [0,0,1,100]; assert rect_distance(r1,r2)==0
608
+ r1 = [0,0,1,1]; r2 = [1,1,2,2]; assert rect_distance(r1,r2)==0
609
+ r1 = [0,0,1,1]; r2 = [1.1,0,0,1.1]; assert abs(rect_distance(r1,r2)-.1) < 0.00001
610
+
611
+ r1 = [0.4,0.8,10,22]; r2 = [100, 101, 200, 210.4]; assert abs(rect_distance(r1,r2)-119.753) < 0.001
612
+ r1 = [0.4,0.8,10,22]; r2 = [101, 101, 200, 210.4]; assert abs(rect_distance(r1,r2)-120.507) < 0.001
613
+ r1 = [0.4,0.8,10,22]; r2 = [120, 120, 200, 210.4]; assert abs(rect_distance(r1,r2)-147.323) < 0.001