Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/callbacks/data_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ def update_sub_api_sequences(api_sequences, local_sub_sequences):
[
Input("sequence_id_on_display", "data"),
Input("detection_fetch_limit", "data"),
Input("detection_fetch_desc", "value"),
Input("detection_fetch_desc_store", "data"),
],
[
State("api_sequences", "data"),
Expand Down
40 changes: 27 additions & 13 deletions app/callbacks/display_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import boto3 # type: ignore
import dash
import logging_config
import numpy as np
import pandas as pd
from botocore.exceptions import ClientError # type: ignore
from dash import Input, Output, State, ctx
Expand Down Expand Up @@ -184,6 +183,14 @@ def select_event_with_button(n_clicks, button_ids, event_id_table_json, api_sequ
]


@app.callback(
Output("detection_fetch_desc_store", "data"),
Input("detection_fetch_desc", "value"),
)
def update_desc_store(value):
return value


@app.callback(
Output("sequence_id_on_display", "data"),
Input("sequence_dropdown", "value"),
Expand All @@ -203,11 +210,12 @@ def update_sequence_on_dropdown_change(selected_sequence_id):
Output("image-slider", "marks"),
Output("image-slider", "min"),
Output("slider-container", "style"),
Output("image-timestamp", "children"),
],
[
Input("image-slider", "value"),
Input("sequence_on_display", "data"),
Input("detection_fetch_desc", "value"),
Input("detection_fetch_desc_store", "data"),
],
[
State("sequence-list-container", "children"),
Expand All @@ -227,11 +235,12 @@ def update_image_and_bbox(slider_value, sequence_on_display, detection_fetch_des
sequence_on_display = pd.read_json(StringIO(sequence_on_display), orient="split")

if sequence_on_display.empty or not len(sequence_list):
return no_alert_image_src, *[{"display": "none"}] * 3, 0, {}, 0, {"display": "none"}
return no_alert_image_src, *[{"display": "none"}] * 3, 0, {}, 0, {"display": "none"}, ""

if not detection_fetch_desc:
sequence_on_display = sequence_on_display[::-1].reset_index(drop=True)

# Extract data
images, boxes, created_at_local_list = zip(
*(
(alert["url"], alert["processed_bboxes"], alert.get("created_at_local"))
Expand All @@ -242,13 +251,14 @@ def update_image_and_bbox(slider_value, sequence_on_display, detection_fetch_des
)

if not images:
return no_alert_image_src, *[{"display": "none"}] * 3, 0, {}, 0, {"display": "none"}
return no_alert_image_src, *[{"display": "none"}] * 3, 0, {}, 0, {"display": "none"}, ""

n_images = len(images)
slider_value = slider_value % n_images
img_src = images[slider_value]
images_bbox_list = boxes[slider_value]

# Compute bbox styles
bbox_styles = [{"display": "none"} for _ in range(3)]
for i, (x0, y0, width, height) in enumerate(images_bbox_list[:3]):
bbox_styles[i] = {
Expand All @@ -263,18 +273,22 @@ def update_image_and_bbox(slider_value, sequence_on_display, detection_fetch_des
"display": "block",
}

# Marks with no labels (just dots)
marks = dict.fromkeys(range(n_images), "")

# Timestamp for the selected image
try:
latest_time = pd.to_datetime(sequence_on_display["created_at_local"].dropna().max())
# Take latest non-null timestamp
latest_ts = pd.to_datetime(next((ts for ts in reversed(created_at_local_list) if ts), None), errors="coerce")
if pd.notnull(latest_ts):
timestamp = latest_ts - pd.Timedelta(seconds=30 * (n_images - 1 - slider_value))
timestamp_str = timestamp.strftime("%H:%M:%S")
else:
timestamp_str = ""
except Exception:
latest_time = datetime.now()

# Compute 5 evenly spaced tick positions
num_marks = 5
tick_indices = sorted(set(int(round(i)) for i in np.linspace(0, n_images - 1, num=num_marks)))

marks = {i: (latest_time - timedelta(seconds=30 * (n_images - 1 - i))).strftime("%H:%M:%S") for i in tick_indices}
timestamp_str = ""

return [img_src, *bbox_styles, n_images - 1, marks, 0, {"display": "block"}]
return [img_src, *bbox_styles, n_images - 1, marks, 0, {"display": "block"}, timestamp_str]


@app.callback(
Expand Down
3 changes: 3 additions & 0 deletions app/layouts/main_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,7 @@ def get_main_layout():
dcc.Store(id="language", storage_type="session", data="fr"),
dcc.Store(id="selected_event_id", storage_type="session", data=None),
dcc.Store(id="detection_fetch_limit", storage_type="session", data=10),
dcc.Store(
id="detection_fetch_desc_store", storage_type="session", data=True
), # or False if that's your default
])
69 changes: 48 additions & 21 deletions app/pages/homepage.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,6 @@ def homepage_layout(user_token, api_cameras, lang="fr", descending_order=True):
className="common-style",
style={"padding": "8px"},
children=[
html.Label(translate("detections_to_fetch", lang), className="mt-2"),
dcc.Input(
id="detection_fetch_limit_input",
type="number",
min=1,
max=50,
step=1,
value=10,
style={"width": "100%"},
),
html.Label(translate("fetch_order", lang), className="mt-3"),
dbc.Checklist(
id="detection_fetch_desc",
options=[{"value": True}],
value=[descending_order],
switch=True,
),
html.Hr(className="my-3"),
create_event_list(),
],
),
Expand Down Expand Up @@ -113,7 +95,7 @@ def homepage_layout(user_token, api_cameras, lang="fr", descending_order=True):
),
dbc.Row(
[
dbc.Col(
dbc.Col( # Play button
dbc.Button(
html.Img(src="assets/images/play-pause.svg"),
id="auto-move-button",
Expand All @@ -122,14 +104,59 @@ def homepage_layout(user_token, api_cameras, lang="fr", descending_order=True):
),
width=1,
),
dbc.Col(
dbc.Col( # Input and toggle
html.Div(
[
html.Span(
translate("detections_to_fetch", lang), style={"marginRight": "6px"}
),
dcc.Input(
id="detection_fetch_limit_input",
type="number",
min=1,
max=50,
step=1,
value=10,
style={"width": "60px", "marginRight": "12px"},
),
dbc.Switch(
id="detection_fetch_desc",
label=translate("fetch_order", lang),
value=descending_order, # value is a bool
style={"display": "inline-block"},
),
],
style={"display": "flex", "alignItems": "center"},
),
width=5,
),
dbc.Col( # Slider
html.Div(
dcc.Slider(id="image-slider", min=0, max=0, step=1, value=0, marks={}),
id="slider-container",
className="common-style-slider",
style={"display": "none"},
),
width=11,
width=5,
),
dbc.Col( # Timestamp
html.Div(
html.Span(
id="image-timestamp",
children="",
style={
"fontFamily": "'Roboto', sans-serif", # match Bootstrap default
"fontSize": "16px",
"fontWeight": "400", # or 500
"color": "#212529", # Bootstrap's default text color
"backgroundColor": "#fffffff", # same as slider background
"padding": "6px 10px",
"borderRadius": "6px",
},
),
style={"display": "flex", "alignItems": "center", "justifyContent": "flex-end"},
),
width=1,
),
],
style={"marginTop": "10px"},
Expand Down
Loading