tappyness1
commited on
Commit
·
26364eb
1
Parent(s):
1609464
initial app
Browse files- .gitignore +3 -0
- app.py +65 -0
- cfg/cfg.yml +32 -0
- data/annotations_trainval2017/annotations/instances_val2017.json +1 -0
- data/annotations_trainval2017/coco_small/000000011149.jpg +0 -0
- data/annotations_trainval2017/coco_small/000000441586.jpg +0 -0
- data/annotations_trainval2017/coco_small/000000576031.jpg +0 -0
- data/annotations_trainval2017/coco_small/000000576052.jpg +0 -0
- environment.yml +16 -0
- requirements.txt +14 -0
- src/confusion_matrix.py +194 -0
- src/data_ingestion/data_ingestion.py +119 -0
- src/error_analysis.py +182 -0
- src/get_data_coco/get_img.py +33 -0
- src/inference.py +124 -0
- src/pred_analysis_STEE.py +595 -0
- src/st_image_tools.py +441 -0
.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)
|