megadetector 5.0.28__py3-none-any.whl → 5.0.29__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 (176) hide show
  1. megadetector/api/batch_processing/api_core/batch_service/score.py +4 -5
  2. megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +1 -1
  3. megadetector/api/batch_processing/api_support/summarize_daily_activity.py +1 -1
  4. megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +2 -2
  5. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +1 -1
  6. megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +1 -1
  7. megadetector/api/synchronous/api_core/tests/load_test.py +2 -3
  8. megadetector/classification/aggregate_classifier_probs.py +3 -3
  9. megadetector/classification/analyze_failed_images.py +5 -5
  10. megadetector/classification/cache_batchapi_outputs.py +5 -5
  11. megadetector/classification/create_classification_dataset.py +11 -12
  12. megadetector/classification/crop_detections.py +10 -10
  13. megadetector/classification/csv_to_json.py +8 -8
  14. megadetector/classification/detect_and_crop.py +13 -15
  15. megadetector/classification/evaluate_model.py +7 -7
  16. megadetector/classification/identify_mislabeled_candidates.py +6 -6
  17. megadetector/classification/json_to_azcopy_list.py +1 -1
  18. megadetector/classification/json_validator.py +29 -32
  19. megadetector/classification/map_classification_categories.py +9 -9
  20. megadetector/classification/merge_classification_detection_output.py +12 -9
  21. megadetector/classification/prepare_classification_script.py +19 -19
  22. megadetector/classification/prepare_classification_script_mc.py +23 -23
  23. megadetector/classification/run_classifier.py +4 -4
  24. megadetector/classification/save_mislabeled.py +6 -6
  25. megadetector/classification/train_classifier.py +1 -1
  26. megadetector/classification/train_classifier_tf.py +9 -9
  27. megadetector/classification/train_utils.py +10 -10
  28. megadetector/data_management/annotations/annotation_constants.py +1 -1
  29. megadetector/data_management/camtrap_dp_to_coco.py +45 -45
  30. megadetector/data_management/cct_json_utils.py +101 -101
  31. megadetector/data_management/cct_to_md.py +49 -49
  32. megadetector/data_management/cct_to_wi.py +33 -33
  33. megadetector/data_management/coco_to_labelme.py +75 -75
  34. megadetector/data_management/coco_to_yolo.py +189 -189
  35. megadetector/data_management/databases/add_width_and_height_to_db.py +3 -2
  36. megadetector/data_management/databases/combine_coco_camera_traps_files.py +38 -38
  37. megadetector/data_management/databases/integrity_check_json_db.py +202 -188
  38. megadetector/data_management/databases/subset_json_db.py +33 -33
  39. megadetector/data_management/generate_crops_from_cct.py +38 -38
  40. megadetector/data_management/get_image_sizes.py +54 -49
  41. megadetector/data_management/labelme_to_coco.py +130 -124
  42. megadetector/data_management/labelme_to_yolo.py +78 -72
  43. megadetector/data_management/lila/create_lila_blank_set.py +81 -83
  44. megadetector/data_management/lila/create_lila_test_set.py +32 -31
  45. megadetector/data_management/lila/create_links_to_md_results_files.py +18 -18
  46. megadetector/data_management/lila/download_lila_subset.py +21 -24
  47. megadetector/data_management/lila/generate_lila_per_image_labels.py +91 -91
  48. megadetector/data_management/lila/get_lila_annotation_counts.py +30 -30
  49. megadetector/data_management/lila/get_lila_image_counts.py +22 -22
  50. megadetector/data_management/lila/lila_common.py +70 -70
  51. megadetector/data_management/lila/test_lila_metadata_urls.py +13 -14
  52. megadetector/data_management/mewc_to_md.py +339 -340
  53. megadetector/data_management/ocr_tools.py +258 -252
  54. megadetector/data_management/read_exif.py +231 -224
  55. megadetector/data_management/remap_coco_categories.py +26 -26
  56. megadetector/data_management/remove_exif.py +31 -20
  57. megadetector/data_management/rename_images.py +187 -187
  58. megadetector/data_management/resize_coco_dataset.py +41 -41
  59. megadetector/data_management/speciesnet_to_md.py +41 -41
  60. megadetector/data_management/wi_download_csv_to_coco.py +55 -55
  61. megadetector/data_management/yolo_output_to_md_output.py +117 -120
  62. megadetector/data_management/yolo_to_coco.py +195 -188
  63. megadetector/detection/change_detection.py +831 -0
  64. megadetector/detection/process_video.py +340 -337
  65. megadetector/detection/pytorch_detector.py +304 -262
  66. megadetector/detection/run_detector.py +177 -164
  67. megadetector/detection/run_detector_batch.py +364 -363
  68. megadetector/detection/run_inference_with_yolov5_val.py +328 -325
  69. megadetector/detection/run_tiled_inference.py +256 -249
  70. megadetector/detection/tf_detector.py +24 -24
  71. megadetector/detection/video_utils.py +290 -282
  72. megadetector/postprocessing/add_max_conf.py +15 -11
  73. megadetector/postprocessing/categorize_detections_by_size.py +44 -44
  74. megadetector/postprocessing/classification_postprocessing.py +415 -415
  75. megadetector/postprocessing/combine_batch_outputs.py +20 -21
  76. megadetector/postprocessing/compare_batch_results.py +528 -517
  77. megadetector/postprocessing/convert_output_format.py +97 -97
  78. megadetector/postprocessing/create_crop_folder.py +219 -146
  79. megadetector/postprocessing/detector_calibration.py +173 -168
  80. megadetector/postprocessing/generate_csv_report.py +508 -499
  81. megadetector/postprocessing/load_api_results.py +23 -20
  82. megadetector/postprocessing/md_to_coco.py +129 -98
  83. megadetector/postprocessing/md_to_labelme.py +89 -83
  84. megadetector/postprocessing/md_to_wi.py +40 -40
  85. megadetector/postprocessing/merge_detections.py +87 -114
  86. megadetector/postprocessing/postprocess_batch_results.py +313 -298
  87. megadetector/postprocessing/remap_detection_categories.py +36 -36
  88. megadetector/postprocessing/render_detection_confusion_matrix.py +205 -199
  89. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +57 -57
  90. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +27 -28
  91. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +702 -677
  92. megadetector/postprocessing/separate_detections_into_folders.py +226 -211
  93. megadetector/postprocessing/subset_json_detector_output.py +265 -262
  94. megadetector/postprocessing/top_folders_to_bottom.py +45 -45
  95. megadetector/postprocessing/validate_batch_results.py +70 -70
  96. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +52 -52
  97. megadetector/taxonomy_mapping/map_new_lila_datasets.py +15 -15
  98. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +14 -14
  99. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +66 -66
  100. megadetector/taxonomy_mapping/retrieve_sample_image.py +16 -16
  101. megadetector/taxonomy_mapping/simple_image_download.py +8 -8
  102. megadetector/taxonomy_mapping/species_lookup.py +33 -33
  103. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +14 -14
  104. megadetector/taxonomy_mapping/taxonomy_graph.py +10 -10
  105. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +13 -13
  106. megadetector/utils/azure_utils.py +22 -22
  107. megadetector/utils/ct_utils.py +1018 -200
  108. megadetector/utils/directory_listing.py +21 -77
  109. megadetector/utils/gpu_test.py +22 -22
  110. megadetector/utils/md_tests.py +541 -518
  111. megadetector/utils/path_utils.py +1457 -398
  112. megadetector/utils/process_utils.py +41 -41
  113. megadetector/utils/sas_blob_utils.py +53 -49
  114. megadetector/utils/split_locations_into_train_val.py +61 -61
  115. megadetector/utils/string_utils.py +147 -26
  116. megadetector/utils/url_utils.py +463 -173
  117. megadetector/utils/wi_utils.py +2629 -2526
  118. megadetector/utils/write_html_image_list.py +137 -137
  119. megadetector/visualization/plot_utils.py +21 -21
  120. megadetector/visualization/render_images_with_thumbnails.py +37 -73
  121. megadetector/visualization/visualization_utils.py +401 -397
  122. megadetector/visualization/visualize_db.py +197 -190
  123. megadetector/visualization/visualize_detector_output.py +79 -73
  124. {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/METADATA +135 -132
  125. megadetector-5.0.29.dist-info/RECORD +163 -0
  126. {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/WHEEL +1 -1
  127. {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/licenses/LICENSE +0 -0
  128. {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/top_level.txt +0 -0
  129. megadetector/data_management/importers/add_nacti_sizes.py +0 -52
  130. megadetector/data_management/importers/add_timestamps_to_icct.py +0 -79
  131. megadetector/data_management/importers/animl_results_to_md_results.py +0 -158
  132. megadetector/data_management/importers/auckland_doc_test_to_json.py +0 -373
  133. megadetector/data_management/importers/auckland_doc_to_json.py +0 -201
  134. megadetector/data_management/importers/awc_to_json.py +0 -191
  135. megadetector/data_management/importers/bellevue_to_json.py +0 -272
  136. megadetector/data_management/importers/cacophony-thermal-importer.py +0 -793
  137. megadetector/data_management/importers/carrizo_shrubfree_2018.py +0 -269
  138. megadetector/data_management/importers/carrizo_trail_cam_2017.py +0 -289
  139. megadetector/data_management/importers/cct_field_adjustments.py +0 -58
  140. megadetector/data_management/importers/channel_islands_to_cct.py +0 -913
  141. megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
  142. megadetector/data_management/importers/eMammal/eMammal_helpers.py +0 -249
  143. megadetector/data_management/importers/eMammal/make_eMammal_json.py +0 -223
  144. megadetector/data_management/importers/ena24_to_json.py +0 -276
  145. megadetector/data_management/importers/filenames_to_json.py +0 -386
  146. megadetector/data_management/importers/helena_to_cct.py +0 -283
  147. megadetector/data_management/importers/idaho-camera-traps.py +0 -1407
  148. megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
  149. megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +0 -387
  150. megadetector/data_management/importers/jb_csv_to_json.py +0 -150
  151. megadetector/data_management/importers/mcgill_to_json.py +0 -250
  152. megadetector/data_management/importers/missouri_to_json.py +0 -490
  153. megadetector/data_management/importers/nacti_fieldname_adjustments.py +0 -79
  154. megadetector/data_management/importers/noaa_seals_2019.py +0 -181
  155. megadetector/data_management/importers/osu-small-animals-to-json.py +0 -364
  156. megadetector/data_management/importers/pc_to_json.py +0 -365
  157. megadetector/data_management/importers/plot_wni_giraffes.py +0 -123
  158. megadetector/data_management/importers/prepare_zsl_imerit.py +0 -131
  159. megadetector/data_management/importers/raic_csv_to_md_results.py +0 -416
  160. megadetector/data_management/importers/rspb_to_json.py +0 -356
  161. megadetector/data_management/importers/save_the_elephants_survey_A.py +0 -320
  162. megadetector/data_management/importers/save_the_elephants_survey_B.py +0 -329
  163. megadetector/data_management/importers/snapshot_safari_importer.py +0 -758
  164. megadetector/data_management/importers/snapshot_serengeti_lila.py +0 -1067
  165. megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
  166. megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
  167. megadetector/data_management/importers/sulross_get_exif.py +0 -65
  168. megadetector/data_management/importers/timelapse_csv_set_to_json.py +0 -490
  169. megadetector/data_management/importers/ubc_to_json.py +0 -399
  170. megadetector/data_management/importers/umn_to_json.py +0 -507
  171. megadetector/data_management/importers/wellington_to_json.py +0 -263
  172. megadetector/data_management/importers/wi_to_json.py +0 -442
  173. megadetector/data_management/importers/zamba_results_to_md_results.py +0 -180
  174. megadetector/data_management/lila/add_locations_to_island_camera_traps.py +0 -101
  175. megadetector/data_management/lila/add_locations_to_nacti.py +0 -151
  176. megadetector-5.0.28.dist-info/RECORD +0 -209
@@ -14,7 +14,7 @@ no detections are present.
14
14
 
15
15
  YOLOv5 output has one text file per image, like so:
16
16
 
17
- 0 0.0141693 0.469758 0.0283385 0.131552 0.761428
17
+ 0 0.0141693 0.469758 0.0283385 0.131552 0.761428
18
18
 
19
19
  That's [class, x_center, y_center, width_of_box, height_of_box, confidence]
20
20
 
@@ -57,39 +57,39 @@ def read_classes_from_yolo_dataset_file(fn):
57
57
  Reads a dictionary mapping integer class IDs to class names from a YOLOv5/YOLOv8
58
58
  dataset.yaml file or a .json file. A .json file should contain a dictionary mapping
59
59
  integer category IDs to string category names.
60
-
60
+
61
61
  Args:
62
- fn (str): YOLOv5/YOLOv8 dataset file with a .yml or .yaml extension, a .json file
62
+ fn (str): YOLOv5/YOLOv8 dataset file with a .yml or .yaml extension, a .json file
63
63
  mapping integer category IDs to category names, or a .txt file with a flat
64
64
  list of classes.
65
-
65
+
66
66
  Returns:
67
67
  dict: a mapping from integer category IDs to category names
68
68
  """
69
-
69
+
70
70
  category_id_to_name = {}
71
-
71
+
72
72
  if fn.endswith('.yml') or fn.endswith('.yaml'):
73
-
73
+
74
74
  with open(fn,'r') as f:
75
75
  lines = f.readlines()
76
-
76
+
77
77
  pat = r'\d+:.+'
78
78
  for s in lines:
79
79
  if re.search(pat,s) is not None:
80
80
  tokens = s.split(':')
81
81
  assert len(tokens) == 2, 'Invalid token in category file {}'.format(fn)
82
82
  category_id_to_name[int(tokens[0].strip())] = tokens[1].strip()
83
-
83
+
84
84
  elif fn.endswith('.json'):
85
-
85
+
86
86
  with open(fn,'r') as f:
87
87
  d_in = json.load(f)
88
88
  for k in d_in.keys():
89
89
  category_id_to_name[int(k)] = d_in[k]
90
-
90
+
91
91
  elif fn.endswith('.txt'):
92
-
92
+
93
93
  with open(fn,'r') as f:
94
94
  lines = f.readlines()
95
95
  next_category_id = 0
@@ -99,20 +99,20 @@ def read_classes_from_yolo_dataset_file(fn):
99
99
  continue
100
100
  category_id_to_name[next_category_id] = s
101
101
  next_category_id += 1
102
-
102
+
103
103
  else:
104
-
104
+
105
105
  raise ValueError('Unrecognized category file type: {}'.format(fn))
106
-
106
+
107
107
  assert len(category_id_to_name) > 0, 'Failed to read class mappings from {}'.format(fn)
108
-
108
+
109
109
  return category_id_to_name
110
-
111
110
 
112
- def yolo_json_output_to_md_output(yolo_json_file,
111
+
112
+ def yolo_json_output_to_md_output(yolo_json_file,
113
113
  image_folder,
114
- output_file,
115
- yolo_category_id_to_name,
114
+ output_file,
115
+ yolo_category_id_to_name,
116
116
  detector_name='unknown',
117
117
  image_id_to_relative_path=None,
118
118
  offset_yolo_class_ids=True,
@@ -121,56 +121,55 @@ def yolo_json_output_to_md_output(yolo_json_file,
121
121
  convert_slashes=True):
122
122
  """
123
123
  Converts a YOLOv5/YOLOv8 .json file to MD .json format.
124
-
124
+
125
125
  Args:
126
-
127
126
  yolo_json_file (str): the .json file to convert from YOLOv5 format to MD output format
128
127
  image_folder (str): the .json file contains relative path names, this is the path base
129
- yolo_category_id_to_name (str or dict): the .json results file contains only numeric
130
- identifiers for categories, but we want names and numbers for the output format;
131
- yolo_category_id_to_name provides that mapping either as a dict or as a YOLOv5
128
+ yolo_category_id_to_name (str or dict): the .json results file contains only numeric
129
+ identifiers for categories, but we want names and numbers for the output format;
130
+ yolo_category_id_to_name provides that mapping either as a dict or as a YOLOv5
132
131
  dataset.yaml file.
133
- detector_name (str, optional): a string that gets put in the output file, not otherwise
132
+ detector_name (str, optional): a string that gets put in the output file, not otherwise
134
133
  used within this function
135
- image_id_to_relative_path (dict, optional): YOLOv5 .json uses only basenames (e.g.
134
+ image_id_to_relative_path (dict, optional): YOLOv5 .json uses only basenames (e.g.
136
135
  abc1234.JPG); by default these will be appended to the input path to create pathnames.
137
136
  If you have a flat folder, this is fine. If you want to map base names to relative paths in
138
- a more complicated way, use this parameter.
139
- offset_yolo_class_ids (bool, optional): YOLOv5 class IDs always start at zero; if you want to
140
- make the output classes start at 1, set offset_yolo_class_ids to True.
141
- truncate_to_standard_md_precision (bool, optional): YOLOv5 .json includes lots of
137
+ a more complicated way, use this parameter.
138
+ offset_yolo_class_ids (bool, optional): YOLOv5 class IDs always start at zero; if you want to
139
+ make the output classes start at 1, set offset_yolo_class_ids to True.
140
+ truncate_to_standard_md_precision (bool, optional): YOLOv5 .json includes lots of
142
141
  (not-super-meaningful) precision, set this to truncate to COORD_DIGITS and CONF_DIGITS.
143
- image_id_to_error (dict, optional): if you want to include image IDs in the output file for which
142
+ image_id_to_error (dict, optional): if you want to include image IDs in the output file for which
144
143
  you couldn't prepare the input file in the first place due to errors, include them here.
145
144
  convert_slashes (bool, optional): force all slashes to be forward slashes in the output file
146
- """
147
-
145
+ """
146
+
148
147
  assert os.path.isfile(yolo_json_file), \
149
148
  'Could not find YOLO .json file {}'.format(yolo_json_file)
150
149
  assert os.path.isdir(image_folder), \
151
150
  'Could not find image folder {}'.format(image_folder)
152
-
151
+
153
152
  if image_id_to_error is None:
154
153
  image_id_to_error = {}
155
-
154
+
156
155
  print('Converting {} to MD format and writing results to {}'.format(
157
156
  yolo_json_file,output_file))
158
-
157
+
159
158
  if isinstance(yolo_category_id_to_name,str):
160
159
  assert os.path.isfile(yolo_category_id_to_name), \
161
160
  'YOLO category mapping specified as a string, but file does not exist: {}'.format(
162
161
  yolo_category_id_to_name)
163
162
  yolo_category_id_to_name = read_classes_from_yolo_dataset_file(yolo_category_id_to_name)
164
-
163
+
165
164
  if image_id_to_relative_path is None:
166
-
165
+
167
166
  image_files = path_utils.find_images(image_folder,recursive=True)
168
167
  image_files = [os.path.relpath(fn,image_folder) for fn in image_files]
169
-
168
+
170
169
  # YOLOv5 identifies images in .json output by ID, which is the filename without
171
170
  # extension. If a mapping is not provided, these need to be unique.
172
171
  image_id_to_relative_path = {}
173
-
172
+
174
173
  for fn in image_files:
175
174
  image_id = os.path.splitext(os.path.basename(fn))[0]
176
175
  if image_id in image_id_to_relative_path:
@@ -180,49 +179,49 @@ def yolo_json_output_to_md_output(yolo_json_file,
180
179
  image_id_to_relative_path[image_id] = fn
181
180
 
182
181
  image_files_relative = sorted(list(image_id_to_relative_path.values()))
183
-
182
+
184
183
  image_file_relative_to_image_id = {}
185
184
  for image_id in image_id_to_relative_path:
186
185
  relative_path = image_id_to_relative_path[image_id]
187
186
  assert relative_path not in image_file_relative_to_image_id, \
188
187
  'Duplication image IDs in YOLO output conversion for image {}'.format(relative_path)
189
188
  image_file_relative_to_image_id[relative_path] = image_id
190
-
189
+
191
190
  with open(yolo_json_file,'r') as f:
192
191
  detections = json.load(f)
193
192
  assert isinstance(detections,list)
194
-
193
+
195
194
  image_id_to_detections = defaultdict(list)
196
-
195
+
197
196
  int_formatted_image_ids = False
198
-
197
+
199
198
  # det = detections[0]
200
199
  for det in detections:
201
-
200
+
202
201
  # This could be a string, but if the YOLOv5 inference script sees that the strings
203
202
  # are really ints, it converts to ints.
204
203
  image_id = det['image_id']
205
204
  image_id_to_detections[image_id].append(det)
206
205
  if isinstance(image_id,int):
207
206
  int_formatted_image_ids = True
208
-
207
+
209
208
  # If there are any ints present, everything should be ints
210
209
  if int_formatted_image_ids:
211
210
  for det in detections:
212
211
  assert isinstance(det['image_id'],int), \
213
212
  'Found mixed int and string image IDs'
214
-
213
+
215
214
  # Convert the keys in image_id_to_error to ints
216
215
  #
217
- # This should error if we're given non-int-friendly IDs
218
- int_formatted_image_id_to_error = {}
216
+ # This should error if we're given non-int-friendly IDs
217
+ int_formatted_image_id_to_error = {}
219
218
  for image_id in image_id_to_error:
220
219
  int_formatted_image_id_to_error[int(image_id)] = \
221
220
  image_id_to_error[image_id]
222
- image_id_to_error = int_formatted_image_id_to_error
223
-
221
+ image_id_to_error = int_formatted_image_id_to_error
222
+
224
223
  # ...if image IDs are formatted as integers in YOLO output
225
-
224
+
226
225
  # In a modified version of val.py, we use negative category IDs to indicate an error
227
226
  # that happened during inference (typically truncated images with valid headers,
228
227
  # so corruption was not detected during val.py's initial corruption check pass.
@@ -232,17 +231,17 @@ def yolo_json_output_to_md_output(yolo_json_file,
232
231
  error_string = det['error']
233
232
  print('Caught inference-time failure {} for image {}'.format(error_string,det['image_id']))
234
233
  image_id_to_error[det['image_id']] = error_string
235
-
234
+
236
235
  output_images = []
237
-
236
+
238
237
  # image_file_relative = image_files_relative[10]
239
238
  for image_file_relative in tqdm(image_files_relative):
240
-
239
+
241
240
  im = {}
242
241
  im['file'] = image_file_relative
243
242
  if convert_slashes:
244
243
  im['file'] = im['file'].replace('\\','/')
245
-
244
+
246
245
  image_id = image_file_relative_to_image_id[image_file_relative]
247
246
  if int_formatted_image_ids:
248
247
  image_id = int(image_id)
@@ -254,7 +253,7 @@ def yolo_json_output_to_md_output(yolo_json_file,
254
253
  detections = []
255
254
  else:
256
255
  detections = image_id_to_detections[image_id]
257
-
256
+
258
257
  image_full_path = os.path.join(image_folder,image_file_relative)
259
258
  try:
260
259
  pil_im = vis_utils.open_image(image_full_path)
@@ -264,17 +263,17 @@ def yolo_json_output_to_md_output(yolo_json_file,
264
263
  im['failure'] = 'Conversion error: {}'.format(s)
265
264
  output_images.append(im)
266
265
  continue
267
-
266
+
268
267
  im['detections'] = []
269
-
268
+
270
269
  image_w = pil_im.size[0]
271
270
  image_h = pil_im.size[1]
272
-
271
+
273
272
  # det = detections[0]
274
273
  for det in detections:
275
-
274
+
276
275
  output_det = {}
277
-
276
+
278
277
  yolo_cat_id = int(det['category_id'])
279
278
  if offset_yolo_class_ids:
280
279
  yolo_cat_id += 1
@@ -284,63 +283,62 @@ def yolo_json_output_to_md_output(yolo_json_file,
284
283
  conf = ct_utils.round_float(conf,CONF_DIGITS)
285
284
  output_det['conf'] = conf
286
285
  input_bbox = det['bbox']
287
-
286
+
288
287
  # YOLO's COCO .json is not *that* COCO-like, but it is COCO-like in
289
288
  # that the boxes are already [xmin/ymin/w/h]
290
289
  box_xmin_absolute = input_bbox[0]
291
290
  box_ymin_absolute = input_bbox[1]
292
291
  box_width_absolute = input_bbox[2]
293
292
  box_height_absolute = input_bbox[3]
294
-
293
+
295
294
  box_xmin_relative = box_xmin_absolute / image_w
296
295
  box_ymin_relative = box_ymin_absolute / image_h
297
296
  box_width_relative = box_width_absolute / image_w
298
297
  box_height_relative = box_height_absolute / image_h
299
-
298
+
300
299
  output_bbox = [box_xmin_relative,box_ymin_relative,
301
300
  box_width_relative,box_height_relative]
302
-
301
+
303
302
  if truncate_to_standard_md_precision:
304
303
  output_bbox = ct_utils.round_float_array(output_bbox,COORD_DIGITS)
305
-
304
+
306
305
  output_det['bbox'] = output_bbox
307
306
  im['detections'].append(output_det)
308
-
309
- # ...for each detection
310
-
307
+
308
+ # ...for each detection
309
+
311
310
  output_images.append(im)
312
-
311
+
313
312
  # ...for each image file
314
-
313
+
315
314
  d = {}
316
315
  d['images'] = output_images
317
316
  d['info'] = {'format_version':'1.4','detector':detector_name}
318
317
  d['detection_categories'] = {}
319
-
318
+
320
319
  for cat_id in yolo_category_id_to_name:
321
320
  yolo_cat_id = int(cat_id)
322
321
  if offset_yolo_class_ids:
323
322
  yolo_cat_id += 1
324
323
  d['detection_categories'][str(yolo_cat_id)] = yolo_category_id_to_name[cat_id]
325
-
326
- with open(output_file,'w') as f:
327
- json.dump(d,f,indent=1)
328
-
324
+
325
+ ct_utils.write_json(output_file, d)
326
+
329
327
  # ...def yolo_json_output_to_md_output(...)
330
-
331
328
 
332
- def yolo_txt_output_to_md_output(input_results_folder,
329
+
330
+ def yolo_txt_output_to_md_output(input_results_folder,
333
331
  image_folder,
334
- output_file,
332
+ output_file,
335
333
  detector_tag=None,
336
334
  truncate_to_standard_md_precision=True):
337
335
  """
338
336
  Converts a folder of YOLO-output .txt files to MD .json format.
339
-
340
- Less finished than the .json conversion function; this .txt conversion assumes
341
- a hard-coded mapping representing the standard MD categories (in MD indexing,
337
+
338
+ Less finished than the .json conversion function; this .txt conversion assumes
339
+ a hard-coded mapping representing the standard MD categories (in MD indexing,
342
340
  1/2/3=animal/person/vehicle; in YOLO indexing, 0/1/2=animal/person/vehicle).
343
-
341
+
344
342
  Args:
345
343
  input_results_folder (str): the folder containing YOLO-output .txt files
346
344
  image_folder (str): the folder where images live, may be the same as
@@ -349,47 +347,47 @@ def yolo_txt_output_to_md_output(input_results_folder,
349
347
  results
350
348
  detector_tag (str, optional): string to put in the 'detector' field in the
351
349
  output file
352
- truncate_to_standard_md_precision (bool, optional): set this to truncate to
350
+ truncate_to_standard_md_precision (bool, optional): set this to truncate to
353
351
  COORD_DIGITS and CONF_DIGITS, like the standard MD pipeline does.
354
352
  """
355
-
353
+
356
354
  assert os.path.isdir(input_results_folder)
357
355
  assert os.path.isdir(image_folder)
358
-
356
+
359
357
  ## Enumerate results files and image files
360
-
358
+
361
359
  yolo_results_files = os.listdir(input_results_folder)
362
360
  yolo_results_files = [f for f in yolo_results_files if f.lower().endswith('.txt')]
363
361
  # print('Found {} results files'.format(len(yolo_results_files)))
364
-
362
+
365
363
  image_files = path_utils.find_images(image_folder,recursive=False)
366
364
  image_files_relative = [os.path.basename(f) for f in image_files]
367
365
  # print('Found {} images'.format(len(image_files)))
368
-
366
+
369
367
  image_files_relative_no_extension = [os.path.splitext(f)[0] for f in image_files_relative]
370
-
368
+
371
369
  ## Make sure that every results file corresponds to an image
372
-
370
+
373
371
  for f in yolo_results_files:
374
372
  result_no_extension = os.path.splitext(f)[0]
375
373
  assert result_no_extension in image_files_relative_no_extension
376
-
374
+
377
375
  ## Build MD output data
378
-
376
+
379
377
  # Map 0-indexed YOLO categories to 1-indexed MD categories
380
378
  yolo_cat_map = { 0: 1, 1: 2, 2: 3 }
381
-
379
+
382
380
  images_entries = []
383
381
 
384
382
  # image_fn = image_files_relative[0]
385
383
  for image_fn in image_files_relative:
386
-
387
- image_name, ext = os.path.splitext(image_fn)
384
+
385
+ image_name, ext = os.path.splitext(image_fn)
388
386
  label_fn = image_name + '.txt'
389
387
  label_path = os.path.join(input_results_folder, label_fn)
390
-
388
+
391
389
  detections = []
392
-
390
+
393
391
  if not os.path.exists(label_path):
394
392
  # This is assumed to be an image with no detections
395
393
  pass
@@ -397,36 +395,36 @@ def yolo_txt_output_to_md_output(input_results_folder,
397
395
  with open(label_path, newline='') as f:
398
396
  reader = csv.reader(f, delimiter=' ')
399
397
  for row in reader:
400
- category = yolo_cat_map[int(row[0])]
401
- api_box = ct_utils.convert_yolo_to_xywh([float(row[1]), float(row[2]),
398
+ category = yolo_cat_map[int(row[0])]
399
+ api_box = ct_utils.convert_yolo_to_xywh([float(row[1]), float(row[2]),
402
400
  float(row[3]), float(row[4])])
403
-
401
+
404
402
  conf = float(row[5])
405
-
403
+
406
404
  if truncate_to_standard_md_precision:
407
405
  conf = ct_utils.round_float(conf, precision=CONF_DIGITS)
408
406
  api_box = ct_utils.round_float_array(api_box, precision=COORD_DIGITS)
409
-
407
+
410
408
  detections.append({
411
409
  'category': str(category),
412
410
  'conf': conf,
413
411
  'bbox': api_box
414
412
  })
415
-
413
+
416
414
  images_entries.append({
417
415
  'file': image_fn,
418
416
  'detections': detections
419
417
  })
420
-
418
+
421
419
  # ...for each image
422
-
420
+
423
421
  ## Save output file
424
-
422
+
425
423
  detector_string = 'converted_from_yolo_format'
426
-
424
+
427
425
  if detector_tag is not None:
428
426
  detector_string = detector_tag
429
-
427
+
430
428
  output_content = {
431
429
  'info': {
432
430
  'detector': detector_string,
@@ -440,24 +438,23 @@ def yolo_txt_output_to_md_output(input_results_folder,
440
438
  },
441
439
  'images': images_entries
442
440
  }
443
-
444
- with open(output_file,'w') as f:
445
- json.dump(output_content,f,indent=1)
446
-
441
+
442
+ ct_utils.write_json(output_file, output_content)
443
+
447
444
  # ...def yolo_txt_output_to_md_output(...)
448
445
 
449
446
 
450
447
  #%% Interactive driver
451
448
 
452
449
  if False:
453
-
450
+
454
451
  pass
455
452
 
456
- #%%
457
-
453
+ #%%
454
+
458
455
  input_results_folder = os.path.expanduser('~/tmp/model-version-experiments/pt-test-kru/exp/labels')
459
456
  image_folder = os.path.expanduser('~/data/KRU-test')
460
- output_file = os.path.expanduser('~/data/mdv5a-yolo-pt-kru.json')
457
+ output_file = os.path.expanduser('~/data/mdv5a-yolo-pt-kru.json')
461
458
  yolo_txt_output_to_md_output(input_results_folder,image_folder,output_file)
462
459
 
463
460