test
Browse files- .gitignore +3 -0
- .streamlit/config.toml +2 -0
- app.py +111 -0
- car_map/index.html +62 -0
- car_map/script.js +149 -0
- car_map/styles.css +61 -0
- custom_style.css +31 -0
- functions.py +49 -0
- requirements.txt +1 -0
.gitignore
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
.streamlit/secrets.toml
|
2 |
+
todo.txt
|
3 |
+
CSVs/*
|
.streamlit/config.toml
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
[theme]
|
2 |
+
base="light"
|
app.py
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import threading
|
3 |
+
from functions import *
|
4 |
+
from streamlit.runtime.scriptrunner.script_run_context import add_script_run_ctx
|
5 |
+
from streamlit.components.v1 import declare_component
|
6 |
+
|
7 |
+
st.set_page_config(layout="wide")
|
8 |
+
|
9 |
+
car_map = declare_component("car_map", path="./car_map")
|
10 |
+
|
11 |
+
if "reset" not in st.session_state:
|
12 |
+
st.session_state["reset"] = False
|
13 |
+
|
14 |
+
with st.sidebar:
|
15 |
+
name = st.text_input("What's your name?", value=st.session_state.get("name", ""))
|
16 |
+
name = name.strip().lower()
|
17 |
+
name_error_placeholder = st.empty()
|
18 |
+
|
19 |
+
if st.button("Start annotation"):
|
20 |
+
if name:
|
21 |
+
st.session_state["name"] = name
|
22 |
+
st.session_state["img"], st.session_state["img_name"] = get_random_image()
|
23 |
+
st.rerun()
|
24 |
+
else:
|
25 |
+
name_error_placeholder.error("Name is required")
|
26 |
+
|
27 |
+
with st.expander("Annotation statistics", expanded=True):
|
28 |
+
st.write(f"**Total images**: {len(df)}")
|
29 |
+
st.write(f"**Total annotated images**: {len(df[df['validated'] == True])}")
|
30 |
+
st.write(f"**Images left to annotate**: {len(df[df['validated'] == False])}")
|
31 |
+
if "name" in st.session_state:
|
32 |
+
st.write(f"**Images annotated by you**: {len(df[(df['annotator_name'] == st.session_state['name']) & (df['validated'] == True)])}")
|
33 |
+
|
34 |
+
with st.expander("Annotation instructions", expanded=False):
|
35 |
+
st.markdown("- Rotate the image of the car if necessary using the rotation buttons.")
|
36 |
+
st.markdown("- Switch between viewing the car map from the back and the front for a better view.")
|
37 |
+
st.markdown("- Check the image for damages and mark the damaged parts on the car map on the right.")
|
38 |
+
st.markdown("- Right click on the car map to increase the severity level of the damage, and left click to decrease it.")
|
39 |
+
st.markdown("- Severity **level 1** is for scratches, **level 2** is for dents, and **level 3** is for cracks, breaks and severe damages.")
|
40 |
+
st.markdown("**⏭ Skip this image**: If you are not sure about the damages, the image will be shown to another annotator.")
|
41 |
+
st.markdown("**❌ Not car**: If the image is taken inside of the car or does not contain a car at all, or the image is not clear (blurry, zoomed-in, bad lighting, etc.)")
|
42 |
+
st.markdown("**✅ Validate**: save the annotations and move to the next image.")
|
43 |
+
st.markdown("🔙: Go back to your previous annotation.")
|
44 |
+
|
45 |
+
# st.download_button("Download annotations", data=df.to_csv(index=False), file_name="annotations.csv", mime="text/csv")
|
46 |
+
|
47 |
+
if "name" in st.session_state:
|
48 |
+
if st.session_state["img"]:
|
49 |
+
col1, col2 = st.columns([10, 8])
|
50 |
+
|
51 |
+
rotation_btns = col1.columns([1, 1])
|
52 |
+
left_btn = rotation_btns[0].button("↶ 90°", help="Rotate image 90° left", use_container_width=True)
|
53 |
+
right_btn = rotation_btns[1].button("↷ 90°", help="Rotate image 90° right", use_container_width=True)
|
54 |
+
|
55 |
+
if "rotation" not in st.session_state:
|
56 |
+
st.session_state["rotation"] = 0
|
57 |
+
if left_btn:
|
58 |
+
st.session_state["img"] = st.session_state["img"].rotate(90, expand=True).resize((1000, 800))
|
59 |
+
st.session_state["rotation"] = (st.session_state["rotation"] + 90) % 360
|
60 |
+
if right_btn:
|
61 |
+
st.session_state["img"] = st.session_state["img"].rotate(-90, expand=True).resize((1000, 800))
|
62 |
+
st.session_state["rotation"] = (st.session_state["rotation"] - 90) % 360
|
63 |
+
|
64 |
+
reset_annotation_btn = col2.button("🧹 Reset annotation", help="Remove all the annotation and start over", use_container_width=True)
|
65 |
+
if reset_annotation_btn:
|
66 |
+
st.session_state["reset"] = True
|
67 |
+
car_map_view = col2.radio("Car map view", ["Back", "Front"], index=0, horizontal=True, label_visibility="collapsed")
|
68 |
+
|
69 |
+
img = st.session_state.get("img")
|
70 |
+
img_name = st.session_state.get("img_name")
|
71 |
+
col1_c1, col1_c2 = col1.columns([1, 10])
|
72 |
+
prev_img_name = st.session_state["prev_img_name"] if "prev_img_name" in st.session_state else None
|
73 |
+
previous_btn = col1_c1.button("🔙", use_container_width=True, help="Go back to the previous image", disabled=(not prev_img_name))
|
74 |
+
if previous_btn:
|
75 |
+
st.session_state["img"] = st.session_state["prev_img"]
|
76 |
+
st.session_state["img_name"] = st.session_state["prev_img_name"]
|
77 |
+
st.session_state["rotation"] = st.session_state["prev_rotation"]
|
78 |
+
st.session_state["prev_img_name"] = None
|
79 |
+
st.session_state["prev_rotation"] = None
|
80 |
+
st.rerun()
|
81 |
+
col1_c2.button(img_name, use_container_width=True, disabled=True)
|
82 |
+
# col1_c2.write(img_name)
|
83 |
+
col1.image(img, use_column_width=True)
|
84 |
+
|
85 |
+
with col2:
|
86 |
+
img_damages = get_img_damages(img_name)
|
87 |
+
annotated_damages = car_map(damages=img_damages, img_name=img_name, view=car_map_view, reset=st.session_state["reset"])
|
88 |
+
bnt_cols = st.columns(3)
|
89 |
+
skip_btn = bnt_cols[0].button("⏭ Skip this image", use_container_width=True, help="Skip this image if you are not sure")
|
90 |
+
not_car_btn = bnt_cols[1].button("❌ Not car", use_container_width=True, help="Click this if the image does not contain a car")
|
91 |
+
next_btn = bnt_cols[2].button("✅ Validate", use_container_width=True, type="primary", help="Validate annotations and move to the next one")
|
92 |
+
|
93 |
+
with open('custom_style.css') as f:
|
94 |
+
st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)
|
95 |
+
|
96 |
+
if not_car_btn or next_btn or skip_btn:
|
97 |
+
annot_name = st.session_state["name"]
|
98 |
+
rotation = st.session_state["rotation"]
|
99 |
+
process_thread = threading.Thread(target=process_image, args=(img_name, annot_name, not not_car_btn, skip_btn, rotation, annotated_damages), daemon=True)
|
100 |
+
add_script_run_ctx(process_thread)
|
101 |
+
process_thread.start()
|
102 |
+
st.session_state["prev_img_name"] = img_name
|
103 |
+
st.session_state["prev_img"] = img
|
104 |
+
st.session_state["prev_rotation"] = st.session_state["rotation"]
|
105 |
+
st.session_state["img"], st.session_state["img_name"] = get_random_image()
|
106 |
+
st.session_state["rotation"] = 0
|
107 |
+
st.session_state["reset"] = False
|
108 |
+
st.rerun()
|
109 |
+
|
110 |
+
else:
|
111 |
+
st.write("No images left to annotate. Thank you for your contribution!")
|
car_map/index.html
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<html>
|
2 |
+
<head>
|
3 |
+
<link href='https://fonts.googleapis.com/css?family=Source Sans Pro' rel='stylesheet'>
|
4 |
+
<link rel="stylesheet" type="text/css" href="styles.css">
|
5 |
+
</head>
|
6 |
+
<body>
|
7 |
+
<!-- Set up your HTML here -->
|
8 |
+
<div id="details-box"></div>
|
9 |
+
<div id="view-assist">
|
10 |
+
<p id="front">Front</p>
|
11 |
+
<img id="arrow" src="https://www.svgrepo.com/show/342408/arrow-top.svg">
|
12 |
+
<p id="back">Back</p>
|
13 |
+
</div>
|
14 |
+
<svg viewBox="0 -180 1600 2250" xmlns="http://www.w3.org/2000/svg" id="car-map">
|
15 |
+
<g id="layer2" transform="matrix(0, 1, -1, 0, 254.000085527972, -254.000194186645)" style="transform-origin: 555.665px 834.02px;">
|
16 |
+
<g id="g4113" transform="translate(-13.78 3.524)">
|
17 |
+
<path id="path3070" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" d="M 357.43 1085.43 C 393.14 1062.57 494.57 1006.86 494.57 1006.86 C 536 991.14 634.57 995.43 686 994 C 737.43 992.57 777.248 996.088 818.148 1012.998 C 859.148 1029.928 936.567 1087.874 953.267 1093.074 C 976.067 1100.214 1080.3 1101.14 1118.9 1114 C 1157.4 1126.86 1144.6 1146.86 1144.6 1146.86 L 1114.6 1142.57 L 1108.8 1195.49 C 1108.8 1195.49 1146 1196.86 1150.3 1211.14 C 1154.6 1225.43 1156 1242.57 1147.4 1252.57 C 1138.9 1262.6 1133.1 1251.14 1128.9 1265.4 C 1124.6 1279.7 1126 1294 1101.7 1292.6 C 1077.4 1291.1 1003.1 1292.6 1003.1 1292.6 C 1003.1 1292.6 987.4 1184 904.6 1186.86 C 821.7 1189.71 808.9 1292.6 808.9 1292.6 L 361.599 1292.6" data-name="Aile arriere gauche"/>
|
18 |
+
<path id="path3070" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" d="M 361.599 1292.6 L 308.86 1292.6 C 308.86 1292.6 303.14 1188.29 211.71 1186.86 C 120.29 1185.43 113.14 1292.6 113.14 1292.6 L 47.43 1292.6 L 28.86 1254 C 28.34 1254 2.61 1254 7.43 1236.86 C 12.08 1220.3 3.14 1195.43 24.57 1195.43 C 46 1195.43 71.71 1196.86 71.71 1196.86 L 87.43 1152.57 L 46 1149.71 C 46 1149.71 80.29 1125.43 164.57 1116.86 C 248.86 1108.29 321.71 1108.29 357.43 1085.43 L 361.599 1292.6 Z" data-name="Aile avant gauche">
|
19 |
+
<title>Path</title>
|
20 |
+
</path>
|
21 |
+
<path id="path3900" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" d="M 886.971 1038.45 L 878.308 983.581 L 873.976 934.969 L 877.826 867.105 L 881.196 845.446 L 886.971 740.039 L 834.99 740.521 L 775.789 746.778 L 752.205 751.109 L 735.841 759.292 L 670.383 797.315 L 599.64 837.66 C 585.56 868.63 581.34 909.45 581.34 931.97 C 581.34 954.49 585.56 1024.944 603.86 1038.944 L 886.971 1038.45 Z" transform="translate(-254 254)" data-name="Porte avant gauche"/>
|
22 |
+
<path id="path3900-6" d="M 680.781 993.783 L 732.762 994.746 L 779.2 1000.17 C 797.5 1012.84 835.5 1050.84 872.1 1104.33 C 908.7 1157.82 880.5 1163.45 853.8 1178.93 C 827 1194.42 804.5 1221.641 786.2 1291.981 L 632.78 1292.419 C 627.15 1267.119 620.12 1209.9 620.12 1187.4 C 621.29 1151.3 622.6 1118.8 626.27 1100.1 C 628.74 1075 631.38 1000.204 632.78 994.574 L 680.781 993.783 Z" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" data-name="Porte arriere gauche"/>
|
23 |
+
<path id="path3884" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" d="m618.57 847.14c15.72-18.57 123.38-81.52 151.43-88.57 29.5-7.41 103-7.14 103-7.14l-7.14 95.71z" transform="translate(-254 254)"/>
|
24 |
+
<path id="path3892" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" d="m898.7 752.84-4.29 94.32h207.19c-11.3-20.75-46.6-74.9-72.9-88.57-14.3-10-82.86-4.33-130-5.75z" transform="translate(-254 254)"/>
|
25 |
+
</g>
|
26 |
+
<path id="path4381-0" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" d="M 1267.042 624.97 C 1267.042 624.97 1336.7 623.56 1336.7 646.09 L 1336.7 1017.7 C 1336.7 1040.2 1264.271 1043 1264.271 1043" data-name="Pare-choc arriere"/>
|
27 |
+
<g id="g4451" transform="translate(-13.78 15.524)">
|
28 |
+
<path id="path4175" style="stroke:#000000;stroke-width:5;fill:none" d="m736.17 416.79s94.31 5.63 152.02 5.63c106.98 0 201.31-5.63 201.31-5.63m-1.4 297s-77.4-5.63-199.91-5.63c-68.97 0-152.02 7.04-152.02 7.04" transform="translate(-254 254)"/>
|
29 |
+
<path id="path4135-9" d="m345.78 632.78h-294.19l-14.076 102.76-7.038 11.26v142.17l7.038 14.07 15.483 101.36h288.55" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" data-name="Capot"/>
|
30 |
+
<path id="path-1" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" d="M 1282.678 961.782 L 1282.677 677.454 L 1214.68 677.456 L 1214.68 961.786 L 1282.678 961.782 Z M 832.7 670.79 C 832.7 670.79 820 718.65 820 743.98 L 820 890.37 C 820 915.71 832.7 967.79 832.7 967.79 L 1038.2 1005.8 L 1146.6 1005.8 C 1146.6 1005.8 1160.6 924.16 1160.6 890.37 L 1160.6 742.58 C 1160.6 704.57 1146.6 632.78 1146.6 632.78 L 1038.2 632.78 L 832.7 670.79 Z" data-name="Malle"/>
|
31 |
+
<path id="path4165" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" d="M 595.41 378.78 L 736.17 416.79 C 736.17 416.79 727.73 466.01 727.73 488.75 L 727.73 636.37 C 727.73 667.34 736.17 715.2 736.17 715.2 L 595.41 748.98 C 595.41 748.98 578.17 687.08 578.17 656.08 C 578.55 622.28 580.28 470.28 580.28 470.28 C 578.87 439.31 595.41 378.78 595.41 378.78 L 595.41 378.78 Z" transform="translate(-254 254)" data-name="Pare-brise avant"/>
|
32 |
+
<path id="path4205" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" d="m1097.9 435.09s-8.4 40.82-8.4 56.3v144.98c0 19.71 7 57.72 7 57.72s94.3 26.74 123.9 26.74h42.2v-312.49h-47.8c-25.4 0-116.9 26.75-116.9 26.75z" transform="translate(-254 254)" data-name="Pare-brise arriere"/>
|
33 |
+
</g>
|
34 |
+
<path id="path4245" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" d="M -74.052 1042.25 L -45.91 1042.1 C -17.754 1042.1 -23.38 1019.6 -23.38 1019.6 L -23.38 923.9 C -36.06 923.9 -34.65 914.29 -34.65 907.01 L -34.65 757.81 C -34.2 738.92 -24.79 740.92 -24.79 740.92 L -23.38 649.42 C -23.38 649.42 -16.855 626.389 -47.821 624.989 C -78.791 623.579 -75.919 624.665 -76.336 624.523 C -76.753 624.38 -144.37 622.97 -144.37 645.49 L -144.37 1017.1 C -144.37 1039.6 -74.052 1042.25 -74.052 1042.25 Z" data-name="Pare-choc avant"/>
|
35 |
+
<g id="g4428" transform="translate(-13.78 15.524)">
|
36 |
+
<path id="path3852-6" d="M 373.313 355.9 L 308.86 355.9 C 308.86 355.9 303.14 460.19 211.71 461.61 C 120.29 463.04 113.14 355.9 113.14 355.9 L 47.429 355.9 L 28.857 394.47 C 28.342 394.47 2.614 394.47 7.429 411.61 C 12.08 428.18 3.143 453.04 24.571 453.04 C 46 453.04 71.714 451.61 71.714 451.61 L 87.429 495.9 L 46 498.76 C 46 498.76 80.286 523.04 164.57 531.61 C 248.86 540.19 321.71 540.19 357.43 563.04" style="stroke: rgb(0, 0, 0); stroke-width: 5px; fill: rgb(255, 255, 255);" data-name="Aile avant droit"/>
|
37 |
+
<path id="path3852-6" d="M 373.313 355.9 L 808.86 355.9 C 808.86 355.9 821.71 458.76 904.57 461.61 C 987.43 464.47 1003.1 355.9 1003.1 355.9 C 1003.1 355.9 1077.4 357.33 1101.7 355.9 C 1126 354.47 1124.6 368.76 1128.9 383.04 C 1133.1 397.33 1138.9 385.9 1147.4 395.9 C 1156 405.9 1154.6 423.04 1150.3 437.33 C 1146 451.61 1108.8 452.98 1108.8 452.98 L 1114.6 505.9 L 1144.6 501.61 C 1144.6 501.61 1157.4 521.61 1118.9 534.47 C 1080.3 547.33 975.802 558.017 953.002 565.157 C 936.302 570.357 853.501 620.391 812.511 637.321 C 771.551 654.231 737.43 655.9 686 654.47 C 634.57 653.04 536 657.33 494.57 641.61 C 494.57 641.61 393.14 585.9 357.43 563.04" style="stroke: rgb(0, 0, 0); stroke-width: 5px; fill: rgb(255, 255, 255);" data-name="Aile arriere droit"/>
|
38 |
+
<path id="path3900-4" d="M 345.64 556.81 C 331.56 525.84 327.34 485.02 327.34 462.5 C 327.34 439.98 331.56 371.01 349.86 356.93 L 635.128 355.533 L 626.117 398.01 L 620.325 451.428 L 622.256 506.133 L 627.405 560.195 L 633.197 655.446 L 570.769 654.802 L 508.984 647.723 L 474.23 632.277 L 345.64 556.81 Z" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" data-name="Porte avant droite"/>
|
39 |
+
<path id="path3942-2" d="M 720.688 654.344 L 632.78 655.34 C 631.38 649.71 628.74 573.44 626.27 548.41 C 622.6 529.69 621.29 497.19 620.12 461.09 C 620.12 438.57 627.15 380.86 632.78 355.52 L 786.21 356.23 C 804.51 426.61 827.03 454.06 853.78 469.54 C 880.52 485.02 908.67 490.65 872.08 544.14 C 835.48 597.63 797.47 635.64 779.17 648.3 L 720.688 654.344 Z" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" data-name="Porte arriere droite"/>
|
40 |
+
<path id="path3892-5" d="m644.7 641.63-4.29-94.32h207.17c-11.31 20.75-46.62 74.9-72.86 88.57-14.29 10-82.88 4.33-130.02 5.75z" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);"/>
|
41 |
+
<path id="path3884-4" d="m364.57 547.33c15.72 18.57 123.38 81.52 151.43 88.57 29.5 7.41 103 7.14 103 7.14l-7.14-95.71z" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);"/>
|
42 |
+
</g>
|
43 |
+
<path id="path4361" style="stroke: rgb(0, 0, 0); stroke-width: 5px; paint-order: stroke; fill: rgb(255, 255, 255);" d="M -71.88 985.879 L -71.88 948.339 L -49.35 948.339 L -26.83 948.339 L -26.83 985.879 L -26.83 1023.376 L -49.35 1023.376 L -71.88 1023.376 L -71.88 985.879 Z" data-name="Feu avant gauche"/>
|
44 |
+
<path id="path4363" style="stroke-width: 5px; stroke: rgb(0, 0, 0); paint-order: stroke; fill: rgb(255, 255, 255);" d="M -71.88 681.701 L -71.88 642.757 L -49.35 642.757 L -26.83 642.757 L -26.83 681.701 L -26.83 720.644 L -49.35 720.644 L -71.88 720.644 L -71.88 681.701 Z" data-name="Feu avant droit"/>
|
45 |
+
<path id="path4610" style="stroke:#000000;stroke-width:2;fill:#c8c8c8" d="m195.66 202.84a76.01 76.01 0 1 1 -152.02 0 76.01 76.01 0 1 1 152.02 0z" transform="translate(78.489 1074.7)"/>
|
46 |
+
<path id="path4612" style="stroke:#000000;stroke-width:2;fill:#ffffff" d="m147.8 72.633a44.339 44.339 0 1 1 -88.681 0 44.339 44.339 0 1 1 88.681 0z" transform="translate(94.676 1204.9)"/>
|
47 |
+
<path id="path4610-9" style="stroke: rgb(0, 0, 0); stroke-width: 2; fill: rgb(200, 200, 200);" d="m195.66 202.84a76.01 76.01 0 1 1 -152.02 0 76.01 76.01 0 1 1 152.02 0z" transform="translate(78.489 187.66)"/>
|
48 |
+
<path id="path4612-4" style="stroke:#000000;stroke-width:2;fill:#ffffff" d="m147.8 72.633a44.339 44.339 0 1 1 -88.681 0 44.339 44.339 0 1 1 88.681 0z" transform="translate(94.676 317.86)"/>
|
49 |
+
<path id="path4610-5" style="stroke:#000000;stroke-width:2;fill:#c8c8c8" d="m195.66 202.84a76.01 76.01 0 1 1 -152.02 0 76.01 76.01 0 1 1 152.02 0z" transform="translate(773.03 187.66)"/>
|
50 |
+
<path id="path4612-1" style="stroke:#000000;stroke-width:2;fill:#ffffff" d="m147.8 72.633a44.339 44.339 0 1 1 -88.681 0 44.339 44.339 0 1 1 88.681 0z" transform="translate(789.21 317.86)"/>
|
51 |
+
<path id="path4610-4" style="stroke:#000000;stroke-width:2;fill:#c8c8c8" d="m195.66 202.84a76.01 76.01 0 1 1 -152.02 0 76.01 76.01 0 1 1 152.02 0z" transform="translate(773.03 1074.7)"/>
|
52 |
+
<path id="path4612-3" style="stroke:#000000;stroke-width:2;fill:#ffffff" d="m147.8 72.633a44.339 44.339 0 1 1 -88.681 0 44.339 44.339 0 1 1 88.681 0z" transform="translate(789.21 1204.9)"/>
|
53 |
+
<path id="path4499" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" d="M 1199.9 633.86 L 1199.9 707.05 L 1267.5 707.05 L 1267.5 624 L 1242.2 609.93 L 1199.9 633.86 Z" data-name="Feu arriere droit"/>
|
54 |
+
<path id="path4501" style="stroke: rgb(0, 0, 0); stroke-width: 5; fill: rgb(255, 255, 255);" d="M 1199.9 963.24 L 1267.5 963.24 L 1267.5 1042.1 L 1243.6 1056.1 L 1199.9 1037.8 L 1199.9 963.24 Z" data-name="Feu arriere gauche"/>
|
55 |
+
<path id="path-2" style="stroke-width: 5px; stroke: rgb(0, 0, 0); paint-order: stroke; fill: rgb(255, 255, 255);" d="M -126.593 832.725 L -126.593 735.712 L -113.949 735.712 L -101.311 735.712 L -101.311 832.725 L -101.311 929.735 L -113.949 929.735 L -126.593 929.735 L -126.593 832.725 Z" data-name="Plaque immatriculation avant"></path>
|
56 |
+
<path id="path-3" style="stroke-width: 5px; stroke: rgb(0, 0, 0); paint-order: stroke; fill: rgb(255, 255, 255);" d="M 1292.264 833.899 L 1292.264 736.886 L 1304.908 736.886 L 1317.546 736.886 L 1317.546 833.899 L 1317.546 930.909 L 1304.908 930.909 L 1292.264 930.909 L 1292.264 833.899 Z" data-name="Plaque immatriculation arriere"></path>
|
57 |
+
</g>
|
58 |
+
</svg>
|
59 |
+
|
60 |
+
<script src="script.js"></script>
|
61 |
+
</body>
|
62 |
+
</html>
|
car_map/script.js
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Just copy & paste these functions as-is:
|
2 |
+
function sendMessageToStreamlitClient(type, data) {
|
3 |
+
var outData = Object.assign({
|
4 |
+
isStreamlitMessage: true,
|
5 |
+
type: type,
|
6 |
+
}, data);
|
7 |
+
window.parent.postMessage(outData, "*");
|
8 |
+
}
|
9 |
+
function init() {
|
10 |
+
sendMessageToStreamlitClient("streamlit:componentReady", {apiVersion: 1});
|
11 |
+
}
|
12 |
+
function setFrameHeight(height) {
|
13 |
+
sendMessageToStreamlitClient("streamlit:setFrameHeight", {height: height});
|
14 |
+
}
|
15 |
+
// The `data` argument can be any JSON-serializable value.
|
16 |
+
function sendDataToPython(data) {
|
17 |
+
// console.log("Sending data: ", data.value);
|
18 |
+
sendMessageToStreamlitClient("streamlit:setComponentValue", data);
|
19 |
+
}
|
20 |
+
|
21 |
+
// Now modify this part of the code to fit your needs:
|
22 |
+
let damaged_parts = {};
|
23 |
+
let severity_colors = {0: "#FFFFFF", 1: "#FFFF00", 2: "#FFA500", 3: "#FF0000"};
|
24 |
+
|
25 |
+
var paths = document.querySelectorAll("[data-name]");
|
26 |
+
paths.forEach(function (path) {
|
27 |
+
path.setAttribute('data-severity', 0);
|
28 |
+
});
|
29 |
+
|
30 |
+
function getDamagedParts() {
|
31 |
+
var damaged_parts = {};
|
32 |
+
paths.forEach(function (path) {
|
33 |
+
var severity = parseInt(path.getAttribute('data-severity'));
|
34 |
+
var part_name = path.dataset.name;
|
35 |
+
damaged_parts[part_name] = severity;
|
36 |
+
});
|
37 |
+
return damaged_parts;
|
38 |
+
}
|
39 |
+
|
40 |
+
paths.forEach(function (path) {
|
41 |
+
path.addEventListener("click", function () {
|
42 |
+
let severity = parseInt(path.getAttribute('data-severity'));
|
43 |
+
severity = (severity + 1) % 4;
|
44 |
+
path.setAttribute('data-severity', severity);
|
45 |
+
path.style.fill = severity_colors[severity];
|
46 |
+
// damaged_parts[path.dataset.name] = severity; // Update the damaged parts state
|
47 |
+
document.getElementById("details-box").innerHTML = `${path.dataset.name} - ${severity}`;
|
48 |
+
sendDataToPython({
|
49 |
+
value: getDamagedParts(),
|
50 |
+
dataType: "json",
|
51 |
+
});
|
52 |
+
});
|
53 |
+
});
|
54 |
+
|
55 |
+
paths.forEach(function (path) {
|
56 |
+
path.addEventListener("contextmenu", function (e) {
|
57 |
+
e.preventDefault();
|
58 |
+
let severity = (parseInt(path.getAttribute('data-severity')) + 3) % 4;
|
59 |
+
path.setAttribute('data-severity', severity);
|
60 |
+
path.style.fill = severity_colors[severity];
|
61 |
+
// damaged_parts[path.dataset.name] = severity; // Update the damaged parts state
|
62 |
+
document.getElementById("details-box").innerHTML = `${path.dataset.name} - ${severity}`;
|
63 |
+
sendDataToPython({
|
64 |
+
value: getDamagedParts(),
|
65 |
+
dataType: "json",
|
66 |
+
});
|
67 |
+
});
|
68 |
+
});
|
69 |
+
|
70 |
+
var tooltipSpan = document.getElementById('details-box');
|
71 |
+
document.addEventListener('mouseover', function (e) {
|
72 |
+
if (e.target.tagName == 'path') {
|
73 |
+
var severity = parseInt(e.target.getAttribute('data-severity'));
|
74 |
+
var part_name = e.target.dataset.name;
|
75 |
+
if (part_name == undefined) {
|
76 |
+
document.getElementById("details-box").style.opacity = "0%";
|
77 |
+
}
|
78 |
+
else {
|
79 |
+
document.getElementById("details-box").innerHTML = part_name + " - " + severity;
|
80 |
+
document.getElementById("details-box").style.opacity = "100%";
|
81 |
+
document.getElementById("details-box").style.display = "block";
|
82 |
+
}
|
83 |
+
}
|
84 |
+
else {
|
85 |
+
document.getElementById("details-box").style.opacity = "0%";
|
86 |
+
}
|
87 |
+
});
|
88 |
+
|
89 |
+
window.onmousemove = function (e) {
|
90 |
+
var x = e.clientX,
|
91 |
+
y = e.clientY;
|
92 |
+
tooltipSpan.style.top = (y + 20) + 'px';
|
93 |
+
tooltipSpan.style.left = (x) + 'px';
|
94 |
+
};
|
95 |
+
|
96 |
+
// data is any JSON-serializable value you sent from Python, and it's already deserialized for you.
|
97 |
+
function onDataFromPython(event) {
|
98 |
+
if (event.data.type !== "streamlit:render") return;
|
99 |
+
damaged_parts = event.data.args.damages; // Access values sent from Python here!
|
100 |
+
img_name = event.data.args.img_name;
|
101 |
+
reset = event.data.args.reset;
|
102 |
+
view = event.data.args.view;
|
103 |
+
if (view == "Front") {
|
104 |
+
document.getElementById("car-map").style.transform = `rotate(180deg)`;
|
105 |
+
document.getElementById("front").style.top = `90%`;
|
106 |
+
document.getElementById("back").style.bottom = `90%`;
|
107 |
+
document.getElementById("arrow").style.rotate = `180deg`;
|
108 |
+
}
|
109 |
+
|
110 |
+
// Reset annotations if requested
|
111 |
+
if (reset) {
|
112 |
+
for (var key in damaged_parts) {
|
113 |
+
damaged_parts[key] = 0; // Set each key's value to 0
|
114 |
+
}
|
115 |
+
paths.forEach(function (path) {
|
116 |
+
path.setAttribute('data-severity', 0);
|
117 |
+
path.style.fill = severity_colors[0]; // Reset to default color
|
118 |
+
});
|
119 |
+
}
|
120 |
+
|
121 |
+
for (var key in damaged_parts) {
|
122 |
+
try {
|
123 |
+
var path = document.querySelector("[data-name='" + unescape(encodeURIComponent(key)) + "']");
|
124 |
+
path.style.fill = severity_colors[damaged_parts[key]];
|
125 |
+
path.setAttribute('data-severity', damaged_parts[key]);
|
126 |
+
}
|
127 |
+
catch (error) {
|
128 |
+
console.log("Error: " + key);
|
129 |
+
}
|
130 |
+
}
|
131 |
+
sendDataToPython({
|
132 |
+
value: damaged_parts,
|
133 |
+
dataType: "json",
|
134 |
+
});
|
135 |
+
}
|
136 |
+
|
137 |
+
// Hook things up!
|
138 |
+
window.addEventListener("message", onDataFromPython);
|
139 |
+
init();
|
140 |
+
|
141 |
+
// Hack to autoset the iframe height.
|
142 |
+
window.addEventListener("load", function() {
|
143 |
+
window.setTimeout(function() {
|
144 |
+
setFrameHeight(document.documentElement.clientHeight)
|
145 |
+
}, 0);
|
146 |
+
});
|
147 |
+
|
148 |
+
// Optionally, if the automatic height computation fails you, give this component a height manually
|
149 |
+
setFrameHeight(440);
|
car_map/styles.css
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');
|
2 |
+
|
3 |
+
path {
|
4 |
+
stroke: white;
|
5 |
+
fill:white;
|
6 |
+
transition: fill .4s ease;
|
7 |
+
cursor: pointer;
|
8 |
+
}
|
9 |
+
|
10 |
+
#car-map {
|
11 |
+
position: absolute;
|
12 |
+
top: 0;
|
13 |
+
left: 0;
|
14 |
+
width: 100%;
|
15 |
+
transform-origin: 50% 35%;
|
16 |
+
}
|
17 |
+
|
18 |
+
#details-box {
|
19 |
+
padding: 0.5rem;
|
20 |
+
border-radius: 8px;
|
21 |
+
font-size: 18px;
|
22 |
+
position: fixed;
|
23 |
+
color: white;
|
24 |
+
font-family: "Source Sans Pro";
|
25 |
+
background-color: black;
|
26 |
+
width: fit-content;
|
27 |
+
transform: translateX(-50%);
|
28 |
+
transition: opacity .4s ease;
|
29 |
+
z-index: 1;
|
30 |
+
display: none;
|
31 |
+
}
|
32 |
+
|
33 |
+
#view-assist {
|
34 |
+
position: absolute;
|
35 |
+
height: 100%;
|
36 |
+
width: 100%;
|
37 |
+
}
|
38 |
+
#view-assist #front {
|
39 |
+
font-family: "Source Sans Pro";
|
40 |
+
position: absolute;
|
41 |
+
top: 20px;
|
42 |
+
right: 25px;
|
43 |
+
margin: 0;
|
44 |
+
}
|
45 |
+
#view-assist #back {
|
46 |
+
font-family: "Source Sans Pro";
|
47 |
+
position: absolute;
|
48 |
+
bottom: 20px;
|
49 |
+
right: 25px;
|
50 |
+
margin: 8px 0;
|
51 |
+
}
|
52 |
+
#view-assist > #arrow {
|
53 |
+
position: absolute;
|
54 |
+
right: 18px;
|
55 |
+
top: 50%;
|
56 |
+
transform: translateY(-50%);
|
57 |
+
width: 50px;
|
58 |
+
height: 50%;
|
59 |
+
object-fit: cover;
|
60 |
+
transform-origin: 50% 0%;
|
61 |
+
}
|
custom_style.css
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.st-emotion-cache-1jicfl2 {
|
2 |
+
padding-top: 4rem;
|
3 |
+
}
|
4 |
+
|
5 |
+
/* .st-emotion-cache-vdokb0 p {
|
6 |
+
text-align: center;
|
7 |
+
margin-top: 7px;
|
8 |
+
} */
|
9 |
+
|
10 |
+
.st-dc, .st-cx {
|
11 |
+
margin-top: 5px !important;
|
12 |
+
}
|
13 |
+
.st-af {
|
14 |
+
justify-content: center;
|
15 |
+
margin-top: 0px;
|
16 |
+
margin-bottom: 0px;
|
17 |
+
}
|
18 |
+
div.row-widget.stRadio > div[role="radiogroup"] > label[data-baseweb="radio"] {
|
19 |
+
background-color: #f4ca9a;
|
20 |
+
padding-right: 38px;
|
21 |
+
padding-left: 30px;
|
22 |
+
padding-bottom: 6px;
|
23 |
+
padding-top: 4px;
|
24 |
+
margin: 0 6px;
|
25 |
+
}
|
26 |
+
div.row-widget.stRadio > div[role="radiogroup"] > label[data-baseweb="radio"]:hover {
|
27 |
+
background-color: #eb9a68;
|
28 |
+
}
|
29 |
+
.st-cj {
|
30 |
+
background-color: rgb(255, 75, 75);
|
31 |
+
}
|
functions.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import boto3
|
2 |
+
from PIL import Image
|
3 |
+
import pandas as pd
|
4 |
+
import streamlit as st
|
5 |
+
import random
|
6 |
+
import io
|
7 |
+
|
8 |
+
s3_client = boto3.client('s3',
|
9 |
+
aws_access_key_id=st.secrets["aws_access_key_id"],
|
10 |
+
aws_secret_access_key=st.secrets["aws_secret_access_key"],
|
11 |
+
region_name='eu-west-3')
|
12 |
+
bucket_name = "sygma-global-data-storage"
|
13 |
+
folder = "car-damage-detection/scrappedImages/"
|
14 |
+
|
15 |
+
csv_folder = "car-damage-detection/CSVs/"
|
16 |
+
s3_df_path = csv_folder + "70k_old_annotations_fixed.csv"
|
17 |
+
response = s3_client.get_object(Bucket=bucket_name, Key=s3_df_path)
|
18 |
+
# df = pd.read_csv("CSVs/70k_old_annotations_fixed.csv", low_memory=False)
|
19 |
+
with io.BytesIO(response['Body'].read()) as bio:
|
20 |
+
df = pd.read_csv(bio, low_memory=False)
|
21 |
+
df = df[df['s3_available'] == True]
|
22 |
+
|
23 |
+
def get_random_image():
|
24 |
+
not_validated_imgs = df[df["validated"] == False]["img_name"].tolist()
|
25 |
+
if len(not_validated_imgs) == 0:
|
26 |
+
return None, None
|
27 |
+
image_name = random.choice(not_validated_imgs)
|
28 |
+
s3_image_path = folder + image_name
|
29 |
+
try:
|
30 |
+
response = s3_client.get_object(Bucket=bucket_name, Key=s3_image_path)
|
31 |
+
image = Image.open(io.BytesIO(response['Body'].read())).resize((1000, 800))
|
32 |
+
return image, image_name
|
33 |
+
except:
|
34 |
+
return get_random_image()
|
35 |
+
|
36 |
+
def get_img_damages(img_name):
|
37 |
+
img_row = df.loc[df["img_name"] == img_name]
|
38 |
+
damages = img_row.iloc[0, 6:].to_dict()
|
39 |
+
return damages
|
40 |
+
|
41 |
+
def process_image(img_name, annotator_name, is_car, skip, rotation, damaged_parts):
|
42 |
+
df.loc[df["img_name"] == img_name, "annotator_name"] = annotator_name
|
43 |
+
df.loc[df["img_name"] == img_name, "is_car"] = is_car
|
44 |
+
df.loc[df["img_name"] == img_name, "rotation"] = rotation
|
45 |
+
if not skip:
|
46 |
+
df.loc[df["img_name"] == img_name, damaged_parts.keys()] = damaged_parts.values()
|
47 |
+
df.loc[df["img_name"] == img_name, "validated"] = not skip
|
48 |
+
# df.to_csv("CSVs/70k_old_annotations_fixed.csv", index=False)
|
49 |
+
s3_client.put_object(Bucket=bucket_name, Key=s3_df_path, Body=df.to_csv(index=False))
|
requirements.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
boto3
|