megadetector 5.0.7__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 +93 -79
- 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 +69 -55
- api/batch_processing/postprocessing/compare_batch_results.py +114 -44
- api/batch_processing/postprocessing/convert_output_format.py +62 -19
- api/batch_processing/postprocessing/load_api_results.py +17 -20
- api/batch_processing/postprocessing/md_to_coco.py +31 -21
- api/batch_processing/postprocessing/md_to_labelme.py +165 -68
- api/batch_processing/postprocessing/merge_detections.py +40 -15
- api/batch_processing/postprocessing/postprocess_batch_results.py +270 -186
- api/batch_processing/postprocessing/remap_detection_categories.py +170 -0
- api/batch_processing/postprocessing/render_detection_confusion_matrix.py +75 -39
- 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 +244 -160
- 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 +107 -59
- data_management/cct_to_md.py +176 -158
- data_management/cct_to_wi.py +247 -219
- data_management/coco_to_labelme.py +272 -0
- data_management/coco_to_yolo.py +86 -62
- 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 +130 -83
- data_management/databases/subset_json_db.py +25 -16
- data_management/generate_crops_from_cct.py +27 -45
- data_management/get_image_sizes.py +188 -144
- 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 -160
- 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 +8 -8
- 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 +309 -159
- data_management/labelme_to_yolo.py +103 -60
- 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 +114 -31
- 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 +92 -90
- data_management/lila/generate_lila_per_image_labels.py +56 -43
- 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 +103 -70
- data_management/lila/test_lila_metadata_urls.py +132 -116
- data_management/ocr_tools.py +173 -128
- data_management/read_exif.py +161 -99
- data_management/remap_coco_categories.py +84 -0
- data_management/remove_exif.py +58 -62
- data_management/resize_coco_dataset.py +32 -44
- data_management/wi_download_csv_to_coco.py +246 -0
- data_management/yolo_output_to_md_output.py +86 -73
- data_management/yolo_to_coco.py +535 -95
- 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 +189 -114
- detection/run_inference_with_yolov5_val.py +118 -51
- detection/run_tiled_inference.py +113 -42
- detection/tf_detector.py +51 -28
- 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 +249 -70
- md_utils/directory_listing.py +59 -64
- md_utils/md_tests.py +968 -862
- md_utils/path_utils.py +655 -155
- 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 +208 -27
- md_utils/write_html_image_list.py +51 -35
- 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 +908 -311
- md_visualization/visualize_db.py +109 -58
- md_visualization/visualize_detector_output.py +61 -42
- {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/METADATA +21 -17
- megadetector-5.0.9.dist-info/RECORD +224 -0
- {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/WHEEL +1 -1
- {megadetector-5.0.7.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
- md_visualization/visualize_megadb.py +0 -183
- megadetector-5.0.7.dist-info/RECORD +0 -202
- {megadetector-5.0.7.dist-info → megadetector-5.0.9.dist-info}/LICENSE +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]
|
|
@@ -39,17 +41,23 @@ def truncate_float_array(xs, precision=3):
|
|
|
39
41
|
|
|
40
42
|
def truncate_float(x, precision=3):
|
|
41
43
|
"""
|
|
42
|
-
Truncates a floating-point value to a specific number of
|
|
44
|
+
Truncates the fractional portion of a floating-point value to a specific number of
|
|
45
|
+
floating-point digits.
|
|
43
46
|
|
|
44
|
-
For example:
|
|
47
|
+
For example:
|
|
48
|
+
|
|
49
|
+
truncate_float(0.0003214884) --> 0.000321
|
|
50
|
+
truncate_float(1.0003214884) --> 1.000321
|
|
45
51
|
|
|
46
52
|
This function is primarily used to achieve a certain float representation
|
|
47
53
|
before exporting to JSON.
|
|
48
54
|
|
|
49
55
|
Args:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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]
|
|
53
61
|
"""
|
|
54
62
|
|
|
55
63
|
assert precision > 0
|
|
@@ -58,41 +66,58 @@ def truncate_float(x, precision=3):
|
|
|
58
66
|
|
|
59
67
|
return 0
|
|
60
68
|
|
|
69
|
+
elif (x > 1):
|
|
70
|
+
|
|
71
|
+
fractional_component = x - 1.0
|
|
72
|
+
return 1 + truncate_float(fractional_component)
|
|
73
|
+
|
|
61
74
|
else:
|
|
62
75
|
|
|
63
76
|
# Determine the factor, which shifts the decimal point of x
|
|
64
77
|
# just behind the last significant digit.
|
|
65
78
|
factor = math.pow(10, precision - 1 - math.floor(math.log10(abs(x))))
|
|
66
79
|
|
|
67
|
-
# Shift decimal point by
|
|
80
|
+
# Shift decimal point by multiplication with factor, flooring, and
|
|
68
81
|
# division by factor.
|
|
69
82
|
return math.floor(x * factor)/factor
|
|
70
83
|
|
|
71
84
|
|
|
72
|
-
def args_to_object(args
|
|
85
|
+
def args_to_object(args, obj):
|
|
73
86
|
"""
|
|
74
87
|
Copies all fields from a Namespace (typically the output from parse_args) to an
|
|
75
88
|
object. Skips fields starting with _. Does not check existence in the target
|
|
76
89
|
object.
|
|
77
90
|
|
|
78
91
|
Args:
|
|
79
|
-
args
|
|
80
|
-
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)
|
|
81
97
|
"""
|
|
82
98
|
|
|
83
99
|
for n, v in inspect.getmembers(args):
|
|
84
100
|
if not n.startswith('_'):
|
|
85
101
|
setattr(obj, n, v)
|
|
86
102
|
|
|
103
|
+
return obj
|
|
104
|
+
|
|
87
105
|
|
|
88
106
|
def pretty_print_object(obj, b_print=True):
|
|
89
107
|
"""
|
|
90
|
-
|
|
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]
|
|
91
116
|
"""
|
|
92
117
|
|
|
93
118
|
# _ = pretty_print_object(obj)
|
|
94
119
|
|
|
95
|
-
#
|
|
120
|
+
# TODO: it's sloppy that I'm making a module-wide change here.
|
|
96
121
|
jsonpickle.set_encoder_options('json', sort_keys=True, indent=2)
|
|
97
122
|
a = jsonpickle.encode(obj)
|
|
98
123
|
s = '{}'.format(a)
|
|
@@ -101,12 +126,19 @@ def pretty_print_object(obj, b_print=True):
|
|
|
101
126
|
return s
|
|
102
127
|
|
|
103
128
|
|
|
104
|
-
def is_list_sorted(L,reverse=False):
|
|
129
|
+
def is_list_sorted(L, reverse=False):
|
|
105
130
|
"""
|
|
106
|
-
Returns
|
|
131
|
+
Returns True if the list L appears to be sorted, otherwise False.
|
|
107
132
|
|
|
108
133
|
Calling is_list_sorted(L,reverse=True) is the same as calling
|
|
109
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
|
|
110
142
|
"""
|
|
111
143
|
|
|
112
144
|
if reverse:
|
|
@@ -117,32 +149,27 @@ def is_list_sorted(L,reverse=False):
|
|
|
117
149
|
|
|
118
150
|
def write_json(path, content, indent=1):
|
|
119
151
|
"""
|
|
120
|
-
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
|
|
121
158
|
"""
|
|
122
159
|
|
|
123
160
|
with open(path, 'w') as f:
|
|
124
161
|
json.dump(content, f, indent=indent)
|
|
125
162
|
|
|
126
163
|
|
|
127
|
-
def is_image_file(s):
|
|
128
|
-
"""
|
|
129
|
-
Checks a file's extension against a hard-coded set of image file extensions;
|
|
130
|
-
return True if it appears to be an image.
|
|
131
|
-
"""
|
|
132
|
-
|
|
133
|
-
ext = os.path.splitext(s)[1]
|
|
134
|
-
return ext.lower() in image_extensions
|
|
135
|
-
|
|
136
|
-
|
|
137
164
|
def convert_yolo_to_xywh(yolo_box):
|
|
138
165
|
"""
|
|
139
166
|
Converts a YOLO format bounding box to [x_min, y_min, width_of_box, height_of_box].
|
|
140
167
|
|
|
141
168
|
Args:
|
|
142
|
-
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]
|
|
143
170
|
|
|
144
171
|
Returns:
|
|
145
|
-
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]
|
|
146
173
|
"""
|
|
147
174
|
|
|
148
175
|
x_center, y_center, width_of_box, height_of_box = yolo_box
|
|
@@ -153,14 +180,14 @@ def convert_yolo_to_xywh(yolo_box):
|
|
|
153
180
|
|
|
154
181
|
def convert_xywh_to_tf(api_box):
|
|
155
182
|
"""
|
|
156
|
-
Converts an xywh bounding box to
|
|
157
|
-
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.
|
|
158
185
|
|
|
159
186
|
Args:
|
|
160
187
|
api_box: bbox output by the batch processing API [x_min, y_min, width_of_box, height_of_box]
|
|
161
188
|
|
|
162
189
|
Returns:
|
|
163
|
-
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]
|
|
164
191
|
"""
|
|
165
192
|
|
|
166
193
|
x_min, y_min, width_of_box, height_of_box = api_box
|
|
@@ -171,14 +198,13 @@ def convert_xywh_to_tf(api_box):
|
|
|
171
198
|
|
|
172
199
|
def convert_xywh_to_xyxy(api_bbox):
|
|
173
200
|
"""
|
|
174
|
-
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.
|
|
175
202
|
|
|
176
|
-
Note that this is also different from the TensorFlow Object Detection API coords format.
|
|
177
203
|
Args:
|
|
178
|
-
api_bbox: bbox
|
|
204
|
+
api_bbox (list): bbox formatted as [x_min, y_min, width_of_box, height_of_box]
|
|
179
205
|
|
|
180
206
|
Returns:
|
|
181
|
-
bbox
|
|
207
|
+
list: bbox formatted as [x_min, y_min, x_max, y_max]
|
|
182
208
|
"""
|
|
183
209
|
|
|
184
210
|
x_min, y_min, width_of_box, height_of_box = api_bbox
|
|
@@ -188,18 +214,18 @@ def convert_xywh_to_xyxy(api_bbox):
|
|
|
188
214
|
|
|
189
215
|
def get_iou(bb1, bb2):
|
|
190
216
|
"""
|
|
191
|
-
Calculates the
|
|
217
|
+
Calculates the intersection over union (IoU) of two bounding boxes.
|
|
192
218
|
|
|
193
219
|
Adapted from:
|
|
194
220
|
|
|
195
221
|
https://stackoverflow.com/questions/25349178/calculating-percentage-of-bounding-box-overlap-for-image-detector-evaluation
|
|
196
222
|
|
|
197
223
|
Args:
|
|
198
|
-
bb1: [x_min, y_min, width_of_box, height_of_box]
|
|
199
|
-
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]
|
|
200
226
|
|
|
201
227
|
Returns:
|
|
202
|
-
intersection_over_union, a float in [0, 1]
|
|
228
|
+
float: intersection_over_union, a float in [0, 1]
|
|
203
229
|
"""
|
|
204
230
|
|
|
205
231
|
bb1 = convert_xywh_to_xyxy(bb1)
|
|
@@ -251,9 +277,14 @@ def _get_max_conf_from_detections(detections):
|
|
|
251
277
|
|
|
252
278
|
def get_max_conf(im):
|
|
253
279
|
"""
|
|
254
|
-
Given an image dict in the format
|
|
255
|
-
|
|
256
|
-
|
|
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
|
|
257
288
|
"""
|
|
258
289
|
|
|
259
290
|
max_conf = 0.0
|
|
@@ -264,7 +295,14 @@ def get_max_conf(im):
|
|
|
264
295
|
|
|
265
296
|
def point_dist(p1,p2):
|
|
266
297
|
"""
|
|
267
|
-
|
|
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
|
|
268
306
|
"""
|
|
269
307
|
|
|
270
308
|
return math.sqrt( ((p1[0]-p2[0])**2) + ((p1[1]-p2[1])**2) )
|
|
@@ -272,13 +310,21 @@ def point_dist(p1,p2):
|
|
|
272
310
|
|
|
273
311
|
def rect_distance(r1, r2, format='x0y0x1y1'):
|
|
274
312
|
"""
|
|
275
|
-
|
|
313
|
+
Computes the minimum distance between two axis-aligned rectangles, each represented as
|
|
276
314
|
(x0,y0,x1,y1) by default.
|
|
277
315
|
|
|
278
|
-
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
|
|
279
325
|
"""
|
|
280
326
|
|
|
281
|
-
assert format in ('x0y0x1y1','x0y0wh')
|
|
327
|
+
assert format in ('x0y0x1y1','x0y0wh'), 'Illegal rectangle format {}'.format(format)
|
|
282
328
|
|
|
283
329
|
if format == 'x0y0wh':
|
|
284
330
|
# Convert to x0y0x1y1 without modifying the original rectangles
|
|
@@ -312,18 +358,17 @@ def rect_distance(r1, r2, format='x0y0x1y1'):
|
|
|
312
358
|
return 0.0
|
|
313
359
|
|
|
314
360
|
|
|
315
|
-
def list_is_sorted(l):
|
|
316
|
-
"""
|
|
317
|
-
Returns True if the list [l] is sorted, else False.
|
|
318
|
-
"""
|
|
319
|
-
|
|
320
|
-
return all(l[i] <= l[i+1] for i in range(len(l)-1))
|
|
321
|
-
|
|
322
|
-
|
|
323
361
|
def split_list_into_fixed_size_chunks(L,n):
|
|
324
362
|
"""
|
|
325
363
|
Split the list or tuple L into chunks of size n (allowing chunks of size n-1 if necessary,
|
|
326
|
-
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
|
|
327
372
|
"""
|
|
328
373
|
|
|
329
374
|
return [L[i * n:(i + 1) * n] for i in range((len(L) + n - 1) // n )]
|
|
@@ -332,11 +377,19 @@ def split_list_into_fixed_size_chunks(L,n):
|
|
|
332
377
|
def split_list_into_n_chunks(L, n, chunk_strategy='greedy'):
|
|
333
378
|
"""
|
|
334
379
|
Splits the list or tuple L into n equally-sized chunks (some chunks may be one
|
|
335
|
-
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).
|
|
336
381
|
|
|
337
382
|
chunk_strategy can be "greedy" (default, if there are k samples per chunk, the first
|
|
338
383
|
k go into the first chunk) or "balanced" (alternate between chunks when pulling
|
|
339
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
|
|
340
393
|
"""
|
|
341
394
|
|
|
342
395
|
if chunk_strategy == 'greedy':
|
|
@@ -352,10 +405,35 @@ def split_list_into_n_chunks(L, n, chunk_strategy='greedy'):
|
|
|
352
405
|
raise ValueError('Invalid chunk strategy: {}'.format(chunk_strategy))
|
|
353
406
|
|
|
354
407
|
|
|
408
|
+
def sort_dictionary_by_key(d,reverse=False):
|
|
409
|
+
"""
|
|
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]
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
d = dict(sorted(d.items(),reverse=reverse))
|
|
421
|
+
return d
|
|
422
|
+
|
|
423
|
+
|
|
355
424
|
def sort_dictionary_by_value(d,sort_values=None,reverse=False):
|
|
356
425
|
"""
|
|
357
426
|
Sorts the dictionary [d] by value. If sort_values is None, uses d.values(),
|
|
358
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]
|
|
359
437
|
"""
|
|
360
438
|
|
|
361
439
|
if sort_values is None:
|
|
@@ -367,16 +445,22 @@ def sort_dictionary_by_value(d,sort_values=None,reverse=False):
|
|
|
367
445
|
|
|
368
446
|
def invert_dictionary(d):
|
|
369
447
|
"""
|
|
370
|
-
|
|
371
|
-
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]
|
|
372
456
|
"""
|
|
373
457
|
|
|
374
458
|
return {v: k for k, v in d.items()}
|
|
375
459
|
|
|
376
460
|
|
|
377
461
|
def image_file_to_camera_folder(image_fn):
|
|
378
|
-
"""
|
|
379
|
-
|
|
462
|
+
r"""
|
|
463
|
+
Removes common overflow folders (e.g. RECNX101, RECNX102) from paths, i.e. turn:
|
|
380
464
|
|
|
381
465
|
a\b\c\RECNX101\image001.jpg
|
|
382
466
|
|
|
@@ -388,6 +472,12 @@ def image_file_to_camera_folder(image_fn):
|
|
|
388
472
|
present.
|
|
389
473
|
|
|
390
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
|
|
391
481
|
"""
|
|
392
482
|
|
|
393
483
|
import re
|
|
@@ -406,6 +496,95 @@ def image_file_to_camera_folder(image_fn):
|
|
|
406
496
|
return camera_folder
|
|
407
497
|
|
|
408
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
|
+
|
|
409
588
|
#%% Test drivers
|
|
410
589
|
|
|
411
590
|
if False:
|