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,7 +10,6 @@ detector output result file (.json), optionally writing an HTML index file.
|
|
|
10
10
|
#%% Imports
|
|
11
11
|
|
|
12
12
|
import argparse
|
|
13
|
-
import json
|
|
14
13
|
import os
|
|
15
14
|
import random
|
|
16
15
|
import sys
|
|
@@ -21,11 +20,13 @@ from functools import partial
|
|
|
21
20
|
from tqdm import tqdm
|
|
22
21
|
|
|
23
22
|
from megadetector.data_management.annotations.annotation_constants import detector_bbox_category_id_to_name
|
|
24
|
-
from megadetector.
|
|
25
|
-
from megadetector.visualization.visualization_utils import blur_detections
|
|
23
|
+
from megadetector.detection.run_detector import get_typical_confidence_threshold_from_results
|
|
26
24
|
from megadetector.utils.ct_utils import get_max_conf
|
|
27
25
|
from megadetector.utils import write_html_image_list
|
|
28
|
-
from megadetector.
|
|
26
|
+
from megadetector.utils.path_utils import path_is_abs
|
|
27
|
+
from megadetector.utils.wi_utils import load_md_or_speciesnet_file
|
|
28
|
+
from megadetector.visualization import visualization_utils as vis_utils
|
|
29
|
+
from megadetector.visualization.visualization_utils import blur_detections
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
#%% Constants
|
|
@@ -53,11 +54,11 @@ def _render_image(entry,
|
|
|
53
54
|
"""
|
|
54
55
|
Internal function for rendering a single image.
|
|
55
56
|
"""
|
|
56
|
-
|
|
57
|
+
|
|
57
58
|
rendering_result = {'failed_image':False,'missing_image':False,
|
|
58
59
|
'skipped_image':False,'annotated_image_path':None,
|
|
59
60
|
'max_conf':None,'file':entry['file']}
|
|
60
|
-
|
|
61
|
+
|
|
61
62
|
image_id = entry['file']
|
|
62
63
|
|
|
63
64
|
if 'failure' in entry and entry['failure'] is not None:
|
|
@@ -65,23 +66,30 @@ def _render_image(entry,
|
|
|
65
66
|
return rendering_result
|
|
66
67
|
|
|
67
68
|
assert 'detections' in entry and entry['detections'] is not None
|
|
68
|
-
|
|
69
|
+
|
|
69
70
|
max_conf = get_max_conf(entry)
|
|
70
71
|
rendering_result['max_conf'] = max_conf
|
|
71
|
-
|
|
72
|
+
|
|
72
73
|
if (max_conf < confidence_threshold) and render_detections_only:
|
|
73
74
|
rendering_result['skipped_image'] = True
|
|
74
75
|
return rendering_result
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
|
|
77
|
+
if images_dir is None:
|
|
78
|
+
image_filename_in_abs = image_id
|
|
79
|
+
assert path_is_abs(image_filename_in_abs), \
|
|
80
|
+
'Absolute paths are required when no image base dir is supplied'
|
|
81
|
+
else:
|
|
82
|
+
assert not path_is_abs(image_id), \
|
|
83
|
+
'Relative paths are required when an image base dir is supplied'
|
|
84
|
+
image_filename_in_abs = os.path.join(images_dir, image_id)
|
|
77
85
|
if not os.path.exists(image_filename_in_abs):
|
|
78
|
-
print(f'Image {image_id} not found
|
|
86
|
+
print(f'Image {image_id} not found')
|
|
79
87
|
rendering_result['missing_image'] = True
|
|
80
88
|
return rendering_result
|
|
81
89
|
|
|
82
90
|
# Load the image
|
|
83
91
|
image = vis_utils.open_image(image_filename_in_abs)
|
|
84
|
-
|
|
92
|
+
|
|
85
93
|
# Find categories we're supposed to blur
|
|
86
94
|
category_ids_to_blur = []
|
|
87
95
|
if category_names_to_blur is not None:
|
|
@@ -90,21 +98,21 @@ def _render_image(entry,
|
|
|
90
98
|
for category_id in detector_label_map:
|
|
91
99
|
if detector_label_map[category_id] in category_names_to_blur:
|
|
92
100
|
category_ids_to_blur.append(category_id)
|
|
93
|
-
|
|
101
|
+
|
|
94
102
|
detections_to_blur = []
|
|
95
103
|
for d in entry['detections']:
|
|
96
104
|
if d['conf'] >= confidence_threshold and d['category'] in category_ids_to_blur:
|
|
97
105
|
detections_to_blur.append(d)
|
|
98
106
|
if len(detections_to_blur) > 0:
|
|
99
107
|
blur_detections(image,detections_to_blur)
|
|
100
|
-
|
|
108
|
+
|
|
101
109
|
# Resize if necessary
|
|
102
110
|
#
|
|
103
111
|
# If output_image_width is -1 or None, this will just return the original image
|
|
104
112
|
image = vis_utils.resize_image(image, output_image_width)
|
|
105
113
|
|
|
106
114
|
vis_utils.render_detection_bounding_boxes(
|
|
107
|
-
entry['detections'], image,
|
|
115
|
+
entry['detections'], image,
|
|
108
116
|
label_map=detector_label_map,
|
|
109
117
|
classification_label_map=classification_label_map,
|
|
110
118
|
confidence_threshold=confidence_threshold,
|
|
@@ -119,18 +127,20 @@ def _render_image(entry,
|
|
|
119
127
|
assert not os.path.isabs(image_id), "Can't preserve paths when operating on absolute paths"
|
|
120
128
|
annotated_img_path = os.path.join(out_dir, image_id)
|
|
121
129
|
os.makedirs(os.path.dirname(annotated_img_path),exist_ok=True)
|
|
122
|
-
|
|
130
|
+
|
|
123
131
|
image.save(annotated_img_path)
|
|
124
|
-
rendering_result['annotated_image_path'] = annotated_img_path
|
|
132
|
+
rendering_result['annotated_image_path'] = annotated_img_path
|
|
125
133
|
|
|
126
134
|
return rendering_result
|
|
127
135
|
|
|
136
|
+
# ...def _render_image(...)
|
|
137
|
+
|
|
128
138
|
|
|
129
139
|
#%% Main function
|
|
130
140
|
|
|
131
141
|
def visualize_detector_output(detector_output_path,
|
|
132
142
|
out_dir,
|
|
133
|
-
images_dir,
|
|
143
|
+
images_dir=None,
|
|
134
144
|
confidence_threshold=0.15,
|
|
135
145
|
sample=-1,
|
|
136
146
|
output_image_width=700,
|
|
@@ -145,36 +155,39 @@ def visualize_detector_output(detector_output_path,
|
|
|
145
155
|
parallelize_rendering_with_threads=True,
|
|
146
156
|
box_sort_order=None,
|
|
147
157
|
category_names_to_blur=None):
|
|
148
|
-
|
|
149
158
|
"""
|
|
150
159
|
Draws bounding boxes on images given the output of a detector.
|
|
151
160
|
|
|
152
161
|
Args:
|
|
153
162
|
detector_output_path (str): path to detector output .json file
|
|
154
163
|
out_dir (str): path to directory for saving annotated images
|
|
155
|
-
images_dir (str): folder where the images live; filenames in
|
|
156
|
-
[detector_output_path] should be relative to [image_dir]
|
|
164
|
+
images_dir (str): folder where the images live; filenames in
|
|
165
|
+
[detector_output_path] should be relative to [image_dir]. Can be None if paths are
|
|
166
|
+
absolute.
|
|
157
167
|
confidence_threshold (float, optional): threshold above which detections will be rendered
|
|
158
168
|
sample (int, optional): maximum number of images to render, -1 for all
|
|
159
169
|
output_image_width (int, optional): width in pixels to resize images for display,
|
|
160
170
|
preserving aspect ration; set to -1 to use original image width
|
|
161
171
|
random_seed (int, optional): seed to use for choosing images when sample != -1
|
|
162
|
-
render_detections_only (bool): only render images with above-threshold detections
|
|
172
|
+
render_detections_only (bool): only render images with above-threshold detections. Empty
|
|
173
|
+
images are discarded after sampling, so if you want to see, e.g., 1000 non-empty images,
|
|
174
|
+
you can set [render_detections_only], but you need to sample more than 1000 images.
|
|
163
175
|
classification_confidence_threshold (float, optional): only show classifications
|
|
164
176
|
above this threshold; does not impact whether images are rendered, only whether
|
|
165
177
|
classification labels (not detection categories) are displayed
|
|
166
178
|
html_output_file (str, optional): output path for an HTML index file (not written
|
|
167
179
|
if None)
|
|
168
|
-
html_output_options (dict, optional): HTML formatting options; see write_html_image_list
|
|
169
|
-
for details
|
|
180
|
+
html_output_options (dict, optional): HTML formatting options; see write_html_image_list
|
|
181
|
+
for details. The most common option you may want to supply here is
|
|
182
|
+
'maxFiguresPerHtmlFile'.
|
|
170
183
|
preserve_path_structure (bool, optional): if False (default), writes images to unique
|
|
171
184
|
names in a flat structure in the output folder; if True, preserves relative paths
|
|
172
185
|
within the output folder
|
|
173
186
|
parallelize_rendering (bool, optional): whether to use concurrent workers for rendering
|
|
174
|
-
parallelize_rendering_n_cores (int, optional): number of concurrent workers to use
|
|
187
|
+
parallelize_rendering_n_cores (int, optional): number of concurrent workers to use
|
|
175
188
|
(ignored if parallelize_rendering is False)
|
|
176
189
|
parallelize_rendering_with_threads (bool, optional): determines whether we use
|
|
177
|
-
threads (True) or processes (False) for parallelization (ignored if parallelize_rendering
|
|
190
|
+
threads (True) or processes (False) for parallelization (ignored if parallelize_rendering
|
|
178
191
|
is False)
|
|
179
192
|
box_sort_order (str, optional): sorting scheme for detection boxes, can be None, "confidence", or
|
|
180
193
|
"reverse_confidence"
|
|
@@ -184,30 +197,29 @@ def visualize_detector_output(detector_output_path,
|
|
|
184
197
|
Returns:
|
|
185
198
|
list: list of paths to annotated images
|
|
186
199
|
"""
|
|
187
|
-
|
|
200
|
+
|
|
188
201
|
assert os.path.exists(detector_output_path), \
|
|
189
202
|
'Detector output file does not exist at {}'.format(detector_output_path)
|
|
190
203
|
|
|
191
|
-
|
|
192
|
-
|
|
204
|
+
if images_dir is not None:
|
|
205
|
+
assert os.path.isdir(images_dir), \
|
|
206
|
+
'Image folder {} is not available'.format(images_dir)
|
|
193
207
|
|
|
194
208
|
os.makedirs(out_dir, exist_ok=True)
|
|
195
209
|
|
|
196
210
|
|
|
197
211
|
##%% Load detector output
|
|
198
212
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
assert 'images' in detector_output, (
|
|
202
|
-
'Detector output file should be a json with an "images" field.')
|
|
213
|
+
detector_output = load_md_or_speciesnet_file(detector_output_path)
|
|
214
|
+
|
|
203
215
|
images = detector_output['images']
|
|
204
|
-
|
|
216
|
+
|
|
205
217
|
if confidence_threshold is None:
|
|
206
218
|
confidence_threshold = get_typical_confidence_threshold_from_results(detector_output)
|
|
207
|
-
|
|
208
|
-
assert confidence_threshold >= 0 and confidence_threshold <= 1,
|
|
209
|
-
f'Confidence threshold {confidence_threshold} is invalid, must be in (0, 1).'
|
|
210
|
-
|
|
219
|
+
|
|
220
|
+
assert confidence_threshold >= 0 and confidence_threshold <= 1, \
|
|
221
|
+
f'Confidence threshold {confidence_threshold} is invalid, must be in (0, 1).'
|
|
222
|
+
|
|
211
223
|
if 'detection_categories' in detector_output:
|
|
212
224
|
detector_label_map = detector_output['detection_categories']
|
|
213
225
|
else:
|
|
@@ -231,81 +243,91 @@ def visualize_detector_output(detector_output_path,
|
|
|
231
243
|
|
|
232
244
|
print('Rendering detections above a confidence threshold of {}'.format(
|
|
233
245
|
confidence_threshold))
|
|
234
|
-
|
|
246
|
+
|
|
235
247
|
classification_label_map = None
|
|
236
|
-
|
|
248
|
+
|
|
237
249
|
if 'classification_categories' in detector_output:
|
|
238
250
|
classification_label_map = detector_output['classification_categories']
|
|
239
|
-
|
|
251
|
+
|
|
240
252
|
rendering_results = []
|
|
241
|
-
|
|
253
|
+
|
|
242
254
|
if parallelize_rendering:
|
|
243
|
-
|
|
255
|
+
|
|
244
256
|
if parallelize_rendering_with_threads:
|
|
245
257
|
worker_string = 'threads'
|
|
246
258
|
else:
|
|
247
259
|
worker_string = 'processes'
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
pool = ThreadPool(parallelize_rendering_n_cores)
|
|
260
|
+
|
|
261
|
+
pool = None
|
|
262
|
+
try:
|
|
263
|
+
if parallelize_rendering_n_cores is None:
|
|
264
|
+
if parallelize_rendering_with_threads:
|
|
265
|
+
pool = ThreadPool()
|
|
266
|
+
else:
|
|
267
|
+
pool = Pool()
|
|
257
268
|
else:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
269
|
+
if parallelize_rendering_with_threads:
|
|
270
|
+
pool = ThreadPool(parallelize_rendering_n_cores)
|
|
271
|
+
else:
|
|
272
|
+
pool = Pool(parallelize_rendering_n_cores)
|
|
273
|
+
print('Rendering images with {} {}'.format(parallelize_rendering_n_cores,
|
|
274
|
+
worker_string))
|
|
275
|
+
rendering_results = list(tqdm(pool.imap(
|
|
276
|
+
partial(_render_image,detector_label_map=detector_label_map,
|
|
277
|
+
classification_label_map=classification_label_map,
|
|
278
|
+
confidence_threshold=confidence_threshold,
|
|
279
|
+
classification_confidence_threshold=classification_confidence_threshold,
|
|
280
|
+
render_detections_only=render_detections_only,
|
|
281
|
+
preserve_path_structure=preserve_path_structure,
|
|
282
|
+
out_dir=out_dir,
|
|
283
|
+
images_dir=images_dir,
|
|
284
|
+
output_image_width=output_image_width,
|
|
285
|
+
box_sort_order=box_sort_order,
|
|
286
|
+
category_names_to_blur=category_names_to_blur),
|
|
287
|
+
images), total=len(images)))
|
|
288
|
+
finally:
|
|
289
|
+
if pool is not None:
|
|
290
|
+
pool.close()
|
|
291
|
+
pool.join()
|
|
292
|
+
print("Pool closed and joined for detector output visualization")
|
|
293
|
+
|
|
275
294
|
else:
|
|
276
|
-
|
|
295
|
+
|
|
277
296
|
for entry in tqdm(images):
|
|
278
|
-
|
|
297
|
+
|
|
279
298
|
rendering_result = _render_image(entry,detector_label_map,classification_label_map,
|
|
280
299
|
confidence_threshold,classification_confidence_threshold,
|
|
281
300
|
render_detections_only,preserve_path_structure,out_dir,
|
|
282
301
|
images_dir,output_image_width,box_sort_order,
|
|
283
302
|
category_names_to_blur=category_names_to_blur)
|
|
284
303
|
rendering_results.append(rendering_result)
|
|
285
|
-
|
|
304
|
+
|
|
286
305
|
# ...for each image
|
|
287
|
-
|
|
306
|
+
|
|
288
307
|
failed_images = [r for r in rendering_results if r['failed_image']]
|
|
289
308
|
missing_images = [r for r in rendering_results if r['missing_image']]
|
|
290
309
|
skipped_images = [r for r in rendering_results if r['skipped_image']]
|
|
291
|
-
|
|
310
|
+
|
|
292
311
|
print('Skipped {} failed images (of {})'.format(len(failed_images),len(images)))
|
|
293
312
|
print('Skipped {} missing images (of {})'.format(len(missing_images),len(images)))
|
|
294
313
|
print('Skipped {} below-threshold images (of {})'.format(len(skipped_images),len(images)))
|
|
295
|
-
|
|
314
|
+
|
|
296
315
|
print(f'Rendered detection results to {out_dir}')
|
|
297
316
|
|
|
298
317
|
annotated_image_paths = [r['annotated_image_path'] for r in rendering_results if \
|
|
299
318
|
r['annotated_image_path'] is not None]
|
|
300
|
-
|
|
319
|
+
|
|
301
320
|
if html_output_file is not None:
|
|
302
|
-
|
|
321
|
+
|
|
303
322
|
html_dir = os.path.dirname(html_output_file)
|
|
304
|
-
|
|
323
|
+
|
|
305
324
|
html_image_info = []
|
|
306
|
-
|
|
325
|
+
|
|
307
326
|
for r in rendering_results:
|
|
308
327
|
d = {}
|
|
328
|
+
if r['annotated_image_path'] is None:
|
|
329
|
+
assert r['failed_image'] or r['missing_image'] or r['skipped_image']
|
|
330
|
+
continue
|
|
309
331
|
annotated_image_path_relative = os.path.relpath(r['annotated_image_path'],html_dir)
|
|
310
332
|
d['filename'] = annotated_image_path_relative
|
|
311
333
|
d['textStyle'] = \
|
|
@@ -313,17 +335,19 @@ def visualize_detector_output(detector_output_path,
|
|
|
313
335
|
'text-align:left;margin-top:20;margin-bottom:5'
|
|
314
336
|
d['title'] = '{} (max conf: {})'.format(r['file'],r['max_conf'])
|
|
315
337
|
html_image_info.append(d)
|
|
316
|
-
|
|
338
|
+
|
|
317
339
|
_ = write_html_image_list.write_html_image_list(html_output_file,html_image_info,
|
|
318
340
|
options=html_output_options)
|
|
319
|
-
|
|
341
|
+
|
|
320
342
|
return annotated_image_paths
|
|
321
343
|
|
|
344
|
+
# ...def visualize_detector_output(...)
|
|
345
|
+
|
|
322
346
|
|
|
323
347
|
#%% Command-line driver
|
|
324
348
|
|
|
325
|
-
def main():
|
|
326
|
-
|
|
349
|
+
def main(): # noqa
|
|
350
|
+
|
|
327
351
|
parser = argparse.ArgumentParser(
|
|
328
352
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
329
353
|
description='Annotate the bounding boxes predicted by a detector above '
|
|
@@ -336,53 +360,56 @@ def main():
|
|
|
336
360
|
help='Path to directory where the annotated images will be saved. '
|
|
337
361
|
'The directory will be created if it does not exist.')
|
|
338
362
|
parser.add_argument(
|
|
339
|
-
'
|
|
363
|
+
'--confidence', type=float, default=0.15,
|
|
340
364
|
help='Value between 0 and 1, indicating the confidence threshold '
|
|
341
365
|
'above which to visualize bounding boxes')
|
|
342
366
|
parser.add_argument(
|
|
343
|
-
'
|
|
367
|
+
'--images_dir', type=str, default=None,
|
|
344
368
|
help='Path to a local directory where images are stored. This '
|
|
345
369
|
'serves as the root directory for image paths in '
|
|
346
|
-
'detector_output_path.')
|
|
370
|
+
'detector_output_path. Omit if image paths are absolute.')
|
|
347
371
|
parser.add_argument(
|
|
348
|
-
'
|
|
372
|
+
'--sample', type=int, default=-1,
|
|
349
373
|
help='Number of images to be annotated and rendered. Set to -1 '
|
|
350
374
|
'(default) to annotate all images in the detector output file. '
|
|
351
375
|
'There may be fewer images if some are not found in images_dir.')
|
|
352
376
|
parser.add_argument(
|
|
353
|
-
'
|
|
377
|
+
'--output_image_width', type=int, default=700,
|
|
354
378
|
help='Integer, desired width in pixels of the output annotated images. '
|
|
355
379
|
'Use -1 to not resize. Default: 700.')
|
|
356
380
|
parser.add_argument(
|
|
357
|
-
'
|
|
381
|
+
'--random_seed', type=int, default=None,
|
|
358
382
|
help='Integer, for deterministic order of image sampling')
|
|
359
383
|
parser.add_argument(
|
|
360
|
-
'
|
|
384
|
+
'--html_output_file', type=str, default=None,
|
|
361
385
|
help='Filename to which we should write an HTML image index (off by default)')
|
|
362
386
|
parser.add_argument(
|
|
363
387
|
'--open_html_output_file', action='store_true',
|
|
364
388
|
help='Open the .html output file when done')
|
|
365
389
|
parser.add_argument(
|
|
366
|
-
'
|
|
390
|
+
'--detections_only', action='store_true',
|
|
367
391
|
help='Only render images with above-threshold detections (by default, '
|
|
368
392
|
'both empty and non-empty images are rendered).')
|
|
369
393
|
parser.add_argument(
|
|
370
|
-
'
|
|
394
|
+
'--preserve_path_structure', action='store_true',
|
|
371
395
|
help='Preserve relative image paths (otherwise flattens and assigns unique file names)')
|
|
372
396
|
parser.add_argument(
|
|
373
397
|
'--category_names_to_blur', default=None, type=str,
|
|
374
398
|
help='Comma-separated list of category names to blur (or a single category name, typically "person")')
|
|
399
|
+
parser.add_argument(
|
|
400
|
+
'--classification_confidence', type=float, default=0.1,
|
|
401
|
+
help='If classification results are present, render results above this threshold')
|
|
375
402
|
|
|
376
403
|
if len(sys.argv[1:]) == 0:
|
|
377
404
|
parser.print_help()
|
|
378
405
|
parser.exit()
|
|
379
406
|
|
|
380
407
|
args = parser.parse_args()
|
|
381
|
-
|
|
408
|
+
|
|
382
409
|
category_names_to_blur = args.category_names_to_blur
|
|
383
410
|
if category_names_to_blur is not None:
|
|
384
411
|
category_names_to_blur = category_names_to_blur.split(',')
|
|
385
|
-
|
|
412
|
+
|
|
386
413
|
visualize_detector_output(
|
|
387
414
|
detector_output_path=args.detector_output_path,
|
|
388
415
|
out_dir=args.out_dir,
|
|
@@ -392,6 +419,7 @@ def main():
|
|
|
392
419
|
output_image_width=args.output_image_width,
|
|
393
420
|
random_seed=args.random_seed,
|
|
394
421
|
render_detections_only=args.detections_only,
|
|
422
|
+
classification_confidence_threshold=args.classification_confidence,
|
|
395
423
|
preserve_path_structure=args.preserve_path_structure,
|
|
396
424
|
html_output_file=args.html_output_file,
|
|
397
425
|
category_names_to_blur=category_names_to_blur)
|
|
@@ -407,12 +435,12 @@ if __name__ == '__main__':
|
|
|
407
435
|
#%% Interactive driver
|
|
408
436
|
|
|
409
437
|
if False:
|
|
410
|
-
|
|
438
|
+
|
|
411
439
|
pass
|
|
412
440
|
|
|
413
441
|
#%%
|
|
414
|
-
|
|
415
|
-
detector_output_path = os.path.expanduser('
|
|
442
|
+
|
|
443
|
+
detector_output_path = os.path.expanduser('detections.json')
|
|
416
444
|
out_dir = r'g:\temp\preview'
|
|
417
445
|
images_dir = r'g:\camera_traps\camera_trap_images'
|
|
418
446
|
confidence_threshold = 0.15
|
|
@@ -443,6 +471,6 @@ if False:
|
|
|
443
471
|
parallelize_rendering,
|
|
444
472
|
parallelize_rendering_n_cores,
|
|
445
473
|
parallelize_rendering_with_threads)
|
|
446
|
-
|
|
474
|
+
|
|
447
475
|
from megadetector.utils.path_utils import open_file
|
|
448
476
|
open_file(html_output_file)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: megadetector
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.29
|
|
4
4
|
Summary: MegaDetector is an AI model that helps conservation folks spend less time doing boring things with camera trap images.
|
|
5
5
|
Author-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
|
|
6
6
|
Maintainer-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
|
|
@@ -34,7 +34,7 @@ Requires-Python: <3.14,>=3.9
|
|
|
34
34
|
Description-Content-Type: text/markdown
|
|
35
35
|
License-File: LICENSE
|
|
36
36
|
Requires-Dist: mkl==2024.0; sys_platform != "darwin"
|
|
37
|
-
Requires-Dist: numpy
|
|
37
|
+
Requires-Dist: numpy>=1.26.4
|
|
38
38
|
Requires-Dist: Pillow>=9.5
|
|
39
39
|
Requires-Dist: tqdm>=4.64.0
|
|
40
40
|
Requires-Dist: jsonpickle>=3.0.2
|
|
@@ -47,10 +47,13 @@ Requires-Dist: scikit-learn>=1.3.1
|
|
|
47
47
|
Requires-Dist: pandas>=2.1.1
|
|
48
48
|
Requires-Dist: python-dateutil
|
|
49
49
|
Requires-Dist: send2trash
|
|
50
|
+
Requires-Dist: python-dateutil
|
|
51
|
+
Requires-Dist: clipboard
|
|
50
52
|
Requires-Dist: dill
|
|
53
|
+
Requires-Dist: ruff
|
|
54
|
+
Requires-Dist: pytest
|
|
51
55
|
Requires-Dist: ultralytics-yolov5==0.1.1
|
|
52
56
|
Requires-Dist: yolov9pip==0.0.4
|
|
53
|
-
Requires-Dist: python-dateutil
|
|
54
57
|
Dynamic: license-file
|
|
55
58
|
|
|
56
59
|
# MegaDetector
|