megadetector 5.0.11__py3-none-any.whl → 5.0.12__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 (201) hide show
  1. megadetector/api/__init__.py +0 -0
  2. megadetector/api/batch_processing/__init__.py +0 -0
  3. megadetector/api/batch_processing/api_core/__init__.py +0 -0
  4. megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
  5. megadetector/api/batch_processing/api_core/batch_service/score.py +439 -0
  6. megadetector/api/batch_processing/api_core/server.py +294 -0
  7. megadetector/api/batch_processing/api_core/server_api_config.py +98 -0
  8. megadetector/api/batch_processing/api_core/server_app_config.py +55 -0
  9. megadetector/api/batch_processing/api_core/server_batch_job_manager.py +220 -0
  10. megadetector/api/batch_processing/api_core/server_job_status_table.py +152 -0
  11. megadetector/api/batch_processing/api_core/server_orchestration.py +360 -0
  12. megadetector/api/batch_processing/api_core/server_utils.py +92 -0
  13. megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
  14. megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +46 -0
  15. megadetector/api/batch_processing/api_support/__init__.py +0 -0
  16. megadetector/api/batch_processing/api_support/summarize_daily_activity.py +152 -0
  17. megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
  18. megadetector/api/batch_processing/integration/digiKam/setup.py +6 -0
  19. megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +465 -0
  20. megadetector/api/batch_processing/integration/eMammal/test_scripts/config_template.py +5 -0
  21. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +126 -0
  22. megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +55 -0
  23. megadetector/api/synchronous/__init__.py +0 -0
  24. megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  25. megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +152 -0
  26. megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +266 -0
  27. megadetector/api/synchronous/api_core/animal_detection_api/config.py +35 -0
  28. megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
  29. megadetector/api/synchronous/api_core/tests/load_test.py +110 -0
  30. megadetector/classification/__init__.py +0 -0
  31. megadetector/classification/aggregate_classifier_probs.py +108 -0
  32. megadetector/classification/analyze_failed_images.py +227 -0
  33. megadetector/classification/cache_batchapi_outputs.py +198 -0
  34. megadetector/classification/create_classification_dataset.py +627 -0
  35. megadetector/classification/crop_detections.py +516 -0
  36. megadetector/classification/csv_to_json.py +226 -0
  37. megadetector/classification/detect_and_crop.py +855 -0
  38. megadetector/classification/efficientnet/__init__.py +9 -0
  39. megadetector/classification/efficientnet/model.py +415 -0
  40. megadetector/classification/efficientnet/utils.py +610 -0
  41. megadetector/classification/evaluate_model.py +520 -0
  42. megadetector/classification/identify_mislabeled_candidates.py +152 -0
  43. megadetector/classification/json_to_azcopy_list.py +63 -0
  44. megadetector/classification/json_validator.py +699 -0
  45. megadetector/classification/map_classification_categories.py +276 -0
  46. megadetector/classification/merge_classification_detection_output.py +506 -0
  47. megadetector/classification/prepare_classification_script.py +194 -0
  48. megadetector/classification/prepare_classification_script_mc.py +228 -0
  49. megadetector/classification/run_classifier.py +287 -0
  50. megadetector/classification/save_mislabeled.py +110 -0
  51. megadetector/classification/train_classifier.py +827 -0
  52. megadetector/classification/train_classifier_tf.py +725 -0
  53. megadetector/classification/train_utils.py +323 -0
  54. megadetector/data_management/__init__.py +0 -0
  55. megadetector/data_management/annotations/__init__.py +0 -0
  56. megadetector/data_management/annotations/annotation_constants.py +34 -0
  57. megadetector/data_management/camtrap_dp_to_coco.py +239 -0
  58. megadetector/data_management/cct_json_utils.py +395 -0
  59. megadetector/data_management/cct_to_md.py +176 -0
  60. megadetector/data_management/cct_to_wi.py +289 -0
  61. megadetector/data_management/coco_to_labelme.py +272 -0
  62. megadetector/data_management/coco_to_yolo.py +662 -0
  63. megadetector/data_management/databases/__init__.py +0 -0
  64. megadetector/data_management/databases/add_width_and_height_to_db.py +33 -0
  65. megadetector/data_management/databases/combine_coco_camera_traps_files.py +206 -0
  66. megadetector/data_management/databases/integrity_check_json_db.py +477 -0
  67. megadetector/data_management/databases/subset_json_db.py +115 -0
  68. megadetector/data_management/generate_crops_from_cct.py +149 -0
  69. megadetector/data_management/get_image_sizes.py +189 -0
  70. megadetector/data_management/importers/add_nacti_sizes.py +52 -0
  71. megadetector/data_management/importers/add_timestamps_to_icct.py +79 -0
  72. megadetector/data_management/importers/animl_results_to_md_results.py +158 -0
  73. megadetector/data_management/importers/auckland_doc_test_to_json.py +373 -0
  74. megadetector/data_management/importers/auckland_doc_to_json.py +201 -0
  75. megadetector/data_management/importers/awc_to_json.py +191 -0
  76. megadetector/data_management/importers/bellevue_to_json.py +273 -0
  77. megadetector/data_management/importers/cacophony-thermal-importer.py +796 -0
  78. megadetector/data_management/importers/carrizo_shrubfree_2018.py +269 -0
  79. megadetector/data_management/importers/carrizo_trail_cam_2017.py +289 -0
  80. megadetector/data_management/importers/cct_field_adjustments.py +58 -0
  81. megadetector/data_management/importers/channel_islands_to_cct.py +913 -0
  82. megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +180 -0
  83. megadetector/data_management/importers/eMammal/eMammal_helpers.py +249 -0
  84. megadetector/data_management/importers/eMammal/make_eMammal_json.py +223 -0
  85. megadetector/data_management/importers/ena24_to_json.py +276 -0
  86. megadetector/data_management/importers/filenames_to_json.py +386 -0
  87. megadetector/data_management/importers/helena_to_cct.py +283 -0
  88. megadetector/data_management/importers/idaho-camera-traps.py +1407 -0
  89. megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +294 -0
  90. megadetector/data_management/importers/jb_csv_to_json.py +150 -0
  91. megadetector/data_management/importers/mcgill_to_json.py +250 -0
  92. megadetector/data_management/importers/missouri_to_json.py +490 -0
  93. megadetector/data_management/importers/nacti_fieldname_adjustments.py +79 -0
  94. megadetector/data_management/importers/noaa_seals_2019.py +181 -0
  95. megadetector/data_management/importers/pc_to_json.py +365 -0
  96. megadetector/data_management/importers/plot_wni_giraffes.py +123 -0
  97. megadetector/data_management/importers/prepare-noaa-fish-data-for-lila.py +359 -0
  98. megadetector/data_management/importers/prepare_zsl_imerit.py +131 -0
  99. megadetector/data_management/importers/rspb_to_json.py +356 -0
  100. megadetector/data_management/importers/save_the_elephants_survey_A.py +320 -0
  101. megadetector/data_management/importers/save_the_elephants_survey_B.py +329 -0
  102. megadetector/data_management/importers/snapshot_safari_importer.py +758 -0
  103. megadetector/data_management/importers/snapshot_safari_importer_reprise.py +665 -0
  104. megadetector/data_management/importers/snapshot_serengeti_lila.py +1067 -0
  105. megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +150 -0
  106. megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +153 -0
  107. megadetector/data_management/importers/sulross_get_exif.py +65 -0
  108. megadetector/data_management/importers/timelapse_csv_set_to_json.py +490 -0
  109. megadetector/data_management/importers/ubc_to_json.py +399 -0
  110. megadetector/data_management/importers/umn_to_json.py +507 -0
  111. megadetector/data_management/importers/wellington_to_json.py +263 -0
  112. megadetector/data_management/importers/wi_to_json.py +442 -0
  113. megadetector/data_management/importers/zamba_results_to_md_results.py +181 -0
  114. megadetector/data_management/labelme_to_coco.py +547 -0
  115. megadetector/data_management/labelme_to_yolo.py +272 -0
  116. megadetector/data_management/lila/__init__.py +0 -0
  117. megadetector/data_management/lila/add_locations_to_island_camera_traps.py +97 -0
  118. megadetector/data_management/lila/add_locations_to_nacti.py +147 -0
  119. megadetector/data_management/lila/create_lila_blank_set.py +558 -0
  120. megadetector/data_management/lila/create_lila_test_set.py +152 -0
  121. megadetector/data_management/lila/create_links_to_md_results_files.py +106 -0
  122. megadetector/data_management/lila/download_lila_subset.py +178 -0
  123. megadetector/data_management/lila/generate_lila_per_image_labels.py +516 -0
  124. megadetector/data_management/lila/get_lila_annotation_counts.py +170 -0
  125. megadetector/data_management/lila/get_lila_image_counts.py +112 -0
  126. megadetector/data_management/lila/lila_common.py +300 -0
  127. megadetector/data_management/lila/test_lila_metadata_urls.py +132 -0
  128. megadetector/data_management/ocr_tools.py +874 -0
  129. megadetector/data_management/read_exif.py +681 -0
  130. megadetector/data_management/remap_coco_categories.py +84 -0
  131. megadetector/data_management/remove_exif.py +66 -0
  132. megadetector/data_management/resize_coco_dataset.py +189 -0
  133. megadetector/data_management/wi_download_csv_to_coco.py +246 -0
  134. megadetector/data_management/yolo_output_to_md_output.py +441 -0
  135. megadetector/data_management/yolo_to_coco.py +676 -0
  136. megadetector/detection/__init__.py +0 -0
  137. megadetector/detection/detector_training/__init__.py +0 -0
  138. megadetector/detection/detector_training/model_main_tf2.py +114 -0
  139. megadetector/detection/process_video.py +702 -0
  140. megadetector/detection/pytorch_detector.py +341 -0
  141. megadetector/detection/run_detector.py +779 -0
  142. megadetector/detection/run_detector_batch.py +1219 -0
  143. megadetector/detection/run_inference_with_yolov5_val.py +917 -0
  144. megadetector/detection/run_tiled_inference.py +934 -0
  145. megadetector/detection/tf_detector.py +189 -0
  146. megadetector/detection/video_utils.py +606 -0
  147. megadetector/postprocessing/__init__.py +0 -0
  148. megadetector/postprocessing/add_max_conf.py +64 -0
  149. megadetector/postprocessing/categorize_detections_by_size.py +163 -0
  150. megadetector/postprocessing/combine_api_outputs.py +249 -0
  151. megadetector/postprocessing/compare_batch_results.py +958 -0
  152. megadetector/postprocessing/convert_output_format.py +396 -0
  153. megadetector/postprocessing/load_api_results.py +195 -0
  154. megadetector/postprocessing/md_to_coco.py +310 -0
  155. megadetector/postprocessing/md_to_labelme.py +330 -0
  156. megadetector/postprocessing/merge_detections.py +401 -0
  157. megadetector/postprocessing/postprocess_batch_results.py +1902 -0
  158. megadetector/postprocessing/remap_detection_categories.py +170 -0
  159. megadetector/postprocessing/render_detection_confusion_matrix.py +660 -0
  160. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +211 -0
  161. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +83 -0
  162. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1631 -0
  163. megadetector/postprocessing/separate_detections_into_folders.py +730 -0
  164. megadetector/postprocessing/subset_json_detector_output.py +696 -0
  165. megadetector/postprocessing/top_folders_to_bottom.py +223 -0
  166. megadetector/taxonomy_mapping/__init__.py +0 -0
  167. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +491 -0
  168. megadetector/taxonomy_mapping/map_new_lila_datasets.py +150 -0
  169. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -0
  170. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +590 -0
  171. megadetector/taxonomy_mapping/retrieve_sample_image.py +71 -0
  172. megadetector/taxonomy_mapping/simple_image_download.py +219 -0
  173. megadetector/taxonomy_mapping/species_lookup.py +834 -0
  174. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +159 -0
  175. megadetector/taxonomy_mapping/taxonomy_graph.py +346 -0
  176. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +83 -0
  177. megadetector/utils/__init__.py +0 -0
  178. megadetector/utils/azure_utils.py +178 -0
  179. megadetector/utils/ct_utils.py +612 -0
  180. megadetector/utils/directory_listing.py +246 -0
  181. megadetector/utils/md_tests.py +968 -0
  182. megadetector/utils/path_utils.py +1044 -0
  183. megadetector/utils/process_utils.py +157 -0
  184. megadetector/utils/sas_blob_utils.py +509 -0
  185. megadetector/utils/split_locations_into_train_val.py +228 -0
  186. megadetector/utils/string_utils.py +92 -0
  187. megadetector/utils/url_utils.py +323 -0
  188. megadetector/utils/write_html_image_list.py +225 -0
  189. megadetector/visualization/__init__.py +0 -0
  190. megadetector/visualization/plot_utils.py +293 -0
  191. megadetector/visualization/render_images_with_thumbnails.py +275 -0
  192. megadetector/visualization/visualization_utils.py +1536 -0
  193. megadetector/visualization/visualize_db.py +550 -0
  194. megadetector/visualization/visualize_detector_output.py +405 -0
  195. {megadetector-5.0.11.dist-info → megadetector-5.0.12.dist-info}/METADATA +1 -1
  196. megadetector-5.0.12.dist-info/RECORD +199 -0
  197. megadetector-5.0.12.dist-info/top_level.txt +1 -0
  198. megadetector-5.0.11.dist-info/RECORD +0 -5
  199. megadetector-5.0.11.dist-info/top_level.txt +0 -1
  200. {megadetector-5.0.11.dist-info → megadetector-5.0.12.dist-info}/LICENSE +0 -0
  201. {megadetector-5.0.11.dist-info → megadetector-5.0.12.dist-info}/WHEEL +0 -0
@@ -0,0 +1,660 @@
1
+ """
2
+
3
+ render_detection_confusion_matrix.py
4
+
5
+ Given a CCT-formatted ground truth file and a MegaDetector-formatted results file,
6
+ render an HTML confusion matrix. Typically used for multi-class detectors. Currently
7
+ assumes a single class per image.
8
+
9
+ """
10
+
11
+ #%% Imports and constants
12
+
13
+ import os
14
+ import json
15
+
16
+ import matplotlib.pyplot as plt
17
+ import numpy as np
18
+
19
+ from tqdm import tqdm
20
+ from collections import defaultdict
21
+ from functools import partial
22
+
23
+ from megadetector.utils.path_utils import find_images
24
+ from megadetector.utils.path_utils import flatten_path
25
+ from megadetector.utils.write_html_image_list import write_html_image_list
26
+ from megadetector.visualization import visualization_utils as vis_utils
27
+ from megadetector.visualization import plot_utils
28
+
29
+ from multiprocessing.pool import ThreadPool
30
+ from multiprocessing.pool import Pool
31
+
32
+
33
+ #%% Support functions
34
+
35
+ def _image_to_output_file(im,preview_images_folder):
36
+ """
37
+ Produces a clean filename from im (if [im] is a str) or im['file'] (if [im] is a dict).
38
+ """
39
+
40
+ if isinstance(im,str):
41
+ filename_relative = im
42
+ else:
43
+ filename_relative = im['file']
44
+
45
+ fn_clean = flatten_path(filename_relative).replace(' ','_')
46
+ return os.path.join(preview_images_folder,fn_clean)
47
+
48
+
49
+ def _render_image(im,render_image_constants):
50
+ """
51
+ Internal function for rendering a single image to the confusion matrix preview folder.
52
+ """
53
+
54
+ filename_to_ground_truth_im = render_image_constants['filename_to_ground_truth_im']
55
+ image_folder = render_image_constants['image_folder']
56
+ preview_images_folder = render_image_constants['preview_images_folder']
57
+ force_render_images = render_image_constants['force_render_images']
58
+ results_category_id_to_name = render_image_constants['results_category_id_to_name']
59
+ rendering_confidence_thresholds = render_image_constants['rendering_confidence_thresholds']
60
+ target_image_size = render_image_constants['target_image_size']
61
+
62
+ assert im['file'] in filename_to_ground_truth_im
63
+
64
+ output_file = _image_to_output_file(im,preview_images_folder)
65
+ if os.path.isfile(output_file) and not force_render_images:
66
+ return output_file
67
+
68
+ input_file = os.path.join(image_folder,im['file'])
69
+ assert os.path.isfile(input_file)
70
+
71
+ detections_to_render = []
72
+
73
+ for det in im['detections']:
74
+ category_name = results_category_id_to_name[det['category']]
75
+ detection_threshold = rendering_confidence_thresholds['default']
76
+ if category_name in rendering_confidence_thresholds:
77
+ detection_threshold = rendering_confidence_thresholds[category_name]
78
+ if det['conf'] > detection_threshold:
79
+ detections_to_render.append(det)
80
+
81
+ vis_utils.draw_bounding_boxes_on_file(input_file, output_file, detections_to_render,
82
+ detector_label_map=results_category_id_to_name,
83
+ label_font_size=20,target_size=target_image_size)
84
+
85
+ return output_file
86
+
87
+
88
+ #%% Main function
89
+
90
+ def render_detection_confusion_matrix(ground_truth_file,
91
+ results_file,
92
+ image_folder,
93
+ preview_folder,
94
+ force_render_images=False,
95
+ confidence_thresholds=None,
96
+ rendering_confidence_thresholds=None,
97
+ target_image_size=(1280,-1),
98
+ parallelize_rendering=True,
99
+ parallelize_rendering_n_cores=None,
100
+ parallelize_rendering_with_threads=False,
101
+ job_name='unknown',
102
+ model_file=None,
103
+ empty_category_name='empty',
104
+ html_image_list_options=None):
105
+ """
106
+ Given a CCT-formatted ground truth file and a MegaDetector-formatted results file,
107
+ render an HTML confusion matrix in [preview_folder. Typically used for multi-class detectors.
108
+ Currently assumes a single class per image.
109
+
110
+ confidence_thresholds and rendering_confidence_thresholds are dictionaries mapping
111
+ class names to thresholds. "default" is a special token that will be used for all
112
+ classes not otherwise assigned thresholds.
113
+
114
+ Args:
115
+ ground_truth_file (str): the CCT-formatted .json file with ground truth information
116
+ results_file (str): the MegaDetector results .json file
117
+ image_folder (str): the folder where images live; filenames in [ground_truth_file] and
118
+ [results_file] should be relative to this folder.
119
+ preview_folder (str): the output folder, i.e. the folder in which we'll create our nifty
120
+ HTML stuff.
121
+ force_rendering_images (bool, optional): if False, skips images that already exist
122
+ confidence_thresholds (dict, optional): a dictionary mapping class names to thresholds;
123
+ all classes not explicitly named here will use the threshold for the "default" category.
124
+ rendering_thresholds (dict, optional): a dictionary mapping class names to thresholds;
125
+ all classes not explicitly named here will use the threshold for the "default" category.
126
+ target_image_size (tuple, optional): output image size, as a pair of ints (width,height). If one
127
+ value is -1 and the other is not, aspect ratio is preserved. If both are -1, the original image
128
+ sizes are preserved.
129
+ parallelize_rendering (bool, optional): enable (default) or disable parallelization when rendering
130
+ parallelize_rendering_n_core (int, optional): number of threads or processes to use for rendering, only
131
+ used if parallelize_rendering is True
132
+ parallelize_rendering_with_threads: whether to use threads (True) or processes (False) when rendering,
133
+ only used if parallelize_rendering is True
134
+ job_name (str, optional): job name to include in big letters in the output file
135
+ model_file (str, optional) model filename to include in HTML output
136
+ empty_category_name (str, optional): special category name that we should treat as empty, typically
137
+ "empty"
138
+ html_image_list_options (dict, optional): options listed passed along to write_html_image_list;
139
+ see write_html_image_list for documentation.
140
+ """
141
+
142
+ ##%% Argument and path handling
143
+
144
+ preview_images_folder = os.path.join(preview_folder,'images')
145
+ os.makedirs(preview_images_folder,exist_ok=True)
146
+
147
+ if confidence_thresholds is None:
148
+ confidence_thresholds = {'default':0.5}
149
+ if rendering_confidence_thresholds is None:
150
+ rendering_confidence_thresholds = {'default':0.4}
151
+
152
+
153
+ ##%% Load ground truth
154
+
155
+ with open(ground_truth_file,'r') as f:
156
+ ground_truth_data_cct = json.load(f)
157
+
158
+ filename_to_ground_truth_im = {}
159
+ for im in ground_truth_data_cct['images']:
160
+ assert im['file_name'] not in filename_to_ground_truth_im
161
+ filename_to_ground_truth_im[im['file_name']] = im
162
+
163
+
164
+ ##%% Confirm that the ground truth images are present in the image folder
165
+
166
+ ground_truth_images = find_images(image_folder,return_relative_paths=True,recursive=True)
167
+ assert len(ground_truth_images) == len(ground_truth_data_cct['images'])
168
+ del ground_truth_images
169
+
170
+
171
+ ##%% Map images to categories
172
+
173
+ # gt_image_id_to_image = {im['id']:im for im in ground_truth_data_cct['images']}
174
+ gt_image_id_to_annotations = defaultdict(list)
175
+
176
+ ground_truth_category_id_to_name = {}
177
+ for c in ground_truth_data_cct['categories']:
178
+ ground_truth_category_id_to_name[c['id']] = c['name']
179
+
180
+ # Add the empty category if necessary
181
+ if empty_category_name not in ground_truth_category_id_to_name.values():
182
+ empty_category_id = max(ground_truth_category_id_to_name.keys())+1
183
+ ground_truth_category_id_to_name[empty_category_id] = empty_category_name
184
+
185
+ ground_truth_category_names = sorted(list(ground_truth_category_id_to_name.values()))
186
+
187
+ for ann in ground_truth_data_cct['annotations']:
188
+ gt_image_id_to_annotations[ann['image_id']].append(ann)
189
+
190
+ gt_filename_to_category_names = defaultdict(set)
191
+
192
+ for im in ground_truth_data_cct['images']:
193
+ annotations_this_image = gt_image_id_to_annotations[im['id']]
194
+ for ann in annotations_this_image:
195
+ category_name = ground_truth_category_id_to_name[ann['category_id']]
196
+ gt_filename_to_category_names[im['file_name']].add(category_name)
197
+
198
+ for filename in gt_filename_to_category_names:
199
+
200
+ category_names_this_file = gt_filename_to_category_names[filename]
201
+
202
+ # The empty category should be exclusive
203
+ if empty_category_name in category_names_this_file:
204
+ assert len(category_names_this_file) == 1, \
205
+ 'Empty category assigned along with another category for {}'.format(filename)
206
+ assert len(category_names_this_file) > 0, \
207
+ 'No ground truth category assigned to {}'.format(filename)
208
+
209
+
210
+ ##%% Load results
211
+
212
+ with open(results_file,'r') as f:
213
+ md_formatted_results = json.load(f)
214
+
215
+ results_category_id_to_name = md_formatted_results['detection_categories']
216
+
217
+
218
+ ##%% Render images with detections
219
+
220
+ render_image_constants = {}
221
+ render_image_constants['filename_to_ground_truth_im'] = filename_to_ground_truth_im
222
+ render_image_constants['image_folder'] = image_folder
223
+ render_image_constants['preview_images_folder'] = preview_images_folder
224
+ render_image_constants['force_render_images'] = force_render_images
225
+ render_image_constants['results_category_id_to_name'] = results_category_id_to_name
226
+ render_image_constants['rendering_confidence_thresholds'] = rendering_confidence_thresholds
227
+ render_image_constants['target_image_size'] = target_image_size
228
+
229
+ if parallelize_rendering:
230
+
231
+ if parallelize_rendering_n_cores is None:
232
+ if parallelize_rendering_with_threads:
233
+ pool = ThreadPool()
234
+ else:
235
+ pool = Pool()
236
+ else:
237
+ if parallelize_rendering_with_threads:
238
+ pool = ThreadPool(parallelize_rendering_n_cores)
239
+ worker_string = 'threads'
240
+ else:
241
+ pool = Pool(parallelize_rendering_n_cores)
242
+ worker_string = 'processes'
243
+ print('Rendering images with {} {}'.format(parallelize_rendering_n_cores,
244
+ worker_string))
245
+
246
+ _ = list(tqdm(pool.imap(partial(_render_image,render_image_constants=render_image_constants),
247
+ md_formatted_results['images']),
248
+ total=len(md_formatted_results['images'])))
249
+
250
+ else:
251
+
252
+ # im = md_formatted_results['images'][0]
253
+ for im in tqdm(md_formatted_results['images']):
254
+ _render_image(im,render_image_constants)
255
+
256
+
257
+ ##%% Map images to predicted categories, and vice-versa
258
+
259
+ filename_to_predicted_categories = defaultdict(set)
260
+ predicted_category_name_to_filenames = defaultdict(set)
261
+
262
+ # im = md_formatted_results['images'][0]
263
+ for im in tqdm(md_formatted_results['images']):
264
+
265
+ assert im['file'] in filename_to_ground_truth_im
266
+
267
+ # det = im['detections'][0]
268
+ for det in im['detections']:
269
+ category_name = results_category_id_to_name[det['category']]
270
+ detection_threshold = confidence_thresholds['default']
271
+ if category_name in confidence_thresholds:
272
+ detection_threshold = confidence_thresholds[category_name]
273
+ if det['conf'] > detection_threshold:
274
+ filename_to_predicted_categories[im['file']].add(category_name)
275
+ predicted_category_name_to_filenames[category_name].add(im['file'])
276
+
277
+ # ...for each detection
278
+
279
+ # ...for each image
280
+
281
+
282
+ ##%% Create TP/TN/FP/FN lists
283
+
284
+ category_name_to_image_lists = {}
285
+
286
+ sub_page_tokens = ['fn','tn','fp','tp']
287
+
288
+ for category_name in ground_truth_category_names:
289
+
290
+ category_name_to_image_lists[category_name] = {}
291
+ for sub_page_token in sub_page_tokens:
292
+ category_name_to_image_lists[category_name][sub_page_token] = []
293
+
294
+ # filename = next(iter(gt_filename_to_category_names))
295
+ for filename in gt_filename_to_category_names.keys():
296
+
297
+ ground_truth_categories_this_image = gt_filename_to_category_names[filename]
298
+ predicted_categories_this_image = filename_to_predicted_categories[filename]
299
+
300
+ for category_name in ground_truth_category_names:
301
+
302
+ assignment = None
303
+
304
+ if category_name == empty_category_name:
305
+ # If this is an empty image
306
+ if category_name in ground_truth_categories_this_image:
307
+ assert len(ground_truth_categories_this_image) == 1
308
+ if len(predicted_categories_this_image) == 0:
309
+ assignment = 'tp'
310
+ else:
311
+ assignment = 'fn'
312
+ # If this not an empty image
313
+ else:
314
+ if len(predicted_categories_this_image) == 0:
315
+ assignment = 'fp'
316
+ else:
317
+ assignment = 'tn'
318
+
319
+ else:
320
+ if category_name in ground_truth_categories_this_image:
321
+ if category_name in predicted_categories_this_image:
322
+ assignment = 'tp'
323
+ else:
324
+ assignment = 'fn'
325
+ else:
326
+ if category_name in predicted_categories_this_image:
327
+ assignment = 'fp'
328
+ else:
329
+ assignment = 'tn'
330
+
331
+ category_name_to_image_lists[category_name][assignment].append(filename)
332
+
333
+ # ...for each filename
334
+
335
+
336
+ ##%% Create confusion matrix
337
+
338
+ gt_category_name_to_category_index = {}
339
+
340
+ for i_category,category_name in enumerate(ground_truth_category_names):
341
+ gt_category_name_to_category_index[category_name] = i_category
342
+
343
+ n_categories = len(gt_category_name_to_category_index)
344
+
345
+ # indexed as [true,predicted]
346
+ confusion_matrix = np.zeros(shape=(n_categories,n_categories),dtype=int)
347
+
348
+ filename_to_results_im = {im['file']:im for im in md_formatted_results['images']}
349
+
350
+ true_predicted_to_file_list = defaultdict(list)
351
+
352
+ # filename = next(iter(gt_filename_to_category_names.keys()))
353
+ for filename in gt_filename_to_category_names.keys():
354
+
355
+ ground_truth_categories_this_image = gt_filename_to_category_names[filename]
356
+ assert len(ground_truth_categories_this_image) == 1
357
+ ground_truth_category_name = next(iter(ground_truth_categories_this_image))
358
+
359
+ results_im = filename_to_results_im[filename]
360
+
361
+ # If there were no detections at all, call this image empty
362
+ if len(results_im['detections']) == 0:
363
+ predicted_category_name = empty_category_name
364
+ # Otherwise look for above-threshold detections
365
+ else:
366
+ results_category_name_to_confidence = defaultdict(int)
367
+ for det in results_im['detections']:
368
+ category_name = results_category_id_to_name[det['category']]
369
+ detection_threshold = confidence_thresholds['default']
370
+ if category_name in confidence_thresholds:
371
+ detection_threshold = confidence_thresholds[category_name]
372
+ if det['conf'] > detection_threshold:
373
+ results_category_name_to_confidence[category_name] = max(
374
+ results_category_name_to_confidence[category_name],det['conf'])
375
+ # If there were no detections above threshold
376
+ if len(results_category_name_to_confidence) == 0:
377
+ predicted_category_name = empty_category_name
378
+ else:
379
+ predicted_category_name = max(results_category_name_to_confidence,
380
+ key=results_category_name_to_confidence.get)
381
+
382
+ ground_truth_category_index = gt_category_name_to_category_index[ground_truth_category_name]
383
+ predicted_category_index = gt_category_name_to_category_index[predicted_category_name]
384
+
385
+ true_predicted_token = ground_truth_category_name + '_' + predicted_category_name
386
+ true_predicted_to_file_list[true_predicted_token].append(filename)
387
+
388
+ confusion_matrix[ground_truth_category_index,predicted_category_index] += 1
389
+
390
+ # ...for each file
391
+
392
+ plt.ioff()
393
+
394
+ fig_h = 3 + 0.3 * n_categories
395
+ fig_w = fig_h
396
+ fig = plt.figure(figsize=(fig_w, fig_h),tight_layout=True)
397
+
398
+ plot_utils.plot_confusion_matrix(
399
+ matrix=confusion_matrix,
400
+ classes=ground_truth_category_names,
401
+ normalize=False,
402
+ title='Confusion matrix',
403
+ cmap=plt.cm.Blues,
404
+ vmax=1.0,
405
+ use_colorbar=False,
406
+ y_label=True,
407
+ fig=fig)
408
+
409
+ cm_figure_fn_relative = 'confusion_matrix.png'
410
+ cm_figure_fn_abs = os.path.join(preview_folder, cm_figure_fn_relative)
411
+ # fig.show()
412
+ fig.savefig(cm_figure_fn_abs,dpi=100)
413
+ plt.close(fig)
414
+
415
+ # open_file(cm_figure_fn_abs)
416
+
417
+
418
+ ##%% Create HTML confusion matrix
419
+
420
+ html_confusion_matrix = '<table class="result-table">\n'
421
+ html_confusion_matrix += '<tr>\n'
422
+ html_confusion_matrix += '<td>{}</td>\n'.format('True category')
423
+ for category_name in ground_truth_category_names:
424
+ html_confusion_matrix += '<td>{}</td>\n'.format('&nbsp;')
425
+ html_confusion_matrix += '</tr>\n'
426
+
427
+ for true_category in ground_truth_category_names:
428
+
429
+ html_confusion_matrix += '<tr>\n'
430
+ html_confusion_matrix += '<td>{}</td>\n'.format(true_category)
431
+
432
+ for predicted_category in ground_truth_category_names:
433
+
434
+ true_predicted_token = true_category + '_' + predicted_category
435
+ image_list = true_predicted_to_file_list[true_predicted_token]
436
+ if len(image_list) == 0:
437
+ td_content = '0'
438
+ else:
439
+ if html_image_list_options is None:
440
+ html_image_list_options = {}
441
+ title_string = 'true: {}, predicted {}'.format(
442
+ true_category,predicted_category)
443
+ html_image_list_options['headerHtml'] = '<h1>{}</h1>'.format(title_string)
444
+
445
+ html_image_info_list = []
446
+
447
+ for image_filename_relative in image_list:
448
+ html_image_info = {}
449
+ detections = filename_to_results_im[image_filename_relative]['detections']
450
+ if len(detections) == 0:
451
+ max_conf = 0
452
+ else:
453
+ max_conf = max([d['conf'] for d in detections])
454
+
455
+ title = '<b>Image</b>: {}, <b>Max conf</b>: {:0.3f}'.format(
456
+ image_filename_relative, max_conf)
457
+ image_link = 'images/' + os.path.basename(
458
+ _image_to_output_file(image_filename_relative,preview_images_folder))
459
+ html_image_info = {
460
+ 'filename': image_link,
461
+ 'title': title,
462
+ 'textStyle':\
463
+ 'font-family:verdana,arial,calibri;font-size:80%;' + \
464
+ 'text-align:left;margin-top:20;margin-bottom:5'
465
+ }
466
+
467
+ html_image_info_list.append(html_image_info)
468
+
469
+ target_html_file_relative = true_predicted_token + '.html'
470
+ target_html_file_abs = os.path.join(preview_folder,target_html_file_relative)
471
+ write_html_image_list(
472
+ filename=target_html_file_abs,
473
+ images=html_image_info_list,
474
+ options=html_image_list_options)
475
+
476
+ td_content = '<a href="{}">{}</a>'.format(target_html_file_relative,
477
+ len(image_list))
478
+
479
+ html_confusion_matrix += '<td>{}</td>\n'.format(td_content)
480
+
481
+ # ...for each predicted category
482
+
483
+ html_confusion_matrix += '</tr>\n'
484
+
485
+ # ...for each true category
486
+
487
+ html_confusion_matrix += '<tr>\n'
488
+ html_confusion_matrix += '<td>&nbsp;</td>\n'
489
+
490
+ for category_name in ground_truth_category_names:
491
+ html_confusion_matrix += '<td class="rotate"><p style="margin-left:20px;">{}</p></td>\n'.format(
492
+ category_name)
493
+ html_confusion_matrix += '</tr>\n'
494
+
495
+ html_confusion_matrix += '</table>'
496
+
497
+
498
+ ##%% Create HTML sub-pages and HTML table
499
+
500
+ html_table = '<table class="result-table">\n'
501
+
502
+ html_table += '<tr>\n'
503
+ html_table += '<td>{}</td>\n'.format('True category')
504
+ for sub_page_token in sub_page_tokens:
505
+ html_table += '<td>{}</td>'.format(sub_page_token)
506
+ html_table += '</tr>\n'
507
+
508
+ filename_to_results_im = {im['file']:im for im in md_formatted_results['images']}
509
+
510
+ sub_page_token_to_page_name = {
511
+ 'fp':'false positives',
512
+ 'tp':'true positives',
513
+ 'fn':'false negatives',
514
+ 'tn':'true negatives'
515
+ }
516
+
517
+ # category_name = ground_truth_category_names[0]
518
+ for category_name in ground_truth_category_names:
519
+
520
+ html_table += '<tr>\n'
521
+
522
+ html_table += '<td>{}</td>\n'.format(category_name)
523
+
524
+ # sub_page_token = sub_page_tokens[0]
525
+ for sub_page_token in sub_page_tokens:
526
+
527
+ html_table += '<td>\n'
528
+
529
+ image_list = category_name_to_image_lists[category_name][sub_page_token]
530
+
531
+ if len(image_list) == 0:
532
+
533
+ html_table += '0\n'
534
+
535
+ else:
536
+
537
+ html_image_list_options = {}
538
+ title_string = '{}: {}'.format(category_name,sub_page_token_to_page_name[sub_page_token])
539
+ html_image_list_options['headerHtml'] = '<h1>{}</h1>'.format(title_string)
540
+
541
+ target_html_file_relative = '{}_{}.html'.format(category_name,sub_page_token)
542
+ target_html_file_abs = os.path.join(preview_folder,target_html_file_relative)
543
+
544
+ html_image_info_list = []
545
+
546
+ # image_filename_relative = image_list[0]
547
+ for image_filename_relative in image_list:
548
+
549
+ source_file = os.path.join(image_folder,image_filename_relative)
550
+ assert os.path.isfile(source_file)
551
+
552
+ html_image_info = {}
553
+ detections = filename_to_results_im[image_filename_relative]['detections']
554
+ if len(detections) == 0:
555
+ max_conf = 0
556
+ else:
557
+ max_conf = max([d['conf'] for d in detections])
558
+
559
+ title = '<b>Image</b>: {}, <b>Max conf</b>: {:0.3f}'.format(
560
+ image_filename_relative, max_conf)
561
+ image_link = 'images/' + os.path.basename(
562
+ _image_to_output_file(image_filename_relative,preview_images_folder))
563
+ html_image_info = {
564
+ 'filename': image_link,
565
+ 'title': title,
566
+ 'linkTarget': source_file,
567
+ 'textStyle':\
568
+ 'font-family:verdana,arial,calibri;font-size:80%;' + \
569
+ 'text-align:left;margin-top:20;margin-bottom:5'
570
+ }
571
+
572
+ html_image_info_list.append(html_image_info)
573
+
574
+ # ...for each image
575
+
576
+ write_html_image_list(
577
+ filename=target_html_file_abs,
578
+ images=html_image_info_list,
579
+ options=html_image_list_options)
580
+
581
+ html_table += '<a href="{}">{}</a>\n'.format(target_html_file_relative,len(image_list))
582
+
583
+ html_table += '</td>\n'
584
+
585
+ # ...for each sub-page
586
+
587
+ html_table += '</tr>\n'
588
+
589
+ # ...for each category
590
+
591
+ html_table += '</table>'
592
+
593
+ html = '<html>\n'
594
+
595
+ style_header = """<head>
596
+ <style type="text/css">
597
+ a { text-decoration: none; }
598
+ body { font-family: segoe ui, calibri, "trebuchet ms", verdana, arial, sans-serif; }
599
+ div.contentdiv { margin-left: 20px; }
600
+ table.result-table { border:1px solid black; border-collapse: collapse; margin-left:50px;}
601
+ td,th { padding:10px; }
602
+ .rotate {
603
+ padding:0px;
604
+ writing-mode:vertical-lr;
605
+ -webkit-transform: rotate(-180deg);
606
+ -moz-transform: rotate(-180deg);
607
+ -ms-transform: rotate(-180deg);
608
+ -o-transform: rotate(-180deg);
609
+ transform: rotate(-180deg);
610
+ }
611
+ </style>
612
+ </head>"""
613
+
614
+ html += style_header + '\n'
615
+
616
+ html += '<body>\n'
617
+
618
+ html += '<h1>Results summary for {}</h1>\n'.format(job_name)
619
+
620
+ if model_file is not None and len(model_file) > 0:
621
+ html += '<p><b>Model file</b>: {}</p>'.format(os.path.basename(model_file))
622
+
623
+ html += '<p><b>Confidence thresholds</b></p>'
624
+
625
+ for c in confidence_thresholds.keys():
626
+ html += '<p style="margin-left:15px;">{}: {}</p>'.format(c,confidence_thresholds[c])
627
+
628
+ html += '<h2>Confusion matrix</h2>\n'
629
+
630
+ html += '<p>...assuming a single category per image.</p>\n'
631
+
632
+ html += '<img src="{}"/>\n'.format(cm_figure_fn_relative)
633
+
634
+ html += '<h2>Confusion matrix (with links)</h2>\n'
635
+
636
+ html += '<p>...assuming a single category per image.</p>\n'
637
+
638
+ html += html_confusion_matrix
639
+
640
+ html += '<h2>Per-class statistics</h2>\n'
641
+
642
+ html += html_table
643
+
644
+ html += '</body>\n'
645
+ html += '<html>\n'
646
+
647
+ target_html_file = os.path.join(preview_folder,'index.html')
648
+
649
+ with open(target_html_file,'w') as f:
650
+ f.write(html)
651
+
652
+
653
+ ##%% Prepare return data
654
+
655
+ confusion_matrix_info = {}
656
+ confusion_matrix_info['html_file'] = target_html_file
657
+
658
+ return confusion_matrix_info
659
+
660
+ # ...render_detection_confusion_matrix(...)