megadetector 10.0.3__py3-none-any.whl → 10.0.5__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.

Files changed (32) hide show
  1. megadetector/data_management/animl_to_md.py +158 -0
  2. megadetector/data_management/cct_json_utils.py +1 -0
  3. megadetector/data_management/speciesnet_to_md.py +2 -2
  4. megadetector/data_management/zamba_to_md.py +188 -0
  5. megadetector/detection/process_video.py +52 -40
  6. megadetector/detection/pytorch_detector.py +24 -34
  7. megadetector/detection/run_detector_batch.py +138 -93
  8. megadetector/detection/run_md_and_speciesnet.py +22 -4
  9. megadetector/detection/video_utils.py +5 -4
  10. megadetector/postprocessing/classification_postprocessing.py +26 -10
  11. megadetector/postprocessing/combine_batch_outputs.py +2 -0
  12. megadetector/postprocessing/generate_csv_report.py +1 -1
  13. megadetector/postprocessing/load_api_results.py +1 -1
  14. megadetector/postprocessing/md_to_wi.py +1 -1
  15. megadetector/postprocessing/postprocess_batch_results.py +1 -1
  16. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1 -1
  17. megadetector/postprocessing/separate_detections_into_folders.py +1 -1
  18. megadetector/postprocessing/subset_json_detector_output.py +1 -3
  19. megadetector/utils/ct_utils.py +71 -0
  20. megadetector/utils/md_tests.py +8 -7
  21. megadetector/utils/path_utils.py +4 -15
  22. megadetector/utils/wi_platform_utils.py +824 -0
  23. megadetector/utils/wi_taxonomy_utils.py +1711 -0
  24. megadetector/visualization/visualization_utils.py +1 -1
  25. megadetector/visualization/visualize_detector_output.py +7 -5
  26. megadetector/visualization/visualize_video_output.py +1 -1
  27. {megadetector-10.0.3.dist-info → megadetector-10.0.5.dist-info}/METADATA +2 -2
  28. {megadetector-10.0.3.dist-info → megadetector-10.0.5.dist-info}/RECORD +31 -28
  29. megadetector/utils/wi_utils.py +0 -2674
  30. {megadetector-10.0.3.dist-info → megadetector-10.0.5.dist-info}/WHEEL +0 -0
  31. {megadetector-10.0.3.dist-info → megadetector-10.0.5.dist-info}/licenses/LICENSE +0 -0
  32. {megadetector-10.0.3.dist-info → megadetector-10.0.5.dist-info}/top_level.txt +0 -0
@@ -42,7 +42,7 @@ import pandas as pd
42
42
 
43
43
  from copy import deepcopy
44
44
 
45
- from megadetector.utils.wi_utils import load_md_or_speciesnet_file
45
+ from megadetector.utils.wi_taxonomy_utils import load_md_or_speciesnet_file
46
46
  from megadetector.utils.ct_utils import get_max_conf
47
47
  from megadetector.utils.ct_utils import is_list_sorted
48
48
  from megadetector.detection.run_detector import \
@@ -24,7 +24,7 @@ from collections.abc import Mapping
24
24
  import pandas as pd
25
25
 
26
26
  from megadetector.utils import ct_utils
27
- from megadetector.utils.wi_utils import load_md_or_speciesnet_file
27
+ from megadetector.utils.wi_taxonomy_utils import load_md_or_speciesnet_file
28
28
 
29
29
 
30
30
  #%% Functions for loading .json results into a Pandas DataFrame, and writing back to .json
@@ -10,7 +10,7 @@ Converts the MD .json format to the WI predictions.json format.
10
10
 
11
11
  import sys
12
12
  import argparse
13
- from megadetector.utils.wi_utils import generate_predictions_json_from_md_results
13
+ from megadetector.utils.wi_taxonomy_utils import generate_predictions_json_from_md_results
14
14
 
15
15
 
16
16
  #%% Command-line driver
@@ -47,7 +47,7 @@ from tqdm import tqdm
47
47
  from megadetector.visualization import visualization_utils as vis_utils
48
48
  from megadetector.visualization import plot_utils
49
49
  from megadetector.utils.write_html_image_list import write_html_image_list
50
- from megadetector.utils.wi_utils import load_md_or_speciesnet_file
50
+ from megadetector.utils.wi_taxonomy_utils import load_md_or_speciesnet_file
51
51
  from megadetector.utils import path_utils
52
52
  from megadetector.utils.ct_utils import args_to_object
53
53
  from megadetector.utils.ct_utils import sets_overlap
@@ -637,7 +637,7 @@ def _find_matches_in_directory(dir_name_and_rows, options):
637
637
 
638
638
  if 'max_detection_conf' not in row or 'detections' not in row or \
639
639
  row['detections'] is None:
640
- print('Skipping row {}'.format(i_directory_row))
640
+ # print('Skipping row {}'.format(i_directory_row))
641
641
  continue
642
642
 
643
643
  # Don't bother checking images with no detections above threshold
@@ -587,7 +587,7 @@ def separate_detections_into_folders(options):
587
587
  # token = tokens[0]
588
588
  for token in tokens:
589
589
  subtokens = token.split('=')
590
- assert len(subtokens) == 2 and is_float(subtokens[1]), \
590
+ assert (len(subtokens) == 2) and (is_float(subtokens[1])), \
591
591
  'Illegal classification threshold {}'.format(token)
592
592
  classification_thresholds[subtokens[0]] = float(subtokens[1])
593
593
 
@@ -221,7 +221,7 @@ def remove_classification_categories_below_count(data, options):
221
221
  classification_category_ids_to_keep = set()
222
222
 
223
223
  for classification_category_id in classification_category_id_to_count:
224
- if classification_category_id_to_count[classification_category_id] > \
224
+ if classification_category_id_to_count[classification_category_id] >= \
225
225
  options.remove_classification_categories_below_count:
226
226
  classification_category_ids_to_keep.add(classification_category_id)
227
227
 
@@ -235,7 +235,6 @@ def remove_classification_categories_below_count(data, options):
235
235
  if n_categories_removed == 0:
236
236
  return data
237
237
 
238
-
239
238
  # Filter the category list
240
239
  output_classification_categories = {}
241
240
  for category_id in data['classification_categories']:
@@ -245,7 +244,6 @@ def remove_classification_categories_below_count(data, options):
245
244
  data['classification_categories'] = output_classification_categories
246
245
  assert len(data['classification_categories']) == len(classification_category_ids_to_keep)
247
246
 
248
-
249
247
  # If necessary, filter the category descriptions
250
248
  if 'classification_category_descriptions' in data:
251
249
  output_classification_category_descriptions = {}
@@ -16,6 +16,8 @@ import builtins
16
16
  import datetime
17
17
  import tempfile
18
18
  import shutil
19
+ import platform
20
+ import sys
19
21
  import uuid
20
22
 
21
23
  import jsonpickle
@@ -848,6 +850,24 @@ def isnan(v):
848
850
  return False
849
851
 
850
852
 
853
+ def compare_values_nan_equal(v0,v1):
854
+ """
855
+ Utility function for comparing two values when we want to return True if both
856
+ values are NaN.
857
+
858
+ Args:
859
+ v0 (object): the first value to compare
860
+ v1 (object): the second value to compare
861
+
862
+ Returns:
863
+ bool: True if v0 == v1, or if both v0 and v1 are NaN
864
+ """
865
+
866
+ if isinstance(v0,float) and isinstance(v1,float) and np.isnan(v0) and np.isnan(v1):
867
+ return True
868
+ return v0 == v1
869
+
870
+
851
871
  def sets_overlap(set1, set2):
852
872
  """
853
873
  Determines whether two sets overlap.
@@ -1053,6 +1073,57 @@ def make_test_folder(subfolder=None):
1053
1073
  append_guid=True)
1054
1074
 
1055
1075
 
1076
+ #%% Environment utilities
1077
+
1078
+ def is_sphinx_build():
1079
+ """
1080
+ Determine whether we are running in the context of our Sphinx build.
1081
+
1082
+ Returns:
1083
+ bool: True if we're running a Sphinx build
1084
+ """
1085
+
1086
+ is_sphinx = hasattr(builtins, '__sphinx_build__')
1087
+ return is_sphinx
1088
+
1089
+
1090
+ def is_running_in_gha():
1091
+ """
1092
+ Determine whether we are running on a GitHub Actions runner.
1093
+
1094
+ Returns:
1095
+ bool: True if we're running in a GHA runner
1096
+ """
1097
+
1098
+ running_in_gha = False
1099
+
1100
+ if ('GITHUB_ACTIONS' in os.environ):
1101
+ # Documentation is inconsistent on how this variable presents itself
1102
+ if isinstance(os.environ['GITHUB_ACTIONS'],bool) and \
1103
+ os.environ['GITHUB_ACTIONS']:
1104
+ running_in_gha = True
1105
+ elif isinstance(os.environ['GITHUB_ACTIONS'],str) and \
1106
+ os.environ['GITHUB_ACTIONS'].lower() == ('true'):
1107
+ running_in_gha = True
1108
+
1109
+ return running_in_gha
1110
+
1111
+
1112
+ def environment_is_wsl():
1113
+ """
1114
+ Determines whether we're running in WSL.
1115
+
1116
+ Returns:
1117
+ True if we're running in WSL
1118
+ """
1119
+
1120
+ if sys.platform not in ('linux','posix'):
1121
+ return False
1122
+ platform_string = ' '.join(platform.uname()).lower()
1123
+ return 'microsoft' in platform_string and 'wsl' in platform_string
1124
+
1125
+
1126
+
1056
1127
  #%% Tests
1057
1128
 
1058
1129
  def test_write_json():
@@ -21,6 +21,7 @@ since much of what it tries to test is, e.g., imports.
21
21
  import os
22
22
  import json
23
23
  import glob
24
+ import sys
24
25
  import tempfile
25
26
  import urllib
26
27
  import urllib.request
@@ -141,9 +142,6 @@ class MDTestOptions:
141
142
  #: Number of cores to use for multi-CPU inference tests
142
143
  self.n_cores_for_multiprocessing_tests = 2
143
144
 
144
- #: Number of cores to use for multi-CPU video tests
145
- self.n_cores_for_video_tests = 2
146
-
147
145
  #: Batch size to use when testing batches of size > 1
148
146
  self.alternative_batch_size = 3
149
147
 
@@ -1062,7 +1060,6 @@ def run_python_tests(options):
1062
1060
  video_options.input_video_file = os.path.join(options.scratch_dir,options.test_videos[0])
1063
1061
  video_options.output_json_file = os.path.join(options.scratch_dir,'single_video_output.json')
1064
1062
  video_options.frame_sample = 10
1065
- video_options.n_cores = options.n_cores_for_video_tests
1066
1063
  video_options.detector_options = copy(options.detector_options)
1067
1064
 
1068
1065
  _ = process_videos(video_options)
@@ -1086,7 +1083,6 @@ def run_python_tests(options):
1086
1083
  video_options.output_video_file = None
1087
1084
  video_options.recursive = True
1088
1085
  video_options.verbose = True
1089
- video_options.n_cores = options.n_cores_for_video_tests
1090
1086
  video_options.json_confidence_threshold = 0.05
1091
1087
  video_options.time_sample = 2
1092
1088
  video_options.detector_options = copy(options.detector_options)
@@ -1295,7 +1291,7 @@ def run_cli_tests(options):
1295
1291
 
1296
1292
  batch_string = ' --batch_size {}'.format(options.alternative_batch_size)
1297
1293
 
1298
- # I reduce the number of loader workers here to force batching to actually appen; with a small
1294
+ # I reduce the number of loader workers here to force batching to actually append; with a small
1299
1295
  # number of images and a few that are intentionally corrupt, with the default number of loader
1300
1296
  # workers we end up with batches that are mostly just one image.
1301
1297
  cmd = base_cmd + ' --use_image_queue --preprocess_on_image_queue --loader_workers 2' + batch_string
@@ -1591,9 +1587,9 @@ def run_cli_tests(options):
1591
1587
 
1592
1588
  cmd += ' "{}" "{}"'.format(options.default_model,options.scratch_dir)
1593
1589
  cmd += ' --output_json_file "{}"'.format(video_inference_output_file)
1594
- cmd += ' --n_cores {}'.format(options.n_cores_for_video_tests)
1595
1590
  cmd += ' --frame_sample 4'
1596
1591
  cmd += ' --verbose'
1592
+ cmd += ' --recursive'
1597
1593
  cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
1598
1594
 
1599
1595
  cmd_results = execute_and_print(cmd)
@@ -1789,6 +1785,11 @@ def test_suite_entry_point():
1789
1785
  options.skip_localhost_downloads = True
1790
1786
  options.skip_import_tests = False
1791
1787
 
1788
+ if sys.platform == 'darwin':
1789
+ print('Detected a Mac environment, widening tolerance')
1790
+ options.max_coord_error = 0.05
1791
+ options.max_conf_error = 0.05
1792
+
1792
1793
  options = download_test_data(options)
1793
1794
 
1794
1795
  run_tests(options)
@@ -36,6 +36,7 @@ from tqdm import tqdm
36
36
  from megadetector.utils.ct_utils import is_iterable
37
37
  from megadetector.utils.ct_utils import make_test_folder
38
38
  from megadetector.utils.ct_utils import sort_dictionary_by_value
39
+ from megadetector.utils.ct_utils import environment_is_wsl
39
40
 
40
41
  # Should all be lower-case
41
42
  IMG_EXTENSIONS = ('.jpg', '.jpeg', '.gif', '.png', '.tif', '.tiff', '.bmp')
@@ -622,21 +623,7 @@ def is_executable(filename):
622
623
  return which(filename) is not None
623
624
 
624
625
 
625
- #%% Platform-independent way to open files in their associated application
626
-
627
- def environment_is_wsl():
628
- """
629
- Determines whether we're running in WSL.
630
-
631
- Returns:
632
- True if we're running in WSL.
633
- """
634
-
635
- if sys.platform not in ('linux','posix'):
636
- return False
637
- platform_string = ' '.join(platform.uname()).lower()
638
- return 'microsoft' in platform_string and 'wsl' in platform_string
639
-
626
+ #%% WSL utilities
640
627
 
641
628
  def wsl_path_to_windows_path(filename, failure_behavior='none'):
642
629
  r"""
@@ -731,6 +718,8 @@ def windows_path_to_wsl_path(filename, failure_behavior='none'):
731
718
  # ...def window_path_to_wsl_path(...)
732
719
 
733
720
 
721
+ #%% Platform-independent file openers
722
+
734
723
  def open_file_in_chrome(filename):
735
724
  """
736
725
  Open a file in chrome, regardless of file type. I typically use this to open