megadetector 5.0.28__py3-none-any.whl → 10.0.0__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 (197) hide show
  1. megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +2 -2
  2. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +1 -1
  3. megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +1 -1
  4. megadetector/classification/aggregate_classifier_probs.py +3 -3
  5. megadetector/classification/analyze_failed_images.py +5 -5
  6. megadetector/classification/cache_batchapi_outputs.py +5 -5
  7. megadetector/classification/create_classification_dataset.py +11 -12
  8. megadetector/classification/crop_detections.py +10 -10
  9. megadetector/classification/csv_to_json.py +8 -8
  10. megadetector/classification/detect_and_crop.py +13 -15
  11. megadetector/classification/efficientnet/model.py +8 -8
  12. megadetector/classification/efficientnet/utils.py +6 -5
  13. megadetector/classification/evaluate_model.py +7 -7
  14. megadetector/classification/identify_mislabeled_candidates.py +6 -6
  15. megadetector/classification/json_to_azcopy_list.py +1 -1
  16. megadetector/classification/json_validator.py +29 -32
  17. megadetector/classification/map_classification_categories.py +9 -9
  18. megadetector/classification/merge_classification_detection_output.py +12 -9
  19. megadetector/classification/prepare_classification_script.py +19 -19
  20. megadetector/classification/prepare_classification_script_mc.py +26 -26
  21. megadetector/classification/run_classifier.py +4 -4
  22. megadetector/classification/save_mislabeled.py +6 -6
  23. megadetector/classification/train_classifier.py +1 -1
  24. megadetector/classification/train_classifier_tf.py +9 -9
  25. megadetector/classification/train_utils.py +10 -10
  26. megadetector/data_management/annotations/annotation_constants.py +1 -2
  27. megadetector/data_management/camtrap_dp_to_coco.py +79 -46
  28. megadetector/data_management/cct_json_utils.py +103 -103
  29. megadetector/data_management/cct_to_md.py +49 -49
  30. megadetector/data_management/cct_to_wi.py +33 -33
  31. megadetector/data_management/coco_to_labelme.py +75 -75
  32. megadetector/data_management/coco_to_yolo.py +210 -193
  33. megadetector/data_management/databases/add_width_and_height_to_db.py +86 -12
  34. megadetector/data_management/databases/combine_coco_camera_traps_files.py +40 -40
  35. megadetector/data_management/databases/integrity_check_json_db.py +228 -200
  36. megadetector/data_management/databases/subset_json_db.py +33 -33
  37. megadetector/data_management/generate_crops_from_cct.py +88 -39
  38. megadetector/data_management/get_image_sizes.py +54 -49
  39. megadetector/data_management/labelme_to_coco.py +133 -125
  40. megadetector/data_management/labelme_to_yolo.py +159 -73
  41. megadetector/data_management/lila/create_lila_blank_set.py +81 -83
  42. megadetector/data_management/lila/create_lila_test_set.py +32 -31
  43. megadetector/data_management/lila/create_links_to_md_results_files.py +18 -18
  44. megadetector/data_management/lila/download_lila_subset.py +21 -24
  45. megadetector/data_management/lila/generate_lila_per_image_labels.py +365 -107
  46. megadetector/data_management/lila/get_lila_annotation_counts.py +35 -33
  47. megadetector/data_management/lila/get_lila_image_counts.py +22 -22
  48. megadetector/data_management/lila/lila_common.py +73 -70
  49. megadetector/data_management/lila/test_lila_metadata_urls.py +28 -19
  50. megadetector/data_management/mewc_to_md.py +344 -340
  51. megadetector/data_management/ocr_tools.py +262 -255
  52. megadetector/data_management/read_exif.py +249 -227
  53. megadetector/data_management/remap_coco_categories.py +90 -28
  54. megadetector/data_management/remove_exif.py +81 -21
  55. megadetector/data_management/rename_images.py +187 -187
  56. megadetector/data_management/resize_coco_dataset.py +588 -120
  57. megadetector/data_management/speciesnet_to_md.py +41 -41
  58. megadetector/data_management/wi_download_csv_to_coco.py +55 -55
  59. megadetector/data_management/yolo_output_to_md_output.py +248 -122
  60. megadetector/data_management/yolo_to_coco.py +333 -191
  61. megadetector/detection/change_detection.py +832 -0
  62. megadetector/detection/process_video.py +340 -337
  63. megadetector/detection/pytorch_detector.py +358 -278
  64. megadetector/detection/run_detector.py +399 -186
  65. megadetector/detection/run_detector_batch.py +404 -377
  66. megadetector/detection/run_inference_with_yolov5_val.py +340 -327
  67. megadetector/detection/run_tiled_inference.py +257 -249
  68. megadetector/detection/tf_detector.py +24 -24
  69. megadetector/detection/video_utils.py +332 -295
  70. megadetector/postprocessing/add_max_conf.py +19 -11
  71. megadetector/postprocessing/categorize_detections_by_size.py +45 -45
  72. megadetector/postprocessing/classification_postprocessing.py +468 -433
  73. megadetector/postprocessing/combine_batch_outputs.py +23 -23
  74. megadetector/postprocessing/compare_batch_results.py +590 -525
  75. megadetector/postprocessing/convert_output_format.py +106 -102
  76. megadetector/postprocessing/create_crop_folder.py +347 -147
  77. megadetector/postprocessing/detector_calibration.py +173 -168
  78. megadetector/postprocessing/generate_csv_report.py +508 -499
  79. megadetector/postprocessing/load_api_results.py +48 -27
  80. megadetector/postprocessing/md_to_coco.py +133 -102
  81. megadetector/postprocessing/md_to_labelme.py +107 -90
  82. megadetector/postprocessing/md_to_wi.py +40 -40
  83. megadetector/postprocessing/merge_detections.py +92 -114
  84. megadetector/postprocessing/postprocess_batch_results.py +319 -301
  85. megadetector/postprocessing/remap_detection_categories.py +91 -38
  86. megadetector/postprocessing/render_detection_confusion_matrix.py +214 -205
  87. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +57 -57
  88. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +27 -28
  89. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +704 -679
  90. megadetector/postprocessing/separate_detections_into_folders.py +226 -211
  91. megadetector/postprocessing/subset_json_detector_output.py +265 -262
  92. megadetector/postprocessing/top_folders_to_bottom.py +45 -45
  93. megadetector/postprocessing/validate_batch_results.py +70 -70
  94. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +52 -52
  95. megadetector/taxonomy_mapping/map_new_lila_datasets.py +18 -19
  96. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +54 -33
  97. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +67 -67
  98. megadetector/taxonomy_mapping/retrieve_sample_image.py +16 -16
  99. megadetector/taxonomy_mapping/simple_image_download.py +8 -8
  100. megadetector/taxonomy_mapping/species_lookup.py +156 -74
  101. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +14 -14
  102. megadetector/taxonomy_mapping/taxonomy_graph.py +10 -10
  103. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +13 -13
  104. megadetector/utils/ct_utils.py +1049 -211
  105. megadetector/utils/directory_listing.py +21 -77
  106. megadetector/utils/gpu_test.py +22 -22
  107. megadetector/utils/md_tests.py +632 -529
  108. megadetector/utils/path_utils.py +1520 -431
  109. megadetector/utils/process_utils.py +41 -41
  110. megadetector/utils/split_locations_into_train_val.py +62 -62
  111. megadetector/utils/string_utils.py +148 -27
  112. megadetector/utils/url_utils.py +489 -176
  113. megadetector/utils/wi_utils.py +2658 -2526
  114. megadetector/utils/write_html_image_list.py +137 -137
  115. megadetector/visualization/plot_utils.py +34 -30
  116. megadetector/visualization/render_images_with_thumbnails.py +39 -74
  117. megadetector/visualization/visualization_utils.py +487 -435
  118. megadetector/visualization/visualize_db.py +232 -198
  119. megadetector/visualization/visualize_detector_output.py +82 -76
  120. {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/METADATA +5 -2
  121. megadetector-10.0.0.dist-info/RECORD +139 -0
  122. {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/WHEEL +1 -1
  123. megadetector/api/batch_processing/api_core/__init__.py +0 -0
  124. megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
  125. megadetector/api/batch_processing/api_core/batch_service/score.py +0 -439
  126. megadetector/api/batch_processing/api_core/server.py +0 -294
  127. megadetector/api/batch_processing/api_core/server_api_config.py +0 -97
  128. megadetector/api/batch_processing/api_core/server_app_config.py +0 -55
  129. megadetector/api/batch_processing/api_core/server_batch_job_manager.py +0 -220
  130. megadetector/api/batch_processing/api_core/server_job_status_table.py +0 -149
  131. megadetector/api/batch_processing/api_core/server_orchestration.py +0 -360
  132. megadetector/api/batch_processing/api_core/server_utils.py +0 -88
  133. megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
  134. megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +0 -46
  135. megadetector/api/batch_processing/api_support/__init__.py +0 -0
  136. megadetector/api/batch_processing/api_support/summarize_daily_activity.py +0 -152
  137. megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
  138. megadetector/api/synchronous/__init__.py +0 -0
  139. megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
  140. megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +0 -151
  141. megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -263
  142. megadetector/api/synchronous/api_core/animal_detection_api/config.py +0 -35
  143. megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
  144. megadetector/api/synchronous/api_core/tests/load_test.py +0 -110
  145. megadetector/data_management/importers/add_nacti_sizes.py +0 -52
  146. megadetector/data_management/importers/add_timestamps_to_icct.py +0 -79
  147. megadetector/data_management/importers/animl_results_to_md_results.py +0 -158
  148. megadetector/data_management/importers/auckland_doc_test_to_json.py +0 -373
  149. megadetector/data_management/importers/auckland_doc_to_json.py +0 -201
  150. megadetector/data_management/importers/awc_to_json.py +0 -191
  151. megadetector/data_management/importers/bellevue_to_json.py +0 -272
  152. megadetector/data_management/importers/cacophony-thermal-importer.py +0 -793
  153. megadetector/data_management/importers/carrizo_shrubfree_2018.py +0 -269
  154. megadetector/data_management/importers/carrizo_trail_cam_2017.py +0 -289
  155. megadetector/data_management/importers/cct_field_adjustments.py +0 -58
  156. megadetector/data_management/importers/channel_islands_to_cct.py +0 -913
  157. megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
  158. megadetector/data_management/importers/eMammal/eMammal_helpers.py +0 -249
  159. megadetector/data_management/importers/eMammal/make_eMammal_json.py +0 -223
  160. megadetector/data_management/importers/ena24_to_json.py +0 -276
  161. megadetector/data_management/importers/filenames_to_json.py +0 -386
  162. megadetector/data_management/importers/helena_to_cct.py +0 -283
  163. megadetector/data_management/importers/idaho-camera-traps.py +0 -1407
  164. megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
  165. megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +0 -387
  166. megadetector/data_management/importers/jb_csv_to_json.py +0 -150
  167. megadetector/data_management/importers/mcgill_to_json.py +0 -250
  168. megadetector/data_management/importers/missouri_to_json.py +0 -490
  169. megadetector/data_management/importers/nacti_fieldname_adjustments.py +0 -79
  170. megadetector/data_management/importers/noaa_seals_2019.py +0 -181
  171. megadetector/data_management/importers/osu-small-animals-to-json.py +0 -364
  172. megadetector/data_management/importers/pc_to_json.py +0 -365
  173. megadetector/data_management/importers/plot_wni_giraffes.py +0 -123
  174. megadetector/data_management/importers/prepare_zsl_imerit.py +0 -131
  175. megadetector/data_management/importers/raic_csv_to_md_results.py +0 -416
  176. megadetector/data_management/importers/rspb_to_json.py +0 -356
  177. megadetector/data_management/importers/save_the_elephants_survey_A.py +0 -320
  178. megadetector/data_management/importers/save_the_elephants_survey_B.py +0 -329
  179. megadetector/data_management/importers/snapshot_safari_importer.py +0 -758
  180. megadetector/data_management/importers/snapshot_serengeti_lila.py +0 -1067
  181. megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
  182. megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
  183. megadetector/data_management/importers/sulross_get_exif.py +0 -65
  184. megadetector/data_management/importers/timelapse_csv_set_to_json.py +0 -490
  185. megadetector/data_management/importers/ubc_to_json.py +0 -399
  186. megadetector/data_management/importers/umn_to_json.py +0 -507
  187. megadetector/data_management/importers/wellington_to_json.py +0 -263
  188. megadetector/data_management/importers/wi_to_json.py +0 -442
  189. megadetector/data_management/importers/zamba_results_to_md_results.py +0 -180
  190. megadetector/data_management/lila/add_locations_to_island_camera_traps.py +0 -101
  191. megadetector/data_management/lila/add_locations_to_nacti.py +0 -151
  192. megadetector/utils/azure_utils.py +0 -178
  193. megadetector/utils/sas_blob_utils.py +0 -509
  194. megadetector-5.0.28.dist-info/RECORD +0 -209
  195. /megadetector/{api/batch_processing/__init__.py → __init__.py} +0 -0
  196. {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/licenses/LICENSE +0 -0
  197. {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/top_level.txt +0 -0
@@ -10,12 +10,14 @@ Create YOLO .txt files in a folder containing labelme .json files.
10
10
 
11
11
  import os
12
12
  import json
13
+ import argparse
13
14
 
14
15
  from multiprocessing.pool import Pool, ThreadPool
15
16
  from functools import partial
16
17
  from tqdm import tqdm
17
18
 
18
19
  from megadetector.utils.path_utils import recursive_file_list
20
+ from megadetector.utils.ct_utils import write_json
19
21
 
20
22
 
21
23
  #%% Main function
@@ -28,23 +30,30 @@ def labelme_file_to_yolo_file(labelme_file,
28
30
  """
29
31
  Convert the single .json file labelme_file to yolo format, writing the results to the text
30
32
  file yolo_file (defaults to s/json/txt).
31
-
33
+
32
34
  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).
34
-
35
+ this function no-ops (i.e., does not generate a YOLO file).
36
+
35
37
  overwrite_behavior should be 'skip' or 'overwrite' (default).
38
+
39
+ Args:
40
+ labelme_file (str): .json file to convert
41
+ category_name_to_category_id (dict): category name --> ID mapping
42
+ yolo_file (str, optional): output .txt file defaults to s/json/txt
43
+ required_token (str, optional): only process filenames containing this token
44
+ overwrite_behavior (str, optional): "skip" or "overwrite"
36
45
  """
37
-
46
+
38
47
  result = {}
39
48
  result['labelme_file'] = labelme_file
40
49
  result['status'] = 'unknown'
41
-
50
+
42
51
  assert os.path.isfile(labelme_file), 'Could not find labelme .json file {}'.format(labelme_file)
43
52
  assert labelme_file.endswith('.json'), 'Illegal labelme .json file {}'.format(labelme_file)
44
-
53
+
45
54
  if yolo_file is None:
46
55
  yolo_file = os.path.splitext(labelme_file)[0] + '.txt'
47
-
56
+
48
57
  if os.path.isfile(yolo_file):
49
58
  if overwrite_behavior == 'skip':
50
59
  result['status'] = 'skip-exists'
@@ -52,31 +61,31 @@ def labelme_file_to_yolo_file(labelme_file,
52
61
  else:
53
62
  assert overwrite_behavior == 'overwrite', \
54
63
  'Unrecognized overwrite behavior {}'.format(overwrite_behavior)
55
-
64
+
56
65
  with open(labelme_file,'r') as f:
57
66
  labelme_data = json.load(f)
58
-
67
+
59
68
  if required_token is not None and required_token not in labelme_data:
60
69
  result['status'] = 'skip-no-required-token'
61
70
  return result
62
-
71
+
63
72
  im_height = labelme_data['imageHeight']
64
73
  im_width = labelme_data['imageWidth']
65
-
74
+
66
75
  yolo_lines = []
67
-
76
+
68
77
  for shape in labelme_data['shapes']:
69
-
78
+
70
79
  assert shape['shape_type'] == 'rectangle', \
71
80
  'I only know how to convert rectangles to YOLO format'
72
81
  assert shape['label'] in category_name_to_category_id, \
73
82
  'Category {} not in category mapping'.format(shape['label'])
74
83
  assert len(shape['points']) == 2, 'Illegal rectangle'
75
84
  category_id = category_name_to_category_id[shape['label']]
76
-
85
+
77
86
  p0 = shape['points'][0]
78
87
  p1 = shape['points'][1]
79
-
88
+
80
89
  # Labelme: [[x0,y0],[x1,y1]] (arbitrarily sorted) (absolute coordinates)
81
90
  #
82
91
  # YOLO: [class, x_center, y_center, width, height] (normalized coordinates)
@@ -84,12 +93,12 @@ def labelme_file_to_yolo_file(labelme_file,
84
93
  maxx_abs = max(p0[0],p1[0])
85
94
  miny_abs = min(p0[1],p1[1])
86
95
  maxy_abs = max(p0[1],p1[1])
87
-
96
+
88
97
  if (minx_abs >= (im_width-1)) or (maxx_abs <= 0) or \
89
98
  (miny_abs >= (im_height-1)) or (maxy_abs <= 0):
90
- print('Skipping invalid shape in {}'.format(labelme_file))
99
+ print('Skipping invalid shape in {}'.format(labelme_file))
91
100
  continue
92
-
101
+
93
102
  # Clip to [0,1]... it's not obvious that the YOLO format doesn't allow bounding
94
103
  # boxes to extend outside the image, but YOLOv5 and YOLOv8 get sad about boxes
95
104
  # that extend outside the image.
@@ -97,30 +106,30 @@ def labelme_file_to_yolo_file(labelme_file,
97
106
  maxy_abs = min(maxy_abs,im_height-1)
98
107
  minx_abs = max(minx_abs,0.0)
99
108
  miny_abs = max(miny_abs,0.0)
100
-
109
+
101
110
  minx_rel = minx_abs / (im_width-1)
102
111
  maxx_rel = maxx_abs / (im_width-1)
103
112
  miny_rel = miny_abs / (im_height-1)
104
113
  maxy_rel = maxy_abs / (im_height-1)
105
-
114
+
106
115
  assert maxx_rel >= minx_rel
107
116
  assert maxy_rel >= miny_rel
108
-
117
+
109
118
  xcenter_rel = (maxx_rel + minx_rel) / 2.0
110
119
  ycenter_rel = (maxy_rel + miny_rel) / 2.0
111
120
  w_rel = maxx_rel - minx_rel
112
121
  h_rel = maxy_rel - miny_rel
113
-
122
+
114
123
  yolo_line = '{} {:.3f} {:.3f} {:.3f} {:.3f}'.format(category_id,
115
124
  xcenter_rel, ycenter_rel, w_rel, h_rel)
116
125
  yolo_lines.append(yolo_line)
117
-
126
+
118
127
  # ...for each shape
119
-
128
+
120
129
  with open(yolo_file,'w') as f:
121
130
  for s in yolo_lines:
122
131
  f.write(s + '\n')
123
-
132
+
124
133
  result['status'] = 'converted'
125
134
  return result
126
135
 
@@ -136,22 +145,31 @@ def labelme_folder_to_yolo(labelme_folder,
136
145
  Given a folder with images and labelme .json files, convert the .json files
137
146
  to YOLO .txt format. If category_name_to_category_id is None, first reads
138
147
  all the labels in the folder to build a zero-indexed name --> ID mapping.
139
-
148
+
140
149
  If required_token is not None and a labelme_file does not contain the key [required_token],
141
150
  it won't be converted. Typically used to specify a field that indicates which files have
142
151
  been reviewed.
143
-
152
+
144
153
  If relative_filenames_to_convert is not None, this should be a list of .json (not image)
145
154
  files that should get converted, relative to the base folder.
146
-
155
+
147
156
  overwrite_behavior should be 'skip' or 'overwrite' (default).
148
-
157
+
149
158
  returns a dict with:
150
159
  'category_name_to_category_id', whether it was passed in or constructed
151
160
  'image_results': a list of results for each image (converted, skipped, error)
152
-
161
+
162
+ Args:
163
+ labelme_folder (str): folder of .json files to convert
164
+ category_name_to_category_id (dict): category name --> ID mapping
165
+ required_token (str, optional): only process filenames containing this token
166
+ overwrite_behavior (str, optional): "skip" or "overwrite"
167
+ relative_filenames_to_convert (list of str, optional): only process filenames on this list
168
+ n_workers (int, optional): parallelism level
169
+ use_threads (bool, optional): whether to use threads (True) or processes (False) for
170
+ parallelism
153
171
  """
154
-
172
+
155
173
  if relative_filenames_to_convert is not None:
156
174
  labelme_files_relative = relative_filenames_to_convert
157
175
  assert all([fn.endswith('.json') for fn in labelme_files_relative]), \
@@ -159,35 +177,35 @@ def labelme_folder_to_yolo(labelme_folder,
159
177
  else:
160
178
  labelme_files_relative = recursive_file_list(labelme_folder,return_relative_paths=True)
161
179
  labelme_files_relative = [fn for fn in labelme_files_relative if fn.endswith('.json')]
162
-
180
+
163
181
  if required_token is None:
164
182
  valid_labelme_files_relative = labelme_files_relative
165
- else:
183
+ else:
166
184
  valid_labelme_files_relative = []
167
-
185
+
168
186
  # fn_relative = labelme_files_relative[-1]
169
187
  for fn_relative in labelme_files_relative:
170
-
171
- fn_abs = os.path.join(labelme_folder,fn_relative)
172
-
188
+
189
+ fn_abs = os.path.join(labelme_folder,fn_relative)
190
+
173
191
  with open(fn_abs,'r') as f:
174
192
  labelme_data = json.load(f)
175
193
  if required_token not in labelme_data:
176
194
  continue
177
-
195
+
178
196
  valid_labelme_files_relative.append(fn_relative)
179
-
197
+
180
198
  print('{} of {} files are valid'.format(len(valid_labelme_files_relative),
181
199
  len(labelme_files_relative)))
182
-
200
+
183
201
  del labelme_files_relative
184
-
202
+
185
203
  if category_name_to_category_id is None:
186
-
204
+
187
205
  category_name_to_category_id = {}
188
-
206
+
189
207
  for fn_relative in valid_labelme_files_relative:
190
-
208
+
191
209
  fn_abs = os.path.join(labelme_folder,fn_relative)
192
210
  with open(fn_abs,'r') as f:
193
211
  labelme_data = json.load(f)
@@ -196,16 +214,16 @@ def labelme_folder_to_yolo(labelme_folder,
196
214
  if label not in category_name_to_category_id:
197
215
  category_name_to_category_id[label] = len(category_name_to_category_id)
198
216
  # ...for each file
199
-
217
+
200
218
  # ...if we need to build a category mapping
201
-
219
+
202
220
  image_results = []
203
-
221
+
204
222
  n_workers = min(n_workers,len(valid_labelme_files_relative))
205
-
223
+
206
224
  if n_workers <= 1:
207
225
  for fn_relative in tqdm(valid_labelme_files_relative):
208
-
226
+
209
227
  fn_abs = os.path.join(labelme_folder,fn_relative)
210
228
  image_result = labelme_file_to_yolo_file(fn_abs,
211
229
  category_name_to_category_id,
@@ -215,35 +233,41 @@ def labelme_folder_to_yolo(labelme_folder,
215
233
  image_results.append(image_result)
216
234
  # ...for each file
217
235
  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)))
236
+ pool = None
237
+ try:
238
+ if use_threads:
239
+ pool = ThreadPool(n_workers)
240
+ else:
241
+ pool = Pool(n_workers)
242
+
243
+ valid_labelme_files_abs = [os.path.join(labelme_folder,fn_relative) for \
244
+ fn_relative in valid_labelme_files_relative]
245
+
246
+ image_results = list(tqdm(pool.imap(
247
+ partial(labelme_file_to_yolo_file,
248
+ category_name_to_category_id=category_name_to_category_id,
249
+ yolo_file=None,
250
+ required_token=required_token,
251
+ overwrite_behavior=overwrite_behavior),
252
+ valid_labelme_files_abs),
253
+ total=len(valid_labelme_files_abs)))
254
+ finally:
255
+ pool.close()
256
+ pool.join()
257
+ print('Pool closed and joined for labelme conversion to YOLO')
234
258
 
235
259
  assert len(valid_labelme_files_relative) == len(image_results)
236
-
260
+
237
261
  print('Converted {} labelme .json files to YOLO'.format(
238
262
  len(valid_labelme_files_relative)))
239
-
263
+
240
264
  labelme_to_yolo_results = {}
241
265
  labelme_to_yolo_results['category_name_to_category_id'] = category_name_to_category_id
242
266
  labelme_to_yolo_results['image_results'] = image_results
243
-
267
+
244
268
  return labelme_to_yolo_results
245
-
246
- # ...def labelme_folder_to_yolo(...)
269
+
270
+ # ...def labelme_folder_to_yolo(...)
247
271
 
248
272
 
249
273
  #%% Interactive driver
@@ -260,13 +284,75 @@ if False:
260
284
  labelme_folder = os.path.expanduser('~/tmp/labels')
261
285
 
262
286
  #%%
263
-
287
+
264
288
  category_name_to_category_id = \
265
289
  labelme_folder_to_yolo(labelme_folder,
266
290
  category_name_to_category_id=category_name_to_category_id,
267
291
  required_token=required_token,
268
292
  overwrite_behavior='overwrite')
269
-
293
+
270
294
  #%% Command-line driver
271
295
 
272
- # TODO
296
+ def main():
297
+ """
298
+ Command-line interface to convert Labelme JSON files to YOLO format
299
+ """
300
+
301
+ parser = argparse.ArgumentParser(
302
+ description='Convert a folder of Labelme .json files to YOLO .txt format'
303
+ )
304
+ parser.add_argument(
305
+ 'labelme_folder',
306
+ type=str,
307
+ help='Folder of Labelme .json files to convert'
308
+ )
309
+ parser.add_argument(
310
+ '--output_category_file',
311
+ type=str,
312
+ default=None,
313
+ help='Path to save the generated category mapping (.json)'
314
+ )
315
+ parser.add_argument(
316
+ '--required_token',
317
+ type=str,
318
+ default=None,
319
+ help='Only process files containing this token as a key in the Labelme JSON dict'
320
+ )
321
+ parser.add_argument(
322
+ '--overwrite_behavior',
323
+ type=str,
324
+ default='overwrite',
325
+ choices=['skip', 'overwrite'],
326
+ help="Behavior if YOLO .txt files exist (default: 'overwrite')"
327
+ )
328
+ parser.add_argument(
329
+ '--n_workers',
330
+ type=int,
331
+ default=1,
332
+ help='Number of workers for parallel processing (default: 1)'
333
+ )
334
+ parser.add_argument(
335
+ '--use_processes',
336
+ action='store_true',
337
+ help='Use processes instead of threads for parallelization (defaults to threads)'
338
+ )
339
+
340
+ args = parser.parse_args()
341
+
342
+ results = labelme_folder_to_yolo(
343
+ labelme_folder=args.labelme_folder,
344
+ category_name_to_category_id=None,
345
+ required_token=args.required_token,
346
+ overwrite_behavior=args.overwrite_behavior,
347
+ relative_filenames_to_convert=None,
348
+ n_workers=args.n_workers,
349
+ use_threads=(not args.use_processes)
350
+ )
351
+
352
+ if args.output_category_file:
353
+ category_map = results['category_name_to_category_id']
354
+ write_json(args.output_category_file,category_map)
355
+ print(f'Saved category mapping to {args.output_category_file}')
356
+
357
+ if __name__ == '__main__':
358
+ main()