megadetector 5.0.27__py3-none-any.whl → 5.0.28__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of megadetector might be problematic. Click here for more details.
- megadetector/data_management/mewc_to_md.py +1 -1
- megadetector/data_management/read_exif.py +2 -0
- megadetector/detection/process_video.py +1 -1
- megadetector/detection/pytorch_detector.py +4 -4
- megadetector/detection/run_detector.py +10 -3
- megadetector/detection/run_detector_batch.py +4 -3
- megadetector/detection/run_tiled_inference.py +65 -13
- megadetector/detection/video_utils.py +2 -2
- megadetector/postprocessing/classification_postprocessing.py +517 -20
- megadetector/postprocessing/create_crop_folder.py +1 -1
- megadetector/postprocessing/generate_csv_report.py +499 -0
- megadetector/postprocessing/load_api_results.py +4 -4
- megadetector/postprocessing/postprocess_batch_results.py +6 -4
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +0 -3
- megadetector/taxonomy_mapping/taxonomy_graph.py +1 -1
- megadetector/utils/ct_utils.py +3 -2
- megadetector/utils/path_utils.py +75 -29
- megadetector/utils/split_locations_into_train_val.py +16 -3
- megadetector/utils/wi_utils.py +68 -410
- megadetector/visualization/visualization_utils.py +25 -9
- megadetector/visualization/visualize_detector_output.py +50 -28
- {megadetector-5.0.27.dist-info → megadetector-5.0.28.dist-info}/METADATA +132 -132
- {megadetector-5.0.27.dist-info → megadetector-5.0.28.dist-info}/RECORD +26 -25
- {megadetector-5.0.27.dist-info → megadetector-5.0.28.dist-info}/WHEEL +1 -1
- {megadetector-5.0.27.dist-info → megadetector-5.0.28.dist-info}/licenses/LICENSE +0 -0
- {megadetector-5.0.27.dist-info → megadetector-5.0.28.dist-info}/top_level.txt +0 -0
megadetector/utils/path_utils.py
CHANGED
|
@@ -72,7 +72,7 @@ def recursive_file_list(base_dir,
|
|
|
72
72
|
assert os.path.isdir(base_dir), '{} is not a folder'.format(base_dir)
|
|
73
73
|
|
|
74
74
|
all_files = []
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
if recursive:
|
|
77
77
|
for root, _, filenames in os.walk(base_dir):
|
|
78
78
|
for filename in filenames:
|
|
@@ -454,6 +454,25 @@ def top_level_folder(p):
|
|
|
454
454
|
# ...top_level_folder()
|
|
455
455
|
|
|
456
456
|
|
|
457
|
+
def path_join(*paths, convert_slashes=True):
|
|
458
|
+
r"""
|
|
459
|
+
Wrapper for os.path.join that optionally converts backslashes to forward slashes.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
*paths (variable-length set of strings): Path components to be joined.
|
|
463
|
+
convert_slashes (bool, optional): whether to convert \\ to /
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
A string with the joined path components.
|
|
467
|
+
"""
|
|
468
|
+
|
|
469
|
+
joined_path = os.path.join(*paths)
|
|
470
|
+
if convert_slashes:
|
|
471
|
+
return joined_path.replace('\\', '/')
|
|
472
|
+
else:
|
|
473
|
+
return joined_path
|
|
474
|
+
|
|
475
|
+
|
|
457
476
|
#%% Test driver for top_level_folder
|
|
458
477
|
|
|
459
478
|
if False:
|
|
@@ -665,10 +684,9 @@ def environment_is_wsl():
|
|
|
665
684
|
return 'microsoft' in platform_string and 'wsl' in platform_string
|
|
666
685
|
|
|
667
686
|
|
|
668
|
-
def wsl_path_to_windows_path(filename):
|
|
687
|
+
def wsl_path_to_windows_path(filename, failure_behavior='none'):
|
|
669
688
|
r"""
|
|
670
|
-
Converts a WSL path to a Windows path,
|
|
671
|
-
converts:
|
|
689
|
+
Converts a WSL path to a Windows path. For example, converts:
|
|
672
690
|
|
|
673
691
|
/mnt/e/a/b/c
|
|
674
692
|
|
|
@@ -678,27 +696,42 @@ def wsl_path_to_windows_path(filename):
|
|
|
678
696
|
|
|
679
697
|
Args:
|
|
680
698
|
filename (str): filename to convert
|
|
699
|
+
failure_behavior (str): what to do if the path can't be processed as a WSL path.
|
|
700
|
+
'none' to return None in this case, 'original' to return the original path.
|
|
681
701
|
|
|
682
702
|
Returns:
|
|
683
|
-
str: Windows equivalent to the WSL path [filename]
|
|
684
|
-
environment is neither Windows nor WSL.
|
|
703
|
+
str: Windows equivalent to the WSL path [filename]
|
|
685
704
|
"""
|
|
686
705
|
|
|
687
|
-
|
|
688
|
-
|
|
706
|
+
assert failure_behavior in ('none','original'), \
|
|
707
|
+
'Unrecognized failure_behavior value {}'.format(failure_behavior)
|
|
689
708
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
result = subprocess.run(['wsl', 'wslpath', '-w', filename], text=True, capture_output=True)
|
|
694
|
-
if result.returncode != 0:
|
|
695
|
-
print('Could not convert path {} from WSL to Windows'.format(filename))
|
|
696
|
-
return None
|
|
709
|
+
# Check whether the path follows the standard WSL mount pattern
|
|
710
|
+
wsl_path_pattern = r'^/mnt/([a-zA-Z])(/.*)?$'
|
|
711
|
+
match = re.match(wsl_path_pattern, filename)
|
|
697
712
|
|
|
698
|
-
|
|
713
|
+
if match:
|
|
714
|
+
|
|
715
|
+
# Extract the drive letter and the rest of the path
|
|
716
|
+
drive_letter = match.group(1)
|
|
717
|
+
path_remainder = match.group(2) if match.group(2) else ''
|
|
718
|
+
|
|
719
|
+
# Convert forward slashes to backslashes for Windows
|
|
720
|
+
path_remainder = path_remainder.replace('/', '\\')
|
|
721
|
+
|
|
722
|
+
# Format the Windows path
|
|
723
|
+
windows_path = f"{drive_letter}:{path_remainder}"
|
|
724
|
+
return windows_path
|
|
699
725
|
|
|
726
|
+
if failure_behavior == 'none':
|
|
727
|
+
return None
|
|
728
|
+
else:
|
|
729
|
+
return filename
|
|
700
730
|
|
|
701
|
-
def
|
|
731
|
+
# ...def wsl_path_to_windows_path(...)
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
def windows_path_to_wsl_path(filename, failure_behavior='none'):
|
|
702
735
|
r"""
|
|
703
736
|
Converts a Windows path to a WSL path, or returns None if that's not possible. E.g.
|
|
704
737
|
converts:
|
|
@@ -711,25 +744,38 @@ def windows_path_to_wsl_path(filename):
|
|
|
711
744
|
|
|
712
745
|
Args:
|
|
713
746
|
filename (str): filename to convert
|
|
747
|
+
failure_behavior (str): what to do if the path can't be processed as a Windows path.
|
|
748
|
+
'none' to return None in this case, 'original' to return the original path.
|
|
714
749
|
|
|
715
750
|
Returns:
|
|
716
|
-
str: WSL equivalent to the Windows path [filename]
|
|
717
|
-
environment is neither Windows nor WSL.
|
|
751
|
+
str: WSL equivalent to the Windows path [filename]
|
|
718
752
|
"""
|
|
719
753
|
|
|
720
|
-
|
|
721
|
-
|
|
754
|
+
assert failure_behavior in ('none','original'), \
|
|
755
|
+
'Unrecognized failure_behavior value {}'.format(failure_behavior)
|
|
722
756
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
757
|
+
filename = filename.replace('\\', '/')
|
|
758
|
+
|
|
759
|
+
# Check whether the path follows a Windows drive letter pattern
|
|
760
|
+
windows_path_pattern = r'^([a-zA-Z]):(/.*)?$'
|
|
761
|
+
match = re.match(windows_path_pattern, filename)
|
|
762
|
+
|
|
763
|
+
if match:
|
|
764
|
+
# Extract the drive letter and the rest of the path
|
|
765
|
+
drive_letter = match.group(1).lower() # Convert to lowercase for WSL
|
|
766
|
+
path_remainder = match.group(2) if match.group(2) else ''
|
|
767
|
+
|
|
768
|
+
# Format the WSL path
|
|
769
|
+
wsl_path = f"/mnt/{drive_letter}{path_remainder}"
|
|
770
|
+
return wsl_path
|
|
771
|
+
|
|
772
|
+
if failure_behavior == 'none':
|
|
729
773
|
return None
|
|
774
|
+
else:
|
|
775
|
+
return filename
|
|
730
776
|
|
|
731
|
-
|
|
732
|
-
|
|
777
|
+
# ...def window_path_to_wsl_path(...)
|
|
778
|
+
|
|
733
779
|
|
|
734
780
|
def open_file_in_chrome(filename):
|
|
735
781
|
"""
|
|
@@ -28,7 +28,8 @@ def split_locations_into_train_val(location_to_category_counts,
|
|
|
28
28
|
target_val_fraction=0.15,
|
|
29
29
|
category_to_max_allowable_error=None,
|
|
30
30
|
category_to_error_weight=None,
|
|
31
|
-
default_max_allowable_error=0.1
|
|
31
|
+
default_max_allowable_error=0.1,
|
|
32
|
+
require_complete_coverage=True):
|
|
32
33
|
"""
|
|
33
34
|
Splits a list of location IDs into training and validation, targeting a specific
|
|
34
35
|
train/val split for each category, but allowing some categories to be tighter or looser
|
|
@@ -63,6 +64,8 @@ def split_locations_into_train_val(location_to_category_counts,
|
|
|
63
64
|
default_max_allowable_error (float, optional): the maximum allowable error for categories not
|
|
64
65
|
present in [category_to_max_allowable_error]. Set to None (or >= 1.0) to disable hard
|
|
65
66
|
constraints for categories not present in [category_to_max_allowable_error]
|
|
67
|
+
require_complete_coverage (bool, optional): require that every category appear in both train and
|
|
68
|
+
val
|
|
66
69
|
|
|
67
70
|
Returns:
|
|
68
71
|
tuple: A two-element tuple:
|
|
@@ -125,7 +128,7 @@ def split_locations_into_train_val(location_to_category_counts,
|
|
|
125
128
|
category_val_fraction = category_val_count / (category_val_count + category_train_count)
|
|
126
129
|
category_to_val_fraction[category_id] = category_val_fraction
|
|
127
130
|
|
|
128
|
-
# Absolute deviation from the target val fraction for each
|
|
131
|
+
# Absolute deviation from the target val fraction for each category
|
|
129
132
|
category_errors = {}
|
|
130
133
|
weighted_category_errors = {}
|
|
131
134
|
|
|
@@ -161,18 +164,28 @@ def split_locations_into_train_val(location_to_category_counts,
|
|
|
161
164
|
seed_satisfies_hard_constraints = True
|
|
162
165
|
|
|
163
166
|
for category in category_to_val_fraction:
|
|
164
|
-
if category in category_to_max_allowable_error:
|
|
167
|
+
if category in category_to_max_allowable_error:
|
|
165
168
|
max_allowable_error = category_to_max_allowable_error[category]
|
|
166
169
|
else:
|
|
167
170
|
if default_max_allowable_error is None:
|
|
168
171
|
continue
|
|
169
172
|
max_allowable_error = default_max_allowable_error
|
|
170
173
|
val_fraction = category_to_val_fraction[category]
|
|
174
|
+
|
|
175
|
+
# If necessary, verify that this category doesn't *only* appear in train or val
|
|
176
|
+
if require_complete_coverage:
|
|
177
|
+
if (val_fraction == 0.0) or (val_fraction == 1.0):
|
|
178
|
+
seed_satisfies_hard_constraints = False
|
|
179
|
+
break
|
|
180
|
+
|
|
181
|
+
# Check whether this category exceeds the hard maximum deviation
|
|
171
182
|
category_error = abs(val_fraction - target_val_fraction)
|
|
172
183
|
if category_error > max_allowable_error:
|
|
173
184
|
seed_satisfies_hard_constraints = False
|
|
174
185
|
break
|
|
175
186
|
|
|
187
|
+
# ...for each category
|
|
188
|
+
|
|
176
189
|
if seed_satisfies_hard_constraints:
|
|
177
190
|
random_seed_to_weighted_average_error[random_seed] = weighted_average_error
|
|
178
191
|
|