megadetector 5.0.27__py3-none-any.whl → 5.0.29__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of megadetector might be problematic. Click here for more details.

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