-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.py
367 lines (327 loc) · 14.3 KB
/
index.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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# Copyright (c) 2022, Colas Droin. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
""" This module is where the app layout is created: the main container, the sidebar and the
different pages. All the dcc.store, used to store client data across pages, are created here. It is
also here that the URL routing is done.
"""
# ==================================================================================================
# --- Imports
# ==================================================================================================
# Standard modules
import dash_bootstrap_components as dbc
from dash import dcc, html
from dash.dependencies import Input, Output, State
import uuid
import logging
import dash_mantine_components as dmc
# LBAE modules
from app import app, data, atlas
from pages import (
sidebar,
home,
load_slice,
lipid_selection,
region_analysis,
threeD_exploration,
scRNAseq,
)
from in_app_documentation.documentation import return_documentation
from config import basic_config
from modules.tools.misc import logmem
# ==================================================================================================
# --- App layout
# ==================================================================================================
def return_main_content():
"""This function compute the elements of the app that are shared across pages, including all the
dcc.store.
Returns:
(html.Div): A div containing the corresponding elements.
"""
# List of empty lipid indexes for the dropdown of page 4, assuming brain 1 is initially selected
empty_lipid_list = [-1 for i in data.get_slice_list(indices="brain_1")]
# Record session id in case sessions need to be individualized
session_id = str(uuid.uuid4())
# Define static content
main_content = html.Div(
children=[
# To handle url since multi-page app
dcc.Location(id="url", refresh=False),
# Record session id, useful to trigger callbacks at initialization
dcc.Store(id="session-id", data=session_id),
# Record the slider index
dcc.Store(id="main-slider", data=1),
# Record the state of the range sliders for low and high resolution spectra in page 2
dcc.Store(id="boundaries-low-resolution-mz-plot"),
dcc.Store(id="boundaries-high-resolution-mz-plot"),
# Record the lipids selected in page 2
dcc.Store(id="page-2-selected-lipid-1", data=-1),
dcc.Store(id="page-2-selected-lipid-2", data=-1),
dcc.Store(id="page-2-selected-lipid-3", data=-1),
dcc.Store(id="page-2-last-selected-lipids", data=[]),
# Record the lipids selected in page 4
dcc.Store(id="page-4-selected-lipid-1", data=empty_lipid_list),
dcc.Store(id="page-4-selected-lipid-2", data=empty_lipid_list),
dcc.Store(id="page-4-selected-lipid-3", data=empty_lipid_list),
dcc.Store(id="page-4-last-selected-regions", data=[]),
dcc.Store(id="page-4-selected-region-1", data=""),
dcc.Store(id="page-4-selected-region-2", data=""),
dcc.Store(id="page-4-selected-region-3", data=""),
dcc.Store(id="page-4-last-selected-lipids", data=[]),
# Record the shapes drawn in page 3
dcc.Store(id="dcc-store-color-mask", data=[]),
dcc.Store(id="dcc-store-reset", data=False),
dcc.Store(id="dcc-store-shapes-and-masks", data=[]),
dcc.Store(id="dcc-store-list-idx-lipids", data=[]),
# Record the annotated paths drawn in page 3
dcc.Store(id="page-3-dcc-store-path-heatmap"),
dcc.Store(id="page-3-dcc-store-basic-figure", data=True),
# Record the computed spectra drawn in page 3
dcc.Store(id="dcc-store-list-mz-spectra", data=[]),
# Record the lipids expressed in the region in page 3
dcc.Store(id="page-3-dcc-store-lipids-region", data=[]),
# Actual app layout
html.Div(
children=[
sidebar.layout,
html.Div(id="content"),
dmc.Center(
id="main-paper-slider",
style={
"position": "fixed",
"bottom": "1rem",
"height": "3rem",
"left": "7rem",
"right": "1rem",
"background-color": "rgba(0, 0, 0, 0.0)",
},
children=[
dmc.Text(
id="main-text-slider",
children="Rostro-caudal coordinate (mm): ",
class_name="pr-4",
size="sm",
),
dmc.Slider(
id="main-slider-1",
min=data.get_slice_list(indices="brain_1")[0],
max=data.get_slice_list(indices="brain_1")[-1],
step=1,
marks=[
{
"value": slice_index,
# Use x coordinate for label
"label": "{:.2f}".format(
atlas.l_original_coor[slice_index - 1][0, 0][0]
),
}
for slice_index in data.get_slice_list(indices="brain_1")[::3]
],
size="xs",
value=data.get_slice_list(indices="brain_1")[0],
color="cyan",
class_name="mt-2 mr-5 ml-2 mb-1 w-50",
),
dmc.Slider(
id="main-slider-2",
min=data.get_slice_list(indices="brain_2")[0],
max=data.get_slice_list(indices="brain_2")[-1],
step=1,
marks=[
{
"value": slice_index,
# Use x coordinate for label
"label": "{:.2f}".format(
atlas.l_original_coor[slice_index - 1][0, 0][0]
),
}
for slice_index in data.get_slice_list(indices="brain_2")[::3]
],
size="xs",
value=data.get_slice_list(indices="brain_2")[0],
color="cyan",
class_name="mt-2 mr-5 ml-2 mb-1 w-50 d-none",
),
dmc.Chips(
id="main-brain",
data=[
{"value": "brain_1", "label": "Brain 1"},
{"value": "brain_2", "label": "Brain 2"},
],
value="brain_1",
class_name="pl-2 pt-1",
color="cyan",
),
],
),
# Documentation in a bottom drawer
dmc.Drawer(
children=return_documentation(app),
id="documentation-offcanvas",
# title="LBAE documentation",
opened=False,
padding="md",
size="90vh",
position="bottom",
),
# Spinner when switching pages
dbc.Spinner(
id="main-spinner",
color="light",
children=html.Div(id="empty-content"),
fullscreen=True,
fullscreen_style={"left": "6rem", "background-color": "#1d1c1f"},
spinner_style={"width": "6rem", "height": "6rem"},
delay_hide=100,
),
],
),
],
)
return main_content
def return_validation_layout(main_content, initial_slice=1, brain="brain_1"):
"""This function compute the layout of the app, including the main container, the sidebar and
the different pages.
Args:
main_content (html.Div): A div containing the elements of the app that are shared across
pages.
initial_slice (int): Index of the slice to be displayed at launch.
Returns:
(html.Div): A div containing the layout of the app.
"""
return html.Div(
[
main_content,
home.layout,
load_slice.return_layout(basic_config, initial_slice),
lipid_selection.return_layout(basic_config, initial_slice),
region_analysis.return_layout(basic_config, initial_slice),
threeD_exploration.return_layout(basic_config, initial_slice),
scRNAseq.return_layout(basic_config, initial_slice, brain),
]
)
# ==================================================================================================
# --- App callbacks
# ==================================================================================================
@app.callback(
Output("content", "children"),
Output("empty-content", "children"),
Input("url", "pathname"),
State("main-slider", "data"),
State("main-brain", "value"),
)
def render_page_content(pathname, slice_index, brain):
"""This callback is used as a URL router."""
# Keep track of the page in the console
if pathname is not None:
logging.info("Page" + pathname + " has been selected" + logmem())
# Set the content according to the current pathname
if pathname == "/":
page = home.layout
elif pathname == "/load-slice":
page = load_slice.return_layout(basic_config, slice_index)
elif pathname == "/lipid-selection":
page = lipid_selection.return_layout(basic_config, slice_index)
elif pathname == "/region-analysis":
page = region_analysis.return_layout(basic_config, slice_index)
elif pathname == "/3D-exploration":
page = threeD_exploration.return_layout(basic_config, slice_index)
elif pathname == "/gene-data":
page = scRNAseq.return_layout(basic_config, slice_index, brain)
else:
# If the user tries to reach a different page, return a 404 message
page = dmc.Center(
dmc.Alert(
title="404: Not found",
children=f"The pathname {pathname} was not recognised...",
color="red",
class_name="mt-5",
),
class_name="mt-5",
)
return page, ""
@app.callback(
Output("documentation-offcanvas", "opened"),
[
Input("sidebar-documentation", "n_clicks"),
],
[State("documentation-offcanvas", "opened")],
)
def toggle_collapse(n1, is_open):
"""This callback triggers the modal windows that toggles the documentation when clicking on the
corresponding button."""
if n1:
return not is_open
return is_open
@app.callback(
Output("main-paper-slider", "class_name"), Input("url", "pathname"), prevent_initial_call=False
)
def hide_slider(pathname):
"""This callback is used to hide the slider div when the user is on a page that does not need it.
"""
# Pages in which the slider is displayed
l_path_with_slider = [
"/load-slice",
"/lipid-selection",
"/region-analysis",
"/3D-exploration",
"/gene-data",
]
# Set the content according to the current pathname
if pathname in l_path_with_slider:
return ""
else:
return "d-none"
@app.callback(
Output("main-slider-1", "style"),
Output("main-slider-2", "style"),
Output("main-text-slider", "style"),
Input("url", "pathname"),
prevent_initial_call=False,
)
def hide_slider_but_leave_brain(pathname):
"""This callback is used to hide the slider but leave brain chips when needed."""
# Pages in which the slider is displayed
l_path_without_slider_but_with_brain = [
"/3D-exploration",
"/gene-data",
]
# Set the content according to the current pathname
if pathname in l_path_without_slider_but_with_brain:
return {"visibility": "hidden"}, {"visibility": "hidden"}, {"visibility": "hidden"}
else:
return {}, {}, {}
@app.callback(
Output("main-slider-1", "class_name"),
Output("main-slider-2", "class_name"),
Output("main-slider-1", "value"),
Output("main-slider-2", "value"),
Input("main-brain", "value"),
State("main-slider-1", "value"),
State("main-slider-2", "value"),
prevent_initial_call=False,
)
def hide_useless_slider(brain, value_1, value_2):
"""This callback is used to update the slider indices with the selected brain."""
if brain == "brain_1":
value_1 = value_2 - data.get_slice_list(indices="brain_1")[-1]
return "mt-2 mr-5 ml-2 mb-1 w-50", "mt-2 mr-5 ml-2 mb-1 w-50 d-none", value_1, value_2
elif brain == "brain_2":
value_2 = value_1 + data.get_slice_list(indices="brain_1")[-1]
return "mt-2 mr-5 ml-2 mb-1 w-50 d-none", "mt-2 mr-5 ml-2 mb-1 w-50", value_1, value_2
app.clientside_callback(
"""
function(value_1, value_2, brain){
if(brain == 'brain_1'){
return value_1;
}
else if(brain == 'brain_2'){
return value_2;
}
}
""",
Output("main-slider", "data"),
Input("main-slider-1", "value"),
Input("main-slider-2", "value"),
State("main-brain", "value"),
)
"""This clientside callback is used to update the slider indices with the selected brain."""