From 9ad43b4e010675d5931541b1e70c012791ff02ef Mon Sep 17 00:00:00 2001 From: surchs Date: Tue, 15 Dec 2020 15:43:52 -0500 Subject: [PATCH 1/6] simple example for interactive position set - uses input fields to both display and change slider position - uses three viewers for volume --- examples/set_slicer_position_interactively.py | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 examples/set_slicer_position_interactively.py diff --git a/examples/set_slicer_position_interactively.py b/examples/set_slicer_position_interactively.py new file mode 100644 index 0000000..731aff3 --- /dev/null +++ b/examples/set_slicer_position_interactively.py @@ -0,0 +1,77 @@ +import dash +import dash_html_components as html +from dash_slicer import VolumeSlicer +import dash_core_components as dcc +from dash.dependencies import Input, Output, State, ALL +import imageio + + +app = dash.Dash(__name__, update_title=None) + +vol = imageio.volread("imageio:stent.npz") +slicer0 = VolumeSlicer(app, vol, axis=0, scene_id="brain") +slicer1 = VolumeSlicer(app, vol, axis=1, scene_id="brain") +slicer2 = VolumeSlicer(app, vol, axis=2, scene_id="brain") + +setpos_store = dcc.Store( + id={"context": "app", "scene": slicer0.scene_id, "name": "setpos"} +) + +slicer_list = [setpos_store] +for sidx, slicer in enumerate([slicer0, slicer1, slicer2]): + slider = dcc.Slider(id=f"slider-{sidx}", max=slicer.nslices) + slicer_list.append(html.Div([slicer.graph, html.Div([slicer.slider, slider], style={"display": "none"}), *slicer.stores])) + +nav_table = html.Div([ + html.Div(""), + html.Div("Voxel position"), + html.Div("X axis"), + dcc.Input(id="x-nav", type="number", placeholder="X value"), + html.Div("Y axis"), + dcc.Input(id="y-nav", type="number", placeholder="Y value"), + html.Div("Z axis"), + dcc.Input(id="z-nav", type="number", placeholder="Z value"), +], style={"display": "grid", "gridTemplateColumns": "10% 10%"}) +slicer_list.append(nav_table) + +app.layout = html.Div(style={ + "display": "grid", + "gridTemplateColumns": "33% 33% 33%", +}, children=slicer_list) + + +@app.callback( + [Output("slider-0", "value"), + Output("slider-1", "value"), + Output("slider-2", "value")], + [Input(slicer0.slider.id, "drag_value"), + Input(slicer1.slider.id, "drag_value"), + Input(slicer2.slider.id, "drag_value")], +) +def write_to_auxiliary_slider(x_slider, y_slider, z_slider): + return x_slider, y_slider, z_slider + + +@app.callback( + [Output("x-nav", "value"), Output("y-nav", "value"), Output("z-nav", "value")], + [Input("slider-0", "value"), Input("slider-1", "value"), Input("slider-2", "value")], +) +def write_to_position_table(x_val, y_val, z_val): + return x_val, y_val, z_val + + +@app.callback( + Output(setpos_store.id, "data"), + [ + Input("x-nav", "value"), + Input("y-nav", "value"), + Input("z-nav", "value"), + ], +) +def write_table_values_to_slicer(x_pos, y_pos, z_pos): + return z_pos, y_pos, x_pos + + +if __name__ == "__main__": + # Note: dev_tools_props_check negatively affects the performance of VolumeSlicer + app.run_server(debug=True, dev_tools_props_check=False) \ No newline at end of file From 995c06c09101c93aab92321d0535781338b04adc Mon Sep 17 00:00:00 2001 From: surchs Date: Tue, 15 Dec 2020 16:03:06 -0500 Subject: [PATCH 2/6] add comments, run doc also unhide the sliders --- examples/set_slicer_position_interactively.py | 72 +++++++++++-------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/examples/set_slicer_position_interactively.py b/examples/set_slicer_position_interactively.py index 731aff3..32954cb 100644 --- a/examples/set_slicer_position_interactively.py +++ b/examples/set_slicer_position_interactively.py @@ -17,56 +17,72 @@ id={"context": "app", "scene": slicer0.scene_id, "name": "setpos"} ) +# Here we create an auxiliary slider for each slicer and encapsulate it inside a Div +# to be added to the app layout slicer_list = [setpos_store] for sidx, slicer in enumerate([slicer0, slicer1, slicer2]): slider = dcc.Slider(id=f"slider-{sidx}", max=slicer.nslices) - slicer_list.append(html.Div([slicer.graph, html.Div([slicer.slider, slider], style={"display": "none"}), *slicer.stores])) + slicer_list.append(html.Div([slicer.graph, slicer.slider, slider, *slicer.stores,])) -nav_table = html.Div([ - html.Div(""), - html.Div("Voxel position"), - html.Div("X axis"), - dcc.Input(id="x-nav", type="number", placeholder="X value"), - html.Div("Y axis"), - dcc.Input(id="y-nav", type="number", placeholder="Y value"), - html.Div("Z axis"), - dcc.Input(id="z-nav", type="number", placeholder="Z value"), -], style={"display": "grid", "gridTemplateColumns": "10% 10%"}) +# Create a small CSS grid with Input fields and text labels to both display +# the slicer axis positions and allow the user to interactively change them +nav_table = html.Div( + [ + html.Div(""), + html.Div("Voxel position"), + html.Div("X axis"), + dcc.Input(id="x-nav", type="number", placeholder="X value"), + html.Div("Y axis"), + dcc.Input(id="y-nav", type="number", placeholder="Y value"), + html.Div("Z axis"), + dcc.Input(id="z-nav", type="number", placeholder="Z value"), + ], + style={"display": "grid", "gridTemplateColumns": "10% 10%"}, +) slicer_list.append(nav_table) -app.layout = html.Div(style={ - "display": "grid", - "gridTemplateColumns": "33% 33% 33%", -}, children=slicer_list) +app.layout = html.Div( + style={"display": "grid", "gridTemplateColumns": "33% 33% 33%",}, + children=slicer_list, +) +# Take the current value from the main slider and copy it to the +# auxiliary slider @app.callback( - [Output("slider-0", "value"), - Output("slider-1", "value"), - Output("slider-2", "value")], - [Input(slicer0.slider.id, "drag_value"), - Input(slicer1.slider.id, "drag_value"), - Input(slicer2.slider.id, "drag_value")], + [ + Output("slider-0", "value"), + Output("slider-1", "value"), + Output("slider-2", "value"), + ], + [ + Input(slicer0.slider.id, "drag_value"), + Input(slicer1.slider.id, "drag_value"), + Input(slicer2.slider.id, "drag_value"), + ], ) def write_to_auxiliary_slider(x_slider, y_slider, z_slider): return x_slider, y_slider, z_slider +# Write the values of the axiliary slider to the input fields in the navigation table @app.callback( [Output("x-nav", "value"), Output("y-nav", "value"), Output("z-nav", "value")], - [Input("slider-0", "value"), Input("slider-1", "value"), Input("slider-2", "value")], + [ + Input("slider-0", "value"), + Input("slider-1", "value"), + Input("slider-2", "value"), + ], ) def write_to_position_table(x_val, y_val, z_val): return x_val, y_val, z_val +# Listen for a user-triggered change to the value of the Input fields in the navigation table +# and set the position of the slicer accordingly @app.callback( Output(setpos_store.id, "data"), - [ - Input("x-nav", "value"), - Input("y-nav", "value"), - Input("z-nav", "value"), - ], + [Input("x-nav", "value"), Input("y-nav", "value"), Input("z-nav", "value"),], ) def write_table_values_to_slicer(x_pos, y_pos, z_pos): return z_pos, y_pos, x_pos @@ -74,4 +90,4 @@ def write_table_values_to_slicer(x_pos, y_pos, z_pos): if __name__ == "__main__": # Note: dev_tools_props_check negatively affects the performance of VolumeSlicer - app.run_server(debug=True, dev_tools_props_check=False) \ No newline at end of file + app.run_server(debug=True, dev_tools_props_check=False) From 9cb930d2fd34a831fded340da4366fbc963d8b00 Mon Sep 17 00:00:00 2001 From: surchs Date: Tue, 15 Dec 2020 16:18:37 -0500 Subject: [PATCH 3/6] some verbose labels in the app --- examples/set_slicer_position_interactively.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/examples/set_slicer_position_interactively.py b/examples/set_slicer_position_interactively.py index 32954cb..a531b82 100644 --- a/examples/set_slicer_position_interactively.py +++ b/examples/set_slicer_position_interactively.py @@ -22,7 +22,19 @@ slicer_list = [setpos_store] for sidx, slicer in enumerate([slicer0, slicer1, slicer2]): slider = dcc.Slider(id=f"slider-{sidx}", max=slicer.nslices) - slicer_list.append(html.Div([slicer.graph, slicer.slider, slider, *slicer.stores,])) + slicer_list.append( + html.Div( + [ + html.Pre("slicer graph"), + slicer.graph, + html.Pre("builtin slider"), + slicer.slider, + html.Pre("auxiliary slider"), + slider, + *slicer.stores, + ] + ) + ) # Create a small CSS grid with Input fields and text labels to both display # the slicer axis positions and allow the user to interactively change them From 64e942af342291cab1c43ed340e27b97c2888ade Mon Sep 17 00:00:00 2001 From: surchs Date: Wed, 16 Dec 2020 11:17:23 -0500 Subject: [PATCH 4/6] removed unused imports and short description --- examples/set_slicer_position_interactively.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/set_slicer_position_interactively.py b/examples/set_slicer_position_interactively.py index a531b82..aead65c 100644 --- a/examples/set_slicer_position_interactively.py +++ b/examples/set_slicer_position_interactively.py @@ -1,8 +1,12 @@ +""" +This is an example that shows how the slicer position can be both read from and written to +by listening to the "drag_value" of the default slider with an auxiliary slider +""" import dash import dash_html_components as html from dash_slicer import VolumeSlicer import dash_core_components as dcc -from dash.dependencies import Input, Output, State, ALL +from dash.dependencies import Input, Output import imageio From c605cdf3e3c7652a6fd3c4185fe788e972ae4326 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Thu, 17 Dec 2020 10:55:04 +0100 Subject: [PATCH 5/6] black --- examples/set_slicer_position_interactively.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/set_slicer_position_interactively.py b/examples/set_slicer_position_interactively.py index aead65c..69a0cd7 100644 --- a/examples/set_slicer_position_interactively.py +++ b/examples/set_slicer_position_interactively.py @@ -58,7 +58,10 @@ slicer_list.append(nav_table) app.layout = html.Div( - style={"display": "grid", "gridTemplateColumns": "33% 33% 33%",}, + style={ + "display": "grid", + "gridTemplateColumns": "33% 33% 33%", + }, children=slicer_list, ) @@ -98,7 +101,11 @@ def write_to_position_table(x_val, y_val, z_val): # and set the position of the slicer accordingly @app.callback( Output(setpos_store.id, "data"), - [Input("x-nav", "value"), Input("y-nav", "value"), Input("z-nav", "value"),], + [ + Input("x-nav", "value"), + Input("y-nav", "value"), + Input("z-nav", "value"), + ], ) def write_table_values_to_slicer(x_pos, y_pos, z_pos): return z_pos, y_pos, x_pos From 451b317ab1ea2a3819e1d040ad52b8c324f5805d Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Thu, 17 Dec 2020 10:55:38 +0100 Subject: [PATCH 6/6] flake --- examples/set_slicer_position_interactively.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/set_slicer_position_interactively.py b/examples/set_slicer_position_interactively.py index 69a0cd7..dde75a9 100644 --- a/examples/set_slicer_position_interactively.py +++ b/examples/set_slicer_position_interactively.py @@ -1,7 +1,8 @@ """ -This is an example that shows how the slicer position can be both read from and written to +This is an example that shows how the slicer position can be both read from and written to by listening to the "drag_value" of the default slider with an auxiliary slider """ + import dash import dash_html_components as html from dash_slicer import VolumeSlicer