Skip to content

Commit

Permalink
MRG: Merge pull request #31 from aerosense-ai/feature/add-time-select…
Browse files Browse the repository at this point in the history
…ion-by-measurement-session

Add ability to plot data by measurement session
  • Loading branch information
cortadocodes authored Jun 14, 2023
2 parents cc453db + e9d6348 commit b2d0598
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 145 deletions.
211 changes: 133 additions & 78 deletions dashboard/callbacks.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import datetime
import datetime as dt
import logging
import threading

import plotly.express as px
import requests
from dash import Input, Output, State
from dash.exceptions import PreventUpdate

from aerosense_tools.plots import plot_connection_statistic, plot_cp_curve, plot_sensors
from aerosense_tools.preprocess import RawSignal, SensorMeasurementSession
from aerosense_tools.queries import ROW_LIMIT, BigQuery
from aerosense_tools.utils import generate_time_range
from dashboard.utils import generate_time_range


logger = logging.getLogger(__name__)


SESSIONS_EXTRACTION_CLOUD_FUNCTION_URL = (
"https://europe-west6-aerosense-twined.cloudfunctions.net/data-gateway-sessions"
)


def register_callbacks(app, cache, cache_timeout, tabs, sensor_types):
"""Register the dashboards callbacks with the app.
Expand All @@ -32,14 +40,7 @@ def register_callbacks(app, cache, cache_timeout, tabs, sensor_types):
State("node-select", "value"),
State("y-axis-select", "value"),
State("time-range-select", "value"),
State("start-date", "date"),
State("start-hour", "value"),
State("start-minute", "value"),
State("start-second", "value"),
State("end-date", "date"),
State("end-hour", "value"),
State("end-minute", "value"),
State("end-second", "value"),
State("measurement-session-select", "value"),
Input("refresh-button", "n_clicks"),
)
@cache.memoize(timeout=cache_timeout, args_to_ignore=["refresh"])
Expand All @@ -48,14 +49,7 @@ def plot_information_sensors_graph(
node_id,
y_axis_column,
time_range,
custom_start_date,
custom_start_hour,
custom_start_minute,
custom_start_second,
custom_end_date,
custom_end_hour,
custom_end_minute,
custom_end_second,
measurement_session,
refresh,
):
"""Plot a graph of the information sensors for the given installation, y-axis column, and time range when these
Expand All @@ -65,32 +59,17 @@ def plot_information_sensors_graph(
:param str node_id:
:param str y_axis_column:
:param str time_range:
:param str|None custom_start_date:
:param int|None custom_start_hour:
:param int|None custom_start_minute:
:param int|None custom_start_second:
:param str|None custom_end_date:
:param int|None custom_end_hour:
:param int|None custom_end_minute:
:param int|None custom_end_second:
:param str measurement_session:
:param int refresh:
:return (plotly.graph_objs.Figure, str):
"""
if not node_id:
node_id = None

custom_start, custom_end = _combine_dates_and_times(
custom_start_date,
custom_start_hour,
custom_start_minute,
custom_start_second,
custom_end_date,
custom_end_hour,
custom_end_minute,
custom_end_second,
)
start, finish = generate_time_range(time_range, measurement_session)

start, finish = generate_time_range(time_range, custom_start, custom_end)
if start is None:
return (px.scatter(), "No measurement session selected.")

if y_axis_column == "battery_info":
df, data_limit_applied = BigQuery().get_sensor_data(
Expand All @@ -101,6 +80,9 @@ def plot_information_sensors_graph(
finish=finish,
)

if df.empty:
return (px.scatter(), "No data to plot.")

figure = plot_sensors(df, line_descriptions=sensor_types[y_axis_column]["variable"])

else:
Expand All @@ -111,6 +93,9 @@ def plot_information_sensors_graph(
finish=finish,
)

if df.empty:
return (px.scatter(), "No data to plot.")

data_limit_applied = ""
figure = plot_connection_statistic(df, y_axis_column)

Expand All @@ -126,14 +111,7 @@ def plot_information_sensors_graph(
State("node-select", "value"),
State("y-axis-select", "value"),
State("time-range-select", "value"),
State("start-date", "date"),
State("start-hour", "value"),
State("start-minute", "value"),
State("start-second", "value"),
State("end-date", "date"),
State("end-hour", "value"),
State("end-minute", "value"),
State("end-second", "value"),
State("measurement-session-select", "value"),
Input("refresh-button", "n_clicks"),
)
@cache.memoize(timeout=cache_timeout, args_to_ignore=["refresh"])
Expand All @@ -142,14 +120,7 @@ def plot_sensors_graph(
node_id,
sensor_name,
time_range,
custom_start_date,
custom_start_hour,
custom_start_minute,
custom_start_second,
custom_end_date,
custom_end_hour,
custom_end_minute,
custom_end_second,
measurement_session,
refresh,
):
"""Plot a graph of the sensor data for the given installation, y-axis column, and time range when these values are
Expand All @@ -159,32 +130,17 @@ def plot_sensors_graph(
:param str node_id:
:param str sensor_name:
:param str time_range:
:param str|None custom_start_date:
:param int|None custom_start_hour:
:param int|None custom_start_minute:
:param int|None custom_start_second:
:param str|None custom_end_date:
:param int|None custom_end_hour:
:param int|None custom_end_minute:
:param int|None custom_end_second:
:param str measurement_session:
:param int refresh:
:return (plotly.graph_objs.Figure, str):
"""
if not node_id:
node_id = None

custom_start, custom_end = _combine_dates_and_times(
custom_start_date,
custom_start_hour,
custom_start_minute,
custom_start_second,
custom_end_date,
custom_end_hour,
custom_end_minute,
custom_end_second,
)
start, finish = generate_time_range(time_range, measurement_session)

start, finish = generate_time_range(time_range, custom_start, custom_end)
if start is None:
return (px.scatter(), "No measurement session selected.")

df, data_limit_applied = BigQuery().get_sensor_data(
installation_reference,
Expand All @@ -195,7 +151,7 @@ def plot_sensors_graph(
)

if df.empty:
return (px.scatter(), "No data to plot")
return (px.scatter(), "No data to plot.")

# Extract only data columns and set index to 'datetime', so that DataFrame is accepted by RawSignal class
data_columns = df.columns[df.columns.str.startswith("f")].tolist()
Expand Down Expand Up @@ -359,9 +315,10 @@ def update_sensor_coordinates_selector(refresh):

@app.callback(
Output("graph-title", "children"),
Input("y-axis-select", "value"),
State("y-axis-select", "value"),
Input("refresh-button", "n_clicks"),
)
def update_graph_title(selected_y_axis):
def update_graph_title(selected_y_axis, refresh):
"""Update the graph title with the name of the currently selected y-axis.
:param str selected_y_axis:
Expand All @@ -384,19 +341,117 @@ def update_graph_title(selected_y_axis):
Output("end-hour", "disabled"),
Output("end-minute", "disabled"),
Output("end-second", "disabled"),
Output("measurement-session-select", "disabled"),
Output("measurement-session-check-button", "disabled"),
],
[
Input("time-range-select", "value"),
],
)
def enable_custom_time_range_select(time_range):
"""Enable the custom time range selection if "Custom" is chosen in the time range selector.
def enable_measurement_session_time_range_select(time_range):
"""Enable the measurement session time range selection if "Measurement session" is chosen in the time range
selector.
:param str time_range:
:return bool:
"""
disabled = time_range != "Custom"
return (disabled, None, disabled, disabled, disabled, disabled, None, disabled, disabled, disabled)
disabled = time_range != "Measurement session"
return (
disabled,
None,
disabled,
disabled,
disabled,
disabled,
None,
disabled,
disabled,
disabled,
disabled,
disabled,
)

@app.callback(
Output("measurement-session-select", "options"),
Output("measurement-session-select", "value"),
State("measurement-session-select", "disabled"),
State("installation-select", "value"),
State("node-select", "value"),
State("y-axis-select", "value"),
State("start-date", "date"),
State("start-hour", "value"),
State("start-minute", "value"),
State("start-second", "value"),
State("end-date", "date"),
State("end-hour", "value"),
State("end-minute", "value"),
State("end-second", "value"),
Input("measurement-session-check-button", "n_clicks"),
)
def update_measurement_session_selector(
measurement_session_selection_disabled,
installation_reference,
node_id,
y_axis,
start_date,
start_hour,
start_minute,
start_second,
end_date,
end_hour,
end_minute,
end_second,
refresh,
):
if measurement_session_selection_disabled:
raise PreventUpdate

start_datetime, finish_datetime = _combine_dates_and_times(
start_date,
start_hour,
start_minute,
start_second,
end_date,
end_hour,
end_minute,
end_second,
)

# The connection statistics always come together, so they have the same measurement sessions.
if y_axis in {"filtered_rssi", "filtered_rssi", "tx_power", "allocated_heap_memory"}:
y_axis = "connection_statistics"

measurement_sessions = BigQuery().get_measurement_sessions(
installation_reference=installation_reference,
node_id=node_id,
sensor_type_reference=y_axis,
start=start_datetime,
finish=finish_datetime,
)

measurement_sessions = [
f"{session[1][0]} to {session[1][1]}"
for session in measurement_sessions[["start_time", "end_time"]].iterrows()
]

if not measurement_sessions:
return [], None

return measurement_sessions, measurement_sessions[0]

@app.callback(
# Use a dummy output.
Output("run-session-extraction-output-placeholder", "children"),
Input("run-session-extraction", "n_clicks"),
prevent_initial_call=True,
)
def run_session_extraction_in_database(refresh):
"""Trigger measurement session extraction in the database.
:return None:
"""
threading.Thread(target=requests.post, args=(SESSIONS_EXTRACTION_CLOUD_FUNCTION_URL,), daemon=True).start()
logger.info("Triggered measurement session extraction cloud function.")

@app.callback(
Output("app", "children"),
Expand Down
4 changes: 2 additions & 2 deletions dashboard/components/time_range_select.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from dash import dcc

from aerosense_tools.utils import TIME_RANGE_OPTIONS
from dashboard.utils import TIME_RANGE_OPTIONS


def TimeRangeSelect():
return dcc.Dropdown(
options=list(TIME_RANGE_OPTIONS.keys()) + ["All time", "Custom"],
options=list(TIME_RANGE_OPTIONS.keys()) + ["All time", "Measurement session"],
id="time-range-select",
value="Last day",
persistence=True,
Expand Down
Loading

0 comments on commit b2d0598

Please sign in to comment.