megadetector 5.0.9__py3-none-any.whl → 5.0.11__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 (226) hide show
  1. {megadetector-5.0.9.dist-info → megadetector-5.0.11.dist-info}/LICENSE +0 -0
  2. {megadetector-5.0.9.dist-info → megadetector-5.0.11.dist-info}/METADATA +12 -11
  3. megadetector-5.0.11.dist-info/RECORD +5 -0
  4. megadetector-5.0.11.dist-info/top_level.txt +1 -0
  5. api/__init__.py +0 -0
  6. api/batch_processing/__init__.py +0 -0
  7. api/batch_processing/api_core/__init__.py +0 -0
  8. api/batch_processing/api_core/batch_service/__init__.py +0 -0
  9. api/batch_processing/api_core/batch_service/score.py +0 -439
  10. api/batch_processing/api_core/server.py +0 -294
  11. api/batch_processing/api_core/server_api_config.py +0 -98
  12. api/batch_processing/api_core/server_app_config.py +0 -55
  13. api/batch_processing/api_core/server_batch_job_manager.py +0 -220
  14. api/batch_processing/api_core/server_job_status_table.py +0 -152
  15. api/batch_processing/api_core/server_orchestration.py +0 -360
  16. api/batch_processing/api_core/server_utils.py +0 -92
  17. api/batch_processing/api_core_support/__init__.py +0 -0
  18. api/batch_processing/api_core_support/aggregate_results_manually.py +0 -46
  19. api/batch_processing/api_support/__init__.py +0 -0
  20. api/batch_processing/api_support/summarize_daily_activity.py +0 -152
  21. api/batch_processing/data_preparation/__init__.py +0 -0
  22. api/batch_processing/data_preparation/manage_local_batch.py +0 -2391
  23. api/batch_processing/data_preparation/manage_video_batch.py +0 -327
  24. api/batch_processing/integration/digiKam/setup.py +0 -6
  25. api/batch_processing/integration/digiKam/xmp_integration.py +0 -465
  26. api/batch_processing/integration/eMammal/test_scripts/config_template.py +0 -5
  27. api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -126
  28. api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +0 -55
  29. api/batch_processing/postprocessing/__init__.py +0 -0
  30. api/batch_processing/postprocessing/add_max_conf.py +0 -64
  31. api/batch_processing/postprocessing/categorize_detections_by_size.py +0 -163
  32. api/batch_processing/postprocessing/combine_api_outputs.py +0 -249
  33. api/batch_processing/postprocessing/compare_batch_results.py +0 -958
  34. api/batch_processing/postprocessing/convert_output_format.py +0 -397
  35. api/batch_processing/postprocessing/load_api_results.py +0 -195
  36. api/batch_processing/postprocessing/md_to_coco.py +0 -310
  37. api/batch_processing/postprocessing/md_to_labelme.py +0 -330
  38. api/batch_processing/postprocessing/merge_detections.py +0 -401
  39. api/batch_processing/postprocessing/postprocess_batch_results.py +0 -1904
  40. api/batch_processing/postprocessing/remap_detection_categories.py +0 -170
  41. api/batch_processing/postprocessing/render_detection_confusion_matrix.py +0 -661
  42. api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +0 -211
  43. api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +0 -82
  44. api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +0 -1631
  45. api/batch_processing/postprocessing/separate_detections_into_folders.py +0 -731
  46. api/batch_processing/postprocessing/subset_json_detector_output.py +0 -696
  47. api/batch_processing/postprocessing/top_folders_to_bottom.py +0 -223
  48. api/synchronous/__init__.py +0 -0
  49. api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  50. api/synchronous/api_core/animal_detection_api/api_backend.py +0 -152
  51. api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -266
  52. api/synchronous/api_core/animal_detection_api/config.py +0 -35
  53. api/synchronous/api_core/animal_detection_api/data_management/annotations/annotation_constants.py +0 -47
  54. api/synchronous/api_core/animal_detection_api/detection/detector_training/copy_checkpoints.py +0 -43
  55. api/synchronous/api_core/animal_detection_api/detection/detector_training/model_main_tf2.py +0 -114
  56. api/synchronous/api_core/animal_detection_api/detection/process_video.py +0 -543
  57. api/synchronous/api_core/animal_detection_api/detection/pytorch_detector.py +0 -304
  58. api/synchronous/api_core/animal_detection_api/detection/run_detector.py +0 -627
  59. api/synchronous/api_core/animal_detection_api/detection/run_detector_batch.py +0 -1029
  60. api/synchronous/api_core/animal_detection_api/detection/run_inference_with_yolov5_val.py +0 -581
  61. api/synchronous/api_core/animal_detection_api/detection/run_tiled_inference.py +0 -754
  62. api/synchronous/api_core/animal_detection_api/detection/tf_detector.py +0 -165
  63. api/synchronous/api_core/animal_detection_api/detection/video_utils.py +0 -495
  64. api/synchronous/api_core/animal_detection_api/md_utils/azure_utils.py +0 -174
  65. api/synchronous/api_core/animal_detection_api/md_utils/ct_utils.py +0 -262
  66. api/synchronous/api_core/animal_detection_api/md_utils/directory_listing.py +0 -251
  67. api/synchronous/api_core/animal_detection_api/md_utils/matlab_porting_tools.py +0 -97
  68. api/synchronous/api_core/animal_detection_api/md_utils/path_utils.py +0 -416
  69. api/synchronous/api_core/animal_detection_api/md_utils/process_utils.py +0 -110
  70. api/synchronous/api_core/animal_detection_api/md_utils/sas_blob_utils.py +0 -509
  71. api/synchronous/api_core/animal_detection_api/md_utils/string_utils.py +0 -59
  72. api/synchronous/api_core/animal_detection_api/md_utils/url_utils.py +0 -144
  73. api/synchronous/api_core/animal_detection_api/md_utils/write_html_image_list.py +0 -226
  74. api/synchronous/api_core/animal_detection_api/md_visualization/visualization_utils.py +0 -841
  75. api/synchronous/api_core/tests/__init__.py +0 -0
  76. api/synchronous/api_core/tests/load_test.py +0 -110
  77. classification/__init__.py +0 -0
  78. classification/aggregate_classifier_probs.py +0 -108
  79. classification/analyze_failed_images.py +0 -227
  80. classification/cache_batchapi_outputs.py +0 -198
  81. classification/create_classification_dataset.py +0 -627
  82. classification/crop_detections.py +0 -516
  83. classification/csv_to_json.py +0 -226
  84. classification/detect_and_crop.py +0 -855
  85. classification/efficientnet/__init__.py +0 -9
  86. classification/efficientnet/model.py +0 -415
  87. classification/efficientnet/utils.py +0 -610
  88. classification/evaluate_model.py +0 -520
  89. classification/identify_mislabeled_candidates.py +0 -152
  90. classification/json_to_azcopy_list.py +0 -63
  91. classification/json_validator.py +0 -695
  92. classification/map_classification_categories.py +0 -276
  93. classification/merge_classification_detection_output.py +0 -506
  94. classification/prepare_classification_script.py +0 -194
  95. classification/prepare_classification_script_mc.py +0 -228
  96. classification/run_classifier.py +0 -286
  97. classification/save_mislabeled.py +0 -110
  98. classification/train_classifier.py +0 -825
  99. classification/train_classifier_tf.py +0 -724
  100. classification/train_utils.py +0 -322
  101. data_management/__init__.py +0 -0
  102. data_management/annotations/__init__.py +0 -0
  103. data_management/annotations/annotation_constants.py +0 -34
  104. data_management/camtrap_dp_to_coco.py +0 -238
  105. data_management/cct_json_utils.py +0 -395
  106. data_management/cct_to_md.py +0 -176
  107. data_management/cct_to_wi.py +0 -289
  108. data_management/coco_to_labelme.py +0 -272
  109. data_management/coco_to_yolo.py +0 -662
  110. data_management/databases/__init__.py +0 -0
  111. data_management/databases/add_width_and_height_to_db.py +0 -33
  112. data_management/databases/combine_coco_camera_traps_files.py +0 -206
  113. data_management/databases/integrity_check_json_db.py +0 -477
  114. data_management/databases/subset_json_db.py +0 -115
  115. data_management/generate_crops_from_cct.py +0 -149
  116. data_management/get_image_sizes.py +0 -188
  117. data_management/importers/add_nacti_sizes.py +0 -52
  118. data_management/importers/add_timestamps_to_icct.py +0 -79
  119. data_management/importers/animl_results_to_md_results.py +0 -158
  120. data_management/importers/auckland_doc_test_to_json.py +0 -372
  121. data_management/importers/auckland_doc_to_json.py +0 -200
  122. data_management/importers/awc_to_json.py +0 -189
  123. data_management/importers/bellevue_to_json.py +0 -273
  124. data_management/importers/cacophony-thermal-importer.py +0 -796
  125. data_management/importers/carrizo_shrubfree_2018.py +0 -268
  126. data_management/importers/carrizo_trail_cam_2017.py +0 -287
  127. data_management/importers/cct_field_adjustments.py +0 -57
  128. data_management/importers/channel_islands_to_cct.py +0 -913
  129. data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
  130. data_management/importers/eMammal/eMammal_helpers.py +0 -249
  131. data_management/importers/eMammal/make_eMammal_json.py +0 -223
  132. data_management/importers/ena24_to_json.py +0 -275
  133. data_management/importers/filenames_to_json.py +0 -385
  134. data_management/importers/helena_to_cct.py +0 -282
  135. data_management/importers/idaho-camera-traps.py +0 -1407
  136. data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
  137. data_management/importers/jb_csv_to_json.py +0 -150
  138. data_management/importers/mcgill_to_json.py +0 -250
  139. data_management/importers/missouri_to_json.py +0 -489
  140. data_management/importers/nacti_fieldname_adjustments.py +0 -79
  141. data_management/importers/noaa_seals_2019.py +0 -181
  142. data_management/importers/pc_to_json.py +0 -365
  143. data_management/importers/plot_wni_giraffes.py +0 -123
  144. data_management/importers/prepare-noaa-fish-data-for-lila.py +0 -359
  145. data_management/importers/prepare_zsl_imerit.py +0 -131
  146. data_management/importers/rspb_to_json.py +0 -356
  147. data_management/importers/save_the_elephants_survey_A.py +0 -320
  148. data_management/importers/save_the_elephants_survey_B.py +0 -332
  149. data_management/importers/snapshot_safari_importer.py +0 -758
  150. data_management/importers/snapshot_safari_importer_reprise.py +0 -665
  151. data_management/importers/snapshot_serengeti_lila.py +0 -1067
  152. data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
  153. data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
  154. data_management/importers/sulross_get_exif.py +0 -65
  155. data_management/importers/timelapse_csv_set_to_json.py +0 -490
  156. data_management/importers/ubc_to_json.py +0 -399
  157. data_management/importers/umn_to_json.py +0 -507
  158. data_management/importers/wellington_to_json.py +0 -263
  159. data_management/importers/wi_to_json.py +0 -441
  160. data_management/importers/zamba_results_to_md_results.py +0 -181
  161. data_management/labelme_to_coco.py +0 -548
  162. data_management/labelme_to_yolo.py +0 -272
  163. data_management/lila/__init__.py +0 -0
  164. data_management/lila/add_locations_to_island_camera_traps.py +0 -97
  165. data_management/lila/add_locations_to_nacti.py +0 -147
  166. data_management/lila/create_lila_blank_set.py +0 -557
  167. data_management/lila/create_lila_test_set.py +0 -151
  168. data_management/lila/create_links_to_md_results_files.py +0 -106
  169. data_management/lila/download_lila_subset.py +0 -177
  170. data_management/lila/generate_lila_per_image_labels.py +0 -515
  171. data_management/lila/get_lila_annotation_counts.py +0 -170
  172. data_management/lila/get_lila_image_counts.py +0 -111
  173. data_management/lila/lila_common.py +0 -300
  174. data_management/lila/test_lila_metadata_urls.py +0 -132
  175. data_management/ocr_tools.py +0 -874
  176. data_management/read_exif.py +0 -681
  177. data_management/remap_coco_categories.py +0 -84
  178. data_management/remove_exif.py +0 -66
  179. data_management/resize_coco_dataset.py +0 -189
  180. data_management/wi_download_csv_to_coco.py +0 -246
  181. data_management/yolo_output_to_md_output.py +0 -441
  182. data_management/yolo_to_coco.py +0 -676
  183. detection/__init__.py +0 -0
  184. detection/detector_training/__init__.py +0 -0
  185. detection/detector_training/model_main_tf2.py +0 -114
  186. detection/process_video.py +0 -703
  187. detection/pytorch_detector.py +0 -337
  188. detection/run_detector.py +0 -779
  189. detection/run_detector_batch.py +0 -1219
  190. detection/run_inference_with_yolov5_val.py +0 -917
  191. detection/run_tiled_inference.py +0 -935
  192. detection/tf_detector.py +0 -188
  193. detection/video_utils.py +0 -606
  194. docs/source/conf.py +0 -43
  195. md_utils/__init__.py +0 -0
  196. md_utils/azure_utils.py +0 -174
  197. md_utils/ct_utils.py +0 -612
  198. md_utils/directory_listing.py +0 -246
  199. md_utils/md_tests.py +0 -968
  200. md_utils/path_utils.py +0 -1044
  201. md_utils/process_utils.py +0 -157
  202. md_utils/sas_blob_utils.py +0 -509
  203. md_utils/split_locations_into_train_val.py +0 -228
  204. md_utils/string_utils.py +0 -92
  205. md_utils/url_utils.py +0 -323
  206. md_utils/write_html_image_list.py +0 -225
  207. md_visualization/__init__.py +0 -0
  208. md_visualization/plot_utils.py +0 -293
  209. md_visualization/render_images_with_thumbnails.py +0 -275
  210. md_visualization/visualization_utils.py +0 -1537
  211. md_visualization/visualize_db.py +0 -551
  212. md_visualization/visualize_detector_output.py +0 -406
  213. megadetector-5.0.9.dist-info/RECORD +0 -224
  214. megadetector-5.0.9.dist-info/top_level.txt +0 -8
  215. taxonomy_mapping/__init__.py +0 -0
  216. taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +0 -491
  217. taxonomy_mapping/map_new_lila_datasets.py +0 -154
  218. taxonomy_mapping/prepare_lila_taxonomy_release.py +0 -142
  219. taxonomy_mapping/preview_lila_taxonomy.py +0 -591
  220. taxonomy_mapping/retrieve_sample_image.py +0 -71
  221. taxonomy_mapping/simple_image_download.py +0 -218
  222. taxonomy_mapping/species_lookup.py +0 -834
  223. taxonomy_mapping/taxonomy_csv_checker.py +0 -159
  224. taxonomy_mapping/taxonomy_graph.py +0 -346
  225. taxonomy_mapping/validate_lila_category_mappings.py +0 -83
  226. {megadetector-5.0.9.dist-info → megadetector-5.0.11.dist-info}/WHEEL +0 -0
@@ -1,841 +0,0 @@
1
- ########
2
- #
3
- # visualization_utils.py
4
- #
5
- # Core rendering functions shared across visualization scripts
6
- #
7
- ########
8
-
9
- #%% Constants and imports
10
-
11
- from io import BytesIO
12
- from typing import Union
13
- import time
14
-
15
- import matplotlib.pyplot as plt
16
- import numpy as np
17
- import requests
18
- from PIL import Image, ImageFile, ImageFont, ImageDraw
19
-
20
- from data_management.annotations import annotation_constants
21
- from data_management.annotations.annotation_constants import (
22
- detector_bbox_category_id_to_name) # here id is int
23
-
24
- ImageFile.LOAD_TRUNCATED_IMAGES = True
25
-
26
- IMAGE_ROTATIONS = {
27
- 3: 180,
28
- 6: 270,
29
- 8: 90
30
- }
31
-
32
- TEXTALIGN_LEFT = 0
33
- TEXTALIGN_RIGHT = 1
34
-
35
- # convert category ID from int to str
36
- DEFAULT_DETECTOR_LABEL_MAP = {
37
- str(k): v for k, v in detector_bbox_category_id_to_name.items()
38
- }
39
-
40
- # Retry on blob storage read failures
41
- n_retries = 10
42
- retry_sleep_time = 0.01
43
- error_names_for_retry = ['ConnectionError']
44
-
45
- DEFAULT_BOX_THICKNESS = 4
46
- DEFAULT_LABEL_FONT_SIZE = 16
47
-
48
-
49
- #%% Functions
50
-
51
- def open_image(input_file: Union[str, BytesIO]) -> Image:
52
- """
53
- Opens an image in binary format using PIL.Image and converts to RGB mode.
54
-
55
- Supports local files or URLs.
56
-
57
- This operation is lazy; image will not be actually loaded until the first
58
- operation that needs to load it (for example, resizing), so file opening
59
- errors can show up later.
60
-
61
- Args:
62
- input_file: str or BytesIO, either a path to an image file (anything
63
- that PIL can open), or an image as a stream of bytes
64
-
65
- Returns:
66
- A PIL image object in RGB mode
67
- """
68
-
69
- if (isinstance(input_file, str)
70
- and input_file.startswith(('http://', 'https://'))):
71
- try:
72
- response = requests.get(input_file)
73
- except Exception as e:
74
- print(f'Error retrieving image {input_file}: {e}')
75
- success = False
76
- if e.__class__.__name__ in error_names_for_retry:
77
- for i_retry in range(0,n_retries):
78
- try:
79
- time.sleep(retry_sleep_time)
80
- response = requests.get(input_file)
81
- except Exception as e:
82
- print(f'Error retrieving image {input_file} on retry {i_retry}: {e}')
83
- continue
84
- print('Succeeded on retry {}'.format(i_retry))
85
- success = True
86
- break
87
- if not success:
88
- raise
89
- try:
90
- image = Image.open(BytesIO(response.content))
91
- except Exception as e:
92
- print(f'Error opening image {input_file}: {e}')
93
- raise
94
-
95
- else:
96
- image = Image.open(input_file)
97
- if image.mode not in ('RGBA', 'RGB', 'L', 'I;16'):
98
- raise AttributeError(
99
- f'Image {input_file} uses unsupported mode {image.mode}')
100
- if image.mode == 'RGBA' or image.mode == 'L':
101
- # PIL.Image.convert() returns a converted copy of this image
102
- image = image.convert(mode='RGB')
103
-
104
- # Alter orientation as needed according to EXIF tag 0x112 (274) for Orientation
105
- #
106
- # https://gist.github.com/dangtrinhnt/a577ece4cbe5364aad28
107
- # https://www.media.mit.edu/pia/Research/deepview/exif.html
108
- #
109
- try:
110
- exif = image._getexif()
111
- orientation: int = exif.get(274, None) # 274 is the key for the Orientation field
112
- if orientation is not None and orientation in IMAGE_ROTATIONS:
113
- image = image.rotate(IMAGE_ROTATIONS[orientation], expand=True) # returns a rotated copy
114
- except Exception:
115
- pass
116
-
117
- return image
118
-
119
-
120
- def exif_preserving_save(pil_image,output_file):
121
- """
122
- Save [pil_image] to [output_file], making a moderate attempt to preserve EXIF
123
- data and JPEG quality. Neither is guaranteed.
124
-
125
- Also see:
126
-
127
- https://discuss.dizzycoding.com/determining-jpg-quality-in-python-pil/
128
-
129
- ...for more ways to preserve jpeg quality if quality='keep' doesn't do the trick.
130
- """
131
-
132
- # Read EXIF metadata
133
- exif = pil_image.info['exif'] if ('exif' in pil_image.info) else None
134
-
135
- # Write output with EXIF metadata if available, and quality='keep' if this is a JPEG
136
- # image. Unfortunately, neither parameter likes "None", so we get a slightly
137
- # icky cascade of if's here.
138
- if exif is not None:
139
- if pil_image.format == "JPEG":
140
- pil_image.save(output_file, exif=exif, quality='keep')
141
- else:
142
- pil_image.save(output_file, exif=exif)
143
- else:
144
- if pil_image.format == "JPEG":
145
- pil_image.save(output_file, quality='keep')
146
- else:
147
- pil_image.save(output_file)
148
-
149
-
150
- def load_image(input_file: Union[str, BytesIO]) -> Image:
151
- """
152
- Loads the image at input_file as a PIL Image into memory.
153
-
154
- Image.open() used in open_image() is lazy and errors will occur downstream
155
- if not explicitly loaded.
156
-
157
- Args:
158
- input_file: str or BytesIO, either a path to an image file (anything
159
- that PIL can open), or an image as a stream of bytes
160
-
161
- Returns: PIL.Image.Image, in RGB mode
162
- """
163
-
164
- image = open_image(input_file)
165
- image.load()
166
- return image
167
-
168
-
169
- def resize_image(image, target_width, target_height=-1):
170
- """
171
- Resizes a PIL image object to the specified width and height; does not resize
172
- in place. If either width or height are -1, resizes with aspect ratio preservation.
173
- If both are -1, returns the original image (does not copy in this case).
174
- """
175
-
176
- # Null operation
177
- if target_width == -1 and target_height == -1:
178
- return image
179
-
180
- elif target_width == -1 or target_height == -1:
181
-
182
- # Aspect ratio as width over height
183
- # ar = w / h
184
- aspect_ratio = image.size[0] / image.size[1]
185
-
186
- if target_width != -1:
187
- # h = w / ar
188
- target_height = int(target_width / aspect_ratio)
189
- else:
190
- # w = ar * h
191
- target_width = int(aspect_ratio * target_height)
192
-
193
- # This parameter changed between Pillow versions 9 and 10, and for a bit, I'd like to
194
- # support both.
195
- try:
196
- resized_image = image.resize((target_width, target_height), Image.ANTIALIAS)
197
- except:
198
- resized_image = image.resize((target_width, target_height), Image.Resampling.LANCZOS)
199
-
200
- return resized_image
201
-
202
-
203
- def show_images_in_a_row(images):
204
-
205
- num = len(images)
206
- assert num > 0
207
-
208
- if isinstance(images[0], str):
209
- images = [Image.open(img) for img in images]
210
-
211
- fig, axarr = plt.subplots(1, num, squeeze=False) # number of rows, number of columns
212
- fig.set_size_inches((num * 5, 25)) # each image is 2 inches wide
213
- for i, img in enumerate(images):
214
- axarr[0, i].set_axis_off()
215
- axarr[0, i].imshow(img)
216
- return fig
217
-
218
-
219
- # The following three functions are modified versions of those at:
220
- #
221
- # https://github.com/tensorflow/models/blob/master/research/object_detection/utils/visualization_utils.py
222
-
223
- DEFAULT_COLORS = [
224
- 'AliceBlue', 'Red', 'RoyalBlue', 'Gold', 'Chartreuse', 'Aqua', 'Azure',
225
- 'Beige', 'Bisque', 'BlanchedAlmond', 'BlueViolet', 'BurlyWood', 'CadetBlue',
226
- 'AntiqueWhite', 'Chocolate', 'Coral', 'CornflowerBlue', 'Cornsilk', 'Crimson',
227
- 'Cyan', 'DarkCyan', 'DarkGoldenRod', 'DarkGrey', 'DarkKhaki', 'DarkOrange',
228
- 'DarkOrchid', 'DarkSalmon', 'DarkSeaGreen', 'DarkTurquoise', 'DarkViolet',
229
- 'DeepPink', 'DeepSkyBlue', 'DodgerBlue', 'FireBrick', 'FloralWhite',
230
- 'ForestGreen', 'Fuchsia', 'Gainsboro', 'GhostWhite', 'GoldenRod',
231
- 'Salmon', 'Tan', 'HoneyDew', 'HotPink', 'IndianRed', 'Ivory', 'Khaki',
232
- 'Lavender', 'LavenderBlush', 'LawnGreen', 'LemonChiffon', 'LightBlue',
233
- 'LightCoral', 'LightCyan', 'LightGoldenRodYellow', 'LightGray', 'LightGrey',
234
- 'LightGreen', 'LightPink', 'LightSalmon', 'LightSeaGreen', 'LightSkyBlue',
235
- 'LightSlateGray', 'LightSlateGrey', 'LightSteelBlue', 'LightYellow', 'Lime',
236
- 'LimeGreen', 'Linen', 'Magenta', 'MediumAquaMarine', 'MediumOrchid',
237
- 'MediumPurple', 'MediumSeaGreen', 'MediumSlateBlue', 'MediumSpringGreen',
238
- 'MediumTurquoise', 'MediumVioletRed', 'MintCream', 'MistyRose', 'Moccasin',
239
- 'NavajoWhite', 'OldLace', 'Olive', 'OliveDrab', 'Orange', 'OrangeRed',
240
- 'Orchid', 'PaleGoldenRod', 'PaleGreen', 'PaleTurquoise', 'PaleVioletRed',
241
- 'PapayaWhip', 'PeachPuff', 'Peru', 'Pink', 'Plum', 'PowderBlue', 'Purple',
242
- 'RosyBrown', 'Aquamarine', 'SaddleBrown', 'Green', 'SandyBrown',
243
- 'SeaGreen', 'SeaShell', 'Sienna', 'Silver', 'SkyBlue', 'SlateBlue',
244
- 'SlateGray', 'SlateGrey', 'Snow', 'SpringGreen', 'SteelBlue', 'GreenYellow',
245
- 'Teal', 'Thistle', 'Tomato', 'Turquoise', 'Violet', 'Wheat', 'White',
246
- 'WhiteSmoke', 'Yellow', 'YellowGreen'
247
- ]
248
-
249
-
250
- def crop_image(detections, image, confidence_threshold=0.15, expansion=0):
251
- """
252
- Crops detections above *confidence_threshold* from the PIL image *image*,
253
- returning a list of PIL images.
254
-
255
- *detections* should be a list of dictionaries with keys 'conf' and 'bbox';
256
- see bbox format description below. Normalized, [x,y,w,h], upper-left-origin.
257
-
258
- *expansion* specifies a number of pixels to include on each side of the box.
259
- """
260
-
261
- ret_images = []
262
-
263
- for detection in detections:
264
-
265
- score = float(detection['conf'])
266
-
267
- if score >= confidence_threshold:
268
-
269
- x1, y1, w_box, h_box = detection['bbox']
270
- ymin,xmin,ymax,xmax = y1, x1, y1 + h_box, x1 + w_box
271
-
272
- # Convert to pixels so we can use the PIL crop() function
273
- im_width, im_height = image.size
274
- (left, right, top, bottom) = (xmin * im_width, xmax * im_width,
275
- ymin * im_height, ymax * im_height)
276
-
277
- if expansion > 0:
278
- left -= expansion
279
- right += expansion
280
- top -= expansion
281
- bottom += expansion
282
-
283
- # PIL's crop() does surprising things if you provide values outside of
284
- # the image, clip inputs
285
- left = max(left,0); right = max(right,0)
286
- top = max(top,0); bottom = max(bottom,0)
287
-
288
- left = min(left,im_width-1); right = min(right,im_width-1)
289
- top = min(top,im_height-1); bottom = min(bottom,im_height-1)
290
-
291
- ret_images.append(image.crop((left, top, right, bottom)))
292
-
293
- # ...if this detection is above threshold
294
-
295
- # ...for each detection
296
-
297
- return ret_images
298
-
299
-
300
- def render_detection_bounding_boxes(detections, image,
301
- label_map={},
302
- classification_label_map=None,
303
- confidence_threshold=0.15, thickness=DEFAULT_BOX_THICKNESS, expansion=0,
304
- classification_confidence_threshold=0.3,
305
- max_classifications=3,
306
- colormap=DEFAULT_COLORS,
307
- textalign=TEXTALIGN_LEFT,
308
- label_font_size=DEFAULT_LABEL_FONT_SIZE,
309
- custom_strings=None):
310
- """
311
- Renders bounding boxes, label, and confidence on an image if confidence is above the threshold.
312
-
313
- Boxes are in the format that's output from the batch processing API.
314
-
315
- Renders classification labels if present.
316
-
317
- Args:
318
-
319
- detections: detections on the image, example content:
320
- [
321
- {
322
- "category": "2",
323
- "conf": 0.996,
324
- "bbox": [
325
- 0.0,
326
- 0.2762,
327
- 0.1234,
328
- 0.2458
329
- ]
330
- }
331
- ]
332
-
333
- ...where the bbox coordinates are [x, y, box_width, box_height].
334
-
335
- (0, 0) is the upper-left. Coordinates are normalized.
336
-
337
- Supports classification results, if *detections* has the format
338
- [
339
- {
340
- "category": "2",
341
- "conf": 0.996,
342
- "bbox": [
343
- 0.0,
344
- 0.2762,
345
- 0.1234,
346
- 0.2458
347
- ]
348
- "classifications": [
349
- ["3", 0.901],
350
- ["1", 0.071],
351
- ["4", 0.025]
352
- ]
353
- }
354
- ]
355
-
356
- image: PIL.Image object
357
-
358
- label_map: optional, mapping the numerical label to a string name. The type of the numerical label
359
- (default string) needs to be consistent with the keys in label_map; no casting is carried out.
360
- If this is None, no labels are shown.
361
-
362
- classification_label_map: optional, mapping of the string class labels to the actual class names.
363
- The type of the numerical label (default string) needs to be consistent with the keys in
364
- label_map; no casting is carried out. If this is None, no classification labels are shown.
365
-
366
- confidence_threshold: optional, threshold above which the bounding box is rendered.
367
-
368
- thickness: line thickness in pixels. Default value is 4.
369
-
370
- expansion: number of pixels to expand bounding boxes on each side. Default is 0.
371
-
372
- classification_confidence_threshold: confidence above which classification result is retained.
373
-
374
- max_classifications: maximum number of classification results retained for one image.
375
-
376
- custom_strings: optional set of strings to append to detection labels, should have the
377
- same length as [detections]. Appended before classification labels, if classification
378
- data is provided.
379
-
380
- image is modified in place.
381
- """
382
-
383
- if custom_strings is not None:
384
- assert len(custom_strings) == len(detections), \
385
- '{} custom strings provided for {} detections'.format(
386
- len(custom_strings),len(detections))
387
-
388
- display_boxes = []
389
-
390
- # list of lists, one list of strings for each bounding box (to accommodate multiple labels)
391
- display_strs = []
392
-
393
- # for color selection
394
- classes = []
395
-
396
- for i_detection,detection in enumerate(detections):
397
-
398
- score = detection['conf']
399
-
400
- # Always render objects with a confidence of "None", this is typically used
401
- # for ground truth data.
402
- if score is None or score >= confidence_threshold:
403
-
404
- x1, y1, w_box, h_box = detection['bbox']
405
- display_boxes.append([y1, x1, y1 + h_box, x1 + w_box])
406
- clss = detection['category']
407
-
408
- # {} is the default, which means "show labels with no mapping", so don't use "if label_map" here
409
- # if label_map:
410
- if label_map is not None:
411
- label = label_map[clss] if clss in label_map else clss
412
- if score is not None:
413
- displayed_label = ['{}: {}%'.format(label, round(100 * score))]
414
- else:
415
- displayed_label = ['{}'.format(label)]
416
- else:
417
- displayed_label = ''
418
-
419
- if custom_strings is not None:
420
- custom_string = custom_strings[i_detection]
421
- if custom_string is not None and len(custom_string) > 0:
422
- if isinstance(displayed_label,str):
423
- displayed_label += ' ' + custom_string
424
- else:
425
- assert len(displayed_label) == 1
426
- displayed_label[0] += ' ' + custom_string
427
-
428
- if 'classifications' in detection:
429
-
430
- # To avoid duplicate colors with detection-only visualization, offset
431
- # the classification class index by the number of detection classes
432
- clss = annotation_constants.NUM_DETECTOR_CATEGORIES + int(detection['classifications'][0][0])
433
- classifications = detection['classifications']
434
- if len(classifications) > max_classifications:
435
- classifications = classifications[0:max_classifications]
436
-
437
- for classification in classifications:
438
-
439
- classification_conf = classification[1]
440
- if classification_conf is not None and \
441
- classification_conf < classification_confidence_threshold:
442
- continue
443
- class_key = classification[0]
444
- if (classification_label_map is not None) and (class_key in classification_label_map):
445
- class_name = classification_label_map[class_key]
446
- else:
447
- class_name = class_key
448
- if classification_conf is not None:
449
- displayed_label += ['{}: {:5.1%}'.format(class_name.lower(), classification_conf)]
450
- else:
451
- displayed_label += ['{}'.format(class_name.lower())]
452
-
453
- # ...for each classification
454
-
455
- # ...if we have classification results
456
-
457
- display_strs.append(displayed_label)
458
- classes.append(clss)
459
-
460
- # ...if the confidence of this detection is above threshold
461
-
462
- # ...for each detection
463
-
464
- display_boxes = np.array(display_boxes)
465
-
466
- draw_bounding_boxes_on_image(image, display_boxes, classes,
467
- display_strs=display_strs, thickness=thickness,
468
- expansion=expansion, colormap=colormap, textalign=textalign,
469
- label_font_size=label_font_size)
470
-
471
-
472
- def draw_bounding_boxes_on_image(image,
473
- boxes,
474
- classes,
475
- thickness=DEFAULT_BOX_THICKNESS,
476
- expansion=0,
477
- display_strs=None,
478
- colormap=DEFAULT_COLORS,
479
- textalign=TEXTALIGN_LEFT,
480
- label_font_size=DEFAULT_LABEL_FONT_SIZE):
481
- """
482
- Draws bounding boxes on an image.
483
-
484
- Args:
485
- image: a PIL.Image object.
486
- boxes: a 2 dimensional numpy array of [N, 4]: (ymin, xmin, ymax, xmax).
487
- The coordinates are in normalized format between [0, 1].
488
- classes: a list of ints or strings (that can be cast to ints) corresponding to the
489
- class labels of the boxes. This is only used for color selection.
490
- thickness: line thickness in pixels. Default value is 4.
491
- expansion: number of pixels to expand bounding boxes on each side. Default is 0.
492
- display_strs: list of list of strings.
493
- a list of strings for each bounding box.
494
- The reason to pass a list of strings for a
495
- bounding box is that it might contain
496
- multiple labels.
497
- """
498
-
499
- boxes_shape = boxes.shape
500
- if not boxes_shape:
501
- return
502
- if len(boxes_shape) != 2 or boxes_shape[1] != 4:
503
- # print('Input must be of size [N, 4], but is ' + str(boxes_shape))
504
- return # no object detection on this image, return
505
- for i in range(boxes_shape[0]):
506
- if display_strs:
507
- display_str_list = display_strs[i]
508
- draw_bounding_box_on_image(image,
509
- boxes[i, 0], boxes[i, 1], boxes[i, 2], boxes[i, 3],
510
- classes[i],
511
- thickness=thickness, expansion=expansion,
512
- display_str_list=display_str_list,
513
- colormap=colormap,
514
- textalign=textalign,
515
- label_font_size=label_font_size)
516
-
517
-
518
- def draw_bounding_box_on_image(image,
519
- ymin,
520
- xmin,
521
- ymax,
522
- xmax,
523
- clss=None,
524
- thickness=DEFAULT_BOX_THICKNESS,
525
- expansion=0,
526
- display_str_list=(),
527
- use_normalized_coordinates=True,
528
- label_font_size=DEFAULT_LABEL_FONT_SIZE,
529
- colormap=DEFAULT_COLORS,
530
- textalign=TEXTALIGN_LEFT):
531
- """
532
- Adds a bounding box to an image.
533
-
534
- Bounding box coordinates can be specified in either absolute (pixel) or
535
- normalized coordinates by setting the use_normalized_coordinates argument.
536
-
537
- Each string in display_str_list is displayed on a separate line above the
538
- bounding box in black text on a rectangle filled with the input 'color'.
539
- If the top of the bounding box extends to the edge of the image, the strings
540
- are displayed below the bounding box.
541
-
542
- Args:
543
- image: a PIL.Image object.
544
- ymin: ymin of bounding box - upper left.
545
- xmin: xmin of bounding box.
546
- ymax: ymax of bounding box.
547
- xmax: xmax of bounding box.
548
- clss: str, the class of the object in this bounding box - will be cast to an int.
549
- thickness: line thickness. Default value is 4.
550
- expansion: number of pixels to expand bounding boxes on each side. Default is 0.
551
- display_str_list: list of strings to display in box
552
- (each to be shown on its own line).
553
- use_normalized_coordinates: If True (default), treat coordinates
554
- ymin, xmin, ymax, xmax as relative to the image. Otherwise treat
555
- coordinates as absolute.
556
- label_font_size: font size to attempt to load arial.ttf with
557
- """
558
-
559
- if clss is None:
560
- color = colormap[1]
561
- else:
562
- color = colormap[int(clss) % len(colormap)]
563
-
564
- draw = ImageDraw.Draw(image)
565
- im_width, im_height = image.size
566
- if use_normalized_coordinates:
567
- (left, right, top, bottom) = (xmin * im_width, xmax * im_width,
568
- ymin * im_height, ymax * im_height)
569
- else:
570
- (left, right, top, bottom) = (xmin, xmax, ymin, ymax)
571
-
572
- if expansion > 0:
573
-
574
- left -= expansion
575
- right += expansion
576
- top -= expansion
577
- bottom += expansion
578
-
579
- # Deliberately trimming to the width of the image only in the case where
580
- # box expansion is turned on. There's not an obvious correct behavior here,
581
- # but the thinking is that if the caller provided an out-of-range bounding
582
- # box, they meant to do that, but at least in the eyes of the person writing
583
- # this comment, if you expand a box for visualization reasons, you don't want
584
- # to end up with part of a box.
585
- #
586
- # A slightly more sophisticated might check whether it was in fact the expansion
587
- # that made this box larger than the image, but this is the case 99.999% of the time
588
- # here, so that doesn't seem necessary.
589
- left = max(left,0); right = max(right,0)
590
- top = max(top,0); bottom = max(bottom,0)
591
-
592
- left = min(left,im_width-1); right = min(right,im_width-1)
593
- top = min(top,im_height-1); bottom = min(bottom,im_height-1)
594
-
595
- # ...if we need to expand boxes
596
-
597
- draw.line([(left, top), (left, bottom), (right, bottom),
598
- (right, top), (left, top)], width=thickness, fill=color)
599
-
600
- try:
601
- font = ImageFont.truetype('arial.ttf', label_font_size)
602
- except IOError:
603
- font = ImageFont.load_default()
604
-
605
- def get_text_size(font,s):
606
-
607
- # This is what we did w/Pillow 9
608
- # w,h = font.getsize(s)
609
-
610
- # I would *think* this would be the equivalent for Pillow 10
611
- # l,t,r,b = font.getbbox(s); w = r-l; h=b-t
612
-
613
- # ...but this actually produces the most similar results to Pillow 9
614
- # l,t,r,b = font.getbbox(s); w = r; h=b
615
-
616
- try:
617
- l,t,r,b = font.getbbox(s); w = r; h=b
618
- except Exception:
619
- w,h = font.getsize(s)
620
-
621
- return w,h
622
-
623
- # If the total height of the display strings added to the top of the bounding
624
- # box exceeds the top of the image, stack the strings below the bounding box
625
- # instead of above.
626
- display_str_heights = [get_text_size(font,ds)[1] for ds in display_str_list]
627
-
628
- # Each display_str has a top and bottom margin of 0.05x.
629
- total_display_str_height = (1 + 2 * 0.05) * sum(display_str_heights)
630
-
631
- if top > total_display_str_height:
632
- text_bottom = top
633
- else:
634
- text_bottom = bottom + total_display_str_height
635
-
636
- # Reverse list and print from bottom to top.
637
- for display_str in display_str_list[::-1]:
638
-
639
- # Skip empty strings
640
- if len(display_str) == 0:
641
- continue
642
-
643
- text_width, text_height = get_text_size(font,display_str)
644
-
645
- text_left = left
646
-
647
- if textalign == TEXTALIGN_RIGHT:
648
- text_left = right - text_width
649
-
650
- margin = np.ceil(0.05 * text_height)
651
-
652
- draw.rectangle(
653
- [(text_left, text_bottom - text_height - 2 * margin), (text_left + text_width,
654
- text_bottom)],
655
- fill=color)
656
-
657
- draw.text(
658
- (text_left + margin, text_bottom - text_height - margin),
659
- display_str,
660
- fill='black',
661
- font=font)
662
-
663
- text_bottom -= (text_height + 2 * margin)
664
-
665
-
666
- def render_iMerit_boxes(boxes, classes, image,
667
- label_map=annotation_constants.annotation_bbox_category_id_to_name):
668
- """
669
- Renders bounding boxes and their category labels on a PIL image.
670
-
671
- Args:
672
- boxes: bounding box annotations from iMerit, format is:
673
- [x_rel, y_rel, w_rel, h_rel] (rel = relative coords)
674
- classes: the class IDs of the predicted class of each box/object
675
- image: PIL.Image object to annotate on
676
- label_map: optional dict mapping classes to a string for display
677
-
678
- Returns:
679
- image will be altered in place
680
- """
681
-
682
- display_boxes = []
683
-
684
- # list of lists, one list of strings for each bounding box (to accommodate multiple labels)
685
- display_strs = []
686
-
687
- for box, clss in zip(boxes, classes):
688
- if len(box) == 0:
689
- assert clss == 5
690
- continue
691
- x_rel, y_rel, w_rel, h_rel = box
692
- ymin, xmin = y_rel, x_rel
693
- ymax = ymin + h_rel
694
- xmax = xmin + w_rel
695
-
696
- display_boxes.append([ymin, xmin, ymax, xmax])
697
-
698
- if label_map:
699
- clss = label_map[int(clss)]
700
- display_strs.append([clss])
701
-
702
- display_boxes = np.array(display_boxes)
703
- draw_bounding_boxes_on_image(image, display_boxes, classes, display_strs=display_strs)
704
-
705
-
706
- def render_megadb_bounding_boxes(boxes_info, image):
707
- """
708
- Args:
709
- boxes_info: list of dict, each dict represents a single detection
710
- {
711
- "category": "animal",
712
- "bbox": [
713
- 0.739,
714
- 0.448,
715
- 0.187,
716
- 0.198
717
- ]
718
- }
719
- where bbox coordinates are normalized [x_min, y_min, width, height]
720
- image: PIL.Image.Image, opened image
721
- """
722
-
723
- display_boxes = []
724
- display_strs = []
725
- classes = [] # ints, for selecting colors
726
-
727
- for b in boxes_info:
728
- x_min, y_min, w_rel, h_rel = b['bbox']
729
- y_max = y_min + h_rel
730
- x_max = x_min + w_rel
731
- display_boxes.append([y_min, x_min, y_max, x_max])
732
- display_strs.append([b['category']])
733
- classes.append(annotation_constants.detector_bbox_category_name_to_id[b['category']])
734
-
735
- display_boxes = np.array(display_boxes)
736
- draw_bounding_boxes_on_image(image, display_boxes, classes, display_strs=display_strs)
737
-
738
-
739
- def render_db_bounding_boxes(boxes, classes, image, original_size=None,
740
- label_map=None, thickness=DEFAULT_BOX_THICKNESS, expansion=0):
741
- """
742
- Render bounding boxes (with class labels) on [image]. This is a wrapper for
743
- draw_bounding_boxes_on_image, allowing the caller to operate on a resized image
744
- by providing the original size of the image; bboxes will be scaled accordingly.
745
-
746
- This function assumes that bounding boxes are in the COCO camera traps format,
747
- with absolute coordinates.
748
- """
749
-
750
- display_boxes = []
751
- display_strs = []
752
-
753
- if original_size is not None:
754
- image_size = original_size
755
- else:
756
- image_size = image.size
757
-
758
- img_width, img_height = image_size
759
-
760
- for box, clss in zip(boxes, classes):
761
-
762
- x_min_abs, y_min_abs, width_abs, height_abs = box[0:4]
763
-
764
- ymin = y_min_abs / img_height
765
- ymax = ymin + height_abs / img_height
766
-
767
- xmin = x_min_abs / img_width
768
- xmax = xmin + width_abs / img_width
769
-
770
- display_boxes.append([ymin, xmin, ymax, xmax])
771
-
772
- if label_map:
773
- clss = label_map[int(clss)]
774
-
775
- # need to be a string here because PIL needs to iterate through chars
776
- display_strs.append([str(clss)])
777
-
778
- display_boxes = np.array(display_boxes)
779
- draw_bounding_boxes_on_image(image, display_boxes, classes, display_strs=display_strs,
780
- thickness=thickness, expansion=expansion)
781
-
782
-
783
- def draw_bounding_boxes_on_file(input_file, output_file, detections, confidence_threshold=0.0,
784
- detector_label_map=DEFAULT_DETECTOR_LABEL_MAP,
785
- thickness=DEFAULT_BOX_THICKNESS, expansion=0,
786
- colormap=DEFAULT_COLORS,
787
- custom_strings=None):
788
- """
789
- Render detection bounding boxes on an image loaded from file, writing the results to a
790
- new image file.
791
-
792
- "detections" is in the API results format:
793
-
794
- [{"category": "2","conf": 0.996,"bbox": [0.0,0.2762,0.1234,0.2458]}]
795
-
796
- ...where the bbox is:
797
-
798
- [x_min, y_min, width_of_box, height_of_box]
799
-
800
- Normalized, with the origin at the upper-left.
801
-
802
- detector_label_map is a dict mapping category IDs to strings.
803
-
804
- custom_strings: optional set of strings to append to detection labels, should have the
805
- same length as [detections]. Appended before classification labels, if classification
806
- data is provided.
807
- """
808
-
809
- image = open_image(input_file)
810
-
811
- render_detection_bounding_boxes(
812
- detections, image, label_map=detector_label_map,
813
- confidence_threshold=confidence_threshold,
814
- thickness=thickness,expansion=expansion,colormap=colormap,
815
- custom_strings=custom_strings)
816
-
817
- image.save(output_file)
818
-
819
-
820
- def draw_db_boxes_on_file(input_file, output_file, boxes, classes=None,
821
- label_map=None, thickness=DEFAULT_BOX_THICKNESS, expansion=0):
822
- """
823
- Render COCO bounding boxes (in absolute coordinates) on an image loaded from file, writing the
824
- results to a new image file.
825
-
826
- classes is a list of integer category IDs.
827
-
828
- detector_label_map is a dict mapping category IDs to strings.
829
- """
830
-
831
- image = open_image(input_file)
832
-
833
- if classes is None:
834
- classes = [0] * len(boxes)
835
-
836
- render_db_bounding_boxes(boxes, classes, image, original_size=None,
837
- label_map=label_map, thickness=thickness, expansion=expansion)
838
-
839
- image.save(output_file)
840
-
841
-