megadetector 5.0.20__py3-none-any.whl → 5.0.22__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/cct_json_utils.py +143 -7
- megadetector/data_management/cct_to_md.py +12 -5
- megadetector/data_management/databases/integrity_check_json_db.py +83 -77
- megadetector/data_management/importers/osu-small-animals-to-json.py +4 -4
- megadetector/data_management/importers/raic_csv_to_md_results.py +416 -0
- megadetector/data_management/importers/zamba_results_to_md_results.py +1 -2
- megadetector/data_management/lila/create_lila_test_set.py +25 -11
- megadetector/data_management/lila/download_lila_subset.py +9 -2
- megadetector/data_management/lila/generate_lila_per_image_labels.py +3 -2
- megadetector/data_management/lila/test_lila_metadata_urls.py +5 -1
- megadetector/data_management/read_exif.py +10 -14
- megadetector/data_management/rename_images.py +1 -1
- megadetector/data_management/yolo_output_to_md_output.py +18 -5
- megadetector/detection/process_video.py +14 -3
- megadetector/detection/pytorch_detector.py +15 -3
- megadetector/detection/run_detector.py +4 -3
- megadetector/detection/run_inference_with_yolov5_val.py +121 -13
- megadetector/detection/video_utils.py +40 -17
- megadetector/postprocessing/classification_postprocessing.py +1 -1
- megadetector/postprocessing/combine_api_outputs.py +1 -1
- megadetector/postprocessing/compare_batch_results.py +931 -142
- megadetector/postprocessing/detector_calibration.py +565 -0
- megadetector/postprocessing/md_to_coco.py +85 -19
- megadetector/postprocessing/postprocess_batch_results.py +32 -21
- megadetector/postprocessing/validate_batch_results.py +174 -64
- megadetector/taxonomy_mapping/map_new_lila_datasets.py +15 -12
- megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +1 -1
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +3 -1
- megadetector/utils/ct_utils.py +64 -2
- megadetector/utils/md_tests.py +15 -13
- megadetector/utils/path_utils.py +153 -37
- megadetector/utils/process_utils.py +9 -3
- megadetector/utils/write_html_image_list.py +21 -6
- megadetector/visualization/visualization_utils.py +329 -102
- megadetector/visualization/visualize_db.py +104 -63
- {megadetector-5.0.20.dist-info → megadetector-5.0.22.dist-info}/LICENSE +0 -0
- {megadetector-5.0.20.dist-info → megadetector-5.0.22.dist-info}/METADATA +143 -142
- {megadetector-5.0.20.dist-info → megadetector-5.0.22.dist-info}/RECORD +40 -39
- {megadetector-5.0.20.dist-info → megadetector-5.0.22.dist-info}/WHEEL +1 -1
- {megadetector-5.0.20.dist-info → megadetector-5.0.22.dist-info}/top_level.txt +0 -0
- megadetector/data_management/importers/prepare-noaa-fish-data-for-lila.py +0 -359
|
@@ -40,6 +40,10 @@ EXIF_IMAGE_ROTATIONS = {
|
|
|
40
40
|
|
|
41
41
|
TEXTALIGN_LEFT = 0
|
|
42
42
|
TEXTALIGN_RIGHT = 1
|
|
43
|
+
TEXTALIGN_CENTER = 2
|
|
44
|
+
|
|
45
|
+
VTEXTALIGN_TOP = 0
|
|
46
|
+
VTEXTALIGN_BOTTOM = 1
|
|
43
47
|
|
|
44
48
|
# Convert category ID from int to str
|
|
45
49
|
DEFAULT_DETECTOR_LABEL_MAP = {
|
|
@@ -413,6 +417,7 @@ def render_detection_bounding_boxes(detections,
|
|
|
413
417
|
max_classifications=3,
|
|
414
418
|
colormap=None,
|
|
415
419
|
textalign=TEXTALIGN_LEFT,
|
|
420
|
+
vtextalign=VTEXTALIGN_TOP,
|
|
416
421
|
label_font_size=DEFAULT_LABEL_FONT_SIZE,
|
|
417
422
|
custom_strings=None):
|
|
418
423
|
"""
|
|
@@ -469,37 +474,27 @@ def render_detection_bounding_boxes(detections,
|
|
|
469
474
|
]
|
|
470
475
|
|
|
471
476
|
image (PIL.Image.Image): image on which we should render detections
|
|
472
|
-
|
|
473
477
|
label_map (dict, optional): optional, mapping the numeric label to a string name. The type of the
|
|
474
478
|
numeric label (typically strings) needs to be consistent with the keys in label_map; no casting is
|
|
475
479
|
carried out. If [label_map] is None, no labels are shown (not even numbers and confidence values).
|
|
476
480
|
If you want category numbers and confidence values without class labels, use the default value,
|
|
477
481
|
the string 'show_categories'.
|
|
478
|
-
|
|
479
482
|
classification_label_map (dict, optional): optional, mapping of the string class labels to the actual
|
|
480
483
|
class names. The type of the numeric label (typically strings) needs to be consistent with the keys
|
|
481
484
|
in label_map; no casting is carried out. If [label_map] is None, no labels are shown (not even numbers
|
|
482
485
|
and confidence values).
|
|
483
|
-
|
|
484
486
|
confidence_threshold (float or dict, optional), threshold above which boxes are rendered. Can also be a
|
|
485
|
-
dictionary mapping category IDs to thresholds.
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
expansion (int, optional): number of pixels to expand bounding boxes on each side
|
|
490
|
-
|
|
487
|
+
dictionary mapping category IDs to thresholds.
|
|
488
|
+
thickness (int, optional): line thickness in pixels
|
|
489
|
+
expansion (int, optional): number of pixels to expand bounding boxes on each side
|
|
491
490
|
classification_confidence_threshold (float, optional): confidence above which classification results
|
|
492
|
-
are displayed
|
|
493
|
-
|
|
494
|
-
max_classifications (int, optional): maximum number of classification results rendered for one image
|
|
495
|
-
|
|
491
|
+
are displayed
|
|
492
|
+
max_classifications (int, optional): maximum number of classification results rendered for one image
|
|
496
493
|
colormap (list, optional): list of color names, used to choose colors for categories by
|
|
497
|
-
indexing with the values in [classes]; defaults to a reasonable set of colors
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
label_font_size (float, optional): font size for labels
|
|
502
|
-
|
|
494
|
+
indexing with the values in [classes]; defaults to a reasonable set of colors
|
|
495
|
+
textalign (int, optional): TEXTALIGN_LEFT, TEXTALIGN_CENTER, or TEXTALIGN_RIGHT
|
|
496
|
+
vtextalign (int, optional): VTEXTALIGN_TOP or VTEXTALIGN_BOTTOM
|
|
497
|
+
label_font_size (float, optional): font size for labels
|
|
503
498
|
custom_strings: optional set of strings to append to detection labels, should have the
|
|
504
499
|
same length as [detections]. Appended before any classification labels.
|
|
505
500
|
"""
|
|
@@ -613,7 +608,8 @@ def render_detection_bounding_boxes(detections,
|
|
|
613
608
|
|
|
614
609
|
draw_bounding_boxes_on_image(image, display_boxes, classes,
|
|
615
610
|
display_strs=display_strs, thickness=thickness,
|
|
616
|
-
expansion=expansion, colormap=colormap,
|
|
611
|
+
expansion=expansion, colormap=colormap,
|
|
612
|
+
textalign=textalign, vtextalign=vtextalign,
|
|
617
613
|
label_font_size=label_font_size)
|
|
618
614
|
|
|
619
615
|
# ...render_detection_bounding_boxes(...)
|
|
@@ -627,6 +623,8 @@ def draw_bounding_boxes_on_image(image,
|
|
|
627
623
|
display_strs=None,
|
|
628
624
|
colormap=None,
|
|
629
625
|
textalign=TEXTALIGN_LEFT,
|
|
626
|
+
vtextalign=VTEXTALIGN_TOP,
|
|
627
|
+
text_rotation=None,
|
|
630
628
|
label_font_size=DEFAULT_LABEL_FONT_SIZE):
|
|
631
629
|
"""
|
|
632
630
|
Draws bounding boxes on an image. Modifies the image in place.
|
|
@@ -647,7 +645,9 @@ def draw_bounding_boxes_on_image(image,
|
|
|
647
645
|
or classification categories and/or confidence values.
|
|
648
646
|
colormap (list, optional): list of color names, used to choose colors for categories by
|
|
649
647
|
indexing with the values in [classes]; defaults to a reasonable set of colors
|
|
650
|
-
textalign (int, optional): TEXTALIGN_LEFT or TEXTALIGN_RIGHT
|
|
648
|
+
textalign (int, optional): TEXTALIGN_LEFT, TEXTALIGN_CENTER, or TEXTALIGN_RIGHT
|
|
649
|
+
vtextalign (int, optional): VTEXTALIGN_TOP or VTEXTALIGN_BOTTOM
|
|
650
|
+
text_rotation (float, optional): rotation to apply to text
|
|
651
651
|
label_font_size (float, optional): font size for labels
|
|
652
652
|
"""
|
|
653
653
|
|
|
@@ -655,23 +655,55 @@ def draw_bounding_boxes_on_image(image,
|
|
|
655
655
|
if not boxes_shape:
|
|
656
656
|
return
|
|
657
657
|
if len(boxes_shape) != 2 or boxes_shape[1] != 4:
|
|
658
|
-
|
|
659
|
-
return # no object detection on this image, return
|
|
658
|
+
return
|
|
660
659
|
for i in range(boxes_shape[0]):
|
|
660
|
+
display_str_list = None
|
|
661
661
|
if display_strs:
|
|
662
662
|
display_str_list = display_strs[i]
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
663
|
+
draw_bounding_box_on_image(image,
|
|
664
|
+
boxes[i, 0], boxes[i, 1], boxes[i, 2], boxes[i, 3],
|
|
665
|
+
classes[i],
|
|
666
|
+
thickness=thickness, expansion=expansion,
|
|
667
|
+
display_str_list=display_str_list,
|
|
668
|
+
colormap=colormap,
|
|
669
|
+
textalign=textalign,
|
|
670
|
+
vtextalign=vtextalign,
|
|
671
|
+
text_rotation=text_rotation,
|
|
672
|
+
label_font_size=label_font_size)
|
|
671
673
|
|
|
672
674
|
# ...draw_bounding_boxes_on_image(...)
|
|
673
675
|
|
|
674
676
|
|
|
677
|
+
def get_text_size(font,s):
|
|
678
|
+
"""
|
|
679
|
+
Get the expected width and height when rendering the string [s] in the font
|
|
680
|
+
[font].
|
|
681
|
+
|
|
682
|
+
Args:
|
|
683
|
+
font (PIL.ImageFont): the font whose size we should query
|
|
684
|
+
s (str): the string whose size we should query
|
|
685
|
+
|
|
686
|
+
Returns:
|
|
687
|
+
tuple: (w,h), both floats in pixel coordinates
|
|
688
|
+
"""
|
|
689
|
+
|
|
690
|
+
# This is what we did w/Pillow 9
|
|
691
|
+
# w,h = font.getsize(s)
|
|
692
|
+
|
|
693
|
+
# I would *think* this would be the equivalent for Pillow 10
|
|
694
|
+
# l,t,r,b = font.getbbox(s); w = r-l; h=b-t
|
|
695
|
+
|
|
696
|
+
# ...but this actually produces the most similar results to Pillow 9
|
|
697
|
+
# l,t,r,b = font.getbbox(s); w = r; h=b
|
|
698
|
+
|
|
699
|
+
try:
|
|
700
|
+
l,t,r,b = font.getbbox(s); w = r; h=b
|
|
701
|
+
except Exception:
|
|
702
|
+
w,h = font.getsize(s)
|
|
703
|
+
|
|
704
|
+
return w,h
|
|
705
|
+
|
|
706
|
+
|
|
675
707
|
def draw_bounding_box_on_image(image,
|
|
676
708
|
ymin,
|
|
677
709
|
xmin,
|
|
@@ -684,7 +716,9 @@ def draw_bounding_box_on_image(image,
|
|
|
684
716
|
use_normalized_coordinates=True,
|
|
685
717
|
label_font_size=DEFAULT_LABEL_FONT_SIZE,
|
|
686
718
|
colormap=None,
|
|
687
|
-
textalign=TEXTALIGN_LEFT
|
|
719
|
+
textalign=TEXTALIGN_LEFT,
|
|
720
|
+
vtextalign=VTEXTALIGN_TOP,
|
|
721
|
+
text_rotation=None):
|
|
688
722
|
"""
|
|
689
723
|
Adds a bounding box to an image. Modifies the image in place.
|
|
690
724
|
|
|
@@ -717,7 +751,9 @@ def draw_bounding_box_on_image(image,
|
|
|
717
751
|
label_font_size (float, optional): font size
|
|
718
752
|
colormap (list, optional): list of color names, used to choose colors for categories by
|
|
719
753
|
indexing with the values in [classes]; defaults to a reasonable set of colors
|
|
720
|
-
textalign (int, optional): TEXTALIGN_LEFT or TEXTALIGN_RIGHT
|
|
754
|
+
textalign (int, optional): TEXTALIGN_LEFT, TEXTALIGN_CENTER, or TEXTALIGN_RIGHT
|
|
755
|
+
vtextalign (int, optional): VTEXTALIGN_TOP or VTEXTALIGN_BOTTOM
|
|
756
|
+
text_rotation (float, optional): rotation to apply to text
|
|
721
757
|
"""
|
|
722
758
|
|
|
723
759
|
if colormap is None:
|
|
@@ -768,71 +804,98 @@ def draw_bounding_box_on_image(image,
|
|
|
768
804
|
draw.line([(left, top), (left, bottom), (right, bottom),
|
|
769
805
|
(right, top), (left, top)], width=thickness, fill=color)
|
|
770
806
|
|
|
771
|
-
|
|
772
|
-
font = ImageFont.truetype('arial.ttf', label_font_size)
|
|
773
|
-
except IOError:
|
|
774
|
-
font = ImageFont.load_default()
|
|
775
|
-
|
|
776
|
-
def get_text_size(font,s):
|
|
807
|
+
if display_str_list is not None:
|
|
777
808
|
|
|
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
809
|
try:
|
|
788
|
-
|
|
789
|
-
except
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
return w,h
|
|
810
|
+
font = ImageFont.truetype('arial.ttf', label_font_size)
|
|
811
|
+
except IOError:
|
|
812
|
+
font = ImageFont.load_default()
|
|
793
813
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
text_bottom = bottom + total_display_str_height
|
|
806
|
-
|
|
807
|
-
# Reverse list and print from bottom to top.
|
|
808
|
-
for display_str in display_str_list[::-1]:
|
|
809
|
-
|
|
810
|
-
# Skip empty strings
|
|
811
|
-
if len(display_str) == 0:
|
|
812
|
-
continue
|
|
813
|
-
|
|
814
|
-
text_width, text_height = get_text_size(font,display_str)
|
|
815
|
-
|
|
816
|
-
text_left = left
|
|
817
|
-
|
|
818
|
-
if textalign == TEXTALIGN_RIGHT:
|
|
819
|
-
text_left = right - text_width
|
|
814
|
+
display_str_heights = [get_text_size(font,ds)[1] for ds in display_str_list]
|
|
815
|
+
|
|
816
|
+
# Each display_str has a top and bottom margin of 0.05x.
|
|
817
|
+
total_display_str_height = (1 + 2 * 0.05) * sum(display_str_heights)
|
|
818
|
+
|
|
819
|
+
# Reverse list and print from bottom to top
|
|
820
|
+
for i_str,display_str in enumerate(display_str_list[::-1]):
|
|
821
|
+
|
|
822
|
+
# Skip empty strings
|
|
823
|
+
if len(display_str) == 0:
|
|
824
|
+
continue
|
|
820
825
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
826
|
+
text_width, text_height = get_text_size(font,display_str)
|
|
827
|
+
margin = int(np.ceil(0.05 * text_height))
|
|
828
|
+
|
|
829
|
+
if text_rotation is not None and text_rotation != 0:
|
|
830
|
+
|
|
831
|
+
assert text_rotation == -90, \
|
|
832
|
+
'Only -90-degree text rotation is supported'
|
|
833
|
+
|
|
834
|
+
image_tmp = Image.new('RGB',(text_width+2*margin,text_height+2*margin))
|
|
835
|
+
image_tmp_draw = ImageDraw.Draw(image_tmp)
|
|
836
|
+
image_tmp_draw.rectangle([0,0,text_width+2*margin,text_height+2*margin],fill=color)
|
|
837
|
+
image_tmp_draw.text( (margin,margin), display_str, font=font, fill='black')
|
|
838
|
+
rotated_text = image_tmp.rotate(text_rotation,expand=1)
|
|
839
|
+
|
|
840
|
+
if textalign == TEXTALIGN_RIGHT:
|
|
841
|
+
text_left = right
|
|
842
|
+
else:
|
|
843
|
+
text_left = left
|
|
844
|
+
text_left = int(text_left + (text_height) * i_str)
|
|
845
|
+
|
|
846
|
+
if vtextalign == VTEXTALIGN_BOTTOM:
|
|
847
|
+
text_top = bottom - text_width
|
|
848
|
+
else:
|
|
849
|
+
text_top = top
|
|
850
|
+
text_left = int(text_left)
|
|
851
|
+
text_top = int(text_top)
|
|
852
|
+
|
|
853
|
+
image.paste(rotated_text,[text_left,text_top])
|
|
854
|
+
|
|
855
|
+
else:
|
|
856
|
+
|
|
857
|
+
# If the total height of the display strings added to the top of the bounding
|
|
858
|
+
# box exceeds the top of the image, stack the strings below the bounding box
|
|
859
|
+
# instead of above, and vice-versa if we're bottom-aligning.
|
|
860
|
+
#
|
|
861
|
+
# If the text just doesn't fit outside the box, we don't try anything fancy,
|
|
862
|
+
# it will just appear outside the image.
|
|
863
|
+
if vtextalign == VTEXTALIGN_TOP:
|
|
864
|
+
text_bottom = top
|
|
865
|
+
if (text_bottom - total_display_str_height) < 0:
|
|
866
|
+
text_bottom = bottom + total_display_str_height
|
|
867
|
+
else:
|
|
868
|
+
assert vtextalign == VTEXTALIGN_BOTTOM, \
|
|
869
|
+
'Unrecognized vertical text alignment {}'.format(vtextalign)
|
|
870
|
+
text_bottom = bottom + total_display_str_height
|
|
871
|
+
if (text_bottom + total_display_str_height) > im_height:
|
|
872
|
+
text_bottom = top
|
|
873
|
+
|
|
874
|
+
text_bottom = int(text_bottom) - i_str * (int(text_height + (2 * margin)))
|
|
875
|
+
|
|
876
|
+
text_left = left
|
|
877
|
+
|
|
878
|
+
if textalign == TEXTALIGN_RIGHT:
|
|
879
|
+
text_left = right - text_width
|
|
880
|
+
elif textalign == TEXTALIGN_CENTER:
|
|
881
|
+
text_left = ((right + left) / 2.0) - (text_width / 2.0)
|
|
882
|
+
text_left = int(text_left)
|
|
883
|
+
|
|
884
|
+
draw.rectangle(
|
|
885
|
+
[(text_left, (text_bottom - text_height) - (2 * margin)),
|
|
886
|
+
(text_left + text_width, text_bottom)],
|
|
887
|
+
fill=color)
|
|
888
|
+
|
|
889
|
+
draw.text(
|
|
890
|
+
(text_left + margin, text_bottom - text_height - margin),
|
|
891
|
+
display_str,
|
|
892
|
+
fill='black',
|
|
893
|
+
font=font)
|
|
894
|
+
|
|
895
|
+
# ...if we're rotating text
|
|
835
896
|
|
|
897
|
+
# ...if we're rendering text
|
|
898
|
+
|
|
836
899
|
# ...def draw_bounding_box_on_image(...)
|
|
837
900
|
|
|
838
901
|
|
|
@@ -885,7 +948,12 @@ def render_db_bounding_boxes(boxes,
|
|
|
885
948
|
original_size=None,
|
|
886
949
|
label_map=None,
|
|
887
950
|
thickness=DEFAULT_BOX_THICKNESS,
|
|
888
|
-
expansion=0
|
|
951
|
+
expansion=0,
|
|
952
|
+
colormap=None,
|
|
953
|
+
textalign=TEXTALIGN_LEFT,
|
|
954
|
+
vtextalign=VTEXTALIGN_TOP,
|
|
955
|
+
text_rotation=None,
|
|
956
|
+
label_font_size=DEFAULT_LABEL_FONT_SIZE):
|
|
889
957
|
"""
|
|
890
958
|
Render bounding boxes (with class labels) on an image. This is a wrapper for
|
|
891
959
|
draw_bounding_boxes_on_image, allowing the caller to operate on a resized image
|
|
@@ -907,6 +975,12 @@ def render_db_bounding_boxes(boxes,
|
|
|
907
975
|
thickness (int, optional): line width
|
|
908
976
|
expansion (int, optional): a number of pixels to include on each side of a cropped
|
|
909
977
|
detection
|
|
978
|
+
colormap (list, optional): list of color names, used to choose colors for categories by
|
|
979
|
+
indexing with the values in [classes]; defaults to a reasonable set of colors
|
|
980
|
+
textalign (int, optional): TEXTALIGN_LEFT, TEXTALIGN_CENTER, or TEXTALIGN_RIGHT
|
|
981
|
+
vtextalign (int, optional): VTEXTALIGN_TOP or VTEXTALIGN_BOTTOM
|
|
982
|
+
text_rotation (float, optional): rotation to apply to text
|
|
983
|
+
label_font_size (float, optional): font size for labels
|
|
910
984
|
"""
|
|
911
985
|
|
|
912
986
|
display_boxes = []
|
|
@@ -944,7 +1018,12 @@ def render_db_bounding_boxes(boxes,
|
|
|
944
1018
|
classes,
|
|
945
1019
|
display_strs=display_strs,
|
|
946
1020
|
thickness=thickness,
|
|
947
|
-
expansion=expansion
|
|
1021
|
+
expansion=expansion,
|
|
1022
|
+
colormap=colormap,
|
|
1023
|
+
textalign=textalign,
|
|
1024
|
+
vtextalign=vtextalign,
|
|
1025
|
+
text_rotation=text_rotation,
|
|
1026
|
+
label_font_size=label_font_size)
|
|
948
1027
|
|
|
949
1028
|
# ...def render_db_bounding_boxes(...)
|
|
950
1029
|
|
|
@@ -968,11 +1047,11 @@ def draw_bounding_boxes_on_file(input_file,
|
|
|
968
1047
|
Args:
|
|
969
1048
|
input_file (str): filename or URL to load
|
|
970
1049
|
output_file (str, optional): filename to which we should write the rendered image
|
|
971
|
-
detections (list): a list of dictionaries with keys 'conf' and '
|
|
1050
|
+
detections (list): a list of dictionaries with keys 'conf', 'bbox', and 'category';
|
|
972
1051
|
boxes are length-four arrays formatted as [x,y,w,h], normalized,
|
|
973
|
-
upper-left origin (this is the standard MD detection format)
|
|
1052
|
+
upper-left origin (this is the standard MD detection format). 'category' is a string-int.
|
|
974
1053
|
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
|
|
1054
|
+
is None, no confidence values or identifiers are shown. If this is {}, just category
|
|
976
1055
|
indices and confidence values are shown.
|
|
977
1056
|
thickness (int, optional): line width in pixels for box rendering
|
|
978
1057
|
expansion (int, optional): box expansion in pixels
|
|
@@ -1043,7 +1122,7 @@ def draw_db_boxes_on_file(input_file,
|
|
|
1043
1122
|
classes = [0] * len(boxes)
|
|
1044
1123
|
|
|
1045
1124
|
render_db_bounding_boxes(boxes, classes, image, original_size=None,
|
|
1046
|
-
|
|
1125
|
+
label_map=label_map, thickness=thickness, expansion=expansion)
|
|
1047
1126
|
|
|
1048
1127
|
image.save(output_file)
|
|
1049
1128
|
|
|
@@ -1125,7 +1204,6 @@ def gray_scale_fraction(image,crop_size=(0.1,0.1)):
|
|
|
1125
1204
|
if r == g and r == b and g == b:
|
|
1126
1205
|
n_gray_pixels += 1
|
|
1127
1206
|
|
|
1128
|
-
|
|
1129
1207
|
# ...def gray_scale_fraction(...)
|
|
1130
1208
|
|
|
1131
1209
|
|
|
@@ -1376,6 +1454,98 @@ def resize_image_folder(input_folder,
|
|
|
1376
1454
|
# ...def resize_image_folder(...)
|
|
1377
1455
|
|
|
1378
1456
|
|
|
1457
|
+
def get_image_size(im,verbose=False):
|
|
1458
|
+
"""
|
|
1459
|
+
Retrieve the size of an image. Returns None if the image fails to load.
|
|
1460
|
+
|
|
1461
|
+
Args:
|
|
1462
|
+
im (str or PIL.Image): filename or PIL image
|
|
1463
|
+
|
|
1464
|
+
Returns:
|
|
1465
|
+
tuple (w,h), or None if the image fails to load.
|
|
1466
|
+
"""
|
|
1467
|
+
|
|
1468
|
+
image_name = '[in memory]'
|
|
1469
|
+
|
|
1470
|
+
try:
|
|
1471
|
+
if isinstance(im,str):
|
|
1472
|
+
image_name = im
|
|
1473
|
+
im = load_image(im)
|
|
1474
|
+
w = im.width
|
|
1475
|
+
h = im.height
|
|
1476
|
+
if w <= 0 or h <= 0:
|
|
1477
|
+
if verbose:
|
|
1478
|
+
print('Error reading width from image {}: {},{}'.format(
|
|
1479
|
+
image_name,w,h))
|
|
1480
|
+
return None
|
|
1481
|
+
return (w,h)
|
|
1482
|
+
except Exception as e:
|
|
1483
|
+
if verbose:
|
|
1484
|
+
print('Error reading width from image {}: {}'.format(
|
|
1485
|
+
image_name,str(e)))
|
|
1486
|
+
return None
|
|
1487
|
+
|
|
1488
|
+
# ...def get_image_size(...)
|
|
1489
|
+
|
|
1490
|
+
|
|
1491
|
+
def parallel_get_image_sizes(filenames,
|
|
1492
|
+
max_workers=16,
|
|
1493
|
+
use_threads=True,
|
|
1494
|
+
recursive=True,
|
|
1495
|
+
verbose=False):
|
|
1496
|
+
"""
|
|
1497
|
+
Retrieve image sizes for a list or folder of images
|
|
1498
|
+
|
|
1499
|
+
Args:
|
|
1500
|
+
filenames (list or str): a list of image filenames or a folder
|
|
1501
|
+
max_workers (int, optional): the number of parallel workers to use; set to <=1 to disable
|
|
1502
|
+
parallelization
|
|
1503
|
+
use_threads (bool, optional): whether to use threads (True) or processes (False) for
|
|
1504
|
+
parallelization
|
|
1505
|
+
recursive (bool, optional): if [filenames] is a folder, whether to search recursively for images.
|
|
1506
|
+
Ignored if [filenames] is a list.
|
|
1507
|
+
verbose (bool, optional): enable additional debug output
|
|
1508
|
+
|
|
1509
|
+
Returns:
|
|
1510
|
+
dict: a dict mapping filenames to (w,h) tuples; the value will be None for images that fail
|
|
1511
|
+
to load.
|
|
1512
|
+
"""
|
|
1513
|
+
|
|
1514
|
+
if isinstance(filenames,str) and os.path.isdir(filenames):
|
|
1515
|
+
if verbose:
|
|
1516
|
+
print('Enumerating images in {}'.format(filenames))
|
|
1517
|
+
filenames = find_images(filenames,recursive=recursive,return_relative_paths=False)
|
|
1518
|
+
|
|
1519
|
+
n_workers = min(max_workers,len(filenames))
|
|
1520
|
+
|
|
1521
|
+
if verbose:
|
|
1522
|
+
print('Getting image sizes for {} images'.format(len(filenames)))
|
|
1523
|
+
|
|
1524
|
+
if n_workers <= 1:
|
|
1525
|
+
|
|
1526
|
+
results = []
|
|
1527
|
+
for filename in filenames:
|
|
1528
|
+
results.append(get_image_size(filename,verbose=verbose))
|
|
1529
|
+
|
|
1530
|
+
else:
|
|
1531
|
+
|
|
1532
|
+
if use_threads:
|
|
1533
|
+
pool = ThreadPool(n_workers)
|
|
1534
|
+
else:
|
|
1535
|
+
pool = Pool(n_workers)
|
|
1536
|
+
|
|
1537
|
+
results = list(tqdm(pool.imap(
|
|
1538
|
+
partial(get_image_size,verbose=verbose),filenames), total=len(filenames)))
|
|
1539
|
+
|
|
1540
|
+
assert len(filenames) == len(results), 'Internal error in parallel_get_image_sizes'
|
|
1541
|
+
|
|
1542
|
+
to_return = {}
|
|
1543
|
+
for i_file,filename in enumerate(filenames):
|
|
1544
|
+
to_return[filename] = results[i_file]
|
|
1545
|
+
|
|
1546
|
+
return to_return
|
|
1547
|
+
|
|
1548
|
+
|
|
1379
1549
|
#%% Image integrity checking functions
|
|
1380
1550
|
|
|
1381
1551
|
def check_image_integrity(filename,modes=None):
|
|
@@ -1494,13 +1664,13 @@ def parallel_check_image_integrity(filenames,
|
|
|
1494
1664
|
with either 'success' or 'error').
|
|
1495
1665
|
"""
|
|
1496
1666
|
|
|
1497
|
-
n_workers = min(max_workers,len(filenames))
|
|
1498
|
-
|
|
1499
1667
|
if isinstance(filenames,str) and os.path.isdir(filenames):
|
|
1500
1668
|
if verbose:
|
|
1501
1669
|
print('Enumerating images in {}'.format(filenames))
|
|
1502
1670
|
filenames = find_images(filenames,recursive=recursive,return_relative_paths=False)
|
|
1503
1671
|
|
|
1672
|
+
n_workers = min(max_workers,len(filenames))
|
|
1673
|
+
|
|
1504
1674
|
if verbose:
|
|
1505
1675
|
print('Checking image integrity for {} filenames'.format(len(filenames)))
|
|
1506
1676
|
|
|
@@ -1527,6 +1697,63 @@ def parallel_check_image_integrity(filenames,
|
|
|
1527
1697
|
|
|
1528
1698
|
if False:
|
|
1529
1699
|
|
|
1700
|
+
#%% Text rendering tests
|
|
1701
|
+
|
|
1702
|
+
import os # noqa
|
|
1703
|
+
import numpy as np # noqa
|
|
1704
|
+
from megadetector.visualization.visualization_utils import \
|
|
1705
|
+
draw_bounding_boxes_on_image, exif_preserving_save, load_image, \
|
|
1706
|
+
TEXTALIGN_LEFT,TEXTALIGN_RIGHT,VTEXTALIGN_BOTTOM,VTEXTALIGN_TOP, \
|
|
1707
|
+
DEFAULT_LABEL_FONT_SIZE
|
|
1708
|
+
|
|
1709
|
+
fn = os.path.expanduser('~\AppData\Local\Temp\md-tests\md-test-images\ena24_7904.jpg')
|
|
1710
|
+
output_fn = r'g:\temp\test.jpg'
|
|
1711
|
+
|
|
1712
|
+
image = load_image(fn)
|
|
1713
|
+
|
|
1714
|
+
w = 0.2; h = 0.2
|
|
1715
|
+
all_boxes = [[0.05, 0.05, 0.25, 0.25],
|
|
1716
|
+
[0.05, 0.35, 0.25, 0.6],
|
|
1717
|
+
[0.35, 0.05, 0.6, 0.25],
|
|
1718
|
+
[0.35, 0.35, 0.6, 0.6]]
|
|
1719
|
+
|
|
1720
|
+
alignments = [
|
|
1721
|
+
[TEXTALIGN_LEFT,VTEXTALIGN_TOP],
|
|
1722
|
+
[TEXTALIGN_LEFT,VTEXTALIGN_BOTTOM],
|
|
1723
|
+
[TEXTALIGN_RIGHT,VTEXTALIGN_TOP],
|
|
1724
|
+
[TEXTALIGN_RIGHT,VTEXTALIGN_BOTTOM]
|
|
1725
|
+
]
|
|
1726
|
+
|
|
1727
|
+
labels = ['left_top','left_bottom','right_top','right_bottom']
|
|
1728
|
+
|
|
1729
|
+
text_rotation = -90
|
|
1730
|
+
n_label_copies = 2
|
|
1731
|
+
|
|
1732
|
+
for i_box,box in enumerate(all_boxes):
|
|
1733
|
+
|
|
1734
|
+
boxes = [box]
|
|
1735
|
+
boxes = np.array(boxes)
|
|
1736
|
+
classes = [i_box]
|
|
1737
|
+
display_strs = [[labels[i_box]]*n_label_copies]
|
|
1738
|
+
textalign = alignments[i_box][0]
|
|
1739
|
+
vtextalign = alignments[i_box][1]
|
|
1740
|
+
draw_bounding_boxes_on_image(image,
|
|
1741
|
+
boxes,
|
|
1742
|
+
classes,
|
|
1743
|
+
thickness=2,
|
|
1744
|
+
expansion=0,
|
|
1745
|
+
display_strs=display_strs,
|
|
1746
|
+
colormap=None,
|
|
1747
|
+
textalign=textalign,
|
|
1748
|
+
vtextalign=vtextalign,
|
|
1749
|
+
label_font_size=DEFAULT_LABEL_FONT_SIZE,
|
|
1750
|
+
text_rotation=text_rotation)
|
|
1751
|
+
|
|
1752
|
+
exif_preserving_save(image,output_fn)
|
|
1753
|
+
from megadetector.utils.path_utils import open_file
|
|
1754
|
+
open_file(output_fn)
|
|
1755
|
+
|
|
1756
|
+
|
|
1530
1757
|
#%% Recursive resize test
|
|
1531
1758
|
|
|
1532
1759
|
from megadetector.visualization.visualization_utils import resize_image_folder # noqa
|