# Ipyvolume¶

IPyvolume is a Python library to visualize 3d volumes and glyphs (e.g. 3d scatter plots), in the Jupyter notebook, with minimal configuration and effort. It is currently pre-1.0, so use at own risk. IPyvolume’s volshow is to 3d arrays what matplotlib’s imshow is to 2d arrays.

Other (more mature but possibly more difficult to use) related packages are yt, VTK and/or Mayavi.

Feedback and contributions are welcome: Github, Email or Twitter.

## Quick intro¶

### Volume¶

For quick resuls, use ipyvolume.widgets.quickvolshow. From a numpy array, we create two boxes, using slicing, and visualize it.

import numpy as np
import ipyvolume as ipv
V = np.zeros((128,128,128)) # our 3d array
# outer box
V[30:-30,30:-30,30:-30] = 0.75
V[35:-35,35:-35,35:-35] = 0.0
# inner box
V[50:-50,50:-50,50:-50] = 0.25
V[55:-55,55:-55,55:-55] = 0.0
ipv.quickvolshow(V, level=[0.25, 0.75], opacity=0.03, level_width=0.1, data_min=0, data_max=1)


### Scatter plot¶

Simple scatter plots are also supported.

import ipyvolume as ipv
import numpy as np
x, y, z = np.random.random((3, 10000))
ipv.quickscatter(x, y, z, size=1, marker="sphere")


### Quiver plot¶

Quiver plots are also supported, showing a vector at each point.

import ipyvolume as ipv
import numpy as np
x, y, z, u, v, w = np.random.random((6, 1000))*2-1
quiver = ipv.quickquiver(x, y, z, u, v, w, size=5)


### Mesh plot¶

And surface/mesh plots, showing surfaces or wireframes.

import ipyvolume as ipv
x, y, z, u, v = ipv.examples.klein_bottle(draw=False)
ipv.figure()
m = ipv.plot_mesh(x, y, z, wireframe=False)
ipv.squarelim()
ipv.show()


### Built on Ipywidgets¶

For anything more sophisticed, use ipyvolume.pylab, ipyvolume’s copy of matplotlib’s 3d plotting (+ volume rendering).

Since ipyvolume is built on ipywidgets, we can link widget’s properties.

import ipyvolume as ipv
import numpy as np
x, y, z, u, v, w = np.random.random((6, 1000))*2-1
selected = np.random.randint(0, 1000, 100)
ipv.figure()
quiver = ipv.quiver(x, y, z, u, v, w, size=5, size_selected=8, selected=selected)

from ipywidgets import FloatSlider, ColorPicker, VBox, jslink
size = FloatSlider(min=0, max=30, step=0.1)
size_selected = FloatSlider(min=0, max=30, step=0.1)
color = ColorPicker()
color_selected = ColorPicker()
VBox([ipv.gcc(), size, size_selected, color, color_selected])


Try changing the slider to the change the size of the vectors, or the colors.

## Quick installation¶

This will most likely work, otherwise read install

pip install ipyvolume
jupyter nbextension enable --py --sys-prefix ipyvolume
jupyter nbextension enable --py --sys-prefix widgetsnbextension


For conda/anaconda, use:

conda install -c conda-forge ipyvolume


Ipyvolume is an offspring project from vaex. Ipyvolume makes use of threejs, an excellent Javascript library for OpenGL/WebGL rendering.

## Contents¶

### Installation¶

#### Using pip¶

Advice: Make sure you use conda or virtualenv. If you are not a root user and want to use the --user argument for pip, you expose the installation to all python environments, which is a bad practice, make sure you know what you are doing.



#### For Jupyter lab users¶

The Jupyter lab extension is not enabled by default (yet).

$conda install -c conda-forge nodejs # or some other way to have a recent node$ jupyter labextension install @jupyter-widgets/jupyterlab-manager
$jupyter labextension install ipyvolume$ jupyter labextension install jupyter-threejs


#### Pre-notebook 5.3¶

If you are still using an old notebook version, ipyvolume and its dependend extension (widgetsnbextension) need to be enabled manually. If unsure, check which extensions are enabled:

$jupyter nbextention list  If not enabled, enable them: $ jupyter nbextension enable --py --sys-prefix ipyvolume
$jupyter nbextension enable --py --sys-prefix widgetsnbextension  #### Pip as user: (but really, do not do this)¶ You have been warned, do this only if you know what you are doing, this might hunt you in the future, and now is a good time to consider learning virtualenv or conda. $ pip install ipyvolume --user
$jupyter nbextension enable --py --user ipyvolume$ jupyter nbextension enable --py --user widgetsnbextension


#### Developer installation¶

$git clone https://github.com/maartenbreddels/ipyvolume.git$ cd ipyvolume
$pip install -e .$ jupyter nbextension install --py --symlink --sys-prefix ipyvolume
$jupyter nbextension enable --py --sys-prefix ipyvolume  For all cases make sure ipywidgets is enabled if you use Jupyter notebook version < 5.3 (using --user instead of --sys-prefix if doing a local install): $ jupyter nbextension enable --py --sys-prefix widgetsnbextension
$jupyter nbextension enable --py --sys-prefix pythreejs$ jupyter nbextension enable --py --sys-prefix ipywebrtc
$jupyter nbextension enable --py --sys-prefix ipyvolume  #### Developer workflow¶ ##### Jupyter notebook (classical)¶ Note: There is never a need to restart the notebook server, nbextensions are picked up after a page reload. Start this command: $ (cd js; npm run watch)


It will

• Watch for changes in the sourcecode and run the typescript compiler for transpilation of the src dir to the lib dir.
• Watch the lib dir, and webpack will build (among other things), ROOT/ipyvolume/static/index.js.

Refresh the page.

### Examples¶

#### Scatter plot¶

A simple scatter plot, plotting 1000 random points.

:

import ipyvolume as ipv
import numpy as np
N = 1000
x, y, z = np.random.normal(0, 1, (3, N))

:

fig = ipv.figure()
scatter = ipv.scatter(x, y, z)
ipv.show()


screenshot

[ ]:

%matplotlib inline


#### Volshow¶

A simple volume rendering example

##### Using the pylab API¶
:

import numpy as np
import ipyvolume as ipv
V = np.zeros((128,128,128)) # our 3d array
# outer box
V[30:-30,30:-30,30:-30] = 0.75
V[35:-35,35:-35,35:-35] = 0.0
# inner box
V[50:-50,50:-50,50:-50] = 0.25
V[55:-55,55:-55,55:-55] = 0.0

ipv.figure()
ipv.volshow(V, level=[0.25, 0.75], opacity=0.03, level_width=0.1, data_min=0, data_max=1)
ipv.view(-30, 40)
ipv.show()

/Users/maartenbreddels/src/ipyvolume/ipyvolume/serialize.py:92: RuntimeWarning: invalid value encountered in true_divide


#### Visualizating a scan of a male head¶

Included in ipyvolume, is a visualuzation of a scan of a human head, see the sourcecode for more details.

[ ]:

import ipyvolume as ipv
fig = ipv.figure()
ipv.view(90, 0)


screenshot

#### Meshes / Surfaces¶

Meshes (or surfaces) in ipyvolume consist of triangles, and are defined by their coordinate (vertices) and faces/triangles, which refer to three vertices.

:

import ipyvolume as ipv
import numpy as np

##### Triangle meshes¶

Lets first construct a ‘solid’, a tetrahedron, consisting out of 4 vertices, and 4 faces (triangles) using plot_trisurf

:

s = 1/2**0.5
# 4 vertices for the tetrahedron
x = np.array([1.,  -1, 0,  0])
y = np.array([0,   0, 1., -1])
z = np.array([-s, -s, s,  s])
# and 4 surfaces (triangles), where the number refer to the vertex index
triangles = [(0, 1, 2), (0, 1, 3), (0, 2, 3), (1,3,2)]

:

ipv.figure()
# we draw the tetrahedron
mesh = ipv.plot_trisurf(x, y, z, triangles=triangles, color='orange')
# and also mark the vertices
ipv.scatter(x, y, z, marker='sphere', color='blue')
ipv.xyzlim(-2, 2)
ipv.show()

##### Surfaces¶

To draw parametric surfaces, which go from $$\Bbb{R}^2 \rightarrow \Bbb{R}^3$$, it’s convenient to use plot_surface, which takes 2d numpy arrays as arguments, assuming they form a regular grid (meaning you do not need to provide the triangles, since they can be inferred from the shape of the arrays). Note that plot_wireframe has a similar api, as does plot_mesh which can do both the surface and wireframe at the same time.

:

# f(u, v) -> (u, v, u*v**2)
a = np.arange(-5, 5)
U, V = np.meshgrid(a, a)
X = U
Y = V
Z = X*Y**2

ipv.figure()
ipv.plot_surface(X, Z, Y, color="orange")
ipv.plot_wireframe(X, Z, Y, color="red")
ipv.show()

##### Colors¶

Vertices can take colors as well, as the example below (adapted from matplotlib) shows.

:

X = np.arange(-5, 5, 0.25*1)
Y = np.arange(-5, 5, 0.25*1)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)

:

from matplotlib import cm
colormap = cm.coolwarm
znorm = Z - Z.min()
znorm /= znorm.ptp()
znorm.min(), znorm.max()
color = colormap(znorm)

:

ipv.figure()
mesh = ipv.plot_surface(X, Z, Y, color=color[...,:3])
ipv.show()

##### Texture mapping¶

Texture mapping can be done by providing a PIL image, and UV coordiante (texture coordinates, between 0 and 1). Note that like almost anything in ipyvolume, these u & v coordinates can be animated, as well as the textures.

:

# import PIL.Image
# image = PIL.Image.open('data/jupyter.png')

:

# fig = p3.figure()
# p3.style.use('dark')
# # we create a sequence of 8 u v coordinates so that the texture moves across the surface.
# u = np.array([X/5 +np.sin(k/8*np.pi)*4. for k in range(8)])
# v = np.array([-Y/5*(1-k/7.) + Z*(k/7.) for k in range(8)])
# mesh = p3.plot_mesh(X, Z, Y, u=u, v=v, texture=image, wireframe=False)
# p3.animation_control(mesh, interval=800, sequence_length=8)
# p3.show()


We now make a small movie / animated gif of 30 frames.

:

# frames = 30
# p3.movie('movie.gif', frames=frames)


And play that movie on a square

:

# p3.figure()
# x = np.array([-1.,  1,  1,  -1])
# y = np.array([-1,  -1, 1., 1])
# z = np.array([0., 0, 0., 0])
# u = x / 2 + 0.5
# v = y / 2 + 0.5
# # square
# triangles = [(0, 1, 2), (0, 2, 3)]
# m = p3.plot_trisurf(x, y, z, triangles=triangles, u=u, v=v, texture=PIL.Image.open('movie.gif'))
# p3.animation_control(m, sequence_length=frames)
# p3.show()


screenshot

#### Animation¶

All (or most of) the changes in scatter and quiver plots are (linearly) interpolated. On top top that, scatter plots and quiver plots can take a sequence of arrays (the first dimension), where only one array is visualized. Together this can make smooth animations with coarse timesteps. Lets see an example.

:

import ipyvolume as ipv
import numpy as np

##### Basic animation¶
:

# only x is a sequence of arrays
x = np.array([[-1, -0.8], [1, -0.1], [0., 0.5]])
y = np.array([0.0, 0.0])
z = np.array([0.0, 0.0])
ipv.figure()
s = ipv.scatter(x, y, z, marker='sphere', size=10)
ipv.xyzlim(-1, 1)
ipv.animation_control(s) # shows controls for animation controls
ipv.show()


You can control which array to visualize, using the scatter.sequence_index property. Actually, the pylab.animate_glyphs is connecting the Slider and Play button to that property, but you can also set it from Python.

:

s.sequence_index = 1

##### Animating color and size¶

We now demonstrate that you can also animate color and size

:

# create 2d grids: x, y, and r
u = np.linspace(-10, 10, 25)
x, y = np.meshgrid(u, u)
r = np.sqrt(x**2+y**2)
print("x,y and z are of shape", x.shape)
# and turn them into 1d
x = x.flatten()
y = y.flatten()
r = r.flatten()
print("and flattened of shape", x.shape)

x,y and z are of shape (25, 25)
and flattened of shape (625,)


Now we only animate the z component

:

# create a sequence of 15 time elements
time = np.linspace(0, np.pi*2, 15)
z = np.array([(np.cos(r + t) * np.exp(-r/5)) for t in time])
print("z is of shape", z.shape)

z is of shape (15, 625)

:

# draw the scatter plot, and add controls with animate_glyphs
ipv.figure()
s = ipv.scatter(x, z, y, marker="sphere")
ipv.animation_control(s, interval=200)
ipv.ylim(-3,3)
ipv.show()

:

# Now also include, color, which containts rgb values
color = np.array([[np.cos(r + t), 1-np.abs(z[i]), 0.1+z[i]*0] for i, t in enumerate(time)])
size = (z+1)
print("color is of shape", color.shape)

color is of shape (15, 3, 625)


color is of the wrong shape, the last dimension should contain the rgb value, i.e. the shape of should be (15, 2500, 3)

:

color = np.transpose(color, (0, 2, 1)) # flip the last axes

:

ipv.figure()
s = ipv.scatter(x, z, y, color=color, size=size, marker="sphere")
ipv.animation_control(s, interval=200)
ipv.ylim(-3,3)
ipv.show()

##### Creating movie files¶

We now make a movie, with a 2 second duration, where we rotate the camera, and change the size of the scatter points.

:

# This is commented out, otherwise it would run on readthedocs
# def set_view(figure, framenr, fraction):
#     ipv.view(fraction*360, (fraction - 0.5) * 180, distance=2 + fraction*2)
#     s.size = size * (2+0.5*np.sin(fraction * 6 * np.pi))
# ipv.movie('wave.gif', set_view, fps=20, frames=40)

##### Resulting gif file¶ ##### Animated quiver¶

Not only scatter plots can be animated, quiver as well, so the direction vector (vx, vy, vz) can also be animated, as shown in the example below, which is a (subsample of) a simulation of a small galaxy orbiting a host galaxy (not visible).

:

import ipyvolume.datasets
stream = ipyvolume.datasets.animated_stream.fetch()
print("shape of steam data", stream.data.shape) # first dimension contains x, y, z, vx, vy, vz, then time, then particle

shape of steam data (6, 200, 1250)

:

fig = ipv.figure()
# instead of doing x=stream.data, y=stream.data, ... vz=stream.data, use *stream.data
# limit to 50 timesteps to avoid having a huge notebook
q = ipv.quiver(*stream.data[:,0:50,:200], color="red", size=7)
ipv.style.use("dark") # looks better
ipv.animation_control(q, interval=200)
ipv.show()

:

# fig.animation = 0 # set to 0 for no interpolation


screenshot