megadetector 10.0.15__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.
Files changed (147) hide show
  1. megadetector/__init__.py +0 -0
  2. megadetector/api/__init__.py +0 -0
  3. megadetector/api/batch_processing/integration/digiKam/setup.py +6 -0
  4. megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +465 -0
  5. megadetector/api/batch_processing/integration/eMammal/test_scripts/config_template.py +5 -0
  6. megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +125 -0
  7. megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +55 -0
  8. megadetector/classification/__init__.py +0 -0
  9. megadetector/classification/aggregate_classifier_probs.py +108 -0
  10. megadetector/classification/analyze_failed_images.py +227 -0
  11. megadetector/classification/cache_batchapi_outputs.py +198 -0
  12. megadetector/classification/create_classification_dataset.py +626 -0
  13. megadetector/classification/crop_detections.py +516 -0
  14. megadetector/classification/csv_to_json.py +226 -0
  15. megadetector/classification/detect_and_crop.py +853 -0
  16. megadetector/classification/efficientnet/__init__.py +9 -0
  17. megadetector/classification/efficientnet/model.py +415 -0
  18. megadetector/classification/efficientnet/utils.py +608 -0
  19. megadetector/classification/evaluate_model.py +520 -0
  20. megadetector/classification/identify_mislabeled_candidates.py +152 -0
  21. megadetector/classification/json_to_azcopy_list.py +63 -0
  22. megadetector/classification/json_validator.py +696 -0
  23. megadetector/classification/map_classification_categories.py +276 -0
  24. megadetector/classification/merge_classification_detection_output.py +509 -0
  25. megadetector/classification/prepare_classification_script.py +194 -0
  26. megadetector/classification/prepare_classification_script_mc.py +228 -0
  27. megadetector/classification/run_classifier.py +287 -0
  28. megadetector/classification/save_mislabeled.py +110 -0
  29. megadetector/classification/train_classifier.py +827 -0
  30. megadetector/classification/train_classifier_tf.py +725 -0
  31. megadetector/classification/train_utils.py +323 -0
  32. megadetector/data_management/__init__.py +0 -0
  33. megadetector/data_management/animl_to_md.py +161 -0
  34. megadetector/data_management/annotations/__init__.py +0 -0
  35. megadetector/data_management/annotations/annotation_constants.py +33 -0
  36. megadetector/data_management/camtrap_dp_to_coco.py +270 -0
  37. megadetector/data_management/cct_json_utils.py +566 -0
  38. megadetector/data_management/cct_to_md.py +184 -0
  39. megadetector/data_management/cct_to_wi.py +293 -0
  40. megadetector/data_management/coco_to_labelme.py +284 -0
  41. megadetector/data_management/coco_to_yolo.py +701 -0
  42. megadetector/data_management/databases/__init__.py +0 -0
  43. megadetector/data_management/databases/add_width_and_height_to_db.py +107 -0
  44. megadetector/data_management/databases/combine_coco_camera_traps_files.py +210 -0
  45. megadetector/data_management/databases/integrity_check_json_db.py +563 -0
  46. megadetector/data_management/databases/subset_json_db.py +195 -0
  47. megadetector/data_management/generate_crops_from_cct.py +200 -0
  48. megadetector/data_management/get_image_sizes.py +164 -0
  49. megadetector/data_management/labelme_to_coco.py +559 -0
  50. megadetector/data_management/labelme_to_yolo.py +349 -0
  51. megadetector/data_management/lila/__init__.py +0 -0
  52. megadetector/data_management/lila/create_lila_blank_set.py +556 -0
  53. megadetector/data_management/lila/create_lila_test_set.py +192 -0
  54. megadetector/data_management/lila/create_links_to_md_results_files.py +106 -0
  55. megadetector/data_management/lila/download_lila_subset.py +182 -0
  56. megadetector/data_management/lila/generate_lila_per_image_labels.py +777 -0
  57. megadetector/data_management/lila/get_lila_annotation_counts.py +174 -0
  58. megadetector/data_management/lila/get_lila_image_counts.py +112 -0
  59. megadetector/data_management/lila/lila_common.py +319 -0
  60. megadetector/data_management/lila/test_lila_metadata_urls.py +164 -0
  61. megadetector/data_management/mewc_to_md.py +344 -0
  62. megadetector/data_management/ocr_tools.py +873 -0
  63. megadetector/data_management/read_exif.py +964 -0
  64. megadetector/data_management/remap_coco_categories.py +195 -0
  65. megadetector/data_management/remove_exif.py +156 -0
  66. megadetector/data_management/rename_images.py +194 -0
  67. megadetector/data_management/resize_coco_dataset.py +665 -0
  68. megadetector/data_management/speciesnet_to_md.py +41 -0
  69. megadetector/data_management/wi_download_csv_to_coco.py +247 -0
  70. megadetector/data_management/yolo_output_to_md_output.py +594 -0
  71. megadetector/data_management/yolo_to_coco.py +984 -0
  72. megadetector/data_management/zamba_to_md.py +188 -0
  73. megadetector/detection/__init__.py +0 -0
  74. megadetector/detection/change_detection.py +840 -0
  75. megadetector/detection/process_video.py +479 -0
  76. megadetector/detection/pytorch_detector.py +1451 -0
  77. megadetector/detection/run_detector.py +1267 -0
  78. megadetector/detection/run_detector_batch.py +2172 -0
  79. megadetector/detection/run_inference_with_yolov5_val.py +1314 -0
  80. megadetector/detection/run_md_and_speciesnet.py +1604 -0
  81. megadetector/detection/run_tiled_inference.py +1044 -0
  82. megadetector/detection/tf_detector.py +209 -0
  83. megadetector/detection/video_utils.py +1379 -0
  84. megadetector/postprocessing/__init__.py +0 -0
  85. megadetector/postprocessing/add_max_conf.py +72 -0
  86. megadetector/postprocessing/categorize_detections_by_size.py +166 -0
  87. megadetector/postprocessing/classification_postprocessing.py +1943 -0
  88. megadetector/postprocessing/combine_batch_outputs.py +249 -0
  89. megadetector/postprocessing/compare_batch_results.py +2110 -0
  90. megadetector/postprocessing/convert_output_format.py +403 -0
  91. megadetector/postprocessing/create_crop_folder.py +629 -0
  92. megadetector/postprocessing/detector_calibration.py +570 -0
  93. megadetector/postprocessing/generate_csv_report.py +522 -0
  94. megadetector/postprocessing/load_api_results.py +223 -0
  95. megadetector/postprocessing/md_to_coco.py +428 -0
  96. megadetector/postprocessing/md_to_labelme.py +351 -0
  97. megadetector/postprocessing/md_to_wi.py +41 -0
  98. megadetector/postprocessing/merge_detections.py +392 -0
  99. megadetector/postprocessing/postprocess_batch_results.py +2140 -0
  100. megadetector/postprocessing/remap_detection_categories.py +226 -0
  101. megadetector/postprocessing/render_detection_confusion_matrix.py +677 -0
  102. megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +206 -0
  103. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +82 -0
  104. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1665 -0
  105. megadetector/postprocessing/separate_detections_into_folders.py +795 -0
  106. megadetector/postprocessing/subset_json_detector_output.py +964 -0
  107. megadetector/postprocessing/top_folders_to_bottom.py +238 -0
  108. megadetector/postprocessing/validate_batch_results.py +332 -0
  109. megadetector/taxonomy_mapping/__init__.py +0 -0
  110. megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +491 -0
  111. megadetector/taxonomy_mapping/map_new_lila_datasets.py +211 -0
  112. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +165 -0
  113. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +543 -0
  114. megadetector/taxonomy_mapping/retrieve_sample_image.py +71 -0
  115. megadetector/taxonomy_mapping/simple_image_download.py +231 -0
  116. megadetector/taxonomy_mapping/species_lookup.py +1008 -0
  117. megadetector/taxonomy_mapping/taxonomy_csv_checker.py +159 -0
  118. megadetector/taxonomy_mapping/taxonomy_graph.py +346 -0
  119. megadetector/taxonomy_mapping/validate_lila_category_mappings.py +83 -0
  120. megadetector/tests/__init__.py +0 -0
  121. megadetector/tests/test_nms_synthetic.py +335 -0
  122. megadetector/utils/__init__.py +0 -0
  123. megadetector/utils/ct_utils.py +1857 -0
  124. megadetector/utils/directory_listing.py +199 -0
  125. megadetector/utils/extract_frames_from_video.py +307 -0
  126. megadetector/utils/gpu_test.py +125 -0
  127. megadetector/utils/md_tests.py +2072 -0
  128. megadetector/utils/path_utils.py +2872 -0
  129. megadetector/utils/process_utils.py +172 -0
  130. megadetector/utils/split_locations_into_train_val.py +237 -0
  131. megadetector/utils/string_utils.py +234 -0
  132. megadetector/utils/url_utils.py +825 -0
  133. megadetector/utils/wi_platform_utils.py +968 -0
  134. megadetector/utils/wi_taxonomy_utils.py +1766 -0
  135. megadetector/utils/write_html_image_list.py +239 -0
  136. megadetector/visualization/__init__.py +0 -0
  137. megadetector/visualization/plot_utils.py +309 -0
  138. megadetector/visualization/render_images_with_thumbnails.py +243 -0
  139. megadetector/visualization/visualization_utils.py +1973 -0
  140. megadetector/visualization/visualize_db.py +630 -0
  141. megadetector/visualization/visualize_detector_output.py +498 -0
  142. megadetector/visualization/visualize_video_output.py +705 -0
  143. megadetector-10.0.15.dist-info/METADATA +115 -0
  144. megadetector-10.0.15.dist-info/RECORD +147 -0
  145. megadetector-10.0.15.dist-info/WHEEL +5 -0
  146. megadetector-10.0.15.dist-info/licenses/LICENSE +19 -0
  147. megadetector-10.0.15.dist-info/top_level.txt +1 -0
@@ -0,0 +1,276 @@
1
+ """
2
+
3
+ map_classification_categories.py
4
+
5
+ Maps a classifier's output categories to desired target categories.
6
+
7
+ In this file, we use the following terminology:
8
+ * "category": a category output by the classifier
9
+ * "target": name of a desired group, comprising >= 1 classifier categories
10
+
11
+ Takes as input 2 label specification JSON files:
12
+
13
+ 1) desired label specification JSON file
14
+ this should not have a target named "other"
15
+
16
+ 2) label specification JSON file of trained classifier
17
+
18
+ The mapping is accomplished as follows:
19
+
20
+ 1. For each category in the classifier label spec, find all taxon nodes that
21
+ belong to that category.
22
+
23
+ 2. Given a target in the desired label spec, find all taxon nodes that belong
24
+ to that target. If there is any classifier category whose nodes are a
25
+ subset of the target nodes, then map the classifier category to that target.
26
+ Any partial intersection between a target's nodes and a category's nodes
27
+ is considered an error.
28
+
29
+ 3. If there are any classifier categories that have not yet been assigned a
30
+ target, group them into the "other" target.
31
+
32
+ This script outputs a JSON file that maps each target to a list of classifier
33
+ categories.
34
+
35
+ The taxonomy mapping parts of this script are very similar to json_validator.py.
36
+
37
+ """
38
+
39
+ #%% Imports
40
+
41
+ from __future__ import annotations
42
+
43
+ import argparse
44
+ import json
45
+ import os
46
+
47
+ from collections import defaultdict
48
+ from collections.abc import Mapping
49
+ from typing import Any, Optional
50
+
51
+ import networkx as nx
52
+ import pandas as pd
53
+ from tqdm import tqdm
54
+
55
+ from megadetector.taxonomy_mapping.taxonomy_graph import (
56
+ build_taxonomy_graph, dag_to_tree, TaxonNode)
57
+ from megadetector.utils import ct_utils
58
+
59
+
60
+ #%% Example usage
61
+
62
+ """
63
+ python map_classification_categories.py \
64
+ desired_label_spec.json \
65
+ /path/to/classifier/label_spec.json \
66
+ $HOME/camera-traps-private/camera_trap_taxonomy_mapping.csv
67
+ """
68
+
69
+
70
+ #%% Main function
71
+
72
+ def main(desired_label_spec_json_path: str,
73
+ classifier_label_spec_json_path: str,
74
+ taxonomy_csv_path: str,
75
+ output_json_path: str,
76
+ classifier_label_index_path: Optional[str]) -> None:
77
+
78
+ print('Reading label spec JSON files')
79
+ with open(desired_label_spec_json_path, 'r') as f:
80
+ target_spec = json.load(f)
81
+ with open(classifier_label_spec_json_path, 'r') as f:
82
+ classifier_spec = json.load(f)
83
+
84
+ if classifier_label_index_path is not None:
85
+ with open(classifier_label_index_path, 'r') as f:
86
+ classifier_labels = set(json.load(f).values())
87
+ assert classifier_labels <= set(classifier_spec.keys())
88
+ if len(classifier_labels) < len(classifier_spec):
89
+ classifier_spec = {
90
+ k: v for k, v in classifier_spec.items()
91
+ if k in classifier_labels
92
+ }
93
+
94
+ print('Building taxonomy hierarchy')
95
+ taxonomy_df = pd.read_csv(taxonomy_csv_path)
96
+ graph, taxon_to_node, label_to_node = build_taxonomy_graph(taxonomy_df)
97
+ dag_to_tree(graph, taxon_to_node)
98
+
99
+ print('Mapping label spec to nodes')
100
+ classifier_label_to_nodes = label_spec_to_nodes(
101
+ classifier_spec, taxon_to_node, label_to_node)
102
+ target_label_to_nodes = label_spec_to_nodes(
103
+ target_spec, taxon_to_node, label_to_node)
104
+
105
+ print('Creating mapping from target to classifier categories')
106
+ target_to_classifier_labels = map_target_to_classifier(
107
+ target_label_to_nodes, classifier_label_to_nodes)
108
+ os.makedirs(os.path.dirname(output_json_path), exist_ok=True)
109
+ ct_utils.write_json(output_json_path, target_to_classifier_labels)
110
+
111
+
112
+ #%% Support functions
113
+
114
+ def map_target_to_classifier(
115
+ target_label_to_nodes: Mapping[str, set[TaxonNode]],
116
+ classifier_label_to_nodes: Mapping[str, set[TaxonNode]]
117
+ ) -> dict[str, list[str]]:
118
+ """
119
+ For each target, if there is any classifier category whose nodes are a
120
+ subset of the target nodes, then assign the classifier category to that
121
+ target. Any partial intersection between a target's nodes and a category's
122
+ nodes is considered an error.
123
+
124
+ Args:
125
+ target_label_to_nodes: dict, maps target to set of nodes,
126
+ all of the sets of nodes should be disjoint
127
+ classifier_label_to_nodes: dict, maps classifier label to set of nodes,
128
+ all of the sets of nodes should be disjoint
129
+
130
+ Returns: dict, maps target label to set of classifier labels
131
+ """
132
+
133
+ remaining_classifier_labels = set(classifier_label_to_nodes.keys())
134
+ target_to_classifier_labels: defaultdict[str, set[str]] = defaultdict(set)
135
+ for target, target_nodes in tqdm(target_label_to_nodes.items()):
136
+ for label, classifier_nodes in classifier_label_to_nodes.items():
137
+ overlap = classifier_nodes & target_nodes
138
+ if len(overlap) == len(classifier_nodes):
139
+ target_to_classifier_labels[target].add(label)
140
+ remaining_classifier_labels.remove(label)
141
+ elif 0 < len(overlap) < len(classifier_nodes): # partial overlap
142
+ raise ValueError('Only partial overlap between target '
143
+ f'{target} and classifier label {label}')
144
+ if len(remaining_classifier_labels) > 0:
145
+ target_to_classifier_labels['other'] = remaining_classifier_labels
146
+ target_to_sorted_labels = {
147
+ target: sorted(labels_set)
148
+ for target, labels_set in target_to_classifier_labels.items()
149
+ }
150
+ return target_to_sorted_labels
151
+
152
+
153
+ def parse_spec(spec_dict: Mapping[str, Any],
154
+ taxon_to_node: dict[tuple[str, str], TaxonNode],
155
+ label_to_node: dict[tuple[str, str], TaxonNode]
156
+ ) -> set[TaxonNode]:
157
+ """
158
+ Args:
159
+ spec_dict: dict, contains keys ['taxa', 'dataset_labels', 'exclude']
160
+ {
161
+ "taxa": [
162
+ {'level': 'family', 'name': 'cervidae', 'datasets': ['idfg']},
163
+ {'level': 'genus', 'name': 'meleagris'} ],
164
+ "dataset_labels": { "idfg_swwlf_2019": ["bird"] },
165
+ "exclude": {...}
166
+ }
167
+ taxon_to_node: dict, maps (taxon_level, taxon_name) to a TaxonNode
168
+ label_to_node: dict, maps (dataset_name, dataset_label) to the lowest
169
+ TaxonNode node in the tree that contains the label
170
+
171
+ Returns: set of TaxonNode, nodes selected by the taxa spec
172
+
173
+ Raises: ValueError, if specification does not match any dataset labels
174
+ """
175
+
176
+ result = set()
177
+ if 'taxa' in spec_dict:
178
+ for taxon in spec_dict['taxa']:
179
+ key = (taxon['level'].lower(), taxon['name'].lower())
180
+ if key in taxon_to_node:
181
+ node = taxon_to_node[key]
182
+ result.add(node)
183
+ result |= nx.descendants(node.graph, node)
184
+ else:
185
+ print(f'Taxon {key} not found in taxonomy graph. Ignoring.')
186
+ if 'dataset_labels' in spec_dict:
187
+ for ds, ds_labels in spec_dict['dataset_labels'].items():
188
+ ds = ds.lower()
189
+ for ds_label in ds_labels:
190
+ node = label_to_node[(ds, ds_label.lower())]
191
+ result.add(node)
192
+ result |= nx.descendants(node.graph, node)
193
+ if 'exclude' in spec_dict:
194
+ result -= parse_spec(spec_dict['exclude'], taxon_to_node, label_to_node)
195
+ if len(result) == 0:
196
+ raise ValueError(f'specification matched no TaxonNode: {spec_dict}')
197
+ return result
198
+
199
+
200
+ def label_spec_to_nodes(label_spec_js: dict[str, dict[str, Any]],
201
+ taxon_to_node: dict[tuple[str, str], TaxonNode],
202
+ label_to_node: dict[tuple[str, str], TaxonNode]
203
+ ) -> dict[str, set[TaxonNode]]:
204
+ """
205
+ Convert label spec to a mapping from classification labels to a set of
206
+ nodes.
207
+
208
+ Args:
209
+ label_spec_js: dict, Python dict representation of JSON file
210
+ see classification/README.md
211
+ taxon_to_node: dict, maps (taxon_level, taxon_name) to a TaxonNode
212
+ label_to_node: dict, maps (dataset_name, dataset_label) to the lowest
213
+ TaxonNode node in the tree that contains the label
214
+
215
+ Returns: dict, maps label name to set of TaxonNode
216
+
217
+ Raises: ValueError, if a classification label specification matches no
218
+ TaxonNode, or if a node is included in two or more classification labels
219
+ """
220
+
221
+ # maps output label name to set of (dataset, dataset_label) tuples
222
+ seen_nodes: set[TaxonNode] = set()
223
+ label_to_nodes: dict[str, set[TaxonNode]] = {}
224
+ for label, spec_dict in label_spec_js.items():
225
+ include_set = parse_spec(spec_dict, taxon_to_node, label_to_node)
226
+ if include_set.isdisjoint(seen_nodes):
227
+ label_to_nodes[label] = include_set
228
+ seen_nodes |= include_set
229
+ else:
230
+ # find which other label (label_b) has intersection
231
+ for label_b, set_b in label_to_nodes.items():
232
+ shared = include_set.intersection(set_b)
233
+ if len(shared) > 0:
234
+ print(f'Labels {label} and {label_b} share images:', shared)
235
+ raise ValueError('Intersection between sets!')
236
+ return label_to_nodes
237
+
238
+
239
+ #%% Command-line driver
240
+
241
+ def _parse_args() -> argparse.Namespace:
242
+
243
+ parser = argparse.ArgumentParser(
244
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
245
+ description='Create mapping from target categories to classifier '
246
+ 'labels.')
247
+ parser.add_argument(
248
+ 'desired_label_spec_json',
249
+ help='path to JSON file containing desired label specification')
250
+ parser.add_argument(
251
+ 'classifier_label_spec_json',
252
+ help='path to JSON file containing label specification of a trained '
253
+ 'classifier')
254
+ parser.add_argument(
255
+ 'taxonomy_csv',
256
+ help='path to taxonomy CSV file')
257
+ parser.add_argument(
258
+ '-o', '--output', required=True,
259
+ help='path to output JSON')
260
+ parser.add_argument(
261
+ '-i', '--classifier-label-index',
262
+ help='(optional) path to label index JSON file for trained classifier, '
263
+ 'needed if not all labels from <classifier_label_spec_json> were '
264
+ 'actually used (e.g., if some labels were filtered out by the '
265
+ '--min-locs argument for create_classification_dataset.py)')
266
+ return parser.parse_args()
267
+
268
+
269
+ if __name__ == '__main__':
270
+
271
+ args = _parse_args()
272
+ main(desired_label_spec_json_path=args.desired_label_spec_json,
273
+ classifier_label_spec_json_path=args.classifier_label_spec_json,
274
+ taxonomy_csv_path=args.taxonomy_csv,
275
+ output_json_path=args.output,
276
+ classifier_label_index_path=args.classifier_label_index)