Hello,
I have been trying to develop a web app with dash slicer to view Nifti MRI files. I have had great success using the following code and specifying a path to the file on my local machine:
import dash
import dash_html_components as html
from dash_slicer import VolumeSlicer
import numpy as np
import dash_bootstrap_components as dbc
import dash_html_components as html
from nilearn import image
external_stylesheets = [dbc.themes.BOOTSTRAP]
app = dash.Dash(__name__, update_title=None, external_stylesheets=external_stylesheets)
server = app.server
img = image.load_img("Nifti/file/path.nii.gz")
img = img.get_data()
print(img.shape)
img = np.copy(np.moveaxis(img, -1, 0))[:, ::-1]
img = np.copy(np.rot90(img, 3, axes= (1,2)))
slicer1 = VolumeSlicer(app, img, axis=0, thumbnail=False)
card = dbc.Card(
[
dbc.CardHeader("Axial"),
dbc.CardBody([slicer1.graph, slicer1.slider, *slicer1.stores]),
]
)
app.layout = html.Div(
[
dbc.Container(
[
dbc.Row(dbc.Col(card))
],
fluid=True,
)
]
)
if __name__ == "__main__":
app.run_server(debug=True, dev_tools_props_check=False)
Instead of specifying the path on my local machine, I am interested in integrating the dcc.Upload component to allow users to drag and drop a file and invoke the dash-slicer on that file. Here is the code I have developed so far using the callback functionality to allow the app to react to the file input:
import base64
import os
import dash
import dash_core_components as dcc
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output
from dash_slicer import VolumeSlicer
import numpy as np
from nilearn import image
from logging import StringTemplateStyle
external_stylesheets = ['https://cdn.jsdelivr.net/npm/bootswatch@4.5.2/dist/cyborg/bootstrap.min.css']
UPLOAD_DIRECTORY = "/project/app_uploaded_files"
if not os.path.exists(UPLOAD_DIRECTORY):
os.makedirs(UPLOAD_DIRECTORY)
app = dash.Dash(__name__, update_title=None, external_stylesheets=external_stylesheets)
server = app.server
app.layout = html.Div(
[
html.H2("Dash-Slicer Upload"),
dcc.Upload(
id="upload-data",
children=html.Div(
["Drag and drop a Nifti file"]
),
style={
"width": "100%",
"height": "60px",
"lineHeight": "60px",
"borderWidth": "1px",
"borderStyle": "dashed",
"borderRadius": "5px",
"textAlign": "center",
"margin": "10px",
},
multiple=True,
),
html.Div(id='slicer-div', children = []),
],
style={"max-width": "500px"},
)
def save_file(name, content):
"""Decode and store an uploaded file."""
data = content.encode("utf8").split(b";base64,")[1]
with open(os.path.join(UPLOAD_DIRECTORY, name), "wb") as fp:
fp.write(base64.decodebytes(data))
@app.callback(
Output(component_id="slicer-div", component_property="children"),
[Input(component_id="upload-data", component_property="filename"),
Input(component_id="upload-data", component_property="contents")],
)
def update_output(uploaded_filename, uploaded_file_contents):
"""Save uploaded files, display file name, display slicer-div component"""
if uploaded_file_contents is None:
return [html.H6("No file currently uploaded")]
else:
for name, data in zip(uploaded_filename, uploaded_file_contents):
save_file(name, data)
print(os.listdir(UPLOAD_DIRECTORY))
location = f"{UPLOAD_DIRECTORY}" + '/' + f"{uploaded_filename[0]}"
print(location)
img = image.load_img(location)
img = img.get_fdata()
img = np.copy(np.moveaxis(img, -1, 0))[:, ::-1]
img = np.copy(np.rot90(img, 3, axes= (1,2)))
slicer1 = VolumeSlicer(app, img, axis=0, thumbnail=False)
card = dbc.Card(
[
dbc.CardHeader('Axial'),
dbc.CardBody([slicer1.graph, slicer1.slider, *slicer1.stores]),
]
)
return [
html.Li(uploaded_filename),
html.Div(location),
html.Div(
[
dbc.Container(
[
dbc.Row(dbc.Col(card))
],
fluid=True,
)
]
)
]
if __name__ == "__main__":
app.run_server(debug=True)
This nearly works, as the ‘slicer-div’ updates when a file is added with the card containing the slicer. However, the dash slicer in the browser is blank with no actual images being shown, although the slider is present at the bottom of the screen. I get the impression that I am not using the callback functionality in the appropriate way to make this work. Any ideas on how this could be improved so that the dash slicer loads the images would be greatly appreciated!
Thanks in advance.