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
|
@@ -19,6 +19,7 @@ from functools import partial
|
|
|
19
19
|
|
|
20
20
|
from megadetector.utils.path_utils import insert_before_extension
|
|
21
21
|
from megadetector.utils.ct_utils import invert_dictionary
|
|
22
|
+
from megadetector.utils.ct_utils import is_list_sorted
|
|
22
23
|
from megadetector.visualization.visualization_utils import crop_image
|
|
23
24
|
from megadetector.visualization.visualization_utils import exif_preserving_save
|
|
24
25
|
|
|
@@ -29,24 +30,24 @@ class CreateCropFolderOptions:
|
|
|
29
30
|
"""
|
|
30
31
|
Options used to parameterize create_crop_folder().
|
|
31
32
|
"""
|
|
32
|
-
|
|
33
|
+
|
|
33
34
|
def __init__(self):
|
|
34
|
-
|
|
35
|
+
|
|
35
36
|
#: Confidence threshold determining which detections get written
|
|
36
37
|
self.confidence_threshold = 0.1
|
|
37
|
-
|
|
38
|
+
|
|
38
39
|
#: Number of pixels to expand each crop
|
|
39
40
|
self.expansion = 0
|
|
40
|
-
|
|
41
|
+
|
|
41
42
|
#: JPEG quality to use for saving crops (None for default)
|
|
42
43
|
self.quality = 95
|
|
43
|
-
|
|
44
|
+
|
|
44
45
|
#: Whether to overwrite existing images
|
|
45
46
|
self.overwrite = True
|
|
46
|
-
|
|
47
|
+
|
|
47
48
|
#: Number of concurrent workers
|
|
48
49
|
self.n_workers = 8
|
|
49
|
-
|
|
50
|
+
|
|
50
51
|
#: Whether to use processes ('process') or threads ('thread') for parallelization
|
|
51
52
|
self.pool_type = 'thread'
|
|
52
53
|
|
|
@@ -54,8 +55,8 @@ class CreateCropFolderOptions:
|
|
|
54
55
|
#:
|
|
55
56
|
#: options.category_names_to_include = ['animal']
|
|
56
57
|
self.category_names_to_include = None
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
|
|
59
|
+
|
|
59
60
|
#%% Support functions
|
|
60
61
|
|
|
61
62
|
def _get_crop_filename(image_fn,crop_id):
|
|
@@ -77,34 +78,34 @@ def _generate_crops_for_single_image(crops_this_image,
|
|
|
77
78
|
"""
|
|
78
79
|
if len(crops_this_image) == 0:
|
|
79
80
|
return
|
|
80
|
-
|
|
81
|
-
image_fn_relative = crops_this_image[0]['image_fn_relative']
|
|
81
|
+
|
|
82
|
+
image_fn_relative = crops_this_image[0]['image_fn_relative']
|
|
82
83
|
input_fn_abs = os.path.join(input_folder,image_fn_relative)
|
|
83
84
|
assert os.path.isfile(input_fn_abs)
|
|
84
|
-
|
|
85
|
+
|
|
85
86
|
detections_to_crop = [c['detection'] for c in crops_this_image]
|
|
86
|
-
|
|
87
|
+
|
|
87
88
|
cropped_images = crop_image(detections_to_crop,
|
|
88
89
|
input_fn_abs,
|
|
89
90
|
confidence_threshold=0,
|
|
90
91
|
expansion=options.expansion)
|
|
91
|
-
|
|
92
|
+
|
|
92
93
|
assert len(cropped_images) == len(crops_this_image)
|
|
93
|
-
|
|
94
|
+
|
|
94
95
|
# i_crop = 0; crop_info = crops_this_image[0]
|
|
95
96
|
for i_crop,crop_info in enumerate(crops_this_image):
|
|
96
|
-
|
|
97
|
+
|
|
97
98
|
assert crop_info['image_fn_relative'] == image_fn_relative
|
|
98
|
-
crop_filename_relative = _get_crop_filename(image_fn_relative, crop_info['crop_id'])
|
|
99
|
+
crop_filename_relative = _get_crop_filename(image_fn_relative, crop_info['crop_id'])
|
|
99
100
|
crop_filename_abs = os.path.join(output_folder,crop_filename_relative).replace('\\','/')
|
|
100
|
-
|
|
101
|
+
|
|
101
102
|
if os.path.isfile(crop_filename_abs) and not options.overwrite:
|
|
102
103
|
continue
|
|
103
|
-
|
|
104
|
-
cropped_image = cropped_images[i_crop]
|
|
105
|
-
os.makedirs(os.path.dirname(crop_filename_abs),exist_ok=True)
|
|
104
|
+
|
|
105
|
+
cropped_image = cropped_images[i_crop]
|
|
106
|
+
os.makedirs(os.path.dirname(crop_filename_abs),exist_ok=True)
|
|
106
107
|
exif_preserving_save(cropped_image,crop_filename_abs,quality=options.quality)
|
|
107
|
-
|
|
108
|
+
|
|
108
109
|
# ...for each crop
|
|
109
110
|
|
|
110
111
|
|
|
@@ -113,119 +114,185 @@ def _generate_crops_for_single_image(crops_this_image,
|
|
|
113
114
|
def crop_results_to_image_results(image_results_file_with_crop_ids,
|
|
114
115
|
crop_results_file,
|
|
115
116
|
output_file,
|
|
116
|
-
delete_crop_information=True
|
|
117
|
+
delete_crop_information=True,
|
|
118
|
+
require_identical_detection_categories=True,
|
|
119
|
+
restrict_to_top_n=-1,
|
|
120
|
+
crop_results_prefix=None,
|
|
121
|
+
detections_without_classification_handling='error'):
|
|
117
122
|
"""
|
|
118
123
|
This function is intended to be run after you have:
|
|
119
|
-
|
|
124
|
+
|
|
120
125
|
1. Run MegaDetector on a folder
|
|
121
126
|
2. Generated a crop folder using create_crop_folder
|
|
122
127
|
3. Run a species classifier on those crops
|
|
123
|
-
|
|
128
|
+
|
|
124
129
|
This function will take the crop-level results and transform them back
|
|
125
|
-
to the original images. Classification categories, if available, are taken
|
|
130
|
+
to the original images. Classification categories, if available, are taken
|
|
126
131
|
from [crop_results_file].
|
|
127
|
-
|
|
132
|
+
|
|
128
133
|
Args:
|
|
129
134
|
image_results_file_with_crop_ids (str): results file for the original images,
|
|
130
|
-
containing crop IDs, likely generated via create_crop_folder. All
|
|
135
|
+
containing crop IDs, likely generated via create_crop_folder. All
|
|
131
136
|
non-standard fields in this file will be passed along to [output_file].
|
|
132
137
|
crop_results_file (str): results file for the crop folder
|
|
133
|
-
output_file (str):
|
|
138
|
+
output_file (str): output .json file, containing crop-level classifications
|
|
134
139
|
mapped back to the image level.
|
|
135
140
|
delete_crop_information (bool, optional): whether to delete the "crop_id" and
|
|
136
141
|
"crop_filename_relative" fields from each detection, if present.
|
|
142
|
+
require_identical_detection_categories (bool, optional): if True, error if
|
|
143
|
+
the image-level and crop-level detection categories are different. If False,
|
|
144
|
+
ignore the crop-level detection categories.
|
|
145
|
+
restrict_to_top_n (int, optional): If >0, removes all but the top N classification
|
|
146
|
+
results for each detection.
|
|
147
|
+
crop_results_prefix (str, optional): if not None, removes this prefix from crop
|
|
148
|
+
results filenames. Intended to support the case where the crop results
|
|
149
|
+
use absolute paths.
|
|
150
|
+
detections_without_classification_handling (str, optional): what to do when we
|
|
151
|
+
encounter a crop that doesn't appear in classification results: 'error',
|
|
152
|
+
or 'include' ("include" means "leave the detection alone, without classifications"
|
|
137
153
|
"""
|
|
138
|
-
|
|
154
|
+
|
|
139
155
|
##%% Validate inputs
|
|
140
|
-
|
|
156
|
+
|
|
141
157
|
assert os.path.isfile(image_results_file_with_crop_ids), \
|
|
142
158
|
'Could not find image-level input file {}'.format(image_results_file_with_crop_ids)
|
|
143
159
|
assert os.path.isfile(crop_results_file), \
|
|
144
160
|
'Could not find crop results file {}'.format(crop_results_file)
|
|
145
161
|
os.makedirs(os.path.dirname(output_file),exist_ok=True)
|
|
146
|
-
|
|
147
|
-
|
|
162
|
+
|
|
163
|
+
|
|
148
164
|
##%% Read input files
|
|
149
|
-
|
|
165
|
+
|
|
150
166
|
print('Reading input...')
|
|
151
|
-
|
|
167
|
+
|
|
152
168
|
with open(image_results_file_with_crop_ids,'r') as f:
|
|
153
169
|
image_results_with_crop_ids = json.load(f)
|
|
154
170
|
with open(crop_results_file,'r') as f:
|
|
155
171
|
crop_results = json.load(f)
|
|
156
172
|
|
|
157
173
|
# Find all the detection categories that need to be consistent
|
|
158
|
-
|
|
174
|
+
used_detection_category_ids = set()
|
|
159
175
|
for im in tqdm(image_results_with_crop_ids['images']):
|
|
160
176
|
if 'detections' not in im or im['detections'] is None:
|
|
161
|
-
continue
|
|
177
|
+
continue
|
|
162
178
|
for det in im['detections']:
|
|
163
179
|
if 'crop_id' in det:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
# Make sure the categories that matter are consistent across the two files
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
180
|
+
used_detection_category_ids.add(det['category'])
|
|
181
|
+
|
|
182
|
+
# Make sure the detection categories that matter are consistent across the two files
|
|
183
|
+
if require_identical_detection_categories:
|
|
184
|
+
for category_id in used_detection_category_ids:
|
|
185
|
+
category_name = image_results_with_crop_ids['detection_categories'][category_id]
|
|
186
|
+
assert category_id in crop_results['detection_categories'] and \
|
|
187
|
+
category_name == crop_results['detection_categories'][category_id], \
|
|
188
|
+
'Crop results and detection results use incompatible categories'
|
|
189
|
+
|
|
173
190
|
crop_filename_to_results = {}
|
|
174
|
-
|
|
191
|
+
|
|
175
192
|
# im = crop_results['images'][0]
|
|
176
|
-
for im in crop_results['images']:
|
|
177
|
-
|
|
178
|
-
|
|
193
|
+
for im in crop_results['images']:
|
|
194
|
+
fn = im['file']
|
|
195
|
+
# Possibly remove a prefix from each filename
|
|
196
|
+
if (crop_results_prefix is not None) and (crop_results_prefix in fn):
|
|
197
|
+
if fn.startswith(crop_results_prefix):
|
|
198
|
+
fn = fn.replace(crop_results_prefix,'',1)
|
|
199
|
+
im['file'] = fn
|
|
200
|
+
crop_filename_to_results[fn] = im
|
|
201
|
+
|
|
179
202
|
if 'classification_categories' in crop_results:
|
|
180
203
|
image_results_with_crop_ids['classification_categories'] = \
|
|
181
204
|
crop_results['classification_categories']
|
|
182
|
-
|
|
205
|
+
|
|
183
206
|
if 'classification_category_descriptions' in crop_results:
|
|
184
207
|
image_results_with_crop_ids['classification_category_descriptions'] = \
|
|
185
208
|
crop_results['classification_category_descriptions']
|
|
186
|
-
|
|
187
|
-
|
|
209
|
+
|
|
210
|
+
|
|
188
211
|
##%% Read classifications from crop results, merge into image-level results
|
|
189
|
-
|
|
212
|
+
|
|
213
|
+
print('Reading classification results...')
|
|
214
|
+
|
|
215
|
+
n_skipped_detections = 0
|
|
216
|
+
|
|
217
|
+
# Loop over the original image-level detections
|
|
218
|
+
#
|
|
190
219
|
# im = image_results_with_crop_ids['images'][0]
|
|
191
|
-
for im in tqdm(image_results_with_crop_ids['images'])
|
|
192
|
-
|
|
220
|
+
for i_image,im in tqdm(enumerate(image_results_with_crop_ids['images']),
|
|
221
|
+
total=len(image_results_with_crop_ids['images'])):
|
|
222
|
+
|
|
193
223
|
if 'detections' not in im or im['detections'] is None:
|
|
194
224
|
continue
|
|
195
|
-
|
|
225
|
+
|
|
226
|
+
# i_det = 0; det = im['detections'][i_det]
|
|
196
227
|
for det in im['detections']:
|
|
197
|
-
|
|
228
|
+
|
|
198
229
|
if 'classifications' in det:
|
|
199
230
|
del det['classifications']
|
|
200
|
-
|
|
231
|
+
|
|
201
232
|
if 'crop_id' in det:
|
|
233
|
+
|
|
234
|
+
# We may be skipping detections with no classification results
|
|
235
|
+
skip_detection = False
|
|
236
|
+
|
|
237
|
+
# Find the corresponding crop in the classification results
|
|
202
238
|
crop_filename_relative = det['crop_filename_relative']
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
239
|
+
if crop_filename_relative not in crop_filename_to_results:
|
|
240
|
+
if detections_without_classification_handling == 'error':
|
|
241
|
+
raise ValueError('Crop lookup error: {}'.format(crop_filename_relative))
|
|
242
|
+
elif detections_without_classification_handling == 'include':
|
|
243
|
+
# Leave this detection unclassified
|
|
244
|
+
skip_detection = True
|
|
245
|
+
else:
|
|
246
|
+
raise ValueError(
|
|
247
|
+
'Illegal value for detections_without_classification_handling: {}'.format(
|
|
248
|
+
detections_without_classification_handling
|
|
249
|
+
))
|
|
250
|
+
|
|
251
|
+
if not skip_detection:
|
|
252
|
+
|
|
253
|
+
crop_results_this_detection = crop_filename_to_results[crop_filename_relative]
|
|
254
|
+
|
|
255
|
+
# Consistency checking
|
|
256
|
+
assert crop_results_this_detection['file'] == crop_filename_relative, \
|
|
257
|
+
'Crop filename mismatch'
|
|
258
|
+
assert len(crop_results_this_detection['detections']) == 1, \
|
|
259
|
+
'Multiple crop results for a single detection'
|
|
260
|
+
assert crop_results_this_detection['detections'][0]['bbox'] == [0,0,1,1], \
|
|
261
|
+
'Invalid crop bounding box'
|
|
262
|
+
|
|
263
|
+
# This check was helpful for the case where crop-level results had already
|
|
264
|
+
# taken detection confidence values from detector output by construct, but this isn't
|
|
265
|
+
# really meaningful for most cases.
|
|
266
|
+
# assert abs(crop_results_this_detection['detections'][0]['conf'] - det['conf']) < 0.01
|
|
267
|
+
|
|
268
|
+
if require_identical_detection_categories:
|
|
269
|
+
assert crop_results_this_detection['detections'][0]['category'] == det['category']
|
|
270
|
+
|
|
271
|
+
# Copy the crop-level classifications
|
|
272
|
+
det['classifications'] = crop_results_this_detection['detections'][0]['classifications']
|
|
273
|
+
confidence_values = [x[1] for x in det['classifications']]
|
|
274
|
+
assert is_list_sorted(confidence_values,reverse=True)
|
|
275
|
+
if restrict_to_top_n > 0:
|
|
276
|
+
det['classifications'] = det['classifications'][0:restrict_to_top_n]
|
|
277
|
+
|
|
214
278
|
if delete_crop_information:
|
|
215
279
|
if 'crop_id' in det:
|
|
216
280
|
del det['crop_id']
|
|
217
281
|
if 'crop_filename_relative' in det:
|
|
218
282
|
del det['crop_filename_relative']
|
|
219
|
-
|
|
283
|
+
|
|
220
284
|
# ...for each detection
|
|
221
|
-
|
|
222
|
-
# ...for each image
|
|
223
|
-
|
|
224
|
-
|
|
285
|
+
|
|
286
|
+
# ...for each image
|
|
287
|
+
|
|
288
|
+
if n_skipped_detections > 0:
|
|
289
|
+
print('Skipped {} detections'.format(n_skipped_detections))
|
|
290
|
+
|
|
291
|
+
|
|
225
292
|
##%% Write output file
|
|
226
|
-
|
|
293
|
+
|
|
227
294
|
print('Writing output file...')
|
|
228
|
-
|
|
295
|
+
|
|
229
296
|
with open(output_file,'w') as f:
|
|
230
297
|
json.dump(image_results_with_crop_ids,f,indent=1)
|
|
231
298
|
|
|
@@ -241,9 +308,9 @@ def create_crop_folder(input_file,
|
|
|
241
308
|
"""
|
|
242
309
|
Given a MegaDetector .json file and a folder of images, creates a new folder
|
|
243
310
|
of images representing all above-threshold crops from the original folder.
|
|
244
|
-
|
|
311
|
+
|
|
245
312
|
Optionally writes a new .json file that attaches unique IDs to each detection.
|
|
246
|
-
|
|
313
|
+
|
|
247
314
|
Args:
|
|
248
315
|
input_file (str): MD-formatted .json file to process
|
|
249
316
|
input_folder (str): Input image folder
|
|
@@ -251,11 +318,11 @@ def create_crop_folder(input_file,
|
|
|
251
318
|
output_file (str, optional): new .json file that attaches unique IDs to each detection.
|
|
252
319
|
crops_output_file (str, optional): new .json file that includes whole-image detections
|
|
253
320
|
for each of the crops, using confidence values from the original results
|
|
254
|
-
options (CreateCropFolderOptions, optional): crop parameters
|
|
321
|
+
options (CreateCropFolderOptions, optional): crop parameters
|
|
255
322
|
"""
|
|
256
|
-
|
|
323
|
+
|
|
257
324
|
## Validate options, prepare output folders
|
|
258
|
-
|
|
325
|
+
|
|
259
326
|
if options is None:
|
|
260
327
|
options = CreateCropFolderOptions()
|
|
261
328
|
|
|
@@ -264,45 +331,45 @@ def create_crop_folder(input_file,
|
|
|
264
331
|
os.makedirs(output_folder,exist_ok=True)
|
|
265
332
|
if output_file is not None:
|
|
266
333
|
os.makedirs(os.path.dirname(output_file),exist_ok=True)
|
|
267
|
-
|
|
268
|
-
|
|
334
|
+
|
|
335
|
+
|
|
269
336
|
##%% Read input
|
|
270
|
-
|
|
271
|
-
print('Reading MD results file...')
|
|
337
|
+
|
|
338
|
+
print('Reading MD results file...')
|
|
272
339
|
with open(input_file,'r') as f:
|
|
273
340
|
detection_results = json.load(f)
|
|
274
|
-
|
|
341
|
+
|
|
275
342
|
category_ids_to_include = None
|
|
276
|
-
|
|
277
|
-
if options.category_names_to_include is not None:
|
|
343
|
+
|
|
344
|
+
if options.category_names_to_include is not None:
|
|
278
345
|
category_id_to_name = detection_results['detection_categories']
|
|
279
|
-
category_name_to_id = invert_dictionary(category_id_to_name)
|
|
346
|
+
category_name_to_id = invert_dictionary(category_id_to_name)
|
|
280
347
|
category_ids_to_include = set()
|
|
281
348
|
for category_name in options.category_names_to_include:
|
|
282
349
|
assert category_name in category_name_to_id, \
|
|
283
350
|
'Unrecognized category name {}'.format(category_name)
|
|
284
|
-
category_ids_to_include.add(category_name_to_id[category_name])
|
|
351
|
+
category_ids_to_include.add(category_name_to_id[category_name])
|
|
285
352
|
|
|
286
353
|
##%% Make a list of crops that we need to create
|
|
287
|
-
|
|
354
|
+
|
|
288
355
|
# Maps input images to list of dicts, with keys 'crop_id','detection'
|
|
289
356
|
image_fn_relative_to_crops = defaultdict(list)
|
|
290
357
|
n_crops = 0
|
|
291
|
-
|
|
358
|
+
|
|
292
359
|
n_detections_excluded_by_category = 0
|
|
293
360
|
|
|
294
361
|
# im = detection_results['images'][0]
|
|
295
362
|
for i_image,im in enumerate(detection_results['images']):
|
|
296
|
-
|
|
363
|
+
|
|
297
364
|
if 'detections' not in im or im['detections'] is None or len(im['detections']) == 0:
|
|
298
365
|
continue
|
|
299
|
-
|
|
366
|
+
|
|
300
367
|
detections_this_image = im['detections']
|
|
301
|
-
|
|
368
|
+
|
|
302
369
|
image_fn_relative = im['file']
|
|
303
|
-
|
|
370
|
+
|
|
304
371
|
for i_detection,det in enumerate(detections_this_image):
|
|
305
|
-
|
|
372
|
+
|
|
306
373
|
if det['conf'] < options.confidence_threshold:
|
|
307
374
|
continue
|
|
308
375
|
|
|
@@ -312,87 +379,93 @@ def create_crop_folder(input_file,
|
|
|
312
379
|
continue
|
|
313
380
|
|
|
314
381
|
det['crop_id'] = i_detection
|
|
315
|
-
|
|
382
|
+
|
|
316
383
|
crop_info = {'image_fn_relative':image_fn_relative,
|
|
317
384
|
'crop_id':i_detection,
|
|
318
385
|
'detection':det}
|
|
319
|
-
|
|
320
|
-
crop_filename_relative = _get_crop_filename(image_fn_relative,
|
|
386
|
+
|
|
387
|
+
crop_filename_relative = _get_crop_filename(image_fn_relative,
|
|
321
388
|
crop_info['crop_id'])
|
|
322
389
|
det['crop_filename_relative'] = crop_filename_relative
|
|
323
390
|
|
|
324
391
|
image_fn_relative_to_crops[image_fn_relative].append(crop_info)
|
|
325
392
|
n_crops += 1
|
|
326
|
-
|
|
327
|
-
# ...for each input image
|
|
393
|
+
|
|
394
|
+
# ...for each input image
|
|
328
395
|
|
|
329
396
|
print('Prepared a list of {} crops from {} of {} input images'.format(
|
|
330
397
|
n_crops,len(image_fn_relative_to_crops),len(detection_results['images'])))
|
|
331
|
-
|
|
398
|
+
|
|
332
399
|
if n_detections_excluded_by_category > 0:
|
|
333
400
|
print('Excluded {} detections by category'.format(n_detections_excluded_by_category))
|
|
334
|
-
|
|
401
|
+
|
|
335
402
|
##%% Generate crops
|
|
336
|
-
|
|
403
|
+
|
|
337
404
|
if options.n_workers <= 1:
|
|
338
|
-
|
|
405
|
+
|
|
339
406
|
# image_fn_relative = next(iter(image_fn_relative_to_crops))
|
|
340
407
|
for image_fn_relative in tqdm(image_fn_relative_to_crops.keys()):
|
|
341
|
-
crops_this_image = image_fn_relative_to_crops[image_fn_relative]
|
|
408
|
+
crops_this_image = image_fn_relative_to_crops[image_fn_relative]
|
|
342
409
|
_generate_crops_for_single_image(crops_this_image=crops_this_image,
|
|
343
410
|
input_folder=input_folder,
|
|
344
411
|
output_folder=output_folder,
|
|
345
412
|
options=options)
|
|
346
|
-
|
|
413
|
+
|
|
347
414
|
else:
|
|
348
|
-
|
|
415
|
+
|
|
349
416
|
print('Creating a {} pool with {} workers'.format(options.pool_type,options.n_workers))
|
|
417
|
+
pool = None
|
|
418
|
+
try:
|
|
419
|
+
if options.pool_type == 'thread':
|
|
420
|
+
pool = ThreadPool(options.n_workers)
|
|
421
|
+
else:
|
|
422
|
+
assert options.pool_type == 'process'
|
|
423
|
+
pool = Pool(options.n_workers)
|
|
424
|
+
|
|
425
|
+
# Each element in this list is the list of crops for a single image
|
|
426
|
+
crop_lists = list(image_fn_relative_to_crops.values())
|
|
427
|
+
|
|
428
|
+
with tqdm(total=len(image_fn_relative_to_crops)) as pbar:
|
|
429
|
+
for i,_ in enumerate(pool.imap_unordered(partial(
|
|
430
|
+
_generate_crops_for_single_image,
|
|
431
|
+
input_folder=input_folder,
|
|
432
|
+
output_folder=output_folder,
|
|
433
|
+
options=options),
|
|
434
|
+
crop_lists)):
|
|
435
|
+
pbar.update()
|
|
436
|
+
finally:
|
|
437
|
+
if pool is not None:
|
|
438
|
+
pool.close()
|
|
439
|
+
pool.join()
|
|
440
|
+
print("Pool closed and joined for crop folder creation")
|
|
441
|
+
|
|
442
|
+
# ...if we're using parallel processing
|
|
443
|
+
|
|
350
444
|
|
|
351
|
-
if options.pool_type == 'thread':
|
|
352
|
-
pool = ThreadPool(options.n_workers)
|
|
353
|
-
else:
|
|
354
|
-
assert options.pool_type == 'process'
|
|
355
|
-
pool = Pool(options.n_workers)
|
|
356
|
-
|
|
357
|
-
# Each element in this list is the list of crops for a single image
|
|
358
|
-
crop_lists = list(image_fn_relative_to_crops.values())
|
|
359
|
-
|
|
360
|
-
with tqdm(total=len(image_fn_relative_to_crops)) as pbar:
|
|
361
|
-
for i,_ in enumerate(pool.imap_unordered(partial(
|
|
362
|
-
_generate_crops_for_single_image,
|
|
363
|
-
input_folder=input_folder,
|
|
364
|
-
output_folder=output_folder,
|
|
365
|
-
options=options),
|
|
366
|
-
crop_lists)):
|
|
367
|
-
pbar.update()
|
|
368
|
-
|
|
369
|
-
# ...if we're using parallel processing
|
|
370
|
-
|
|
371
|
-
|
|
372
445
|
##%% Write output file
|
|
373
|
-
|
|
446
|
+
|
|
374
447
|
if output_file is not None:
|
|
375
448
|
with open(output_file,'w') as f:
|
|
376
449
|
json.dump(detection_results,f,indent=1)
|
|
377
|
-
|
|
450
|
+
|
|
378
451
|
if crops_output_file is not None:
|
|
379
|
-
|
|
452
|
+
|
|
380
453
|
original_images = detection_results['images']
|
|
381
|
-
|
|
454
|
+
|
|
382
455
|
detection_results_cropped = detection_results
|
|
383
456
|
detection_results_cropped['images'] = []
|
|
384
|
-
|
|
457
|
+
|
|
385
458
|
# im = original_images[0]
|
|
386
459
|
for im in original_images:
|
|
387
|
-
|
|
460
|
+
|
|
388
461
|
if 'detections' not in im or im['detections'] is None or len(im['detections']) == 0:
|
|
389
462
|
continue
|
|
390
|
-
|
|
391
|
-
detections_this_image = im['detections']
|
|
463
|
+
|
|
464
|
+
detections_this_image = im['detections']
|
|
392
465
|
image_fn_relative = im['file']
|
|
393
|
-
|
|
466
|
+
|
|
394
467
|
for i_detection,det in enumerate(detections_this_image):
|
|
395
|
-
|
|
468
|
+
|
|
396
469
|
if 'crop_id' in det:
|
|
397
470
|
im_out = {}
|
|
398
471
|
im_out['file'] = det['crop_filename_relative']
|
|
@@ -402,19 +475,19 @@ def create_crop_folder(input_file,
|
|
|
402
475
|
det_out['bbox'] = [0, 0, 1, 1]
|
|
403
476
|
im_out['detections'] = [det_out]
|
|
404
477
|
detection_results_cropped['images'].append(im_out)
|
|
405
|
-
|
|
478
|
+
|
|
406
479
|
# ...if we need to include this crop in the new .json file
|
|
407
|
-
|
|
480
|
+
|
|
408
481
|
# ...for each crop
|
|
409
|
-
|
|
482
|
+
|
|
410
483
|
# ...for each original image
|
|
411
|
-
|
|
484
|
+
|
|
412
485
|
with open(crops_output_file,'w') as f:
|
|
413
486
|
json.dump(detection_results_cropped,f,indent=1)
|
|
414
|
-
|
|
487
|
+
|
|
415
488
|
# ...def create_crop_folder()
|
|
416
489
|
|
|
417
490
|
|
|
418
491
|
#%% Command-line driver
|
|
419
492
|
|
|
420
|
-
# TODO
|
|
493
|
+
# TODO
|