megadetector 5.0.28__py3-none-any.whl → 10.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of megadetector might be problematic. Click here for more details.
- 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/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/efficientnet/model.py +8 -8
- megadetector/classification/efficientnet/utils.py +6 -5
- 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 +26 -26
- 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 -2
- megadetector/data_management/camtrap_dp_to_coco.py +79 -46
- megadetector/data_management/cct_json_utils.py +103 -103
- 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 +210 -193
- megadetector/data_management/databases/add_width_and_height_to_db.py +86 -12
- megadetector/data_management/databases/combine_coco_camera_traps_files.py +40 -40
- megadetector/data_management/databases/integrity_check_json_db.py +228 -200
- megadetector/data_management/databases/subset_json_db.py +33 -33
- megadetector/data_management/generate_crops_from_cct.py +88 -39
- megadetector/data_management/get_image_sizes.py +54 -49
- megadetector/data_management/labelme_to_coco.py +133 -125
- megadetector/data_management/labelme_to_yolo.py +159 -73
- 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 +365 -107
- megadetector/data_management/lila/get_lila_annotation_counts.py +35 -33
- megadetector/data_management/lila/get_lila_image_counts.py +22 -22
- megadetector/data_management/lila/lila_common.py +73 -70
- megadetector/data_management/lila/test_lila_metadata_urls.py +28 -19
- megadetector/data_management/mewc_to_md.py +344 -340
- megadetector/data_management/ocr_tools.py +262 -255
- megadetector/data_management/read_exif.py +249 -227
- megadetector/data_management/remap_coco_categories.py +90 -28
- megadetector/data_management/remove_exif.py +81 -21
- megadetector/data_management/rename_images.py +187 -187
- megadetector/data_management/resize_coco_dataset.py +588 -120
- 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 +248 -122
- megadetector/data_management/yolo_to_coco.py +333 -191
- megadetector/detection/change_detection.py +832 -0
- megadetector/detection/process_video.py +340 -337
- megadetector/detection/pytorch_detector.py +358 -278
- megadetector/detection/run_detector.py +399 -186
- megadetector/detection/run_detector_batch.py +404 -377
- megadetector/detection/run_inference_with_yolov5_val.py +340 -327
- megadetector/detection/run_tiled_inference.py +257 -249
- megadetector/detection/tf_detector.py +24 -24
- megadetector/detection/video_utils.py +332 -295
- megadetector/postprocessing/add_max_conf.py +19 -11
- megadetector/postprocessing/categorize_detections_by_size.py +45 -45
- megadetector/postprocessing/classification_postprocessing.py +468 -433
- megadetector/postprocessing/combine_batch_outputs.py +23 -23
- megadetector/postprocessing/compare_batch_results.py +590 -525
- megadetector/postprocessing/convert_output_format.py +106 -102
- megadetector/postprocessing/create_crop_folder.py +347 -147
- megadetector/postprocessing/detector_calibration.py +173 -168
- megadetector/postprocessing/generate_csv_report.py +508 -499
- megadetector/postprocessing/load_api_results.py +48 -27
- megadetector/postprocessing/md_to_coco.py +133 -102
- megadetector/postprocessing/md_to_labelme.py +107 -90
- megadetector/postprocessing/md_to_wi.py +40 -40
- megadetector/postprocessing/merge_detections.py +92 -114
- megadetector/postprocessing/postprocess_batch_results.py +319 -301
- megadetector/postprocessing/remap_detection_categories.py +91 -38
- megadetector/postprocessing/render_detection_confusion_matrix.py +214 -205
- 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 +704 -679
- 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 +18 -19
- megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +54 -33
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +67 -67
- megadetector/taxonomy_mapping/retrieve_sample_image.py +16 -16
- megadetector/taxonomy_mapping/simple_image_download.py +8 -8
- megadetector/taxonomy_mapping/species_lookup.py +156 -74
- 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/ct_utils.py +1049 -211
- megadetector/utils/directory_listing.py +21 -77
- megadetector/utils/gpu_test.py +22 -22
- megadetector/utils/md_tests.py +632 -529
- megadetector/utils/path_utils.py +1520 -431
- megadetector/utils/process_utils.py +41 -41
- megadetector/utils/split_locations_into_train_val.py +62 -62
- megadetector/utils/string_utils.py +148 -27
- megadetector/utils/url_utils.py +489 -176
- megadetector/utils/wi_utils.py +2658 -2526
- megadetector/utils/write_html_image_list.py +137 -137
- megadetector/visualization/plot_utils.py +34 -30
- megadetector/visualization/render_images_with_thumbnails.py +39 -74
- megadetector/visualization/visualization_utils.py +487 -435
- megadetector/visualization/visualize_db.py +232 -198
- megadetector/visualization/visualize_detector_output.py +82 -76
- {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/METADATA +5 -2
- megadetector-10.0.0.dist-info/RECORD +139 -0
- {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/WHEEL +1 -1
- megadetector/api/batch_processing/api_core/__init__.py +0 -0
- megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
- megadetector/api/batch_processing/api_core/batch_service/score.py +0 -439
- megadetector/api/batch_processing/api_core/server.py +0 -294
- megadetector/api/batch_processing/api_core/server_api_config.py +0 -97
- megadetector/api/batch_processing/api_core/server_app_config.py +0 -55
- megadetector/api/batch_processing/api_core/server_batch_job_manager.py +0 -220
- megadetector/api/batch_processing/api_core/server_job_status_table.py +0 -149
- megadetector/api/batch_processing/api_core/server_orchestration.py +0 -360
- megadetector/api/batch_processing/api_core/server_utils.py +0 -88
- megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
- megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +0 -46
- megadetector/api/batch_processing/api_support/__init__.py +0 -0
- megadetector/api/batch_processing/api_support/summarize_daily_activity.py +0 -152
- megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
- megadetector/api/synchronous/__init__.py +0 -0
- megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
- megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +0 -151
- megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -263
- megadetector/api/synchronous/api_core/animal_detection_api/config.py +0 -35
- megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
- megadetector/api/synchronous/api_core/tests/load_test.py +0 -110
- 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/utils/azure_utils.py +0 -178
- megadetector/utils/sas_blob_utils.py +0 -509
- megadetector-5.0.28.dist-info/RECORD +0 -209
- /megadetector/{api/batch_processing/__init__.py → __init__.py} +0 -0
- {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/licenses/LICENSE +0 -0
- {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,507 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
umn_to_json.py
|
|
4
|
-
|
|
5
|
-
Prepare images and metadata for the Orinoquía Camera Traps dataset.
|
|
6
|
-
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
#%% Imports and constants
|
|
10
|
-
|
|
11
|
-
import os
|
|
12
|
-
import json
|
|
13
|
-
import pandas as pd
|
|
14
|
-
import shutil
|
|
15
|
-
import uuid
|
|
16
|
-
import datetime
|
|
17
|
-
import dateutil.parser
|
|
18
|
-
|
|
19
|
-
from collections import defaultdict
|
|
20
|
-
from tqdm import tqdm
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
from multiprocessing.pool import ThreadPool
|
|
23
|
-
|
|
24
|
-
input_base = "f:\\"
|
|
25
|
-
image_base = os.path.join(input_base,'2021.11.24-images\jan2020')
|
|
26
|
-
ground_truth_file = os.path.join(input_base,'images_hv_jan2020_reviewed_force_nonblank.csv')
|
|
27
|
-
|
|
28
|
-
# For two deployments, we're only processing imagse in the "detections" subfolder
|
|
29
|
-
detection_only_deployments = ['N23','N32']
|
|
30
|
-
deployments_to_ignore = ['N18','N28']
|
|
31
|
-
|
|
32
|
-
MISSING_COMMON_NAME_TOKEN = 'MISSING'
|
|
33
|
-
|
|
34
|
-
assert os.path.isfile(ground_truth_file)
|
|
35
|
-
assert os.path.isdir(image_base)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
#%% Enumerate deployment folders
|
|
39
|
-
|
|
40
|
-
deployment_folders = os.listdir(image_base)
|
|
41
|
-
deployment_folders = [s for s in deployment_folders if os.path.isdir(os.path.join(image_base,s))]
|
|
42
|
-
deployment_folders = set(deployment_folders)
|
|
43
|
-
print('Listed {} deployment folders'.format(len(deployment_folders)))
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
#%% Load ground truth
|
|
47
|
-
|
|
48
|
-
ground_truth_df = pd.read_csv(ground_truth_file)
|
|
49
|
-
|
|
50
|
-
print('Loaded {} ground truth annotations'.format(
|
|
51
|
-
len(ground_truth_df)))
|
|
52
|
-
|
|
53
|
-
# i_row = 0; row = ground_truth_df.iloc[i_row]
|
|
54
|
-
for i_row,row in tqdm(ground_truth_df.iterrows()):
|
|
55
|
-
if not isinstance(row['common_name'],str):
|
|
56
|
-
print('Warning: missing common name for {}'.format(row['filename']))
|
|
57
|
-
row['common_name'] = MISSING_COMMON_NAME_TOKEN
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
#%% Create relative paths for ground truth data
|
|
61
|
-
|
|
62
|
-
# Some deployment folders have no subfolders, e.g. this is a valid file name:
|
|
63
|
-
#
|
|
64
|
-
# M00/01010132.JPG
|
|
65
|
-
#
|
|
66
|
-
# But some deployment folders have subfolders, e.g. this is also a valid file name:
|
|
67
|
-
#
|
|
68
|
-
# N17/100EK113/07160020.JPG
|
|
69
|
-
#
|
|
70
|
-
# So we can't find files by just concatenating folder and file names, we have to enumerate and explicitly
|
|
71
|
-
# map what will appear in the ground truth as "folder/filename" to complete relative paths.
|
|
72
|
-
|
|
73
|
-
deployment_name_to_file_mappings = {}
|
|
74
|
-
|
|
75
|
-
n_filenames_ignored = 0
|
|
76
|
-
n_deployments_ignored = 0
|
|
77
|
-
|
|
78
|
-
# deployment_name = list(deployment_folders)[0]
|
|
79
|
-
for deployment_name in tqdm(deployment_folders):
|
|
80
|
-
|
|
81
|
-
file_mappings = {}
|
|
82
|
-
|
|
83
|
-
if deployment_name in deployments_to_ignore:
|
|
84
|
-
print('Ignoring deployment {}'.format(deployment_name))
|
|
85
|
-
n_deployments_ignored += 1
|
|
86
|
-
continue
|
|
87
|
-
|
|
88
|
-
# Enumerate all files in this folder
|
|
89
|
-
absolute_deployment_folder = os.path.join(image_base,deployment_name)
|
|
90
|
-
assert os.path.isdir(absolute_deployment_folder)
|
|
91
|
-
|
|
92
|
-
files = list(Path(absolute_deployment_folder).rglob('*'))
|
|
93
|
-
files = [p for p in files if not p.is_dir()]
|
|
94
|
-
files = [str(s) for s in files]
|
|
95
|
-
files = [s.replace('\\','/') for s in files]
|
|
96
|
-
# print('Enumerated {} files for deployment {}'.format(len(files),deployment_name))
|
|
97
|
-
|
|
98
|
-
# filename = files[100]
|
|
99
|
-
for filename in files:
|
|
100
|
-
|
|
101
|
-
if deployment_name in detection_only_deployments and 'detection' not in filename:
|
|
102
|
-
n_filenames_ignored += 1
|
|
103
|
-
continue
|
|
104
|
-
|
|
105
|
-
if '.DS_Store' in filename:
|
|
106
|
-
n_filenames_ignored += 1
|
|
107
|
-
continue
|
|
108
|
-
|
|
109
|
-
relative_path = os.path.relpath(filename,absolute_deployment_folder).replace('\\','/')
|
|
110
|
-
image_name = relative_path.split('/')[-1]
|
|
111
|
-
assert image_name not in file_mappings, 'Redundant image name {} in deployment {}'.format(
|
|
112
|
-
image_name,deployment_name)
|
|
113
|
-
assert '\\' not in relative_path
|
|
114
|
-
file_mappings[image_name] = relative_path
|
|
115
|
-
|
|
116
|
-
# ...for each file in this deployment
|
|
117
|
-
|
|
118
|
-
deployment_name_to_file_mappings[deployment_name] = file_mappings
|
|
119
|
-
|
|
120
|
-
# ...for each deployment
|
|
121
|
-
|
|
122
|
-
print('Processed deployments, ignored {} deployments and {} files'.format(
|
|
123
|
-
n_deployments_ignored,n_filenames_ignored))
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
#%% Add relative paths to our ground truth table
|
|
127
|
-
|
|
128
|
-
ground_truth_df['relative_path'] = None
|
|
129
|
-
|
|
130
|
-
# i_row = 0; row = ground_truth_df.iloc[i_row]
|
|
131
|
-
for i_row,row in tqdm(ground_truth_df.iterrows(),total=len(ground_truth_df)):
|
|
132
|
-
|
|
133
|
-
# row['filename'] looks like, e.g. A01/01080001.JPG. This is not actually a path, it's
|
|
134
|
-
# just the deployment ID and the image name, separated by a slash.
|
|
135
|
-
|
|
136
|
-
deployment_name = row['filename'].split('/')[0]
|
|
137
|
-
|
|
138
|
-
assert deployment_name in deployment_folders, 'Could not find deployment folder {}'.format(deployment_name)
|
|
139
|
-
assert deployment_name in deployment_name_to_file_mappings, 'Could not find deployment folder {}'.format(deployment_name)
|
|
140
|
-
|
|
141
|
-
file_mappings = deployment_name_to_file_mappings[deployment_name]
|
|
142
|
-
|
|
143
|
-
# Find the relative path for this image
|
|
144
|
-
image_name = row['filename'].split('/')[-1]
|
|
145
|
-
assert image_name in file_mappings, 'No mappings for image {} in deployment {}'.format(
|
|
146
|
-
image_name,deployment_name)
|
|
147
|
-
relative_path = os.path.join(deployment_name,file_mappings[image_name]).replace('\\','/')
|
|
148
|
-
|
|
149
|
-
# Make sure this image file exists
|
|
150
|
-
absolute_path = os.path.join(image_base,relative_path)
|
|
151
|
-
assert os.path.isfile(absolute_path), 'Could not find file {}'.format(absolute_path)
|
|
152
|
-
|
|
153
|
-
ground_truth_df.loc[i_row,'relative_path'] = relative_path
|
|
154
|
-
|
|
155
|
-
# ...for each row in the ground truth table
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
#%% Take everything out of Pandas
|
|
159
|
-
|
|
160
|
-
ground_truth_dicts = ground_truth_df.to_dict('records')
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
#%% Convert string timestamps to Python datetimes
|
|
164
|
-
|
|
165
|
-
all_locations = set()
|
|
166
|
-
|
|
167
|
-
# im = ground_truth_dicts[0]
|
|
168
|
-
for im in tqdm(ground_truth_dicts):
|
|
169
|
-
dt = dateutil.parser.isoparse(im['timestamp'])
|
|
170
|
-
assert dt.year == 2020
|
|
171
|
-
im['datetime'] = dt
|
|
172
|
-
|
|
173
|
-
# Filenames look like, e.g., N36/100EK113/06040726.JPG
|
|
174
|
-
im['location'] = im['relative_path'].split('/')[0]
|
|
175
|
-
assert len(im['location']) == 3
|
|
176
|
-
all_locations.add(im['location'])
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
#%% Synthesize sequence information
|
|
180
|
-
|
|
181
|
-
locations = all_locations
|
|
182
|
-
print('Found {} locations'.format(len(locations)))
|
|
183
|
-
|
|
184
|
-
locations = list(locations)
|
|
185
|
-
|
|
186
|
-
sequences = set()
|
|
187
|
-
sequence_to_images = defaultdict(list)
|
|
188
|
-
images = ground_truth_dicts
|
|
189
|
-
max_seconds_within_sequence = 10
|
|
190
|
-
|
|
191
|
-
# Sort images by time within each location
|
|
192
|
-
# i_location=0; location = locations[i_location]
|
|
193
|
-
for i_location,location in tqdm(enumerate(locations)):
|
|
194
|
-
|
|
195
|
-
images_this_location = [im for im in images if im['location'] == location]
|
|
196
|
-
sorted_images_this_location = sorted(images_this_location, key = lambda im: im['datetime'])
|
|
197
|
-
|
|
198
|
-
current_sequence_id = None
|
|
199
|
-
next_frame_number = 0
|
|
200
|
-
previous_datetime = None
|
|
201
|
-
|
|
202
|
-
# previous_datetime = sorted_images_this_location[0]['datetime']
|
|
203
|
-
# im = sorted_images_this_camera[1]
|
|
204
|
-
for i_image,im in enumerate(sorted_images_this_location):
|
|
205
|
-
|
|
206
|
-
# Timestamp for this image, may be None
|
|
207
|
-
dt = im['datetime']
|
|
208
|
-
|
|
209
|
-
# Start a new sequence if:
|
|
210
|
-
#
|
|
211
|
-
# * This image has no timestamp
|
|
212
|
-
# * This image has a frame number of zero
|
|
213
|
-
# * We have no previous image timestamp
|
|
214
|
-
#
|
|
215
|
-
if dt is None:
|
|
216
|
-
delta = None
|
|
217
|
-
elif previous_datetime is None:
|
|
218
|
-
delta = None
|
|
219
|
-
else:
|
|
220
|
-
assert isinstance(dt,datetime.datetime)
|
|
221
|
-
delta = (dt - previous_datetime).total_seconds()
|
|
222
|
-
|
|
223
|
-
# Start a new sequence if necessary
|
|
224
|
-
if delta is None or delta > max_seconds_within_sequence:
|
|
225
|
-
next_frame_number = 0
|
|
226
|
-
current_sequence_id = str(uuid.uuid1())
|
|
227
|
-
sequences.add(current_sequence_id)
|
|
228
|
-
assert current_sequence_id is not None
|
|
229
|
-
|
|
230
|
-
im['seq_id'] = current_sequence_id
|
|
231
|
-
im['synthetic_frame_number'] = next_frame_number
|
|
232
|
-
next_frame_number = next_frame_number + 1
|
|
233
|
-
previous_datetime = dt
|
|
234
|
-
sequence_to_images[im['seq_id']].append(im)
|
|
235
|
-
|
|
236
|
-
# ...for each image in this location
|
|
237
|
-
|
|
238
|
-
# ...for each location
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
#%% Create category dict and category IDs
|
|
242
|
-
|
|
243
|
-
categories_to_counts = defaultdict(int)
|
|
244
|
-
category_mappings = {'blank':'empty',
|
|
245
|
-
'mammal':'unknown_mammal',
|
|
246
|
-
'dasypus_species':'unknown_armadillo',
|
|
247
|
-
'bird':'unknown_bird',
|
|
248
|
-
'bos_species':'cattle',
|
|
249
|
-
'possum_family':'unknown_possum',
|
|
250
|
-
'cervidae_family':'unknown_cervid',
|
|
251
|
-
'unknown_species':'unknown',
|
|
252
|
-
'lizards_and_snakes':'unknown_reptile',
|
|
253
|
-
'caprimulgidae_family':'unknown_nightjar',
|
|
254
|
-
'turtle_order':'unknown_turtle',
|
|
255
|
-
'ornate_tití_monkey':'ornate_titi_monkey',
|
|
256
|
-
'saimiri_species':'unknown_squirrel_monkey',
|
|
257
|
-
'peccary_family':'unknown_peccary',
|
|
258
|
-
'pecari_species':'unknown_peccary',
|
|
259
|
-
'alouatta_species':'unknown_howler_monkey',
|
|
260
|
-
'human-camera_trapper':'human',
|
|
261
|
-
'weasel_family':'unknown_weasel',
|
|
262
|
-
'motorcycle':'human',
|
|
263
|
-
'eira_species':'unknown_tayra',
|
|
264
|
-
'sapajus_species':'unknown_capuchin_monkey',
|
|
265
|
-
'red_brocket':'red_brocket_deer'
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
for c in category_mappings.values():
|
|
269
|
-
assert ' ' not in c
|
|
270
|
-
|
|
271
|
-
# im = images[0]
|
|
272
|
-
for im in tqdm(images):
|
|
273
|
-
|
|
274
|
-
category_name = im['common_name'].lower().replace("'",'').replace(' ','_')
|
|
275
|
-
if category_name in category_mappings:
|
|
276
|
-
category_name = category_mappings[category_name]
|
|
277
|
-
categories_to_counts[category_name] += 1
|
|
278
|
-
im['category_name'] = category_name
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
categories_to_counts_sorted = {k: v for k, v in sorted(categories_to_counts.items(),
|
|
282
|
-
key=lambda item: item[1],reverse=True)}
|
|
283
|
-
|
|
284
|
-
for s in categories_to_counts_sorted.keys():
|
|
285
|
-
print('{}: {}'.format(s,categories_to_counts_sorted[s]))
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
#%% Imports and constants (.json generation)
|
|
289
|
-
|
|
290
|
-
import os
|
|
291
|
-
import uuid
|
|
292
|
-
import datetime
|
|
293
|
-
from tqdm import tqdm
|
|
294
|
-
|
|
295
|
-
from megadetector.data_management.databases import integrity_check_json_db
|
|
296
|
-
|
|
297
|
-
output_base = 'f:\orinoquia_camera_traps'
|
|
298
|
-
output_image_base = os.path.join(output_base,'images')
|
|
299
|
-
os.makedirs(output_image_base,exist_ok=True)
|
|
300
|
-
|
|
301
|
-
output_json_filename = os.path.join(output_base, 'orinoquia_camera_traps.json')
|
|
302
|
-
output_encoding = 'utf-8'
|
|
303
|
-
read_image_sizes = False
|
|
304
|
-
|
|
305
|
-
info = {}
|
|
306
|
-
info['year'] = 2020
|
|
307
|
-
info['version'] = '1.0'
|
|
308
|
-
info['description'] = 'Orinoquia Camera Traps'
|
|
309
|
-
info['contributor'] = 'University of Minnesota'
|
|
310
|
-
info['date_created'] = str(datetime.date.today())
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
#%% Count frames in each sequence
|
|
314
|
-
|
|
315
|
-
sequence_id_to_n_frames = defaultdict(int)
|
|
316
|
-
|
|
317
|
-
for im in tqdm(images):
|
|
318
|
-
seq_id = im['seq_id']
|
|
319
|
-
sequence_id_to_n_frames[seq_id] = sequence_id_to_n_frames[seq_id] + 1
|
|
320
|
-
|
|
321
|
-
for im in tqdm(images):
|
|
322
|
-
seq_id = im['seq_id']
|
|
323
|
-
im['seq_num_frames'] = sequence_id_to_n_frames[seq_id]
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
#%% Double check images with multiple annotations
|
|
327
|
-
|
|
328
|
-
filename_to_images = defaultdict(list)
|
|
329
|
-
|
|
330
|
-
# im = images[0]
|
|
331
|
-
for im in tqdm(images):
|
|
332
|
-
fn = im['relative_path']
|
|
333
|
-
filename_to_images[fn].append(im)
|
|
334
|
-
|
|
335
|
-
filenames_with_multiple_annotations = [fn for fn in filename_to_images.keys() if len(filename_to_images[fn]) > 1]
|
|
336
|
-
|
|
337
|
-
print('Found {} filenames with multiple annotations'.format(len(filenames_with_multiple_annotations)))
|
|
338
|
-
|
|
339
|
-
for fn in filenames_with_multiple_annotations:
|
|
340
|
-
images_this_file = filename_to_images[fn]
|
|
341
|
-
print(fn + ': ')
|
|
342
|
-
for im in images_this_file:
|
|
343
|
-
print(im['category_name'])
|
|
344
|
-
print('')
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
#%% Assemble dictionaries
|
|
348
|
-
|
|
349
|
-
images_out = []
|
|
350
|
-
image_id_to_image = {}
|
|
351
|
-
annotations = []
|
|
352
|
-
categories = []
|
|
353
|
-
|
|
354
|
-
category_name_to_category = {}
|
|
355
|
-
category_id_to_category = {}
|
|
356
|
-
|
|
357
|
-
# Force the empty category to be ID 0
|
|
358
|
-
empty_category = {}
|
|
359
|
-
empty_category['name'] = 'empty'
|
|
360
|
-
empty_category['id'] = 0
|
|
361
|
-
empty_category['count'] = 0
|
|
362
|
-
|
|
363
|
-
category_id_to_category[0] = empty_category
|
|
364
|
-
category_name_to_category['empty'] = empty_category
|
|
365
|
-
categories.append(empty_category)
|
|
366
|
-
next_id = 1
|
|
367
|
-
|
|
368
|
-
# input_im = images[0]
|
|
369
|
-
for input_im in tqdm(images):
|
|
370
|
-
|
|
371
|
-
category_name = input_im['category_name'].lower().strip()
|
|
372
|
-
|
|
373
|
-
if category_name not in category_name_to_category:
|
|
374
|
-
|
|
375
|
-
category_id = next_id
|
|
376
|
-
next_id += 1
|
|
377
|
-
category = {}
|
|
378
|
-
category['id'] = category_id
|
|
379
|
-
category['name'] = category_name
|
|
380
|
-
category['count'] = 0
|
|
381
|
-
categories.append(category)
|
|
382
|
-
category_name_to_category[category_name] = category
|
|
383
|
-
category_id_to_category[category_id] = category
|
|
384
|
-
|
|
385
|
-
else:
|
|
386
|
-
|
|
387
|
-
category = category_name_to_category[category_name]
|
|
388
|
-
|
|
389
|
-
category_id = category['id']
|
|
390
|
-
category['count'] += 1
|
|
391
|
-
|
|
392
|
-
im = {}
|
|
393
|
-
im['id'] = input_im['relative_path'].replace('/','_')
|
|
394
|
-
im['datetime'] = str(input_im['datetime'])
|
|
395
|
-
im['file_name'] = input_im['relative_path']
|
|
396
|
-
im['seq_id'] = input_im['seq_id']
|
|
397
|
-
im['frame_num'] = input_im['synthetic_frame_number']
|
|
398
|
-
im['seq_num_frames'] = input_im['seq_num_frames']
|
|
399
|
-
im['location'] = input_im['location']
|
|
400
|
-
|
|
401
|
-
if im['id'] in image_id_to_image:
|
|
402
|
-
print('Warning: image ID {} ({}) has multiple annotations'.format(im['id'],im['id'].replace('_','/')))
|
|
403
|
-
else:
|
|
404
|
-
image_id_to_image[im['id']] = im
|
|
405
|
-
images_out.append(im)
|
|
406
|
-
|
|
407
|
-
ann = {}
|
|
408
|
-
|
|
409
|
-
ann['id'] = str(uuid.uuid1())
|
|
410
|
-
ann['image_id'] = im['id']
|
|
411
|
-
ann['category_id'] = category_id
|
|
412
|
-
ann['sequence_level_annotation'] = False
|
|
413
|
-
annotations.append(ann)
|
|
414
|
-
|
|
415
|
-
# ...for each image
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
#%% Write output .json
|
|
419
|
-
|
|
420
|
-
data = {}
|
|
421
|
-
data['info'] = info
|
|
422
|
-
data['images'] = images_out
|
|
423
|
-
data['annotations'] = annotations
|
|
424
|
-
data['categories'] = categories
|
|
425
|
-
|
|
426
|
-
with open(output_json_filename, 'w') as f:
|
|
427
|
-
json.dump(data, f, indent=1)
|
|
428
|
-
|
|
429
|
-
print('Finished writing json to {}'.format(output_json_filename))
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
#%% Validate .json file
|
|
433
|
-
|
|
434
|
-
options = integrity_check_json_db.IntegrityCheckOptions()
|
|
435
|
-
options.baseDir = output_base
|
|
436
|
-
options.bCheckImageSizes = False
|
|
437
|
-
options.bCheckImageExistence = False
|
|
438
|
-
options.bFindUnusedImages = False
|
|
439
|
-
|
|
440
|
-
_, _, _ = integrity_check_json_db.integrity_check_json_db(output_json_filename, options)
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
#%% Map relative paths to annotation categories
|
|
444
|
-
|
|
445
|
-
category_id_to_category_names = {c['id']:c['name'] for c in data['categories']}
|
|
446
|
-
image_id_to_category_names = defaultdict(list)
|
|
447
|
-
|
|
448
|
-
# ann = data['annotations'][0]
|
|
449
|
-
for ann in data['annotations']:
|
|
450
|
-
category_name = category_id_to_category_names[ann['category_id']]
|
|
451
|
-
image_id_to_category_names[ann['image_id']].append(category_name)
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
#%% Copy images to output
|
|
455
|
-
|
|
456
|
-
# EXCLUDE HUMAN AND MISSING
|
|
457
|
-
|
|
458
|
-
# im = data['images'][0]
|
|
459
|
-
def copy_image(im):
|
|
460
|
-
|
|
461
|
-
image_id = im['id']
|
|
462
|
-
category_names_this_image = image_id_to_category_names[image_id]
|
|
463
|
-
assert len(category_names_this_image) > 0
|
|
464
|
-
if ('human' in category_names_this_image) or ('missing' in category_names_this_image):
|
|
465
|
-
prefix = 'private'
|
|
466
|
-
else:
|
|
467
|
-
prefix = 'public'
|
|
468
|
-
input_fn_absolute = os.path.join(image_base,im['file_name'])
|
|
469
|
-
output_fn_absolute = os.path.join(output_image_base,prefix,im['file_name'])
|
|
470
|
-
dirname = os.path.dirname(output_fn_absolute)
|
|
471
|
-
os.makedirs(dirname,exist_ok=True)
|
|
472
|
-
shutil.copy(input_fn_absolute,output_fn_absolute)
|
|
473
|
-
|
|
474
|
-
n_threads = 10
|
|
475
|
-
|
|
476
|
-
# im = images[0]
|
|
477
|
-
if n_threads == 1:
|
|
478
|
-
for im in tqdm(data['images']):
|
|
479
|
-
copy_image(im)
|
|
480
|
-
else:
|
|
481
|
-
pool = ThreadPool(n_threads)
|
|
482
|
-
with tqdm(total=len(data['images'])) as pbar:
|
|
483
|
-
for i,_ in enumerate(pool.imap_unordered(copy_image,data['images'])):
|
|
484
|
-
pbar.update()
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
#%% Preview labels
|
|
488
|
-
|
|
489
|
-
from megadetector.visualization import visualize_db
|
|
490
|
-
|
|
491
|
-
viz_options = visualize_db.DbVizOptions()
|
|
492
|
-
viz_options.num_to_visualize = 100
|
|
493
|
-
viz_options.trim_to_images_with_bboxes = False
|
|
494
|
-
viz_options.add_search_links = False
|
|
495
|
-
viz_options.sort_by_filename = False
|
|
496
|
-
viz_options.parallelize_rendering = True
|
|
497
|
-
viz_options.include_filename_links = True
|
|
498
|
-
|
|
499
|
-
# viz_options.classes_to_exclude = ['test']
|
|
500
|
-
html_output_file, _ = visualize_db.visualize_db(db_path=output_json_filename,
|
|
501
|
-
output_dir=os.path.join(
|
|
502
|
-
output_base,'preview'),
|
|
503
|
-
image_base_dir=os.path.join(output_image_base,'public'),
|
|
504
|
-
options=viz_options)
|
|
505
|
-
os.startfile(html_output_file)
|
|
506
|
-
|
|
507
|
-
|