megadetector 5.0.5__py3-none-any.whl → 5.0.7__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.
- api/batch_processing/data_preparation/manage_local_batch.py +302 -263
- api/batch_processing/data_preparation/manage_video_batch.py +81 -2
- api/batch_processing/postprocessing/add_max_conf.py +1 -0
- api/batch_processing/postprocessing/categorize_detections_by_size.py +50 -19
- api/batch_processing/postprocessing/compare_batch_results.py +110 -60
- api/batch_processing/postprocessing/load_api_results.py +56 -70
- api/batch_processing/postprocessing/md_to_coco.py +1 -1
- api/batch_processing/postprocessing/md_to_labelme.py +2 -1
- api/batch_processing/postprocessing/postprocess_batch_results.py +240 -81
- api/batch_processing/postprocessing/render_detection_confusion_matrix.py +625 -0
- api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +71 -23
- api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +1 -1
- api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +227 -75
- api/batch_processing/postprocessing/subset_json_detector_output.py +132 -5
- api/batch_processing/postprocessing/top_folders_to_bottom.py +1 -1
- api/synchronous/api_core/animal_detection_api/detection/run_detector_batch.py +2 -2
- classification/prepare_classification_script.py +191 -191
- data_management/coco_to_yolo.py +68 -45
- data_management/databases/integrity_check_json_db.py +7 -5
- data_management/generate_crops_from_cct.py +3 -3
- data_management/get_image_sizes.py +8 -6
- data_management/importers/add_timestamps_to_icct.py +79 -0
- data_management/importers/animl_results_to_md_results.py +160 -0
- data_management/importers/auckland_doc_test_to_json.py +4 -4
- data_management/importers/auckland_doc_to_json.py +1 -1
- data_management/importers/awc_to_json.py +5 -5
- data_management/importers/bellevue_to_json.py +5 -5
- data_management/importers/carrizo_shrubfree_2018.py +5 -5
- data_management/importers/carrizo_trail_cam_2017.py +5 -5
- data_management/importers/cct_field_adjustments.py +2 -3
- data_management/importers/channel_islands_to_cct.py +4 -4
- data_management/importers/ena24_to_json.py +5 -5
- data_management/importers/helena_to_cct.py +10 -10
- data_management/importers/idaho-camera-traps.py +12 -12
- data_management/importers/idfg_iwildcam_lila_prep.py +8 -8
- data_management/importers/jb_csv_to_json.py +4 -4
- data_management/importers/missouri_to_json.py +1 -1
- data_management/importers/noaa_seals_2019.py +1 -1
- data_management/importers/pc_to_json.py +5 -5
- data_management/importers/prepare-noaa-fish-data-for-lila.py +4 -4
- data_management/importers/prepare_zsl_imerit.py +5 -5
- data_management/importers/rspb_to_json.py +4 -4
- data_management/importers/save_the_elephants_survey_A.py +5 -5
- data_management/importers/save_the_elephants_survey_B.py +6 -6
- data_management/importers/snapshot_safari_importer.py +9 -9
- data_management/importers/snapshot_serengeti_lila.py +9 -9
- data_management/importers/timelapse_csv_set_to_json.py +5 -7
- data_management/importers/ubc_to_json.py +4 -4
- data_management/importers/umn_to_json.py +4 -4
- data_management/importers/wellington_to_json.py +1 -1
- data_management/importers/wi_to_json.py +2 -2
- data_management/importers/zamba_results_to_md_results.py +181 -0
- data_management/labelme_to_coco.py +35 -7
- data_management/labelme_to_yolo.py +229 -0
- data_management/lila/add_locations_to_island_camera_traps.py +1 -1
- data_management/lila/add_locations_to_nacti.py +147 -0
- data_management/lila/create_lila_blank_set.py +474 -0
- data_management/lila/create_lila_test_set.py +2 -1
- data_management/lila/create_links_to_md_results_files.py +106 -0
- data_management/lila/download_lila_subset.py +46 -21
- data_management/lila/generate_lila_per_image_labels.py +23 -14
- data_management/lila/get_lila_annotation_counts.py +17 -11
- data_management/lila/lila_common.py +14 -11
- data_management/lila/test_lila_metadata_urls.py +116 -0
- data_management/ocr_tools.py +829 -0
- data_management/resize_coco_dataset.py +13 -11
- data_management/yolo_output_to_md_output.py +84 -12
- data_management/yolo_to_coco.py +38 -20
- detection/process_video.py +36 -14
- detection/pytorch_detector.py +23 -8
- detection/run_detector.py +76 -19
- detection/run_detector_batch.py +178 -63
- detection/run_inference_with_yolov5_val.py +326 -57
- detection/run_tiled_inference.py +153 -43
- detection/video_utils.py +34 -8
- md_utils/ct_utils.py +172 -1
- md_utils/md_tests.py +372 -51
- md_utils/path_utils.py +167 -39
- md_utils/process_utils.py +26 -7
- md_utils/split_locations_into_train_val.py +215 -0
- md_utils/string_utils.py +10 -0
- md_utils/url_utils.py +0 -2
- md_utils/write_html_image_list.py +9 -26
- md_visualization/plot_utils.py +12 -8
- md_visualization/visualization_utils.py +106 -7
- md_visualization/visualize_db.py +16 -8
- md_visualization/visualize_detector_output.py +208 -97
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/METADATA +3 -6
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/RECORD +98 -121
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/WHEEL +1 -1
- taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +1 -1
- taxonomy_mapping/map_new_lila_datasets.py +43 -39
- taxonomy_mapping/prepare_lila_taxonomy_release.py +5 -2
- taxonomy_mapping/preview_lila_taxonomy.py +27 -27
- taxonomy_mapping/species_lookup.py +33 -13
- taxonomy_mapping/taxonomy_csv_checker.py +7 -5
- api/synchronous/api_core/yolov5/detect.py +0 -252
- api/synchronous/api_core/yolov5/export.py +0 -607
- api/synchronous/api_core/yolov5/hubconf.py +0 -146
- api/synchronous/api_core/yolov5/models/__init__.py +0 -0
- api/synchronous/api_core/yolov5/models/common.py +0 -738
- api/synchronous/api_core/yolov5/models/experimental.py +0 -104
- api/synchronous/api_core/yolov5/models/tf.py +0 -574
- api/synchronous/api_core/yolov5/models/yolo.py +0 -338
- api/synchronous/api_core/yolov5/train.py +0 -670
- api/synchronous/api_core/yolov5/utils/__init__.py +0 -36
- api/synchronous/api_core/yolov5/utils/activations.py +0 -103
- api/synchronous/api_core/yolov5/utils/augmentations.py +0 -284
- api/synchronous/api_core/yolov5/utils/autoanchor.py +0 -170
- api/synchronous/api_core/yolov5/utils/autobatch.py +0 -66
- api/synchronous/api_core/yolov5/utils/aws/__init__.py +0 -0
- api/synchronous/api_core/yolov5/utils/aws/resume.py +0 -40
- api/synchronous/api_core/yolov5/utils/benchmarks.py +0 -148
- api/synchronous/api_core/yolov5/utils/callbacks.py +0 -71
- api/synchronous/api_core/yolov5/utils/dataloaders.py +0 -1087
- api/synchronous/api_core/yolov5/utils/downloads.py +0 -178
- api/synchronous/api_core/yolov5/utils/flask_rest_api/example_request.py +0 -19
- api/synchronous/api_core/yolov5/utils/flask_rest_api/restapi.py +0 -46
- api/synchronous/api_core/yolov5/utils/general.py +0 -1018
- api/synchronous/api_core/yolov5/utils/loggers/__init__.py +0 -187
- api/synchronous/api_core/yolov5/utils/loggers/wandb/__init__.py +0 -0
- api/synchronous/api_core/yolov5/utils/loggers/wandb/log_dataset.py +0 -27
- api/synchronous/api_core/yolov5/utils/loggers/wandb/sweep.py +0 -41
- api/synchronous/api_core/yolov5/utils/loggers/wandb/wandb_utils.py +0 -577
- api/synchronous/api_core/yolov5/utils/loss.py +0 -234
- api/synchronous/api_core/yolov5/utils/metrics.py +0 -355
- api/synchronous/api_core/yolov5/utils/plots.py +0 -489
- api/synchronous/api_core/yolov5/utils/torch_utils.py +0 -314
- api/synchronous/api_core/yolov5/val.py +0 -394
- md_utils/matlab_porting_tools.py +0 -97
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/LICENSE +0 -0
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/top_level.txt +0 -0
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
# visualize_detector_output.py
|
|
4
4
|
#
|
|
5
5
|
# Render images with bounding boxes annotated on them to a folder, based on a
|
|
6
|
-
# detector output result file (json)
|
|
7
|
-
# Azure Blob Storage.
|
|
6
|
+
# detector output result file (json), optionally writing an HTML index file.
|
|
8
7
|
#
|
|
9
8
|
########
|
|
10
9
|
|
|
@@ -15,51 +14,115 @@ import json
|
|
|
15
14
|
import os
|
|
16
15
|
import random
|
|
17
16
|
import sys
|
|
18
|
-
from
|
|
17
|
+
from multiprocessing.pool import ThreadPool
|
|
18
|
+
from multiprocessing.pool import Pool
|
|
19
|
+
from typing import List
|
|
20
|
+
from functools import partial
|
|
19
21
|
|
|
20
22
|
from tqdm import tqdm
|
|
21
23
|
|
|
22
|
-
from data_management.annotations.annotation_constants import
|
|
23
|
-
detector_bbox_category_id_to_name) # here id is int
|
|
24
|
+
from data_management.annotations.annotation_constants import detector_bbox_category_id_to_name
|
|
24
25
|
from md_visualization import visualization_utils as vis_utils
|
|
25
26
|
from md_utils.ct_utils import get_max_conf
|
|
27
|
+
from md_utils import write_html_image_list
|
|
28
|
+
from detection.run_detector import get_typical_confidence_threshold_from_results
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
#%% Constants
|
|
29
32
|
|
|
30
|
-
#
|
|
33
|
+
# This will only be used if a category mapping is not available in the results file.
|
|
31
34
|
DEFAULT_DETECTOR_LABEL_MAP = {
|
|
32
35
|
str(k): v for k, v in detector_bbox_category_id_to_name.items()
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
|
|
39
|
+
#%% Support functions
|
|
40
|
+
|
|
41
|
+
def render_image(entry,
|
|
42
|
+
detector_label_map,classification_label_map,
|
|
43
|
+
confidence_threshold,classification_confidence_threshold,
|
|
44
|
+
render_detections_only,preserve_path_structure,out_dir,images_dir,
|
|
45
|
+
output_image_width):
|
|
46
|
+
|
|
47
|
+
rendering_result = {'failed_image':False,'missing_image':False,
|
|
48
|
+
'skipped_image':False,'annotated_image_path':None,
|
|
49
|
+
'max_conf':None,'file':entry['file']}
|
|
50
|
+
|
|
51
|
+
image_id = entry['file']
|
|
52
|
+
|
|
53
|
+
if 'failure' in entry and entry['failure'] is not None:
|
|
54
|
+
rendering_result['failed_image'] = True
|
|
55
|
+
return rendering_result
|
|
56
|
+
|
|
57
|
+
assert 'detections' in entry and entry['detections'] is not None
|
|
58
|
+
|
|
59
|
+
max_conf = get_max_conf(entry)
|
|
60
|
+
rendering_result['max_conf'] = max_conf
|
|
61
|
+
|
|
62
|
+
if (max_conf < confidence_threshold) and render_detections_only:
|
|
63
|
+
rendering_result['skipped_image'] = True
|
|
64
|
+
return rendering_result
|
|
65
|
+
|
|
66
|
+
image_obj = os.path.join(images_dir, image_id)
|
|
67
|
+
if not os.path.exists(image_obj):
|
|
68
|
+
print(f'Image {image_id} not found in images_dir')
|
|
69
|
+
rendering_result['missing_image'] = True
|
|
70
|
+
return rendering_result
|
|
71
|
+
|
|
72
|
+
# If output_image_width is -1 or None, this will just return the original image
|
|
73
|
+
image = vis_utils.resize_image(
|
|
74
|
+
vis_utils.open_image(image_obj), output_image_width)
|
|
75
|
+
|
|
76
|
+
vis_utils.render_detection_bounding_boxes(
|
|
77
|
+
entry['detections'], image,
|
|
78
|
+
label_map=detector_label_map,
|
|
79
|
+
classification_label_map=classification_label_map,
|
|
80
|
+
confidence_threshold=confidence_threshold,
|
|
81
|
+
classification_confidence_threshold=classification_confidence_threshold)
|
|
82
|
+
|
|
83
|
+
if not preserve_path_structure:
|
|
84
|
+
for char in ['/', '\\', ':']:
|
|
85
|
+
image_id = image_id.replace(char, '~')
|
|
86
|
+
annotated_img_path = os.path.join(out_dir, f'anno_{image_id}')
|
|
87
|
+
else:
|
|
88
|
+
assert not os.path.isabs(image_id), "Can't preserve paths when operating on absolute paths"
|
|
89
|
+
annotated_img_path = os.path.join(out_dir, image_id)
|
|
90
|
+
os.makedirs(os.path.dirname(annotated_img_path),exist_ok=True)
|
|
91
|
+
|
|
92
|
+
image.save(annotated_img_path)
|
|
93
|
+
rendering_result['annotated_image_path'] = annotated_img_path
|
|
94
|
+
|
|
95
|
+
return rendering_result
|
|
96
|
+
|
|
97
|
+
|
|
36
98
|
#%% Main function
|
|
37
99
|
|
|
38
100
|
def visualize_detector_output(detector_output_path: str,
|
|
39
101
|
out_dir: str,
|
|
40
102
|
images_dir: str,
|
|
41
|
-
is_azure: bool = False,
|
|
42
103
|
confidence_threshold: float = 0.15,
|
|
43
104
|
sample: int = -1,
|
|
44
105
|
output_image_width: int = 700,
|
|
45
|
-
random_seed:
|
|
106
|
+
random_seed: int = None,
|
|
46
107
|
render_detections_only: bool = False,
|
|
47
108
|
classification_confidence_threshold = 0.1,
|
|
48
|
-
html_output_file=None,
|
|
49
|
-
html_output_options=None,
|
|
50
|
-
preserve_path_structure=False
|
|
109
|
+
html_output_file = None,
|
|
110
|
+
html_output_options = None,
|
|
111
|
+
preserve_path_structure = False,
|
|
112
|
+
parallelize_rendering = False,
|
|
113
|
+
parallelize_rendering_n_cores = 10,
|
|
114
|
+
parallelize_rendering_with_threads = True) -> List[str]:
|
|
51
115
|
|
|
52
116
|
"""
|
|
53
|
-
Draw bounding boxes on images given the output of
|
|
117
|
+
Draw bounding boxes on images given the output of a detector.
|
|
54
118
|
|
|
55
119
|
Args:
|
|
56
120
|
detector_output_path: str, path to detector output json file
|
|
57
121
|
out_dir: str, path to directory for saving annotated images
|
|
58
|
-
images_dir: str, path to
|
|
59
|
-
Storage container
|
|
60
|
-
is_azure: bool, whether images_dir points to an Azure URL
|
|
122
|
+
images_dir: str, path to images dir
|
|
61
123
|
confidence: float, threshold above which annotations will be rendered
|
|
62
124
|
sample: int, maximum number of images to annotate, -1 for all
|
|
125
|
+
random_seed: seed for sampling (not relevant if sample == -1)
|
|
63
126
|
output_image_width: int, width in pixels to resize images for display,
|
|
64
127
|
set to -1 to use original image width
|
|
65
128
|
random_seed: int, for deterministic image sampling when sample != -1
|
|
@@ -68,16 +131,11 @@ def visualize_detector_output(detector_output_path: str,
|
|
|
68
131
|
Returns: list of str, paths to annotated images
|
|
69
132
|
"""
|
|
70
133
|
|
|
71
|
-
assert
|
|
72
|
-
|
|
134
|
+
assert os.path.exists(detector_output_path), \
|
|
135
|
+
'Detector output file does not exist at {}'.format(detector_output_path)
|
|
73
136
|
|
|
74
|
-
assert os.path.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if is_azure:
|
|
78
|
-
from md_utils import sas_blob_utils
|
|
79
|
-
else:
|
|
80
|
-
assert os.path.isdir(images_dir)
|
|
137
|
+
assert os.path.isdir(images_dir), \
|
|
138
|
+
'Image folder {} is not available'.format(images_dir)
|
|
81
139
|
|
|
82
140
|
os.makedirs(out_dir, exist_ok=True)
|
|
83
141
|
|
|
@@ -89,12 +147,18 @@ def visualize_detector_output(detector_output_path: str,
|
|
|
89
147
|
assert 'images' in detector_output, (
|
|
90
148
|
'Detector output file should be a json with an "images" field.')
|
|
91
149
|
images = detector_output['images']
|
|
92
|
-
|
|
150
|
+
|
|
151
|
+
if confidence_threshold is None:
|
|
152
|
+
confidence_threshold = get_typical_confidence_threshold_from_results(detector_output)
|
|
153
|
+
|
|
154
|
+
assert confidence_threshold >= 0 and confidence_threshold <= 1, (
|
|
155
|
+
f'Confidence threshold {confidence_threshold} is invalid, must be in (0, 1).')
|
|
156
|
+
|
|
93
157
|
if 'detection_categories' in detector_output:
|
|
94
158
|
print('Using custom label mapping')
|
|
95
159
|
detector_label_map = detector_output['detection_categories']
|
|
96
160
|
else:
|
|
97
|
-
detector_label_map = DEFAULT_DETECTOR_LABEL_MAP
|
|
161
|
+
detector_label_map = DEFAULT_DETECTOR_LABEL_MAP
|
|
98
162
|
|
|
99
163
|
num_images = len(images)
|
|
100
164
|
print(f'Detector output file contains {num_images} entries.')
|
|
@@ -118,85 +182,89 @@ def visualize_detector_output(detector_output_path: str,
|
|
|
118
182
|
print('Rendering detections above a confidence threshold of {}'.format(
|
|
119
183
|
confidence_threshold))
|
|
120
184
|
|
|
121
|
-
num_saved = 0
|
|
122
|
-
annotated_img_paths = []
|
|
123
|
-
failed_images = []
|
|
124
|
-
missing_images = []
|
|
125
|
-
|
|
126
185
|
classification_label_map = None
|
|
127
186
|
|
|
128
187
|
if 'classification_categories' in detector_output:
|
|
129
188
|
classification_label_map = detector_output['classification_categories']
|
|
130
189
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if 'failure' in entry and entry['failure'] is not None:
|
|
136
|
-
failed_images.append(image_id)
|
|
137
|
-
continue
|
|
138
|
-
|
|
139
|
-
assert 'detections' in entry and entry['detections'] is not None
|
|
140
|
-
|
|
141
|
-
max_conf = get_max_conf(entry)
|
|
142
|
-
if (max_conf < confidence_threshold) and render_detections_only:
|
|
143
|
-
continue
|
|
190
|
+
rendering_results = []
|
|
191
|
+
|
|
192
|
+
if parallelize_rendering:
|
|
144
193
|
|
|
145
|
-
if
|
|
146
|
-
|
|
147
|
-
container_uri=images_dir, blob_name=image_id)
|
|
148
|
-
if not sas_blob_utils.check_blob_exists(blob_uri):
|
|
149
|
-
container = sas_blob_utils.get_container_from_uri(images_dir)
|
|
150
|
-
print(f'Image {image_id} not found in blob container '
|
|
151
|
-
f'{container}; skipped.')
|
|
152
|
-
continue
|
|
153
|
-
# BytesIO object
|
|
154
|
-
image_obj, _ = sas_blob_utils.download_blob_to_stream(blob_uri)
|
|
194
|
+
if parallelize_rendering_with_threads:
|
|
195
|
+
worker_string = 'threads'
|
|
155
196
|
else:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
vis_utils.open_image(image_obj), output_image_width)
|
|
164
|
-
|
|
165
|
-
vis_utils.render_detection_bounding_boxes(
|
|
166
|
-
entry['detections'], image, label_map=detector_label_map,
|
|
167
|
-
classification_label_map = classification_label_map,
|
|
168
|
-
confidence_threshold=confidence_threshold,
|
|
169
|
-
classification_confidence_threshold=classification_confidence_threshold)
|
|
170
|
-
|
|
171
|
-
if not preserve_path_structure:
|
|
172
|
-
for char in ['/', '\\', ':']:
|
|
173
|
-
image_id = image_id.replace(char, '~')
|
|
174
|
-
annotated_img_path = os.path.join(out_dir, f'anno_{image_id}')
|
|
197
|
+
worker_string = 'processes'
|
|
198
|
+
|
|
199
|
+
if parallelize_rendering_n_cores is None:
|
|
200
|
+
if parallelize_rendering_with_threads:
|
|
201
|
+
pool = ThreadPool()
|
|
202
|
+
else:
|
|
203
|
+
pool = Pool()
|
|
175
204
|
else:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
205
|
+
if parallelize_rendering_with_threads:
|
|
206
|
+
pool = ThreadPool(parallelize_rendering_n_cores)
|
|
207
|
+
else:
|
|
208
|
+
pool = Pool(parallelize_rendering_n_cores)
|
|
209
|
+
print('Rendering images with {} {}'.format(parallelize_rendering_n_cores,
|
|
210
|
+
worker_string))
|
|
211
|
+
rendering_results = list(tqdm(pool.imap(
|
|
212
|
+
partial(render_image,detector_label_map=detector_label_map,
|
|
213
|
+
classification_label_map=classification_label_map,
|
|
214
|
+
confidence_threshold=confidence_threshold,
|
|
215
|
+
classification_confidence_threshold=classification_confidence_threshold,
|
|
216
|
+
render_detections_only=render_detections_only,
|
|
217
|
+
preserve_path_structure=preserve_path_structure,
|
|
218
|
+
out_dir=out_dir,
|
|
219
|
+
images_dir=images_dir,
|
|
220
|
+
output_image_width=output_image_width),
|
|
221
|
+
images), total=len(images)))
|
|
222
|
+
|
|
223
|
+
else:
|
|
224
|
+
|
|
225
|
+
for entry in tqdm(images):
|
|
226
|
+
|
|
227
|
+
rendering_result = render_image(entry,detector_label_map,classification_label_map,
|
|
228
|
+
confidence_threshold,classification_confidence_threshold,
|
|
229
|
+
render_detections_only,preserve_path_structure,out_dir,
|
|
230
|
+
images_dir,output_image_width)
|
|
231
|
+
rendering_results.append(rendering_result)
|
|
232
|
+
|
|
186
233
|
# ...for each image
|
|
187
234
|
|
|
235
|
+
failed_images = [r for r in rendering_results if r['failed_image']]
|
|
236
|
+
missing_images = [r for r in rendering_results if r['missing_image']]
|
|
237
|
+
skipped_images = [r for r in rendering_results if r['skipped_image']]
|
|
238
|
+
|
|
188
239
|
print('Skipped {} failed images (of {})'.format(len(failed_images),len(images)))
|
|
189
240
|
print('Skipped {} missing images (of {})'.format(len(missing_images),len(images)))
|
|
241
|
+
print('Skipped {} below-threshold images (of {})'.format(len(skipped_images),len(images)))
|
|
190
242
|
|
|
191
|
-
print(f'Rendered detection results
|
|
192
|
-
f'saved to {out_dir}')
|
|
243
|
+
print(f'Rendered detection results to {out_dir}')
|
|
193
244
|
|
|
245
|
+
annotated_image_paths = [r['annotated_image_path'] for r in rendering_results if \
|
|
246
|
+
r['annotated_image_path'] is not None]
|
|
247
|
+
|
|
194
248
|
if html_output_file is not None:
|
|
195
|
-
|
|
196
|
-
|
|
249
|
+
|
|
250
|
+
html_dir = os.path.dirname(html_output_file)
|
|
251
|
+
|
|
252
|
+
html_image_info = []
|
|
253
|
+
|
|
254
|
+
for r in rendering_results:
|
|
255
|
+
d = {}
|
|
256
|
+
annotated_image_path_relative = os.path.relpath(r['annotated_image_path'],html_dir)
|
|
257
|
+
d['filename'] = annotated_image_path_relative
|
|
258
|
+
d['textStyle'] = \
|
|
259
|
+
'font-family:verdana,arial,calibri;font-size:80%;' + \
|
|
260
|
+
'text-align:left;margin-top:20;margin-bottom:5'
|
|
261
|
+
d['title'] = '{} (max conf: {})'.format(r['file'],r['max_conf'])
|
|
262
|
+
html_image_info.append(d)
|
|
263
|
+
|
|
264
|
+
_ = write_html_image_list.write_html_image_list(html_output_file,html_image_info,
|
|
197
265
|
options=html_output_options)
|
|
198
266
|
|
|
199
|
-
return
|
|
267
|
+
return annotated_image_paths
|
|
200
268
|
|
|
201
269
|
|
|
202
270
|
#%% Command-line driver
|
|
@@ -221,15 +289,9 @@ def main() -> None:
|
|
|
221
289
|
'above which to visualize bounding boxes')
|
|
222
290
|
parser.add_argument(
|
|
223
291
|
'-i', '--images_dir', type=str, default=None,
|
|
224
|
-
help='Path to a local directory
|
|
225
|
-
'Azure blob storage container where images are stored. This '
|
|
292
|
+
help='Path to a local directory where images are stored. This '
|
|
226
293
|
'serves as the root directory for image paths in '
|
|
227
|
-
'detector_output_path.
|
|
228
|
-
'flag. You can use Azure Storage Explorer to obtain a SAS URL.')
|
|
229
|
-
parser.add_argument(
|
|
230
|
-
'-a', '--is-azure', action='store_true',
|
|
231
|
-
help='Flag that indidcates images_dir is an Azure blob storage '
|
|
232
|
-
'container URL.')
|
|
294
|
+
'detector_output_path.')
|
|
233
295
|
parser.add_argument(
|
|
234
296
|
'-n', '--sample', type=int, default=-1,
|
|
235
297
|
help='Number of images to be annotated and rendered. Set to -1 '
|
|
@@ -245,6 +307,9 @@ def main() -> None:
|
|
|
245
307
|
parser.add_argument(
|
|
246
308
|
'-html', '--html_output_file', type=str, default=None,
|
|
247
309
|
help='Filename to which we should write an HTML image index (off by default)')
|
|
310
|
+
parser.add_argument(
|
|
311
|
+
'--open_html_output_file', action='store_true',
|
|
312
|
+
help='Open the .html output file when done')
|
|
248
313
|
parser.add_argument(
|
|
249
314
|
'-do', '--detections_only', action='store_true',
|
|
250
315
|
help='Only render images with above-threshold detections (by default, '
|
|
@@ -263,7 +328,6 @@ def main() -> None:
|
|
|
263
328
|
out_dir=args.out_dir,
|
|
264
329
|
confidence_threshold=args.confidence,
|
|
265
330
|
images_dir=args.images_dir,
|
|
266
|
-
is_azure=args.is_azure,
|
|
267
331
|
sample=args.sample,
|
|
268
332
|
output_image_width=args.output_image_width,
|
|
269
333
|
random_seed=args.random_seed,
|
|
@@ -271,6 +335,53 @@ def main() -> None:
|
|
|
271
335
|
preserve_path_structure=args.preserve_path_structure,
|
|
272
336
|
html_output_file=args.html_output_file)
|
|
273
337
|
|
|
338
|
+
if args.html_output_file is not None and args.open_html_output_file:
|
|
339
|
+
from md_utils.path_utils import open_file
|
|
340
|
+
open_file(args.html_output_file)
|
|
274
341
|
|
|
275
342
|
if __name__ == '__main__':
|
|
276
343
|
main()
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
#%% Interactive driver
|
|
347
|
+
|
|
348
|
+
if False:
|
|
349
|
+
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
#%%
|
|
353
|
+
|
|
354
|
+
detector_output_path = os.path.expanduser('~/postprocessing/bellevue-camera-traps/bellevue-camera-traps-2023-12-05-v5a.0.0/combined_api_outputs/bellevue-camera-traps-2023-12-05-v5a.0.0_detections.json')
|
|
355
|
+
out_dir = r'g:\temp\preview'
|
|
356
|
+
images_dir = r'g:\camera_traps\camera_trap_images'
|
|
357
|
+
confidence_threshold = 0.15
|
|
358
|
+
sample = 50
|
|
359
|
+
output_image_width = 700
|
|
360
|
+
random_seed = 1
|
|
361
|
+
render_detections_only = True
|
|
362
|
+
classification_confidence_threshold = 0.1
|
|
363
|
+
html_output_file = os.path.join(out_dir,'index.html')
|
|
364
|
+
html_output_options = None
|
|
365
|
+
preserve_path_structure = False
|
|
366
|
+
parallelize_rendering = True
|
|
367
|
+
parallelize_rendering_n_cores = 10
|
|
368
|
+
parallelize_rendering_with_threads = False
|
|
369
|
+
|
|
370
|
+
_ = visualize_detector_output(detector_output_path,
|
|
371
|
+
out_dir,
|
|
372
|
+
images_dir,
|
|
373
|
+
confidence_threshold,
|
|
374
|
+
sample,
|
|
375
|
+
output_image_width,
|
|
376
|
+
random_seed,
|
|
377
|
+
render_detections_only,
|
|
378
|
+
classification_confidence_threshold,
|
|
379
|
+
html_output_file,
|
|
380
|
+
html_output_options,
|
|
381
|
+
preserve_path_structure,
|
|
382
|
+
parallelize_rendering,
|
|
383
|
+
parallelize_rendering_n_cores,
|
|
384
|
+
parallelize_rendering_with_threads)
|
|
385
|
+
|
|
386
|
+
from md_utils.path_utils import open_file
|
|
387
|
+
open_file(html_output_file)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: megadetector
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.7
|
|
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>
|
|
@@ -47,10 +47,7 @@ Requires-Dist: seaborn >=0.12.2
|
|
|
47
47
|
Requires-Dist: scikit-learn >=1.3.1
|
|
48
48
|
Requires-Dist: pandas >=2.1.1
|
|
49
49
|
Requires-Dist: PyYAML >=6.0.1
|
|
50
|
-
Requires-Dist:
|
|
51
|
-
Requires-Dist: torchvision >=0.15.2
|
|
52
|
-
Requires-Dist: ultralytics ==8.0.186
|
|
53
|
-
Requires-Dist: yolov5 ==7.0.12
|
|
50
|
+
Requires-Dist: ultralytics-yolov5 ==0.1.1
|
|
54
51
|
|
|
55
52
|
# MegaDetector
|
|
56
53
|
|
|
@@ -71,7 +68,7 @@ If you are a computer-vision-y person looking to run or fine-tune MegaDetector p
|
|
|
71
68
|
|
|
72
69
|
## Reasons you might want to use this package
|
|
73
70
|
|
|
74
|
-
If you want to
|
|
71
|
+
If you want to programmatically interact with the postprocessing tools from the MegaDetector repo, or programmatically run MegaDetector in a way that produces [Timelapse](https://saul.cpsc.ucalgary.ca/timelapse)-friendly output (i.e., output in the standard [MegaDetector output format](https://github.com/agentmorris/MegaDetector/tree/main/api/batch_processing#megadetector-batch-output-format)), this package might be for you.
|
|
75
72
|
|
|
76
73
|
Although even if that describes you, you <i>still</i> might be better off cloning the MegaDetector repo. Pip-installability requires that some dependencies be newer than what was available at the time MDv5 was trained, so results are <i>very slightly</i> different than results produced in the "official" environment. These differences <i>probably</i> don't matter much, but they have not been formally characterized.
|
|
77
74
|
|