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
@@ -16,18 +16,21 @@ import math
16
16
  import os
17
17
  import sys
18
18
  import time
19
+
20
+ import pandas as pd
21
+ import numpy as np
22
+
23
+ import humanfriendly
24
+
19
25
  from itertools import compress
20
26
  from multiprocessing.pool import ThreadPool
21
27
  from multiprocessing.pool import Pool
22
-
23
- import pandas as pd
24
28
  from tqdm import tqdm
25
- import humanfriendly
26
29
 
27
30
  from md_utils.write_html_image_list import write_html_image_list
31
+ from data_management.cct_json_utils import IndexedJsonDb
28
32
 
29
33
  import md_visualization.visualization_utils as vis_utils
30
- from data_management.cct_json_utils import IndexedJsonDb
31
34
 
32
35
 
33
36
  #%% Settings
@@ -40,8 +43,16 @@ class DbVizOptions:
40
43
  # Target size for rendering; set either dimension to -1 to preserve aspect ratio
41
44
  #
42
45
  # If viz_size is None or (-1,-1), the original image size is used.
43
- viz_size = (675, -1)
46
+ viz_size = (800, -1)
47
+
48
+ # The most relevant option one might want to set here is:
49
+ #
50
+ # htmlOptions['maxFiguresPerHtmlFile']
51
+ #
52
+ # ...which can be used to paginate previews to a number of images that will load well
53
+ # in a browser (5000 is a reasonable limit).
44
54
  htmlOptions = write_html_image_list()
55
+
45
56
  sort_by_filename = True
46
57
  trim_to_images_with_bboxes = False
47
58
 
@@ -82,6 +93,11 @@ class DbVizOptions:
82
93
  # Should we show absolute (vs. relative) paths for each image?
83
94
  show_full_paths = False
84
95
 
96
+ # Set to False to skip existing images
97
+ force_rendering = True
98
+
99
+ verbose = False
100
+
85
101
 
86
102
  #%% Helper functions
87
103
 
@@ -264,7 +280,7 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
264
280
  categoryName = label_map[categoryID]
265
281
  if options.add_search_links:
266
282
  categoryName = categoryName.replace('"','')
267
- categoryName = '<a href="https://www.bing.com/images/search?q={}">{}</a>'.format(
283
+ categoryName = '<a href="https://www.google.com/search?tbm=isch&q={}">{}</a>'.format(
268
284
  categoryName,categoryName)
269
285
  imageCategories.add(categoryName)
270
286
 
@@ -309,14 +325,22 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
309
325
  if options.include_filename_links:
310
326
  filename_text = '<a href="{}">{}</a>'.format(img_path,filename_text)
311
327
 
328
+ flagString = ''
329
+
330
+ def isnan(x):
331
+ return (isinstance(x,float) and np.isnan(x))
332
+
333
+ if ('flags' in img) and (not isnan(img['flags'])):
334
+ flagString = ', flags: {}'.format(str(img['flags']))
335
+
312
336
  # We're adding html for an image before we render it, so it's possible this image will
313
337
  # fail to render. For applications where this script is being used to debua a database
314
338
  # (the common case?), this is useful behavior, for other applications, this is annoying.
315
339
  image_dict = \
316
340
  {
317
341
  'filename': '{}/{}'.format('rendered_images', file_name),
318
- 'title': '{}<br/>{}, num boxes: {}, {}class labels: {}{}'.format(
319
- filename_text, img_id, len(bboxes), frameString, imageClasses, labelLevelString),
342
+ 'title': '{}<br/>{}, num boxes: {}, {}class labels: {}{}{}'.format(
343
+ filename_text, img_id, len(bboxes), frameString, imageClasses, labelLevelString, flagString),
320
344
  'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;' + \
321
345
  'text-align:left;margin-top:20;margin-bottom:5'
322
346
  }
@@ -333,7 +357,13 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
333
357
  bboxes = rendering_info['bboxes']
334
358
  bboxClasses = rendering_info['boxClasses']
335
359
  output_file_name = rendering_info['output_file_name']
360
+ output_full_path = os.path.join(output_dir, 'rendered_images', output_file_name)
336
361
 
362
+ if (os.path.isfile(output_full_path)) and (not options.force_rendering):
363
+ if options.verbose:
364
+ print('Skipping existing image {}'.format(output_full_path))
365
+ return True
366
+
337
367
  if not img_path.startswith('http'):
338
368
  if not os.path.exists(img_path):
339
369
  print('Image {} cannot be found'.format(img_path))
@@ -348,7 +378,7 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
348
378
  image = vis_utils.resize_image(original_image, options.viz_size[0],
349
379
  options.viz_size[1])
350
380
  except Exception as e:
351
- print('Image {} failed to open. Error: {}'.format(img_path, e))
381
+ print('Image {} failed to open, error: {}'.format(img_path, e))
352
382
  return False
353
383
 
354
384
  vis_utils.render_db_bounding_boxes(boxes=bboxes, classes=bboxClasses,
@@ -357,7 +387,8 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
357
387
  thickness=options.box_thickness,
358
388
  expansion=options.box_expansion)
359
389
 
360
- image.save(os.path.join(output_dir, 'rendered_images', output_file_name))
390
+ image.save(output_full_path)
391
+
361
392
  return True
362
393
 
363
394
  # ...def render_image_info
@@ -3,8 +3,7 @@
3
3
  # visualize_detector_output.py
4
4
  #
5
5
  # Render images with bounding boxes annotated on them to a folder, based on a
6
- # detector output result file (json). The original images can be local or in
7
- # Azure Blob Storage.
6
+ # detector output result file (json), optionally writing an HTML index file.
8
7
  #
9
8
  ########
10
9
 
@@ -15,51 +14,115 @@ import json
15
14
  import os
16
15
  import random
17
16
  import sys
18
- from typing import List, Optional
17
+ from multiprocessing.pool import ThreadPool
18
+ from multiprocessing.pool import Pool
19
+ from typing import List
20
+ from functools import partial
19
21
 
20
22
  from tqdm import tqdm
21
23
 
22
- from data_management.annotations.annotation_constants import (
23
- detector_bbox_category_id_to_name) # here id is int
24
+ from data_management.annotations.annotation_constants import detector_bbox_category_id_to_name
24
25
  from md_visualization import visualization_utils as vis_utils
25
26
  from md_utils.ct_utils import get_max_conf
27
+ from md_utils import write_html_image_list
28
+ from detection.run_detector import get_typical_confidence_threshold_from_results
26
29
 
27
30
 
28
31
  #%% Constants
29
32
 
30
- # convert category ID from int to str
33
+ # This will only be used if a category mapping is not available in the results file.
31
34
  DEFAULT_DETECTOR_LABEL_MAP = {
32
35
  str(k): v for k, v in detector_bbox_category_id_to_name.items()
33
36
  }
34
37
 
35
38
 
39
+ #%% Support functions
40
+
41
+ def render_image(entry,
42
+ detector_label_map,classification_label_map,
43
+ confidence_threshold,classification_confidence_threshold,
44
+ render_detections_only,preserve_path_structure,out_dir,images_dir,
45
+ output_image_width):
46
+
47
+ rendering_result = {'failed_image':False,'missing_image':False,
48
+ 'skipped_image':False,'annotated_image_path':None,
49
+ 'max_conf':None,'file':entry['file']}
50
+
51
+ image_id = entry['file']
52
+
53
+ if 'failure' in entry and entry['failure'] is not None:
54
+ rendering_result['failed_image'] = True
55
+ return rendering_result
56
+
57
+ assert 'detections' in entry and entry['detections'] is not None
58
+
59
+ max_conf = get_max_conf(entry)
60
+ rendering_result['max_conf'] = max_conf
61
+
62
+ if (max_conf < confidence_threshold) and render_detections_only:
63
+ rendering_result['skipped_image'] = True
64
+ return rendering_result
65
+
66
+ image_obj = os.path.join(images_dir, image_id)
67
+ if not os.path.exists(image_obj):
68
+ print(f'Image {image_id} not found in images_dir')
69
+ rendering_result['missing_image'] = True
70
+ return rendering_result
71
+
72
+ # If output_image_width is -1 or None, this will just return the original image
73
+ image = vis_utils.resize_image(
74
+ vis_utils.open_image(image_obj), output_image_width)
75
+
76
+ vis_utils.render_detection_bounding_boxes(
77
+ entry['detections'], image,
78
+ label_map=detector_label_map,
79
+ classification_label_map=classification_label_map,
80
+ confidence_threshold=confidence_threshold,
81
+ classification_confidence_threshold=classification_confidence_threshold)
82
+
83
+ if not preserve_path_structure:
84
+ for char in ['/', '\\', ':']:
85
+ image_id = image_id.replace(char, '~')
86
+ annotated_img_path = os.path.join(out_dir, f'anno_{image_id}')
87
+ else:
88
+ assert not os.path.isabs(image_id), "Can't preserve paths when operating on absolute paths"
89
+ annotated_img_path = os.path.join(out_dir, image_id)
90
+ os.makedirs(os.path.dirname(annotated_img_path),exist_ok=True)
91
+
92
+ image.save(annotated_img_path)
93
+ rendering_result['annotated_image_path'] = annotated_img_path
94
+
95
+ return rendering_result
96
+
97
+
36
98
  #%% Main function
37
99
 
38
100
  def visualize_detector_output(detector_output_path: str,
39
101
  out_dir: str,
40
102
  images_dir: str,
41
- is_azure: bool = False,
42
103
  confidence_threshold: float = 0.15,
43
104
  sample: int = -1,
44
105
  output_image_width: int = 700,
45
- random_seed: Optional[int] = None,
106
+ random_seed: int = None,
46
107
  render_detections_only: bool = False,
47
108
  classification_confidence_threshold = 0.1,
48
109
  html_output_file = None,
49
110
  html_output_options = None,
50
- preserve_path_structure = False) -> List[str]:
111
+ preserve_path_structure = False,
112
+ parallelize_rendering = False,
113
+ parallelize_rendering_n_cores = 10,
114
+ parallelize_rendering_with_threads = True) -> List[str]:
51
115
 
52
116
  """
53
- Draw bounding boxes on images given the output of the detector.
117
+ Draw bounding boxes on images given the output of a detector.
54
118
 
55
119
  Args:
56
120
  detector_output_path: str, path to detector output json file
57
121
  out_dir: str, path to directory for saving annotated images
58
- images_dir: str, path to local images dir, or a SAS URL to an Azure Blob
59
- Storage container
60
- is_azure: bool, whether images_dir points to an Azure URL
122
+ images_dir: str, path to images dir
61
123
  confidence: float, threshold above which annotations will be rendered
62
124
  sample: int, maximum number of images to annotate, -1 for all
125
+ random_seed: seed for sampling (not relevant if sample == -1)
63
126
  output_image_width: int, width in pixels to resize images for display,
64
127
  set to -1 to use original image width
65
128
  random_seed: int, for deterministic image sampling when sample != -1
@@ -68,16 +131,11 @@ def visualize_detector_output(detector_output_path: str,
68
131
  Returns: list of str, paths to annotated images
69
132
  """
70
133
 
71
- assert confidence_threshold >= 0 and confidence_threshold <= 1, (
72
- f'Confidence threshold {confidence_threshold} is invalid, must be in (0, 1).')
73
-
74
- assert os.path.exists(detector_output_path), (
75
- f'Detector output file does not exist at {detector_output_path}.')
134
+ assert os.path.exists(detector_output_path), \
135
+ 'Detector output file does not exist at {}'.format(detector_output_path)
76
136
 
77
- if is_azure:
78
- from md_utils import sas_blob_utils
79
- else:
80
- assert os.path.isdir(images_dir)
137
+ assert os.path.isdir(images_dir), \
138
+ 'Image folder {} is not available'.format(images_dir)
81
139
 
82
140
  os.makedirs(out_dir, exist_ok=True)
83
141
 
@@ -89,12 +147,18 @@ def visualize_detector_output(detector_output_path: str,
89
147
  assert 'images' in detector_output, (
90
148
  'Detector output file should be a json with an "images" field.')
91
149
  images = detector_output['images']
92
-
150
+
151
+ if confidence_threshold is None:
152
+ confidence_threshold = get_typical_confidence_threshold_from_results(detector_output)
153
+
154
+ assert confidence_threshold >= 0 and confidence_threshold <= 1, (
155
+ f'Confidence threshold {confidence_threshold} is invalid, must be in (0, 1).')
156
+
93
157
  if 'detection_categories' in detector_output:
94
158
  print('Using custom label mapping')
95
159
  detector_label_map = detector_output['detection_categories']
96
160
  else:
97
- detector_label_map = DEFAULT_DETECTOR_LABEL_MAP
161
+ detector_label_map = DEFAULT_DETECTOR_LABEL_MAP
98
162
 
99
163
  num_images = len(images)
100
164
  print(f'Detector output file contains {num_images} entries.')
@@ -118,85 +182,89 @@ def visualize_detector_output(detector_output_path: str,
118
182
  print('Rendering detections above a confidence threshold of {}'.format(
119
183
  confidence_threshold))
120
184
 
121
- num_saved = 0
122
- annotated_img_paths = []
123
- failed_images = []
124
- missing_images = []
125
-
126
185
  classification_label_map = None
127
186
 
128
187
  if 'classification_categories' in detector_output:
129
188
  classification_label_map = detector_output['classification_categories']
130
189
 
131
- for entry in tqdm(images):
132
-
133
- image_id = entry['file']
134
-
135
- if 'failure' in entry and entry['failure'] is not None:
136
- failed_images.append(image_id)
137
- continue
138
-
139
- assert 'detections' in entry and entry['detections'] is not None
140
-
141
- max_conf = get_max_conf(entry)
142
- if (max_conf < confidence_threshold) and render_detections_only:
143
- continue
190
+ rendering_results = []
191
+
192
+ if parallelize_rendering:
144
193
 
145
- if is_azure:
146
- blob_uri = sas_blob_utils.build_blob_uri(
147
- container_uri=images_dir, blob_name=image_id)
148
- if not sas_blob_utils.check_blob_exists(blob_uri):
149
- container = sas_blob_utils.get_container_from_uri(images_dir)
150
- print(f'Image {image_id} not found in blob container '
151
- f'{container}; skipped.')
152
- continue
153
- # BytesIO object
154
- image_obj, _ = sas_blob_utils.download_blob_to_stream(blob_uri)
194
+ if parallelize_rendering_with_threads:
195
+ worker_string = 'threads'
155
196
  else:
156
- image_obj = os.path.join(images_dir, image_id)
157
- if not os.path.exists(image_obj):
158
- print(f'Image {image_id} not found in images_dir')
159
- missing_images.append(image_id)
160
- continue
161
-
162
- image = vis_utils.resize_image(
163
- vis_utils.open_image(image_obj), output_image_width)
164
-
165
- vis_utils.render_detection_bounding_boxes(
166
- entry['detections'], image, label_map=detector_label_map,
167
- classification_label_map = classification_label_map,
168
- confidence_threshold=confidence_threshold,
169
- classification_confidence_threshold=classification_confidence_threshold)
170
-
171
- if not preserve_path_structure:
172
- for char in ['/', '\\', ':']:
173
- image_id = image_id.replace(char, '~')
174
- annotated_img_path = os.path.join(out_dir, f'anno_{image_id}')
197
+ worker_string = 'processes'
198
+
199
+ if parallelize_rendering_n_cores is None:
200
+ if parallelize_rendering_with_threads:
201
+ pool = ThreadPool()
202
+ else:
203
+ pool = Pool()
175
204
  else:
176
- assert not os.path.isabs(image_id), "Can't preserve paths when operating on absolute paths"
177
- annotated_img_path = os.path.join(out_dir, image_id)
178
- os.makedirs(os.path.dirname(annotated_img_path),exist_ok=True)
179
- annotated_img_paths.append(annotated_img_path)
180
- image.save(annotated_img_path)
181
- num_saved += 1
182
-
183
- if is_azure:
184
- image_obj.close()
185
-
205
+ if parallelize_rendering_with_threads:
206
+ pool = ThreadPool(parallelize_rendering_n_cores)
207
+ else:
208
+ pool = Pool(parallelize_rendering_n_cores)
209
+ print('Rendering images with {} {}'.format(parallelize_rendering_n_cores,
210
+ worker_string))
211
+ rendering_results = list(tqdm(pool.imap(
212
+ partial(render_image,detector_label_map=detector_label_map,
213
+ classification_label_map=classification_label_map,
214
+ confidence_threshold=confidence_threshold,
215
+ classification_confidence_threshold=classification_confidence_threshold,
216
+ render_detections_only=render_detections_only,
217
+ preserve_path_structure=preserve_path_structure,
218
+ out_dir=out_dir,
219
+ images_dir=images_dir,
220
+ output_image_width=output_image_width),
221
+ images), total=len(images)))
222
+
223
+ else:
224
+
225
+ for entry in tqdm(images):
226
+
227
+ rendering_result = render_image(entry,detector_label_map,classification_label_map,
228
+ confidence_threshold,classification_confidence_threshold,
229
+ render_detections_only,preserve_path_structure,out_dir,
230
+ images_dir,output_image_width)
231
+ rendering_results.append(rendering_result)
232
+
186
233
  # ...for each image
187
234
 
235
+ failed_images = [r for r in rendering_results if r['failed_image']]
236
+ missing_images = [r for r in rendering_results if r['missing_image']]
237
+ skipped_images = [r for r in rendering_results if r['skipped_image']]
238
+
188
239
  print('Skipped {} failed images (of {})'.format(len(failed_images),len(images)))
189
240
  print('Skipped {} missing images (of {})'.format(len(missing_images),len(images)))
241
+ print('Skipped {} below-threshold images (of {})'.format(len(skipped_images),len(images)))
190
242
 
191
- print(f'Rendered detection results on {num_saved} images, '
192
- f'saved to {out_dir}')
243
+ print(f'Rendered detection results to {out_dir}')
193
244
 
245
+ annotated_image_paths = [r['annotated_image_path'] for r in rendering_results if \
246
+ r['annotated_image_path'] is not None]
247
+
194
248
  if html_output_file is not None:
195
- from md_utils import write_html_image_list
196
- write_html_image_list.write_html_image_list(html_output_file,annotated_img_paths,
249
+
250
+ html_dir = os.path.dirname(html_output_file)
251
+
252
+ html_image_info = []
253
+
254
+ for r in rendering_results:
255
+ d = {}
256
+ annotated_image_path_relative = os.path.relpath(r['annotated_image_path'],html_dir)
257
+ d['filename'] = annotated_image_path_relative
258
+ d['textStyle'] = \
259
+ 'font-family:verdana,arial,calibri;font-size:80%;' + \
260
+ 'text-align:left;margin-top:20;margin-bottom:5'
261
+ d['title'] = '{} (max conf: {})'.format(r['file'],r['max_conf'])
262
+ html_image_info.append(d)
263
+
264
+ _ = write_html_image_list.write_html_image_list(html_output_file,html_image_info,
197
265
  options=html_output_options)
198
266
 
199
- return annotated_img_paths
267
+ return annotated_image_paths
200
268
 
201
269
 
202
270
  #%% Command-line driver
@@ -221,15 +289,9 @@ def main() -> None:
221
289
  'above which to visualize bounding boxes')
222
290
  parser.add_argument(
223
291
  '-i', '--images_dir', type=str, default=None,
224
- help='Path to a local directory or a SAS URL (in double quotes) to an '
225
- 'Azure blob storage container where images are stored. This '
292
+ help='Path to a local directory where images are stored. This '
226
293
  'serves as the root directory for image paths in '
227
- 'detector_output_path. If an Azure URL, pass the -a/--is-azure '
228
- 'flag. You can use Azure Storage Explorer to obtain a SAS URL.')
229
- parser.add_argument(
230
- '-a', '--is-azure', action='store_true',
231
- help='Flag that indidcates images_dir is an Azure blob storage '
232
- 'container URL.')
294
+ 'detector_output_path.')
233
295
  parser.add_argument(
234
296
  '-n', '--sample', type=int, default=-1,
235
297
  help='Number of images to be annotated and rendered. Set to -1 '
@@ -266,7 +328,6 @@ def main() -> None:
266
328
  out_dir=args.out_dir,
267
329
  confidence_threshold=args.confidence,
268
330
  images_dir=args.images_dir,
269
- is_azure=args.is_azure,
270
331
  sample=args.sample,
271
332
  output_image_width=args.output_image_width,
272
333
  random_seed=args.random_seed,
@@ -290,17 +351,37 @@ if False:
290
351
 
291
352
  #%%
292
353
 
293
- detector_output_path = r"g:\temp\animl-runs\animl-runs\Coati_v2\manifest.csv.json"
354
+ detector_output_path = os.path.expanduser('~/postprocessing/bellevue-camera-traps/bellevue-camera-traps-2023-12-05-v5a.0.0/combined_api_outputs/bellevue-camera-traps-2023-12-05-v5a.0.0_detections.json')
294
355
  out_dir = r'g:\temp\preview'
295
- images_dir = r"g:\temp\animl-runs\animl-runs\Coati_v2"
296
- is_azure = False
356
+ images_dir = r'g:\camera_traps\camera_trap_images'
297
357
  confidence_threshold = 0.15
298
- sample = -1
358
+ sample = 50
299
359
  output_image_width = 700
300
- random_seed = None
301
- render_detections_only = False
360
+ random_seed = 1
361
+ render_detections_only = True
302
362
  classification_confidence_threshold = 0.1
303
- html_output_file = None
304
- html_output_options = None,
363
+ html_output_file = os.path.join(out_dir,'index.html')
364
+ html_output_options = None
305
365
  preserve_path_structure = False
306
-
366
+ parallelize_rendering = True
367
+ parallelize_rendering_n_cores = 10
368
+ parallelize_rendering_with_threads = False
369
+
370
+ _ = visualize_detector_output(detector_output_path,
371
+ out_dir,
372
+ images_dir,
373
+ confidence_threshold,
374
+ sample,
375
+ output_image_width,
376
+ random_seed,
377
+ render_detections_only,
378
+ classification_confidence_threshold,
379
+ html_output_file,
380
+ html_output_options,
381
+ preserve_path_structure,
382
+ parallelize_rendering,
383
+ parallelize_rendering_n_cores,
384
+ parallelize_rendering_with_threads)
385
+
386
+ from md_utils.path_utils import open_file
387
+ open_file(html_output_file)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: megadetector
3
- Version: 5.0.6
3
+ Version: 5.0.8
4
4
  Summary: MegaDetector is an AI model that helps conservation folks spend less time doing boring things with camera trap images.
5
5
  Author-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
6
6
  Maintainer-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
@@ -60,7 +60,7 @@ If you want to learn more about what MegaDetector is all about, head over to the
60
60
 
61
61
  ### If you are an ecologist...
62
62
 
63
- If you are an ecologist looking to use MegaDetector to help you get through your camera trap images, you probably don't want this package. We recommend starting with our "[Getting started with MegaDetector](https://github.com/agentmorris/MegaDetector/blob/main/collaborations.md)" page, then digging in to the [MegaDetector User Guide](https://github.com/agentmorris/MegaDetector/blob/main/megadetector.md), which will walk you through the process of using MegaDetector. That journey will <i>not</i> involve this package.
63
+ If you are an ecologist looking to use MegaDetector to help you get through your camera trap images, you probably don't want this package. We recommend starting with our "[Getting started with MegaDetector](https://github.com/agentmorris/MegaDetector/blob/main/collaborations.md)" page, then digging in to the [MegaDetector User Guide](https://github.com/agentmorris/MegaDetector/blob/main/megadetector.md), which will walk you through the process of using MegaDetector. That journey will <i>not</i> involve this Python package.
64
64
 
65
65
  ### If you are a computer-vision-y type...
66
66
 
@@ -68,7 +68,7 @@ If you are a computer-vision-y person looking to run or fine-tune MegaDetector p
68
68
 
69
69
  ## Reasons you might want to use this package
70
70
 
71
- If you want to programatically interact with the postprocessing tools from the MegaDetector repo, or programmatically run MegaDetector in a way that produces [Timelapse](https://saul.cpsc.ucalgary.ca/timelapse)-friendly output (i.e., output in the standard [MegaDetector output format](https://github.com/agentmorris/MegaDetector/tree/main/api/batch_processing#megadetector-batch-output-format)), this package might be for you.
71
+ If you want to programmatically interact with the postprocessing tools from the MegaDetector repo, or programmatically run MegaDetector in a way that produces [Timelapse](https://saul.cpsc.ucalgary.ca/timelapse)-friendly output (i.e., output in the standard [MegaDetector output format](https://github.com/agentmorris/MegaDetector/tree/main/api/batch_processing#megadetector-batch-output-format)), this package might be for you.
72
72
 
73
73
  Although even if that describes you, you <i>still</i> might be better off cloning the MegaDetector repo. Pip-installability requires that some dependencies be newer than what was available at the time MDv5 was trained, so results are <i>very slightly</i> different than results produced in the "official" environment. These differences <i>probably</i> don't matter much, but they have not been formally characterized.
74
74
 
@@ -95,15 +95,13 @@ temporary_filename = url_utils.download_url(image_url)
95
95
 
96
96
  image = vis_utils.load_image(temporary_filename)
97
97
 
98
- # This will automatically download MDv5a to the system temp folder;
99
- # you can also specify a filename explicitly, or set the $MDV5A
100
- # environment variable to point to the model file.
98
+ # This will automatically download MDv5a; you can also specify a filename.
101
99
  model = run_detector.load_detector('MDV5A')
102
100
 
103
101
  result = model.generate_detections_one_image(image)
104
102
 
105
103
  detections_above_threshold = [d for d in result['detections'] if d['conf'] > 0.2]
106
- print('Found {} detection above threshold'.format(len(detections_above_threshold)))
104
+ print('Found {} detections above threshold'.format(len(detections_above_threshold)))
107
105
  ```
108
106
 
109
107
  #### Run MegaDetector on a folder of images
@@ -120,14 +118,14 @@ output_file = os.path.expanduser('~/megadetector_output_test.json')
120
118
  # Recursively find images
121
119
  image_file_names = path_utils.find_images(image_folder,recursive=True)
122
120
 
123
- # This will automatically download MDv5a to the system temp folder;
124
- # you can also specify a filename explicitly, or set the $MDV5A
125
- # environment variable to point to the model file.
121
+ # This will automatically download MDv5a; you can also specify a filename.
126
122
  results = load_and_run_detector_batch('MDV5A', image_file_names)
127
123
 
128
- # Write results as relative filenames, this is what Timelapse
129
- # and other downstream tools expect.
130
- write_results_to_file(results,output_file,relative_path_base=image_folder)
124
+ # Write results to a format that Timelapse and other downstream tools like.
125
+ write_results_to_file(results,
126
+ output_file,
127
+ relative_path_base=image_folder,
128
+ detector_file=detector_filename)
131
129
  ```
132
130
 
133
131
  ## Contact