aam-demo / app.py
apaladugu3
Added some more content and instructions
ee0867c
#!/usr/bin/env python3
import pandas as pd
import dash
from dash import dcc, html
import webbrowser
from src.viz import Visualizer as viz
import plotly.graph_objects as go
import os
import datetime
import plotly.express as px
import json
import subprocess
start_time = 0#240
end_time = 1700#600
start_date = datetime.datetime(2024, 6, 7, 12, 0, 0)
end_date = start_date + datetime.timedelta(seconds=end_time)
# Initialize the Dash app
app = dash.Dash(__name__, suppress_callback_exceptions=True)
# Global dictionary to store data
data_store = {}
def parse_action_trace(path, exclude = []):
# Parses action data from CSV files into a dictionary.
# This function reads CSV files specified in a dictionary where each key-value pair
# corresponds to an condition ID and its associated file. It extracts columns related to
# time, action name, agent name, action end time, and attributes from each file and
# stores them in a nested dictionary structure.
# Args:
# files_dict (dict): A dictionary where keys are action IDs and values are file paths.
# Returns:
# dict: A dictionary where each key is an condition ID and the value is another dictionary
# containing parsed data from the corresponding CSV file.
df = pd.read_csv(path, engine = "python")
# Convert to events list for timeline plotting
events = []
for i in range(len(df['time'])):
if not any(excl in df['actionName'][i] for excl in exclude):
event = {
'agent': df['agentName'][i],
'name': df['actionName'][i],
'start': start_date + datetime.timedelta(seconds=df['time'][i]),
'end': start_date + datetime.timedelta(seconds=df['time'][i] + max(df['actionEndTime'][i], 2)),
'duration': max(df['actionEndTime'][i], 2),
'attributes': df['attributes'][i]
}
events.append(event)
return events
def parse_all_data(path):
# Get the list of subfolders in the 'data' folder
subfolders = [f.name for f in os.scandir(path) if f.is_dir()]
# Load all data at the start of the program
# Load the CSV files into pandas DataFrames from the selected subfolder
data = {}
exclude = ['Detect_crash','Detect_conflict','Flight_Dynamics','Reroute_flight','Show_radar','Direct_to_waypoint','Change_heading','fly']
for subfolder in subfolders:
data[subfolder] = {
'wnd_action_trace': pd.read_csv(f'{path}/{subfolder}/actionTrace_Agent_PIC Vista.csv'),
'all_action_trace': parse_action_trace(f'{path}/{subfolder}/actionTrace.csv', exclude),
}
data[subfolder]['aircraft_data'] = {}
for file in os.listdir(f'{path}/{subfolder}'):
if file.endswith('_acstate.csv'):
aircraft_id = file.split('_')[0]
data[subfolder]['aircraft_data'][aircraft_id] = pd.read_csv(f'{path}/{subfolder}/{file}')
subfolder_labels = [{'label': i, 'value': i} for i in subfolders]
return subfolder_labels, data
# Create the layout for the app
app.layout = html.Div([
html.Div([
html.B("This is an interactive demonstration of case study results to showcase the ability of modeling and simulation to help envision distributed work systems"),
html.Br(),
dcc.Markdown("This interactive demo was performed under NASA contract number 80NSSC23CA121.", id="explanation")
], style={'width': '100%', 'border': '1px solid gray'}),
html.Div([
dcc.Markdown("It is envisioned that in future operatoins, each UAV is going to be controlled using a command and control (C2) link that allows communication with the remote pilot. This architecture can have potentially dangerous implications in the case of a failure of the command and control link. The UAV's are envisioned to be programmed with one or more contingency procedures that are to be executed in the case of a lost link. For example, to divert to and land at the nearest vertiport or continue along its preapproved route. This brings the question of how will the rest of the system need to react to the change in the aircraft trajectory. Is a system like this robust to an event like loss of link? ", id="explanation2")
], style={'width': '100%', 'border': '1px solid gray'}),
html.Div([
dcc.Markdown("If the dropdown menu is empty please wait a few seconds to allow the data to load. If the data doesnt load then click the submit button to force reload the dropdown. The files that contain data will take a few seconds to load so please be patient.", id="explanation"),
html.Button('Submit', id='submit-button', n_clicks=0)
], style={'width': '100%', 'border': '1px solid gray'}),
html.Div([
# html.Div([
# html.Div("Select the desired input conditions, then click 'Simulate!':", id='text1'),
# html.Label("Work Allocation: "),
# dcc.Input( id='work-allocation', type='text', placeholder='The path to the CSV file that has the work allocation.', style={'width': '80%'}),
# html.Br(),
# html.Label("Contingency Time: "),
# dcc.Input( id='cont-time', type='text', placeholder='Time at which the lost link event occurs (set to "ALONGTIMEAWAY" (no quotes) if no lost link event needs to occur)', style={'width': '80%'}),
# html.Br(),
# html.Label("Contingency Aircraft Response: "),
# dcc.RadioItems(
# id='ConAircraftResponse',
# options=[
# {'label': 'Divert', 'value': '1'},
# {'label': 'Continue', 'value': '2'}
# ],
# value=True,
# labelStyle={'display': 'inline-block'}
# ),
# html.Br(),
# html.Label("Other Aircraft Response: "),
# dcc.RadioItems(
# id='OtherAircraftResponse',
# options=[
# {'label': 'Increase Separation', 'value': '1'},
# {'label': 'Continue', 'value': '2'}
# ],
# value=True,
# labelStyle={'display': 'inline-block'}
# ),
#
# html.Br(),
# html.Button('Simulate!', id='button', n_clicks=0),
# ], style={'width': '50%', 'border': '1px solid gray'}),
html.Div([
# html.Div("Next, input the relative path to your results folder, then click 'Submit'. This will load the data:", id='text2'),
# dcc.Input(
# id='path-input',
# # value='data',
# type='text',
# placeholder='Type your path... For example, ../wmc5.1/Scenario/AAMv2/Results',
# style={'width': '70%'} # Make the input wider
# ),
# html.Button('Submit', id='submit-button', n_clicks=0),
# Create a dropdown menu with the subfolders as options
html.Div("Select which condition to load in the interactive graphs:", id='text2'),
dcc.Dropdown(
id='subfolder-dropdown',
value=None #subfolders[0] # Default value
),
dcc.Interval(
id='interval-component', # Unique ID for the interval component
interval=1*1000, # In milliseconds (1 second)
n_intervals=0, # Start at 0, increment once
max_intervals=1 # Fire only once
)
], style={'width': '100%', 'border': '1px solid gray'}),
], style={'display': 'flex'}),
html.Div([
html.Div([
dcc.Graph(id='map'), # Second graph
dcc.Dropdown(
id='variable-dropdown',
# options=[{'label': i, 'value': i} for i in subfolders],
value=None #subfolders[0] # Default value
),
dcc.Graph(id='altitude-series') # Third graph
], style={'width': '40%', 'display': 'inline-block', 'border': '1px solid gray'}),
html.Div([
dcc.Graph(id='timeline'),
], style={'width': '60%', 'display': 'inline-block', 'border': '1px solid gray'})
], style={'display': 'flex'}),
html.Div(
dcc.Slider(
id='time-slider',
min=start_time,
max=end_time,
value=start_time+100,
marks={str(time): str(time) for time in range(start_time, end_time+1, 50)},
updatemode='drag'
),
style={'width': '100%'} # Set the width of the div containing the slider to 80% of the container
)
])
@app.callback(
dash.dependencies.Output('button', 'n_clicks'), # Add this output to reset button clicks
[dash.dependencies.Input('work-allocation', 'value')],
[dash.dependencies.Input('cont-time', 'value')],
[dash.dependencies.Input('ConAircraftResponse', 'value')],
[dash.dependencies.Input('OtherAircraftResponse', 'value')],
[dash.dependencies.Input('button', 'n_clicks')],
)
def run_wmc(workAllocation, selected_time, ConAircraftResponse, OtherAircraftResponse, nc_clicks):
if nc_clicks > 0:
# try:
print("Running WMC with input arguments",workAllocation,selected_time,ConAircraftResponse+OtherAircraftResponse)
# Create a string representing the command to be run
command = f"wmc/AAMv2 {workAllocation} {selected_time} {ConAircraftResponse + OtherAircraftResponse} 4000"
# Write the command to a wrapper.sh file
with open("wmc/wrapper.sh", "w") as file:
file.write("#!/bin/bash\n")
file.write(command + "\n")
# Make the wrapper.sh script executable
subprocess.run(["chmod", "+x", "wmc/wrapper.sh"])
# Run the wrapper.sh file
process = subprocess.Popen(["wmc/wrapper.sh"])
#process = subprocess.Popen(['wmc/AAMv2', workAllocation, selected_time, ConAircraftResponse+OtherAircraftResponse, "4000"]) # return f"WMC was run!"
# process = subprocess.Popen(['./UAM', 'Example', '240', 'True']) # return f"WMC was run!"
try:
process.wait(timeout=50) # Wait for the process to complete
except subprocess.TimeoutExpired:
process.terminate() # Terminate the process after timeout
# Continue with the rest of the code
print("Completed running WMC...")
return 0
# except:
# return f"error!"
# Load data when the app starts and store it in the hidden div
@app.callback(
dash.dependencies.Output('subfolder-dropdown', 'options'),
dash.dependencies.Output('variable-dropdown', 'options'),
dash.dependencies.Output('submit-button', 'n_clicks'), # Add this output to reset submit button clicks
[dash.dependencies.Input('interval-component', 'n_intervals'),
dash.dependencies.Input('submit-button', 'n_clicks')],
# [dash.dependencies.Input('path-input', 'value')],
)
def load_data(n_intervals, n_clicks):
selected_path = "Results"
print("selected path is",selected_path)
global data_store
if n_intervals > 0 or n_clicks > 0:
if selected_path is None or not os.path.isdir(selected_path):
return [], []
subfolder_labels, data = parse_all_data(selected_path)
print('subfolders',subfolder_labels)
if data is None:
data = {}
# Store data in the global dictionary
data_store = data
labels = list(data[list(data.keys())[0]]['aircraft_data'][list(data[list(data.keys())[0]]['aircraft_data'].keys())[0]].columns.tolist()) # Extracting column names for the first aircraft-specific dataframe
return subfolder_labels, labels, 0
else:
return [], [], 0
# Define the callback to update the graphs based on the slider value
@app.callback(
# dash.dependencies.Output('time-series', 'figure'),
dash.dependencies.Output('map', 'figure'),
dash.dependencies.Output('altitude-series', 'figure'),
dash.dependencies.Output('timeline', 'figure'),
# dash.dependencies.Input('path-input', 'value'),
[dash.dependencies.Input('subfolder-dropdown', 'value'),
dash.dependencies.Input('time-slider', 'value'),
dash.dependencies.Input('variable-dropdown','value')]
)
# def update_graph(selected_subfolder, selected_time):
# # Load the CSV files into pandas DataFrames from the selected subfolder
# df1 = pd.read_csv(f'data/{selected_subfolder}/actionTrace_Agent_PIC Blaze.csv')
# df2 = pd.read_csv(f'data/{selected_subfolder}/SNLBlaze_acstate.csv')
# df3 = pd.read_csv(f'data/{selected_subfolder}/GCCRaven_acstate.csv')
# exclude = ['Detect_crash','Detect_conflict','Flight_Dynamics','Reroute_flight','Show_radar','Direct_to_waypoint','Change_heading']
# events = parse_action_trace(f'data/{selected_subfolder}/actionTrace.csv', exclude)
def update_graph(selected_subfolder, selected_time, selected_variable):
if selected_subfolder not in data_store:
return go.Figure(), go.Figure(), go.Figure()#go.Figure(layout=dict(autosize=True, width=None, height=300))#, go.Figure()
wnd_action_trace = data_store[selected_subfolder].get('wnd_action_trace', pd.DataFrame())
all_action_trace = data_store[selected_subfolder].get('all_action_trace', [])
aircraft_data = data_store[selected_subfolder].get('aircraft_data', {})
filtered_wnd_action_trace = wnd_action_trace[(wnd_action_trace['time'] >= start_time) & (wnd_action_trace['time'] <= selected_time)]
filtered_all_action_trace = []
for event in all_action_trace:
if (event['start'] - start_date).total_seconds() <= selected_time:
event_copy = event.copy() # Create a local copy of the event
if (event_copy['end'] - start_date).total_seconds() > selected_time:
event_copy['end'] = start_date + datetime.timedelta(seconds=selected_time)
filtered_all_action_trace.append(event_copy)
ac_data = {}
for aircraft_id, df in aircraft_data.items():
filtered_df = df[(df['time'] >= start_time) & (df['time'] <= selected_time)]
ac_data[aircraft_id] = filtered_df
fig1 = go.Figure(layout=dict(autosize=True, width=None, height=500))
fig1['layout']['uirevision'] = 'Hello world!'
fig1 = viz.plot_trajectory(fig1, ac_data)
# fig2 = go.Figure(layout=dict(autosize=True, width=1200, height=1000))
# fig2 = viz.wnd_visualization(fig2, filtered_wnd_action_trace, selected_time, start_time, end_time)
# fig2['layout']['uirevision'] = 'Hello world!'
if selected_variable is not None:
fig3 = go.Figure(layout=dict(autosize=True, width=None, height=300))
for aircraft_id, df in ac_data.items():
fig3.add_trace(go.Scatter(x=df['time'], y=df[selected_variable], mode='lines', name=aircraft_id))
fig3.update_layout(xaxis_title='Real Time', yaxis_title=selected_variable, showlegend=True, xaxis=dict(range=[start_time, end_time]))#, yaxis=dict(range=[0, 1200])
fig3['layout']['uirevision'] = 'Hello world!'
else:
fig3 = go.Figure(layout=dict(autosize=True, width=None, height=300))
fig4 = go.Figure(layout=dict(autosize=True, width=None, height=2100))
fig4 = viz.timeline(fig4, filtered_all_action_trace, selected_time, start_date, end_date)
fig4['layout']['uirevision'] = 'Hello world!'
fig4.update_layout(height=900) # Explicitly set the height again
# return fig2, fig1, fig3#, fig4
return fig1, fig3, fig4
# Run the app
if __name__ == '__main__':
# webbrowser.open_new("http://127.0.0.1:8050/")
# app.run_server(debug=True)
app.run_server(debug=False, host='0.0.0.0', port=7860)