megadetector 5.0.11__py3-none-any.whl → 5.0.12__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 (201) 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 +98 -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 +152 -0
  11. megadetector/api/batch_processing/api_core/server_orchestration.py +360 -0
  12. megadetector/api/batch_processing/api_core/server_utils.py +92 -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 +126 -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 +266 -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 +610 -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 +239 -0
  58. megadetector/data_management/cct_json_utils.py +395 -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 +272 -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 +477 -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 +796 -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 +874 -0
  129. megadetector/data_management/read_exif.py +681 -0
  130. megadetector/data_management/remap_coco_categories.py +84 -0
  131. megadetector/data_management/remove_exif.py +66 -0
  132. megadetector/data_management/resize_coco_dataset.py +189 -0
  133. megadetector/data_management/wi_download_csv_to_coco.py +246 -0
  134. megadetector/data_management/yolo_output_to_md_output.py +441 -0
  135. megadetector/data_management/yolo_to_coco.py +676 -0
  136. megadetector/detection/__init__.py +0 -0
  137. megadetector/detection/detector_training/__init__.py +0 -0
  138. megadetector/detection/detector_training/model_main_tf2.py +114 -0
  139. megadetector/detection/process_video.py +702 -0
  140. megadetector/detection/pytorch_detector.py +341 -0
  141. megadetector/detection/run_detector.py +779 -0
  142. megadetector/detection/run_detector_batch.py +1219 -0
  143. megadetector/detection/run_inference_with_yolov5_val.py +917 -0
  144. megadetector/detection/run_tiled_inference.py +934 -0
  145. megadetector/detection/tf_detector.py +189 -0
  146. megadetector/detection/video_utils.py +606 -0
  147. megadetector/postprocessing/__init__.py +0 -0
  148. megadetector/postprocessing/add_max_conf.py +64 -0
  149. megadetector/postprocessing/categorize_detections_by_size.py +163 -0
  150. megadetector/postprocessing/combine_api_outputs.py +249 -0
  151. megadetector/postprocessing/compare_batch_results.py +958 -0
  152. megadetector/postprocessing/convert_output_format.py +396 -0
  153. megadetector/postprocessing/load_api_results.py +195 -0
  154. megadetector/postprocessing/md_to_coco.py +310 -0
  155. megadetector/postprocessing/md_to_labelme.py +330 -0
  156. megadetector/postprocessing/merge_detections.py +401 -0
  157. megadetector/postprocessing/postprocess_batch_results.py +1902 -0
  158. megadetector/postprocessing/remap_detection_categories.py +170 -0
  159. megadetector/postprocessing/render_detection_confusion_matrix.py +660 -0
  160. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +211 -0
  161. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +83 -0
  162. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1631 -0
  163. megadetector/postprocessing/separate_detections_into_folders.py +730 -0
  164. megadetector/postprocessing/subset_json_detector_output.py +696 -0
  165. megadetector/postprocessing/top_folders_to_bottom.py +223 -0
  166. megadetector/taxonomy_mapping/__init__.py +0 -0
  167. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +491 -0
  168. megadetector/taxonomy_mapping/map_new_lila_datasets.py +150 -0
  169. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -0
  170. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +590 -0
  171. megadetector/taxonomy_mapping/retrieve_sample_image.py +71 -0
  172. megadetector/taxonomy_mapping/simple_image_download.py +219 -0
  173. megadetector/taxonomy_mapping/species_lookup.py +834 -0
  174. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +159 -0
  175. megadetector/taxonomy_mapping/taxonomy_graph.py +346 -0
  176. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +83 -0
  177. megadetector/utils/__init__.py +0 -0
  178. megadetector/utils/azure_utils.py +178 -0
  179. megadetector/utils/ct_utils.py +612 -0
  180. megadetector/utils/directory_listing.py +246 -0
  181. megadetector/utils/md_tests.py +968 -0
  182. megadetector/utils/path_utils.py +1044 -0
  183. megadetector/utils/process_utils.py +157 -0
  184. megadetector/utils/sas_blob_utils.py +509 -0
  185. megadetector/utils/split_locations_into_train_val.py +228 -0
  186. megadetector/utils/string_utils.py +92 -0
  187. megadetector/utils/url_utils.py +323 -0
  188. megadetector/utils/write_html_image_list.py +225 -0
  189. megadetector/visualization/__init__.py +0 -0
  190. megadetector/visualization/plot_utils.py +293 -0
  191. megadetector/visualization/render_images_with_thumbnails.py +275 -0
  192. megadetector/visualization/visualization_utils.py +1536 -0
  193. megadetector/visualization/visualize_db.py +550 -0
  194. megadetector/visualization/visualize_detector_output.py +405 -0
  195. {megadetector-5.0.11.dist-info → megadetector-5.0.12.dist-info}/METADATA +1 -1
  196. megadetector-5.0.12.dist-info/RECORD +199 -0
  197. megadetector-5.0.12.dist-info/top_level.txt +1 -0
  198. megadetector-5.0.11.dist-info/RECORD +0 -5
  199. megadetector-5.0.11.dist-info/top_level.txt +0 -1
  200. {megadetector-5.0.11.dist-info → megadetector-5.0.12.dist-info}/LICENSE +0 -0
  201. {megadetector-5.0.11.dist-info → megadetector-5.0.12.dist-info}/WHEEL +0 -0
@@ -0,0 +1,612 @@
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.
121
+ jsonpickle.set_encoder_options('json', sort_keys=True, indent=2)
122
+ a = jsonpickle.encode(obj)
123
+ s = '{}'.format(a)
124
+ if b_print:
125
+ print(s)
126
+ return s
127
+
128
+
129
+ def is_list_sorted(L, reverse=False):
130
+ """
131
+ Returns True if the list L appears to be sorted, otherwise False.
132
+
133
+ Calling is_list_sorted(L,reverse=True) is the same as calling
134
+ is_list_sorted(L.reverse(),reverse=False).
135
+
136
+ Args:
137
+ L (list): list to evaluate
138
+ reverse (bool, optional): whether to reverse the list before evaluating sort status
139
+
140
+ Returns:
141
+ bool: True if the list L appears to be sorted, otherwise False
142
+ """
143
+
144
+ if reverse:
145
+ return all(L[i] >= L[i + 1] for i in range(len(L)-1))
146
+ else:
147
+ return all(L[i] <= L[i + 1] for i in range(len(L)-1))
148
+
149
+
150
+ def write_json(path, content, indent=1):
151
+ """
152
+ Standardized wrapper for json.dump().
153
+
154
+ Args:
155
+ path (str): filename to write to
156
+ content (object): object to dump
157
+ indent (int, optional): indentation depth passed to json.dump
158
+ """
159
+
160
+ with open(path, 'w') as f:
161
+ json.dump(content, f, indent=indent)
162
+
163
+
164
+ def convert_yolo_to_xywh(yolo_box):
165
+ """
166
+ Converts a YOLO format bounding box to [x_min, y_min, width_of_box, height_of_box].
167
+
168
+ Args:
169
+ yolo_box (list): bounding box of format [x_center, y_center, width_of_box, height_of_box]
170
+
171
+ Returns:
172
+ list: bbox with coordinates represented as [x_min, y_min, width_of_box, height_of_box]
173
+ """
174
+
175
+ x_center, y_center, width_of_box, height_of_box = yolo_box
176
+ x_min = x_center - width_of_box / 2.0
177
+ y_min = y_center - height_of_box / 2.0
178
+ return [x_min, y_min, width_of_box, height_of_box]
179
+
180
+
181
+ def convert_xywh_to_tf(api_box):
182
+ """
183
+ Converts an xywh bounding box (the format used in MD output) to the [y_min, x_min, y_max, x_max]
184
+ format that the TensorFlow Object Detection API uses.
185
+
186
+ Args:
187
+ api_box: bbox output by the batch processing API [x_min, y_min, width_of_box, height_of_box]
188
+
189
+ Returns:
190
+ list: bbox with coordinates represented as [y_min, x_min, y_max, x_max]
191
+ """
192
+
193
+ x_min, y_min, width_of_box, height_of_box = api_box
194
+ x_max = x_min + width_of_box
195
+ y_max = y_min + height_of_box
196
+ return [y_min, x_min, y_max, x_max]
197
+
198
+
199
+ def convert_xywh_to_xyxy(api_bbox):
200
+ """
201
+ Converts an xywh bounding box (the MD output format) to an xyxy bounding box.
202
+
203
+ Args:
204
+ api_bbox (list): bbox formatted as [x_min, y_min, width_of_box, height_of_box]
205
+
206
+ Returns:
207
+ list: bbox formatted as [x_min, y_min, x_max, y_max]
208
+ """
209
+
210
+ x_min, y_min, width_of_box, height_of_box = api_bbox
211
+ x_max, y_max = x_min + width_of_box, y_min + height_of_box
212
+ return [x_min, y_min, x_max, y_max]
213
+
214
+
215
+ def get_iou(bb1, bb2):
216
+ """
217
+ Calculates the intersection over union (IoU) of two bounding boxes.
218
+
219
+ Adapted from:
220
+
221
+ https://stackoverflow.com/questions/25349178/calculating-percentage-of-bounding-box-overlap-for-image-detector-evaluation
222
+
223
+ Args:
224
+ bb1 (list): [x_min, y_min, width_of_box, height_of_box]
225
+ bb2 (list): [x_min, y_min, width_of_box, height_of_box]
226
+
227
+ Returns:
228
+ float: intersection_over_union, a float in [0, 1]
229
+ """
230
+
231
+ bb1 = convert_xywh_to_xyxy(bb1)
232
+ bb2 = convert_xywh_to_xyxy(bb2)
233
+
234
+ assert bb1[0] < bb1[2], 'Malformed bounding box (x2 >= x1)'
235
+ assert bb1[1] < bb1[3], 'Malformed bounding box (y2 >= y1)'
236
+
237
+ assert bb2[0] < bb2[2], 'Malformed bounding box (x2 >= x1)'
238
+ assert bb2[1] < bb2[3], 'Malformed bounding box (y2 >= y1)'
239
+
240
+ # Determine the coordinates of the intersection rectangle
241
+ x_left = max(bb1[0], bb2[0])
242
+ y_top = max(bb1[1], bb2[1])
243
+ x_right = min(bb1[2], bb2[2])
244
+ y_bottom = min(bb1[3], bb2[3])
245
+
246
+ if x_right < x_left or y_bottom < y_top:
247
+ return 0.0
248
+
249
+ # The intersection of two axis-aligned bounding boxes is always an
250
+ # axis-aligned bounding box
251
+ intersection_area = (x_right - x_left) * (y_bottom - y_top)
252
+
253
+ # Compute the area of both AABBs
254
+ bb1_area = (bb1[2] - bb1[0]) * (bb1[3] - bb1[1])
255
+ bb2_area = (bb2[2] - bb2[0]) * (bb2[3] - bb2[1])
256
+
257
+ # Compute the intersection over union by taking the intersection
258
+ # area and dividing it by the sum of prediction + ground-truth
259
+ # areas - the intersection area.
260
+ iou = intersection_area / float(bb1_area + bb2_area - intersection_area)
261
+ assert iou >= 0.0, 'Illegal IOU < 0'
262
+ assert iou <= 1.0, 'Illegal IOU > 1'
263
+ return iou
264
+
265
+
266
+ def _get_max_conf_from_detections(detections):
267
+ """
268
+ Internal function used by get_max_conf(); don't call this directly.
269
+ """
270
+
271
+ max_conf = 0.0
272
+ if detections is not None and len(detections) > 0:
273
+ confidences = [det['conf'] for det in detections]
274
+ max_conf = max(confidences)
275
+ return max_conf
276
+
277
+
278
+ def get_max_conf(im):
279
+ """
280
+ Given an image dict in the MD output format, computes the maximum detection confidence for any
281
+ class. Returns 0.0 (rather than None) if there was a failure or 'detections' isn't present.
282
+
283
+ Args:
284
+ im (dict): image dictionary in the MD output format (with a 'detections' field)
285
+
286
+ Returns:
287
+ float: the maximum detection confidence across all classes
288
+ """
289
+
290
+ max_conf = 0.0
291
+ if 'detections' in im and im['detections'] is not None and len(im['detections']) > 0:
292
+ max_conf = _get_max_conf_from_detections(im['detections'])
293
+ return max_conf
294
+
295
+
296
+ def point_dist(p1,p2):
297
+ """
298
+ Computes the distance between two points, represented as length-two tuples.
299
+
300
+ Args:
301
+ p1: point, formatted as (x,y)
302
+ p2: point, formatted as (x,y)
303
+
304
+ Returns:
305
+ float: the Euclidean distance between p1 and p2
306
+ """
307
+
308
+ return math.sqrt( ((p1[0]-p2[0])**2) + ((p1[1]-p2[1])**2) )
309
+
310
+
311
+ def rect_distance(r1, r2, format='x0y0x1y1'):
312
+ """
313
+ Computes the minimum distance between two axis-aligned rectangles, each represented as
314
+ (x0,y0,x1,y1) by default.
315
+
316
+ Can also specify "format" as x0y0wh for MD-style bbox formatting (x0,y0,w,h).
317
+
318
+ Args:
319
+ r1: rectangle, formatted as (x0,y0,x1,y1) or (x0,y0,xy,y1)
320
+ r2: rectangle, formatted as (x0,y0,x1,y1) or (x0,y0,xy,y1)
321
+ format (str, optional): whether the boxes are formatted as 'x0y0x1y1' (default) or 'x0y0wh'
322
+
323
+ Returns:
324
+ float: the minimum distance between r1 and r2
325
+ """
326
+
327
+ assert format in ('x0y0x1y1','x0y0wh'), 'Illegal rectangle format {}'.format(format)
328
+
329
+ if format == 'x0y0wh':
330
+ # Convert to x0y0x1y1 without modifying the original rectangles
331
+ r1 = [r1[0],r1[1],r1[0]+r1[2],r1[1]+r1[3]]
332
+ r2 = [r2[0],r2[1],r2[0]+r2[2],r2[1]+r2[3]]
333
+
334
+ # https://stackoverflow.com/a/26178015
335
+ x1, y1, x1b, y1b = r1
336
+ x2, y2, x2b, y2b = r2
337
+ left = x2b < x1
338
+ right = x1b < x2
339
+ bottom = y2b < y1
340
+ top = y1b < y2
341
+ if top and left:
342
+ return point_dist((x1, y1b), (x2b, y2))
343
+ elif left and bottom:
344
+ return point_dist((x1, y1), (x2b, y2b))
345
+ elif bottom and right:
346
+ return point_dist((x1b, y1), (x2, y2b))
347
+ elif right and top:
348
+ return point_dist((x1b, y1b), (x2, y2))
349
+ elif left:
350
+ return x1 - x2b
351
+ elif right:
352
+ return x2 - x1b
353
+ elif bottom:
354
+ return y1 - y2b
355
+ elif top:
356
+ return y2 - y1b
357
+ else:
358
+ return 0.0
359
+
360
+
361
+ def split_list_into_fixed_size_chunks(L,n):
362
+ """
363
+ Split the list or tuple L into chunks of size n (allowing chunks of size n-1 if necessary,
364
+ i.e. len(L) does not have to be a multiple of n).
365
+
366
+ Args:
367
+ L (list): list to split into chunks
368
+ n (int): preferred chunk size
369
+
370
+ Returns:
371
+ list: list of chunks, where each chunk is a list of length n or n-1
372
+ """
373
+
374
+ return [L[i * n:(i + 1) * n] for i in range((len(L) + n - 1) // n )]
375
+
376
+
377
+ def split_list_into_n_chunks(L, n, chunk_strategy='greedy'):
378
+ """
379
+ Splits the list or tuple L into n equally-sized chunks (some chunks may be one
380
+ element smaller than others, i.e. len(L) does not have to be a multiple of n).
381
+
382
+ chunk_strategy can be "greedy" (default, if there are k samples per chunk, the first
383
+ k go into the first chunk) or "balanced" (alternate between chunks when pulling
384
+ items from the list).
385
+
386
+ Args:
387
+ L (list): list to split into chunks
388
+ n (int): number of chunks
389
+ chunk_strategy (str, optiopnal): "greedy" or "balanced"; see above
390
+
391
+ Returns:
392
+ list: list of chunks, each of which is a list
393
+ """
394
+
395
+ if chunk_strategy == 'greedy':
396
+ k, m = divmod(len(L), n)
397
+ return list(L[i*k+min(i, m):(i+1)*k+min(i+1, m)] for i in range(n))
398
+ elif chunk_strategy == 'balanced':
399
+ chunks = [ [] for _ in range(n) ]
400
+ for i_item,item in enumerate(L):
401
+ i_chunk = i_item % n
402
+ chunks[i_chunk].append(item)
403
+ return chunks
404
+ else:
405
+ raise ValueError('Invalid chunk strategy: {}'.format(chunk_strategy))
406
+
407
+
408
+ def sort_dictionary_by_key(d,reverse=False):
409
+ """
410
+ Sorts the dictionary [d] by key.
411
+
412
+ Args:
413
+ d (dict): dictionary to sort
414
+ reverse (bool, optional): whether to sort in reverse (descending) order
415
+
416
+ Returns:
417
+ dict: sorted copy of [d]
418
+ """
419
+
420
+ d = dict(sorted(d.items(),reverse=reverse))
421
+ return d
422
+
423
+
424
+ def sort_dictionary_by_value(d,sort_values=None,reverse=False):
425
+ """
426
+ Sorts the dictionary [d] by value. If sort_values is None, uses d.values(),
427
+ otherwise uses the dictionary sort_values as the sorting criterion.
428
+
429
+ Args:
430
+ d (dict): dictionary to sort
431
+ sort_values (dict, optional): dictionary mapping keys in [d] to sort values (defaults
432
+ to None, uses [d] itself for sorting)
433
+ reverse (bool, optional): whether to sort in reverse (descending) order
434
+
435
+ Returns:
436
+ dict: sorted copy of [d]
437
+ """
438
+
439
+ if sort_values is None:
440
+ d = {k: v for k, v in sorted(d.items(), key=lambda item: item[1], reverse=reverse)}
441
+ else:
442
+ d = {k: v for k, v in sorted(d.items(), key=lambda item: sort_values[item[0]], reverse=reverse)}
443
+ return d
444
+
445
+
446
+ def invert_dictionary(d):
447
+ """
448
+ Creates a new dictionary that maps d.values() to d.keys(). Does not check
449
+ uniqueness.
450
+
451
+ Args:
452
+ d (dict): dictionary to invert
453
+
454
+ Returns:
455
+ dict: inverted copy of [d]
456
+ """
457
+
458
+ return {v: k for k, v in d.items()}
459
+
460
+
461
+ def image_file_to_camera_folder(image_fn):
462
+ r"""
463
+ Removes common overflow folders (e.g. RECNX101, RECNX102) from paths, i.e. turn:
464
+
465
+ a\b\c\RECNX101\image001.jpg
466
+
467
+ ...into:
468
+
469
+ a\b\c
470
+
471
+ Returns the same thing as os.dirname() (i.e., just the folder name) if no overflow folders are
472
+ present.
473
+
474
+ Always converts backslashes to slashes.
475
+
476
+ Args:
477
+ image_fn (str): the image filename from which we should remove overflow folders
478
+
479
+ Returns:
480
+ str: a version of [image_fn] from which camera overflow folders have been removed
481
+ """
482
+
483
+ import re
484
+
485
+ # 100RECNX is the overflow folder style for Reconyx cameras
486
+ # 100EK113 is (for some reason) the overflow folder style for Bushnell cameras
487
+ # 100_BTCF is the overflow folder style for Browning cameras
488
+ # 100MEDIA is the overflow folder style used on a number of consumer-grade cameras
489
+ patterns = ['\/\d+RECNX\/','\/\d+EK\d+\/','\/\d+_BTCF\/','\/\d+MEDIA\/']
490
+
491
+ image_fn = image_fn.replace('\\','/')
492
+ for pat in patterns:
493
+ image_fn = re.sub(pat,'/',image_fn)
494
+ camera_folder = os.path.dirname(image_fn)
495
+
496
+ return camera_folder
497
+
498
+
499
+ def is_float(v):
500
+ """
501
+ Determines whether v is either a float or a string representation of a float.
502
+
503
+ Args:
504
+ v (object): object to evaluate
505
+
506
+ Returns:
507
+ bool: True if [v] is a float or a string representation of a float, otherwise False
508
+ """
509
+
510
+ try:
511
+ _ = float(v)
512
+ return True
513
+ except ValueError:
514
+ return False
515
+
516
+
517
+ def is_iterable(x):
518
+ """
519
+ Uses duck typing to assess whether [x] is iterable (list, set, dict, etc.).
520
+
521
+ Args:
522
+ x (object): the object to test
523
+
524
+ Returns:
525
+ bool: True if [x] appears to be iterable, otherwise False
526
+ """
527
+
528
+ try:
529
+ _ = iter(x)
530
+ except:
531
+ return False
532
+ return True
533
+
534
+
535
+ def is_empty(v):
536
+ """
537
+ A common definition of "empty" used throughout the repo, particularly when loading
538
+ data from .csv files. "empty" includes None, '', and NaN.
539
+
540
+ Args:
541
+ v: the object to evaluate for emptiness
542
+
543
+ Returns:
544
+ bool: True if [v] is None, '', or NaN, otherwise False
545
+ """
546
+ if v is None:
547
+ return True
548
+ if isinstance(v,str) and v == '':
549
+ return True
550
+ if isinstance(v,float) and np.isnan(v):
551
+ return True
552
+ return False
553
+
554
+
555
+ def isnan(v):
556
+ """
557
+ Returns True if v is a nan-valued float, otherwise returns False.
558
+
559
+ Args:
560
+ v: the object to evaluate for nan-ness
561
+
562
+ Returns:
563
+ bool: True if v is a nan-valued float, otherwise False
564
+ """
565
+
566
+ try:
567
+ return np.isnan(v)
568
+ except Exception:
569
+ return False
570
+
571
+
572
+ def sets_overlap(set1, set2):
573
+ """
574
+ Determines whether two sets overlap.
575
+
576
+ Args:
577
+ set1 (set): the first set to compare (converted to a set if it's not already)
578
+ set2 (set): the second set to compare (converted to a set if it's not already)
579
+
580
+ Returns:
581
+ bool: True if any elements are shared between set1 and set2
582
+ """
583
+
584
+ return not set(set1).isdisjoint(set(set2))
585
+
586
+
587
+
588
+ #%% Test drivers
589
+
590
+ if False:
591
+
592
+ pass
593
+
594
+ #%% Test image_file_to_camera_folder()
595
+
596
+ relative_path = 'a/b/c/d/100EK113/blah.jpg'
597
+ print(image_file_to_camera_folder(relative_path))
598
+
599
+ relative_path = 'a/b/c/d/100RECNX/blah.jpg'
600
+ print(image_file_to_camera_folder(relative_path))
601
+
602
+
603
+ #%% Test a few rectangle distances
604
+
605
+ r1 = [0,0,1,1]; r2 = [0,0,1,1]; assert rect_distance(r1,r2)==0
606
+ r1 = [0,0,1,1]; r2 = [0,0,1,100]; assert rect_distance(r1,r2)==0
607
+ r1 = [0,0,1,1]; r2 = [1,1,2,2]; assert rect_distance(r1,r2)==0
608
+ r1 = [0,0,1,1]; r2 = [1.1,0,0,1.1]; assert abs(rect_distance(r1,r2)-.1) < 0.00001
609
+
610
+ r1 = [0.4,0.8,10,22]; r2 = [100, 101, 200, 210.4]; assert abs(rect_distance(r1,r2)-119.753) < 0.001
611
+ r1 = [0.4,0.8,10,22]; r2 = [101, 101, 200, 210.4]; assert abs(rect_distance(r1,r2)-120.507) < 0.001
612
+ r1 = [0.4,0.8,10,22]; r2 = [120, 120, 200, 210.4]; assert abs(rect_distance(r1,r2)-147.323) < 0.001