megadetector 5.0.7__py3-none-any.whl → 5.0.9__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 (191) hide show
  1. api/__init__.py +0 -0
  2. api/batch_processing/__init__.py +0 -0
  3. api/batch_processing/api_core/__init__.py +0 -0
  4. api/batch_processing/api_core/batch_service/__init__.py +0 -0
  5. api/batch_processing/api_core/batch_service/score.py +0 -1
  6. api/batch_processing/api_core/server_job_status_table.py +0 -1
  7. api/batch_processing/api_core_support/__init__.py +0 -0
  8. api/batch_processing/api_core_support/aggregate_results_manually.py +0 -1
  9. api/batch_processing/api_support/__init__.py +0 -0
  10. api/batch_processing/api_support/summarize_daily_activity.py +0 -1
  11. api/batch_processing/data_preparation/__init__.py +0 -0
  12. api/batch_processing/data_preparation/manage_local_batch.py +93 -79
  13. api/batch_processing/data_preparation/manage_video_batch.py +8 -8
  14. api/batch_processing/integration/digiKam/xmp_integration.py +0 -1
  15. api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -1
  16. api/batch_processing/postprocessing/__init__.py +0 -0
  17. api/batch_processing/postprocessing/add_max_conf.py +12 -12
  18. api/batch_processing/postprocessing/categorize_detections_by_size.py +32 -14
  19. api/batch_processing/postprocessing/combine_api_outputs.py +69 -55
  20. api/batch_processing/postprocessing/compare_batch_results.py +114 -44
  21. api/batch_processing/postprocessing/convert_output_format.py +62 -19
  22. api/batch_processing/postprocessing/load_api_results.py +17 -20
  23. api/batch_processing/postprocessing/md_to_coco.py +31 -21
  24. api/batch_processing/postprocessing/md_to_labelme.py +165 -68
  25. api/batch_processing/postprocessing/merge_detections.py +40 -15
  26. api/batch_processing/postprocessing/postprocess_batch_results.py +270 -186
  27. api/batch_processing/postprocessing/remap_detection_categories.py +170 -0
  28. api/batch_processing/postprocessing/render_detection_confusion_matrix.py +75 -39
  29. api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +53 -44
  30. api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +25 -14
  31. api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +244 -160
  32. api/batch_processing/postprocessing/separate_detections_into_folders.py +159 -114
  33. api/batch_processing/postprocessing/subset_json_detector_output.py +146 -169
  34. api/batch_processing/postprocessing/top_folders_to_bottom.py +77 -43
  35. api/synchronous/__init__.py +0 -0
  36. api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  37. api/synchronous/api_core/animal_detection_api/api_backend.py +0 -2
  38. api/synchronous/api_core/animal_detection_api/api_frontend.py +266 -268
  39. api/synchronous/api_core/animal_detection_api/config.py +35 -35
  40. api/synchronous/api_core/tests/__init__.py +0 -0
  41. api/synchronous/api_core/tests/load_test.py +109 -109
  42. classification/__init__.py +0 -0
  43. classification/aggregate_classifier_probs.py +21 -24
  44. classification/analyze_failed_images.py +11 -13
  45. classification/cache_batchapi_outputs.py +51 -51
  46. classification/create_classification_dataset.py +69 -68
  47. classification/crop_detections.py +54 -53
  48. classification/csv_to_json.py +97 -100
  49. classification/detect_and_crop.py +105 -105
  50. classification/evaluate_model.py +43 -42
  51. classification/identify_mislabeled_candidates.py +47 -46
  52. classification/json_to_azcopy_list.py +10 -10
  53. classification/json_validator.py +72 -71
  54. classification/map_classification_categories.py +44 -43
  55. classification/merge_classification_detection_output.py +68 -68
  56. classification/prepare_classification_script.py +157 -154
  57. classification/prepare_classification_script_mc.py +228 -228
  58. classification/run_classifier.py +27 -26
  59. classification/save_mislabeled.py +30 -30
  60. classification/train_classifier.py +20 -20
  61. classification/train_classifier_tf.py +21 -22
  62. classification/train_utils.py +10 -10
  63. data_management/__init__.py +0 -0
  64. data_management/annotations/__init__.py +0 -0
  65. data_management/annotations/annotation_constants.py +18 -31
  66. data_management/camtrap_dp_to_coco.py +238 -0
  67. data_management/cct_json_utils.py +107 -59
  68. data_management/cct_to_md.py +176 -158
  69. data_management/cct_to_wi.py +247 -219
  70. data_management/coco_to_labelme.py +272 -0
  71. data_management/coco_to_yolo.py +86 -62
  72. data_management/databases/__init__.py +0 -0
  73. data_management/databases/add_width_and_height_to_db.py +20 -16
  74. data_management/databases/combine_coco_camera_traps_files.py +35 -31
  75. data_management/databases/integrity_check_json_db.py +130 -83
  76. data_management/databases/subset_json_db.py +25 -16
  77. data_management/generate_crops_from_cct.py +27 -45
  78. data_management/get_image_sizes.py +188 -144
  79. data_management/importers/add_nacti_sizes.py +8 -8
  80. data_management/importers/add_timestamps_to_icct.py +78 -78
  81. data_management/importers/animl_results_to_md_results.py +158 -160
  82. data_management/importers/auckland_doc_test_to_json.py +9 -9
  83. data_management/importers/auckland_doc_to_json.py +8 -8
  84. data_management/importers/awc_to_json.py +7 -7
  85. data_management/importers/bellevue_to_json.py +15 -15
  86. data_management/importers/cacophony-thermal-importer.py +13 -13
  87. data_management/importers/carrizo_shrubfree_2018.py +8 -8
  88. data_management/importers/carrizo_trail_cam_2017.py +8 -8
  89. data_management/importers/cct_field_adjustments.py +9 -9
  90. data_management/importers/channel_islands_to_cct.py +10 -10
  91. data_management/importers/eMammal/copy_and_unzip_emammal.py +1 -0
  92. data_management/importers/ena24_to_json.py +7 -7
  93. data_management/importers/filenames_to_json.py +8 -8
  94. data_management/importers/helena_to_cct.py +7 -7
  95. data_management/importers/idaho-camera-traps.py +7 -7
  96. data_management/importers/idfg_iwildcam_lila_prep.py +10 -10
  97. data_management/importers/jb_csv_to_json.py +9 -9
  98. data_management/importers/mcgill_to_json.py +8 -8
  99. data_management/importers/missouri_to_json.py +18 -18
  100. data_management/importers/nacti_fieldname_adjustments.py +10 -10
  101. data_management/importers/noaa_seals_2019.py +8 -8
  102. data_management/importers/pc_to_json.py +7 -7
  103. data_management/importers/plot_wni_giraffes.py +7 -7
  104. data_management/importers/prepare-noaa-fish-data-for-lila.py +359 -359
  105. data_management/importers/prepare_zsl_imerit.py +7 -7
  106. data_management/importers/rspb_to_json.py +8 -8
  107. data_management/importers/save_the_elephants_survey_A.py +8 -8
  108. data_management/importers/save_the_elephants_survey_B.py +9 -9
  109. data_management/importers/snapshot_safari_importer.py +26 -26
  110. data_management/importers/snapshot_safari_importer_reprise.py +665 -665
  111. data_management/importers/snapshot_serengeti_lila.py +14 -14
  112. data_management/importers/sulross_get_exif.py +8 -9
  113. data_management/importers/timelapse_csv_set_to_json.py +11 -11
  114. data_management/importers/ubc_to_json.py +13 -13
  115. data_management/importers/umn_to_json.py +7 -7
  116. data_management/importers/wellington_to_json.py +8 -8
  117. data_management/importers/wi_to_json.py +9 -9
  118. data_management/importers/zamba_results_to_md_results.py +181 -181
  119. data_management/labelme_to_coco.py +309 -159
  120. data_management/labelme_to_yolo.py +103 -60
  121. data_management/lila/__init__.py +0 -0
  122. data_management/lila/add_locations_to_island_camera_traps.py +9 -9
  123. data_management/lila/add_locations_to_nacti.py +147 -147
  124. data_management/lila/create_lila_blank_set.py +114 -31
  125. data_management/lila/create_lila_test_set.py +8 -8
  126. data_management/lila/create_links_to_md_results_files.py +106 -106
  127. data_management/lila/download_lila_subset.py +92 -90
  128. data_management/lila/generate_lila_per_image_labels.py +56 -43
  129. data_management/lila/get_lila_annotation_counts.py +18 -15
  130. data_management/lila/get_lila_image_counts.py +11 -11
  131. data_management/lila/lila_common.py +103 -70
  132. data_management/lila/test_lila_metadata_urls.py +132 -116
  133. data_management/ocr_tools.py +173 -128
  134. data_management/read_exif.py +161 -99
  135. data_management/remap_coco_categories.py +84 -0
  136. data_management/remove_exif.py +58 -62
  137. data_management/resize_coco_dataset.py +32 -44
  138. data_management/wi_download_csv_to_coco.py +246 -0
  139. data_management/yolo_output_to_md_output.py +86 -73
  140. data_management/yolo_to_coco.py +535 -95
  141. detection/__init__.py +0 -0
  142. detection/detector_training/__init__.py +0 -0
  143. detection/process_video.py +85 -33
  144. detection/pytorch_detector.py +43 -25
  145. detection/run_detector.py +157 -72
  146. detection/run_detector_batch.py +189 -114
  147. detection/run_inference_with_yolov5_val.py +118 -51
  148. detection/run_tiled_inference.py +113 -42
  149. detection/tf_detector.py +51 -28
  150. detection/video_utils.py +606 -521
  151. docs/source/conf.py +43 -0
  152. md_utils/__init__.py +0 -0
  153. md_utils/azure_utils.py +9 -9
  154. md_utils/ct_utils.py +249 -70
  155. md_utils/directory_listing.py +59 -64
  156. md_utils/md_tests.py +968 -862
  157. md_utils/path_utils.py +655 -155
  158. md_utils/process_utils.py +157 -133
  159. md_utils/sas_blob_utils.py +20 -20
  160. md_utils/split_locations_into_train_val.py +45 -32
  161. md_utils/string_utils.py +33 -10
  162. md_utils/url_utils.py +208 -27
  163. md_utils/write_html_image_list.py +51 -35
  164. md_visualization/__init__.py +0 -0
  165. md_visualization/plot_utils.py +102 -109
  166. md_visualization/render_images_with_thumbnails.py +34 -34
  167. md_visualization/visualization_utils.py +908 -311
  168. md_visualization/visualize_db.py +109 -58
  169. md_visualization/visualize_detector_output.py +61 -42
  170. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/METADATA +21 -17
  171. megadetector-5.0.9.dist-info/RECORD +224 -0
  172. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/WHEEL +1 -1
  173. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/top_level.txt +1 -0
  174. taxonomy_mapping/__init__.py +0 -0
  175. taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +342 -335
  176. taxonomy_mapping/map_new_lila_datasets.py +154 -154
  177. taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -134
  178. taxonomy_mapping/preview_lila_taxonomy.py +591 -591
  179. taxonomy_mapping/retrieve_sample_image.py +12 -12
  180. taxonomy_mapping/simple_image_download.py +11 -11
  181. taxonomy_mapping/species_lookup.py +10 -10
  182. taxonomy_mapping/taxonomy_csv_checker.py +18 -18
  183. taxonomy_mapping/taxonomy_graph.py +47 -47
  184. taxonomy_mapping/validate_lila_category_mappings.py +83 -76
  185. data_management/cct_json_to_filename_json.py +0 -89
  186. data_management/cct_to_csv.py +0 -140
  187. data_management/databases/remove_corrupted_images_from_db.py +0 -191
  188. detection/detector_training/copy_checkpoints.py +0 -43
  189. md_visualization/visualize_megadb.py +0 -183
  190. megadetector-5.0.7.dist-info/RECORD +0 -202
  191. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/LICENSE +0 -0
docs/source/conf.py ADDED
@@ -0,0 +1,43 @@
1
+ import sys
2
+ import os
3
+
4
+ project = 'MegaDetector'
5
+ copyright = '2024, Your friendly neighborhood MegaDetector team'
6
+ author = 'Your friendly neighborhood MegaDetector team'
7
+
8
+ sys.path.insert(0, os.path.abspath("../.."))
9
+
10
+ extensions = [
11
+ "sphinx.ext.napoleon",
12
+ "sphinx.ext.autodoc",
13
+ "sphinx.ext.viewcode",
14
+ "sphinx_mdinclude",
15
+ "sphinx_argparse_cli"
16
+ ]
17
+
18
+ autodoc_mock_imports = ["azure", "deepdiff", "magic", "tensorflow", "pytesseract"]
19
+
20
+ myst_enable_extensions = [
21
+ "colon_fence",
22
+ ]
23
+
24
+ html_theme = 'sphinx_rtd_theme'
25
+
26
+ # collapse_navigation doesn't actually work
27
+ html_theme_options = {'navigation_depth': 2, 'collapse_navigation': False}
28
+
29
+ # html_theme = 'sphinx_book_theme'
30
+ # html_theme_options['show_navbar_depth'] = 2
31
+
32
+ # html_static_path = ['_static']
33
+
34
+ # Hide "bases: object" from all classes that don't define a base class
35
+ from sphinx.ext import autodoc
36
+
37
+ class MockedClassDocumenter(autodoc.ClassDocumenter):
38
+ def add_line(self, line: str, source: str, *lineno: int) -> None:
39
+ if line == " Bases: :py:class:`object`":
40
+ return
41
+ super().add_line(line, source, *lineno)
42
+
43
+ autodoc.ClassDocumenter = MockedClassDocumenter
md_utils/__init__.py ADDED
File without changes
md_utils/azure_utils.py CHANGED
@@ -1,12 +1,12 @@
1
- ########
2
- #
3
- # azure_utils.py
4
- #
5
- # Miscellaneous Azure Blob Storage utilities
6
- #
7
- # Requires azure-storage-blob>=12.4.0
8
- #
9
- ########
1
+ """
2
+
3
+ azure_utils.py
4
+
5
+ Miscellaneous Azure Blob Storage utilities
6
+
7
+ Requires azure-storage-blob>=12.4.0
8
+
9
+ """
10
10
 
11
11
  import json
12
12
  from md_utils import path_utils
md_utils/ct_utils.py CHANGED
@@ -1,14 +1,13 @@
1
- ########
2
- #
3
- # ct_utils.py
4
- #
5
- # Numeric/geometry utility functions
6
- #
7
- ########
1
+ """
2
+
3
+ ct_utils.py
4
+
5
+ Numeric/geometry/array utility functions.
6
+
7
+ """
8
8
 
9
9
  #%% Imports and constants
10
10
 
11
- import argparse
12
11
  import inspect
13
12
  import json
14
13
  import math
@@ -26,12 +25,15 @@ image_extensions = ['.jpg', '.jpeg', '.gif', '.png']
26
25
 
27
26
  def truncate_float_array(xs, precision=3):
28
27
  """
29
- Vectorized version of truncate_float(...)
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
30
 
31
31
  Args:
32
- xs (list of float) List of floats to truncate
33
- precision (int) The number of significant digits to preserve, should be
34
- greater or equal 1
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
35
37
  """
36
38
 
37
39
  return [truncate_float(x, precision=precision) for x in xs]
@@ -39,17 +41,23 @@ def truncate_float_array(xs, precision=3):
39
41
 
40
42
  def truncate_float(x, precision=3):
41
43
  """
42
- Truncates a floating-point value to a specific number of significant digits.
44
+ Truncates the fractional portion of a floating-point value to a specific number of
45
+ floating-point digits.
43
46
 
44
- For example: truncate_float(0.0003214884) --> 0.000321
47
+ For example:
48
+
49
+ truncate_float(0.0003214884) --> 0.000321
50
+ truncate_float(1.0003214884) --> 1.000321
45
51
 
46
52
  This function is primarily used to achieve a certain float representation
47
53
  before exporting to JSON.
48
54
 
49
55
  Args:
50
- x (float) Scalar to truncate
51
- precision (int) The number of significant digits to preserve, should be
52
- greater or equal 1
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]
53
61
  """
54
62
 
55
63
  assert precision > 0
@@ -58,41 +66,58 @@ def truncate_float(x, precision=3):
58
66
 
59
67
  return 0
60
68
 
69
+ elif (x > 1):
70
+
71
+ fractional_component = x - 1.0
72
+ return 1 + truncate_float(fractional_component)
73
+
61
74
  else:
62
75
 
63
76
  # Determine the factor, which shifts the decimal point of x
64
77
  # just behind the last significant digit.
65
78
  factor = math.pow(10, precision - 1 - math.floor(math.log10(abs(x))))
66
79
 
67
- # Shift decimal point by multiplicatipon with factor, flooring, and
80
+ # Shift decimal point by multiplication with factor, flooring, and
68
81
  # division by factor.
69
82
  return math.floor(x * factor)/factor
70
83
 
71
84
 
72
- def args_to_object(args: argparse.Namespace, obj: object) -> None:
85
+ def args_to_object(args, obj):
73
86
  """
74
87
  Copies all fields from a Namespace (typically the output from parse_args) to an
75
88
  object. Skips fields starting with _. Does not check existence in the target
76
89
  object.
77
90
 
78
91
  Args:
79
- args: argparse.Namespace
80
- obj: class or object whose whose attributes will be updated
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)
81
97
  """
82
98
 
83
99
  for n, v in inspect.getmembers(args):
84
100
  if not n.startswith('_'):
85
101
  setattr(obj, n, v)
86
102
 
103
+ return obj
104
+
87
105
 
88
106
  def pretty_print_object(obj, b_print=True):
89
107
  """
90
- Prints an arbitrary object as .json
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]
91
116
  """
92
117
 
93
118
  # _ = pretty_print_object(obj)
94
119
 
95
- # Sloppy that I'm making a module-wide change here...
120
+ # TODO: it's sloppy that I'm making a module-wide change here.
96
121
  jsonpickle.set_encoder_options('json', sort_keys=True, indent=2)
97
122
  a = jsonpickle.encode(obj)
98
123
  s = '{}'.format(a)
@@ -101,12 +126,19 @@ def pretty_print_object(obj, b_print=True):
101
126
  return s
102
127
 
103
128
 
104
- def is_list_sorted(L,reverse=False):
129
+ def is_list_sorted(L, reverse=False):
105
130
  """
106
- Returns true if the list L appears to be sorted, otherwise False.
131
+ Returns True if the list L appears to be sorted, otherwise False.
107
132
 
108
133
  Calling is_list_sorted(L,reverse=True) is the same as calling
109
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
110
142
  """
111
143
 
112
144
  if reverse:
@@ -117,32 +149,27 @@ def is_list_sorted(L,reverse=False):
117
149
 
118
150
  def write_json(path, content, indent=1):
119
151
  """
120
- Standardized wrapper for json.dump
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
121
158
  """
122
159
 
123
160
  with open(path, 'w') as f:
124
161
  json.dump(content, f, indent=indent)
125
162
 
126
163
 
127
- def is_image_file(s):
128
- """
129
- Checks a file's extension against a hard-coded set of image file extensions;
130
- return True if it appears to be an image.
131
- """
132
-
133
- ext = os.path.splitext(s)[1]
134
- return ext.lower() in image_extensions
135
-
136
-
137
164
  def convert_yolo_to_xywh(yolo_box):
138
165
  """
139
166
  Converts a YOLO format bounding box to [x_min, y_min, width_of_box, height_of_box].
140
167
 
141
168
  Args:
142
- yolo_box: bounding box of format [x_center, y_center, width_of_box, height_of_box].
169
+ yolo_box (list): bounding box of format [x_center, y_center, width_of_box, height_of_box]
143
170
 
144
171
  Returns:
145
- bbox with coordinates represented as [x_min, y_min, width_of_box, height_of_box].
172
+ list: bbox with coordinates represented as [x_min, y_min, width_of_box, height_of_box]
146
173
  """
147
174
 
148
175
  x_center, y_center, width_of_box, height_of_box = yolo_box
@@ -153,14 +180,14 @@ def convert_yolo_to_xywh(yolo_box):
153
180
 
154
181
  def convert_xywh_to_tf(api_box):
155
182
  """
156
- Converts an xywh bounding box to an [y_min, x_min, y_max, x_max] box that the TensorFlow
157
- Object Detection API uses
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.
158
185
 
159
186
  Args:
160
187
  api_box: bbox output by the batch processing API [x_min, y_min, width_of_box, height_of_box]
161
188
 
162
189
  Returns:
163
- bbox with coordinates represented as [y_min, x_min, y_max, x_max]
190
+ list: bbox with coordinates represented as [y_min, x_min, y_max, x_max]
164
191
  """
165
192
 
166
193
  x_min, y_min, width_of_box, height_of_box = api_box
@@ -171,14 +198,13 @@ def convert_xywh_to_tf(api_box):
171
198
 
172
199
  def convert_xywh_to_xyxy(api_bbox):
173
200
  """
174
- Converts an xywh bounding box to an xyxy bounding box.
201
+ Converts an xywh bounding box (the MD output format) to an xyxy bounding box.
175
202
 
176
- Note that this is also different from the TensorFlow Object Detection API coords format.
177
203
  Args:
178
- api_bbox: bbox output by the batch processing API [x_min, y_min, width_of_box, height_of_box]
204
+ api_bbox (list): bbox formatted as [x_min, y_min, width_of_box, height_of_box]
179
205
 
180
206
  Returns:
181
- bbox with coordinates represented as [x_min, y_min, x_max, y_max]
207
+ list: bbox formatted as [x_min, y_min, x_max, y_max]
182
208
  """
183
209
 
184
210
  x_min, y_min, width_of_box, height_of_box = api_bbox
@@ -188,18 +214,18 @@ def convert_xywh_to_xyxy(api_bbox):
188
214
 
189
215
  def get_iou(bb1, bb2):
190
216
  """
191
- Calculates the Intersection over Union (IoU) of two bounding boxes.
217
+ Calculates the intersection over union (IoU) of two bounding boxes.
192
218
 
193
219
  Adapted from:
194
220
 
195
221
  https://stackoverflow.com/questions/25349178/calculating-percentage-of-bounding-box-overlap-for-image-detector-evaluation
196
222
 
197
223
  Args:
198
- bb1: [x_min, y_min, width_of_box, height_of_box]
199
- bb2: [x_min, y_min, width_of_box, height_of_box]
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]
200
226
 
201
227
  Returns:
202
- intersection_over_union, a float in [0, 1]
228
+ float: intersection_over_union, a float in [0, 1]
203
229
  """
204
230
 
205
231
  bb1 = convert_xywh_to_xyxy(bb1)
@@ -251,9 +277,14 @@ def _get_max_conf_from_detections(detections):
251
277
 
252
278
  def get_max_conf(im):
253
279
  """
254
- Given an image dict in the format used by the batch API, compute the maximum detection
255
- confidence for any class. Returns 0.0 (not None) if there was a failure and 'detections'
256
- isn't present.
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
257
288
  """
258
289
 
259
290
  max_conf = 0.0
@@ -264,7 +295,14 @@ def get_max_conf(im):
264
295
 
265
296
  def point_dist(p1,p2):
266
297
  """
267
- Distance between two points, represented as length-two tuples.
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
268
306
  """
269
307
 
270
308
  return math.sqrt( ((p1[0]-p2[0])**2) + ((p1[1]-p2[1])**2) )
@@ -272,13 +310,21 @@ def point_dist(p1,p2):
272
310
 
273
311
  def rect_distance(r1, r2, format='x0y0x1y1'):
274
312
  """
275
- Minimum distance between two axis-aligned rectangles, each represented as
313
+ Computes the minimum distance between two axis-aligned rectangles, each represented as
276
314
  (x0,y0,x1,y1) by default.
277
315
 
278
- Can also specify "format" as x0y0wh for MD-style bbox formatting (x0,y0,w,h).
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
279
325
  """
280
326
 
281
- assert format in ('x0y0x1y1','x0y0wh')
327
+ assert format in ('x0y0x1y1','x0y0wh'), 'Illegal rectangle format {}'.format(format)
282
328
 
283
329
  if format == 'x0y0wh':
284
330
  # Convert to x0y0x1y1 without modifying the original rectangles
@@ -312,18 +358,17 @@ def rect_distance(r1, r2, format='x0y0x1y1'):
312
358
  return 0.0
313
359
 
314
360
 
315
- def list_is_sorted(l):
316
- """
317
- Returns True if the list [l] is sorted, else False.
318
- """
319
-
320
- return all(l[i] <= l[i+1] for i in range(len(l)-1))
321
-
322
-
323
361
  def split_list_into_fixed_size_chunks(L,n):
324
362
  """
325
363
  Split the list or tuple L into chunks of size n (allowing chunks of size n-1 if necessary,
326
- i.e. len(L) does not have to be a multiple of n.
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
327
372
  """
328
373
 
329
374
  return [L[i * n:(i + 1) * n] for i in range((len(L) + n - 1) // n )]
@@ -332,11 +377,19 @@ def split_list_into_fixed_size_chunks(L,n):
332
377
  def split_list_into_n_chunks(L, n, chunk_strategy='greedy'):
333
378
  """
334
379
  Splits the list or tuple L into n equally-sized chunks (some chunks may be one
335
- element smaller than others, i.e. len(L) does not have to be a multiple of n.
380
+ element smaller than others, i.e. len(L) does not have to be a multiple of n).
336
381
 
337
382
  chunk_strategy can be "greedy" (default, if there are k samples per chunk, the first
338
383
  k go into the first chunk) or "balanced" (alternate between chunks when pulling
339
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
340
393
  """
341
394
 
342
395
  if chunk_strategy == 'greedy':
@@ -352,10 +405,35 @@ def split_list_into_n_chunks(L, n, chunk_strategy='greedy'):
352
405
  raise ValueError('Invalid chunk strategy: {}'.format(chunk_strategy))
353
406
 
354
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
+
355
424
  def sort_dictionary_by_value(d,sort_values=None,reverse=False):
356
425
  """
357
426
  Sorts the dictionary [d] by value. If sort_values is None, uses d.values(),
358
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]
359
437
  """
360
438
 
361
439
  if sort_values is None:
@@ -367,16 +445,22 @@ def sort_dictionary_by_value(d,sort_values=None,reverse=False):
367
445
 
368
446
  def invert_dictionary(d):
369
447
  """
370
- Create a new dictionary that maps d.values() to d.keys(). Does not check
371
- uniqueness.
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]
372
456
  """
373
457
 
374
458
  return {v: k for k, v in d.items()}
375
459
 
376
460
 
377
461
  def image_file_to_camera_folder(image_fn):
378
- """
379
- Remove common overflow folders (e.g. RECNX101, RECNX102) from paths, i.e. turn:
462
+ r"""
463
+ Removes common overflow folders (e.g. RECNX101, RECNX102) from paths, i.e. turn:
380
464
 
381
465
  a\b\c\RECNX101\image001.jpg
382
466
 
@@ -388,6 +472,12 @@ def image_file_to_camera_folder(image_fn):
388
472
  present.
389
473
 
390
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
391
481
  """
392
482
 
393
483
  import re
@@ -406,6 +496,95 @@ def image_file_to_camera_folder(image_fn):
406
496
  return camera_folder
407
497
 
408
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
+
409
588
  #%% Test drivers
410
589
 
411
590
  if False: