megadetector 5.0.7__py3-none-any.whl → 5.0.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of megadetector might be problematic. Click here for more details.
- api/__init__.py +0 -0
- api/batch_processing/__init__.py +0 -0
- api/batch_processing/api_core/__init__.py +0 -0
- api/batch_processing/api_core/batch_service/__init__.py +0 -0
- api/batch_processing/api_core/batch_service/score.py +0 -1
- api/batch_processing/api_core/server_job_status_table.py +0 -1
- api/batch_processing/api_core_support/__init__.py +0 -0
- api/batch_processing/api_core_support/aggregate_results_manually.py +0 -1
- api/batch_processing/api_support/__init__.py +0 -0
- api/batch_processing/api_support/summarize_daily_activity.py +0 -1
- api/batch_processing/data_preparation/__init__.py +0 -0
- api/batch_processing/data_preparation/manage_local_batch.py +93 -79
- api/batch_processing/data_preparation/manage_video_batch.py +8 -8
- api/batch_processing/integration/digiKam/xmp_integration.py +0 -1
- api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -1
- api/batch_processing/postprocessing/__init__.py +0 -0
- api/batch_processing/postprocessing/add_max_conf.py +12 -12
- api/batch_processing/postprocessing/categorize_detections_by_size.py +32 -14
- api/batch_processing/postprocessing/combine_api_outputs.py +69 -55
- api/batch_processing/postprocessing/compare_batch_results.py +114 -44
- api/batch_processing/postprocessing/convert_output_format.py +62 -19
- api/batch_processing/postprocessing/load_api_results.py +17 -20
- api/batch_processing/postprocessing/md_to_coco.py +31 -21
- api/batch_processing/postprocessing/md_to_labelme.py +165 -68
- api/batch_processing/postprocessing/merge_detections.py +40 -15
- api/batch_processing/postprocessing/postprocess_batch_results.py +270 -186
- api/batch_processing/postprocessing/remap_detection_categories.py +170 -0
- api/batch_processing/postprocessing/render_detection_confusion_matrix.py +75 -39
- api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +53 -44
- api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +25 -14
- api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +244 -160
- api/batch_processing/postprocessing/separate_detections_into_folders.py +159 -114
- api/batch_processing/postprocessing/subset_json_detector_output.py +146 -169
- api/batch_processing/postprocessing/top_folders_to_bottom.py +77 -43
- api/synchronous/__init__.py +0 -0
- api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
- api/synchronous/api_core/animal_detection_api/api_backend.py +0 -2
- api/synchronous/api_core/animal_detection_api/api_frontend.py +266 -268
- api/synchronous/api_core/animal_detection_api/config.py +35 -35
- api/synchronous/api_core/tests/__init__.py +0 -0
- api/synchronous/api_core/tests/load_test.py +109 -109
- classification/__init__.py +0 -0
- classification/aggregate_classifier_probs.py +21 -24
- classification/analyze_failed_images.py +11 -13
- classification/cache_batchapi_outputs.py +51 -51
- classification/create_classification_dataset.py +69 -68
- classification/crop_detections.py +54 -53
- classification/csv_to_json.py +97 -100
- classification/detect_and_crop.py +105 -105
- classification/evaluate_model.py +43 -42
- classification/identify_mislabeled_candidates.py +47 -46
- classification/json_to_azcopy_list.py +10 -10
- classification/json_validator.py +72 -71
- classification/map_classification_categories.py +44 -43
- classification/merge_classification_detection_output.py +68 -68
- classification/prepare_classification_script.py +157 -154
- classification/prepare_classification_script_mc.py +228 -228
- classification/run_classifier.py +27 -26
- classification/save_mislabeled.py +30 -30
- classification/train_classifier.py +20 -20
- classification/train_classifier_tf.py +21 -22
- classification/train_utils.py +10 -10
- data_management/__init__.py +0 -0
- data_management/annotations/__init__.py +0 -0
- data_management/annotations/annotation_constants.py +18 -31
- data_management/camtrap_dp_to_coco.py +238 -0
- data_management/cct_json_utils.py +107 -59
- data_management/cct_to_md.py +176 -158
- data_management/cct_to_wi.py +247 -219
- data_management/coco_to_labelme.py +272 -0
- data_management/coco_to_yolo.py +86 -62
- data_management/databases/__init__.py +0 -0
- data_management/databases/add_width_and_height_to_db.py +20 -16
- data_management/databases/combine_coco_camera_traps_files.py +35 -31
- data_management/databases/integrity_check_json_db.py +130 -83
- data_management/databases/subset_json_db.py +25 -16
- data_management/generate_crops_from_cct.py +27 -45
- data_management/get_image_sizes.py +188 -144
- data_management/importers/add_nacti_sizes.py +8 -8
- data_management/importers/add_timestamps_to_icct.py +78 -78
- data_management/importers/animl_results_to_md_results.py +158 -160
- data_management/importers/auckland_doc_test_to_json.py +9 -9
- data_management/importers/auckland_doc_to_json.py +8 -8
- data_management/importers/awc_to_json.py +7 -7
- data_management/importers/bellevue_to_json.py +15 -15
- data_management/importers/cacophony-thermal-importer.py +13 -13
- data_management/importers/carrizo_shrubfree_2018.py +8 -8
- data_management/importers/carrizo_trail_cam_2017.py +8 -8
- data_management/importers/cct_field_adjustments.py +9 -9
- data_management/importers/channel_islands_to_cct.py +10 -10
- data_management/importers/eMammal/copy_and_unzip_emammal.py +1 -0
- data_management/importers/ena24_to_json.py +7 -7
- data_management/importers/filenames_to_json.py +8 -8
- data_management/importers/helena_to_cct.py +7 -7
- data_management/importers/idaho-camera-traps.py +7 -7
- data_management/importers/idfg_iwildcam_lila_prep.py +10 -10
- data_management/importers/jb_csv_to_json.py +9 -9
- data_management/importers/mcgill_to_json.py +8 -8
- data_management/importers/missouri_to_json.py +18 -18
- data_management/importers/nacti_fieldname_adjustments.py +10 -10
- data_management/importers/noaa_seals_2019.py +8 -8
- data_management/importers/pc_to_json.py +7 -7
- data_management/importers/plot_wni_giraffes.py +7 -7
- data_management/importers/prepare-noaa-fish-data-for-lila.py +359 -359
- data_management/importers/prepare_zsl_imerit.py +7 -7
- data_management/importers/rspb_to_json.py +8 -8
- data_management/importers/save_the_elephants_survey_A.py +8 -8
- data_management/importers/save_the_elephants_survey_B.py +9 -9
- data_management/importers/snapshot_safari_importer.py +26 -26
- data_management/importers/snapshot_safari_importer_reprise.py +665 -665
- data_management/importers/snapshot_serengeti_lila.py +14 -14
- data_management/importers/sulross_get_exif.py +8 -9
- data_management/importers/timelapse_csv_set_to_json.py +11 -11
- data_management/importers/ubc_to_json.py +13 -13
- data_management/importers/umn_to_json.py +7 -7
- data_management/importers/wellington_to_json.py +8 -8
- data_management/importers/wi_to_json.py +9 -9
- data_management/importers/zamba_results_to_md_results.py +181 -181
- data_management/labelme_to_coco.py +309 -159
- data_management/labelme_to_yolo.py +103 -60
- data_management/lila/__init__.py +0 -0
- data_management/lila/add_locations_to_island_camera_traps.py +9 -9
- data_management/lila/add_locations_to_nacti.py +147 -147
- data_management/lila/create_lila_blank_set.py +114 -31
- data_management/lila/create_lila_test_set.py +8 -8
- data_management/lila/create_links_to_md_results_files.py +106 -106
- data_management/lila/download_lila_subset.py +92 -90
- data_management/lila/generate_lila_per_image_labels.py +56 -43
- data_management/lila/get_lila_annotation_counts.py +18 -15
- data_management/lila/get_lila_image_counts.py +11 -11
- data_management/lila/lila_common.py +103 -70
- data_management/lila/test_lila_metadata_urls.py +132 -116
- data_management/ocr_tools.py +173 -128
- data_management/read_exif.py +161 -99
- data_management/remap_coco_categories.py +84 -0
- data_management/remove_exif.py +58 -62
- data_management/resize_coco_dataset.py +32 -44
- data_management/wi_download_csv_to_coco.py +246 -0
- data_management/yolo_output_to_md_output.py +86 -73
- data_management/yolo_to_coco.py +535 -95
- detection/__init__.py +0 -0
- detection/detector_training/__init__.py +0 -0
- detection/process_video.py +85 -33
- detection/pytorch_detector.py +43 -25
- detection/run_detector.py +157 -72
- detection/run_detector_batch.py +189 -114
- detection/run_inference_with_yolov5_val.py +118 -51
- detection/run_tiled_inference.py +113 -42
- detection/tf_detector.py +51 -28
- detection/video_utils.py +606 -521
- docs/source/conf.py +43 -0
- md_utils/__init__.py +0 -0
- md_utils/azure_utils.py +9 -9
- md_utils/ct_utils.py +249 -70
- md_utils/directory_listing.py +59 -64
- md_utils/md_tests.py +968 -862
- md_utils/path_utils.py +655 -155
- md_utils/process_utils.py +157 -133
- md_utils/sas_blob_utils.py +20 -20
- md_utils/split_locations_into_train_val.py +45 -32
- md_utils/string_utils.py +33 -10
- md_utils/url_utils.py +208 -27
- md_utils/write_html_image_list.py +51 -35
- md_visualization/__init__.py +0 -0
- md_visualization/plot_utils.py +102 -109
- md_visualization/render_images_with_thumbnails.py +34 -34
- md_visualization/visualization_utils.py +908 -311
- md_visualization/visualize_db.py +109 -58
- md_visualization/visualize_detector_output.py +61 -42
- {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/METADATA +21 -17
- megadetector-5.0.9.dist-info/RECORD +224 -0
- {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/WHEEL +1 -1
- {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/top_level.txt +1 -0
- taxonomy_mapping/__init__.py +0 -0
- taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +342 -335
- taxonomy_mapping/map_new_lila_datasets.py +154 -154
- taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -134
- taxonomy_mapping/preview_lila_taxonomy.py +591 -591
- taxonomy_mapping/retrieve_sample_image.py +12 -12
- taxonomy_mapping/simple_image_download.py +11 -11
- taxonomy_mapping/species_lookup.py +10 -10
- taxonomy_mapping/taxonomy_csv_checker.py +18 -18
- taxonomy_mapping/taxonomy_graph.py +47 -47
- taxonomy_mapping/validate_lila_category_mappings.py +83 -76
- data_management/cct_json_to_filename_json.py +0 -89
- data_management/cct_to_csv.py +0 -140
- data_management/databases/remove_corrupted_images_from_db.py +0 -191
- detection/detector_training/copy_checkpoints.py +0 -43
- md_visualization/visualize_megadb.py +0 -183
- megadetector-5.0.7.dist-info/RECORD +0 -202
- {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/LICENSE +0 -0
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
#
|
|
17
|
-
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
postprocess_batch_results.py
|
|
4
|
+
|
|
5
|
+
Given a .json or .csv file containing MD results, do one or more of the following:
|
|
6
|
+
|
|
7
|
+
* Sample detections/non-detections and render to HTML (when ground truth isn't
|
|
8
|
+
available) (this is 99.9% of what this module is for)
|
|
9
|
+
* Evaluate detector precision/recall, optionally rendering results (requires
|
|
10
|
+
ground truth)
|
|
11
|
+
* Sample true/false positives/negatives and render to HTML (requires ground
|
|
12
|
+
truth)
|
|
13
|
+
|
|
14
|
+
Ground truth, if available, must be in COCO Camera Traps format:
|
|
15
|
+
|
|
16
|
+
https://github.com/agentmorris/MegaDetector/blob/main/data_management/README.md#coco-camera-traps-format
|
|
17
|
+
|
|
18
|
+
"""
|
|
18
19
|
|
|
19
20
|
#%% Constants and imports
|
|
20
21
|
|
|
@@ -27,11 +28,9 @@ import os
|
|
|
27
28
|
import sys
|
|
28
29
|
import time
|
|
29
30
|
import uuid
|
|
30
|
-
import urllib
|
|
31
31
|
import warnings
|
|
32
32
|
import random
|
|
33
33
|
|
|
34
|
-
from typing import Any, Dict, Iterable, Optional, Tuple
|
|
35
34
|
from enum import IntEnum
|
|
36
35
|
from multiprocessing.pool import ThreadPool
|
|
37
36
|
from multiprocessing.pool import Pool
|
|
@@ -52,8 +51,7 @@ from md_utils.write_html_image_list import write_html_image_list
|
|
|
52
51
|
from md_utils import path_utils
|
|
53
52
|
from data_management.cct_json_utils import (CameraTrapJsonUtils, IndexedJsonDb)
|
|
54
53
|
from api.batch_processing.postprocessing.load_api_results import load_api_results
|
|
55
|
-
from md_utils.ct_utils import args_to_object
|
|
56
|
-
from md_utils.ct_utils import invert_dictionary
|
|
54
|
+
from md_utils.ct_utils import args_to_object, sets_overlap
|
|
57
55
|
|
|
58
56
|
from detection.run_detector import get_typical_confidence_threshold_from_results
|
|
59
57
|
|
|
@@ -65,136 +63,163 @@ warnings.filterwarnings('ignore', '(Possibly )?corrupt EXIF data', UserWarning)
|
|
|
65
63
|
DEFAULT_NEGATIVE_CLASSES = ['empty']
|
|
66
64
|
DEFAULT_UNKNOWN_CLASSES = ['unknown', 'unlabeled', 'ambiguous']
|
|
67
65
|
|
|
68
|
-
|
|
69
|
-
def has_overlap(set1: Iterable, set2: Iterable) -> bool:
|
|
70
|
-
"""
|
|
71
|
-
Check whether two sets overlap.
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
return not set(set1).isdisjoint(set(set2))
|
|
75
|
-
|
|
76
|
-
|
|
77
66
|
# Make sure there is no overlap between the two sets, because this will cause
|
|
78
67
|
# issues in the code
|
|
79
|
-
assert not
|
|
68
|
+
assert not sets_overlap(DEFAULT_NEGATIVE_CLASSES, DEFAULT_UNKNOWN_CLASSES), (
|
|
80
69
|
'Default negative and unknown classes cannot overlap.')
|
|
81
70
|
|
|
82
71
|
|
|
83
72
|
class PostProcessingOptions:
|
|
84
|
-
|
|
73
|
+
"""
|
|
74
|
+
Options used to parameterize process_batch_results().
|
|
75
|
+
"""
|
|
76
|
+
|
|
85
77
|
### Required inputs
|
|
86
78
|
|
|
87
|
-
|
|
79
|
+
#: MD results .json file to process
|
|
80
|
+
md_results_file = ''
|
|
81
|
+
|
|
82
|
+
#: Folder to which we should write HTML output
|
|
88
83
|
output_dir = ''
|
|
89
84
|
|
|
90
85
|
### Options
|
|
91
86
|
|
|
92
|
-
|
|
87
|
+
#: Folder where images live (filenames in [md_results_file] should be relative to this folder)
|
|
93
88
|
image_base_dir = '.'
|
|
94
89
|
|
|
95
|
-
ground_truth_json_file = ''
|
|
96
|
-
|
|
97
90
|
## These apply only when we're doing ground-truth comparisons
|
|
98
91
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
#: Optional .json file containing ground truth information
|
|
93
|
+
ground_truth_json_file = ''
|
|
94
|
+
|
|
95
|
+
#: Classes we'll treat as negative
|
|
96
|
+
#:
|
|
97
|
+
#: Include the token "#NO_LABELS#" to indicate that an image with no annotations
|
|
98
|
+
#: should be considered empty.
|
|
103
99
|
negative_classes = DEFAULT_NEGATIVE_CLASSES
|
|
104
100
|
|
|
105
|
-
|
|
101
|
+
#: Classes we'll treat as neither positive nor negative
|
|
106
102
|
unlabeled_classes = DEFAULT_UNKNOWN_CLASSES
|
|
107
103
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
104
|
+
#: A list of output sets that we should count, but not render images for.
|
|
105
|
+
#:
|
|
106
|
+
#: Typically used to preview sets with lots of empties, where you don't want to
|
|
107
|
+
#: subset but also don't want to render 100,000 empty images.
|
|
108
|
+
#:
|
|
109
|
+
#: detections, non_detections
|
|
110
|
+
#: detections_animal, detections_person, detections_vehicle
|
|
115
111
|
rendering_bypass_sets = []
|
|
116
112
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
113
|
+
#: If this is None, choose a confidence threshold based on the detector version.
|
|
114
|
+
#:
|
|
115
|
+
#: This can either be a float or a dictionary mapping category names (not IDs) to
|
|
116
|
+
#: thresholds. The category "default" can be used to specify thresholds for
|
|
117
|
+
#: other categories. Currently the use of a dict here is not supported when
|
|
118
|
+
#: ground truth is supplied.
|
|
123
119
|
confidence_threshold = None
|
|
124
120
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
#: Confidence threshold to apply to classification (not detection) results
|
|
122
|
+
#:
|
|
123
|
+
#: Only a float is supported here (unlike the "confidence_threshold" parameter, which
|
|
124
|
+
#: can be a dict).
|
|
129
125
|
classification_confidence_threshold = 0.5
|
|
130
126
|
|
|
131
|
-
|
|
127
|
+
#: Used for summary statistics only
|
|
132
128
|
target_recall = 0.9
|
|
133
129
|
|
|
134
|
-
|
|
130
|
+
#: Number of images to sample, -1 for "all images"
|
|
135
131
|
num_images_to_sample = 500
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
sample_seed
|
|
133
|
+
#: Random seed for sampling, or None
|
|
134
|
+
sample_seed = 0 # None
|
|
139
135
|
|
|
136
|
+
#: Image width for images in the HTML output
|
|
140
137
|
viz_target_width = 800
|
|
141
138
|
|
|
139
|
+
#: Line width (in pixels) for rendering detections
|
|
142
140
|
line_thickness = 4
|
|
141
|
+
|
|
142
|
+
#: Box expansion (in pixels) for rendering detections
|
|
143
143
|
box_expansion = 0
|
|
144
144
|
|
|
145
|
+
#: Job name to include in big letters in the output HTML
|
|
145
146
|
job_name_string = None
|
|
147
|
+
|
|
148
|
+
#: Model version string to include in the output HTML
|
|
146
149
|
model_version_string = None
|
|
147
150
|
|
|
148
|
-
|
|
151
|
+
#: Sort order for the output, should be one of "filename", "confidence", or "random"
|
|
149
152
|
html_sort_order = 'filename'
|
|
150
153
|
|
|
154
|
+
#: If True, images in the output HTML will be links back to the original images
|
|
151
155
|
link_images_to_originals = True
|
|
152
156
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
157
|
+
#: Optionally separate detections into categories (animal/vehicle/human)
|
|
158
|
+
#:
|
|
159
|
+
#: Currently only supported when ground truth is unavailable
|
|
156
160
|
separate_detections_by_category = True
|
|
157
161
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
#: Optionally replace one or more strings in filenames with other strings;
|
|
163
|
+
#: useful for taking a set of results generated for one folder structure
|
|
164
|
+
#: and applying them to a slightly different folder structure.
|
|
161
165
|
api_output_filename_replacements = {}
|
|
166
|
+
|
|
167
|
+
#: Optionally replace one or more strings in filenames with other strings;
|
|
168
|
+
#: useful for taking a set of results generated for one folder structure
|
|
169
|
+
#: and applying them to a slightly different folder structure.
|
|
162
170
|
ground_truth_filename_replacements = {}
|
|
163
171
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
api_detection_results
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
#: Allow bypassing API output loading when operating on previously-loaded
|
|
173
|
+
#: results. If present, this is a Pandas DataFrame. Almost never useful.
|
|
174
|
+
api_detection_results = None
|
|
175
|
+
|
|
176
|
+
#: Allow bypassing API output loading when operating on previously-loaded
|
|
177
|
+
#: results. If present, this is a str --> obj dict. Almost never useful.
|
|
178
|
+
api_other_fields = None
|
|
179
|
+
|
|
180
|
+
#: Should we also split out a separate report about the detections that were
|
|
181
|
+
#: just below our main confidence threshold?
|
|
182
|
+
#:
|
|
183
|
+
#: Currently only supported when ground truth is unavailable.
|
|
173
184
|
include_almost_detections = False
|
|
174
185
|
|
|
175
|
-
|
|
176
|
-
|
|
186
|
+
#: Only a float is supported here (unlike the "confidence_threshold" parameter, which
|
|
187
|
+
#: can be a dict).
|
|
177
188
|
almost_detection_confidence_threshold = None
|
|
178
189
|
|
|
179
|
-
|
|
180
|
-
parallelize_rendering_n_cores: Optional[int] = 100
|
|
181
|
-
parallelize_rendering_with_threads = True
|
|
190
|
+
#: Enable/disable rendering parallelization
|
|
182
191
|
parallelize_rendering = False
|
|
183
192
|
|
|
193
|
+
#: Number of threads/processes to use for rendering parallelization
|
|
194
|
+
parallelize_rendering_n_cores = 25
|
|
195
|
+
|
|
196
|
+
#: Whether to use threads (True) or processes (False) for rendering parallelization
|
|
197
|
+
parallelize_rendering_with_threads = True
|
|
198
|
+
|
|
199
|
+
#: When classification results are present, should be sort alphabetically by class name (False)
|
|
200
|
+
#: or in descending order by frequency (True)?
|
|
184
201
|
sort_classification_results_by_count = False
|
|
185
202
|
|
|
186
|
-
|
|
187
|
-
|
|
203
|
+
#: Should we split individual pages up into smaller pages if there are more than
|
|
204
|
+
#: N images?
|
|
188
205
|
max_figures_per_html_file = None
|
|
189
206
|
|
|
190
207
|
# ...PostProcessingOptions
|
|
191
208
|
|
|
192
209
|
|
|
193
210
|
class PostProcessingResults:
|
|
194
|
-
|
|
211
|
+
"""
|
|
212
|
+
Return format from process_batch_results
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
#: HTML file to which preview information was written
|
|
195
216
|
output_html_file = ''
|
|
196
|
-
|
|
197
|
-
|
|
217
|
+
|
|
218
|
+
#: Pandas Dataframe containing detection results
|
|
219
|
+
api_detection_results = None
|
|
220
|
+
|
|
221
|
+
#: str --> obj dictionary containing other information loaded from the results file
|
|
222
|
+
api_other_fields = None
|
|
198
223
|
|
|
199
224
|
|
|
200
225
|
##%% Helper classes and functions
|
|
@@ -203,6 +228,8 @@ class DetectionStatus(IntEnum):
|
|
|
203
228
|
"""
|
|
204
229
|
Flags used to mark images as positive or negative for P/R analysis
|
|
205
230
|
(according to ground truth and/or detector output)
|
|
231
|
+
|
|
232
|
+
:meta private:
|
|
206
233
|
"""
|
|
207
234
|
|
|
208
235
|
DS_NEGATIVE = 0
|
|
@@ -225,11 +252,9 @@ class DetectionStatus(IntEnum):
|
|
|
225
252
|
DS_ALMOST = 5
|
|
226
253
|
|
|
227
254
|
|
|
228
|
-
def
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
unknown_classes: Iterable[str] = DEFAULT_UNKNOWN_CLASSES
|
|
232
|
-
) -> Tuple[int, int, int, int]:
|
|
255
|
+
def _mark_detection_status(indexed_db,
|
|
256
|
+
negative_classes=DEFAULT_NEGATIVE_CLASSES,
|
|
257
|
+
unknown_classes=DEFAULT_UNKNOWN_CLASSES):
|
|
233
258
|
"""
|
|
234
259
|
For each image in indexed_db.db['images'], add a '_detection_status' field
|
|
235
260
|
to indicate whether to treat this image as positive, negative, ambiguous,
|
|
@@ -261,8 +286,8 @@ def mark_detection_status(
|
|
|
261
286
|
# - unknown / unassigned-type labels
|
|
262
287
|
# - negative-type labels
|
|
263
288
|
# - positive labels (i.e., labels that are neither unknown nor negative)
|
|
264
|
-
has_unknown_labels =
|
|
265
|
-
has_negative_labels =
|
|
289
|
+
has_unknown_labels = sets_overlap(category_names, unknown_classes)
|
|
290
|
+
has_negative_labels = sets_overlap(category_names, negative_classes)
|
|
266
291
|
has_positive_labels = 0 < len(category_names - (unknown_classes | negative_classes))
|
|
267
292
|
# assert has_unknown_labels is False, '{} has unknown labels'.format(annotations)
|
|
268
293
|
|
|
@@ -317,23 +342,27 @@ def mark_detection_status(
|
|
|
317
342
|
|
|
318
343
|
return n_negative, n_positive, n_unknown, n_ambiguous
|
|
319
344
|
|
|
320
|
-
# ...
|
|
345
|
+
# ..._mark_detection_status()
|
|
321
346
|
|
|
322
347
|
|
|
323
|
-
def is_sas_url(s
|
|
348
|
+
def is_sas_url(s) -> bool:
|
|
324
349
|
"""
|
|
325
350
|
Placeholder for a more robust way to verify that a link is a SAS URL.
|
|
326
351
|
99.999% of the time this will suffice for what we're using it for right now.
|
|
352
|
+
|
|
353
|
+
:meta private:
|
|
327
354
|
"""
|
|
328
355
|
|
|
329
356
|
return (s.startswith(('http://', 'https://')) and ('core.windows.net' in s)
|
|
330
357
|
and ('?' in s))
|
|
331
358
|
|
|
332
359
|
|
|
333
|
-
def relative_sas_url(folder_url
|
|
360
|
+
def relative_sas_url(folder_url, relative_path):
|
|
334
361
|
"""
|
|
335
362
|
Given a container-level or folder-level SAS URL, create a SAS URL to the
|
|
336
363
|
specified relative path.
|
|
364
|
+
|
|
365
|
+
:meta private:
|
|
337
366
|
"""
|
|
338
367
|
|
|
339
368
|
relative_path = relative_path.replace('%','%25')
|
|
@@ -351,7 +380,7 @@ def relative_sas_url(folder_url: str, relative_path: str) -> Optional[str]:
|
|
|
351
380
|
return tokens[0] + relative_path + '?' + tokens[1]
|
|
352
381
|
|
|
353
382
|
|
|
354
|
-
def
|
|
383
|
+
def _render_bounding_boxes(
|
|
355
384
|
image_base_dir,
|
|
356
385
|
image_relative_path,
|
|
357
386
|
display_name,
|
|
@@ -363,6 +392,9 @@ def render_bounding_boxes(
|
|
|
363
392
|
options=None):
|
|
364
393
|
"""
|
|
365
394
|
Renders detection bounding boxes on a single image.
|
|
395
|
+
|
|
396
|
+
This is an internal function; if you want tools for rendering boxes on images, see
|
|
397
|
+
md_visualization.visualization_utils.
|
|
366
398
|
|
|
367
399
|
The source image is:
|
|
368
400
|
|
|
@@ -381,6 +413,8 @@ def render_bounding_boxes(
|
|
|
381
413
|
|
|
382
414
|
Returns the html info struct for this image in the format that's used for
|
|
383
415
|
write_html_image_list.
|
|
416
|
+
|
|
417
|
+
:meta private:
|
|
384
418
|
"""
|
|
385
419
|
|
|
386
420
|
if options is None:
|
|
@@ -450,7 +484,7 @@ def render_bounding_boxes(
|
|
|
450
484
|
rendering_confidence_threshold = {}
|
|
451
485
|
for category_id in category_ids:
|
|
452
486
|
rendering_confidence_threshold[category_id] = \
|
|
453
|
-
|
|
487
|
+
_get_threshold_for_category_id(category_id, options, detection_categories)
|
|
454
488
|
|
|
455
489
|
vis_utils.render_detection_bounding_boxes(
|
|
456
490
|
detections, image,
|
|
@@ -484,14 +518,21 @@ def render_bounding_boxes(
|
|
|
484
518
|
|
|
485
519
|
# Optionally add links back to the original images
|
|
486
520
|
if options.link_images_to_originals and (image_full_path is not None):
|
|
487
|
-
|
|
521
|
+
|
|
522
|
+
# Handling special characters in links has been pushed down into
|
|
523
|
+
# write_html_image_list
|
|
524
|
+
#
|
|
525
|
+
# link_target = image_full_path.replace('\\','/')
|
|
526
|
+
# link_target = urllib.parse.quote(link_target)
|
|
527
|
+
link_target = image_full_path
|
|
528
|
+
info['linkTarget'] = link_target
|
|
488
529
|
|
|
489
530
|
return info
|
|
490
531
|
|
|
491
|
-
# ...
|
|
532
|
+
# ..._render_bounding_boxes
|
|
492
533
|
|
|
493
534
|
|
|
494
|
-
def
|
|
535
|
+
def _prepare_html_subpages(images_html, output_dir, options=None):
|
|
495
536
|
"""
|
|
496
537
|
Write out a series of html image lists, e.g. the "detections" or "non-detections"
|
|
497
538
|
pages.
|
|
@@ -557,11 +598,13 @@ def prepare_html_subpages(images_html, output_dir, options=None):
|
|
|
557
598
|
|
|
558
599
|
return image_counts
|
|
559
600
|
|
|
560
|
-
# ...
|
|
601
|
+
# ..._prepare_html_subpages()
|
|
561
602
|
|
|
562
603
|
|
|
563
|
-
|
|
564
|
-
|
|
604
|
+
def _get_threshold_for_category_name(category_name,options):
|
|
605
|
+
"""
|
|
606
|
+
Determines the confidence threshold we should use for a specific category name.
|
|
607
|
+
"""
|
|
565
608
|
|
|
566
609
|
if isinstance(options.confidence_threshold,float):
|
|
567
610
|
return options.confidence_threshold
|
|
@@ -580,10 +623,12 @@ def get_threshold_for_category_name(category_name,options):
|
|
|
580
623
|
return options.confidence_threshold['default']
|
|
581
624
|
|
|
582
625
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
626
|
+
def _get_threshold_for_category_id(category_id,options,detection_categories):
|
|
627
|
+
"""
|
|
628
|
+
Determines the confidence threshold we should use for a specific category ID.
|
|
629
|
+
|
|
630
|
+
[detection_categories] is a dict mapping category IDs to names.
|
|
631
|
+
"""
|
|
587
632
|
|
|
588
633
|
if isinstance(options.confidence_threshold,float):
|
|
589
634
|
return options.confidence_threshold
|
|
@@ -593,66 +638,73 @@ def get_threshold_for_category_id(category_id,options,detection_categories):
|
|
|
593
638
|
|
|
594
639
|
category_name = detection_categories[category_id]
|
|
595
640
|
|
|
596
|
-
return
|
|
641
|
+
return _get_threshold_for_category_name(category_name,options)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def _get_positive_categories(detections,options,detection_categories):
|
|
645
|
+
"""
|
|
646
|
+
Gets a sorted list of unique categories (as string IDs) above the threshold for this image
|
|
597
647
|
|
|
648
|
+
[detection_categories] is a dict mapping category IDs to names.
|
|
649
|
+
"""
|
|
598
650
|
|
|
599
|
-
# Get a sorted list of unique categories (as string IDs) above the threshold for this image
|
|
600
|
-
#
|
|
601
|
-
# "detection_categories" is a dict mapping category IDs to names.
|
|
602
|
-
def get_positive_categories(detections,options,detection_categories):
|
|
603
651
|
positive_categories = set()
|
|
604
652
|
for d in detections:
|
|
605
|
-
threshold =
|
|
653
|
+
threshold = _get_threshold_for_category_id(d['category'], options, detection_categories)
|
|
606
654
|
if d['conf'] >= threshold:
|
|
607
655
|
positive_categories.add(d['category'])
|
|
608
656
|
return sorted(positive_categories)
|
|
609
657
|
|
|
610
658
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
659
|
+
def _has_positive_detection(detections,options,detection_categories):
|
|
660
|
+
"""
|
|
661
|
+
Determines whether any positive detections are present in the detection list
|
|
662
|
+
[detections].
|
|
663
|
+
"""
|
|
614
664
|
|
|
615
665
|
found_positive_detection = False
|
|
616
666
|
for d in detections:
|
|
617
|
-
threshold =
|
|
667
|
+
threshold = _get_threshold_for_category_id(d['category'], options, detection_categories)
|
|
618
668
|
if d['conf'] >= threshold:
|
|
619
669
|
found_positive_detection = True
|
|
620
670
|
break
|
|
621
671
|
return found_positive_detection
|
|
622
672
|
|
|
623
673
|
|
|
624
|
-
|
|
625
|
-
#
|
|
626
|
-
# Returns a list of rendering structs, where the first item is a category (e.g. "detections_animal"),
|
|
627
|
-
# and the second is a dict of information needed for rendering. E.g.:
|
|
628
|
-
#
|
|
629
|
-
# [['detections_animal',
|
|
630
|
-
# {
|
|
631
|
-
# 'filename': 'detections_animal/detections_animal_blah~01060415.JPG',
|
|
632
|
-
# 'title': '<b>Result type</b>: detections_animal,
|
|
633
|
-
# <b>Image</b>: blah\\01060415.JPG,
|
|
634
|
-
# <b>Max conf</b>: 0.897',
|
|
635
|
-
# 'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;text-align:left;margin-top:20;margin-bottom:5',
|
|
636
|
-
# 'linkTarget': 'full_path_to_%5C01060415.JPG'
|
|
637
|
-
# }]]
|
|
638
|
-
#
|
|
639
|
-
# When no classification data is present, this list will always be length-1. When
|
|
640
|
-
# classification data is present, an image may appear in multiple categories.
|
|
641
|
-
#
|
|
642
|
-
# Populates the 'max_conf' field of the first element of the list.
|
|
643
|
-
#
|
|
644
|
-
# Returns None if there are any errors.
|
|
645
|
-
def render_image_no_gt(file_info,detection_categories_to_results_name,
|
|
674
|
+
def _render_image_no_gt(file_info,detection_categories_to_results_name,
|
|
646
675
|
detection_categories,classification_categories,
|
|
647
676
|
options):
|
|
648
|
-
|
|
677
|
+
"""
|
|
678
|
+
Renders an image (with no ground truth information)
|
|
679
|
+
|
|
680
|
+
Returns a list of rendering structs, where the first item is a category (e.g. "detections_animal"),
|
|
681
|
+
and the second is a dict of information needed for rendering. E.g.:
|
|
682
|
+
|
|
683
|
+
[['detections_animal',
|
|
684
|
+
{
|
|
685
|
+
'filename': 'detections_animal/detections_animal_blah~01060415.JPG',
|
|
686
|
+
'title': '<b>Result type</b>: detections_animal,
|
|
687
|
+
<b>Image</b>: blah\\01060415.JPG,
|
|
688
|
+
<b>Max conf</b>: 0.897',
|
|
689
|
+
'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;text-align:left;margin-top:20;margin-bottom:5',
|
|
690
|
+
'linkTarget': 'full_path_to_%5C01060415.JPG'
|
|
691
|
+
}]]
|
|
692
|
+
|
|
693
|
+
When no classification data is present, this list will always be length-1. When
|
|
694
|
+
classification data is present, an image may appear in multiple categories.
|
|
695
|
+
|
|
696
|
+
Populates the 'max_conf' field of the first element of the list.
|
|
697
|
+
|
|
698
|
+
Returns None if there are any errors.
|
|
699
|
+
"""
|
|
700
|
+
|
|
649
701
|
image_relative_path = file_info[0]
|
|
650
702
|
max_conf = file_info[1]
|
|
651
703
|
detections = file_info[2]
|
|
652
704
|
|
|
653
705
|
# Determine whether any positive detections are present (using a threshold that
|
|
654
706
|
# may vary by category)
|
|
655
|
-
found_positive_detection =
|
|
707
|
+
found_positive_detection = _has_positive_detection(detections,options,detection_categories)
|
|
656
708
|
|
|
657
709
|
detection_status = DetectionStatus.DS_UNASSIGNED
|
|
658
710
|
if found_positive_detection:
|
|
@@ -668,7 +720,7 @@ def render_image_no_gt(file_info,detection_categories_to_results_name,
|
|
|
668
720
|
|
|
669
721
|
if detection_status == DetectionStatus.DS_POSITIVE:
|
|
670
722
|
if options.separate_detections_by_category:
|
|
671
|
-
positive_categories = tuple(
|
|
723
|
+
positive_categories = tuple(_get_positive_categories(detections,options,detection_categories))
|
|
672
724
|
if positive_categories not in detection_categories_to_results_name:
|
|
673
725
|
raise ValueError('Error: {} not in category mapping (file {})'.format(
|
|
674
726
|
str(positive_categories),image_relative_path))
|
|
@@ -690,7 +742,7 @@ def render_image_no_gt(file_info,detection_categories_to_results_name,
|
|
|
690
742
|
rendering_options.confidence_threshold = \
|
|
691
743
|
rendering_options.almost_detection_confidence_threshold
|
|
692
744
|
|
|
693
|
-
rendered_image_html_info =
|
|
745
|
+
rendered_image_html_info = _render_bounding_boxes(
|
|
694
746
|
image_base_dir=options.image_base_dir,
|
|
695
747
|
image_relative_path=image_relative_path,
|
|
696
748
|
display_name=display_name,
|
|
@@ -738,18 +790,20 @@ def render_image_no_gt(file_info,detection_categories_to_results_name,
|
|
|
738
790
|
|
|
739
791
|
image_result[0][1]['max_conf'] = max_conf
|
|
740
792
|
|
|
741
|
-
# ...if we got valid rendering info back from
|
|
793
|
+
# ...if we got valid rendering info back from _render_bounding_boxes()
|
|
742
794
|
|
|
743
795
|
return image_result
|
|
744
796
|
|
|
745
|
-
# ...def
|
|
797
|
+
# ...def _render_image_no_gt()
|
|
746
798
|
|
|
747
799
|
|
|
748
|
-
|
|
749
|
-
# data format.
|
|
750
|
-
def render_image_with_gt(file_info,ground_truth_indexed_db,
|
|
800
|
+
def _render_image_with_gt(file_info,ground_truth_indexed_db,
|
|
751
801
|
detection_categories,classification_categories,options):
|
|
752
|
-
|
|
802
|
+
"""
|
|
803
|
+
Render an image with ground truth information. See _render_image_no_gt for return
|
|
804
|
+
data format.
|
|
805
|
+
"""
|
|
806
|
+
|
|
753
807
|
image_relative_path = file_info[0]
|
|
754
808
|
max_conf = file_info[1]
|
|
755
809
|
detections = file_info[2]
|
|
@@ -775,7 +829,7 @@ def render_image_with_gt(file_info,ground_truth_indexed_db,
|
|
|
775
829
|
|
|
776
830
|
gt_presence = bool(gt_status)
|
|
777
831
|
|
|
778
|
-
gt_classes = CameraTrapJsonUtils.
|
|
832
|
+
gt_classes = CameraTrapJsonUtils.annotations_to_class_names(
|
|
779
833
|
annotations, ground_truth_indexed_db.cat_id_to_name)
|
|
780
834
|
gt_class_summary = ','.join(gt_classes)
|
|
781
835
|
|
|
@@ -784,7 +838,7 @@ def render_image_with_gt(file_info,ground_truth_indexed_db,
|
|
|
784
838
|
f'ground truth status (status: {gt_status}, classes: {gt_class_summary})')
|
|
785
839
|
return None
|
|
786
840
|
|
|
787
|
-
detected =
|
|
841
|
+
detected = _has_positive_detection(detections, options, detection_categories)
|
|
788
842
|
|
|
789
843
|
if gt_presence and detected:
|
|
790
844
|
if '_classification_accuracy' not in image.keys():
|
|
@@ -804,7 +858,7 @@ def render_image_with_gt(file_info,ground_truth_indexed_db,
|
|
|
804
858
|
res.upper(), str(gt_presence), gt_class_summary,
|
|
805
859
|
max_conf * 100, image_relative_path)
|
|
806
860
|
|
|
807
|
-
rendered_image_html_info =
|
|
861
|
+
rendered_image_html_info = _render_bounding_boxes(
|
|
808
862
|
image_base_dir=options.image_base_dir,
|
|
809
863
|
image_relative_path=image_relative_path,
|
|
810
864
|
display_name=display_name,
|
|
@@ -823,14 +877,35 @@ def render_image_with_gt(file_info,ground_truth_indexed_db,
|
|
|
823
877
|
|
|
824
878
|
return image_result
|
|
825
879
|
|
|
826
|
-
# ...def
|
|
880
|
+
# ...def _render_image_with_gt()
|
|
827
881
|
|
|
828
882
|
|
|
829
883
|
#%% Main function
|
|
830
884
|
|
|
831
|
-
def process_batch_results(options:
|
|
832
|
-
|
|
885
|
+
def process_batch_results(options):
|
|
886
|
+
|
|
887
|
+
"""
|
|
888
|
+
Given a .json or .csv file containing MD results, do one or more of the following:
|
|
889
|
+
|
|
890
|
+
* Sample detections/non-detections and render to HTML (when ground truth isn't
|
|
891
|
+
available) (this is 99.9% of what this module is for)
|
|
892
|
+
* Evaluate detector precision/recall, optionally rendering results (requires
|
|
893
|
+
ground truth)
|
|
894
|
+
* Sample true/false positives/negatives and render to HTML (requires ground
|
|
895
|
+
truth)
|
|
833
896
|
|
|
897
|
+
Ground truth, if available, must be in COCO Camera Traps format:
|
|
898
|
+
|
|
899
|
+
https://github.com/agentmorris/MegaDetector/blob/main/data_management/README.md#coco-camera-traps-format
|
|
900
|
+
|
|
901
|
+
Args:
|
|
902
|
+
options (PostProcessingOptions): everything we need to render a preview/analysis for
|
|
903
|
+
this set of results; see the PostProcessingOptions class for details.
|
|
904
|
+
|
|
905
|
+
Returns:
|
|
906
|
+
PostProcessingResults: information about the results/preview, most importantly the HTML filename
|
|
907
|
+
of the output. See the PostProcessingResults class for details.
|
|
908
|
+
"""
|
|
834
909
|
ppresults = PostProcessingResults()
|
|
835
910
|
|
|
836
911
|
##%% Expand some options for convenience
|
|
@@ -847,8 +922,8 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
847
922
|
|
|
848
923
|
ground_truth_indexed_db = None
|
|
849
924
|
|
|
850
|
-
if (options.ground_truth_json_file is not None):
|
|
851
|
-
assert (options.confidence_threshold is None) or (isinstance(confidence_threshold,float)), \
|
|
925
|
+
if (options.ground_truth_json_file is not None) and (len(options.ground_truth_json_file) > 0):
|
|
926
|
+
assert (options.confidence_threshold is None) or (isinstance(options.confidence_threshold,float)), \
|
|
852
927
|
'Variable confidence thresholds are not supported when supplying ground truth'
|
|
853
928
|
|
|
854
929
|
if (options.ground_truth_json_file is not None) and (len(options.ground_truth_json_file) > 0):
|
|
@@ -863,7 +938,7 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
863
938
|
filename_replacements=options.ground_truth_filename_replacements)
|
|
864
939
|
|
|
865
940
|
# Mark images in the ground truth as positive or negative
|
|
866
|
-
n_negative, n_positive, n_unknown, n_ambiguous =
|
|
941
|
+
n_negative, n_positive, n_unknown, n_ambiguous = _mark_detection_status(
|
|
867
942
|
ground_truth_indexed_db, negative_classes=options.negative_classes,
|
|
868
943
|
unknown_classes=options.unlabeled_classes)
|
|
869
944
|
print(f'Finished loading and indexing ground truth: {n_negative} '
|
|
@@ -876,7 +951,7 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
876
951
|
# If the caller hasn't supplied results, load them
|
|
877
952
|
if options.api_detection_results is None:
|
|
878
953
|
detections_df, other_fields = load_api_results(
|
|
879
|
-
options.api_output_file,
|
|
954
|
+
options.api_output_file, force_forward_slashes=True,
|
|
880
955
|
filename_replacements=options.api_output_filename_replacements)
|
|
881
956
|
ppresults.api_detection_results = detections_df
|
|
882
957
|
ppresults.api_other_fields = other_fields
|
|
@@ -895,7 +970,10 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
895
970
|
print('Choosing default confidence threshold of {} based on MD version'.format(
|
|
896
971
|
options.confidence_threshold))
|
|
897
972
|
|
|
898
|
-
if options.almost_detection_confidence_threshold is None:
|
|
973
|
+
if options.almost_detection_confidence_threshold is None and options.include_almost_detections:
|
|
974
|
+
assert isinstance(options.confidence_threshold,float), \
|
|
975
|
+
'If you are using a dictionary of confidence thresholds and almost-detections are enabled, ' + \
|
|
976
|
+
'you need to supply a threshold for almost detections.'
|
|
899
977
|
options.almost_detection_confidence_threshold = options.confidence_threshold - 0.05
|
|
900
978
|
if options.almost_detection_confidence_threshold < 0:
|
|
901
979
|
options.almost_detection_confidence_threshold = 0
|
|
@@ -929,7 +1007,7 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
929
1007
|
|
|
930
1008
|
detections = row['detections']
|
|
931
1009
|
max_conf = row['max_detection_conf']
|
|
932
|
-
if
|
|
1010
|
+
if _has_positive_detection(detections, options, detection_categories):
|
|
933
1011
|
n_positives += 1
|
|
934
1012
|
elif (options.almost_detection_confidence_threshold is not None) and \
|
|
935
1013
|
(max_conf >= options.almost_detection_confidence_threshold):
|
|
@@ -1087,7 +1165,7 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1087
1165
|
(precision_at_confidence_threshold + recall_at_confidence_threshold)
|
|
1088
1166
|
|
|
1089
1167
|
print('At a confidence threshold of {:.1%}, precision={:.1%}, recall={:.1%}, f1={:.1%}'.format(
|
|
1090
|
-
|
|
1168
|
+
options.confidence_threshold, precision_at_confidence_threshold,
|
|
1091
1169
|
recall_at_confidence_threshold, f1))
|
|
1092
1170
|
|
|
1093
1171
|
##%% Collect classification results, if they exist
|
|
@@ -1279,7 +1357,7 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1279
1357
|
worker_string))
|
|
1280
1358
|
|
|
1281
1359
|
rendering_results = list(tqdm(pool.imap(
|
|
1282
|
-
partial(
|
|
1360
|
+
partial(_render_image_with_gt,
|
|
1283
1361
|
ground_truth_indexed_db=ground_truth_indexed_db,
|
|
1284
1362
|
detection_categories=detection_categories,
|
|
1285
1363
|
classification_categories=classification_categories,
|
|
@@ -1287,9 +1365,10 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1287
1365
|
files_to_render), total=len(files_to_render)))
|
|
1288
1366
|
else:
|
|
1289
1367
|
for file_info in tqdm(files_to_render):
|
|
1290
|
-
rendering_results.append(
|
|
1368
|
+
rendering_results.append(_render_image_with_gt(
|
|
1291
1369
|
file_info,ground_truth_indexed_db,
|
|
1292
|
-
detection_categories,classification_categories
|
|
1370
|
+
detection_categories,classification_categories,
|
|
1371
|
+
options=options))
|
|
1293
1372
|
elapsed = time.time() - start_time
|
|
1294
1373
|
|
|
1295
1374
|
# Map all the rendering results in the list rendering_results into the
|
|
@@ -1303,7 +1382,7 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1303
1382
|
images_html[assignment[0]].append(assignment[1])
|
|
1304
1383
|
|
|
1305
1384
|
# Prepare the individual html image files
|
|
1306
|
-
image_counts =
|
|
1385
|
+
image_counts = _prepare_html_subpages(images_html, output_dir, options)
|
|
1307
1386
|
|
|
1308
1387
|
print('{} images rendered (of {})'.format(image_rendered_count,image_count))
|
|
1309
1388
|
|
|
@@ -1319,6 +1398,12 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1319
1398
|
image_counts['tp']
|
|
1320
1399
|
)
|
|
1321
1400
|
|
|
1401
|
+
confidence_threshold_string = ''
|
|
1402
|
+
if isinstance(options.confidence_threshold,float):
|
|
1403
|
+
confidence_threshold_string = '{:.2%}'.format(options.confidence_threshold)
|
|
1404
|
+
else:
|
|
1405
|
+
confidence_threshold_string = str(options.confidence_threshold)
|
|
1406
|
+
|
|
1322
1407
|
index_page = """<html>
|
|
1323
1408
|
{}
|
|
1324
1409
|
<body>
|
|
@@ -1333,7 +1418,7 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1333
1418
|
|
|
1334
1419
|
<h3>Sample images</h3>
|
|
1335
1420
|
<div class="contentdiv">
|
|
1336
|
-
<p>A sample of {} images, annotated with detections above {
|
|
1421
|
+
<p>A sample of {} images, annotated with detections above confidence {}.</p>
|
|
1337
1422
|
<a href="tp.html">True positives (TP)</a> ({}) ({:0.1%})<br/>
|
|
1338
1423
|
CLASSIFICATION_PLACEHOLDER_1
|
|
1339
1424
|
<a href="tn.html">True negatives (TN)</a> ({}) ({:0.1%})<br/>
|
|
@@ -1343,7 +1428,7 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1343
1428
|
</div>
|
|
1344
1429
|
""".format(
|
|
1345
1430
|
style_header,job_name_string,model_version_string,
|
|
1346
|
-
image_count,
|
|
1431
|
+
image_count, confidence_threshold_string,
|
|
1347
1432
|
all_tp_count, all_tp_count/total_count,
|
|
1348
1433
|
image_counts['tn'], image_counts['tn']/total_count,
|
|
1349
1434
|
image_counts['fp'], image_counts['fp']/total_count,
|
|
@@ -1353,11 +1438,11 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1353
1438
|
index_page += """
|
|
1354
1439
|
<h3>Detection results</h3>
|
|
1355
1440
|
<div class="contentdiv">
|
|
1356
|
-
<p>At a confidence threshold of {
|
|
1441
|
+
<p>At a confidence threshold of {}, precision={:0.1%}, recall={:0.1%}</p>
|
|
1357
1442
|
<p><strong>Precision/recall summary for all {} images</strong></p><img src="{}"><br/>
|
|
1358
1443
|
</div>
|
|
1359
1444
|
""".format(
|
|
1360
|
-
|
|
1445
|
+
confidence_threshold_string, precision_at_confidence_threshold, recall_at_confidence_threshold,
|
|
1361
1446
|
len(detections_df), pr_figure_relative_filename
|
|
1362
1447
|
)
|
|
1363
1448
|
|
|
@@ -1457,7 +1542,7 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1457
1542
|
detections_this_row = row['detections']
|
|
1458
1543
|
above_threshold_category_ids_this_row = set()
|
|
1459
1544
|
for detection in detections_this_row:
|
|
1460
|
-
threshold =
|
|
1545
|
+
threshold = _get_threshold_for_category_id(detection['category'], options, detection_categories)
|
|
1461
1546
|
if detection['conf'] >= threshold:
|
|
1462
1547
|
above_threshold_category_ids_this_row.add(detection['category'])
|
|
1463
1548
|
if len(above_threshold_category_ids_this_row) == 0:
|
|
@@ -1520,11 +1605,11 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1520
1605
|
print('Rendering images with {} {}'.format(options.parallelize_rendering_n_cores,
|
|
1521
1606
|
worker_string))
|
|
1522
1607
|
|
|
1523
|
-
#
|
|
1608
|
+
# _render_image_no_gt(file_info,detection_categories_to_results_name,
|
|
1524
1609
|
# detection_categories,classification_categories)
|
|
1525
1610
|
|
|
1526
1611
|
rendering_results = list(tqdm(pool.imap(
|
|
1527
|
-
partial(
|
|
1612
|
+
partial(_render_image_no_gt,
|
|
1528
1613
|
detection_categories_to_results_name=detection_categories_to_results_name,
|
|
1529
1614
|
detection_categories=detection_categories,
|
|
1530
1615
|
classification_categories=classification_categories,
|
|
@@ -1532,7 +1617,7 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1532
1617
|
files_to_render), total=len(files_to_render)))
|
|
1533
1618
|
else:
|
|
1534
1619
|
for file_info in tqdm(files_to_render):
|
|
1535
|
-
rendering_results.append(
|
|
1620
|
+
rendering_results.append(_render_image_no_gt(file_info,
|
|
1536
1621
|
detection_categories_to_results_name,
|
|
1537
1622
|
detection_categories,
|
|
1538
1623
|
classification_categories,
|
|
@@ -1556,7 +1641,7 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1556
1641
|
images_html[assignment[0]].append(assignment[1])
|
|
1557
1642
|
|
|
1558
1643
|
# Prepare the individual html image files
|
|
1559
|
-
image_counts =
|
|
1644
|
+
image_counts = _prepare_html_subpages(images_html, output_dir, options)
|
|
1560
1645
|
|
|
1561
1646
|
if image_rendered_count == 0:
|
|
1562
1647
|
seconds_per_image = 0.0
|
|
@@ -1589,7 +1674,7 @@ def process_batch_results(options: PostProcessingOptions
|
|
|
1589
1674
|
|
|
1590
1675
|
confidence_threshold_string = ''
|
|
1591
1676
|
if isinstance(options.confidence_threshold,float):
|
|
1592
|
-
confidence_threshold_string = '{:.
|
|
1677
|
+
confidence_threshold_string = '{:.2%}'.format(options.confidence_threshold)
|
|
1593
1678
|
else:
|
|
1594
1679
|
confidence_threshold_string = str(options.confidence_threshold)
|
|
1595
1680
|
|
|
@@ -1711,18 +1796,17 @@ if False:
|
|
|
1711
1796
|
|
|
1712
1797
|
#%%
|
|
1713
1798
|
|
|
1714
|
-
base_dir = r'
|
|
1799
|
+
base_dir = r'g:\temp'
|
|
1715
1800
|
options = PostProcessingOptions()
|
|
1716
1801
|
options.image_base_dir = base_dir
|
|
1717
|
-
options.output_dir = os.path.join(base_dir, '
|
|
1718
|
-
options.api_output_filename_replacements = {} # {'20190430cameratraps\\':''}
|
|
1719
|
-
options.ground_truth_filename_replacements = {} # {'\\data\\blob\\':''}
|
|
1802
|
+
options.output_dir = os.path.join(base_dir, 'preview')
|
|
1720
1803
|
options.api_output_file = os.path.join(base_dir, 'results.json')
|
|
1721
|
-
options.
|
|
1722
|
-
|
|
1804
|
+
options.confidence_threshold = {'person':0.5,'animal':0.5,'vehicle':0.01}
|
|
1805
|
+
options.include_almost_detections = True
|
|
1806
|
+
options.almost_detection_confidence_threshold = 0.001
|
|
1723
1807
|
|
|
1724
1808
|
ppresults = process_batch_results(options)
|
|
1725
|
-
#
|
|
1809
|
+
# from md_utils.path_utils import open_file; open_file(ppresults.output_html_file)
|
|
1726
1810
|
|
|
1727
1811
|
|
|
1728
1812
|
#%% Command-line driver
|