megadetector 5.0.20__py3-none-any.whl → 5.0.21__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/importers/osu-small-animals-to-json.py +4 -4
- megadetector/data_management/yolo_output_to_md_output.py +18 -5
- megadetector/detection/video_utils.py +19 -7
- megadetector/postprocessing/combine_api_outputs.py +1 -1
- megadetector/postprocessing/detector_calibration.py +367 -0
- megadetector/postprocessing/md_to_coco.py +2 -1
- megadetector/postprocessing/postprocess_batch_results.py +32 -20
- megadetector/postprocessing/validate_batch_results.py +118 -58
- megadetector/utils/md_tests.py +14 -12
- megadetector/utils/path_utils.py +139 -30
- megadetector/utils/write_html_image_list.py +16 -5
- megadetector/visualization/visualization_utils.py +126 -23
- megadetector/visualization/visualize_db.py +104 -63
- {megadetector-5.0.20.dist-info → megadetector-5.0.21.dist-info}/METADATA +1 -1
- {megadetector-5.0.20.dist-info → megadetector-5.0.21.dist-info}/RECORD +18 -18
- {megadetector-5.0.20.dist-info → megadetector-5.0.21.dist-info}/WHEEL +1 -1
- megadetector/data_management/importers/prepare-noaa-fish-data-for-lila.py +0 -359
- {megadetector-5.0.20.dist-info → megadetector-5.0.21.dist-info}/LICENSE +0 -0
- {megadetector-5.0.20.dist-info → megadetector-5.0.21.dist-info}/top_level.txt +0 -0
|
@@ -672,6 +672,36 @@ def draw_bounding_boxes_on_image(image,
|
|
|
672
672
|
# ...draw_bounding_boxes_on_image(...)
|
|
673
673
|
|
|
674
674
|
|
|
675
|
+
def get_text_size(font,s):
|
|
676
|
+
"""
|
|
677
|
+
Get the expected width and height when rendering the string [s] in the font
|
|
678
|
+
[font].
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
font (PIL.ImageFont): the font whose size we should query
|
|
682
|
+
s (str): the string whose size we should query
|
|
683
|
+
|
|
684
|
+
Returns:
|
|
685
|
+
tuple: (w,h), both floats in pixel coordinatess
|
|
686
|
+
"""
|
|
687
|
+
|
|
688
|
+
# This is what we did w/Pillow 9
|
|
689
|
+
# w,h = font.getsize(s)
|
|
690
|
+
|
|
691
|
+
# I would *think* this would be the equivalent for Pillow 10
|
|
692
|
+
# l,t,r,b = font.getbbox(s); w = r-l; h=b-t
|
|
693
|
+
|
|
694
|
+
# ...but this actually produces the most similar results to Pillow 9
|
|
695
|
+
# l,t,r,b = font.getbbox(s); w = r; h=b
|
|
696
|
+
|
|
697
|
+
try:
|
|
698
|
+
l,t,r,b = font.getbbox(s); w = r; h=b
|
|
699
|
+
except Exception:
|
|
700
|
+
w,h = font.getsize(s)
|
|
701
|
+
|
|
702
|
+
return w,h
|
|
703
|
+
|
|
704
|
+
|
|
675
705
|
def draw_bounding_box_on_image(image,
|
|
676
706
|
ymin,
|
|
677
707
|
xmin,
|
|
@@ -773,24 +803,6 @@ def draw_bounding_box_on_image(image,
|
|
|
773
803
|
except IOError:
|
|
774
804
|
font = ImageFont.load_default()
|
|
775
805
|
|
|
776
|
-
def get_text_size(font,s):
|
|
777
|
-
|
|
778
|
-
# This is what we did w/Pillow 9
|
|
779
|
-
# w,h = font.getsize(s)
|
|
780
|
-
|
|
781
|
-
# I would *think* this would be the equivalent for Pillow 10
|
|
782
|
-
# l,t,r,b = font.getbbox(s); w = r-l; h=b-t
|
|
783
|
-
|
|
784
|
-
# ...but this actually produces the most similar results to Pillow 9
|
|
785
|
-
# l,t,r,b = font.getbbox(s); w = r; h=b
|
|
786
|
-
|
|
787
|
-
try:
|
|
788
|
-
l,t,r,b = font.getbbox(s); w = r; h=b
|
|
789
|
-
except Exception:
|
|
790
|
-
w,h = font.getsize(s)
|
|
791
|
-
|
|
792
|
-
return w,h
|
|
793
|
-
|
|
794
806
|
# If the total height of the display strings added to the top of the bounding
|
|
795
807
|
# box exceeds the top of the image, stack the strings below the bounding box
|
|
796
808
|
# instead of above.
|
|
@@ -972,7 +984,7 @@ def draw_bounding_boxes_on_file(input_file,
|
|
|
972
984
|
boxes are length-four arrays formatted as [x,y,w,h], normalized,
|
|
973
985
|
upper-left origin (this is the standard MD detection format)
|
|
974
986
|
detector_label_map (dict, optional): a dict mapping category IDs to strings. If this
|
|
975
|
-
is None, no confidence values or identifiers are shown If this is {}, just category
|
|
987
|
+
is None, no confidence values or identifiers are shown. If this is {}, just category
|
|
976
988
|
indices and confidence values are shown.
|
|
977
989
|
thickness (int, optional): line width in pixels for box rendering
|
|
978
990
|
expansion (int, optional): box expansion in pixels
|
|
@@ -1043,7 +1055,7 @@ def draw_db_boxes_on_file(input_file,
|
|
|
1043
1055
|
classes = [0] * len(boxes)
|
|
1044
1056
|
|
|
1045
1057
|
render_db_bounding_boxes(boxes, classes, image, original_size=None,
|
|
1046
|
-
|
|
1058
|
+
label_map=label_map, thickness=thickness, expansion=expansion)
|
|
1047
1059
|
|
|
1048
1060
|
image.save(output_file)
|
|
1049
1061
|
|
|
@@ -1125,7 +1137,6 @@ def gray_scale_fraction(image,crop_size=(0.1,0.1)):
|
|
|
1125
1137
|
if r == g and r == b and g == b:
|
|
1126
1138
|
n_gray_pixels += 1
|
|
1127
1139
|
|
|
1128
|
-
|
|
1129
1140
|
# ...def gray_scale_fraction(...)
|
|
1130
1141
|
|
|
1131
1142
|
|
|
@@ -1376,6 +1387,98 @@ def resize_image_folder(input_folder,
|
|
|
1376
1387
|
# ...def resize_image_folder(...)
|
|
1377
1388
|
|
|
1378
1389
|
|
|
1390
|
+
def get_image_size(im,verbose=False):
|
|
1391
|
+
"""
|
|
1392
|
+
Retrieve the size of an image. Returns None if the image fails to load.
|
|
1393
|
+
|
|
1394
|
+
Args:
|
|
1395
|
+
im (str or PIL.Image): filename or PIL image
|
|
1396
|
+
|
|
1397
|
+
Returns:
|
|
1398
|
+
tuple (w,h), or None if the image fails to load.
|
|
1399
|
+
"""
|
|
1400
|
+
|
|
1401
|
+
image_name = '[in memory]'
|
|
1402
|
+
|
|
1403
|
+
try:
|
|
1404
|
+
if isinstance(im,str):
|
|
1405
|
+
image_name = im
|
|
1406
|
+
im = load_image(im)
|
|
1407
|
+
w = im.width
|
|
1408
|
+
h = im.height
|
|
1409
|
+
if w <= 0 or h <= 0:
|
|
1410
|
+
if verbose:
|
|
1411
|
+
print('Error reading width from image {}: {},{}'.format(
|
|
1412
|
+
image_name,w,h))
|
|
1413
|
+
return None
|
|
1414
|
+
return (w,h)
|
|
1415
|
+
except Exception as e:
|
|
1416
|
+
if verbose:
|
|
1417
|
+
print('Error reading width from image {}: {}'.format(
|
|
1418
|
+
image_name,str(e)))
|
|
1419
|
+
return None
|
|
1420
|
+
|
|
1421
|
+
# ...def get_image_size(...)
|
|
1422
|
+
|
|
1423
|
+
|
|
1424
|
+
def parallel_get_image_sizes(filenames,
|
|
1425
|
+
max_workers=16,
|
|
1426
|
+
use_threads=True,
|
|
1427
|
+
recursive=True,
|
|
1428
|
+
verbose=False):
|
|
1429
|
+
"""
|
|
1430
|
+
Retrieve image sizes for a list or folder of images
|
|
1431
|
+
|
|
1432
|
+
Args:
|
|
1433
|
+
filenames (list or str): a list of image filenames or a folder
|
|
1434
|
+
max_workers (int, optional): the number of parallel workers to use; set to <=1 to disable
|
|
1435
|
+
parallelization
|
|
1436
|
+
use_threads (bool, optional): whether to use threads (True) or processes (False) for
|
|
1437
|
+
parallelization
|
|
1438
|
+
recursive (bool, optional): if [filenames] is a folder, whether to search recursively for images.
|
|
1439
|
+
Ignored if [filenames] is a list.
|
|
1440
|
+
verbose (bool, optional): enable additional debug output
|
|
1441
|
+
|
|
1442
|
+
Returns:
|
|
1443
|
+
dict: a dict mapping filenames to (w,h) tuples; values will be None for images that fail
|
|
1444
|
+
to load.
|
|
1445
|
+
"""
|
|
1446
|
+
|
|
1447
|
+
if isinstance(filenames,str) and os.path.isdir(filenames):
|
|
1448
|
+
if verbose:
|
|
1449
|
+
print('Enumerating images in {}'.format(filenames))
|
|
1450
|
+
filenames = find_images(filenames,recursive=recursive,return_relative_paths=False)
|
|
1451
|
+
|
|
1452
|
+
n_workers = min(max_workers,len(filenames))
|
|
1453
|
+
|
|
1454
|
+
if verbose:
|
|
1455
|
+
print('Getting image sizes for {} images'.format(len(filenames)))
|
|
1456
|
+
|
|
1457
|
+
if n_workers <= 1:
|
|
1458
|
+
|
|
1459
|
+
results = []
|
|
1460
|
+
for filename in filenames:
|
|
1461
|
+
results.append(get_image_size(filename,verbose=verbose))
|
|
1462
|
+
|
|
1463
|
+
else:
|
|
1464
|
+
|
|
1465
|
+
if use_threads:
|
|
1466
|
+
pool = ThreadPool(n_workers)
|
|
1467
|
+
else:
|
|
1468
|
+
pool = Pool(n_workers)
|
|
1469
|
+
|
|
1470
|
+
results = list(tqdm(pool.imap(
|
|
1471
|
+
partial(get_image_size,verbose=verbose),filenames), total=len(filenames)))
|
|
1472
|
+
|
|
1473
|
+
assert len(filenames) == len(results), 'Internal error in parallel_get_image_sizes'
|
|
1474
|
+
|
|
1475
|
+
to_return = {}
|
|
1476
|
+
for i_file,filename in enumerate(filenames):
|
|
1477
|
+
to_return[filename] = results[i_file]
|
|
1478
|
+
|
|
1479
|
+
return to_return
|
|
1480
|
+
|
|
1481
|
+
|
|
1379
1482
|
#%% Image integrity checking functions
|
|
1380
1483
|
|
|
1381
1484
|
def check_image_integrity(filename,modes=None):
|
|
@@ -1494,13 +1597,13 @@ def parallel_check_image_integrity(filenames,
|
|
|
1494
1597
|
with either 'success' or 'error').
|
|
1495
1598
|
"""
|
|
1496
1599
|
|
|
1497
|
-
n_workers = min(max_workers,len(filenames))
|
|
1498
|
-
|
|
1499
1600
|
if isinstance(filenames,str) and os.path.isdir(filenames):
|
|
1500
1601
|
if verbose:
|
|
1501
1602
|
print('Enumerating images in {}'.format(filenames))
|
|
1502
1603
|
filenames = find_images(filenames,recursive=recursive,return_relative_paths=False)
|
|
1503
1604
|
|
|
1605
|
+
n_workers = min(max_workers,len(filenames))
|
|
1606
|
+
|
|
1504
1607
|
if verbose:
|
|
1505
1608
|
print('Checking image integrity for {} filenames'.format(len(filenames)))
|
|
1506
1609
|
|
|
@@ -32,6 +32,9 @@ from megadetector.utils.write_html_image_list import write_html_image_list
|
|
|
32
32
|
from megadetector.data_management.cct_json_utils import IndexedJsonDb
|
|
33
33
|
from megadetector.visualization import visualization_utils as vis_utils
|
|
34
34
|
|
|
35
|
+
def isnan(x):
|
|
36
|
+
return (isinstance(x,float) and np.isnan(x))
|
|
37
|
+
|
|
35
38
|
|
|
36
39
|
#%% Settings
|
|
37
40
|
|
|
@@ -84,12 +87,12 @@ class DbVizOptions:
|
|
|
84
87
|
#: Number of pixels to expand each bounding box
|
|
85
88
|
self.box_expansion = 0
|
|
86
89
|
|
|
87
|
-
#: Only include images that contain annotations with these class names (not IDs)
|
|
90
|
+
#: Only include images that contain annotations with these class names (not IDs) (list)
|
|
88
91
|
#:
|
|
89
92
|
#: Mutually exclusive with classes_to_exclude
|
|
90
93
|
self.classes_to_include = None
|
|
91
94
|
|
|
92
|
-
#: Exclude images that contain annotations with these class names (not IDs)
|
|
95
|
+
#: Exclude images that contain annotations with these class names (not IDs) (list)
|
|
93
96
|
#:
|
|
94
97
|
#: Mutually exclusive with classes_to_include
|
|
95
98
|
self.classes_to_exclude = None
|
|
@@ -117,6 +120,12 @@ class DbVizOptions:
|
|
|
117
120
|
#: Should we show absolute (True) or relative (False) paths for each image?
|
|
118
121
|
self.show_full_paths = False
|
|
119
122
|
|
|
123
|
+
#: List of additional fields in the image struct that we should print in image headers
|
|
124
|
+
self.extra_image_fields_to_print = None
|
|
125
|
+
|
|
126
|
+
#: List of additional fields in the annotation struct that we should print in image headers
|
|
127
|
+
self.extra_annotation_fields_to_print = None
|
|
128
|
+
|
|
120
129
|
#: Set to False to skip existing images
|
|
121
130
|
self.force_rendering = True
|
|
122
131
|
|
|
@@ -164,6 +173,12 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
164
173
|
if options is None:
|
|
165
174
|
options = DbVizOptions()
|
|
166
175
|
|
|
176
|
+
# Consistency checking for fields with specific format requirements
|
|
177
|
+
|
|
178
|
+
# This should be a list, but if someone specifies a string, do a reasonable thing
|
|
179
|
+
if isinstance(options.extra_image_fields_to_print,str):
|
|
180
|
+
options.extra_image_fields_to_print = [options.extra_image_fields_to_print]
|
|
181
|
+
|
|
167
182
|
if not options.parallelize_rendering_with_threads:
|
|
168
183
|
print('Warning: process-based parallelization is not yet supported by visualize_db')
|
|
169
184
|
options.parallelize_rendering_with_threads = True
|
|
@@ -190,61 +205,69 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
190
205
|
annotations = image_db['annotations']
|
|
191
206
|
images = image_db['images']
|
|
192
207
|
categories = image_db['categories']
|
|
193
|
-
|
|
208
|
+
|
|
194
209
|
# Optionally remove all images without bounding boxes, *before* sampling
|
|
195
210
|
if options.trim_to_images_with_bboxes:
|
|
196
211
|
|
|
197
|
-
|
|
198
|
-
for
|
|
212
|
+
b_has_bbox = [False] * len(annotations)
|
|
213
|
+
for i_ann,ann in enumerate(annotations):
|
|
199
214
|
if 'bbox' in ann:
|
|
200
215
|
assert isinstance(ann['bbox'],list)
|
|
201
|
-
|
|
202
|
-
|
|
216
|
+
b_has_bbox[i_ann] = True
|
|
217
|
+
annotations_with_boxes = list(compress(annotations, b_has_bbox))
|
|
203
218
|
|
|
204
|
-
|
|
205
|
-
|
|
219
|
+
image_ids_with_boxes = [x['image_id'] for x in annotations_with_boxes]
|
|
220
|
+
image_ids_with_boxes = set(image_ids_with_boxes)
|
|
206
221
|
|
|
207
|
-
|
|
208
|
-
for
|
|
222
|
+
image_has_box = [False] * len(images)
|
|
223
|
+
for i_image,image in enumerate(images):
|
|
209
224
|
imageID = image['id']
|
|
210
|
-
if imageID in
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
images =
|
|
225
|
+
if imageID in image_ids_with_boxes:
|
|
226
|
+
image_has_box[i_image] = True
|
|
227
|
+
images_with_bboxes = list(compress(images, image_has_box))
|
|
228
|
+
images = images_with_bboxes
|
|
214
229
|
|
|
215
230
|
# Optionally include/remove images with specific labels, *before* sampling
|
|
216
231
|
|
|
217
232
|
assert (not ((options.classes_to_exclude is not None) and \
|
|
218
233
|
(options.classes_to_include is not None))), \
|
|
219
234
|
'Cannot specify an inclusion and exclusion list'
|
|
220
|
-
|
|
235
|
+
|
|
236
|
+
if options.classes_to_exclude is not None:
|
|
237
|
+
assert isinstance(options.classes_to_exclude,list), \
|
|
238
|
+
'If supplied, classes_to_exclude should be a list'
|
|
239
|
+
|
|
240
|
+
if options.classes_to_include is not None:
|
|
241
|
+
assert isinstance(options.classes_to_include,list), \
|
|
242
|
+
'If supplied, classes_to_include should be a list'
|
|
243
|
+
|
|
221
244
|
if (options.classes_to_exclude is not None) or (options.classes_to_include is not None):
|
|
222
245
|
|
|
223
246
|
print('Indexing database')
|
|
224
247
|
indexed_db = IndexedJsonDb(image_db)
|
|
225
|
-
|
|
226
|
-
for
|
|
248
|
+
b_valid_class = [True] * len(images)
|
|
249
|
+
for i_image,image in enumerate(images):
|
|
227
250
|
classes = indexed_db.get_classes_for_image(image)
|
|
228
251
|
if options.classes_to_exclude is not None:
|
|
229
|
-
for
|
|
230
|
-
if
|
|
231
|
-
|
|
252
|
+
for excluded_class in options.classes_to_exclude:
|
|
253
|
+
if excluded_class in classes:
|
|
254
|
+
b_valid_class[i_image] = False
|
|
232
255
|
break
|
|
233
256
|
elif options.classes_to_include is not None:
|
|
234
|
-
|
|
257
|
+
b_valid_class[i_image] = False
|
|
235
258
|
if options.multiple_categories_tag in options.classes_to_include:
|
|
236
259
|
if len(classes) > 1:
|
|
237
|
-
|
|
238
|
-
if not
|
|
260
|
+
b_valid_class[i_image] = True
|
|
261
|
+
if not b_valid_class[i_image]:
|
|
239
262
|
for c in classes:
|
|
240
263
|
if c in options.classes_to_include:
|
|
241
|
-
|
|
264
|
+
b_valid_class[i_image] = True
|
|
242
265
|
break
|
|
243
266
|
else:
|
|
244
267
|
raise ValueError('Illegal include/exclude combination')
|
|
245
268
|
|
|
246
|
-
|
|
247
|
-
images =
|
|
269
|
+
images_with_valid_classes = list(compress(images, b_valid_class))
|
|
270
|
+
images = images_with_valid_classes
|
|
248
271
|
|
|
249
272
|
# ...if we need to include/exclude categories
|
|
250
273
|
|
|
@@ -270,12 +293,12 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
270
293
|
|
|
271
294
|
# Set of dicts representing inputs to render_db_bounding_boxes:
|
|
272
295
|
#
|
|
273
|
-
# bboxes,
|
|
296
|
+
# bboxes, box_classes, image_path
|
|
274
297
|
rendering_info = []
|
|
275
298
|
|
|
276
299
|
print('Preparing rendering list')
|
|
277
300
|
|
|
278
|
-
for
|
|
301
|
+
for i_image,img in tqdm(df_img.iterrows(),total=len(df_img)):
|
|
279
302
|
|
|
280
303
|
img_id = img['id']
|
|
281
304
|
assert img_id is not None
|
|
@@ -291,17 +314,27 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
291
314
|
annos_i = df_anno.loc[df_anno['image_id'] == img_id, :] # all annotations on this image
|
|
292
315
|
|
|
293
316
|
bboxes = []
|
|
294
|
-
|
|
317
|
+
box_classes = []
|
|
295
318
|
|
|
296
319
|
# All the class labels we've seen for this image (with out without bboxes)
|
|
297
|
-
|
|
320
|
+
image_categories = set()
|
|
298
321
|
|
|
299
|
-
|
|
322
|
+
extra_annotation_field_string = ''
|
|
323
|
+
annotation_level_for_image = ''
|
|
300
324
|
|
|
301
325
|
# Iterate over annotations for this image
|
|
302
|
-
#
|
|
303
|
-
for
|
|
304
|
-
|
|
326
|
+
# i_ann = 0; anno = annos_i.iloc[i_ann]
|
|
327
|
+
for i_ann,anno in annos_i.iterrows():
|
|
328
|
+
|
|
329
|
+
if options.extra_annotation_fields_to_print is not None:
|
|
330
|
+
field_names = list(anno.index)
|
|
331
|
+
for field_name in field_names:
|
|
332
|
+
if field_name in options.extra_annotation_fields_to_print:
|
|
333
|
+
field_value = anno[field_name]
|
|
334
|
+
if (field_value is not None) and (not isnan(field_value)):
|
|
335
|
+
extra_annotation_field_string += ' ({}:{})'.format(
|
|
336
|
+
field_name,field_value)
|
|
337
|
+
|
|
305
338
|
if options.confidence_threshold is not None:
|
|
306
339
|
assert options.confidence_field_name in anno, \
|
|
307
340
|
'Error: confidence thresholding requested, ' + \
|
|
@@ -316,18 +349,18 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
316
349
|
annLevel = 'sequence'
|
|
317
350
|
else:
|
|
318
351
|
annLevel = 'image'
|
|
319
|
-
if
|
|
320
|
-
|
|
321
|
-
elif
|
|
322
|
-
|
|
352
|
+
if annotation_level_for_image == '':
|
|
353
|
+
annotation_level_for_image = annLevel
|
|
354
|
+
elif annotation_level_for_image != annLevel:
|
|
355
|
+
annotation_level_for_image = 'mixed'
|
|
323
356
|
|
|
324
|
-
|
|
325
|
-
|
|
357
|
+
category_id = anno['category_id']
|
|
358
|
+
category_name = label_map[category_id]
|
|
326
359
|
if options.add_search_links:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
360
|
+
category_name = category_name.replace('"','')
|
|
361
|
+
category_name = '<a href="https://www.google.com/search?tbm=isch&q={}">{}</a>'.format(
|
|
362
|
+
category_name,category_name)
|
|
363
|
+
image_categories.add(category_name)
|
|
331
364
|
|
|
332
365
|
if 'bbox' in anno:
|
|
333
366
|
bbox = anno['bbox']
|
|
@@ -335,11 +368,11 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
335
368
|
assert math.isnan(bbox), "I shouldn't see a bbox that's neither a box nor NaN"
|
|
336
369
|
continue
|
|
337
370
|
bboxes.append(bbox)
|
|
338
|
-
|
|
371
|
+
box_classes.append(anno['category_id'])
|
|
339
372
|
|
|
340
373
|
# ...for each of this image's annotations
|
|
341
374
|
|
|
342
|
-
|
|
375
|
+
image_classes = ', '.join(image_categories)
|
|
343
376
|
|
|
344
377
|
img_id_string = str(img_id).lower()
|
|
345
378
|
file_name = '{}_gt.jpg'.format(os.path.splitext(img_id_string)[0])
|
|
@@ -349,19 +382,19 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
349
382
|
for c in illegal_characters:
|
|
350
383
|
file_name = file_name.replace(c,'~')
|
|
351
384
|
|
|
352
|
-
rendering_info.append({'bboxes':bboxes, '
|
|
385
|
+
rendering_info.append({'bboxes':bboxes, 'box_classes':box_classes, 'img_path':img_path,
|
|
353
386
|
'output_file_name':file_name})
|
|
354
387
|
|
|
355
|
-
|
|
356
|
-
if len(
|
|
357
|
-
|
|
388
|
+
label_level_string = ''
|
|
389
|
+
if len(annotation_level_for_image) > 0:
|
|
390
|
+
label_level_string = ' (annotation level: {})'.format(annotation_level_for_image)
|
|
358
391
|
|
|
359
392
|
if 'frame_num' in img and 'seq_num_frames' in img:
|
|
360
|
-
|
|
393
|
+
frame_string = ' frame: {} of {},'.format(img['frame_num'],img['seq_num_frames'])
|
|
361
394
|
elif 'frame_num' in img:
|
|
362
|
-
|
|
395
|
+
frame_string = ' frame: {},'.format(img['frame_num'])
|
|
363
396
|
else:
|
|
364
|
-
|
|
397
|
+
frame_string = ''
|
|
365
398
|
|
|
366
399
|
if options.show_full_paths:
|
|
367
400
|
filename_text = img_path
|
|
@@ -370,22 +403,30 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
370
403
|
if options.include_filename_links:
|
|
371
404
|
filename_text = '<a href="{}">{}</a>'.format(img_path,filename_text)
|
|
372
405
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
def isnan(x):
|
|
376
|
-
return (isinstance(x,float) and np.isnan(x))
|
|
406
|
+
flag_string = ''
|
|
377
407
|
|
|
378
408
|
if ('flags' in img) and (not isnan(img['flags'])):
|
|
379
|
-
|
|
409
|
+
flag_string = ', flags: {}'.format(str(img['flags']))
|
|
380
410
|
|
|
411
|
+
extra_field_string = ''
|
|
412
|
+
|
|
413
|
+
if options.extra_image_fields_to_print is not None:
|
|
414
|
+
for field_name in options.extra_image_fields_to_print:
|
|
415
|
+
if field_name in img:
|
|
416
|
+
# Always include a leading comma; this either separates us from the
|
|
417
|
+
# previous field in [extra_fields_to_print] or from the rest of the string
|
|
418
|
+
extra_field_string += ', {}: {}'.format(
|
|
419
|
+
field_name,str(img[field_name]))
|
|
420
|
+
|
|
381
421
|
# We're adding html for an image before we render it, so it's possible this image will
|
|
382
422
|
# fail to render. For applications where this script is being used to debua a database
|
|
383
423
|
# (the common case?), this is useful behavior, for other applications, this is annoying.
|
|
384
424
|
image_dict = \
|
|
385
425
|
{
|
|
386
426
|
'filename': '{}/{}'.format('rendered_images', file_name),
|
|
387
|
-
'title': '{}<br/>{}, num boxes: {},
|
|
388
|
-
filename_text, img_id, len(bboxes),
|
|
427
|
+
'title': '{}<br/>{}, num boxes: {},{} class labels: {}{}{}{}{}'.format(
|
|
428
|
+
filename_text, img_id, len(bboxes), frame_string, image_classes,
|
|
429
|
+
label_level_string, flag_string, extra_field_string, extra_annotation_field_string),
|
|
389
430
|
'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;' + \
|
|
390
431
|
'text-align:left;margin-top:20;margin-bottom:5'
|
|
391
432
|
}
|
|
@@ -400,7 +441,7 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
400
441
|
|
|
401
442
|
img_path = rendering_info['img_path']
|
|
402
443
|
bboxes = rendering_info['bboxes']
|
|
403
|
-
|
|
444
|
+
bbox_classes = rendering_info['box_classes']
|
|
404
445
|
output_file_name = rendering_info['output_file_name']
|
|
405
446
|
output_full_path = os.path.join(output_dir, 'rendered_images', output_file_name)
|
|
406
447
|
|
|
@@ -426,7 +467,7 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
426
467
|
print('Image {} failed to open, error: {}'.format(img_path, e))
|
|
427
468
|
return False
|
|
428
469
|
|
|
429
|
-
vis_utils.render_db_bounding_boxes(boxes=bboxes, classes=
|
|
470
|
+
vis_utils.render_db_bounding_boxes(boxes=bboxes, classes=bbox_classes,
|
|
430
471
|
image=image, original_size=original_size,
|
|
431
472
|
label_map=label_map,
|
|
432
473
|
thickness=options.box_thickness,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: megadetector
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.21
|
|
4
4
|
Summary: MegaDetector is an AI model that helps conservation folks spend less time doing boring things with camera trap images.
|
|
5
5
|
Author-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
|
|
6
6
|
Maintainer-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
|
|
@@ -69,7 +69,7 @@ megadetector/data_management/remove_exif.py,sha256=vIWnJfw1i9JgyQKUDGEzzqkHro4nd
|
|
|
69
69
|
megadetector/data_management/rename_images.py,sha256=AG3YIxXEYdGmK4G-rv0_XZIylPqOZpS6gfEkydF6oDg,6918
|
|
70
70
|
megadetector/data_management/resize_coco_dataset.py,sha256=AaiV7efIcNnqsXsnQckmHq2G__7ZQHBV_jN6rhZfMjo,6810
|
|
71
71
|
megadetector/data_management/wi_download_csv_to_coco.py,sha256=ilnJZhNZK-FGUR-AfUSWjIDUk9Gytgxw7IOK_N8WKLE,8350
|
|
72
|
-
megadetector/data_management/yolo_output_to_md_output.py,sha256=
|
|
72
|
+
megadetector/data_management/yolo_output_to_md_output.py,sha256=VuU9G6QOeAXOa7JsuHjSYhE3Y7MjEd2bPtceugOOILY,17920
|
|
73
73
|
megadetector/data_management/yolo_to_coco.py,sha256=TzAagQ2ATbB_tn1oZxrHCWsrFGO_OhfZmi-3X45WdDU,26180
|
|
74
74
|
megadetector/data_management/annotations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
75
75
|
megadetector/data_management/annotations/annotation_constants.py,sha256=1597MpAr_HdidIHoDFj4RgUO3K5e2Xm2bGafGeonR2k,953
|
|
@@ -101,10 +101,9 @@ megadetector/data_management/importers/mcgill_to_json.py,sha256=dfSxU1hHimyGT6Zt
|
|
|
101
101
|
megadetector/data_management/importers/missouri_to_json.py,sha256=C0ia3eCEZujVUKE2gmQc6ScsK8kXWM7m0ibeKgHfXNo,14848
|
|
102
102
|
megadetector/data_management/importers/nacti_fieldname_adjustments.py,sha256=1oDCSuFXhc2b7JPIzkSb3DkusacdAjMM2GQZnhfFQCg,2027
|
|
103
103
|
megadetector/data_management/importers/noaa_seals_2019.py,sha256=oar378j46fm27ygcbjrgN1rbq6h1SC8utAdSPNqiQt4,5152
|
|
104
|
-
megadetector/data_management/importers/osu-small-animals-to-json.py,sha256=
|
|
104
|
+
megadetector/data_management/importers/osu-small-animals-to-json.py,sha256=wBbnY8kqZrzRiujrNK750DB3mq14EyIz4Zlx9JHRTkw,10096
|
|
105
105
|
megadetector/data_management/importers/pc_to_json.py,sha256=VmVvY5Fr8jMLmRkDZI9CuyLvrNuLrspJA9Q8Auxbw1A,10762
|
|
106
106
|
megadetector/data_management/importers/plot_wni_giraffes.py,sha256=KdEjbItDOXbXj0fr0celfMp7z31Rr3S29SLWBCMY-4M,3772
|
|
107
|
-
megadetector/data_management/importers/prepare-noaa-fish-data-for-lila.py,sha256=Pq5tSKWTIGEAGxBiGaO5Tz0QvKZ6QgJTIQ3raDAhjkk,12435
|
|
108
107
|
megadetector/data_management/importers/prepare_zsl_imerit.py,sha256=ohrUaTXIGg1M4_liptWaPa-4g3yNvc1E4o_knfHSE-8,3775
|
|
109
108
|
megadetector/data_management/importers/rspb_to_json.py,sha256=y03v1d1un9mI3HZRCZinMB1pEkNvTb70S7Qkr3F76qg,9841
|
|
110
109
|
megadetector/data_management/importers/save_the_elephants_survey_A.py,sha256=lugw8m5Nh2Fhs-FYo9L0mDL3_29nAweLxEul6GekdkI,10669
|
|
@@ -143,27 +142,28 @@ megadetector/detection/run_detector_batch.py,sha256=a98fzorcGtQaOYa5AGW2XPoJpbHe
|
|
|
143
142
|
megadetector/detection/run_inference_with_yolov5_val.py,sha256=2miU2QZG_zp3rEPyoKf2XozuMpW6zAW4bAoyg6hSe-k,48691
|
|
144
143
|
megadetector/detection/run_tiled_inference.py,sha256=vw0713eNuMiEOjHfweQl58zPHNxPOMdFWZ8bTDLhlMY,37883
|
|
145
144
|
megadetector/detection/tf_detector.py,sha256=5V94a0gR6WmGPacKm59hl1eYEZI8cG04frF4EvHrmzU,8285
|
|
146
|
-
megadetector/detection/video_utils.py,sha256=
|
|
145
|
+
megadetector/detection/video_utils.py,sha256=TmUIcnnqk3VEXtk9MXHKAvixqCCVMvj5HHuBOmPBNDk,43036
|
|
147
146
|
megadetector/detection/detector_training/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
148
147
|
megadetector/detection/detector_training/model_main_tf2.py,sha256=YwNsZ7hkIFaEuwKU0rHG_VyqiR_0E01BbdlD0Yx4Smo,4936
|
|
149
148
|
megadetector/postprocessing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
150
149
|
megadetector/postprocessing/add_max_conf.py,sha256=qTE1_0RwGAy6jLDkHrIo2pS84yNbUV11s4IZuAYGdIU,1514
|
|
151
150
|
megadetector/postprocessing/categorize_detections_by_size.py,sha256=YdapcvjA6Dz2dPa2AFf1Dwyl7C-OmmP4G4OjhTOuaF4,5797
|
|
152
151
|
megadetector/postprocessing/classification_postprocessing.py,sha256=8uvlA0Gc8nakM5IE5Pud7WZfmF5kEhcYvxgQXcI9kl0,30429
|
|
153
|
-
megadetector/postprocessing/combine_api_outputs.py,sha256=
|
|
152
|
+
megadetector/postprocessing/combine_api_outputs.py,sha256=zBGpSLbcQUiLYxgJrjZXjBwc2dOwAytV30UFnroP2Fg,8536
|
|
154
153
|
megadetector/postprocessing/compare_batch_results.py,sha256=7O5c6-JsIDpuIGobks_R9j8MPuiZQRnEtNnJQsJqICM,38918
|
|
155
154
|
megadetector/postprocessing/convert_output_format.py,sha256=HwThfK76UPEAGa3KQbJ_tMKIrUvJ3JhKoQVWJt9dPBk,15447
|
|
155
|
+
megadetector/postprocessing/detector_calibration.py,sha256=WHIj-i91geXZjNV2Am2783PL2iGAebkeVFJZhc1K6uY,12702
|
|
156
156
|
megadetector/postprocessing/load_api_results.py,sha256=FqcaiPMuqTojZOV3Jn14pJESpuwjWGbZtcvJuVXUaDM,6861
|
|
157
|
-
megadetector/postprocessing/md_to_coco.py,sha256=
|
|
157
|
+
megadetector/postprocessing/md_to_coco.py,sha256=AhlI2w2kOu1Y1b4yliyu81WsMBxYXcBJ0YAF5laX9v8,12406
|
|
158
158
|
megadetector/postprocessing/md_to_labelme.py,sha256=hejMKVxaz_xdtsGDPTQkeWuis7gzT-VOrL2Qf8ym1x0,11703
|
|
159
159
|
megadetector/postprocessing/merge_detections.py,sha256=AEMgMivhph1vph_t_Qv85d9iHynT2nvq7otN4KGrDLU,17776
|
|
160
|
-
megadetector/postprocessing/postprocess_batch_results.py,sha256=
|
|
160
|
+
megadetector/postprocessing/postprocess_batch_results.py,sha256=JnH3bezK9lm5Sljlsxgp9_JFUgUzcjRDjRRbLOYy7qk,79879
|
|
161
161
|
megadetector/postprocessing/remap_detection_categories.py,sha256=d9IYTa0i_KbbrarJc_mczABmdwypscl5-KpK8Hx_z8o,6640
|
|
162
162
|
megadetector/postprocessing/render_detection_confusion_matrix.py,sha256=_wsk4W0PbNiqmFuHy-EA0Z07B1tQLMsdCTPatnHAdZw,27382
|
|
163
163
|
megadetector/postprocessing/separate_detections_into_folders.py,sha256=k42gxnL8hbBiV0e2T-jmFrhxzIxnhi57Nx9cDSSL5s0,31218
|
|
164
164
|
megadetector/postprocessing/subset_json_detector_output.py,sha256=PDgb6cnsFm9d4E7_sMVIguLIU7s79uFQa2CRCxAO0F4,27064
|
|
165
165
|
megadetector/postprocessing/top_folders_to_bottom.py,sha256=Dqk-KZXiRlIYlmLZmk6aUapmaaLJUKOf8wK1kxt9W6A,6283
|
|
166
|
-
megadetector/postprocessing/validate_batch_results.py,sha256=
|
|
166
|
+
megadetector/postprocessing/validate_batch_results.py,sha256=bC2wSuR1ir3gW-VF6zFq6TqoMIxDXIK4eyTM8oBq6u8,8598
|
|
167
167
|
megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py,sha256=e4Y9CyMyd-bLN3il8tu76vI0nVYHZlhZr6vcL0J4zQ0,9832
|
|
168
168
|
megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py,sha256=tARPxuY0OyQgpKU2XqiQPko3f-hHnWuISB8ZlZgXwxI,2819
|
|
169
169
|
megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py,sha256=vEmWLSSv0_rxDwhjz_S9YaKZ_LM2tADTz2JYb_zUCnc,67923
|
|
@@ -182,23 +182,23 @@ megadetector/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
|
182
182
|
megadetector/utils/azure_utils.py,sha256=0BdnkG2hW-X0yFpsJqmBhOd2wysz_LvhuyImPJMVPJs,6271
|
|
183
183
|
megadetector/utils/ct_utils.py,sha256=Ecac5CLEIrEi89JFuoqdOMxiOdmbno106a1MT2SVdJY,19956
|
|
184
184
|
megadetector/utils/directory_listing.py,sha256=r4rg2xA4O9ZVxVtzPZzXIXa0DOEukAJMTTNcNSiQcuM,9668
|
|
185
|
-
megadetector/utils/md_tests.py,sha256=
|
|
186
|
-
megadetector/utils/path_utils.py,sha256=
|
|
185
|
+
megadetector/utils/md_tests.py,sha256=DvGfRZXpes4bg8S_-btA2NEW8X7k8vXfRaOZewauVxM,61189
|
|
186
|
+
megadetector/utils/path_utils.py,sha256=Kn7Ro37MapRW78_eraK_V_4_I-V8H9pgtvTDL4q7_a8,40571
|
|
187
187
|
megadetector/utils/process_utils.py,sha256=2SdFVxqob-YUW2BTjUEavNuRH3jA4V05fbKMtrVSd3c,5635
|
|
188
188
|
megadetector/utils/sas_blob_utils.py,sha256=k76EcMmJc_otrEHcfV2fxAC6fNhxU88FxM3ddSYrsKU,16917
|
|
189
189
|
megadetector/utils/split_locations_into_train_val.py,sha256=jvaDu1xKB51L3Xq2nXQo0XtXRjNRf8RglBApl1g6gHo,10101
|
|
190
190
|
megadetector/utils/string_utils.py,sha256=ZQapJodzvTDyQhjZgMoMl3-9bqnKAUlORpws8Db9AkA,2050
|
|
191
191
|
megadetector/utils/torch_test.py,sha256=aEYE-1vGt5PujD0bHAVRTJiLrKFlGWpS8zeYhqEYZLY,853
|
|
192
192
|
megadetector/utils/url_utils.py,sha256=yybWwJ-vl2A6Fci66i-xt_dl3Uqh72Ylnb8XOT2Grog,14835
|
|
193
|
-
megadetector/utils/write_html_image_list.py,sha256=
|
|
193
|
+
megadetector/utils/write_html_image_list.py,sha256=zz98QFQlUIb-kKC-I7llf4EXbNh3PULZBtCZpfMVMfM,9148
|
|
194
194
|
megadetector/visualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
195
195
|
megadetector/visualization/plot_utils.py,sha256=lOfU3uPrcuHZagV_1SN8erT8PujIepocgw6KZ17Ej6c,10671
|
|
196
196
|
megadetector/visualization/render_images_with_thumbnails.py,sha256=kgJYW8BsqRO4C7T3sqItdBuSkZ64I1vOtIWAsVG4XBI,10589
|
|
197
|
-
megadetector/visualization/visualization_utils.py,sha256=
|
|
198
|
-
megadetector/visualization/visualize_db.py,sha256=
|
|
197
|
+
megadetector/visualization/visualization_utils.py,sha256=1_xUzuiA-GTD7XNsRwZiemXjQZooOa4p4_nqMd9u6F4,66269
|
|
198
|
+
megadetector/visualization/visualize_db.py,sha256=tswoWqyAo_S5RW76yvPEEWkUVEzn2NJrX1lfDl2jqY4,24392
|
|
199
199
|
megadetector/visualization/visualize_detector_output.py,sha256=LY8QgDWpWlXVLZJUskvT29CdkNvIlEsFTk4DC_lS6pk,17052
|
|
200
|
-
megadetector-5.0.
|
|
201
|
-
megadetector-5.0.
|
|
202
|
-
megadetector-5.0.
|
|
203
|
-
megadetector-5.0.
|
|
204
|
-
megadetector-5.0.
|
|
200
|
+
megadetector-5.0.21.dist-info/LICENSE,sha256=RMa3qq-7Cyk7DdtqRj_bP1oInGFgjyHn9-PZ3PcrqIs,1100
|
|
201
|
+
megadetector-5.0.21.dist-info/METADATA,sha256=Spj46ZROGJbmTdn_D-pOTHFAODg1npXS8xyiErghlKo,7468
|
|
202
|
+
megadetector-5.0.21.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
203
|
+
megadetector-5.0.21.dist-info/top_level.txt,sha256=wf9DXa8EwiOSZ4G5IPjakSxBPxTDjhYYnqWRfR-zS4M,13
|
|
204
|
+
megadetector-5.0.21.dist-info/RECORD,,
|