|
9 | 9 | from dash_slicer import VolumeSlicer
|
10 | 10 | from dash.dependencies import Input, Output, State, ALL
|
11 | 11 | import imageio
|
| 12 | +from skimage import measure |
12 | 13 |
|
13 | 14 |
|
14 | 15 | app = dash.Dash(__name__, update_title=None)
|
|
67 | 68 | dcc.Graph(id="3Dgraph", figure=go.Figure()),
|
68 | 69 | ]
|
69 | 70 | ),
|
| 71 | + html.Div( |
| 72 | + [ |
| 73 | + html.Div("Threshold level"), |
| 74 | + dcc.Slider(id="level", max=2000, value=500), |
| 75 | + html.Div("Contrast limits"), |
| 76 | + dcc.RangeSlider(id="clim", max=2000, value=(0, 800)), |
| 77 | + ] |
| 78 | + ), |
70 | 79 | dcc.Markdown(
|
71 | 80 | """
|
72 | 81 | Take note of:
|
73 |
| - * Full-res thumbnails for axis 0. |
74 |
| - * Very low-res thumbnails for axis 1. |
75 |
| - * Default low-res thumbnails for axis 2. |
76 |
| - * The `reverse_y` is false for axis 1. |
77 |
| - * Elongated voxels for axis 1 and 2. |
78 |
| - * An origin in the thousands, visible in the 3D view. |
79 |
| - * A custom brighter green for axis 2. |
| 82 | +
|
| 83 | + Axis 0: |
| 84 | + * Full-res thumbnails. |
| 85 | +
|
| 86 | + Axis 1: |
| 87 | + * Very low-res thumbnails. |
| 88 | + * Elongated voxels. |
| 89 | + * The `reverse_y` is false. |
| 90 | + * Yellow overlay based on threshold. |
| 91 | +
|
| 92 | + Axis 2: |
| 93 | + * Default low-res thumbnails. |
| 94 | + * Elongated voxels. |
| 95 | + * Yellow contour based on threshold.. |
| 96 | + * A custom brighter green indicator. |
| 97 | +
|
| 98 | + 3D view: |
| 99 | + * An origin in the thousands. |
| 100 | +
|
80 | 101 | """
|
81 | 102 | ),
|
82 | 103 | ],
|
|
101 | 122 | let s = {
|
102 | 123 | type: 'scatter3d',
|
103 | 124 | x: xyz[0], y: xyz[1], z: xyz[2],
|
104 |
| - mode: 'lines', line: {color: state.color} |
| 125 | + mode: 'lines', line: {color: state.color}, |
| 126 | + hoverinfo: 'skip', |
| 127 | + showlegend: false, |
105 | 128 | };
|
106 | 129 | traces.push(s);
|
107 | 130 | }
|
|
116 | 139 | )
|
117 | 140 |
|
118 | 141 |
|
| 142 | +# Callback to add overlay in axis 1 |
| 143 | +@app.callback( |
| 144 | + Output(slicer1.overlay_data.id, "data"), |
| 145 | + [Input("level", "value")], |
| 146 | +) |
| 147 | +def update_overlay(level): |
| 148 | + return slicer1.create_overlay_data(vol > level, "#ffff00") |
| 149 | + |
| 150 | + |
| 151 | +# Callback to add contours in axes 2 |
| 152 | +@app.callback( |
| 153 | + Output(slicer2.extra_traces.id, "data"), |
| 154 | + [Input(slicer2.state.id, "data"), Input("level", "value")], |
| 155 | +) |
| 156 | +def update_contour(state, level): |
| 157 | + if not state: |
| 158 | + return dash.no_update |
| 159 | + slice = vol[:, :, state["index"]] |
| 160 | + contours = measure.find_contours(slice, level) |
| 161 | + traces = [] |
| 162 | + for contour in contours: |
| 163 | + traces.append( |
| 164 | + { |
| 165 | + "type": "scatter", |
| 166 | + "mode": "lines", |
| 167 | + "line": {"color": "yellow", "width": 3}, |
| 168 | + "x": contour[:, 1] * spacing[1] + ori[1], |
| 169 | + "y": contour[:, 0] * spacing[0] + ori[0], |
| 170 | + "hoverinfo": "skip", |
| 171 | + "showlegend": False, |
| 172 | + } |
| 173 | + ) |
| 174 | + return traces |
| 175 | + |
| 176 | + |
| 177 | +# Callback to set contrast limits |
| 178 | +@app.callback( |
| 179 | + [ |
| 180 | + Output(slicer0.clim.id, "data"), |
| 181 | + Output(slicer1.clim.id, "data"), |
| 182 | + Output(slicer2.clim.id, "data"), |
| 183 | + ], |
| 184 | + [Input("clim", "value")], |
| 185 | +) |
| 186 | +def update_clim(clim): |
| 187 | + return [clim, clim, clim] |
| 188 | + |
| 189 | + |
119 | 190 | if __name__ == "__main__":
|
120 | 191 | # Note: dev_tools_props_check negatively affects the performance of VolumeSlicer
|
121 | 192 | app.run_server(debug=True, dev_tools_props_check=False)
|
0 commit comments