How to generate a paraboloid surface and make it respond to event - python

I am new to vispy and computer graphics. I have to generate a paraboloid according to a certain equation whose center and parameters vary according to the user input. I have gone through vispy documentation and examples and got some idea regarding the package.
The paraboloid that I need to generate should have rotational symmetry such as shown in the figure below:
Whereas what I got is here
My code is given below. I have modified the isosurface.py example in the vispy examples.
import sys
import numpy as np
from vispy import app, scene
from matplotlib import pyplot as plt
# Create a canvas with a 3D viewport
canvas = scene.SceneCanvas(keys='interactive')
view = canvas.central_widget.add_view()
## Define a scalar field from which we will generate an isosurface
def psi3(i, j, k, offset=(25, 25, 25)):
x = i-offset[0]
y = j-offset[1]
z = k-offset[2]
r = (0.2*x**2 + 0.2*y**2 - 4*z)
return r
# Create isosurface visual
data = np.fromfunction(psi3, (50, 50, 50))
surface = scene.visuals.Isosurface(data, level=data.max() / 4., color=(0.5, 0.6, 1, 1), shading='smooth', parent=view.scene)
surface.transform = scene.transforms.STTransform(translate=(-25, -25, -25))
# Add a 3D axis to keep us oriented
axis = scene.visuals.XYZAxis(parent=view.scene)
# Use a 3D camera
# Manual bounds; Mesh visual does not provide bounds yet
# Note how you can set bounds before assigning the camera to the viewbox
cam = scene.TurntableCamera(elevation=30, azimuth=30)
cam.set_range((-10, 10), (-10, 10), (-10, 10))
view.camera = cam
if __name__ == '__main__':
canvas.show()
if sys.flags.interactive == 0:
app.run()
My queries are the following:
How do I make the paraboloid look like in the first image (without the edges getting clipped off)
Is there a better way to draw the paraboloid other than using isosurfaces. The coefficients of the paraboloid should be varied by the user.
How to make the paraboloid respond to mouse events: hover, drag-drop etc. I understand from the documentation that I have to couple it to the Node class. I am unable to figure out the exact way to do this as I am a newbie.
Edit:
Here is the corresponding code using matplotlib for generating the required paraboloid. Also I am able to create a paraboloidal strip in matplotlib.
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
# Create the surface
radius = 5
hole_radius = 4
# Generate the grid in cylindrical coordinates
r = np.linspace(0, radius, 100)
theta = np.linspace(0, 2 * np.pi, 100)
R, THETA = np.meshgrid(r, theta)
X, Y = R * np.cos(THETA), R * np.sin(THETA)
a=0.6;b=0.6;c=0.6
Z1 = (X/a)**2+(Y/b)**2 # Elliptic paraboloid
# Do not plot the inner region
x = np.where(X**2+Y**2<=hole_radius**2,np.NAN,X)
y = np.where(X**2+Y**2<=hole_radius**2,np.NAN,Y)
# Plot the surface
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_surface(x, y, Z1, cmap=cm.coolwarm, linewidth=0, antialiased=True, cstride=2, rstride=2)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
plt.show()
This results in the following:
The difference between the surface plot of vispy and matplotlib is that the latter work by accepting 2D arrays for x and y, whereas vispy's SurfacePlot() accepts only 1D vectors in both x and y.
Since the grid in cylindrical coordinates and converting them to cartesian coordinates for plotting, the grid cannot be generated by replicating the 1D x and y vectors.
Update:
As pointed by #djhoesem, isosurface is not the correct method to do this.

I'm the maintainer of vispy, but have very little isosurface experience. Let's see what I can answer:
The easiest thing I saw to do this is to make levels even smaller (I made it zero for testing). I'm not sure how this effects performance or the output exactly but the isosurface function mentions the paper it is based on. Maybe that can tell you more.
See Paul Bourke, "Polygonising a Scalar Field"
(http://paulbourke.net/geometry/polygonise/)
To make the parameters controllable by the user you could subclass the existing Isosurface class and add properties to control these. However, this will probably perform poorly if you want immediate feedback since you'd have to regenerate the numpy array and rerun all the other calculations on the CPU. The IsosurfaceVisual class expects volumetric data and then converts it to an isosurface. It generates a mesh that the MeshVisual understands (IsosurfaceVisual is a subclass of MeshVisual). If you want anything better you'd probably have to write your own shader code to do it. Depends what your exact requirements are (do you have to accept any formula with controllable coefficients?).
The scene.visuals Visual classes are already subclasses of the Node class so you don't have to do anything extra there. The other SceneCanvas-based examples should give you some ideas of how you can handle mouse events. That said, you mentioned "drag-drop", I'm not sure that would be something you'd handle in VisPy land but more likely in PyQt5 land (if that's the backend you are using).

Added the feature via pull request: https://github.com/vispy/vispy/pull/1863#event-3344873987
Made a small modification to the code and resolved the issue.
The corresponding example can be found here:
https://github.com/vispy/vispy/blob/master/examples/basics/visuals/axially_symmetric_surfaces.py

Related

3D Phase portrait of Rössler System using Python

I'm running into a specific problem when attempting to plot the 3D phase portrait of the Rössler system in Python. The problem is that certain arrows are excessively long, and thus the visualization isn't a good one at all:
Bad 3d phase portrait
This is my code so far, and I don't really know what to alter to make an appropriate plot. Any help would be much appreciated.
# Axes, grid
fig = plt.figure(figsize=(10, 10))
ax = plt.axes(projection ='3d')
x, y, z = np.meshgrid(np.arange(-20, 20, 4),
np.arange(-20, 20, 4),
np.arange(0, 20, 4))
# Define vector field
u = -y - z
v = x + (1/5)*y
w = 1/5 + (x - 5/2)*z
# Plot vector field
ax.quiver(x, y, z, u, v, w, length=0.1, normalize = False)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()
I haven't been able to try any alternatives largely because I'm not really sure of what to do.
I believe that you won't be able to accurately visualize this vector field with quivers, because there is quite a big variation in magnitude in your view area. A better way is to visualize streamlines, and that's not easy either:
matplotlib doesn't support 3D streamlines.
Plotly support streamtubes, but when I tried it on my vector fields I usually got "mehhh" results, meaning it was really difficult to get them to proper scale.
I believe that the easiest solution is to use the SymPy Plotting Backend module, which exposes a plot_vector function that also plot streamlines. Note that I am the developer of this module, so I'm going to showcase it for you. In particular, this module exposes a backend to create plots with K3D-Jupyter which, for this particular vector field, produces excellent results.
from sympy import *
from spb import *
var("x:z, u:w")
u = -y - z
v = x + (1/5)*y
w = 1/5 + (x - 5/2)*z
plot_vector(
[u, v, w], (x, -20, 20), (y, -20, 20), (z, 0, 20), # vector field and ranges
backend=KB, # chose the plotting library: K3D-Jupyter
streamlines=True, # enable or disable streamlines
stream_kw={ # customize streamlines
"starts": True, # Ask the streamlines to start from random points
"npoints": 150 # number of starting points for the streamlines
},
n=50, # number of discretization points. Don't go too crazy as memory requirements is at least n^3
use_cm=True, # use color map. Colors indicates the magnitude of the vector field.
)
In order to use the streamline functionalities, you will have to install all the requirements, which are quite heavy (If I recall correctly, at least 200MB).
pip install sympy_plot_backends[all]

Data analysis of a 3D form in python

My question must have been answered already somewhere but i couldn't find it.
I have a binary numpy 3D array (shape =(512, 512, 304) ) in which there is a random form (labelled as 1). Any other point is labelled as 0.
Lets take for a simple example a sphere.
I want to plot this form on a 3D plot where we can see the sphere.
I already tried 3D plot matplotlib but couldn't get the hand of it
I used the interactive function from (ipywidgets) to print it slice by slice but that's not effetive
I also want to calculate the volume of the form (it may be a completely random poly polyhedron)
I am looking for advices more than answer
Thanks in advance
You can use voxels, although it will be very slow if you try to run it with an array that big. You can for example plot only a 10% of the elements:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Make a sphere
x, y, z = np.ogrid[-1:1:512j, -1:1:512j, -1:1:304j]
sphere = np.sqrt(x * x + y * y + z * z) < 0.5
# Make 3D axis
ax = plt.figure().add_subplot(projection='3d')
# Make voxels figures at 10% resolution
ax.voxels(filled=sphere[::10, ::10, ::10])
ax.figure.show()
Output:
One way to put it is to plot it using widgets:
def show_axial(image_array, slice):
plt.imshow(img_array[:, :, mr_slice].T, cmap="gray")
interact(show_axial,img_array = fixed(im_arr), slice=widgets.IntSlider(max=im_arr.shape[2] - 1,min = 0))
That gaves a 2d plot with a slide bar that ranged between 0 to the image width and make it showable slide by slide.

Mapping the color scale of 3D isosurface on a scalar field

Let's say we have some 3D complex valued function f(x,y,z). Using Plotly, I'm trying to plot isosurfaces of the magnitude |f(x,y,z)| of such function. So far, everything is OK and my code seems to do well, please find below a working example on atomic orbitals functions :
import chart_studio.plotly as py
import plotly.graph_objs as go
import scipy.special as scispe
import numpy as np
import math
a=5.29e-11 # Bohr radius (m)
def orbital(n,l,m,r,theta,phi): # Complex function I want to plot
L=scispe.genlaguerre(n-l-1,2*l+1) # Laguerre polynomial
radial= (2/(n*a))**(3/2) * np.sqrt(math.factorial(n-l-1)/(2*n*math.factorial(n+l))) * np.exp(-2*r/n) * (2*r/n)**l * L(2*r/n)
wavefunction = radial * scispe.sph_harm(m,l, phi, theta)
return wavefunction
#Quantum numbers
n=2
l=1
m=0
goodspan = (3 * n**2 - l * (l+1))/2 #Plot span adpated to the mean electron position
x, y, z = np.mgrid[-goodspan:goodspan:40j, -goodspan:goodspan:40j, -goodspan:goodspan:40j] #in units of a
r = np.sqrt(x**2 + y**2 + z**2) #Function has to be evaluated in spherical coordinates
theta = np.arccos(z/r)
phi = np.arctan(y/x)
AO=orbital(n,l,m,r,theta,phi)
magnitude = abs(AO) # Compute the magnitude of the function
phase = np.angle(AO) # Compute the phase of the function
isoprob = np.amax(magnitude)/2 # Set value the isosurface
fig = go.Figure(data=go.Isosurface(
x=x.flatten(),
y=y.flatten(),
z=z.flatten(),
value=magnitude.flatten(),
opacity=0.5,
isomin=isoprob,
isomax=isoprob,
surface_count=1,
caps=dict(x_show=True, y_show=True)
))
fig.show()
which gives me this :
At this point, the color scale of the graph is attributed depending on the value of the magnitude |f(x,y,z)|, so that a single isosurface is always uniform in color.
Now, instead to have a color scale mapped on the magnitude |f(x,y,z)|, I would like it to be mapped on the value of the phase Ф(x,y,z) = arg(f(x,y,z)), so that the color of each point of a ploted isosurface tells us about the value of the field Ф(x,y,z) (which would be distributed on [-π,π] ideally) instead of |f(x,y,z)| in thsi point.
Basically, I would like to do this with Plotly instead of Mayavi if it's possible.
It seems to me that all of that has something to do with a special way to set the cmin and cmax parameters of the function Isosurface, but I can't figure out how to do this.
As #gnodab mentioned in his comment, plotly isosurfaces do not really support colouring the surfaces by a fifth dimension (at least there is no obvious way to do it). I am also not sure if it might be possible to extract the data describing the isosurface somehow to be re-plotted as a regular surface.
In this post, however, they describe how to generate an isosurface with skimage.measure.marching_cubes_lewiner which is then plotted and coloured by a custom colorscale with plotly as 'mesh3d' trace. This might be what you want. If I find the time, I'll give that a try and edit my answer later.
Given #Jan Joswig's answer and the link they provided, the quick/compact way of doing it will be:
import plotly.graph_objects as go
from skimage import measure
import numpy as np
xyz_shape = vol.shape
verts, faces = measure.marching_cubes(vol, .5)[:2] # iso-surface at .5 level
x, y, z = verts.T
I, J, K = faces.T
fig = go.Figure(
data=[go.Mesh3d(
x=x,
y=y,
z=z,
color='lightpink',
opacity=0.50,
i=I,
j=J,
k=K, )])
fig.show()

Matplotlib: How to increase colormap/linewidth quality in streamplot?

I have the following code to generate a streamplot based on an interp1d-Interpolation of discrete data:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
from scipy.interpolate import interp1d
# CSV Import
a1array=pd.read_csv('a1.csv', sep=',',header=None).values
rv=a1array[:,0]
a1v=a1array[:,1]
da1vM=a1array[:,2]
a1 = interp1d(rv, a1v)
da1M = interp1d(rv, da1vM)
# Bx and By vector components
def bx(x ,y):
rad = np.sqrt(x**2+y**2)
if rad == 0:
return 0
else:
return x*y/rad**4*(-2*a1(rad)+rad*da1M(rad))/2.87445E-19*1E-12
def by(x ,y):
rad = np.sqrt(x**2+y**2)
if rad == 0:
return 4.02995937E-04/2.87445E-19*1E-12
else:
return -1/rad**4*(2*a1(rad)*y**2+rad*da1M(rad)*x**2)/2.87445E-19*1E-12
Bx = np.vectorize(bx, otypes=[np.float])
By = np.vectorize(by, otypes=[np.float])
# Grid
num_steps = 11
Y, X = np.mgrid[-25:25:(num_steps * 1j), 0:25:(num_steps * 1j)]
Vx = Bx(X, Y)
Vy = By(X, Y)
speed = np.sqrt(Bx(X, Y)**2+By(X, Y)**2)
lw = 2*speed / speed.max()+.5
# Star Radius
circle3 = plt.Circle((0, 0), 16.3473140, color='black', fill=False)
# Plot
fig0, ax0 = plt.subplots(num=None, figsize=(11,9), dpi=80, facecolor='w', edgecolor='k')
strm = ax0.streamplot(X, Y, Vx, Vy, color=speed, linewidth=lw,density=[1,2], cmap=plt.cm.jet)
ax0.streamplot(-X, Y, -Vx, Vy, color=speed, linewidth=lw,density=[1,2], cmap=plt.cm.jet)
ax0.add_artist(circle3)
cbar=fig0.colorbar(strm.lines,fraction=0.046, pad=0.04)
cbar.set_label('B[GT]', rotation=270, labelpad=8)
cbar.set_clim(0,1500)
cbar.draw_all()
ax0.set_ylim([-25,25])
ax0.set_xlim([-25,25])
ax0.set_xlabel('x [km]')
ax0.set_ylabel('z [km]')
ax0.set_aspect(1)
plt.title('polyEos(0.05,2), M/R=0.2, B_r(0,0)=1402GT', y=1.01)
plt.savefig('MR02Br1402.pdf',bbox_inches=0)
plt.show(fig0)
I uploaded the csv-file here if you want to try some stuff https://www.dropbox.com/s/4t7jixpglt0mkl5/a1.csv?dl=0.
Which generates the following plot:
I am actually pretty happy with the result except for one small detail, which I can not figure out: If one looks closely the linewidth and the color change in rather big steps, which is especially visible at the center:
Is there some way/option with which I can decrease the size of this steps to especially make the colormap smother?
I had another look at this and it wasnt as painful as I thought it might be.
Add:
subdiv = 15
points = np.arange(len(t[0]))
interp_points = np.linspace(0, len(t[0]), subdiv * len(t[0]))
tgx = np.interp(interp_points, points, tgx)
tgy = np.interp(interp_points, points, tgy)
tx = np.interp(interp_points, points, tx)
ty = np.interp(interp_points, points, ty)
after ty is initialised in the trajectories loop (line 164 in my version). Just substitute whatever number of subdivisions you want for subdiv = 15. All the segments in the streamplot will be subdivided into as many equally sized segments as you choose. The colors and linewidths for each will still be properly obtained from interpolating the data.
Its not as neat as changing the integration step but it does plot exactly the same trajectories.
If you don't mind changing the streamplot code (matplotlib/streamplot.py), you could simply decrease the size of the integration steps. Inside _integrate_rk12() the maximum step size is defined as:
maxds = min(1. / dmap.mask.nx, 1. / dmap.mask.ny, 0.1)
If you decrease that, lets say:
maxds = 0.1 * min(1. / dmap.mask.nx, 1. / dmap.mask.ny, 0.1)
I get this result (left = new, right = original):
Of course, this makes the code about 10x slower, and I haven't thoroughly tested it, but it seems to work (as a quick hack) for this example.
About the density (mentioned in the comments): I personally don't see the problem of that. It's not like we are trying to visualize the actual path line of (e.g.) a particle; the density is already some arbitrary (controllable) choice, and yes it is influenced by choices in the integration, but I don't thing that it changes the (not quite sure how to call this) required visualization we're after.
The results (density) do seem to converge a bit for decreasing step sizes, this shows the results for decreasing the integration step with a factor {1,5,10,20}:
You could increase the density parameter to get more smooth color transitions,
but then use the start_points parameter to reduce your overall clutter.
The start_points parameter allows you to explicity choose the location and
number of trajectories to draw. It overrides the default, which is to plot
as many as possible to fill up the entire plot.
But first you need one little fix to your existing code:
According to the streamplot documentation, the X and Y args should be 1d arrays, not 2d arrays as produced by mgrid.
It looks like passing in 2d arrays is supported, but it is undocumented
and it is currently not compatible with the start_points parameter.
Here is how I revised your X, Y, Vx, Vy and speed:
# Grid
num_steps = 11
Y = np.linspace(-25, 25, num_steps)
X = np.linspace(0, 25, num_steps)
Ygrid, Xgrid = np.mgrid[-25:25:(num_steps * 1j), 0:25:(num_steps * 1j)]
Vx = Bx(Xgrid, Ygrid)
Vy = By(Xgrid, Ygrid)
speed = np.hypot(Vx, Vy)
lw = 3*speed / speed.max()+.5
Now you can explicitly set your start_points parameter. The start points are actually
"seed" points. Any given stream trajectory will grow in both directions
from the seed point. So if you put a seed point right in the center of
the example plot, it will grow both up and down to produce a vertical
stream line.
Besides controlling the number of trajectories, using the
start_points parameter also controls the order they are
drawn. This is important when considering how trajectories terminate.
They will either hit the border of the plot, or they will terminate if
they hit a cell of the plot that already has a trajectory. That means
your first seeds will tend to grow longer and your later seeds will tend
to get limited by previous ones. Some of the later seeds may not grow
at all. The default seeding strategy is to plant a seed at every cell,
which is pretty obnoxious if you have a high density. It also orders
them by planting seeds first along the plot borders and spiraling inward.
This may not be ideal for your particular case. I found a very simple
strategy for your example was to just plant a few seeds between those
two points of zero velocity, y=0 and x from -10 to 10. Those trajectories
grow to their fullest and fill in most of the plot without clutter.
Here is how I create the seed points and set the density:
num_streams = 8
stptsy = np.zeros((num_streams,), np.float)
stptsx_left = np.linspace(0, -10.0, num_streams)
stptsx_right = np.linspace(0, 10.0, num_streams)
stpts_left = np.column_stack((stptsx_left, stptsy))
stpts_right = np.column_stack((stptsx_right, stptsy))
density = (3,6)
And here is how I modify the calls to streamplot:
strm = ax0.streamplot(X, Y, Vx, Vy, color=speed, linewidth=lw, density=density,
cmap=plt.cm.jet, start_points=stpts_right)
ax0.streamplot(-X, Y, -Vx, Vy, color=speed, linewidth=lw,density=density,
cmap=plt.cm.jet, start_points=stpts_left)
The result basically looks like the original, but with smoother color transitions and only 15 stream lines. (sorry no reputation to inline the image)
I think your best bet is to use a colormap other than jet. Perhaps cmap=plt.cmap.plasma.
Wierd looking graphs obscure understanding of the data.
For data which is ordered in some way, like by the speed vector magnitude in this case, uniform sequential colormaps will always look smoother. The brightness of sequential maps varies monotonically over the color range, removing large percieved color changes over small ranges of data. The uniform maps vary linearly over their whole range which makes the main features in the data much more visually apparent.
(source: matplotlib.org)
The jet colormap spans a very wide variety of brightnesses over its range with in inflexion in the middle. This is responsible for the particularly egregious red to blue transition around the center region of your graph.
(source: matplotlib.org)
The matplotlib user guide on choosing a color map has a few recomendations for about selecting an appropriate map for a given data set.
I dont think there is much else you can do to improve this by just changing parameters in your plot.
The streamplot divides the graph into cells with 30*density[x,y] in each direction, at most one streamline goes through each cell. The only setting which directly increases the number of segments is the density of the grid matplotlib uses. Increasing the Y density will decrease the segment length so that the middle region may transition more smoothly. The cost of this is an inevitable cluttering of the graph in regions where the streamlines are horizontal.
You could also try to normalise the speeds differently so the the change is artifically lowered in near the center. At the end of the day though it seems like it defeats the point of the graph. The graph should provide a useful view of the data for a human to understand. Using a colormap with strange inflexions or warping the data so that it looks nicer removes some understanding which could otherwise be obtained from looking at the graph.
A more detailed discussion about the issues with colormaps like jet can be found on this blog.

Python-VTK 3D Spline Regression through STL model of vascular tree

I need to create a spline or polyline representation of a vascular tree model (see below).
The model is in a STL format, thus I have the x-y-z coordinates of all vertices. The lines should run through the center of the vessel mesh thus I thought that the best approach would be a spline regression through the vertex cloud. In addition it would be great if I can have the radius of the vessel at given points, e.g. the coordinates of the polyline.
I looked through this forum and the VTK website (assuming they have a straightforward implementation for this sort of thing) but so far I haven't found something I can use. Does anyone know of a Python module or VTK class (which I would call from Python) that can do this? The python modules I found on this are all for 2D data.
Thanks a lot!
EDIT:
I came across this library called VMTK that deals almost exclusively with vessel segmentation and has functionality for what they call 'centerline calculation'. However, they usually require the vessels to be 'cut' at their ends and 'source points' to be defined. In the case of my model, however, one can see that the end points are 'capped' which makes matters more complicated. If I find a solution I'll post here
I don't know any software or python classes exactly on your problem.
Maybe python interpolate.splev will help you with a single vessel.
You may try the following code as an example:
from scipy import interpolate
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
# 3D example
total_rad = 10
z_factor = 3
noise = 0.1
num_true_pts = 200
s_true = np.linspace(0, total_rad, num_true_pts)
x_true = np.cos(s_true)
y_true = np.sin(s_true)
z_true = s_true/z_factor
num_sample_pts = 100
s_sample = np.linspace(0, total_rad, num_sample_pts)
x_sample = np.cos(s_sample) + noise * np.random.randn(num_sample_pts)
y_sample = np.sin(s_sample) + noise * np.random.randn(num_sample_pts)
z_sample = s_sample/z_factor + noise * np.random.randn(num_sample_pts)
tck, u = interpolate.splprep([x_sample,y_sample,z_sample], s=2)
x_knots, y_knots, z_knots = interpolate.splev(tck[0], tck)
u_fine = np.linspace(0,1,num_true_pts)
x_fine, y_fine, z_fine = interpolate.splev(u_fine, tck)
fig2 = plt.figure(2)
ax3d = fig2.add_subplot(111, projection='3d')
# blue line shows true helix
ax3d.plot(x_true, y_true, z_true, 'b')
# red stars show distorted sample around a blue line
ax3d.plot(x_sample, y_sample, z_sample, 'r*')
# green line and dots show fitted curve
ax3d.plot(x_knots, y_knots, z_knots, 'go')
ax3d.plot(x_fine, y_fine, z_fine, 'g')
plt.show()
This code uses noisy centerline path of a single vessel and fit it with a smooth curve (see the result below):
interolation result
Usually, two user seeds are used to mark centerline ends, in the case of centerline representation as in VMTK.
The other way to get centerlines automatically is to voxelize your stl mesh, costruct a voxel skeleton, and separate skeletal segment to represent each vessel. Then you can interpolate each centerline to get the smooth curves. Unprocessed skeletal segments usualy have zigzags.

Categories

Resources