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