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.

Files changed (191) hide show
  1. api/__init__.py +0 -0
  2. api/batch_processing/__init__.py +0 -0
  3. api/batch_processing/api_core/__init__.py +0 -0
  4. api/batch_processing/api_core/batch_service/__init__.py +0 -0
  5. api/batch_processing/api_core/batch_service/score.py +0 -1
  6. api/batch_processing/api_core/server_job_status_table.py +0 -1
  7. api/batch_processing/api_core_support/__init__.py +0 -0
  8. api/batch_processing/api_core_support/aggregate_results_manually.py +0 -1
  9. api/batch_processing/api_support/__init__.py +0 -0
  10. api/batch_processing/api_support/summarize_daily_activity.py +0 -1
  11. api/batch_processing/data_preparation/__init__.py +0 -0
  12. api/batch_processing/data_preparation/manage_local_batch.py +93 -79
  13. api/batch_processing/data_preparation/manage_video_batch.py +8 -8
  14. api/batch_processing/integration/digiKam/xmp_integration.py +0 -1
  15. api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -1
  16. api/batch_processing/postprocessing/__init__.py +0 -0
  17. api/batch_processing/postprocessing/add_max_conf.py +12 -12
  18. api/batch_processing/postprocessing/categorize_detections_by_size.py +32 -14
  19. api/batch_processing/postprocessing/combine_api_outputs.py +69 -55
  20. api/batch_processing/postprocessing/compare_batch_results.py +114 -44
  21. api/batch_processing/postprocessing/convert_output_format.py +62 -19
  22. api/batch_processing/postprocessing/load_api_results.py +17 -20
  23. api/batch_processing/postprocessing/md_to_coco.py +31 -21
  24. api/batch_processing/postprocessing/md_to_labelme.py +165 -68
  25. api/batch_processing/postprocessing/merge_detections.py +40 -15
  26. api/batch_processing/postprocessing/postprocess_batch_results.py +270 -186
  27. api/batch_processing/postprocessing/remap_detection_categories.py +170 -0
  28. api/batch_processing/postprocessing/render_detection_confusion_matrix.py +75 -39
  29. api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +53 -44
  30. api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +25 -14
  31. api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +244 -160
  32. api/batch_processing/postprocessing/separate_detections_into_folders.py +159 -114
  33. api/batch_processing/postprocessing/subset_json_detector_output.py +146 -169
  34. api/batch_processing/postprocessing/top_folders_to_bottom.py +77 -43
  35. api/synchronous/__init__.py +0 -0
  36. api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  37. api/synchronous/api_core/animal_detection_api/api_backend.py +0 -2
  38. api/synchronous/api_core/animal_detection_api/api_frontend.py +266 -268
  39. api/synchronous/api_core/animal_detection_api/config.py +35 -35
  40. api/synchronous/api_core/tests/__init__.py +0 -0
  41. api/synchronous/api_core/tests/load_test.py +109 -109
  42. classification/__init__.py +0 -0
  43. classification/aggregate_classifier_probs.py +21 -24
  44. classification/analyze_failed_images.py +11 -13
  45. classification/cache_batchapi_outputs.py +51 -51
  46. classification/create_classification_dataset.py +69 -68
  47. classification/crop_detections.py +54 -53
  48. classification/csv_to_json.py +97 -100
  49. classification/detect_and_crop.py +105 -105
  50. classification/evaluate_model.py +43 -42
  51. classification/identify_mislabeled_candidates.py +47 -46
  52. classification/json_to_azcopy_list.py +10 -10
  53. classification/json_validator.py +72 -71
  54. classification/map_classification_categories.py +44 -43
  55. classification/merge_classification_detection_output.py +68 -68
  56. classification/prepare_classification_script.py +157 -154
  57. classification/prepare_classification_script_mc.py +228 -228
  58. classification/run_classifier.py +27 -26
  59. classification/save_mislabeled.py +30 -30
  60. classification/train_classifier.py +20 -20
  61. classification/train_classifier_tf.py +21 -22
  62. classification/train_utils.py +10 -10
  63. data_management/__init__.py +0 -0
  64. data_management/annotations/__init__.py +0 -0
  65. data_management/annotations/annotation_constants.py +18 -31
  66. data_management/camtrap_dp_to_coco.py +238 -0
  67. data_management/cct_json_utils.py +107 -59
  68. data_management/cct_to_md.py +176 -158
  69. data_management/cct_to_wi.py +247 -219
  70. data_management/coco_to_labelme.py +272 -0
  71. data_management/coco_to_yolo.py +86 -62
  72. data_management/databases/__init__.py +0 -0
  73. data_management/databases/add_width_and_height_to_db.py +20 -16
  74. data_management/databases/combine_coco_camera_traps_files.py +35 -31
  75. data_management/databases/integrity_check_json_db.py +130 -83
  76. data_management/databases/subset_json_db.py +25 -16
  77. data_management/generate_crops_from_cct.py +27 -45
  78. data_management/get_image_sizes.py +188 -144
  79. data_management/importers/add_nacti_sizes.py +8 -8
  80. data_management/importers/add_timestamps_to_icct.py +78 -78
  81. data_management/importers/animl_results_to_md_results.py +158 -160
  82. data_management/importers/auckland_doc_test_to_json.py +9 -9
  83. data_management/importers/auckland_doc_to_json.py +8 -8
  84. data_management/importers/awc_to_json.py +7 -7
  85. data_management/importers/bellevue_to_json.py +15 -15
  86. data_management/importers/cacophony-thermal-importer.py +13 -13
  87. data_management/importers/carrizo_shrubfree_2018.py +8 -8
  88. data_management/importers/carrizo_trail_cam_2017.py +8 -8
  89. data_management/importers/cct_field_adjustments.py +9 -9
  90. data_management/importers/channel_islands_to_cct.py +10 -10
  91. data_management/importers/eMammal/copy_and_unzip_emammal.py +1 -0
  92. data_management/importers/ena24_to_json.py +7 -7
  93. data_management/importers/filenames_to_json.py +8 -8
  94. data_management/importers/helena_to_cct.py +7 -7
  95. data_management/importers/idaho-camera-traps.py +7 -7
  96. data_management/importers/idfg_iwildcam_lila_prep.py +10 -10
  97. data_management/importers/jb_csv_to_json.py +9 -9
  98. data_management/importers/mcgill_to_json.py +8 -8
  99. data_management/importers/missouri_to_json.py +18 -18
  100. data_management/importers/nacti_fieldname_adjustments.py +10 -10
  101. data_management/importers/noaa_seals_2019.py +8 -8
  102. data_management/importers/pc_to_json.py +7 -7
  103. data_management/importers/plot_wni_giraffes.py +7 -7
  104. data_management/importers/prepare-noaa-fish-data-for-lila.py +359 -359
  105. data_management/importers/prepare_zsl_imerit.py +7 -7
  106. data_management/importers/rspb_to_json.py +8 -8
  107. data_management/importers/save_the_elephants_survey_A.py +8 -8
  108. data_management/importers/save_the_elephants_survey_B.py +9 -9
  109. data_management/importers/snapshot_safari_importer.py +26 -26
  110. data_management/importers/snapshot_safari_importer_reprise.py +665 -665
  111. data_management/importers/snapshot_serengeti_lila.py +14 -14
  112. data_management/importers/sulross_get_exif.py +8 -9
  113. data_management/importers/timelapse_csv_set_to_json.py +11 -11
  114. data_management/importers/ubc_to_json.py +13 -13
  115. data_management/importers/umn_to_json.py +7 -7
  116. data_management/importers/wellington_to_json.py +8 -8
  117. data_management/importers/wi_to_json.py +9 -9
  118. data_management/importers/zamba_results_to_md_results.py +181 -181
  119. data_management/labelme_to_coco.py +309 -159
  120. data_management/labelme_to_yolo.py +103 -60
  121. data_management/lila/__init__.py +0 -0
  122. data_management/lila/add_locations_to_island_camera_traps.py +9 -9
  123. data_management/lila/add_locations_to_nacti.py +147 -147
  124. data_management/lila/create_lila_blank_set.py +114 -31
  125. data_management/lila/create_lila_test_set.py +8 -8
  126. data_management/lila/create_links_to_md_results_files.py +106 -106
  127. data_management/lila/download_lila_subset.py +92 -90
  128. data_management/lila/generate_lila_per_image_labels.py +56 -43
  129. data_management/lila/get_lila_annotation_counts.py +18 -15
  130. data_management/lila/get_lila_image_counts.py +11 -11
  131. data_management/lila/lila_common.py +103 -70
  132. data_management/lila/test_lila_metadata_urls.py +132 -116
  133. data_management/ocr_tools.py +173 -128
  134. data_management/read_exif.py +161 -99
  135. data_management/remap_coco_categories.py +84 -0
  136. data_management/remove_exif.py +58 -62
  137. data_management/resize_coco_dataset.py +32 -44
  138. data_management/wi_download_csv_to_coco.py +246 -0
  139. data_management/yolo_output_to_md_output.py +86 -73
  140. data_management/yolo_to_coco.py +535 -95
  141. detection/__init__.py +0 -0
  142. detection/detector_training/__init__.py +0 -0
  143. detection/process_video.py +85 -33
  144. detection/pytorch_detector.py +43 -25
  145. detection/run_detector.py +157 -72
  146. detection/run_detector_batch.py +189 -114
  147. detection/run_inference_with_yolov5_val.py +118 -51
  148. detection/run_tiled_inference.py +113 -42
  149. detection/tf_detector.py +51 -28
  150. detection/video_utils.py +606 -521
  151. docs/source/conf.py +43 -0
  152. md_utils/__init__.py +0 -0
  153. md_utils/azure_utils.py +9 -9
  154. md_utils/ct_utils.py +249 -70
  155. md_utils/directory_listing.py +59 -64
  156. md_utils/md_tests.py +968 -862
  157. md_utils/path_utils.py +655 -155
  158. md_utils/process_utils.py +157 -133
  159. md_utils/sas_blob_utils.py +20 -20
  160. md_utils/split_locations_into_train_val.py +45 -32
  161. md_utils/string_utils.py +33 -10
  162. md_utils/url_utils.py +208 -27
  163. md_utils/write_html_image_list.py +51 -35
  164. md_visualization/__init__.py +0 -0
  165. md_visualization/plot_utils.py +102 -109
  166. md_visualization/render_images_with_thumbnails.py +34 -34
  167. md_visualization/visualization_utils.py +908 -311
  168. md_visualization/visualize_db.py +109 -58
  169. md_visualization/visualize_detector_output.py +61 -42
  170. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/METADATA +21 -17
  171. megadetector-5.0.9.dist-info/RECORD +224 -0
  172. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/WHEEL +1 -1
  173. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/top_level.txt +1 -0
  174. taxonomy_mapping/__init__.py +0 -0
  175. taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +342 -335
  176. taxonomy_mapping/map_new_lila_datasets.py +154 -154
  177. taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -134
  178. taxonomy_mapping/preview_lila_taxonomy.py +591 -591
  179. taxonomy_mapping/retrieve_sample_image.py +12 -12
  180. taxonomy_mapping/simple_image_download.py +11 -11
  181. taxonomy_mapping/species_lookup.py +10 -10
  182. taxonomy_mapping/taxonomy_csv_checker.py +18 -18
  183. taxonomy_mapping/taxonomy_graph.py +47 -47
  184. taxonomy_mapping/validate_lila_category_mappings.py +83 -76
  185. data_management/cct_json_to_filename_json.py +0 -89
  186. data_management/cct_to_csv.py +0 -140
  187. data_management/databases/remove_corrupted_images_from_db.py +0 -191
  188. detection/detector_training/copy_checkpoints.py +0 -43
  189. md_visualization/visualize_megadb.py +0 -183
  190. megadetector-5.0.7.dist-info/RECORD +0 -202
  191. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/LICENSE +0 -0
@@ -1,20 +1,21 @@
1
- ########
2
- #
3
- # postprocess_batch_results.py
4
- #
5
- # Given a .json or .csv file representing the output from the batch detection API,
6
- # do one or more of the following:
7
- #
8
- # * Evaluate detector precision/recall, optionally rendering results (requires
9
- # ground truth)
10
- # * Sample true/false positives/negatives and render to HTML (requires ground
11
- # truth)
12
- # * Sample detections/non-detections and render to HTML (when ground truth isn't
13
- # available)
14
- #
15
- # Ground truth, if available, must be in the COCO Camera Traps format.
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 has_overlap(DEFAULT_NEGATIVE_CLASSES, DEFAULT_UNKNOWN_CLASSES), (
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
- api_output_file = ''
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
- # Can be a folder or a SAS URL
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
- # Classes we'll treat as negative
100
- #
101
- # Include the token "#NO_LABELS#" to indicate that an image with no annotations
102
- # should be considered empty.
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
- # Classes we'll treat as neither positive nor negative
101
+ #: Classes we'll treat as neither positive nor negative
106
102
  unlabeled_classes = DEFAULT_UNKNOWN_CLASSES
107
103
 
108
- # A list of output sets that we should count, but not render images for.
109
- #
110
- # Typically used to preview sets with lots of empties, where you don't want to
111
- # subset but also don't want to render 100,000 empty images.
112
- #
113
- # detections, non_detections
114
- # detections_animal, detections_person, detections_vehicle
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
- # If this is None, choose a confidence threshold based on the detector version.
118
- #
119
- # This can either be a float or a dictionary mapping category names (not IDs) to
120
- # thresholds. The category "default" can be used to specify thresholds for
121
- # other categories. Currently the use of a dict here is not supported when
122
- # ground truth is supplied.
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
- # Confidence threshold to apply to classification (not detection) results
126
- #
127
- # Only a float is supported here (unlike the "confidence_threshold" parameter, which
128
- # can be a dict).
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
- # Used for summary statistics only
127
+ #: Used for summary statistics only
132
128
  target_recall = 0.9
133
129
 
134
- # Number of images to sample, -1 for "all images"
130
+ #: Number of images to sample, -1 for "all images"
135
131
  num_images_to_sample = 500
136
132
 
137
- # Random seed for sampling, or None
138
- sample_seed: Optional[int] = 0 # None
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
- # Sort order for the output, should be one of "filename", "confidence", or "random"
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
- # Optionally separate detections into categories (animal/vehicle/human)
154
- #
155
- # Currently only supported when ground truth is unavailable
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
- # Optionally replace one or more strings in filenames with other strings;
159
- # useful for taking a set of results generated for one folder structure
160
- # and applying them to a slightly different folder structure.
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
- # Allow bypassing API output loading when operating on previously-loaded
165
- # results
166
- api_detection_results: Optional[pd.DataFrame] = None
167
- api_other_fields: Optional[Dict[str, Any]] = None
168
-
169
- # Should we also split out a separate report about the detections that were
170
- # just below our main confidence threshold?
171
- #
172
- # Currently only supported when ground truth is unavailable
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
- # Only a float is supported here (unlike the "confidence_threshold" parameter, which
176
- # can be a dict).
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
- # Control rendering parallelization
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
- # Should we split individual pages up into smaller pages if there are more than
187
- # N images?
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
- api_detection_results: Optional[pd.DataFrame] = None
197
- api_other_fields: Optional[Dict[str, Any]] = None
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 mark_detection_status(
229
- indexed_db: IndexedJsonDb,
230
- negative_classes: Iterable[str] = DEFAULT_NEGATIVE_CLASSES,
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 = has_overlap(category_names, unknown_classes)
265
- has_negative_labels = has_overlap(category_names, negative_classes)
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
- # ...mark_detection_status()
345
+ # ..._mark_detection_status()
321
346
 
322
347
 
323
- def is_sas_url(s: str) -> bool:
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: str, relative_path: str) -> Optional[str]:
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 render_bounding_boxes(
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
- get_threshold_for_category_id(category_id, options, detection_categories)
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
- info['linkTarget'] = urllib.parse.quote(image_full_path)
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
- # ...render_bounding_boxes
532
+ # ..._render_bounding_boxes
492
533
 
493
534
 
494
- def prepare_html_subpages(images_html, output_dir, options=None):
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
- # ...prepare_html_subpages()
601
+ # ..._prepare_html_subpages()
561
602
 
562
603
 
563
- # Determine the confidence threshold we should use for a specific category name
564
- def get_threshold_for_category_name(category_name,options):
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
- # Determine the confidence threshold we should use for a specific category ID
584
- #
585
- # detection_categories is a dict mapping category IDs to names.
586
- def get_threshold_for_category_id(category_id,options,detection_categories):
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 get_threshold_for_category_name(category_name,options)
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 = get_threshold_for_category_id(d['category'], options, detection_categories)
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
- # Determine whether any positive detections are present in the detection list
612
- # [detections].
613
- def has_positive_detection(detections,options,detection_categories):
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 = get_threshold_for_category_id(d['category'], options, detection_categories)
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
- # Render an image (with no ground truth information)
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 = has_positive_detection(detections,options,detection_categories)
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(get_positive_categories(detections,options,detection_categories))
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 = render_bounding_boxes(
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 render_bounding_boxes()
793
+ # ...if we got valid rendering info back from _render_bounding_boxes()
742
794
 
743
795
  return image_result
744
796
 
745
- # ...def render_image_no_gt()
797
+ # ...def _render_image_no_gt()
746
798
 
747
799
 
748
- # Render an image with ground truth information. See render_image_no_gt for return
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.annotations_to_classnames(
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 = has_positive_detection(detections, options, detection_categories)
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 = render_bounding_boxes(
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 render_image_with_gt()
880
+ # ...def _render_image_with_gt()
827
881
 
828
882
 
829
883
  #%% Main function
830
884
 
831
- def process_batch_results(options: PostProcessingOptions
832
- ) -> PostProcessingResults:
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 = mark_detection_status(
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, normalize_paths=True,
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 has_positive_detection(detections, options, detection_categories):
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
- str(options.confidence_threshold), precision_at_confidence_threshold,
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(render_image_with_gt,
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(render_image_with_gt(
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 = prepare_html_subpages(images_html, output_dir, options)
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 {:.1%} confidence.</p>
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, str(options.confidence_threshold),
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 {:0.1%}, precision={:0.1%}, recall={:0.1%}</p>
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
- str(options.confidence_threshold), precision_at_confidence_threshold, recall_at_confidence_threshold,
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 = get_threshold_for_category_id(detection['category'], options, detection_categories)
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
- # render_image_no_gt(file_info,detection_categories_to_results_name,
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(render_image_no_gt,
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(render_image_no_gt(file_info,
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 = prepare_html_subpages(images_html, output_dir, options)
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 = '{:.1%}'.format(options.confidence_threshold)
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'G:\temp\md'
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, 'postprocessing')
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.ground_truth_json_file = os.path.join(base_dir, 'gt.json')
1722
- # options.unlabeled_classes = ['human']
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
- # os.start(ppresults.output_html_file)
1809
+ # from md_utils.path_utils import open_file; open_file(ppresults.output_html_file)
1726
1810
 
1727
1811
 
1728
1812
  #%% Command-line driver