tappyness1 commited on
Commit
26364eb
·
1 Parent(s): 1609464

initial app

Browse files
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ *.ipynb_checkpoints
2
+ __pycache__
3
+ *.ipynb
app.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from src.st_image_tools import ImageTool
3
+
4
+ def call_in_image_tool(cfg_path):
5
+ image_tool = ImageTool(cfg_path)
6
+ return image_tool
7
+
8
+ def main(cfg_path="cfg/cfg.yml"):
9
+ """_summary_
10
+
11
+ Args:
12
+ cfg_path (str, optional): _description_. Defaults to "cfg/cfg.yml".
13
+
14
+ Returns:
15
+ _type_: _description_
16
+ """
17
+ st.set_page_config(layout="wide")
18
+
19
+ st.markdown(
20
+ """ <style>
21
+ #MainMenu {visibility: hidden;}
22
+ footer {visibility: hidden;}
23
+ </style> """,
24
+ unsafe_allow_html=True,
25
+ )
26
+
27
+ image_tool = call_in_image_tool(cfg_path)
28
+
29
+ # Select Plot Option
30
+ # st.sidebar.markdown("Checkboxes")
31
+ # checkbox_one = st.sidebar.checkbox("Show Image", value=True) # rename as necessary
32
+ checkbox_two = st.sidebar.checkbox("Show Inference", value=True)
33
+ checkbox_three = st.sidebar.checkbox("Show Ground Truth", value=True)
34
+ checkbox_four = st.sidebar.checkbox("Show Side by Side (GT and Pred)", value=False)
35
+
36
+ option = st.sidebar.selectbox("Select Image", image_tool.all_img)
37
+
38
+ if checkbox_two:
39
+
40
+ if checkbox_three:
41
+ if checkbox_four:
42
+ image_tool.plot_with_preds_gt(option=option, side_by_side=True)
43
+ else:
44
+ image_tool.plot_with_preds_gt(option=option, plot_type="all")
45
+
46
+ else:
47
+ image_tool.plot_with_preds_gt(option=option, plot_type="pred")
48
+
49
+ elif checkbox_three:
50
+
51
+ if checkbox_two:
52
+ if checkbox_four:
53
+ image_tool.plot_with_preds_gt(option=option, side_by_side=True)
54
+ else:
55
+ image_tool.plot_with_preds_gt(option=option, plot_type="all")
56
+
57
+ else:
58
+ image_tool.plot_with_preds_gt(option=option, plot_type="gt")
59
+
60
+ else:
61
+ image_tool.plot_with_preds_gt(option=option)
62
+
63
+
64
+ if __name__ == "__main__":
65
+ main()
cfg/cfg.yml ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ error_analysis:
2
+ # detection_classes: ["person", "bicycle"]
3
+ labels_dict: {"person": 1, "bicycle": 2} # GT's index for the classes should be zero-index but COCO wants to be special.
4
+ inference_labels_dict: {"person": 0, "bicycle": 1} # model's index for the classes. Should be zero-index, but sometimes not
5
+ conf_thresholds: [0.2, 0.35, 0.5, 0.65, 0.8] # some call it score threshold
6
+ iou_thresholds: [0.2, 0.35, 0.5, 0.65, 0.8] # back in my day we call it NMS threshold *shakes fist*
7
+ # nms_thresholds: [0.2, 0.5, 0.8]
8
+ bbox_format: "pascal_voc" # yolo / coco / pascal_voc (WIP feature)
9
+ peekingduck: True # False if using your own model for inference without peekingduck wrapper, else True
10
+ ground_truth_format: "coco" # yolo / coco / pascal_voc (WIP feature)
11
+ idx_base : 1 # to indicate whether the class index is zero or one based. Applies to both GT and pred class
12
+ task: seg # either "det" or "seg"
13
+
14
+ pkd:
15
+ model: "yolact_edge" # either "yolo" or "yolact_edge"
16
+ yolo_ver: "v4tiny"
17
+ yolact_ver: "r50-fpn"
18
+
19
+ dataset:
20
+ classes: ["person", "bicycle"] # same as ['error_analysis']['detection_classes'] field above
21
+ img_folder_path: 'data/annotations_trainval2017/coco_small/' # relative path from root for saving the coco dataset images
22
+ annotations_folder_path: 'data/annotations_trainval2017/annotations/' # relative path from root to the annotations file
23
+ annotations_fname: "instances_val2017.json" # what is the name of your json file?
24
+
25
+ visual_tool:
26
+ bbox_thickness: 2 # how thicc you want the bbox to be
27
+ font_scale: 1 # how big you want the fonts to be
28
+ font_thickness: 2 # how thicc you want the fonts to be
29
+ pred_colour: [255, 0, 0] # prediction colour, [B,G,R]
30
+ gt_colour: [0, 255, 0] # Ground truth colour, [B,G,R]
31
+ conf_threshold: 0.2 # Confidence Threshold for use in the Visual Tool. [0, 1]
32
+ iou_threshold: 0.2 # IOU/NMS Threshold for use in the Visual Tool. [0, 1]
data/annotations_trainval2017/annotations/instances_val2017.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"annotations": [{"segmentation": [[406.42,181.47,410.12,183.82,423.56,175.42,423.56,179.12,419.53,183.82,425.92,193.91,426.92,209.03,424.91,214.41,418.86,223.49,411.8,224.5,406.76,222.14,404.07,206.01,400.03,207.69,395.66,189.2,403.39,181.81],[400.86,150.77,418.55,149.9,417.68,146.88,400.43,146.02]],"area": 1052.5614000000007,"iscrowd": 0,"image_id": 441586,"bbox": [395.66,146.02,31.26,78.48],"category_id": 2,"id": 128928},{"segmentation": [[550.06,162.05,552.14,159.37,558.69,158.48,562.25,158.48,561.36,162.94,565.82,166.51,579.8,171.57,583.07,167.4,590.8,165.02,592.58,169.78,597.34,174.54,603.29,175.43,605.37,182.87,604.77,196.84,591.1,202.79,578.01,200.41,574.15,194.46,569.98,196.25,571.17,191.79,563.44,179.0,560.17,175.43,557.5,174.54,558.09,176.62,558.98,186.73,545.9,195.35,536.98,191.19,536.38,181.97,541.44,176.03,547.09,171.86,551.55,170.67,554.52,168.3,555.41,165.02,553.63,161.75,552.44,161.75]],"area": 1679.4667500000005,"iscrowd": 0,"image_id": 441586,"bbox": [536.38,158.48,68.99,44.31],"category_id": 2,"id": 131214},{"segmentation": [[350.34,272.33,348.42,263.71,347.47,257.01,346.51,239.78,342.68,218.72,336.94,193.83,334.07,175.65,335.98,151.72,336.94,136.4,336.94,122.04,336.94,104.81,336.94,103.86,334.07,96.2,334.07,96.2,325.45,113.43,323.54,124.92,319.71,137.36,313.01,141.19,309.18,140.23,306.31,135.44,309.18,108.64,311.09,98.11,312.05,88.54,316.84,79.93,326.41,70.35,330.24,66.53,339.81,56.95,337.89,54.08,336.94,43.55,335.98,34.94,338.85,24.41,355.12,17.71,371.4,19.62,376.18,27.28,382.88,38.77,387.67,52.17,397.24,69.4,403.94,81.84,407.77,96.2,407.77,103.86,405.86,123.96,397.24,141.19,388.63,161.29,387.67,198.62,386.71,225.42,383.84,236.91,380.97,244.57,380.01,250.31,378.1,257.01,377.14,262.75,371.4,265.63,366.61,263.71,366.61,246.48,366.61,233.08,367.57,223.51,367.57,200.53,367.57,192.88,365.65,175.65,358.95,194.79,358.95,210.11,361.82,223.51,358.95,235.95,355.12,253.18,356.08,264.67,356.08,268.5]],"area": 13597.403899999998,"iscrowd": 0,"image_id": 441586,"bbox": [306.31,17.71,101.46,254.62],"category_id": 1,"id": 224536},{"segmentation": [[252.78,131.62,255.52,126.13,256.35,124.21,257.44,118.45,258.27,115.71,263.75,119.28,265.67,123.94,269.51,130.25,269.51,134.09,268.14,138.75,266.22,151.91,265.95,157.95,265.4,163.16,259.36,167.0,258.54,165.9,257.17,158.22,257.17,151.09,256.35,148.07,255.25,145.61,255.52,140.12,252.78,135.73,249.77,132.44]],"area": 529.5225499999995,"iscrowd": 0,"image_id": 441586,"bbox": [249.77,115.71,19.74,51.29],"category_id": 1,"id": 236500},{"segmentation": [[405.9,403.99,459.38,394.44,459.38,394.44,482.3,372.47,488.99,331.4,470.84,282.7,455.56,263.6,452.7,262.64,427.87,250.22,387.75,248.31,387.75,248.31,365.79,265.51,357.19,276.97,341.91,276.97,347.64,260.73,340.96,209.16,340.96,209.16,326.63,203.43,333.31,166.18,333.31,161.4,319.94,135.62,317.08,145.17,318.03,154.72,322.81,169.04,322.81,169.04,325.67,173.82,316.12,177.64,290.34,185.28,286.52,197.7,286.52,198.65,276.97,215.84,276.01,258.82,286.52,285.56,326.63,289.38,345.73,324.72,349.55,332.36,351.46,335.22,364.83,375.34,364.83,375.34,387.75,401.12]],"area": 24496.91995,"iscrowd": 0,"image_id": 441586,"bbox": [276.01,135.62,212.98,268.37],"category_id": 4,"id": 245321},{"segmentation": [[100.49,132.54,99.78,129.85,99.78,127.97,100.13,125.56,101.12,124.58,102.73,123.15,104.34,123.15,104.43,122.34,104.52,121.45,103.89,120.91,103.35,120.82,103.44,118.94,104.07,118.14,104.7,117.78,106.22,117.15,108.81,117.24,109.52,118.14,109.52,121.18,109.88,122.16,113.55,124.04,114.71,125.29,115.43,127.26,111.13,126.99,106.57,126.63,105.5,126.9,104.07,127.08,103.98,128.24,103.71,130.84,103.71,131.28,103.8,133.52,103.71,133.79,103.71,134.06,101.39,134.15],[105.59,134.06,105.23,136.74,112.21,136.74,113.91,135.67,115.78,134.95,116.14,133.61,116.14,133.61,116.14,133.61,105.32,133.34]],"area": 140.68389999999982,"iscrowd": 0,"image_id": 441586,"bbox": [99.78,117.15,16.36,19.59],"category_id": 1,"id": 1237637},{"segmentation": [[115.82,126.99,128.23,127.43,129.88,132.7,130.98,132.04,130.98,128.52,127.24,123.47,124.6,121.82,123.07,116.11,118.23,115.45,117.57,118.64,118.45,122.26,114.72,122.81],[116.58,133.8,118.23,136.76,127.13,136.21,127.35,133.91]],"area": 136.59869999999984,"iscrowd": 0,"image_id": 441586,"bbox": [114.72,115.45,16.26,21.31],"category_id": 1,"id": 1254020},{"segmentation": [[525.33,82.16,533.15,74.5,543.26,74.19,549.23,80.63,548.31,88.9,540.65,96.72,529.93,96.56,525.18,91.66]],"area": 432.5689499999999,"iscrowd": 0,"image_id": 441586,"bbox": [525.18,74.19,24.05,22.53],"category_id": 13,"id": 1389230},{"segmentation": [[399.9,147.78,400.2,156.12,401.09,157.91,410.03,154.04,413.01,152.25,416.59,152.25,418.68,150.76,415.7,147.48,417.49,140.63,421.06,137.05,426.13,136.75,430.0,140.03,429.71,145.1,427.32,149.86,427.02,154.93,431.2,158.81,427.92,164.77,422.25,175.8,416.29,179.67,411.82,180.56,407.35,182.95,402.28,183.25,406.76,176.99,413.91,164.77,410.63,162.68,407.05,161.79,397.22,163.58,396.03,152.85]],"area": 743.4173000000001,"iscrowd": 0,"image_id": 441586,"bbox": [396.03,136.75,35.17,46.5],"category_id": 1,"id": 1727927},{"segmentation": [[103.28,272.37,73.12,280.59,48.44,274.19,35.65,249.52,30.16,248.6,31.08,233.98,51.18,210.22,94.14,207.47,102.37,214.78,103.28,198.33,93.23,194.68,85.0,188.28,90.48,181.88,105.11,182.8,116.08,186.45,121.56,191.02,113.33,192.85,108.76,192.85,105.11,197.42,172.74,197.42,171.83,191.02,160.86,190.11,162.69,180.05,163.6,170.91,148.98,174.57,150.81,169.09,159.03,163.6,167.26,166.34,166.34,160.86,157.2,158.12,166.34,151.72,172.74,169.09,166.34,184.62,175.48,189.19,182.8,208.39,181.88,215.7,200.16,212.96,221.18,223.01,233.06,244.95,226.67,266.88,216.61,282.42,195.59,286.99,171.83,281.51,161.77,273.28,155.38,259.57,150.81,243.12,159.95,227.58,173.66,219.35,174.57,215.7,171.83,204.73,163.6,206.56,157.2,220.27,148.06,214.78,141.67,214.78,129.78,223.92,120.65,212.96,115.16,208.39,109.68,210.22,111.51,217.53,116.08,223.01,122.47,243.12,132.53,246.77,130.7,254.09,125.22,260.48,123.39,260.48,118.82,260.48,116.08,262.31,112.42,263.23]],"area": 11828.584050000001,"iscrowd": 0,"image_id": 441586,"bbox": [30.16,151.72,202.9,135.27],"category_id": 2,"id": 126632},{"segmentation": [[222.16,121.37,204.15,131.43,198.32,148.39,198.32,171.17,205.21,201.89,210.51,210.37,211.03,222.56,214.74,222.56,220.57,211.96,246.53,222.03,267.19,205.6,276.73,216.2,294.21,211.96,298.45,193.42,345.6,158.98,345.6,166.93,340.3,174.35,330.24,198.72,324.41,224.68,332.35,227.32,350.9,262.29,372.62,271.83,403.35,272.36,422.95,242.16,416.59,203.48,404.93,176.47,389.57,161.63,395.93,156.33,402.82,113.42,371.56,116.6,342.42,112.36,357.78,106.0,345.6,103.36,320.17,103.36,296.86,98.06,294.21,99.65,293.15,107.06,320.17,107.06,333.94,110.24,338.71,112.36,339.77,136.73,275.14,119.25,268.25,108.65,282.56,107.59,286.26,100.18,283.09,99.12,275.14,98.06,272.49,98.06,268.78,94.88,266.13,92.76,258.19,91.17,252.36,95.41,256.6,102.83,261.36,104.94,263.48,106.53,266.66,108.12,266.66,113.42,264.01,118.19,260.83,118.72,252.89,113.95,249.18,111.3,241.76,109.71,238.05,108.65,228.52,108.12,224.81,108.12,216.33,110.24,216.33,120.31]],"area": 23082.81575,"iscrowd": 0,"image_id": 11149,"bbox": [198.32,91.17,224.63,181.19],"category_id": 2,"id": 126643},{"segmentation": [[0.0,133.87,0.0,265.63,14.36,280.83,21.96,279.14,26.18,269.85,30.41,261.4,38.85,263.09,50.68,263.09,54.05,271.54,55.74,286.74,61.66,308.7,68.41,323.06,72.64,333.19,83.61,345.86,96.28,350.93,120.78,349.24,130.91,339.95,135.14,324.75,136.82,310.39,136.82,295.19,135.98,280.83,132.6,259.71,126.69,247.89,119.93,235.22,113.18,223.4,108.95,212.42,103.89,208.19,99.66,202.28,95.44,192.99,92.91,182.01,90.37,171.88,89.53,163.43,89.53,154.98,90.37,151.6,108.11,138.94,103.04,130.49,95.44,130.49,90.37,132.18,88.68,133.02,81.93,133.02,77.7,132.18,79.39,126.27,81.08,122.04,85.3,119.51,88.68,111.91,88.68,103.46,92.91,93.33,97.13,89.95,103.04,84.88,111.49,76.44,111.49,69.68,106.42,68.83,90.37,68.83,86.15,68.83,85.3,75.59,86.15,77.28,84.46,102.62,75.17,105.15,71.79,106.84,67.57,110.22,61.66,106.84,53.21,97.55,47.3,97.55,39.7,103.46,36.32,106.0,36.32,110.22,33.78,115.29,29.56,118.67,27.03,121.2,21.11,121.2,16.05,115.29,8.45,109.38,0.84,104.31]],"area": 22842.389549999996,"iscrowd": 0,"image_id": 11149,"bbox": [0.0,68.83,136.82,282.1],"category_id": 4,"id": 151281}, {"segmentation": [[21.11,120.35,30.41,103.46,34.63,95.02,39.7,99.24,58.28,99.24,67.57,103.46,75.17,103.46,73.48,97.55,51.52,76.44,13.51,51.94,22.8,40.12,22.8,23.23,19.43,13.09,17.74,7.18,10.14,5.49,3.38,3.8,0.0,46.03,1.69,108.53,1.69,108.53,19.43,121.2]],"area": 3560.2923000000005,"iscrowd": 0,"image_id": 11149,"bbox": [0.0,3.8,75.17,117.4],"category_id": 1,"id": 197935},{"segmentation": [[355.4,44.07,359.52,125.03,386.96,119.54,400.68,120.91,400.68,134.64,380.1,145.61,369.12,146.99,375.98,174.43,393.82,200.5,389.7,284.2,391.08,326.74,403.43,341.84,426.75,341.84,434.99,341.84,448.71,334.98,452.83,321.25,441.85,318.51,437.73,317.14,419.89,285.58,421.26,273.23,421.26,260.88,426.75,219.71,424.01,193.64,422.64,173.06,430.87,174.43,441.85,163.45,437.73,152.47,437.73,126.4,445.96,75.63,448.71,49.56,448.71,34.47,445.96,27.6,419.89,24.86,374.61,24.86,367.75,30.35,366.38,35.84,359.52,42.7,359.52,45.44]],"area": 17385.67285,"iscrowd": 0,"image_id": 492937,"bbox": [355.4,24.86,97.43,316.98],"category_id": 1,"id": 198827},{"segmentation": [[417.13,288.9,436.52,268.68,439.04,228.23,434.83,143.12,445.79,111.1,445.79,98.46,458.43,79.92,450.0,57.16,453.37,34.41,471.07,1.54,498.88,1.54,500.0,245.08,488.76,260.25,486.24,281.32,485.39,310.81,474.44,315.03,467.7,315.87,460.96,313.34,471.91,233.29,470.22,222.33,469.38,222.33,460.11,259.41,453.37,293.12,423.88,297.33]],"area": 15959.356599999996,"iscrowd": 0,"image_id": 11149,"bbox": [417.13,1.54,82.87,314.33],"category_id": 1,"id": 199247}, {"segmentation": [[94.17,98.47,95.01,90.91,98.37,89.22,109.3,89.22,113.51,94.27,114.35,97.63,114.35,106.88,116.03,117.81,118.55,140.51,131.17,112.77,131.17,91.75,125.28,82.5,119.39,76.61,108.46,71.57,108.46,69.89,110.99,63.16,116.03,60.64,125.28,58.95,143.78,72.41,153.87,82.5,146.3,97.63,148.82,124.54,176.57,131.26,193.39,153.97,199.27,212.82,161.43,230.48,134.53,228.8,110.99,206.1,103.42,178.35,97.53,175.83,88.28,171.62,91.65,148.08,105.1,146.4,110.99,133.79,108.46,127.06,111.83,107.72,98.37,97.63]],"area": 10372.88825,"iscrowd": 0,"image_id": 11149,"bbox": [88.28,58.95,110.99,171.53],"category_id": 2,"id": 240899}, {"segmentation": [[282.61,469.21,290.16,422.83,299.87,354.88,285.84,340.85,253.48,330.07,231.91,309.57,224.36,296.63,239.46,258.88,237.3,239.46,240.54,207.1,258.88,172.58,283.69,147.78,299.87,134.83,303.1,114.34,310.65,87.37,319.28,78.74,339.78,84.13,351.64,102.47,362.43,122.97,359.19,142.38,352.72,151.01,362.43,173.66,362.43,195.24,361.35,221.12,376.45,244.85,403.42,273.98,382.92,285.84,366.74,288.0,354.88,306.34,341.93,318.2,344.09,347.33,347.33,374.29,347.33,393.71,345.17,408.81,362.43,422.83,369.98,432.54,351.64,441.17,337.62,440.09,333.3,459.51]],"area": 33491.37034999999,"iscrowd": 0,"image_id": 576031,"bbox": [224.36,78.74,179.06,390.47],"category_id": 1,"id": 445741}, {"segmentation": [[269.13,162.23,265.79,160.38,265.79,158.02,272.35,149.85,272.1,138.09,273.59,129.68,276.19,123.24,279.78,121.26,285.6,121.63,289.56,124.11,290.67,127.2,295.13,137.6,295.87,137.85,287.08,148.0]],"area": 613.1277999999993,"iscrowd": 0,"image_id": 576031,"bbox": [265.79,121.26,30.08,40.97],"category_id": 1,"id": 473571}, {"segmentation": [[188.78,186.73,182.84,195.79,182.84,205.17,182.84,221.42,177.84,224.55,168.78,224.55,164.72,223.92,166.59,219.55,169.72,218.92,170.03,211.73,172.84,201.73,172.84,190.17,174.09,183.29,171.28,175.16,170.97,165.79,170.03,154.22,161.59,157.03,149.09,155.47,145.02,158.28,144.09,153.28,143.77,148.91,147.84,147.66,155.03,150.78,165.97,142.97,170.34,140.47,172.53,134.22,168.78,130.15,167.53,122.96,170.34,115.15,176.59,112.02,183.47,116.4,183.78,124.21,184.1,128.28,186.6,131.4,192.22,134.53,195.35,137.65,195.35,145.78,203.79,167.66,205.35,172.66,205.35,178.29,202.54,185.48,200.97,193.29,207.85,210.17,212.23,218.92,211.6,221.74,206.29,223.92,200.97,225.49,197.54,225.49,196.6,222.67,200.35,219.24,197.85,214.55,192.53,206.73,190.97,201.42,188.78,189.23]],"area": 3038.4778499999998,"iscrowd": 0,"image_id": 576031,"bbox": [143.77,112.02,68.46,113.47],"category_id": 1,"id": 498483}, {"segmentation": [[169.58,141.88,170.12,139.69,170.67,136.95,169.58,133.94,167.66,130.1,167.66,125.71,167.38,125.17,163.27,126.54,157.52,133.39,155.6,136.95,155.33,139.96,155.33,141.88,152.86,145.45,152.59,147.64,152.59,149.28,152.59,149.83,155.87,149.28,159.16,146.27,163.27,143.53],[152.04,157.23,152.31,163.26,153.68,166.82,154.78,170.38,156.15,174.77,154.78,181.89,147.65,192.58,144.09,198.34,141.9,203.27,143.27,204.91,146.01,206.01,146.83,207.38,149.57,208.2,151.22,209.57,153.68,209.85,156.15,209.85,154.5,208.2,152.86,202.72,153.96,200.53,157.79,193.13,161.9,188.47,163.82,187.65,166.56,197.79,167.66,203.54,168.21,207.65,168.21,210.67,170.67,208.75,173.14,194.77,172.86,187.1,172.59,181.62,171.77,176.96,169.03,170.66,168.75,167.37,168.75,163.26,169.3,156.96,169.85,155.04]],"area": 1103.7693,"iscrowd": 0,"image_id": 576031,"bbox": [141.9,125.17,31.24,85.5],"category_id": 1,"id": 515669}, {"segmentation": [[201.13,438.05,288.11,443.26,283.65,434.34,202.62,433.59],[320.07,446.23,545.32,461.1,570.59,451.43,549.04,454.41,323.05,436.57],[208.57,455.89,300.75,480.0,314.87,478.2,259.86,466.3,210.8,453.66]],"area": 2864.165400000003,"iscrowd": 0,"image_id": 576031,"bbox": [201.13,433.59,369.46,46.41],"category_id": 35,"id": 1453611}, {"segmentation": [[431.59,362.72,438.02,334.25,435.26,320.48,433.43,304.87,430.67,290.18,422.41,271.81,424.25,260.79,425.16,247.94,404.96,247.94,405.88,222.22,410.47,191.0,403.12,186.41,414.14,169.88,402.21,158.86,405.88,147.84,419.65,149.68,448.12,144.17,451.79,161.62,448.12,170.8,463.73,183.66,481.18,217.63,492.2,234.16,492.2,248.85,496.79,247.94,491.28,255.28,493.12,275.48,495.87,292.01,486.69,296.6,487.61,314.05,490.36,328.74,477.51,331.5,473.83,319.56,479.34,297.52,479.34,278.24,479.34,270.89,472.0,309.46,472.91,328.74,470.16,333.34,470.16,352.62,470.16,358.13,477.51,370.98,473.83,382.92,455.47,378.33,452.71,370.07,446.28,372.82,431.59,373.74,428.84,361.8]],"area": 13001.949300000002,"iscrowd": 0,"image_id": 576052,"bbox": [402.21,144.17,94.58,238.75],"category_id": 19,"id": 53914}, {"segmentation": [[428.25,158.67,427.93,145.36,433.45,129.77,445.14,121.0,439.94,113.86,437.99,109.96,438.32,101.52,444.49,92.1,449.68,91.13,457.15,90.8,459.42,93.4,465.92,102.17,467.87,107.69,464.94,115.16,468.52,120.68,478.91,126.85,483.78,151.2,474.69,165.49,499.04,190.49,507.16,238.2,515.27,244.7,516.25,248.27,508.78,252.16,494.17,245.02,484.1,199.6,467.54,178.82,453.9,170.38,438.32,162.58]],"area": 4388.146849999999,"iscrowd": 0,"image_id": 576052,"bbox": [427.93,90.8,88.32,161.36],"category_id": 1,"id": 196260}]}
data/annotations_trainval2017/coco_small/000000011149.jpg ADDED
data/annotations_trainval2017/coco_small/000000441586.jpg ADDED
data/annotations_trainval2017/coco_small/000000576031.jpg ADDED
data/annotations_trainval2017/coco_small/000000576052.jpg ADDED
environment.yml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ dependencies:
2
+ - python=3.8
3
+ - pip
4
+ - pip:
5
+ - peekingduck # only necessary for pkd
6
+ - typeguard == 2.13.3 # only necessary for pkd
7
+ - beautifulsoup4==4.11.2
8
+ - opencv-python==4.7.0.68
9
+ - pandas==1.5.3
10
+ - numpy==1.24.2
11
+ - cython
12
+ - pycocotools-windows # change to pycocotools if non-Windows
13
+ - jupyter==1.0.0
14
+ - pyyaml
15
+ - streamlit==1.20.0
16
+ - plotly==5.13.1
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pandas
2
+ plotly
3
+ datasets
4
+ peekingduck
5
+ beautifulsoup4==4.11.2
6
+ opencv-python==4.7.0.68
7
+ pandas==1.5.3
8
+ numpy==1.24.2
9
+ cython
10
+ pycocotools
11
+ jupyter==1.0.0
12
+ pyyaml
13
+ # streamlit==1.20.0
14
+ plotly==5.13.1
src/confusion_matrix.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ def box_iou_calc(boxes1, boxes2):
5
+ # https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
6
+ """
7
+ Return intersection-over-union (Jaccard index) of boxes.
8
+ Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
9
+ Arguments:
10
+ boxes1 (Array[N, 4])
11
+ boxes2 (Array[M, 4])
12
+ Returns:
13
+ iou (Array[N, M]): the NxM matrix containing the pairwise
14
+ IoU values for every element in boxes1 and boxes2
15
+
16
+ This implementation is taken from the above link and changed so that it only uses numpy.
17
+ """
18
+
19
+ def box_area(box):
20
+ # box = 4xn
21
+ return (box[2] - box[0]) * (box[3] - box[1])
22
+
23
+
24
+ area1 = box_area(boxes1.T)
25
+ area2 = box_area(boxes2.T)
26
+
27
+ lt = np.maximum(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2]
28
+ rb = np.minimum(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2]
29
+
30
+ inter = np.prod(np.clip(rb - lt, a_min = 0, a_max = None), 2)
31
+ return inter / (area1[:, None] + area2 - inter) # iou = inter / (area1 + area2 - inter)
32
+
33
+ def mask_iou_calc(pred_masks, gt_masks):
34
+ """Helper function calculate the IOU of masks
35
+
36
+ Args:
37
+ pred_masks (_type_): N x H x W, array of N masks
38
+ gt_masks (_type_): M x H x W, an array of M masks
39
+
40
+ Returns:
41
+ iou: an array of NxM of IOU ([0,1])
42
+ N rows - number of actual labels
43
+ M columns - number of preds
44
+ """
45
+ if pred_masks.size == 0:
46
+ return np.array([])
47
+
48
+ # build function to take in two masks, compare them and see what their iou is.
49
+ # similar to above but in mask.
50
+ tp = np.sum(np.multiply(pred_masks[:, None], gt_masks), axis = (2,3))
51
+ fp = np.sum(np.where(pred_masks[:, None] > gt_masks, 1, 0), axis = (2,3))
52
+ fn = np.sum(np.where(pred_masks[:, None] < gt_masks, 1, 0), axis = (2,3))
53
+
54
+ # print (f"tp: {tp}")
55
+ # print (f"fp: {fp}")
56
+ # print (f"fn: {fn}")
57
+ iou = tp / (tp + fn + fp)
58
+
59
+ return iou.T
60
+
61
+ class ConfusionMatrix:
62
+
63
+ def __init__(self, num_classes, CONF_THRESHOLD = 0.2, IOU_THRESHOLD = 0.5):
64
+ self.matrix = np.zeros((num_classes + 1, num_classes + 1))
65
+ self.num_classes = num_classes
66
+ self.CONF_THRESHOLD = CONF_THRESHOLD
67
+ self.IOU_THRESHOLD = IOU_THRESHOLD
68
+ self.got_tpfpfn = False
69
+
70
+ def process_batch(self, detections, labels, return_matches=False, task = "det"):
71
+ '''
72
+ Return intersection-over-union (Jaccard index) of boxes.
73
+ Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
74
+ Arguments:
75
+ detections (Array[N, 6]), x1, y1, x2, y2, conf, class
76
+ labels (Array[M, 5]), class, x1, y1, x2, y2
77
+ Returns:
78
+ None, updates confusion matrix accordingly
79
+ '''
80
+
81
+ if task == 'det':
82
+ detections = detections[detections[:, 4] > self.CONF_THRESHOLD]
83
+ gt_classes = labels[:, 0].astype(np.int16)
84
+ detection_classes = detections[:, 5].astype(np.int16)
85
+ all_ious = box_iou_calc(labels[:, 1:], detections[:, :4])
86
+ want_idx = np.where(all_ious > self.IOU_THRESHOLD)
87
+
88
+ elif task == 'seg':
89
+ detections = [detection for detection in detections if detection[1] > self.CONF_THRESHOLD]
90
+ gt_classes = np.array([label[0]for label in labels], dtype = np.int16)
91
+ detection_classes = np.array([detection[2] for detection in detections], dtype = np.int16)
92
+ all_ious = mask_iou_calc(np.array([detection[0] for detection in detections]), np.array([label[1] for label in labels]))
93
+ want_idx = np.where(all_ious > self.IOU_THRESHOLD)
94
+
95
+ all_matches = []
96
+ for i in range(want_idx[0].shape[0]):
97
+ all_matches.append([want_idx[0][i], want_idx[1][i], all_ious[want_idx[0][i], want_idx[1][i]]])
98
+
99
+ all_matches = np.array(all_matches)
100
+ if all_matches.shape[0] > 0: # if there is match
101
+ all_matches = all_matches[all_matches[:, 2].argsort()[::-1]]
102
+
103
+ all_matches = all_matches[np.unique(all_matches[:, 1], return_index = True)[1]]
104
+
105
+ all_matches = all_matches[all_matches[:, 2].argsort()[::-1]]
106
+
107
+ all_matches = all_matches[np.unique(all_matches[:, 0], return_index = True)[1]]
108
+
109
+ for i, label in enumerate(labels):
110
+ if all_matches.shape[0] > 0 and all_matches[all_matches[:, 0] == i].shape[0] == 1:
111
+ gt_class = gt_classes[i]
112
+ detection_class = detection_classes[int(all_matches[all_matches[:, 0] == i, 1][0])]
113
+ self.matrix[(gt_class), detection_class] += 1
114
+ else:
115
+ gt_class = gt_classes[i]
116
+ self.matrix[(gt_class), self.num_classes] += 1
117
+
118
+ for i, detection in enumerate(detections):
119
+ if all_matches.shape[0] and all_matches[all_matches[:, 1] == i].shape[0] == 0:
120
+ detection_class = detection_classes[i]
121
+ self.matrix[self.num_classes ,detection_class] += 1
122
+
123
+ if return_matches:
124
+ return all_matches
125
+
126
+ def get_tpfpfn(self):
127
+ self.tp = np.diag(self.matrix).sum()
128
+ fp = self.matrix.copy()
129
+ np.fill_diagonal(fp, 0)
130
+ self.fp = fp[:,:-1].sum()
131
+ self.fn = self.matrix[:-1, -1].sum()
132
+ self.got_tpfpfn = True
133
+
134
+ def get_PR(self):
135
+ if not self.got_tpfpfn:
136
+ self.get_tpfpfn()
137
+ # print (tp, fp, fn)
138
+ self.precision = self.tp / (self.tp+self.fp)
139
+ self.recall = self.tp/(self.tp+self.fn)
140
+
141
+ def return_matrix(self):
142
+ return self.matrix
143
+
144
+ def process_full_matrix(self):
145
+ """method to process matrix to something more readable
146
+ """
147
+ for idx, i in enumerate(self.matrix):
148
+ i[0] = idx
149
+ self.matrix = np.delete(self.matrix, 0, 0)
150
+
151
+ def print_matrix_as_df(self):
152
+ """method to print out processed matrix
153
+ """
154
+ df = pd.DataFrame(self.matrix)
155
+ print (df.to_string(index=False))
156
+
157
+ # def print_matrix(self):
158
+ # for i in range(self.num_classes + 1):
159
+ # print(' '.join(map(str, self.matrix[i])))
160
+
161
+ def return_as_csv(self, csv_file_path):
162
+ """method to print out processed matrix
163
+ """
164
+ df = pd.DataFrame(self.matrix)
165
+ df.to_csv(csv_file_path, index = False)
166
+ print (f"saved to: {csv_file_path}")
167
+
168
+ def return_as_df(self):
169
+ """method to print out processed matrix
170
+ """
171
+ df = pd.DataFrame(self.matrix)
172
+ # df = df.set_index(0)
173
+ # df.set_index(0)
174
+ # print(df.columns)
175
+ return df
176
+
177
+ if __name__ == '__main__':
178
+ # # test IOU for segmentation masks
179
+ gtMasks = np.array([[[1, 1, 0],
180
+ [0, 1, 0],
181
+ [0, 0, 0]],
182
+ [[1, 1, 0],
183
+ [0, 1, 1],
184
+ [0, 0, 0]]])
185
+ predMasks = np.array([[[1, 1, 0],
186
+ [0, 1, 1],
187
+ [0, 0, 0]],
188
+ [[1, 1, 0],
189
+ [0, 1, 0],
190
+ [0, 0, 0]]])
191
+
192
+ # IOU is 0.75
193
+ IOU = mask_iou_calc(predMasks, gtMasks)
194
+ print (IOU.shape)
src/data_ingestion/data_ingestion.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import json
3
+ import os
4
+ import numpy as np
5
+ from PIL import Image, ImageDraw
6
+
7
+ def convert_seg_coord_to_mask(img_size, coords):
8
+ """Converts the segmentation coords found in COCO dataset to mask
9
+
10
+ Args:
11
+ img_size (_type_): _description_
12
+ coords (_type_): _description_
13
+
14
+ Returns:
15
+ _type_: _description_
16
+ """
17
+
18
+ img = Image.new('L', img_size, 0)
19
+ for polygon in coords:
20
+ ImageDraw.Draw(img).polygon(polygon, outline=1, fill=1)
21
+ mask = np.array(img)
22
+
23
+ return mask
24
+
25
+ class AnnotsGTGetter:
26
+
27
+ def __init__(self, cfg_obj):
28
+
29
+ self.cfg_obj = cfg_obj
30
+
31
+ self.img_folder_path = cfg_obj['dataset']['img_folder_path']
32
+ self.json_folder_path = cfg_obj['dataset']['annotations_folder_path']
33
+ self.annot_json_fname = cfg_obj['dataset']['annotations_fname']
34
+ self.labels_dict = cfg_obj['error_analysis']['labels_dict']
35
+ self.task = cfg_obj['error_analysis']['task']
36
+
37
+ json_file = open(self.json_folder_path + self.annot_json_fname)
38
+ self.annot_data = json.load(json_file)
39
+
40
+ self.img_ids_in_json = [annot['image_id'] for annot in self.annot_data['annotations']]
41
+ self.all_imgs = os.listdir(self.img_folder_path)
42
+
43
+ return
44
+
45
+ def get_imgs(self):
46
+ """method to get the mutually -inclusive- images between the img_ids in json and those in the folder path
47
+
48
+ not needed because all images in folder were accounted for in the json...
49
+ """
50
+ all_img_ids_in_folder = [int(i[:-4]) for i in self.all_imgs]
51
+
52
+ all_imgs_found = [i for i in all_img_ids_in_folder if i in self.img_ids_in_json]
53
+
54
+ print (len(all_imgs_found))
55
+
56
+
57
+ def get_annots(self, img_fname = '000000576052.jpg'):
58
+ """retrieve annotation given a filename
59
+
60
+ Args:
61
+ img_fname (_type_): image file name
62
+
63
+ Returns:
64
+ np array: all annotations of an image
65
+ """
66
+
67
+ # change img_fname for extraction purpose
68
+ # assumes jpg, png, but not jpeg...
69
+ # TODO - what if jpeg?
70
+ annots = []
71
+ img_id = int(img_fname[:-4])
72
+ img = Image.open(self.img_folder_path + img_fname)
73
+
74
+ for annot in self.annot_data['annotations']:
75
+ if img_id == annot['image_id']:
76
+ if annot['category_id'] in list(self.labels_dict.values()):
77
+ if self.task == "det":
78
+ annots.append([annot['category_id'],annot['bbox'][0],annot['bbox'][1],annot['bbox'][2],annot['bbox'][3]])
79
+ elif self.task == "seg":
80
+ # call convert_seg_coord_to_mask to convert segmentations [x1,y1,x2,y2,...,xn,yn] to binary mask
81
+ mask = convert_seg_coord_to_mask(img.size, annot['segmentation'])
82
+ annots.append([annot['category_id'], mask])
83
+
84
+ if self.task == "det":
85
+ return np.array(annots)
86
+ elif self.task == "seg":
87
+ return annots
88
+
89
+ def get_gt_annots(self):
90
+ """goes into the image folder, calls get_annots to extract image annotation
91
+
92
+ Returns:
93
+ dict: all annotations
94
+ """
95
+
96
+ # create dictionary of gt annots
97
+ # for img in os.listdir(self.img_folder_path):
98
+ # self.get_annots(img)
99
+ all_gt_annots = {img: self.get_annots(img) for img in os.listdir(self.img_folder_path)}
100
+
101
+ return all_gt_annots
102
+
103
+ if __name__ == '__main__':
104
+ import yaml
105
+ # get_annots()
106
+ cfg_file = open("cfg/cfg.yml")
107
+ cfg_obj = yaml.load(cfg_file, Loader=yaml.FullLoader)
108
+ annots_obj = AnnotsGTGetter(cfg_obj)
109
+ gt_dict = annots_obj.get_gt_annots()
110
+ # print (gt_dict)
111
+ # annots_obj.get_imgs()
112
+
113
+ # # to output mask
114
+ # img_annots = gt_dict['000000576031.jpg']
115
+
116
+ # import matplotlib.pyplot as plt
117
+ # plt.imshow(img_annots[0][1])
118
+ # plt.show()
119
+
src/error_analysis.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from peekingduck.pipeline.nodes.model import yolo as pkd_yolo
2
+ from peekingduck.pipeline.nodes.model import yolact_edge as pkd_yolact
3
+ from src.data_ingestion.data_ingestion import AnnotsGTGetter
4
+ from src.inference import Inference
5
+ from src.confusion_matrix import ConfusionMatrix
6
+ import yaml
7
+ from itertools import product
8
+ import pandas as pd
9
+ import numpy as np
10
+
11
+ def transform_gt_bbox_format(ground_truth, img_size, format = "coco"):
12
+ """transforms ground truth bbox format to pascal voc for confusion matrix
13
+
14
+ Args:
15
+ ground_truth (_type_): nx5 numpy array, if coco - n x [class, x, y, w, h], if yolo - n x [class, x-mid, y-mid, w, h]
16
+ img_size (_type_): [Height * Weight * Dimension] values vector
17
+ format (str, optional): . Defaults to "coco".
18
+
19
+ Returns:
20
+ _type_: ground_truth. Transformed ground truth to pascal voc format
21
+ """
22
+ if format == "coco":
23
+ ground_truth[:, 3] = (ground_truth[:, 1] + ground_truth[:, 3])/img_size[1]
24
+ ground_truth[:, 1] = (ground_truth[:, 1]) /img_size[1]
25
+ ground_truth[:, 4] = (ground_truth[:, 2] + ground_truth[:, 4])/img_size[0]
26
+ ground_truth[:, 2] = (ground_truth[:, 2]) /img_size[0]
27
+
28
+ return ground_truth
29
+
30
+ def load_model(cfg_obj, iou_threshold, score_threshold):
31
+
32
+ pkd = cfg_obj['error_analysis']['peekingduck']
33
+ task = cfg_obj['error_analysis']['task']
34
+
35
+ if pkd:
36
+
37
+ pkd_model = cfg_obj['pkd']['model']
38
+ # assert task == "seg" and pkd_model == "yolact_edge", "For segmentation tasks, make sure task is seg and pkd_model is yolact_edge"
39
+ # assert task == "det" and pkd_model == "yolo", "For detection tasks, make sure task is det and pkd_model is yolo"
40
+ # only instantiates the v4tiny model, but you are free to change this to other pkd model
41
+ if pkd_model == "yolo":
42
+ yolo_ver = cfg_obj['pkd']['yolo_ver']
43
+ model = pkd_yolo.Node(model_type = yolo_ver,
44
+ detect= list(cfg_obj['error_analysis']['inference_labels_dict'].keys()),
45
+ iou_threshold = iou_threshold,
46
+ score_threshold = score_threshold)
47
+
48
+ if pkd_model == "yolact_edge":
49
+ yolact_ver = cfg_obj['pkd']['yolact_ver']
50
+ model = pkd_yolact.Node(model_type = yolact_ver,
51
+ detect= list(cfg_obj['error_analysis']['inference_labels_dict'].values()),
52
+ iou_threshold = iou_threshold,
53
+ score_threshold = score_threshold)
54
+
55
+ else:
56
+ # call in your own model
57
+ # self.model = <your model import here>
58
+ # make sure that your model has iou_threshold and score_threshold attributes
59
+ # you can easily set those attributes in this else block
60
+ pass
61
+
62
+ return model
63
+
64
+ class ErrorAnalysis:
65
+
66
+ def __init__(self, cfg_path = 'cfg/cfg.yml'):
67
+
68
+ cfg_file = open(cfg_path)
69
+ self.cfg_obj = yaml.load(cfg_file, Loader=yaml.FullLoader)
70
+ # self.nms_thresh = self.cfg_obj['error_analysis']['nms_thresholds']
71
+ self.iou_thresh = self.cfg_obj['error_analysis']['iou_thresholds']
72
+ self.conf_thresh = self.cfg_obj['error_analysis']['conf_thresholds']
73
+ self.inference_folder = self.cfg_obj['dataset']['img_folder_path']
74
+ self.task = self.cfg_obj['error_analysis']['task']
75
+ base_iou_threshold = self.cfg_obj['visual_tool']['iou_threshold']
76
+ base_score_threshold = self.cfg_obj['visual_tool']['conf_threshold']
77
+
78
+ self.cm_results = []
79
+
80
+ # instantiate a "base" model with configs already
81
+ self.model = load_model(self.cfg_obj, base_iou_threshold, base_score_threshold)
82
+
83
+ def generate_inference(self, img_fname = "000000576052.jpg"):
84
+ """Run inference on img based on the image file name. Path to the folder is determined by cfg
85
+
86
+ Args:
87
+ img_fname (str, optional): _description_. Defaults to "000000576052.jpg".
88
+
89
+ Returns:
90
+ ndarray, tuple: if task is 'det': ndarray - n x [x1, y1, x2, y2, score, class], (H, W, D)
91
+ ndarray, tuple: if task is 'seg': list - n x [[array of binary mask], score, class], (H, W, D)
92
+ """
93
+
94
+ inference_obj = Inference(self.model, self.cfg_obj)
95
+ img_path = f"{self.inference_folder}{img_fname}"
96
+ inference_outputs = inference_obj.run_inference_path(img_path)
97
+
98
+ return inference_outputs
99
+
100
+ def get_annots(self):
101
+ """get GT annotations from dataset
102
+ """
103
+
104
+ annots_obj = AnnotsGTGetter(cfg_obj = self.cfg_obj)
105
+ self.gt_dict = annots_obj.get_gt_annots()
106
+
107
+ def generate_conf_matrix(self,iou_threshold = 0.5, conf_threshold = 0.2):
108
+ """generate the confusion matrix by running inference on each image
109
+ """
110
+
111
+ num_classes = len(list(self.cfg_obj['error_analysis']['labels_dict'].keys()))
112
+ ground_truth_format = self.cfg_obj["error_analysis"]["ground_truth_format"]
113
+ idx_base = self.cfg_obj["error_analysis"]["idx_base"]
114
+
115
+ # TODO - currently, Conf Matrix is 0 indexed but all my classes are one-based index.
116
+ # need to find a better to resolve this
117
+ # Infuriating.
118
+ cm = ConfusionMatrix(num_classes=num_classes, CONF_THRESHOLD = conf_threshold, IOU_THRESHOLD=iou_threshold)
119
+
120
+ for fname in list(self.gt_dict.keys()):
121
+
122
+ inference_output, img_size = self.generate_inference(fname)
123
+ ground_truth = self.gt_dict[fname].copy()
124
+
125
+ if self.task == "det":
126
+
127
+ # deduct index_base from each inference's class index
128
+ inference_output[:, -1] -= idx_base
129
+ # deduct index_base from each groundtruth's class index
130
+ ground_truth[:, 0] -= idx_base
131
+ # inference is in x1, y1, x2, y2, scores, class, so OK
132
+ # coco gt is in x, y, width, height - need to change to suit conf matrix
133
+ # img shape is (H, W, D) so plug in accordingly to normalise
134
+ ground_truth = transform_gt_bbox_format(ground_truth=ground_truth, img_size=img_size, format = ground_truth_format)
135
+
136
+ else:
137
+ # deduct index_base from each groundtruth's class index
138
+ ground_truth = [[gt[0] - idx_base, gt[1]] for gt in ground_truth]
139
+
140
+ cm.process_batch(inference_output, ground_truth, task = self.task)
141
+
142
+ cm.get_PR()
143
+
144
+ return cm.matrix, cm.precision, cm.recall
145
+
146
+ def generate_conf_matrices(self, print_matrix = True):
147
+ """generates the confidence matrices
148
+ """
149
+
150
+ # get all combinations of the threshold values:
151
+ combinations = list(product(self.iou_thresh, self.conf_thresh))
152
+ # print (combinations)
153
+ comb_cms = {}
154
+ for comb in combinations:
155
+ # print (f"IOU: {comb[0]}, Conf: {comb[1]}")
156
+ self.model = load_model(self.cfg_obj, iou_threshold=comb[0], score_threshold=comb[1])
157
+ returned_matrix, precision, recall = self.generate_conf_matrix(iou_threshold = comb[0], conf_threshold = comb[1])
158
+ # print (returned_matrix)
159
+ # print (f"precision: {precision}")
160
+ # print (f"recall: {recall}")
161
+ comb_cms[f"IOU: {comb[0]}, Conf: {comb[1]}"] = returned_matrix
162
+ self.cm_results.append([comb[0], comb[1], precision, recall])
163
+
164
+ if print_matrix:
165
+ for k, v in comb_cms.items():
166
+ print (k)
167
+ print (v)
168
+
169
+ def proc_pr_table(self):
170
+
171
+ self.cm_table = pd.DataFrame(self.cm_results, columns = ['IOU_Threshold', 'Score Threshold', 'Precision', 'Recall'])
172
+
173
+ print (self.cm_table)
174
+
175
+
176
+ if __name__ == "__main__":
177
+ ea_games = ErrorAnalysis()
178
+ # print (ea_games.generate_inference())
179
+ ea_games.get_annots()
180
+ ea_games.generate_conf_matrices()
181
+ # print (ea_games.generate_conf_matrix())
182
+ # print (ea_games.gt_dict)
src/get_data_coco/get_img.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pycocotools.coco import COCO
2
+ import requests
3
+ import yaml
4
+
5
+ def get_images(cfg_path = 'cfg/cfg.yml'):
6
+ """To query and get coco dataset by a certain subset e.g. 'person'
7
+
8
+ Args:
9
+ path (str, optional): _description_. Defaults to 'data/annotations_trainval2017/annotations/instances_val2017.json'.
10
+ catNms (list, optional): _description_. Defaults to ['person'].
11
+ """
12
+
13
+ cfg_file = open(cfg_path)
14
+ cfg = yaml.load(cfg_file, Loader=yaml.FullLoader)
15
+
16
+ # instantiate COCO specifying the annotations json path
17
+ coco = COCO(cfg['dataset']['annotations_folder_path'] + cfg['dataset']['annotations_fname'])
18
+ # Specify a list of category names of interest
19
+ catIds = coco.getCatIds(catNms=cfg['dataset']['classes'])
20
+ # Get the corresponding image ids and images using loadImgs
21
+ imgIds = coco.getImgIds(catIds=catIds)
22
+ images = coco.loadImgs(imgIds)
23
+
24
+ # Save the images into a local folder
25
+ for im in images:
26
+ img_data = requests.get(im['coco_url']).content
27
+ with open(cfg['dataset']['img_folder_path'] + im['file_name'], 'wb') as handler:
28
+ handler.write(img_data)
29
+
30
+ return
31
+
32
+ if __name__ == '__main__':
33
+ get_images(cfg_path = 'cfg/cfg.yml')
src/inference.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from peekingduck.pipeline.nodes.model import yolo as pkd_yolo
2
+ import cv2
3
+ from collections import defaultdict
4
+ import numpy as np
5
+ import warnings
6
+ warnings.simplefilter(action='ignore', category=FutureWarning)
7
+
8
+ def convert_labels(inference_labels_dict, bbox_labels):
9
+ for k, v in inference_labels_dict.items():
10
+ bbox_labels[bbox_labels == k] = v
11
+
12
+ # FutureWarning: elementwise comparison failed; returning scalar, but in the future will perform elementwise comparison
13
+ # throws up this warning because making a change string to int is something that numpy disagrees with (???).
14
+
15
+ return bbox_labels
16
+
17
+ def process_masks(inference_outputs, inference_labels_dict):
18
+
19
+ mask_labels = convert_labels(inference_labels_dict, inference_outputs["bbox_labels"])
20
+ masks = inference_outputs["masks"]
21
+ scores = inference_outputs['bbox_scores']
22
+
23
+ processed_output = [[masks[i], scores[i], int(mask_labels[i])] for i in range(len(scores))]
24
+
25
+ return processed_output
26
+
27
+ def process_bboxes(inference_outputs, inference_labels_dict):
28
+
29
+ bbox_labels = inference_outputs["bbox_labels"]
30
+ bbox_labels = convert_labels(inference_labels_dict, bbox_labels)
31
+ bboxes = inference_outputs["bboxes"]
32
+ bbox_scores = inference_outputs["bbox_scores"]
33
+
34
+ # stack the bbox_scores and bbox_labels
35
+ # hence, array(['score', 'score','score']) and array(['class','class','class'])
36
+ # becomes array([['score','class'], ['score','class'],['score','class']])
37
+ stacked = np.stack((bbox_scores, bbox_labels), axis = 1)
38
+
39
+ # concatenate the values of the bbox wih the stacked values above
40
+ # use concatenate here because it is 1xnxm with 1xnxl dimension so it works
41
+ # it's just maths, people!
42
+ concated = np.concatenate((bboxes, stacked), axis = 1)
43
+
44
+ return concated.astype(np.float32)
45
+
46
+ def run_inference(img_matrix, model, inference_labels_dict = {'person': 1, 'bicycle': 2}, task = "det"):
47
+ """Helper function to run per image inference, get bbox, labels and scores and stack them for confusion matrix output
48
+
49
+ Args:
50
+ img_matrix (np.array): _description_
51
+ model: _description_
52
+ labels_dict (dict, optional): _description_. Defaults to {'person': 0, 'bicycle': 1}.
53
+
54
+ Returns:
55
+ concated (np.array): concatenated inference of n x (bbox (default is x1, y1, x2, y2), score, class)
56
+ img_matrix.shape (np vector): vector with [Height * Weight * Dimension] values
57
+ """
58
+ # print(img_matrix.shape)
59
+ # for img_matrix, it's HxWxD. Need to resize it for the confusion matrix
60
+
61
+ # modify this to change the run to your model's inference method eg model(img) in pytorch
62
+ inference_inputs = {"img": img_matrix}
63
+ inference_outputs = model.run(inference_inputs)
64
+
65
+ # pkd outputs for segmentation - {'bboxes': [[],[]..,[]], 'bbox_labels':[], 'bbox_scores':[], 'masks':[[[],[],[]]]}
66
+
67
+ if task == "seg":
68
+ processed_output = process_masks(inference_outputs, inference_labels_dict)
69
+
70
+ elif task == "det":
71
+ processed_output = process_bboxes(inference_outputs, inference_labels_dict)
72
+
73
+ return processed_output, img_matrix.shape
74
+
75
+ class Inference:
76
+
77
+ def __init__(self, model, cfg_obj):
78
+
79
+ self.model = model
80
+ self.labels_dict = cfg_obj['error_analysis']['labels_dict']
81
+ self.inference_labels_dict = cfg_obj['error_analysis']['inference_labels_dict']
82
+ self.task = cfg_obj['error_analysis']['task']
83
+
84
+ def run_inference_path(self, img_path):
85
+ """use if img_path is specified
86
+
87
+ Args:
88
+ img_path (_type_): _description_
89
+
90
+ Returns:
91
+ _type_: _description_
92
+ """
93
+ image_orig = cv2.imread(img_path)
94
+ image_orig = cv2.cvtColor(image_orig, cv2.COLOR_BGR2RGB)
95
+
96
+ output = run_inference(image_orig, self.model, inference_labels_dict = self.inference_labels_dict, task = self.task)
97
+
98
+ return output
99
+
100
+ def run_inference_byte(self, img_bytes):
101
+ """use if the img_bytes is passed in instead of path
102
+
103
+ Args:
104
+ img_bytes (_type_): _description_
105
+
106
+ Returns:
107
+ _type_: _description_
108
+ """
109
+ img_decoded = cv2.imdecode(np.frombuffer(img_bytes, np.uint8), -1)
110
+ img_decoded = cv2.cvtColor(img_decoded, cv2.COLOR_BGR2RGB)
111
+
112
+ output = run_inference(img_decoded, self.model, labels_dict = self.inference_labels_dict, task = self.task)
113
+
114
+ return output
115
+
116
+ if __name__ == "__main__":
117
+ import yaml
118
+ from src.error_analysis import load_model
119
+ cfg_file = open("cfg/cfg.yml")
120
+ cfg_obj = yaml.load(cfg_file, Loader=yaml.FullLoader)
121
+ img_path = "./data/annotations_trainval2017/coco_person/000000576052.jpg"
122
+ inference_obj = Inference(model = load_model(cfg_obj), cfg_obj = cfg_obj)
123
+ output = inference_obj.run_inference_path(img_path)
124
+ print (output)
src/pred_analysis_STEE.py ADDED
@@ -0,0 +1,595 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import cv2
4
+ import time
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ import xml.etree.ElementTree as ET
9
+
10
+ from pathlib import Path
11
+ from torchvision import transforms
12
+ from configparser import ConfigParser, ExtendedInterpolation
13
+ from ast import literal_eval
14
+
15
+ from src.models.model import Model
16
+ from src.models.eval.confusion_matrix import ConfusionMatrix
17
+
18
+
19
+ def generate_inference_from_img_folder(csv_file, model_cfg, img_folder, ckpt_file,
20
+ nms_thresh, conf_thresh, device="cuda" ,csv_path=None):
21
+ """[Retrieve the inference information of the test images given a model checkpoint trained]
22
+
23
+ Parameters
24
+ ----------
25
+ csv_file : [str]
26
+ [path of the csv file containing the information of the test images]
27
+ model_cfg : [str]
28
+ [path of the model config file to use, specific to the checkpoint file]
29
+ img_folder : [str]
30
+ [folder containing the images]
31
+ ckpt_file : [str]
32
+ [path of the model checkpoint file to use for model inference]
33
+ nms_thresh : [float]
34
+ [Non-maximum suppression threshold to use for the model inference, values between 0 to 1]
35
+ conf_thresh : [float]
36
+ [Confidence threshold to use for the model inference, values between 0 to 1]
37
+ device : str, optional
38
+ [device to use for inference, option: "cuda" or "cpu"], by default "cuda"
39
+ csv_path : [str], optional
40
+ [path to save the pandas.DataFrame output as a csv], by default None i.e. csv not generated
41
+
42
+ Returns
43
+ -------
44
+ df : [pandas.DataFrame]
45
+ [dataframe containing the inference information of the test images]
46
+ """
47
+
48
+ pl_config = ConfigParser(interpolation=ExtendedInterpolation())
49
+ pl_config.read(model_cfg)
50
+
51
+ model_selected = Model(pl_config)
52
+
53
+ df_original = pd.read_csv(csv_file)
54
+ # Only perform inference on test images with at least 1 ground truth.
55
+ df_test = df_original[df_original['remarks_xml'] == 'Available xml file'].reset_index()
56
+ df_test = df_test[df_test['set_type'] == 'Test'].reset_index()
57
+
58
+ img_number = 0
59
+ prediction_info_list = []
60
+ for _,rows in df_test.iterrows():
61
+ img_file = rows["image_file_name"]
62
+ img_number += 1
63
+ inference_start_time = time.time()
64
+ img_file_path = os.path.join(img_folder,img_file)
65
+
66
+ # Perform inference on image with ckpt file with device either "cuda" or "cpu"
67
+ # img_inference = model_selected.inference(device='cpu', img_path=img_file_path, ckpt_path=ckpt_file)
68
+ img_inference = model_selected.inference(
69
+ device=device, img_path=img_file_path, ckpt_path=ckpt_file, nms_thresh=nms_thresh, conf_thresh=conf_thresh)
70
+
71
+ # Sieve out inference
72
+ predicted_boxes_unsorted = img_inference[0].tolist()
73
+ predicted_labels_unsorted = img_inference[1].tolist()
74
+ predicted_confidence_unsorted = img_inference[2].tolist()
75
+
76
+ # print(f"Pre Boxes: {predicted_boxes}")
77
+ # print(f"Pre Labels: {predicted_labels}")
78
+ # print(f"Pre Labels: {predicted_confidence}")
79
+
80
+ # Sorting input
81
+ predicted_boxes = [x for _,x in sorted(zip(predicted_confidence_unsorted,predicted_boxes_unsorted), reverse=True)]
82
+ predicted_labels = [x for _,x in sorted(zip(predicted_confidence_unsorted,predicted_labels_unsorted), reverse=True)]
83
+ predicted_confidence = sorted(predicted_confidence_unsorted, reverse=True)
84
+
85
+ # print(f"Post Boxes: {predicted_boxes}")
86
+ # print(f"Post Labels: {predicted_labels}")
87
+ # print(f"Post Labels: {predicted_confidence}")
88
+
89
+ predicted_boxes_int = []
90
+ for box in predicted_boxes:
91
+ box_int = [round(x) for x in box]
92
+ predicted_boxes_int.append(box_int)
93
+
94
+ # Prepare inputs for confusion matrix
95
+ cm_detections_list = []
96
+ for prediction in range(len(predicted_boxes)):
97
+ detection_list = predicted_boxes[prediction]
98
+ detection_list.append(predicted_confidence[prediction])
99
+ detection_list.append(predicted_labels[prediction])
100
+ cm_detections_list.append(detection_list)
101
+
102
+ # Re generate predicted boxes
103
+ predicted_boxes = [x for _,x in sorted(zip(predicted_confidence_unsorted,predicted_boxes_unsorted), reverse=True)]
104
+
105
+ inference_time_per_image = round(time.time() - inference_start_time, 2)
106
+ if img_number%100 == 0:
107
+ print(f'Performing inference on Image {img_number}: {img_file_path}')
108
+ print(f'Time taken for image: {inference_time_per_image}')
109
+
110
+ prediction_info = {
111
+ "image_file_path": img_file_path,
112
+ "image_file_name": img_file,
113
+ "number_of_predictions": len(predicted_boxes),
114
+ "predicted_boxes": predicted_boxes,
115
+ "predicted_boxes_int": predicted_boxes_int,
116
+ "predicted_labels": predicted_labels,
117
+ "predicted_confidence": predicted_confidence,
118
+ "cm_detections_list": cm_detections_list,
119
+ "inference_time": inference_time_per_image
120
+ }
121
+ prediction_info_list.append(prediction_info)
122
+
123
+ df = pd.DataFrame(prediction_info_list)
124
+
125
+ if csv_path is not None:
126
+ df.to_csv(csv_path, index=False)
127
+ print ("Dataframe saved as csv to " + csv_path)
128
+
129
+ return df
130
+
131
+ def get_gt_from_img_folder(csv_file, img_folder, xml_folder, names_file, map_start_index=1, csv_path=None):
132
+ """[Retrieve the ground truth information of the test images]
133
+
134
+ Parameters
135
+ ----------
136
+ csv_file : [str]
137
+ [path of the csv file containing the information of the test images]
138
+ img_folder : [str]
139
+ [folder containing the images]
140
+ xml_folder : [str]
141
+ [folder containing the xml files associated with the images]
142
+ names_file : [str]
143
+ [names file containing the class labels of interest]
144
+ map_start_index : int, optional
145
+ [attach a number to each class label listed in names file, starting from number given by map_start_index], by default 1
146
+ csv_path : [str], optional
147
+ [path to save the pandas.DataFrame output as a csv], by default None i.e. csv not generated
148
+
149
+ Returns
150
+ -------
151
+ df : [pandas.DataFrame]
152
+ [dataframe containing the ground truth information of the test images]
153
+ """
154
+
155
+ df_original = pd.read_csv(csv_file)
156
+
157
+ # Only perform inference on test images with at least 1 ground truth.
158
+ df_test = df_original[df_original['remarks_xml'] == 'Available xml file'].reset_index()
159
+ df_test = df_test[df_test['set_type'] == 'Test'].reset_index()
160
+
161
+ # Create a dictionary to map numeric class as class labels
162
+ class_labels_dict = {}
163
+ with open(names_file) as f:
164
+ for index,line in enumerate(f):
165
+ idx = index + map_start_index
166
+ class_labels = line.splitlines()[0]
167
+ class_labels_dict[class_labels] = idx
168
+
169
+ gt_info_list = []
170
+ # for img_file in os.listdir(img_folder):
171
+ # if re.search(".jpg", img_file):
172
+ for _,rows in df_test.iterrows():
173
+ img_file = rows["image_file_name"]
174
+ # file_stem = Path(img_file_path).stem
175
+
176
+ # Get img tensor
177
+ img_file_path = os.path.join(img_folder,img_file)
178
+ img = cv2.imread(filename = img_file_path)
179
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
180
+
181
+ # Get associated xml file
182
+ file_stem = Path(img_file_path).stem
183
+ xml_file_path = xml_folder + file_stem + ".xml"
184
+
185
+ tree = ET.parse(xml_file_path)
186
+ root = tree.getroot()
187
+
188
+ for image_detail in root.findall('size'):
189
+ image_width = float(image_detail.find('width').text)
190
+ image_height = float(image_detail.find('height').text)
191
+
192
+ class_index_list = []
193
+ bb_list = []
194
+ truncated_list = []
195
+ occluded_list = []
196
+ for item in root.findall('object'):
197
+ if item.find('truncated') is not None:
198
+ truncated = int(item.find('truncated').text)
199
+ else:
200
+ truncated = 0
201
+
202
+ if item.find('occluded').text is not None:
203
+ occluded = int(item.find('occluded').text)
204
+ else:
205
+ occluded = 0
206
+
207
+ for bb_details in item.findall('bndbox'):
208
+ class_label = item.find('name').text
209
+ class_index = class_labels_dict[class_label]
210
+ xmin = float(bb_details.find('xmin').text)
211
+ ymin = float(bb_details.find('ymin').text)
212
+ xmax = float(bb_details.find('xmax').text)
213
+ ymax = float(bb_details.find('ymax').text)
214
+
215
+ class_index_list.append(class_index)
216
+ bb_list.append([xmin,ymin,xmax,ymax])
217
+ truncated_list.append(truncated)
218
+ occluded_list.append(occluded)
219
+
220
+ transform = A.Compose([
221
+ A.Resize(608,608),
222
+ ToTensor()
223
+ ],
224
+ bbox_params=A.BboxParams(format='pascal_voc',
225
+ label_fields=['class_labels']),
226
+ )
227
+
228
+ augmented = transform(image=img, bboxes = bb_list, class_labels = class_index_list)
229
+ # img comes out as int, need to change to float.
230
+ img = augmented['image'].float()
231
+ gt_boxes = augmented['bboxes']
232
+ gt_boxes_list = [list(box) for box in gt_boxes]
233
+ gt_labels = augmented['class_labels']
234
+
235
+ gt_boxes_int = []
236
+ for box in gt_boxes:
237
+ box_int = [round(x) for x in box]
238
+ gt_boxes_int.append(box_int)
239
+
240
+ cm_gt_list = []
241
+ for gt in range(len(gt_boxes)):
242
+ gt_list = [gt_labels[gt]]
243
+ gt_list.extend(gt_boxes[gt])
244
+ cm_gt_list.append(gt_list)
245
+
246
+ # Calculate and Group by Size of Ground Truth
247
+ gt_area_list = []
248
+ gt_area_type = []
249
+ for gt_box in gt_boxes:
250
+ gt_area = (gt_box[3] - gt_box[1]) * (gt_box[2] - gt_box[0])
251
+ gt_area_list.append(gt_area)
252
+
253
+ if gt_area < 32*32:
254
+ area_type = "S"
255
+ gt_area_type.append(area_type)
256
+ elif gt_area < 96*96:
257
+ area_type = "M"
258
+ gt_area_type.append(area_type)
259
+ else:
260
+ area_type = "L"
261
+ gt_area_type.append(area_type)
262
+
263
+ gt_info = {
264
+ "image_file_path": img_file_path,
265
+ "image_file_name": img_file,
266
+ "image_width": image_width,
267
+ "image_height": image_height,
268
+ "number_of_gt": len(gt_boxes_list),
269
+ "gt_labels": gt_labels,
270
+ "gt_boxes": gt_boxes_list,
271
+ "gt_boxes_int": gt_boxes_int,
272
+ "cm_gt_list": cm_gt_list,
273
+ "gt_area_list": gt_area_list,
274
+ "gt_area_type": gt_area_type,
275
+ "truncated_list": truncated_list,
276
+ "occluded_list": occluded_list
277
+ }
278
+ gt_info_list.append(gt_info)
279
+
280
+ df = pd.DataFrame(gt_info_list)
281
+
282
+ if csv_path is not None:
283
+ df.to_csv(csv_path, index=False)
284
+ print ("Dataframe saved as csv to " + csv_path)
285
+
286
+ return df
287
+
288
+ def combine_gt_predictions(csv_file, img_folder, xml_folder, names_file, model_cfg, ckpt_file, csv_save_folder,
289
+ device="cuda", nms_threshold=0.1, confidence_threshold=0.7, iou_threshold=0.4, gt_statistics=True):
290
+ """[Retrieve the combined inference and ground truth information of the test images]
291
+
292
+ Parameters
293
+ ----------
294
+ csv_file : [str]
295
+ [path of the csv file containing the information of the test images]
296
+ img_folder : [str]
297
+ [folder containing the images]
298
+ xml_folder : [str]
299
+ [folder containing the xml files associated with the images]
300
+ names_file : [str]
301
+ [names file containing the class labels of interest]
302
+ model_cfg : [str]
303
+ [path of the model config file to use, specific to the checkpoint file]
304
+ ckpt_file : [str]
305
+ [path of the model checkpoint file to use for model inference]
306
+ csv_save_folder : [str]
307
+ [folder to save the generated csv files]
308
+ device : str, optional
309
+ [device to use for inference, option: "cuda" or "cpu"], by default "cuda"
310
+ nms_threshold : float, optional
311
+ [Non-maximum suppression threshold to use for the model inference, values between 0 to 1], by default 0.1
312
+ confidence_threshold : float, optional
313
+ [Confidence threshold to use for the model inference, values between 0 to 1], by default 0.7
314
+ iou_threshold : float, optional
315
+ [IOU threshold to use for identifying true positives from the predictions and ground truth], by default 0.4
316
+ gt_statistics : bool, optional
317
+ [option to generate the df_gt_analysis], by default True
318
+
319
+ Returns
320
+ -------
321
+ df_full : [pandas.DataFrame]
322
+ [dataframe containing the combined inference and ground truth information of the test images by image]
323
+ df_gt_analysis : pandas.DataFrame, optional
324
+ [dataframe containing the combined inference and ground truth information of the test images by ground truth]
325
+ """
326
+
327
+ print(f"NMS Threshold: {nms_threshold}")
328
+ print(f"Confidence Threshold: {confidence_threshold}")
329
+ print(f"IOU Threshold: {iou_threshold}")
330
+
331
+ df_gt = get_gt_from_img_folder(
332
+ csv_file, img_folder, xml_folder, names_file)
333
+ print("Successful Generation of Ground Truth Information")
334
+ df_predictions = generate_inference_from_img_folder(
335
+ csv_file, model_cfg, img_folder, ckpt_file,
336
+ nms_thresh=nms_threshold, conf_thresh=confidence_threshold, device=device)
337
+ print("Successful Generation of Inference")
338
+
339
+ df_all = pd.merge(df_gt, df_predictions, how='left', on=["image_file_path", "image_file_name"])
340
+ print("Successful Merging")
341
+
342
+ class_labels_list = []
343
+ with open(names_file) as f:
344
+ for index,line in enumerate(f):
345
+ class_labels = line.splitlines()[0]
346
+ class_labels_list.append(class_labels)
347
+
348
+ combined_info_list = []
349
+ for _,rows in df_all.iterrows():
350
+ img_file = rows["image_file_name"]
351
+ predicted_boxes = rows["predicted_boxes"]
352
+ predicted_labels = rows["predicted_labels"]
353
+ predicted_confidence = rows["predicted_confidence"]
354
+ gt_boxes = rows["gt_boxes"]
355
+ gt_labels = rows["gt_labels"]
356
+ cm_gt_list = rows["cm_gt_list"]
357
+ cm_detections_list = rows["cm_detections_list"]
358
+
359
+ if rows["number_of_predictions"] == 0:
360
+ # Ground Truth Analysis
361
+ gt_summary_list = []
362
+ gt_match_list = []
363
+ gt_match_idx_list = []
364
+ gt_match_idx_conf_list = []
365
+ gt_match_idx_bb_list = []
366
+ for idx in range(len(gt_labels)):
367
+ gt_summary = "NO"
368
+ match = ["GT", idx, "-"]
369
+ match_idx = "-"
370
+ match_bb = "-"
371
+ gt_summary_list.append(gt_summary)
372
+ gt_match_list.append(tuple(match))
373
+ gt_match_idx_list.append(match_idx)
374
+ gt_match_idx_conf_list.append(match_idx)
375
+ gt_match_idx_bb_list.append(match_bb)
376
+
377
+ combined_info = {
378
+ "image_file_name": img_file,
379
+ "number_of_predictions_conf": [],
380
+ "predicted_labels_conf": [],
381
+ "predicted_confidence_conf": [],
382
+ "num_matches": [],
383
+ "num_mismatch": [],
384
+ "labels_hit": [],
385
+ "pairs_mislabel_gt_prediction": [],
386
+ "gt_match_idx_list": gt_match_idx_list,
387
+ "gt_match_idx_conf_list": gt_match_idx_conf_list,
388
+ "gt_match_idx_bb_list": gt_match_idx_bb_list,
389
+ "prediction_match": [],
390
+ "gt_analysis": gt_summary_list,
391
+ "prediction_analysis": [],
392
+ "gt_match": gt_match_list
393
+ }
394
+
395
+ else:
396
+
397
+ # Generate Confusion Matrix with their corresponding matches
398
+ CM = ConfusionMatrix(
399
+ num_classes=len(class_labels_list)+1,
400
+ CONF_THRESHOLD = confidence_threshold,
401
+ IOU_THRESHOLD = iou_threshold)
402
+
403
+ matching_boxes = CM.process_batch(
404
+ detections=np.asarray(cm_detections_list),
405
+ labels=np.asarray(cm_gt_list),
406
+ return_matches=True)
407
+
408
+ predicted_confidence_count = len([confidence for confidence in predicted_confidence if confidence > confidence_threshold])
409
+ predicted_confidence_round = [round(confidence, 4) for confidence in predicted_confidence]
410
+
411
+ predicted_confidence_conf = predicted_confidence_round[:predicted_confidence_count]
412
+ predicted_labels_conf = predicted_labels[:predicted_confidence_count]
413
+ predicted_boxes_conf = predicted_boxes[:predicted_confidence_count]
414
+
415
+ number_of_predictions_conf = len(predicted_labels_conf)
416
+
417
+ match_correct_list = []
418
+ match_wrong_list = []
419
+ gt_matched_idx_dict = {}
420
+ predicted_matched_idx_dict = {}
421
+ gt_mismatch_idx_dict = {}
422
+ predicted_mismatch_idx_dict = {}
423
+ labels_hit = []
424
+ pairs_mislabel_gt_prediction = []
425
+
426
+ for match in matching_boxes:
427
+ gt_idx = int(match[0])
428
+ predicted_idx = int(match[1])
429
+ iou = round(match[2], 4)
430
+ match = [gt_idx, predicted_idx, iou]
431
+
432
+ if gt_labels[gt_idx] == predicted_labels_conf[predicted_idx]:
433
+ match_correct_list.append(match)
434
+ gt_matched_idx_dict[gt_idx] = match
435
+ predicted_matched_idx_dict[predicted_idx] = match
436
+ labels_hit.append(gt_labels[gt_idx])
437
+ else:
438
+ match_wrong_list.append(match)
439
+ gt_mismatch_idx_dict[gt_idx] = match
440
+ predicted_mismatch_idx_dict[predicted_idx] = match
441
+ pairs_mislabel_gt_prediction.append(
442
+ [gt_labels[gt_idx],predicted_labels_conf[predicted_idx]])
443
+
444
+ # Ground Truth Analysis
445
+ gt_summary_list = []
446
+ gt_match_list = []
447
+ gt_match_idx_list = []
448
+ gt_match_idx_conf_list = []
449
+ gt_match_idx_bb_list = []
450
+ for idx in range(len(gt_labels)):
451
+ if idx in gt_matched_idx_dict.keys():
452
+ gt_summary = "MATCH"
453
+ match = gt_matched_idx_dict[idx]
454
+ match_idx = predicted_labels_conf[match[1]]
455
+ match_conf = predicted_confidence_conf[match[1]]
456
+ match_bb = predicted_boxes_conf[match[1]]
457
+ elif idx in gt_mismatch_idx_dict.keys():
458
+ gt_summary = "MISMATCH"
459
+ match = gt_mismatch_idx_dict[idx]
460
+ match_idx = predicted_labels_conf[match[1]]
461
+ match_conf = predicted_confidence_conf[match[1]]
462
+ match_bb = predicted_boxes_conf[match[1]]
463
+ else:
464
+ gt_summary = "NO"
465
+ match = ["GT", idx, "-"]
466
+ match_idx = "-"
467
+ match_conf = "-"
468
+ match_bb = "-"
469
+ gt_summary_list.append(gt_summary)
470
+ gt_match_list.append(tuple(match))
471
+ gt_match_idx_list.append(match_idx)
472
+ gt_match_idx_conf_list.append(match_conf)
473
+ gt_match_idx_bb_list.append(match_bb)
474
+
475
+ # Prediction Analysis
476
+ prediction_summary_list = []
477
+ prediction_match_list = []
478
+ for idx in range(len(predicted_labels_conf)):
479
+ if idx in predicted_matched_idx_dict.keys():
480
+ prediction_summary = "MATCH"
481
+ match = predicted_matched_idx_dict[idx]
482
+ elif idx in predicted_mismatch_idx_dict.keys():
483
+ prediction_summary = "MISMATCH"
484
+ match = predicted_mismatch_idx_dict[idx]
485
+ else:
486
+ prediction_summary = "NO"
487
+ match = [idx, "P", "-"]
488
+ prediction_summary_list.append(prediction_summary)
489
+ prediction_match_list.append(tuple(match))
490
+
491
+ combined_info = {
492
+ "image_file_name": img_file,
493
+ "number_of_predictions_conf": number_of_predictions_conf,
494
+ "predicted_labels_conf": predicted_labels_conf,
495
+ "predicted_confidence_conf": predicted_confidence_conf,
496
+ "num_matches": len(match_correct_list),
497
+ "num_mismatch": len(match_wrong_list),
498
+ "labels_hit": labels_hit,
499
+ "pairs_mislabel_gt_prediction": pairs_mislabel_gt_prediction,
500
+ "gt_match_idx_list": gt_match_idx_list,
501
+ "gt_match_idx_conf_list": gt_match_idx_conf_list,
502
+ "gt_match_idx_bb_list": gt_match_idx_bb_list,
503
+ "gt_match": gt_match_list,
504
+ "prediction_match": prediction_match_list,
505
+ "gt_analysis": gt_summary_list,
506
+ "prediction_analysis": prediction_summary_list
507
+ }
508
+
509
+ combined_info_list.append(combined_info)
510
+
511
+ df_combined = pd.DataFrame(combined_info_list)
512
+
513
+ df_full = pd.merge(df_all, df_combined , how='left', on=["image_file_name"])
514
+
515
+ csv_path_combined = f"{csv_save_folder}df_inference_details_nms_{nms_threshold}_conf_{confidence_threshold}_iou_{iou_threshold}.csv"
516
+
517
+ df_full.to_csv(csv_path_combined, index=False)
518
+ print ("Dataframe saved as csv to " + csv_path_combined)
519
+
520
+ if gt_statistics:
521
+ print("Generating Statistics for Single Ground Truth")
522
+ csv_path_gt = f"{csv_save_folder}df_gt_details_nms_{nms_threshold}_conf_{confidence_threshold}_iou_{iou_threshold}.csv"
523
+ df_gt_analysis = __get_single_gt_analysis(csv_output=csv_path_gt, df_input=df_full)
524
+
525
+ return df_full, df_gt_analysis
526
+
527
+ else:
528
+ return df_full
529
+
530
+ def __get_single_gt_analysis(csv_output, df_input=None,csv_input=None):
531
+
532
+ if df_input is None:
533
+ df_gt = pd.read_csv(csv_input)
534
+
535
+ # Apply literal eval of columns containing information on Ground Truth
536
+ df_gt.gt_labels = df_gt.gt_labels.apply(literal_eval)
537
+ df_gt.gt_boxes = df_gt.gt_boxes.apply(literal_eval)
538
+ df_gt.gt_boxes_int = df_gt.gt_boxes_int.apply(literal_eval)
539
+ df_gt.gt_area_list = df_gt.gt_area_list.apply(literal_eval)
540
+ df_gt.gt_area_type = df_gt.gt_area_type.apply(literal_eval)
541
+ df_gt.truncated_list = df_gt.truncated_list.apply(literal_eval)
542
+ df_gt.occluded_list = df_gt.occluded_list.apply(literal_eval)
543
+ df_gt.gt_match_idx_list = df_gt.gt_match_idx_list.apply(literal_eval)
544
+ df_gt.gt_match_idx_conf_list = df_gt.gt_match_idx_conf_list.apply(literal_eval)
545
+ df_gt.gt_match_idx_bb_list = df_gt.gt_match_idx_bb_list.apply(literal_eval)
546
+ df_gt.gt_match = df_gt.gt_match.apply(literal_eval)
547
+ df_gt.gt_analysis = df_gt.gt_analysis.apply(literal_eval)
548
+
549
+ else:
550
+ df_gt = df_input
551
+
552
+ gt_info_list = []
553
+ for _,rows in df_gt.iterrows():
554
+ # print(rows["image_file_name"])
555
+ for idx in range(rows["number_of_gt"]):
556
+ df_gt_image_dict = {
557
+ "GT_Image": rows["image_file_name"],
558
+ "GT_Label": rows["gt_labels"][idx],
559
+ "GT_Boxes": rows["gt_boxes"][idx],
560
+ "GT_Boxes_Int": rows["gt_boxes_int"][idx],
561
+ "GT_Area": rows["gt_area_list"][idx],
562
+ "GT_Area_Type": rows["gt_area_type"][idx],
563
+ "Truncated": rows["truncated_list"][idx],
564
+ "Occluded": rows["occluded_list"][idx],
565
+ "GT_Match": rows["gt_match"][idx],
566
+ "IOU": rows["gt_match"][idx][2],
567
+ "GT_Match_IDX": rows["gt_match_idx_list"][idx],
568
+ "GT_Confidence_IDX": rows["gt_match_idx_conf_list"][idx],
569
+ "GT_Predicted_Boxes_IDX": rows["gt_match_idx_bb_list"][idx],
570
+ "GT_Analysis": rows["gt_analysis"][idx]
571
+ }
572
+ gt_info_list.append(df_gt_image_dict)
573
+
574
+ df_final = pd.DataFrame(gt_info_list)
575
+ df_final = df_final.reset_index(drop=True)
576
+
577
+ df_final.to_csv(csv_output, index=False)
578
+ print ("Dataframe saved as csv to " + csv_output)
579
+
580
+ return df_final
581
+
582
+ if __name__ == '__main__':
583
+
584
+ combine_gt_predictions(
585
+ csv_file="/polyaxon-data/workspace/stee/voc_image_annotations_batch123.csv",
586
+ img_folder="/polyaxon-data/workspace/stee/data_batch123",
587
+ xml_folder="/polyaxon-data/workspace/stee/data_batch123/Annotations/",
588
+ names_file="/polyaxon-data/workspace/stee/data_batch123/obj.names",
589
+ model_cfg="cfg/cfg_frcn.ini",
590
+ ckpt_file="/polyaxon-data/workspace/stee/andy/epoch=99-step=61899.ckpt",
591
+ csv_save_folder="/polyaxon-data/workspace/stee/andy/generation/",
592
+ nms_threshold=0.9,
593
+ confidence_threshold=0.3,
594
+ iou_threshold=0.4,
595
+ gt_statistics=False)
src/st_image_tools.py ADDED
@@ -0,0 +1,441 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import plotly.express as px
4
+ import cv2
5
+ from src.error_analysis import ErrorAnalysis, transform_gt_bbox_format
6
+ import yaml
7
+ import os
8
+ from src.confusion_matrix import ConfusionMatrix
9
+ from plotly.subplots import make_subplots
10
+ import plotly.graph_objects as go
11
+ import pandas as pd
12
+
13
+
14
+ def amend_cm_df(cm_df, labels_dict):
15
+ """Helper function to amend the index and column name for readability
16
+ Example - index currently is 0, 1 ... -> GT - person
17
+ Likewise in Column - 0, 1 ... -> Pred - person etc
18
+
19
+ Args:
20
+ cm_df (_type_): confusion matrix dataframe.
21
+ labels_dict (_type_): dictionary of the class labels
22
+
23
+ Returns:
24
+ cm_df: confusion matrix dataframe with index and column names filled
25
+ """
26
+
27
+ index_list = list(labels_dict.values())
28
+ index_list.append("background")
29
+
30
+ cm_df = cm_df.set_axis([f"GT - {elem}" for elem in index_list])
31
+ cm_df = cm_df.set_axis([f"Pred - {elem}" for elem in index_list], axis=1)
32
+ cm_df = cm_df.astype(int)
33
+
34
+ return cm_df
35
+
36
+ def find_top_left_pos(mask):
37
+ """gets the top left position of the mask
38
+
39
+ Args:
40
+ mask (_type_): _description_
41
+
42
+ Returns:
43
+ _type_: _description_
44
+ """
45
+
46
+ return np.unravel_index(np.argmax(mask, axis=None), mask.shape)
47
+
48
+
49
+ class ImageTool:
50
+
51
+ def __init__(self, cfg_path="cfg/cfg.yml"):
52
+
53
+ # getting the config object
54
+ cfg_file = open(cfg_path)
55
+ self.cfg_obj = yaml.load(cfg_file, Loader=yaml.FullLoader)
56
+
57
+ # initialising the model and getting the annotations
58
+ self.ea_obj = ErrorAnalysis(cfg_path)
59
+ self.inference_folder = self.ea_obj.inference_folder
60
+ self.ea_obj.get_annots()
61
+ self.gt_annots = self.ea_obj.gt_dict
62
+ self.all_img = os.listdir(self.inference_folder)
63
+ self.ea_obj.model.score_threshold = self.cfg_obj["visual_tool"]["conf_threshold"]
64
+ self.ea_obj.model.iou_threshold = self.cfg_obj["visual_tool"]["iou_threshold"]
65
+
66
+ # for labels
67
+ self.labels_dict = self.cfg_obj["error_analysis"]["labels_dict"]
68
+ self.labels_dict = {v: k for k, v in self.labels_dict.items()}
69
+ self.inference_labels_dict = self.cfg_obj["error_analysis"]["inference_labels_dict"]
70
+ self.inference_labels_dict = {v: k for k, v in self.inference_labels_dict.items()}
71
+ self.idx_base = self.cfg_obj["error_analysis"]["idx_base"]
72
+
73
+ # for visualisation
74
+ self.bbox_thickness = self.cfg_obj["visual_tool"]["bbox_thickness"]
75
+ self.font_scale = self.cfg_obj["visual_tool"]["font_scale"]
76
+ self.font_thickness = self.cfg_obj["visual_tool"]["font_thickness"]
77
+ self.pred_colour = tuple(self.cfg_obj["visual_tool"]["pred_colour"])
78
+ self.gt_colour = tuple(self.cfg_obj["visual_tool"]["gt_colour"])
79
+
80
+ def show_img(self, img_fname="000000011149.jpg", show_preds=False, show_gt=False):
81
+ """generate img with option to overlay with GT and/or preds
82
+
83
+ Args:
84
+ img_fname (str, optional): Filename of the image. Defaults to "000000011149.jpg".
85
+ show_preds (bool, optional): Toggle True to run model to get the preds. Defaults to False.
86
+ show_gt (bool, optional): Toggle True to get the GT labels/boxes. Defaults to False.
87
+
88
+ Returns:
89
+ fig (Plotly Figure): image with overlays if toggled True
90
+ cm_df (pd.DataFrame): confusion matrix of the pred versus GT
91
+ cm_tpfpfn_dict (Dict): confusion matrix dictionary of tp/fp/fn
92
+ """
93
+
94
+ # get the image's file path. Concatenates with the folder in question
95
+ img = cv2.imread(f"{self.inference_folder}{img_fname}")
96
+
97
+ labels = {"x": "X", "y": "Y", "color": "Colour"}
98
+
99
+ if show_preds:
100
+
101
+ preds = self.get_preds(img_fname)
102
+ if self.ea_obj.task == "det":
103
+ img = self.draw_pred_bboxes(img, preds)
104
+ elif self.ea_obj.task == "seg":
105
+ img = self.draw_pred_masks(img, preds)
106
+
107
+ if show_gt:
108
+
109
+ gt_annots = self.get_gt_annot(img_fname)
110
+
111
+ if self.ea_obj.task == "det":
112
+ img = self.draw_gt_bboxes(img, preds)
113
+ elif self.ea_obj.task == "seg":
114
+ img = self.draw_gt_masks(img, gt_annots)
115
+
116
+ fig = px.imshow(img[..., ::-1], aspect="equal", labels=labels)
117
+
118
+ if show_gt and show_preds:
119
+
120
+ cm_df, cm_tpfpfn_dict = self.generate_cm_one_image(preds, gt_annots)
121
+ return [fig, cm_df, cm_tpfpfn_dict]
122
+
123
+ return fig
124
+
125
+ def show_img_sbs(self, img_fname="000000011149.jpg"):
126
+ """generate two imageso with confusion matrix and tp/fp/fn. fig1 is image with GT overlay, while fig2 is the image witih pred overlay.
127
+
128
+ Args:
129
+ img_fname (str, optional): Filename of the image. Defaults to "000000011149.jpg".
130
+
131
+ Returns:
132
+ list: fig1 - imshow of image with GT overlay
133
+ fig2 - imshow of image with pred overlay
134
+ cm_df - confusion matrix dataframe
135
+ cm_tpfpfn_df - confusion matrix dictionary of tp/fp/fn
136
+ """
137
+
138
+ # shows the image side by side
139
+ img = cv2.imread(f"{self.inference_folder}{img_fname}")
140
+ labels = {"x": "X", "y": "Y", "color": "Colour"}
141
+
142
+ img_pred = img.copy()
143
+ img_gt = img.copy()
144
+
145
+ preds = self.get_preds(img_fname)
146
+
147
+ gt_annots = self.get_gt_annot(img_fname)
148
+
149
+ if self.ea_obj.task == 'det':
150
+ img_pred = self.draw_pred_bboxes(img_pred, preds)
151
+ img_gt = self.draw_gt_bboxes(img_gt, gt_annots)
152
+
153
+ elif self.ea_obj.task == 'seg':
154
+ img_pred = self.draw_pred_masks(img_pred, preds)
155
+ img_gt = self.draw_gt_masks(img_gt, gt_annots)
156
+
157
+
158
+ fig1 = px.imshow(img_gt[..., ::-1], aspect="equal", labels=labels)
159
+ fig2 = px.imshow(img_pred[..., ::-1], aspect="equal", labels=labels)
160
+ fig2.update_yaxes(visible=False)
161
+
162
+ cm_df, cm_tpfpfn_df = self.generate_cm_one_image(preds, gt_annots)
163
+
164
+ return [fig1, fig2, cm_df, cm_tpfpfn_df]
165
+
166
+ def generate_cm_one_image(self, preds, gt_annots):
167
+ """Generates confusion matrix between the inference and the Ground Truth of an image
168
+
169
+ Args:
170
+ preds (array): inference output of the model on the image
171
+ gt_annots (array): Ground Truth labels of the image
172
+
173
+ Returns:
174
+ cm_df (DataFrame): Confusion matrix dataframe.
175
+ cm_tpfpfn_df (DataFrame): TP/FP/FN dataframe
176
+ """
177
+
178
+ num_classes = len(list(self.cfg_obj["error_analysis"]["labels_dict"].keys()))
179
+ idx_base = self.cfg_obj["error_analysis"]["idx_base"]
180
+
181
+ conf_threshold, iou_threshold = (
182
+ self.ea_obj.model.score_threshold,
183
+ self.ea_obj.model.iou_threshold,
184
+ )
185
+ cm = ConfusionMatrix(
186
+ num_classes=num_classes,
187
+ CONF_THRESHOLD=conf_threshold,
188
+ IOU_THRESHOLD=iou_threshold,
189
+ )
190
+ if self.ea_obj.task == 'det':
191
+ gt_annots[:, 0] -= idx_base
192
+ preds[:, -1] -= idx_base
193
+ elif self.ea_obj.task == 'seg':
194
+ gt_annots = [[gt[0] - idx_base, gt[1]] for gt in gt_annots]
195
+
196
+ cm.process_batch(preds, gt_annots, task = self.ea_obj.task)
197
+
198
+ confusion_matrix_df = cm.return_as_df()
199
+ cm.get_tpfpfn()
200
+
201
+ cm_tpfpfn_dict = {
202
+ "True Positive": cm.tp,
203
+ "False Positive": cm.fp,
204
+ "False Negative": cm.fn,
205
+ }
206
+
207
+ cm_tpfpfn_df = pd.DataFrame(cm_tpfpfn_dict, index=[0])
208
+ cm_tpfpfn_df = cm_tpfpfn_df.set_axis(["Values"], axis=0)
209
+ cm_tpfpfn_df = cm_tpfpfn_df.astype(int)
210
+ # amend df
211
+
212
+ confusion_matrix_df = amend_cm_df(confusion_matrix_df, self.labels_dict)
213
+ # print (cm.matrix)
214
+
215
+ return confusion_matrix_df, cm_tpfpfn_df
216
+
217
+ def get_preds(self, img_fname="000000011149.jpg"):
218
+ """Using the model in the Error Analysis object, run inference to get outputs
219
+
220
+ Args:
221
+ img_fname (str): Image filename. Defaults to "000000011149.jpg".
222
+
223
+ Returns:
224
+ outputs (array): Inference output of the model on the image
225
+ """
226
+
227
+ # run inference using the error analysis object per image
228
+ outputs, img_shape = self.ea_obj.generate_inference(img_fname)
229
+
230
+ if self.ea_obj.task == 'det':
231
+ # converts image coordinates from normalised to integer values
232
+ # image shape is [Y, X, C] (because Rows are Y)
233
+ # So don't get confused!
234
+ outputs[:, 0] *= img_shape[1]
235
+ outputs[:, 1] *= img_shape[0]
236
+ outputs[:, 2] *= img_shape[1]
237
+ outputs[:, 3] *= img_shape[0]
238
+
239
+ return outputs
240
+
241
+ def get_gt_annot(self, img_fname):
242
+ """Retrieve the Ground Truth annotations of the image.
243
+
244
+ Args:
245
+ img_fname (_type_): Image filename
246
+
247
+ Returns:
248
+ grount_truth (array): GT labels of the image
249
+ """
250
+ ground_truth = self.gt_annots[img_fname].copy()
251
+ img = cv2.imread(f"{self.inference_folder}{img_fname}")
252
+
253
+ # converts image coordinates from normalised to integer values
254
+ # image shape is [Y, X, C] (because Rows are Y)
255
+ # So don't get confused!
256
+ if self.ea_obj.task == 'det':
257
+ img_shape = img.shape
258
+ ground_truth = transform_gt_bbox_format(ground_truth, img_shape, format="coco")
259
+ ground_truth[:, 1] *= img_shape[1]
260
+ ground_truth[:, 2] *= img_shape[0]
261
+ ground_truth[:, 3] *= img_shape[1]
262
+ ground_truth[:, 4] *= img_shape[0]
263
+
264
+ return ground_truth
265
+
266
+ def draw_pred_masks(self, img_pred, inference_outputs):
267
+ """Overlay mask onto img_pred
268
+
269
+ Args:
270
+ img_pred (_type_): _description_
271
+ preds (_type_): _description_
272
+ """
273
+
274
+ pred_mask = sum([output[0] for output in inference_outputs])
275
+ pred_mask = np.where(pred_mask > 1, 1, pred_mask)
276
+ # mask_3d = np.stack((mask,mask,mask),axis=0)
277
+ # mask_3d = mask_3d.reshape(mask.shape[0], mask.shape[1], 3)
278
+ colour = np.array(self.pred_colour, dtype='uint8')
279
+ masked_img = np.where(pred_mask[...,None], colour, img_pred)
280
+ masked_img = masked_img.astype(np.uint8)
281
+
282
+ img_pred = cv2.addWeighted(img_pred, 0.8, masked_img, 0.2, 0)
283
+
284
+ def put_text_ina_mask(output, img):
285
+
286
+ coords = find_top_left_pos(output[0])
287
+
288
+ img = cv2.putText(img, self.inference_labels_dict[output[2]], (coords[1], coords[0] + 5), fontFace = cv2.FONT_HERSHEY_SIMPLEX, fontScale = self.font_scale,
289
+ color = self.pred_colour, thickness = self.font_thickness)
290
+
291
+ return img
292
+
293
+ for output in inference_outputs:
294
+ img_pred = put_text_ina_mask(output, img_pred)
295
+
296
+ return img_pred
297
+
298
+ def draw_gt_masks(self, img_gt, gt_outputs):
299
+ """Overlay mask onto img_pred
300
+
301
+ Args:
302
+ img_pred (_type_): _description_
303
+ preds (_type_): _description_
304
+ """
305
+
306
+ gt_mask = sum([output[1] for output in gt_outputs])
307
+ gt_mask = np.where(gt_mask > 1, 1, gt_mask)
308
+ # mask_3d = np.stack((mask,mask,mask),axis=0)
309
+ # mask_3d = mask_3d.reshape(mask.shape[0], mask.shape[1], 3)
310
+ colour = np.array(self.gt_colour, dtype='uint8')
311
+ masked_img = np.where(gt_mask[...,None], colour, img_gt)
312
+
313
+ def put_text_ina_mask(output, img):
314
+
315
+ coords = find_top_left_pos(output[1])
316
+
317
+ img = cv2.putText(img, self.labels_dict[output[0]], (coords[1], coords[0] + 5), fontFace = cv2.FONT_HERSHEY_SIMPLEX, fontScale = self.font_scale,
318
+ color = self.gt_colour, thickness = self.font_thickness)
319
+
320
+ return img
321
+
322
+ img_gt = cv2.addWeighted(img_gt, 0.8, masked_img, 0.2,0)
323
+
324
+ for output in gt_outputs:
325
+ img_gt = put_text_ina_mask(output, img_gt)
326
+
327
+ return img_gt
328
+
329
+ def draw_pred_bboxes(self, img_pred, preds):
330
+ """Draws the preds onto the image
331
+
332
+ Args:
333
+ img_pred (array): image
334
+ preds (array): model inference outputs
335
+
336
+ Returns:
337
+ img_pred (array): image with outputs on overlay
338
+ """
339
+ for pred in preds:
340
+ pred = pred.astype(int)
341
+ img_pred = cv2.rectangle(
342
+ img_pred,
343
+ (pred[0], pred[1]),
344
+ (pred[2], pred[3]),
345
+ color=self.pred_colour,
346
+ thickness=self.bbox_thickness,
347
+ )
348
+ img_pred = cv2.putText(
349
+ img_pred,
350
+ self.labels_dict[pred[5]],
351
+ (pred[0] + 5, pred[1] + 25),
352
+ color=self.pred_colour,
353
+ fontFace=cv2.FONT_HERSHEY_SIMPLEX,
354
+ fontScale=self.font_scale,
355
+ thickness=self.font_thickness,
356
+ )
357
+ return img_pred
358
+
359
+ def draw_gt_bboxes(self, img_gt, gt_annots, **kwargs):
360
+ """Draws the GT onto the image
361
+
362
+ Args:
363
+ img_gt (array): image
364
+ gt_annots (array): GT labels
365
+
366
+ Returns:
367
+ img_gt (array): image with GT overlay
368
+ """
369
+ for annot in gt_annots:
370
+ annot = annot.astype(int)
371
+ # print (annot)
372
+ img_gt = cv2.rectangle(
373
+ img_gt,
374
+ (annot[1], annot[2]),
375
+ (annot[3], annot[4]),
376
+ color=self.gt_colour,
377
+ thickness=self.bbox_thickness,
378
+ )
379
+ img_gt = cv2.putText(
380
+ img_gt,
381
+ self.labels_dict[annot[0]],
382
+ (annot[1] + 5, annot[2] + 25),
383
+ color=(0, 255, 0),
384
+ fontFace=cv2.FONT_HERSHEY_SIMPLEX,
385
+ fontScale=self.font_scale,
386
+ thickness=self.font_thickness,
387
+ )
388
+ return img_gt
389
+
390
+ def plot_with_preds_gt(self, option, side_by_side=False, plot_type=None):
391
+ """Rules on what plot to generate
392
+
393
+ Args:
394
+ option (_string_): image filename. Toggled on the app itself. See app.py
395
+ side_by_side (bool, optional): Whether to have two plots side by side.
396
+ Defaults to False.
397
+ plot_type (_type_, optional): "all" - both GT and pred will be plotted,
398
+ "pred" - only preds,
399
+ "GT" - only ground truth
400
+ None - only image generated
401
+ Will be overridden if side_by_side = True
402
+ Defaults to None.
403
+ """
404
+
405
+ if plot_type == "all":
406
+ plot, df, cm_tpfpfn_df = self.show_img(
407
+ option, show_preds=True, show_gt=True
408
+ )
409
+ st.plotly_chart(plot, use_container_width=True)
410
+ st.caption("Blue: Model BBox, Green: GT BBox")
411
+
412
+ st.table(df)
413
+ st.table(cm_tpfpfn_df)
414
+
415
+ elif plot_type == "pred":
416
+ st.plotly_chart(
417
+ self.show_img(option, show_preds=True), use_container_width=True
418
+ )
419
+
420
+ elif plot_type == "gt":
421
+ st.plotly_chart(
422
+ self.show_img(option, show_gt=True), use_container_width=True
423
+ )
424
+
425
+ elif side_by_side:
426
+
427
+ plot1, plot2, df, cm_tpfpfn_df = self.show_img_sbs(option)
428
+ col1, col2 = st.columns(2)
429
+
430
+ with col1:
431
+ col1.subheader("Ground Truth")
432
+ st.plotly_chart(plot1, use_container_width=True)
433
+ with col2:
434
+ col2.subheader("Prediction")
435
+ st.plotly_chart(plot2, use_container_width=True)
436
+
437
+ st.table(df)
438
+ st.table(cm_tpfpfn_df)
439
+
440
+ else:
441
+ st.plotly_chart(self.show_img(option), use_container_width=True)