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.
- 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/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/efficientnet/model.py +8 -8
- megadetector/classification/efficientnet/utils.py +6 -5
- 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 +26 -26
- 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 -2
- megadetector/data_management/camtrap_dp_to_coco.py +79 -46
- megadetector/data_management/cct_json_utils.py +103 -103
- 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 +210 -193
- megadetector/data_management/databases/add_width_and_height_to_db.py +86 -12
- megadetector/data_management/databases/combine_coco_camera_traps_files.py +40 -40
- megadetector/data_management/databases/integrity_check_json_db.py +228 -200
- megadetector/data_management/databases/subset_json_db.py +33 -33
- megadetector/data_management/generate_crops_from_cct.py +88 -39
- megadetector/data_management/get_image_sizes.py +54 -49
- megadetector/data_management/labelme_to_coco.py +133 -125
- megadetector/data_management/labelme_to_yolo.py +159 -73
- 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 +365 -107
- megadetector/data_management/lila/get_lila_annotation_counts.py +35 -33
- megadetector/data_management/lila/get_lila_image_counts.py +22 -22
- megadetector/data_management/lila/lila_common.py +73 -70
- megadetector/data_management/lila/test_lila_metadata_urls.py +28 -19
- megadetector/data_management/mewc_to_md.py +344 -340
- megadetector/data_management/ocr_tools.py +262 -255
- megadetector/data_management/read_exif.py +249 -227
- megadetector/data_management/remap_coco_categories.py +90 -28
- megadetector/data_management/remove_exif.py +81 -21
- megadetector/data_management/rename_images.py +187 -187
- megadetector/data_management/resize_coco_dataset.py +588 -120
- 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 +248 -122
- megadetector/data_management/yolo_to_coco.py +333 -191
- megadetector/detection/change_detection.py +832 -0
- megadetector/detection/process_video.py +340 -337
- megadetector/detection/pytorch_detector.py +358 -278
- megadetector/detection/run_detector.py +399 -186
- megadetector/detection/run_detector_batch.py +404 -377
- megadetector/detection/run_inference_with_yolov5_val.py +340 -327
- megadetector/detection/run_tiled_inference.py +257 -249
- megadetector/detection/tf_detector.py +24 -24
- megadetector/detection/video_utils.py +332 -295
- megadetector/postprocessing/add_max_conf.py +19 -11
- megadetector/postprocessing/categorize_detections_by_size.py +45 -45
- megadetector/postprocessing/classification_postprocessing.py +468 -433
- megadetector/postprocessing/combine_batch_outputs.py +23 -23
- megadetector/postprocessing/compare_batch_results.py +590 -525
- megadetector/postprocessing/convert_output_format.py +106 -102
- megadetector/postprocessing/create_crop_folder.py +347 -147
- megadetector/postprocessing/detector_calibration.py +173 -168
- megadetector/postprocessing/generate_csv_report.py +508 -499
- megadetector/postprocessing/load_api_results.py +48 -27
- megadetector/postprocessing/md_to_coco.py +133 -102
- megadetector/postprocessing/md_to_labelme.py +107 -90
- megadetector/postprocessing/md_to_wi.py +40 -40
- megadetector/postprocessing/merge_detections.py +92 -114
- megadetector/postprocessing/postprocess_batch_results.py +319 -301
- megadetector/postprocessing/remap_detection_categories.py +91 -38
- megadetector/postprocessing/render_detection_confusion_matrix.py +214 -205
- 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 +704 -679
- 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 +18 -19
- megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +54 -33
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +67 -67
- megadetector/taxonomy_mapping/retrieve_sample_image.py +16 -16
- megadetector/taxonomy_mapping/simple_image_download.py +8 -8
- megadetector/taxonomy_mapping/species_lookup.py +156 -74
- megadetector/taxonomy_mapping/taxonomy_csv_checker.py +14 -14
- megadetector/taxonomy_mapping/taxonomy_graph.py +10 -10
- megadetector/taxonomy_mapping/validate_lila_category_mappings.py +13 -13
- megadetector/utils/ct_utils.py +1049 -211
- megadetector/utils/directory_listing.py +21 -77
- megadetector/utils/gpu_test.py +22 -22
- megadetector/utils/md_tests.py +632 -529
- megadetector/utils/path_utils.py +1520 -431
- megadetector/utils/process_utils.py +41 -41
- megadetector/utils/split_locations_into_train_val.py +62 -62
- megadetector/utils/string_utils.py +148 -27
- megadetector/utils/url_utils.py +489 -176
- megadetector/utils/wi_utils.py +2658 -2526
- megadetector/utils/write_html_image_list.py +137 -137
- megadetector/visualization/plot_utils.py +34 -30
- megadetector/visualization/render_images_with_thumbnails.py +39 -74
- megadetector/visualization/visualization_utils.py +487 -435
- megadetector/visualization/visualize_db.py +232 -198
- megadetector/visualization/visualize_detector_output.py +82 -76
- {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/METADATA +5 -2
- megadetector-10.0.0.dist-info/RECORD +139 -0
- {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/WHEEL +1 -1
- megadetector/api/batch_processing/api_core/__init__.py +0 -0
- megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
- megadetector/api/batch_processing/api_core/batch_service/score.py +0 -439
- megadetector/api/batch_processing/api_core/server.py +0 -294
- megadetector/api/batch_processing/api_core/server_api_config.py +0 -97
- megadetector/api/batch_processing/api_core/server_app_config.py +0 -55
- megadetector/api/batch_processing/api_core/server_batch_job_manager.py +0 -220
- megadetector/api/batch_processing/api_core/server_job_status_table.py +0 -149
- megadetector/api/batch_processing/api_core/server_orchestration.py +0 -360
- megadetector/api/batch_processing/api_core/server_utils.py +0 -88
- megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
- megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +0 -46
- megadetector/api/batch_processing/api_support/__init__.py +0 -0
- megadetector/api/batch_processing/api_support/summarize_daily_activity.py +0 -152
- megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
- megadetector/api/synchronous/__init__.py +0 -0
- megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
- megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +0 -151
- megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +0 -263
- megadetector/api/synchronous/api_core/animal_detection_api/config.py +0 -35
- megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
- megadetector/api/synchronous/api_core/tests/load_test.py +0 -110
- 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/utils/azure_utils.py +0 -178
- megadetector/utils/sas_blob_utils.py +0 -509
- megadetector-5.0.28.dist-info/RECORD +0 -209
- /megadetector/{api/batch_processing/__init__.py → __init__.py} +0 -0
- {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/licenses/LICENSE +0 -0
- {megadetector-5.0.28.dist-info → megadetector-10.0.0.dist-info}/top_level.txt +0 -0
|
@@ -8,9 +8,11 @@ Converts a folder of labelme-formatted .json files to COCO.
|
|
|
8
8
|
|
|
9
9
|
#%% Constants and imports
|
|
10
10
|
|
|
11
|
-
import json
|
|
12
11
|
import os
|
|
12
|
+
import sys
|
|
13
|
+
import json
|
|
13
14
|
import uuid
|
|
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
|
|
@@ -230,6 +232,8 @@ def labelme_to_coco(input_folder,
|
|
|
230
232
|
category_id_to_category_name (dict, optional): dict mapping category IDs to category names;
|
|
231
233
|
really used to map Labelme category names to COCO category IDs. IDs will be auto-generated
|
|
232
234
|
if this is None.
|
|
235
|
+
empty_category_name (str, optional): if images are present without boxes, the category name
|
|
236
|
+
we should use for whole-image (and not-very-COCO-like) empty categories.
|
|
233
237
|
empty_category_id (int, optional): category ID to use for the not-very-COCO-like "empty" category;
|
|
234
238
|
also see the no_json_handling parameter.
|
|
235
239
|
info_struct (dict, optional): dict to stash in the "info" field of the resulting COCO dict
|
|
@@ -240,9 +244,9 @@ def labelme_to_coco(input_folder,
|
|
|
240
244
|
use_folders_as_labels (bool, optional): if this is True, class names will be pulled from folder names,
|
|
241
245
|
useful if you have images like a/b/cat/image001.jpg, a/b/dog/image002.jpg, etc.
|
|
242
246
|
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,
|
|
247
|
+
no_json_handling (str, optional): how to deal with image files that have no corresponding .json files,
|
|
244
248
|
can be:
|
|
245
|
-
|
|
249
|
+
|
|
246
250
|
- 'skip': ignore image files with no corresponding .json files
|
|
247
251
|
- 'empty': treat image files with no corresponding .json files as empty
|
|
248
252
|
- 'error': throw an error when an image file has no corresponding .json file
|
|
@@ -252,15 +256,15 @@ def labelme_to_coco(input_folder,
|
|
|
252
256
|
parallelization
|
|
253
257
|
use_threads (bool, optional): whether to use threads (True) or processes (False) for parallelization,
|
|
254
258
|
not relevant if max_workers <= 1
|
|
255
|
-
|
|
259
|
+
|
|
256
260
|
Returns:
|
|
257
261
|
dict: a COCO-formatted dictionary, identical to what's written to [output_file] if [output_file] is not None.
|
|
258
262
|
"""
|
|
259
|
-
|
|
263
|
+
|
|
260
264
|
if max_workers > 1:
|
|
261
265
|
assert category_id_to_category_name is not None, \
|
|
262
266
|
'When parallelizing labelme --> COCO conversion, you must supply a category mapping'
|
|
263
|
-
|
|
267
|
+
|
|
264
268
|
if category_id_to_category_name is None:
|
|
265
269
|
category_name_to_id = {}
|
|
266
270
|
else:
|
|
@@ -270,7 +274,7 @@ def labelme_to_coco(input_folder,
|
|
|
270
274
|
category_name_to_id[category_name] = int(category_name_to_id[category_name])
|
|
271
275
|
except ValueError:
|
|
272
276
|
raise ValueError('Category IDs must be ints or string-formatted ints')
|
|
273
|
-
|
|
277
|
+
|
|
274
278
|
# If the user supplied an explicit empty category ID, and the empty category
|
|
275
279
|
# name is already in category_name_to_id, make sure they match.
|
|
276
280
|
if empty_category_id is not None:
|
|
@@ -285,13 +289,13 @@ def labelme_to_coco(input_folder,
|
|
|
285
289
|
empty_category_id = category_name_to_id[empty_category_name]
|
|
286
290
|
|
|
287
291
|
del category_id_to_category_name
|
|
288
|
-
|
|
292
|
+
|
|
289
293
|
# Enumerate images
|
|
290
|
-
print('Enumerating images in {}'.format(input_folder))
|
|
294
|
+
print('Enumerating images in {}'.format(input_folder))
|
|
291
295
|
image_filenames_relative = path_utils.find_images(input_folder,recursive=recursive,
|
|
292
296
|
return_relative_paths=True,
|
|
293
|
-
convert_slashes=True)
|
|
294
|
-
|
|
297
|
+
convert_slashes=True)
|
|
298
|
+
|
|
295
299
|
# Remove any images we're supposed to skip
|
|
296
300
|
if (relative_paths_to_include is not None) or (relative_paths_to_exclude is not None):
|
|
297
301
|
image_filenames_relative_to_process = []
|
|
@@ -305,65 +309,71 @@ def labelme_to_coco(input_folder,
|
|
|
305
309
|
len(image_filenames_relative_to_process),
|
|
306
310
|
len(image_filenames_relative)))
|
|
307
311
|
image_filenames_relative = image_filenames_relative_to_process
|
|
308
|
-
|
|
312
|
+
|
|
309
313
|
# If the user supplied a category ID to use for empty images...
|
|
310
314
|
if empty_category_id is not None:
|
|
311
315
|
try:
|
|
312
316
|
empty_category_id = int(empty_category_id)
|
|
313
317
|
except ValueError:
|
|
314
318
|
raise ValueError('Category IDs must be ints or string-formatted ints')
|
|
315
|
-
|
|
319
|
+
|
|
316
320
|
if empty_category_id is None:
|
|
317
321
|
empty_category_id = _add_category(empty_category_name,category_name_to_id)
|
|
318
|
-
|
|
322
|
+
|
|
319
323
|
if max_workers <= 1:
|
|
320
|
-
|
|
324
|
+
|
|
321
325
|
image_results = []
|
|
322
326
|
for image_fn_relative in tqdm(image_filenames_relative):
|
|
323
|
-
|
|
327
|
+
|
|
324
328
|
result = _process_labelme_file(image_fn_relative,input_folder,use_folders_as_labels,
|
|
325
329
|
no_json_handling,validate_image_sizes,
|
|
326
|
-
category_name_to_id,allow_new_categories=True)
|
|
330
|
+
category_name_to_id,allow_new_categories=True)
|
|
327
331
|
image_results.append(result)
|
|
328
|
-
|
|
329
|
-
else:
|
|
330
|
-
|
|
332
|
+
|
|
333
|
+
else:
|
|
334
|
+
|
|
331
335
|
n_workers = min(max_workers,len(image_filenames_relative))
|
|
332
336
|
assert category_name_to_id is not None
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
337
|
+
|
|
338
|
+
pool = None
|
|
339
|
+
try:
|
|
340
|
+
if use_threads:
|
|
341
|
+
pool = ThreadPool(n_workers)
|
|
342
|
+
else:
|
|
343
|
+
pool = Pool(n_workers)
|
|
344
|
+
|
|
345
|
+
image_results = list(tqdm(pool.imap(
|
|
346
|
+
partial(_process_labelme_file,
|
|
347
|
+
input_folder=input_folder,
|
|
348
|
+
use_folders_as_labels=use_folders_as_labels,
|
|
349
|
+
no_json_handling=no_json_handling,
|
|
350
|
+
validate_image_sizes=validate_image_sizes,
|
|
351
|
+
category_name_to_id=category_name_to_id,
|
|
352
|
+
allow_new_categories=False
|
|
353
|
+
),image_filenames_relative), total=len(image_filenames_relative)))
|
|
354
|
+
finally:
|
|
355
|
+
pool.close()
|
|
356
|
+
pool.join()
|
|
357
|
+
print("Pool closed and joined for labelme file processing")
|
|
358
|
+
|
|
349
359
|
images = []
|
|
350
360
|
annotations = []
|
|
351
|
-
|
|
361
|
+
|
|
352
362
|
# Flatten the lists of images and annotations
|
|
353
363
|
for result in image_results:
|
|
354
364
|
im = result['im']
|
|
355
365
|
annotations_this_image = result['annotations_this_image']
|
|
356
|
-
|
|
366
|
+
|
|
357
367
|
if im is None:
|
|
358
368
|
assert annotations_this_image is None
|
|
359
369
|
else:
|
|
360
370
|
images.append(im)
|
|
361
371
|
annotations.extend(annotations_this_image)
|
|
362
|
-
|
|
372
|
+
|
|
363
373
|
output_dict = {}
|
|
364
374
|
output_dict['images'] = images
|
|
365
375
|
output_dict['annotations'] = annotations
|
|
366
|
-
|
|
376
|
+
|
|
367
377
|
if info_struct is None:
|
|
368
378
|
info_struct = {}
|
|
369
379
|
if 'description' not in info_struct:
|
|
@@ -371,17 +381,17 @@ def labelme_to_coco(input_folder,
|
|
|
371
381
|
'Converted to COCO from labelme annotations in folder {}'.format(input_folder)
|
|
372
382
|
if 'version' not in info_struct:
|
|
373
383
|
info_struct['version'] = 1.0
|
|
374
|
-
|
|
384
|
+
|
|
375
385
|
output_dict['info'] = info_struct
|
|
376
386
|
categories = []
|
|
377
387
|
for category_name in category_name_to_id:
|
|
378
388
|
categories.append({'name':category_name,'id':category_name_to_id[category_name]})
|
|
379
389
|
output_dict['categories'] = categories
|
|
380
|
-
|
|
390
|
+
|
|
381
391
|
if output_file is not None:
|
|
382
392
|
with open(output_file,'w') as f:
|
|
383
393
|
json.dump(output_dict,f,indent=1)
|
|
384
|
-
|
|
394
|
+
|
|
385
395
|
return output_dict
|
|
386
396
|
|
|
387
397
|
# ...def labelme_to_coco()
|
|
@@ -389,59 +399,59 @@ def labelme_to_coco(input_folder,
|
|
|
389
399
|
|
|
390
400
|
def find_empty_labelme_files(input_folder,recursive=True):
|
|
391
401
|
"""
|
|
392
|
-
Returns a list of all image files in in [input_folder] associated with .json files that have
|
|
402
|
+
Returns a list of all image files in in [input_folder] associated with .json files that have
|
|
393
403
|
no boxes in them. Also returns a list of images with no associated .json files. Specifically,
|
|
394
404
|
returns a dict:
|
|
395
|
-
|
|
405
|
+
|
|
396
406
|
.. code-block: none
|
|
397
|
-
|
|
407
|
+
|
|
398
408
|
{
|
|
399
409
|
'images_with_empty_json_files':[list],
|
|
400
410
|
'images_with_no_json_files':[list],
|
|
401
411
|
'images_with_non_empty_json_files':[list]
|
|
402
412
|
}
|
|
403
|
-
|
|
413
|
+
|
|
404
414
|
Args:
|
|
405
415
|
input_folder (str): the folder to search for empty (i.e., box-less) Labelme .json files
|
|
406
416
|
recursive (bool, optional): whether to recurse into [input_folder]
|
|
407
|
-
|
|
417
|
+
|
|
408
418
|
Returns:
|
|
409
419
|
dict: a dict with fields:
|
|
410
|
-
- images_with_empty_json_files: a list of all image files in [input_folder] associated with
|
|
420
|
+
- images_with_empty_json_files: a list of all image files in [input_folder] associated with
|
|
411
421
|
.json files that have no boxes in them
|
|
412
422
|
- images_with_no_json_files: a list of images in [input_folder] with no associated .json files
|
|
413
423
|
- images_with_non_empty_json_files: a list of images in [input_folder] associated with .json
|
|
414
|
-
files that have at least one box
|
|
424
|
+
files that have at least one box
|
|
415
425
|
"""
|
|
416
426
|
image_filenames_relative = path_utils.find_images(input_folder,recursive=True,
|
|
417
427
|
return_relative_paths=True)
|
|
418
|
-
|
|
428
|
+
|
|
419
429
|
images_with_empty_json_files = []
|
|
420
430
|
images_with_no_json_files = []
|
|
421
431
|
images_with_non_empty_json_files = []
|
|
422
|
-
|
|
432
|
+
|
|
423
433
|
# fn_relative = image_filenames_relative[0]
|
|
424
434
|
for fn_relative in image_filenames_relative:
|
|
425
|
-
|
|
435
|
+
|
|
426
436
|
image_fn_abs = os.path.join(input_folder,fn_relative)
|
|
427
437
|
json_fn_abs = os.path.splitext(image_fn_abs)[0] + '.json'
|
|
428
|
-
|
|
438
|
+
|
|
429
439
|
if not os.path.isfile(json_fn_abs):
|
|
430
440
|
images_with_no_json_files.append(fn_relative)
|
|
431
441
|
continue
|
|
432
|
-
|
|
442
|
+
|
|
433
443
|
else:
|
|
434
444
|
# Read the .json file
|
|
435
445
|
with open(json_fn_abs,'r') as f:
|
|
436
|
-
labelme_data = json.load(f)
|
|
446
|
+
labelme_data = json.load(f)
|
|
437
447
|
shapes = labelme_data['shapes']
|
|
438
448
|
if len(shapes) == 0:
|
|
439
449
|
images_with_empty_json_files.append(fn_relative)
|
|
440
450
|
else:
|
|
441
451
|
images_with_non_empty_json_files.append(fn_relative)
|
|
442
|
-
|
|
452
|
+
|
|
443
453
|
# ...for every image
|
|
444
|
-
|
|
454
|
+
|
|
445
455
|
return {'images_with_empty_json_files':images_with_empty_json_files,
|
|
446
456
|
'images_with_no_json_files':images_with_no_json_files,
|
|
447
457
|
'images_with_non_empty_json_files':images_with_non_empty_json_files}
|
|
@@ -452,22 +462,22 @@ def find_empty_labelme_files(input_folder,recursive=True):
|
|
|
452
462
|
#%% Interactive driver
|
|
453
463
|
|
|
454
464
|
if False:
|
|
455
|
-
|
|
465
|
+
|
|
456
466
|
pass
|
|
457
467
|
|
|
458
468
|
#%% Options
|
|
459
|
-
|
|
469
|
+
|
|
460
470
|
empty_category_name = 'empty'
|
|
461
471
|
empty_category_id = None
|
|
462
472
|
category_id_to_category_name = None
|
|
463
473
|
info_struct = None
|
|
464
|
-
|
|
474
|
+
|
|
465
475
|
input_folder = os.path.expanduser('~/data/md-test')
|
|
466
476
|
output_file = os.path.expanduser('~/data/md-test-labelme-to-coco.json')
|
|
467
|
-
|
|
468
|
-
|
|
477
|
+
|
|
478
|
+
|
|
469
479
|
#%% Programmatic execution
|
|
470
|
-
|
|
480
|
+
|
|
471
481
|
output_dict = labelme_to_coco(input_folder,output_file,
|
|
472
482
|
category_id_to_category_name=category_id_to_category_name,
|
|
473
483
|
empty_category_name=empty_category_name,
|
|
@@ -476,25 +486,25 @@ if False:
|
|
|
476
486
|
use_folders_as_labels=False,
|
|
477
487
|
validate_image_sizes=False,
|
|
478
488
|
no_json_handling='empty')
|
|
479
|
-
|
|
480
|
-
|
|
489
|
+
|
|
490
|
+
|
|
481
491
|
#%% Validate
|
|
482
|
-
|
|
492
|
+
|
|
483
493
|
from megadetector.data_management.databases import integrity_check_json_db
|
|
484
|
-
|
|
494
|
+
|
|
485
495
|
options = integrity_check_json_db.IntegrityCheckOptions()
|
|
486
|
-
|
|
496
|
+
|
|
487
497
|
options.baseDir = input_folder
|
|
488
498
|
options.bCheckImageSizes = True
|
|
489
499
|
options.bCheckImageExistence = True
|
|
490
500
|
options.bFindUnusedImages = True
|
|
491
501
|
options.bRequireLocation = False
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
502
|
+
|
|
503
|
+
sortec_categories, _, error_info = integrity_check_json_db.integrity_check_json_db(output_file,options)
|
|
504
|
+
|
|
495
505
|
|
|
496
506
|
#%% Preview
|
|
497
|
-
|
|
507
|
+
|
|
498
508
|
from megadetector.visualization import visualize_db
|
|
499
509
|
options = visualize_db.DbVizOptions()
|
|
500
510
|
options.parallelize_rendering = True
|
|
@@ -503,38 +513,36 @@ if False:
|
|
|
503
513
|
|
|
504
514
|
html_file,_ = visualize_db.visualize_db(output_file,os.path.expanduser('~/tmp/labelme_to_coco_preview'),
|
|
505
515
|
input_folder,options)
|
|
506
|
-
|
|
516
|
+
|
|
507
517
|
|
|
508
518
|
from megadetector.utils import path_utils # noqa
|
|
509
519
|
path_utils.open_file(html_file)
|
|
510
|
-
|
|
511
|
-
|
|
520
|
+
|
|
521
|
+
|
|
512
522
|
#%% Prepare command line
|
|
513
523
|
|
|
514
524
|
s = 'python labelme_to_coco.py {} {}'.format(input_folder,output_file)
|
|
515
525
|
print(s)
|
|
516
526
|
import clipboard; clipboard.copy(s)
|
|
517
527
|
|
|
518
|
-
|
|
519
|
-
#%% Command-line driver
|
|
520
528
|
|
|
521
|
-
|
|
529
|
+
#%% Command-line driver
|
|
522
530
|
|
|
523
|
-
def main():
|
|
531
|
+
def main(): # noqa
|
|
524
532
|
|
|
525
533
|
parser = argparse.ArgumentParser(
|
|
526
534
|
description='Convert labelme-formatted data to COCO')
|
|
527
|
-
|
|
535
|
+
|
|
528
536
|
parser.add_argument(
|
|
529
537
|
'input_folder',
|
|
530
538
|
type=str,
|
|
531
539
|
help='Path to images and .json annotation files')
|
|
532
|
-
|
|
540
|
+
|
|
533
541
|
parser.add_argument(
|
|
534
542
|
'output_file',
|
|
535
543
|
type=str,
|
|
536
544
|
help='Output filename (.json)')
|
|
537
|
-
|
|
545
|
+
|
|
538
546
|
if len(sys.argv[1:]) == 0:
|
|
539
547
|
parser.print_help()
|
|
540
548
|
parser.exit()
|
|
@@ -542,6 +550,6 @@ def main():
|
|
|
542
550
|
args = parser.parse_args()
|
|
543
551
|
|
|
544
552
|
labelme_to_coco(args.input_folder,args.output_file)
|
|
545
|
-
|
|
553
|
+
|
|
546
554
|
if __name__ == '__main__':
|
|
547
555
|
main()
|