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
|
@@ -151,6 +151,80 @@ if False:
|
|
|
151
151
|
|
|
152
152
|
pass
|
|
153
153
|
|
|
154
|
+
#%% Render one or more sample videos...
|
|
155
|
+
|
|
156
|
+
# ...while we still have the frames and detections around
|
|
157
|
+
|
|
158
|
+
## Imports
|
|
159
|
+
|
|
160
|
+
from md_visualization import visualize_detector_output
|
|
161
|
+
from detection.video_utils import frames_to_video
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
## Constants and paths
|
|
165
|
+
|
|
166
|
+
confidence_threshold = 0.2
|
|
167
|
+
input_fs = 30
|
|
168
|
+
|
|
169
|
+
filtered_output_filename = '/a/b/c/blah_detections.filtered_rde_0.150_0.850_10_1.000.json'
|
|
170
|
+
video_fn_relative = '4.10cam6/IMG_0022.MP4'
|
|
171
|
+
output_video_base = os.path.expanduser('~/tmp/video_preview')
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
## Filename handling
|
|
175
|
+
|
|
176
|
+
video_fn_relative = video_fn_relative.replace('\\','/')
|
|
177
|
+
video_fn_flat = video_fn_relative.replace('/','#')
|
|
178
|
+
video_name = os.path.splitext(video_fn_flat)[0]
|
|
179
|
+
output_video = os.path.join(output_video_base,'{}_detections.mp4'.format(video_name))
|
|
180
|
+
output_fs = input_fs / every_n_frames
|
|
181
|
+
|
|
182
|
+
rendered_detections_folder = os.path.join(output_video_base,'rendered_detections_{}'.format(video_name))
|
|
183
|
+
os.makedirs(rendered_detections_folder,exist_ok=True)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## Find frames corresponding to this video
|
|
187
|
+
|
|
188
|
+
with open(filtered_output_filename,'r') as f:
|
|
189
|
+
frame_results = json.load(f)
|
|
190
|
+
|
|
191
|
+
frame_results_this_video = []
|
|
192
|
+
|
|
193
|
+
# im = frame_results['images'][0]
|
|
194
|
+
for im in frame_results['images']:
|
|
195
|
+
if im['file'].replace('\\','/').startswith(video_fn_relative):
|
|
196
|
+
frame_results_this_video.append(im)
|
|
197
|
+
|
|
198
|
+
assert len(frame_results_this_video) > 0, \
|
|
199
|
+
'No frame results matched {}'.format(video_fn_relative)
|
|
200
|
+
print('Found {} matching frame results'.format(len(frame_results_this_video)))
|
|
201
|
+
|
|
202
|
+
frame_results['images'] = frame_results_this_video
|
|
203
|
+
|
|
204
|
+
frames_json = os.path.join(rendered_detections_folder,video_fn_flat + '.json')
|
|
205
|
+
|
|
206
|
+
with open(frames_json,'w') as f:
|
|
207
|
+
json.dump(frame_results,f,indent=1)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
## Render detections on those frames
|
|
211
|
+
|
|
212
|
+
detected_frame_files = visualize_detector_output.visualize_detector_output(
|
|
213
|
+
detector_output_path=frames_json,
|
|
214
|
+
out_dir=rendered_detections_folder,
|
|
215
|
+
images_dir=output_folder_base,
|
|
216
|
+
confidence_threshold=confidence_threshold,
|
|
217
|
+
preserve_path_structure=True,
|
|
218
|
+
output_image_width=-1)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
## Render the output video
|
|
222
|
+
|
|
223
|
+
frames_to_video(detected_frame_files, output_fs, output_video, codec_spec='h264')
|
|
224
|
+
|
|
225
|
+
# from md_utils.path_utils import open_file; open_file(output_video)
|
|
226
|
+
|
|
227
|
+
|
|
154
228
|
#%% Test a possibly-broken video
|
|
155
229
|
|
|
156
230
|
fn = '/datadrive/tmp/video.AVI'
|
|
@@ -175,8 +249,12 @@ if False:
|
|
|
175
249
|
import os
|
|
176
250
|
import nbformat as nbf
|
|
177
251
|
|
|
178
|
-
|
|
179
|
-
'
|
|
252
|
+
if os.name == 'nt':
|
|
253
|
+
git_base = r'c:\git'
|
|
254
|
+
else:
|
|
255
|
+
git_base = os.path.expanduer('~/git')
|
|
256
|
+
|
|
257
|
+
input_py_file = git_base + '/MegaDetector/api/batch_processing/data_preparation/manage_video_batch.py'
|
|
180
258
|
assert os.path.isfile(input_py_file)
|
|
181
259
|
output_ipynb_file = input_py_file.replace('.py','.ipynb')
|
|
182
260
|
|
|
@@ -246,3 +324,4 @@ while(True):
|
|
|
246
324
|
write_code_cell(current_cell)
|
|
247
325
|
|
|
248
326
|
nbf.write(nb,output_ipynb_file)
|
|
327
|
+
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# categorize_detections_by_size.py
|
|
4
4
|
#
|
|
5
5
|
# Given an API output .json file, creates a separate category for bounding boxes
|
|
6
|
-
# above
|
|
6
|
+
# above one or more size thresholds.
|
|
7
7
|
#
|
|
8
8
|
########
|
|
9
9
|
|
|
@@ -11,12 +11,16 @@
|
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
13
|
|
|
14
|
+
from collections import defaultdict
|
|
15
|
+
from tqdm import tqdm
|
|
16
|
+
|
|
14
17
|
|
|
15
18
|
#%% Support classes
|
|
16
19
|
|
|
17
20
|
class SizeCategorizationOptions:
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
# Should be sorted from smallest to largest
|
|
23
|
+
size_thresholds = [0.95]
|
|
20
24
|
|
|
21
25
|
# List of category numbers to use in separation; uses all categories if None
|
|
22
26
|
categories_to_separate = None
|
|
@@ -24,12 +28,13 @@ class SizeCategorizationOptions:
|
|
|
24
28
|
# Can be "size", "width", or "height"
|
|
25
29
|
measurement = 'size'
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
# Should have the same length as "size_thresholds"
|
|
32
|
+
size_category_names = ['large_detection']
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
|
|
30
35
|
#%% Main functions
|
|
31
36
|
|
|
32
|
-
def categorize_detections_by_size(input_file,output_file,options=None):
|
|
37
|
+
def categorize_detections_by_size(input_file,output_file=None,options=None):
|
|
33
38
|
|
|
34
39
|
if options is None:
|
|
35
40
|
options = SizeCategorizationOptions()
|
|
@@ -38,6 +43,14 @@ def categorize_detections_by_size(input_file,output_file,options=None):
|
|
|
38
43
|
options.categories_to_separate = \
|
|
39
44
|
[str(c) for c in options.categories_to_separate]
|
|
40
45
|
|
|
46
|
+
assert len(options.size_thresholds) == len(options.size_category_names), \
|
|
47
|
+
'Options struct should have the same number of category names and size thresholds'
|
|
48
|
+
|
|
49
|
+
# Sort size thresholds and names from largest to smallest
|
|
50
|
+
options.size_category_names = [x for _,x in sorted(zip(options.size_thresholds,
|
|
51
|
+
options.size_category_names),reverse=True)]
|
|
52
|
+
options.size_thresholds = sorted(options.size_thresholds,reverse=True)
|
|
53
|
+
|
|
41
54
|
with open(input_file) as f:
|
|
42
55
|
data = json.load(f)
|
|
43
56
|
|
|
@@ -45,12 +58,18 @@ def categorize_detections_by_size(input_file,output_file,options=None):
|
|
|
45
58
|
category_keys = list(detection_categories.keys())
|
|
46
59
|
category_keys = [int(k) for k in category_keys]
|
|
47
60
|
max_key = max(category_keys)
|
|
48
|
-
large_detection_category_id = str(max_key+1)
|
|
49
|
-
detection_categories[large_detection_category_id] = options.output_category_name
|
|
50
|
-
|
|
51
|
-
print('Creating large-box category for {} with ID {}'.format(
|
|
52
|
-
options.output_category_name,large_detection_category_id))
|
|
53
61
|
|
|
62
|
+
threshold_to_category_id = {}
|
|
63
|
+
for i_threshold,threshold in enumerate(options.size_thresholds):
|
|
64
|
+
|
|
65
|
+
category_id = str(max_key+1)
|
|
66
|
+
max_key += 1
|
|
67
|
+
detection_categories[category_id] = options.size_category_names[i_threshold]
|
|
68
|
+
threshold_to_category_id[i_threshold] = category_id
|
|
69
|
+
|
|
70
|
+
print('Creating category for {} with ID {}'.format(
|
|
71
|
+
options.size_category_names[i_threshold],category_id))
|
|
72
|
+
|
|
54
73
|
images = data['images']
|
|
55
74
|
|
|
56
75
|
print('Loaded {} images'.format(len(images)))
|
|
@@ -58,10 +77,10 @@ def categorize_detections_by_size(input_file,output_file,options=None):
|
|
|
58
77
|
# For each image...
|
|
59
78
|
#
|
|
60
79
|
# im = images[0]
|
|
80
|
+
|
|
81
|
+
category_id_to_count = defaultdict(int)
|
|
61
82
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
for im in images:
|
|
83
|
+
for im in tqdm(images):
|
|
65
84
|
|
|
66
85
|
if im['detections'] is None:
|
|
67
86
|
assert im['failure'] is not None and len(im['failure']) > 0
|
|
@@ -95,19 +114,31 @@ def categorize_detections_by_size(input_file,output_file,options=None):
|
|
|
95
114
|
metric = h
|
|
96
115
|
assert metric is not None
|
|
97
116
|
|
|
98
|
-
|
|
117
|
+
for i_threshold,threshold in enumerate(options.size_thresholds):
|
|
99
118
|
|
|
100
|
-
|
|
101
|
-
|
|
119
|
+
if metric >= threshold:
|
|
120
|
+
|
|
121
|
+
category_id = threshold_to_category_id[i_threshold]
|
|
122
|
+
|
|
123
|
+
category_id_to_count[category_id] += 1
|
|
124
|
+
d['category'] = category_id
|
|
125
|
+
|
|
126
|
+
break
|
|
102
127
|
|
|
128
|
+
# ...for each threshold
|
|
103
129
|
# ...for each detection
|
|
104
130
|
|
|
105
131
|
# ...for each image
|
|
106
132
|
|
|
107
|
-
|
|
133
|
+
for i_threshold in range(0,len(options.size_thresholds)):
|
|
134
|
+
category_name = options.size_category_names[i_threshold]
|
|
135
|
+
category_id = threshold_to_category_id[i_threshold]
|
|
136
|
+
category_count = category_id_to_count[category_id]
|
|
137
|
+
print('Found {} detections in category {}'.format(category_count,category_name))
|
|
108
138
|
|
|
109
|
-
|
|
110
|
-
|
|
139
|
+
if output_file is not None:
|
|
140
|
+
with open(output_file,'w') as f:
|
|
141
|
+
json.dump(data,f,indent=1)
|
|
111
142
|
|
|
112
143
|
return data
|
|
113
144
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
#
|
|
5
5
|
# Compare sets of batch results; typically used to compare:
|
|
6
6
|
#
|
|
7
|
-
# * MegaDetector versions
|
|
7
|
+
# * Results from different MegaDetector versions
|
|
8
8
|
# * Results before/after RDE
|
|
9
9
|
# * Results with/without augmentation
|
|
10
10
|
#
|
|
@@ -36,9 +36,6 @@ from md_utils import path_utils
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
#%% Constants and support classes
|
|
39
|
-
|
|
40
|
-
# We will confirm that this matches what we load from each file
|
|
41
|
-
default_detection_categories = {'1': 'animal', '2': 'person', '3': 'vehicle'}
|
|
42
39
|
|
|
43
40
|
class PairwiseBatchComparisonOptions:
|
|
44
41
|
"""
|
|
@@ -52,8 +49,8 @@ class PairwiseBatchComparisonOptions:
|
|
|
52
49
|
results_description_a = None
|
|
53
50
|
results_description_b = None
|
|
54
51
|
|
|
55
|
-
detection_thresholds_a = {'animal':0.15,'person':0.15,'vehicle':0.15}
|
|
56
|
-
detection_thresholds_b = {'animal':0.15,'person':0.15,'vehicle':0.15}
|
|
52
|
+
detection_thresholds_a = {'animal':0.15,'person':0.15,'vehicle':0.15,'default':0.15}
|
|
53
|
+
detection_thresholds_b = {'animal':0.15,'person':0.15,'vehicle':0.15,'default':0.15}
|
|
57
54
|
|
|
58
55
|
rendering_confidence_threshold_a = 0.1
|
|
59
56
|
rendering_confidence_threshold_b = 0.1
|
|
@@ -71,16 +68,26 @@ class BatchComparisonOptions:
|
|
|
71
68
|
job_name = ''
|
|
72
69
|
|
|
73
70
|
max_images_per_category = 1000
|
|
71
|
+
max_images_per_page = None
|
|
74
72
|
colormap_a = ['Red']
|
|
75
73
|
colormap_b = ['RoyalBlue']
|
|
76
74
|
|
|
77
75
|
# Process-based parallelization isn't supported yet; this must be "True"
|
|
78
76
|
parallelize_rendering_with_threads = True
|
|
79
77
|
|
|
78
|
+
# List of filenames to include in the comparison, or None to use all files
|
|
79
|
+
filenames_to_include = None
|
|
80
|
+
|
|
81
|
+
# Compare only detections/non-detections, ignore categories (still renders categories)
|
|
82
|
+
class_agnostic_comparison = False
|
|
83
|
+
|
|
80
84
|
target_width = 800
|
|
81
85
|
n_rendering_workers = 20
|
|
82
86
|
random_seed = 0
|
|
83
87
|
|
|
88
|
+
# Default to sorting by filename
|
|
89
|
+
sort_by_confidence = False
|
|
90
|
+
|
|
84
91
|
error_on_non_matching_lists = True
|
|
85
92
|
|
|
86
93
|
pairwise_options = []
|
|
@@ -90,7 +97,7 @@ class BatchComparisonOptions:
|
|
|
90
97
|
|
|
91
98
|
class PairwiseBatchComparisonResults:
|
|
92
99
|
"""
|
|
93
|
-
The results from a single pairwise comparison
|
|
100
|
+
The results from a single pairwise comparison.
|
|
94
101
|
"""
|
|
95
102
|
|
|
96
103
|
html_content = None
|
|
@@ -98,7 +105,7 @@ class PairwiseBatchComparisonResults:
|
|
|
98
105
|
|
|
99
106
|
# A dictionary with keys including:
|
|
100
107
|
#
|
|
101
|
-
#
|
|
108
|
+
# common_detections
|
|
102
109
|
# common_non_detections
|
|
103
110
|
# detections_a_only
|
|
104
111
|
# detections_b_only
|
|
@@ -207,7 +214,8 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
207
214
|
# in the options object.
|
|
208
215
|
assert options.pairwise_options is None
|
|
209
216
|
|
|
210
|
-
|
|
217
|
+
if options.random_seed is not None:
|
|
218
|
+
random.seed(options.random_seed)
|
|
211
219
|
|
|
212
220
|
# Warn the user if some "detections" might not get rendered
|
|
213
221
|
max_classification_threshold_a = max(list(pairwise_options.detection_thresholds_a.values()))
|
|
@@ -241,10 +249,20 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
241
249
|
with open(pairwise_options.results_filename_b,'r') as f:
|
|
242
250
|
results_b = json.load(f)
|
|
243
251
|
|
|
244
|
-
#
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
252
|
+
# Don't let path separators confuse things
|
|
253
|
+
for im in results_a['images']:
|
|
254
|
+
if 'file' in im:
|
|
255
|
+
im['file'] = im['file'].replace('\\','/')
|
|
256
|
+
for im in results_b['images']:
|
|
257
|
+
if 'file' in im:
|
|
258
|
+
im['file'] = im['file'].replace('\\','/')
|
|
259
|
+
|
|
260
|
+
if not options.class_agnostic_comparison:
|
|
261
|
+
assert results_a['detection_categories'] == results_b['detection_categories'], \
|
|
262
|
+
"Cannot perform a class-sensitive comparison across results with different categories"
|
|
263
|
+
|
|
264
|
+
detection_categories_a = results_a['detection_categories']
|
|
265
|
+
detection_categories_b = results_b['detection_categories']
|
|
248
266
|
|
|
249
267
|
if pairwise_options.results_description_a is None:
|
|
250
268
|
if 'detector' not in results_a['info']:
|
|
@@ -286,6 +304,10 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
286
304
|
assert len(filenames_a) == len(images_a)
|
|
287
305
|
assert len(filenames_b_set) == len(images_b)
|
|
288
306
|
|
|
307
|
+
if options.filenames_to_include is None:
|
|
308
|
+
filenames_to_compare = filenames_a
|
|
309
|
+
else:
|
|
310
|
+
filenames_to_compare = options.filenames_to_include
|
|
289
311
|
|
|
290
312
|
##%% Find differences
|
|
291
313
|
|
|
@@ -298,9 +320,9 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
298
320
|
detections_a_only = {}
|
|
299
321
|
detections_b_only = {}
|
|
300
322
|
class_transitions = {}
|
|
301
|
-
|
|
302
|
-
# fn =
|
|
303
|
-
for fn in tqdm(
|
|
323
|
+
|
|
324
|
+
# fn = filenames_to_compare[0]
|
|
325
|
+
for fn in tqdm(filenames_to_compare):
|
|
304
326
|
|
|
305
327
|
if fn not in filename_to_image_b:
|
|
306
328
|
|
|
@@ -330,14 +352,19 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
330
352
|
|
|
331
353
|
category_id = det['category']
|
|
332
354
|
|
|
333
|
-
if category_id not in
|
|
355
|
+
if category_id not in detection_categories_a:
|
|
334
356
|
print('Warning: unexpected category {} for model A on file {}'.format(category_id,fn))
|
|
335
357
|
invalid_category_error = True
|
|
336
358
|
break
|
|
337
359
|
|
|
338
360
|
conf = det['conf']
|
|
339
361
|
|
|
340
|
-
if
|
|
362
|
+
if detection_categories_a[category_id] in pairwise_options.detection_thresholds_a:
|
|
363
|
+
conf_thresh = pairwise_options.detection_thresholds_a[detection_categories_a[category_id]]
|
|
364
|
+
else:
|
|
365
|
+
conf_thresh = pairwise_options.detection_thresholds_a['default']
|
|
366
|
+
|
|
367
|
+
if conf >= conf_thresh:
|
|
341
368
|
categories_above_threshold_a.add(category_id)
|
|
342
369
|
|
|
343
370
|
if invalid_category_error:
|
|
@@ -349,14 +376,19 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
349
376
|
|
|
350
377
|
category_id = det['category']
|
|
351
378
|
|
|
352
|
-
if category_id not in
|
|
379
|
+
if category_id not in detection_categories_b:
|
|
353
380
|
print('Warning: unexpected category {} for model B on file {}'.format(category_id,fn))
|
|
354
381
|
invalid_category_error = True
|
|
355
382
|
break
|
|
356
383
|
|
|
357
384
|
conf = det['conf']
|
|
358
385
|
|
|
359
|
-
if
|
|
386
|
+
if detection_categories_b[category_id] in pairwise_options.detection_thresholds_b:
|
|
387
|
+
conf_thresh = pairwise_options.detection_thresholds_b[detection_categories_b[category_id]]
|
|
388
|
+
else:
|
|
389
|
+
conf_thresh = pairwise_options.detection_thresholds_a['default']
|
|
390
|
+
|
|
391
|
+
if conf >= conf_thresh:
|
|
360
392
|
categories_above_threshold_b.add(category_id)
|
|
361
393
|
|
|
362
394
|
if invalid_category_error:
|
|
@@ -368,7 +400,8 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
368
400
|
detection_b = (len(categories_above_threshold_b) > 0)
|
|
369
401
|
|
|
370
402
|
if detection_a and detection_b:
|
|
371
|
-
if categories_above_threshold_a == categories_above_threshold_b
|
|
403
|
+
if (categories_above_threshold_a == categories_above_threshold_b) or \
|
|
404
|
+
options.class_agnostic_comparison:
|
|
372
405
|
common_detections[fn] = im_pair
|
|
373
406
|
else:
|
|
374
407
|
class_transitions[fn] = im_pair
|
|
@@ -383,7 +416,7 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
383
416
|
# ...for each filename
|
|
384
417
|
|
|
385
418
|
print('Of {} files:\n{} common detections\n{} common non-detections\n{} A only\n{} B only\n{} class transitions'.format(
|
|
386
|
-
len(
|
|
419
|
+
len(filenames_to_compare),len(common_detections),
|
|
387
420
|
len(common_non_detections),len(detections_a_only),
|
|
388
421
|
len(detections_b_only),len(class_transitions)))
|
|
389
422
|
|
|
@@ -453,14 +486,16 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
453
486
|
# Choose detection pairs we're going to render for this category
|
|
454
487
|
image_pairs = categories_to_image_pairs[category]
|
|
455
488
|
image_filenames = list(image_pairs.keys())
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
489
|
+
|
|
490
|
+
if options.max_images_per_category is not None and options.max_images_per_category > 0:
|
|
491
|
+
if len(image_filenames) > options.max_images_per_category:
|
|
492
|
+
print('Sampling {} of {} image pairs for category {}'.format(
|
|
493
|
+
options.max_images_per_category,
|
|
494
|
+
len(image_filenames),
|
|
495
|
+
category))
|
|
496
|
+
image_filenames = random.sample(image_filenames,
|
|
497
|
+
options.max_images_per_category)
|
|
498
|
+
assert len(image_filenames) <= options.max_images_per_category
|
|
464
499
|
|
|
465
500
|
input_image_absolute_paths = [os.path.join(options.image_folder,fn) for fn in image_filenames]
|
|
466
501
|
|
|
@@ -492,15 +527,34 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
492
527
|
max_conf_b = maxempty([det['conf'] for det in image_b['detections']])
|
|
493
528
|
|
|
494
529
|
title = input_path_relative + ' (max conf {:.2f},{:.2f})'.format(max_conf_a,max_conf_b)
|
|
530
|
+
|
|
531
|
+
# Only used if sort_by_confidence is True
|
|
532
|
+
if category == 'common_detections':
|
|
533
|
+
sort_conf = max(max_conf_a,max_conf_b)
|
|
534
|
+
elif category == 'common_non_detections':
|
|
535
|
+
sort_conf = max(max_conf_a,max_conf_b)
|
|
536
|
+
elif category == 'detections_a_only':
|
|
537
|
+
sort_conf = max_conf_a
|
|
538
|
+
elif category == 'detections_b_only':
|
|
539
|
+
sort_conf = max_conf_b
|
|
540
|
+
elif category == 'class_transitions':
|
|
541
|
+
sort_conf = max(max_conf_a,max_conf_b)
|
|
542
|
+
else:
|
|
543
|
+
print('Warning: unknown sort category {}'.format(category))
|
|
544
|
+
sort_conf = max(max_conf_a,max_conf_b)
|
|
545
|
+
|
|
495
546
|
info = {
|
|
496
547
|
'filename': fn,
|
|
497
548
|
'title': title,
|
|
498
549
|
'textStyle': 'font-family:verdana,arial,calibri;font-size:' + \
|
|
499
550
|
'80%;text-align:left;margin-top:20;margin-bottom:5',
|
|
500
|
-
'linkTarget': urllib.parse.quote(input_image_absolute_paths[i_fn])
|
|
551
|
+
'linkTarget': urllib.parse.quote(input_image_absolute_paths[i_fn]),
|
|
552
|
+
'sort_conf':sort_conf
|
|
501
553
|
}
|
|
502
554
|
image_info.append(info)
|
|
503
555
|
|
|
556
|
+
# ...for each image
|
|
557
|
+
|
|
504
558
|
category_page_header_string = '<h1>{}</h1>'.format(categories_to_page_titles[category])
|
|
505
559
|
category_page_header_string += '<p style="font-weight:bold;">\n'
|
|
506
560
|
category_page_header_string += 'Model A: {}<br/>\n'.format(
|
|
@@ -521,11 +575,18 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
521
575
|
str(pairwise_options.rendering_confidence_threshold_b))
|
|
522
576
|
category_page_header_string += '</p>\n'
|
|
523
577
|
|
|
578
|
+
# Default to sorting by filename
|
|
579
|
+
if options.sort_by_confidence:
|
|
580
|
+
image_info = sorted(image_info, key=lambda d: d['sort_conf'], reverse=True)
|
|
581
|
+
else:
|
|
582
|
+
image_info = sorted(image_info, key=lambda d: d['filename'])
|
|
583
|
+
|
|
524
584
|
write_html_image_list(
|
|
525
585
|
category_html_filename,
|
|
526
586
|
images=image_info,
|
|
527
587
|
options={
|
|
528
|
-
'headerHtml': category_page_header_string
|
|
588
|
+
'headerHtml': category_page_header_string,
|
|
589
|
+
'maxFiguresPerHtmlFile': options.max_images_per_page
|
|
529
590
|
})
|
|
530
591
|
|
|
531
592
|
# ...for each category
|
|
@@ -559,7 +620,7 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
559
620
|
html_output_string += '<br/>'
|
|
560
621
|
|
|
561
622
|
html_output_string += ('Of {} total files:<br/><br/><div style="margin-left:15px;">{} common detections<br/>{} common non-detections<br/>{} A only<br/>{} B only<br/>{} class transitions</div><br/>'.format(
|
|
562
|
-
len(
|
|
623
|
+
len(filenames_to_compare),len(common_detections),
|
|
563
624
|
len(common_non_detections),len(detections_a_only),
|
|
564
625
|
len(detections_b_only),len(class_transitions)))
|
|
565
626
|
|
|
@@ -583,7 +644,7 @@ def pairwise_compare_batch_results(options,output_index,pairwise_options):
|
|
|
583
644
|
|
|
584
645
|
return pairwise_results
|
|
585
646
|
|
|
586
|
-
# ...def
|
|
647
|
+
# ...def pairwise_compare_batch_results()
|
|
587
648
|
|
|
588
649
|
|
|
589
650
|
def compare_batch_results(options):
|
|
@@ -663,12 +724,9 @@ def n_way_comparison(filenames,options,detection_thresholds=None,rendering_thres
|
|
|
663
724
|
pairwise_options.rendering_confidence_threshold_a = rendering_thresholds[i]
|
|
664
725
|
pairwise_options.rendering_confidence_threshold_b = rendering_thresholds[j]
|
|
665
726
|
|
|
666
|
-
pairwise_options.detection_thresholds_a = {'
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
pairwise_options.detection_thresholds_b = {'animal':detection_thresholds[j],
|
|
670
|
-
'person':detection_thresholds[j],
|
|
671
|
-
'vehicle':detection_thresholds[j]}
|
|
727
|
+
pairwise_options.detection_thresholds_a = {'default':detection_thresholds[i]}
|
|
728
|
+
pairwise_options.detection_thresholds_b = {'default':detection_thresholds[j]}
|
|
729
|
+
|
|
672
730
|
options.pairwise_options.append(pairwise_options)
|
|
673
731
|
|
|
674
732
|
return compare_batch_results(options)
|
|
@@ -679,32 +737,25 @@ def n_way_comparison(filenames,options,detection_thresholds=None,rendering_thres
|
|
|
679
737
|
#%% Interactive driver
|
|
680
738
|
|
|
681
739
|
if False:
|
|
682
|
-
|
|
683
|
-
#%% Running KGA test
|
|
684
|
-
|
|
685
|
-
# CUDA_VISIBLE_DEVICES=0 python run_detector_batch.py ~/models/camera_traps/megadetector/md_v5.0.0/md_v5a.0.0.pt ~/data/KGA/ ~/data/KGA-5a.json --recursive --output_relative_filenames --quiet
|
|
686
|
-
# CUDA_VISIBLE_DEVICES=1 python run_detector_batch.py ~/models/camera_traps/megadetector/md_v5.0.0/md_v5b.0.0.pt ~/data/KGA/ ~/data/KGA-5b.json --recursive --output_relative_filenames --quiet
|
|
687
|
-
|
|
688
|
-
# python run_detector_batch.py ~/models/camera_traps/megadetector/md_v4.1.0/md_v4.1.0.pb ~/data/KGA ~/data/KGA-4.json --recursive --output_relative_filenames --quiet
|
|
689
|
-
|
|
690
|
-
# CUDA_VISIBLE_DEVICES=0 python run_detector_batch.py ~/models/camera_traps/megadetector/md_v5.0.0/md_v5a.0.0.pt ~/data/KGA/ ~/data/KGA-5a-pillow-9.2.0.json --recursive --output_relative_filenames --quiet
|
|
691
|
-
|
|
692
|
-
|
|
740
|
+
|
|
693
741
|
#%% Test two-way comparison
|
|
694
742
|
|
|
695
743
|
options = BatchComparisonOptions()
|
|
696
744
|
|
|
697
|
-
options.parallelize_rendering_with_threads =
|
|
745
|
+
options.parallelize_rendering_with_threads = True
|
|
746
|
+
|
|
747
|
+
options.job_name = 'BCT'
|
|
748
|
+
options.output_folder = r'g:\temp\comparisons'
|
|
749
|
+
options.image_folder = r'g:\camera_traps\camera_trap_images'
|
|
750
|
+
options.max_images_per_category = 100
|
|
751
|
+
options.sort_by_confidence = True
|
|
698
752
|
|
|
699
|
-
options.job_name = 'KGA-test'
|
|
700
|
-
options.output_folder = os.path.expanduser('~/tmp/md-comparison-test')
|
|
701
|
-
options.image_folder = os.path.expanduser('~/data/KGA')
|
|
702
|
-
|
|
703
753
|
options.pairwise_options = []
|
|
704
754
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
os.path.
|
|
755
|
+
results_base = os.path.expanduser('~/postprocessing/bellevue-camera-traps')
|
|
756
|
+
filenames = [
|
|
757
|
+
os.path.join(results_base,r'bellevue-camera-traps-2023-12-05-v5a.0.0\combined_api_outputs\bellevue-camera-traps-2023-12-05-v5a.0.0_detections.json'),
|
|
758
|
+
os.path.join(results_base,r'bellevue-camera-traps-2023-12-05-aug-v5a.0.0\combined_api_outputs\bellevue-camera-traps-2023-12-05-aug-v5a.0.0_detections.json')
|
|
708
759
|
]
|
|
709
760
|
|
|
710
761
|
detection_thresholds = [0.15,0.15]
|
|
@@ -835,4 +886,3 @@ def main():
|
|
|
835
886
|
if __name__ == '__main__':
|
|
836
887
|
|
|
837
888
|
main()
|
|
838
|
-
|