megadetector 5.0.6__py3-none-any.whl → 5.0.8__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 +297 -202
- api/batch_processing/data_preparation/manage_video_batch.py +7 -2
- api/batch_processing/postprocessing/add_max_conf.py +1 -0
- api/batch_processing/postprocessing/combine_api_outputs.py +2 -2
- api/batch_processing/postprocessing/compare_batch_results.py +111 -61
- api/batch_processing/postprocessing/convert_output_format.py +24 -6
- api/batch_processing/postprocessing/load_api_results.py +56 -72
- api/batch_processing/postprocessing/md_to_labelme.py +119 -51
- api/batch_processing/postprocessing/merge_detections.py +30 -5
- api/batch_processing/postprocessing/postprocess_batch_results.py +175 -55
- api/batch_processing/postprocessing/remap_detection_categories.py +163 -0
- api/batch_processing/postprocessing/render_detection_confusion_matrix.py +628 -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 +224 -76
- api/batch_processing/postprocessing/subset_json_detector_output.py +132 -5
- api/batch_processing/postprocessing/top_folders_to_bottom.py +1 -1
- classification/prepare_classification_script.py +191 -191
- data_management/cct_json_utils.py +7 -2
- data_management/coco_to_labelme.py +263 -0
- data_management/coco_to_yolo.py +72 -48
- data_management/databases/integrity_check_json_db.py +75 -64
- data_management/databases/subset_json_db.py +1 -1
- data_management/generate_crops_from_cct.py +1 -1
- data_management/get_image_sizes.py +44 -26
- data_management/importers/animl_results_to_md_results.py +3 -5
- data_management/importers/noaa_seals_2019.py +2 -2
- data_management/importers/zamba_results_to_md_results.py +2 -2
- data_management/labelme_to_coco.py +264 -127
- data_management/labelme_to_yolo.py +96 -53
- data_management/lila/create_lila_blank_set.py +557 -0
- data_management/lila/create_lila_test_set.py +2 -1
- data_management/lila/create_links_to_md_results_files.py +1 -1
- data_management/lila/download_lila_subset.py +138 -45
- data_management/lila/generate_lila_per_image_labels.py +23 -14
- data_management/lila/get_lila_annotation_counts.py +16 -10
- data_management/lila/lila_common.py +15 -42
- data_management/lila/test_lila_metadata_urls.py +116 -0
- data_management/read_exif.py +65 -16
- data_management/remap_coco_categories.py +84 -0
- data_management/resize_coco_dataset.py +14 -31
- data_management/wi_download_csv_to_coco.py +239 -0
- data_management/yolo_output_to_md_output.py +40 -13
- data_management/yolo_to_coco.py +313 -100
- detection/process_video.py +36 -14
- detection/pytorch_detector.py +1 -1
- detection/run_detector.py +73 -18
- detection/run_detector_batch.py +116 -27
- detection/run_inference_with_yolov5_val.py +135 -27
- detection/run_tiled_inference.py +153 -43
- detection/tf_detector.py +2 -1
- detection/video_utils.py +4 -2
- md_utils/ct_utils.py +101 -6
- md_utils/md_tests.py +264 -17
- md_utils/path_utils.py +326 -47
- 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 +66 -3
- md_utils/write_html_image_list.py +12 -2
- md_visualization/visualization_utils.py +380 -74
- md_visualization/visualize_db.py +41 -10
- md_visualization/visualize_detector_output.py +185 -104
- {megadetector-5.0.6.dist-info → megadetector-5.0.8.dist-info}/METADATA +11 -13
- {megadetector-5.0.6.dist-info → megadetector-5.0.8.dist-info}/RECORD +74 -67
- {megadetector-5.0.6.dist-info → megadetector-5.0.8.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
- md_visualization/visualize_megadb.py +0 -183
- {megadetector-5.0.6.dist-info → megadetector-5.0.8.dist-info}/LICENSE +0 -0
- {megadetector-5.0.6.dist-info → megadetector-5.0.8.dist-info}/top_level.txt +0 -0
md_visualization/visualize_db.py
CHANGED
|
@@ -16,18 +16,21 @@ import math
|
|
|
16
16
|
import os
|
|
17
17
|
import sys
|
|
18
18
|
import time
|
|
19
|
+
|
|
20
|
+
import pandas as pd
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
import humanfriendly
|
|
24
|
+
|
|
19
25
|
from itertools import compress
|
|
20
26
|
from multiprocessing.pool import ThreadPool
|
|
21
27
|
from multiprocessing.pool import Pool
|
|
22
|
-
|
|
23
|
-
import pandas as pd
|
|
24
28
|
from tqdm import tqdm
|
|
25
|
-
import humanfriendly
|
|
26
29
|
|
|
27
30
|
from md_utils.write_html_image_list import write_html_image_list
|
|
31
|
+
from data_management.cct_json_utils import IndexedJsonDb
|
|
28
32
|
|
|
29
33
|
import md_visualization.visualization_utils as vis_utils
|
|
30
|
-
from data_management.cct_json_utils import IndexedJsonDb
|
|
31
34
|
|
|
32
35
|
|
|
33
36
|
#%% Settings
|
|
@@ -40,8 +43,16 @@ class DbVizOptions:
|
|
|
40
43
|
# Target size for rendering; set either dimension to -1 to preserve aspect ratio
|
|
41
44
|
#
|
|
42
45
|
# If viz_size is None or (-1,-1), the original image size is used.
|
|
43
|
-
viz_size = (
|
|
46
|
+
viz_size = (800, -1)
|
|
47
|
+
|
|
48
|
+
# The most relevant option one might want to set here is:
|
|
49
|
+
#
|
|
50
|
+
# htmlOptions['maxFiguresPerHtmlFile']
|
|
51
|
+
#
|
|
52
|
+
# ...which can be used to paginate previews to a number of images that will load well
|
|
53
|
+
# in a browser (5000 is a reasonable limit).
|
|
44
54
|
htmlOptions = write_html_image_list()
|
|
55
|
+
|
|
45
56
|
sort_by_filename = True
|
|
46
57
|
trim_to_images_with_bboxes = False
|
|
47
58
|
|
|
@@ -82,6 +93,11 @@ class DbVizOptions:
|
|
|
82
93
|
# Should we show absolute (vs. relative) paths for each image?
|
|
83
94
|
show_full_paths = False
|
|
84
95
|
|
|
96
|
+
# Set to False to skip existing images
|
|
97
|
+
force_rendering = True
|
|
98
|
+
|
|
99
|
+
verbose = False
|
|
100
|
+
|
|
85
101
|
|
|
86
102
|
#%% Helper functions
|
|
87
103
|
|
|
@@ -264,7 +280,7 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
264
280
|
categoryName = label_map[categoryID]
|
|
265
281
|
if options.add_search_links:
|
|
266
282
|
categoryName = categoryName.replace('"','')
|
|
267
|
-
categoryName = '<a href="https://www.
|
|
283
|
+
categoryName = '<a href="https://www.google.com/search?tbm=isch&q={}">{}</a>'.format(
|
|
268
284
|
categoryName,categoryName)
|
|
269
285
|
imageCategories.add(categoryName)
|
|
270
286
|
|
|
@@ -309,14 +325,22 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
309
325
|
if options.include_filename_links:
|
|
310
326
|
filename_text = '<a href="{}">{}</a>'.format(img_path,filename_text)
|
|
311
327
|
|
|
328
|
+
flagString = ''
|
|
329
|
+
|
|
330
|
+
def isnan(x):
|
|
331
|
+
return (isinstance(x,float) and np.isnan(x))
|
|
332
|
+
|
|
333
|
+
if ('flags' in img) and (not isnan(img['flags'])):
|
|
334
|
+
flagString = ', flags: {}'.format(str(img['flags']))
|
|
335
|
+
|
|
312
336
|
# We're adding html for an image before we render it, so it's possible this image will
|
|
313
337
|
# fail to render. For applications where this script is being used to debua a database
|
|
314
338
|
# (the common case?), this is useful behavior, for other applications, this is annoying.
|
|
315
339
|
image_dict = \
|
|
316
340
|
{
|
|
317
341
|
'filename': '{}/{}'.format('rendered_images', file_name),
|
|
318
|
-
'title': '{}<br/>{}, num boxes: {}, {}class labels: {}{}'.format(
|
|
319
|
-
filename_text, img_id, len(bboxes), frameString, imageClasses, labelLevelString),
|
|
342
|
+
'title': '{}<br/>{}, num boxes: {}, {}class labels: {}{}{}'.format(
|
|
343
|
+
filename_text, img_id, len(bboxes), frameString, imageClasses, labelLevelString, flagString),
|
|
320
344
|
'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;' + \
|
|
321
345
|
'text-align:left;margin-top:20;margin-bottom:5'
|
|
322
346
|
}
|
|
@@ -333,7 +357,13 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
333
357
|
bboxes = rendering_info['bboxes']
|
|
334
358
|
bboxClasses = rendering_info['boxClasses']
|
|
335
359
|
output_file_name = rendering_info['output_file_name']
|
|
360
|
+
output_full_path = os.path.join(output_dir, 'rendered_images', output_file_name)
|
|
336
361
|
|
|
362
|
+
if (os.path.isfile(output_full_path)) and (not options.force_rendering):
|
|
363
|
+
if options.verbose:
|
|
364
|
+
print('Skipping existing image {}'.format(output_full_path))
|
|
365
|
+
return True
|
|
366
|
+
|
|
337
367
|
if not img_path.startswith('http'):
|
|
338
368
|
if not os.path.exists(img_path):
|
|
339
369
|
print('Image {} cannot be found'.format(img_path))
|
|
@@ -348,7 +378,7 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
348
378
|
image = vis_utils.resize_image(original_image, options.viz_size[0],
|
|
349
379
|
options.viz_size[1])
|
|
350
380
|
except Exception as e:
|
|
351
|
-
print('Image {} failed to open
|
|
381
|
+
print('Image {} failed to open, error: {}'.format(img_path, e))
|
|
352
382
|
return False
|
|
353
383
|
|
|
354
384
|
vis_utils.render_db_bounding_boxes(boxes=bboxes, classes=bboxClasses,
|
|
@@ -357,7 +387,8 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
357
387
|
thickness=options.box_thickness,
|
|
358
388
|
expansion=options.box_expansion)
|
|
359
389
|
|
|
360
|
-
image.save(
|
|
390
|
+
image.save(output_full_path)
|
|
391
|
+
|
|
361
392
|
return True
|
|
362
393
|
|
|
363
394
|
# ...def render_image_info
|
|
@@ -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
109
|
html_output_file = None,
|
|
49
110
|
html_output_options = None,
|
|
50
|
-
preserve_path_structure = False
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
assert os.path.exists(detector_output_path), (
|
|
75
|
-
f'Detector output file does not exist at {detector_output_path}.')
|
|
134
|
+
assert os.path.exists(detector_output_path), \
|
|
135
|
+
'Detector output file does not exist at {}'.format(detector_output_path)
|
|
76
136
|
|
|
77
|
-
|
|
78
|
-
|
|
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 '
|
|
@@ -266,7 +328,6 @@ def main() -> None:
|
|
|
266
328
|
out_dir=args.out_dir,
|
|
267
329
|
confidence_threshold=args.confidence,
|
|
268
330
|
images_dir=args.images_dir,
|
|
269
|
-
is_azure=args.is_azure,
|
|
270
331
|
sample=args.sample,
|
|
271
332
|
output_image_width=args.output_image_width,
|
|
272
333
|
random_seed=args.random_seed,
|
|
@@ -290,17 +351,37 @@ if False:
|
|
|
290
351
|
|
|
291
352
|
#%%
|
|
292
353
|
|
|
293
|
-
detector_output_path =
|
|
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')
|
|
294
355
|
out_dir = r'g:\temp\preview'
|
|
295
|
-
images_dir = r
|
|
296
|
-
is_azure = False
|
|
356
|
+
images_dir = r'g:\camera_traps\camera_trap_images'
|
|
297
357
|
confidence_threshold = 0.15
|
|
298
|
-
sample =
|
|
358
|
+
sample = 50
|
|
299
359
|
output_image_width = 700
|
|
300
|
-
random_seed =
|
|
301
|
-
render_detections_only =
|
|
360
|
+
random_seed = 1
|
|
361
|
+
render_detections_only = True
|
|
302
362
|
classification_confidence_threshold = 0.1
|
|
303
|
-
html_output_file =
|
|
304
|
-
html_output_options = None
|
|
363
|
+
html_output_file = os.path.join(out_dir,'index.html')
|
|
364
|
+
html_output_options = None
|
|
305
365
|
preserve_path_structure = False
|
|
306
|
-
|
|
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.8
|
|
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>
|
|
@@ -60,7 +60,7 @@ If you want to learn more about what MegaDetector is all about, head over to the
|
|
|
60
60
|
|
|
61
61
|
### If you are an ecologist...
|
|
62
62
|
|
|
63
|
-
If you are an ecologist looking to use MegaDetector to help you get through your camera trap images, you probably don't want this package. We recommend starting with our "[Getting started with MegaDetector](https://github.com/agentmorris/MegaDetector/blob/main/collaborations.md)" page, then digging in to the [MegaDetector User Guide](https://github.com/agentmorris/MegaDetector/blob/main/megadetector.md), which will walk you through the process of using MegaDetector. That journey will <i>not</i> involve this package.
|
|
63
|
+
If you are an ecologist looking to use MegaDetector to help you get through your camera trap images, you probably don't want this package. We recommend starting with our "[Getting started with MegaDetector](https://github.com/agentmorris/MegaDetector/blob/main/collaborations.md)" page, then digging in to the [MegaDetector User Guide](https://github.com/agentmorris/MegaDetector/blob/main/megadetector.md), which will walk you through the process of using MegaDetector. That journey will <i>not</i> involve this Python package.
|
|
64
64
|
|
|
65
65
|
### If you are a computer-vision-y type...
|
|
66
66
|
|
|
@@ -68,7 +68,7 @@ If you are a computer-vision-y person looking to run or fine-tune MegaDetector p
|
|
|
68
68
|
|
|
69
69
|
## Reasons you might want to use this package
|
|
70
70
|
|
|
71
|
-
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.
|
|
72
72
|
|
|
73
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.
|
|
74
74
|
|
|
@@ -95,15 +95,13 @@ temporary_filename = url_utils.download_url(image_url)
|
|
|
95
95
|
|
|
96
96
|
image = vis_utils.load_image(temporary_filename)
|
|
97
97
|
|
|
98
|
-
# This will automatically download MDv5a
|
|
99
|
-
# you can also specify a filename explicitly, or set the $MDV5A
|
|
100
|
-
# environment variable to point to the model file.
|
|
98
|
+
# This will automatically download MDv5a; you can also specify a filename.
|
|
101
99
|
model = run_detector.load_detector('MDV5A')
|
|
102
100
|
|
|
103
101
|
result = model.generate_detections_one_image(image)
|
|
104
102
|
|
|
105
103
|
detections_above_threshold = [d for d in result['detections'] if d['conf'] > 0.2]
|
|
106
|
-
print('Found {}
|
|
104
|
+
print('Found {} detections above threshold'.format(len(detections_above_threshold)))
|
|
107
105
|
```
|
|
108
106
|
|
|
109
107
|
#### Run MegaDetector on a folder of images
|
|
@@ -120,14 +118,14 @@ output_file = os.path.expanduser('~/megadetector_output_test.json')
|
|
|
120
118
|
# Recursively find images
|
|
121
119
|
image_file_names = path_utils.find_images(image_folder,recursive=True)
|
|
122
120
|
|
|
123
|
-
# This will automatically download MDv5a
|
|
124
|
-
# you can also specify a filename explicitly, or set the $MDV5A
|
|
125
|
-
# environment variable to point to the model file.
|
|
121
|
+
# This will automatically download MDv5a; you can also specify a filename.
|
|
126
122
|
results = load_and_run_detector_batch('MDV5A', image_file_names)
|
|
127
123
|
|
|
128
|
-
# Write results
|
|
129
|
-
|
|
130
|
-
|
|
124
|
+
# Write results to a format that Timelapse and other downstream tools like.
|
|
125
|
+
write_results_to_file(results,
|
|
126
|
+
output_file,
|
|
127
|
+
relative_path_base=image_folder,
|
|
128
|
+
detector_file=detector_filename)
|
|
131
129
|
```
|
|
132
130
|
|
|
133
131
|
## Contact
|