megadetector 5.0.28__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.
- megadetector/api/batch_processing/api_core/batch_service/score.py +4 -5
- megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +1 -1
- megadetector/api/batch_processing/api_support/summarize_daily_activity.py +1 -1
- megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +2 -2
- megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +1 -1
- megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +1 -1
- megadetector/api/synchronous/api_core/tests/load_test.py +2 -3
- megadetector/classification/aggregate_classifier_probs.py +3 -3
- megadetector/classification/analyze_failed_images.py +5 -5
- megadetector/classification/cache_batchapi_outputs.py +5 -5
- megadetector/classification/create_classification_dataset.py +11 -12
- megadetector/classification/crop_detections.py +10 -10
- megadetector/classification/csv_to_json.py +8 -8
- megadetector/classification/detect_and_crop.py +13 -15
- megadetector/classification/evaluate_model.py +7 -7
- megadetector/classification/identify_mislabeled_candidates.py +6 -6
- megadetector/classification/json_to_azcopy_list.py +1 -1
- megadetector/classification/json_validator.py +29 -32
- megadetector/classification/map_classification_categories.py +9 -9
- megadetector/classification/merge_classification_detection_output.py +12 -9
- megadetector/classification/prepare_classification_script.py +19 -19
- megadetector/classification/prepare_classification_script_mc.py +23 -23
- megadetector/classification/run_classifier.py +4 -4
- megadetector/classification/save_mislabeled.py +6 -6
- megadetector/classification/train_classifier.py +1 -1
- megadetector/classification/train_classifier_tf.py +9 -9
- megadetector/classification/train_utils.py +10 -10
- megadetector/data_management/annotations/annotation_constants.py +1 -1
- megadetector/data_management/camtrap_dp_to_coco.py +45 -45
- megadetector/data_management/cct_json_utils.py +101 -101
- megadetector/data_management/cct_to_md.py +49 -49
- megadetector/data_management/cct_to_wi.py +33 -33
- megadetector/data_management/coco_to_labelme.py +75 -75
- megadetector/data_management/coco_to_yolo.py +189 -189
- megadetector/data_management/databases/add_width_and_height_to_db.py +3 -2
- megadetector/data_management/databases/combine_coco_camera_traps_files.py +38 -38
- megadetector/data_management/databases/integrity_check_json_db.py +202 -188
- megadetector/data_management/databases/subset_json_db.py +33 -33
- megadetector/data_management/generate_crops_from_cct.py +38 -38
- megadetector/data_management/get_image_sizes.py +54 -49
- megadetector/data_management/labelme_to_coco.py +130 -124
- megadetector/data_management/labelme_to_yolo.py +78 -72
- megadetector/data_management/lila/create_lila_blank_set.py +81 -83
- megadetector/data_management/lila/create_lila_test_set.py +32 -31
- megadetector/data_management/lila/create_links_to_md_results_files.py +18 -18
- megadetector/data_management/lila/download_lila_subset.py +21 -24
- megadetector/data_management/lila/generate_lila_per_image_labels.py +91 -91
- megadetector/data_management/lila/get_lila_annotation_counts.py +30 -30
- megadetector/data_management/lila/get_lila_image_counts.py +22 -22
- megadetector/data_management/lila/lila_common.py +70 -70
- megadetector/data_management/lila/test_lila_metadata_urls.py +13 -14
- megadetector/data_management/mewc_to_md.py +339 -340
- megadetector/data_management/ocr_tools.py +258 -252
- megadetector/data_management/read_exif.py +231 -224
- megadetector/data_management/remap_coco_categories.py +26 -26
- megadetector/data_management/remove_exif.py +31 -20
- megadetector/data_management/rename_images.py +187 -187
- megadetector/data_management/resize_coco_dataset.py +41 -41
- megadetector/data_management/speciesnet_to_md.py +41 -41
- megadetector/data_management/wi_download_csv_to_coco.py +55 -55
- megadetector/data_management/yolo_output_to_md_output.py +117 -120
- megadetector/data_management/yolo_to_coco.py +195 -188
- megadetector/detection/change_detection.py +831 -0
- megadetector/detection/process_video.py +340 -337
- megadetector/detection/pytorch_detector.py +304 -262
- megadetector/detection/run_detector.py +177 -164
- megadetector/detection/run_detector_batch.py +364 -363
- megadetector/detection/run_inference_with_yolov5_val.py +328 -325
- megadetector/detection/run_tiled_inference.py +256 -249
- megadetector/detection/tf_detector.py +24 -24
- megadetector/detection/video_utils.py +290 -282
- megadetector/postprocessing/add_max_conf.py +15 -11
- megadetector/postprocessing/categorize_detections_by_size.py +44 -44
- megadetector/postprocessing/classification_postprocessing.py +415 -415
- megadetector/postprocessing/combine_batch_outputs.py +20 -21
- megadetector/postprocessing/compare_batch_results.py +528 -517
- megadetector/postprocessing/convert_output_format.py +97 -97
- megadetector/postprocessing/create_crop_folder.py +219 -146
- megadetector/postprocessing/detector_calibration.py +173 -168
- megadetector/postprocessing/generate_csv_report.py +508 -499
- megadetector/postprocessing/load_api_results.py +23 -20
- megadetector/postprocessing/md_to_coco.py +129 -98
- megadetector/postprocessing/md_to_labelme.py +89 -83
- megadetector/postprocessing/md_to_wi.py +40 -40
- megadetector/postprocessing/merge_detections.py +87 -114
- megadetector/postprocessing/postprocess_batch_results.py +313 -298
- megadetector/postprocessing/remap_detection_categories.py +36 -36
- megadetector/postprocessing/render_detection_confusion_matrix.py +205 -199
- megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +57 -57
- megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +27 -28
- megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +702 -677
- megadetector/postprocessing/separate_detections_into_folders.py +226 -211
- megadetector/postprocessing/subset_json_detector_output.py +265 -262
- megadetector/postprocessing/top_folders_to_bottom.py +45 -45
- megadetector/postprocessing/validate_batch_results.py +70 -70
- megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +52 -52
- megadetector/taxonomy_mapping/map_new_lila_datasets.py +15 -15
- megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +14 -14
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +66 -66
- megadetector/taxonomy_mapping/retrieve_sample_image.py +16 -16
- megadetector/taxonomy_mapping/simple_image_download.py +8 -8
- megadetector/taxonomy_mapping/species_lookup.py +33 -33
- megadetector/taxonomy_mapping/taxonomy_csv_checker.py +14 -14
- megadetector/taxonomy_mapping/taxonomy_graph.py +10 -10
- megadetector/taxonomy_mapping/validate_lila_category_mappings.py +13 -13
- megadetector/utils/azure_utils.py +22 -22
- megadetector/utils/ct_utils.py +1018 -200
- megadetector/utils/directory_listing.py +21 -77
- megadetector/utils/gpu_test.py +22 -22
- megadetector/utils/md_tests.py +541 -518
- megadetector/utils/path_utils.py +1457 -398
- megadetector/utils/process_utils.py +41 -41
- megadetector/utils/sas_blob_utils.py +53 -49
- megadetector/utils/split_locations_into_train_val.py +61 -61
- megadetector/utils/string_utils.py +147 -26
- megadetector/utils/url_utils.py +463 -173
- megadetector/utils/wi_utils.py +2629 -2526
- megadetector/utils/write_html_image_list.py +137 -137
- megadetector/visualization/plot_utils.py +21 -21
- megadetector/visualization/render_images_with_thumbnails.py +37 -73
- megadetector/visualization/visualization_utils.py +401 -397
- megadetector/visualization/visualize_db.py +197 -190
- megadetector/visualization/visualize_detector_output.py +79 -73
- {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/METADATA +135 -132
- megadetector-5.0.29.dist-info/RECORD +163 -0
- {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/WHEEL +1 -1
- {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/licenses/LICENSE +0 -0
- {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/top_level.txt +0 -0
- megadetector/data_management/importers/add_nacti_sizes.py +0 -52
- megadetector/data_management/importers/add_timestamps_to_icct.py +0 -79
- megadetector/data_management/importers/animl_results_to_md_results.py +0 -158
- megadetector/data_management/importers/auckland_doc_test_to_json.py +0 -373
- megadetector/data_management/importers/auckland_doc_to_json.py +0 -201
- megadetector/data_management/importers/awc_to_json.py +0 -191
- megadetector/data_management/importers/bellevue_to_json.py +0 -272
- megadetector/data_management/importers/cacophony-thermal-importer.py +0 -793
- megadetector/data_management/importers/carrizo_shrubfree_2018.py +0 -269
- megadetector/data_management/importers/carrizo_trail_cam_2017.py +0 -289
- megadetector/data_management/importers/cct_field_adjustments.py +0 -58
- megadetector/data_management/importers/channel_islands_to_cct.py +0 -913
- megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
- megadetector/data_management/importers/eMammal/eMammal_helpers.py +0 -249
- megadetector/data_management/importers/eMammal/make_eMammal_json.py +0 -223
- megadetector/data_management/importers/ena24_to_json.py +0 -276
- megadetector/data_management/importers/filenames_to_json.py +0 -386
- megadetector/data_management/importers/helena_to_cct.py +0 -283
- megadetector/data_management/importers/idaho-camera-traps.py +0 -1407
- megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
- megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +0 -387
- megadetector/data_management/importers/jb_csv_to_json.py +0 -150
- megadetector/data_management/importers/mcgill_to_json.py +0 -250
- megadetector/data_management/importers/missouri_to_json.py +0 -490
- megadetector/data_management/importers/nacti_fieldname_adjustments.py +0 -79
- megadetector/data_management/importers/noaa_seals_2019.py +0 -181
- megadetector/data_management/importers/osu-small-animals-to-json.py +0 -364
- megadetector/data_management/importers/pc_to_json.py +0 -365
- megadetector/data_management/importers/plot_wni_giraffes.py +0 -123
- megadetector/data_management/importers/prepare_zsl_imerit.py +0 -131
- megadetector/data_management/importers/raic_csv_to_md_results.py +0 -416
- megadetector/data_management/importers/rspb_to_json.py +0 -356
- megadetector/data_management/importers/save_the_elephants_survey_A.py +0 -320
- megadetector/data_management/importers/save_the_elephants_survey_B.py +0 -329
- megadetector/data_management/importers/snapshot_safari_importer.py +0 -758
- megadetector/data_management/importers/snapshot_serengeti_lila.py +0 -1067
- megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
- megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
- megadetector/data_management/importers/sulross_get_exif.py +0 -65
- megadetector/data_management/importers/timelapse_csv_set_to_json.py +0 -490
- megadetector/data_management/importers/ubc_to_json.py +0 -399
- megadetector/data_management/importers/umn_to_json.py +0 -507
- megadetector/data_management/importers/wellington_to_json.py +0 -263
- megadetector/data_management/importers/wi_to_json.py +0 -442
- megadetector/data_management/importers/zamba_results_to_md_results.py +0 -180
- megadetector/data_management/lila/add_locations_to_island_camera_traps.py +0 -101
- megadetector/data_management/lila/add_locations_to_nacti.py +0 -151
- megadetector-5.0.28.dist-info/RECORD +0 -209
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|