-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathdash_app.py
144 lines (116 loc) · 4.1 KB
/
dash_app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import dash
from dash import dcc, html
import plotly.graph_objects as go
import zmq
import json
import threading
import plotly
import argparse
import subprocess
import os
def create_dash_app():
"""
Create the dash app with 3 plot layout: 3d plot, error curves and the final fit.
return: the dash app
"""
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.Div(children=[
dcc.Graph(id='3d-plot', style={'display': 'inline-block'}),
dcc.Graph(id='error-curves', style={'display': 'inline-block'}),
dcc.Graph(id='final-fit', style={'display': 'inline-block'})
])
]
)
return app
def create_zmq_socket():
"""
Set up the ZeroMQ socket for receiving data from
the optimization script.
"""
context = zmq.Context()
socket = context.socket(zmq.SUB)
# Bind to the address where the optim script sends data
socket.bind("tcp://127.0.0.1:5555")
# Subscribe to all topics
socket.setsockopt(zmq.SUBSCRIBE, b"")
return socket
def create_thread(socket,app):
"""
Create a background thread to continuously update the figure.
:param socket: the zmq socket
:param app: the dash app
"""
thread = threading.Thread(target=update_figure, args=(socket,app)) #,outq))
thread.daemon = True
thread.start()
def update_figure(socket,app):
"""
Update the dash app figures with the data
received from the optimization script.
:param socket: the zmq socket
:param app: the dash app
"""
while True:
try:
msg = socket.recv_string()
data = json.loads(msg)
fig = plotly.io.from_json(data)
type_of_plot = fig.data[0].meta["dash_plot"]
if type_of_plot == "3d-plot":
app.layout['3d-plot'].figure = fig
elif type_of_plot == "error-curves":
app.layout['error-curves'].figure = fig
elif type_of_plot == "final-fit":
app.layout['final-fit'].figure = fig
# return fig
except Exception as e:
print("Error while updating figure:", e)
def run_dash_app_as_subprocess(port):
"""
Run dahs app in a subprocess.
:param port: port to run the dash app on.
The visualization should be available at localhost:<port>
If running on a remote server, make sure to forward the port
locally.
"""
dash_app_process = subprocess.Popen(["python",
"dash_app.py",
"--port",
f"{port}"],
stderr=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
# text=True
)
dash_app_process_pid = dash_app_process.pid
return dash_app_process, dash_app_process_pid
def terminate_dash_app_subprocess(dash_app_process,dash_app_process_pid):
"""
Terminate the dash app subprocess asking nicely first, then forcefully.
:param dash_app_process: the dash app subprocess
:param dash_app_process_pid: the pid of the dash app subprocess
"""
# ask nicely first
dash_app_process.kill()
outs, errs = dash_app_process.communicate()
if outs:
print("Dash app not terminated - outs:")
print(outs)
if errs:
print("Dash app not terminated - errors:")
print(errs)
# if dash app is still running, kill it forcefully
dash_app_is_still_running = dash_app_process.poll() is None
if dash_app_is_still_running:
print("Forcefully terminating dash app.")
os.kill(dash_app_process_pid, 0)
print("Dash app terminated.")
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("--port", type=int, default=8050)
args = parser.parse_args()
app = create_dash_app()
socket = create_zmq_socket()
create_thread(socket,app)
app.run_server(debug=False, host='localhost', port=args.port)