Linear and nonlinear slices in volumetric data, as graphs of functions of two variables, were defined in this Jupyter Notebook http://nbviewer.jupyter.org/github/empet/Plotly-plots/blob/master/Plotly-Slice-in-volumetric-data.ipynb. Here we illustrate how to plot an isosurface in volumetric data.
In [10]:
import plotly.graph_objs as go
import numpy as np
from skimage import measure
We define an isosurface of equation $F(x,y,z)=x^4 + y^4 + z^4 - (x^2+y^2+z^2)^2 + 3(x^2+x^2+z^2) - 3=1.2$ in the volume $[-2, 2]\times[-2, 2]\times [-2, 2]$, on which a scalar field (like density or another physical property) is defined by $\psi(x,y, z)= −xe^−(x^2+y^22+z^2)$.
An isosurface $F(x,y,z)=c$ is plotted as a trisurf surface, having the vertices and faces (triangles) of a triangulation returned by the function skimage.measure.marching_cubes_lewiner(F,c)
.
The isosurface is colored with a colorscale based on the values of the scalar field, $\psi(x,y,z)$, at its points.
The following function returns the intensities at the vertices of the triangulation of the isosurface:
In [3]:
def intensity_func(x,y,z):
return -x * np.exp(-(x**2 + y**2 + z**2))
In [4]:
def plotly_triangular_mesh(vertices, faces, intensities=intensity_func, colorscale="Viridis",
showscale=False, reversescale=False, plot_edges=False):
# vertices: a numpy array of shape (n_vertices, 3)
# faces: a numpy array of shape (n_faces, 3)
# intensities can be either a function of (x,y,z) or a list of values
x, y, z = vertices.T
I, J, K = faces.T
if hasattr(intensities, '__call__'):
intensity = intensities(x,y,z) # the intensities are computed here via the passed function,
# that returns a list of vertices intensities
elif isinstance(intensities, (list, np.ndarray)):
intensity = intensities #intensities are given in a list
else:
raise ValueError("intensities can be either a function or a list, np.array")
mesh = go.Mesh3d(x=x,
y=y,
z=z,
colorscale=colorscale,
reversescale=reversescale,
intensity= intensity,
i=I,
j=J,
k=K,
name='',
showscale=showscale
)
if showscale is True:
mesh.update(colorbar=dict(thickness=20, ticklen=4, len=0.75))
if plot_edges is False: # the triangle sides are not plotted
return [mesh]
else: #plot edges
#define the lists Xe, Ye, Ze, of x, y, resp z coordinates of edge end points for each triangle
#None separates data corresponding to two consecutive triangles
tri_vertices= vertices[faces]
Xe=[]
Ye=[]
Ze=[]
for T in tri_vertices:
Xe += [T[k%3][0] for k in range(4)]+[ None]
Ye += [T[k%3][1] for k in range(4)]+[ None]
Ze += [T[k%3][2] for k in range(4)]+[ None]
#define the lines to be plotted
lines = go.Scatter3d(
x=Xe,
y=Ye,
z=Ze,
mode='lines',
name='',
line=dict(color= 'rgb(70,70,70)', width=1)
)
return [mesh, lines]
Define a meshgrid on our volume and the function that for the (iso)surface equation:
In [5]:
X, Y, Z = np.mgrid[-2:2:50j, -2:2:50j, -2:2:50j]
surf_eq = X**4 + Y**4 + Z**4 - (X**2+Y**2+Z**2)**2 + 3*(X**2+Y**2+Z**2) - 3
Although our 3D data is defined in $[-2,2]^3$, the function measure.marching_cubes_lewiner
returns verts
translated such that they belong
to the parallelipiped $[0,4]^3$, provided that the spacing key in this function is the same as the spacing
of voxels in the initial parallelipiped,
i.e. spacing=(X[1,0, 0]-X[0,0,0], Y[0,1, 0]-Y[0,0,0], Z[0,0, 1]-Z[0,0,0])
:
In [6]:
verts, faces = measure.marching_cubes_lewiner(surf_eq, 1.2,
spacing=(X[1,0, 0]-X[0,0,0], Y[0,1, 0]-Y[0,0,0],
Z[0,0, 1]-Z[0,0,0]))[:2]
title = 'Isosurface in volumetric data'
Now we translate the verts back in the original parallelipiped:
In [7]:
verts = verts-2
In [8]:
pl_BrBG=[[0.0, 'rgb(84, 48, 5)'],
[0.1, 'rgb(138, 80, 9)'],
[0.2, 'rgb(191, 129, 45)'],
[0.3, 'rgb(222, 192, 123)'],
[0.4, 'rgb(246, 232, 195)'],
[0.5, 'rgb(244, 244, 244)'],
[0.6, 'rgb(199, 234, 229)'],
[0.7, 'rgb(126, 203, 192)'],
[0.8, 'rgb(53, 151, 143)'],
[0.9, 'rgb(0, 101, 93)'],
[1.0, 'rgb(0, 60, 48)']]
In [11]:
data = plotly_triangular_mesh(verts, faces, colorscale=pl_BrBG,
showscale=True)
In [12]:
axis = dict(showbackground=True,
backgroundcolor="rgb(230, 230,230)",
gridcolor="rgb(255, 255, 255)",
zerolinecolor="rgb(255, 255, 255)")
noaxis = dict(visible=False)
layout = go.Layout(
title=title,
font=dict(family='Balto'),
showlegend=False,
width=800,
height=800,
scene=dict(xaxis=axis,
yaxis=axis,
zaxis=axis,
aspectratio=dict(x=1,
y=1,
z=1)
)
)
In [13]:
fig = go.Figure(data=data, layout=layout)
In [11]:
import plotly.plotly as py
py.sign_in('username', 'api_key')
py.iplot(fig, filename='isosurface-volume')
Out[11]:
In [2]:
from IPython.core.display import HTML
def css_styling():
styles = open("./custom.css", "r").read()
return HTML(styles)
css_styling()
Out[2]: