megadetector 5.0.11__py3-none-any.whl → 5.0.13__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/__init__.py +0 -0
- megadetector/api/batch_processing/__init__.py +0 -0
- 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 +439 -0
- megadetector/api/batch_processing/api_core/server.py +294 -0
- megadetector/api/batch_processing/api_core/server_api_config.py +97 -0
- megadetector/api/batch_processing/api_core/server_app_config.py +55 -0
- megadetector/api/batch_processing/api_core/server_batch_job_manager.py +220 -0
- megadetector/api/batch_processing/api_core/server_job_status_table.py +149 -0
- megadetector/api/batch_processing/api_core/server_orchestration.py +360 -0
- megadetector/api/batch_processing/api_core/server_utils.py +88 -0
- megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
- megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +46 -0
- megadetector/api/batch_processing/api_support/__init__.py +0 -0
- megadetector/api/batch_processing/api_support/summarize_daily_activity.py +152 -0
- megadetector/api/batch_processing/data_preparation/__init__.py +0 -0
- megadetector/api/batch_processing/integration/digiKam/setup.py +6 -0
- megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +465 -0
- megadetector/api/batch_processing/integration/eMammal/test_scripts/config_template.py +5 -0
- megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +125 -0
- megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +55 -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 +152 -0
- megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +263 -0
- megadetector/api/synchronous/api_core/animal_detection_api/config.py +35 -0
- megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
- megadetector/api/synchronous/api_core/tests/load_test.py +110 -0
- megadetector/classification/__init__.py +0 -0
- megadetector/classification/aggregate_classifier_probs.py +108 -0
- megadetector/classification/analyze_failed_images.py +227 -0
- megadetector/classification/cache_batchapi_outputs.py +198 -0
- megadetector/classification/create_classification_dataset.py +627 -0
- megadetector/classification/crop_detections.py +516 -0
- megadetector/classification/csv_to_json.py +226 -0
- megadetector/classification/detect_and_crop.py +855 -0
- megadetector/classification/efficientnet/__init__.py +9 -0
- megadetector/classification/efficientnet/model.py +415 -0
- megadetector/classification/efficientnet/utils.py +607 -0
- megadetector/classification/evaluate_model.py +520 -0
- megadetector/classification/identify_mislabeled_candidates.py +152 -0
- megadetector/classification/json_to_azcopy_list.py +63 -0
- megadetector/classification/json_validator.py +699 -0
- megadetector/classification/map_classification_categories.py +276 -0
- megadetector/classification/merge_classification_detection_output.py +506 -0
- megadetector/classification/prepare_classification_script.py +194 -0
- megadetector/classification/prepare_classification_script_mc.py +228 -0
- megadetector/classification/run_classifier.py +287 -0
- megadetector/classification/save_mislabeled.py +110 -0
- megadetector/classification/train_classifier.py +827 -0
- megadetector/classification/train_classifier_tf.py +725 -0
- megadetector/classification/train_utils.py +323 -0
- megadetector/data_management/__init__.py +0 -0
- megadetector/data_management/annotations/__init__.py +0 -0
- megadetector/data_management/annotations/annotation_constants.py +34 -0
- megadetector/data_management/camtrap_dp_to_coco.py +237 -0
- megadetector/data_management/cct_json_utils.py +404 -0
- megadetector/data_management/cct_to_md.py +176 -0
- megadetector/data_management/cct_to_wi.py +289 -0
- megadetector/data_management/coco_to_labelme.py +283 -0
- megadetector/data_management/coco_to_yolo.py +662 -0
- megadetector/data_management/databases/__init__.py +0 -0
- megadetector/data_management/databases/add_width_and_height_to_db.py +33 -0
- megadetector/data_management/databases/combine_coco_camera_traps_files.py +206 -0
- megadetector/data_management/databases/integrity_check_json_db.py +493 -0
- megadetector/data_management/databases/subset_json_db.py +115 -0
- megadetector/data_management/generate_crops_from_cct.py +149 -0
- megadetector/data_management/get_image_sizes.py +189 -0
- megadetector/data_management/importers/add_nacti_sizes.py +52 -0
- megadetector/data_management/importers/add_timestamps_to_icct.py +79 -0
- megadetector/data_management/importers/animl_results_to_md_results.py +158 -0
- megadetector/data_management/importers/auckland_doc_test_to_json.py +373 -0
- megadetector/data_management/importers/auckland_doc_to_json.py +201 -0
- megadetector/data_management/importers/awc_to_json.py +191 -0
- megadetector/data_management/importers/bellevue_to_json.py +273 -0
- megadetector/data_management/importers/cacophony-thermal-importer.py +793 -0
- megadetector/data_management/importers/carrizo_shrubfree_2018.py +269 -0
- megadetector/data_management/importers/carrizo_trail_cam_2017.py +289 -0
- megadetector/data_management/importers/cct_field_adjustments.py +58 -0
- megadetector/data_management/importers/channel_islands_to_cct.py +913 -0
- megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +180 -0
- megadetector/data_management/importers/eMammal/eMammal_helpers.py +249 -0
- megadetector/data_management/importers/eMammal/make_eMammal_json.py +223 -0
- megadetector/data_management/importers/ena24_to_json.py +276 -0
- megadetector/data_management/importers/filenames_to_json.py +386 -0
- megadetector/data_management/importers/helena_to_cct.py +283 -0
- megadetector/data_management/importers/idaho-camera-traps.py +1407 -0
- megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +294 -0
- megadetector/data_management/importers/jb_csv_to_json.py +150 -0
- megadetector/data_management/importers/mcgill_to_json.py +250 -0
- megadetector/data_management/importers/missouri_to_json.py +490 -0
- megadetector/data_management/importers/nacti_fieldname_adjustments.py +79 -0
- megadetector/data_management/importers/noaa_seals_2019.py +181 -0
- megadetector/data_management/importers/pc_to_json.py +365 -0
- megadetector/data_management/importers/plot_wni_giraffes.py +123 -0
- megadetector/data_management/importers/prepare-noaa-fish-data-for-lila.py +359 -0
- megadetector/data_management/importers/prepare_zsl_imerit.py +131 -0
- megadetector/data_management/importers/rspb_to_json.py +356 -0
- megadetector/data_management/importers/save_the_elephants_survey_A.py +320 -0
- megadetector/data_management/importers/save_the_elephants_survey_B.py +329 -0
- megadetector/data_management/importers/snapshot_safari_importer.py +758 -0
- megadetector/data_management/importers/snapshot_safari_importer_reprise.py +665 -0
- megadetector/data_management/importers/snapshot_serengeti_lila.py +1067 -0
- megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +150 -0
- megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +153 -0
- megadetector/data_management/importers/sulross_get_exif.py +65 -0
- megadetector/data_management/importers/timelapse_csv_set_to_json.py +490 -0
- megadetector/data_management/importers/ubc_to_json.py +399 -0
- megadetector/data_management/importers/umn_to_json.py +507 -0
- megadetector/data_management/importers/wellington_to_json.py +263 -0
- megadetector/data_management/importers/wi_to_json.py +442 -0
- megadetector/data_management/importers/zamba_results_to_md_results.py +181 -0
- megadetector/data_management/labelme_to_coco.py +547 -0
- megadetector/data_management/labelme_to_yolo.py +272 -0
- megadetector/data_management/lila/__init__.py +0 -0
- megadetector/data_management/lila/add_locations_to_island_camera_traps.py +97 -0
- megadetector/data_management/lila/add_locations_to_nacti.py +147 -0
- megadetector/data_management/lila/create_lila_blank_set.py +558 -0
- megadetector/data_management/lila/create_lila_test_set.py +152 -0
- megadetector/data_management/lila/create_links_to_md_results_files.py +106 -0
- megadetector/data_management/lila/download_lila_subset.py +178 -0
- megadetector/data_management/lila/generate_lila_per_image_labels.py +516 -0
- megadetector/data_management/lila/get_lila_annotation_counts.py +170 -0
- megadetector/data_management/lila/get_lila_image_counts.py +112 -0
- megadetector/data_management/lila/lila_common.py +300 -0
- megadetector/data_management/lila/test_lila_metadata_urls.py +132 -0
- megadetector/data_management/ocr_tools.py +870 -0
- megadetector/data_management/read_exif.py +809 -0
- megadetector/data_management/remap_coco_categories.py +84 -0
- megadetector/data_management/remove_exif.py +66 -0
- megadetector/data_management/rename_images.py +187 -0
- megadetector/data_management/resize_coco_dataset.py +189 -0
- megadetector/data_management/wi_download_csv_to_coco.py +247 -0
- megadetector/data_management/yolo_output_to_md_output.py +446 -0
- megadetector/data_management/yolo_to_coco.py +676 -0
- megadetector/detection/__init__.py +0 -0
- megadetector/detection/detector_training/__init__.py +0 -0
- megadetector/detection/detector_training/model_main_tf2.py +114 -0
- megadetector/detection/process_video.py +846 -0
- megadetector/detection/pytorch_detector.py +355 -0
- megadetector/detection/run_detector.py +779 -0
- megadetector/detection/run_detector_batch.py +1219 -0
- megadetector/detection/run_inference_with_yolov5_val.py +1087 -0
- megadetector/detection/run_tiled_inference.py +934 -0
- megadetector/detection/tf_detector.py +192 -0
- megadetector/detection/video_utils.py +698 -0
- megadetector/postprocessing/__init__.py +0 -0
- megadetector/postprocessing/add_max_conf.py +64 -0
- megadetector/postprocessing/categorize_detections_by_size.py +165 -0
- megadetector/postprocessing/classification_postprocessing.py +716 -0
- megadetector/postprocessing/combine_api_outputs.py +249 -0
- megadetector/postprocessing/compare_batch_results.py +966 -0
- megadetector/postprocessing/convert_output_format.py +396 -0
- megadetector/postprocessing/load_api_results.py +195 -0
- megadetector/postprocessing/md_to_coco.py +310 -0
- megadetector/postprocessing/md_to_labelme.py +330 -0
- megadetector/postprocessing/merge_detections.py +412 -0
- megadetector/postprocessing/postprocess_batch_results.py +1908 -0
- megadetector/postprocessing/remap_detection_categories.py +170 -0
- megadetector/postprocessing/render_detection_confusion_matrix.py +660 -0
- megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +211 -0
- megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +83 -0
- megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1635 -0
- megadetector/postprocessing/separate_detections_into_folders.py +730 -0
- megadetector/postprocessing/subset_json_detector_output.py +700 -0
- megadetector/postprocessing/top_folders_to_bottom.py +223 -0
- megadetector/taxonomy_mapping/__init__.py +0 -0
- megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +491 -0
- megadetector/taxonomy_mapping/map_new_lila_datasets.py +150 -0
- megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -0
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +588 -0
- megadetector/taxonomy_mapping/retrieve_sample_image.py +71 -0
- megadetector/taxonomy_mapping/simple_image_download.py +219 -0
- megadetector/taxonomy_mapping/species_lookup.py +834 -0
- megadetector/taxonomy_mapping/taxonomy_csv_checker.py +159 -0
- megadetector/taxonomy_mapping/taxonomy_graph.py +346 -0
- megadetector/taxonomy_mapping/validate_lila_category_mappings.py +83 -0
- megadetector/utils/__init__.py +0 -0
- megadetector/utils/azure_utils.py +178 -0
- megadetector/utils/ct_utils.py +613 -0
- megadetector/utils/directory_listing.py +246 -0
- megadetector/utils/md_tests.py +1164 -0
- megadetector/utils/path_utils.py +1045 -0
- megadetector/utils/process_utils.py +160 -0
- megadetector/utils/sas_blob_utils.py +509 -0
- megadetector/utils/split_locations_into_train_val.py +228 -0
- megadetector/utils/string_utils.py +92 -0
- megadetector/utils/url_utils.py +323 -0
- megadetector/utils/write_html_image_list.py +225 -0
- megadetector/visualization/__init__.py +0 -0
- megadetector/visualization/plot_utils.py +293 -0
- megadetector/visualization/render_images_with_thumbnails.py +275 -0
- megadetector/visualization/visualization_utils.py +1536 -0
- megadetector/visualization/visualize_db.py +552 -0
- megadetector/visualization/visualize_detector_output.py +405 -0
- {megadetector-5.0.11.dist-info → megadetector-5.0.13.dist-info}/LICENSE +0 -0
- {megadetector-5.0.11.dist-info → megadetector-5.0.13.dist-info}/METADATA +2 -2
- megadetector-5.0.13.dist-info/RECORD +201 -0
- megadetector-5.0.13.dist-info/top_level.txt +1 -0
- megadetector-5.0.11.dist-info/RECORD +0 -5
- megadetector-5.0.11.dist-info/top_level.txt +0 -1
- {megadetector-5.0.11.dist-info → megadetector-5.0.13.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
md_to_coco.py
|
|
4
|
+
|
|
5
|
+
"Converts" MegaDetector output files to COCO format. "Converts" is in quotes because
|
|
6
|
+
this is an opinionated transformation that requires a confidence threshold.
|
|
7
|
+
|
|
8
|
+
Does not currently handle classification information.
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
#%% Constants and imports
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import json
|
|
16
|
+
import uuid
|
|
17
|
+
|
|
18
|
+
from tqdm import tqdm
|
|
19
|
+
|
|
20
|
+
from megadetector.visualization import visualization_utils as vis_utils
|
|
21
|
+
|
|
22
|
+
default_confidence_threshold = 0.15
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
#%% Functions
|
|
26
|
+
|
|
27
|
+
def md_to_coco(md_results_file,
|
|
28
|
+
coco_output_file=None,
|
|
29
|
+
image_folder=None,
|
|
30
|
+
confidence_threshold=default_confidence_threshold,
|
|
31
|
+
validate_image_sizes=False,
|
|
32
|
+
info=None,
|
|
33
|
+
preserve_nonstandard_metadata=True,
|
|
34
|
+
include_failed_images=True):
|
|
35
|
+
"""
|
|
36
|
+
"Converts" MegaDetector output files to COCO format. "Converts" is in quotes because
|
|
37
|
+
this is an opinionated transformation that requires a confidence threshold.
|
|
38
|
+
|
|
39
|
+
A folder of images is required if width and height information are not available
|
|
40
|
+
in the MD results file.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
md_results_file (str): MD results .json file to convert to COCO format
|
|
44
|
+
coco_output_file (str, optional): COCO .json file to write; if this is None, we'll return
|
|
45
|
+
a COCO-formatted dict, but won't write it to disk
|
|
46
|
+
image_folder (str, optional): folder of images, required if 'width' and 'height' are not
|
|
47
|
+
present in the MD results file (they are not required by the format)
|
|
48
|
+
confidence_threshold (float, optional): boxes below this confidence threshold will not be
|
|
49
|
+
included in the output data
|
|
50
|
+
validate_image_sizes (bool, optional): if this is True, we'll check the image sizes
|
|
51
|
+
regardless of whether "width" and "height" are present in the MD results file.
|
|
52
|
+
info (dict, optional): arbitrary metadata to include in an "info" field in the COCO-formatted
|
|
53
|
+
output
|
|
54
|
+
preserve_nonstandard_metadata (bool, optional): if this is True, confidence will be preserved in a
|
|
55
|
+
non-standard "conf" field in each annotation, and any random fields present in each image's data
|
|
56
|
+
(e.g. EXIF metadata) will be propagated to COCO output
|
|
57
|
+
include_failed_images (boo, optional): if this is True, failed images will be propagated to COCO output
|
|
58
|
+
with a non-empty "failure" field and no other fields, otherwise failed images will be skipped.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
dict: the COCO data dict, identical to what's written to [coco_output_file] if [coco_output_file]
|
|
62
|
+
is not None.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
with open(md_results_file,'r') as f:
|
|
66
|
+
md_results = json.load(f)
|
|
67
|
+
|
|
68
|
+
coco_images = []
|
|
69
|
+
coco_annotations = []
|
|
70
|
+
|
|
71
|
+
# im = md_results['images'][0]
|
|
72
|
+
for im in tqdm(md_results['images']):
|
|
73
|
+
|
|
74
|
+
coco_im = {}
|
|
75
|
+
coco_im['id'] = im['file']
|
|
76
|
+
coco_im['file_name'] = im['file']
|
|
77
|
+
|
|
78
|
+
# There is no concept of this in the COCO standard
|
|
79
|
+
if 'failure' in im and im['failure'] is not None:
|
|
80
|
+
if include_failed_images:
|
|
81
|
+
coco_im['failure'] = im['failure']
|
|
82
|
+
coco_images.append(coco_im)
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
# Read/validate image size
|
|
86
|
+
w = None
|
|
87
|
+
h = None
|
|
88
|
+
|
|
89
|
+
if ('width' not in im) or ('height' not in im) or validate_image_sizes:
|
|
90
|
+
if image_folder is None:
|
|
91
|
+
raise ValueError('Must provide an image folder when height/width need to be read from images')
|
|
92
|
+
image_file_abs = os.path.join(image_folder,im['file'])
|
|
93
|
+
pil_im = vis_utils.open_image(image_file_abs)
|
|
94
|
+
w = pil_im.width
|
|
95
|
+
h = pil_im.height
|
|
96
|
+
if validate_image_sizes:
|
|
97
|
+
if 'width' in im:
|
|
98
|
+
assert im['width'] == w, 'Width mismatch for image {}'.format(im['file'])
|
|
99
|
+
if 'height' in im:
|
|
100
|
+
assert im['height'] == h, 'Height mismatch for image {}'.format(im['file'])
|
|
101
|
+
else:
|
|
102
|
+
w = im['width']
|
|
103
|
+
h = im['height']
|
|
104
|
+
|
|
105
|
+
coco_im['width'] = w
|
|
106
|
+
coco_im['height'] = h
|
|
107
|
+
|
|
108
|
+
# Add other, non-standard fields to the output dict
|
|
109
|
+
if preserve_nonstandard_metadata:
|
|
110
|
+
for k in im.keys():
|
|
111
|
+
if k not in ('file','detections','width','height'):
|
|
112
|
+
coco_im[k] = im[k]
|
|
113
|
+
|
|
114
|
+
coco_images.append(coco_im)
|
|
115
|
+
|
|
116
|
+
# detection = im['detections'][0]
|
|
117
|
+
for detection in im['detections']:
|
|
118
|
+
|
|
119
|
+
# Skip below-threshold detections
|
|
120
|
+
if confidence_threshold is not None and detection['conf'] < confidence_threshold:
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
# Create an annotation
|
|
124
|
+
ann = {}
|
|
125
|
+
ann['id'] = str(uuid.uuid1())
|
|
126
|
+
ann['image_id'] = coco_im['id']
|
|
127
|
+
|
|
128
|
+
md_category_id = detection['category']
|
|
129
|
+
coco_category_id = int(md_category_id)
|
|
130
|
+
ann['category_id'] = coco_category_id
|
|
131
|
+
|
|
132
|
+
# In very esoteric cases, we use the empty category (0) in MD-formatted output files
|
|
133
|
+
if md_category_id != '0':
|
|
134
|
+
|
|
135
|
+
assert 'bbox' in detection,\
|
|
136
|
+
'Oops: non-empty category with no bbox in {}'.format(im['file'])
|
|
137
|
+
|
|
138
|
+
ann['bbox'] = detection['bbox']
|
|
139
|
+
# MegaDetector: [x,y,width,height] (normalized, origin upper-left)
|
|
140
|
+
# COCO: [x,y,width,height] (absolute, origin upper-left)
|
|
141
|
+
ann['bbox'][0] = ann['bbox'][0] * coco_im['width']
|
|
142
|
+
ann['bbox'][1] = ann['bbox'][1] * coco_im['height']
|
|
143
|
+
ann['bbox'][2] = ann['bbox'][2] * coco_im['width']
|
|
144
|
+
ann['bbox'][3] = ann['bbox'][3] * coco_im['height']
|
|
145
|
+
|
|
146
|
+
else:
|
|
147
|
+
|
|
148
|
+
print('Warning: empty category annotation in file {}'.format(im['file']))
|
|
149
|
+
|
|
150
|
+
if preserve_nonstandard_metadata:
|
|
151
|
+
ann['conf'] = detection['conf']
|
|
152
|
+
|
|
153
|
+
coco_annotations.append(ann)
|
|
154
|
+
|
|
155
|
+
# ...for each detection
|
|
156
|
+
|
|
157
|
+
# ...for each image
|
|
158
|
+
|
|
159
|
+
output_dict = {}
|
|
160
|
+
|
|
161
|
+
if info is not None:
|
|
162
|
+
output_dict['info'] = info
|
|
163
|
+
else:
|
|
164
|
+
output_dict['info'] = {'description':'Converted from MD results file {}'.format(md_results_file)}
|
|
165
|
+
output_dict['info']['confidence_threshold'] = confidence_threshold
|
|
166
|
+
|
|
167
|
+
output_dict['images'] = coco_images
|
|
168
|
+
output_dict['annotations'] = coco_annotations
|
|
169
|
+
|
|
170
|
+
output_dict['categories'] = []
|
|
171
|
+
|
|
172
|
+
for md_category_id in md_results['detection_categories'].keys():
|
|
173
|
+
|
|
174
|
+
coco_category_id = int(md_category_id)
|
|
175
|
+
coco_category = {'id':coco_category_id,
|
|
176
|
+
'name':md_results['detection_categories'][md_category_id]}
|
|
177
|
+
output_dict['categories'].append(coco_category)
|
|
178
|
+
|
|
179
|
+
if coco_output_file is not None:
|
|
180
|
+
with open(coco_output_file,'w') as f:
|
|
181
|
+
json.dump(output_dict,f,indent=1)
|
|
182
|
+
|
|
183
|
+
return output_dict
|
|
184
|
+
|
|
185
|
+
# def md_to_coco(...)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
#%% Interactive driver
|
|
189
|
+
|
|
190
|
+
if False:
|
|
191
|
+
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
#%% Configure options
|
|
195
|
+
|
|
196
|
+
md_results_file = os.path.expanduser('~/data/md-test.json')
|
|
197
|
+
coco_output_file = os.path.expanduser('~/data/md-test-coco.json')
|
|
198
|
+
image_folder = os.path.expanduser('~/data/md-test')
|
|
199
|
+
validate_image_sizes = True
|
|
200
|
+
confidence_threshold = 0.2
|
|
201
|
+
validate_image_sizes=True
|
|
202
|
+
info=None
|
|
203
|
+
preserve_nonstandard_metadata=True
|
|
204
|
+
include_failed_images=False
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
#%% Programmatic execution
|
|
208
|
+
|
|
209
|
+
output_dict = md_to_coco(md_results_file,
|
|
210
|
+
coco_output_file=coco_output_file,
|
|
211
|
+
image_folder=image_folder,
|
|
212
|
+
confidence_threshold=confidence_threshold,
|
|
213
|
+
validate_image_sizes=validate_image_sizes,
|
|
214
|
+
info=info,
|
|
215
|
+
preserve_nonstandard_metadata=preserve_nonstandard_metadata,
|
|
216
|
+
include_failed_images=include_failed_images)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
#%% Command-line example
|
|
220
|
+
|
|
221
|
+
s = f'python md_to_coco.py {md_results_file} {coco_output_file} {confidence_threshold} '
|
|
222
|
+
if image_folder is not None:
|
|
223
|
+
s += f' --image_folder {image_folder}'
|
|
224
|
+
if preserve_nonstandard_metadata:
|
|
225
|
+
s += ' --preserve_nonstandard_metadata'
|
|
226
|
+
if include_failed_images:
|
|
227
|
+
s += ' --include_failed_images'
|
|
228
|
+
|
|
229
|
+
print(s); import clipboard; clipboard.copy(s)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
#%% Preview the resulting file
|
|
233
|
+
|
|
234
|
+
from megadetector.visualization import visualize_db
|
|
235
|
+
options = visualize_db.DbVizOptions()
|
|
236
|
+
options.parallelize_rendering = True
|
|
237
|
+
options.viz_size = (900, -1)
|
|
238
|
+
options.num_to_visualize = 5000
|
|
239
|
+
|
|
240
|
+
html_file,_ = visualize_db.visualize_db(coco_output_file,
|
|
241
|
+
os.path.expanduser('~/tmp/md_to_coco_preview'),
|
|
242
|
+
image_folder,options)
|
|
243
|
+
|
|
244
|
+
from megadetector.utils import path_utils # noqa
|
|
245
|
+
path_utils.open_file(html_file)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
#%% Command-line driver
|
|
249
|
+
|
|
250
|
+
import sys,argparse
|
|
251
|
+
|
|
252
|
+
def main():
|
|
253
|
+
|
|
254
|
+
parser = argparse.ArgumentParser(
|
|
255
|
+
description='"Convert" MD output to COCO format, in quotes because this is an opinionated transformation that requires a confidence threshold')
|
|
256
|
+
|
|
257
|
+
parser.add_argument(
|
|
258
|
+
'md_results_file',
|
|
259
|
+
type=str,
|
|
260
|
+
help='Path to MD results file (.json)')
|
|
261
|
+
|
|
262
|
+
parser.add_argument(
|
|
263
|
+
'coco_output_file',
|
|
264
|
+
type=str,
|
|
265
|
+
help='Output filename (.json)')
|
|
266
|
+
|
|
267
|
+
parser.add_argument(
|
|
268
|
+
'confidence_threshold',
|
|
269
|
+
type=float,
|
|
270
|
+
default=default_confidence_threshold,
|
|
271
|
+
help='Confidence threshold (default {})'.format(default_confidence_threshold)
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
parser.add_argument(
|
|
275
|
+
'--image_folder',
|
|
276
|
+
type=str,
|
|
277
|
+
default=None,
|
|
278
|
+
help='Image folder, only required if we will need to access image sizes'
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
parser.add_argument(
|
|
282
|
+
'--preserve_nonstandard_metadata',
|
|
283
|
+
action='store_true',
|
|
284
|
+
help='Preserve metadata that isn\'t normally included in ' +
|
|
285
|
+
'COCO-formatted data (e.g. EXIF metadata, confidence values)'
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
parser.add_argument(
|
|
289
|
+
'--include_failed_images',
|
|
290
|
+
action='store_true',
|
|
291
|
+
help='Keep a record of corrupted images in the output; may not be completely COCO-compliant'
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if len(sys.argv[1:]) == 0:
|
|
295
|
+
parser.print_help()
|
|
296
|
+
parser.exit()
|
|
297
|
+
|
|
298
|
+
args = parser.parse_args()
|
|
299
|
+
|
|
300
|
+
md_to_coco(args.md_results_file,
|
|
301
|
+
args.coco_output_file,
|
|
302
|
+
args.image_folder,
|
|
303
|
+
args.confidence_threshold,
|
|
304
|
+
validate_image_sizes=False,
|
|
305
|
+
info=None,
|
|
306
|
+
preserve_nonstandard_metadata=args.preserve_nonstandard_metadata,
|
|
307
|
+
include_failed_images=args.include_failed_images)
|
|
308
|
+
|
|
309
|
+
if __name__ == '__main__':
|
|
310
|
+
main()
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
md_to_labelme.py
|
|
4
|
+
|
|
5
|
+
"Converts" a MegaDetector output .json file to labelme format (one .json per image
|
|
6
|
+
file). "Convert" is in quotes because this is an opinionated transformation that
|
|
7
|
+
requires a confidence threshold.
|
|
8
|
+
|
|
9
|
+
TODO:
|
|
10
|
+
|
|
11
|
+
* support variable confidence thresholds across classes
|
|
12
|
+
* support classification data
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
#%% Imports and constants
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import json
|
|
20
|
+
|
|
21
|
+
from tqdm import tqdm
|
|
22
|
+
|
|
23
|
+
from multiprocessing.pool import Pool
|
|
24
|
+
from multiprocessing.pool import ThreadPool
|
|
25
|
+
from functools import partial
|
|
26
|
+
|
|
27
|
+
from megadetector.visualization.visualization_utils import open_image
|
|
28
|
+
from megadetector.utils.ct_utils import truncate_float
|
|
29
|
+
from megadetector.detection.run_detector import DEFAULT_DETECTOR_LABEL_MAP
|
|
30
|
+
|
|
31
|
+
output_precision = 3
|
|
32
|
+
default_confidence_threshold = 0.15
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
#%% Functions
|
|
36
|
+
|
|
37
|
+
def get_labelme_dict_for_image(im,image_base_name=None,category_id_to_name=None,
|
|
38
|
+
info=None,confidence_threshold=None):
|
|
39
|
+
"""
|
|
40
|
+
For the given image struct in MD results format, reformat the detections into
|
|
41
|
+
labelme format.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
im (dict): MegaDetector-formatted results dict, must include 'height' and 'width' fields
|
|
45
|
+
image_base_name (str, optional): written directly to the 'imagePath' field in the output;
|
|
46
|
+
defaults to os.path.basename(im['file']).
|
|
47
|
+
category_id_to_name (dict, optional): maps string-int category IDs to category names, defaults
|
|
48
|
+
to the standard MD categories
|
|
49
|
+
info (dict, optional): arbitrary metadata to write to the "detector_info" field in the output
|
|
50
|
+
dict
|
|
51
|
+
confidence_threshold (float, optional): only detections at or above this confidence threshold
|
|
52
|
+
will be included in the output dict
|
|
53
|
+
|
|
54
|
+
Return:
|
|
55
|
+
dict: labelme-formatted dictionary, suitable for writing directly to a labelme-formatted .json file
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
if image_base_name is None:
|
|
59
|
+
image_base_name = os.path.basename(im['file'])
|
|
60
|
+
|
|
61
|
+
if category_id_to_name:
|
|
62
|
+
category_id_to_name = DEFAULT_DETECTOR_LABEL_MAP
|
|
63
|
+
|
|
64
|
+
if confidence_threshold is None:
|
|
65
|
+
confidence_threshold = -1.0
|
|
66
|
+
|
|
67
|
+
output_dict = {}
|
|
68
|
+
if info is not None:
|
|
69
|
+
output_dict['detector_info'] = info
|
|
70
|
+
output_dict['version'] = '5.3.0a0'
|
|
71
|
+
output_dict['flags'] = {}
|
|
72
|
+
output_dict['shapes'] = []
|
|
73
|
+
output_dict['imagePath'] = image_base_name
|
|
74
|
+
output_dict['imageHeight'] = im['height']
|
|
75
|
+
output_dict['imageWidth'] = im['width']
|
|
76
|
+
output_dict['imageData'] = None
|
|
77
|
+
output_dict['detections'] = im['detections']
|
|
78
|
+
|
|
79
|
+
# det = im['detections'][1]
|
|
80
|
+
for det in im['detections']:
|
|
81
|
+
|
|
82
|
+
if det['conf'] < confidence_threshold:
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
shape = {}
|
|
86
|
+
shape['conf'] = det['conf']
|
|
87
|
+
shape['label'] = category_id_to_name[det['category']]
|
|
88
|
+
shape['shape_type'] = 'rectangle'
|
|
89
|
+
shape['description'] = ''
|
|
90
|
+
shape['group_id'] = None
|
|
91
|
+
|
|
92
|
+
# MD boxes are [x_min, y_min, width_of_box, height_of_box] (relative)
|
|
93
|
+
#
|
|
94
|
+
# labelme boxes are [[x0,y0],[x1,y1]] (absolute)
|
|
95
|
+
x0 = truncate_float(det['bbox'][0] * im['width'],output_precision)
|
|
96
|
+
y0 = truncate_float(det['bbox'][1] * im['height'],output_precision)
|
|
97
|
+
x1 = truncate_float(x0 + det['bbox'][2] * im['width'],output_precision)
|
|
98
|
+
y1 = truncate_float(y0 + det['bbox'][3] * im['height'],output_precision)
|
|
99
|
+
shape['points'] = [[x0,y0],[x1,y1]]
|
|
100
|
+
output_dict['shapes'].append(shape)
|
|
101
|
+
|
|
102
|
+
# ...for each detection
|
|
103
|
+
|
|
104
|
+
return output_dict
|
|
105
|
+
|
|
106
|
+
# ...def get_labelme_dict_for_image()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _write_output_for_image(im,image_base,extension_prefix,info,
|
|
110
|
+
confidence_threshold,category_id_to_name,overwrite,
|
|
111
|
+
verbose=False):
|
|
112
|
+
|
|
113
|
+
if 'failure' in im and im['failure'] is not None:
|
|
114
|
+
assert 'detections' not in im or im['detections'] is None
|
|
115
|
+
if verbose:
|
|
116
|
+
print('Skipping labelme file generation for failed image {}'.format(
|
|
117
|
+
im['file']))
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
im_full_path = os.path.join(image_base,im['file'])
|
|
121
|
+
json_path = os.path.splitext(im_full_path)[0] + extension_prefix + '.json'
|
|
122
|
+
|
|
123
|
+
if (not overwrite) and (os.path.isfile(json_path)):
|
|
124
|
+
if verbose:
|
|
125
|
+
print('Skipping existing file {}'.format(json_path))
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
output_dict = get_labelme_dict_for_image(im,
|
|
129
|
+
image_base_name=os.path.basename(im_full_path),
|
|
130
|
+
category_id_to_name=category_id_to_name,
|
|
131
|
+
info=info,
|
|
132
|
+
confidence_threshold=confidence_threshold)
|
|
133
|
+
|
|
134
|
+
with open(json_path,'w') as f:
|
|
135
|
+
json.dump(output_dict,f,indent=1)
|
|
136
|
+
|
|
137
|
+
# ...def write_output_for_image(...)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def md_to_labelme(results_file,image_base,confidence_threshold=None,
|
|
142
|
+
overwrite=False,extension_prefix='',n_workers=1,
|
|
143
|
+
use_threads=False,bypass_image_size_read=False,
|
|
144
|
+
verbose=False):
|
|
145
|
+
"""
|
|
146
|
+
For all the images in [results_file], write a .json file in labelme format alongside the
|
|
147
|
+
corresponding relative path within image_base.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
results_file (str): MD results .json file to convert to Labelme format
|
|
151
|
+
image_base (str): folder of images; filenames in [results_file] should be relative to
|
|
152
|
+
this folder
|
|
153
|
+
confidence_threshold (float, optional): only detections at or above this confidence threshold
|
|
154
|
+
will be included in the output dict
|
|
155
|
+
overwrite (bool, optional): whether to overwrite existing output files; if this is False
|
|
156
|
+
and the output file for an image exists, we'll skip that image
|
|
157
|
+
extension_prefix (str, optional): if non-empty, "extension_prefix" will be inserted before the .json
|
|
158
|
+
extension
|
|
159
|
+
n_workers (int, optional): enables multiprocessing if > 1
|
|
160
|
+
use_threads (bool, optional): if [n_workers] > 1, determines whether we parallelize via threads (True)
|
|
161
|
+
or processes (False)
|
|
162
|
+
bypass_image_size_read (bool, optional): if True, skips reading image sizes and trusts whatever is in
|
|
163
|
+
the MD results file (don't set this to "True" if your MD results file doesn't contain image sizes)
|
|
164
|
+
verbose (bool, optional): enables additionald ebug output
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
if extension_prefix is None:
|
|
168
|
+
extension_prefix = ''
|
|
169
|
+
|
|
170
|
+
# Load MD results if necessary
|
|
171
|
+
if isinstance(results_file,dict):
|
|
172
|
+
md_results = results_file
|
|
173
|
+
else:
|
|
174
|
+
print('Loading MD results...')
|
|
175
|
+
with open(results_file,'r') as f:
|
|
176
|
+
md_results = json.load(f)
|
|
177
|
+
|
|
178
|
+
# Read image sizes if necessary
|
|
179
|
+
if bypass_image_size_read:
|
|
180
|
+
|
|
181
|
+
print('Bypassing image size read')
|
|
182
|
+
|
|
183
|
+
else:
|
|
184
|
+
|
|
185
|
+
# TODO: parallelize this loop
|
|
186
|
+
|
|
187
|
+
print('Reading image sizes...')
|
|
188
|
+
|
|
189
|
+
# im = md_results['images'][0]
|
|
190
|
+
for im in tqdm(md_results['images']):
|
|
191
|
+
|
|
192
|
+
# Make sure this file exists
|
|
193
|
+
im_full_path = os.path.join(image_base,im['file'])
|
|
194
|
+
assert os.path.isfile(im_full_path), 'Image file {} does not exist'.format(im_full_path)
|
|
195
|
+
|
|
196
|
+
json_path = os.path.splitext(im_full_path)[0] + extension_prefix + '.json'
|
|
197
|
+
|
|
198
|
+
# Don't even bother reading sizes for files we're not going to generate
|
|
199
|
+
if (not overwrite) and (os.path.isfile(json_path)):
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
# Load w/h information if necessary
|
|
203
|
+
if 'height' not in im or 'width' not in im:
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
pil_im = open_image(im_full_path)
|
|
207
|
+
im['width'] = pil_im.width
|
|
208
|
+
im['height'] = pil_im.height
|
|
209
|
+
except Exception:
|
|
210
|
+
print('Warning: cannot open image {}, treating as a failure during inference'.format(
|
|
211
|
+
im_full_path))
|
|
212
|
+
if 'failure' not in im:
|
|
213
|
+
im['failure'] = 'Failure image access'
|
|
214
|
+
|
|
215
|
+
# ...if we need to read w/h information
|
|
216
|
+
|
|
217
|
+
# ...for each image
|
|
218
|
+
|
|
219
|
+
# ...if we're not bypassing image size read
|
|
220
|
+
|
|
221
|
+
print('\nGenerating labelme files...')
|
|
222
|
+
|
|
223
|
+
# Write output
|
|
224
|
+
if n_workers <= 1:
|
|
225
|
+
for im in tqdm(md_results['images']):
|
|
226
|
+
_write_output_for_image(im,image_base,extension_prefix,md_results['info'],confidence_threshold,
|
|
227
|
+
md_results['detection_categories'],overwrite,verbose)
|
|
228
|
+
else:
|
|
229
|
+
if use_threads:
|
|
230
|
+
print('Starting parallel thread pool with {} workers'.format(n_workers))
|
|
231
|
+
pool = ThreadPool(n_workers)
|
|
232
|
+
else:
|
|
233
|
+
print('Starting parallel process pool with {} workers'.format(n_workers))
|
|
234
|
+
pool = Pool(n_workers)
|
|
235
|
+
_ = list(tqdm(pool.imap(
|
|
236
|
+
partial(_write_output_for_image,
|
|
237
|
+
image_base=image_base,extension_prefix=extension_prefix,
|
|
238
|
+
info=md_results['info'],confidence_threshold=confidence_threshold,
|
|
239
|
+
category_id_to_name=md_results['detection_categories'],
|
|
240
|
+
overwrite=overwrite,verbose=verbose),
|
|
241
|
+
md_results['images']),
|
|
242
|
+
total=len(md_results['images'])))
|
|
243
|
+
|
|
244
|
+
# ...for each image
|
|
245
|
+
|
|
246
|
+
# ...def md_to_labelme()
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
#%% Interactive driver
|
|
250
|
+
|
|
251
|
+
if False:
|
|
252
|
+
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
#%% Configure options
|
|
256
|
+
|
|
257
|
+
md_results_file = os.path.expanduser('~/data/md-test.json')
|
|
258
|
+
coco_output_file = os.path.expanduser('~/data/md-test-coco.json')
|
|
259
|
+
image_folder = os.path.expanduser('~/data/md-test')
|
|
260
|
+
confidence_threshold = 0.2
|
|
261
|
+
overwrite = True
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
#%% Programmatic execution
|
|
265
|
+
|
|
266
|
+
md_to_labelme(results_file=md_results_file,
|
|
267
|
+
image_base=image_folder,
|
|
268
|
+
confidence_threshold=confidence_threshold,
|
|
269
|
+
overwrite=overwrite)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
#%% Command-line execution
|
|
273
|
+
|
|
274
|
+
s = 'python md_to_labelme.py {} {} --confidence_threshold {}'.format(md_results_file,
|
|
275
|
+
image_folder,
|
|
276
|
+
confidence_threshold)
|
|
277
|
+
if overwrite:
|
|
278
|
+
s += ' --overwrite'
|
|
279
|
+
|
|
280
|
+
print(s)
|
|
281
|
+
import clipboard; clipboard.copy(s)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
#%% Opening labelme
|
|
285
|
+
|
|
286
|
+
s = 'python labelme {}'.format(image_folder)
|
|
287
|
+
print(s)
|
|
288
|
+
import clipboard; clipboard.copy(s)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
#%% Command-line driver
|
|
292
|
+
|
|
293
|
+
import sys,argparse
|
|
294
|
+
|
|
295
|
+
def main():
|
|
296
|
+
|
|
297
|
+
parser = argparse.ArgumentParser(
|
|
298
|
+
description='Convert MD output to labelme annotation format')
|
|
299
|
+
parser.add_argument(
|
|
300
|
+
'results_file',
|
|
301
|
+
type=str,
|
|
302
|
+
help='Path to MD results file (.json)')
|
|
303
|
+
|
|
304
|
+
parser.add_argument(
|
|
305
|
+
'image_base',
|
|
306
|
+
type=str,
|
|
307
|
+
help='Path to images (also the output folder)')
|
|
308
|
+
|
|
309
|
+
parser.add_argument(
|
|
310
|
+
'--confidence_threshold',
|
|
311
|
+
type=float,
|
|
312
|
+
default=default_confidence_threshold,
|
|
313
|
+
help='Confidence threshold (default {})'.format(default_confidence_threshold)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
parser.add_argument(
|
|
317
|
+
'--overwrite',
|
|
318
|
+
action='store_true',
|
|
319
|
+
help='Overwrite existing labelme .json files')
|
|
320
|
+
|
|
321
|
+
if len(sys.argv[1:]) == 0:
|
|
322
|
+
parser.print_help()
|
|
323
|
+
parser.exit()
|
|
324
|
+
|
|
325
|
+
args = parser.parse_args()
|
|
326
|
+
|
|
327
|
+
md_to_labelme(args.results_file,args.image_base,args.confidence_threshold,args.overwrite)
|
|
328
|
+
|
|
329
|
+
if __name__ == '__main__':
|
|
330
|
+
main()
|