megadetector 5.0.8__py3-none-any.whl → 5.0.10__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 (190) 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 +65 -65
  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 +68 -54
  20. api/batch_processing/postprocessing/compare_batch_results.py +113 -43
  21. api/batch_processing/postprocessing/convert_output_format.py +41 -16
  22. api/batch_processing/postprocessing/load_api_results.py +16 -17
  23. api/batch_processing/postprocessing/md_to_coco.py +31 -21
  24. api/batch_processing/postprocessing/md_to_labelme.py +52 -22
  25. api/batch_processing/postprocessing/merge_detections.py +14 -14
  26. api/batch_processing/postprocessing/postprocess_batch_results.py +246 -174
  27. api/batch_processing/postprocessing/remap_detection_categories.py +32 -25
  28. api/batch_processing/postprocessing/render_detection_confusion_matrix.py +60 -27
  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 +242 -158
  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 +102 -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 -263
  71. data_management/coco_to_yolo.py +79 -58
  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 +62 -24
  76. data_management/databases/subset_json_db.py +24 -15
  77. data_management/generate_crops_from_cct.py +27 -45
  78. data_management/get_image_sizes.py +188 -162
  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 -158
  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 +7 -7
  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 +65 -24
  120. data_management/labelme_to_yolo.py +8 -8
  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 +13 -13
  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 +44 -110
  128. data_management/lila/generate_lila_per_image_labels.py +55 -42
  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 +96 -33
  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 +110 -97
  135. data_management/remap_coco_categories.py +83 -83
  136. data_management/remove_exif.py +58 -62
  137. data_management/resize_coco_dataset.py +30 -23
  138. data_management/wi_download_csv_to_coco.py +246 -239
  139. data_management/yolo_output_to_md_output.py +86 -73
  140. data_management/yolo_to_coco.py +300 -60
  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 +179 -113
  147. detection/run_inference_with_yolov5_val.py +108 -48
  148. detection/run_tiled_inference.py +111 -40
  149. detection/tf_detector.py +51 -29
  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 +228 -68
  155. md_utils/directory_listing.py +59 -64
  156. md_utils/md_tests.py +968 -871
  157. md_utils/path_utils.py +460 -134
  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 +176 -60
  163. md_utils/write_html_image_list.py +40 -33
  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 +597 -291
  168. md_visualization/visualize_db.py +76 -48
  169. md_visualization/visualize_detector_output.py +61 -42
  170. {megadetector-5.0.8.dist-info → megadetector-5.0.10.dist-info}/METADATA +13 -7
  171. megadetector-5.0.10.dist-info/RECORD +224 -0
  172. {megadetector-5.0.8.dist-info → megadetector-5.0.10.dist-info}/top_level.txt +1 -0
  173. taxonomy_mapping/__init__.py +0 -0
  174. taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +342 -335
  175. taxonomy_mapping/map_new_lila_datasets.py +154 -154
  176. taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -134
  177. taxonomy_mapping/preview_lila_taxonomy.py +591 -591
  178. taxonomy_mapping/retrieve_sample_image.py +12 -12
  179. taxonomy_mapping/simple_image_download.py +11 -11
  180. taxonomy_mapping/species_lookup.py +10 -10
  181. taxonomy_mapping/taxonomy_csv_checker.py +18 -18
  182. taxonomy_mapping/taxonomy_graph.py +47 -47
  183. taxonomy_mapping/validate_lila_category_mappings.py +83 -76
  184. data_management/cct_json_to_filename_json.py +0 -89
  185. data_management/cct_to_csv.py +0 -140
  186. data_management/databases/remove_corrupted_images_from_db.py +0 -191
  187. detection/detector_training/copy_checkpoints.py +0 -43
  188. megadetector-5.0.8.dist-info/RECORD +0 -205
  189. {megadetector-5.0.8.dist-info → megadetector-5.0.10.dist-info}/LICENSE +0 -0
  190. {megadetector-5.0.8.dist-info → megadetector-5.0.10.dist-info}/WHEEL +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]
@@ -51,9 +53,11 @@ def truncate_float(x, precision=3):
51
53
  before exporting to JSON.
52
54
 
53
55
  Args:
54
- x (float) Scalar to truncate
55
- precision (int) The number of significant digits to preserve, should be
56
- 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]
57
61
  """
58
62
 
59
63
  assert precision > 0
@@ -78,30 +82,42 @@ def truncate_float(x, precision=3):
78
82
  return math.floor(x * factor)/factor
79
83
 
80
84
 
81
- def args_to_object(args: argparse.Namespace, obj: object) -> None:
85
+ def args_to_object(args, obj):
82
86
  """
83
87
  Copies all fields from a Namespace (typically the output from parse_args) to an
84
88
  object. Skips fields starting with _. Does not check existence in the target
85
89
  object.
86
90
 
87
91
  Args:
88
- args: argparse.Namespace
89
- 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)
90
97
  """
91
98
 
92
99
  for n, v in inspect.getmembers(args):
93
100
  if not n.startswith('_'):
94
101
  setattr(obj, n, v)
95
102
 
103
+ return obj
104
+
96
105
 
97
106
  def pretty_print_object(obj, b_print=True):
98
107
  """
99
- 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]
100
116
  """
101
117
 
102
118
  # _ = pretty_print_object(obj)
103
119
 
104
- # Sloppy that I'm making a module-wide change here...
120
+ # TODO: it's sloppy that I'm making a module-wide change here.
105
121
  jsonpickle.set_encoder_options('json', sort_keys=True, indent=2)
106
122
  a = jsonpickle.encode(obj)
107
123
  s = '{}'.format(a)
@@ -110,12 +126,19 @@ def pretty_print_object(obj, b_print=True):
110
126
  return s
111
127
 
112
128
 
113
- def is_list_sorted(L,reverse=False):
129
+ def is_list_sorted(L, reverse=False):
114
130
  """
115
- 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.
116
132
 
117
133
  Calling is_list_sorted(L,reverse=True) is the same as calling
118
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
119
142
  """
120
143
 
121
144
  if reverse:
@@ -126,32 +149,27 @@ def is_list_sorted(L,reverse=False):
126
149
 
127
150
  def write_json(path, content, indent=1):
128
151
  """
129
- 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
130
158
  """
131
159
 
132
160
  with open(path, 'w') as f:
133
161
  json.dump(content, f, indent=indent)
134
162
 
135
163
 
136
- def is_image_file(s):
137
- """
138
- Checks a file's extension against a hard-coded set of image file extensions;
139
- return True if it appears to be an image.
140
- """
141
-
142
- ext = os.path.splitext(s)[1]
143
- return ext.lower() in image_extensions
144
-
145
-
146
164
  def convert_yolo_to_xywh(yolo_box):
147
165
  """
148
166
  Converts a YOLO format bounding box to [x_min, y_min, width_of_box, height_of_box].
149
167
 
150
168
  Args:
151
- 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]
152
170
 
153
171
  Returns:
154
- 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]
155
173
  """
156
174
 
157
175
  x_center, y_center, width_of_box, height_of_box = yolo_box
@@ -162,14 +180,14 @@ def convert_yolo_to_xywh(yolo_box):
162
180
 
163
181
  def convert_xywh_to_tf(api_box):
164
182
  """
165
- Converts an xywh bounding box to an [y_min, x_min, y_max, x_max] box that the TensorFlow
166
- 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.
167
185
 
168
186
  Args:
169
187
  api_box: bbox output by the batch processing API [x_min, y_min, width_of_box, height_of_box]
170
188
 
171
189
  Returns:
172
- 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]
173
191
  """
174
192
 
175
193
  x_min, y_min, width_of_box, height_of_box = api_box
@@ -180,15 +198,13 @@ def convert_xywh_to_tf(api_box):
180
198
 
181
199
  def convert_xywh_to_xyxy(api_bbox):
182
200
  """
183
- 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.
184
202
 
185
- Note that this is also different from the TensorFlow Object Detection API coords format.
186
-
187
203
  Args:
188
- 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]
189
205
 
190
206
  Returns:
191
- 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]
192
208
  """
193
209
 
194
210
  x_min, y_min, width_of_box, height_of_box = api_bbox
@@ -198,18 +214,18 @@ def convert_xywh_to_xyxy(api_bbox):
198
214
 
199
215
  def get_iou(bb1, bb2):
200
216
  """
201
- Calculates the Intersection over Union (IoU) of two bounding boxes.
217
+ Calculates the intersection over union (IoU) of two bounding boxes.
202
218
 
203
219
  Adapted from:
204
220
 
205
221
  https://stackoverflow.com/questions/25349178/calculating-percentage-of-bounding-box-overlap-for-image-detector-evaluation
206
222
 
207
223
  Args:
208
- bb1: [x_min, y_min, width_of_box, height_of_box]
209
- 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]
210
226
 
211
227
  Returns:
212
- intersection_over_union, a float in [0, 1]
228
+ float: intersection_over_union, a float in [0, 1]
213
229
  """
214
230
 
215
231
  bb1 = convert_xywh_to_xyxy(bb1)
@@ -261,9 +277,14 @@ def _get_max_conf_from_detections(detections):
261
277
 
262
278
  def get_max_conf(im):
263
279
  """
264
- Given an image dict in the format used by the batch API, compute the maximum detection
265
- confidence for any class. Returns 0.0 (not None) if there was a failure and 'detections'
266
- 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
267
288
  """
268
289
 
269
290
  max_conf = 0.0
@@ -274,7 +295,14 @@ def get_max_conf(im):
274
295
 
275
296
  def point_dist(p1,p2):
276
297
  """
277
- 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
278
306
  """
279
307
 
280
308
  return math.sqrt( ((p1[0]-p2[0])**2) + ((p1[1]-p2[1])**2) )
@@ -282,13 +310,21 @@ def point_dist(p1,p2):
282
310
 
283
311
  def rect_distance(r1, r2, format='x0y0x1y1'):
284
312
  """
285
- Minimum distance between two axis-aligned rectangles, each represented as
313
+ Computes the minimum distance between two axis-aligned rectangles, each represented as
286
314
  (x0,y0,x1,y1) by default.
287
315
 
288
- 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
289
325
  """
290
326
 
291
- assert format in ('x0y0x1y1','x0y0wh')
327
+ assert format in ('x0y0x1y1','x0y0wh'), 'Illegal rectangle format {}'.format(format)
292
328
 
293
329
  if format == 'x0y0wh':
294
330
  # Convert to x0y0x1y1 without modifying the original rectangles
@@ -322,18 +358,17 @@ def rect_distance(r1, r2, format='x0y0x1y1'):
322
358
  return 0.0
323
359
 
324
360
 
325
- def list_is_sorted(l):
326
- """
327
- Returns True if the list [l] is sorted, else False.
328
- """
329
-
330
- return all(l[i] <= l[i+1] for i in range(len(l)-1))
331
-
332
-
333
361
  def split_list_into_fixed_size_chunks(L,n):
334
362
  """
335
363
  Split the list or tuple L into chunks of size n (allowing chunks of size n-1 if necessary,
336
- 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
337
372
  """
338
373
 
339
374
  return [L[i * n:(i + 1) * n] for i in range((len(L) + n - 1) // n )]
@@ -342,11 +377,19 @@ def split_list_into_fixed_size_chunks(L,n):
342
377
  def split_list_into_n_chunks(L, n, chunk_strategy='greedy'):
343
378
  """
344
379
  Splits the list or tuple L into n equally-sized chunks (some chunks may be one
345
- 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).
346
381
 
347
382
  chunk_strategy can be "greedy" (default, if there are k samples per chunk, the first
348
383
  k go into the first chunk) or "balanced" (alternate between chunks when pulling
349
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
350
393
  """
351
394
 
352
395
  if chunk_strategy == 'greedy':
@@ -365,6 +408,13 @@ def split_list_into_n_chunks(L, n, chunk_strategy='greedy'):
365
408
  def sort_dictionary_by_key(d,reverse=False):
366
409
  """
367
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]
368
418
  """
369
419
 
370
420
  d = dict(sorted(d.items(),reverse=reverse))
@@ -375,6 +425,15 @@ def sort_dictionary_by_value(d,sort_values=None,reverse=False):
375
425
  """
376
426
  Sorts the dictionary [d] by value. If sort_values is None, uses d.values(),
377
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]
378
437
  """
379
438
 
380
439
  if sort_values is None:
@@ -386,16 +445,22 @@ def sort_dictionary_by_value(d,sort_values=None,reverse=False):
386
445
 
387
446
  def invert_dictionary(d):
388
447
  """
389
- Create a new dictionary that maps d.values() to d.keys(). Does not check
390
- 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]
391
456
  """
392
457
 
393
458
  return {v: k for k, v in d.items()}
394
459
 
395
460
 
396
461
  def image_file_to_camera_folder(image_fn):
397
- """
398
- 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:
399
464
 
400
465
  a\b\c\RECNX101\image001.jpg
401
466
 
@@ -407,6 +472,12 @@ def image_file_to_camera_folder(image_fn):
407
472
  present.
408
473
 
409
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
410
481
  """
411
482
 
412
483
  import re
@@ -425,6 +496,95 @@ def image_file_to_camera_folder(image_fn):
425
496
  return camera_folder
426
497
 
427
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
+
428
588
  #%% Test drivers
429
589
 
430
590
  if False: