megadetector 5.0.11__py3-none-any.whl → 5.0.13__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 (203) hide show
  1. megadetector/api/__init__.py +0 -0
  2. megadetector/api/batch_processing/__init__.py +0 -0
  3. megadetector/api/batch_processing/api_core/__init__.py +0 -0
  4. megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
  5. megadetector/api/batch_processing/api_core/batch_service/score.py +439 -0
  6. megadetector/api/batch_processing/api_core/server.py +294 -0
  7. megadetector/api/batch_processing/api_core/server_api_config.py +97 -0
  8. megadetector/api/batch_processing/api_core/server_app_config.py +55 -0
  9. megadetector/api/batch_processing/api_core/server_batch_job_manager.py +220 -0
  10. megadetector/api/batch_processing/api_core/server_job_status_table.py +149 -0
  11. megadetector/api/batch_processing/api_core/server_orchestration.py +360 -0
  12. megadetector/api/batch_processing/api_core/server_utils.py +88 -0
  13. megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
  14. megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +46 -0
  15. megadetector/api/batch_processing/api_support/__init__.py +0 -0
  16. megadetector/api/batch_processing/api_support/summarize_daily_activity.py +152 -0
  17. megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
  18. megadetector/api/batch_processing/integration/digiKam/setup.py +6 -0
  19. megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +465 -0
  20. megadetector/api/batch_processing/integration/eMammal/test_scripts/config_template.py +5 -0
  21. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +125 -0
  22. megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +55 -0
  23. megadetector/api/synchronous/__init__.py +0 -0
  24. megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  25. megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +152 -0
  26. megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +263 -0
  27. megadetector/api/synchronous/api_core/animal_detection_api/config.py +35 -0
  28. megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
  29. megadetector/api/synchronous/api_core/tests/load_test.py +110 -0
  30. megadetector/classification/__init__.py +0 -0
  31. megadetector/classification/aggregate_classifier_probs.py +108 -0
  32. megadetector/classification/analyze_failed_images.py +227 -0
  33. megadetector/classification/cache_batchapi_outputs.py +198 -0
  34. megadetector/classification/create_classification_dataset.py +627 -0
  35. megadetector/classification/crop_detections.py +516 -0
  36. megadetector/classification/csv_to_json.py +226 -0
  37. megadetector/classification/detect_and_crop.py +855 -0
  38. megadetector/classification/efficientnet/__init__.py +9 -0
  39. megadetector/classification/efficientnet/model.py +415 -0
  40. megadetector/classification/efficientnet/utils.py +607 -0
  41. megadetector/classification/evaluate_model.py +520 -0
  42. megadetector/classification/identify_mislabeled_candidates.py +152 -0
  43. megadetector/classification/json_to_azcopy_list.py +63 -0
  44. megadetector/classification/json_validator.py +699 -0
  45. megadetector/classification/map_classification_categories.py +276 -0
  46. megadetector/classification/merge_classification_detection_output.py +506 -0
  47. megadetector/classification/prepare_classification_script.py +194 -0
  48. megadetector/classification/prepare_classification_script_mc.py +228 -0
  49. megadetector/classification/run_classifier.py +287 -0
  50. megadetector/classification/save_mislabeled.py +110 -0
  51. megadetector/classification/train_classifier.py +827 -0
  52. megadetector/classification/train_classifier_tf.py +725 -0
  53. megadetector/classification/train_utils.py +323 -0
  54. megadetector/data_management/__init__.py +0 -0
  55. megadetector/data_management/annotations/__init__.py +0 -0
  56. megadetector/data_management/annotations/annotation_constants.py +34 -0
  57. megadetector/data_management/camtrap_dp_to_coco.py +237 -0
  58. megadetector/data_management/cct_json_utils.py +404 -0
  59. megadetector/data_management/cct_to_md.py +176 -0
  60. megadetector/data_management/cct_to_wi.py +289 -0
  61. megadetector/data_management/coco_to_labelme.py +283 -0
  62. megadetector/data_management/coco_to_yolo.py +662 -0
  63. megadetector/data_management/databases/__init__.py +0 -0
  64. megadetector/data_management/databases/add_width_and_height_to_db.py +33 -0
  65. megadetector/data_management/databases/combine_coco_camera_traps_files.py +206 -0
  66. megadetector/data_management/databases/integrity_check_json_db.py +493 -0
  67. megadetector/data_management/databases/subset_json_db.py +115 -0
  68. megadetector/data_management/generate_crops_from_cct.py +149 -0
  69. megadetector/data_management/get_image_sizes.py +189 -0
  70. megadetector/data_management/importers/add_nacti_sizes.py +52 -0
  71. megadetector/data_management/importers/add_timestamps_to_icct.py +79 -0
  72. megadetector/data_management/importers/animl_results_to_md_results.py +158 -0
  73. megadetector/data_management/importers/auckland_doc_test_to_json.py +373 -0
  74. megadetector/data_management/importers/auckland_doc_to_json.py +201 -0
  75. megadetector/data_management/importers/awc_to_json.py +191 -0
  76. megadetector/data_management/importers/bellevue_to_json.py +273 -0
  77. megadetector/data_management/importers/cacophony-thermal-importer.py +793 -0
  78. megadetector/data_management/importers/carrizo_shrubfree_2018.py +269 -0
  79. megadetector/data_management/importers/carrizo_trail_cam_2017.py +289 -0
  80. megadetector/data_management/importers/cct_field_adjustments.py +58 -0
  81. megadetector/data_management/importers/channel_islands_to_cct.py +913 -0
  82. megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +180 -0
  83. megadetector/data_management/importers/eMammal/eMammal_helpers.py +249 -0
  84. megadetector/data_management/importers/eMammal/make_eMammal_json.py +223 -0
  85. megadetector/data_management/importers/ena24_to_json.py +276 -0
  86. megadetector/data_management/importers/filenames_to_json.py +386 -0
  87. megadetector/data_management/importers/helena_to_cct.py +283 -0
  88. megadetector/data_management/importers/idaho-camera-traps.py +1407 -0
  89. megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +294 -0
  90. megadetector/data_management/importers/jb_csv_to_json.py +150 -0
  91. megadetector/data_management/importers/mcgill_to_json.py +250 -0
  92. megadetector/data_management/importers/missouri_to_json.py +490 -0
  93. megadetector/data_management/importers/nacti_fieldname_adjustments.py +79 -0
  94. megadetector/data_management/importers/noaa_seals_2019.py +181 -0
  95. megadetector/data_management/importers/pc_to_json.py +365 -0
  96. megadetector/data_management/importers/plot_wni_giraffes.py +123 -0
  97. megadetector/data_management/importers/prepare-noaa-fish-data-for-lila.py +359 -0
  98. megadetector/data_management/importers/prepare_zsl_imerit.py +131 -0
  99. megadetector/data_management/importers/rspb_to_json.py +356 -0
  100. megadetector/data_management/importers/save_the_elephants_survey_A.py +320 -0
  101. megadetector/data_management/importers/save_the_elephants_survey_B.py +329 -0
  102. megadetector/data_management/importers/snapshot_safari_importer.py +758 -0
  103. megadetector/data_management/importers/snapshot_safari_importer_reprise.py +665 -0
  104. megadetector/data_management/importers/snapshot_serengeti_lila.py +1067 -0
  105. megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +150 -0
  106. megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +153 -0
  107. megadetector/data_management/importers/sulross_get_exif.py +65 -0
  108. megadetector/data_management/importers/timelapse_csv_set_to_json.py +490 -0
  109. megadetector/data_management/importers/ubc_to_json.py +399 -0
  110. megadetector/data_management/importers/umn_to_json.py +507 -0
  111. megadetector/data_management/importers/wellington_to_json.py +263 -0
  112. megadetector/data_management/importers/wi_to_json.py +442 -0
  113. megadetector/data_management/importers/zamba_results_to_md_results.py +181 -0
  114. megadetector/data_management/labelme_to_coco.py +547 -0
  115. megadetector/data_management/labelme_to_yolo.py +272 -0
  116. megadetector/data_management/lila/__init__.py +0 -0
  117. megadetector/data_management/lila/add_locations_to_island_camera_traps.py +97 -0
  118. megadetector/data_management/lila/add_locations_to_nacti.py +147 -0
  119. megadetector/data_management/lila/create_lila_blank_set.py +558 -0
  120. megadetector/data_management/lila/create_lila_test_set.py +152 -0
  121. megadetector/data_management/lila/create_links_to_md_results_files.py +106 -0
  122. megadetector/data_management/lila/download_lila_subset.py +178 -0
  123. megadetector/data_management/lila/generate_lila_per_image_labels.py +516 -0
  124. megadetector/data_management/lila/get_lila_annotation_counts.py +170 -0
  125. megadetector/data_management/lila/get_lila_image_counts.py +112 -0
  126. megadetector/data_management/lila/lila_common.py +300 -0
  127. megadetector/data_management/lila/test_lila_metadata_urls.py +132 -0
  128. megadetector/data_management/ocr_tools.py +870 -0
  129. megadetector/data_management/read_exif.py +809 -0
  130. megadetector/data_management/remap_coco_categories.py +84 -0
  131. megadetector/data_management/remove_exif.py +66 -0
  132. megadetector/data_management/rename_images.py +187 -0
  133. megadetector/data_management/resize_coco_dataset.py +189 -0
  134. megadetector/data_management/wi_download_csv_to_coco.py +247 -0
  135. megadetector/data_management/yolo_output_to_md_output.py +446 -0
  136. megadetector/data_management/yolo_to_coco.py +676 -0
  137. megadetector/detection/__init__.py +0 -0
  138. megadetector/detection/detector_training/__init__.py +0 -0
  139. megadetector/detection/detector_training/model_main_tf2.py +114 -0
  140. megadetector/detection/process_video.py +846 -0
  141. megadetector/detection/pytorch_detector.py +355 -0
  142. megadetector/detection/run_detector.py +779 -0
  143. megadetector/detection/run_detector_batch.py +1219 -0
  144. megadetector/detection/run_inference_with_yolov5_val.py +1087 -0
  145. megadetector/detection/run_tiled_inference.py +934 -0
  146. megadetector/detection/tf_detector.py +192 -0
  147. megadetector/detection/video_utils.py +698 -0
  148. megadetector/postprocessing/__init__.py +0 -0
  149. megadetector/postprocessing/add_max_conf.py +64 -0
  150. megadetector/postprocessing/categorize_detections_by_size.py +165 -0
  151. megadetector/postprocessing/classification_postprocessing.py +716 -0
  152. megadetector/postprocessing/combine_api_outputs.py +249 -0
  153. megadetector/postprocessing/compare_batch_results.py +966 -0
  154. megadetector/postprocessing/convert_output_format.py +396 -0
  155. megadetector/postprocessing/load_api_results.py +195 -0
  156. megadetector/postprocessing/md_to_coco.py +310 -0
  157. megadetector/postprocessing/md_to_labelme.py +330 -0
  158. megadetector/postprocessing/merge_detections.py +412 -0
  159. megadetector/postprocessing/postprocess_batch_results.py +1908 -0
  160. megadetector/postprocessing/remap_detection_categories.py +170 -0
  161. megadetector/postprocessing/render_detection_confusion_matrix.py +660 -0
  162. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +211 -0
  163. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +83 -0
  164. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1635 -0
  165. megadetector/postprocessing/separate_detections_into_folders.py +730 -0
  166. megadetector/postprocessing/subset_json_detector_output.py +700 -0
  167. megadetector/postprocessing/top_folders_to_bottom.py +223 -0
  168. megadetector/taxonomy_mapping/__init__.py +0 -0
  169. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +491 -0
  170. megadetector/taxonomy_mapping/map_new_lila_datasets.py +150 -0
  171. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -0
  172. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +588 -0
  173. megadetector/taxonomy_mapping/retrieve_sample_image.py +71 -0
  174. megadetector/taxonomy_mapping/simple_image_download.py +219 -0
  175. megadetector/taxonomy_mapping/species_lookup.py +834 -0
  176. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +159 -0
  177. megadetector/taxonomy_mapping/taxonomy_graph.py +346 -0
  178. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +83 -0
  179. megadetector/utils/__init__.py +0 -0
  180. megadetector/utils/azure_utils.py +178 -0
  181. megadetector/utils/ct_utils.py +613 -0
  182. megadetector/utils/directory_listing.py +246 -0
  183. megadetector/utils/md_tests.py +1164 -0
  184. megadetector/utils/path_utils.py +1045 -0
  185. megadetector/utils/process_utils.py +160 -0
  186. megadetector/utils/sas_blob_utils.py +509 -0
  187. megadetector/utils/split_locations_into_train_val.py +228 -0
  188. megadetector/utils/string_utils.py +92 -0
  189. megadetector/utils/url_utils.py +323 -0
  190. megadetector/utils/write_html_image_list.py +225 -0
  191. megadetector/visualization/__init__.py +0 -0
  192. megadetector/visualization/plot_utils.py +293 -0
  193. megadetector/visualization/render_images_with_thumbnails.py +275 -0
  194. megadetector/visualization/visualization_utils.py +1536 -0
  195. megadetector/visualization/visualize_db.py +552 -0
  196. megadetector/visualization/visualize_detector_output.py +405 -0
  197. {megadetector-5.0.11.dist-info → megadetector-5.0.13.dist-info}/LICENSE +0 -0
  198. {megadetector-5.0.11.dist-info → megadetector-5.0.13.dist-info}/METADATA +2 -2
  199. megadetector-5.0.13.dist-info/RECORD +201 -0
  200. megadetector-5.0.13.dist-info/top_level.txt +1 -0
  201. megadetector-5.0.11.dist-info/RECORD +0 -5
  202. megadetector-5.0.11.dist-info/top_level.txt +0 -1
  203. {megadetector-5.0.11.dist-info → megadetector-5.0.13.dist-info}/WHEEL +0 -0
@@ -0,0 +1,404 @@
1
+ """
2
+
3
+ cct_json_utils.py
4
+
5
+ Utilities for working with COCO Camera Traps .json databases:
6
+
7
+ https://github.com/agentmorris/MegaDetector/blob/main/megadetector/data_management/README.md#coco-cameratraps-format
8
+
9
+ """
10
+
11
+ #%% Constants and imports
12
+
13
+ import json
14
+ import os
15
+
16
+ from tqdm import tqdm
17
+ from collections import defaultdict, OrderedDict
18
+
19
+
20
+ #%% Classes
21
+
22
+ class CameraTrapJsonUtils:
23
+ """
24
+ Miscellaneous utility functions for working with COCO Camera Traps databases
25
+ """
26
+
27
+ @staticmethod
28
+ def annotations_to_string(annotations, cat_id_to_name):
29
+ """
30
+ Given a list of annotations and a mapping from class IDs to names, produces
31
+ a comma-delimited string containing a list of class names, sorted alphabetically.
32
+
33
+ Args:
34
+ annotations (list): a list of annotation dicts
35
+ cat_id_to_name (dict): a dict mapping category IDs to category names
36
+
37
+ Returns:
38
+ str: a comma-delimited list of class names
39
+ """
40
+
41
+ class_names = CameraTrapJsonUtils.annotations_to_class_names(annotations, cat_id_to_name)
42
+ return ','.join(class_names)
43
+
44
+
45
+ @staticmethod
46
+ def annotations_to_class_names(annotations, cat_id_to_name):
47
+ """
48
+ Given a list of annotations and a mapping from class IDs to names, produces
49
+ a list of class names, sorted alphabetically.
50
+
51
+ Args:
52
+ annotations (list): a list of annotation dicts
53
+ cat_id_to_name (dict): a dict mapping category IDs to category names
54
+
55
+ Returns:
56
+ list: a list of class names present in [annotations]
57
+ """
58
+
59
+ # Collect all names
60
+ class_names = [cat_id_to_name[ann['category_id']] for ann in annotations]
61
+ # Make names unique and sort
62
+ class_names = sorted(set(class_names))
63
+ return class_names
64
+
65
+
66
+ @staticmethod
67
+ def order_db_keys(db):
68
+ """
69
+ Given a dict representing a JSON database in the COCO Camera Trap
70
+ format, returns an OrderedDict with keys in the order of 'info',
71
+ 'categories', 'annotations' and 'images'. When this OrderedDict is
72
+ serialized with json.dump(), the order of the keys are preserved.
73
+
74
+ Args:
75
+ db (dict): a JSON database in the COCO Camera Trap format
76
+
77
+ Returns:
78
+ dict: the same content as [db] but as an OrderedDict with keys ordered for
79
+ readability
80
+ """
81
+
82
+ ordered = OrderedDict([
83
+ ('info', db['info']),
84
+ ('categories', db['categories']),
85
+ ('annotations', db['annotations']),
86
+ ('images', db['images'])])
87
+ return ordered
88
+
89
+
90
+ @staticmethod
91
+ def group_annotations_by_image_field(db_indexed, image_field='seq_id'):
92
+ """
93
+ Given an instance of IndexedJsonDb, group annotation entries by a field in the
94
+ image entry. Typically used to find all the annotations associated with a sequence.
95
+
96
+ Args:
97
+ db_indexed (IndexedJsonDb): an initialized IndexedJsonDb, typically loaded from a
98
+ COCO Camera Traps .json file
99
+ image_field (str, optional): a field by which to group annotations (defaults
100
+ to 'seq_id')
101
+
102
+ Returns:
103
+ dict: a dict mapping objects (typically strings, in fact typically sequence IDs) to
104
+ lists of annotations
105
+ """
106
+
107
+ image_id_to_image_field = {}
108
+ for image_id, image_entry in db_indexed.image_id_to_image.items():
109
+ image_id_to_image_field[image_id] = image_entry[image_field]
110
+
111
+ res = defaultdict(list)
112
+ for annotations in db_indexed.image_id_to_annotations.values():
113
+ for annotation_entry in annotations:
114
+ field_value = image_id_to_image_field[annotation_entry['image_id']]
115
+ res[field_value].append(annotation_entry)
116
+ return res
117
+
118
+
119
+ @staticmethod
120
+ def get_entries_for_locations(db, locations):
121
+ """
122
+ Given a dict representing a JSON database in the COCO Camera Trap format, returns a dict
123
+ with the 'images' and 'annotations' fields in the CCT format, each is an array that only
124
+ includes entries in the original [db] that are in the [locations] set.
125
+
126
+ Args:
127
+ db (dict): a dict representing a JSON database in the COCO Camera Trap format
128
+ locations (set): a set or list of locations to include; each item is a string
129
+
130
+ Returns:
131
+ dict: a dict with the 'images' and 'annotations' fields in the CCT format
132
+ """
133
+
134
+ locations = set(locations)
135
+ print('Original DB has {} image and {} annotation entries.'.format(
136
+ len(db['images']), len(db['annotations'])))
137
+ new_db = { 'images': [], 'annotations': [] }
138
+ new_images = set()
139
+ for i in db['images']:
140
+ # cast location to string as the entries in locations are strings
141
+ if str(i['location']) in locations:
142
+ new_db['images'].append(i)
143
+ new_images.add(i['id'])
144
+ for a in db['annotations']:
145
+ if a['image_id'] in new_images:
146
+ new_db['annotations'].append(a)
147
+ print(
148
+ 'New DB has {} image and {} annotation entries.'.format(
149
+ len(new_db['images']), len(new_db['annotations'])))
150
+ return new_db
151
+
152
+
153
+ class IndexedJsonDb:
154
+ """
155
+ Wrapper for a COCO Camera Traps database.
156
+
157
+ Handles boilerplate dictionary creation that we do almost every time we load
158
+ a .json database.
159
+ """
160
+
161
+ def __init__(self,
162
+ json_filename,
163
+ b_normalize_paths=False,
164
+ filename_replacements=None,
165
+ b_convert_classes_to_lower=True,
166
+ b_force_forward_slashes=True):
167
+ """
168
+ Constructor for IndexedJsonDb that loads from a .json file or CCT-formatted dict.
169
+
170
+ Args:
171
+ json_filename (str): filename to load, or an already-loaded dict
172
+ b_normalize_paths (bool, optional): whether to invoke os.path.normpath on
173
+ all filenames. Not relevant if b_force_forward_slashes is True.
174
+ filename_replacements (dict, optional): a set of string --> string mappings
175
+ that will trigger replacements in all filenames, typically used to remove
176
+ leading folders
177
+ b_convert_classes_to_lower (bool, optional): whether to convert all class
178
+ names to lowercase
179
+ b_force_forward_slashes (bool, optional): whether to convert backslashes to
180
+ forward slashes in all path names
181
+ """
182
+
183
+ if isinstance(json_filename, str):
184
+ with open(json_filename) as f:
185
+ self.db = json.load(f)
186
+ else:
187
+ self.db = json_filename
188
+
189
+ assert 'images' in self.db, (
190
+ f'Could not find image list in file {json_filename}, are you sure '
191
+ 'this is a COCO camera traps file?')
192
+
193
+ if b_convert_classes_to_lower:
194
+ # Convert classnames to lowercase to simplify comparisons later
195
+ for c in self.db['categories']:
196
+ c['name'] = c['name'].lower()
197
+
198
+ # Normalize paths to simplify comparisons later
199
+ if b_normalize_paths:
200
+ for im in self.db['images']:
201
+ im['file_name'] = os.path.normpath(im['file_name'])
202
+
203
+ if b_force_forward_slashes:
204
+ for im in self.db['images']:
205
+ im['file_name'] = im['file_name'].replace('\\','/')
206
+
207
+ if filename_replacements is not None:
208
+ for s in filename_replacements:
209
+ # Make custom replacements in filenames, typically used to
210
+ # accommodate changes in root paths after DB construction
211
+ r = filename_replacements[s]
212
+ for im in self.db['images']:
213
+ im['file_name'] = im['file_name'].replace(s, r)
214
+
215
+ ### Build useful mappings to facilitate working with the DB
216
+
217
+ # Category ID <--> name
218
+ self.cat_id_to_name = {
219
+ cat['id']: cat['name'] for cat in self.db['categories']}
220
+ self.cat_name_to_id = {
221
+ cat['name']: cat['id'] for cat in self.db['categories']}
222
+
223
+ # Image filename --> ID
224
+ self.filename_to_id = {
225
+ im['file_name']: im['id'] for im in self.db['images']}
226
+
227
+ # Image ID --> image object
228
+ self.image_id_to_image = {im['id']: im for im in self.db['images']}
229
+
230
+ # Image ID --> annotations
231
+ # Each image can potentially multiple annotations, hence using lists
232
+ self.image_id_to_annotations = {}
233
+ self.image_id_to_annotations = defaultdict(list)
234
+ for ann in self.db['annotations']:
235
+ self.image_id_to_annotations[ann['image_id']].append(ann)
236
+
237
+ # ...__init__
238
+
239
+
240
+ def get_annotations_for_image(self, image):
241
+ """
242
+ Finds all the annnotations associated with the image dict [image].
243
+
244
+ Args:
245
+ image (dict): an image dict loaded from a CCT .json file. Only the 'id' field
246
+ is used.
247
+
248
+ Returns:
249
+ list: list of annotations associated with this image. Returns None if the db
250
+ has not been loaded, or [] if no annotations are available for this image.
251
+ """
252
+
253
+ if self.db is None:
254
+ return None
255
+
256
+ if image['id'] not in self.image_id_to_annotations:
257
+ return []
258
+
259
+ image_annotations = self.image_id_to_annotations[image['id']]
260
+ return image_annotations
261
+
262
+
263
+ def get_classes_for_image(self, image):
264
+ """
265
+ Returns a list of class names associated with [image].
266
+
267
+ Args:
268
+ image (dict): an image dict loaded from a CCT .json file. Only the 'id' field
269
+ is used.
270
+
271
+ Returns:
272
+ list: list of class names associated with this image. Returns None if the db
273
+ has not been loaded, or [] if no annotations are available for this image.
274
+ """
275
+
276
+ if self.db is None:
277
+ return None
278
+
279
+ if image['id'] not in self.image_id_to_annotations:
280
+ return []
281
+
282
+ class_ids = []
283
+ image_annotations = self.image_id_to_annotations[image['id']]
284
+ for ann in image_annotations:
285
+ class_ids.append(ann['category_id'])
286
+ class_ids = sorted(set(class_ids))
287
+ class_names = [self.cat_id_to_name[x] for x in class_ids]
288
+
289
+ return class_names
290
+
291
+ # ...class IndexedJsonDb
292
+
293
+ class SequenceOptions:
294
+ """
295
+ Options parameterizing the grouping of images into sequences by time.
296
+ """
297
+
298
+ def __init__(self):
299
+ #: Images separated by <= this duration will be grouped into the same sequence.
300
+ self.episode_interval_seconds = 60.0
301
+
302
+
303
+ #%% Functions
304
+
305
+ def create_sequences(image_info,options=None):
306
+ """
307
+ Synthesizes episodes/sequences/bursts for the images in [image_info].
308
+
309
+ Modifies [image_info] in place, populating the 'seq_id', 'seq_num_frames', and 'frame_num'
310
+ fields for each image.
311
+
312
+ Args:
313
+ image_info (str, dict, or list): a dict in CCT format, a CCT .json file, or just the 'images' component
314
+ of a CCT dataset (a list of dicts with fields 'file_name' (str), 'datetime' (datetime), and
315
+ 'location' (str)).
316
+ """
317
+
318
+ if options is None:
319
+ options = SequenceOptions()
320
+
321
+ if isinstance(image_info,str):
322
+ with open(image_info,'r') as f:
323
+ image_info = json.load(f)
324
+
325
+ if isinstance(image_info,dict):
326
+ image_info = image_info['images']
327
+
328
+ # Find all unique locations
329
+ locations = set()
330
+ for im in image_info:
331
+ locations.add(im['location'])
332
+
333
+ print('Found {} locations'.format(len(locations)))
334
+ locations = list(locations)
335
+ locations.sort()
336
+
337
+ all_sequences = set()
338
+
339
+ # i_location = 0; location = locations[i_location]
340
+ for i_location,location in tqdm(enumerate(locations),total=len(locations)):
341
+
342
+ images_this_location = [im for im in image_info if im['location'] == location]
343
+
344
+ # Sorting datetimes fails when there are None's in the list. So instead of sorting datetimes
345
+ # directly, sort tuples with a boolean for none-ness, then the datetime itself.
346
+ #
347
+ # https://stackoverflow.com/questions/18411560/sort-list-while-pushing-none-values-to-the-end
348
+ sorted_images_this_location = sorted(images_this_location,
349
+ key = lambda im: (im['datetime'] is None,im['datetime']))
350
+
351
+ sequence_id_to_images_this_location = defaultdict(list)
352
+
353
+ current_sequence_id = None
354
+ next_frame_number = 0
355
+ next_sequence_number = 0
356
+ previous_datetime = None
357
+
358
+ # previous_datetime = sorted_images_this_location[0]['datetime']
359
+ # im = sorted_images_this_location[1]
360
+ for im in sorted_images_this_location:
361
+
362
+ invalid_datetime = False
363
+
364
+ if previous_datetime is None:
365
+ delta = None
366
+ elif im['datetime'] is None:
367
+ invalid_datetime = True
368
+ else:
369
+ delta = (im['datetime'] - previous_datetime).total_seconds()
370
+
371
+ # Start a new sequence if necessary, including the case where this datetime is invalid
372
+ if delta is None or delta > options.episode_interval_seconds or invalid_datetime:
373
+ next_frame_number = 0
374
+ current_sequence_id = 'location_{}_sequence_index_{}'.format(
375
+ location,str(next_sequence_number).zfill(5))
376
+ next_sequence_number = next_sequence_number + 1
377
+ assert current_sequence_id not in all_sequences
378
+ all_sequences.add(current_sequence_id)
379
+
380
+ im['seq_id'] = current_sequence_id
381
+ im['seq_num_frames'] = None
382
+ im['frame_num'] = next_frame_number
383
+ sequence_id_to_images_this_location[current_sequence_id].append(im)
384
+ next_frame_number = next_frame_number + 1
385
+
386
+ # If this was an invalid datetime, this will record the previous datetime
387
+ # as None, which will force the next image to start a new sequence.
388
+ previous_datetime = im['datetime']
389
+
390
+ # ...for each image in this location
391
+
392
+ # Fill in seq_num_frames
393
+ for seq_id in sequence_id_to_images_this_location.keys():
394
+ assert seq_id in sequence_id_to_images_this_location
395
+ images_this_sequence = sequence_id_to_images_this_location[seq_id]
396
+ assert len(images_this_sequence) > 0
397
+ for im in images_this_sequence:
398
+ im['seq_num_frames'] = len(images_this_sequence)
399
+
400
+ # ...for each location
401
+
402
+ print('Created {} sequences from {} images'.format(len(all_sequences),len(image_info)))
403
+
404
+ # ...create_sequences()
@@ -0,0 +1,176 @@
1
+ """
2
+
3
+ cct_to_md.py
4
+
5
+ "Converts" a COCO Camera Traps file to a MD results file. Currently ignores
6
+ non-bounding-box annotations, and gives all annotations a confidence of 1.0.
7
+
8
+ The only reason to do this is if you are going to add information to an existing
9
+ CCT-formatted dataset, and you want to do that in Timelapse.
10
+
11
+ Currently assumes that width and height are present in the input data, does not
12
+ read them from images.
13
+
14
+ """
15
+
16
+ #%% Constants and imports
17
+
18
+ import os
19
+ import json
20
+
21
+ from collections import defaultdict
22
+ from tqdm import tqdm
23
+
24
+
25
+ #%% Functions
26
+
27
+ def cct_to_md(input_filename,output_filename=None):
28
+ """
29
+ "Converts" a COCO Camera Traps file to a MD results file. Currently ignores
30
+ non-bounding-box annotations, and gives all annotations a confidence of 1.0.
31
+
32
+ The only reason to do this is if you are going to add information to an existing
33
+ CCT-formatted dataset, and you want to do that in Timelapse.
34
+
35
+ Currently assumes that width and height are present in the input data, does not
36
+ read them from images.
37
+
38
+ Args:
39
+ input_filename (str): the COCO Camera Traps .json file to read
40
+ output_filename (str, optional): the .json file to write in MD results format
41
+
42
+ Returns:
43
+ dict: MD-formatted results, identical to the content of [output_filename] if
44
+ [output_filename] is not None
45
+ """
46
+
47
+ ## Validate input
48
+
49
+ assert os.path.isfile(input_filename)
50
+
51
+ if (output_filename is None):
52
+
53
+ tokens = os.path.splitext(input_filename)
54
+ assert len(tokens) == 2
55
+ output_filename = tokens[0] + '_md-format' + tokens[1]
56
+
57
+
58
+ ## Read input
59
+
60
+ with open(input_filename,'r') as f:
61
+ d = json.load(f)
62
+
63
+ for s in ['annotations','images','categories']:
64
+ assert s in d.keys(), 'Cannot find category {} in input file, is this a CCT file?'.format(s)
65
+
66
+
67
+ ## Prepare metadata
68
+
69
+ image_id_to_annotations = defaultdict(list)
70
+
71
+ # ann = d['annotations'][0]
72
+ for ann in tqdm(d['annotations']):
73
+ image_id_to_annotations[ann['image_id']].append(ann)
74
+
75
+ category_id_to_name = {}
76
+ for cat in d['categories']:
77
+ category_id_to_name[str(cat['id'])] = cat['name']
78
+
79
+ results = {}
80
+
81
+ info = {}
82
+ info['format_version'] = "1.3"
83
+ info['detector'] = 'cct_to_md'
84
+ results['info'] = info
85
+ results['detection_categories'] = category_id_to_name
86
+
87
+
88
+ ## Process images
89
+
90
+ images_out = []
91
+
92
+ # im = d['images'][0]
93
+ for im in tqdm(d['images']):
94
+
95
+ im_out = {}
96
+ im_out['file'] = im['file_name']
97
+ im_out['location'] = im['location']
98
+ im_out['id'] = im['id']
99
+
100
+ image_h = im['height']
101
+ image_w = im['width']
102
+
103
+ detections = []
104
+
105
+ annotations_this_image = image_id_to_annotations[im['id']]
106
+
107
+ # This field is no longer included in MD output files by default
108
+ # max_detection_conf = 0
109
+
110
+ for ann in annotations_this_image:
111
+
112
+ if 'bbox' in ann:
113
+
114
+ det = {}
115
+ det['category'] = str(ann['category_id'])
116
+ det['conf'] = 1.0
117
+ # max_detection_conf = 1.0
118
+
119
+ # MegaDetector: [x,y,width,height] (normalized, origin upper-left)
120
+ # CCT: [x,y,width,height] (absolute, origin upper-left)
121
+ bbox_in = ann['bbox']
122
+ bbox_out = [bbox_in[0]/image_w,bbox_in[1]/image_h,
123
+ bbox_in[2]/image_w,bbox_in[3]/image_h]
124
+ det['bbox'] = bbox_out
125
+ detections.append(det)
126
+
127
+ # ...if there's a bounding box
128
+
129
+ # ...for each annotation
130
+
131
+ im_out['detections'] = detections
132
+
133
+ # This field is no longer included in MD output files by default
134
+ # im_out['max_detection_conf'] = max_detection_conf
135
+
136
+ images_out.append(im_out)
137
+
138
+ # ...for each image
139
+
140
+
141
+ ## Write output
142
+
143
+ results['images'] = images_out
144
+
145
+ with open(output_filename,'w') as f:
146
+ json.dump(results, f, indent=1)
147
+
148
+ return output_filename
149
+
150
+ # ...cct_to_md()
151
+
152
+
153
+ #%% Interactive driver
154
+
155
+ if False:
156
+
157
+ pass
158
+
159
+ #%%
160
+
161
+ input_filename = r"G:\temp\noaa_estuary_fish.json"
162
+ output_filename = None
163
+ output_filename = cct_to_md(input_filename,output_filename)
164
+
165
+ #%%
166
+
167
+ from megadetector.visualization import visualize_detector_output
168
+
169
+ visualize_detector_output.visualize_detector_output(
170
+ detector_output_path=output_filename,
171
+ out_dir=r'g:\temp\fish_output',
172
+ images_dir=r'g:\temp\noaa_estuary_fish-images\JPEGImages',
173
+ output_image_width=-1,
174
+ sample=100,
175
+ render_detections_only=True)
176
+