megadetector 10.0.8__py3-none-any.whl → 10.0.10__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/read_exif.py +15 -2
- megadetector/detection/pytorch_detector.py +3 -0
- megadetector/detection/run_detector.py +1 -2
- megadetector/detection/run_detector_batch.py +84 -52
- megadetector/detection/run_md_and_speciesnet.py +81 -23
- megadetector/detection/run_tiled_inference.py +56 -15
- megadetector/detection/tf_detector.py +3 -5
- megadetector/postprocessing/classification_postprocessing.py +12 -13
- megadetector/postprocessing/compare_batch_results.py +48 -28
- megadetector/postprocessing/postprocess_batch_results.py +1 -1
- megadetector/postprocessing/subset_json_detector_output.py +80 -0
- megadetector/utils/directory_listing.py +3 -0
- megadetector/utils/path_utils.py +67 -0
- megadetector/utils/string_utils.py +21 -0
- megadetector/utils/wi_platform_utils.py +168 -24
- megadetector/utils/wi_taxonomy_utils.py +1 -0
- megadetector/visualization/visualize_detector_output.py +1 -0
- {megadetector-10.0.8.dist-info → megadetector-10.0.10.dist-info}/METADATA +1 -1
- {megadetector-10.0.8.dist-info → megadetector-10.0.10.dist-info}/RECORD +22 -22
- {megadetector-10.0.8.dist-info → megadetector-10.0.10.dist-info}/WHEEL +0 -0
- {megadetector-10.0.8.dist-info → megadetector-10.0.10.dist-info}/licenses/LICENSE +0 -0
- {megadetector-10.0.8.dist-info → megadetector-10.0.10.dist-info}/top_level.txt +0 -0
|
@@ -61,7 +61,18 @@ DEFAULT_DETECTION_CONFIDENCE_THRESHOLD_FOR_OUTPUT = DEFAULT_OUTPUT_CONFIDENCE_TH
|
|
|
61
61
|
DEFAULT_DETECTOR_BATCH_SIZE = 1
|
|
62
62
|
DEFAULT_CLASSIFIER_BATCH_SIZE = 8
|
|
63
63
|
DEFAULT_LOADER_WORKERS = 4
|
|
64
|
-
|
|
64
|
+
|
|
65
|
+
# This determines the maximum number of images that can get read from disk
|
|
66
|
+
# on each of the producer workers before blocking. The actual size of the queue
|
|
67
|
+
# will be MAX_IMAGE_QUEUE_SIZE_PER_WORKER * n_workers. This is only used for
|
|
68
|
+
# the classification step.
|
|
69
|
+
MAX_IMAGE_QUEUE_SIZE_PER_WORKER = 10
|
|
70
|
+
|
|
71
|
+
# This determines the maximum number of crops that can accumulate in the queue
|
|
72
|
+
# used to communicate between the producers (which read and crop images) and the
|
|
73
|
+
# consumer (which runs the classifier). This is only used for the classification step.
|
|
74
|
+
MAX_BATCH_QUEUE_SIZE = 300
|
|
75
|
+
|
|
65
76
|
DEAFULT_SECONDS_PER_VIDEO_FRAME = 1.0
|
|
66
77
|
|
|
67
78
|
# Max number of classification scores to include per detection
|
|
@@ -71,6 +82,11 @@ DEFAULT_TOP_N_SCORES = 2
|
|
|
71
82
|
# cumulative confidence is above this value
|
|
72
83
|
ROLLUP_TARGET_CONFIDENCE = 0.5
|
|
73
84
|
|
|
85
|
+
# When the called supplies an existing MD results file, should we validate it before
|
|
86
|
+
# starting classification? This tends
|
|
87
|
+
VALIDATE_DETECTION_FILE = False
|
|
88
|
+
|
|
89
|
+
|
|
74
90
|
verbose = False
|
|
75
91
|
|
|
76
92
|
|
|
@@ -109,10 +125,10 @@ class CropBatch:
|
|
|
109
125
|
"""
|
|
110
126
|
|
|
111
127
|
def __init__(self):
|
|
112
|
-
|
|
128
|
+
#: List of preprocessed images
|
|
113
129
|
self.crops = []
|
|
114
130
|
|
|
115
|
-
|
|
131
|
+
#: List of CropMetadata objects
|
|
116
132
|
self.metadata = []
|
|
117
133
|
|
|
118
134
|
def add_crop(self, crop_data, metadata):
|
|
@@ -192,6 +208,7 @@ def _process_image_detections(file_path: str,
|
|
|
192
208
|
|
|
193
209
|
# Preprocess the crop
|
|
194
210
|
try:
|
|
211
|
+
|
|
195
212
|
preprocessed_crop = classifier.preprocess(
|
|
196
213
|
image,
|
|
197
214
|
bboxes=[speciesnet_bbox],
|
|
@@ -199,6 +216,7 @@ def _process_image_detections(file_path: str,
|
|
|
199
216
|
)
|
|
200
217
|
|
|
201
218
|
if preprocessed_crop is not None:
|
|
219
|
+
|
|
202
220
|
metadata = CropMetadata(
|
|
203
221
|
image_file=file_path,
|
|
204
222
|
detection_index=detection_index,
|
|
@@ -207,10 +225,11 @@ def _process_image_detections(file_path: str,
|
|
|
207
225
|
original_height=original_height
|
|
208
226
|
)
|
|
209
227
|
|
|
210
|
-
# Send individual crop
|
|
228
|
+
# Send individual crop to the consumer
|
|
211
229
|
batch_queue.put(('crop', preprocessed_crop, metadata))
|
|
212
230
|
|
|
213
231
|
except Exception as e:
|
|
232
|
+
|
|
214
233
|
print('Warning: failed to preprocess crop from {}, detection {}: {}'.format(
|
|
215
234
|
file_path, detection_index, str(e)))
|
|
216
235
|
|
|
@@ -226,6 +245,8 @@ def _process_image_detections(file_path: str,
|
|
|
226
245
|
'Failed to preprocess crop: {}'.format(str(e)),
|
|
227
246
|
failure_metadata))
|
|
228
247
|
|
|
248
|
+
# ...try/except
|
|
249
|
+
|
|
229
250
|
# ...for each detection in this image
|
|
230
251
|
|
|
231
252
|
# ...def _process_image_detections(...)
|
|
@@ -256,6 +277,7 @@ def _process_video_detections(file_path: str,
|
|
|
256
277
|
frame_to_detections = {}
|
|
257
278
|
|
|
258
279
|
for detection_index, detection in enumerate(detections):
|
|
280
|
+
|
|
259
281
|
conf = detection['conf']
|
|
260
282
|
if conf < detection_confidence_threshold:
|
|
261
283
|
continue
|
|
@@ -267,6 +289,8 @@ def _process_video_detections(file_path: str,
|
|
|
267
289
|
frame_to_detections[frame_number] = []
|
|
268
290
|
frame_to_detections[frame_number].append((detection_index, detection))
|
|
269
291
|
|
|
292
|
+
# ...for each detection in this video
|
|
293
|
+
|
|
270
294
|
if len(frames_with_detections) == 0:
|
|
271
295
|
return
|
|
272
296
|
|
|
@@ -290,6 +314,7 @@ def _process_video_detections(file_path: str,
|
|
|
290
314
|
return
|
|
291
315
|
frame_number = int(match.group(1))
|
|
292
316
|
|
|
317
|
+
# Only process frames for which we have detection results
|
|
293
318
|
if frame_number not in frame_to_detections:
|
|
294
319
|
return
|
|
295
320
|
|
|
@@ -360,13 +385,16 @@ def _process_video_detections(file_path: str,
|
|
|
360
385
|
|
|
361
386
|
# Process the video frames
|
|
362
387
|
try:
|
|
388
|
+
|
|
363
389
|
run_callback_on_frames(
|
|
364
390
|
input_video_file=absolute_file_path,
|
|
365
391
|
frame_callback=frame_callback,
|
|
366
392
|
frames_to_process=frames_to_process,
|
|
367
393
|
verbose=verbose
|
|
368
394
|
)
|
|
395
|
+
|
|
369
396
|
except Exception as e:
|
|
397
|
+
|
|
370
398
|
print('Warning: failed to process video {}: {}'.format(file_path, str(e)))
|
|
371
399
|
|
|
372
400
|
# Send failure information to consumer for the whole video
|
|
@@ -448,6 +476,7 @@ def _crop_producer_func(image_queue: JoinableQueue,
|
|
|
448
476
|
is_video = is_video_file(file_path)
|
|
449
477
|
|
|
450
478
|
if is_video:
|
|
479
|
+
|
|
451
480
|
# Process video
|
|
452
481
|
_process_video_detections(
|
|
453
482
|
file_path=file_path,
|
|
@@ -457,7 +486,9 @@ def _crop_producer_func(image_queue: JoinableQueue,
|
|
|
457
486
|
detection_confidence_threshold=detection_confidence_threshold,
|
|
458
487
|
batch_queue=batch_queue
|
|
459
488
|
)
|
|
489
|
+
|
|
460
490
|
else:
|
|
491
|
+
|
|
461
492
|
# Process image
|
|
462
493
|
_process_image_detections(
|
|
463
494
|
file_path=file_path,
|
|
@@ -571,9 +602,9 @@ def _crop_consumer_func(batch_queue: Queue,
|
|
|
571
602
|
item_type, data, metadata = item
|
|
572
603
|
|
|
573
604
|
if metadata.image_file not in all_results:
|
|
574
|
-
|
|
605
|
+
all_results[metadata.image_file] = {}
|
|
575
606
|
|
|
576
|
-
# We should never be processing the same
|
|
607
|
+
# We should never be processing the same detection twice
|
|
577
608
|
assert metadata.detection_index not in all_results[metadata.image_file]
|
|
578
609
|
|
|
579
610
|
if item_type == 'failure':
|
|
@@ -601,6 +632,7 @@ def _crop_consumer_func(batch_queue: Queue,
|
|
|
601
632
|
|
|
602
633
|
# ...while (we have items to process)
|
|
603
634
|
|
|
635
|
+
# Send all the results at once back to the main process
|
|
604
636
|
results_queue.put(all_results)
|
|
605
637
|
|
|
606
638
|
if verbose:
|
|
@@ -828,7 +860,7 @@ def _run_detection_step(source_folder: str,
|
|
|
828
860
|
batch_size=detector_batch_size,
|
|
829
861
|
include_image_size=False,
|
|
830
862
|
include_image_timestamp=False,
|
|
831
|
-
|
|
863
|
+
include_exif_tags=None,
|
|
832
864
|
loader_workers=detector_worker_threads,
|
|
833
865
|
preprocess_on_image_queue=True
|
|
834
866
|
)
|
|
@@ -914,9 +946,11 @@ def _run_classification_step(detector_results_file: str,
|
|
|
914
946
|
top_n_scores (int, optional): maximum number of scores to include for each detection
|
|
915
947
|
"""
|
|
916
948
|
|
|
917
|
-
print('Starting
|
|
949
|
+
print('Starting classification step...')
|
|
918
950
|
|
|
919
951
|
# Load MegaDetector results
|
|
952
|
+
print('Reading detection results from {}'.format(detector_results_file))
|
|
953
|
+
|
|
920
954
|
with open(detector_results_file, 'r') as f:
|
|
921
955
|
detector_results = json.load(f)
|
|
922
956
|
|
|
@@ -936,10 +970,22 @@ def _run_classification_step(detector_results_file: str,
|
|
|
936
970
|
print('Set multiprocessing start method to spawn (was {})'.format(
|
|
937
971
|
original_start_method))
|
|
938
972
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
973
|
+
## Set up multiprocessing queues
|
|
974
|
+
|
|
975
|
+
# This queue receives lists of image filenames (and associated detection results)
|
|
976
|
+
# from the "main" thread (the one you're reading right now). Items are pulled off
|
|
977
|
+
# of this queue by producer workers (on _crop_producer_func), where the corresponding
|
|
978
|
+
# images are loaded from disk and preprocessed into crops.
|
|
979
|
+
image_queue = JoinableQueue(maxsize= \
|
|
980
|
+
classifier_worker_threads * MAX_IMAGE_QUEUE_SIZE_PER_WORKER)
|
|
981
|
+
|
|
982
|
+
# This queue receives cropped images from producers (on _crop_producer_func); those
|
|
983
|
+
# crops are pulled off of this queue by the consumer (on _crop_consumer_func).
|
|
984
|
+
batch_queue = Queue(maxsize=MAX_BATCH_QUEUE_SIZE)
|
|
985
|
+
|
|
986
|
+
# This is not really used as a queue, rather it's just used to send all the results
|
|
987
|
+
# at once from the consumer process to the main process (the one you're reading right
|
|
988
|
+
# now).
|
|
943
989
|
results_queue = Queue()
|
|
944
990
|
|
|
945
991
|
# Start producer workers
|
|
@@ -951,7 +997,9 @@ def _run_classification_step(detector_results_file: str,
|
|
|
951
997
|
p.start()
|
|
952
998
|
producers.append(p)
|
|
953
999
|
|
|
954
|
-
|
|
1000
|
+
|
|
1001
|
+
## Start consumer worker
|
|
1002
|
+
|
|
955
1003
|
consumer = Process(target=_crop_consumer_func,
|
|
956
1004
|
args=(batch_queue, results_queue, classifier_model,
|
|
957
1005
|
classifier_batch_size, classifier_worker_threads,
|
|
@@ -974,16 +1022,23 @@ def _run_classification_step(detector_results_file: str,
|
|
|
974
1022
|
|
|
975
1023
|
print('Finished waiting for input queue')
|
|
976
1024
|
|
|
977
|
-
|
|
1025
|
+
|
|
1026
|
+
## Wait for results
|
|
1027
|
+
|
|
978
1028
|
classification_results = results_queue.get()
|
|
979
1029
|
|
|
980
|
-
|
|
1030
|
+
|
|
1031
|
+
## Clean up processes
|
|
1032
|
+
|
|
981
1033
|
for p in producers:
|
|
982
1034
|
p.join()
|
|
983
1035
|
consumer.join()
|
|
984
1036
|
|
|
985
1037
|
print('Finished waiting for workers')
|
|
986
1038
|
|
|
1039
|
+
|
|
1040
|
+
## Format results and write output
|
|
1041
|
+
|
|
987
1042
|
class CategoryState:
|
|
988
1043
|
"""
|
|
989
1044
|
Helper class to manage classification category IDs.
|
|
@@ -1257,15 +1312,18 @@ def main():
|
|
|
1257
1312
|
print('Intermediate files: {}'.format(temp_folder))
|
|
1258
1313
|
|
|
1259
1314
|
# Determine detector output file path
|
|
1260
|
-
if args.detections_file:
|
|
1315
|
+
if args.detections_file is not None:
|
|
1261
1316
|
detector_output_file = args.detections_file
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1317
|
+
if VALIDATE_DETECTION_FILE:
|
|
1318
|
+
print('Using existing detections file: {}'.format(detector_output_file))
|
|
1319
|
+
validation_options = ValidateBatchResultsOptions()
|
|
1320
|
+
validation_options.check_image_existence = True
|
|
1321
|
+
validation_options.relative_path_base = args.source
|
|
1322
|
+
validation_options.raise_errors = True
|
|
1323
|
+
validate_batch_results(detector_output_file,options=validation_options)
|
|
1324
|
+
print('Validated detections file')
|
|
1325
|
+
else:
|
|
1326
|
+
print('Bypassing validation of {}'.format(args.detections_file))
|
|
1269
1327
|
else:
|
|
1270
1328
|
detector_output_file = os.path.join(temp_folder, 'detector_output.json')
|
|
1271
1329
|
|
|
@@ -39,7 +39,7 @@ from torchvision import ops
|
|
|
39
39
|
from megadetector.detection.run_inference_with_yolov5_val import \
|
|
40
40
|
YoloInferenceOptions,run_inference_with_yolo_val
|
|
41
41
|
from megadetector.detection.run_detector_batch import \
|
|
42
|
-
load_and_run_detector_batch,write_results_to_file
|
|
42
|
+
load_and_run_detector_batch,write_results_to_file,default_loaders
|
|
43
43
|
from megadetector.detection.run_detector import \
|
|
44
44
|
try_download_known_detector, CONF_DIGITS, COORD_DIGITS
|
|
45
45
|
from megadetector.utils import path_utils
|
|
@@ -406,7 +406,9 @@ def run_tiled_inference(model_file,
|
|
|
406
406
|
detector_options=None,
|
|
407
407
|
use_image_queue=True,
|
|
408
408
|
preprocess_on_image_queue=True,
|
|
409
|
-
|
|
409
|
+
loader_workers=default_loaders,
|
|
410
|
+
inference_size=None,
|
|
411
|
+
verbose=False):
|
|
410
412
|
"""
|
|
411
413
|
Runs inference using [model_file] on the images in [image_folder], fist splitting each image up
|
|
412
414
|
into tiles of size [tile_size_x] x [tile_size_y], writing those tiles to [tiling_folder],
|
|
@@ -451,16 +453,17 @@ def run_tiled_inference(model_file,
|
|
|
451
453
|
image_list (list, optional): .json file containing a list of specific images to process. If
|
|
452
454
|
this is supplied, and the paths are absolute, [image_folder] will be ignored. If this is supplied,
|
|
453
455
|
and the paths are relative, they should be relative to [image_folder]
|
|
454
|
-
augment (bool, optional): apply test-time augmentation
|
|
455
|
-
is None
|
|
456
|
+
augment (bool, optional): apply test-time augmentation
|
|
456
457
|
detector_options (dict, optional): parameters to pass to run_detector, only relevant if
|
|
457
458
|
yolo_inference_options is None
|
|
458
459
|
use_image_queue (bool, optional): whether to use a loader worker queue, only relevant if
|
|
459
460
|
yolo_inference_options is None
|
|
460
461
|
preprocess_on_image_queue (bool, optional): whether the image queue should also be responsible
|
|
461
462
|
for preprocessing
|
|
463
|
+
loader_workers (int, optional): number of preprocessing loader workers to use
|
|
462
464
|
inference_size (int, optional): override the default inference image size, only relevant if
|
|
463
465
|
yolo_inference_options is None
|
|
466
|
+
verbose (bool, optional): enable additional debug output
|
|
464
467
|
|
|
465
468
|
Returns:
|
|
466
469
|
dict: MD-formatted results dictionary, identical to what's written to [output_file]
|
|
@@ -522,7 +525,8 @@ def run_tiled_inference(model_file,
|
|
|
522
525
|
|
|
523
526
|
all_image_patch_info = None
|
|
524
527
|
|
|
525
|
-
print('Extracting patches from {} images'.format(
|
|
528
|
+
print('Extracting patches from {} images on {} workers'.format(
|
|
529
|
+
len(image_files_relative),n_patch_extraction_workers))
|
|
526
530
|
|
|
527
531
|
n_workers = n_patch_extraction_workers
|
|
528
532
|
|
|
@@ -632,7 +636,9 @@ def run_tiled_inference(model_file,
|
|
|
632
636
|
detector_options=detector_options,
|
|
633
637
|
use_image_queue=use_image_queue,
|
|
634
638
|
preprocess_on_image_queue=preprocess_on_image_queue,
|
|
635
|
-
image_size=inference_size
|
|
639
|
+
image_size=inference_size,
|
|
640
|
+
verbose_output=verbose,
|
|
641
|
+
loader_workers=loader_workers)
|
|
636
642
|
|
|
637
643
|
patch_level_output_file = os.path.join(tiling_folder,folder_name + '_patch_level_results.json')
|
|
638
644
|
|
|
@@ -847,12 +853,12 @@ if False:
|
|
|
847
853
|
yolo_inference_options.yolo_working_folder = os.path.expanduser('~/git/yolov5')
|
|
848
854
|
|
|
849
855
|
run_tiled_inference(model_file, image_folder, tiling_folder, output_file,
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
+
tile_size_x=tile_size_x, tile_size_y=tile_size_y,
|
|
857
|
+
tile_overlap=tile_overlap,
|
|
858
|
+
checkpoint_path=checkpoint_path,
|
|
859
|
+
checkpoint_frequency=checkpoint_frequency,
|
|
860
|
+
remove_tiles=remove_tiles,
|
|
861
|
+
yolo_inference_options=yolo_inference_options)
|
|
856
862
|
|
|
857
863
|
|
|
858
864
|
#%% Run tiled inference (generate a command)
|
|
@@ -930,6 +936,14 @@ def main():
|
|
|
930
936
|
'--no_remove_tiles',
|
|
931
937
|
action='store_true',
|
|
932
938
|
help='Tiles are removed by default; this option suppresses tile deletion')
|
|
939
|
+
parser.add_argument(
|
|
940
|
+
'--augment',
|
|
941
|
+
action='store_true',
|
|
942
|
+
help='Enable test-time augmentation')
|
|
943
|
+
parser.add_argument(
|
|
944
|
+
'--verbose',
|
|
945
|
+
action='store_true',
|
|
946
|
+
help='Enable additional debug output')
|
|
933
947
|
parser.add_argument(
|
|
934
948
|
'--tile_size_x',
|
|
935
949
|
type=int,
|
|
@@ -960,6 +974,21 @@ def main():
|
|
|
960
974
|
type=str,
|
|
961
975
|
default=None,
|
|
962
976
|
help=('A list of detector options (key-value pairs)'))
|
|
977
|
+
parser.add_argument(
|
|
978
|
+
'--inference_size',
|
|
979
|
+
type=int,
|
|
980
|
+
default=None,
|
|
981
|
+
help=('Run inference at a non-default size'))
|
|
982
|
+
parser.add_argument(
|
|
983
|
+
'--n_patch_extraction_workers',
|
|
984
|
+
type=int,
|
|
985
|
+
default=1,
|
|
986
|
+
help=('Number of workers to use for patch extraction'))
|
|
987
|
+
parser.add_argument(
|
|
988
|
+
'--loader_workers',
|
|
989
|
+
type=int,
|
|
990
|
+
default=default_loaders,
|
|
991
|
+
help=('Number of workers to use for image loading and preprocessing (0 to disable)'))
|
|
963
992
|
|
|
964
993
|
# detector_options = parse_kvp_list(args.detector_options)
|
|
965
994
|
|
|
@@ -987,11 +1016,23 @@ def main():
|
|
|
987
1016
|
|
|
988
1017
|
remove_tiles = (not args.no_remove_tiles)
|
|
989
1018
|
|
|
990
|
-
|
|
991
|
-
|
|
1019
|
+
use_image_queue = (args.loader_workers > 0)
|
|
1020
|
+
|
|
1021
|
+
run_tiled_inference(model_file,
|
|
1022
|
+
args.image_folder,
|
|
1023
|
+
args.tiling_folder,
|
|
1024
|
+
args.output_file,
|
|
1025
|
+
tile_size_x=args.tile_size_x,
|
|
1026
|
+
tile_size_y=args.tile_size_y,
|
|
992
1027
|
tile_overlap=args.tile_overlap,
|
|
993
1028
|
remove_tiles=remove_tiles,
|
|
994
|
-
image_list=args.image_list
|
|
1029
|
+
image_list=args.image_list,
|
|
1030
|
+
augment=args.augment,
|
|
1031
|
+
inference_size=args.inference_size,
|
|
1032
|
+
verbose=args.verbose,
|
|
1033
|
+
n_patch_extraction_workers=args.n_patch_extraction_workers,
|
|
1034
|
+
loader_workers=args.loader_workers,
|
|
1035
|
+
use_image_queue=use_image_queue)
|
|
995
1036
|
|
|
996
1037
|
if __name__ == '__main__':
|
|
997
1038
|
main()
|
|
@@ -138,8 +138,8 @@ class TFDetector:
|
|
|
138
138
|
image_id,
|
|
139
139
|
detection_threshold,
|
|
140
140
|
image_size=None,
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
augment=False,
|
|
142
|
+
verbose=False):
|
|
143
143
|
"""
|
|
144
144
|
Runs the detector on an image.
|
|
145
145
|
|
|
@@ -152,10 +152,9 @@ class TFDetector:
|
|
|
152
152
|
image_size (tuple, optional): image size to use for inference, only mess with this
|
|
153
153
|
if (a) you're using a model other than MegaDetector or (b) you know what you're
|
|
154
154
|
doing
|
|
155
|
-
skip_image_resizing (bool, optional): whether to skip internal image resizing (and rely on external
|
|
156
|
-
resizing). Not currently supported, but included here for compatibility with PTDetector.
|
|
157
155
|
augment (bool, optional): enable image augmentation. Not currently supported, but included
|
|
158
156
|
here for compatibility with PTDetector.
|
|
157
|
+
verbose (bool, optional): enable additional debug output
|
|
159
158
|
|
|
160
159
|
Returns:
|
|
161
160
|
dict: a dictionary with the following fields:
|
|
@@ -166,7 +165,6 @@ class TFDetector:
|
|
|
166
165
|
"""
|
|
167
166
|
|
|
168
167
|
assert image_size is None, 'Image sizing not supported for TF detectors'
|
|
169
|
-
assert not skip_image_resizing, 'Image sizing not supported for TF detectors'
|
|
170
168
|
assert not augment, 'Image augmentation is not supported for TF detectors'
|
|
171
169
|
|
|
172
170
|
if detection_threshold is None:
|
|
@@ -1168,7 +1168,7 @@ def restrict_to_taxa_list(taxa_list,
|
|
|
1168
1168
|
# Convert all NaN values in the "common" column to empty strings
|
|
1169
1169
|
taxa_list_df['common'] = taxa_list_df['common'].fillna('')
|
|
1170
1170
|
|
|
1171
|
-
# Create a dictionary mapping
|
|
1171
|
+
# Create a dictionary mapping source Latin names to target common names
|
|
1172
1172
|
target_latin_to_common = {}
|
|
1173
1173
|
|
|
1174
1174
|
for i_row,row in taxa_list_df.iterrows():
|
|
@@ -1332,7 +1332,7 @@ def restrict_to_taxa_list(taxa_list,
|
|
|
1332
1332
|
_insert_taxonomy_string(new_taxon_string)
|
|
1333
1333
|
|
|
1334
1334
|
|
|
1335
|
-
##%% Make sure all
|
|
1335
|
+
##%% Make sure all taxa on the allow-list are in the taxonomy
|
|
1336
1336
|
|
|
1337
1337
|
n_failed_mappings = 0
|
|
1338
1338
|
|
|
@@ -1498,7 +1498,8 @@ def restrict_to_taxa_list(taxa_list,
|
|
|
1498
1498
|
if (protected_common_names is not None) and \
|
|
1499
1499
|
(common_name in protected_common_names):
|
|
1500
1500
|
if verbose:
|
|
1501
|
-
print('Not messing with protected category {}'.format(
|
|
1501
|
+
print('Not messing with protected category {}:\n{}'.format(
|
|
1502
|
+
common_name,input_taxon_string))
|
|
1502
1503
|
input_category_id_to_output_taxon_string[input_category_id] = \
|
|
1503
1504
|
input_taxon_string
|
|
1504
1505
|
continue
|
|
@@ -1578,12 +1579,13 @@ def restrict_to_taxa_list(taxa_list,
|
|
|
1578
1579
|
output_taxon_string = speciesnet_latin_name_to_taxon_string[target_taxon]
|
|
1579
1580
|
input_category_id_to_output_taxon_string[input_category_id] = output_taxon_string
|
|
1580
1581
|
|
|
1581
|
-
# ...for each category
|
|
1582
|
+
# ...for each category (mapping input category IDs to output taxon strings)
|
|
1582
1583
|
|
|
1583
1584
|
|
|
1584
|
-
##%%
|
|
1585
|
+
##%% Map input category IDs to output category IDs
|
|
1585
1586
|
|
|
1586
|
-
speciesnet_taxon_string_to_latin_name =
|
|
1587
|
+
speciesnet_taxon_string_to_latin_name = \
|
|
1588
|
+
invert_dictionary(speciesnet_latin_name_to_taxon_string)
|
|
1587
1589
|
|
|
1588
1590
|
input_category_id_to_output_category_id = {}
|
|
1589
1591
|
output_taxon_string_to_category_id = {}
|
|
@@ -1604,7 +1606,8 @@ def restrict_to_taxa_list(taxa_list,
|
|
|
1604
1606
|
if speciesnet_latin_name in speciesnet_latin_name_to_output_common_name:
|
|
1605
1607
|
custom_common_name = speciesnet_latin_name_to_output_common_name[speciesnet_latin_name]
|
|
1606
1608
|
if custom_common_name != output_common_name:
|
|
1607
|
-
|
|
1609
|
+
if verbose:
|
|
1610
|
+
print('Substituting common name {} for {}'.format(custom_common_name,output_common_name))
|
|
1608
1611
|
output_common_name = custom_common_name
|
|
1609
1612
|
|
|
1610
1613
|
# Do we need to create a new output category?
|
|
@@ -1625,20 +1628,16 @@ def restrict_to_taxa_list(taxa_list,
|
|
|
1625
1628
|
if False:
|
|
1626
1629
|
original_common_name = \
|
|
1627
1630
|
input_category_id_to_common_name[input_category_id]
|
|
1628
|
-
|
|
1629
1631
|
original_taxon_string = \
|
|
1630
1632
|
input_category_id_to_taxonomy_string[input_category_id]
|
|
1631
|
-
|
|
1632
1633
|
print('Mapping {} ({}) to:\n{} ({})\n'.format(
|
|
1633
1634
|
original_common_name,original_taxon_string,
|
|
1634
1635
|
output_common_name,output_taxon_string))
|
|
1635
|
-
print('Mapping {} to {}'.format(
|
|
1636
|
-
original_common_name,output_common_name,))
|
|
1637
1636
|
|
|
1638
|
-
# ...for each category
|
|
1637
|
+
# ...for each category (mapping input category IDs to output category IDs)
|
|
1639
1638
|
|
|
1640
1639
|
|
|
1641
|
-
|
|
1640
|
+
##%% Remap all category labels
|
|
1642
1641
|
|
|
1643
1642
|
assert len(set(output_taxon_string_to_category_id.keys())) == \
|
|
1644
1643
|
len(set(output_taxon_string_to_category_id.values())), \
|