diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..c9f51f6
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,2 @@
+VISION_API_USERNAME=
+VISION_API_PASSWORD=
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 0aae18e..76c22be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
# Data files ignore rules are on data/.gitignore
+**/**/test.py
+test.py
# Byte-compiled / optimized / DLL files
__pycache__/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..a2f4ca5
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,26 @@
+repos:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: check-added-large-files # prevents adding large files
+ - id: detect-private-key # detects private keys
+ - id: fix-byte-order-marker # fixes BOM
+ - id: fix-encoding-pragma # fixes encoding pragma
+ - id: no-commit-to-branch # prevents committing to protected branches
+ - id: trailing-whitespace # prevents trailing whitespace
+
+- repo: https://github.com/psf/black
+ rev: 22.12.0
+ hooks:
+ - id: black
+ language_version: python3.10
+
+- repo: https://github.com/PyCQA/isort
+ rev: 5.12.0
+ hooks:
+ - id: isort
+
+- repo: https://github.com/PyCQA/flake8
+ rev: 6.0.0
+ hooks:
+ - id: flake8
\ No newline at end of file
diff --git a/README.md b/README.md
index 5803abf..044020c 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,10 @@
# Detecção de alagamentos
+
+## Develop
+
+
+`poetry install --no-root`
+
+Add .env vars missing from .env.example.
+
+`export POETRY_DOTENV_LOCATION=.env && streamlit run app/📣\ Home.py`
\ No newline at end of file
diff --git a/app/Home.py b/app/Home.py
index b3ad9cf..1aa91c9 100644
--- a/app/Home.py
+++ b/app/Home.py
@@ -1,272 +1,153 @@
-from io import StringIO
-from typing import Union
-import folium
-import pandas as pd
+# -*- coding: utf-8 -*-
+# import folium # noqa
-import requests
import streamlit as st
-from streamlit_folium import st_folium
-from st_aggrid import AgGrid, GridOptionsBuilder, ColumnsAutoSizeMode
-
-
-st.set_page_config(layout="wide")
-st.image("./data/logo/logo.png", width=300)
+from streamlit_folium import st_folium # noqa
+from utils.utils import (
+ create_map,
+ display_agrid_table,
+ display_camera_details,
+ get_cameras,
+ get_cameras_cache,
+ get_filted_cameras_objects,
+ get_icon_color,
+ treat_data,
+)
+st.set_page_config(layout="wide", initial_sidebar_state="collapsed")
+# st.image("./data/logo/logo.png", width=300)
-def get_icon_color(label: Union[bool, None]):
- if label is True:
- return "red"
- elif label is False:
- return "green"
- else:
- return "gray"
+DEFAULT_OBJECT = "water_level"
+st.markdown("## Identificações | Vision AI")
-def create_map(chart_data, location=None):
- chart_data = chart_data.fillna("")
- # center map on the mean of the coordinates
+# Function to fetch and update data
+def fetch_and_update_data(bypass_cash=False):
+ page_size = 3000
+ only_active = False
+ use_mock_data = False
+ update_mock_data = False
- if location is not None:
- m = folium.Map(location=location, zoom_start=18)
- elif len(chart_data) > 0:
- m = folium.Map(
- location=[chart_data["latitude"].mean(), chart_data["longitude"].mean()],
- zoom_start=11,
+ if bypass_cash:
+ return get_cameras(
+ page_size=page_size,
+ only_active=only_active,
+ use_mock_data=use_mock_data,
+ update_mock_data=update_mock_data,
)
- else:
- m = folium.Map(location=[-22.917690, -43.413861], zoom_start=11)
-
- for _, row in chart_data.iterrows():
- icon_color = get_icon_color(row["label"])
- folium.Marker(
- location=[row["latitude"], row["longitude"]],
- # Adicionar id_camera ao tooltip
- tooltip=f"ID: {row['id_camera']}",
- # Alterar a cor do ícone de acordo com o status
- icon=folium.features.DivIcon(
- icon_size=(15, 15),
- icon_anchor=(7, 7),
- html=f'
',
- ),
- ).add_to(m)
- return m
-
-
-def label_emoji(label):
- if label is True:
- return "🔴"
- elif label is False:
- return "🟢"
- else:
- return "⚫"
-
-
-@st.cache_data(ttl=60)
-def load_alagamento_detectado_ia():
- raw_api_data = requests.get(
- "https://api.dados.rio/v2/clima_alagamento/alagamento_detectado_ia/"
- ).json()
- last_update = pd.to_datetime(
- requests.get(
- "https://api.dados.rio/v2/clima_alagamento/ultima_atualizacao_alagamento_detectado_ia/"
- ).text.strip('"')
- )
-
- dataframe = pd.json_normalize(
- raw_api_data,
- record_path="ai_classification",
- meta=[
- "datetime",
- "id_camera",
- "url_camera",
- "latitude",
- "longitude",
- "image_url",
- ],
- )
-
- dataframe = dataframe.sort_values(by="label", ascending=True).reset_index(drop=True)
-
- ## get more camera information
- url = "https://docs.google.com/spreadsheets/d/122uOaPr8YdW5PTzrxSPF-FD0tgco596HqgB7WK7cHFw/edit#gid=914166579"
- request_url = url.replace("edit#gid=", "export?format=csv&gid=")
- response = requests.get(request_url)
- cameras = pd.read_csv(StringIO(response.content.decode("utf-8")), dtype=str)
- camera_cols = [
- "id_camera",
- "bairro",
- "subprefeitura",
- "id_bolsao",
- "bacia",
- "sub_bacia",
- ]
-
- cameras = cameras[camera_cols]
- dataframe = pd.merge(dataframe, cameras, how="left", on="id_camera")
-
- return dataframe, last_update
-
-
-def get_table_cameras_with_images(dataframe):
- # filter only flooded cameras
- table_data = (
- dataframe[dataframe["label"].notnull()]
- .sort_values(by=["label", "id_camera"], ascending=False)
- .reset_index(drop=True)
+ return get_cameras_cache(
+ page_size=page_size,
+ only_active=only_active,
+ use_mock_data=use_mock_data,
+ update_mock_data=update_mock_data,
)
- table_data["emoji"] = table_data["label"].apply(label_emoji)
- col_order = [
- "emoji",
- "id_camera",
- "object",
- "bairro",
- "subprefeitura",
- "id_bolsao",
- "bacia",
- "sub_bacia",
- "image_url",
- ]
- table_data = table_data[col_order]
- return table_data
+cameras = fetch_and_update_data()
+# Add a button for updating data
+if st.button("Update Data"):
+ cameras = fetch_and_update_data(bypass_cash=True)
+ st.success("Data updated successfully!")
+cameras_identifications = treat_data(cameras)
-def get_agrid_table(data_with_image):
- # Configure AgGrid
- gb = GridOptionsBuilder.from_dataframe(data_with_image)
- gb.configure_selection("single", use_checkbox=False)
- gb.configure_side_bar() # if you need a side bar
- gb.configure_pagination(paginationAutoPageSize=False, paginationPageSize=20)
+if len(cameras_identifications) > 0:
- # configure individual columns
- gb.configure_column("id_camera", header_name="ID Camera", pinned="left")
- gb.configure_column("emoji", header_name="", pinned="left")
- gb.configure_column("object", header_name="Identificador")
- gb.configure_column("image_url", header_name="URL Imagem")
- gb.configure_column("bairro", header_name="Bairro")
- gb.configure_column("subprefeitura", header_name="Subprefeitura")
- gb.configure_column("id_bolsao", header_name="ID Bolsão")
- gb.configure_column("bacia", header_name="Bacia")
- gb.configure_column("sub_bacia", header_name="Sub Bacia")
+ col1, col2 = st.columns(2)
- gb.configure_column("image_url", header_name="URL Imagem")
+ with col1:
+ objects = cameras_identifications["object"].unique().tolist()
+ objects.sort()
+ # dropdown to filter by object
+ object_filter = st.selectbox(
+ "Filtrar por objeto",
+ objects,
+ index=objects.index(DEFAULT_OBJECT),
+ )
- gb.configure_grid_options(enableCellTextSelection=True)
- # Build grid options
- grid_options = gb.build()
+ with col2:
+ labels = (
+ cameras_identifications[
+ cameras_identifications["object"] == object_filter
+ ][ # noqa
+ "label"
+ ]
+ .dropna()
+ .unique()
+ .tolist()
+ )
+ labels_default = labels.copy()
+
+ # if object_filter == "road_blockade":
+ # labels_default.remove("normal")
+ # dropdown to select label given selected object
+ label_filter = st.multiselect(
+ "Filtrar por label",
+ labels,
+ # if object_filter return minor and major, else return all labels
+ default=labels_default,
+ )
- # Set auto size mode (if you still want to use it, otherwise remove this line)
- grid_response = AgGrid(
- data_with_image,
- gridOptions=grid_options,
- columns_auto_size_mode=ColumnsAutoSizeMode.FIT_CONTENTS,
- # update_mode=GridUpdateMode.MODEL_CHANGED | GridUpdateMode.COLUMN_RESIZED,
- # fit_columns_on_grid_load=True
- # height=400,
- # width="50%",
+ cameras_identifications_filter = get_filted_cameras_objects(
+ cameras_identifications, object_filter, label_filter
)
-
- selected_row = grid_response["selected_rows"]
- return selected_row
-
-
-chart_data, last_update = load_alagamento_detectado_ia()
-data_with_image = get_table_cameras_with_images(chart_data)
-folium_map = create_map(chart_data)
-
-
-st.markdown("# Mapa de Alagamentos | Vision AI")
-st.markdown(
- """
- Esta aplicação usa as câmeras instaladas na cidade para detectar alagamentos e bolsões de água em tempo real.
-
- Ela usa o modelo Gemini Pro Vision para identificar alagamentos em imagens.
- """
-)
-
-
-st.markdown(
- f"""
-
- ----
-
- ### Status snapshots:
- - **Ultima atualização**: {str(last_update)}
- - Total: {len(chart_data)}
- - Sucessos: {len(data_with_image)}
- - Falhas:{len(chart_data) - len(data_with_image)}
-
- ----
-
- ### Tabela de Status de Alagamentos
-""",
-)
-
-selected_row = get_agrid_table(data_with_image)
-st.markdown("----")
-st.markdown("### Mapa de Câmeras")
-st.markdown("Selecione uma Câmera na tabela visualizar no mapa.")
-
-if selected_row:
- selected_camera_id = selected_row[0]["id_camera"]
- camera_data = chart_data[chart_data["id_camera"] == selected_camera_id]
- if not camera_data.empty:
- camera_location = [
- camera_data.iloc[0]["latitude"],
- camera_data.iloc[0]["longitude"],
+ # make two cols
+ col1, col2 = st.columns(2)
+ folium_map = create_map(cameras_identifications_filter)
+
+ with col1:
+ selected_cols = [
+ "index",
+ "id",
+ "object",
+ "label",
+ "timestamp",
+ "snapshot_timestamp",
+ "bairro",
]
- folium_map = create_map(chart_data, location=camera_location)
- map_data = st_folium(folium_map, key="fig1", height=600, width=1200)
-
- image_url = camera_data.iloc[0]["image_url"]
- st.markdown("----")
- st.markdown("### 📷 Câmera snapshot")
- st.markdown("Selecione uma Câmera na tabela visualizar o snapshot.")
-
- if image_url is None:
- st.markdown("Falha ao capturar o snapshot da câmera.")
+ aggrid_table = cameras_identifications_filter.copy()
+ aggrid_table["index"] = aggrid_table["label"].apply(
+ lambda label: get_icon_color(label=label, type="emoji")
+ )
+ aggrid_table = aggrid_table[selected_cols]
+ # aggrid_table = aggrid_table[selected_cols]
+ st.markdown("### 📈 Identificações")
+ selected_row = display_agrid_table(aggrid_table) # noqa
+
+ with col2:
+ if selected_row:
+ camera_id = selected_row[0]["id"]
+ row = cameras_identifications_filter[
+ cameras_identifications_filter["id"] == camera_id
+ ]
+ # get first row
+ row = row.head(1).to_dict("records")[0]
+ camera_location = [row["latitude"], row["longitude"]]
+ folium_map = create_map(
+ cameras_identifications_filter,
+ location=camera_location, # noqa
+ )
+
+ display_camera_details(
+ row=row, cameras_identifications=cameras_identifications
+ ) # noqa
else:
- st.image(image_url)
-
-
+ st.markdown(
+ """
+ ### 📷 Câmera snapshot
+ Selecione uma Câmera na tabela para visualizar mais detalhes.
+ """
+ )
+
+ with col1:
+ st.markdown("### 📍 Mapa")
+ st_folium(folium_map, key="fig1", height=600, width="100%")
+
+ # for camera_id in cameras_identifications_filter.index:
+ # row = cameras_filter.loc[camera_id]
+ # display_camera_details(
+ # row=row, cameras_identifications=cameras_identifications
+ # )
+ # time.sleep(2)
else:
- map_data = st_folium(folium_map, key="fig1", height=600, width=1200)
-
- st.markdown("----")
- st.markdown("### 📷 Câmera snapshot")
- st.markdown("Selecione uma Câmera na tabela visualizar o snapshot.")
-
-# select chart_data obj based on last_object_clicked coordinates
-obj_coord = map_data["last_object_clicked"]
-
-
-# if obj_coord is None:
-# st.write("Clique no marcador para ver mais detalhes.")
-# else:
-# selected_data = chart_data[
-# (chart_data["latitude"] == obj_coord["lat"])
-# & (chart_data["longitude"] == obj_coord["lng"])
-# ]
-
-# image_url = selected_data["image_url"].values[0]
-# selected_data = (
-# selected_data[["id_camera", "url_camera"]]
-# .rename(
-# columns={
-# "id_camera": "ID",
-# "url_camera": "🎥 Feed",
-# }
-# )
-# .T
-# )
-
-# selected_data.columns = ["Informações"]
-
-# st.markdown("### 📷 Camera snapshot")
-# if image_url is None:
-# st.markdown("Falha ao capturar o snapshot da câmera.")
-# else:
-# st.image(image_url)
-
-# selected_data
+ st.error("No cameras with identifications")
diff --git a/app/pages/Experimente o Modelo_Vision AI.py b/app/hide/Experimente o Modelo_Vision AI.py
similarity index 98%
rename from app/pages/Experimente o Modelo_Vision AI.py
rename to app/hide/Experimente o Modelo_Vision AI.py
index 6d5d300..e7c52ad 100644
--- a/app/pages/Experimente o Modelo_Vision AI.py
+++ b/app/hide/Experimente o Modelo_Vision AI.py
@@ -1,8 +1,7 @@
+# -*- coding: utf-8 -*-
import streamlit as st
-
from utils.model import run_model
-
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB
st.set_page_config(layout="wide", page_title="Experimente o Modelo")
diff --git "a/app/hide/Pontos Cr\303\255ticos em Tempo Real.py" "b/app/hide/Pontos Cr\303\255ticos em Tempo Real.py"
index a938e7f..5a7f832 100644
--- "a/app/hide/Pontos Cr\303\255ticos em Tempo Real.py"
+++ "b/app/hide/Pontos Cr\303\255ticos em Tempo Real.py"
@@ -1,11 +1,10 @@
+# -*- coding: utf-8 -*-
import folium
import pandas as pd
-
import requests
import streamlit as st
from streamlit_folium import st_folium
-
st.set_page_config(layout="wide", page_title="Pontos Críticos em Tempo Real")
st.image("./data/logo/logo.png", width=300)
diff --git a/app/pages/Pontos de Alagamento_Vision AI.py b/app/hide/Pontos de Alagamento_Vision AI.py
similarity index 98%
rename from app/pages/Pontos de Alagamento_Vision AI.py
rename to app/hide/Pontos de Alagamento_Vision AI.py
index 3bd900f..6c8ce47 100644
--- a/app/pages/Pontos de Alagamento_Vision AI.py
+++ b/app/hide/Pontos de Alagamento_Vision AI.py
@@ -1,6 +1,7 @@
-import streamlit as st
-import requests
+# -*- coding: utf-8 -*-
import pandas as pd
+import requests
+import streamlit as st
st.set_page_config(layout="wide", page_title="Pontos de Alagamento")
st.image("./data/logo/logo.png", width=300)
diff --git a/app/pages/Classificador de Labels.py b/app/pages/Classificador de Labels.py
new file mode 100644
index 0000000..76f8213
--- /dev/null
+++ b/app/pages/Classificador de Labels.py
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+import json
+
+import pandas as pd
+import streamlit as st
+from utils.utils import explode_df, get_objects_cache, get_objetcs_labels_df
+
+st.set_page_config(layout="wide", initial_sidebar_state="collapsed")
+# st.image("./data/logo/logo.png", width=300)
+
+st.markdown("# Classificador de labels | Vision AI")
+
+objects = pd.DataFrame(get_objects_cache())
+
+labels = get_objetcs_labels_df(objects)
+# labels.index = labels["name"]
+# labels = labels.drop(columns=["name"])
+
+
+# https://docs.google.com/document/d/1PRCjbIJw4_g3-p4gLjYoN0qTaenephyZyOsiOfVGWzM/edit
+
+
+def get_translation(label):
+ markdown_translation = [
+ {
+ "object": "image_corrupted",
+ "title": "A imagem está corrompida?",
+ "condition": "Se a resposta for 'Sim', pule para a próxima imagem.", # noqa
+ "explanation": "Confira se os detalhes na imagem estão claros e definidos. Uma imagem considerada nítida permite a identificação dos objetos e do cenário.", # noqa
+ "labels": {
+ "true": "Sim",
+ "false": "Não",
+ },
+ },
+ {
+ "object": "rain",
+ "title": "Há indício de chuva?",
+ "condition": "Se a resposta for 'Não', associe o rótulo ‘Baixa ou Indiferente’ à opção 3 e pule para 4.", # noqa
+ "explanation": " Inspeção visual para presença de água na pista, que pode variar desde uma leve umidade até condições de alagamento evidente.", # noqa
+ "labels": {
+ "true": "Sim",
+ "false": "Não",
+ },
+ },
+ {
+ "object": "water_level",
+ "title": "Qual o nível de acúmulo de água?",
+ "condition": "Se a resposta for 'Não', pule para a próxima imagem.", # noqa
+ "explanation": "Estime o volume de água presente na pista, categorizando-o como um muito baixa (menos que ¼ da roda de um veículo de passeio), bolsão (entre ¼ e ½ da roda), ou alagamento (mais que ½ da roda).", # noqa
+ "labels": {
+ "low": "Baixa ou Indiferente",
+ "medium": "Bolsão d'água",
+ "high": "Alagamento",
+ },
+ },
+ {
+ "object": "road_blockade",
+ "title": "Há algum bloqueio na via?",
+ "condition": "",
+ "explanation": "Avalie se há algum obstáculo na via que impeça a circulação de veículos. O obstáculo pode ser um acúmulo de água, árvore caída, carro enguiçado, entre outros.", # noqa
+ "labels": {
+ "free": "Sem obstáculos",
+ "partially": "Via parcialmente bloqueada",
+ "totally": "Via bloqueada",
+ },
+ },
+ ]
+
+ for translation in markdown_translation:
+ if translation["object"] == label:
+ return translation
+
+
+snapshots_identifications = [
+ {
+ "object": "image_corrupted",
+ "label": "none",
+ },
+ {
+ "object": "rain",
+ "label": "none",
+ },
+ {
+ "object": "water_level",
+ "label": "none",
+ },
+ {
+ "object": "road_blockade",
+ "label": "none",
+ },
+]
+
+mock_snapshots = pd.read_csv("./data/temp/mock_image_classification.csv")
+mock_snapshots_list = mock_snapshots["image_url"].tolist()
+snapshots = [
+ {
+ "snapshot_url": snapshot_url,
+ "snapshot_identification": list(snapshots_identifications),
+ }
+ for snapshot_url in mock_snapshots_list
+]
+
+snapshots_objects = explode_df(
+ pd.DataFrame(data=snapshots), "snapshot_identification"
+) # noqa
+
+objects_number = {
+ object_name: i + 1
+ for i, object_name in enumerate(
+ snapshots_objects["object"].unique().tolist()
+ ) # noqa
+}
+snapshots_objects["question_number"] = snapshots_objects["object"].map(
+ objects_number
+) # noqa
+
+
+def put_selected_label(label, snapshots_options):
+ snapshots_to_put = snapshots_options.to_dict()
+ snapshots_to_put["label"] = label
+ # # TODO: make a put for selected object/label
+ if (snapshots_to_put.get("object") == "image_corrupted") and (
+ label == "true"
+ ): # noqa
+ print("HEREEE")
+ st.session_state.row_index += 3
+
+ print(json.dumps(snapshots_to_put, indent=4), "\n")
+
+
+customized_button = st.markdown(
+ """
+ """,
+ unsafe_allow_html=True,
+)
+
+
+def buttom(
+ label,
+ label_translated,
+ row,
+):
+ if st.button(
+ label_translated,
+ on_click=put_selected_label,
+ args=(
+ label,
+ row,
+ ),
+ ): # noqa
+ pass
+
+
+# Create a state variable to keep track of the current image index
+if "row_index" not in st.session_state:
+ st.session_state.row_index = 0
+
+
+# Check if there are more images to review
+if st.session_state.row_index >= len(snapshots_objects):
+ st.write("You have reviewed all images.")
+ if st.button("Reset"):
+ st.markdown("TODO: Reset the state variables and start over.")
+
+else:
+ # Get the current image from the DataFrame
+ row = snapshots_objects.iloc[st.session_state.row_index] # noqa
+ # Extract relevant information
+ name = row["object"]
+ translate_dict = get_translation(name)
+ snapshot_url = row["snapshot_url"]
+ question_number = row["question_number"]
+ labels_options = labels[labels["name"] == name]
+
+ choices = labels_options["value"].tolist()
+ choices.sort()
+
+ if "true" in choices:
+ choices = ["true", "false"]
+
+ # st.write"
+ col1, col2 = st.columns([1, 1.5])
+ with col2:
+ st.image(snapshot_url)
+ with col1:
+ st.markdown(
+ f"### {question_number}. {translate_dict.get('title')}",
+ )
+ st.markdown(
+ f"**Explicação:** {translate_dict.get('explanation')}",
+ )
+ # place labels in a grid of 2 columns
+ for i, label in enumerate(choices):
+ label_translated = translate_dict.get("labels").get(label)
+ with col1:
+ buttom(
+ label=label,
+ label_translated=label_translated,
+ row=row,
+ )
+ # else:
+ # with col4:
+ # buttom(
+ # label=label,
+ # label_translated=label_translated,
+ # row=row,
+ # )
+
+ st.session_state.row_index += 1 # noqa
diff --git a/app/pages/Visualizar Prompt.py b/app/pages/Visualizar Prompt.py
new file mode 100644
index 0000000..3a73e19
--- /dev/null
+++ b/app/pages/Visualizar Prompt.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+import json
+
+import pandas as pd
+import streamlit as st
+from utils.utils import (
+ get_objects,
+ get_objects_cache,
+ get_objetcs_labels_df,
+ get_prompts,
+ get_prompts_cache,
+)
+
+st.set_page_config(layout="wide", initial_sidebar_state="collapsed")
+# st.image("./data/logo/logo.png", width=300)
+
+st.markdown("# Visualizar Prompt | Vision AI")
+
+
+# Function to fetch and update data
+def fetch_and_update_prompts(bypass_cash=False):
+ if bypass_cash:
+ return get_prompts()
+ return get_prompts_cache()
+
+
+def fetch_and_update_objects(bypass_cash=False):
+ if bypass_cash:
+ return get_objects()
+ return get_objects_cache()
+
+
+prompt_data = fetch_and_update_prompts()
+objects_data = fetch_and_update_objects()
+
+# Add a button for updating data
+if st.button("Update Data"):
+ prompt_data = fetch_and_update_prompts(bypass_cash=True)
+ objects_data = fetch_and_update_objects(bypass_cash=True)
+ st.success("Data updated successfully!")
+
+objects = pd.DataFrame(objects_data)
+labels = get_objetcs_labels_df(objects, keep_null=True)
+
+prompt_parameters = prompt_data[0]
+prompt_text = prompt_parameters.get("prompt_text")
+prompt_objects = prompt_parameters.get("objects")
+
+
+selected_labels_cols = ["name", "criteria", "identification_guide", "value"]
+labels = labels[selected_labels_cols]
+labels = labels[labels["name"].isin(prompt_objects)]
+labels = labels.rename(columns={"name": "object", "value": "label"})
+objects_table_md = labels.to_markdown(index=False)
+
+
+output_schema = """{\n "$defs": {\n "Object": {\n "properties": {\n "object": {\n"description": "The object from the objects table",\n"title": "Object",\n"type": "string"\n },\n "label_explanation": {\n"description": "Highly detailed visual description of the image given the object context",\n"title": "Label Explanation",\n"type": "string"\n },\n "label": {\n"anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n],\n"description": "Label indicating the condition or characteristic of the object",\n"title": "Label"\n }\n },\n "required": [\n "object",\n "label_explanation",\n "label"\n ],\n "title": "Object",\n "type": "object"\n }\n },\n "properties": {\n "objects": {\n "items": {\n "$ref": "#/$defs/Object"\n },\n "title": "Objects",\n "type": "array"\n }\n },\n "required": [\n "objects"\n ],\n "title": "Output",\n "type": "object"\n}\n""" # noqa
+output_schema = json.dumps(json.loads(output_schema), indent=4)
+output_example = """{\n "objects": [\n {\n "object": "