megadetector 5.0.29__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/classification/efficientnet/model.py +8 -8
- megadetector/classification/efficientnet/utils.py +6 -5
- megadetector/classification/prepare_classification_script_mc.py +3 -3
- megadetector/data_management/annotations/annotation_constants.py +0 -1
- megadetector/data_management/camtrap_dp_to_coco.py +34 -1
- megadetector/data_management/cct_json_utils.py +2 -2
- megadetector/data_management/coco_to_yolo.py +22 -5
- megadetector/data_management/databases/add_width_and_height_to_db.py +85 -12
- megadetector/data_management/databases/combine_coco_camera_traps_files.py +2 -2
- megadetector/data_management/databases/integrity_check_json_db.py +29 -15
- megadetector/data_management/generate_crops_from_cct.py +50 -1
- megadetector/data_management/labelme_to_coco.py +4 -2
- megadetector/data_management/labelme_to_yolo.py +82 -2
- megadetector/data_management/lila/generate_lila_per_image_labels.py +276 -18
- megadetector/data_management/lila/get_lila_annotation_counts.py +5 -3
- megadetector/data_management/lila/lila_common.py +3 -0
- megadetector/data_management/lila/test_lila_metadata_urls.py +15 -5
- megadetector/data_management/mewc_to_md.py +5 -0
- megadetector/data_management/ocr_tools.py +4 -3
- megadetector/data_management/read_exif.py +20 -5
- megadetector/data_management/remap_coco_categories.py +66 -4
- megadetector/data_management/remove_exif.py +50 -1
- megadetector/data_management/rename_images.py +3 -3
- megadetector/data_management/resize_coco_dataset.py +563 -95
- megadetector/data_management/yolo_output_to_md_output.py +131 -2
- megadetector/data_management/yolo_to_coco.py +140 -5
- megadetector/detection/change_detection.py +4 -3
- megadetector/detection/pytorch_detector.py +60 -22
- megadetector/detection/run_detector.py +225 -25
- megadetector/detection/run_detector_batch.py +42 -16
- megadetector/detection/run_inference_with_yolov5_val.py +12 -2
- megadetector/detection/run_tiled_inference.py +1 -0
- megadetector/detection/video_utils.py +53 -24
- megadetector/postprocessing/add_max_conf.py +4 -0
- megadetector/postprocessing/categorize_detections_by_size.py +1 -1
- megadetector/postprocessing/classification_postprocessing.py +55 -20
- megadetector/postprocessing/combine_batch_outputs.py +3 -2
- megadetector/postprocessing/compare_batch_results.py +64 -10
- megadetector/postprocessing/convert_output_format.py +12 -8
- megadetector/postprocessing/create_crop_folder.py +137 -10
- megadetector/postprocessing/load_api_results.py +26 -8
- megadetector/postprocessing/md_to_coco.py +4 -4
- megadetector/postprocessing/md_to_labelme.py +18 -7
- megadetector/postprocessing/merge_detections.py +5 -0
- megadetector/postprocessing/postprocess_batch_results.py +6 -3
- megadetector/postprocessing/remap_detection_categories.py +55 -2
- megadetector/postprocessing/render_detection_confusion_matrix.py +9 -6
- megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +2 -2
- megadetector/taxonomy_mapping/map_new_lila_datasets.py +3 -4
- megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +40 -19
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +1 -1
- megadetector/taxonomy_mapping/species_lookup.py +123 -41
- megadetector/utils/ct_utils.py +133 -113
- megadetector/utils/md_tests.py +93 -13
- megadetector/utils/path_utils.py +137 -107
- megadetector/utils/split_locations_into_train_val.py +2 -2
- megadetector/utils/string_utils.py +7 -7
- megadetector/utils/url_utils.py +81 -58
- megadetector/utils/wi_utils.py +46 -17
- megadetector/visualization/plot_utils.py +13 -9
- megadetector/visualization/render_images_with_thumbnails.py +2 -1
- megadetector/visualization/visualization_utils.py +94 -46
- megadetector/visualization/visualize_db.py +36 -9
- megadetector/visualization/visualize_detector_output.py +4 -4
- {megadetector-5.0.29.dist-info → megadetector-10.0.0.dist-info}/METADATA +135 -135
- megadetector-10.0.0.dist-info/RECORD +139 -0
- {megadetector-5.0.29.dist-info → megadetector-10.0.0.dist-info}/licenses/LICENSE +0 -0
- {megadetector-5.0.29.dist-info → megadetector-10.0.0.dist-info}/top_level.txt +0 -0
- 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 -438
- 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 -109
- megadetector/utils/azure_utils.py +0 -178
- megadetector/utils/sas_blob_utils.py +0 -513
- megadetector-5.0.29.dist-info/RECORD +0 -163
- /megadetector/{api/batch_processing/__init__.py → __init__.py} +0 -0
- {megadetector-5.0.29.dist-info → megadetector-10.0.0.dist-info}/WHEEL +0 -0
|
@@ -32,6 +32,7 @@ import time
|
|
|
32
32
|
import json
|
|
33
33
|
import warnings
|
|
34
34
|
import tempfile
|
|
35
|
+
import zipfile
|
|
35
36
|
|
|
36
37
|
import humanfriendly
|
|
37
38
|
from tqdm import tqdm
|
|
@@ -40,6 +41,7 @@ from megadetector.utils import path_utils as path_utils
|
|
|
40
41
|
from megadetector.visualization import visualization_utils as vis_utils
|
|
41
42
|
from megadetector.utils.url_utils import download_url
|
|
42
43
|
from megadetector.utils.ct_utils import parse_kvp_list
|
|
44
|
+
from megadetector.utils.path_utils import compute_file_hash
|
|
43
45
|
|
|
44
46
|
# ignoring all "PIL cannot read EXIF metainfo for the images" warnings
|
|
45
47
|
warnings.filterwarnings('ignore', '(Possibly )?corrupt EXIF data', UserWarning)
|
|
@@ -81,29 +83,66 @@ USE_MODEL_NATIVE_CLASSES = False
|
|
|
81
83
|
#
|
|
82
84
|
# Order matters here.
|
|
83
85
|
model_string_to_model_version = {
|
|
86
|
+
|
|
87
|
+
# Specific model versions that might be expressed in a variety of ways
|
|
84
88
|
'mdv2':'v2.0.0',
|
|
85
89
|
'mdv3':'v3.0.0',
|
|
86
90
|
'mdv4':'v4.1.0',
|
|
87
|
-
'mdv5a':'v5a.0.
|
|
88
|
-
'mdv5b':'v5b.0.
|
|
91
|
+
'mdv5a':'v5a.0.1',
|
|
92
|
+
'mdv5b':'v5b.0.1',
|
|
93
|
+
|
|
89
94
|
'v2':'v2.0.0',
|
|
90
95
|
'v3':'v3.0.0',
|
|
91
96
|
'v4':'v4.1.0',
|
|
92
97
|
'v4.1':'v4.1.0',
|
|
93
|
-
'v5a.0.0':'v5a.0.
|
|
94
|
-
'v5b.0.0':'v5b.0.
|
|
98
|
+
'v5a.0.0':'v5a.0.1',
|
|
99
|
+
'v5b.0.0':'v5b.0.1',
|
|
100
|
+
|
|
101
|
+
'md1000-redwood':'v1000.0.0-redwood',
|
|
102
|
+
'md1000-cedar':'v1000.0.0-cedar',
|
|
103
|
+
'md1000-larch':'v1000.0.0-larch',
|
|
104
|
+
'md1000-sorrel':'v1000.0.0-sorrel',
|
|
105
|
+
'md1000-spruce':'v1000.0.0-spruce',
|
|
106
|
+
|
|
107
|
+
'mdv1000-redwood':'v1000.0.0-redwood',
|
|
108
|
+
'mdv1000-cedar':'v1000.0.0-cedar',
|
|
109
|
+
'mdv1000-larch':'v1000.0.0-larch',
|
|
110
|
+
'mdv1000-sorrel':'v1000.0.0-sorrel',
|
|
111
|
+
'mdv1000-spruce':'v1000.0.0-spruce',
|
|
112
|
+
|
|
113
|
+
'v1000-redwood':'v1000.0.0-redwood',
|
|
114
|
+
'v1000-cedar':'v1000.0.0-cedar',
|
|
115
|
+
'v1000-larch':'v1000.0.0-larch',
|
|
116
|
+
'v1000-sorrel':'v1000.0.0-sorrel',
|
|
117
|
+
'v1000-spruce':'v1000.0.0-spruce',
|
|
118
|
+
|
|
119
|
+
# Arguably less specific model versions
|
|
95
120
|
'redwood':'v1000.0.0-redwood',
|
|
96
121
|
'spruce':'v1000.0.0-spruce',
|
|
97
122
|
'cedar':'v1000.0.0-cedar',
|
|
98
123
|
'larch':'v1000.0.0-larch',
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
'
|
|
124
|
+
|
|
125
|
+
# Opinionated defaults
|
|
126
|
+
'mdv5':'v5a.0.1',
|
|
127
|
+
'md5':'v5a.0.1',
|
|
128
|
+
'mdv1000':'v1000.0.0-redwood',
|
|
129
|
+
'md1000':'v1000.0.0-redwood',
|
|
130
|
+
'default':'v5a.0.1',
|
|
131
|
+
'megadetector':'v5a.0.1',
|
|
102
132
|
}
|
|
103
133
|
|
|
104
|
-
|
|
134
|
+
# python -m http.server 8181
|
|
135
|
+
model_url_base = 'https://github.com/agentmorris/MegaDetector/releases/download/v1000.0/'
|
|
105
136
|
assert model_url_base.endswith('/')
|
|
106
137
|
|
|
138
|
+
if os.environ.get('MD_MODEL_URL_BASE') is not None:
|
|
139
|
+
model_url_base = os.environ['MD_MODEL_URL_BASE']
|
|
140
|
+
print('Model URL base provided via environment variable: {}'.format(
|
|
141
|
+
model_url_base
|
|
142
|
+
))
|
|
143
|
+
if not model_url_base.endswith('/'):
|
|
144
|
+
model_url_base += '/'
|
|
145
|
+
|
|
107
146
|
# Maps canonical model version numbers to metadata
|
|
108
147
|
known_models = {
|
|
109
148
|
'v2.0.0':
|
|
@@ -137,7 +176,8 @@ known_models = {
|
|
|
137
176
|
'conservative_detection_threshold':0.05,
|
|
138
177
|
'image_size':1280,
|
|
139
178
|
'model_type':'yolov5',
|
|
140
|
-
'normalized_typical_inference_speed':1.0
|
|
179
|
+
'normalized_typical_inference_speed':1.0,
|
|
180
|
+
'md5':'ec1d7603ec8cf642d6e0cd008ba2be8c'
|
|
141
181
|
},
|
|
142
182
|
'v5b.0.0':
|
|
143
183
|
{
|
|
@@ -146,29 +186,58 @@ known_models = {
|
|
|
146
186
|
'conservative_detection_threshold':0.05,
|
|
147
187
|
'image_size':1280,
|
|
148
188
|
'model_type':'yolov5',
|
|
149
|
-
'normalized_typical_inference_speed':1.0
|
|
189
|
+
'normalized_typical_inference_speed':1.0,
|
|
190
|
+
'md5':'bc235e73f53c5c95e66ea0d1b2cbf542'
|
|
191
|
+
},
|
|
192
|
+
'v5a.0.1':
|
|
193
|
+
{
|
|
194
|
+
'url':'https://github.com/agentmorris/MegaDetector/releases/download/v5.0/md_v5a.0.1.pt',
|
|
195
|
+
'typical_detection_threshold':0.2,
|
|
196
|
+
'conservative_detection_threshold':0.05,
|
|
197
|
+
'image_size':1280,
|
|
198
|
+
'model_type':'yolov5',
|
|
199
|
+
'normalized_typical_inference_speed':1.0,
|
|
200
|
+
'md5':'60f8e7ec1308554df258ed1f4040bc4f'
|
|
201
|
+
},
|
|
202
|
+
'v5b.0.1':
|
|
203
|
+
{
|
|
204
|
+
'url':'https://github.com/agentmorris/MegaDetector/releases/download/v5.0/md_v5b.0.1.pt',
|
|
205
|
+
'typical_detection_threshold':0.2,
|
|
206
|
+
'conservative_detection_threshold':0.05,
|
|
207
|
+
'image_size':1280,
|
|
208
|
+
'model_type':'yolov5',
|
|
209
|
+
'normalized_typical_inference_speed':1.0,
|
|
210
|
+
'md5':'f17ed6fedfac2e403606a08c89984905'
|
|
150
211
|
},
|
|
151
|
-
|
|
152
|
-
# Fake values for testing
|
|
153
212
|
'v1000.0.0-redwood':
|
|
154
213
|
{
|
|
155
|
-
'
|
|
156
|
-
'
|
|
214
|
+
'url':model_url_base + 'md_v1000.0.0-redwood.pt',
|
|
215
|
+
'normalized_typical_inference_speed':1.0,
|
|
216
|
+
'md5':'74474b3aec9cf1a990da38b37ddf9197'
|
|
157
217
|
},
|
|
158
218
|
'v1000.0.0-spruce':
|
|
159
219
|
{
|
|
160
|
-
'
|
|
161
|
-
'
|
|
220
|
+
'url':model_url_base + 'md_v1000.0.0-spruce.pt',
|
|
221
|
+
'normalized_typical_inference_speed':12.7,
|
|
222
|
+
'md5':'1c9d1d2b3ba54931881471fdd508e6f2'
|
|
162
223
|
},
|
|
163
224
|
'v1000.0.0-larch':
|
|
164
225
|
{
|
|
165
|
-
'
|
|
166
|
-
'
|
|
226
|
+
'url':model_url_base + 'md_v1000.0.0-larch.pt',
|
|
227
|
+
'normalized_typical_inference_speed':2.4,
|
|
228
|
+
'md5':'cab94ebd190c2278e12fb70ffd548b6d'
|
|
167
229
|
},
|
|
168
230
|
'v1000.0.0-cedar':
|
|
169
231
|
{
|
|
170
|
-
'
|
|
171
|
-
'
|
|
232
|
+
'url':model_url_base + 'md_v1000.0.0-cedar.pt',
|
|
233
|
+
'normalized_typical_inference_speed':2.0,
|
|
234
|
+
'md5':'3d6472c9b95ba687b59ebe255f7c576b'
|
|
235
|
+
},
|
|
236
|
+
'v1000.0.0-sorrel':
|
|
237
|
+
{
|
|
238
|
+
'url':model_url_base + 'md_v1000.0.0-sorrel.pt',
|
|
239
|
+
'normalized_typical_inference_speed':7.0,
|
|
240
|
+
'md5':'4339a2c8af7a381f18ded7ac2a4df03e'
|
|
172
241
|
}
|
|
173
242
|
}
|
|
174
243
|
|
|
@@ -337,7 +406,8 @@ def get_detector_version_from_model_file(detector_filename,verbose=False):
|
|
|
337
406
|
|
|
338
407
|
if version_string_based_on_filename != version_string_based_on_model_file:
|
|
339
408
|
print(
|
|
340
|
-
'Warning: model version string in file
|
|
409
|
+
'Warning: model version string in file:' + \
|
|
410
|
+
'\n\n{}\n\n...is:\n\n{}\n\n...but the filename implies:\n\n{}'.format(
|
|
341
411
|
os.path.basename(detector_filename),
|
|
342
412
|
version_string_based_on_model_file,
|
|
343
413
|
version_string_based_on_filename))
|
|
@@ -602,6 +672,7 @@ def load_and_run_detector(model_file,
|
|
|
602
672
|
exists
|
|
603
673
|
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
604
674
|
by different detectors
|
|
675
|
+
verbose (bool, optional): enable additional debug output
|
|
605
676
|
"""
|
|
606
677
|
|
|
607
678
|
if len(image_file_names) == 0:
|
|
@@ -707,7 +778,8 @@ def load_and_run_detector(model_file,
|
|
|
707
778
|
time_infer.append(elapsed)
|
|
708
779
|
|
|
709
780
|
except Exception as e:
|
|
710
|
-
print('An error occurred while running the detector on image {}: {}'.format(
|
|
781
|
+
print('An error occurred while running the detector on image {}: {}'.format(
|
|
782
|
+
im_file, str(e)))
|
|
711
783
|
continue
|
|
712
784
|
|
|
713
785
|
try:
|
|
@@ -756,6 +828,55 @@ def load_and_run_detector(model_file,
|
|
|
756
828
|
# ...def load_and_run_detector()
|
|
757
829
|
|
|
758
830
|
|
|
831
|
+
def _validate_zip_file(file_path, file_description='file'):
|
|
832
|
+
"""
|
|
833
|
+
Validates that a .pt file is a valid zip file.
|
|
834
|
+
|
|
835
|
+
Args:
|
|
836
|
+
file_path (str): path to the file to validate
|
|
837
|
+
file_description (str): descriptive string for error messages
|
|
838
|
+
|
|
839
|
+
Returns:
|
|
840
|
+
bool: True if valid, False otherwise
|
|
841
|
+
"""
|
|
842
|
+
try:
|
|
843
|
+
with zipfile.ZipFile(file_path, 'r') as zipf:
|
|
844
|
+
zipf.testzip()
|
|
845
|
+
return True
|
|
846
|
+
except (zipfile.BadZipFile, zipfile.LargeZipFile) as e:
|
|
847
|
+
print('{} {} appears to be corrupted (bad zip): {}'.format(
|
|
848
|
+
file_description.capitalize(), file_path, str(e)))
|
|
849
|
+
return False
|
|
850
|
+
except Exception as e:
|
|
851
|
+
print('Error validating {}: {}'.format(file_description, str(e)))
|
|
852
|
+
return False
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
def _validate_md5_hash(file_path, expected_hash, file_description='file'):
|
|
856
|
+
"""
|
|
857
|
+
Validates that a file has the expected MD5 hash.
|
|
858
|
+
|
|
859
|
+
Args:
|
|
860
|
+
file_path (str): path to the file to validate
|
|
861
|
+
expected_hash (str): expected MD5 hash
|
|
862
|
+
file_description (str): descriptive string for error messages
|
|
863
|
+
|
|
864
|
+
Returns:
|
|
865
|
+
bool: True if hash matches, False otherwise
|
|
866
|
+
"""
|
|
867
|
+
try:
|
|
868
|
+
actual_hash = compute_file_hash(file_path, algorithm='md5').lower()
|
|
869
|
+
expected_hash = expected_hash.lower()
|
|
870
|
+
if actual_hash != expected_hash:
|
|
871
|
+
print('{} {} has incorrect hash. Expected: {}, Actual: {}'.format(
|
|
872
|
+
file_description.capitalize(), file_path, expected_hash, actual_hash))
|
|
873
|
+
return False
|
|
874
|
+
return True
|
|
875
|
+
except Exception as e:
|
|
876
|
+
print('Error computing hash for {}: {}'.format(file_description, str(e)))
|
|
877
|
+
return False
|
|
878
|
+
|
|
879
|
+
|
|
759
880
|
def _download_model(model_name,force_download=False):
|
|
760
881
|
"""
|
|
761
882
|
Downloads one of the known models to local temp space if it hasn't already been downloaded.
|
|
@@ -782,13 +903,92 @@ def _download_model(model_name,force_download=False):
|
|
|
782
903
|
if model_name.lower() not in known_models:
|
|
783
904
|
print('Unrecognized downloadable model {}'.format(model_name))
|
|
784
905
|
return None
|
|
785
|
-
|
|
906
|
+
|
|
907
|
+
model_info = known_models[model_name.lower()]
|
|
908
|
+
url = model_info['url']
|
|
786
909
|
destination_filename = os.path.join(model_tempdir,url.split('/')[-1])
|
|
787
|
-
|
|
788
|
-
|
|
910
|
+
|
|
911
|
+
# Check whether the file already exists, in which case we want to validate it
|
|
912
|
+
if os.path.exists(destination_filename) and not force_download:
|
|
913
|
+
|
|
914
|
+
# Only validate .pt files, not .pb files
|
|
915
|
+
if destination_filename.endswith('.pt'):
|
|
916
|
+
|
|
917
|
+
is_valid = True
|
|
918
|
+
|
|
919
|
+
# Check whether the file is a valid zip file (.pt files are zip files in disguise)
|
|
920
|
+
if not _validate_zip_file(destination_filename,
|
|
921
|
+
'existing model file'):
|
|
922
|
+
is_valid = False
|
|
923
|
+
|
|
924
|
+
# Check MD5 hash if available
|
|
925
|
+
if is_valid and \
|
|
926
|
+
('md5' in model_info) and \
|
|
927
|
+
(model_info['md5'] is not None) and \
|
|
928
|
+
(len(model_info['md5'].strip()) > 0):
|
|
929
|
+
|
|
930
|
+
if not _validate_md5_hash(destination_filename, model_info['md5'],
|
|
931
|
+
'existing model file'):
|
|
932
|
+
is_valid = False
|
|
933
|
+
|
|
934
|
+
# If validation failed, delete the corrupted file and re-download
|
|
935
|
+
if not is_valid:
|
|
936
|
+
print('Deleting corrupted model file and re-downloading: {}'.format(
|
|
937
|
+
destination_filename))
|
|
938
|
+
try:
|
|
939
|
+
os.remove(destination_filename)
|
|
940
|
+
# This should be a no-op at this point, but it can't hurt
|
|
941
|
+
force_download = True
|
|
942
|
+
except Exception as e:
|
|
943
|
+
print('Warning: failed to delete corrupted file {}: {}'.format(
|
|
944
|
+
destination_filename, str(e)))
|
|
945
|
+
# Continue with download attempt anyway, setting force_download to True
|
|
946
|
+
force_download = True
|
|
947
|
+
else:
|
|
948
|
+
print('Model {} already exists and is valid at {}'.format(
|
|
949
|
+
model_name, destination_filename))
|
|
950
|
+
return destination_filename
|
|
951
|
+
|
|
952
|
+
# Download the model
|
|
953
|
+
try:
|
|
954
|
+
local_file = download_url(url,
|
|
955
|
+
destination_filename=destination_filename,
|
|
956
|
+
progress_updater=None,
|
|
957
|
+
force_download=force_download,
|
|
958
|
+
verbose=True)
|
|
959
|
+
except Exception as e:
|
|
960
|
+
print('Error downloading model {} from {}: {}'.format(model_name, url, str(e)))
|
|
961
|
+
raise
|
|
962
|
+
|
|
963
|
+
# Validate the downloaded file if it's a .pt file
|
|
964
|
+
if local_file and local_file.endswith('.pt'):
|
|
965
|
+
|
|
966
|
+
# Check if the downloaded file is a valid zip file
|
|
967
|
+
if not _validate_zip_file(local_file, "downloaded model file"):
|
|
968
|
+
# Clean up the corrupted download
|
|
969
|
+
try:
|
|
970
|
+
os.remove(local_file)
|
|
971
|
+
except Exception:
|
|
972
|
+
pass
|
|
973
|
+
return None
|
|
974
|
+
|
|
975
|
+
# Check MD5 hash if available
|
|
976
|
+
if ('md5' in model_info) and \
|
|
977
|
+
(model_info['md5'] is not None) and \
|
|
978
|
+
(len(model_info['md5'].strip()) > 0):
|
|
979
|
+
|
|
980
|
+
if not _validate_md5_hash(local_file, model_info['md5'], "downloaded model file"):
|
|
981
|
+
# Clean up the corrupted download
|
|
982
|
+
try:
|
|
983
|
+
os.remove(local_file)
|
|
984
|
+
except Exception:
|
|
985
|
+
pass
|
|
986
|
+
return None
|
|
987
|
+
|
|
789
988
|
print('Model {} available at {}'.format(model_name,local_file))
|
|
790
989
|
return local_file
|
|
791
990
|
|
|
991
|
+
# ...def _download_model(...)
|
|
792
992
|
|
|
793
993
|
def try_download_known_detector(detector_file,force_download=False,verbose=False):
|
|
794
994
|
"""
|
|
@@ -135,7 +135,9 @@ def _producer_func(q,
|
|
|
135
135
|
assert isinstance(preprocessor,str)
|
|
136
136
|
detector_options = deepcopy(detector_options)
|
|
137
137
|
detector_options['preprocess_only'] = True
|
|
138
|
-
preprocessor = load_detector(preprocessor,
|
|
138
|
+
preprocessor = load_detector(preprocessor,
|
|
139
|
+
detector_options=detector_options,
|
|
140
|
+
verbose=verbose)
|
|
139
141
|
|
|
140
142
|
for im_file in image_files:
|
|
141
143
|
|
|
@@ -209,7 +211,9 @@ def _consumer_func(q,
|
|
|
209
211
|
start_time = time.time()
|
|
210
212
|
|
|
211
213
|
if isinstance(model_file,str):
|
|
212
|
-
detector = load_detector(model_file,
|
|
214
|
+
detector = load_detector(model_file,
|
|
215
|
+
detector_options=detector_options,
|
|
216
|
+
verbose=verbose)
|
|
213
217
|
elapsed = time.time() - start_time
|
|
214
218
|
print('Loaded model (before queueing) in {}, printing updates every {} images'.format(
|
|
215
219
|
humanfriendly.format_timespan(elapsed),n_queue_print))
|
|
@@ -323,7 +327,7 @@ def run_detector_with_image_queue(image_files,
|
|
|
323
327
|
confidence_threshold (float): minimum confidence detection to include in
|
|
324
328
|
output
|
|
325
329
|
quiet (bool, optional): suppress per-image console printouts
|
|
326
|
-
image_size (
|
|
330
|
+
image_size (int, optional): image size to use for inference, only mess with this
|
|
327
331
|
if (a) you're using a model other than MegaDetector or (b) you know what you're
|
|
328
332
|
doing
|
|
329
333
|
include_image_size (bool, optional): should we include image size in the output for each image?
|
|
@@ -333,6 +337,8 @@ def run_detector_with_image_queue(image_files,
|
|
|
333
337
|
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
334
338
|
by different detectors
|
|
335
339
|
loader_workers (int, optional): number of loaders to use
|
|
340
|
+
preprocess_on_image_queue (bool, optional): if the image queue is enabled, should it handle
|
|
341
|
+
image loading and preprocessing (True), or just image loading (False)?
|
|
336
342
|
|
|
337
343
|
Returns:
|
|
338
344
|
list: list of dicts in the format returned by process_image()
|
|
@@ -344,6 +350,9 @@ def run_detector_with_image_queue(image_files,
|
|
|
344
350
|
if loader_workers <= 0:
|
|
345
351
|
loader_workers = 1
|
|
346
352
|
|
|
353
|
+
if detector_options is None:
|
|
354
|
+
detector_options = {}
|
|
355
|
+
|
|
347
356
|
q = multiprocessing.JoinableQueue(max_queue_size)
|
|
348
357
|
return_queue = multiprocessing.Queue(1)
|
|
349
358
|
|
|
@@ -492,13 +501,13 @@ def process_images(im_files,
|
|
|
492
501
|
Runs a detector (typically MegaDetector) over a list of image files on a single thread.
|
|
493
502
|
|
|
494
503
|
Args:
|
|
495
|
-
im_files (list: paths to image files
|
|
504
|
+
im_files (list): paths to image files
|
|
496
505
|
detector (str or detector object): loaded model or str; if this is a string, it can be a
|
|
497
506
|
path to a .pb/.pt model file or a known model identifier (e.g. "MDV5A")
|
|
498
507
|
confidence_threshold (float): only detections above this threshold are returned
|
|
499
508
|
use_image_queue (bool, optional): separate image loading onto a dedicated worker process
|
|
500
509
|
quiet (bool, optional): suppress per-image printouts
|
|
501
|
-
image_size (
|
|
510
|
+
image_size (int, optional): image size to use for inference, only mess with this
|
|
502
511
|
if (a) you're using a model other than MegaDetector or (b) you know what you're
|
|
503
512
|
doing
|
|
504
513
|
checkpoint_queue (Queue, optional): internal parameter used to pass image queues around
|
|
@@ -509,6 +518,8 @@ def process_images(im_files,
|
|
|
509
518
|
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
510
519
|
by different detectors
|
|
511
520
|
loader_workers (int, optional): number of loaders to use (only relevant when using image queue)
|
|
521
|
+
preprocess_on_image_queue (bool, optional): if the image queue is enabled, should it handle
|
|
522
|
+
image loading and preprocessing (True), or just image loading (False)?
|
|
512
523
|
|
|
513
524
|
Returns:
|
|
514
525
|
list: list of dicts, in which each dict represents detections on one image,
|
|
@@ -518,10 +529,15 @@ def process_images(im_files,
|
|
|
518
529
|
if isinstance(detector, str):
|
|
519
530
|
|
|
520
531
|
start_time = time.time()
|
|
521
|
-
detector = load_detector(detector,
|
|
532
|
+
detector = load_detector(detector,
|
|
533
|
+
detector_options=detector_options,
|
|
534
|
+
verbose=verbose)
|
|
522
535
|
elapsed = time.time() - start_time
|
|
523
536
|
print('Loaded model (batch level) in {}'.format(humanfriendly.format_timespan(elapsed)))
|
|
524
537
|
|
|
538
|
+
if detector_options is None:
|
|
539
|
+
detector_options = {}
|
|
540
|
+
|
|
525
541
|
if use_image_queue:
|
|
526
542
|
|
|
527
543
|
run_detector_with_image_queue(im_files,
|
|
@@ -582,7 +598,7 @@ def process_image(im_file,
|
|
|
582
598
|
image (Image, optional): previously-loaded image, if available, used when a worker
|
|
583
599
|
thread is handling image loads
|
|
584
600
|
quiet (bool, optional): suppress per-image printouts
|
|
585
|
-
image_size (
|
|
601
|
+
image_size (int, optional): image size to use for inference, only mess with this
|
|
586
602
|
if (a) you're using a model other than MegaDetector or (b) you know what you're
|
|
587
603
|
doing
|
|
588
604
|
include_image_size (bool, optional): should we include image size in the output for each image?
|
|
@@ -705,7 +721,7 @@ def load_and_run_detector_batch(model_file,
|
|
|
705
721
|
image_file_names (list or str): list of strings (image filenames), a single image filename,
|
|
706
722
|
a folder to recursively search for images in, or a .json or .txt file containing a list
|
|
707
723
|
of images.
|
|
708
|
-
checkpoint_path (str, optional)
|
|
724
|
+
checkpoint_path (str, optional): path to use for checkpoints (if None, checkpointing
|
|
709
725
|
is disabled)
|
|
710
726
|
confidence_threshold (float, optional): only detections above this threshold are returned
|
|
711
727
|
checkpoint_frequency (int, optional): int, write results to JSON checkpoint file every N
|
|
@@ -715,10 +731,10 @@ def load_and_run_detector_batch(model_file,
|
|
|
715
731
|
n_cores (int, optional): number of parallel worker to use, ignored if we're running on a GPU
|
|
716
732
|
use_image_queue (bool, optional): use a dedicated worker for image loading
|
|
717
733
|
quiet (bool, optional): disable per-image console output
|
|
718
|
-
image_size (
|
|
734
|
+
image_size (int, optional): image size to use for inference, only mess with this
|
|
719
735
|
if (a) you're using a model other than MegaDetector or (b) you know what you're
|
|
720
736
|
doing
|
|
721
|
-
class_mapping_filename (str, optional)
|
|
737
|
+
class_mapping_filename (str, optional): use a non-default class mapping supplied in a .json
|
|
722
738
|
file or YOLOv5 dataset.yaml file
|
|
723
739
|
include_image_size (bool, optional): should we include image size in the output for each image?
|
|
724
740
|
include_image_timestamp (bool, optional): should we include image timestamps in the output for each image?
|
|
@@ -730,6 +746,8 @@ def load_and_run_detector_batch(model_file,
|
|
|
730
746
|
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
731
747
|
by different detectors
|
|
732
748
|
loader_workers (int, optional): number of loaders to use, only relevant when use_image_queue is True
|
|
749
|
+
preprocess_on_image_queue (bool, optional): if the image queue is enabled, should it handle
|
|
750
|
+
image loading and preprocessing (True), or just image loading (False)?
|
|
733
751
|
|
|
734
752
|
Returns:
|
|
735
753
|
results: list of dicts; each dict represents detections on one image
|
|
@@ -739,6 +757,9 @@ def load_and_run_detector_batch(model_file,
|
|
|
739
757
|
if n_cores is None or n_cores <= 0:
|
|
740
758
|
n_cores = 1
|
|
741
759
|
|
|
760
|
+
if detector_options is None:
|
|
761
|
+
detector_options = {}
|
|
762
|
+
|
|
742
763
|
if confidence_threshold is None:
|
|
743
764
|
confidence_threshold=run_detector.DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD
|
|
744
765
|
|
|
@@ -790,17 +811,19 @@ def load_and_run_detector_batch(model_file,
|
|
|
790
811
|
|
|
791
812
|
already_processed = set([i['file'] for i in results])
|
|
792
813
|
|
|
793
|
-
model_file = try_download_known_detector(model_file,
|
|
814
|
+
model_file = try_download_known_detector(model_file,
|
|
815
|
+
force_download=force_model_download,
|
|
816
|
+
verbose=verbose)
|
|
794
817
|
|
|
795
818
|
print('GPU available: {}'.format(is_gpu_available(model_file)))
|
|
796
819
|
|
|
797
|
-
if n_cores > 1 and is_gpu_available(model_file):
|
|
820
|
+
if (n_cores > 1) and is_gpu_available(model_file):
|
|
798
821
|
|
|
799
822
|
print('Warning: multiple cores requested, but a GPU is available; parallelization across ' + \
|
|
800
823
|
'GPUs is not currently supported, defaulting to one GPU')
|
|
801
824
|
n_cores = 1
|
|
802
825
|
|
|
803
|
-
if n_cores > 1 and use_image_queue:
|
|
826
|
+
if (n_cores > 1) and use_image_queue:
|
|
804
827
|
|
|
805
828
|
print('Warning: multiple cores requested, but the image queue is enabled; parallelization ' + \
|
|
806
829
|
'with the image queue is not currently supported, defaulting to one worker')
|
|
@@ -830,7 +853,9 @@ def load_and_run_detector_batch(model_file,
|
|
|
830
853
|
|
|
831
854
|
# Load the detector
|
|
832
855
|
start_time = time.time()
|
|
833
|
-
detector = load_detector(model_file,
|
|
856
|
+
detector = load_detector(model_file,
|
|
857
|
+
detector_options=detector_options,
|
|
858
|
+
verbose=verbose)
|
|
834
859
|
elapsed = time.time() - start_time
|
|
835
860
|
print('Loaded model in {}'.format(humanfriendly.format_timespan(elapsed)))
|
|
836
861
|
|
|
@@ -1089,7 +1114,7 @@ def write_results_to_file(results,
|
|
|
1089
1114
|
|
|
1090
1115
|
if detector_file is not None:
|
|
1091
1116
|
detector_filename = os.path.basename(detector_file)
|
|
1092
|
-
detector_version = get_detector_version_from_filename(detector_filename)
|
|
1117
|
+
detector_version = get_detector_version_from_filename(detector_filename,verbose=True)
|
|
1093
1118
|
detector_metadata = get_detector_metadata_from_version_string(detector_version)
|
|
1094
1119
|
info['detector'] = detector_filename
|
|
1095
1120
|
info['detector_metadata'] = detector_metadata
|
|
@@ -1409,7 +1434,8 @@ def main(): # noqa
|
|
|
1409
1434
|
# If the specified detector file is really the name of a known model, find
|
|
1410
1435
|
# (and possibly download) that model
|
|
1411
1436
|
args.detector_file = try_download_known_detector(args.detector_file,
|
|
1412
|
-
force_download=args.force_model_download
|
|
1437
|
+
force_download=args.force_model_download,
|
|
1438
|
+
verbose=verbose)
|
|
1413
1439
|
|
|
1414
1440
|
assert os.path.exists(args.detector_file), \
|
|
1415
1441
|
'detector file {} does not exist'.format(args.detector_file)
|
|
@@ -191,6 +191,10 @@ class YoloInferenceOptions:
|
|
|
191
191
|
#: across calls, this can be set to False.
|
|
192
192
|
self.append_job_id_to_symlink_folder = True
|
|
193
193
|
|
|
194
|
+
#: By default, we turn category ID 0 coming out of the YOLO .json file
|
|
195
|
+
#: into category 1 in the MD-formatted .json file.
|
|
196
|
+
self.offset_yolo_category_ids = True
|
|
197
|
+
|
|
194
198
|
# ...def __init__()
|
|
195
199
|
|
|
196
200
|
# ...YoloInferenceOptions()
|
|
@@ -225,7 +229,7 @@ def get_stats_for_category(filename,category='all'):
|
|
|
225
229
|
|
|
226
230
|
Args:
|
|
227
231
|
filename (str): a text file containing console output from a YOLO val run
|
|
228
|
-
category (
|
|
232
|
+
category (str, optional): a category name
|
|
229
233
|
|
|
230
234
|
Returns:
|
|
231
235
|
dict: a dict with fields n_images, n_labels, P, R, mAP50, and mAP50-95
|
|
@@ -957,7 +961,8 @@ def run_inference_with_yolo_val(options):
|
|
|
957
961
|
yolo_category_id_to_name=options.yolo_category_id_to_name,
|
|
958
962
|
detector_name=os.path.basename(model_filename),
|
|
959
963
|
image_id_to_relative_path=image_id_to_relative_path,
|
|
960
|
-
image_id_to_error=image_id_to_error
|
|
964
|
+
image_id_to_error=image_id_to_error,
|
|
965
|
+
offset_yolo_class_ids=options.offset_yolo_category_ids)
|
|
961
966
|
|
|
962
967
|
|
|
963
968
|
##%% Clean up
|
|
@@ -1058,6 +1063,9 @@ def main(): # noqa
|
|
|
1058
1063
|
parser.add_argument(
|
|
1059
1064
|
'--nonrecursive', action='store_true',
|
|
1060
1065
|
help='disable recursive folder processing')
|
|
1066
|
+
parser.add_argument(
|
|
1067
|
+
'--no_offset_class_ids', action='store_true',
|
|
1068
|
+
help='disable class ID offsetting')
|
|
1061
1069
|
|
|
1062
1070
|
parser.add_argument(
|
|
1063
1071
|
'--preview_yolo_command_only', action='store_true',
|
|
@@ -1113,6 +1121,7 @@ def main(): # noqa
|
|
|
1113
1121
|
options.remove_yolo_results_folder = (not options.no_remove_yolo_results_folder)
|
|
1114
1122
|
options.use_symlinks = (not options.no_use_symlinks)
|
|
1115
1123
|
options.augment = (options.augment_enabled > 0)
|
|
1124
|
+
options.offset_yolo_category_ids = (not options.no_offset_class_ids)
|
|
1116
1125
|
|
|
1117
1126
|
del options.nonrecursive
|
|
1118
1127
|
del options.no_remove_symlink_folder
|
|
@@ -1120,6 +1129,7 @@ def main(): # noqa
|
|
|
1120
1129
|
del options.no_use_symlinks
|
|
1121
1130
|
del options.augment_enabled
|
|
1122
1131
|
del options.yolo_dataset_file
|
|
1132
|
+
del options.no_offset_class_ids
|
|
1123
1133
|
|
|
1124
1134
|
print(options.__dict__)
|
|
1125
1135
|
|
|
@@ -447,6 +447,7 @@ def run_tiled_inference(model_file,
|
|
|
447
447
|
run_inference_with_yolov5_val.py, rather than with run_detector_batch.py, using these options
|
|
448
448
|
n_patch_extraction_workers (int, optional): number of workers to use for patch extraction;
|
|
449
449
|
set to <= 1 to disable parallelization
|
|
450
|
+
overwrite_tiles (bool, optional): whether to overwrite image files for individual tiles if they exist
|
|
450
451
|
image_list (list, optional): .json file containing a list of specific images to process. If
|
|
451
452
|
this is supplied, and the paths are absolute, [image_folder] will be ignored. If this is supplied,
|
|
452
453
|
and the paths are relative, they should be relative to [image_folder]
|