megadetector 5.0.8__py3-none-any.whl → 5.0.9__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.9.dist-info}/METADATA +13 -7
- megadetector-5.0.9.dist-info/RECORD +224 -0
- {megadetector-5.0.8.dist-info → megadetector-5.0.9.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.9.dist-info}/LICENSE +0 -0
- {megadetector-5.0.8.dist-info → megadetector-5.0.9.dist-info}/WHEEL +0 -0
docs/source/conf.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
project = 'MegaDetector'
|
|
5
|
+
copyright = '2024, Your friendly neighborhood MegaDetector team'
|
|
6
|
+
author = 'Your friendly neighborhood MegaDetector team'
|
|
7
|
+
|
|
8
|
+
sys.path.insert(0, os.path.abspath("../.."))
|
|
9
|
+
|
|
10
|
+
extensions = [
|
|
11
|
+
"sphinx.ext.napoleon",
|
|
12
|
+
"sphinx.ext.autodoc",
|
|
13
|
+
"sphinx.ext.viewcode",
|
|
14
|
+
"sphinx_mdinclude",
|
|
15
|
+
"sphinx_argparse_cli"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
autodoc_mock_imports = ["azure", "deepdiff", "magic", "tensorflow", "pytesseract"]
|
|
19
|
+
|
|
20
|
+
myst_enable_extensions = [
|
|
21
|
+
"colon_fence",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
html_theme = 'sphinx_rtd_theme'
|
|
25
|
+
|
|
26
|
+
# collapse_navigation doesn't actually work
|
|
27
|
+
html_theme_options = {'navigation_depth': 2, 'collapse_navigation': False}
|
|
28
|
+
|
|
29
|
+
# html_theme = 'sphinx_book_theme'
|
|
30
|
+
# html_theme_options['show_navbar_depth'] = 2
|
|
31
|
+
|
|
32
|
+
# html_static_path = ['_static']
|
|
33
|
+
|
|
34
|
+
# Hide "bases: object" from all classes that don't define a base class
|
|
35
|
+
from sphinx.ext import autodoc
|
|
36
|
+
|
|
37
|
+
class MockedClassDocumenter(autodoc.ClassDocumenter):
|
|
38
|
+
def add_line(self, line: str, source: str, *lineno: int) -> None:
|
|
39
|
+
if line == " Bases: :py:class:`object`":
|
|
40
|
+
return
|
|
41
|
+
super().add_line(line, source, *lineno)
|
|
42
|
+
|
|
43
|
+
autodoc.ClassDocumenter = MockedClassDocumenter
|
md_utils/__init__.py
ADDED
|
File without changes
|
md_utils/azure_utils.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
azure_utils.py
|
|
4
|
+
|
|
5
|
+
Miscellaneous Azure Blob Storage utilities
|
|
6
|
+
|
|
7
|
+
Requires azure-storage-blob>=12.4.0
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
10
|
|
|
11
11
|
import json
|
|
12
12
|
from md_utils import path_utils
|
md_utils/ct_utils.py
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
ct_utils.py
|
|
4
|
+
|
|
5
|
+
Numeric/geometry/array utility functions.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
8
|
|
|
9
9
|
#%% Imports and constants
|
|
10
10
|
|
|
11
|
-
import argparse
|
|
12
11
|
import inspect
|
|
13
12
|
import json
|
|
14
13
|
import math
|
|
@@ -26,12 +25,15 @@ image_extensions = ['.jpg', '.jpeg', '.gif', '.png']
|
|
|
26
25
|
|
|
27
26
|
def truncate_float_array(xs, precision=3):
|
|
28
27
|
"""
|
|
29
|
-
Vectorized version of truncate_float(...)
|
|
28
|
+
Vectorized version of truncate_float(...), truncates the fractional portion of each
|
|
29
|
+
floating-point value to a specific number of floating-point digits.
|
|
30
30
|
|
|
31
31
|
Args:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
xs (list): list of floats to truncate
|
|
33
|
+
precision (int, optional): the number of significant digits to preserve, should be >= 1
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
list: list of truncated floats
|
|
35
37
|
"""
|
|
36
38
|
|
|
37
39
|
return [truncate_float(x, precision=precision) for x in xs]
|
|
@@ -51,9 +53,11 @@ def truncate_float(x, precision=3):
|
|
|
51
53
|
before exporting to JSON.
|
|
52
54
|
|
|
53
55
|
Args:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
x (float): scalar to truncate
|
|
57
|
+
precision (int, optional): the number of significant digits to preserve, should be >= 1
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
float: truncated version of [x]
|
|
57
61
|
"""
|
|
58
62
|
|
|
59
63
|
assert precision > 0
|
|
@@ -78,30 +82,42 @@ def truncate_float(x, precision=3):
|
|
|
78
82
|
return math.floor(x * factor)/factor
|
|
79
83
|
|
|
80
84
|
|
|
81
|
-
def args_to_object(args
|
|
85
|
+
def args_to_object(args, obj):
|
|
82
86
|
"""
|
|
83
87
|
Copies all fields from a Namespace (typically the output from parse_args) to an
|
|
84
88
|
object. Skips fields starting with _. Does not check existence in the target
|
|
85
89
|
object.
|
|
86
90
|
|
|
87
91
|
Args:
|
|
88
|
-
args
|
|
89
|
-
obj:
|
|
92
|
+
args (argparse.Namespace): the namespace to convert to an object
|
|
93
|
+
obj (object): object whose whose attributes will be updated
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
object: the modified object (modified in place, but also returned)
|
|
90
97
|
"""
|
|
91
98
|
|
|
92
99
|
for n, v in inspect.getmembers(args):
|
|
93
100
|
if not n.startswith('_'):
|
|
94
101
|
setattr(obj, n, v)
|
|
95
102
|
|
|
103
|
+
return obj
|
|
104
|
+
|
|
96
105
|
|
|
97
106
|
def pretty_print_object(obj, b_print=True):
|
|
98
107
|
"""
|
|
99
|
-
|
|
108
|
+
Converts an arbitrary object to .json, optionally printing the .json representation.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
obj (object): object to print
|
|
112
|
+
b_print (bool, optional): whether to print the object
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
str: .json reprepresentation of [obj]
|
|
100
116
|
"""
|
|
101
117
|
|
|
102
118
|
# _ = pretty_print_object(obj)
|
|
103
119
|
|
|
104
|
-
#
|
|
120
|
+
# TODO: it's sloppy that I'm making a module-wide change here.
|
|
105
121
|
jsonpickle.set_encoder_options('json', sort_keys=True, indent=2)
|
|
106
122
|
a = jsonpickle.encode(obj)
|
|
107
123
|
s = '{}'.format(a)
|
|
@@ -110,12 +126,19 @@ def pretty_print_object(obj, b_print=True):
|
|
|
110
126
|
return s
|
|
111
127
|
|
|
112
128
|
|
|
113
|
-
def is_list_sorted(L,reverse=False):
|
|
129
|
+
def is_list_sorted(L, reverse=False):
|
|
114
130
|
"""
|
|
115
|
-
Returns
|
|
131
|
+
Returns True if the list L appears to be sorted, otherwise False.
|
|
116
132
|
|
|
117
133
|
Calling is_list_sorted(L,reverse=True) is the same as calling
|
|
118
134
|
is_list_sorted(L.reverse(),reverse=False).
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
L (list): list to evaluate
|
|
138
|
+
reverse (bool, optional): whether to reverse the list before evaluating sort status
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
bool: True if the list L appears to be sorted, otherwise False
|
|
119
142
|
"""
|
|
120
143
|
|
|
121
144
|
if reverse:
|
|
@@ -126,32 +149,27 @@ def is_list_sorted(L,reverse=False):
|
|
|
126
149
|
|
|
127
150
|
def write_json(path, content, indent=1):
|
|
128
151
|
"""
|
|
129
|
-
Standardized wrapper for json.dump
|
|
152
|
+
Standardized wrapper for json.dump().
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
path (str): filename to write to
|
|
156
|
+
content (object): object to dump
|
|
157
|
+
indent (int, optional): indentation depth passed to json.dump
|
|
130
158
|
"""
|
|
131
159
|
|
|
132
160
|
with open(path, 'w') as f:
|
|
133
161
|
json.dump(content, f, indent=indent)
|
|
134
162
|
|
|
135
163
|
|
|
136
|
-
def is_image_file(s):
|
|
137
|
-
"""
|
|
138
|
-
Checks a file's extension against a hard-coded set of image file extensions;
|
|
139
|
-
return True if it appears to be an image.
|
|
140
|
-
"""
|
|
141
|
-
|
|
142
|
-
ext = os.path.splitext(s)[1]
|
|
143
|
-
return ext.lower() in image_extensions
|
|
144
|
-
|
|
145
|
-
|
|
146
164
|
def convert_yolo_to_xywh(yolo_box):
|
|
147
165
|
"""
|
|
148
166
|
Converts a YOLO format bounding box to [x_min, y_min, width_of_box, height_of_box].
|
|
149
167
|
|
|
150
168
|
Args:
|
|
151
|
-
yolo_box: bounding box of format [x_center, y_center, width_of_box, height_of_box]
|
|
169
|
+
yolo_box (list): bounding box of format [x_center, y_center, width_of_box, height_of_box]
|
|
152
170
|
|
|
153
171
|
Returns:
|
|
154
|
-
bbox with coordinates represented as [x_min, y_min, width_of_box, height_of_box]
|
|
172
|
+
list: bbox with coordinates represented as [x_min, y_min, width_of_box, height_of_box]
|
|
155
173
|
"""
|
|
156
174
|
|
|
157
175
|
x_center, y_center, width_of_box, height_of_box = yolo_box
|
|
@@ -162,14 +180,14 @@ def convert_yolo_to_xywh(yolo_box):
|
|
|
162
180
|
|
|
163
181
|
def convert_xywh_to_tf(api_box):
|
|
164
182
|
"""
|
|
165
|
-
Converts an xywh bounding box to
|
|
166
|
-
Object Detection API uses
|
|
183
|
+
Converts an xywh bounding box (the format used in MD output) to the [y_min, x_min, y_max, x_max]
|
|
184
|
+
format that the TensorFlow Object Detection API uses.
|
|
167
185
|
|
|
168
186
|
Args:
|
|
169
187
|
api_box: bbox output by the batch processing API [x_min, y_min, width_of_box, height_of_box]
|
|
170
188
|
|
|
171
189
|
Returns:
|
|
172
|
-
bbox with coordinates represented as [y_min, x_min, y_max, x_max]
|
|
190
|
+
list: bbox with coordinates represented as [y_min, x_min, y_max, x_max]
|
|
173
191
|
"""
|
|
174
192
|
|
|
175
193
|
x_min, y_min, width_of_box, height_of_box = api_box
|
|
@@ -180,15 +198,13 @@ def convert_xywh_to_tf(api_box):
|
|
|
180
198
|
|
|
181
199
|
def convert_xywh_to_xyxy(api_bbox):
|
|
182
200
|
"""
|
|
183
|
-
Converts an xywh bounding box to an xyxy bounding box.
|
|
201
|
+
Converts an xywh bounding box (the MD output format) to an xyxy bounding box.
|
|
184
202
|
|
|
185
|
-
Note that this is also different from the TensorFlow Object Detection API coords format.
|
|
186
|
-
|
|
187
203
|
Args:
|
|
188
|
-
api_bbox: bbox
|
|
204
|
+
api_bbox (list): bbox formatted as [x_min, y_min, width_of_box, height_of_box]
|
|
189
205
|
|
|
190
206
|
Returns:
|
|
191
|
-
bbox
|
|
207
|
+
list: bbox formatted as [x_min, y_min, x_max, y_max]
|
|
192
208
|
"""
|
|
193
209
|
|
|
194
210
|
x_min, y_min, width_of_box, height_of_box = api_bbox
|
|
@@ -198,18 +214,18 @@ def convert_xywh_to_xyxy(api_bbox):
|
|
|
198
214
|
|
|
199
215
|
def get_iou(bb1, bb2):
|
|
200
216
|
"""
|
|
201
|
-
Calculates the
|
|
217
|
+
Calculates the intersection over union (IoU) of two bounding boxes.
|
|
202
218
|
|
|
203
219
|
Adapted from:
|
|
204
220
|
|
|
205
221
|
https://stackoverflow.com/questions/25349178/calculating-percentage-of-bounding-box-overlap-for-image-detector-evaluation
|
|
206
222
|
|
|
207
223
|
Args:
|
|
208
|
-
bb1: [x_min, y_min, width_of_box, height_of_box]
|
|
209
|
-
bb2: [x_min, y_min, width_of_box, height_of_box]
|
|
224
|
+
bb1 (list): [x_min, y_min, width_of_box, height_of_box]
|
|
225
|
+
bb2 (list): [x_min, y_min, width_of_box, height_of_box]
|
|
210
226
|
|
|
211
227
|
Returns:
|
|
212
|
-
intersection_over_union, a float in [0, 1]
|
|
228
|
+
float: intersection_over_union, a float in [0, 1]
|
|
213
229
|
"""
|
|
214
230
|
|
|
215
231
|
bb1 = convert_xywh_to_xyxy(bb1)
|
|
@@ -261,9 +277,14 @@ def _get_max_conf_from_detections(detections):
|
|
|
261
277
|
|
|
262
278
|
def get_max_conf(im):
|
|
263
279
|
"""
|
|
264
|
-
Given an image dict in the format
|
|
265
|
-
|
|
266
|
-
|
|
280
|
+
Given an image dict in the MD output format, computes the maximum detection confidence for any
|
|
281
|
+
class. Returns 0.0 (rather than None) if there was a failure or 'detections' isn't present.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
im (dict): image dictionary in the MD output format (with a 'detections' field)
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
float: the maximum detection confidence across all classes
|
|
267
288
|
"""
|
|
268
289
|
|
|
269
290
|
max_conf = 0.0
|
|
@@ -274,7 +295,14 @@ def get_max_conf(im):
|
|
|
274
295
|
|
|
275
296
|
def point_dist(p1,p2):
|
|
276
297
|
"""
|
|
277
|
-
|
|
298
|
+
Computes the distance between two points, represented as length-two tuples.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
p1: point, formatted as (x,y)
|
|
302
|
+
p2: point, formatted as (x,y)
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
float: the Euclidean distance between p1 and p2
|
|
278
306
|
"""
|
|
279
307
|
|
|
280
308
|
return math.sqrt( ((p1[0]-p2[0])**2) + ((p1[1]-p2[1])**2) )
|
|
@@ -282,13 +310,21 @@ def point_dist(p1,p2):
|
|
|
282
310
|
|
|
283
311
|
def rect_distance(r1, r2, format='x0y0x1y1'):
|
|
284
312
|
"""
|
|
285
|
-
|
|
313
|
+
Computes the minimum distance between two axis-aligned rectangles, each represented as
|
|
286
314
|
(x0,y0,x1,y1) by default.
|
|
287
315
|
|
|
288
|
-
Can also specify "format" as x0y0wh for MD-style bbox formatting (x0,y0,w,h).
|
|
316
|
+
Can also specify "format" as x0y0wh for MD-style bbox formatting (x0,y0,w,h).
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
r1: rectangle, formatted as (x0,y0,x1,y1) or (x0,y0,xy,y1)
|
|
320
|
+
r2: rectangle, formatted as (x0,y0,x1,y1) or (x0,y0,xy,y1)
|
|
321
|
+
format (str, optional): whether the boxes are formatted as 'x0y0x1y1' (default) or 'x0y0wh'
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
float: the minimum distance between r1 and r2
|
|
289
325
|
"""
|
|
290
326
|
|
|
291
|
-
assert format in ('x0y0x1y1','x0y0wh')
|
|
327
|
+
assert format in ('x0y0x1y1','x0y0wh'), 'Illegal rectangle format {}'.format(format)
|
|
292
328
|
|
|
293
329
|
if format == 'x0y0wh':
|
|
294
330
|
# Convert to x0y0x1y1 without modifying the original rectangles
|
|
@@ -322,18 +358,17 @@ def rect_distance(r1, r2, format='x0y0x1y1'):
|
|
|
322
358
|
return 0.0
|
|
323
359
|
|
|
324
360
|
|
|
325
|
-
def list_is_sorted(l):
|
|
326
|
-
"""
|
|
327
|
-
Returns True if the list [l] is sorted, else False.
|
|
328
|
-
"""
|
|
329
|
-
|
|
330
|
-
return all(l[i] <= l[i+1] for i in range(len(l)-1))
|
|
331
|
-
|
|
332
|
-
|
|
333
361
|
def split_list_into_fixed_size_chunks(L,n):
|
|
334
362
|
"""
|
|
335
363
|
Split the list or tuple L into chunks of size n (allowing chunks of size n-1 if necessary,
|
|
336
|
-
i.e. len(L) does not have to be a multiple of n.
|
|
364
|
+
i.e. len(L) does not have to be a multiple of n).
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
L (list): list to split into chunks
|
|
368
|
+
n (int): preferred chunk size
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
list: list of chunks, where each chunk is a list of length n or n-1
|
|
337
372
|
"""
|
|
338
373
|
|
|
339
374
|
return [L[i * n:(i + 1) * n] for i in range((len(L) + n - 1) // n )]
|
|
@@ -342,11 +377,19 @@ def split_list_into_fixed_size_chunks(L,n):
|
|
|
342
377
|
def split_list_into_n_chunks(L, n, chunk_strategy='greedy'):
|
|
343
378
|
"""
|
|
344
379
|
Splits the list or tuple L into n equally-sized chunks (some chunks may be one
|
|
345
|
-
element smaller than others, i.e. len(L) does not have to be a multiple of n.
|
|
380
|
+
element smaller than others, i.e. len(L) does not have to be a multiple of n).
|
|
346
381
|
|
|
347
382
|
chunk_strategy can be "greedy" (default, if there are k samples per chunk, the first
|
|
348
383
|
k go into the first chunk) or "balanced" (alternate between chunks when pulling
|
|
349
384
|
items from the list).
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
L (list): list to split into chunks
|
|
388
|
+
n (int): number of chunks
|
|
389
|
+
chunk_strategy (str, optiopnal): "greedy" or "balanced"; see above
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
list: list of chunks, each of which is a list
|
|
350
393
|
"""
|
|
351
394
|
|
|
352
395
|
if chunk_strategy == 'greedy':
|
|
@@ -365,6 +408,13 @@ def split_list_into_n_chunks(L, n, chunk_strategy='greedy'):
|
|
|
365
408
|
def sort_dictionary_by_key(d,reverse=False):
|
|
366
409
|
"""
|
|
367
410
|
Sorts the dictionary [d] by key.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
d (dict): dictionary to sort
|
|
414
|
+
reverse (bool, optional): whether to sort in reverse (descending) order
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
dict: sorted copy of [d]
|
|
368
418
|
"""
|
|
369
419
|
|
|
370
420
|
d = dict(sorted(d.items(),reverse=reverse))
|
|
@@ -375,6 +425,15 @@ def sort_dictionary_by_value(d,sort_values=None,reverse=False):
|
|
|
375
425
|
"""
|
|
376
426
|
Sorts the dictionary [d] by value. If sort_values is None, uses d.values(),
|
|
377
427
|
otherwise uses the dictionary sort_values as the sorting criterion.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
d (dict): dictionary to sort
|
|
431
|
+
sort_values (dict, optional): dictionary mapping keys in [d] to sort values (defaults
|
|
432
|
+
to None, uses [d] itself for sorting)
|
|
433
|
+
reverse (bool, optional): whether to sort in reverse (descending) order
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
dict: sorted copy of [d]
|
|
378
437
|
"""
|
|
379
438
|
|
|
380
439
|
if sort_values is None:
|
|
@@ -386,16 +445,22 @@ def sort_dictionary_by_value(d,sort_values=None,reverse=False):
|
|
|
386
445
|
|
|
387
446
|
def invert_dictionary(d):
|
|
388
447
|
"""
|
|
389
|
-
|
|
390
|
-
uniqueness.
|
|
448
|
+
Creates a new dictionary that maps d.values() to d.keys(). Does not check
|
|
449
|
+
uniqueness.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
d (dict): dictionary to invert
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
dict: inverted copy of [d]
|
|
391
456
|
"""
|
|
392
457
|
|
|
393
458
|
return {v: k for k, v in d.items()}
|
|
394
459
|
|
|
395
460
|
|
|
396
461
|
def image_file_to_camera_folder(image_fn):
|
|
397
|
-
"""
|
|
398
|
-
|
|
462
|
+
r"""
|
|
463
|
+
Removes common overflow folders (e.g. RECNX101, RECNX102) from paths, i.e. turn:
|
|
399
464
|
|
|
400
465
|
a\b\c\RECNX101\image001.jpg
|
|
401
466
|
|
|
@@ -407,6 +472,12 @@ def image_file_to_camera_folder(image_fn):
|
|
|
407
472
|
present.
|
|
408
473
|
|
|
409
474
|
Always converts backslashes to slashes.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
image_fn (str): the image filename from which we should remove overflow folders
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
str: a version of [image_fn] from which camera overflow folders have been removed
|
|
410
481
|
"""
|
|
411
482
|
|
|
412
483
|
import re
|
|
@@ -425,6 +496,95 @@ def image_file_to_camera_folder(image_fn):
|
|
|
425
496
|
return camera_folder
|
|
426
497
|
|
|
427
498
|
|
|
499
|
+
def is_float(v):
|
|
500
|
+
"""
|
|
501
|
+
Determines whether v is either a float or a string representation of a float.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
v (object): object to evaluate
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
bool: True if [v] is a float or a string representation of a float, otherwise False
|
|
508
|
+
"""
|
|
509
|
+
|
|
510
|
+
try:
|
|
511
|
+
_ = float(v)
|
|
512
|
+
return True
|
|
513
|
+
except ValueError:
|
|
514
|
+
return False
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def is_iterable(x):
|
|
518
|
+
"""
|
|
519
|
+
Uses duck typing to assess whether [x] is iterable (list, set, dict, etc.).
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
x (object): the object to test
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
bool: True if [x] appears to be iterable, otherwise False
|
|
526
|
+
"""
|
|
527
|
+
|
|
528
|
+
try:
|
|
529
|
+
_ = iter(x)
|
|
530
|
+
except:
|
|
531
|
+
return False
|
|
532
|
+
return True
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def is_empty(v):
|
|
536
|
+
"""
|
|
537
|
+
A common definition of "empty" used throughout the repo, particularly when loading
|
|
538
|
+
data from .csv files. "empty" includes None, '', and NaN.
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
v: the object to evaluate for emptiness
|
|
542
|
+
|
|
543
|
+
Returns:
|
|
544
|
+
bool: True if [v] is None, '', or NaN, otherwise False
|
|
545
|
+
"""
|
|
546
|
+
if v is None:
|
|
547
|
+
return True
|
|
548
|
+
if isinstance(v,str) and v == '':
|
|
549
|
+
return True
|
|
550
|
+
if isinstance(v,float) and np.isnan(v):
|
|
551
|
+
return True
|
|
552
|
+
return False
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def isnan(v):
|
|
556
|
+
"""
|
|
557
|
+
Returns True if v is a nan-valued float, otherwise returns False.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
v: the object to evaluate for nan-ness
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
bool: True if v is a nan-valued float, otherwise False
|
|
564
|
+
"""
|
|
565
|
+
|
|
566
|
+
try:
|
|
567
|
+
return np.isnan(v)
|
|
568
|
+
except Exception:
|
|
569
|
+
return False
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def sets_overlap(set1, set2):
|
|
573
|
+
"""
|
|
574
|
+
Determines whether two sets overlap.
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
set1 (set): the first set to compare (converted to a set if it's not already)
|
|
578
|
+
set2 (set): the second set to compare (converted to a set if it's not already)
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
bool: True if any elements are shared between set1 and set2
|
|
582
|
+
"""
|
|
583
|
+
|
|
584
|
+
return not set(set1).isdisjoint(set(set2))
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
|
|
428
588
|
#%% Test drivers
|
|
429
589
|
|
|
430
590
|
if False:
|