megadetector 5.0.27__py3-none-any.whl → 5.0.29__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 (176) hide show
  1. megadetector/api/batch_processing/api_core/batch_service/score.py +4 -5
  2. megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +1 -1
  3. megadetector/api/batch_processing/api_support/summarize_daily_activity.py +1 -1
  4. megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +2 -2
  5. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +1 -1
  6. megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +1 -1
  7. megadetector/api/synchronous/api_core/tests/load_test.py +2 -3
  8. megadetector/classification/aggregate_classifier_probs.py +3 -3
  9. megadetector/classification/analyze_failed_images.py +5 -5
  10. megadetector/classification/cache_batchapi_outputs.py +5 -5
  11. megadetector/classification/create_classification_dataset.py +11 -12
  12. megadetector/classification/crop_detections.py +10 -10
  13. megadetector/classification/csv_to_json.py +8 -8
  14. megadetector/classification/detect_and_crop.py +13 -15
  15. megadetector/classification/evaluate_model.py +7 -7
  16. megadetector/classification/identify_mislabeled_candidates.py +6 -6
  17. megadetector/classification/json_to_azcopy_list.py +1 -1
  18. megadetector/classification/json_validator.py +29 -32
  19. megadetector/classification/map_classification_categories.py +9 -9
  20. megadetector/classification/merge_classification_detection_output.py +12 -9
  21. megadetector/classification/prepare_classification_script.py +19 -19
  22. megadetector/classification/prepare_classification_script_mc.py +23 -23
  23. megadetector/classification/run_classifier.py +4 -4
  24. megadetector/classification/save_mislabeled.py +6 -6
  25. megadetector/classification/train_classifier.py +1 -1
  26. megadetector/classification/train_classifier_tf.py +9 -9
  27. megadetector/classification/train_utils.py +10 -10
  28. megadetector/data_management/annotations/annotation_constants.py +1 -1
  29. megadetector/data_management/camtrap_dp_to_coco.py +45 -45
  30. megadetector/data_management/cct_json_utils.py +101 -101
  31. megadetector/data_management/cct_to_md.py +49 -49
  32. megadetector/data_management/cct_to_wi.py +33 -33
  33. megadetector/data_management/coco_to_labelme.py +75 -75
  34. megadetector/data_management/coco_to_yolo.py +189 -189
  35. megadetector/data_management/databases/add_width_and_height_to_db.py +3 -2
  36. megadetector/data_management/databases/combine_coco_camera_traps_files.py +38 -38
  37. megadetector/data_management/databases/integrity_check_json_db.py +202 -188
  38. megadetector/data_management/databases/subset_json_db.py +33 -33
  39. megadetector/data_management/generate_crops_from_cct.py +38 -38
  40. megadetector/data_management/get_image_sizes.py +54 -49
  41. megadetector/data_management/labelme_to_coco.py +130 -124
  42. megadetector/data_management/labelme_to_yolo.py +78 -72
  43. megadetector/data_management/lila/create_lila_blank_set.py +81 -83
  44. megadetector/data_management/lila/create_lila_test_set.py +32 -31
  45. megadetector/data_management/lila/create_links_to_md_results_files.py +18 -18
  46. megadetector/data_management/lila/download_lila_subset.py +21 -24
  47. megadetector/data_management/lila/generate_lila_per_image_labels.py +91 -91
  48. megadetector/data_management/lila/get_lila_annotation_counts.py +30 -30
  49. megadetector/data_management/lila/get_lila_image_counts.py +22 -22
  50. megadetector/data_management/lila/lila_common.py +70 -70
  51. megadetector/data_management/lila/test_lila_metadata_urls.py +13 -14
  52. megadetector/data_management/mewc_to_md.py +339 -340
  53. megadetector/data_management/ocr_tools.py +258 -252
  54. megadetector/data_management/read_exif.py +232 -223
  55. megadetector/data_management/remap_coco_categories.py +26 -26
  56. megadetector/data_management/remove_exif.py +31 -20
  57. megadetector/data_management/rename_images.py +187 -187
  58. megadetector/data_management/resize_coco_dataset.py +41 -41
  59. megadetector/data_management/speciesnet_to_md.py +41 -41
  60. megadetector/data_management/wi_download_csv_to_coco.py +55 -55
  61. megadetector/data_management/yolo_output_to_md_output.py +117 -120
  62. megadetector/data_management/yolo_to_coco.py +195 -188
  63. megadetector/detection/change_detection.py +831 -0
  64. megadetector/detection/process_video.py +341 -338
  65. megadetector/detection/pytorch_detector.py +308 -266
  66. megadetector/detection/run_detector.py +186 -166
  67. megadetector/detection/run_detector_batch.py +366 -364
  68. megadetector/detection/run_inference_with_yolov5_val.py +328 -325
  69. megadetector/detection/run_tiled_inference.py +312 -253
  70. megadetector/detection/tf_detector.py +24 -24
  71. megadetector/detection/video_utils.py +291 -283
  72. megadetector/postprocessing/add_max_conf.py +15 -11
  73. megadetector/postprocessing/categorize_detections_by_size.py +44 -44
  74. megadetector/postprocessing/classification_postprocessing.py +808 -311
  75. megadetector/postprocessing/combine_batch_outputs.py +20 -21
  76. megadetector/postprocessing/compare_batch_results.py +528 -517
  77. megadetector/postprocessing/convert_output_format.py +97 -97
  78. megadetector/postprocessing/create_crop_folder.py +220 -147
  79. megadetector/postprocessing/detector_calibration.py +173 -168
  80. megadetector/postprocessing/generate_csv_report.py +508 -0
  81. megadetector/postprocessing/load_api_results.py +25 -22
  82. megadetector/postprocessing/md_to_coco.py +129 -98
  83. megadetector/postprocessing/md_to_labelme.py +89 -83
  84. megadetector/postprocessing/md_to_wi.py +40 -40
  85. megadetector/postprocessing/merge_detections.py +87 -114
  86. megadetector/postprocessing/postprocess_batch_results.py +319 -302
  87. megadetector/postprocessing/remap_detection_categories.py +36 -36
  88. megadetector/postprocessing/render_detection_confusion_matrix.py +205 -199
  89. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +57 -57
  90. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +27 -28
  91. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +702 -677
  92. megadetector/postprocessing/separate_detections_into_folders.py +226 -211
  93. megadetector/postprocessing/subset_json_detector_output.py +265 -262
  94. megadetector/postprocessing/top_folders_to_bottom.py +45 -45
  95. megadetector/postprocessing/validate_batch_results.py +70 -70
  96. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +52 -52
  97. megadetector/taxonomy_mapping/map_new_lila_datasets.py +15 -15
  98. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +14 -14
  99. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +66 -69
  100. megadetector/taxonomy_mapping/retrieve_sample_image.py +16 -16
  101. megadetector/taxonomy_mapping/simple_image_download.py +8 -8
  102. megadetector/taxonomy_mapping/species_lookup.py +33 -33
  103. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +14 -14
  104. megadetector/taxonomy_mapping/taxonomy_graph.py +11 -11
  105. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +13 -13
  106. megadetector/utils/azure_utils.py +22 -22
  107. megadetector/utils/ct_utils.py +1019 -200
  108. megadetector/utils/directory_listing.py +21 -77
  109. megadetector/utils/gpu_test.py +22 -22
  110. megadetector/utils/md_tests.py +541 -518
  111. megadetector/utils/path_utils.py +1511 -406
  112. megadetector/utils/process_utils.py +41 -41
  113. megadetector/utils/sas_blob_utils.py +53 -49
  114. megadetector/utils/split_locations_into_train_val.py +73 -60
  115. megadetector/utils/string_utils.py +147 -26
  116. megadetector/utils/url_utils.py +463 -173
  117. megadetector/utils/wi_utils.py +2629 -2868
  118. megadetector/utils/write_html_image_list.py +137 -137
  119. megadetector/visualization/plot_utils.py +21 -21
  120. megadetector/visualization/render_images_with_thumbnails.py +37 -73
  121. megadetector/visualization/visualization_utils.py +424 -404
  122. megadetector/visualization/visualize_db.py +197 -190
  123. megadetector/visualization/visualize_detector_output.py +126 -98
  124. {megadetector-5.0.27.dist-info → megadetector-5.0.29.dist-info}/METADATA +6 -3
  125. megadetector-5.0.29.dist-info/RECORD +163 -0
  126. {megadetector-5.0.27.dist-info → megadetector-5.0.29.dist-info}/WHEEL +1 -1
  127. megadetector/data_management/importers/add_nacti_sizes.py +0 -52
  128. megadetector/data_management/importers/add_timestamps_to_icct.py +0 -79
  129. megadetector/data_management/importers/animl_results_to_md_results.py +0 -158
  130. megadetector/data_management/importers/auckland_doc_test_to_json.py +0 -373
  131. megadetector/data_management/importers/auckland_doc_to_json.py +0 -201
  132. megadetector/data_management/importers/awc_to_json.py +0 -191
  133. megadetector/data_management/importers/bellevue_to_json.py +0 -272
  134. megadetector/data_management/importers/cacophony-thermal-importer.py +0 -793
  135. megadetector/data_management/importers/carrizo_shrubfree_2018.py +0 -269
  136. megadetector/data_management/importers/carrizo_trail_cam_2017.py +0 -289
  137. megadetector/data_management/importers/cct_field_adjustments.py +0 -58
  138. megadetector/data_management/importers/channel_islands_to_cct.py +0 -913
  139. megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
  140. megadetector/data_management/importers/eMammal/eMammal_helpers.py +0 -249
  141. megadetector/data_management/importers/eMammal/make_eMammal_json.py +0 -223
  142. megadetector/data_management/importers/ena24_to_json.py +0 -276
  143. megadetector/data_management/importers/filenames_to_json.py +0 -386
  144. megadetector/data_management/importers/helena_to_cct.py +0 -283
  145. megadetector/data_management/importers/idaho-camera-traps.py +0 -1407
  146. megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
  147. megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +0 -387
  148. megadetector/data_management/importers/jb_csv_to_json.py +0 -150
  149. megadetector/data_management/importers/mcgill_to_json.py +0 -250
  150. megadetector/data_management/importers/missouri_to_json.py +0 -490
  151. megadetector/data_management/importers/nacti_fieldname_adjustments.py +0 -79
  152. megadetector/data_management/importers/noaa_seals_2019.py +0 -181
  153. megadetector/data_management/importers/osu-small-animals-to-json.py +0 -364
  154. megadetector/data_management/importers/pc_to_json.py +0 -365
  155. megadetector/data_management/importers/plot_wni_giraffes.py +0 -123
  156. megadetector/data_management/importers/prepare_zsl_imerit.py +0 -131
  157. megadetector/data_management/importers/raic_csv_to_md_results.py +0 -416
  158. megadetector/data_management/importers/rspb_to_json.py +0 -356
  159. megadetector/data_management/importers/save_the_elephants_survey_A.py +0 -320
  160. megadetector/data_management/importers/save_the_elephants_survey_B.py +0 -329
  161. megadetector/data_management/importers/snapshot_safari_importer.py +0 -758
  162. megadetector/data_management/importers/snapshot_serengeti_lila.py +0 -1067
  163. megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
  164. megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
  165. megadetector/data_management/importers/sulross_get_exif.py +0 -65
  166. megadetector/data_management/importers/timelapse_csv_set_to_json.py +0 -490
  167. megadetector/data_management/importers/ubc_to_json.py +0 -399
  168. megadetector/data_management/importers/umn_to_json.py +0 -507
  169. megadetector/data_management/importers/wellington_to_json.py +0 -263
  170. megadetector/data_management/importers/wi_to_json.py +0 -442
  171. megadetector/data_management/importers/zamba_results_to_md_results.py +0 -180
  172. megadetector/data_management/lila/add_locations_to_island_camera_traps.py +0 -101
  173. megadetector/data_management/lila/add_locations_to_nacti.py +0 -151
  174. megadetector-5.0.27.dist-info/RECORD +0 -208
  175. {megadetector-5.0.27.dist-info → megadetector-5.0.29.dist-info}/licenses/LICENSE +0 -0
  176. {megadetector-5.0.27.dist-info → megadetector-5.0.29.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
421
  options (SequenceOptions): options parameterizing the assembly of images into sequences;
422
422
  see the SequenceOptions class for details.
423
-
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',