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
|
@@ -14,7 +14,7 @@ no detections are present.
|
|
|
14
14
|
|
|
15
15
|
YOLOv5 output has one text file per image, like so:
|
|
16
16
|
|
|
17
|
-
0 0.0141693 0.469758 0.0283385 0.131552 0.761428
|
|
17
|
+
0 0.0141693 0.469758 0.0283385 0.131552 0.761428
|
|
18
18
|
|
|
19
19
|
That's [class, x_center, y_center, width_of_box, height_of_box, confidence]
|
|
20
20
|
|
|
@@ -40,6 +40,8 @@ import json
|
|
|
40
40
|
import csv
|
|
41
41
|
import os
|
|
42
42
|
import re
|
|
43
|
+
import sys
|
|
44
|
+
import argparse
|
|
43
45
|
|
|
44
46
|
from collections import defaultdict
|
|
45
47
|
from tqdm import tqdm
|
|
@@ -57,39 +59,39 @@ def read_classes_from_yolo_dataset_file(fn):
|
|
|
57
59
|
Reads a dictionary mapping integer class IDs to class names from a YOLOv5/YOLOv8
|
|
58
60
|
dataset.yaml file or a .json file. A .json file should contain a dictionary mapping
|
|
59
61
|
integer category IDs to string category names.
|
|
60
|
-
|
|
62
|
+
|
|
61
63
|
Args:
|
|
62
|
-
fn (str): YOLOv5/YOLOv8 dataset file with a .yml or .yaml extension, a .json file
|
|
64
|
+
fn (str): YOLOv5/YOLOv8 dataset file with a .yml or .yaml extension, a .json file
|
|
63
65
|
mapping integer category IDs to category names, or a .txt file with a flat
|
|
64
66
|
list of classes.
|
|
65
|
-
|
|
67
|
+
|
|
66
68
|
Returns:
|
|
67
69
|
dict: a mapping from integer category IDs to category names
|
|
68
70
|
"""
|
|
69
|
-
|
|
71
|
+
|
|
70
72
|
category_id_to_name = {}
|
|
71
|
-
|
|
73
|
+
|
|
72
74
|
if fn.endswith('.yml') or fn.endswith('.yaml'):
|
|
73
|
-
|
|
75
|
+
|
|
74
76
|
with open(fn,'r') as f:
|
|
75
77
|
lines = f.readlines()
|
|
76
|
-
|
|
78
|
+
|
|
77
79
|
pat = r'\d+:.+'
|
|
78
80
|
for s in lines:
|
|
79
81
|
if re.search(pat,s) is not None:
|
|
80
82
|
tokens = s.split(':')
|
|
81
83
|
assert len(tokens) == 2, 'Invalid token in category file {}'.format(fn)
|
|
82
84
|
category_id_to_name[int(tokens[0].strip())] = tokens[1].strip()
|
|
83
|
-
|
|
85
|
+
|
|
84
86
|
elif fn.endswith('.json'):
|
|
85
|
-
|
|
87
|
+
|
|
86
88
|
with open(fn,'r') as f:
|
|
87
89
|
d_in = json.load(f)
|
|
88
90
|
for k in d_in.keys():
|
|
89
91
|
category_id_to_name[int(k)] = d_in[k]
|
|
90
|
-
|
|
92
|
+
|
|
91
93
|
elif fn.endswith('.txt'):
|
|
92
|
-
|
|
94
|
+
|
|
93
95
|
with open(fn,'r') as f:
|
|
94
96
|
lines = f.readlines()
|
|
95
97
|
next_category_id = 0
|
|
@@ -99,20 +101,20 @@ def read_classes_from_yolo_dataset_file(fn):
|
|
|
99
101
|
continue
|
|
100
102
|
category_id_to_name[next_category_id] = s
|
|
101
103
|
next_category_id += 1
|
|
102
|
-
|
|
104
|
+
|
|
103
105
|
else:
|
|
104
|
-
|
|
106
|
+
|
|
105
107
|
raise ValueError('Unrecognized category file type: {}'.format(fn))
|
|
106
|
-
|
|
108
|
+
|
|
107
109
|
assert len(category_id_to_name) > 0, 'Failed to read class mappings from {}'.format(fn)
|
|
108
|
-
|
|
110
|
+
|
|
109
111
|
return category_id_to_name
|
|
110
|
-
|
|
111
112
|
|
|
112
|
-
|
|
113
|
+
|
|
114
|
+
def yolo_json_output_to_md_output(yolo_json_file,
|
|
113
115
|
image_folder,
|
|
114
|
-
output_file,
|
|
115
|
-
yolo_category_id_to_name,
|
|
116
|
+
output_file,
|
|
117
|
+
yolo_category_id_to_name,
|
|
116
118
|
detector_name='unknown',
|
|
117
119
|
image_id_to_relative_path=None,
|
|
118
120
|
offset_yolo_class_ids=True,
|
|
@@ -121,56 +123,56 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
121
123
|
convert_slashes=True):
|
|
122
124
|
"""
|
|
123
125
|
Converts a YOLOv5/YOLOv8 .json file to MD .json format.
|
|
124
|
-
|
|
126
|
+
|
|
125
127
|
Args:
|
|
126
|
-
|
|
127
|
-
yolo_json_file (str): the .json file to convert from YOLOv5 format to MD output format
|
|
128
|
+
yolo_json_file (str): the YOLO-formatted .json file to convert to MD output format
|
|
128
129
|
image_folder (str): the .json file contains relative path names, this is the path base
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
output_file (str): the MD-formatted .json file to write
|
|
131
|
+
yolo_category_id_to_name (str or dict): the .json results file contains only numeric
|
|
132
|
+
identifiers for categories, but we want names and numbers for the output format;
|
|
133
|
+
yolo_category_id_to_name provides that mapping either as a dict or as a YOLOv5
|
|
132
134
|
dataset.yaml file.
|
|
133
|
-
detector_name (str, optional): a string that gets put in the output file, not otherwise
|
|
135
|
+
detector_name (str, optional): a string that gets put in the output file, not otherwise
|
|
134
136
|
used within this function
|
|
135
|
-
image_id_to_relative_path (dict, optional): YOLOv5 .json uses only basenames (e.g.
|
|
137
|
+
image_id_to_relative_path (dict, optional): YOLOv5 .json uses only basenames (e.g.
|
|
136
138
|
abc1234.JPG); by default these will be appended to the input path to create pathnames.
|
|
137
139
|
If you have a flat folder, this is fine. If you want to map base names to relative paths in
|
|
138
|
-
a more complicated way, use this parameter.
|
|
139
|
-
offset_yolo_class_ids (bool, optional): YOLOv5 class IDs always start at zero; if you want to
|
|
140
|
-
make the output classes start at 1, set offset_yolo_class_ids to True.
|
|
141
|
-
truncate_to_standard_md_precision (bool, optional): YOLOv5 .json includes lots of
|
|
140
|
+
a more complicated way, use this parameter.
|
|
141
|
+
offset_yolo_class_ids (bool, optional): YOLOv5 class IDs always start at zero; if you want to
|
|
142
|
+
make the output classes start at 1, set offset_yolo_class_ids to True.
|
|
143
|
+
truncate_to_standard_md_precision (bool, optional): YOLOv5 .json includes lots of
|
|
142
144
|
(not-super-meaningful) precision, set this to truncate to COORD_DIGITS and CONF_DIGITS.
|
|
143
|
-
image_id_to_error (dict, optional): if you want to include image IDs in the output file for which
|
|
145
|
+
image_id_to_error (dict, optional): if you want to include image IDs in the output file for which
|
|
144
146
|
you couldn't prepare the input file in the first place due to errors, include them here.
|
|
145
147
|
convert_slashes (bool, optional): force all slashes to be forward slashes in the output file
|
|
146
|
-
"""
|
|
147
|
-
|
|
148
|
+
"""
|
|
149
|
+
|
|
148
150
|
assert os.path.isfile(yolo_json_file), \
|
|
149
151
|
'Could not find YOLO .json file {}'.format(yolo_json_file)
|
|
150
152
|
assert os.path.isdir(image_folder), \
|
|
151
153
|
'Could not find image folder {}'.format(image_folder)
|
|
152
|
-
|
|
154
|
+
|
|
153
155
|
if image_id_to_error is None:
|
|
154
156
|
image_id_to_error = {}
|
|
155
|
-
|
|
157
|
+
|
|
156
158
|
print('Converting {} to MD format and writing results to {}'.format(
|
|
157
159
|
yolo_json_file,output_file))
|
|
158
|
-
|
|
160
|
+
|
|
159
161
|
if isinstance(yolo_category_id_to_name,str):
|
|
160
162
|
assert os.path.isfile(yolo_category_id_to_name), \
|
|
161
163
|
'YOLO category mapping specified as a string, but file does not exist: {}'.format(
|
|
162
164
|
yolo_category_id_to_name)
|
|
163
165
|
yolo_category_id_to_name = read_classes_from_yolo_dataset_file(yolo_category_id_to_name)
|
|
164
|
-
|
|
166
|
+
|
|
165
167
|
if image_id_to_relative_path is None:
|
|
166
|
-
|
|
168
|
+
|
|
167
169
|
image_files = path_utils.find_images(image_folder,recursive=True)
|
|
168
170
|
image_files = [os.path.relpath(fn,image_folder) for fn in image_files]
|
|
169
|
-
|
|
171
|
+
|
|
170
172
|
# YOLOv5 identifies images in .json output by ID, which is the filename without
|
|
171
173
|
# extension. If a mapping is not provided, these need to be unique.
|
|
172
174
|
image_id_to_relative_path = {}
|
|
173
|
-
|
|
175
|
+
|
|
174
176
|
for fn in image_files:
|
|
175
177
|
image_id = os.path.splitext(os.path.basename(fn))[0]
|
|
176
178
|
if image_id in image_id_to_relative_path:
|
|
@@ -180,49 +182,49 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
180
182
|
image_id_to_relative_path[image_id] = fn
|
|
181
183
|
|
|
182
184
|
image_files_relative = sorted(list(image_id_to_relative_path.values()))
|
|
183
|
-
|
|
185
|
+
|
|
184
186
|
image_file_relative_to_image_id = {}
|
|
185
187
|
for image_id in image_id_to_relative_path:
|
|
186
188
|
relative_path = image_id_to_relative_path[image_id]
|
|
187
189
|
assert relative_path not in image_file_relative_to_image_id, \
|
|
188
190
|
'Duplication image IDs in YOLO output conversion for image {}'.format(relative_path)
|
|
189
191
|
image_file_relative_to_image_id[relative_path] = image_id
|
|
190
|
-
|
|
192
|
+
|
|
191
193
|
with open(yolo_json_file,'r') as f:
|
|
192
194
|
detections = json.load(f)
|
|
193
195
|
assert isinstance(detections,list)
|
|
194
|
-
|
|
196
|
+
|
|
195
197
|
image_id_to_detections = defaultdict(list)
|
|
196
|
-
|
|
198
|
+
|
|
197
199
|
int_formatted_image_ids = False
|
|
198
|
-
|
|
200
|
+
|
|
199
201
|
# det = detections[0]
|
|
200
202
|
for det in detections:
|
|
201
|
-
|
|
203
|
+
|
|
202
204
|
# This could be a string, but if the YOLOv5 inference script sees that the strings
|
|
203
205
|
# are really ints, it converts to ints.
|
|
204
206
|
image_id = det['image_id']
|
|
205
207
|
image_id_to_detections[image_id].append(det)
|
|
206
208
|
if isinstance(image_id,int):
|
|
207
209
|
int_formatted_image_ids = True
|
|
208
|
-
|
|
210
|
+
|
|
209
211
|
# If there are any ints present, everything should be ints
|
|
210
212
|
if int_formatted_image_ids:
|
|
211
213
|
for det in detections:
|
|
212
214
|
assert isinstance(det['image_id'],int), \
|
|
213
215
|
'Found mixed int and string image IDs'
|
|
214
|
-
|
|
216
|
+
|
|
215
217
|
# Convert the keys in image_id_to_error to ints
|
|
216
218
|
#
|
|
217
|
-
# This should error if we're given non-int-friendly IDs
|
|
218
|
-
int_formatted_image_id_to_error = {}
|
|
219
|
+
# This should error if we're given non-int-friendly IDs
|
|
220
|
+
int_formatted_image_id_to_error = {}
|
|
219
221
|
for image_id in image_id_to_error:
|
|
220
222
|
int_formatted_image_id_to_error[int(image_id)] = \
|
|
221
223
|
image_id_to_error[image_id]
|
|
222
|
-
image_id_to_error = int_formatted_image_id_to_error
|
|
223
|
-
|
|
224
|
+
image_id_to_error = int_formatted_image_id_to_error
|
|
225
|
+
|
|
224
226
|
# ...if image IDs are formatted as integers in YOLO output
|
|
225
|
-
|
|
227
|
+
|
|
226
228
|
# In a modified version of val.py, we use negative category IDs to indicate an error
|
|
227
229
|
# that happened during inference (typically truncated images with valid headers,
|
|
228
230
|
# so corruption was not detected during val.py's initial corruption check pass.
|
|
@@ -232,17 +234,17 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
232
234
|
error_string = det['error']
|
|
233
235
|
print('Caught inference-time failure {} for image {}'.format(error_string,det['image_id']))
|
|
234
236
|
image_id_to_error[det['image_id']] = error_string
|
|
235
|
-
|
|
237
|
+
|
|
236
238
|
output_images = []
|
|
237
|
-
|
|
239
|
+
|
|
238
240
|
# image_file_relative = image_files_relative[10]
|
|
239
241
|
for image_file_relative in tqdm(image_files_relative):
|
|
240
|
-
|
|
242
|
+
|
|
241
243
|
im = {}
|
|
242
244
|
im['file'] = image_file_relative
|
|
243
245
|
if convert_slashes:
|
|
244
246
|
im['file'] = im['file'].replace('\\','/')
|
|
245
|
-
|
|
247
|
+
|
|
246
248
|
image_id = image_file_relative_to_image_id[image_file_relative]
|
|
247
249
|
if int_formatted_image_ids:
|
|
248
250
|
image_id = int(image_id)
|
|
@@ -254,7 +256,7 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
254
256
|
detections = []
|
|
255
257
|
else:
|
|
256
258
|
detections = image_id_to_detections[image_id]
|
|
257
|
-
|
|
259
|
+
|
|
258
260
|
image_full_path = os.path.join(image_folder,image_file_relative)
|
|
259
261
|
try:
|
|
260
262
|
pil_im = vis_utils.open_image(image_full_path)
|
|
@@ -264,17 +266,17 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
264
266
|
im['failure'] = 'Conversion error: {}'.format(s)
|
|
265
267
|
output_images.append(im)
|
|
266
268
|
continue
|
|
267
|
-
|
|
269
|
+
|
|
268
270
|
im['detections'] = []
|
|
269
|
-
|
|
271
|
+
|
|
270
272
|
image_w = pil_im.size[0]
|
|
271
273
|
image_h = pil_im.size[1]
|
|
272
|
-
|
|
274
|
+
|
|
273
275
|
# det = detections[0]
|
|
274
276
|
for det in detections:
|
|
275
|
-
|
|
277
|
+
|
|
276
278
|
output_det = {}
|
|
277
|
-
|
|
279
|
+
|
|
278
280
|
yolo_cat_id = int(det['category_id'])
|
|
279
281
|
if offset_yolo_class_ids:
|
|
280
282
|
yolo_cat_id += 1
|
|
@@ -284,63 +286,62 @@ def yolo_json_output_to_md_output(yolo_json_file,
|
|
|
284
286
|
conf = ct_utils.round_float(conf,CONF_DIGITS)
|
|
285
287
|
output_det['conf'] = conf
|
|
286
288
|
input_bbox = det['bbox']
|
|
287
|
-
|
|
289
|
+
|
|
288
290
|
# YOLO's COCO .json is not *that* COCO-like, but it is COCO-like in
|
|
289
291
|
# that the boxes are already [xmin/ymin/w/h]
|
|
290
292
|
box_xmin_absolute = input_bbox[0]
|
|
291
293
|
box_ymin_absolute = input_bbox[1]
|
|
292
294
|
box_width_absolute = input_bbox[2]
|
|
293
295
|
box_height_absolute = input_bbox[3]
|
|
294
|
-
|
|
296
|
+
|
|
295
297
|
box_xmin_relative = box_xmin_absolute / image_w
|
|
296
298
|
box_ymin_relative = box_ymin_absolute / image_h
|
|
297
299
|
box_width_relative = box_width_absolute / image_w
|
|
298
300
|
box_height_relative = box_height_absolute / image_h
|
|
299
|
-
|
|
301
|
+
|
|
300
302
|
output_bbox = [box_xmin_relative,box_ymin_relative,
|
|
301
303
|
box_width_relative,box_height_relative]
|
|
302
|
-
|
|
304
|
+
|
|
303
305
|
if truncate_to_standard_md_precision:
|
|
304
306
|
output_bbox = ct_utils.round_float_array(output_bbox,COORD_DIGITS)
|
|
305
|
-
|
|
307
|
+
|
|
306
308
|
output_det['bbox'] = output_bbox
|
|
307
309
|
im['detections'].append(output_det)
|
|
308
|
-
|
|
309
|
-
# ...for each detection
|
|
310
|
-
|
|
310
|
+
|
|
311
|
+
# ...for each detection
|
|
312
|
+
|
|
311
313
|
output_images.append(im)
|
|
312
|
-
|
|
314
|
+
|
|
313
315
|
# ...for each image file
|
|
314
|
-
|
|
316
|
+
|
|
315
317
|
d = {}
|
|
316
318
|
d['images'] = output_images
|
|
317
319
|
d['info'] = {'format_version':'1.4','detector':detector_name}
|
|
318
320
|
d['detection_categories'] = {}
|
|
319
|
-
|
|
321
|
+
|
|
320
322
|
for cat_id in yolo_category_id_to_name:
|
|
321
323
|
yolo_cat_id = int(cat_id)
|
|
322
324
|
if offset_yolo_class_ids:
|
|
323
325
|
yolo_cat_id += 1
|
|
324
326
|
d['detection_categories'][str(yolo_cat_id)] = yolo_category_id_to_name[cat_id]
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
|
|
328
|
+
ct_utils.write_json(output_file, d)
|
|
329
|
+
|
|
329
330
|
# ...def yolo_json_output_to_md_output(...)
|
|
330
|
-
|
|
331
331
|
|
|
332
|
-
|
|
332
|
+
|
|
333
|
+
def yolo_txt_output_to_md_output(input_results_folder,
|
|
333
334
|
image_folder,
|
|
334
|
-
output_file,
|
|
335
|
+
output_file,
|
|
335
336
|
detector_tag=None,
|
|
336
337
|
truncate_to_standard_md_precision=True):
|
|
337
338
|
"""
|
|
338
339
|
Converts a folder of YOLO-output .txt files to MD .json format.
|
|
339
|
-
|
|
340
|
-
Less finished than the .json conversion function; this .txt conversion assumes
|
|
341
|
-
a hard-coded mapping representing the standard MD categories (in MD indexing,
|
|
340
|
+
|
|
341
|
+
Less finished than the .json conversion function; this .txt conversion assumes
|
|
342
|
+
a hard-coded mapping representing the standard MD categories (in MD indexing,
|
|
342
343
|
1/2/3=animal/person/vehicle; in YOLO indexing, 0/1/2=animal/person/vehicle).
|
|
343
|
-
|
|
344
|
+
|
|
344
345
|
Args:
|
|
345
346
|
input_results_folder (str): the folder containing YOLO-output .txt files
|
|
346
347
|
image_folder (str): the folder where images live, may be the same as
|
|
@@ -349,47 +350,47 @@ def yolo_txt_output_to_md_output(input_results_folder,
|
|
|
349
350
|
results
|
|
350
351
|
detector_tag (str, optional): string to put in the 'detector' field in the
|
|
351
352
|
output file
|
|
352
|
-
truncate_to_standard_md_precision (bool, optional): set this to truncate to
|
|
353
|
+
truncate_to_standard_md_precision (bool, optional): set this to truncate to
|
|
353
354
|
COORD_DIGITS and CONF_DIGITS, like the standard MD pipeline does.
|
|
354
355
|
"""
|
|
355
|
-
|
|
356
|
+
|
|
356
357
|
assert os.path.isdir(input_results_folder)
|
|
357
358
|
assert os.path.isdir(image_folder)
|
|
358
|
-
|
|
359
|
+
|
|
359
360
|
## Enumerate results files and image files
|
|
360
|
-
|
|
361
|
+
|
|
361
362
|
yolo_results_files = os.listdir(input_results_folder)
|
|
362
363
|
yolo_results_files = [f for f in yolo_results_files if f.lower().endswith('.txt')]
|
|
363
364
|
# print('Found {} results files'.format(len(yolo_results_files)))
|
|
364
|
-
|
|
365
|
+
|
|
365
366
|
image_files = path_utils.find_images(image_folder,recursive=False)
|
|
366
367
|
image_files_relative = [os.path.basename(f) for f in image_files]
|
|
367
368
|
# print('Found {} images'.format(len(image_files)))
|
|
368
|
-
|
|
369
|
+
|
|
369
370
|
image_files_relative_no_extension = [os.path.splitext(f)[0] for f in image_files_relative]
|
|
370
|
-
|
|
371
|
+
|
|
371
372
|
## Make sure that every results file corresponds to an image
|
|
372
|
-
|
|
373
|
+
|
|
373
374
|
for f in yolo_results_files:
|
|
374
375
|
result_no_extension = os.path.splitext(f)[0]
|
|
375
376
|
assert result_no_extension in image_files_relative_no_extension
|
|
376
|
-
|
|
377
|
+
|
|
377
378
|
## Build MD output data
|
|
378
|
-
|
|
379
|
+
|
|
379
380
|
# Map 0-indexed YOLO categories to 1-indexed MD categories
|
|
380
381
|
yolo_cat_map = { 0: 1, 1: 2, 2: 3 }
|
|
381
|
-
|
|
382
|
+
|
|
382
383
|
images_entries = []
|
|
383
384
|
|
|
384
385
|
# image_fn = image_files_relative[0]
|
|
385
386
|
for image_fn in image_files_relative:
|
|
386
|
-
|
|
387
|
-
image_name, ext = os.path.splitext(image_fn)
|
|
387
|
+
|
|
388
|
+
image_name, ext = os.path.splitext(image_fn)
|
|
388
389
|
label_fn = image_name + '.txt'
|
|
389
390
|
label_path = os.path.join(input_results_folder, label_fn)
|
|
390
|
-
|
|
391
|
+
|
|
391
392
|
detections = []
|
|
392
|
-
|
|
393
|
+
|
|
393
394
|
if not os.path.exists(label_path):
|
|
394
395
|
# This is assumed to be an image with no detections
|
|
395
396
|
pass
|
|
@@ -397,36 +398,36 @@ def yolo_txt_output_to_md_output(input_results_folder,
|
|
|
397
398
|
with open(label_path, newline='') as f:
|
|
398
399
|
reader = csv.reader(f, delimiter=' ')
|
|
399
400
|
for row in reader:
|
|
400
|
-
category = yolo_cat_map[int(row[0])]
|
|
401
|
-
api_box = ct_utils.convert_yolo_to_xywh([float(row[1]), float(row[2]),
|
|
401
|
+
category = yolo_cat_map[int(row[0])]
|
|
402
|
+
api_box = ct_utils.convert_yolo_to_xywh([float(row[1]), float(row[2]),
|
|
402
403
|
float(row[3]), float(row[4])])
|
|
403
|
-
|
|
404
|
+
|
|
404
405
|
conf = float(row[5])
|
|
405
|
-
|
|
406
|
+
|
|
406
407
|
if truncate_to_standard_md_precision:
|
|
407
408
|
conf = ct_utils.round_float(conf, precision=CONF_DIGITS)
|
|
408
409
|
api_box = ct_utils.round_float_array(api_box, precision=COORD_DIGITS)
|
|
409
|
-
|
|
410
|
+
|
|
410
411
|
detections.append({
|
|
411
412
|
'category': str(category),
|
|
412
413
|
'conf': conf,
|
|
413
414
|
'bbox': api_box
|
|
414
415
|
})
|
|
415
|
-
|
|
416
|
+
|
|
416
417
|
images_entries.append({
|
|
417
418
|
'file': image_fn,
|
|
418
419
|
'detections': detections
|
|
419
420
|
})
|
|
420
|
-
|
|
421
|
+
|
|
421
422
|
# ...for each image
|
|
422
|
-
|
|
423
|
+
|
|
423
424
|
## Save output file
|
|
424
|
-
|
|
425
|
+
|
|
425
426
|
detector_string = 'converted_from_yolo_format'
|
|
426
|
-
|
|
427
|
+
|
|
427
428
|
if detector_tag is not None:
|
|
428
429
|
detector_string = detector_tag
|
|
429
|
-
|
|
430
|
+
|
|
430
431
|
output_content = {
|
|
431
432
|
'info': {
|
|
432
433
|
'detector': detector_string,
|
|
@@ -440,27 +441,152 @@ def yolo_txt_output_to_md_output(input_results_folder,
|
|
|
440
441
|
},
|
|
441
442
|
'images': images_entries
|
|
442
443
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
444
|
+
|
|
445
|
+
ct_utils.write_json(output_file, output_content)
|
|
446
|
+
|
|
447
447
|
# ...def yolo_txt_output_to_md_output(...)
|
|
448
448
|
|
|
449
449
|
|
|
450
450
|
#%% Interactive driver
|
|
451
451
|
|
|
452
452
|
if False:
|
|
453
|
-
|
|
453
|
+
|
|
454
454
|
pass
|
|
455
455
|
|
|
456
|
-
#%%
|
|
457
|
-
|
|
456
|
+
#%%
|
|
457
|
+
|
|
458
458
|
input_results_folder = os.path.expanduser('~/tmp/model-version-experiments/pt-test-kru/exp/labels')
|
|
459
459
|
image_folder = os.path.expanduser('~/data/KRU-test')
|
|
460
|
-
output_file = os.path.expanduser('~/data/mdv5a-yolo-pt-kru.json')
|
|
460
|
+
output_file = os.path.expanduser('~/data/mdv5a-yolo-pt-kru.json')
|
|
461
461
|
yolo_txt_output_to_md_output(input_results_folder,image_folder,output_file)
|
|
462
462
|
|
|
463
463
|
|
|
464
464
|
#%% Command-line driver
|
|
465
465
|
|
|
466
|
-
|
|
466
|
+
def main():
|
|
467
|
+
"""
|
|
468
|
+
Command-line interface to convert YOLOv5/YOLOv8 output (.json or .txt)
|
|
469
|
+
to MegaDetector output format.
|
|
470
|
+
"""
|
|
471
|
+
|
|
472
|
+
parser = argparse.ArgumentParser(
|
|
473
|
+
description='Converts YOLOv5 output (.json or .txt) to MD output format.'
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
# The first argument determines which series of additional arguments are supported, for
|
|
477
|
+
# json/txt input
|
|
478
|
+
subparsers = parser.add_subparsers(dest='mode', required=True,
|
|
479
|
+
help="Mode of operation: 'json' for YOLO JSON output, 'txt' for YOLO TXT output.")
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
## 'json' mode subparser
|
|
483
|
+
|
|
484
|
+
parser_json = subparsers.add_parser('json', help='Convert YOLO-formatted .json results.')
|
|
485
|
+
|
|
486
|
+
parser_json.add_argument(
|
|
487
|
+
'yolo_json_file', type=str,
|
|
488
|
+
help='Path to the input YOLO-formatted .json results file'
|
|
489
|
+
)
|
|
490
|
+
parser_json.add_argument(
|
|
491
|
+
'image_folder', type=str,
|
|
492
|
+
help='Path to the image folder'
|
|
493
|
+
)
|
|
494
|
+
parser_json.add_argument(
|
|
495
|
+
'output_file', type=str,
|
|
496
|
+
help='Path to the MD-formatted .json output file'
|
|
497
|
+
)
|
|
498
|
+
parser_json.add_argument(
|
|
499
|
+
'yolo_category_id_to_name_file', type=str,
|
|
500
|
+
help='Path to the .yml, .yaml, .json, or .txt file mapping YOLO category IDs to names'
|
|
501
|
+
)
|
|
502
|
+
parser_json.add_argument(
|
|
503
|
+
'--detector_name', type=str, default='unknown',
|
|
504
|
+
help="Detector name to store in the output file (default: 'unknown')"
|
|
505
|
+
)
|
|
506
|
+
parser_json.add_argument(
|
|
507
|
+
'--image_id_to_relative_path_file', type=str, default=None,
|
|
508
|
+
help='Path to a .json file mapping image IDs to relative paths'
|
|
509
|
+
)
|
|
510
|
+
parser_json.add_argument(
|
|
511
|
+
'--offset_yolo_class_ids', type=str, default='true', choices=['true', 'false'],
|
|
512
|
+
help="Offset YOLO class IDs in the output (default: 'true')"
|
|
513
|
+
)
|
|
514
|
+
parser_json.add_argument(
|
|
515
|
+
'--truncate_to_standard_md_precision', type=str, default='true', choices=['true', 'false'],
|
|
516
|
+
help="Truncate coordinates and confidences to standard MD precision (default: 'true')"
|
|
517
|
+
)
|
|
518
|
+
parser_json.add_argument(
|
|
519
|
+
'--convert_slashes', type=str, default='true', choices=['true', 'false'],
|
|
520
|
+
help="Convert backslashes to forward slashes in output file paths (default: 'true')"
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
## 'txt' mode subparser
|
|
525
|
+
|
|
526
|
+
parser_txt = subparsers.add_parser('txt', help='Convert YOLO-formatted .txt results from a folder')
|
|
527
|
+
parser_txt.add_argument(
|
|
528
|
+
'input_results_folder', type=str,
|
|
529
|
+
help='Path to the folder containing YOLO .txt output files'
|
|
530
|
+
)
|
|
531
|
+
parser_txt.add_argument(
|
|
532
|
+
'image_folder', type=str,
|
|
533
|
+
help='Path to the image folder'
|
|
534
|
+
)
|
|
535
|
+
parser_txt.add_argument(
|
|
536
|
+
'output_file', type=str,
|
|
537
|
+
help='Path to the MD-formatted .json file output'
|
|
538
|
+
)
|
|
539
|
+
parser_txt.add_argument(
|
|
540
|
+
'--detector_tag', type=str, default=None,
|
|
541
|
+
help='Detector tag to store in the output file'
|
|
542
|
+
)
|
|
543
|
+
parser_txt.add_argument(
|
|
544
|
+
'--truncate_to_standard_md_precision', type=str, default='true', choices=['true', 'false'],
|
|
545
|
+
help="Truncate coordinates and confidences to standard MD precision (default: 'true')."
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
args = parser.parse_args()
|
|
549
|
+
|
|
550
|
+
if args.mode == 'json':
|
|
551
|
+
|
|
552
|
+
image_id_to_relative_path = None
|
|
553
|
+
if args.image_id_to_relative_path_file:
|
|
554
|
+
try:
|
|
555
|
+
with open(args.image_id_to_relative_path_file, 'r') as f:
|
|
556
|
+
image_id_to_relative_path = json.load(f)
|
|
557
|
+
except Exception as e:
|
|
558
|
+
print(f"Error loading image_id_to_relative_path_file: {e}")
|
|
559
|
+
sys.exit(1)
|
|
560
|
+
|
|
561
|
+
offset_yolo_class_ids = args.offset_yolo_class_ids.lower() == 'true'
|
|
562
|
+
truncate_json = args.truncate_to_standard_md_precision.lower() == 'true'
|
|
563
|
+
convert_slashes = args.convert_slashes.lower() == 'true'
|
|
564
|
+
|
|
565
|
+
yolo_json_output_to_md_output(
|
|
566
|
+
yolo_json_file=args.yolo_json_file,
|
|
567
|
+
image_folder=args.image_folder,
|
|
568
|
+
output_file=args.output_file,
|
|
569
|
+
yolo_category_id_to_name=args.yolo_category_id_to_name_file, # Function handles reading this file
|
|
570
|
+
detector_name=args.detector_name,
|
|
571
|
+
image_id_to_relative_path=image_id_to_relative_path,
|
|
572
|
+
offset_yolo_class_ids=offset_yolo_class_ids,
|
|
573
|
+
truncate_to_standard_md_precision=truncate_json,
|
|
574
|
+
convert_slashes=convert_slashes
|
|
575
|
+
)
|
|
576
|
+
print('Converted {} to {}'.format(args.yolo_json_file,args.output_file))
|
|
577
|
+
|
|
578
|
+
elif args.mode == 'txt':
|
|
579
|
+
|
|
580
|
+
truncate_txt = args.truncate_to_standard_md_precision.lower() == 'true'
|
|
581
|
+
|
|
582
|
+
yolo_txt_output_to_md_output(
|
|
583
|
+
input_results_folder=args.input_results_folder,
|
|
584
|
+
image_folder=args.image_folder,
|
|
585
|
+
output_file=args.output_file,
|
|
586
|
+
detector_tag=args.detector_tag,
|
|
587
|
+
truncate_to_standard_md_precision=truncate_txt
|
|
588
|
+
)
|
|
589
|
+
print('Converted results from {} to {}'.format(args.input_results_folder,args.output_file))
|
|
590
|
+
|
|
591
|
+
if __name__ == '__main__':
|
|
592
|
+
main()
|