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,1164 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
md_tests.py
|
|
4
|
+
|
|
5
|
+
A series of tests to validate basic repo functionality and verify either "correct"
|
|
6
|
+
inference behavior, or - when operating in environments other than the training
|
|
7
|
+
environment - acceptable deviation from the correct results.
|
|
8
|
+
|
|
9
|
+
This module should not depend on anything else in this repo outside of the
|
|
10
|
+
tests themselves, even if it means some duplicated code (e.g. for downloading files),
|
|
11
|
+
since much of what it tries to test is, e.g., imports.
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
#%% Imports and constants
|
|
16
|
+
|
|
17
|
+
### Only standard imports belong here, not MD-specific imports ###
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import json
|
|
21
|
+
import glob
|
|
22
|
+
import tempfile
|
|
23
|
+
import urllib
|
|
24
|
+
import urllib.request
|
|
25
|
+
import zipfile
|
|
26
|
+
import subprocess
|
|
27
|
+
import argparse
|
|
28
|
+
import inspect
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
#%% Classes
|
|
32
|
+
|
|
33
|
+
class MDTestOptions:
|
|
34
|
+
"""
|
|
35
|
+
Options controlling test behavior
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
|
|
40
|
+
## Required ##
|
|
41
|
+
|
|
42
|
+
#: Force CPU execution
|
|
43
|
+
self.disable_gpu = False
|
|
44
|
+
|
|
45
|
+
#: If GPU execution is requested, but a GPU is not available, should we error?
|
|
46
|
+
self.cpu_execution_is_error = False
|
|
47
|
+
|
|
48
|
+
#: Skip tests related to video processing
|
|
49
|
+
self.skip_video_tests = False
|
|
50
|
+
|
|
51
|
+
#: Skip tests launched via Python functions (as opposed to CLIs)
|
|
52
|
+
self.skip_python_tests = False
|
|
53
|
+
|
|
54
|
+
#: Skip CLI tests
|
|
55
|
+
self.skip_cli_tests = False
|
|
56
|
+
|
|
57
|
+
#: Force a specific folder for temporary input/output
|
|
58
|
+
self.scratch_dir = None
|
|
59
|
+
|
|
60
|
+
#: Where does the test data live?
|
|
61
|
+
self.test_data_url = 'https://lila.science/public/md-test-package.zip'
|
|
62
|
+
|
|
63
|
+
#: Download test data even if it appears to have already been downloaded
|
|
64
|
+
self.force_data_download = False
|
|
65
|
+
|
|
66
|
+
#: Unzip test data even if it appears to have already been unzipped
|
|
67
|
+
self.force_data_unzip = False
|
|
68
|
+
|
|
69
|
+
#: By default, any unexpected behavior is an error; this forces most errors to
|
|
70
|
+
#: be treated as warnings.
|
|
71
|
+
self.warning_mode = False
|
|
72
|
+
|
|
73
|
+
#: How much deviation from the expected detection coordinates should we allow before
|
|
74
|
+
#: a disrepancy becomes an error?
|
|
75
|
+
self.max_coord_error = 0.001
|
|
76
|
+
|
|
77
|
+
#: How much deviation from the expected confidence values should we allow before
|
|
78
|
+
#: a disrepancy becomes an error?
|
|
79
|
+
self.max_conf_error = 0.005
|
|
80
|
+
|
|
81
|
+
#: Current working directory when running CLI tests
|
|
82
|
+
self.cli_working_dir = None
|
|
83
|
+
|
|
84
|
+
#: YOLOv5 installation, only relevant if we're testing run_inference_with_yolov5_val.
|
|
85
|
+
#:
|
|
86
|
+
#: If this is None, we'll skip that test.
|
|
87
|
+
self.yolo_working_dir = None
|
|
88
|
+
|
|
89
|
+
#: fourcc code to use for video tests that involve rendering video
|
|
90
|
+
self.video_fourcc = 'mp4v'
|
|
91
|
+
|
|
92
|
+
#: Default model to use for testing (filename, URL, or well-known model string)
|
|
93
|
+
self.default_model = 'MDV5A'
|
|
94
|
+
|
|
95
|
+
#: For comparison tests, use a model that produces slightly different output
|
|
96
|
+
self.alt_model = 'MDV5B'
|
|
97
|
+
|
|
98
|
+
#: PYTHONPATH to set for CLI tests; if None, inherits from the parent process. Only
|
|
99
|
+
#: impacts the called functions, not the parent process.
|
|
100
|
+
self.cli_test_pythonpath = None
|
|
101
|
+
|
|
102
|
+
# ...class MDTestOptions()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
#%% Support functions
|
|
106
|
+
|
|
107
|
+
def get_expected_results_filename(gpu_is_available):
|
|
108
|
+
"""
|
|
109
|
+
Expected results vary just a little across inference environments, particularly
|
|
110
|
+
between PT 1.x and 2.x, so when making sure things are working acceptably, we
|
|
111
|
+
compare to a reference file that matches the current environment.
|
|
112
|
+
|
|
113
|
+
This function gets the correct filename to compare to current results, depending
|
|
114
|
+
on whether a GPU is available.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
gpu_is_available (bool): whether a GPU is available
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
str: relative filename of the results file we should use (within the test
|
|
121
|
+
data zipfile)
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
if gpu_is_available:
|
|
125
|
+
hw_string = 'gpu'
|
|
126
|
+
else:
|
|
127
|
+
hw_string = 'cpu'
|
|
128
|
+
import torch
|
|
129
|
+
torch_version = str(torch.__version__)
|
|
130
|
+
if torch_version.startswith('1'):
|
|
131
|
+
assert torch_version == '1.10.1', 'Only tested against PT 1.10.1 and PT 2.x'
|
|
132
|
+
pt_string = 'pt1.10.1'
|
|
133
|
+
else:
|
|
134
|
+
assert torch_version.startswith('2'), 'Unknown torch version: {}'.format(torch_version)
|
|
135
|
+
pt_string = 'pt2.x'
|
|
136
|
+
|
|
137
|
+
# A hack for now to account for the fact that even with acceleration enabled and PT2
|
|
138
|
+
# installed, Apple silicon appears to provide the same results as CPU/PT1 inference
|
|
139
|
+
try:
|
|
140
|
+
import torch
|
|
141
|
+
m1_inference = torch.backends.mps.is_built and torch.backends.mps.is_available()
|
|
142
|
+
if m1_inference:
|
|
143
|
+
print('I appear to be running on M1/M2 hardware')
|
|
144
|
+
hw_string = 'cpu'
|
|
145
|
+
pt_string = 'pt1.10.1'
|
|
146
|
+
except Exception:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
return 'md-test-results-{}-{}.json'.format(hw_string,pt_string)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def download_test_data(options=None):
|
|
153
|
+
"""
|
|
154
|
+
Downloads the test zipfile if necessary, unzips if necessary.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
options (MDTestOptions, optional): see MDTestOptions for details
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
MDTestOptions: the same object passed in as input, or the options that
|
|
161
|
+
were used if [options] was supplied as None
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
if options is None:
|
|
165
|
+
options = MDTestOptions()
|
|
166
|
+
|
|
167
|
+
if options.scratch_dir is None:
|
|
168
|
+
tempdir_base = tempfile.gettempdir()
|
|
169
|
+
scratch_dir = os.path.join(tempdir_base,'md-tests')
|
|
170
|
+
else:
|
|
171
|
+
scratch_dir = options.scratch_dir
|
|
172
|
+
|
|
173
|
+
os.makedirs(scratch_dir,exist_ok=True)
|
|
174
|
+
|
|
175
|
+
# See whether we've already downloaded the data zipfile
|
|
176
|
+
download_zipfile = True
|
|
177
|
+
if not options.force_data_download:
|
|
178
|
+
local_zipfile = os.path.join(scratch_dir,options.test_data_url.split('/')[-1])
|
|
179
|
+
if os.path.isfile(local_zipfile):
|
|
180
|
+
url_info = urllib.request.urlopen(options.test_data_url).info()
|
|
181
|
+
remote_size = int(url_info['Content-Length'])
|
|
182
|
+
target_file_size = os.path.getsize(local_zipfile)
|
|
183
|
+
if remote_size == target_file_size:
|
|
184
|
+
download_zipfile = False
|
|
185
|
+
|
|
186
|
+
if download_zipfile:
|
|
187
|
+
print('Downloading test data zipfile')
|
|
188
|
+
urllib.request.urlretrieve(options.test_data_url, local_zipfile)
|
|
189
|
+
print('Finished download to {}'.format(local_zipfile))
|
|
190
|
+
else:
|
|
191
|
+
print('Bypassing test data zipfile download for {}'.format(local_zipfile))
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
## Unzip data
|
|
195
|
+
|
|
196
|
+
zipf = zipfile.ZipFile(local_zipfile)
|
|
197
|
+
zip_contents = zipf.filelist
|
|
198
|
+
|
|
199
|
+
# file_info = zip_contents[1]
|
|
200
|
+
for file_info in zip_contents:
|
|
201
|
+
|
|
202
|
+
expected_size = file_info.file_size
|
|
203
|
+
if expected_size == 0:
|
|
204
|
+
continue
|
|
205
|
+
fn_relative = file_info.filename
|
|
206
|
+
target_file = os.path.join(scratch_dir,fn_relative)
|
|
207
|
+
unzip_file = True
|
|
208
|
+
if (not options.force_data_unzip) and os.path.isfile(target_file):
|
|
209
|
+
existing_file_size = os.path.getsize(target_file)
|
|
210
|
+
if existing_file_size == expected_size:
|
|
211
|
+
unzip_file = False
|
|
212
|
+
if unzip_file:
|
|
213
|
+
os.makedirs(os.path.dirname(target_file),exist_ok=True)
|
|
214
|
+
with open(target_file,'wb') as f:
|
|
215
|
+
f.write(zipf.read(fn_relative))
|
|
216
|
+
|
|
217
|
+
# ...for each file in the zipfile
|
|
218
|
+
|
|
219
|
+
# Warn if file are present that aren't expected
|
|
220
|
+
test_files = glob.glob(os.path.join(scratch_dir,'**/*'), recursive=True)
|
|
221
|
+
test_files = [os.path.relpath(fn,scratch_dir).replace('\\','/') for fn in test_files]
|
|
222
|
+
test_files_set = set(test_files)
|
|
223
|
+
expected_images_set = set(zipf.namelist())
|
|
224
|
+
for fn in expected_images_set:
|
|
225
|
+
if fn.endswith('/'):
|
|
226
|
+
continue
|
|
227
|
+
assert fn in test_files_set, 'File {} is missing from the test image folder'.format(fn)
|
|
228
|
+
|
|
229
|
+
# Populate the test options with test data information
|
|
230
|
+
options.scratch_dir = scratch_dir
|
|
231
|
+
options.all_test_files = test_files
|
|
232
|
+
options.test_images = [fn for fn in test_files if os.path.splitext(fn.lower())[1] in ('.jpg','.jpeg','.png')]
|
|
233
|
+
options.test_videos = [fn for fn in test_files if os.path.splitext(fn.lower())[1] in ('.mp4','.avi')]
|
|
234
|
+
options.test_videos = [fn for fn in options.test_videos if 'rendered' not in fn]
|
|
235
|
+
options.test_videos = [fn for fn in options.test_videos if \
|
|
236
|
+
os.path.isfile(os.path.join(scratch_dir,fn))]
|
|
237
|
+
|
|
238
|
+
print('Finished unzipping and enumerating test data')
|
|
239
|
+
|
|
240
|
+
return options
|
|
241
|
+
|
|
242
|
+
# ...def download_test_data(...)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def is_gpu_available(verbose=True):
|
|
246
|
+
"""
|
|
247
|
+
Checks whether a GPU (including M1/M2 MPS) is available.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
verbose (bool, optional): enable additional debug console output
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
bool: whether a GPU is available
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
# Import torch inside this function, so we have a chance to set CUDA_VISIBLE_DEVICES
|
|
257
|
+
# before checking GPU availability.
|
|
258
|
+
import torch
|
|
259
|
+
gpu_available = torch.cuda.is_available()
|
|
260
|
+
|
|
261
|
+
if gpu_available:
|
|
262
|
+
if verbose:
|
|
263
|
+
print('CUDA available: {}'.format(gpu_available))
|
|
264
|
+
device_ids = list(range(torch.cuda.device_count()))
|
|
265
|
+
if len(device_ids) > 1:
|
|
266
|
+
print('Found multiple devices: {}'.format(str(device_ids)))
|
|
267
|
+
else:
|
|
268
|
+
try:
|
|
269
|
+
gpu_available = torch.backends.mps.is_built and torch.backends.mps.is_available()
|
|
270
|
+
except AttributeError:
|
|
271
|
+
pass
|
|
272
|
+
if gpu_available:
|
|
273
|
+
print('Metal performance shaders available')
|
|
274
|
+
|
|
275
|
+
if not gpu_available:
|
|
276
|
+
print('No GPU available')
|
|
277
|
+
|
|
278
|
+
return gpu_available
|
|
279
|
+
|
|
280
|
+
# ...def is_gpu_available(...)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def output_files_are_identical(fn1,fn2,verbose=False):
|
|
284
|
+
"""
|
|
285
|
+
Checks whether two MD-formatted output files are identical other than file sorting.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
fn1 (str): the first filename to compare
|
|
289
|
+
fn2 (str): the second filename to compare
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
bool: whether [fn1] and [fn2] are identical other than file sorting.
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
if verbose:
|
|
296
|
+
print('Comparing {} to {}'.format(fn1,fn2))
|
|
297
|
+
|
|
298
|
+
with open(fn1,'r') as f:
|
|
299
|
+
fn1_results = json.load(f)
|
|
300
|
+
fn1_results['images'] = \
|
|
301
|
+
sorted(fn1_results['images'], key=lambda d: d['file'])
|
|
302
|
+
|
|
303
|
+
with open(fn2,'r') as f:
|
|
304
|
+
fn2_results = json.load(f)
|
|
305
|
+
fn2_results['images'] = \
|
|
306
|
+
sorted(fn2_results['images'], key=lambda d: d['file'])
|
|
307
|
+
|
|
308
|
+
if len(fn1_results['images']) != len(fn1_results['images']):
|
|
309
|
+
if verbose:
|
|
310
|
+
print('{} images in {}, {} images in {}'.format(
|
|
311
|
+
len(fn1_results['images']),fn1,
|
|
312
|
+
len(fn2_results['images']),fn2))
|
|
313
|
+
return False
|
|
314
|
+
|
|
315
|
+
for i_image,fn1_image in enumerate(fn1_results['images']):
|
|
316
|
+
|
|
317
|
+
fn2_image = fn2_results['images'][i_image]
|
|
318
|
+
|
|
319
|
+
if fn1_image['file'] != fn2_image['file']:
|
|
320
|
+
if verbose:
|
|
321
|
+
print('Filename difference: {} vs {} '.format(fn1_image['file'],fn1_image['file']))
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
if fn1_image != fn2_image:
|
|
325
|
+
if verbose:
|
|
326
|
+
print('Image-level difference in image {}'.format(fn1_image['file']))
|
|
327
|
+
return False
|
|
328
|
+
|
|
329
|
+
return True
|
|
330
|
+
|
|
331
|
+
# ...def output_files_are_identical(...)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _args_to_object(args, obj):
|
|
335
|
+
"""
|
|
336
|
+
Copies all fields from a Namespace (typically the output from parse_args) to an
|
|
337
|
+
object. Skips fields starting with _. Does not check existence in the target
|
|
338
|
+
object.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
args (argparse.Namespace): the namespace to convert to an object
|
|
342
|
+
obj (object): object whose whose attributes will be updated
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
object: the modified object (modified in place, but also returned)
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
for n, v in inspect.getmembers(args):
|
|
349
|
+
if not n.startswith('_'):
|
|
350
|
+
setattr(obj, n, v)
|
|
351
|
+
|
|
352
|
+
return obj
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
#%% CLI functions
|
|
356
|
+
|
|
357
|
+
# These are copied from process_utils.py to avoid imports outside of the test
|
|
358
|
+
# functions.
|
|
359
|
+
|
|
360
|
+
os.environ["PYTHONUNBUFFERED"] = "1"
|
|
361
|
+
|
|
362
|
+
def execute(cmd):
|
|
363
|
+
"""
|
|
364
|
+
Runs [cmd] (a single string) in a shell, yielding each line of output to the caller.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
cmd (str): command to run
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
int: the command's return code, always zero, otherwise a CalledProcessError is raised
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
# https://stackoverflow.com/questions/4417546/constantly-print-subprocess-output-while-process-is-running
|
|
374
|
+
popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
375
|
+
shell=True, universal_newlines=True)
|
|
376
|
+
for stdout_line in iter(popen.stdout.readline, ""):
|
|
377
|
+
yield stdout_line
|
|
378
|
+
popen.stdout.close()
|
|
379
|
+
return_code = popen.wait()
|
|
380
|
+
if return_code:
|
|
381
|
+
raise subprocess.CalledProcessError(return_code, cmd)
|
|
382
|
+
return return_code
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def execute_and_print(cmd,print_output=True,catch_exceptions=False):
|
|
386
|
+
"""
|
|
387
|
+
Runs [cmd] (a single string) in a shell, capturing (and optionally printing) output.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
cmd (str): command to run
|
|
391
|
+
print_output (bool, optional): whether to print output from [cmd]
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
dict: a dictionary with fields "status" (the process return code) and "output"
|
|
395
|
+
(the content of stdout)
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
to_return = {'status':'unknown','output':''}
|
|
399
|
+
output=[]
|
|
400
|
+
try:
|
|
401
|
+
for s in execute(cmd):
|
|
402
|
+
output.append(s)
|
|
403
|
+
if print_output:
|
|
404
|
+
print(s,end='',flush=True)
|
|
405
|
+
to_return['status'] = 0
|
|
406
|
+
except subprocess.CalledProcessError as cpe:
|
|
407
|
+
if not catch_exceptions:
|
|
408
|
+
raise
|
|
409
|
+
print('execute_and_print caught error: {}'.format(cpe.output))
|
|
410
|
+
to_return['status'] = cpe.returncode
|
|
411
|
+
to_return['output'] = output
|
|
412
|
+
|
|
413
|
+
return to_return
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
#%% Python tests
|
|
417
|
+
|
|
418
|
+
def run_python_tests(options):
|
|
419
|
+
"""
|
|
420
|
+
Runs Python-based (as opposed to CLI-based) package tests.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
options (MDTestOptions): see MDTestOptions for details
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
print('\n*** Starting module tests ***\n')
|
|
427
|
+
|
|
428
|
+
## Prepare data
|
|
429
|
+
|
|
430
|
+
download_test_data(options)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
## Run inference on an image
|
|
434
|
+
|
|
435
|
+
from megadetector.detection import run_detector
|
|
436
|
+
from megadetector.visualization import visualization_utils as vis_utils
|
|
437
|
+
image_fn = os.path.join(options.scratch_dir,options.test_images[0])
|
|
438
|
+
model = run_detector.load_detector(options.default_model)
|
|
439
|
+
pil_im = vis_utils.load_image(image_fn)
|
|
440
|
+
result = model.generate_detections_one_image(pil_im) # noqa
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
## Run inference on a folder
|
|
444
|
+
|
|
445
|
+
from megadetector.detection.run_detector_batch import load_and_run_detector_batch,write_results_to_file
|
|
446
|
+
from megadetector.utils import path_utils
|
|
447
|
+
|
|
448
|
+
image_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
449
|
+
assert os.path.isdir(image_folder), 'Test image folder {} is not available'.format(image_folder)
|
|
450
|
+
inference_output_file = os.path.join(options.scratch_dir,'folder_inference_output.json')
|
|
451
|
+
image_file_names = path_utils.find_images(image_folder,recursive=True)
|
|
452
|
+
results = load_and_run_detector_batch(options.default_model, image_file_names, quiet=True)
|
|
453
|
+
_ = write_results_to_file(results,inference_output_file,
|
|
454
|
+
relative_path_base=image_folder,detector_file=options.default_model)
|
|
455
|
+
|
|
456
|
+
# Read results
|
|
457
|
+
with open(inference_output_file,'r') as f:
|
|
458
|
+
results_from_file = json.load(f) # noqa
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
## Verify results
|
|
462
|
+
|
|
463
|
+
# Read expected results
|
|
464
|
+
expected_results_filename = get_expected_results_filename(is_gpu_available(verbose=False))
|
|
465
|
+
|
|
466
|
+
with open(os.path.join(options.scratch_dir,expected_results_filename),'r') as f:
|
|
467
|
+
expected_results = json.load(f)
|
|
468
|
+
|
|
469
|
+
filename_to_results = {im['file'].replace('\\','/'):im for im in results_from_file['images']}
|
|
470
|
+
filename_to_results_expected = {im['file'].replace('\\','/'):im for im in expected_results['images']}
|
|
471
|
+
|
|
472
|
+
assert len(filename_to_results) == len(filename_to_results_expected), \
|
|
473
|
+
'Error: expected {} files in results, found {}'.format(
|
|
474
|
+
len(filename_to_results_expected),
|
|
475
|
+
len(filename_to_results))
|
|
476
|
+
|
|
477
|
+
max_coord_error = 0
|
|
478
|
+
max_conf_error = 0
|
|
479
|
+
|
|
480
|
+
# fn = next(iter(filename_to_results.keys()))
|
|
481
|
+
for fn in filename_to_results.keys():
|
|
482
|
+
|
|
483
|
+
actual_image_results = filename_to_results[fn]
|
|
484
|
+
expected_image_results = filename_to_results_expected[fn]
|
|
485
|
+
|
|
486
|
+
if 'failure' in actual_image_results:
|
|
487
|
+
assert 'failure' in expected_image_results and \
|
|
488
|
+
'detections' not in actual_image_results and \
|
|
489
|
+
'detections' not in expected_image_results
|
|
490
|
+
continue
|
|
491
|
+
assert 'failure' not in expected_image_results
|
|
492
|
+
|
|
493
|
+
actual_detections = actual_image_results['detections']
|
|
494
|
+
expected_detections = expected_image_results['detections']
|
|
495
|
+
|
|
496
|
+
s = 'expected {} detections for file {}, found {}'.format(
|
|
497
|
+
len(expected_detections),fn,len(actual_detections))
|
|
498
|
+
s += '\nExpected results file: {}\nActual results file: {}'.format(
|
|
499
|
+
expected_results_filename,inference_output_file)
|
|
500
|
+
|
|
501
|
+
if options.warning_mode:
|
|
502
|
+
if len(actual_detections) != len(expected_detections):
|
|
503
|
+
print('Warning: {}'.format(s))
|
|
504
|
+
continue
|
|
505
|
+
assert len(actual_detections) == len(expected_detections), \
|
|
506
|
+
'Error: {}'.format(s)
|
|
507
|
+
|
|
508
|
+
# i_det = 0
|
|
509
|
+
for i_det in range(0,len(actual_detections)):
|
|
510
|
+
actual_det = actual_detections[i_det]
|
|
511
|
+
expected_det = expected_detections[i_det]
|
|
512
|
+
assert actual_det['category'] == expected_det['category']
|
|
513
|
+
conf_err = abs(actual_det['conf'] - expected_det['conf'])
|
|
514
|
+
coord_differences = []
|
|
515
|
+
for i_coord in range(0,4):
|
|
516
|
+
coord_differences.append(abs(actual_det['bbox'][i_coord]-expected_det['bbox'][i_coord]))
|
|
517
|
+
coord_err = max(coord_differences)
|
|
518
|
+
|
|
519
|
+
if conf_err > max_conf_error:
|
|
520
|
+
max_conf_error = conf_err
|
|
521
|
+
if coord_err > max_coord_error:
|
|
522
|
+
max_coord_error = coord_err
|
|
523
|
+
|
|
524
|
+
# ...for each detection
|
|
525
|
+
|
|
526
|
+
# ...for each image
|
|
527
|
+
|
|
528
|
+
if not options.warning_mode:
|
|
529
|
+
|
|
530
|
+
assert max_conf_error <= options.max_conf_error, \
|
|
531
|
+
'Confidence error {} is greater than allowable ({})'.format(
|
|
532
|
+
max_conf_error,options.max_conf_error)
|
|
533
|
+
|
|
534
|
+
assert max_coord_error <= options.max_coord_error, \
|
|
535
|
+
'Coord error {} is greater than allowable ({})'.format(
|
|
536
|
+
max_coord_error,options.max_coord_error)
|
|
537
|
+
|
|
538
|
+
print('Max conf error: {}'.format(max_conf_error))
|
|
539
|
+
print('Max coord error: {}'.format(max_coord_error))
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
## Postprocess results
|
|
543
|
+
|
|
544
|
+
from megadetector.postprocessing.postprocess_batch_results import \
|
|
545
|
+
PostProcessingOptions,process_batch_results
|
|
546
|
+
postprocessing_options = PostProcessingOptions()
|
|
547
|
+
|
|
548
|
+
postprocessing_options.md_results_file = inference_output_file
|
|
549
|
+
postprocessing_options.output_dir = os.path.join(options.scratch_dir,'postprocessing_output')
|
|
550
|
+
postprocessing_options.image_base_dir = image_folder
|
|
551
|
+
|
|
552
|
+
postprocessing_results = process_batch_results(postprocessing_options)
|
|
553
|
+
assert os.path.isfile(postprocessing_results.output_html_file), \
|
|
554
|
+
'Postprocessing output file {} not found'.format(postprocessing_results.output_html_file)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
## Partial RDE test
|
|
558
|
+
|
|
559
|
+
from megadetector.postprocessing.repeat_detection_elimination.repeat_detections_core import \
|
|
560
|
+
RepeatDetectionOptions, find_repeat_detections
|
|
561
|
+
|
|
562
|
+
rde_options = RepeatDetectionOptions()
|
|
563
|
+
rde_options.occurrenceThreshold = 2
|
|
564
|
+
rde_options.confidenceMin = 0.001
|
|
565
|
+
rde_options.outputBase = os.path.join(options.scratch_dir,'rde_working_dir')
|
|
566
|
+
rde_options.imageBase = image_folder
|
|
567
|
+
rde_output_file = inference_output_file.replace('.json','_filtered.json')
|
|
568
|
+
assert rde_output_file != inference_output_file
|
|
569
|
+
rde_results = find_repeat_detections(inference_output_file, rde_output_file, rde_options)
|
|
570
|
+
assert os.path.isfile(rde_results.filterFile),\
|
|
571
|
+
'Could not find RDE output file {}'.format(rde_results.filterFile)
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
## Run inference on a folder (with YOLOv5 val script)
|
|
575
|
+
|
|
576
|
+
if options.yolo_working_dir is None:
|
|
577
|
+
|
|
578
|
+
print('Skipping YOLO val inference tests, no YOLO folder supplied')
|
|
579
|
+
|
|
580
|
+
else:
|
|
581
|
+
|
|
582
|
+
from megadetector.detection.run_inference_with_yolov5_val import \
|
|
583
|
+
YoloInferenceOptions, run_inference_with_yolo_val
|
|
584
|
+
|
|
585
|
+
inference_output_file_yolo_val = os.path.join(options.scratch_dir,'folder_inference_output_yolo_val.json')
|
|
586
|
+
|
|
587
|
+
yolo_inference_options = YoloInferenceOptions()
|
|
588
|
+
yolo_inference_options.input_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
589
|
+
yolo_inference_options.output_file = inference_output_file_yolo_val
|
|
590
|
+
yolo_inference_options.yolo_working_folder = options.yolo_working_dir
|
|
591
|
+
yolo_inference_options.model_filename = options.default_model
|
|
592
|
+
yolo_inference_options.augment = False
|
|
593
|
+
yolo_inference_options.overwrite_handling = 'overwrite'
|
|
594
|
+
|
|
595
|
+
run_inference_with_yolo_val(yolo_inference_options)
|
|
596
|
+
|
|
597
|
+
# Run again, without symlinks this time
|
|
598
|
+
|
|
599
|
+
from megadetector.utils.path_utils import insert_before_extension
|
|
600
|
+
inference_output_file_yolo_val_no_links = insert_before_extension(inference_output_file_yolo_val,
|
|
601
|
+
'no-links')
|
|
602
|
+
yolo_inference_options.output_file = inference_output_file_yolo_val_no_links
|
|
603
|
+
yolo_inference_options.use_symlinks = False
|
|
604
|
+
run_inference_with_yolo_val(yolo_inference_options)
|
|
605
|
+
|
|
606
|
+
# Run again, with chunked inference and symlinks
|
|
607
|
+
|
|
608
|
+
inference_output_file_yolo_val_checkpoints = insert_before_extension(inference_output_file_yolo_val,
|
|
609
|
+
'checkpoints')
|
|
610
|
+
yolo_inference_options.output_file = inference_output_file_yolo_val_checkpoints
|
|
611
|
+
yolo_inference_options.use_symlinks = True
|
|
612
|
+
yolo_inference_options.checkpoint_frequency = 5
|
|
613
|
+
run_inference_with_yolo_val(yolo_inference_options)
|
|
614
|
+
|
|
615
|
+
# Run again, with chunked inference and no symlinks
|
|
616
|
+
|
|
617
|
+
inference_output_file_yolo_val_checkpoints_no_links = \
|
|
618
|
+
insert_before_extension(inference_output_file_yolo_val,'checkpoints-no-links')
|
|
619
|
+
yolo_inference_options.output_file = inference_output_file_yolo_val_checkpoints_no_links
|
|
620
|
+
yolo_inference_options.use_symlinks = False
|
|
621
|
+
yolo_inference_options.checkpoint_frequency = 5
|
|
622
|
+
run_inference_with_yolo_val(yolo_inference_options)
|
|
623
|
+
|
|
624
|
+
fn1 = inference_output_file_yolo_val
|
|
625
|
+
|
|
626
|
+
output_files_to_compare = [
|
|
627
|
+
inference_output_file_yolo_val_no_links,
|
|
628
|
+
inference_output_file_yolo_val_checkpoints,
|
|
629
|
+
inference_output_file_yolo_val_checkpoints_no_links
|
|
630
|
+
]
|
|
631
|
+
|
|
632
|
+
for fn2 in output_files_to_compare:
|
|
633
|
+
assert output_files_are_identical(fn1, fn2, verbose=True)
|
|
634
|
+
|
|
635
|
+
# ...if we need to run the YOLO val inference tests
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
if not options.skip_video_tests:
|
|
639
|
+
|
|
640
|
+
## Video test (single video)
|
|
641
|
+
|
|
642
|
+
from megadetector.detection.process_video import ProcessVideoOptions, process_video
|
|
643
|
+
|
|
644
|
+
video_options = ProcessVideoOptions()
|
|
645
|
+
video_options.model_file = options.default_model
|
|
646
|
+
video_options.input_video_file = os.path.join(options.scratch_dir,options.test_videos[0])
|
|
647
|
+
video_options.output_json_file = os.path.join(options.scratch_dir,'single_video_output.json')
|
|
648
|
+
video_options.output_video_file = os.path.join(options.scratch_dir,'video_scratch/rendered_video.mp4')
|
|
649
|
+
video_options.frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder')
|
|
650
|
+
video_options.frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder')
|
|
651
|
+
video_options.render_output_video = True
|
|
652
|
+
# video_options.keep_rendered_frames = False
|
|
653
|
+
# video_options.keep_rendered_frames = False
|
|
654
|
+
video_options.force_extracted_frame_folder_deletion = True
|
|
655
|
+
video_options.force_rendered_frame_folder_deletion = True
|
|
656
|
+
# video_options.reuse_results_if_available = False
|
|
657
|
+
# video_options.reuse_frames_if_available = False
|
|
658
|
+
video_options.recursive = True
|
|
659
|
+
video_options.verbose = False
|
|
660
|
+
video_options.fourcc = options.video_fourcc
|
|
661
|
+
# video_options.rendering_confidence_threshold = None
|
|
662
|
+
# video_options.json_confidence_threshold = 0.005
|
|
663
|
+
video_options.frame_sample = 5
|
|
664
|
+
video_options.n_cores = 5
|
|
665
|
+
# video_options.debug_max_frames = -1
|
|
666
|
+
# video_options.class_mapping_filename = None
|
|
667
|
+
|
|
668
|
+
_ = process_video(video_options)
|
|
669
|
+
|
|
670
|
+
assert os.path.isfile(video_options.output_video_file), \
|
|
671
|
+
'Python video test failed to render output video file'
|
|
672
|
+
assert os.path.isfile(video_options.output_json_file), \
|
|
673
|
+
'Python video test failed to render output .json file'
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
## Video test (folder)
|
|
677
|
+
|
|
678
|
+
from megadetector.detection.process_video import ProcessVideoOptions, process_video_folder
|
|
679
|
+
|
|
680
|
+
video_options = ProcessVideoOptions()
|
|
681
|
+
video_options.model_file = options.default_model
|
|
682
|
+
video_options.input_video_file = os.path.join(options.scratch_dir,
|
|
683
|
+
os.path.dirname(options.test_videos[0]))
|
|
684
|
+
video_options.output_json_file = os.path.join(options.scratch_dir,'video_folder_output.json')
|
|
685
|
+
# video_options.output_video_file = None
|
|
686
|
+
video_options.frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder')
|
|
687
|
+
video_options.frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder')
|
|
688
|
+
video_options.render_output_video = False
|
|
689
|
+
# video_options.keep_rendered_frames = False
|
|
690
|
+
# video_options.keep_rendered_frames = False
|
|
691
|
+
video_options.force_extracted_frame_folder_deletion = True
|
|
692
|
+
video_options.force_rendered_frame_folder_deletion = True
|
|
693
|
+
# video_options.reuse_results_if_available = False
|
|
694
|
+
# video_options.reuse_frames_if_available = False
|
|
695
|
+
video_options.recursive = True
|
|
696
|
+
video_options.verbose = True
|
|
697
|
+
video_options.fourcc = options.video_fourcc
|
|
698
|
+
# video_options.rendering_confidence_threshold = None
|
|
699
|
+
# video_options.json_confidence_threshold = 0.005
|
|
700
|
+
video_options.frame_sample = 5
|
|
701
|
+
video_options.n_cores = 5
|
|
702
|
+
# video_options.debug_max_frames = -1
|
|
703
|
+
# video_options.class_mapping_filename = None
|
|
704
|
+
|
|
705
|
+
_ = process_video_folder(video_options)
|
|
706
|
+
|
|
707
|
+
assert os.path.isfile(video_options.output_json_file), \
|
|
708
|
+
'Python video test failed to render output .json file'
|
|
709
|
+
|
|
710
|
+
# ...if we're not skipping video tests
|
|
711
|
+
|
|
712
|
+
print('\n*** Finished module tests ***\n')
|
|
713
|
+
|
|
714
|
+
# ...def run_python_tests(...)
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
#%% Command-line tests
|
|
718
|
+
|
|
719
|
+
def run_cli_tests(options):
|
|
720
|
+
"""
|
|
721
|
+
Runs CLI (as opposed to Python-based) package tests.
|
|
722
|
+
|
|
723
|
+
Args:
|
|
724
|
+
options (MDTestOptions): see MDTestOptions for details
|
|
725
|
+
"""
|
|
726
|
+
|
|
727
|
+
print('\n*** Starting CLI tests ***\n')
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
## Environment management
|
|
731
|
+
|
|
732
|
+
if options.cli_test_pythonpath is not None:
|
|
733
|
+
os.environ['PYTHONPATH'] = options.cli_test_pythonpath
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
## chdir if necessary
|
|
737
|
+
|
|
738
|
+
if options.cli_working_dir is not None:
|
|
739
|
+
os.chdir(options.cli_working_dir)
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
## Prepare data
|
|
743
|
+
|
|
744
|
+
download_test_data(options)
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
## Run inference on an image
|
|
748
|
+
|
|
749
|
+
image_fn = os.path.join(options.scratch_dir,options.test_images[0])
|
|
750
|
+
output_dir = os.path.join(options.scratch_dir,'single_image_test')
|
|
751
|
+
if options.cli_working_dir is None:
|
|
752
|
+
cmd = 'python -m megadetector.detection.run_detector'
|
|
753
|
+
else:
|
|
754
|
+
cmd = 'python megadetector/detection/run_detector.py'
|
|
755
|
+
cmd += ' "{}" --image_file "{}" --output_dir "{}"'.format(
|
|
756
|
+
options.default_model,image_fn,output_dir)
|
|
757
|
+
print('Running: {}'.format(cmd))
|
|
758
|
+
cmd_results = execute_and_print(cmd)
|
|
759
|
+
|
|
760
|
+
if options.cpu_execution_is_error:
|
|
761
|
+
gpu_available_via_cli = False
|
|
762
|
+
for s in cmd_results['output']:
|
|
763
|
+
if 'GPU available: True' in s:
|
|
764
|
+
gpu_available_via_cli = True
|
|
765
|
+
break
|
|
766
|
+
if not gpu_available_via_cli:
|
|
767
|
+
raise Exception('GPU execution is required, but not available')
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
## Run inference on a folder
|
|
771
|
+
|
|
772
|
+
image_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
773
|
+
assert os.path.isdir(image_folder), 'Test image folder {} is not available'.format(image_folder)
|
|
774
|
+
inference_output_file = os.path.join(options.scratch_dir,'folder_inference_output.json')
|
|
775
|
+
if options.cli_working_dir is None:
|
|
776
|
+
cmd = 'python -m megadetector.detection.run_detector_batch'
|
|
777
|
+
else:
|
|
778
|
+
cmd = 'python megadetector/detection/run_detector_batch.py'
|
|
779
|
+
cmd += ' "{}" "{}" "{}" --recursive'.format(
|
|
780
|
+
options.default_model,image_folder,inference_output_file)
|
|
781
|
+
cmd += ' --output_relative_filenames --quiet --include_image_size'
|
|
782
|
+
cmd += ' --include_image_timestamp --include_exif_data'
|
|
783
|
+
print('Running: {}'.format(cmd))
|
|
784
|
+
cmd_results = execute_and_print(cmd)
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
## Run again with checkpointing enabled, make sure the results are the same
|
|
788
|
+
|
|
789
|
+
cmd += ' --checkpoint_frequency 5'
|
|
790
|
+
from megadetector.utils.path_utils import insert_before_extension
|
|
791
|
+
inference_output_file_checkpoint = insert_before_extension(inference_output_file,'_checkpoint')
|
|
792
|
+
assert inference_output_file_checkpoint != inference_output_file
|
|
793
|
+
cmd = cmd.replace(inference_output_file,inference_output_file_checkpoint)
|
|
794
|
+
print('Running: {}'.format(cmd))
|
|
795
|
+
cmd_results = execute_and_print(cmd)
|
|
796
|
+
|
|
797
|
+
assert output_files_are_identical(fn1=inference_output_file,
|
|
798
|
+
fn2=inference_output_file_checkpoint,verbose=True)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
## Postprocessing
|
|
802
|
+
|
|
803
|
+
postprocessing_output_dir = os.path.join(options.scratch_dir,'postprocessing_output_cli')
|
|
804
|
+
|
|
805
|
+
if options.cli_working_dir is None:
|
|
806
|
+
cmd = 'python -m megadetector.postprocessing.postprocess_batch_results'
|
|
807
|
+
else:
|
|
808
|
+
cmd = 'python megadetector/postprocessing/postprocess_batch_results.py'
|
|
809
|
+
cmd += ' "{}" "{}"'.format(
|
|
810
|
+
inference_output_file,postprocessing_output_dir)
|
|
811
|
+
cmd += ' --image_base_dir "{}"'.format(image_folder)
|
|
812
|
+
print('Running: {}'.format(cmd))
|
|
813
|
+
cmd_results = execute_and_print(cmd)
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
## RDE
|
|
817
|
+
|
|
818
|
+
rde_output_dir = os.path.join(options.scratch_dir,'rde_output_cli')
|
|
819
|
+
|
|
820
|
+
if options.cli_working_dir is None:
|
|
821
|
+
cmd = 'python -m megadetector.postprocessing.repeat_detection_elimination.find_repeat_detections'
|
|
822
|
+
else:
|
|
823
|
+
cmd = 'python megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py'
|
|
824
|
+
cmd += ' "{}"'.format(inference_output_file)
|
|
825
|
+
cmd += ' --imageBase "{}"'.format(image_folder)
|
|
826
|
+
cmd += ' --outputBase "{}"'.format(rde_output_dir)
|
|
827
|
+
cmd += ' --occurrenceThreshold 1' # Use an absurd number here to make sure we get some suspicious detections
|
|
828
|
+
print('Running: {}'.format(cmd))
|
|
829
|
+
cmd_results = execute_and_print(cmd)
|
|
830
|
+
|
|
831
|
+
# Find the latest filtering folder
|
|
832
|
+
filtering_output_dir = os.listdir(rde_output_dir)
|
|
833
|
+
filtering_output_dir = [fn for fn in filtering_output_dir if fn.startswith('filtering_')]
|
|
834
|
+
filtering_output_dir = [os.path.join(rde_output_dir,fn) for fn in filtering_output_dir]
|
|
835
|
+
filtering_output_dir = [fn for fn in filtering_output_dir if os.path.isdir(fn)]
|
|
836
|
+
filtering_output_dir = sorted(filtering_output_dir)[-1]
|
|
837
|
+
|
|
838
|
+
print('Using RDE filtering folder {}'.format(filtering_output_dir))
|
|
839
|
+
|
|
840
|
+
filtered_output_file = inference_output_file.replace('.json','_filtered.json')
|
|
841
|
+
|
|
842
|
+
if options.cli_working_dir is None:
|
|
843
|
+
cmd = 'python -m megadetector.postprocessing.repeat_detection_elimination.remove_repeat_detections'
|
|
844
|
+
else:
|
|
845
|
+
cmd = 'python megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py'
|
|
846
|
+
cmd += ' "{}" "{}" "{}"'.format(inference_output_file,filtered_output_file,filtering_output_dir)
|
|
847
|
+
print('Running: {}'.format(cmd))
|
|
848
|
+
cmd_results = execute_and_print(cmd)
|
|
849
|
+
|
|
850
|
+
assert os.path.isfile(filtered_output_file), \
|
|
851
|
+
'Could not find RDE output file {}'.format(filtered_output_file)
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
## Run inference on a folder (tiled)
|
|
855
|
+
|
|
856
|
+
image_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
857
|
+
tiling_folder = os.path.join(options.scratch_dir,'tiling-folder')
|
|
858
|
+
inference_output_file_tiled = os.path.join(options.scratch_dir,'folder_inference_output_tiled.json')
|
|
859
|
+
if options.cli_working_dir is None:
|
|
860
|
+
cmd = 'python -m megadetector.detection.run_tiled_inference'
|
|
861
|
+
else:
|
|
862
|
+
cmd = 'python megadetector/detection/run_tiled_inference.py'
|
|
863
|
+
cmd += ' "{}" "{}" "{}" "{}"'.format(
|
|
864
|
+
options.default_model,image_folder,tiling_folder,inference_output_file_tiled)
|
|
865
|
+
cmd += ' --overwrite_handling overwrite'
|
|
866
|
+
print('Running: {}'.format(cmd))
|
|
867
|
+
cmd_results = execute_and_print(cmd)
|
|
868
|
+
|
|
869
|
+
with open(inference_output_file_tiled,'r') as f:
|
|
870
|
+
results_from_file = json.load(f) # noqa
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
## Run inference on a folder (augmented)
|
|
874
|
+
|
|
875
|
+
if options.yolo_working_dir is None:
|
|
876
|
+
|
|
877
|
+
print('Bypassing YOLOv5 val tests, no yolo folder supplied')
|
|
878
|
+
|
|
879
|
+
else:
|
|
880
|
+
|
|
881
|
+
image_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
882
|
+
yolo_results_folder = os.path.join(options.scratch_dir,'yolo-output-folder')
|
|
883
|
+
yolo_symlink_folder = os.path.join(options.scratch_dir,'yolo-symlink_folder')
|
|
884
|
+
inference_output_file_yolo_val = os.path.join(options.scratch_dir,'folder_inference_output_yolo_val.json')
|
|
885
|
+
if options.cli_working_dir is None:
|
|
886
|
+
cmd = 'python -m megadetector.detection.run_inference_with_yolov5_val'
|
|
887
|
+
else:
|
|
888
|
+
cmd = 'python megadetector/detection/run_inference_with_yolov5_val.py'
|
|
889
|
+
cmd += ' "{}" "{}" "{}"'.format(
|
|
890
|
+
options.default_model,image_folder,inference_output_file_yolo_val)
|
|
891
|
+
cmd += ' --yolo_working_folder "{}"'.format(options.yolo_working_dir)
|
|
892
|
+
cmd += ' --yolo_results_folder "{}"'.format(yolo_results_folder)
|
|
893
|
+
cmd += ' --symlink_folder "{}"'.format(yolo_symlink_folder)
|
|
894
|
+
cmd += ' --augment_enabled 1'
|
|
895
|
+
# cmd += ' --no_use_symlinks'
|
|
896
|
+
cmd += ' --overwrite_handling overwrite'
|
|
897
|
+
print('Running: {}'.format(cmd))
|
|
898
|
+
cmd_results = execute_and_print(cmd)
|
|
899
|
+
|
|
900
|
+
# Run again with checkpointing, make sure the output are identical
|
|
901
|
+
cmd += ' --checkpoint_frequency 5'
|
|
902
|
+
inference_output_file_yolo_val_checkpoint = \
|
|
903
|
+
os.path.join(options.scratch_dir,'folder_inference_output_yolo_val_checkpoint.json')
|
|
904
|
+
assert inference_output_file_yolo_val_checkpoint != inference_output_file_yolo_val
|
|
905
|
+
cmd = cmd.replace(inference_output_file_yolo_val,inference_output_file_yolo_val_checkpoint)
|
|
906
|
+
cmd_results = execute_and_print(cmd)
|
|
907
|
+
|
|
908
|
+
assert output_files_are_identical(fn1=inference_output_file_yolo_val,
|
|
909
|
+
fn2=inference_output_file_yolo_val_checkpoint)
|
|
910
|
+
|
|
911
|
+
if not options.skip_video_tests:
|
|
912
|
+
|
|
913
|
+
## Video test
|
|
914
|
+
|
|
915
|
+
video_inference_output_file = os.path.join(options.scratch_dir,'video_inference_output.json')
|
|
916
|
+
output_video_file = os.path.join(options.scratch_dir,'video_scratch/cli_rendered_video.mp4')
|
|
917
|
+
frame_folder = os.path.join(options.scratch_dir,'video_scratch/frame_folder_cli')
|
|
918
|
+
frame_rendering_folder = os.path.join(options.scratch_dir,'video_scratch/rendered_frame_folder_cli')
|
|
919
|
+
|
|
920
|
+
video_fn = os.path.join(options.scratch_dir,options.test_videos[-1])
|
|
921
|
+
assert os.path.isfile(video_fn), 'Could not find video file {}'.format(video_fn)
|
|
922
|
+
|
|
923
|
+
output_dir = os.path.join(options.scratch_dir,'single_video_test_cli')
|
|
924
|
+
if options.cli_working_dir is None:
|
|
925
|
+
cmd = 'python -m megadetector.detection.process_video'
|
|
926
|
+
else:
|
|
927
|
+
cmd = 'python megadetector/detection/process_video.py'
|
|
928
|
+
cmd += ' "{}" "{}"'.format(options.default_model,video_fn)
|
|
929
|
+
cmd += ' --frame_folder "{}" --frame_rendering_folder "{}" --output_json_file "{}" --output_video_file "{}"'.format(
|
|
930
|
+
frame_folder,frame_rendering_folder,video_inference_output_file,output_video_file)
|
|
931
|
+
cmd += ' --render_output_video --fourcc {}'.format(options.video_fourcc)
|
|
932
|
+
cmd += ' --force_extracted_frame_folder_deletion --force_rendered_frame_folder_deletion --n_cores 5 --frame_sample 3'
|
|
933
|
+
cmd += ' --verbose'
|
|
934
|
+
print('Running: {}'.format(cmd))
|
|
935
|
+
cmd_results = execute_and_print(cmd)
|
|
936
|
+
|
|
937
|
+
# ...if we're not skipping video tests
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
## Run inference on a folder (with MDV5B, so we can do a comparison)
|
|
941
|
+
|
|
942
|
+
image_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
943
|
+
inference_output_file_alt = os.path.join(options.scratch_dir,'folder_inference_output_alt.json')
|
|
944
|
+
if options.cli_working_dir is None:
|
|
945
|
+
cmd = 'python -m megadetector.detection.run_detector_batch'
|
|
946
|
+
else:
|
|
947
|
+
cmd = 'python megadetector/detection/run_detector_batch.py'
|
|
948
|
+
cmd += ' "{}" "{}" "{}" --recursive'.format(
|
|
949
|
+
options.alt_model,image_folder,inference_output_file_alt)
|
|
950
|
+
cmd += ' --output_relative_filenames --quiet --include_image_size'
|
|
951
|
+
cmd += ' --include_image_timestamp --include_exif_data'
|
|
952
|
+
print('Running: {}'.format(cmd))
|
|
953
|
+
cmd_results = execute_and_print(cmd)
|
|
954
|
+
|
|
955
|
+
with open(inference_output_file_alt,'r') as f:
|
|
956
|
+
results_from_file = json.load(f) # noqa
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
## Compare the two files
|
|
960
|
+
|
|
961
|
+
comparison_output_folder = os.path.join(options.scratch_dir,'results_comparison')
|
|
962
|
+
image_folder = os.path.join(options.scratch_dir,'md-test-images')
|
|
963
|
+
results_files_string = '"{}" "{}"'.format(
|
|
964
|
+
inference_output_file,inference_output_file_alt)
|
|
965
|
+
if options.cli_working_dir is None:
|
|
966
|
+
cmd = 'python -m megadetector.postprocessing.compare_batch_results'
|
|
967
|
+
else:
|
|
968
|
+
cmd = 'python megadetector/postprocessing/compare_batch_results.py'
|
|
969
|
+
cmd += ' "{}" "{}" {}'.format(comparison_output_folder,image_folder,results_files_string)
|
|
970
|
+
print('Running: {}'.format(cmd))
|
|
971
|
+
cmd_results = execute_and_print(cmd)
|
|
972
|
+
|
|
973
|
+
assert cmd_results['status'] == 0, 'Error generating comparison HTML'
|
|
974
|
+
assert os.path.isfile(os.path.join(comparison_output_folder,'index.html')), \
|
|
975
|
+
'Failed to generate comparison HTML'
|
|
976
|
+
|
|
977
|
+
print('\n*** Finished CLI tests ***\n')
|
|
978
|
+
|
|
979
|
+
# ...def run_cli_tests(...)
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
#%% Main test wrapper
|
|
983
|
+
|
|
984
|
+
def run_tests(options):
|
|
985
|
+
"""
|
|
986
|
+
Runs Python-based and/or CLI-based package tests.
|
|
987
|
+
|
|
988
|
+
Args:
|
|
989
|
+
options (MDTestOptions): see MDTestOptions for details
|
|
990
|
+
"""
|
|
991
|
+
|
|
992
|
+
# Prepare data folder
|
|
993
|
+
download_test_data(options)
|
|
994
|
+
|
|
995
|
+
if options.disable_gpu:
|
|
996
|
+
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
|
|
997
|
+
|
|
998
|
+
# Verify GPU
|
|
999
|
+
gpu_available = is_gpu_available()
|
|
1000
|
+
|
|
1001
|
+
# If the GPU is required and isn't available, error
|
|
1002
|
+
if options.cpu_execution_is_error and (not gpu_available):
|
|
1003
|
+
raise ValueError('GPU not available, and cpu_execution_is_error is set')
|
|
1004
|
+
|
|
1005
|
+
# If the GPU should be disabled, verify that it is
|
|
1006
|
+
if options.disable_gpu:
|
|
1007
|
+
assert (not gpu_available), 'CPU execution specified, but the GPU appears to be available'
|
|
1008
|
+
|
|
1009
|
+
# Run python tests
|
|
1010
|
+
if not options.skip_python_tests:
|
|
1011
|
+
run_python_tests(options)
|
|
1012
|
+
|
|
1013
|
+
# Run CLI tests
|
|
1014
|
+
if not options.skip_cli_tests:
|
|
1015
|
+
run_cli_tests(options)
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
#%% Interactive driver
|
|
1019
|
+
|
|
1020
|
+
if False:
|
|
1021
|
+
|
|
1022
|
+
pass
|
|
1023
|
+
|
|
1024
|
+
#%%
|
|
1025
|
+
|
|
1026
|
+
options = MDTestOptions()
|
|
1027
|
+
|
|
1028
|
+
options.disable_gpu = False
|
|
1029
|
+
options.cpu_execution_is_error = False
|
|
1030
|
+
options.skip_video_tests = False
|
|
1031
|
+
options.skip_python_tests = False
|
|
1032
|
+
options.skip_cli_tests = False
|
|
1033
|
+
options.scratch_dir = None
|
|
1034
|
+
options.test_data_url = 'https://lila.science/public/md-test-package.zip'
|
|
1035
|
+
options.force_data_download = False
|
|
1036
|
+
options.force_data_unzip = False
|
|
1037
|
+
options.warning_mode = True
|
|
1038
|
+
options.max_coord_error = 0.001
|
|
1039
|
+
options.max_conf_error = 0.005
|
|
1040
|
+
options.cli_working_dir = r'c:\git\MegaDetector'
|
|
1041
|
+
options.yolo_working_dir = r'c:\git\yolov5-md'
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
#%%
|
|
1045
|
+
|
|
1046
|
+
run_tests(options)
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
#%% Command-line driver
|
|
1050
|
+
|
|
1051
|
+
def main():
|
|
1052
|
+
|
|
1053
|
+
options = MDTestOptions()
|
|
1054
|
+
|
|
1055
|
+
parser = argparse.ArgumentParser(
|
|
1056
|
+
description='MegaDetector test suite')
|
|
1057
|
+
|
|
1058
|
+
parser.add_argument(
|
|
1059
|
+
'--disable_gpu',
|
|
1060
|
+
action='store_true',
|
|
1061
|
+
help='Disable GPU operation')
|
|
1062
|
+
|
|
1063
|
+
parser.add_argument(
|
|
1064
|
+
'--cpu_execution_is_error',
|
|
1065
|
+
action='store_true',
|
|
1066
|
+
help='Fail if the GPU appears not to be available')
|
|
1067
|
+
|
|
1068
|
+
parser.add_argument(
|
|
1069
|
+
'--scratch_dir',
|
|
1070
|
+
default=None,
|
|
1071
|
+
type=str,
|
|
1072
|
+
help='Directory for temporary storage (defaults to system temp dir)')
|
|
1073
|
+
|
|
1074
|
+
parser.add_argument(
|
|
1075
|
+
'--skip_video_tests',
|
|
1076
|
+
action='store_true',
|
|
1077
|
+
help='Skip tests related to video (which can be slow)')
|
|
1078
|
+
|
|
1079
|
+
parser.add_argument(
|
|
1080
|
+
'--skip_python_tests',
|
|
1081
|
+
action='store_true',
|
|
1082
|
+
help='Skip python tests')
|
|
1083
|
+
|
|
1084
|
+
parser.add_argument(
|
|
1085
|
+
'--skip_cli_tests',
|
|
1086
|
+
action='store_true',
|
|
1087
|
+
help='Skip CLI tests')
|
|
1088
|
+
|
|
1089
|
+
parser.add_argument(
|
|
1090
|
+
'--force_data_download',
|
|
1091
|
+
action='store_true',
|
|
1092
|
+
help='Force download of the test data file, even if it\'s already available')
|
|
1093
|
+
|
|
1094
|
+
parser.add_argument(
|
|
1095
|
+
'--force_data_unzip',
|
|
1096
|
+
action='store_true',
|
|
1097
|
+
help='Force extraction of all files in the test data file, even if they\'re already available')
|
|
1098
|
+
|
|
1099
|
+
parser.add_argument(
|
|
1100
|
+
'--warning_mode',
|
|
1101
|
+
action='store_true',
|
|
1102
|
+
help='Turns numeric/content errors into warnings')
|
|
1103
|
+
|
|
1104
|
+
parser.add_argument(
|
|
1105
|
+
'--max_conf_error',
|
|
1106
|
+
type=float,
|
|
1107
|
+
default=options.max_conf_error,
|
|
1108
|
+
help='Maximum tolerable confidence value deviation from expected (default {})'.format(
|
|
1109
|
+
options.max_conf_error))
|
|
1110
|
+
|
|
1111
|
+
parser.add_argument(
|
|
1112
|
+
'--max_coord_error',
|
|
1113
|
+
type=float,
|
|
1114
|
+
default=options.max_coord_error,
|
|
1115
|
+
help='Maximum tolerable coordinate value deviation from expected (default {})'.format(
|
|
1116
|
+
options.max_coord_error))
|
|
1117
|
+
|
|
1118
|
+
parser.add_argument(
|
|
1119
|
+
'--cli_working_dir',
|
|
1120
|
+
type=str,
|
|
1121
|
+
default=None,
|
|
1122
|
+
help='Working directory for CLI tests')
|
|
1123
|
+
|
|
1124
|
+
parser.add_argument(
|
|
1125
|
+
'--yolo_working_dir',
|
|
1126
|
+
type=str,
|
|
1127
|
+
default=None,
|
|
1128
|
+
help='Working directory for yolo inference tests')
|
|
1129
|
+
|
|
1130
|
+
parser.add_argument(
|
|
1131
|
+
'--cli_test_pythonpath',
|
|
1132
|
+
type=str,
|
|
1133
|
+
default=None,
|
|
1134
|
+
help='PYTHONPATH to set for CLI tests; if None, inherits from the parent process'
|
|
1135
|
+
)
|
|
1136
|
+
|
|
1137
|
+
# token used for linting
|
|
1138
|
+
#
|
|
1139
|
+
# no_arguments_required
|
|
1140
|
+
|
|
1141
|
+
args = parser.parse_args()
|
|
1142
|
+
|
|
1143
|
+
_args_to_object(args,options)
|
|
1144
|
+
|
|
1145
|
+
run_tests(options)
|
|
1146
|
+
|
|
1147
|
+
if __name__ == '__main__':
|
|
1148
|
+
main()
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
#%% Sample invocations
|
|
1152
|
+
|
|
1153
|
+
"""
|
|
1154
|
+
# Windows
|
|
1155
|
+
set PYTHONPATH=c:\git\MegaDetector;c:\git\yolov5-md
|
|
1156
|
+
python md_tests.py --cli_working_dir "c:\git\MegaDetector" --yolo_working_dir "c:\git\yolov5-md" --cli_test_pythonpath "c:\git\MegaDetector;c:\git\yolov5-md"
|
|
1157
|
+
|
|
1158
|
+
# Linux
|
|
1159
|
+
export PYTHONPATH=/mnt/c/git/MegaDetector:/mnt/c/git/yolov5-md
|
|
1160
|
+
python md_tests.py --cli_working_dir "/mnt/c/git/MegaDetector" --yolo_working_dir "/mnt/c/git/yolov5-md" --cli_test_pythonpath "/mnt/c/git/MegaDetector:/mnt/c/git/yolov5-md"
|
|
1161
|
+
|
|
1162
|
+
python -c "import md_tests; print(md_tests.get_expected_results_filename(True))"
|
|
1163
|
+
"""
|
|
1164
|
+
|