megadetector 5.0.27__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.
- megadetector/api/batch_processing/api_core/batch_service/score.py +4 -5
- megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +1 -1
- megadetector/api/batch_processing/api_support/summarize_daily_activity.py +1 -1
- megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +2 -2
- megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +1 -1
- megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +1 -1
- megadetector/api/synchronous/api_core/tests/load_test.py +2 -3
- megadetector/classification/aggregate_classifier_probs.py +3 -3
- megadetector/classification/analyze_failed_images.py +5 -5
- megadetector/classification/cache_batchapi_outputs.py +5 -5
- megadetector/classification/create_classification_dataset.py +11 -12
- megadetector/classification/crop_detections.py +10 -10
- megadetector/classification/csv_to_json.py +8 -8
- megadetector/classification/detect_and_crop.py +13 -15
- megadetector/classification/evaluate_model.py +7 -7
- megadetector/classification/identify_mislabeled_candidates.py +6 -6
- megadetector/classification/json_to_azcopy_list.py +1 -1
- megadetector/classification/json_validator.py +29 -32
- megadetector/classification/map_classification_categories.py +9 -9
- megadetector/classification/merge_classification_detection_output.py +12 -9
- megadetector/classification/prepare_classification_script.py +19 -19
- megadetector/classification/prepare_classification_script_mc.py +23 -23
- megadetector/classification/run_classifier.py +4 -4
- megadetector/classification/save_mislabeled.py +6 -6
- megadetector/classification/train_classifier.py +1 -1
- megadetector/classification/train_classifier_tf.py +9 -9
- megadetector/classification/train_utils.py +10 -10
- megadetector/data_management/annotations/annotation_constants.py +1 -1
- megadetector/data_management/camtrap_dp_to_coco.py +45 -45
- megadetector/data_management/cct_json_utils.py +101 -101
- megadetector/data_management/cct_to_md.py +49 -49
- megadetector/data_management/cct_to_wi.py +33 -33
- megadetector/data_management/coco_to_labelme.py +75 -75
- megadetector/data_management/coco_to_yolo.py +189 -189
- megadetector/data_management/databases/add_width_and_height_to_db.py +3 -2
- megadetector/data_management/databases/combine_coco_camera_traps_files.py +38 -38
- megadetector/data_management/databases/integrity_check_json_db.py +202 -188
- megadetector/data_management/databases/subset_json_db.py +33 -33
- megadetector/data_management/generate_crops_from_cct.py +38 -38
- megadetector/data_management/get_image_sizes.py +54 -49
- megadetector/data_management/labelme_to_coco.py +130 -124
- megadetector/data_management/labelme_to_yolo.py +78 -72
- megadetector/data_management/lila/create_lila_blank_set.py +81 -83
- megadetector/data_management/lila/create_lila_test_set.py +32 -31
- megadetector/data_management/lila/create_links_to_md_results_files.py +18 -18
- megadetector/data_management/lila/download_lila_subset.py +21 -24
- megadetector/data_management/lila/generate_lila_per_image_labels.py +91 -91
- megadetector/data_management/lila/get_lila_annotation_counts.py +30 -30
- megadetector/data_management/lila/get_lila_image_counts.py +22 -22
- megadetector/data_management/lila/lila_common.py +70 -70
- megadetector/data_management/lila/test_lila_metadata_urls.py +13 -14
- megadetector/data_management/mewc_to_md.py +339 -340
- megadetector/data_management/ocr_tools.py +258 -252
- megadetector/data_management/read_exif.py +232 -223
- megadetector/data_management/remap_coco_categories.py +26 -26
- megadetector/data_management/remove_exif.py +31 -20
- megadetector/data_management/rename_images.py +187 -187
- megadetector/data_management/resize_coco_dataset.py +41 -41
- megadetector/data_management/speciesnet_to_md.py +41 -41
- megadetector/data_management/wi_download_csv_to_coco.py +55 -55
- megadetector/data_management/yolo_output_to_md_output.py +117 -120
- megadetector/data_management/yolo_to_coco.py +195 -188
- megadetector/detection/change_detection.py +831 -0
- megadetector/detection/process_video.py +341 -338
- megadetector/detection/pytorch_detector.py +308 -266
- megadetector/detection/run_detector.py +186 -166
- megadetector/detection/run_detector_batch.py +366 -364
- megadetector/detection/run_inference_with_yolov5_val.py +328 -325
- megadetector/detection/run_tiled_inference.py +312 -253
- megadetector/detection/tf_detector.py +24 -24
- megadetector/detection/video_utils.py +291 -283
- megadetector/postprocessing/add_max_conf.py +15 -11
- megadetector/postprocessing/categorize_detections_by_size.py +44 -44
- megadetector/postprocessing/classification_postprocessing.py +808 -311
- megadetector/postprocessing/combine_batch_outputs.py +20 -21
- megadetector/postprocessing/compare_batch_results.py +528 -517
- megadetector/postprocessing/convert_output_format.py +97 -97
- megadetector/postprocessing/create_crop_folder.py +220 -147
- megadetector/postprocessing/detector_calibration.py +173 -168
- megadetector/postprocessing/generate_csv_report.py +508 -0
- megadetector/postprocessing/load_api_results.py +25 -22
- megadetector/postprocessing/md_to_coco.py +129 -98
- megadetector/postprocessing/md_to_labelme.py +89 -83
- megadetector/postprocessing/md_to_wi.py +40 -40
- megadetector/postprocessing/merge_detections.py +87 -114
- megadetector/postprocessing/postprocess_batch_results.py +319 -302
- megadetector/postprocessing/remap_detection_categories.py +36 -36
- megadetector/postprocessing/render_detection_confusion_matrix.py +205 -199
- megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +57 -57
- megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +27 -28
- megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +702 -677
- megadetector/postprocessing/separate_detections_into_folders.py +226 -211
- megadetector/postprocessing/subset_json_detector_output.py +265 -262
- megadetector/postprocessing/top_folders_to_bottom.py +45 -45
- megadetector/postprocessing/validate_batch_results.py +70 -70
- megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +52 -52
- megadetector/taxonomy_mapping/map_new_lila_datasets.py +15 -15
- megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +14 -14
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +66 -69
- megadetector/taxonomy_mapping/retrieve_sample_image.py +16 -16
- megadetector/taxonomy_mapping/simple_image_download.py +8 -8
- megadetector/taxonomy_mapping/species_lookup.py +33 -33
- megadetector/taxonomy_mapping/taxonomy_csv_checker.py +14 -14
- megadetector/taxonomy_mapping/taxonomy_graph.py +11 -11
- megadetector/taxonomy_mapping/validate_lila_category_mappings.py +13 -13
- megadetector/utils/azure_utils.py +22 -22
- megadetector/utils/ct_utils.py +1019 -200
- megadetector/utils/directory_listing.py +21 -77
- megadetector/utils/gpu_test.py +22 -22
- megadetector/utils/md_tests.py +541 -518
- megadetector/utils/path_utils.py +1511 -406
- megadetector/utils/process_utils.py +41 -41
- megadetector/utils/sas_blob_utils.py +53 -49
- megadetector/utils/split_locations_into_train_val.py +73 -60
- megadetector/utils/string_utils.py +147 -26
- megadetector/utils/url_utils.py +463 -173
- megadetector/utils/wi_utils.py +2629 -2868
- megadetector/utils/write_html_image_list.py +137 -137
- megadetector/visualization/plot_utils.py +21 -21
- megadetector/visualization/render_images_with_thumbnails.py +37 -73
- megadetector/visualization/visualization_utils.py +424 -404
- megadetector/visualization/visualize_db.py +197 -190
- megadetector/visualization/visualize_detector_output.py +126 -98
- {megadetector-5.0.27.dist-info → megadetector-5.0.29.dist-info}/METADATA +6 -3
- megadetector-5.0.29.dist-info/RECORD +163 -0
- {megadetector-5.0.27.dist-info → megadetector-5.0.29.dist-info}/WHEEL +1 -1
- megadetector/data_management/importers/add_nacti_sizes.py +0 -52
- megadetector/data_management/importers/add_timestamps_to_icct.py +0 -79
- megadetector/data_management/importers/animl_results_to_md_results.py +0 -158
- megadetector/data_management/importers/auckland_doc_test_to_json.py +0 -373
- megadetector/data_management/importers/auckland_doc_to_json.py +0 -201
- megadetector/data_management/importers/awc_to_json.py +0 -191
- megadetector/data_management/importers/bellevue_to_json.py +0 -272
- megadetector/data_management/importers/cacophony-thermal-importer.py +0 -793
- megadetector/data_management/importers/carrizo_shrubfree_2018.py +0 -269
- megadetector/data_management/importers/carrizo_trail_cam_2017.py +0 -289
- megadetector/data_management/importers/cct_field_adjustments.py +0 -58
- megadetector/data_management/importers/channel_islands_to_cct.py +0 -913
- megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
- megadetector/data_management/importers/eMammal/eMammal_helpers.py +0 -249
- megadetector/data_management/importers/eMammal/make_eMammal_json.py +0 -223
- megadetector/data_management/importers/ena24_to_json.py +0 -276
- megadetector/data_management/importers/filenames_to_json.py +0 -386
- megadetector/data_management/importers/helena_to_cct.py +0 -283
- megadetector/data_management/importers/idaho-camera-traps.py +0 -1407
- megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
- megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +0 -387
- megadetector/data_management/importers/jb_csv_to_json.py +0 -150
- megadetector/data_management/importers/mcgill_to_json.py +0 -250
- megadetector/data_management/importers/missouri_to_json.py +0 -490
- megadetector/data_management/importers/nacti_fieldname_adjustments.py +0 -79
- megadetector/data_management/importers/noaa_seals_2019.py +0 -181
- megadetector/data_management/importers/osu-small-animals-to-json.py +0 -364
- megadetector/data_management/importers/pc_to_json.py +0 -365
- megadetector/data_management/importers/plot_wni_giraffes.py +0 -123
- megadetector/data_management/importers/prepare_zsl_imerit.py +0 -131
- megadetector/data_management/importers/raic_csv_to_md_results.py +0 -416
- megadetector/data_management/importers/rspb_to_json.py +0 -356
- megadetector/data_management/importers/save_the_elephants_survey_A.py +0 -320
- megadetector/data_management/importers/save_the_elephants_survey_B.py +0 -329
- megadetector/data_management/importers/snapshot_safari_importer.py +0 -758
- megadetector/data_management/importers/snapshot_serengeti_lila.py +0 -1067
- megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
- megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
- megadetector/data_management/importers/sulross_get_exif.py +0 -65
- megadetector/data_management/importers/timelapse_csv_set_to_json.py +0 -490
- megadetector/data_management/importers/ubc_to_json.py +0 -399
- megadetector/data_management/importers/umn_to_json.py +0 -507
- megadetector/data_management/importers/wellington_to_json.py +0 -263
- megadetector/data_management/importers/wi_to_json.py +0 -442
- megadetector/data_management/importers/zamba_results_to_md_results.py +0 -180
- megadetector/data_management/lila/add_locations_to_island_camera_traps.py +0 -101
- megadetector/data_management/lila/add_locations_to_nacti.py +0 -151
- megadetector-5.0.27.dist-info/RECORD +0 -208
- {megadetector-5.0.27.dist-info → megadetector-5.0.29.dist-info}/licenses/LICENSE +0 -0
- {megadetector-5.0.27.dist-info → megadetector-5.0.29.dist-info}/top_level.txt +0 -0
|
@@ -11,6 +11,8 @@ Converts a folder of labelme-formatted .json files to COCO.
|
|
|
11
11
|
import json
|
|
12
12
|
import os
|
|
13
13
|
import uuid
|
|
14
|
+
import sys
|
|
15
|
+
import argparse
|
|
14
16
|
|
|
15
17
|
from multiprocessing.pool import Pool, ThreadPool
|
|
16
18
|
from functools import partial
|
|
@@ -25,9 +27,9 @@ from megadetector.visualization.visualization_utils import open_image
|
|
|
25
27
|
def _add_category(category_name,category_name_to_id,candidate_category_id=0):
|
|
26
28
|
"""
|
|
27
29
|
Adds the category [category_name] to the dict [category_name_to_id], by default
|
|
28
|
-
using the next available integer index.
|
|
30
|
+
using the next available integer index.
|
|
29
31
|
"""
|
|
30
|
-
|
|
32
|
+
|
|
31
33
|
if category_name in category_name_to_id:
|
|
32
34
|
return category_name_to_id[category_name]
|
|
33
35
|
while candidate_category_id in category_name_to_id.values():
|
|
@@ -42,33 +44,33 @@ def _process_labelme_file(image_fn_relative,input_folder,use_folders_as_labels,
|
|
|
42
44
|
"""
|
|
43
45
|
Internal function for processing each image; this support function facilitates parallelization.
|
|
44
46
|
"""
|
|
45
|
-
|
|
47
|
+
|
|
46
48
|
result = {}
|
|
47
49
|
result['im'] = None
|
|
48
50
|
result['annotations_this_image'] = None
|
|
49
51
|
result['status'] = None
|
|
50
|
-
|
|
52
|
+
|
|
51
53
|
image_fn_abs = os.path.join(input_folder,image_fn_relative)
|
|
52
54
|
json_fn_abs = os.path.splitext(image_fn_abs)[0] + '.json'
|
|
53
|
-
|
|
55
|
+
|
|
54
56
|
im = {}
|
|
55
57
|
im['id'] = image_fn_relative
|
|
56
58
|
im['file_name'] = image_fn_relative
|
|
57
|
-
|
|
59
|
+
|
|
58
60
|
# If there's no .json file for this image...
|
|
59
61
|
if not os.path.isfile(json_fn_abs):
|
|
60
|
-
|
|
62
|
+
|
|
61
63
|
# Either skip it...
|
|
62
64
|
if no_json_handling == 'skip':
|
|
63
65
|
print('Skipping image {} (no .json file)'.format(image_fn_relative))
|
|
64
66
|
result['status'] = 'skipped (no .json file)'
|
|
65
67
|
return result
|
|
66
|
-
|
|
68
|
+
|
|
67
69
|
# ...or error
|
|
68
70
|
elif no_json_handling == 'error':
|
|
69
71
|
raise ValueError('Image file {} has no corresponding .json file'.format(
|
|
70
72
|
image_fn_relative))
|
|
71
|
-
|
|
73
|
+
|
|
72
74
|
# ...or treat it as empty.
|
|
73
75
|
elif no_json_handling == 'empty':
|
|
74
76
|
try:
|
|
@@ -79,23 +81,23 @@ def _process_labelme_file(image_fn_relative,input_folder,use_folders_as_labels,
|
|
|
79
81
|
return result
|
|
80
82
|
im['width'] = pil_im.width
|
|
81
83
|
im['height'] = pil_im.height
|
|
82
|
-
|
|
84
|
+
|
|
83
85
|
# Just in case we need to differentiate between "no .json file" and "a .json file with no annotations"
|
|
84
86
|
im['no_labelme_json'] = True
|
|
85
87
|
shapes = []
|
|
86
88
|
else:
|
|
87
89
|
raise ValueError('Unrecognized specifier {} for handling images with no .json files'.format(
|
|
88
|
-
no_json_handling))
|
|
89
|
-
|
|
90
|
+
no_json_handling))
|
|
91
|
+
|
|
90
92
|
# If we found a .json file for this image...
|
|
91
93
|
else:
|
|
92
|
-
|
|
94
|
+
|
|
93
95
|
# Read the .json file
|
|
94
96
|
with open(json_fn_abs,'r') as f:
|
|
95
|
-
labelme_data = json.load(f)
|
|
97
|
+
labelme_data = json.load(f)
|
|
96
98
|
im['width'] = labelme_data['imageWidth']
|
|
97
99
|
im['height'] = labelme_data['imageHeight']
|
|
98
|
-
|
|
100
|
+
|
|
99
101
|
if validate_image_sizes:
|
|
100
102
|
try:
|
|
101
103
|
pil_im = open_image(image_fn_abs)
|
|
@@ -116,55 +118,55 @@ def _process_labelme_file(image_fn_relative,input_folder,use_folders_as_labels,
|
|
|
116
118
|
im['flags'] = labelme_data['flags']
|
|
117
119
|
|
|
118
120
|
annotations_this_image = []
|
|
119
|
-
|
|
121
|
+
|
|
120
122
|
if len(shapes) == 0:
|
|
121
|
-
|
|
123
|
+
|
|
122
124
|
if allow_new_categories:
|
|
123
125
|
category_id = _add_category('empty',category_name_to_id)
|
|
124
126
|
else:
|
|
125
127
|
assert 'empty' in category_name_to_id
|
|
126
128
|
category_id = category_name_to_id['empty']
|
|
127
|
-
|
|
129
|
+
|
|
128
130
|
ann = {}
|
|
129
131
|
ann['id'] = str(uuid.uuid1())
|
|
130
132
|
ann['image_id'] = im['id']
|
|
131
133
|
ann['category_id'] = category_id
|
|
132
134
|
ann['sequence_level_annotation'] = False
|
|
133
135
|
annotations_this_image.append(ann)
|
|
134
|
-
|
|
136
|
+
|
|
135
137
|
else:
|
|
136
|
-
|
|
138
|
+
|
|
137
139
|
for shape in shapes:
|
|
138
|
-
|
|
140
|
+
|
|
139
141
|
if shape['shape_type'] != 'rectangle':
|
|
140
142
|
print('Only rectangles are supported, skipping an annotation of type {} in {}'.format(
|
|
141
143
|
shape['shape_type'],image_fn_relative))
|
|
142
144
|
continue
|
|
143
|
-
|
|
145
|
+
|
|
144
146
|
if use_folders_as_labels:
|
|
145
147
|
category_name = os.path.basename(os.path.dirname(image_fn_abs))
|
|
146
148
|
else:
|
|
147
|
-
category_name = shape['label']
|
|
148
|
-
|
|
149
|
+
category_name = shape['label']
|
|
150
|
+
|
|
149
151
|
if allow_new_categories:
|
|
150
152
|
category_id = _add_category(category_name,category_name_to_id)
|
|
151
153
|
else:
|
|
152
154
|
assert category_name in category_name_to_id
|
|
153
155
|
category_id = category_name_to_id[category_name]
|
|
154
|
-
|
|
156
|
+
|
|
155
157
|
points = shape['points']
|
|
156
158
|
if len(points) != 2:
|
|
157
159
|
print('Warning: illegal rectangle with {} points for {}'.format(
|
|
158
160
|
len(points),image_fn_relative))
|
|
159
161
|
continue
|
|
160
|
-
|
|
162
|
+
|
|
161
163
|
p0 = points[0]
|
|
162
164
|
p1 = points[1]
|
|
163
165
|
x0 = min(p0[0],p1[0])
|
|
164
166
|
x1 = max(p0[0],p1[0])
|
|
165
167
|
y0 = min(p0[1],p1[1])
|
|
166
168
|
y1 = max(p0[1],p1[1])
|
|
167
|
-
|
|
169
|
+
|
|
168
170
|
bbox = [x0,y0,abs(x1-x0),abs(y1-y0)]
|
|
169
171
|
ann = {}
|
|
170
172
|
ann['id'] = str(uuid.uuid1())
|
|
@@ -173,14 +175,14 @@ def _process_labelme_file(image_fn_relative,input_folder,use_folders_as_labels,
|
|
|
173
175
|
ann['sequence_level_annotation'] = False
|
|
174
176
|
ann['bbox'] = bbox
|
|
175
177
|
annotations_this_image.append(ann)
|
|
176
|
-
|
|
178
|
+
|
|
177
179
|
# ...for each shape
|
|
178
|
-
|
|
180
|
+
|
|
179
181
|
result['im'] = im
|
|
180
182
|
result['annotations_this_image'] = annotations_this_image
|
|
181
183
|
|
|
182
184
|
return result
|
|
183
|
-
|
|
185
|
+
|
|
184
186
|
# ...def _process_labelme_file(...)
|
|
185
187
|
|
|
186
188
|
|
|
@@ -198,31 +200,31 @@ def labelme_to_coco(input_folder,
|
|
|
198
200
|
recursive=True,
|
|
199
201
|
no_json_handling='skip',
|
|
200
202
|
validate_image_sizes=True,
|
|
201
|
-
max_workers=1,
|
|
203
|
+
max_workers=1,
|
|
202
204
|
use_threads=True):
|
|
203
205
|
"""
|
|
204
206
|
Finds all images in [input_folder] that have corresponding .json files, and converts
|
|
205
207
|
to a COCO .json file.
|
|
206
|
-
|
|
208
|
+
|
|
207
209
|
Currently only supports bounding box annotations and image-level flags (i.e., does not
|
|
208
210
|
support point or general polygon annotations).
|
|
209
|
-
|
|
211
|
+
|
|
210
212
|
Labelme's image-level flags don't quite fit the COCO annotations format, so they are attached
|
|
211
213
|
to image objects, rather than annotation objects.
|
|
212
|
-
|
|
213
|
-
If output_file is None, just returns the resulting dict, does not write to file.
|
|
214
|
-
|
|
214
|
+
|
|
215
|
+
If output_file is None, just returns the resulting dict, does not write to file.
|
|
216
|
+
|
|
215
217
|
if use_folders_as_labels is False (default), the output labels come from the labelme
|
|
216
218
|
.json files. If use_folders_as_labels is True, the lowest-level folder name containing
|
|
217
219
|
each .json file will determine the output label. E.g., if use_folders_as_labels is True,
|
|
218
220
|
and the folder contains:
|
|
219
|
-
|
|
221
|
+
|
|
220
222
|
images/train/lion/image0001.json
|
|
221
|
-
|
|
222
|
-
...all boxes in image0001.json will be given the label "lion", regardless of the labels in the
|
|
223
|
-
file. Empty images in the "lion" folder will still be given the label "empty" (or
|
|
223
|
+
|
|
224
|
+
...all boxes in image0001.json will be given the label "lion", regardless of the labels in the
|
|
225
|
+
file. Empty images in the "lion" folder will still be given the label "empty" (or
|
|
224
226
|
[empty_category_name]).
|
|
225
|
-
|
|
227
|
+
|
|
226
228
|
Args:
|
|
227
229
|
input_folder (str): input folder to search for images and Labelme .json files
|
|
228
230
|
output_file (str, optional): output file to which we should write COCO-formatted data; if None
|
|
@@ -240,9 +242,9 @@ def labelme_to_coco(input_folder,
|
|
|
240
242
|
use_folders_as_labels (bool, optional): if this is True, class names will be pulled from folder names,
|
|
241
243
|
useful if you have images like a/b/cat/image001.jpg, a/b/dog/image002.jpg, etc.
|
|
242
244
|
recursive (bool, optional): whether to recurse into [input_folder]
|
|
243
|
-
no_json_handling (str, optional): how to deal with image files that have no corresponding .json files,
|
|
245
|
+
no_json_handling (str, optional): how to deal with image files that have no corresponding .json files,
|
|
244
246
|
can be:
|
|
245
|
-
|
|
247
|
+
|
|
246
248
|
- 'skip': ignore image files with no corresponding .json files
|
|
247
249
|
- 'empty': treat image files with no corresponding .json files as empty
|
|
248
250
|
- 'error': throw an error when an image file has no corresponding .json file
|
|
@@ -252,15 +254,15 @@ def labelme_to_coco(input_folder,
|
|
|
252
254
|
parallelization
|
|
253
255
|
use_threads (bool, optional): whether to use threads (True) or processes (False) for parallelization,
|
|
254
256
|
not relevant if max_workers <= 1
|
|
255
|
-
|
|
257
|
+
|
|
256
258
|
Returns:
|
|
257
259
|
dict: a COCO-formatted dictionary, identical to what's written to [output_file] if [output_file] is not None.
|
|
258
260
|
"""
|
|
259
|
-
|
|
261
|
+
|
|
260
262
|
if max_workers > 1:
|
|
261
263
|
assert category_id_to_category_name is not None, \
|
|
262
264
|
'When parallelizing labelme --> COCO conversion, you must supply a category mapping'
|
|
263
|
-
|
|
265
|
+
|
|
264
266
|
if category_id_to_category_name is None:
|
|
265
267
|
category_name_to_id = {}
|
|
266
268
|
else:
|
|
@@ -270,7 +272,7 @@ def labelme_to_coco(input_folder,
|
|
|
270
272
|
category_name_to_id[category_name] = int(category_name_to_id[category_name])
|
|
271
273
|
except ValueError:
|
|
272
274
|
raise ValueError('Category IDs must be ints or string-formatted ints')
|
|
273
|
-
|
|
275
|
+
|
|
274
276
|
# If the user supplied an explicit empty category ID, and the empty category
|
|
275
277
|
# name is already in category_name_to_id, make sure they match.
|
|
276
278
|
if empty_category_id is not None:
|
|
@@ -285,13 +287,13 @@ def labelme_to_coco(input_folder,
|
|
|
285
287
|
empty_category_id = category_name_to_id[empty_category_name]
|
|
286
288
|
|
|
287
289
|
del category_id_to_category_name
|
|
288
|
-
|
|
290
|
+
|
|
289
291
|
# Enumerate images
|
|
290
|
-
print('Enumerating images in {}'.format(input_folder))
|
|
292
|
+
print('Enumerating images in {}'.format(input_folder))
|
|
291
293
|
image_filenames_relative = path_utils.find_images(input_folder,recursive=recursive,
|
|
292
294
|
return_relative_paths=True,
|
|
293
|
-
convert_slashes=True)
|
|
294
|
-
|
|
295
|
+
convert_slashes=True)
|
|
296
|
+
|
|
295
297
|
# Remove any images we're supposed to skip
|
|
296
298
|
if (relative_paths_to_include is not None) or (relative_paths_to_exclude is not None):
|
|
297
299
|
image_filenames_relative_to_process = []
|
|
@@ -305,65 +307,71 @@ def labelme_to_coco(input_folder,
|
|
|
305
307
|
len(image_filenames_relative_to_process),
|
|
306
308
|
len(image_filenames_relative)))
|
|
307
309
|
image_filenames_relative = image_filenames_relative_to_process
|
|
308
|
-
|
|
310
|
+
|
|
309
311
|
# If the user supplied a category ID to use for empty images...
|
|
310
312
|
if empty_category_id is not None:
|
|
311
313
|
try:
|
|
312
314
|
empty_category_id = int(empty_category_id)
|
|
313
315
|
except ValueError:
|
|
314
316
|
raise ValueError('Category IDs must be ints or string-formatted ints')
|
|
315
|
-
|
|
317
|
+
|
|
316
318
|
if empty_category_id is None:
|
|
317
319
|
empty_category_id = _add_category(empty_category_name,category_name_to_id)
|
|
318
|
-
|
|
320
|
+
|
|
319
321
|
if max_workers <= 1:
|
|
320
|
-
|
|
322
|
+
|
|
321
323
|
image_results = []
|
|
322
324
|
for image_fn_relative in tqdm(image_filenames_relative):
|
|
323
|
-
|
|
325
|
+
|
|
324
326
|
result = _process_labelme_file(image_fn_relative,input_folder,use_folders_as_labels,
|
|
325
327
|
no_json_handling,validate_image_sizes,
|
|
326
|
-
category_name_to_id,allow_new_categories=True)
|
|
328
|
+
category_name_to_id,allow_new_categories=True)
|
|
327
329
|
image_results.append(result)
|
|
328
|
-
|
|
329
|
-
else:
|
|
330
|
-
|
|
330
|
+
|
|
331
|
+
else:
|
|
332
|
+
|
|
331
333
|
n_workers = min(max_workers,len(image_filenames_relative))
|
|
332
334
|
assert category_name_to_id is not None
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
335
|
+
|
|
336
|
+
pool = None
|
|
337
|
+
try:
|
|
338
|
+
if use_threads:
|
|
339
|
+
pool = ThreadPool(n_workers)
|
|
340
|
+
else:
|
|
341
|
+
pool = Pool(n_workers)
|
|
342
|
+
|
|
343
|
+
image_results = list(tqdm(pool.imap(
|
|
344
|
+
partial(_process_labelme_file,
|
|
345
|
+
input_folder=input_folder,
|
|
346
|
+
use_folders_as_labels=use_folders_as_labels,
|
|
347
|
+
no_json_handling=no_json_handling,
|
|
348
|
+
validate_image_sizes=validate_image_sizes,
|
|
349
|
+
category_name_to_id=category_name_to_id,
|
|
350
|
+
allow_new_categories=False
|
|
351
|
+
),image_filenames_relative), total=len(image_filenames_relative)))
|
|
352
|
+
finally:
|
|
353
|
+
pool.close()
|
|
354
|
+
pool.join()
|
|
355
|
+
print("Pool closed and joined for labelme file processing")
|
|
356
|
+
|
|
349
357
|
images = []
|
|
350
358
|
annotations = []
|
|
351
|
-
|
|
359
|
+
|
|
352
360
|
# Flatten the lists of images and annotations
|
|
353
361
|
for result in image_results:
|
|
354
362
|
im = result['im']
|
|
355
363
|
annotations_this_image = result['annotations_this_image']
|
|
356
|
-
|
|
364
|
+
|
|
357
365
|
if im is None:
|
|
358
366
|
assert annotations_this_image is None
|
|
359
367
|
else:
|
|
360
368
|
images.append(im)
|
|
361
369
|
annotations.extend(annotations_this_image)
|
|
362
|
-
|
|
370
|
+
|
|
363
371
|
output_dict = {}
|
|
364
372
|
output_dict['images'] = images
|
|
365
373
|
output_dict['annotations'] = annotations
|
|
366
|
-
|
|
374
|
+
|
|
367
375
|
if info_struct is None:
|
|
368
376
|
info_struct = {}
|
|
369
377
|
if 'description' not in info_struct:
|
|
@@ -371,17 +379,17 @@ def labelme_to_coco(input_folder,
|
|
|
371
379
|
'Converted to COCO from labelme annotations in folder {}'.format(input_folder)
|
|
372
380
|
if 'version' not in info_struct:
|
|
373
381
|
info_struct['version'] = 1.0
|
|
374
|
-
|
|
382
|
+
|
|
375
383
|
output_dict['info'] = info_struct
|
|
376
384
|
categories = []
|
|
377
385
|
for category_name in category_name_to_id:
|
|
378
386
|
categories.append({'name':category_name,'id':category_name_to_id[category_name]})
|
|
379
387
|
output_dict['categories'] = categories
|
|
380
|
-
|
|
388
|
+
|
|
381
389
|
if output_file is not None:
|
|
382
390
|
with open(output_file,'w') as f:
|
|
383
391
|
json.dump(output_dict,f,indent=1)
|
|
384
|
-
|
|
392
|
+
|
|
385
393
|
return output_dict
|
|
386
394
|
|
|
387
395
|
# ...def labelme_to_coco()
|
|
@@ -389,59 +397,59 @@ def labelme_to_coco(input_folder,
|
|
|
389
397
|
|
|
390
398
|
def find_empty_labelme_files(input_folder,recursive=True):
|
|
391
399
|
"""
|
|
392
|
-
Returns a list of all image files in in [input_folder] associated with .json files that have
|
|
400
|
+
Returns a list of all image files in in [input_folder] associated with .json files that have
|
|
393
401
|
no boxes in them. Also returns a list of images with no associated .json files. Specifically,
|
|
394
402
|
returns a dict:
|
|
395
|
-
|
|
403
|
+
|
|
396
404
|
.. code-block: none
|
|
397
|
-
|
|
405
|
+
|
|
398
406
|
{
|
|
399
407
|
'images_with_empty_json_files':[list],
|
|
400
408
|
'images_with_no_json_files':[list],
|
|
401
409
|
'images_with_non_empty_json_files':[list]
|
|
402
410
|
}
|
|
403
|
-
|
|
411
|
+
|
|
404
412
|
Args:
|
|
405
413
|
input_folder (str): the folder to search for empty (i.e., box-less) Labelme .json files
|
|
406
414
|
recursive (bool, optional): whether to recurse into [input_folder]
|
|
407
|
-
|
|
415
|
+
|
|
408
416
|
Returns:
|
|
409
417
|
dict: a dict with fields:
|
|
410
|
-
- images_with_empty_json_files: a list of all image files in [input_folder] associated with
|
|
418
|
+
- images_with_empty_json_files: a list of all image files in [input_folder] associated with
|
|
411
419
|
.json files that have no boxes in them
|
|
412
420
|
- images_with_no_json_files: a list of images in [input_folder] with no associated .json files
|
|
413
421
|
- images_with_non_empty_json_files: a list of images in [input_folder] associated with .json
|
|
414
|
-
files that have at least one box
|
|
422
|
+
files that have at least one box
|
|
415
423
|
"""
|
|
416
424
|
image_filenames_relative = path_utils.find_images(input_folder,recursive=True,
|
|
417
425
|
return_relative_paths=True)
|
|
418
|
-
|
|
426
|
+
|
|
419
427
|
images_with_empty_json_files = []
|
|
420
428
|
images_with_no_json_files = []
|
|
421
429
|
images_with_non_empty_json_files = []
|
|
422
|
-
|
|
430
|
+
|
|
423
431
|
# fn_relative = image_filenames_relative[0]
|
|
424
432
|
for fn_relative in image_filenames_relative:
|
|
425
|
-
|
|
433
|
+
|
|
426
434
|
image_fn_abs = os.path.join(input_folder,fn_relative)
|
|
427
435
|
json_fn_abs = os.path.splitext(image_fn_abs)[0] + '.json'
|
|
428
|
-
|
|
436
|
+
|
|
429
437
|
if not os.path.isfile(json_fn_abs):
|
|
430
438
|
images_with_no_json_files.append(fn_relative)
|
|
431
439
|
continue
|
|
432
|
-
|
|
440
|
+
|
|
433
441
|
else:
|
|
434
442
|
# Read the .json file
|
|
435
443
|
with open(json_fn_abs,'r') as f:
|
|
436
|
-
labelme_data = json.load(f)
|
|
444
|
+
labelme_data = json.load(f)
|
|
437
445
|
shapes = labelme_data['shapes']
|
|
438
446
|
if len(shapes) == 0:
|
|
439
447
|
images_with_empty_json_files.append(fn_relative)
|
|
440
448
|
else:
|
|
441
449
|
images_with_non_empty_json_files.append(fn_relative)
|
|
442
|
-
|
|
450
|
+
|
|
443
451
|
# ...for every image
|
|
444
|
-
|
|
452
|
+
|
|
445
453
|
return {'images_with_empty_json_files':images_with_empty_json_files,
|
|
446
454
|
'images_with_no_json_files':images_with_no_json_files,
|
|
447
455
|
'images_with_non_empty_json_files':images_with_non_empty_json_files}
|
|
@@ -452,22 +460,22 @@ def find_empty_labelme_files(input_folder,recursive=True):
|
|
|
452
460
|
#%% Interactive driver
|
|
453
461
|
|
|
454
462
|
if False:
|
|
455
|
-
|
|
463
|
+
|
|
456
464
|
pass
|
|
457
465
|
|
|
458
466
|
#%% Options
|
|
459
|
-
|
|
467
|
+
|
|
460
468
|
empty_category_name = 'empty'
|
|
461
469
|
empty_category_id = None
|
|
462
470
|
category_id_to_category_name = None
|
|
463
471
|
info_struct = None
|
|
464
|
-
|
|
472
|
+
|
|
465
473
|
input_folder = os.path.expanduser('~/data/md-test')
|
|
466
474
|
output_file = os.path.expanduser('~/data/md-test-labelme-to-coco.json')
|
|
467
|
-
|
|
468
|
-
|
|
475
|
+
|
|
476
|
+
|
|
469
477
|
#%% Programmatic execution
|
|
470
|
-
|
|
478
|
+
|
|
471
479
|
output_dict = labelme_to_coco(input_folder,output_file,
|
|
472
480
|
category_id_to_category_name=category_id_to_category_name,
|
|
473
481
|
empty_category_name=empty_category_name,
|
|
@@ -476,25 +484,25 @@ if False:
|
|
|
476
484
|
use_folders_as_labels=False,
|
|
477
485
|
validate_image_sizes=False,
|
|
478
486
|
no_json_handling='empty')
|
|
479
|
-
|
|
480
|
-
|
|
487
|
+
|
|
488
|
+
|
|
481
489
|
#%% Validate
|
|
482
|
-
|
|
490
|
+
|
|
483
491
|
from megadetector.data_management.databases import integrity_check_json_db
|
|
484
|
-
|
|
492
|
+
|
|
485
493
|
options = integrity_check_json_db.IntegrityCheckOptions()
|
|
486
|
-
|
|
494
|
+
|
|
487
495
|
options.baseDir = input_folder
|
|
488
496
|
options.bCheckImageSizes = True
|
|
489
497
|
options.bCheckImageExistence = True
|
|
490
498
|
options.bFindUnusedImages = True
|
|
491
499
|
options.bRequireLocation = False
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
500
|
+
|
|
501
|
+
sortec_categories, _, error_info = integrity_check_json_db.integrity_check_json_db(output_file,options)
|
|
502
|
+
|
|
495
503
|
|
|
496
504
|
#%% Preview
|
|
497
|
-
|
|
505
|
+
|
|
498
506
|
from megadetector.visualization import visualize_db
|
|
499
507
|
options = visualize_db.DbVizOptions()
|
|
500
508
|
options.parallelize_rendering = True
|
|
@@ -503,38 +511,36 @@ if False:
|
|
|
503
511
|
|
|
504
512
|
html_file,_ = visualize_db.visualize_db(output_file,os.path.expanduser('~/tmp/labelme_to_coco_preview'),
|
|
505
513
|
input_folder,options)
|
|
506
|
-
|
|
514
|
+
|
|
507
515
|
|
|
508
516
|
from megadetector.utils import path_utils # noqa
|
|
509
517
|
path_utils.open_file(html_file)
|
|
510
|
-
|
|
511
|
-
|
|
518
|
+
|
|
519
|
+
|
|
512
520
|
#%% Prepare command line
|
|
513
521
|
|
|
514
522
|
s = 'python labelme_to_coco.py {} {}'.format(input_folder,output_file)
|
|
515
523
|
print(s)
|
|
516
524
|
import clipboard; clipboard.copy(s)
|
|
517
525
|
|
|
518
|
-
|
|
519
|
-
#%% Command-line driver
|
|
520
526
|
|
|
521
|
-
|
|
527
|
+
#%% Command-line driver
|
|
522
528
|
|
|
523
|
-
def main():
|
|
529
|
+
def main(): # noqa
|
|
524
530
|
|
|
525
531
|
parser = argparse.ArgumentParser(
|
|
526
532
|
description='Convert labelme-formatted data to COCO')
|
|
527
|
-
|
|
533
|
+
|
|
528
534
|
parser.add_argument(
|
|
529
535
|
'input_folder',
|
|
530
536
|
type=str,
|
|
531
537
|
help='Path to images and .json annotation files')
|
|
532
|
-
|
|
538
|
+
|
|
533
539
|
parser.add_argument(
|
|
534
540
|
'output_file',
|
|
535
541
|
type=str,
|
|
536
542
|
help='Output filename (.json)')
|
|
537
|
-
|
|
543
|
+
|
|
538
544
|
if len(sys.argv[1:]) == 0:
|
|
539
545
|
parser.print_help()
|
|
540
546
|
parser.exit()
|
|
@@ -542,6 +548,6 @@ def main():
|
|
|
542
548
|
args = parser.parse_args()
|
|
543
549
|
|
|
544
550
|
labelme_to_coco(args.input_folder,args.output_file)
|
|
545
|
-
|
|
551
|
+
|
|
546
552
|
if __name__ == '__main__':
|
|
547
553
|
main()
|