megadetector 5.0.28__py3-none-any.whl → 10.0.0__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.
- megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +2 -2
- megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +1 -1
- megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +1 -1
- megadetector/classification/aggregate_classifier_probs.py +3 -3
- megadetector/classification/analyze_failed_images.py +5 -5
- megadetector/classification/cache_batchapi_outputs.py +5 -5
- megadetector/classification/create_classification_dataset.py +11 -12
- megadetector/classification/crop_detections.py +10 -10
- megadetector/classification/csv_to_json.py +8 -8
- megadetector/classification/detect_and_crop.py +13 -15
- megadetector/classification/efficientnet/model.py +8 -8
- megadetector/classification/efficientnet/utils.py +6 -5
- megadetector/classification/evaluate_model.py +7 -7
- megadetector/classification/identify_mislabeled_candidates.py +6 -6
- megadetector/classification/json_to_azcopy_list.py +1 -1
- megadetector/classification/json_validator.py +29 -32
- megadetector/classification/map_classification_categories.py +9 -9
- megadetector/classification/merge_classification_detection_output.py +12 -9
- megadetector/classification/prepare_classification_script.py +19 -19
- megadetector/classification/prepare_classification_script_mc.py +26 -26
- megadetector/classification/run_classifier.py +4 -4
- megadetector/classification/save_mislabeled.py +6 -6
- megadetector/classification/train_classifier.py +1 -1
- megadetector/classification/train_classifier_tf.py +9 -9
- megadetector/classification/train_utils.py +10 -10
- megadetector/data_management/annotations/annotation_constants.py +1 -2
- megadetector/data_management/camtrap_dp_to_coco.py +79 -46
- megadetector/data_management/cct_json_utils.py +103 -103
- megadetector/data_management/cct_to_md.py +49 -49
- megadetector/data_management/cct_to_wi.py +33 -33
- megadetector/data_management/coco_to_labelme.py +75 -75
- megadetector/data_management/coco_to_yolo.py +210 -193
- megadetector/data_management/databases/add_width_and_height_to_db.py +86 -12
- megadetector/data_management/databases/combine_coco_camera_traps_files.py +40 -40
- megadetector/data_management/databases/integrity_check_json_db.py +228 -200
- megadetector/data_management/databases/subset_json_db.py +33 -33
- megadetector/data_management/generate_crops_from_cct.py +88 -39
- megadetector/data_management/get_image_sizes.py +54 -49
- megadetector/data_management/labelme_to_coco.py +133 -125
- megadetector/data_management/labelme_to_yolo.py +159 -73
- megadetector/data_management/lila/create_lila_blank_set.py +81 -83
- megadetector/data_management/lila/create_lila_test_set.py +32 -31
- megadetector/data_management/lila/create_links_to_md_results_files.py +18 -18
- megadetector/data_management/lila/download_lila_subset.py +21 -24
- megadetector/data_management/lila/generate_lila_per_image_labels.py +365 -107
- megadetector/data_management/lila/get_lila_annotation_counts.py +35 -33
- megadetector/data_management/lila/get_lila_image_counts.py +22 -22
- megadetector/data_management/lila/lila_common.py +73 -70
- megadetector/data_management/lila/test_lila_metadata_urls.py +28 -19
- megadetector/data_management/mewc_to_md.py +344 -340
- megadetector/data_management/ocr_tools.py +262 -255
- megadetector/data_management/read_exif.py +249 -227
- megadetector/data_management/remap_coco_categories.py +90 -28
- megadetector/data_management/remove_exif.py +81 -21
- megadetector/data_management/rename_images.py +187 -187
- megadetector/data_management/resize_coco_dataset.py +588 -120
- megadetector/data_management/speciesnet_to_md.py +41 -41
- megadetector/data_management/wi_download_csv_to_coco.py +55 -55
- megadetector/data_management/yolo_output_to_md_output.py +248 -122
- megadetector/data_management/yolo_to_coco.py +333 -191
- megadetector/detection/change_detection.py +832 -0
- megadetector/detection/process_video.py +340 -337
- megadetector/detection/pytorch_detector.py +358 -278
- megadetector/detection/run_detector.py +399 -186
- megadetector/detection/run_detector_batch.py +404 -377
- megadetector/detection/run_inference_with_yolov5_val.py +340 -327
- megadetector/detection/run_tiled_inference.py +257 -249
- megadetector/detection/tf_detector.py +24 -24
- megadetector/detection/video_utils.py +332 -295
- megadetector/postprocessing/add_max_conf.py +19 -11
- megadetector/postprocessing/categorize_detections_by_size.py +45 -45
- megadetector/postprocessing/classification_postprocessing.py +468 -433
- megadetector/postprocessing/combine_batch_outputs.py +23 -23
- megadetector/postprocessing/compare_batch_results.py +590 -525
- megadetector/postprocessing/convert_output_format.py +106 -102
- megadetector/postprocessing/create_crop_folder.py +347 -147
- megadetector/postprocessing/detector_calibration.py +173 -168
- megadetector/postprocessing/generate_csv_report.py +508 -499
- megadetector/postprocessing/load_api_results.py +48 -27
- megadetector/postprocessing/md_to_coco.py +133 -102
- megadetector/postprocessing/md_to_labelme.py +107 -90
- megadetector/postprocessing/md_to_wi.py +40 -40
- megadetector/postprocessing/merge_detections.py +92 -114
- megadetector/postprocessing/postprocess_batch_results.py +319 -301
- megadetector/postprocessing/remap_detection_categories.py +91 -38
- megadetector/postprocessing/render_detection_confusion_matrix.py +214 -205
- megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +57 -57
- megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +27 -28
- megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +704 -679
- megadetector/postprocessing/separate_detections_into_folders.py +226 -211
- megadetector/postprocessing/subset_json_detector_output.py +265 -262
- megadetector/postprocessing/top_folders_to_bottom.py +45 -45
- megadetector/postprocessing/validate_batch_results.py +70 -70
- megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +52 -52
- megadetector/taxonomy_mapping/map_new_lila_datasets.py +18 -19
- megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +54 -33
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +67 -67
- megadetector/taxonomy_mapping/retrieve_sample_image.py +16 -16
- megadetector/taxonomy_mapping/simple_image_download.py +8 -8
- megadetector/taxonomy_mapping/species_lookup.py +156 -74
- megadetector/taxonomy_mapping/taxonomy_csv_checker.py +14 -14
- megadetector/taxonomy_mapping/taxonomy_graph.py +10 -10
- megadetector/taxonomy_mapping/validate_lila_category_mappings.py +13 -13
- megadetector/utils/ct_utils.py +1049 -211
- megadetector/utils/directory_listing.py +21 -77
- megadetector/utils/gpu_test.py +22 -22
- megadetector/utils/md_tests.py +632 -529
- megadetector/utils/path_utils.py +1520 -431
- megadetector/utils/process_utils.py +41 -41
- megadetector/utils/split_locations_into_train_val.py +62 -62
- megadetector/utils/string_utils.py +148 -27
- megadetector/utils/url_utils.py +489 -176
- megadetector/utils/wi_utils.py +2658 -2526
- megadetector/utils/write_html_image_list.py +137 -137
- megadetector/visualization/plot_utils.py +34 -30
- megadetector/visualization/render_images_with_thumbnails.py +39 -74
- megadetector/visualization/visualization_utils.py +487 -435
- megadetector/visualization/visualize_db.py +232 -198
- megadetector/visualization/visualize_detector_output.py +82 -76
- {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/METADATA +5 -2
- megadetector-10.0.0.dist-info/RECORD +139 -0
- {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/WHEEL +1 -1
- megadetector/api/batch_processing/api_core/__init__.py +0 -0
- megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
- megadetector/api/batch_processing/api_core/batch_service/score.py +0 -439
- megadetector/api/batch_processing/api_core/server.py +0 -294
- megadetector/api/batch_processing/api_core/server_api_config.py +0 -97
- megadetector/api/batch_processing/api_core/server_app_config.py +0 -55
- megadetector/api/batch_processing/api_core/server_batch_job_manager.py +0 -220
- megadetector/api/batch_processing/api_core/server_job_status_table.py +0 -149
- megadetector/api/batch_processing/api_core/server_orchestration.py +0 -360
- megadetector/api/batch_processing/api_core/server_utils.py +0 -88
- megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
- megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +0 -46
- megadetector/api/batch_processing/api_support/__init__.py +0 -0
- megadetector/api/batch_processing/api_support/summarize_daily_activity.py +0 -152
- megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
- megadetector/api/synchronous/__init__.py +0 -0
- megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
- megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +0 -151
- megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -263
- megadetector/api/synchronous/api_core/animal_detection_api/config.py +0 -35
- megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
- megadetector/api/synchronous/api_core/tests/load_test.py +0 -110
- megadetector/data_management/importers/add_nacti_sizes.py +0 -52
- megadetector/data_management/importers/add_timestamps_to_icct.py +0 -79
- megadetector/data_management/importers/animl_results_to_md_results.py +0 -158
- megadetector/data_management/importers/auckland_doc_test_to_json.py +0 -373
- megadetector/data_management/importers/auckland_doc_to_json.py +0 -201
- megadetector/data_management/importers/awc_to_json.py +0 -191
- megadetector/data_management/importers/bellevue_to_json.py +0 -272
- megadetector/data_management/importers/cacophony-thermal-importer.py +0 -793
- megadetector/data_management/importers/carrizo_shrubfree_2018.py +0 -269
- megadetector/data_management/importers/carrizo_trail_cam_2017.py +0 -289
- megadetector/data_management/importers/cct_field_adjustments.py +0 -58
- megadetector/data_management/importers/channel_islands_to_cct.py +0 -913
- megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
- megadetector/data_management/importers/eMammal/eMammal_helpers.py +0 -249
- megadetector/data_management/importers/eMammal/make_eMammal_json.py +0 -223
- megadetector/data_management/importers/ena24_to_json.py +0 -276
- megadetector/data_management/importers/filenames_to_json.py +0 -386
- megadetector/data_management/importers/helena_to_cct.py +0 -283
- megadetector/data_management/importers/idaho-camera-traps.py +0 -1407
- megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
- megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +0 -387
- megadetector/data_management/importers/jb_csv_to_json.py +0 -150
- megadetector/data_management/importers/mcgill_to_json.py +0 -250
- megadetector/data_management/importers/missouri_to_json.py +0 -490
- megadetector/data_management/importers/nacti_fieldname_adjustments.py +0 -79
- megadetector/data_management/importers/noaa_seals_2019.py +0 -181
- megadetector/data_management/importers/osu-small-animals-to-json.py +0 -364
- megadetector/data_management/importers/pc_to_json.py +0 -365
- megadetector/data_management/importers/plot_wni_giraffes.py +0 -123
- megadetector/data_management/importers/prepare_zsl_imerit.py +0 -131
- megadetector/data_management/importers/raic_csv_to_md_results.py +0 -416
- megadetector/data_management/importers/rspb_to_json.py +0 -356
- megadetector/data_management/importers/save_the_elephants_survey_A.py +0 -320
- megadetector/data_management/importers/save_the_elephants_survey_B.py +0 -329
- megadetector/data_management/importers/snapshot_safari_importer.py +0 -758
- megadetector/data_management/importers/snapshot_serengeti_lila.py +0 -1067
- megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
- megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
- megadetector/data_management/importers/sulross_get_exif.py +0 -65
- megadetector/data_management/importers/timelapse_csv_set_to_json.py +0 -490
- megadetector/data_management/importers/ubc_to_json.py +0 -399
- megadetector/data_management/importers/umn_to_json.py +0 -507
- megadetector/data_management/importers/wellington_to_json.py +0 -263
- megadetector/data_management/importers/wi_to_json.py +0 -442
- megadetector/data_management/importers/zamba_results_to_md_results.py +0 -180
- megadetector/data_management/lila/add_locations_to_island_camera_traps.py +0 -101
- megadetector/data_management/lila/add_locations_to_nacti.py +0 -151
- megadetector/utils/azure_utils.py +0 -178
- megadetector/utils/sas_blob_utils.py +0 -509
- megadetector-5.0.28.dist-info/RECORD +0 -209
- /megadetector/{api/batch_processing/__init__.py → __init__.py} +0 -0
- {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/licenses/LICENSE +0 -0
- {megadetector-5.0.28.dist-info → megadetector-10.0.0.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 =
|
|
341
|
-
|
|
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
|
+
|