megadetector 5.0.28__py3-none-any.whl → 10.0.0__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 (197) hide show
  1. megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +2 -2
  2. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +1 -1
  3. megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +1 -1
  4. megadetector/classification/aggregate_classifier_probs.py +3 -3
  5. megadetector/classification/analyze_failed_images.py +5 -5
  6. megadetector/classification/cache_batchapi_outputs.py +5 -5
  7. megadetector/classification/create_classification_dataset.py +11 -12
  8. megadetector/classification/crop_detections.py +10 -10
  9. megadetector/classification/csv_to_json.py +8 -8
  10. megadetector/classification/detect_and_crop.py +13 -15
  11. megadetector/classification/efficientnet/model.py +8 -8
  12. megadetector/classification/efficientnet/utils.py +6 -5
  13. megadetector/classification/evaluate_model.py +7 -7
  14. megadetector/classification/identify_mislabeled_candidates.py +6 -6
  15. megadetector/classification/json_to_azcopy_list.py +1 -1
  16. megadetector/classification/json_validator.py +29 -32
  17. megadetector/classification/map_classification_categories.py +9 -9
  18. megadetector/classification/merge_classification_detection_output.py +12 -9
  19. megadetector/classification/prepare_classification_script.py +19 -19
  20. megadetector/classification/prepare_classification_script_mc.py +26 -26
  21. megadetector/classification/run_classifier.py +4 -4
  22. megadetector/classification/save_mislabeled.py +6 -6
  23. megadetector/classification/train_classifier.py +1 -1
  24. megadetector/classification/train_classifier_tf.py +9 -9
  25. megadetector/classification/train_utils.py +10 -10
  26. megadetector/data_management/annotations/annotation_constants.py +1 -2
  27. megadetector/data_management/camtrap_dp_to_coco.py +79 -46
  28. megadetector/data_management/cct_json_utils.py +103 -103
  29. megadetector/data_management/cct_to_md.py +49 -49
  30. megadetector/data_management/cct_to_wi.py +33 -33
  31. megadetector/data_management/coco_to_labelme.py +75 -75
  32. megadetector/data_management/coco_to_yolo.py +210 -193
  33. megadetector/data_management/databases/add_width_and_height_to_db.py +86 -12
  34. megadetector/data_management/databases/combine_coco_camera_traps_files.py +40 -40
  35. megadetector/data_management/databases/integrity_check_json_db.py +228 -200
  36. megadetector/data_management/databases/subset_json_db.py +33 -33
  37. megadetector/data_management/generate_crops_from_cct.py +88 -39
  38. megadetector/data_management/get_image_sizes.py +54 -49
  39. megadetector/data_management/labelme_to_coco.py +133 -125
  40. megadetector/data_management/labelme_to_yolo.py +159 -73
  41. megadetector/data_management/lila/create_lila_blank_set.py +81 -83
  42. megadetector/data_management/lila/create_lila_test_set.py +32 -31
  43. megadetector/data_management/lila/create_links_to_md_results_files.py +18 -18
  44. megadetector/data_management/lila/download_lila_subset.py +21 -24
  45. megadetector/data_management/lila/generate_lila_per_image_labels.py +365 -107
  46. megadetector/data_management/lila/get_lila_annotation_counts.py +35 -33
  47. megadetector/data_management/lila/get_lila_image_counts.py +22 -22
  48. megadetector/data_management/lila/lila_common.py +73 -70
  49. megadetector/data_management/lila/test_lila_metadata_urls.py +28 -19
  50. megadetector/data_management/mewc_to_md.py +344 -340
  51. megadetector/data_management/ocr_tools.py +262 -255
  52. megadetector/data_management/read_exif.py +249 -227
  53. megadetector/data_management/remap_coco_categories.py +90 -28
  54. megadetector/data_management/remove_exif.py +81 -21
  55. megadetector/data_management/rename_images.py +187 -187
  56. megadetector/data_management/resize_coco_dataset.py +588 -120
  57. megadetector/data_management/speciesnet_to_md.py +41 -41
  58. megadetector/data_management/wi_download_csv_to_coco.py +55 -55
  59. megadetector/data_management/yolo_output_to_md_output.py +248 -122
  60. megadetector/data_management/yolo_to_coco.py +333 -191
  61. megadetector/detection/change_detection.py +832 -0
  62. megadetector/detection/process_video.py +340 -337
  63. megadetector/detection/pytorch_detector.py +358 -278
  64. megadetector/detection/run_detector.py +399 -186
  65. megadetector/detection/run_detector_batch.py +404 -377
  66. megadetector/detection/run_inference_with_yolov5_val.py +340 -327
  67. megadetector/detection/run_tiled_inference.py +257 -249
  68. megadetector/detection/tf_detector.py +24 -24
  69. megadetector/detection/video_utils.py +332 -295
  70. megadetector/postprocessing/add_max_conf.py +19 -11
  71. megadetector/postprocessing/categorize_detections_by_size.py +45 -45
  72. megadetector/postprocessing/classification_postprocessing.py +468 -433
  73. megadetector/postprocessing/combine_batch_outputs.py +23 -23
  74. megadetector/postprocessing/compare_batch_results.py +590 -525
  75. megadetector/postprocessing/convert_output_format.py +106 -102
  76. megadetector/postprocessing/create_crop_folder.py +347 -147
  77. megadetector/postprocessing/detector_calibration.py +173 -168
  78. megadetector/postprocessing/generate_csv_report.py +508 -499
  79. megadetector/postprocessing/load_api_results.py +48 -27
  80. megadetector/postprocessing/md_to_coco.py +133 -102
  81. megadetector/postprocessing/md_to_labelme.py +107 -90
  82. megadetector/postprocessing/md_to_wi.py +40 -40
  83. megadetector/postprocessing/merge_detections.py +92 -114
  84. megadetector/postprocessing/postprocess_batch_results.py +319 -301
  85. megadetector/postprocessing/remap_detection_categories.py +91 -38
  86. megadetector/postprocessing/render_detection_confusion_matrix.py +214 -205
  87. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +57 -57
  88. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +27 -28
  89. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +704 -679
  90. megadetector/postprocessing/separate_detections_into_folders.py +226 -211
  91. megadetector/postprocessing/subset_json_detector_output.py +265 -262
  92. megadetector/postprocessing/top_folders_to_bottom.py +45 -45
  93. megadetector/postprocessing/validate_batch_results.py +70 -70
  94. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +52 -52
  95. megadetector/taxonomy_mapping/map_new_lila_datasets.py +18 -19
  96. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +54 -33
  97. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +67 -67
  98. megadetector/taxonomy_mapping/retrieve_sample_image.py +16 -16
  99. megadetector/taxonomy_mapping/simple_image_download.py +8 -8
  100. megadetector/taxonomy_mapping/species_lookup.py +156 -74
  101. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +14 -14
  102. megadetector/taxonomy_mapping/taxonomy_graph.py +10 -10
  103. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +13 -13
  104. megadetector/utils/ct_utils.py +1049 -211
  105. megadetector/utils/directory_listing.py +21 -77
  106. megadetector/utils/gpu_test.py +22 -22
  107. megadetector/utils/md_tests.py +632 -529
  108. megadetector/utils/path_utils.py +1520 -431
  109. megadetector/utils/process_utils.py +41 -41
  110. megadetector/utils/split_locations_into_train_val.py +62 -62
  111. megadetector/utils/string_utils.py +148 -27
  112. megadetector/utils/url_utils.py +489 -176
  113. megadetector/utils/wi_utils.py +2658 -2526
  114. megadetector/utils/write_html_image_list.py +137 -137
  115. megadetector/visualization/plot_utils.py +34 -30
  116. megadetector/visualization/render_images_with_thumbnails.py +39 -74
  117. megadetector/visualization/visualization_utils.py +487 -435
  118. megadetector/visualization/visualize_db.py +232 -198
  119. megadetector/visualization/visualize_detector_output.py +82 -76
  120. {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/METADATA +5 -2
  121. megadetector-10.0.0.dist-info/RECORD +139 -0
  122. {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/WHEEL +1 -1
  123. megadetector/api/batch_processing/api_core/__init__.py +0 -0
  124. megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
  125. megadetector/api/batch_processing/api_core/batch_service/score.py +0 -439
  126. megadetector/api/batch_processing/api_core/server.py +0 -294
  127. megadetector/api/batch_processing/api_core/server_api_config.py +0 -97
  128. megadetector/api/batch_processing/api_core/server_app_config.py +0 -55
  129. megadetector/api/batch_processing/api_core/server_batch_job_manager.py +0 -220
  130. megadetector/api/batch_processing/api_core/server_job_status_table.py +0 -149
  131. megadetector/api/batch_processing/api_core/server_orchestration.py +0 -360
  132. megadetector/api/batch_processing/api_core/server_utils.py +0 -88
  133. megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
  134. megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +0 -46
  135. megadetector/api/batch_processing/api_support/__init__.py +0 -0
  136. megadetector/api/batch_processing/api_support/summarize_daily_activity.py +0 -152
  137. megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
  138. megadetector/api/synchronous/__init__.py +0 -0
  139. megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  140. megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +0 -151
  141. megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -263
  142. megadetector/api/synchronous/api_core/animal_detection_api/config.py +0 -35
  143. megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
  144. megadetector/api/synchronous/api_core/tests/load_test.py +0 -110
  145. megadetector/data_management/importers/add_nacti_sizes.py +0 -52
  146. megadetector/data_management/importers/add_timestamps_to_icct.py +0 -79
  147. megadetector/data_management/importers/animl_results_to_md_results.py +0 -158
  148. megadetector/data_management/importers/auckland_doc_test_to_json.py +0 -373
  149. megadetector/data_management/importers/auckland_doc_to_json.py +0 -201
  150. megadetector/data_management/importers/awc_to_json.py +0 -191
  151. megadetector/data_management/importers/bellevue_to_json.py +0 -272
  152. megadetector/data_management/importers/cacophony-thermal-importer.py +0 -793
  153. megadetector/data_management/importers/carrizo_shrubfree_2018.py +0 -269
  154. megadetector/data_management/importers/carrizo_trail_cam_2017.py +0 -289
  155. megadetector/data_management/importers/cct_field_adjustments.py +0 -58
  156. megadetector/data_management/importers/channel_islands_to_cct.py +0 -913
  157. megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
  158. megadetector/data_management/importers/eMammal/eMammal_helpers.py +0 -249
  159. megadetector/data_management/importers/eMammal/make_eMammal_json.py +0 -223
  160. megadetector/data_management/importers/ena24_to_json.py +0 -276
  161. megadetector/data_management/importers/filenames_to_json.py +0 -386
  162. megadetector/data_management/importers/helena_to_cct.py +0 -283
  163. megadetector/data_management/importers/idaho-camera-traps.py +0 -1407
  164. megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
  165. megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +0 -387
  166. megadetector/data_management/importers/jb_csv_to_json.py +0 -150
  167. megadetector/data_management/importers/mcgill_to_json.py +0 -250
  168. megadetector/data_management/importers/missouri_to_json.py +0 -490
  169. megadetector/data_management/importers/nacti_fieldname_adjustments.py +0 -79
  170. megadetector/data_management/importers/noaa_seals_2019.py +0 -181
  171. megadetector/data_management/importers/osu-small-animals-to-json.py +0 -364
  172. megadetector/data_management/importers/pc_to_json.py +0 -365
  173. megadetector/data_management/importers/plot_wni_giraffes.py +0 -123
  174. megadetector/data_management/importers/prepare_zsl_imerit.py +0 -131
  175. megadetector/data_management/importers/raic_csv_to_md_results.py +0 -416
  176. megadetector/data_management/importers/rspb_to_json.py +0 -356
  177. megadetector/data_management/importers/save_the_elephants_survey_A.py +0 -320
  178. megadetector/data_management/importers/save_the_elephants_survey_B.py +0 -329
  179. megadetector/data_management/importers/snapshot_safari_importer.py +0 -758
  180. megadetector/data_management/importers/snapshot_serengeti_lila.py +0 -1067
  181. megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
  182. megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
  183. megadetector/data_management/importers/sulross_get_exif.py +0 -65
  184. megadetector/data_management/importers/timelapse_csv_set_to_json.py +0 -490
  185. megadetector/data_management/importers/ubc_to_json.py +0 -399
  186. megadetector/data_management/importers/umn_to_json.py +0 -507
  187. megadetector/data_management/importers/wellington_to_json.py +0 -263
  188. megadetector/data_management/importers/wi_to_json.py +0 -442
  189. megadetector/data_management/importers/zamba_results_to_md_results.py +0 -180
  190. megadetector/data_management/lila/add_locations_to_island_camera_traps.py +0 -101
  191. megadetector/data_management/lila/add_locations_to_nacti.py +0 -151
  192. megadetector/utils/azure_utils.py +0 -178
  193. megadetector/utils/sas_blob_utils.py +0 -509
  194. megadetector-5.0.28.dist-info/RECORD +0 -209
  195. /megadetector/{api/batch_processing/__init__.py → __init__.py} +0 -0
  196. {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/licenses/LICENSE +0 -0
  197. {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/top_level.txt +0 -0
@@ -25,21 +25,21 @@ class CameraTrapJsonUtils:
25
25
  """
26
26
  Miscellaneous utility functions for working with COCO Camera Traps databases
27
27
  """
28
-
28
+
29
29
  @staticmethod
30
30
  def annotations_to_string(annotations, cat_id_to_name):
31
31
  """
32
32
  Given a list of annotations and a mapping from class IDs to names, produces
33
33
  a comma-delimited string containing a list of class names, sorted alphabetically.
34
-
34
+
35
35
  Args:
36
36
  annotations (list): a list of annotation dicts
37
37
  cat_id_to_name (dict): a dict mapping category IDs to category names
38
-
38
+
39
39
  Returns:
40
40
  str: a comma-delimited list of class names
41
41
  """
42
-
42
+
43
43
  class_names = CameraTrapJsonUtils.annotations_to_class_names(annotations, cat_id_to_name)
44
44
  return ','.join(class_names)
45
45
 
@@ -49,15 +49,15 @@ class CameraTrapJsonUtils:
49
49
  """
50
50
  Given a list of annotations and a mapping from class IDs to names, produces
51
51
  a list of class names, sorted alphabetically.
52
-
52
+
53
53
  Args:
54
54
  annotations (list): a list of annotation dicts
55
55
  cat_id_to_name (dict): a dict mapping category IDs to category names
56
-
56
+
57
57
  Returns:
58
58
  list: a list of class names present in [annotations]
59
59
  """
60
-
60
+
61
61
  # Collect all names
62
62
  class_names = [cat_id_to_name[ann['category_id']] for ann in annotations]
63
63
  # Make names unique and sort
@@ -77,10 +77,10 @@ class CameraTrapJsonUtils:
77
77
  db (dict): a JSON database in the COCO Camera Trap format
78
78
 
79
79
  Returns:
80
- dict: the same content as [db] but as an OrderedDict with keys ordered for
81
- readability
80
+ dict: the same content as [db] but as an OrderedDict with keys ordered for
81
+ readability
82
82
  """
83
-
83
+
84
84
  ordered = OrderedDict([
85
85
  ('info', db['info']),
86
86
  ('categories', db['categories']),
@@ -94,18 +94,18 @@ class CameraTrapJsonUtils:
94
94
  """
95
95
  Given an instance of IndexedJsonDb, group annotation entries by a field in the
96
96
  image entry. Typically used to find all the annotations associated with a sequence.
97
-
97
+
98
98
  Args:
99
- db_indexed (IndexedJsonDb): an initialized IndexedJsonDb, typically loaded from a
99
+ db_indexed (IndexedJsonDb): an initialized IndexedJsonDb, typically loaded from a
100
100
  COCO Camera Traps .json file
101
101
  image_field (str, optional): a field by which to group annotations (defaults
102
102
  to 'seq_id')
103
-
103
+
104
104
  Returns:
105
105
  dict: a dict mapping objects (typically strings, in fact typically sequence IDs) to
106
- lists of annotations
106
+ lists of annotations
107
107
  """
108
-
108
+
109
109
  image_id_to_image_field = {}
110
110
  for image_id, image_entry in db_indexed.image_id_to_image.items():
111
111
  image_id_to_image_field[image_id] = image_entry[image_field]
@@ -124,7 +124,7 @@ class CameraTrapJsonUtils:
124
124
  Given a dict representing a JSON database in the COCO Camera Trap format, returns a dict
125
125
  with the 'images' and 'annotations' fields in the CCT format, each is an array that only
126
126
  includes entries in the original [db] that are in the [locations] set.
127
-
127
+
128
128
  Args:
129
129
  db (dict): a dict representing a JSON database in the COCO Camera Trap format
130
130
  locations (set): a set or list of locations to include; each item is a string
@@ -132,7 +132,7 @@ class CameraTrapJsonUtils:
132
132
  Returns:
133
133
  dict: a dict with the 'images' and 'annotations' fields in the CCT format
134
134
  """
135
-
135
+
136
136
  locations = set(locations)
137
137
  print('Original DB has {} image and {} annotation entries.'.format(
138
138
  len(db['images']), len(db['annotations'])))
@@ -160,7 +160,7 @@ class IndexedJsonDb:
160
160
  a .json database.
161
161
  """
162
162
 
163
- def __init__(self,
163
+ def __init__(self,
164
164
  json_filename,
165
165
  b_normalize_paths=False,
166
166
  filename_replacements=None,
@@ -168,10 +168,10 @@ class IndexedJsonDb:
168
168
  b_force_forward_slashes=True):
169
169
  """
170
170
  Constructor for IndexedJsonDb that loads from a .json file or CCT-formatted dict.
171
-
171
+
172
172
  Args:
173
173
  json_filename (str): filename to load, or an already-loaded dict
174
- b_normalize_paths (bool, optional): whether to invoke os.path.normpath on
174
+ b_normalize_paths (bool, optional): whether to invoke os.path.normpath on
175
175
  all filenames. Not relevant if b_force_forward_slashes is True.
176
176
  filename_replacements (dict, optional): a set of string --> string mappings
177
177
  that will trigger replacements in all filenames, typically used to remove
@@ -179,9 +179,9 @@ class IndexedJsonDb:
179
179
  b_convert_classes_to_lower (bool, optional): whether to convert all class
180
180
  names to lowercase
181
181
  b_force_forward_slashes (bool, optional): whether to convert backslashes to
182
- forward slashes in all path names
182
+ forward slashes in all path names
183
183
  """
184
-
184
+
185
185
  if isinstance(json_filename, str):
186
186
  with open(json_filename) as f:
187
187
  self.db = json.load(f)
@@ -191,7 +191,7 @@ class IndexedJsonDb:
191
191
  assert 'images' in self.db, (
192
192
  f'Could not find image list in file {json_filename}, are you sure '
193
193
  'this is a COCO camera traps file?')
194
-
194
+
195
195
  if b_convert_classes_to_lower:
196
196
  # Convert classnames to lowercase to simplify comparisons later
197
197
  for c in self.db['categories']:
@@ -242,16 +242,16 @@ class IndexedJsonDb:
242
242
  def get_annotations_for_image(self, image):
243
243
  """
244
244
  Finds all the annnotations associated with the image dict [image].
245
-
245
+
246
246
  Args:
247
247
  image (dict): an image dict loaded from a CCT .json file. Only the 'id' field
248
248
  is used.
249
-
249
+
250
250
  Returns:
251
- list: list of annotations associated with this image. Returns None if the db
252
- has not been loaded, or [] if no annotations are available for this image.
251
+ list: list of annotations associated with this image. Returns None if the db
252
+ has not been loaded, or [] if no annotations are available for this image.
253
253
  """
254
-
254
+
255
255
  if self.db is None:
256
256
  return None
257
257
 
@@ -269,12 +269,12 @@ class IndexedJsonDb:
269
269
  Args:
270
270
  image (dict): an image dict loaded from a CCT .json file. Only the 'id' field
271
271
  is used.
272
-
272
+
273
273
  Returns:
274
- list: list of class names associated with this image. Returns None if the db
275
- has not been loaded, or [] if no annotations are available for this image.
274
+ list: list of class names associated with this image. Returns None if the db
275
+ has not been loaded, or [] if no annotations are available for this image.
276
276
  """
277
-
277
+
278
278
  if self.db is None:
279
279
  return None
280
280
 
@@ -296,27 +296,27 @@ class SequenceOptions:
296
296
  """
297
297
  Options parameterizing the grouping of images into sequences by time.
298
298
  """
299
-
299
+
300
300
  def __init__(self):
301
301
  #: Images separated by <= this duration will be grouped into the same sequence.
302
302
  self.episode_interval_seconds = 60.0
303
-
303
+
304
304
  #: How to handle invalid datetimes: 'error' or 'none'
305
305
  self.datetime_conversion_failure_behavior = 'none'
306
-
307
-
306
+
307
+
308
308
  #%% Functions
309
309
 
310
310
  def write_object_with_serialized_datetimes(d,json_fn):
311
311
  """
312
312
  Writes the object [d] to the .json file [json_fn] with a standard approach
313
313
  to serializing Python datetime objects.
314
-
314
+
315
315
  Args:
316
316
  d (obj): the object to write, typically a dict
317
- json_fn (str): the output filename
317
+ json_fn (str): the output filename
318
318
  """
319
-
319
+
320
320
  # This writes datetimes as:
321
321
  #
322
322
  # 2022-12-31T09:52:50
@@ -325,7 +325,7 @@ def write_object_with_serialized_datetimes(d,json_fn):
325
325
  return obj.isoformat()
326
326
  raise TypeError('Object {} (type {}) not serializable'.format(
327
327
  str(obj),type(obj)))
328
-
328
+
329
329
  with open(json_fn,'w') as f:
330
330
  json.dump(d,f,indent=1,default=json_serialize_datetime)
331
331
 
@@ -333,23 +333,23 @@ def write_object_with_serialized_datetimes(d,json_fn):
333
333
  def parse_datetimes_from_cct_image_list(images,conversion_failure_behavior='error'):
334
334
  """
335
335
  Given the "images" field from a COCO camera traps dictionary, converts all
336
- string-formatted datetime fields to Python datetimes, making reasonable assumptions
336
+ string-formatted datetime fields to Python datetimes, making reasonable assumptions
337
337
  about datetime representations. Modifies [images] in place.
338
-
338
+
339
339
  Args:
340
340
  images (list): a list of dicts in CCT images format
341
341
  conversion_failure_behavior (str, optional): determines what happens on a failed
342
342
  conversion; can be "error" (raise an error), "str" (leave as a string), or
343
343
  "none" (convert to None)
344
-
344
+
345
345
  Returns:
346
346
  images: the input list, with datetimes converted (after modifying in place)
347
347
  """
348
-
348
+
349
349
  assert isinstance(images,list)
350
-
350
+
351
351
  for im in images:
352
-
352
+
353
353
  if 'datetime' not in im:
354
354
  continue
355
355
  if isinstance(im['datetime'],datetime.datetime):
@@ -367,11 +367,11 @@ def parse_datetimes_from_cct_image_list(images,conversion_failure_behavior='erro
367
367
  elif conversion_failure_behavior == 'none':
368
368
  print('Warning: {}'.format(s))
369
369
  im['datetime'] = None
370
-
371
- # ...for each image
372
-
370
+
371
+ # ...for each image
372
+
373
373
  return images
374
-
374
+
375
375
  # ...def parse_datetimes_from_cct_image_list(...)
376
376
 
377
377
 
@@ -381,76 +381,76 @@ def parse_datetimes_from_cct_dict(d,conversion_failure_behavior='error'):
381
381
  converts all string-formatted datetime fields to Python datetimes, making
382
382
  reasonable assumptions about datetime representations. Modifies [d] in place
383
383
  if [d] is supplied as a dict
384
-
384
+
385
385
  Args:
386
386
  d (dict or str): a dict in CCT format or a filename pointing to a CCT .json file
387
387
  conversion_failure_behavior (str, optional): determines what happens on a failed
388
388
  conversion; can be "error" (raise an error), "str" (leave as a string), or
389
389
  "none" (convert to None)
390
-
390
+
391
391
  Returns:
392
392
  dict: the CCT dict with converted datetimes.
393
393
  """
394
-
395
- if isinstance(d,str):
394
+
395
+ if isinstance(d,str):
396
396
  assert os.path.isfile(d), 'Could not find .json file {}'.format(d)
397
397
  with open(d,'r') as f:
398
398
  d = json.load(f)
399
-
399
+
400
400
  images = d['images']
401
-
401
+
402
402
  # Modifies in place
403
403
  _ = parse_datetimes_from_cct_image_list(images)
404
-
404
+
405
405
  return d
406
-
406
+
407
407
  # ...def parse_datetimes_from_cct_dict(...)
408
408
 
409
409
 
410
410
  def create_sequences(image_info,options=None):
411
411
  """
412
412
  Synthesizes episodes/sequences/bursts for the images in [image_info].
413
-
414
- Modifies [image_info] in place, populating the 'seq_id', 'seq_num_frames', and 'frame_num'
413
+
414
+ Modifies [image_info] in place, populating the 'seq_id', 'seq_num_frames', and 'frame_num'
415
415
  fields for each image.
416
-
416
+
417
417
  Args:
418
- image_info (str, dict, or list): a dict in CCT format, a CCT .json file, or just the
419
- 'images' component of a CCT dataset (a list of dicts with fields 'file_name' (str),
418
+ image_info (str, dict, or list): a dict in CCT format, a CCT .json file, or just the
419
+ 'images' component of a CCT dataset (a list of dicts with fields 'file_name' (str),
420
420
  'datetime' (datetime), and 'location' (str)).
421
- options (SequenceOptions): options parameterizing the assembly of images into sequences;
422
- see the SequenceOptions class for details.
423
-
421
+ options (SequenceOptions, optional): options parameterizing the assembly of images into
422
+ sequences; see the SequenceOptions class for details.
423
+
424
424
  Returns:
425
425
  image_info: if [image_info] is passed as a list, returns the list, otherwise returns
426
- a CCT-formatted dict.
426
+ a CCT-formatted dict.
427
427
  """
428
-
428
+
429
429
  if options is None:
430
430
  options = SequenceOptions()
431
-
431
+
432
432
  to_return = None
433
-
433
+
434
434
  if isinstance(image_info,list):
435
435
  to_return = image_info
436
-
436
+
437
437
  elif isinstance(image_info,str):
438
438
  with open(image_info,'r') as f:
439
439
  d = json.load(f)
440
440
  to_return = d
441
441
  image_info = d['images']
442
-
442
+
443
443
  elif isinstance(image_info,dict):
444
444
  to_return = image_info
445
445
  image_info = image_info['images']
446
-
446
+
447
447
  else:
448
448
  raise ValueError('Unrecognized type for [image_info]')
449
-
449
+
450
450
  # Modifies the images in place
451
- _ = parse_datetimes_from_cct_image_list(image_info,
451
+ _ = parse_datetimes_from_cct_image_list(image_info,
452
452
  conversion_failure_behavior=options.datetime_conversion_failure_behavior)
453
-
453
+
454
454
  n_invalid_datetimes = 0
455
455
  for im in image_info:
456
456
  if not isinstance(im['datetime'],datetime.datetime):
@@ -459,50 +459,50 @@ def create_sequences(image_info,options=None):
459
459
  if n_invalid_datetimes > 0:
460
460
  print('Warning: {} of {} images have invalid datetimes'.format(
461
461
  n_invalid_datetimes,len(image_info)))
462
-
462
+
463
463
  # Find all unique locations
464
464
  locations = set()
465
465
  for im in image_info:
466
466
  locations.add(im['location'])
467
-
468
- print('Found {} locations'.format(len(locations)))
467
+
468
+ print('Found {} locations'.format(len(locations)))
469
469
  locations = list(locations)
470
470
  locations.sort()
471
-
471
+
472
472
  all_sequences = set()
473
-
473
+
474
474
  # i_location = 0; location = locations[i_location]
475
475
  for i_location,location in tqdm(enumerate(locations),total=len(locations)):
476
-
477
- images_this_location = [im for im in image_info if im['location'] == location]
478
-
479
- # Sorting datetimes fails when there are None's in the list. So instead of sorting datetimes
476
+
477
+ images_this_location = [im for im in image_info if im['location'] == location]
478
+
479
+ # Sorting datetimes fails when there are None's in the list. So instead of sorting datetimes
480
480
  # directly, sort tuples with a boolean for none-ness, then the datetime itself.
481
481
  #
482
482
  # https://stackoverflow.com/questions/18411560/sort-list-while-pushing-none-values-to-the-end
483
- sorted_images_this_location = sorted(images_this_location,
483
+ sorted_images_this_location = sorted(images_this_location,
484
484
  key = lambda im: (im['datetime'] is None,im['datetime']))
485
-
485
+
486
486
  sequence_id_to_images_this_location = defaultdict(list)
487
487
 
488
488
  current_sequence_id = None
489
489
  next_frame_number = 0
490
490
  next_sequence_number = 0
491
491
  previous_datetime = None
492
-
492
+
493
493
  # previous_datetime = sorted_images_this_location[0]['datetime']
494
494
  # im = sorted_images_this_location[1]
495
495
  for im in sorted_images_this_location:
496
-
496
+
497
497
  invalid_datetime = False
498
-
498
+
499
499
  if previous_datetime is None:
500
500
  delta = None
501
501
  elif im['datetime'] is None:
502
502
  invalid_datetime = True
503
503
  else:
504
504
  delta = (im['datetime'] - previous_datetime).total_seconds()
505
-
505
+
506
506
  # Start a new sequence if necessary, including the case where this datetime is invalid
507
507
  if delta is None or delta > options.episode_interval_seconds or invalid_datetime:
508
508
  next_frame_number = 0
@@ -510,20 +510,20 @@ def create_sequences(image_info,options=None):
510
510
  location,str(next_sequence_number).zfill(5))
511
511
  next_sequence_number = next_sequence_number + 1
512
512
  assert current_sequence_id not in all_sequences
513
- all_sequences.add(current_sequence_id)
514
-
513
+ all_sequences.add(current_sequence_id)
514
+
515
515
  im['seq_id'] = current_sequence_id
516
516
  im['seq_num_frames'] = None
517
517
  im['frame_num'] = next_frame_number
518
518
  sequence_id_to_images_this_location[current_sequence_id].append(im)
519
519
  next_frame_number = next_frame_number + 1
520
-
520
+
521
521
  # If this was an invalid datetime, this will record the previous datetime
522
522
  # as None, which will force the next image to start a new sequence.
523
523
  previous_datetime = im['datetime']
524
-
524
+
525
525
  # ...for each image in this location
526
-
526
+
527
527
  # Fill in seq_num_frames
528
528
  for seq_id in sequence_id_to_images_this_location.keys():
529
529
  assert seq_id in sequence_id_to_images_this_location
@@ -531,11 +531,11 @@ def create_sequences(image_info,options=None):
531
531
  assert len(images_this_sequence) > 0
532
532
  for im in images_this_sequence:
533
533
  im['seq_num_frames'] = len(images_this_sequence)
534
-
534
+
535
535
  # ...for each location
536
-
536
+
537
537
  print('Created {} sequences from {} images'.format(len(all_sequences),len(image_info)))
538
-
538
+
539
539
  return to_return
540
540
 
541
541
  # ...def create_sequences(...)
@@ -544,10 +544,10 @@ def create_sequences(image_info,options=None):
544
544
  #%% Test drivers
545
545
 
546
546
  if False:
547
-
547
+
548
548
  pass
549
549
 
550
- #%%
551
-
550
+ #%%
551
+
552
552
  fn = r'g:\temp\test.json'
553
- d = parse_datetimes_from_cct_dict(fn,conversion_failure_behavior='error')
553
+ d = parse_datetimes_from_cct_dict(fn,conversion_failure_behavior='error')
@@ -28,92 +28,92 @@ def cct_to_md(input_filename,output_filename=None):
28
28
  """
29
29
  "Converts" a COCO Camera Traps file to a MD results file. Currently ignores
30
30
  non-bounding-box annotations. If the semi-standard "score" field is present in
31
- an annotation, or the totally non-standard "conf" field is present, it will be
31
+ an annotation, or the totally non-standard "conf" field is present, it will be
32
32
  transferred to the output, otherwise a confidence value of 1.0 is assumed for
33
33
  all annotations.
34
34
 
35
- The main reason to run this script the scenario where you are going to add information
35
+ The main reason to run this script the scenario where you are going to add information
36
36
  to an existing CCT-formatted dataset, and you want to do that in Timelapse.
37
37
 
38
38
  Currently assumes that width and height are present in the input data, does not
39
39
  read them from images.
40
-
40
+
41
41
  Args:
42
42
  input_filename (str): the COCO Camera Traps .json file to read
43
43
  output_filename (str, optional): the .json file to write in MD results format
44
-
44
+
45
45
  Returns:
46
46
  dict: MD-formatted results, identical to the content of [output_filename] if
47
47
  [output_filename] is not None
48
48
  """
49
-
49
+
50
50
  ## Validate input
51
-
51
+
52
52
  assert os.path.isfile(input_filename)
53
-
53
+
54
54
  if (output_filename is None):
55
-
55
+
56
56
  tokens = os.path.splitext(input_filename)
57
57
  assert len(tokens) == 2
58
58
  output_filename = tokens[0] + '_md-format' + tokens[1]
59
-
60
-
59
+
60
+
61
61
  ## Read input
62
-
62
+
63
63
  with open(input_filename,'r') as f:
64
64
  d = json.load(f)
65
-
65
+
66
66
  for s in ['annotations','images','categories']:
67
67
  assert s in d.keys(), 'Cannot find category {} in input file, is this a CCT file?'.format(s)
68
-
69
-
68
+
69
+
70
70
  ## Prepare metadata
71
-
71
+
72
72
  image_id_to_annotations = defaultdict(list)
73
-
73
+
74
74
  # ann = d['annotations'][0]
75
75
  for ann in tqdm(d['annotations']):
76
76
  image_id_to_annotations[ann['image_id']].append(ann)
77
-
77
+
78
78
  category_id_to_name = {}
79
79
  for cat in d['categories']:
80
80
  category_id_to_name[str(cat['id'])] = cat['name']
81
-
81
+
82
82
  results = {}
83
-
83
+
84
84
  info = {}
85
85
  info['format_version'] = '1.4'
86
86
  info['detector'] = 'cct_to_md'
87
87
  results['info'] = info
88
88
  results['detection_categories'] = category_id_to_name
89
-
90
-
89
+
90
+
91
91
  ## Process images
92
-
92
+
93
93
  images_out = []
94
-
94
+
95
95
  # im = d['images'][0]
96
96
  for im in tqdm(d['images']):
97
-
97
+
98
98
  im_out = {}
99
99
  im_out['file'] = im['file_name']
100
100
  im_out['location'] = im['location']
101
101
  im_out['id'] = im['id']
102
-
102
+
103
103
  image_h = im['height']
104
104
  image_w = im['width']
105
-
105
+
106
106
  detections = []
107
-
107
+
108
108
  annotations_this_image = image_id_to_annotations[im['id']]
109
-
109
+
110
110
  # This field is no longer included in MD output files by default
111
111
  # max_detection_conf = 0
112
-
112
+
113
113
  for ann in annotations_this_image:
114
-
114
+
115
115
  if 'bbox' in ann:
116
-
116
+
117
117
  det = {}
118
118
  det['category'] = str(ann['category_id'])
119
119
  if 'score' in ann:
@@ -122,7 +122,7 @@ def cct_to_md(input_filename,output_filename=None):
122
122
  det['conf'] = ann['conf']
123
123
  else:
124
124
  det['conf'] = 1.0
125
-
125
+
126
126
  # MegaDetector: [x,y,width,height] (normalized, origin upper-left)
127
127
  # CCT: [x,y,width,height] (absolute, origin upper-left)
128
128
  bbox_in = ann['bbox']
@@ -130,37 +130,37 @@ def cct_to_md(input_filename,output_filename=None):
130
130
  bbox_in[2]/image_w,bbox_in[3]/image_h]
131
131
  det['bbox'] = bbox_out
132
132
  detections.append(det)
133
-
133
+
134
134
  # ...if there's a bounding box
135
-
135
+
136
136
  # ...for each annotation
137
-
137
+
138
138
  im_out['detections'] = detections
139
-
139
+
140
140
  # This field is no longer included in MD output files by default
141
141
  # im_out['max_detection_conf'] = max_detection_conf
142
-
142
+
143
143
  images_out.append(im_out)
144
-
144
+
145
145
  # ...for each image
146
-
147
-
146
+
147
+
148
148
  ## Write output
149
-
149
+
150
150
  results['images'] = images_out
151
-
151
+
152
152
  with open(output_filename,'w') as f:
153
153
  json.dump(results, f, indent=1)
154
-
154
+
155
155
  return output_filename
156
156
 
157
- # ...cct_to_md()
158
-
157
+ # ...cct_to_md()
158
+
159
159
 
160
160
  #%% Interactive driver
161
161
 
162
162
  if False:
163
-
163
+
164
164
  pass
165
165
 
166
166
  #%%
@@ -168,11 +168,11 @@ if False:
168
168
  input_filename = r"G:\temp\noaa_estuary_fish.json"
169
169
  output_filename = None
170
170
  output_filename = cct_to_md(input_filename,output_filename)
171
-
171
+
172
172
  #%%
173
-
173
+
174
174
  from megadetector.visualization import visualize_detector_output
175
-
175
+
176
176
  visualize_detector_output.visualize_detector_output(
177
177
  detector_output_path=output_filename,
178
178
  out_dir=r'g:\temp\fish_output',