{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n# Advanced interactive visualization\n\nIn DIPY_ we created a thin interface to access many of the capabilities\navailable in the FURY 3D visualization library but tailored to the\nneeds of structural and diffusion imaging.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\nfrom dipy.viz import actor, window, ui" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In ``window`` we have all the objects that connect what needs to be rendered\nto the display or the disk e.g., for saving screenshots. So, there you will\nfind key objects and functions like the ``Scene`` class which holds and\nprovides access to all the actors and the ``show`` function which displays what\nis in the scene on a window. Also, this module provides access to functions\nfor opening/saving dialogs and printing screenshots (see ``snapshot``).\n\nIn the ``actor`` module we can find all the different primitives e.g.,\nstreamtubes, lines, image slices, etc.\n\nIn the ``ui`` module we have some other objects which allow to add buttons\nand sliders and these interact both with windows and actors. Because of this\nthey need input from the operating system so they can process events.\n\nLet's get started. In this tutorial, we will visualize some bundles\ntogether with FA or T1. We will be able to change the slices using\na ``LineSlider2D`` widget.\n\nFirst we need to fetch and load some datasets.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from dipy.tracking.streamline import Streamlines\nfrom dipy.data.fetcher import fetch_bundles_2_subjects, read_bundles_2_subjects\n\nfetch_bundles_2_subjects()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following function outputs a dictionary with the required bundles e.g. ``af\nleft`` (left arcuate fasciculus) and maps, e.g. FA for a specific subject.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "res = read_bundles_2_subjects('subj_1', ['t1', 'fa'],\n ['af.left', 'cst.right', 'cc_1'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will use 3 bundles, FA and the affine transformation that brings the voxel\ncoordinates to world coordinates (RAS 1mm).\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "streamlines = Streamlines(res['af.left'])\nstreamlines.extend(res['cst.right'])\nstreamlines.extend(res['cc_1'])\n\ndata = res['fa']\nshape = data.shape\naffine = res['affine']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With our current design it is easy to decide in which space you want the\nstreamlines and slices to appear. The default we have here is to appear in\nworld coordinates (RAS 1mm).\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "world_coords = True" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we want to see the objects in native space we need to make sure that all\nobjects which are currently in world coordinates are transformed back to\nnative space using the inverse of the affine.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "if not world_coords:\n from dipy.tracking.streamline import transform_streamlines\n streamlines = transform_streamlines(streamlines, np.linalg.inv(affine))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we create, a ``Scene`` object and add the streamlines using the ``line``\nfunction and an image plane using the ``slice`` function.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "scene = window.Scene()\nstream_actor = actor.line(streamlines)\n\nif not world_coords:\n image_actor_z = actor.slicer(data, affine=np.eye(4))\nelse:\n image_actor_z = actor.slicer(data, affine)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also change also the opacity of the slicer.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "slicer_opacity = 0.6\nimage_actor_z.opacity(slicer_opacity)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can add additional slicers by copying the original and adjusting the\n``display_extent``.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "image_actor_x = image_actor_z.copy()\nx_midpoint = int(np.round(shape[0] / 2))\nimage_actor_x.display_extent(x_midpoint,\n x_midpoint, 0,\n shape[1] - 1,\n 0,\n shape[2] - 1)\n\nimage_actor_y = image_actor_z.copy()\ny_midpoint = int(np.round(shape[1] / 2))\nimage_actor_y.display_extent(0,\n shape[0] - 1,\n y_midpoint,\n y_midpoint,\n 0,\n shape[2] - 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Connect the actors with the Scene.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "scene.add(stream_actor)\nscene.add(image_actor_z)\nscene.add(image_actor_x)\nscene.add(image_actor_y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we would like to change the position of each ``image_actor`` using a\nslider. The sliders are widgets which require access to different areas of the\nvisualization pipeline and therefore we don't recommend using them with\n``show``. The more appropriate way is to use them with the ``ShowManager``\nobject which allows accessing the pipeline in different areas. Here is how:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "show_m = window.ShowManager(scene, size=(1200, 900))\nshow_m.initialize()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After we have initialized the ``ShowManager`` we can go ahead and create\nsliders to move the slices and change their opacity.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "line_slider_z = ui.LineSlider2D(min_value=0,\n max_value=shape[2] - 1,\n initial_value=shape[2] / 2,\n text_template=\"{value:.0f}\",\n length=140)\n\nline_slider_x = ui.LineSlider2D(min_value=0,\n max_value=shape[0] - 1,\n initial_value=shape[0] / 2,\n text_template=\"{value:.0f}\",\n length=140)\n\nline_slider_y = ui.LineSlider2D(min_value=0,\n max_value=shape[1] - 1,\n initial_value=shape[1] / 2,\n text_template=\"{value:.0f}\",\n length=140)\n\nopacity_slider = ui.LineSlider2D(min_value=0.0,\n max_value=1.0,\n initial_value=slicer_opacity,\n length=140)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we will write callbacks for the sliders and register them.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def change_slice_z(slider):\n z = int(np.round(slider.value))\n image_actor_z.display_extent(0, shape[0] - 1, 0, shape[1] - 1, z, z)\n\n\ndef change_slice_x(slider):\n x = int(np.round(slider.value))\n image_actor_x.display_extent(x, x, 0, shape[1] - 1, 0, shape[2] - 1)\n\n\ndef change_slice_y(slider):\n y = int(np.round(slider.value))\n image_actor_y.display_extent(0, shape[0] - 1, y, y, 0, shape[2] - 1)\n\n\ndef change_opacity(slider):\n slicer_opacity = slider.value\n image_actor_z.opacity(slicer_opacity)\n image_actor_x.opacity(slicer_opacity)\n image_actor_y.opacity(slicer_opacity)\n\n\nline_slider_z.on_change = change_slice_z\nline_slider_x.on_change = change_slice_x\nline_slider_y.on_change = change_slice_y\nopacity_slider.on_change = change_opacity" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll also create text labels to identify the sliders.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def build_label(text):\n label = ui.TextBlock2D()\n label.message = text\n label.font_size = 18\n label.font_family = 'Arial'\n label.justification = 'left'\n label.bold = False\n label.italic = False\n label.shadow = False\n label.background_color = (0, 0, 0)\n label.color = (1, 1, 1)\n\n return label\n\n\nline_slider_label_z = build_label(text=\"Z Slice\")\nline_slider_label_x = build_label(text=\"X Slice\")\nline_slider_label_y = build_label(text=\"Y Slice\")\nopacity_slider_label = build_label(text=\"Opacity\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we will create a ``panel`` to contain the sliders and labels.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "panel = ui.Panel2D(size=(300, 200),\n color=(1, 1, 1),\n opacity=0.1,\n align=\"right\")\npanel.center = (1030, 120)\n\npanel.add_element(line_slider_label_x, (0.1, 0.75))\npanel.add_element(line_slider_x, (0.38, 0.75))\npanel.add_element(line_slider_label_y, (0.1, 0.55))\npanel.add_element(line_slider_y, (0.38, 0.55))\npanel.add_element(line_slider_label_z, (0.1, 0.35))\npanel.add_element(line_slider_z, (0.38, 0.35))\npanel.add_element(opacity_slider_label, (0.1, 0.15))\npanel.add_element(opacity_slider, (0.38, 0.15))\n\nscene.add(panel)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, we can render all the widgets and everything else in the screen and\nstart the interaction using ``show_m.start()``.\n\n\nHowever, if you change the window size, the panel will not update its position\nproperly. The solution to this issue is to update the position of the panel\nusing its ``re_align`` method every time the window size changes.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "global size\nsize = scene.GetSize()\n\n\ndef win_callback(obj, event):\n global size\n if size != obj.GetSize():\n size_old = size\n size = obj.GetSize()\n size_change = [size[0] - size_old[0], 0]\n panel.re_align(size_change)\n\n\nshow_m.initialize()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, please set the following variable to ``True`` to interact with the\ndatasets in 3D.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "interactive = False\n\nscene.zoom(1.5)\nscene.reset_clipping_range()\n\nif interactive:\n\n show_m.add_window_callback(win_callback)\n show_m.render()\n show_m.start()\n\nelse:\n\n window.record(scene, out_path='bundles_and_3_slices.png', size=(1200, 900),\n reset_camera=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ".. figure:: bundles_and_3_slices.png\n :align: center\n\n A few bundles with interactive slicing.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "del show_m" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## References\n\n.. [Garyfallidis2021] Garyfallidis, Eleftherios, Serge Koudoro, Javier Guaje,\n Marc-Alexandre C\u00f4t\u00e9, Soham Biswas, David Reagan, Nasim Anousheh,\n Filipi Silva, Geoffrey Fox, and Fury Contributors.\n \"FURY: advanced scientific visualization.\" Journal of Open Source Software\n 6 no. 64 (2021): 3384.\n https://doi.org/10.21105/joss.03384\n\n.. include:: ../links_names.inc\n\n\n" ] } ], "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.9.13" } }, "nbformat": 4, "nbformat_minor": 0 }