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
|
@@ -10,8 +10,8 @@ https://github.com/agentmorris/MegaDetector/tree/main/megadetector/api/batch_pro
|
|
|
10
10
|
This enables the results to be used in our post-processing pipeline; see postprocess_batch_results.py.
|
|
11
11
|
|
|
12
12
|
This script can save results to checkpoints intermittently, in case disaster
|
|
13
|
-
strikes. To enable this, set --checkpoint_frequency to n > 0, and results
|
|
14
|
-
will be saved as a checkpoint every n images. Checkpoints will be written
|
|
13
|
+
strikes. To enable this, set --checkpoint_frequency to n > 0, and results
|
|
14
|
+
will be saved as a checkpoint every n images. Checkpoints will be written
|
|
15
15
|
to a file in the same directory as the output_file, and after all images
|
|
16
16
|
are processed and final results file written to output_file, the temporary
|
|
17
17
|
checkpoint file will be deleted. If you want to resume from a checkpoint, set
|
|
@@ -26,10 +26,10 @@ run a gazillion MegaDetector images on multiple GPUs using this script, we just
|
|
|
26
26
|
one GPU *per invocation of this script*. Dividing a big batch of images into one chunk
|
|
27
27
|
per GPU happens outside of this script.
|
|
28
28
|
|
|
29
|
-
Does not have a command-line option to bind the process to a particular GPU, but you can
|
|
29
|
+
Does not have a command-line option to bind the process to a particular GPU, but you can
|
|
30
30
|
prepend with "CUDA_VISIBLE_DEVICES=0 ", for example, to bind to GPU 0, e.g.:
|
|
31
31
|
|
|
32
|
-
CUDA_VISIBLE_DEVICES=0 python detection/run_detector_batch.py md_v4.1.0.pb ~/data ~/mdv4test.json
|
|
32
|
+
CUDA_VISIBLE_DEVICES=0 python detection/run_detector_batch.py md_v4.1.0.pb ~/data ~/mdv4test.json
|
|
33
33
|
|
|
34
34
|
You can disable GPU processing entirely by setting CUDA_VISIBLE_DEVICES=''.
|
|
35
35
|
|
|
@@ -70,6 +70,7 @@ from megadetector.detection.run_detector import \
|
|
|
70
70
|
get_detector_metadata_from_version_string
|
|
71
71
|
|
|
72
72
|
from megadetector.utils import path_utils
|
|
73
|
+
from megadetector.utils import ct_utils
|
|
73
74
|
from megadetector.utils.ct_utils import parse_kvp_list
|
|
74
75
|
from megadetector.utils.ct_utils import split_list_into_n_chunks
|
|
75
76
|
from megadetector.utils.ct_utils import sort_list_of_dicts_by_key
|
|
@@ -92,7 +93,7 @@ max_queue_size = 10
|
|
|
92
93
|
# How often should we print progress when using the image queue?
|
|
93
94
|
n_queue_print = 1000
|
|
94
95
|
|
|
95
|
-
# TODO: it's a little sloppy that these are module-level globals, but in practice it
|
|
96
|
+
# TODO: it's a little sloppy that these are module-level globals, but in practice it
|
|
96
97
|
# doesn't really matter, so I'm not in a big rush to move these to options until I do
|
|
97
98
|
# a larger cleanup of all the long argument lists in this module.
|
|
98
99
|
#
|
|
@@ -116,40 +117,40 @@ def _producer_func(q,
|
|
|
116
117
|
verbose=False,
|
|
117
118
|
image_size=None,
|
|
118
119
|
augment=None):
|
|
119
|
-
"""
|
|
120
|
+
"""
|
|
120
121
|
Producer function; only used when using the (optional) image queue.
|
|
121
|
-
|
|
122
|
-
Reads up to images from disk and puts them on the blocking queue for
|
|
123
|
-
processing. Each image is queued as a tuple of [filename,Image]. Sends
|
|
122
|
+
|
|
123
|
+
Reads up to images from disk and puts them on the blocking queue for
|
|
124
|
+
processing. Each image is queued as a tuple of [filename,Image]. Sends
|
|
124
125
|
"None" to the queue when finished.
|
|
125
|
-
|
|
126
|
+
|
|
126
127
|
The "detector" argument is only used for preprocessing.
|
|
127
128
|
"""
|
|
128
|
-
|
|
129
|
+
|
|
129
130
|
if verbose:
|
|
130
131
|
print('Producer starting: ID {}, preprocessor {}'.format(producer_id,preprocessor))
|
|
131
132
|
sys.stdout.flush()
|
|
132
|
-
|
|
133
|
-
if preprocessor is not None:
|
|
133
|
+
|
|
134
|
+
if preprocessor is not None:
|
|
134
135
|
assert isinstance(preprocessor,str)
|
|
135
136
|
detector_options = deepcopy(detector_options)
|
|
136
137
|
detector_options['preprocess_only'] = True
|
|
137
138
|
preprocessor = load_detector(preprocessor,detector_options=detector_options,verbose=verbose)
|
|
138
|
-
|
|
139
|
+
|
|
139
140
|
for im_file in image_files:
|
|
140
|
-
|
|
141
|
+
|
|
141
142
|
try:
|
|
142
143
|
if verbose:
|
|
143
144
|
print('Loading image {} on producer {}'.format(im_file,producer_id))
|
|
144
145
|
sys.stdout.flush()
|
|
145
146
|
image = vis_utils.load_image(im_file)
|
|
146
|
-
|
|
147
|
+
|
|
147
148
|
if preprocessor is not None:
|
|
148
|
-
|
|
149
|
+
|
|
149
150
|
image_info = preprocessor.generate_detections_one_image(
|
|
150
|
-
image,
|
|
151
|
-
im_file,
|
|
152
|
-
detection_threshold=None,
|
|
151
|
+
image,
|
|
152
|
+
im_file,
|
|
153
|
+
detection_threshold=None,
|
|
153
154
|
image_size=image_size,
|
|
154
155
|
skip_image_resizing=False,
|
|
155
156
|
augment=augment,
|
|
@@ -158,29 +159,29 @@ def _producer_func(q,
|
|
|
158
159
|
if 'failure' in image_info:
|
|
159
160
|
assert image_info['failure'] == run_detector.FAILURE_INFER
|
|
160
161
|
raise
|
|
161
|
-
|
|
162
|
+
|
|
162
163
|
image = image_info
|
|
163
|
-
|
|
164
|
+
|
|
164
165
|
except Exception as e:
|
|
165
166
|
print('Producer process: image {} cannot be loaded:\n{}'.format(im_file,str(e)))
|
|
166
|
-
image = run_detector.FAILURE_IMAGE_OPEN
|
|
167
|
-
|
|
167
|
+
image = run_detector.FAILURE_IMAGE_OPEN
|
|
168
|
+
|
|
168
169
|
if verbose:
|
|
169
170
|
print('Queueing image {} from producer {}'.format(im_file,producer_id))
|
|
170
171
|
sys.stdout.flush()
|
|
171
|
-
|
|
172
|
+
|
|
172
173
|
q.put([im_file,image,producer_id])
|
|
173
|
-
|
|
174
|
+
|
|
174
175
|
# This is a signal to the consumer function that a worker has finished
|
|
175
176
|
q.put(None)
|
|
176
|
-
|
|
177
|
+
|
|
177
178
|
if verbose:
|
|
178
179
|
print('Loader worker {} finished'.format(producer_id))
|
|
179
180
|
sys.stdout.flush()
|
|
180
181
|
|
|
181
182
|
# ...def _producer_func(...)
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
|
|
184
|
+
|
|
184
185
|
def _consumer_func(q,
|
|
185
186
|
return_queue,
|
|
186
187
|
model_file,
|
|
@@ -188,25 +189,25 @@ def _consumer_func(q,
|
|
|
188
189
|
loader_workers,
|
|
189
190
|
image_size=None,
|
|
190
191
|
include_image_size=False,
|
|
191
|
-
include_image_timestamp=False,
|
|
192
|
+
include_image_timestamp=False,
|
|
192
193
|
include_exif_data=False,
|
|
193
194
|
augment=False,
|
|
194
195
|
detector_options=None,
|
|
195
196
|
preprocess_on_image_queue=default_preprocess_on_image_queue,
|
|
196
197
|
n_total_images=None
|
|
197
198
|
):
|
|
198
|
-
"""
|
|
199
|
+
"""
|
|
199
200
|
Consumer function; only used when using the (optional) image queue.
|
|
200
|
-
|
|
201
|
+
|
|
201
202
|
Pulls images from a blocking queue and processes them. Returns when "None" has
|
|
202
203
|
been read from each loader's queue.
|
|
203
204
|
"""
|
|
204
|
-
|
|
205
|
+
|
|
205
206
|
if verbose:
|
|
206
207
|
print('Consumer starting'); sys.stdout.flush()
|
|
207
208
|
|
|
208
209
|
start_time = time.time()
|
|
209
|
-
|
|
210
|
+
|
|
210
211
|
if isinstance(model_file,str):
|
|
211
212
|
detector = load_detector(model_file,detector_options=detector_options,verbose=verbose)
|
|
212
213
|
elapsed = time.time() - start_time
|
|
@@ -216,21 +217,21 @@ def _consumer_func(q,
|
|
|
216
217
|
else:
|
|
217
218
|
detector = model_file
|
|
218
219
|
print('Detector of type {} passed to consumer function'.format(type(detector)))
|
|
219
|
-
|
|
220
|
+
|
|
220
221
|
results = []
|
|
221
|
-
|
|
222
|
+
|
|
222
223
|
n_images_processed = 0
|
|
223
224
|
n_queues_finished = 0
|
|
224
|
-
|
|
225
|
+
|
|
225
226
|
pbar = None
|
|
226
227
|
if n_total_images is not None:
|
|
227
228
|
# TODO: in principle I should close this pbar
|
|
228
229
|
pbar = tqdm(total=n_total_images)
|
|
229
|
-
|
|
230
|
+
|
|
230
231
|
while True:
|
|
231
|
-
|
|
232
|
+
|
|
232
233
|
r = q.get()
|
|
233
|
-
|
|
234
|
+
|
|
234
235
|
# Is this the last image in one of the producer queues?
|
|
235
236
|
if r is None:
|
|
236
237
|
n_queues_finished += 1
|
|
@@ -246,7 +247,7 @@ def _consumer_func(q,
|
|
|
246
247
|
n_images_processed += 1
|
|
247
248
|
im_file = r[0]
|
|
248
249
|
image = r[1]
|
|
249
|
-
|
|
250
|
+
|
|
250
251
|
"""
|
|
251
252
|
result['img_processed'] = img
|
|
252
253
|
result['img_original'] = img_original
|
|
@@ -255,19 +256,19 @@ def _consumer_func(q,
|
|
|
255
256
|
result['letterbox_ratio'] = letterbox_ratio
|
|
256
257
|
result['letterbox_pad'] = letterbox_pad
|
|
257
258
|
"""
|
|
258
|
-
|
|
259
|
+
|
|
259
260
|
if pbar is not None:
|
|
260
261
|
pbar.update(1)
|
|
261
|
-
|
|
262
|
+
|
|
262
263
|
if False:
|
|
263
264
|
if verbose or ((n_images_processed % n_queue_print) == 1):
|
|
264
265
|
elapsed = time.time() - start_time
|
|
265
266
|
images_per_second = n_images_processed / elapsed
|
|
266
267
|
print('De-queued image {} ({:.2f}/s) ({})'.format(n_images_processed,
|
|
267
268
|
images_per_second,
|
|
268
|
-
im_file))
|
|
269
|
+
im_file))
|
|
269
270
|
sys.stdout.flush()
|
|
270
|
-
|
|
271
|
+
|
|
271
272
|
if isinstance(image,str):
|
|
272
273
|
# This is how the producer function communicates read errors
|
|
273
274
|
results.append({'file': im_file,
|
|
@@ -276,7 +277,7 @@ def _consumer_func(q,
|
|
|
276
277
|
print('Expected a dict, received an image of type {}'.format(type(image)))
|
|
277
278
|
results.append({'file': im_file,
|
|
278
279
|
'failure': 'illegal image type'})
|
|
279
|
-
|
|
280
|
+
|
|
280
281
|
else:
|
|
281
282
|
results.append(process_image(im_file=im_file,
|
|
282
283
|
detector=detector,
|
|
@@ -285,14 +286,14 @@ def _consumer_func(q,
|
|
|
285
286
|
quiet=True,
|
|
286
287
|
image_size=image_size,
|
|
287
288
|
include_image_size=include_image_size,
|
|
288
|
-
include_image_timestamp=include_image_timestamp,
|
|
289
|
+
include_image_timestamp=include_image_timestamp,
|
|
289
290
|
include_exif_data=include_exif_data,
|
|
290
291
|
augment=augment,
|
|
291
292
|
skip_image_resizing=preprocess_on_image_queue))
|
|
292
293
|
if verbose:
|
|
293
294
|
print('Processed image {}'.format(im_file)); sys.stdout.flush()
|
|
294
295
|
q.task_done()
|
|
295
|
-
|
|
296
|
+
|
|
296
297
|
# ...while True (consumer loop)
|
|
297
298
|
|
|
298
299
|
# ...def _consumer_func(...)
|
|
@@ -303,7 +304,7 @@ def run_detector_with_image_queue(image_files,
|
|
|
303
304
|
confidence_threshold,
|
|
304
305
|
quiet=False,
|
|
305
306
|
image_size=None,
|
|
306
|
-
include_image_size=False,
|
|
307
|
+
include_image_size=False,
|
|
307
308
|
include_image_timestamp=False,
|
|
308
309
|
include_exif_data=False,
|
|
309
310
|
augment=False,
|
|
@@ -311,11 +312,11 @@ def run_detector_with_image_queue(image_files,
|
|
|
311
312
|
loader_workers=default_loaders,
|
|
312
313
|
preprocess_on_image_queue=default_preprocess_on_image_queue):
|
|
313
314
|
"""
|
|
314
|
-
Driver function for the (optional) multiprocessing-based image queue; only used
|
|
315
|
-
when --use_image_queue is specified. Starts a reader process to read images from disk, but
|
|
315
|
+
Driver function for the (optional) multiprocessing-based image queue; only used
|
|
316
|
+
when --use_image_queue is specified. Starts a reader process to read images from disk, but
|
|
316
317
|
processes images in the process from which this function is called (i.e., does not currently
|
|
317
318
|
spawn a separate consumer process).
|
|
318
|
-
|
|
319
|
+
|
|
319
320
|
Args:
|
|
320
321
|
image_files (str): list of absolute paths to images
|
|
321
322
|
model_file (str): filename or model identifier (e.g. "MDV5A")
|
|
@@ -329,36 +330,36 @@ def run_detector_with_image_queue(image_files,
|
|
|
329
330
|
include_image_timestamp (bool, optional): should we include image timestamps in the output for each image?
|
|
330
331
|
include_exif_data (bool, optional): should we include EXIF data in the output for each image?
|
|
331
332
|
augment (bool, optional): enable image augmentation
|
|
332
|
-
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
333
|
+
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
333
334
|
by different detectors
|
|
334
335
|
loader_workers (int, optional): number of loaders to use
|
|
335
|
-
|
|
336
|
+
|
|
336
337
|
Returns:
|
|
337
338
|
list: list of dicts in the format returned by process_image()
|
|
338
339
|
"""
|
|
339
|
-
|
|
340
|
+
|
|
340
341
|
# Validate inputs
|
|
341
342
|
assert isinstance(model_file,str)
|
|
342
|
-
|
|
343
|
+
|
|
343
344
|
if loader_workers <= 0:
|
|
344
345
|
loader_workers = 1
|
|
345
|
-
|
|
346
|
+
|
|
346
347
|
q = multiprocessing.JoinableQueue(max_queue_size)
|
|
347
348
|
return_queue = multiprocessing.Queue(1)
|
|
348
|
-
|
|
349
|
+
|
|
349
350
|
producers = []
|
|
350
|
-
|
|
351
|
+
|
|
351
352
|
worker_string = 'thread' if use_threads_for_queue else 'process'
|
|
352
353
|
print('Starting a {} pool with {} workers'.format(worker_string,loader_workers))
|
|
353
|
-
|
|
354
|
+
|
|
354
355
|
preprocessor = None
|
|
355
|
-
|
|
356
|
+
|
|
356
357
|
if preprocess_on_image_queue:
|
|
357
358
|
print('Enabling image queue preprocessing')
|
|
358
359
|
preprocessor = model_file
|
|
359
|
-
|
|
360
|
+
|
|
360
361
|
n_total_images = len(image_files)
|
|
361
|
-
|
|
362
|
+
|
|
362
363
|
chunks = split_list_into_n_chunks(image_files, loader_workers, chunk_strategy='greedy')
|
|
363
364
|
for i_chunk,chunk in enumerate(chunks):
|
|
364
365
|
if use_threads_for_queue:
|
|
@@ -379,11 +380,11 @@ def run_detector_with_image_queue(image_files,
|
|
|
379
380
|
image_size,
|
|
380
381
|
augment))
|
|
381
382
|
producers.append(producer)
|
|
382
|
-
|
|
383
|
+
|
|
383
384
|
for producer in producers:
|
|
384
385
|
producer.daemon = False
|
|
385
386
|
producer.start()
|
|
386
|
-
|
|
387
|
+
|
|
387
388
|
if run_separate_consumer_process:
|
|
388
389
|
if use_threads_for_queue:
|
|
389
390
|
consumer = Thread(target=_consumer_func,args=(q,
|
|
@@ -393,7 +394,7 @@ def run_detector_with_image_queue(image_files,
|
|
|
393
394
|
loader_workers,
|
|
394
395
|
image_size,
|
|
395
396
|
include_image_size,
|
|
396
|
-
include_image_timestamp,
|
|
397
|
+
include_image_timestamp,
|
|
397
398
|
include_exif_data,
|
|
398
399
|
augment,
|
|
399
400
|
detector_options,
|
|
@@ -407,7 +408,7 @@ def run_detector_with_image_queue(image_files,
|
|
|
407
408
|
loader_workers,
|
|
408
409
|
image_size,
|
|
409
410
|
include_image_size,
|
|
410
|
-
include_image_timestamp,
|
|
411
|
+
include_image_timestamp,
|
|
411
412
|
include_exif_data,
|
|
412
413
|
augment,
|
|
413
414
|
detector_options,
|
|
@@ -423,7 +424,7 @@ def run_detector_with_image_queue(image_files,
|
|
|
423
424
|
loader_workers,
|
|
424
425
|
image_size,
|
|
425
426
|
include_image_size,
|
|
426
|
-
include_image_timestamp,
|
|
427
|
+
include_image_timestamp,
|
|
427
428
|
include_exif_data,
|
|
428
429
|
augment,
|
|
429
430
|
detector_options,
|
|
@@ -434,21 +435,21 @@ def run_detector_with_image_queue(image_files,
|
|
|
434
435
|
producer.join()
|
|
435
436
|
if verbose:
|
|
436
437
|
print('Producer {} finished'.format(i_producer))
|
|
437
|
-
|
|
438
|
+
|
|
438
439
|
if verbose:
|
|
439
440
|
print('All producers finished')
|
|
440
|
-
|
|
441
|
+
|
|
441
442
|
if run_separate_consumer_process:
|
|
442
443
|
consumer.join()
|
|
443
444
|
if verbose:
|
|
444
445
|
print('Consumer loop finished')
|
|
445
|
-
|
|
446
|
+
|
|
446
447
|
q.join()
|
|
447
448
|
if verbose:
|
|
448
449
|
print('Queue joined')
|
|
449
450
|
|
|
450
451
|
results = return_queue.get()
|
|
451
|
-
|
|
452
|
+
|
|
452
453
|
return results
|
|
453
454
|
|
|
454
455
|
# ...def run_detector_with_image_queue(...)
|
|
@@ -459,29 +460,29 @@ def run_detector_with_image_queue(image_files,
|
|
|
459
460
|
def _chunks_by_number_of_chunks(ls, n):
|
|
460
461
|
"""
|
|
461
462
|
Splits a list into n even chunks.
|
|
462
|
-
|
|
463
|
+
|
|
463
464
|
External callers should use ct_utils.split_list_into_n_chunks().
|
|
464
465
|
|
|
465
466
|
Args:
|
|
466
467
|
ls (list): list to break up into chunks
|
|
467
468
|
n (int): number of chunks
|
|
468
469
|
"""
|
|
469
|
-
|
|
470
|
+
|
|
470
471
|
for i in range(0, n):
|
|
471
472
|
yield ls[i::n]
|
|
472
473
|
|
|
473
474
|
|
|
474
475
|
#%% Image processing functions
|
|
475
476
|
|
|
476
|
-
def process_images(im_files,
|
|
477
|
-
detector,
|
|
478
|
-
confidence_threshold,
|
|
479
|
-
use_image_queue=False,
|
|
480
|
-
quiet=False,
|
|
481
|
-
image_size=None,
|
|
482
|
-
checkpoint_queue=None,
|
|
483
|
-
include_image_size=False,
|
|
484
|
-
include_image_timestamp=False,
|
|
477
|
+
def process_images(im_files,
|
|
478
|
+
detector,
|
|
479
|
+
confidence_threshold,
|
|
480
|
+
use_image_queue=False,
|
|
481
|
+
quiet=False,
|
|
482
|
+
image_size=None,
|
|
483
|
+
checkpoint_queue=None,
|
|
484
|
+
include_image_size=False,
|
|
485
|
+
include_image_timestamp=False,
|
|
485
486
|
include_exif_data=False,
|
|
486
487
|
augment=False,
|
|
487
488
|
detector_options=None,
|
|
@@ -489,9 +490,9 @@ def process_images(im_files,
|
|
|
489
490
|
preprocess_on_image_queue=default_preprocess_on_image_queue):
|
|
490
491
|
"""
|
|
491
492
|
Runs a detector (typically MegaDetector) over a list of image files on a single thread.
|
|
492
|
-
|
|
493
|
+
|
|
493
494
|
Args:
|
|
494
|
-
im_files (list: paths to image files
|
|
495
|
+
im_files (list: paths to image files
|
|
495
496
|
detector (str or detector object): loaded model or str; if this is a string, it can be a
|
|
496
497
|
path to a .pb/.pt model file or a known model identifier (e.g. "MDV5A")
|
|
497
498
|
confidence_threshold (float): only detections above this threshold are returned
|
|
@@ -505,7 +506,7 @@ def process_images(im_files,
|
|
|
505
506
|
include_image_timestamp (bool, optional): should we include image timestamps in the output for each image?
|
|
506
507
|
include_exif_data (bool, optional): should we include EXIF data in the output for each image?
|
|
507
508
|
augment (bool, optional): enable image augmentation
|
|
508
|
-
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
509
|
+
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
509
510
|
by different detectors
|
|
510
511
|
loader_workers (int, optional): number of loaders to use (only relevant when using image queue)
|
|
511
512
|
|
|
@@ -513,60 +514,60 @@ def process_images(im_files,
|
|
|
513
514
|
list: list of dicts, in which each dict represents detections on one image,
|
|
514
515
|
see the 'images' key in https://github.com/agentmorris/MegaDetector/tree/main/megadetector/api/batch_processing#batch-processing-api-output-format
|
|
515
516
|
"""
|
|
516
|
-
|
|
517
|
+
|
|
517
518
|
if isinstance(detector, str):
|
|
518
|
-
|
|
519
|
+
|
|
519
520
|
start_time = time.time()
|
|
520
521
|
detector = load_detector(detector,detector_options=detector_options,verbose=verbose)
|
|
521
522
|
elapsed = time.time() - start_time
|
|
522
523
|
print('Loaded model (batch level) in {}'.format(humanfriendly.format_timespan(elapsed)))
|
|
523
524
|
|
|
524
525
|
if use_image_queue:
|
|
525
|
-
|
|
526
|
-
run_detector_with_image_queue(im_files,
|
|
527
|
-
detector,
|
|
528
|
-
confidence_threshold,
|
|
529
|
-
quiet=quiet,
|
|
526
|
+
|
|
527
|
+
run_detector_with_image_queue(im_files,
|
|
528
|
+
detector,
|
|
529
|
+
confidence_threshold,
|
|
530
|
+
quiet=quiet,
|
|
530
531
|
image_size=image_size,
|
|
531
|
-
include_image_size=include_image_size,
|
|
532
|
+
include_image_size=include_image_size,
|
|
532
533
|
include_image_timestamp=include_image_timestamp,
|
|
533
534
|
include_exif_data=include_exif_data,
|
|
534
535
|
augment=augment,
|
|
535
536
|
detector_options=detector_options,
|
|
536
537
|
loader_workers=loader_workers,
|
|
537
538
|
preprocess_on_image_queue=preprocess_on_image_queue)
|
|
538
|
-
|
|
539
|
-
else:
|
|
540
|
-
|
|
539
|
+
|
|
540
|
+
else:
|
|
541
|
+
|
|
541
542
|
results = []
|
|
542
543
|
for im_file in im_files:
|
|
543
|
-
result = process_image(im_file,
|
|
544
|
-
detector,
|
|
544
|
+
result = process_image(im_file,
|
|
545
|
+
detector,
|
|
545
546
|
confidence_threshold,
|
|
546
|
-
quiet=quiet,
|
|
547
|
-
image_size=image_size,
|
|
548
|
-
include_image_size=include_image_size,
|
|
547
|
+
quiet=quiet,
|
|
548
|
+
image_size=image_size,
|
|
549
|
+
include_image_size=include_image_size,
|
|
549
550
|
include_image_timestamp=include_image_timestamp,
|
|
550
551
|
include_exif_data=include_exif_data,
|
|
551
552
|
augment=augment)
|
|
552
553
|
|
|
553
554
|
if checkpoint_queue is not None:
|
|
554
555
|
checkpoint_queue.put(result)
|
|
555
|
-
results.append(result)
|
|
556
|
-
|
|
556
|
+
results.append(result)
|
|
557
|
+
|
|
557
558
|
return results
|
|
558
559
|
|
|
559
560
|
# ...def process_images(...)
|
|
560
561
|
|
|
561
562
|
|
|
562
|
-
def process_image(im_file,
|
|
563
|
-
detector,
|
|
564
|
-
confidence_threshold,
|
|
565
|
-
image=None,
|
|
566
|
-
quiet=False,
|
|
567
|
-
image_size=None,
|
|
563
|
+
def process_image(im_file,
|
|
564
|
+
detector,
|
|
565
|
+
confidence_threshold,
|
|
566
|
+
image=None,
|
|
567
|
+
quiet=False,
|
|
568
|
+
image_size=None,
|
|
568
569
|
include_image_size=False,
|
|
569
|
-
include_image_timestamp=False,
|
|
570
|
+
include_image_timestamp=False,
|
|
570
571
|
include_exif_data=False,
|
|
571
572
|
skip_image_resizing=False,
|
|
572
573
|
augment=False):
|
|
@@ -575,7 +576,7 @@ def process_image(im_file,
|
|
|
575
576
|
|
|
576
577
|
Args:
|
|
577
578
|
im_file (str): path to image file
|
|
578
|
-
detector (detector object): loaded model, this can no longer be a string by the time
|
|
579
|
+
detector (detector object): loaded model, this can no longer be a string by the time
|
|
579
580
|
you get this far down the pipeline
|
|
580
581
|
confidence_threshold (float): only detections above this threshold are returned
|
|
581
582
|
image (Image, optional): previously-loaded image, if available, used when a worker
|
|
@@ -583,22 +584,22 @@ def process_image(im_file,
|
|
|
583
584
|
quiet (bool, optional): suppress per-image printouts
|
|
584
585
|
image_size (tuple, optional): image size to use for inference, only mess with this
|
|
585
586
|
if (a) you're using a model other than MegaDetector or (b) you know what you're
|
|
586
|
-
doing
|
|
587
|
+
doing
|
|
587
588
|
include_image_size (bool, optional): should we include image size in the output for each image?
|
|
588
589
|
include_image_timestamp (bool, optional): should we include image timestamps in the output for each image?
|
|
589
|
-
include_exif_data (bool, optional): should we include EXIF data in the output for each image?
|
|
590
|
+
include_exif_data (bool, optional): should we include EXIF data in the output for each image?
|
|
590
591
|
skip_image_resizing (bool, optional): whether to skip internal image resizing and rely on external resizing
|
|
591
592
|
augment (bool, optional): enable image augmentation
|
|
592
593
|
|
|
593
594
|
Returns:
|
|
594
595
|
dict: dict representing detections on one image,
|
|
595
|
-
see the 'images' key in
|
|
596
|
+
see the 'images' key in
|
|
596
597
|
https://github.com/agentmorris/MegaDetector/tree/main/megadetector/api/batch_processing#batch-processing-api-output-format
|
|
597
598
|
"""
|
|
598
|
-
|
|
599
|
+
|
|
599
600
|
if not quiet:
|
|
600
601
|
print('Processing image {}'.format(im_file))
|
|
601
|
-
|
|
602
|
+
|
|
602
603
|
if image is None:
|
|
603
604
|
try:
|
|
604
605
|
image = vis_utils.load_image(im_file)
|
|
@@ -612,11 +613,11 @@ def process_image(im_file,
|
|
|
612
613
|
return result
|
|
613
614
|
|
|
614
615
|
try:
|
|
615
|
-
|
|
616
|
+
|
|
616
617
|
result = detector.generate_detections_one_image(
|
|
617
|
-
image,
|
|
618
|
-
im_file,
|
|
619
|
-
detection_threshold=confidence_threshold,
|
|
618
|
+
image,
|
|
619
|
+
im_file,
|
|
620
|
+
detection_threshold=confidence_threshold,
|
|
620
621
|
image_size=image_size,
|
|
621
622
|
skip_image_resizing=skip_image_resizing,
|
|
622
623
|
augment=augment)
|
|
@@ -632,7 +633,7 @@ def process_image(im_file,
|
|
|
632
633
|
if isinstance(image,dict):
|
|
633
634
|
image = image['img_original_pil']
|
|
634
635
|
|
|
635
|
-
if include_image_size:
|
|
636
|
+
if include_image_size:
|
|
636
637
|
result['width'] = image.width
|
|
637
638
|
result['height'] = image.height
|
|
638
639
|
|
|
@@ -651,13 +652,13 @@ def _load_custom_class_mapping(class_mapping_filename):
|
|
|
651
652
|
"""
|
|
652
653
|
This is an experimental hack to allow the use of non-MD YOLOv5 models through
|
|
653
654
|
the same infrastructure; it disables the code that enforces MDv5-like class lists.
|
|
654
|
-
|
|
655
|
+
|
|
655
656
|
Should be a .json file that maps int-strings to strings, or a YOLOv5 dataset.yaml file.
|
|
656
657
|
"""
|
|
657
|
-
|
|
658
|
+
|
|
658
659
|
if class_mapping_filename is None:
|
|
659
660
|
return
|
|
660
|
-
|
|
661
|
+
|
|
661
662
|
run_detector.USE_MODEL_NATIVE_CLASSES = True
|
|
662
663
|
if class_mapping_filename.endswith('.json'):
|
|
663
664
|
with open(class_mapping_filename,'r') as f:
|
|
@@ -668,28 +669,28 @@ def _load_custom_class_mapping(class_mapping_filename):
|
|
|
668
669
|
class_mapping = {str(k):v for k,v in class_mapping.items()}
|
|
669
670
|
else:
|
|
670
671
|
raise ValueError('Unrecognized class mapping file {}'.format(class_mapping_filename))
|
|
671
|
-
|
|
672
|
+
|
|
672
673
|
print('Loaded custom class mapping:')
|
|
673
674
|
print(class_mapping)
|
|
674
675
|
run_detector.DEFAULT_DETECTOR_LABEL_MAP = class_mapping
|
|
675
676
|
return class_mapping
|
|
676
|
-
|
|
677
|
-
|
|
677
|
+
|
|
678
|
+
|
|
678
679
|
#%% Main function
|
|
679
680
|
|
|
680
|
-
def load_and_run_detector_batch(model_file,
|
|
681
|
-
image_file_names,
|
|
681
|
+
def load_and_run_detector_batch(model_file,
|
|
682
|
+
image_file_names,
|
|
682
683
|
checkpoint_path=None,
|
|
683
684
|
confidence_threshold=run_detector.DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD,
|
|
684
|
-
checkpoint_frequency=-1,
|
|
685
|
-
results=None,
|
|
685
|
+
checkpoint_frequency=-1,
|
|
686
|
+
results=None,
|
|
686
687
|
n_cores=1,
|
|
687
|
-
use_image_queue=False,
|
|
688
|
-
quiet=False,
|
|
689
|
-
image_size=None,
|
|
690
|
-
class_mapping_filename=None,
|
|
691
|
-
include_image_size=False,
|
|
692
|
-
include_image_timestamp=False,
|
|
688
|
+
use_image_queue=False,
|
|
689
|
+
quiet=False,
|
|
690
|
+
image_size=None,
|
|
691
|
+
class_mapping_filename=None,
|
|
692
|
+
include_image_size=False,
|
|
693
|
+
include_image_timestamp=False,
|
|
693
694
|
include_exif_data=False,
|
|
694
695
|
augment=False,
|
|
695
696
|
force_model_download=False,
|
|
@@ -698,19 +699,18 @@ def load_and_run_detector_batch(model_file,
|
|
|
698
699
|
preprocess_on_image_queue=default_preprocess_on_image_queue):
|
|
699
700
|
"""
|
|
700
701
|
Load a model file and run it on a list of images.
|
|
701
|
-
|
|
702
|
+
|
|
702
703
|
Args:
|
|
703
|
-
|
|
704
704
|
model_file (str): path to model file, or supported model string (e.g. "MDV5A")
|
|
705
|
-
image_file_names (list or str): list of strings (image filenames), a single image filename,
|
|
706
|
-
a folder to recursively search for images in, or a .json or .txt file containing a list
|
|
705
|
+
image_file_names (list or str): list of strings (image filenames), a single image filename,
|
|
706
|
+
a folder to recursively search for images in, or a .json or .txt file containing a list
|
|
707
707
|
of images.
|
|
708
708
|
checkpoint_path (str, optional), path to use for checkpoints (if None, checkpointing
|
|
709
709
|
is disabled)
|
|
710
710
|
confidence_threshold (float, optional): only detections above this threshold are returned
|
|
711
|
-
checkpoint_frequency (int, optional): int, write results to JSON checkpoint file every N
|
|
711
|
+
checkpoint_frequency (int, optional): int, write results to JSON checkpoint file every N
|
|
712
712
|
images, -1 disabled checkpointing
|
|
713
|
-
results (list, optional): list of dicts, existing results loaded from checkpoint; generally
|
|
713
|
+
results (list, optional): list of dicts, existing results loaded from checkpoint; generally
|
|
714
714
|
not useful if you're using this function outside of the CLI
|
|
715
715
|
n_cores (int, optional): number of parallel worker to use, ignored if we're running on a GPU
|
|
716
716
|
use_image_queue (bool, optional): use a dedicated worker for image loading
|
|
@@ -718,7 +718,7 @@ def load_and_run_detector_batch(model_file,
|
|
|
718
718
|
image_size (tuple, optional): image size to use for inference, only mess with this
|
|
719
719
|
if (a) you're using a model other than MegaDetector or (b) you know what you're
|
|
720
720
|
doing
|
|
721
|
-
class_mapping_filename (str, optional), use a non-default class mapping supplied in a .json
|
|
721
|
+
class_mapping_filename (str, optional), use a non-default class mapping supplied in a .json
|
|
722
722
|
file or YOLOv5 dataset.yaml file
|
|
723
723
|
include_image_size (bool, optional): should we include image size in the output for each image?
|
|
724
724
|
include_image_timestamp (bool, optional): should we include image timestamps in the output for each image?
|
|
@@ -727,37 +727,37 @@ def load_and_run_detector_batch(model_file,
|
|
|
727
727
|
force_model_download (bool, optional): force downloading the model file if
|
|
728
728
|
a named model (e.g. "MDV5A") is supplied, even if the local file already
|
|
729
729
|
exists
|
|
730
|
-
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
730
|
+
detector_options (dict, optional): key/value pairs that are interpreted differently
|
|
731
731
|
by different detectors
|
|
732
732
|
loader_workers (int, optional): number of loaders to use, only relevant when use_image_queue is True
|
|
733
|
-
|
|
733
|
+
|
|
734
734
|
Returns:
|
|
735
735
|
results: list of dicts; each dict represents detections on one image
|
|
736
736
|
"""
|
|
737
|
-
|
|
737
|
+
|
|
738
738
|
# Validate input arguments
|
|
739
739
|
if n_cores is None or n_cores <= 0:
|
|
740
740
|
n_cores = 1
|
|
741
|
-
|
|
741
|
+
|
|
742
742
|
if confidence_threshold is None:
|
|
743
743
|
confidence_threshold=run_detector.DEFAULT_OUTPUT_CONFIDENCE_THRESHOLD
|
|
744
|
-
|
|
744
|
+
|
|
745
745
|
# Disable checkpointing if checkpoint_path is None
|
|
746
746
|
if checkpoint_frequency is None or checkpoint_path is None:
|
|
747
747
|
checkpoint_frequency = -1
|
|
748
748
|
|
|
749
749
|
if class_mapping_filename is not None:
|
|
750
750
|
_load_custom_class_mapping(class_mapping_filename)
|
|
751
|
-
|
|
751
|
+
|
|
752
752
|
# Handle the case where image_file_names is not yet actually a list
|
|
753
753
|
if isinstance(image_file_names,str):
|
|
754
|
-
|
|
754
|
+
|
|
755
755
|
# Find the images to score; images can be a directory, may need to recurse
|
|
756
756
|
if os.path.isdir(image_file_names):
|
|
757
757
|
image_dir = image_file_names
|
|
758
758
|
image_file_names = path_utils.find_images(image_dir, True)
|
|
759
759
|
print('{} image files found in folder {}'.format(len(image_file_names),image_dir))
|
|
760
|
-
|
|
760
|
+
|
|
761
761
|
# A single file, or a list of image paths
|
|
762
762
|
elif os.path.isfile(image_file_names):
|
|
763
763
|
list_file = image_file_names
|
|
@@ -780,43 +780,43 @@ def load_and_run_detector_batch(model_file,
|
|
|
780
780
|
'File {} supplied as [image_file_names] argument, but extension is neither .json nor .txt'\
|
|
781
781
|
.format(
|
|
782
782
|
list_file))
|
|
783
|
-
else:
|
|
783
|
+
else:
|
|
784
784
|
raise ValueError(
|
|
785
785
|
'{} supplied as [image_file_names] argument, but it does not appear to be a file or folder'.format(
|
|
786
786
|
image_file_names))
|
|
787
|
-
|
|
787
|
+
|
|
788
788
|
if results is None:
|
|
789
789
|
results = []
|
|
790
790
|
|
|
791
791
|
already_processed = set([i['file'] for i in results])
|
|
792
792
|
|
|
793
793
|
model_file = try_download_known_detector(model_file, force_download=force_model_download)
|
|
794
|
-
|
|
794
|
+
|
|
795
795
|
print('GPU available: {}'.format(is_gpu_available(model_file)))
|
|
796
|
-
|
|
796
|
+
|
|
797
797
|
if n_cores > 1 and is_gpu_available(model_file):
|
|
798
|
-
|
|
798
|
+
|
|
799
799
|
print('Warning: multiple cores requested, but a GPU is available; parallelization across ' + \
|
|
800
800
|
'GPUs is not currently supported, defaulting to one GPU')
|
|
801
801
|
n_cores = 1
|
|
802
802
|
|
|
803
803
|
if n_cores > 1 and use_image_queue:
|
|
804
|
-
|
|
804
|
+
|
|
805
805
|
print('Warning: multiple cores requested, but the image queue is enabled; parallelization ' + \
|
|
806
806
|
'with the image queue is not currently supported, defaulting to one worker')
|
|
807
807
|
n_cores = 1
|
|
808
|
-
|
|
808
|
+
|
|
809
809
|
if use_image_queue:
|
|
810
|
-
|
|
810
|
+
|
|
811
811
|
assert checkpoint_frequency < 0, \
|
|
812
812
|
'Using an image queue is not currently supported when checkpointing is enabled'
|
|
813
813
|
assert len(results) == 0, \
|
|
814
814
|
'Using an image queue with results loaded from a checkpoint is not currently supported'
|
|
815
815
|
assert n_cores <= 1
|
|
816
|
-
results = run_detector_with_image_queue(image_file_names,
|
|
817
|
-
model_file,
|
|
818
|
-
confidence_threshold,
|
|
819
|
-
quiet,
|
|
816
|
+
results = run_detector_with_image_queue(image_file_names,
|
|
817
|
+
model_file,
|
|
818
|
+
confidence_threshold,
|
|
819
|
+
quiet,
|
|
820
820
|
image_size=image_size,
|
|
821
821
|
include_image_size=include_image_size,
|
|
822
822
|
include_image_timestamp=include_image_timestamp,
|
|
@@ -825,7 +825,7 @@ def load_and_run_detector_batch(model_file,
|
|
|
825
825
|
detector_options=detector_options,
|
|
826
826
|
loader_workers=loader_workers,
|
|
827
827
|
preprocess_on_image_queue=preprocess_on_image_queue)
|
|
828
|
-
|
|
828
|
+
|
|
829
829
|
elif n_cores <= 1:
|
|
830
830
|
|
|
831
831
|
# Load the detector
|
|
@@ -848,11 +848,11 @@ def load_and_run_detector_batch(model_file,
|
|
|
848
848
|
|
|
849
849
|
count += 1
|
|
850
850
|
|
|
851
|
-
result = process_image(im_file,
|
|
852
|
-
detector,
|
|
853
|
-
confidence_threshold,
|
|
854
|
-
quiet=quiet,
|
|
855
|
-
image_size=image_size,
|
|
851
|
+
result = process_image(im_file,
|
|
852
|
+
detector,
|
|
853
|
+
confidence_threshold,
|
|
854
|
+
quiet=quiet,
|
|
855
|
+
image_size=image_size,
|
|
856
856
|
include_image_size=include_image_size,
|
|
857
857
|
include_image_timestamp=include_image_timestamp,
|
|
858
858
|
include_exif_data=include_exif_data,
|
|
@@ -861,97 +861,100 @@ def load_and_run_detector_batch(model_file,
|
|
|
861
861
|
|
|
862
862
|
# Write a checkpoint if necessary
|
|
863
863
|
if (checkpoint_frequency != -1) and ((count % checkpoint_frequency) == 0):
|
|
864
|
-
|
|
864
|
+
|
|
865
865
|
print('Writing a new checkpoint after having processed {} images since '
|
|
866
866
|
'last restart'.format(count))
|
|
867
|
-
|
|
867
|
+
|
|
868
868
|
_write_checkpoint(checkpoint_path, results)
|
|
869
|
-
|
|
869
|
+
|
|
870
870
|
else:
|
|
871
|
-
|
|
871
|
+
|
|
872
872
|
# Multiprocessing is enabled at this point
|
|
873
|
-
|
|
873
|
+
|
|
874
874
|
# When using multiprocessing, tell the workers to load the model on each
|
|
875
875
|
# process, by passing the model_file string as the "model" argument to
|
|
876
876
|
# process_images.
|
|
877
877
|
detector = model_file
|
|
878
878
|
|
|
879
|
-
print('Creating pool with {} cores'.format(n_cores))
|
|
879
|
+
print('Creating worker pool with {} cores'.format(n_cores))
|
|
880
880
|
|
|
881
881
|
if len(already_processed) > 0:
|
|
882
882
|
n_images_all = len(image_file_names)
|
|
883
883
|
image_file_names = [fn for fn in image_file_names if fn not in already_processed]
|
|
884
884
|
print('Loaded {} of {} images from checkpoint'.format(
|
|
885
885
|
len(already_processed),n_images_all))
|
|
886
|
-
|
|
887
|
-
# Divide images into chunks; we'll send one chunk to each worker process
|
|
886
|
+
|
|
887
|
+
# Divide images into chunks; we'll send one chunk to each worker process
|
|
888
888
|
image_batches = list(_chunks_by_number_of_chunks(image_file_names, n_cores))
|
|
889
|
-
|
|
890
|
-
pool = workerpool(n_cores)
|
|
891
|
-
|
|
892
|
-
if checkpoint_path is not None:
|
|
893
|
-
|
|
894
|
-
# Multiprocessing and checkpointing are both enabled at this point
|
|
895
|
-
|
|
896
|
-
checkpoint_queue = Manager().Queue()
|
|
897
|
-
|
|
898
|
-
# Pass the "results" array (which may already contain images loaded from an existing
|
|
899
|
-
# checkpoint) to the checkpoint queue handler function, which will append results to
|
|
900
|
-
# the list as they become available.
|
|
901
|
-
checkpoint_thread = Thread(target=_checkpoint_queue_handler,
|
|
902
|
-
args=(checkpoint_path, checkpoint_frequency,
|
|
903
|
-
checkpoint_queue, results), daemon=True)
|
|
904
|
-
checkpoint_thread.start()
|
|
905
|
-
|
|
906
|
-
pool.map(partial(process_images,
|
|
907
|
-
detector=detector,
|
|
908
|
-
confidence_threshold=confidence_threshold,
|
|
909
|
-
use_image_queue=False,
|
|
910
|
-
quiet=quiet,
|
|
911
|
-
image_size=image_size,
|
|
912
|
-
checkpoint_queue=checkpoint_queue,
|
|
913
|
-
include_image_size=include_image_size,
|
|
914
|
-
include_image_timestamp=include_image_timestamp,
|
|
915
|
-
include_exif_data=include_exif_data,
|
|
916
|
-
augment=augment,
|
|
917
|
-
detector_options=detector_options),
|
|
918
|
-
image_batches)
|
|
919
|
-
|
|
920
|
-
checkpoint_queue.put(None)
|
|
921
889
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
# Multprocessing is enabled, but checkpointing is not
|
|
925
|
-
|
|
926
|
-
new_results = pool.map(partial(process_images,
|
|
927
|
-
detector=detector,
|
|
928
|
-
confidence_threshold=confidence_threshold,
|
|
929
|
-
use_image_queue=False,
|
|
930
|
-
quiet=quiet,
|
|
931
|
-
checkpoint_queue=None,
|
|
932
|
-
image_size=image_size,
|
|
933
|
-
include_image_size=include_image_size,
|
|
934
|
-
include_image_timestamp=include_image_timestamp,
|
|
935
|
-
include_exif_data=include_exif_data,
|
|
936
|
-
augment=augment,
|
|
937
|
-
detector_options=detector_options),
|
|
938
|
-
image_batches)
|
|
939
|
-
|
|
940
|
-
new_results = list(itertools.chain.from_iterable(new_results))
|
|
941
|
-
|
|
942
|
-
# Append the results we just computed to "results", which is *usually* empty, but will
|
|
943
|
-
# be non-empty if we resumed from a checkpoint
|
|
944
|
-
results += new_results
|
|
945
|
-
|
|
946
|
-
# ...if checkpointing is/isn't enabled
|
|
947
|
-
|
|
890
|
+
pool = None
|
|
948
891
|
try:
|
|
949
|
-
pool
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
892
|
+
pool = workerpool(n_cores)
|
|
893
|
+
|
|
894
|
+
if checkpoint_path is not None:
|
|
895
|
+
|
|
896
|
+
# Multiprocessing and checkpointing are both enabled at this point
|
|
897
|
+
|
|
898
|
+
checkpoint_queue = Manager().Queue()
|
|
899
|
+
|
|
900
|
+
# Pass the "results" array (which may already contain images loaded from an existing
|
|
901
|
+
# checkpoint) to the checkpoint queue handler function, which will append results to
|
|
902
|
+
# the list as they become available.
|
|
903
|
+
checkpoint_thread = Thread(target=_checkpoint_queue_handler,
|
|
904
|
+
args=(checkpoint_path, checkpoint_frequency,
|
|
905
|
+
checkpoint_queue, results), daemon=True)
|
|
906
|
+
checkpoint_thread.start()
|
|
907
|
+
|
|
908
|
+
pool.map(partial(process_images,
|
|
909
|
+
detector=detector,
|
|
910
|
+
confidence_threshold=confidence_threshold,
|
|
911
|
+
use_image_queue=False,
|
|
912
|
+
quiet=quiet,
|
|
913
|
+
image_size=image_size,
|
|
914
|
+
checkpoint_queue=checkpoint_queue,
|
|
915
|
+
include_image_size=include_image_size,
|
|
916
|
+
include_image_timestamp=include_image_timestamp,
|
|
917
|
+
include_exif_data=include_exif_data,
|
|
918
|
+
augment=augment,
|
|
919
|
+
detector_options=detector_options),
|
|
920
|
+
image_batches)
|
|
921
|
+
|
|
922
|
+
checkpoint_queue.put(None)
|
|
923
|
+
|
|
924
|
+
else:
|
|
925
|
+
|
|
926
|
+
# Multprocessing is enabled, but checkpointing is not
|
|
927
|
+
|
|
928
|
+
new_results = pool.map(partial(process_images,
|
|
929
|
+
detector=detector,
|
|
930
|
+
confidence_threshold=confidence_threshold,
|
|
931
|
+
use_image_queue=False,
|
|
932
|
+
quiet=quiet,
|
|
933
|
+
checkpoint_queue=None,
|
|
934
|
+
image_size=image_size,
|
|
935
|
+
include_image_size=include_image_size,
|
|
936
|
+
include_image_timestamp=include_image_timestamp,
|
|
937
|
+
include_exif_data=include_exif_data,
|
|
938
|
+
augment=augment,
|
|
939
|
+
detector_options=detector_options),
|
|
940
|
+
image_batches)
|
|
941
|
+
|
|
942
|
+
new_results = list(itertools.chain.from_iterable(new_results))
|
|
943
|
+
|
|
944
|
+
# Append the results we just computed to "results", which is *usually* empty, but will
|
|
945
|
+
# be non-empty if we resumed from a checkpoint
|
|
946
|
+
results += new_results
|
|
947
|
+
|
|
948
|
+
# ...if checkpointing is/isn't enabled
|
|
949
|
+
|
|
950
|
+
finally:
|
|
951
|
+
if pool is not None:
|
|
952
|
+
pool.close()
|
|
953
|
+
pool.join()
|
|
954
|
+
print("Pool closed and joined for multi-core inference")
|
|
955
|
+
|
|
953
956
|
# ...if we're running (1) with image queue, (2) on one core, or (3) on multiple cores
|
|
954
|
-
|
|
957
|
+
|
|
955
958
|
# 'results' may have been modified in place, but we also return it for
|
|
956
959
|
# backwards-compatibility.
|
|
957
960
|
return results
|
|
@@ -964,21 +967,21 @@ def _checkpoint_queue_handler(checkpoint_path, checkpoint_frequency, checkpoint_
|
|
|
964
967
|
Thread function to accumulate results and write checkpoints when checkpointing and
|
|
965
968
|
multiprocessing are both enabled.
|
|
966
969
|
"""
|
|
967
|
-
|
|
970
|
+
|
|
968
971
|
result_count = 0
|
|
969
972
|
while True:
|
|
970
|
-
result = checkpoint_queue.get()
|
|
971
|
-
if result is None:
|
|
972
|
-
break
|
|
973
|
-
|
|
973
|
+
result = checkpoint_queue.get()
|
|
974
|
+
if result is None:
|
|
975
|
+
break
|
|
976
|
+
|
|
974
977
|
result_count +=1
|
|
975
978
|
results.append(result)
|
|
976
979
|
|
|
977
980
|
if (checkpoint_frequency != -1) and (result_count % checkpoint_frequency == 0):
|
|
978
|
-
|
|
981
|
+
|
|
979
982
|
print('Writing a new checkpoint after having processed {} images since '
|
|
980
983
|
'last restart'.format(result_count))
|
|
981
|
-
|
|
984
|
+
|
|
982
985
|
_write_checkpoint(checkpoint_path, results)
|
|
983
986
|
|
|
984
987
|
|
|
@@ -986,20 +989,19 @@ def _write_checkpoint(checkpoint_path, results):
|
|
|
986
989
|
"""
|
|
987
990
|
Writes the 'images' field in the dict 'results' to a json checkpoint file.
|
|
988
991
|
"""
|
|
989
|
-
|
|
990
|
-
assert checkpoint_path is not None
|
|
991
|
-
|
|
992
|
+
|
|
993
|
+
assert checkpoint_path is not None
|
|
994
|
+
|
|
992
995
|
# Back up any previous checkpoints, to protect against crashes while we're writing
|
|
993
996
|
# the checkpoint file.
|
|
994
997
|
checkpoint_tmp_path = None
|
|
995
998
|
if os.path.isfile(checkpoint_path):
|
|
996
999
|
checkpoint_tmp_path = checkpoint_path + '_tmp'
|
|
997
1000
|
shutil.copyfile(checkpoint_path,checkpoint_tmp_path)
|
|
998
|
-
|
|
1001
|
+
|
|
999
1002
|
# Write the new checkpoint
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
+
ct_utils.write_json(checkpoint_path, {'images': results}, force_str=True)
|
|
1004
|
+
|
|
1003
1005
|
# Remove the backup checkpoint if it exists
|
|
1004
1006
|
if checkpoint_tmp_path is not None:
|
|
1005
1007
|
os.remove(checkpoint_tmp_path)
|
|
@@ -1008,33 +1010,33 @@ def _write_checkpoint(checkpoint_path, results):
|
|
|
1008
1010
|
def get_image_datetime(image):
|
|
1009
1011
|
"""
|
|
1010
1012
|
Reads EXIF datetime from a PIL Image object.
|
|
1011
|
-
|
|
1013
|
+
|
|
1012
1014
|
Args:
|
|
1013
1015
|
image (Image): the PIL Image object from which we should read datetime information
|
|
1014
|
-
|
|
1016
|
+
|
|
1015
1017
|
Returns:
|
|
1016
1018
|
str: the EXIF datetime from [image] (a PIL Image object), if available, as a string;
|
|
1017
1019
|
returns None if EXIF datetime is not available.
|
|
1018
1020
|
"""
|
|
1019
|
-
|
|
1021
|
+
|
|
1020
1022
|
exif_tags = read_exif.read_pil_exif(image,exif_options)
|
|
1021
|
-
|
|
1023
|
+
|
|
1022
1024
|
try:
|
|
1023
1025
|
datetime_str = exif_tags['DateTimeOriginal']
|
|
1024
1026
|
_ = time.strptime(datetime_str, '%Y:%m:%d %H:%M:%S')
|
|
1025
1027
|
return datetime_str
|
|
1026
1028
|
|
|
1027
1029
|
except Exception:
|
|
1028
|
-
return None
|
|
1030
|
+
return None
|
|
1029
1031
|
|
|
1030
1032
|
|
|
1031
|
-
def write_results_to_file(results,
|
|
1032
|
-
output_file,
|
|
1033
|
-
relative_path_base=None,
|
|
1034
|
-
detector_file=None,
|
|
1035
|
-
info=None,
|
|
1033
|
+
def write_results_to_file(results,
|
|
1034
|
+
output_file,
|
|
1035
|
+
relative_path_base=None,
|
|
1036
|
+
detector_file=None,
|
|
1037
|
+
info=None,
|
|
1036
1038
|
include_max_conf=False,
|
|
1037
|
-
custom_metadata=None,
|
|
1039
|
+
custom_metadata=None,
|
|
1038
1040
|
force_forward_slashes=True):
|
|
1039
1041
|
"""
|
|
1040
1042
|
Writes list of detection results to JSON output file. Format matches:
|
|
@@ -1056,11 +1058,11 @@ def write_results_to_file(results,
|
|
|
1056
1058
|
a dictionary, but no type/format checks are performed
|
|
1057
1059
|
force_forward_slashes (bool, optional): convert all slashes in filenames within [results] to
|
|
1058
1060
|
forward slashes
|
|
1059
|
-
|
|
1061
|
+
|
|
1060
1062
|
Returns:
|
|
1061
1063
|
dict: the MD-formatted dictionary that was written to [output_file]
|
|
1062
1064
|
"""
|
|
1063
|
-
|
|
1065
|
+
|
|
1064
1066
|
if relative_path_base is not None:
|
|
1065
1067
|
results_relative = []
|
|
1066
1068
|
for r in results:
|
|
@@ -1076,68 +1078,67 @@ def write_results_to_file(results,
|
|
|
1076
1078
|
r_converted['file'] = r_converted['file'].replace('\\','/')
|
|
1077
1079
|
results_converted.append(r_converted)
|
|
1078
1080
|
results = results_converted
|
|
1079
|
-
|
|
1081
|
+
|
|
1080
1082
|
# The typical case: we need to build the 'info' struct
|
|
1081
1083
|
if info is None:
|
|
1082
|
-
|
|
1083
|
-
info = {
|
|
1084
|
+
|
|
1085
|
+
info = {
|
|
1084
1086
|
'detection_completion_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
1085
|
-
'format_version': '1.4'
|
|
1087
|
+
'format_version': '1.4'
|
|
1086
1088
|
}
|
|
1087
|
-
|
|
1089
|
+
|
|
1088
1090
|
if detector_file is not None:
|
|
1089
1091
|
detector_filename = os.path.basename(detector_file)
|
|
1090
1092
|
detector_version = get_detector_version_from_filename(detector_filename)
|
|
1091
1093
|
detector_metadata = get_detector_metadata_from_version_string(detector_version)
|
|
1092
|
-
info['detector'] = detector_filename
|
|
1094
|
+
info['detector'] = detector_filename
|
|
1093
1095
|
info['detector_metadata'] = detector_metadata
|
|
1094
1096
|
else:
|
|
1095
1097
|
info['detector'] = 'unknown'
|
|
1096
1098
|
info['detector_metadata'] = get_detector_metadata_from_version_string('unknown')
|
|
1097
|
-
|
|
1099
|
+
|
|
1098
1100
|
# If the caller supplied the entire "info" struct
|
|
1099
1101
|
else:
|
|
1100
|
-
|
|
1101
|
-
if detector_file is not None:
|
|
1102
|
+
|
|
1103
|
+
if detector_file is not None:
|
|
1102
1104
|
print('Warning (write_results_to_file): info struct and detector file ' + \
|
|
1103
1105
|
'supplied, ignoring detector file')
|
|
1104
1106
|
|
|
1105
1107
|
if custom_metadata is not None:
|
|
1106
1108
|
info['custom_metadata'] = custom_metadata
|
|
1107
|
-
|
|
1109
|
+
|
|
1108
1110
|
# The 'max_detection_conf' field used to be included by default, and it caused all kinds
|
|
1109
1111
|
# of headaches, so it's no longer included unless the user explicitly requests it.
|
|
1110
1112
|
if not include_max_conf:
|
|
1111
1113
|
for im in results:
|
|
1112
1114
|
if 'max_detection_conf' in im:
|
|
1113
1115
|
del im['max_detection_conf']
|
|
1114
|
-
|
|
1116
|
+
|
|
1115
1117
|
# Sort results by filename; not required by the format, but convenient for consistency
|
|
1116
1118
|
results = sort_list_of_dicts_by_key(results,'file')
|
|
1117
|
-
|
|
1119
|
+
|
|
1118
1120
|
# Sort detections in descending order by confidence; not required by the format, but
|
|
1119
1121
|
# convenient for consistency
|
|
1120
1122
|
for r in results:
|
|
1121
1123
|
if ('detections' in r) and (r['detections'] is not None):
|
|
1122
1124
|
r['detections'] = sort_list_of_dicts_by_key(r['detections'], 'conf', reverse=True)
|
|
1123
|
-
|
|
1125
|
+
|
|
1124
1126
|
final_output = {
|
|
1125
1127
|
'images': results,
|
|
1126
1128
|
'detection_categories': run_detector.DEFAULT_DETECTOR_LABEL_MAP,
|
|
1127
1129
|
'info': info
|
|
1128
1130
|
}
|
|
1129
|
-
|
|
1131
|
+
|
|
1130
1132
|
# Create the folder where the output file belongs; this will fail if
|
|
1131
1133
|
# this is a relative path with no folder component
|
|
1132
1134
|
try:
|
|
1133
1135
|
os.makedirs(os.path.dirname(output_file),exist_ok=True)
|
|
1134
1136
|
except Exception:
|
|
1135
1137
|
pass
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
json.dump(final_output, f, indent=1, default=str)
|
|
1138
|
+
|
|
1139
|
+
ct_utils.write_json(output_file, final_output, force_str=True)
|
|
1139
1140
|
print('Output file saved at {}'.format(output_file))
|
|
1140
|
-
|
|
1141
|
+
|
|
1141
1142
|
return final_output
|
|
1142
1143
|
|
|
1143
1144
|
# ...def write_results_to_file(...)
|
|
@@ -1146,15 +1147,15 @@ def write_results_to_file(results,
|
|
|
1146
1147
|
#%% Interactive driver
|
|
1147
1148
|
|
|
1148
1149
|
if False:
|
|
1149
|
-
|
|
1150
|
+
|
|
1150
1151
|
pass
|
|
1151
1152
|
|
|
1152
1153
|
#%%
|
|
1153
|
-
|
|
1154
|
+
|
|
1154
1155
|
model_file = 'MDV5A'
|
|
1155
1156
|
image_dir = r'g:\camera_traps\camera_trap_images'
|
|
1156
1157
|
output_file = r'g:\temp\md-test.json'
|
|
1157
|
-
|
|
1158
|
+
|
|
1158
1159
|
recursive = True
|
|
1159
1160
|
output_relative_filenames = True
|
|
1160
1161
|
include_max_conf = False
|
|
@@ -1162,7 +1163,7 @@ if False:
|
|
|
1162
1163
|
image_size = None
|
|
1163
1164
|
use_image_queue = False
|
|
1164
1165
|
confidence_threshold = 0.0001
|
|
1165
|
-
checkpoint_frequency = 5
|
|
1166
|
+
checkpoint_frequency = 5
|
|
1166
1167
|
checkpoint_path = None
|
|
1167
1168
|
resume_from_checkpoint = 'auto'
|
|
1168
1169
|
allow_checkpoint_overwrite = False
|
|
@@ -1172,11 +1173,11 @@ if False:
|
|
|
1172
1173
|
include_image_timestamp = True
|
|
1173
1174
|
include_exif_data = True
|
|
1174
1175
|
overwrite_handling = None
|
|
1175
|
-
|
|
1176
|
+
|
|
1176
1177
|
# Generate a command line
|
|
1177
1178
|
cmd = 'python run_detector_batch.py "{}" "{}" "{}"'.format(
|
|
1178
1179
|
model_file,image_dir,output_file)
|
|
1179
|
-
|
|
1180
|
+
|
|
1180
1181
|
if recursive:
|
|
1181
1182
|
cmd += ' --recursive'
|
|
1182
1183
|
if output_relative_filenames:
|
|
@@ -1211,18 +1212,18 @@ if False:
|
|
|
1211
1212
|
cmd += ' --include_exif_data'
|
|
1212
1213
|
if overwrite_handling is not None:
|
|
1213
1214
|
cmd += ' --overwrite_handling {}'.format(overwrite_handling)
|
|
1214
|
-
|
|
1215
|
+
|
|
1215
1216
|
print(cmd)
|
|
1216
1217
|
import clipboard; clipboard.copy(cmd)
|
|
1217
|
-
|
|
1218
|
-
|
|
1218
|
+
|
|
1219
|
+
|
|
1219
1220
|
#%% Run inference interactively
|
|
1220
|
-
|
|
1221
|
-
image_file_names = path_utils.find_images(image_dir, recursive=False)
|
|
1221
|
+
|
|
1222
|
+
image_file_names = path_utils.find_images(image_dir, recursive=False)
|
|
1222
1223
|
results = None
|
|
1223
|
-
|
|
1224
|
+
|
|
1224
1225
|
start_time = time.time()
|
|
1225
|
-
|
|
1226
|
+
|
|
1226
1227
|
results = load_and_run_detector_batch(model_file=model_file,
|
|
1227
1228
|
image_file_names=image_file_names,
|
|
1228
1229
|
checkpoint_path=checkpoint_path,
|
|
@@ -1233,21 +1234,22 @@ if False:
|
|
|
1233
1234
|
use_image_queue=use_image_queue,
|
|
1234
1235
|
quiet=quiet,
|
|
1235
1236
|
image_size=image_size)
|
|
1236
|
-
|
|
1237
|
+
|
|
1237
1238
|
elapsed = time.time() - start_time
|
|
1238
|
-
|
|
1239
|
+
|
|
1239
1240
|
print('Finished inference in {}'.format(humanfriendly.format_timespan(elapsed)))
|
|
1240
1241
|
|
|
1241
|
-
|
|
1242
|
+
|
|
1242
1243
|
#%% Command-line driver
|
|
1243
1244
|
|
|
1244
|
-
def main():
|
|
1245
|
-
|
|
1245
|
+
def main(): # noqa
|
|
1246
|
+
|
|
1246
1247
|
parser = argparse.ArgumentParser(
|
|
1247
1248
|
description='Module to run a TF/PT animal detection model on lots of images')
|
|
1248
1249
|
parser.add_argument(
|
|
1249
1250
|
'detector_file',
|
|
1250
|
-
help='Path to detector model file (.pb or .pt). Can also be the strings "MDV4",
|
|
1251
|
+
help='Path to detector model file (.pb or .pt). Can also be the strings "MDV4", ' + \
|
|
1252
|
+
'"MDV5A", or "MDV5B" to request automatic download.')
|
|
1251
1253
|
parser.add_argument(
|
|
1252
1254
|
'image_file',
|
|
1253
1255
|
help=\
|
|
@@ -1279,7 +1281,7 @@ def main():
|
|
|
1279
1281
|
'--image_size',
|
|
1280
1282
|
type=int,
|
|
1281
1283
|
default=None,
|
|
1282
|
-
help=('Force image resizing to a specific integer size on the long axis (not recommended to change this)'))
|
|
1284
|
+
help=('Force image resizing to a specific integer size on the long axis (not recommended to change this)'))
|
|
1283
1285
|
parser.add_argument(
|
|
1284
1286
|
'--augment',
|
|
1285
1287
|
action='store_true',
|
|
@@ -1316,7 +1318,7 @@ def main():
|
|
|
1316
1318
|
type=str,
|
|
1317
1319
|
default=None,
|
|
1318
1320
|
help='File name to which checkpoints will be written if checkpoint_frequency is > 0, ' + \
|
|
1319
|
-
'defaults to md_checkpoint_[date].json in the same folder as the output file')
|
|
1321
|
+
'defaults to md_checkpoint_[date].json in the same folder as the output file')
|
|
1320
1322
|
parser.add_argument(
|
|
1321
1323
|
'--resume_from_checkpoint',
|
|
1322
1324
|
type=str,
|
|
@@ -1367,7 +1369,7 @@ def main():
|
|
|
1367
1369
|
type=str,
|
|
1368
1370
|
default='overwrite',
|
|
1369
1371
|
help='What should we do if the output file exists? overwrite/skip/error (default overwrite)'
|
|
1370
|
-
)
|
|
1372
|
+
)
|
|
1371
1373
|
parser.add_argument(
|
|
1372
1374
|
'--force_model_download',
|
|
1373
1375
|
action='store_true',
|
|
@@ -1387,28 +1389,28 @@ def main():
|
|
|
1387
1389
|
metavar='KEY=VALUE',
|
|
1388
1390
|
default='',
|
|
1389
1391
|
help='Detector-specific options, as a space-separated list of key-value pairs')
|
|
1390
|
-
|
|
1392
|
+
|
|
1391
1393
|
if len(sys.argv[1:]) == 0:
|
|
1392
1394
|
parser.print_help()
|
|
1393
1395
|
parser.exit()
|
|
1394
1396
|
|
|
1395
1397
|
args = parser.parse_args()
|
|
1396
|
-
|
|
1398
|
+
|
|
1397
1399
|
global verbose
|
|
1398
1400
|
global use_threads_for_queue
|
|
1399
|
-
|
|
1401
|
+
|
|
1400
1402
|
if args.verbose:
|
|
1401
1403
|
verbose = True
|
|
1402
1404
|
if args.use_threads_for_queue:
|
|
1403
1405
|
use_threads_for_queue = True
|
|
1404
|
-
|
|
1406
|
+
|
|
1405
1407
|
detector_options = parse_kvp_list(args.detector_options)
|
|
1406
|
-
|
|
1407
|
-
# If the specified detector file is really the name of a known model, find
|
|
1408
|
+
|
|
1409
|
+
# If the specified detector file is really the name of a known model, find
|
|
1408
1410
|
# (and possibly download) that model
|
|
1409
|
-
args.detector_file = try_download_known_detector(args.detector_file,
|
|
1411
|
+
args.detector_file = try_download_known_detector(args.detector_file,
|
|
1410
1412
|
force_download=args.force_model_download)
|
|
1411
|
-
|
|
1413
|
+
|
|
1412
1414
|
assert os.path.exists(args.detector_file), \
|
|
1413
1415
|
'detector file {} does not exist'.format(args.detector_file)
|
|
1414
1416
|
assert 0.0 <= args.threshold <= 1.0, 'Confidence threshold needs to be between 0 and 1'
|
|
@@ -1439,12 +1441,12 @@ def main():
|
|
|
1439
1441
|
|
|
1440
1442
|
if len(output_dir) > 0:
|
|
1441
1443
|
os.makedirs(output_dir,exist_ok=True)
|
|
1442
|
-
|
|
1444
|
+
|
|
1443
1445
|
assert not os.path.isdir(args.output_file), 'Specified output file is a directory'
|
|
1444
|
-
|
|
1446
|
+
|
|
1445
1447
|
if args.class_mapping_filename is not None:
|
|
1446
1448
|
_load_custom_class_mapping(args.class_mapping_filename)
|
|
1447
|
-
|
|
1449
|
+
|
|
1448
1450
|
# Load the checkpoint if available
|
|
1449
1451
|
#
|
|
1450
1452
|
# File paths in the checkpoint are always absolute paths; conversion to relative paths
|
|
@@ -1463,7 +1465,7 @@ def main():
|
|
|
1463
1465
|
len(checkpoint_files),output_dir))
|
|
1464
1466
|
checkpoint_files = sorted(checkpoint_files)
|
|
1465
1467
|
checkpoint_file_relative = checkpoint_files[-1]
|
|
1466
|
-
checkpoint_file = os.path.join(output_dir,checkpoint_file_relative)
|
|
1468
|
+
checkpoint_file = os.path.join(output_dir,checkpoint_file_relative)
|
|
1467
1469
|
else:
|
|
1468
1470
|
checkpoint_file = args.resume_from_checkpoint
|
|
1469
1471
|
assert os.path.exists(checkpoint_file), \
|
|
@@ -1483,7 +1485,7 @@ def main():
|
|
|
1483
1485
|
if os.path.isdir(args.image_file):
|
|
1484
1486
|
image_file_names = path_utils.find_images(args.image_file, args.recursive)
|
|
1485
1487
|
if len(image_file_names) > 0:
|
|
1486
|
-
print('{} image files found in the input directory'.format(len(image_file_names)))
|
|
1488
|
+
print('{} image files found in the input directory'.format(len(image_file_names)))
|
|
1487
1489
|
else:
|
|
1488
1490
|
if (args.recursive):
|
|
1489
1491
|
print('No image files found in directory {}, exiting'.format(args.image_file))
|
|
@@ -1492,14 +1494,14 @@ def main():
|
|
|
1492
1494
|
'--recursive?'.format(
|
|
1493
1495
|
args.image_file))
|
|
1494
1496
|
return
|
|
1495
|
-
|
|
1497
|
+
|
|
1496
1498
|
# A json list of image paths
|
|
1497
|
-
elif os.path.isfile(args.image_file) and args.image_file.endswith('.json'):
|
|
1499
|
+
elif os.path.isfile(args.image_file) and args.image_file.endswith('.json'):
|
|
1498
1500
|
with open(args.image_file) as f:
|
|
1499
1501
|
image_file_names = json.load(f)
|
|
1500
1502
|
print('Loaded {} image filenames from .json list file {}'.format(
|
|
1501
1503
|
len(image_file_names),args.image_file))
|
|
1502
|
-
|
|
1504
|
+
|
|
1503
1505
|
# A text list of image paths
|
|
1504
1506
|
elif os.path.isfile(args.image_file) and args.image_file.endswith('.txt'):
|
|
1505
1507
|
with open(args.image_file) as f:
|
|
@@ -1507,51 +1509,51 @@ def main():
|
|
|
1507
1509
|
image_file_names = [fn.strip() for fn in image_file_names if len(fn.strip()) > 0]
|
|
1508
1510
|
print('Loaded {} image filenames from .txt list file {}'.format(
|
|
1509
1511
|
len(image_file_names),args.image_file))
|
|
1510
|
-
|
|
1512
|
+
|
|
1511
1513
|
# A single image file
|
|
1512
1514
|
elif os.path.isfile(args.image_file) and path_utils.is_image_file(args.image_file):
|
|
1513
1515
|
image_file_names = [args.image_file]
|
|
1514
1516
|
print('Processing image {}'.format(args.image_file))
|
|
1515
|
-
|
|
1516
|
-
else:
|
|
1517
|
+
|
|
1518
|
+
else:
|
|
1517
1519
|
raise ValueError('image_file specified is not a directory, a json list, or an image file, '
|
|
1518
1520
|
'(or does not have recognizable extensions).')
|
|
1519
1521
|
|
|
1520
|
-
# At this point, regardless of how they were specified, [image_file_names] is a list of
|
|
1522
|
+
# At this point, regardless of how they were specified, [image_file_names] is a list of
|
|
1521
1523
|
# absolute image paths.
|
|
1522
1524
|
assert len(image_file_names) > 0, 'Specified image_file does not point to valid image files'
|
|
1523
|
-
|
|
1525
|
+
|
|
1524
1526
|
# Convert to forward slashes to facilitate comparison with previous results
|
|
1525
1527
|
image_file_names = [fn.replace('\\','/') for fn in image_file_names]
|
|
1526
|
-
|
|
1528
|
+
|
|
1527
1529
|
# We can head off many problems related to incorrect command line formulation if we confirm
|
|
1528
|
-
# that one image exists before proceeding. The use of the first image for this test is
|
|
1530
|
+
# that one image exists before proceeding. The use of the first image for this test is
|
|
1529
1531
|
# arbitrary.
|
|
1530
1532
|
assert os.path.exists(image_file_names[0]), \
|
|
1531
1533
|
'The first image to be processed does not exist at {}'.format(image_file_names[0])
|
|
1532
1534
|
|
|
1533
1535
|
# Possibly load results from a previous pass
|
|
1534
1536
|
previous_results = None
|
|
1535
|
-
|
|
1537
|
+
|
|
1536
1538
|
if args.previous_results_file is not None:
|
|
1537
|
-
|
|
1539
|
+
|
|
1538
1540
|
assert os.path.isfile(args.previous_results_file), \
|
|
1539
1541
|
'Could not find previous results file {}'.format(args.previous_results_file)
|
|
1540
1542
|
with open(args.previous_results_file,'r') as f:
|
|
1541
1543
|
previous_results = json.load(f)
|
|
1542
|
-
|
|
1544
|
+
|
|
1543
1545
|
assert previous_results['detection_categories'] == run_detector.DEFAULT_DETECTOR_LABEL_MAP, \
|
|
1544
1546
|
"Can't merge previous results when those results use a different set of detection categories"
|
|
1545
|
-
|
|
1547
|
+
|
|
1546
1548
|
print('Loaded previous results for {} images from {}'.format(
|
|
1547
1549
|
len(previous_results['images']), args.previous_results_file))
|
|
1548
|
-
|
|
1549
|
-
# Convert previous result filenames to absolute paths if necessary
|
|
1550
|
+
|
|
1551
|
+
# Convert previous result filenames to absolute paths if necessary
|
|
1550
1552
|
#
|
|
1551
|
-
# We asserted above to make sure that we are using relative paths and processing a
|
|
1553
|
+
# We asserted above to make sure that we are using relative paths and processing a
|
|
1552
1554
|
# folder, but just to be super-clear...
|
|
1553
1555
|
assert os.path.isdir(args.image_file)
|
|
1554
|
-
|
|
1556
|
+
|
|
1555
1557
|
previous_image_files_set = set()
|
|
1556
1558
|
for im in previous_results['images']:
|
|
1557
1559
|
assert not os.path.isabs(im['file']), \
|
|
@@ -1559,54 +1561,53 @@ def main():
|
|
|
1559
1561
|
fn_abs = os.path.join(args.image_file,im['file']).replace('\\','/')
|
|
1560
1562
|
# Absolute paths are expected at the final output stage below
|
|
1561
1563
|
im['file'] = fn_abs
|
|
1562
|
-
previous_image_files_set.add(fn_abs)
|
|
1563
|
-
|
|
1564
|
+
previous_image_files_set.add(fn_abs)
|
|
1565
|
+
|
|
1564
1566
|
image_file_names_to_keep = []
|
|
1565
1567
|
for fn_abs in image_file_names:
|
|
1566
1568
|
if fn_abs not in previous_image_files_set:
|
|
1567
1569
|
image_file_names_to_keep.append(fn_abs)
|
|
1568
|
-
|
|
1570
|
+
|
|
1569
1571
|
print('Based on previous results file, processing {} of {} images'.format(
|
|
1570
1572
|
len(image_file_names_to_keep), len(image_file_names)))
|
|
1571
|
-
|
|
1573
|
+
|
|
1572
1574
|
image_file_names = image_file_names_to_keep
|
|
1573
|
-
|
|
1575
|
+
|
|
1574
1576
|
# ...if we're handling previous results
|
|
1575
|
-
|
|
1577
|
+
|
|
1576
1578
|
# Test that we can write to the output_file's dir if checkpointing requested
|
|
1577
1579
|
if args.checkpoint_frequency != -1:
|
|
1578
|
-
|
|
1580
|
+
|
|
1579
1581
|
if args.checkpoint_path is not None:
|
|
1580
1582
|
checkpoint_path = args.checkpoint_path
|
|
1581
1583
|
else:
|
|
1582
1584
|
checkpoint_path = os.path.join(output_dir,
|
|
1583
1585
|
'md_checkpoint_{}.json'.format(
|
|
1584
1586
|
datetime.now().strftime("%Y%m%d%H%M%S")))
|
|
1585
|
-
|
|
1587
|
+
|
|
1586
1588
|
# Don't overwrite existing checkpoint files, this is a sure-fire way to eventually
|
|
1587
1589
|
# erase someone's checkpoint.
|
|
1588
1590
|
if (checkpoint_path is not None) and (not args.allow_checkpoint_overwrite) \
|
|
1589
1591
|
and (args.resume_from_checkpoint is None):
|
|
1590
|
-
|
|
1592
|
+
|
|
1591
1593
|
assert not os.path.isfile(checkpoint_path), \
|
|
1592
1594
|
f'Checkpoint path {checkpoint_path} already exists, delete or move it before ' + \
|
|
1593
1595
|
're-using the same checkpoint path, or specify --allow_checkpoint_overwrite'
|
|
1594
1596
|
|
|
1595
|
-
|
|
1597
|
+
|
|
1596
1598
|
# Confirm that we can write to the checkpoint path; this avoids issues where
|
|
1597
1599
|
# we crash after several thousand images.
|
|
1598
1600
|
#
|
|
1599
|
-
# But actually, commenting this out for now... the scenario where we are resuming from a
|
|
1601
|
+
# But actually, commenting this out for now... the scenario where we are resuming from a
|
|
1600
1602
|
# checkpoint, then immediately overwrite that checkpoint with empty data is higher-risk
|
|
1601
1603
|
# than the annoyance of crashing a few minutes after starting a job.
|
|
1602
1604
|
if False:
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1605
|
+
ct_utils.write_json(checkpoint_path, {'images': []}, indent=None)
|
|
1606
|
+
|
|
1606
1607
|
print('The checkpoint file will be written to {}'.format(checkpoint_path))
|
|
1607
|
-
|
|
1608
|
+
|
|
1608
1609
|
else:
|
|
1609
|
-
|
|
1610
|
+
|
|
1610
1611
|
if args.checkpoint_path is not None:
|
|
1611
1612
|
print('Warning: checkpointing disabled because checkpoint_frequency is -1, ' + \
|
|
1612
1613
|
'but a checkpoint path was specified')
|
|
@@ -1641,23 +1642,23 @@ def main():
|
|
|
1641
1642
|
len(results),humanfriendly.format_timespan(elapsed),images_per_second))
|
|
1642
1643
|
|
|
1643
1644
|
relative_path_base = None
|
|
1644
|
-
|
|
1645
|
-
# We asserted above to make sure that if output_relative_filenames is set,
|
|
1645
|
+
|
|
1646
|
+
# We asserted above to make sure that if output_relative_filenames is set,
|
|
1646
1647
|
# args.image_file is a folder, but we'll double-check for clarity.
|
|
1647
1648
|
if args.output_relative_filenames:
|
|
1648
1649
|
assert os.path.isdir(args.image_file)
|
|
1649
1650
|
relative_path_base = args.image_file
|
|
1650
|
-
|
|
1651
|
+
|
|
1651
1652
|
# Merge results from a previous file if necessary
|
|
1652
1653
|
if previous_results is not None:
|
|
1653
1654
|
previous_filenames_set = set([im['file'] for im in previous_results['images']])
|
|
1654
1655
|
new_filenames_set = set([im['file'] for im in results])
|
|
1655
1656
|
assert len(previous_filenames_set.intersection(new_filenames_set)) == 0, \
|
|
1656
1657
|
'Previous results handling error: redundant image filenames'
|
|
1657
|
-
results.extend(previous_results['images'])
|
|
1658
|
-
|
|
1659
|
-
write_results_to_file(results,
|
|
1660
|
-
args.output_file,
|
|
1658
|
+
results.extend(previous_results['images'])
|
|
1659
|
+
|
|
1660
|
+
write_results_to_file(results,
|
|
1661
|
+
args.output_file,
|
|
1661
1662
|
relative_path_base=relative_path_base,
|
|
1662
1663
|
detector_file=args.detector_file,
|
|
1663
1664
|
include_max_conf=args.include_max_conf)
|