import streamlit as st import threading from functions import * from streamlit.runtime.scriptrunner.script_run_context import add_script_run_ctx from streamlit.components.v1 import declare_component st.set_page_config(layout="wide") car_map = declare_component("car_map", path="./car_map") if "reset" not in st.session_state: st.session_state["reset"] = False if "parts_filter" not in st.session_state: st.session_state["parts_filter"] = [] with st.sidebar: name = st.text_input("Name *", value=st.session_state.get("name", "")) name = name.strip().lower() name_error_placeholder = st.empty() st.session_state["parts_filter"] = st.multiselect("Filter images by damaged parts (optional)", get_car_parts_count(), help="Select one or more parts to filter the images by.", default=st.session_state["parts_filter"]) if st.button("Start annotation"): if name: st.session_state["name"] = name st.session_state["img"], st.session_state["img_name"] = get_random_image(st.session_state["parts_filter"]) st.rerun() else: name_error_placeholder.error("Name is required") with st.expander("Annotation statistics", expanded=True): st.write(f"**Total images**: {len(df)}") st.write(f"**Total annotated images**: {len(df[df['validated'] == True])}") st.write(f"**Images left to annotate**: {len(df[df['validated'] == False])}") if "name" in st.session_state: st.write(f"**Images annotated by you**: {len(df[(df['annotator_name'] == st.session_state['name']) & (df['validated'] == True)])}") with st.expander("Annotation instructions", expanded=False): st.markdown("- Rotate the image of the car if necessary using the rotation buttons.") st.markdown("- Switch between viewing the car map from the back and the front for a better view.") st.markdown("- Check the image for damages and mark the damaged parts on the car map on the right.") st.markdown("- Right click on the car map to increase the severity level of the damage, and left click to decrease it.") st.markdown("- Severity **level 1** is for scratches, **level 2** is for dents, and **level 3** is for cracks, breaks and severe damages.") st.markdown("**⏭ Skip this image**: If you are not sure about the damages, the image will be shown to another annotator.") 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.)") st.markdown("**✅ Validate**: save the annotations and move to the next image.") st.markdown("🔙: Go back to your previous annotation.") # st.download_button("Download annotations", data=df.to_csv(index=False), file_name="annotations.csv", mime="text/csv") if "name" in st.session_state: if st.session_state["img"]: col1, col2 = st.columns([10, 8]) rotation_btns = col1.columns([1, 1]) left_btn = rotation_btns[0].button("↶ 90°", help="Rotate image 90° left", use_container_width=True) right_btn = rotation_btns[1].button("↷ 90°", help="Rotate image 90° right", use_container_width=True) if "rotation" not in st.session_state: st.session_state["rotation"] = 0 if left_btn: st.session_state["img"] = st.session_state["img"].rotate(90, expand=True).resize((1000, 800)) st.session_state["rotation"] = (st.session_state["rotation"] + 90) % 360 if right_btn: st.session_state["img"] = st.session_state["img"].rotate(-90, expand=True).resize((1000, 800)) st.session_state["rotation"] = (st.session_state["rotation"] - 90) % 360 reset_annotation_btn = col2.button("🧹 Reset annotation", help="Remove all the annotation and start over", use_container_width=True) if reset_annotation_btn: st.session_state["reset"] = True car_map_view = col2.radio("Car map view", ["Back", "Front"], index=0, horizontal=True, label_visibility="collapsed") img = st.session_state.get("img") img_name = st.session_state.get("img_name") col1_c1, col1_c2 = col1.columns([1, 10]) prev_img_name = st.session_state["prev_img_name"] if "prev_img_name" in st.session_state else None previous_btn = col1_c1.button("🔙", use_container_width=True, help="Go back to the previous image", disabled=(not prev_img_name)) if previous_btn: st.session_state["img"] = st.session_state["prev_img"] st.session_state["img_name"] = st.session_state["prev_img_name"] st.session_state["rotation"] = st.session_state["prev_rotation"] st.session_state["prev_img_name"] = None st.session_state["prev_rotation"] = None st.rerun() col1_c2.button(img_name, use_container_width=True, disabled=True) # col1_c2.write(img_name) col1.image(img, use_column_width=True) with col2: img_damages = get_img_damages(img_name) annotated_damages = car_map(damages=img_damages, img_name=img_name, view=car_map_view, reset=st.session_state["reset"]) bnt_cols = st.columns(3) skip_btn = bnt_cols[0].button("⏭ Skip this image", use_container_width=True, help="Skip this image if you are not sure") not_car_btn = bnt_cols[1].button("❌ Not car", use_container_width=True, help="Click this if the image does not contain a car") next_btn = bnt_cols[2].button("✅ Validate", use_container_width=True, type="primary", help="Validate annotations and move to the next one") with open('custom_style.css') as f: st.markdown(f'', unsafe_allow_html=True) if not_car_btn or next_btn or skip_btn: annot_name = st.session_state["name"] rotation = st.session_state["rotation"] process_thread = threading.Thread(target=process_image, args=(img_name, annot_name, not not_car_btn, skip_btn, rotation, annotated_damages), daemon=True) add_script_run_ctx(process_thread) process_thread.start() st.session_state["prev_img_name"] = img_name st.session_state["prev_img"] = img st.session_state["prev_rotation"] = st.session_state["rotation"] st.session_state["img"], st.session_state["img_name"] = get_random_image(st.session_state["parts_filter"]) st.session_state["rotation"] = 0 st.session_state["reset"] = False st.rerun() else: st.write("No images left to annotate. Thank you for your contribution!")