megadetector 10.0.13__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.

Files changed (147) hide show
  1. megadetector/__init__.py +0 -0
  2. megadetector/api/__init__.py +0 -0
  3. megadetector/api/batch_processing/integration/digiKam/setup.py +6 -0
  4. megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +465 -0
  5. megadetector/api/batch_processing/integration/eMammal/test_scripts/config_template.py +5 -0
  6. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +125 -0
  7. megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +55 -0
  8. megadetector/classification/__init__.py +0 -0
  9. megadetector/classification/aggregate_classifier_probs.py +108 -0
  10. megadetector/classification/analyze_failed_images.py +227 -0
  11. megadetector/classification/cache_batchapi_outputs.py +198 -0
  12. megadetector/classification/create_classification_dataset.py +626 -0
  13. megadetector/classification/crop_detections.py +516 -0
  14. megadetector/classification/csv_to_json.py +226 -0
  15. megadetector/classification/detect_and_crop.py +853 -0
  16. megadetector/classification/efficientnet/__init__.py +9 -0
  17. megadetector/classification/efficientnet/model.py +415 -0
  18. megadetector/classification/efficientnet/utils.py +608 -0
  19. megadetector/classification/evaluate_model.py +520 -0
  20. megadetector/classification/identify_mislabeled_candidates.py +152 -0
  21. megadetector/classification/json_to_azcopy_list.py +63 -0
  22. megadetector/classification/json_validator.py +696 -0
  23. megadetector/classification/map_classification_categories.py +276 -0
  24. megadetector/classification/merge_classification_detection_output.py +509 -0
  25. megadetector/classification/prepare_classification_script.py +194 -0
  26. megadetector/classification/prepare_classification_script_mc.py +228 -0
  27. megadetector/classification/run_classifier.py +287 -0
  28. megadetector/classification/save_mislabeled.py +110 -0
  29. megadetector/classification/train_classifier.py +827 -0
  30. megadetector/classification/train_classifier_tf.py +725 -0
  31. megadetector/classification/train_utils.py +323 -0
  32. megadetector/data_management/__init__.py +0 -0
  33. megadetector/data_management/animl_to_md.py +161 -0
  34. megadetector/data_management/annotations/__init__.py +0 -0
  35. megadetector/data_management/annotations/annotation_constants.py +33 -0
  36. megadetector/data_management/camtrap_dp_to_coco.py +270 -0
  37. megadetector/data_management/cct_json_utils.py +566 -0
  38. megadetector/data_management/cct_to_md.py +184 -0
  39. megadetector/data_management/cct_to_wi.py +293 -0
  40. megadetector/data_management/coco_to_labelme.py +284 -0
  41. megadetector/data_management/coco_to_yolo.py +702 -0
  42. megadetector/data_management/databases/__init__.py +0 -0
  43. megadetector/data_management/databases/add_width_and_height_to_db.py +107 -0
  44. megadetector/data_management/databases/combine_coco_camera_traps_files.py +210 -0
  45. megadetector/data_management/databases/integrity_check_json_db.py +528 -0
  46. megadetector/data_management/databases/subset_json_db.py +195 -0
  47. megadetector/data_management/generate_crops_from_cct.py +200 -0
  48. megadetector/data_management/get_image_sizes.py +164 -0
  49. megadetector/data_management/labelme_to_coco.py +559 -0
  50. megadetector/data_management/labelme_to_yolo.py +349 -0
  51. megadetector/data_management/lila/__init__.py +0 -0
  52. megadetector/data_management/lila/create_lila_blank_set.py +556 -0
  53. megadetector/data_management/lila/create_lila_test_set.py +187 -0
  54. megadetector/data_management/lila/create_links_to_md_results_files.py +106 -0
  55. megadetector/data_management/lila/download_lila_subset.py +182 -0
  56. megadetector/data_management/lila/generate_lila_per_image_labels.py +777 -0
  57. megadetector/data_management/lila/get_lila_annotation_counts.py +174 -0
  58. megadetector/data_management/lila/get_lila_image_counts.py +112 -0
  59. megadetector/data_management/lila/lila_common.py +319 -0
  60. megadetector/data_management/lila/test_lila_metadata_urls.py +164 -0
  61. megadetector/data_management/mewc_to_md.py +344 -0
  62. megadetector/data_management/ocr_tools.py +873 -0
  63. megadetector/data_management/read_exif.py +964 -0
  64. megadetector/data_management/remap_coco_categories.py +195 -0
  65. megadetector/data_management/remove_exif.py +156 -0
  66. megadetector/data_management/rename_images.py +194 -0
  67. megadetector/data_management/resize_coco_dataset.py +663 -0
  68. megadetector/data_management/speciesnet_to_md.py +41 -0
  69. megadetector/data_management/wi_download_csv_to_coco.py +247 -0
  70. megadetector/data_management/yolo_output_to_md_output.py +594 -0
  71. megadetector/data_management/yolo_to_coco.py +876 -0
  72. megadetector/data_management/zamba_to_md.py +188 -0
  73. megadetector/detection/__init__.py +0 -0
  74. megadetector/detection/change_detection.py +840 -0
  75. megadetector/detection/process_video.py +479 -0
  76. megadetector/detection/pytorch_detector.py +1451 -0
  77. megadetector/detection/run_detector.py +1267 -0
  78. megadetector/detection/run_detector_batch.py +2159 -0
  79. megadetector/detection/run_inference_with_yolov5_val.py +1314 -0
  80. megadetector/detection/run_md_and_speciesnet.py +1494 -0
  81. megadetector/detection/run_tiled_inference.py +1038 -0
  82. megadetector/detection/tf_detector.py +209 -0
  83. megadetector/detection/video_utils.py +1379 -0
  84. megadetector/postprocessing/__init__.py +0 -0
  85. megadetector/postprocessing/add_max_conf.py +72 -0
  86. megadetector/postprocessing/categorize_detections_by_size.py +166 -0
  87. megadetector/postprocessing/classification_postprocessing.py +1752 -0
  88. megadetector/postprocessing/combine_batch_outputs.py +249 -0
  89. megadetector/postprocessing/compare_batch_results.py +2110 -0
  90. megadetector/postprocessing/convert_output_format.py +403 -0
  91. megadetector/postprocessing/create_crop_folder.py +629 -0
  92. megadetector/postprocessing/detector_calibration.py +570 -0
  93. megadetector/postprocessing/generate_csv_report.py +522 -0
  94. megadetector/postprocessing/load_api_results.py +223 -0
  95. megadetector/postprocessing/md_to_coco.py +428 -0
  96. megadetector/postprocessing/md_to_labelme.py +351 -0
  97. megadetector/postprocessing/md_to_wi.py +41 -0
  98. megadetector/postprocessing/merge_detections.py +392 -0
  99. megadetector/postprocessing/postprocess_batch_results.py +2077 -0
  100. megadetector/postprocessing/remap_detection_categories.py +226 -0
  101. megadetector/postprocessing/render_detection_confusion_matrix.py +677 -0
  102. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +206 -0
  103. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +82 -0
  104. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1665 -0
  105. megadetector/postprocessing/separate_detections_into_folders.py +795 -0
  106. megadetector/postprocessing/subset_json_detector_output.py +964 -0
  107. megadetector/postprocessing/top_folders_to_bottom.py +238 -0
  108. megadetector/postprocessing/validate_batch_results.py +332 -0
  109. megadetector/taxonomy_mapping/__init__.py +0 -0
  110. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +491 -0
  111. megadetector/taxonomy_mapping/map_new_lila_datasets.py +213 -0
  112. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +165 -0
  113. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +543 -0
  114. megadetector/taxonomy_mapping/retrieve_sample_image.py +71 -0
  115. megadetector/taxonomy_mapping/simple_image_download.py +224 -0
  116. megadetector/taxonomy_mapping/species_lookup.py +1008 -0
  117. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +159 -0
  118. megadetector/taxonomy_mapping/taxonomy_graph.py +346 -0
  119. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +83 -0
  120. megadetector/tests/__init__.py +0 -0
  121. megadetector/tests/test_nms_synthetic.py +335 -0
  122. megadetector/utils/__init__.py +0 -0
  123. megadetector/utils/ct_utils.py +1857 -0
  124. megadetector/utils/directory_listing.py +199 -0
  125. megadetector/utils/extract_frames_from_video.py +307 -0
  126. megadetector/utils/gpu_test.py +125 -0
  127. megadetector/utils/md_tests.py +2072 -0
  128. megadetector/utils/path_utils.py +2832 -0
  129. megadetector/utils/process_utils.py +172 -0
  130. megadetector/utils/split_locations_into_train_val.py +237 -0
  131. megadetector/utils/string_utils.py +234 -0
  132. megadetector/utils/url_utils.py +825 -0
  133. megadetector/utils/wi_platform_utils.py +968 -0
  134. megadetector/utils/wi_taxonomy_utils.py +1759 -0
  135. megadetector/utils/write_html_image_list.py +239 -0
  136. megadetector/visualization/__init__.py +0 -0
  137. megadetector/visualization/plot_utils.py +309 -0
  138. megadetector/visualization/render_images_with_thumbnails.py +243 -0
  139. megadetector/visualization/visualization_utils.py +1940 -0
  140. megadetector/visualization/visualize_db.py +630 -0
  141. megadetector/visualization/visualize_detector_output.py +479 -0
  142. megadetector/visualization/visualize_video_output.py +705 -0
  143. megadetector-10.0.13.dist-info/METADATA +134 -0
  144. megadetector-10.0.13.dist-info/RECORD +147 -0
  145. megadetector-10.0.13.dist-info/WHEEL +5 -0
  146. megadetector-10.0.13.dist-info/licenses/LICENSE +19 -0
  147. megadetector-10.0.13.dist-info/top_level.txt +1 -0
@@ -0,0 +1,570 @@
1
+ """
2
+
3
+ detector_calibration.py
4
+
5
+ Tools for comparing/calibrating confidence values from detectors, particularly different
6
+ versions of MegaDetector.
7
+
8
+ """
9
+
10
+ #%% Constants and imports
11
+
12
+ import random
13
+ import copy
14
+
15
+ from tqdm import tqdm
16
+ from enum import IntEnum
17
+ from collections import defaultdict
18
+
19
+ import numpy as np
20
+ import matplotlib
21
+ import matplotlib.pyplot as plt
22
+
23
+ from megadetector.postprocessing.validate_batch_results import \
24
+ validate_batch_results, ValidateBatchResultsOptions
25
+ from megadetector.utils.ct_utils import get_iou, max_none, is_iterable
26
+
27
+
28
+ #%% Classes
29
+
30
+ class CalibrationOptions:
31
+ """
32
+ Options controlling comparison/calibration behavior.
33
+ """
34
+
35
+ def __init__(self):
36
+
37
+ #: IoU threshold used for determining whether two detections are the same
38
+ #:
39
+ #: When multiple detections match, we will only use the highest-matching IoU.
40
+ self.iou_threshold = 0.6
41
+
42
+ #: Minimum confidence threshold to consider for calibration (should be lower than
43
+ #: the lowest value you would use in realistic situations)
44
+ self.confidence_threshold = 0.025
45
+
46
+ #: Should we populate the data_a and data_b fields in the return value?
47
+ self.return_data = False
48
+
49
+ #: Model name to use in printouts and plots for result set A
50
+ self.model_name_a = 'model_a'
51
+
52
+ #: Model name to use in printouts and plots for result set B
53
+ self.model_name_b = 'model_b'
54
+
55
+ #: Maximum number of samples to use for plotting or calibration per category,
56
+ #: or None to use all paired values. If separate_plots_by_category is False,
57
+ #: this is the overall number of points sampled.
58
+ self.max_samples_per_category = None
59
+
60
+ #: Should we make separate plots for each category? Mutually exclusive with
61
+ #: separate_plots_by_correctness.
62
+ self.separate_plots_by_category = True
63
+
64
+ #: Should we make separate plots for TPs/FPs? Mutually exclusive with
65
+ #: separate_plots_by_category.
66
+ self.separate_plots_by_correctness = False
67
+
68
+ #: List of category IDs to use for plotting comparisons, or None to plot
69
+ #: all categories.
70
+ self.categories_to_plot = None
71
+
72
+ #: Optionally map category ID to name in plot labels
73
+ self.category_id_to_name = None
74
+
75
+ #: Enable additional debug output
76
+ self.verbose = True
77
+
78
+ # ...class CalibrationOptions
79
+
80
+ class CalibrationMatchColumns(IntEnum):
81
+ """
82
+ Enumeration defining columns in the calibration_matches list we'll assemble below.
83
+ """
84
+
85
+ COLUMN_CONF_A = 0
86
+ COLUMN_CONF_B = 1
87
+ COLUMN_IOU = 2
88
+ COLUMN_I_IMAGE = 3
89
+ COLUMN_CATEGORY_ID = 4
90
+ COLUMN_MATCHES_GT = 5
91
+
92
+ class CalibrationResults:
93
+ """
94
+ Results of a model-to-model comparison.
95
+ """
96
+
97
+ def __init__(self):
98
+
99
+ #: List of tuples: [conf_a, conf_b, iou, i_image, category_id, matches_gt]
100
+ #:
101
+ #: If ground truth is supplied, [matches_gt] is a bool indicating whether either
102
+ #: of the detected boxes matches a ground truth box of the same category. If
103
+ #: ground truth is not supplied, [matches_gt] is None.
104
+ self.calibration_matches = []
105
+
106
+ #: Populated with the data loaded from json_filename_a if options.return_data is True
107
+ self.data_a = None
108
+
109
+ #: Populated with the data loaded from json_filename_b if options.return_data is True
110
+ self.data_b = None
111
+
112
+ # ...class CalibrationResults
113
+
114
+
115
+ #%% Calibration functions
116
+
117
+ def compare_model_confidence_values(json_filename_a,json_filename_b,json_filename_gt=None,options=None):
118
+ """
119
+ Compare confidence values across two .json results files. Compares only detections that
120
+ can be matched by IoU, i.e., does not do anything with detections that only appear in one file.
121
+
122
+ Args:
123
+ json_filename_a (str or dict): filename containing results from the first model to be compared;
124
+ should refer to the same images as [json_filename_b]. Can also be a loaded results dict.
125
+ json_filename_b (str or dict): filename containing results from the second model to be compared;
126
+ should refer to the same images as [json_filename_a]. Can also be a loaded results dict.
127
+ json_filename_gt (str or dict, optional): filename containing ground truth; should refer to the
128
+ same images as [json_filename_a] and [json_filename_b]. Can also be a loaded results dict.
129
+ Should be in COCO format.
130
+ options (CalibrationOptions, optional): all the parameters used to control this process, see
131
+ CalibrationOptions for details
132
+
133
+ Returns:
134
+ CalibrationResults: description of the comparison results
135
+ """
136
+
137
+ ## Option handling
138
+
139
+ if options is None:
140
+ options = CalibrationOptions()
141
+
142
+ validation_options = ValidateBatchResultsOptions()
143
+ validation_options.return_data = True
144
+
145
+ if isinstance(json_filename_a,str):
146
+ results_a = validate_batch_results(json_filename_a,options=validation_options)
147
+ assert len(results_a['validation_results']['errors']) == 0
148
+ else:
149
+ assert isinstance(json_filename_a,dict)
150
+ results_a = json_filename_a
151
+
152
+ if isinstance(json_filename_b,str):
153
+ results_b = validate_batch_results(json_filename_b,options=validation_options)
154
+ assert len(results_b['validation_results']['errors']) == 0
155
+ else:
156
+ assert isinstance(json_filename_b,dict)
157
+ results_b = json_filename_b
158
+
159
+ # Load ground truth, if supplied
160
+ gt_data = None
161
+
162
+ if json_filename_gt is not None:
163
+ if isinstance(json_filename_gt,str):
164
+ gt_data = validate_batch_results(json_filename_gt,
165
+ options=validation_options)
166
+ else:
167
+ assert isinstance(json_filename_gt,dict)
168
+ gt_data = json_filename_gt
169
+
170
+ ## Make sure these results sets are comparable
171
+
172
+ image_filenames_a = [im['file'] for im in results_a['images']]
173
+ image_filenames_b = [im['file'] for im in results_b['images']]
174
+
175
+ assert set(image_filenames_a) == set(image_filenames_b), \
176
+ 'Cannot calibrate non-matching image sets'
177
+
178
+ categories_a = results_a['detection_categories']
179
+ categories_b = results_b['detection_categories']
180
+ assert set(categories_a.keys()) == set(categories_b.keys())
181
+ for k in categories_a.keys():
182
+ assert categories_a[k] == categories_b[k], 'Category mismatch'
183
+
184
+
185
+ ## Load ground truth if necessary
186
+
187
+ gt_category_name_to_id = None
188
+ gt_image_id_to_annotations = None
189
+ image_filename_to_gt_im = None
190
+
191
+ if gt_data is not None:
192
+
193
+ gt_category_name_to_id = {}
194
+ for c in gt_data['categories']:
195
+ gt_category_name_to_id[c['name']] = c['id']
196
+
197
+ image_filename_to_gt_im = {}
198
+ for im in gt_data['images']:
199
+ assert 'width' in im and 'height' in im, \
200
+ 'I can only compare against GT that has "width" and "height" fields'
201
+ image_filename_to_gt_im[im['file_name']] = im
202
+
203
+ assert set(image_filename_to_gt_im.keys()) == set(image_filenames_a), \
204
+ 'Ground truth filename list does not match image filename list'
205
+
206
+ gt_image_id_to_annotations = defaultdict(list)
207
+ for ann in gt_data['annotations']:
208
+ gt_image_id_to_annotations[ann['image_id']].append(ann)
209
+
210
+
211
+ ## Compare detections
212
+
213
+ image_filename_b_to_im = {}
214
+ for im in results_b['images']:
215
+ image_filename_b_to_im[im['file']] = im
216
+
217
+ n_detections_a = 0
218
+ n_detections_a_queried = 0
219
+ n_detections_a_matched = 0
220
+
221
+ calibration_matches = []
222
+
223
+ # For each image
224
+ # im_a = results_a['images'][0]
225
+ for i_image,im_a in tqdm(enumerate(results_a['images']),total=len(results_a['images'])):
226
+
227
+ fn = im_a['file']
228
+ im_b = image_filename_b_to_im[fn]
229
+
230
+ if 'detections' not in im_a or im_a['detections'] is None:
231
+ continue
232
+ if 'detections' not in im_b or im_b['detections'] is None:
233
+ continue
234
+
235
+ im_gt = None
236
+ if gt_data is not None:
237
+ im_gt = image_filename_to_gt_im[fn]
238
+
239
+ # For each detection in result set A...
240
+ #
241
+ # det_a = im_a['detections'][0]
242
+ for det_a in im_a['detections']:
243
+
244
+ n_detections_a += 1
245
+
246
+ conf_a = det_a['conf']
247
+ category_id = det_a['category']
248
+
249
+ # Is this above threshold?
250
+ if conf_a < options.confidence_threshold:
251
+ continue
252
+
253
+ n_detections_a_queried += 1
254
+
255
+ bbox_a = det_a['bbox']
256
+
257
+ best_iou = None
258
+ best_iou_conf = None
259
+ best_bbox_b = None
260
+
261
+ # For each detection in result set B...
262
+ #
263
+ # det_b = im_b['detections'][0]
264
+ for det_b in im_b['detections']:
265
+
266
+ # Is this the same category?
267
+ if det_b['category'] != category_id:
268
+ continue
269
+
270
+ conf_b = det_b['conf']
271
+
272
+ # Is this above threshold?
273
+ if conf_b < options.confidence_threshold:
274
+ continue
275
+
276
+ bbox_b = det_b['bbox']
277
+
278
+ iou = get_iou(bbox_a,bbox_b)
279
+
280
+ # Is this an adequate IoU to consider?
281
+ if iou < options.iou_threshold:
282
+ continue
283
+
284
+ # Is this the best match so far?
285
+ if best_iou is None or iou > best_iou:
286
+ best_iou = iou
287
+ best_iou_conf = conf_b
288
+ best_bbox_b = bbox_b
289
+
290
+ # ...for each detection in im_b
291
+
292
+ # If we found a match between A and B
293
+ if best_iou is not None:
294
+
295
+ n_detections_a_matched += 1
296
+
297
+ # Does this pair of matched detections also match a ground truth box?
298
+ matches_gt = None
299
+
300
+ if im_gt is not None:
301
+
302
+ def max_iou_between_detection_and_gt(detection_box,category_name,im_gt,gt_annotations):
303
+
304
+ max_iou = None
305
+
306
+ # Which category ID are we looking for?
307
+ gt_category_id_for_detected_category_name = \
308
+ gt_category_name_to_id[category_name]
309
+
310
+ # For each GT annotation
311
+ #
312
+ # ann = gt_annotations[0]
313
+ for ann in gt_annotations:
314
+
315
+ # Only match against boxes in the same category
316
+ if ann['category_id'] != gt_category_id_for_detected_category_name:
317
+ continue
318
+ if 'bbox' not in ann:
319
+ continue
320
+
321
+ # Normalize this box
322
+ #
323
+ # COCO format: [x,y,width,height]
324
+ # normalized format: [x_min, y_min, width_of_box, height_of_box]
325
+ normalized_gt_box = [ann['bbox'][0]/im_gt['width'],ann['bbox'][1]/im_gt['height'],
326
+ ann['bbox'][2]/im_gt['width'],ann['bbox'][3]/im_gt['height']]
327
+
328
+ iou = get_iou(detection_box, normalized_gt_box)
329
+ if max_iou is None or iou > max_iou:
330
+ max_iou = iou
331
+
332
+ # ...for each gt box
333
+
334
+ return max_iou
335
+
336
+ # ...def min_iou_between_detections_and_gt(...)
337
+
338
+ gt_annotations = gt_image_id_to_annotations[im_gt['id']]
339
+
340
+ # If they matched, the A and B boxes have the same category by definition
341
+ category_name = categories_a[det_a['category']]
342
+
343
+ max_iou_with_bbox_a = \
344
+ max_iou_between_detection_and_gt(bbox_a,category_name,im_gt,gt_annotations)
345
+ max_iou_with_bbox_b = \
346
+ max_iou_between_detection_and_gt(best_bbox_b,category_name,im_gt,gt_annotations)
347
+
348
+ max_iou_with_either_detection_set = max_none(max_iou_with_bbox_a,
349
+ max_iou_with_bbox_b)
350
+
351
+ matches_gt = False
352
+ if (max_iou_with_either_detection_set is not None) and \
353
+ (max_iou_with_either_detection_set >= options.iou_threshold):
354
+ matches_gt = True
355
+
356
+ # ...if we have ground truth
357
+
358
+ conf_result = [conf_a,best_iou_conf,best_iou,i_image,category_id,matches_gt]
359
+ calibration_matches.append(conf_result)
360
+
361
+ # ...if we had a match between A and B
362
+ # ...for each detection in im_a
363
+
364
+ # ...for each image in result set A
365
+
366
+ if options.verbose:
367
+
368
+ print('\nOf {} detections in result set A, queried {}, matched {}'.format(
369
+ n_detections_a,n_detections_a_queried,n_detections_a_matched))
370
+
371
+ if gt_data is not None:
372
+ n_matches = 0
373
+ for m in calibration_matches:
374
+ assert m[CalibrationMatchColumns.COLUMN_MATCHES_GT] is not None
375
+ if m[CalibrationMatchColumns.COLUMN_MATCHES_GT]:
376
+ n_matches += 1
377
+ print('{} matches also matched ground truth'.format(n_matches))
378
+
379
+ assert len(calibration_matches) == n_detections_a_matched
380
+
381
+ calibration_results = CalibrationResults()
382
+ calibration_results.calibration_matches = calibration_matches
383
+
384
+ if options.return_data:
385
+ calibration_results.data_a = results_a
386
+ calibration_results.data_b = results_b
387
+
388
+ return calibration_results
389
+
390
+ # ...def compare_model_confidence_values(...)
391
+
392
+
393
+ #%% Plotting functions
394
+
395
+ def plot_matched_confidence_values(calibration_results,output_filename,options=None):
396
+ """
397
+ Given a set of paired confidence values for matching detections (from
398
+ compare_model_confidence_values), plot histograms of those pairs for each
399
+ detection category.
400
+
401
+ Args:
402
+ calibration_results (CalibrationResults): output from a call to
403
+ compare_model_confidence_values, containing paired confidence
404
+ values for two sets of detection results.
405
+ output_filename (str): filename to write the plot (.png or .jpg)
406
+ options (CalibrationOptions, optional): plotting options, see
407
+ CalibrationOptions for details.
408
+ """
409
+
410
+ fig_w = 12
411
+ fig_h = 8
412
+ n_hist_bins = 80
413
+
414
+ if options is None:
415
+ options = CalibrationOptions()
416
+
417
+ assert not (options.separate_plots_by_category and \
418
+ options.separate_plots_by_correctness), \
419
+ 'separate_plots_by_category and separate_plots_by_correctness are mutually exclusive'
420
+
421
+ category_id_to_name = None
422
+ category_to_samples = None
423
+
424
+ calibration_matches = calibration_results.calibration_matches
425
+
426
+ # If we're just lumping everything into one plot
427
+ if (not options.separate_plots_by_category) and (not options.separate_plots_by_correctness):
428
+
429
+ category_id_to_name = {'0':'all_categories'}
430
+ category_to_samples = {'0': []}
431
+
432
+ # Make everything category "0" (arbitrary)
433
+ calibration_matches = copy.deepcopy(calibration_matches)
434
+ for m in calibration_matches:
435
+ m[CalibrationMatchColumns.COLUMN_CATEGORY_ID] = '0'
436
+ if (options.max_samples_per_category is not None) and \
437
+ (len(calibration_matches) > options.max_samples_per_category):
438
+ calibration_matches = \
439
+ random.sample(calibration_matches,options.max_samples_per_category)
440
+ category_to_samples['0'] = calibration_matches
441
+
442
+ # If we're separating into lines for FPs and TPs (but not separating by category)
443
+ elif options.separate_plots_by_correctness:
444
+
445
+ assert not options.separate_plots_by_category
446
+
447
+ category_id_tp = '0'
448
+ category_id_fp = '1'
449
+
450
+ category_id_to_name = {category_id_tp:'TP', category_id_fp:'FP'}
451
+ category_to_samples = {category_id_tp: [], category_id_fp: []}
452
+
453
+ for m in calibration_matches:
454
+ assert m[CalibrationMatchColumns.COLUMN_MATCHES_GT] is not None, \
455
+ "Can't plot by correctness when GT status is not available for every match"
456
+ if m[CalibrationMatchColumns.COLUMN_MATCHES_GT]:
457
+ category_to_samples[category_id_tp].append(m)
458
+ else:
459
+ category_to_samples[category_id_fp].append(m)
460
+
461
+ # If we're separating by category
462
+ else:
463
+
464
+ assert options.separate_plots_by_category
465
+
466
+ category_to_samples = defaultdict(list)
467
+
468
+ category_to_matches = defaultdict(list)
469
+ for m in calibration_matches:
470
+ category_id = m[CalibrationMatchColumns.COLUMN_CATEGORY_ID]
471
+ category_to_matches[category_id].append(m)
472
+
473
+ category_id_to_name = None
474
+ if options.category_id_to_name is not None:
475
+ category_id_to_name = options.category_id_to_name
476
+
477
+ for i_category,category_id in enumerate(category_to_matches.keys()):
478
+
479
+ matches_this_category = category_to_matches[category_id]
480
+
481
+ if (options.max_samples_per_category is None) or \
482
+ (len(matches_this_category) <= options.max_samples_per_category):
483
+ category_to_samples[category_id] = matches_this_category
484
+ else:
485
+ assert len(matches_this_category) > options.max_samples_per_category
486
+ category_to_samples[category_id] = random.sample(matches_this_category,options.max_samples_per_category)
487
+
488
+ del category_to_matches
489
+
490
+ del calibration_matches
491
+
492
+ if options.verbose:
493
+ n_samples_for_histogram = 0
494
+ for c in category_to_samples:
495
+ n_samples_for_histogram += len(category_to_samples[c])
496
+ print('Creating a histogram based on {} samples'.format(n_samples_for_histogram))
497
+
498
+ categories_to_plot = list(category_to_samples.keys())
499
+
500
+ if options.categories_to_plot is not None:
501
+ categories_to_plot = [category_id for category_id in categories_to_plot if\
502
+ category_id in options.categories_to_plot]
503
+
504
+ n_subplots = len(categories_to_plot)
505
+
506
+ plt.ioff()
507
+
508
+ fig = matplotlib.figure.Figure(figsize=(fig_w, fig_h), tight_layout=True)
509
+ # fig,axes = plt.subplots(nrows=n_subplots,ncols=1)
510
+
511
+ axes = fig.subplots(n_subplots, 1)
512
+
513
+ if not is_iterable(axes):
514
+ assert n_subplots == 1
515
+ axes = [axes]
516
+
517
+ # i_category = 0; category_id = categories_to_plot[i_category]
518
+ for i_category,category_id in enumerate(categories_to_plot):
519
+
520
+ ax = axes[i_category]
521
+
522
+ category_string = str(category_id)
523
+ if (category_id_to_name is not None) and (category_id in category_id_to_name):
524
+ category_string = category_id_to_name[category_id]
525
+
526
+ samples_this_category = category_to_samples[category_id]
527
+ x = [m[0] for m in samples_this_category]
528
+ y = [m[1] for m in samples_this_category]
529
+
530
+ weights_a = np.ones_like(x)/float(len(x))
531
+ weights_b = np.ones_like(y)/float(len(y))
532
+
533
+ # Plot the first lie a little thicker so the second line will always show up
534
+ ax.hist(x,histtype='step',bins=n_hist_bins,density=False,color='red',weights=weights_a,linewidth=3.0)
535
+ ax.hist(y,histtype='step',bins=n_hist_bins,density=False,color='blue',weights=weights_b,linewidth=1.5)
536
+
537
+ ax.legend([options.model_name_a,options.model_name_b])
538
+ ax.set_ylabel(category_string)
539
+ # plt.tight_layout()
540
+
541
+ # I experimented with heat maps, but they weren't very informative.
542
+ # Leaving this code here in case I revisit. Note to self: scatter plots
543
+ # were a disaster.
544
+ if False:
545
+ heatmap, xedges, yedges = np.histogram2d(x, y, bins=30)
546
+ extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
547
+ plt.imshow(heatmap.T, extent=extent, origin='lower', norm='log')
548
+
549
+ # ...for each category for which we need to generate a histogram
550
+
551
+ plt.close(fig)
552
+ fig.savefig(output_filename,dpi=100)
553
+
554
+ # ...def plot_matched_confidence_values(...)
555
+
556
+
557
+ #%% Interactive driver(s)
558
+
559
+ if False:
560
+
561
+ #%%
562
+
563
+ options = ValidateBatchResultsOptions()
564
+ # json_filename = r'g:\temp\format.json'
565
+ # json_filename = r'g:\temp\test-videos\video_results.json'
566
+ json_filename = r'g:\temp\test-videos\image_results.json'
567
+ options.check_image_existence = True
568
+ options.relative_path_base = r'g:\temp\test-videos'
569
+ validate_batch_results(json_filename,options)
570
+