megadetector 5.0.7__py3-none-any.whl → 5.0.9__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 (191) hide show
  1. api/__init__.py +0 -0
  2. api/batch_processing/__init__.py +0 -0
  3. api/batch_processing/api_core/__init__.py +0 -0
  4. api/batch_processing/api_core/batch_service/__init__.py +0 -0
  5. api/batch_processing/api_core/batch_service/score.py +0 -1
  6. api/batch_processing/api_core/server_job_status_table.py +0 -1
  7. api/batch_processing/api_core_support/__init__.py +0 -0
  8. api/batch_processing/api_core_support/aggregate_results_manually.py +0 -1
  9. api/batch_processing/api_support/__init__.py +0 -0
  10. api/batch_processing/api_support/summarize_daily_activity.py +0 -1
  11. api/batch_processing/data_preparation/__init__.py +0 -0
  12. api/batch_processing/data_preparation/manage_local_batch.py +93 -79
  13. api/batch_processing/data_preparation/manage_video_batch.py +8 -8
  14. api/batch_processing/integration/digiKam/xmp_integration.py +0 -1
  15. api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -1
  16. api/batch_processing/postprocessing/__init__.py +0 -0
  17. api/batch_processing/postprocessing/add_max_conf.py +12 -12
  18. api/batch_processing/postprocessing/categorize_detections_by_size.py +32 -14
  19. api/batch_processing/postprocessing/combine_api_outputs.py +69 -55
  20. api/batch_processing/postprocessing/compare_batch_results.py +114 -44
  21. api/batch_processing/postprocessing/convert_output_format.py +62 -19
  22. api/batch_processing/postprocessing/load_api_results.py +17 -20
  23. api/batch_processing/postprocessing/md_to_coco.py +31 -21
  24. api/batch_processing/postprocessing/md_to_labelme.py +165 -68
  25. api/batch_processing/postprocessing/merge_detections.py +40 -15
  26. api/batch_processing/postprocessing/postprocess_batch_results.py +270 -186
  27. api/batch_processing/postprocessing/remap_detection_categories.py +170 -0
  28. api/batch_processing/postprocessing/render_detection_confusion_matrix.py +75 -39
  29. api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +53 -44
  30. api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +25 -14
  31. api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +244 -160
  32. api/batch_processing/postprocessing/separate_detections_into_folders.py +159 -114
  33. api/batch_processing/postprocessing/subset_json_detector_output.py +146 -169
  34. api/batch_processing/postprocessing/top_folders_to_bottom.py +77 -43
  35. api/synchronous/__init__.py +0 -0
  36. api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  37. api/synchronous/api_core/animal_detection_api/api_backend.py +0 -2
  38. api/synchronous/api_core/animal_detection_api/api_frontend.py +266 -268
  39. api/synchronous/api_core/animal_detection_api/config.py +35 -35
  40. api/synchronous/api_core/tests/__init__.py +0 -0
  41. api/synchronous/api_core/tests/load_test.py +109 -109
  42. classification/__init__.py +0 -0
  43. classification/aggregate_classifier_probs.py +21 -24
  44. classification/analyze_failed_images.py +11 -13
  45. classification/cache_batchapi_outputs.py +51 -51
  46. classification/create_classification_dataset.py +69 -68
  47. classification/crop_detections.py +54 -53
  48. classification/csv_to_json.py +97 -100
  49. classification/detect_and_crop.py +105 -105
  50. classification/evaluate_model.py +43 -42
  51. classification/identify_mislabeled_candidates.py +47 -46
  52. classification/json_to_azcopy_list.py +10 -10
  53. classification/json_validator.py +72 -71
  54. classification/map_classification_categories.py +44 -43
  55. classification/merge_classification_detection_output.py +68 -68
  56. classification/prepare_classification_script.py +157 -154
  57. classification/prepare_classification_script_mc.py +228 -228
  58. classification/run_classifier.py +27 -26
  59. classification/save_mislabeled.py +30 -30
  60. classification/train_classifier.py +20 -20
  61. classification/train_classifier_tf.py +21 -22
  62. classification/train_utils.py +10 -10
  63. data_management/__init__.py +0 -0
  64. data_management/annotations/__init__.py +0 -0
  65. data_management/annotations/annotation_constants.py +18 -31
  66. data_management/camtrap_dp_to_coco.py +238 -0
  67. data_management/cct_json_utils.py +107 -59
  68. data_management/cct_to_md.py +176 -158
  69. data_management/cct_to_wi.py +247 -219
  70. data_management/coco_to_labelme.py +272 -0
  71. data_management/coco_to_yolo.py +86 -62
  72. data_management/databases/__init__.py +0 -0
  73. data_management/databases/add_width_and_height_to_db.py +20 -16
  74. data_management/databases/combine_coco_camera_traps_files.py +35 -31
  75. data_management/databases/integrity_check_json_db.py +130 -83
  76. data_management/databases/subset_json_db.py +25 -16
  77. data_management/generate_crops_from_cct.py +27 -45
  78. data_management/get_image_sizes.py +188 -144
  79. data_management/importers/add_nacti_sizes.py +8 -8
  80. data_management/importers/add_timestamps_to_icct.py +78 -78
  81. data_management/importers/animl_results_to_md_results.py +158 -160
  82. data_management/importers/auckland_doc_test_to_json.py +9 -9
  83. data_management/importers/auckland_doc_to_json.py +8 -8
  84. data_management/importers/awc_to_json.py +7 -7
  85. data_management/importers/bellevue_to_json.py +15 -15
  86. data_management/importers/cacophony-thermal-importer.py +13 -13
  87. data_management/importers/carrizo_shrubfree_2018.py +8 -8
  88. data_management/importers/carrizo_trail_cam_2017.py +8 -8
  89. data_management/importers/cct_field_adjustments.py +9 -9
  90. data_management/importers/channel_islands_to_cct.py +10 -10
  91. data_management/importers/eMammal/copy_and_unzip_emammal.py +1 -0
  92. data_management/importers/ena24_to_json.py +7 -7
  93. data_management/importers/filenames_to_json.py +8 -8
  94. data_management/importers/helena_to_cct.py +7 -7
  95. data_management/importers/idaho-camera-traps.py +7 -7
  96. data_management/importers/idfg_iwildcam_lila_prep.py +10 -10
  97. data_management/importers/jb_csv_to_json.py +9 -9
  98. data_management/importers/mcgill_to_json.py +8 -8
  99. data_management/importers/missouri_to_json.py +18 -18
  100. data_management/importers/nacti_fieldname_adjustments.py +10 -10
  101. data_management/importers/noaa_seals_2019.py +8 -8
  102. data_management/importers/pc_to_json.py +7 -7
  103. data_management/importers/plot_wni_giraffes.py +7 -7
  104. data_management/importers/prepare-noaa-fish-data-for-lila.py +359 -359
  105. data_management/importers/prepare_zsl_imerit.py +7 -7
  106. data_management/importers/rspb_to_json.py +8 -8
  107. data_management/importers/save_the_elephants_survey_A.py +8 -8
  108. data_management/importers/save_the_elephants_survey_B.py +9 -9
  109. data_management/importers/snapshot_safari_importer.py +26 -26
  110. data_management/importers/snapshot_safari_importer_reprise.py +665 -665
  111. data_management/importers/snapshot_serengeti_lila.py +14 -14
  112. data_management/importers/sulross_get_exif.py +8 -9
  113. data_management/importers/timelapse_csv_set_to_json.py +11 -11
  114. data_management/importers/ubc_to_json.py +13 -13
  115. data_management/importers/umn_to_json.py +7 -7
  116. data_management/importers/wellington_to_json.py +8 -8
  117. data_management/importers/wi_to_json.py +9 -9
  118. data_management/importers/zamba_results_to_md_results.py +181 -181
  119. data_management/labelme_to_coco.py +309 -159
  120. data_management/labelme_to_yolo.py +103 -60
  121. data_management/lila/__init__.py +0 -0
  122. data_management/lila/add_locations_to_island_camera_traps.py +9 -9
  123. data_management/lila/add_locations_to_nacti.py +147 -147
  124. data_management/lila/create_lila_blank_set.py +114 -31
  125. data_management/lila/create_lila_test_set.py +8 -8
  126. data_management/lila/create_links_to_md_results_files.py +106 -106
  127. data_management/lila/download_lila_subset.py +92 -90
  128. data_management/lila/generate_lila_per_image_labels.py +56 -43
  129. data_management/lila/get_lila_annotation_counts.py +18 -15
  130. data_management/lila/get_lila_image_counts.py +11 -11
  131. data_management/lila/lila_common.py +103 -70
  132. data_management/lila/test_lila_metadata_urls.py +132 -116
  133. data_management/ocr_tools.py +173 -128
  134. data_management/read_exif.py +161 -99
  135. data_management/remap_coco_categories.py +84 -0
  136. data_management/remove_exif.py +58 -62
  137. data_management/resize_coco_dataset.py +32 -44
  138. data_management/wi_download_csv_to_coco.py +246 -0
  139. data_management/yolo_output_to_md_output.py +86 -73
  140. data_management/yolo_to_coco.py +535 -95
  141. detection/__init__.py +0 -0
  142. detection/detector_training/__init__.py +0 -0
  143. detection/process_video.py +85 -33
  144. detection/pytorch_detector.py +43 -25
  145. detection/run_detector.py +157 -72
  146. detection/run_detector_batch.py +189 -114
  147. detection/run_inference_with_yolov5_val.py +118 -51
  148. detection/run_tiled_inference.py +113 -42
  149. detection/tf_detector.py +51 -28
  150. detection/video_utils.py +606 -521
  151. docs/source/conf.py +43 -0
  152. md_utils/__init__.py +0 -0
  153. md_utils/azure_utils.py +9 -9
  154. md_utils/ct_utils.py +249 -70
  155. md_utils/directory_listing.py +59 -64
  156. md_utils/md_tests.py +968 -862
  157. md_utils/path_utils.py +655 -155
  158. md_utils/process_utils.py +157 -133
  159. md_utils/sas_blob_utils.py +20 -20
  160. md_utils/split_locations_into_train_val.py +45 -32
  161. md_utils/string_utils.py +33 -10
  162. md_utils/url_utils.py +208 -27
  163. md_utils/write_html_image_list.py +51 -35
  164. md_visualization/__init__.py +0 -0
  165. md_visualization/plot_utils.py +102 -109
  166. md_visualization/render_images_with_thumbnails.py +34 -34
  167. md_visualization/visualization_utils.py +908 -311
  168. md_visualization/visualize_db.py +109 -58
  169. md_visualization/visualize_detector_output.py +61 -42
  170. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/METADATA +21 -17
  171. megadetector-5.0.9.dist-info/RECORD +224 -0
  172. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/WHEEL +1 -1
  173. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/top_level.txt +1 -0
  174. taxonomy_mapping/__init__.py +0 -0
  175. taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +342 -335
  176. taxonomy_mapping/map_new_lila_datasets.py +154 -154
  177. taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -134
  178. taxonomy_mapping/preview_lila_taxonomy.py +591 -591
  179. taxonomy_mapping/retrieve_sample_image.py +12 -12
  180. taxonomy_mapping/simple_image_download.py +11 -11
  181. taxonomy_mapping/species_lookup.py +10 -10
  182. taxonomy_mapping/taxonomy_csv_checker.py +18 -18
  183. taxonomy_mapping/taxonomy_graph.py +47 -47
  184. taxonomy_mapping/validate_lila_category_mappings.py +83 -76
  185. data_management/cct_json_to_filename_json.py +0 -89
  186. data_management/cct_to_csv.py +0 -140
  187. data_management/databases/remove_corrupted_images_from_db.py +0 -191
  188. detection/detector_training/copy_checkpoints.py +0 -43
  189. md_visualization/visualize_megadb.py +0 -183
  190. megadetector-5.0.7.dist-info/RECORD +0 -202
  191. {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/LICENSE +0 -0
@@ -1,16 +1,19 @@
1
- ########
2
- #
3
- # labelme_to_yolo.py
4
- #
5
- # Create YOLO .txt files in a folder containing labelme .json files.
6
- #
7
- ########
1
+ """
2
+
3
+ labelme_to_yolo.py
4
+
5
+ Create YOLO .txt files in a folder containing labelme .json files.
6
+
7
+ """
8
8
 
9
9
  #%% Imports
10
10
 
11
11
  import os
12
12
  import json
13
13
 
14
+ from multiprocessing.pool import Pool, ThreadPool
15
+ from functools import partial
16
+
14
17
  from md_utils.path_utils import recursive_file_list
15
18
  from tqdm import tqdm
16
19
 
@@ -21,22 +24,21 @@ def labelme_file_to_yolo_file(labelme_file,
21
24
  category_name_to_category_id,
22
25
  yolo_file=None,
23
26
  required_token=None,
24
- right_edge_quantization_threshold=None,
25
27
  overwrite_behavior='overwrite'):
26
28
  """
27
29
  Convert the single .json file labelme_file to yolo format, writing the results to the text
28
30
  file yolo_file (defaults to s/json/txt).
29
31
 
30
- If required_token is not None and the labelme_file does not contain the key [required_token],
31
- no-ops.
32
+ If required_token is not None and the dict in labelme_file does not contain the key [required_token],
33
+ this function no-ops (i.e., does not generate a YOLO file).
32
34
 
33
- right_edge_quantization_threshold is an off-by-default hack to handle cases where
34
- boxes that really should be running off the right side of the image only extend like 99%
35
- of the way there, due to what appears to be a slight bias inherent to MD. If a box extends
36
- within [right_edge_quantization_threshold] (a small number, from 0 to 1, but probably around
37
- 0.02) of the right edge of the image, it will be extended to the far right edge.
35
+ overwrite_behavior should be 'skip' or 'overwrite' (default).
38
36
  """
39
37
 
38
+ result = {}
39
+ result['labelme_file'] = labelme_file
40
+ result['status'] = 'unknown'
41
+
40
42
  assert os.path.isfile(labelme_file), 'Could not find labelme .json file {}'.format(labelme_file)
41
43
  assert labelme_file.endswith('.json'), 'Illegal labelme .json file {}'.format(labelme_file)
42
44
 
@@ -45,7 +47,8 @@ def labelme_file_to_yolo_file(labelme_file,
45
47
 
46
48
  if os.path.isfile(yolo_file):
47
49
  if overwrite_behavior == 'skip':
48
- return
50
+ result['status'] = 'skip-exists'
51
+ return result
49
52
  else:
50
53
  assert overwrite_behavior == 'overwrite', \
51
54
  'Unrecognized overwrite behavior {}'.format(overwrite_behavior)
@@ -54,7 +57,8 @@ def labelme_file_to_yolo_file(labelme_file,
54
57
  labelme_data = json.load(f)
55
58
 
56
59
  if required_token is not None and required_token not in labelme_data:
57
- return
60
+ result['status'] = 'skip-no-required-token'
61
+ return result
58
62
 
59
63
  im_height = labelme_data['imageHeight']
60
64
  im_width = labelme_data['imageWidth']
@@ -73,7 +77,7 @@ def labelme_file_to_yolo_file(labelme_file,
73
77
  p0 = shape['points'][0]
74
78
  p1 = shape['points'][1]
75
79
 
76
- # LabelMe: [[x0,y0],[x1,y1]] (arbitrarily sorted) (absolute coordinates)
80
+ # Labelme: [[x0,y0],[x1,y1]] (arbitrarily sorted) (absolute coordinates)
77
81
  #
78
82
  # YOLO: [class, x_center, y_center, width, height] (normalized coordinates)
79
83
  minx_abs = min(p0[0],p1[0])
@@ -83,10 +87,12 @@ def labelme_file_to_yolo_file(labelme_file,
83
87
 
84
88
  if (minx_abs >= (im_width-1)) or (maxx_abs <= 0) or \
85
89
  (miny_abs >= (im_height-1)) or (maxy_abs <= 0):
86
- print('Skipping invalid shape in {}'.format(labelme_file))
90
+ print('Skipping invalid shape in {}'.format(labelme_file))
87
91
  continue
88
92
 
89
- # Clip to [0,1]
93
+ # Clip to [0,1]... it's not obvious that the YOLO format doesn't allow bounding
94
+ # boxes to extend outside the image, but YOLOv5 and YOLOv8 get sad about boxes
95
+ # that extend outside the image.
90
96
  maxx_abs = min(maxx_abs,im_width-1)
91
97
  maxy_abs = min(maxy_abs,im_height-1)
92
98
  minx_abs = max(minx_abs,0.0)
@@ -97,11 +103,6 @@ def labelme_file_to_yolo_file(labelme_file,
97
103
  miny_rel = miny_abs / (im_height-1)
98
104
  maxy_rel = maxy_abs / (im_height-1)
99
105
 
100
- if (right_edge_quantization_threshold is not None):
101
- right_edge_distance = 1.0 - maxx_rel
102
- if right_edge_distance < right_edge_quantization_threshold:
103
- maxx_rel = 1.0
104
-
105
106
  assert maxx_rel >= minx_rel
106
107
  assert maxy_rel >= miny_rel
107
108
 
@@ -119,32 +120,45 @@ def labelme_file_to_yolo_file(labelme_file,
119
120
  with open(yolo_file,'w') as f:
120
121
  for s in yolo_lines:
121
122
  f.write(s + '\n')
122
-
123
+
124
+ result['status'] = 'converted'
125
+ return result
126
+
123
127
 
124
128
  def labelme_folder_to_yolo(labelme_folder,
125
129
  category_name_to_category_id=None,
126
130
  required_token=None,
127
- right_edge_quantization_threshold=None,
128
- overwrite_behavior='overwrite'):
131
+ overwrite_behavior='overwrite',
132
+ relative_filenames_to_convert=None,
133
+ n_workers=1,
134
+ use_threads=True):
129
135
  """
130
136
  Given a folder with images and labelme .json files, convert the .json files
131
137
  to YOLO .txt format. If category_name_to_category_id is None, first reads
132
138
  all the labels in the folder to build a zero-indexed name --> ID mapping.
133
139
 
134
140
  If required_token is not None and a labelme_file does not contain the key [required_token],
135
- it won't be converted.
141
+ it won't be converted. Typically used to specify a field that indicates which files have
142
+ been reviewed.
136
143
 
137
- right_edge_quantization_threshold is an off-by-default hack to handle cases where
138
- boxes that really should be running off the right side of the image only extend like 99%
139
- of the way there, due to what appears to be a slight bias inherent to MD. If a box extends
140
- within [right_edge_quantization_threshold] (a small number, from 0 to 1, but probably around
141
- 0.02) of the right edge of the image, it will be extended to the far right edge.
144
+ If relative_filenames_to_convert is not None, this should be a list of .json (not image)
145
+ files that should get converted, relative to the base folder.
142
146
 
143
- returns category_name_to_category_id, whether it was passed in or constructed.
147
+ overwrite_behavior should be 'skip' or 'overwrite' (default).
148
+
149
+ returns a dict with:
150
+ 'category_name_to_category_id', whether it was passed in or constructed
151
+ 'image_results': a list of results for each image (converted, skipped, error)
152
+
144
153
  """
145
154
 
146
- labelme_files_relative = recursive_file_list(labelme_folder,return_relative_paths=True)
147
- labelme_files_relative = [fn for fn in labelme_files_relative if fn.endswith('.json')]
155
+ if relative_filenames_to_convert is not None:
156
+ labelme_files_relative = relative_filenames_to_convert
157
+ assert all([fn.endswith('.json') for fn in labelme_files_relative]), \
158
+ 'relative_filenames_to_convert contains non-json files'
159
+ else:
160
+ labelme_files_relative = recursive_file_list(labelme_folder,return_relative_paths=True)
161
+ labelme_files_relative = [fn for fn in labelme_files_relative if fn.endswith('.json')]
148
162
 
149
163
  if required_token is None:
150
164
  valid_labelme_files_relative = labelme_files_relative
@@ -163,9 +177,9 @@ def labelme_folder_to_yolo(labelme_folder,
163
177
 
164
178
  valid_labelme_files_relative.append(fn_relative)
165
179
 
166
- print('{} of {} files are valid'.format(len(valid_labelme_files_relative),
167
- len(labelme_files_relative)))
168
-
180
+ print('{} of {} files are valid'.format(len(valid_labelme_files_relative),
181
+ len(labelme_files_relative)))
182
+
169
183
  del labelme_files_relative
170
184
 
171
185
  if category_name_to_category_id is None:
@@ -184,26 +198,54 @@ def labelme_folder_to_yolo(labelme_folder,
184
198
  # ...for each file
185
199
 
186
200
  # ...if we need to build a category mapping
187
-
188
- for fn_relative in tqdm(valid_labelme_files_relative):
189
-
190
- fn_abs = os.path.join(labelme_folder,fn_relative)
191
- labelme_file_to_yolo_file(fn_abs,
192
- category_name_to_category_id,
193
- yolo_file=None,
194
- required_token=required_token,
195
- right_edge_quantization_threshold=\
196
- right_edge_quantization_threshold,
197
- overwrite_behavior=overwrite_behavior)
198
201
 
199
- # ...for each file
202
+ image_results = []
203
+
204
+ n_workers = min(n_workers,len(valid_labelme_files_relative))
205
+
206
+ if n_workers <= 1:
207
+ for fn_relative in tqdm(valid_labelme_files_relative):
208
+
209
+ fn_abs = os.path.join(labelme_folder,fn_relative)
210
+ image_result = labelme_file_to_yolo_file(fn_abs,
211
+ category_name_to_category_id,
212
+ yolo_file=None,
213
+ required_token=required_token,
214
+ overwrite_behavior=overwrite_behavior)
215
+ image_results.append(image_result)
216
+ # ...for each file
217
+ else:
218
+ if use_threads:
219
+ pool = ThreadPool(n_workers)
220
+ else:
221
+ pool = Pool(n_workers)
222
+
223
+ valid_labelme_files_abs = [os.path.join(labelme_folder,fn_relative) for \
224
+ fn_relative in valid_labelme_files_relative]
225
+
226
+ image_results = list(tqdm(pool.imap(
227
+ partial(labelme_file_to_yolo_file,
228
+ category_name_to_category_id=category_name_to_category_id,
229
+ yolo_file=None,
230
+ required_token=required_token,
231
+ overwrite_behavior=overwrite_behavior),
232
+ valid_labelme_files_abs),
233
+ total=len(valid_labelme_files_abs)))
234
+
235
+ assert len(valid_labelme_files_relative) == len(image_results)
200
236
 
201
237
  print('Converted {} labelme .json files to YOLO'.format(
202
238
  len(valid_labelme_files_relative)))
203
239
 
204
- return category_name_to_category_id
240
+ labelme_to_yolo_results = {}
241
+ labelme_to_yolo_results['category_name_to_category_id'] = category_name_to_category_id
242
+ labelme_to_yolo_results['image_results'] = image_results
205
243
 
206
-
244
+ return labelme_to_yolo_results
245
+
246
+ # ...def labelme_folder_to_yolo(...)
247
+
248
+
207
249
  #%% Interactive driver
208
250
 
209
251
  if False:
@@ -212,18 +254,19 @@ if False:
212
254
 
213
255
  #%%
214
256
 
215
- import os
216
257
  labelme_file = os.path.expanduser('~/tmp/labels/x.json')
217
- yolo_file = None
218
258
  required_token = 'saved_by_labelme'
219
- right_edge_quantization_threshold = 0.015
220
259
  category_name_to_category_id = {'animal':0}
260
+ labelme_folder = os.path.expanduser('~/tmp/labels')
221
261
 
222
262
  #%%
223
263
 
224
- labelme_folder = os.path.expanduser('~/tmp/labels')
225
-
226
-
264
+ category_name_to_category_id = \
265
+ labelme_folder_to_yolo(labelme_folder,
266
+ category_name_to_category_id=category_name_to_category_id,
267
+ required_token=required_token,
268
+ overwrite_behavior='overwrite')
269
+
227
270
  #%% Command-line driver
228
271
 
229
272
  # TODO
File without changes
@@ -1,12 +1,12 @@
1
- ########
2
- #
3
- # add_locations_to_island_camera_traps.py
4
- #
5
- # The Island Conservation Camera Traps dataset had unique camera identifiers embedded
6
- # in filenames, but not in the proper metadata fields. This script copies that information
7
- # to metadata.
8
- #
9
- ########
1
+ """
2
+
3
+ add_locations_to_island_camera_traps.py
4
+
5
+ The Island Conservation Camera Traps dataset had unique camera identifiers embedded
6
+ in filenames, but not in the proper metadata fields. This script copies that information
7
+ to metadata.
8
+
9
+ """
10
10
 
11
11
  #%% Imports and constants
12
12
 
@@ -1,147 +1,147 @@
1
- ########
2
- #
3
- # add_locations_to_nacti.py
4
- #
5
- # As of 10.2023, NACTI metadata only has very coarse location information (e.g. "Florida"),
6
- # but camera IDs are embedded in filenames. This script pulls that information from filenames
7
- # and adds it to metadata.
8
- #
9
- ########
10
-
11
- #%% Imports and constants
12
-
13
- import os
14
- import json
15
- import shutil
16
-
17
- from tqdm import tqdm
18
- from collections import defaultdict
19
-
20
- input_file = r'd:\lila\nacti\nacti_metadata.json.1.13\nacti_metadata.json'
21
- output_file = r'g:\temp\nacti_metadata.1.14.json'
22
-
23
-
24
- #%% Read metadata
25
-
26
- with open(input_file,'r') as f:
27
- d = json.load(f)
28
-
29
- assert d['info']['version'] == 1.13
30
-
31
-
32
- #%% Map images to locations (according to the metadata)
33
-
34
- file_name_to_original_location = {}
35
-
36
- # im = dataset_labels['images'][0]
37
- for im in tqdm(d['images']):
38
- file_name_to_original_location[im['file_name']] = im['location']
39
-
40
- original_locations = set(file_name_to_original_location.values())
41
-
42
- print('Found {} locations in the original metadata:'.format(len(original_locations)))
43
- for loc in original_locations:
44
- print('[{}]'.format(loc))
45
-
46
-
47
- #%% Map images to new locations
48
-
49
- def path_to_location(relative_path):
50
-
51
- relative_path = relative_path.replace('\\','/')
52
- if relative_path in file_name_to_original_location:
53
- location_name = file_name_to_original_location[relative_path]
54
- if location_name == 'San Juan Mntns, Colorado':
55
- # "part0/sub000/2010_Unit150_Ivan097_img0003.jpg"
56
- tokens = relative_path.split('/')[-1].split('_')
57
- assert tokens[1].startswith('Unit')
58
- location_name = 'sanjuan_{}_{}_{}'.format(tokens[0],tokens[1],tokens[2])
59
- elif location_name == 'Lebec, California':
60
- # "part0/sub035/CA-03_08_13_2015_CA-03_0009738.jpg"
61
- tokens = relative_path.split('/')[-1].split('_')
62
- assert tokens[0].startswith('CA-') or tokens[0].startswith('TAG-')
63
- location_name = 'lebec_{}'.format(tokens[0])
64
- elif location_name == 'Archbold, FL':
65
- # "part1/sub110/FL-01_01_25_2016_FL-01_0040421.jpg"
66
- tokens = relative_path.split('/')[-1].split('_')
67
- assert tokens[0].startswith('FL-')
68
- location_name = 'archbold_{}'.format(tokens[0])
69
- else:
70
- assert location_name == ''
71
- tokens = relative_path.split('/')[-1].split('_')
72
- if tokens[0].startswith('CA-') or tokens[0].startswith('TAG-') or tokens[0].startswith('FL-'):
73
- location_name = '{}'.format(tokens[0])
74
-
75
- else:
76
-
77
- location_name = 'unknown'
78
-
79
- # print('Returning location {} for file {}'.format(location_name,relative_path))
80
-
81
- return location_name
82
-
83
- file_name_to_updated_location = {}
84
- updated_location_to_count = defaultdict(int)
85
- for im in tqdm(d['images']):
86
-
87
- updated_location = path_to_location(im['file_name'])
88
- file_name_to_updated_location[im['file_name']] = updated_location
89
- updated_location_to_count[updated_location] += 1
90
-
91
- updated_location_to_count = {k: v for k, v in sorted(updated_location_to_count.items(),
92
- key=lambda item: item[1],
93
- reverse=True)}
94
-
95
- updated_locations = set(file_name_to_updated_location.values())
96
-
97
- print('Found {} updated locations in the original metadata:'.format(len(updated_locations)))
98
- for loc in updated_location_to_count:
99
- print('{}: {}'.format(loc,updated_location_to_count[loc]))
100
-
101
-
102
- #%% Re-write metadata
103
-
104
- for im in d['images']:
105
- im['location'] = file_name_to_updated_location[im['file_name']]
106
- d['info']['version'] = 1.14
107
-
108
- with open(output_file,'w') as f:
109
- json.dump(d,f,indent=1)
110
-
111
-
112
- #%% For each location, sample some random images to make sure they look consistent
113
-
114
- input_base = r'd:\lila\nacti-unzipped'
115
- assert os.path.isdir(input_base)
116
-
117
- location_to_images = defaultdict(list)
118
-
119
- for im in d['images']:
120
- location_to_images[im['location']].append(im)
121
-
122
- n_to_sample = 10
123
- import random
124
- random.seed(0)
125
- sampling_folder_base = r'g:\temp\nacti_samples'
126
-
127
- for location in tqdm(location_to_images):
128
-
129
- images_this_location = location_to_images[location]
130
- if len(images_this_location) > n_to_sample:
131
- images_this_location = random.sample(images_this_location,n_to_sample)
132
-
133
- for i_image,im in enumerate(images_this_location):
134
-
135
- fn_relative = im['file_name']
136
- source_fn_abs = os.path.join(input_base,fn_relative)
137
- assert os.path.isfile(source_fn_abs)
138
- ext = os.path.splitext(fn_relative)[1]
139
- target_fn_abs = os.path.join(sampling_folder_base,'{}/{}'.format(
140
- location,'image_{}{}'.format(str(i_image).zfill(2),ext)))
141
- os.makedirs(os.path.dirname(target_fn_abs),exist_ok=True)
142
- shutil.copyfile(source_fn_abs,target_fn_abs)
143
-
144
- # ...for each image
145
-
146
- # ...for each location
147
-
1
+ """
2
+
3
+ add_locations_to_nacti.py
4
+
5
+ As of 10.2023, NACTI metadata only has very coarse location information (e.g. "Florida"),
6
+ but camera IDs are embedded in filenames. This script pulls that information from filenames
7
+ and adds it to metadata.
8
+
9
+ """
10
+
11
+ #%% Imports and constants
12
+
13
+ import os
14
+ import json
15
+ import shutil
16
+
17
+ from tqdm import tqdm
18
+ from collections import defaultdict
19
+
20
+ input_file = r'd:\lila\nacti\nacti_metadata.json.1.13\nacti_metadata.json'
21
+ output_file = r'g:\temp\nacti_metadata.1.14.json'
22
+
23
+
24
+ #%% Read metadata
25
+
26
+ with open(input_file,'r') as f:
27
+ d = json.load(f)
28
+
29
+ assert d['info']['version'] == 1.13
30
+
31
+
32
+ #%% Map images to locations (according to the metadata)
33
+
34
+ file_name_to_original_location = {}
35
+
36
+ # im = dataset_labels['images'][0]
37
+ for im in tqdm(d['images']):
38
+ file_name_to_original_location[im['file_name']] = im['location']
39
+
40
+ original_locations = set(file_name_to_original_location.values())
41
+
42
+ print('Found {} locations in the original metadata:'.format(len(original_locations)))
43
+ for loc in original_locations:
44
+ print('[{}]'.format(loc))
45
+
46
+
47
+ #%% Map images to new locations
48
+
49
+ def path_to_location(relative_path):
50
+
51
+ relative_path = relative_path.replace('\\','/')
52
+ if relative_path in file_name_to_original_location:
53
+ location_name = file_name_to_original_location[relative_path]
54
+ if location_name == 'San Juan Mntns, Colorado':
55
+ # "part0/sub000/2010_Unit150_Ivan097_img0003.jpg"
56
+ tokens = relative_path.split('/')[-1].split('_')
57
+ assert tokens[1].startswith('Unit')
58
+ location_name = 'sanjuan_{}_{}_{}'.format(tokens[0],tokens[1],tokens[2])
59
+ elif location_name == 'Lebec, California':
60
+ # "part0/sub035/CA-03_08_13_2015_CA-03_0009738.jpg"
61
+ tokens = relative_path.split('/')[-1].split('_')
62
+ assert tokens[0].startswith('CA-') or tokens[0].startswith('TAG-')
63
+ location_name = 'lebec_{}'.format(tokens[0])
64
+ elif location_name == 'Archbold, FL':
65
+ # "part1/sub110/FL-01_01_25_2016_FL-01_0040421.jpg"
66
+ tokens = relative_path.split('/')[-1].split('_')
67
+ assert tokens[0].startswith('FL-')
68
+ location_name = 'archbold_{}'.format(tokens[0])
69
+ else:
70
+ assert location_name == ''
71
+ tokens = relative_path.split('/')[-1].split('_')
72
+ if tokens[0].startswith('CA-') or tokens[0].startswith('TAG-') or tokens[0].startswith('FL-'):
73
+ location_name = '{}'.format(tokens[0])
74
+
75
+ else:
76
+
77
+ location_name = 'unknown'
78
+
79
+ # print('Returning location {} for file {}'.format(location_name,relative_path))
80
+
81
+ return location_name
82
+
83
+ file_name_to_updated_location = {}
84
+ updated_location_to_count = defaultdict(int)
85
+ for im in tqdm(d['images']):
86
+
87
+ updated_location = path_to_location(im['file_name'])
88
+ file_name_to_updated_location[im['file_name']] = updated_location
89
+ updated_location_to_count[updated_location] += 1
90
+
91
+ updated_location_to_count = {k: v for k, v in sorted(updated_location_to_count.items(),
92
+ key=lambda item: item[1],
93
+ reverse=True)}
94
+
95
+ updated_locations = set(file_name_to_updated_location.values())
96
+
97
+ print('Found {} updated locations in the original metadata:'.format(len(updated_locations)))
98
+ for loc in updated_location_to_count:
99
+ print('{}: {}'.format(loc,updated_location_to_count[loc]))
100
+
101
+
102
+ #%% Re-write metadata
103
+
104
+ for im in d['images']:
105
+ im['location'] = file_name_to_updated_location[im['file_name']]
106
+ d['info']['version'] = 1.14
107
+
108
+ with open(output_file,'w') as f:
109
+ json.dump(d,f,indent=1)
110
+
111
+
112
+ #%% For each location, sample some random images to make sure they look consistent
113
+
114
+ input_base = r'd:\lila\nacti-unzipped'
115
+ assert os.path.isdir(input_base)
116
+
117
+ location_to_images = defaultdict(list)
118
+
119
+ for im in d['images']:
120
+ location_to_images[im['location']].append(im)
121
+
122
+ n_to_sample = 10
123
+ import random
124
+ random.seed(0)
125
+ sampling_folder_base = r'g:\temp\nacti_samples'
126
+
127
+ for location in tqdm(location_to_images):
128
+
129
+ images_this_location = location_to_images[location]
130
+ if len(images_this_location) > n_to_sample:
131
+ images_this_location = random.sample(images_this_location,n_to_sample)
132
+
133
+ for i_image,im in enumerate(images_this_location):
134
+
135
+ fn_relative = im['file_name']
136
+ source_fn_abs = os.path.join(input_base,fn_relative)
137
+ assert os.path.isfile(source_fn_abs)
138
+ ext = os.path.splitext(fn_relative)[1]
139
+ target_fn_abs = os.path.join(sampling_folder_base,'{}/{}'.format(
140
+ location,'image_{}{}'.format(str(i_image).zfill(2),ext)))
141
+ os.makedirs(os.path.dirname(target_fn_abs),exist_ok=True)
142
+ shutil.copyfile(source_fn_abs,target_fn_abs)
143
+
144
+ # ...for each image
145
+
146
+ # ...for each location
147
+