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