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
@@ -36,14 +36,23 @@ taxonomy_urls = {
36
36
  }
37
37
 
38
38
  files_to_unzip = {
39
- 'GBIF': ['backbone/Taxon.tsv', 'backbone/VernacularName.tsv'],
39
+ # GBIF used to put everything in a "backbone" folder within the zipfile, but as of
40
+ # 12.2023, this is no longer the case.
41
+ # 'GBIF': ['backbone/Taxon.tsv', 'backbone/VernacularName.tsv'],
42
+ 'GBIF': ['Taxon.tsv', 'VernacularName.tsv'],
40
43
  'iNaturalist': ['taxa.csv']
41
44
  }
42
45
 
43
46
  # As of 2020.05.12:
44
47
  #
45
48
  # GBIF: ~777MB zipped, ~1.6GB taxonomy
46
- # iNat: ~2.2GB zipped, ~51MB taxonomy
49
+ # iNat: ~2.2GB zipped, ~51MB taxonomy (most of the zipfile is observations)
50
+
51
+ # As of 2023.12.29:
52
+ #
53
+ # GBIF: ~948MB zipped, ~2.2GB taxonomy
54
+ # iNat: ~6.7GB zipped, ~62MB taxonomy (most of the zipfile is observations)
55
+
47
56
 
48
57
  os.makedirs(taxonomy_download_dir, exist_ok=True)
49
58
  for taxonomy_name in taxonomy_urls:
@@ -99,15 +108,16 @@ def initialize_taxonomy_lookup(force_init=False) -> None:
99
108
  gbif_taxon_id_to_scientific,\
100
109
  gbif_scientific_to_taxon_id
101
110
 
111
+
102
112
  ## Load serialized taxonomy info if we've already saved it
103
113
 
104
114
  if (not force_init) and (inat_taxonomy is not None):
105
115
  print('Skipping taxonomy re-init')
106
116
  return
107
117
 
108
- if os.path.isfile(serialized_structures_file):
118
+ if (not force_init) and (os.path.isfile(serialized_structures_file)):
109
119
 
110
- print(f'Reading taxonomy data from {serialized_structures_file}')
120
+ print(f'De-serializing taxonomy data from {serialized_structures_file}')
111
121
 
112
122
  with open(serialized_structures_file, 'rb') as f:
113
123
  structures_to_serialize = pickle.load(f)
@@ -125,6 +135,7 @@ def initialize_taxonomy_lookup(force_init=False) -> None:
125
135
  gbif_vernacular_to_taxon_id,\
126
136
  gbif_taxon_id_to_scientific,\
127
137
  gbif_scientific_to_taxon_id = structures_to_serialize
138
+
128
139
  return
129
140
 
130
141
 
@@ -135,6 +146,9 @@ def initialize_taxonomy_lookup(force_init=False) -> None:
135
146
  for taxonomy_name, zip_url in taxonomy_urls.items():
136
147
 
137
148
  need_to_download = False
149
+
150
+ if force_init:
151
+ need_to_download = True
138
152
 
139
153
  # Don't download the zipfile if we've already unzipped what we need
140
154
  for fn in files_to_unzip[taxonomy_name]:
@@ -150,11 +164,11 @@ def initialize_taxonomy_lookup(force_init=False) -> None:
150
164
  zipfile_path = os.path.join(
151
165
  taxonomy_download_dir, zip_url.split('/')[-1])
152
166
 
153
- # Bypasses download if the file exists already
167
+ # Bypasses download if the file exists already (unless force_init is set)
154
168
  url_utils.download_url(
155
169
  zip_url, os.path.join(zipfile_path),
156
170
  progress_updater=url_utils.DownloadProgressBar(),
157
- verbose=True)
171
+ verbose=True,force_download=force_init)
158
172
 
159
173
  # Unzip the files we need
160
174
  files_we_need = files_to_unzip[taxonomy_name]
@@ -166,7 +180,7 @@ def initialize_taxonomy_lookup(force_init=False) -> None:
166
180
  target_file = os.path.join(
167
181
  taxonomy_download_dir, taxonomy_name, os.path.basename(fn))
168
182
 
169
- if os.path.isfile(target_file):
183
+ if (not force_init) and (os.path.isfile(target_file)):
170
184
  print(f'Bypassing unzip of {target_file}, file exists')
171
185
  else:
172
186
  os.makedirs(os.path.basename(target_file),exist_ok=True)
@@ -185,13 +199,16 @@ def initialize_taxonomy_lookup(force_init=False) -> None:
185
199
  # name file
186
200
 
187
201
  # Load iNat taxonomy
188
- inat_taxonomy = pd.read_csv(os.path.join(taxonomy_download_dir, 'iNaturalist', 'taxa.csv'))
202
+ inat_taxonomy_file = os.path.join(taxonomy_download_dir, 'iNaturalist', 'taxa.csv')
203
+ print('Loading iNat taxonomy from {}'.format(inat_taxonomy_file))
204
+ inat_taxonomy = pd.read_csv(inat_taxonomy_file)
189
205
  inat_taxonomy['scientificName'] = inat_taxonomy['scientificName'].fillna('').str.strip()
190
206
  inat_taxonomy['vernacularName'] = inat_taxonomy['vernacularName'].fillna('').str.strip()
191
207
 
192
208
  # Load GBIF taxonomy
193
- gbif_taxonomy = pd.read_csv(os.path.join(
194
- taxonomy_download_dir, 'GBIF', 'Taxon.tsv'), sep='\t')
209
+ gbif_taxonomy_file = os.path.join(taxonomy_download_dir, 'GBIF', 'Taxon.tsv')
210
+ print('Loading GBIF taxonomy from {}'.format(gbif_taxonomy_file))
211
+ gbif_taxonomy = pd.read_csv(gbif_taxonomy_file, sep='\t')
195
212
  gbif_taxonomy['scientificName'] = gbif_taxonomy['scientificName'].fillna('').str.strip()
196
213
  gbif_taxonomy['canonicalName'] = gbif_taxonomy['canonicalName'].fillna('').str.strip()
197
214
 
@@ -249,7 +266,8 @@ def initialize_taxonomy_lookup(force_init=False) -> None:
249
266
 
250
267
  # Build iNat dictionaries
251
268
 
252
- # row = inat_taxonomy.iloc[0]
269
+ print('Building lookup dictionaries for iNat taxonomy')
270
+
253
271
  for i_row, row in tqdm(inat_taxonomy.iterrows(), total=len(inat_taxonomy)):
254
272
 
255
273
  taxon_id = row['taxonID']
@@ -267,6 +285,8 @@ def initialize_taxonomy_lookup(force_init=False) -> None:
267
285
 
268
286
  # Build GBIF dictionaries
269
287
 
288
+ print('Building lookup dictionaries for GBIF taxonomy')
289
+
270
290
  for i_row, row in tqdm(gbif_taxonomy.iterrows(), total=len(gbif_taxonomy)):
271
291
 
272
292
  taxon_id = row['taxonID']
@@ -320,13 +340,13 @@ def initialize_taxonomy_lookup(force_init=False) -> None:
320
340
  gbif_scientific_to_taxon_id
321
341
  ]
322
342
 
323
- print('Serializing...', end='')
343
+ print('Serializing to {}...'.format(serialized_structures_file), end='')
324
344
  if not os.path.isfile(serialized_structures_file):
325
345
  with open(serialized_structures_file, 'wb') as p:
326
346
  pickle.dump(structures_to_serialize, p)
327
347
  print(' done')
328
348
 
329
- # ...def initialize_taxonomy_lookup()
349
+ # ...def initialize_taxonomy_lookup(...)
330
350
 
331
351
 
332
352
  def get_scientific_name_from_row(r):
@@ -45,7 +45,7 @@ def check_taxonomy_csv(csv_path: str) -> None:
45
45
  num_taxon_level_errors = 0
46
46
  num_scientific_name_errors = 0
47
47
 
48
- for i, row in taxonomy_df.iterrows():
48
+ for i_row, row in taxonomy_df.iterrows():
49
49
 
50
50
  ds = row['dataset_name']
51
51
  ds_label = row['query']
@@ -81,14 +81,14 @@ def check_taxonomy_csv(csv_path: str) -> None:
81
81
  node.add_id(id_source, int(taxon_id)) # np.int64 -> int
82
82
  if j == 0:
83
83
  if level != taxon_level:
84
- print(f'row: {i}, {ds}, {ds_label}')
84
+ print(f'row: {i_row}, {ds}, {ds_label}')
85
85
  print(f'- taxonomy_level column: {level}, '
86
86
  f'level from taxonomy_string: {taxon_level}')
87
87
  print()
88
88
  num_taxon_level_errors += 1
89
89
 
90
90
  if scientific_name != taxon_name:
91
- print(f'row: {i}, {ds}, {ds_label}')
91
+ print(f'row: {i_row}, {ds}, {ds_label}')
92
92
  print(f'- scientific_name column: {scientific_name}, '
93
93
  f'name from taxonomy_string: {taxon_name}')
94
94
  print()
@@ -97,7 +97,7 @@ def check_taxonomy_csv(csv_path: str) -> None:
97
97
  taxon_child = node
98
98
 
99
99
  # ...for each row in the taxonomy file
100
-
100
+
101
101
  assert nx.is_directed_acyclic_graph(graph)
102
102
 
103
103
  for node in graph.nodes:
@@ -123,6 +123,8 @@ def check_taxonomy_csv(csv_path: str) -> None:
123
123
  except AssertionError as e:
124
124
  print(f'At least one node has unresolved ambiguous parents: {e}')
125
125
 
126
+ print('Processed {} rows from {}'.format(len(taxonomy_df),csv_path))
127
+
126
128
  print('num taxon level errors:', num_taxon_level_errors)
127
129
  print('num scientific name errors:', num_scientific_name_errors)
128
130
 
@@ -154,4 +156,4 @@ if False:
154
156
  import os
155
157
  csv_path = os.path.expanduser('~/lila/lila-taxonomy-mapping_release.csv')
156
158
  check_taxonomy_csv(csv_path)
157
-
159
+
@@ -1,183 +0,0 @@
1
- ########
2
- #
3
- # visualize_megadb.py
4
- #
5
- # Create visual previews of images/sequences in MegaDB.
6
- #
7
- ########
8
-
9
- #%% Imports
10
-
11
- import argparse
12
- import json
13
- import os
14
- import sys
15
- from random import shuffle
16
- from multiprocessing.pool import ThreadPool
17
- from functools import partial
18
- import io
19
-
20
- from tqdm import tqdm
21
-
22
- from data_management.megadb.megadb_utils import MegadbUtils
23
- from md_utils.write_html_image_list import write_html_image_list
24
- from md_visualization import visualization_utils as vis_utils
25
-
26
-
27
- #%% Support functions
28
-
29
- def render_image_info(rendering, args):
30
-
31
- storage_client = rendering['storage_client']
32
- image_obj = io.BytesIO()
33
-
34
- try:
35
- storage_client.download_blob(rendering['blob_path']).readinto(image_obj)
36
- except Exception as e:
37
- print(f'Image not found in blob storage: {rendering["blob_path"]}')
38
- print(e)
39
- return
40
-
41
- # resize is for displaying them more quickly
42
- image = vis_utils.resize_image(
43
- vis_utils.open_image(image_obj), args.output_image_width)
44
- vis_utils.render_megadb_bounding_boxes(rendering['bbox'], image)
45
-
46
- annotated_img_name = rendering['annotated_img_name']
47
- annotated_img_path = os.path.join(
48
- args.output_dir, 'rendered_images', annotated_img_name)
49
- image.save(annotated_img_path)
50
-
51
-
52
- def visualize_sequences(datasets_table, sequences, args):
53
-
54
- num_images = 0
55
-
56
- images_html = []
57
- rendering_info = []
58
-
59
- for seq in sequences:
60
- if 'images' not in seq:
61
- continue
62
-
63
- # dataset and seq_id are required fields
64
- dataset_name = seq['dataset']
65
- seq_id = seq['seq_id']
66
-
67
- # sort the images in the sequence
68
-
69
- images_in_seq = sorted(seq['images'], key=lambda x: x['frame_num']) if len(seq['images']) > 1 else seq['images']
70
-
71
- for im in images_in_seq:
72
- if args.trim_to_images_bboxes_labeled and 'bbox' not in im:
73
- continue
74
-
75
- num_images += 1
76
-
77
- blob_path = MegadbUtils.get_full_path(
78
- datasets_table, dataset_name, im['file'])
79
- frame_num = im.get('frame_num', -1)
80
-
81
- # if no class label on the image, show class label on the sequence
82
- im_class = im.get('class', None)
83
- if im_class is None:
84
- im_class = seq.get('class', [])
85
-
86
- rendering = {}
87
- rendering['storage_client'] = MegadbUtils.get_storage_client(
88
- datasets_table, dataset_name)
89
- rendering['blob_path'] = blob_path
90
- rendering['bbox'] = im.get('bbox', [])
91
-
92
- annotated_img_name = 'anno_' + blob_path.replace('/', args.pathsep_replacement).replace('\\', args.pathsep_replacement)
93
- rendering['annotated_img_name'] = annotated_img_name
94
-
95
- rendering_info.append(rendering)
96
-
97
- images_html.append({
98
- 'filename': 'rendered_images/{}'.format(annotated_img_name),
99
- 'title': 'Seq ID: {}. Frame number: {}<br/> Image file: {}<br/> number of boxes: {}, image class labels: {}'.format(seq_id, frame_num, blob_path, len(rendering['bbox']), im_class),
100
- 'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;text-align:left;margin-top:20;margin-bottom:5'
101
- })
102
-
103
- if num_images >= args.num_to_visualize:
104
- print('num_images visualized is {}'.format(num_images))
105
- break
106
-
107
- # pool = ThreadPool()
108
- render_image_info_partial = partial(render_image_info, args=args)
109
- # print('len of rendering_info', len(rendering_info))
110
- # tqdm(pool.imap_unordered(render_image_info_partial, rendering_info), total=len(rendering_info))
111
-
112
- for rendering in tqdm(rendering_info):
113
- render_image_info_partial(rendering)
114
-
115
- print('Making HTML...')
116
-
117
- html_path = os.path.join(args.output_dir, 'index.html')
118
- # options = write_html_image_list()
119
- # options['headerHtml']
120
- write_html_image_list(
121
- filename=html_path,
122
- images=images_html
123
- )
124
-
125
-
126
- #%% Command-line driver
127
-
128
- def main():
129
-
130
- parser = argparse.ArgumentParser()
131
- parser.add_argument(
132
- 'megadb_entries', type=str,
133
- help='Path to a json list of MegaDB entries')
134
- parser.add_argument(
135
- 'output_dir', action='store', type=str,
136
- help='Output directory for html and rendered images')
137
- parser.add_argument(
138
- '--trim_to_images_bboxes_labeled', action='store_true',
139
- help='Only include images that have been sent for bbox labeling (but '
140
- 'may be actually empty). Turn this on if QAing annotations.')
141
- parser.add_argument(
142
- '--num_to_visualize', action='store', type=int, default=200,
143
- help='Number of images to visualize (all comformant images in a '
144
- 'sequence are shown, so may be a few more than specified). '
145
- 'Sequences are shuffled. Default: 200. Use -1 to visualize all.')
146
- parser.add_argument(
147
- '--pathsep_replacement', action='store', type=str, default='~',
148
- help='Replace path separators in relative filenames with another '
149
- 'character (default ~)')
150
- parser.add_argument(
151
- '-w', '--output_image_width', type=int, default=700,
152
- help='an integer indicating the desired width in pixels of the output '
153
- 'annotated images. Use -1 to not resize.')
154
-
155
- if len(sys.argv[1:]) == 0:
156
- parser.print_help()
157
- parser.exit()
158
-
159
- args = parser.parse_args()
160
-
161
- assert 'COSMOS_ENDPOINT' in os.environ and 'COSMOS_KEY' in os.environ
162
-
163
- os.makedirs(args.output_dir, exist_ok=True)
164
- os.makedirs(os.path.join(args.output_dir, 'rendered_images'))
165
-
166
- print('Connecting to MegaDB to get the datasets table...')
167
- megadb_utils = MegadbUtils()
168
- datasets_table = megadb_utils.get_datasets_table()
169
-
170
- print('Loading the MegaDB entries...')
171
- with open(args.megadb_entries) as f:
172
- sequences = json.load(f)
173
- print('Total number of sequences: {}'.format(len(sequences)))
174
-
175
- # print('Checking that the MegaDB entries conform to the schema...')
176
- # sequences_schema_check.sequences_schema_check(sequences)
177
-
178
- shuffle(sequences)
179
- visualize_sequences(datasets_table, sequences, args)
180
-
181
-
182
- if __name__ == '__main__':
183
- main()