{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Animating slices of a human head MRI scan, using Plotly FigureWidget" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import plotly.graph_objects as go\n", "import numpy as np\n", "from skimage import io" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Read the volume data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "volume = (io.imread(\"https://s3.amazonaws.com/assets.datacamp.com/blog_assets/attention-mri.tif\")).T\n", "volume.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_slices = volume.shape[0]\n", "r, c = volume[0].shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Set the height of the first slice to be visualized:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "height = (volume.shape[0]-1) / 10\n", "grid = np.linspace(0, height, n_slices)\n", "slice_step = grid[1] - grid[0]\n", "slice_step" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The normalized slice values are color-mapped to the bone colorscale (the usual colorscale for medical images)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pl_bone=[[0.0, 'rgb(0, 0, 0)'],\n", " [0.05, 'rgb(10, 10, 14)'],\n", " [0.1, 'rgb(21, 21, 30)'],\n", " [0.15, 'rgb(33, 33, 46)'],\n", " [0.2, 'rgb(44, 44, 62)'],\n", " [0.25, 'rgb(56, 55, 77)'],\n", " [0.3, 'rgb(66, 66, 92)'],\n", " [0.35, 'rgb(77, 77, 108)'],\n", " [0.4, 'rgb(89, 92, 121)'],\n", " [0.45, 'rgb(100, 107, 132)'],\n", " [0.5, 'rgb(112, 123, 143)'],\n", " [0.55, 'rgb(122, 137, 154)'],\n", " [0.6, 'rgb(133, 153, 165)'],\n", " [0.65, 'rgb(145, 169, 177)'],\n", " [0.7, 'rgb(156, 184, 188)'],\n", " [0.75, 'rgb(168, 199, 199)'],\n", " [0.8, 'rgb(185, 210, 210)'],\n", " [0.85, 'rgb(203, 221, 221)'],\n", " [0.9, 'rgb(220, 233, 233)'],\n", " [0.95, 'rgb(238, 244, 244)'],\n", " [1.0, 'rgb(255, 255, 255)']]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "initial_slice = dict(type='surface',\n", " z=height*np.ones((r,c)),\n", " surfacecolor=np.flipud(volume[-1]),\n", " colorscale=pl_bone,\n", " showscale=False\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Define the plot layout:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layout3d = dict(title='Head Scanning', \n", " font=dict(family='Balto'),\n", " width=650,\n", " height=650,\n", " scene=dict(zaxis=dict(range=[-0.1, 6.8], autorange=False)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fw = go.FigureWidget(data=[initial_slice], layout=layout3d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#fw" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import ipywidgets as iw\n", "slider = iw.IntSlider(value=0, min=0, \n", " max=n_slices-1, step=1, \n", " description='slice #')\n", "slider.layout = dict(margin='1px 80px 40px 5px', width='400px')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def slice_changed(change):\n", " k=slider.value\n", " fw.data[0].update(z=(height-slice_step*k)*np.ones((r,c)),\n", " surfacecolor=np.flipud(volume[-1-k]))\n", " \n", "\n", "slider.observe(slice_changed, 'value')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#help(iw.Play)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "play_button = iw.Play(value=0, min=0, max=n_slices-1,\n", " step=1, interval=40)\n", "play_button.layout = dict(margin='1px 10px 50px 100px')\n", "iw.jslink((play_button, 'value'), (slider, 'value'))\n", "iw.VBox([fw, iw.HBox([play_button, slider])])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Animation without slider (watch the above cell):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for k in range(n_slices):\n", " with fw.batch_animate(duration=10, easing='cubic-in-out'):\n", " fw.data[0].update(z=(height-k*slice_step)*np.ones((r,c)),\n", " surfacecolor=np.flipud(volume[-1-k]))\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It seems that the latter animation (without slider) is faster." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For a much faster animation see this gif file created from the fig generated in the notebook `scanning-the-head-classic.ipynb`:" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.3" } }, "nbformat": 4, "nbformat_minor": 2 }