megadetector 5.0.8__py3-none-any.whl → 5.0.10__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.
- api/__init__.py +0 -0
- api/batch_processing/__init__.py +0 -0
- api/batch_processing/api_core/__init__.py +0 -0
- api/batch_processing/api_core/batch_service/__init__.py +0 -0
- api/batch_processing/api_core/batch_service/score.py +0 -1
- api/batch_processing/api_core/server_job_status_table.py +0 -1
- api/batch_processing/api_core_support/__init__.py +0 -0
- api/batch_processing/api_core_support/aggregate_results_manually.py +0 -1
- api/batch_processing/api_support/__init__.py +0 -0
- api/batch_processing/api_support/summarize_daily_activity.py +0 -1
- api/batch_processing/data_preparation/__init__.py +0 -0
- api/batch_processing/data_preparation/manage_local_batch.py +65 -65
- api/batch_processing/data_preparation/manage_video_batch.py +8 -8
- api/batch_processing/integration/digiKam/xmp_integration.py +0 -1
- api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +0 -1
- api/batch_processing/postprocessing/__init__.py +0 -0
- api/batch_processing/postprocessing/add_max_conf.py +12 -12
- api/batch_processing/postprocessing/categorize_detections_by_size.py +32 -14
- api/batch_processing/postprocessing/combine_api_outputs.py +68 -54
- api/batch_processing/postprocessing/compare_batch_results.py +113 -43
- api/batch_processing/postprocessing/convert_output_format.py +41 -16
- api/batch_processing/postprocessing/load_api_results.py +16 -17
- api/batch_processing/postprocessing/md_to_coco.py +31 -21
- api/batch_processing/postprocessing/md_to_labelme.py +52 -22
- api/batch_processing/postprocessing/merge_detections.py +14 -14
- api/batch_processing/postprocessing/postprocess_batch_results.py +246 -174
- api/batch_processing/postprocessing/remap_detection_categories.py +32 -25
- api/batch_processing/postprocessing/render_detection_confusion_matrix.py +60 -27
- api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +53 -44
- api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +25 -14
- api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +242 -158
- api/batch_processing/postprocessing/separate_detections_into_folders.py +159 -114
- api/batch_processing/postprocessing/subset_json_detector_output.py +146 -169
- api/batch_processing/postprocessing/top_folders_to_bottom.py +77 -43
- api/synchronous/__init__.py +0 -0
- api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
- api/synchronous/api_core/animal_detection_api/api_backend.py +0 -2
- api/synchronous/api_core/animal_detection_api/api_frontend.py +266 -268
- api/synchronous/api_core/animal_detection_api/config.py +35 -35
- api/synchronous/api_core/tests/__init__.py +0 -0
- api/synchronous/api_core/tests/load_test.py +109 -109
- classification/__init__.py +0 -0
- classification/aggregate_classifier_probs.py +21 -24
- classification/analyze_failed_images.py +11 -13
- classification/cache_batchapi_outputs.py +51 -51
- classification/create_classification_dataset.py +69 -68
- classification/crop_detections.py +54 -53
- classification/csv_to_json.py +97 -100
- classification/detect_and_crop.py +105 -105
- classification/evaluate_model.py +43 -42
- classification/identify_mislabeled_candidates.py +47 -46
- classification/json_to_azcopy_list.py +10 -10
- classification/json_validator.py +72 -71
- classification/map_classification_categories.py +44 -43
- classification/merge_classification_detection_output.py +68 -68
- classification/prepare_classification_script.py +157 -154
- classification/prepare_classification_script_mc.py +228 -228
- classification/run_classifier.py +27 -26
- classification/save_mislabeled.py +30 -30
- classification/train_classifier.py +20 -20
- classification/train_classifier_tf.py +21 -22
- classification/train_utils.py +10 -10
- data_management/__init__.py +0 -0
- data_management/annotations/__init__.py +0 -0
- data_management/annotations/annotation_constants.py +18 -31
- data_management/camtrap_dp_to_coco.py +238 -0
- data_management/cct_json_utils.py +102 -59
- data_management/cct_to_md.py +176 -158
- data_management/cct_to_wi.py +247 -219
- data_management/coco_to_labelme.py +272 -263
- data_management/coco_to_yolo.py +79 -58
- data_management/databases/__init__.py +0 -0
- data_management/databases/add_width_and_height_to_db.py +20 -16
- data_management/databases/combine_coco_camera_traps_files.py +35 -31
- data_management/databases/integrity_check_json_db.py +62 -24
- data_management/databases/subset_json_db.py +24 -15
- data_management/generate_crops_from_cct.py +27 -45
- data_management/get_image_sizes.py +188 -162
- data_management/importers/add_nacti_sizes.py +8 -8
- data_management/importers/add_timestamps_to_icct.py +78 -78
- data_management/importers/animl_results_to_md_results.py +158 -158
- data_management/importers/auckland_doc_test_to_json.py +9 -9
- data_management/importers/auckland_doc_to_json.py +8 -8
- data_management/importers/awc_to_json.py +7 -7
- data_management/importers/bellevue_to_json.py +15 -15
- data_management/importers/cacophony-thermal-importer.py +13 -13
- data_management/importers/carrizo_shrubfree_2018.py +8 -8
- data_management/importers/carrizo_trail_cam_2017.py +8 -8
- data_management/importers/cct_field_adjustments.py +9 -9
- data_management/importers/channel_islands_to_cct.py +10 -10
- data_management/importers/eMammal/copy_and_unzip_emammal.py +1 -0
- data_management/importers/ena24_to_json.py +7 -7
- data_management/importers/filenames_to_json.py +8 -8
- data_management/importers/helena_to_cct.py +7 -7
- data_management/importers/idaho-camera-traps.py +7 -7
- data_management/importers/idfg_iwildcam_lila_prep.py +10 -10
- data_management/importers/jb_csv_to_json.py +9 -9
- data_management/importers/mcgill_to_json.py +8 -8
- data_management/importers/missouri_to_json.py +18 -18
- data_management/importers/nacti_fieldname_adjustments.py +10 -10
- data_management/importers/noaa_seals_2019.py +7 -7
- data_management/importers/pc_to_json.py +7 -7
- data_management/importers/plot_wni_giraffes.py +7 -7
- data_management/importers/prepare-noaa-fish-data-for-lila.py +359 -359
- data_management/importers/prepare_zsl_imerit.py +7 -7
- data_management/importers/rspb_to_json.py +8 -8
- data_management/importers/save_the_elephants_survey_A.py +8 -8
- data_management/importers/save_the_elephants_survey_B.py +9 -9
- data_management/importers/snapshot_safari_importer.py +26 -26
- data_management/importers/snapshot_safari_importer_reprise.py +665 -665
- data_management/importers/snapshot_serengeti_lila.py +14 -14
- data_management/importers/sulross_get_exif.py +8 -9
- data_management/importers/timelapse_csv_set_to_json.py +11 -11
- data_management/importers/ubc_to_json.py +13 -13
- data_management/importers/umn_to_json.py +7 -7
- data_management/importers/wellington_to_json.py +8 -8
- data_management/importers/wi_to_json.py +9 -9
- data_management/importers/zamba_results_to_md_results.py +181 -181
- data_management/labelme_to_coco.py +65 -24
- data_management/labelme_to_yolo.py +8 -8
- data_management/lila/__init__.py +0 -0
- data_management/lila/add_locations_to_island_camera_traps.py +9 -9
- data_management/lila/add_locations_to_nacti.py +147 -147
- data_management/lila/create_lila_blank_set.py +13 -13
- data_management/lila/create_lila_test_set.py +8 -8
- data_management/lila/create_links_to_md_results_files.py +106 -106
- data_management/lila/download_lila_subset.py +44 -110
- data_management/lila/generate_lila_per_image_labels.py +55 -42
- data_management/lila/get_lila_annotation_counts.py +18 -15
- data_management/lila/get_lila_image_counts.py +11 -11
- data_management/lila/lila_common.py +96 -33
- data_management/lila/test_lila_metadata_urls.py +132 -116
- data_management/ocr_tools.py +173 -128
- data_management/read_exif.py +110 -97
- data_management/remap_coco_categories.py +83 -83
- data_management/remove_exif.py +58 -62
- data_management/resize_coco_dataset.py +30 -23
- data_management/wi_download_csv_to_coco.py +246 -239
- data_management/yolo_output_to_md_output.py +86 -73
- data_management/yolo_to_coco.py +300 -60
- detection/__init__.py +0 -0
- detection/detector_training/__init__.py +0 -0
- detection/process_video.py +85 -33
- detection/pytorch_detector.py +43 -25
- detection/run_detector.py +157 -72
- detection/run_detector_batch.py +179 -113
- detection/run_inference_with_yolov5_val.py +108 -48
- detection/run_tiled_inference.py +111 -40
- detection/tf_detector.py +51 -29
- detection/video_utils.py +606 -521
- docs/source/conf.py +43 -0
- md_utils/__init__.py +0 -0
- md_utils/azure_utils.py +9 -9
- md_utils/ct_utils.py +228 -68
- md_utils/directory_listing.py +59 -64
- md_utils/md_tests.py +968 -871
- md_utils/path_utils.py +460 -134
- md_utils/process_utils.py +157 -133
- md_utils/sas_blob_utils.py +20 -20
- md_utils/split_locations_into_train_val.py +45 -32
- md_utils/string_utils.py +33 -10
- md_utils/url_utils.py +176 -60
- md_utils/write_html_image_list.py +40 -33
- md_visualization/__init__.py +0 -0
- md_visualization/plot_utils.py +102 -109
- md_visualization/render_images_with_thumbnails.py +34 -34
- md_visualization/visualization_utils.py +597 -291
- md_visualization/visualize_db.py +76 -48
- md_visualization/visualize_detector_output.py +61 -42
- {megadetector-5.0.8.dist-info → megadetector-5.0.10.dist-info}/METADATA +13 -7
- megadetector-5.0.10.dist-info/RECORD +224 -0
- {megadetector-5.0.8.dist-info → megadetector-5.0.10.dist-info}/top_level.txt +1 -0
- taxonomy_mapping/__init__.py +0 -0
- taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +342 -335
- taxonomy_mapping/map_new_lila_datasets.py +154 -154
- taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -134
- taxonomy_mapping/preview_lila_taxonomy.py +591 -591
- taxonomy_mapping/retrieve_sample_image.py +12 -12
- taxonomy_mapping/simple_image_download.py +11 -11
- taxonomy_mapping/species_lookup.py +10 -10
- taxonomy_mapping/taxonomy_csv_checker.py +18 -18
- taxonomy_mapping/taxonomy_graph.py +47 -47
- taxonomy_mapping/validate_lila_category_mappings.py +83 -76
- data_management/cct_json_to_filename_json.py +0 -89
- data_management/cct_to_csv.py +0 -140
- data_management/databases/remove_corrupted_images_from_db.py +0 -191
- detection/detector_training/copy_checkpoints.py +0 -43
- megadetector-5.0.8.dist-info/RECORD +0 -205
- {megadetector-5.0.8.dist-info → megadetector-5.0.10.dist-info}/LICENSE +0 -0
- {megadetector-5.0.8.dist-info → megadetector-5.0.10.dist-info}/WHEEL +0 -0
|
@@ -1,39 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
run_inference_with_yolov5_val.py
|
|
4
|
+
|
|
5
|
+
Runs a folder of images through MegaDetector (or another YOLOv5/YOLOv8 model) with YOLO's
|
|
6
|
+
val.py, converting the output to the standard MD format. The reasons this script exists,
|
|
7
|
+
as an alternative to the standard run_detector_batch.py are:
|
|
8
|
+
|
|
9
|
+
* This script provides access to YOLO's test-time augmentation tools.
|
|
10
|
+
* This script serves a reference implementation: by any reasonable definition, YOLOv5's
|
|
11
|
+
val.py produces the "correct" result for any image, since it matches what was used in
|
|
12
|
+
training.
|
|
13
|
+
* This script works for any Ultralytics detection model, including YOLOv8 models
|
|
14
|
+
|
|
15
|
+
YOLOv5's val.py uses each file's base name as a unique identifier, which doesn't work
|
|
16
|
+
when you have typical camera trap images like:
|
|
17
|
+
|
|
18
|
+
* a/b/c/RECONYX0001.JPG
|
|
19
|
+
* d/e/f/RECONYX0001.JPG
|
|
20
|
+
|
|
21
|
+
...both of which would just be "RECONYX0001.JPG". So this script jumps through a bunch of
|
|
22
|
+
hoops to put a symlinks in a flat folder, run YOLOv5 on that folder, and map the results back
|
|
23
|
+
to the real files.
|
|
24
|
+
|
|
25
|
+
If you are running a YOLOv5 model, this script currently requires the caller to supply the path
|
|
26
|
+
where a working YOLOv5 install lives, and assumes that the current conda environment is all set up for
|
|
27
|
+
YOLOv5. If you are running a YOLOv8 model, the folder doesn't matter, but it assumes that ultralytics
|
|
28
|
+
tools are available in the current environment.
|
|
29
|
+
|
|
30
|
+
By default, this script uses symlinks to format the input images in a way that YOLO's
|
|
31
|
+
val.py likes, as per above. This requires admin privileges on Windows... actually technically this
|
|
32
|
+
only requires permissions to create symbolic links, but I've never seen a case where someone has
|
|
33
|
+
that permission and *doesn't* have admin privileges. If you are running this script on
|
|
34
|
+
Windows and you don't have admin privileges, use --no_use_symlinks, which will make copies of images,
|
|
35
|
+
rather than using symlinks.
|
|
36
|
+
|
|
37
|
+
TODO:
|
|
38
|
+
|
|
39
|
+
* Multiple GPU support
|
|
40
|
+
* Checkpointing
|
|
41
|
+
* Support alternative class names at the command line (currently defaults to MD classes,
|
|
42
|
+
though other class names can be supplied programmatically)
|
|
43
|
+
|
|
44
|
+
"""
|
|
37
45
|
|
|
38
46
|
#%% Imports
|
|
39
47
|
|
|
@@ -60,59 +68,112 @@ default_image_size_with_no_augmentation = 1280
|
|
|
60
68
|
#%% Options class
|
|
61
69
|
|
|
62
70
|
class YoloInferenceOptions:
|
|
63
|
-
|
|
71
|
+
"""
|
|
72
|
+
Parameters that control the behavior of run_inference_with_yolov5_val(), including
|
|
73
|
+
the input/output filenames.
|
|
74
|
+
"""
|
|
75
|
+
|
|
64
76
|
## Required ##
|
|
65
77
|
|
|
78
|
+
#: Folder of images to process
|
|
66
79
|
input_folder = None
|
|
80
|
+
|
|
81
|
+
#: Model filename (ending in .pt), or a well-known model name (e.g. "MDV5A")
|
|
67
82
|
model_filename = None
|
|
83
|
+
|
|
84
|
+
#: .json output file, in MD results format
|
|
68
85
|
output_file = None
|
|
69
86
|
|
|
87
|
+
|
|
70
88
|
## Optional ##
|
|
71
89
|
|
|
72
|
-
|
|
90
|
+
#: Required for older YOLOv5 inference, not for newer ulytralytics/YOLOv8 inference
|
|
73
91
|
yolo_working_folder = None
|
|
74
92
|
|
|
75
|
-
|
|
76
|
-
|
|
93
|
+
#: Currently 'yolov5' and 'ultralytics' are supported, and really these are proxies for
|
|
94
|
+
#: "the yolov5 repo" and "the ultralytics repo".
|
|
77
95
|
model_type = 'yolov5'
|
|
78
96
|
|
|
97
|
+
#: Image size to use; this is a single int, which in ultralytics's terminology means
|
|
98
|
+
#: "scale the long side of the image to this size, and preserve aspect ratio".
|
|
79
99
|
image_size = default_image_size_with_augmentation
|
|
100
|
+
|
|
101
|
+
#: Detections below this threshold will not be included in the output file
|
|
80
102
|
conf_thres = '0.001'
|
|
103
|
+
|
|
104
|
+
#: Batch size... has no impact on results, but may create memory issues if you set
|
|
105
|
+
#: this to large values
|
|
81
106
|
batch_size = 1
|
|
107
|
+
|
|
108
|
+
#: Device string: typically '0' for GPU 0, '1' for GPU 1, etc., or 'cpu'
|
|
82
109
|
device_string = '0'
|
|
110
|
+
|
|
111
|
+
#: Should we enable test-time augmentation?
|
|
83
112
|
augment = True
|
|
113
|
+
|
|
114
|
+
#: Should we enable half-precision inference?
|
|
84
115
|
half_precision_enabled = None
|
|
85
116
|
|
|
117
|
+
#: Where should we stash the temporary symlinks used to give unique identifiers to image files?
|
|
118
|
+
#:
|
|
119
|
+
#: If this is None, we'll create a folder in system temp space.
|
|
86
120
|
symlink_folder = None
|
|
121
|
+
|
|
122
|
+
#: Should we use symlinks to give unique identifiers to image files (vs. copies)?
|
|
87
123
|
use_symlinks = True
|
|
88
124
|
|
|
125
|
+
#: Temporary folder to stash intermediate YOLO results.
|
|
126
|
+
#:
|
|
127
|
+
#: If this is None, we'll create a folder in system temp space.
|
|
89
128
|
yolo_results_folder = None
|
|
90
129
|
|
|
130
|
+
#: Should we remove the symlink folder when we're done?
|
|
91
131
|
remove_symlink_folder = True
|
|
132
|
+
|
|
133
|
+
#: Should we remove the intermediate results folder when we're done?
|
|
92
134
|
remove_yolo_results_folder = True
|
|
93
135
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
136
|
+
#: These are deliberately offset from the standard MD categories; YOLOv5
|
|
137
|
+
#: needs categories IDs to start at 0.
|
|
138
|
+
#:
|
|
139
|
+
#: This can also be a string that points to a YOLO dataset.yaml file.
|
|
98
140
|
yolo_category_id_to_name = {0:'animal',1:'person',2:'vehicle'}
|
|
99
141
|
|
|
100
|
-
|
|
142
|
+
#: What should we do if the output file already exists?
|
|
143
|
+
#:
|
|
144
|
+
#: Can be 'error', 'skip', or 'overwrite'.
|
|
101
145
|
overwrite_handling = 'skip'
|
|
102
146
|
|
|
147
|
+
#: If True, we'll do a dry run that lets you preview the YOLO val command, without
|
|
148
|
+
#: actually running it.
|
|
103
149
|
preview_yolo_command_only = False
|
|
104
150
|
|
|
151
|
+
#: By default, if any errors occur while we're copying images or creating symlinks, it's
|
|
152
|
+
#: game over. If this is True, those errors become warnings, and we plow ahead.
|
|
105
153
|
treat_copy_failures_as_warnings = False
|
|
106
154
|
|
|
155
|
+
#: Save YOLO console output
|
|
107
156
|
save_yolo_debug_output = False
|
|
108
157
|
|
|
158
|
+
#: Whether to search for images recursively within [input_folder]
|
|
109
159
|
recursive = True
|
|
110
160
|
|
|
111
161
|
|
|
162
|
+
# ...YoloInferenceOptions()
|
|
163
|
+
|
|
164
|
+
|
|
112
165
|
#%% Main function
|
|
113
166
|
|
|
114
167
|
def run_inference_with_yolo_val(options):
|
|
115
|
-
|
|
168
|
+
"""
|
|
169
|
+
Runs a folder of images through MegaDetector (or another YOLOv5/YOLOv8 model) with YOLO's
|
|
170
|
+
val.py, converting the output to the standard MD format.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
options (YoloInferenceOptions): all the parameters used to control this process,
|
|
174
|
+
including filenames; see YoloInferenceOptions for details
|
|
175
|
+
"""
|
|
176
|
+
|
|
116
177
|
##%% Input and path handling
|
|
117
178
|
|
|
118
179
|
if options.model_type == 'yolov8':
|
|
@@ -606,8 +667,7 @@ def main():
|
|
|
606
667
|
|
|
607
668
|
print(options.__dict__)
|
|
608
669
|
|
|
609
|
-
run_inference_with_yolo_val(options)
|
|
610
|
-
|
|
670
|
+
run_inference_with_yolo_val(options)
|
|
611
671
|
|
|
612
672
|
if __name__ == '__main__':
|
|
613
673
|
main()
|
detection/run_tiled_inference.py
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
run_tiled_inference.py
|
|
4
|
+
|
|
5
|
+
**This script is experimental, YMMV.**
|
|
6
|
+
|
|
7
|
+
Runs inference on a folder, fist splitting each image up into tiles of size
|
|
8
|
+
MxN (typically the native inference size of your detector), writing those
|
|
9
|
+
tiles out to a temporary folder, then de-duplicating the resulting detections before
|
|
10
|
+
merging them back into a set of detections that make sense on the original images.
|
|
11
|
+
|
|
12
|
+
This approach will likely fail to detect very large animals, so if you expect both large
|
|
13
|
+
and small animals (in terms of pixel size), this script is best used in
|
|
14
|
+
conjunction with a traditional inference pass that looks at whole images.
|
|
15
|
+
|
|
16
|
+
Currently requires temporary storage at least as large as the input data, generally
|
|
17
|
+
a lot more than that (depending on the overlap between adjacent tiles). This is
|
|
18
|
+
inefficient, but easy to debug.
|
|
19
|
+
|
|
20
|
+
Programmatic invocation supports using YOLOv5's inference scripts (and test-time
|
|
21
|
+
augmentation); the command-line interface only supports standard inference right now.
|
|
22
|
+
|
|
23
|
+
"""
|
|
22
24
|
|
|
23
25
|
#%% Imports and constants
|
|
24
26
|
|
|
@@ -54,17 +56,24 @@ parallelization_uses_threads = False
|
|
|
54
56
|
|
|
55
57
|
def get_patch_boundaries(image_size,patch_size,patch_stride=None):
|
|
56
58
|
"""
|
|
57
|
-
|
|
58
|
-
and a stride (x,y)
|
|
59
|
+
Computes a list of patch starting coordinates (x,y) given an image size (w,h)
|
|
60
|
+
and a stride (x,y)
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
as the stride relative to the patch size (0.1 == 10% stride).
|
|
62
|
-
|
|
63
|
-
Patch size is guaranteed, stride may deviate to make sure all pixels are covered.
|
|
62
|
+
Patch size is guaranteed, but the stride may deviate to make sure all pixels are covered.
|
|
64
63
|
I.e., we move by regular strides until the current patch walks off the right/bottom,
|
|
65
64
|
at which point it backs up to one patch from the end. So if your image is 15
|
|
66
65
|
pixels wide and you have a stride of 10 pixels, you will get starting positions
|
|
67
66
|
of 0 (from 0 to 9) and 5 (from 5 to 14).
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
image_size (tuple): size of the image you want to divide into patches, as a length-2 tuple (w,h)
|
|
70
|
+
patch_size (tuple): patch size into which you want to divide an image, as a length-2 tuple (w,h)
|
|
71
|
+
patch_stride (tuple or float, optional): stride between patches, as a length-2 tuple (x,y), or a
|
|
72
|
+
float; if this is a float, it's interpreted as the stride relative to the patch size
|
|
73
|
+
(0.1 == 10% stride). Defaults to half the patch size.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
list: list of length-2 tuples, each representing the x/y start position of a patch
|
|
68
77
|
"""
|
|
69
78
|
|
|
70
79
|
if patch_stride is None:
|
|
@@ -163,23 +172,50 @@ def get_patch_boundaries(image_size,patch_size,patch_stride=None):
|
|
|
163
172
|
|
|
164
173
|
|
|
165
174
|
def patch_info_to_patch_name(image_name,patch_x_min,patch_y_min):
|
|
175
|
+
"""
|
|
176
|
+
Gives a unique string name to an x/y coordinate, e.g. turns ("a.jpg",10,20) into
|
|
177
|
+
"a.jpg_0010_0020".
|
|
166
178
|
|
|
179
|
+
Args:
|
|
180
|
+
image_name (str): image identifier
|
|
181
|
+
patch_x_min (int): x coordinate
|
|
182
|
+
patch_y_min (int): y coordinate
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
str: name for this patch, e.g. "a.jpg_0010_0020"
|
|
186
|
+
"""
|
|
167
187
|
patch_name = image_name + '_' + \
|
|
168
188
|
str(patch_x_min).zfill(4) + '_' + str(patch_y_min).zfill(4)
|
|
169
189
|
return patch_name
|
|
170
190
|
|
|
171
191
|
|
|
172
|
-
def extract_patch_from_image(im,
|
|
173
|
-
|
|
192
|
+
def extract_patch_from_image(im,
|
|
193
|
+
patch_xy,
|
|
194
|
+
patch_size,
|
|
195
|
+
patch_image_fn=None,
|
|
196
|
+
patch_folder=None,
|
|
197
|
+
image_name=None,
|
|
198
|
+
overwrite=True):
|
|
174
199
|
"""
|
|
175
|
-
Extracts a patch from the provided image,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
200
|
+
Extracts a patch from the provided image, and writes that patch out to a new file.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
im (str or Image): image from which we should extract a patch, can be a filename or
|
|
204
|
+
a PIL Image object.
|
|
205
|
+
patch_xy (tuple): length-2 tuple of ints (x,y) representing the upper-left corner
|
|
206
|
+
of the patch to extract
|
|
207
|
+
patch_size (tuple): length-2 tuple of ints (w,h) representing the size of the
|
|
208
|
+
patch to extract
|
|
209
|
+
patch_image_fn (str, optional): image filename to write the patch to; if this is None
|
|
210
|
+
the filename will be generated from [image_name] and the patch coordinates
|
|
211
|
+
patch_folder (str, optional): folder in which the image lives; only used to generate
|
|
212
|
+
a patch filename, so only required if [patch_image_fn] is None
|
|
213
|
+
image_name (str, optional): the identifier of the source image; only used to generate
|
|
214
|
+
a patch filename, so only required if [patch_image_fn] is None
|
|
215
|
+
overwrite (bool, optional): whether to overwrite an existing patch image
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
dict: a dictionary with fields xmin,xmax,ymin,ymax,patch_fn
|
|
183
219
|
"""
|
|
184
220
|
|
|
185
221
|
if isinstance(im,str):
|
|
@@ -223,10 +259,20 @@ def extract_patch_from_image(im,patch_xy,patch_size,
|
|
|
223
259
|
|
|
224
260
|
return patch_info
|
|
225
261
|
|
|
262
|
+
# ...def extract_patch_from_image(...)
|
|
263
|
+
|
|
226
264
|
|
|
227
265
|
def in_place_nms(md_results, iou_thres=0.45, verbose=True):
|
|
228
266
|
"""
|
|
229
|
-
Run torch.ops.nms in-place on MD-formatted detection results
|
|
267
|
+
Run torch.ops.nms in-place on MD-formatted detection results.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
md_results (dict): detection results for a list of images, in MD results format (i.e.,
|
|
271
|
+
containing a list of image dicts with the key 'images', each of which has a list
|
|
272
|
+
of detections with the key 'detections')
|
|
273
|
+
iou_thres (float, optional): IoU threshold above which we will treat two detections as
|
|
274
|
+
redundant
|
|
275
|
+
verbose (bool, optional): enable additional debug console output
|
|
230
276
|
"""
|
|
231
277
|
|
|
232
278
|
n_detections_before = 0
|
|
@@ -343,7 +389,7 @@ def run_tiled_inference(model_file, image_folder, tiling_folder, output_file,
|
|
|
343
389
|
overwrite_tiles=True,
|
|
344
390
|
image_list=None):
|
|
345
391
|
"""
|
|
346
|
-
|
|
392
|
+
Runs inference using [model_file] on the images in [image_folder], fist splitting each image up
|
|
347
393
|
into tiles of size [tile_size_x] x [tile_size_y], writing those tiles to [tiling_folder],
|
|
348
394
|
then de-duplicating the results before merging them back into a set of detections that make
|
|
349
395
|
sense on the original images and writing those results to [output_file].
|
|
@@ -360,7 +406,32 @@ def run_tiled_inference(model_file, image_folder, tiling_folder, output_file,
|
|
|
360
406
|
|
|
361
407
|
if yolo_inference_options is supplied, it should be an instance of YoloInferenceOptions; in
|
|
362
408
|
this case the model will be run with run_inference_with_yolov5_val. This is typically used to
|
|
363
|
-
run the model with test-time augmentation.
|
|
409
|
+
run the model with test-time augmentation.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
model_file (str): model filename (ending in .pt), or a well-known model name (e.g. "MDV5A")
|
|
413
|
+
image_folder (str): the folder of images to proess (always recursive)
|
|
414
|
+
tiling_folder (str): folder for temporary tile storage; see caveats above
|
|
415
|
+
output_file (str): .json file to which we should write MD-formatted results
|
|
416
|
+
tile_size_x (int, optional): tile width
|
|
417
|
+
tile_size_y (int, optional): tile height
|
|
418
|
+
tile_overlap (float, optional): overlap between adjacenet tiles, as a fraction of the
|
|
419
|
+
tile size
|
|
420
|
+
checkpoint_path (str, optional): checkpoint path; passed directly to run_detector_batch; see
|
|
421
|
+
run_detector_batch for details
|
|
422
|
+
checkpoint_frequency (int, optional): checkpoint frequency; passed directly to run_detector_batch; see
|
|
423
|
+
run_detector_batch for details
|
|
424
|
+
remove_tiles (bool, optional): whether to delete the tiles when we're done
|
|
425
|
+
yolo_inference_options (YoloInferenceOptions, optional): if not None, will run inference with
|
|
426
|
+
run_inference_with_yolov5_val.py, rather than with run_detector_batch.py, using these options
|
|
427
|
+
n_patch_extraction_workers (int, optional): number of workers to use for patch extraction;
|
|
428
|
+
set to <= 1 to disable parallelization
|
|
429
|
+
image_list (list, optional): .json file containing a list of specific images to process. If
|
|
430
|
+
this is supplied, and the paths are absolute, [image_folder] will be ignored. If this is supplied,
|
|
431
|
+
and the paths are relative, they should be relative to [image_folder].
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
dict: MD-formatted results dictionary, identical to what's written to [output_file]
|
|
364
435
|
"""
|
|
365
436
|
|
|
366
437
|
##%% Validate arguments
|
detection/tf_detector.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
tf_detector.py
|
|
4
|
+
|
|
5
|
+
Module containing the class TFDetector, for loading and running a TensorFlow detection model.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
#%% Imports and constants
|
|
9
10
|
|
|
10
11
|
import numpy as np
|
|
11
12
|
|
|
@@ -18,36 +19,41 @@ print('TensorFlow version:', tf.__version__)
|
|
|
18
19
|
print('Is GPU available? tf.test.is_gpu_available:', tf.test.is_gpu_available())
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
#%% Classes
|
|
23
|
+
|
|
21
24
|
class TFDetector:
|
|
22
25
|
"""
|
|
23
26
|
A detector model loaded at the time of initialization. It is intended to be used with
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
TensorFlow-based versions of MegaDetector (v2, v3, or v4). If someone can find v1, I
|
|
28
|
+
suppose you could use this class for v1 also.
|
|
26
29
|
"""
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
#: TF versions of MD were trained with batch size of 1, and the resizing function is a
|
|
32
|
+
#: part of the inference graph, so this is fixed.
|
|
33
|
+
#:
|
|
34
|
+
#: :meta private:
|
|
30
35
|
BATCH_SIZE = 1
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
def __init__(self, model_path):
|
|
34
39
|
"""
|
|
35
|
-
Loads model from model_path and starts a tf.Session with this graph. Obtains
|
|
40
|
+
Loads a model from [model_path] and starts a tf.Session with this graph. Obtains
|
|
36
41
|
input and output tensor handles.
|
|
37
42
|
"""
|
|
38
43
|
|
|
39
44
|
detection_graph = TFDetector.__load_model(model_path)
|
|
40
45
|
self.tf_session = tf.Session(graph=detection_graph)
|
|
41
|
-
|
|
42
46
|
self.image_tensor = detection_graph.get_tensor_by_name('image_tensor:0')
|
|
43
47
|
self.box_tensor = detection_graph.get_tensor_by_name('detection_boxes:0')
|
|
44
48
|
self.score_tensor = detection_graph.get_tensor_by_name('detection_scores:0')
|
|
45
49
|
self.class_tensor = detection_graph.get_tensor_by_name('detection_classes:0')
|
|
46
50
|
|
|
51
|
+
|
|
47
52
|
@staticmethod
|
|
48
|
-
def
|
|
53
|
+
def __round_and_make_float(d, precision=4):
|
|
49
54
|
return truncate_float(float(d), precision=precision)
|
|
50
55
|
|
|
56
|
+
|
|
51
57
|
@staticmethod
|
|
52
58
|
def __convert_coords(tf_coords):
|
|
53
59
|
"""
|
|
@@ -70,9 +76,10 @@ class TFDetector:
|
|
|
70
76
|
|
|
71
77
|
# convert numpy floats to Python floats
|
|
72
78
|
for i, d in enumerate(new):
|
|
73
|
-
new[i] = TFDetector.
|
|
79
|
+
new[i] = TFDetector.__round_and_make_float(d, precision=COORD_DIGITS)
|
|
74
80
|
return new
|
|
75
81
|
|
|
82
|
+
|
|
76
83
|
@staticmethod
|
|
77
84
|
def __load_model(model_path):
|
|
78
85
|
"""
|
|
@@ -96,7 +103,12 @@ class TFDetector:
|
|
|
96
103
|
|
|
97
104
|
return detection_graph
|
|
98
105
|
|
|
106
|
+
|
|
99
107
|
def _generate_detections_one_image(self, image):
|
|
108
|
+
"""
|
|
109
|
+
Runs the detector on a single image.
|
|
110
|
+
"""
|
|
111
|
+
|
|
100
112
|
np_im = np.asarray(image, np.uint8)
|
|
101
113
|
im_w_batch_dim = np.expand_dims(np_im, axis=0)
|
|
102
114
|
|
|
@@ -111,30 +123,36 @@ class TFDetector:
|
|
|
111
123
|
|
|
112
124
|
return box_tensor_out, score_tensor_out, class_tensor_out
|
|
113
125
|
|
|
126
|
+
|
|
114
127
|
def generate_detections_one_image(self, image, image_id, detection_threshold, image_size=None,
|
|
115
128
|
skip_image_resizing=False):
|
|
116
129
|
"""
|
|
117
|
-
|
|
130
|
+
Runs the detector on an image.
|
|
118
131
|
|
|
119
132
|
Args:
|
|
120
|
-
image: the PIL Image object
|
|
121
|
-
image_id: a path to identify the image; will be in the "file" field of the output object
|
|
122
|
-
detection_threshold:
|
|
133
|
+
image (Image): the PIL Image object on which we should run the detector
|
|
134
|
+
image_id (str): a path to identify the image; will be in the "file" field of the output object
|
|
135
|
+
detection_threshold (float): only detections above this threshold will be included in the return
|
|
136
|
+
value
|
|
137
|
+
image_size (tuple, optional): image size to use for inference, only mess with this
|
|
138
|
+
if (a) you're using a model other than MegaDetector or (b) you know what you're
|
|
139
|
+
doing
|
|
140
|
+
skip_image_resizing (bool, optional): whether to skip internal image resizing (and rely on external
|
|
141
|
+
resizing)... not currently supported, but included here for compatibility with PTDetector.
|
|
123
142
|
|
|
124
143
|
Returns:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
- 'failure'
|
|
144
|
+
dict: a dictionary with the following fields:
|
|
145
|
+
- 'file' (filename, always present)
|
|
146
|
+
- 'max_detection_conf' (removed from MegaDetector output files by default, but generated here)
|
|
147
|
+
- 'detections' (a list of detection objects containing keys 'category', 'conf', and 'bbox')
|
|
148
|
+
- 'failure' (a failure string, or None if everything went fine)
|
|
131
149
|
"""
|
|
132
150
|
|
|
133
151
|
assert image_size is None, 'Image sizing not supported for TF detectors'
|
|
134
152
|
assert not skip_image_resizing, 'Image sizing not supported for TF detectors'
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
|
|
154
|
+
result = { 'file': image_id }
|
|
155
|
+
|
|
138
156
|
try:
|
|
139
157
|
b_box, b_score, b_class = self._generate_detections_one_image(image)
|
|
140
158
|
|
|
@@ -164,3 +182,7 @@ class TFDetector:
|
|
|
164
182
|
print('TFDetector: image {} failed during inference: {}'.format(image_id, str(e)))
|
|
165
183
|
|
|
166
184
|
return result
|
|
185
|
+
|
|
186
|
+
# ...def generate_detections_one_image(...)
|
|
187
|
+
|
|
188
|
+
# ...class TFDetector
|