Skip to content

Commit

Permalink
MRG: Merge pull request #30 from aerosense-ai/feature/add-cp-plots
Browse files Browse the repository at this point in the history
Replace pressure profile plot with Cp plot
  • Loading branch information
cortadocodes authored Jun 7, 2023
2 parents b6f6138 + fcd722d commit cc453db
Show file tree
Hide file tree
Showing 9 changed files with 800 additions and 428 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ repos:
args: ["--config=setup.cfg"]
language_version: python3

- repo: https://github.com/timothycrosley/isort
rev: 5.0.9
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
require_serial: true
Expand Down
4 changes: 2 additions & 2 deletions dashboard/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from aerosense_tools.queries import BigQuery
from dashboard.callbacks import register_callbacks
from dashboard.layouts import create_pressure_profile_tab_layout, create_sensors_tab_layout
from dashboard.layouts import create_cp_plot_tab_layout, create_sensors_tab_layout


EXCLUDED_SENSORS = {"microphone", "connection_statistics", "battery_info"}
Expand Down Expand Up @@ -41,7 +41,7 @@
graph_id="sensors-graph",
data_limit_warning_id="sensor-data-limit-warning",
),
"pressure_profile": create_pressure_profile_tab_layout(app),
"cp_plot": create_cp_plot_tab_layout(app),
}

app.layout = html.Div(
Expand Down
71 changes: 56 additions & 15 deletions dashboard/callbacks.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import datetime
import datetime as dt
import plotly.express as px
import logging

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

from aerosense_tools.plots import plot_connection_statistic, plot_pressure_bar_chart, plot_sensors
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, get_cleaned_sensor_column_names
from aerosense_tools.utils import generate_time_range


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -198,8 +198,8 @@ def plot_sensors_graph(
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()
sensor_data = df[["datetime"] + data_columns].set_index('datetime')
data_columns = df.columns[df.columns.str.startswith("f")].tolist()
sensor_data = df[["datetime"] + data_columns].set_index("datetime")
sensor_data.columns = sensor_types[sensor_name]["sensors"]
# Use pre-process library
raw_data = RawSignal(sensor_data, sensor_name)
Expand All @@ -216,8 +216,8 @@ def plot_sensors_graph(
return (figure, [])

@cache.memoize(timeout=0)
def get_pressure_profiles_for_time_window(installation_reference, node_id, start_datetime, finish_datetime):
"""Get pressure profiles for the given node during the given time window along with the minimum and maximum
def get_pressure_data_for_time_window(installation_reference, node_id, start_datetime, finish_datetime):
"""Get pressure data for the given node during the given time window along with the minimum and maximum
pressures over all the sensors over that window.
:param str installation_reference:
Expand All @@ -235,34 +235,54 @@ def get_pressure_profiles_for_time_window(installation_reference, node_id, start
)

logger.info(
"Downloaded pressure profile %d second time window for start datetime %r and finish datetime %r.",
"Downloaded pressure data %d second time window for start datetime %r and finish datetime %r.",
(finish_datetime - start_datetime).seconds,
start_datetime.isoformat(),
finish_datetime.isoformat(),
)

sensor_column_names, _ = get_cleaned_sensor_column_names(df)
df_with_sensors_only = df[sensor_column_names]
return (df, df_with_sensors_only.min().min(), df_with_sensors_only.max().max())
return df

@app.callback(
Output("pressure-profile-graph", "figure"),
State("installation-select", "value"),
State("node-select", "value"),
State("sensor-coordinates-select", "value"),
State("air-density-input", "value"),
State("u-input", "value"),
State("p-inf-input", "value"),
State("cp-minimum-input", "value"),
State("cp-maximum-input", "value"),
State("date-select", "date"),
State("hour", "value"),
State("minute", "value"),
State("second", "value"),
Input("time-slider", "value"),
Input("refresh-button", "n_clicks"),
)
def plot_pressure_profile_graph(installation_reference, node_id, date, hour, minute, second, time_delta, refresh):
@cache.memoize(timeout=cache_timeout, args_to_ignore=["refresh"])
def plot_cp_graph(
installation_reference,
node_id,
sensor_coordinates_reference,
air_density,
u,
p_inf,
cp_minimum,
cp_maximum,
date,
hour,
minute,
second,
time_delta,
refresh,
):
if not node_id:
node_id = None

initial_datetime = dt.datetime.combine(date=dt.date.fromisoformat(date), time=dt.time(hour, minute, second))

df, minimum, maximum = get_pressure_profiles_for_time_window(
df = get_pressure_data_for_time_window(
installation_reference=installation_reference,
node_id=node_id,
start_datetime=initial_datetime,
Expand All @@ -276,8 +296,17 @@ def plot_pressure_profile_graph(installation_reference, node_id, date, hour, min
& (df["datetime"] < slider_datetime + dt.timedelta(seconds=0.5))
]

logger.debug("Filtered pressure profile time window for single datetime.")
return plot_pressure_bar_chart(df, minimum, maximum)
logger.debug("Filtered pressure data time window for single datetime.")

return plot_cp_curve(
df=df,
sensor_coordinates_reference=sensor_coordinates_reference,
air_density=air_density,
u=u,
p_inf=p_inf,
cp_minimum=cp_minimum,
cp_maximum=cp_maximum,
)

@app.callback(
Output("installation-select", "options"),
Expand Down Expand Up @@ -316,6 +345,18 @@ def update_node_selector(installation_reference):

return nodes, first_option

@app.callback(
Output("sensor-coordinates-select", "options"),
Input("sensor-coordinates-check-button", "n_clicks"),
)
def update_sensor_coordinates_selector(refresh):
"""Update the sensor coordinates selector with any new ones when the refresh button is clicked.
:param int refresh:
:return list:
"""
return BigQuery().get_sensor_coordinates()["reference"]

@app.callback(
Output("graph-title", "children"),
Input("y-axis-select", "value"),
Expand Down
1 change: 1 addition & 0 deletions dashboard/components/installation_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ def InstallationSelect(current_installation_reference=None):
options=installations,
id="installation-select",
value=current_installation_reference or installations[0]["value"],
persistence=True,
)
2 changes: 1 addition & 1 deletion dashboard/components/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def Navigation(selected_tab="information_sensors"):
children=[
dcc.Tab(label="Information sensors", value="information_sensors"),
dcc.Tab(label="Sensors", value="sensors"),
dcc.Tab(label="Pressure profile", value="pressure_profile"),
dcc.Tab(label="Cp plot", value="cp_plot"),
],
persistence=True,
),
Expand Down
14 changes: 14 additions & 0 deletions dashboard/components/sensor_coordinates_select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from dash import dcc

from aerosense_tools.queries import BigQuery


def SensorCoordinatesSelect():
sensor_coordinates = BigQuery().get_sensor_coordinates()

return dcc.Dropdown(
options=sensor_coordinates["reference"],
id="sensor-coordinates-select",
value=sensor_coordinates.iloc[0]["reference"],
persistence=True,
)
141 changes: 124 additions & 17 deletions dashboard/layouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from dashboard.components import About, InstallationSelect, Logo, Navigation, Title
from dashboard.components.node_select import NodeSelect
from dashboard.components.sensor_coordinates_select import SensorCoordinatesSelect
from dashboard.components.sensor_select import SensorSelect
from dashboard.components.time_range_select import TimeRangeSelect

Expand Down Expand Up @@ -157,8 +158,8 @@ def create_sensors_tab_layout(app, tab_name, sensor_names, graph_id, data_limit_
]


def create_pressure_profile_tab_layout(app):
"""Create the layout corresponding to the pressure profile tab.
def create_cp_plot_tab_layout(app):
"""Create the layout corresponding to the Cp plot tab.
:param dash.Dash app:
:return list:
Expand All @@ -167,7 +168,7 @@ def create_pressure_profile_tab_layout(app):
html.Div(
[
html.Div([Logo(app.get_asset_url("logo.png")), Title(), About()]),
Navigation(selected_tab="pressure_profile"),
Navigation(selected_tab="cp_plot"),
html.Br(),
html.Div(
[
Expand All @@ -176,23 +177,124 @@ def create_pressure_profile_tab_layout(app):
InstallationSelect(),
html.Label("Node ID"),
NodeSelect(),
html.Label("Sensor coordinates reference"),
SensorCoordinatesSelect(),
html.Div(
[
html.Div(
[
html.Label("Air density"),
dash_daq.NumericInput(
id="air-density-input",
value=1.225,
min=0,
max=1e9,
size=120,
persistence=True,
),
],
style={"display": "inline-block"},
),
html.Div(
[
html.Label("u"),
dash_daq.NumericInput(
id="u-input",
value=10,
min=0,
max=1e9,
size=120,
persistence=True,
),
],
style={"display": "inline-block"},
),
html.Div(
[
html.Label("p_inf"),
dash_daq.NumericInput(
id="p-inf-input",
value=1e5,
min=0,
max=1e12,
size=120,
persistence=True,
),
],
style={"display": "inline-block"},
),
html.Div(
[
html.Label("Cp minimum"),
dash_daq.NumericInput(
id="cp-minimum-input",
value=-10,
min=-1000,
max=1000,
size=120,
persistence=True,
),
],
style={"display": "inline-block"},
),
html.Div(
[
html.Label("Cp maximum"),
dash_daq.NumericInput(
id="cp-maximum-input",
value=3,
min=-1000,
max=1000,
size=120,
persistence=True,
),
],
style={"display": "inline-block"},
),
],
style={"margin": "10px 0"},
),
html.Br(),
html.Label(html.B("Start date/time")),
html.Label("Date"),
dcc.DatePickerSingle(
id="date-select",
date=datetime.datetime.now().date().isoformat(),
display_format="Do MMM Y",
persistence=True,
html.Div(
[
html.Div(
[
html.Label("Date"),
dcc.DatePickerSingle(
id="date-select",
date=datetime.datetime.now().date().isoformat(),
display_format="Do MMM Y",
persistence=True,
),
],
style={"display": "inline-block"},
),
html.Div(
[
html.Label("Hour"),
dash_daq.NumericInput(id="hour", value=0, min=0, max=23, persistence=True),
],
style={"display": "inline-block"},
),
html.Div(
[
html.Label("Minute"),
dash_daq.NumericInput(id="minute", value=0, min=0, max=59, persistence=True),
],
style={"display": "inline-block"},
),
html.Div(
[
html.Label("Second"),
dash_daq.NumericInput(id="second", value=0, min=0, max=59, persistence=True),
],
style={"display": "inline-block"},
),
],
style={"margin": "10px 0"},
),
html.Br(),
html.Label("Hour"),
dash_daq.NumericInput(id="hour", value=0, min=0, max=23, persistence=True),
html.Label("Minute"),
dash_daq.NumericInput(id="minute", value=0, min=0, max=59, persistence=True),
html.Label("Second"),
dash_daq.NumericInput(id="second", value=0, min=0, max=59, persistence=True),
html.Br(),
html.Label(html.B("Step forward (seconds)")),
html.Br(),
dcc.Slider(
Expand All @@ -207,6 +309,11 @@ def create_pressure_profile_tab_layout(app):
html.Br(),
html.Button("Plot", id="refresh-button", n_clicks=0),
html.Button("Check for new installations", id="installation-check-button", n_clicks=0),
html.Button(
"Check for new sensor coordinates",
id="sensor-coordinates-check-button",
n_clicks=0,
),
],
id="buttons-section",
className="sidebar-content",
Expand All @@ -218,7 +325,7 @@ def create_pressure_profile_tab_layout(app):
[
html.Div(
[
html.H3("Pressure profile", id="graph-title"),
html.H3("Cp plot", id="graph-title"),
],
className="text-box",
),
Expand Down
Loading

0 comments on commit cc453db

Please sign in to comment.