3D Phase portrait of Rössler System using Python - 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]

Related

Converting matplotlib's streamplot coordiantes to numpy coordiantes

I'm currently working with matplotlib in order to create a module of a specific vector field using matplotlib.pyplot.streamplot
after the creation and coloring of the lines in the streamplot, i'm trying to color the whitespace around the divergence points around the plot, im trying to achieve a gradient of color that is dependent on the distance of the white pixels around it.
The streamplot in question is built according to:
xs=np.linspace(-10,10,2000)
ys=np.linspace(-10,10,2000)
Therefore, if the divergence is located (for demonstration purposes) at (0,0) it will be located exactly in the middle of the plot.
Now, the only method i can think of for coloring according to distance from it, is kind of clunky since it requires me to:
add a matplotlib.patches.Rectangle on top of the divergence point in a specific color that is not in the image yet.
convert the plot, with the streamlines and rectangles (one rectangle for each divergence point in streamplot) to a np.array
find the new coordinates of the colors of the rectangles (they represent the location of the divergence point in the new np.array created from streamplot).
calculate the pixels like i want from the colored pixels.
This whole method feels way to clunky and over-complicating, and obviously slower than i could do. im sure theres a way to convert the coordinates from the matplotlib plot to the ones in np.array somehow or perhaps handle the coloring in matplotlib which will be even easier.
sadly i couldn't find a solution that answers this specific need yet.
thanks in advance for any help given!
EDIT
I'm adding an example (not my code, but a representation of what I wish to achieve).
I want to clarify that the solution of adding a patches.circle on top of a circle patch is not my go to, since i'm looking to keep my painting options more dynamic.
If you can define the color intensity you want as a 2-dimensional function, you can plot that function with plt.imshow() and then put the streamplot on top of it. You just need to transform the coordinates linearly to match the image coordinates.
Here is an example:
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = [10, 10]
# plot 2d function
grid = np.arange(-1, 1, 0.001)
x, y = np.meshgrid(grid, grid)
z = 1 - (x ** 2 + y ** 2) ** 0.5
plt.imshow(z, cmap='Blues')
# streamplot example from matplotlib docs (modified)
w = 3
Y, X = np.mgrid[-w:w:100j, -w:w:100j]
U = Y ** 2
V = X ** 2
# transform according to previous plot
n = len(grid) / 2
scale = n / w
X = (X + w) * scale
Y = (Y + w) * scale
U = (U + w) * scale
V = (V + w) * scale
plt.xticks(ticks=[0, n, 2*n],
labels=[-w, 0, w])
plt.yticks(ticks=[0, n, 2*n],
labels=[-w, 0, w])
plt.streamplot(X, Y, U, V);

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

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

How to scale Graphs and Vector field on the same plot nicely? [weird scales ]

I'm new to matplotlib and trying to plot 2 graphs and one vector field on them on the same plot.
When I run the code below, I ran into some scaling problems. I asssume it's due to the undefined y axis on the graphs but I couldn't figure out how to plot them with y. When I define y's interval, I got very weird pics. I need some high resolution pics [0,7],[0,7] for the graphs and vector fields. When I ran the code below, I got results from the negative y axis which I don't want to plot.
I also wonder how I can make this plot bigger and higher resolution. The current image's size is pretty small.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
#graphs
a=1
b=1
x= np.mgrid[0:6:15j] #problematic part I assume
k = b/(a*x)
l = (-1 + (b+1)*x) / (a*(x**2))
ca =plt.plot(x, k, x,l)
#Vector Field,Quiver
Y, X = np.mgrid[0:6:12j, 0:6:12j]
U = 1- (b + 1)* X + ( a*(X**2) * Y)
V = (b * X) - (a * X**2 * Y)
speed = np.sqrt(U**2 + V**2)
UN = U/speed
VN = V/speed
quiv = plt.quiver(X, Y, UN, VN,
color='Black',
headlength=5)
plt.show()
When i run your code, I get runtime warnings for division by zero, which turns the first value of k infinite, and l -infinite. not sure that's your problem though, what exactly do you mean by scaling problems? do you want the graph to be focused on a specific range of x and y? you would normally use plt.xlim and plt.ylim for that sort of thing. try adding something like this:
plt.ylim((-0.2,6.2))
if you're running this from the command line, you should also be able to resize the graph manually (just by dragging the corner of the window), and save that graph, for a larger image. if you need an even higher resolution or more precise control, check out the savefig function. you can call it before running 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.

Mayavi points3d with different size and colors

Is it possible in mayavi to specify individually both the size and the colors of every point?
That API is cumbersome to me.
points3d(x, y, z...)
points3d(x, y, z, s, ...)
points3d(x, y, z, f, ...)
x, y and z are numpy arrays, or lists, all of the same shape, giving the positions of the points.
If only 3 arrays x, y, z are given, all the points are drawn with the same size and color.
In addition, you can pass a fourth array s of the same shape as x, y, and z giving an associated scalar value for each point, or a function f(x, y, z) returning the scalar value. This scalar value can be used to modulate the color and the size of the points.
So in this case scalar controls both the size and the color and it's not possible to disentangle them. I want a way to specify size as a (N,1) array and color as another (N,1) array individually..
Seems complicated?
Each VTK source has a dataset for both scalars and vectors.
The trick I use in my program to getting the color and size to differ is to bypass the mayavi source and directly in the VTK source, use scalars for color and vectors for size (it probably works the other way around as well).
nodes = points3d(x,y,z)
nodes.glyph.scale_mode = 'scale_by_vector'
#this sets the vectors to be a 3x5000 vector showing some random scalars
nodes.mlab_source.dataset.point_data.vectors = np.tile( np.random.random((5000,)), (3,1))
nodes.mlab_source.dataset.point_data.scalars = np.random.random((5000,))
You may need to transpose the 5000x3 vector data or otherwise shift the matrix dimensions somehow.
I agree that the API that Mayavi provides here is unpleasant. The Mayavi documentation suggests the following hack (which I have paraphrased slightly) to independently adjust the size and color of points.
pts = mayavi.mlab.quiver3d(x, y, z, sx, sy, sz, scalars=c, mode="sphere", scale_factor=f)
pts.glyph.color_mode = "color_by_scalar"
pts.glyph.glyph_source.glyph_source.center = [0,0,0]
This will display x,y,z points as spheres, even though you're calling mayavi.mlab.quiver3d. Mayavi will use the norm of sx,sy,sz vectors to determine the size the points, and will use the scalar values in c to index into a color map. You can optionally supply a constant size scaling factor, which will be applied to all the points.
This is certainly not the most self-documenting code you'll ever write, but it works.
I also agree that API is ugly. I just did a simple and complete example with using #aestrivex's idea:
from mayavi.mlab import *
import numpy as np
K = 10
xx = np.arange(0, K, 1)
yy = np.arange(0, K, 1)
x, y = np.meshgrid(xx, yy)
x, y = x.flatten(), y.flatten()
z = np.zeros(K*K)
colors = 1.0 * (x + y)/(max(x)+max(y))
nodes = points3d(x, y, z, scale_factor=0.5)
nodes.glyph.scale_mode = 'scale_by_vector'
nodes.mlab_source.dataset.point_data.scalars = colors
show()
which produces:
If, as in my case, anyone is trying to for example update the scale of a single point, or the color of a point upon a clicking action or a keypress event, you may need to add the following line to make sure it updates the scalars even after the figure is already displayed (I add the complete example of my function that modifies the size of a point upon clicking on it as it might be helpful to some people) :
def picker(picker):
if picker.actor in glyphs.actor.actors:
point_id = picker.point_id//glyph_points.shape[0]
# If the no points have been selected, we have '-1'
if point_id != -1:
glyphs.mlab_source.dataset.point_data.scalars[point_id] = 10
# following line is necessary for the live update
glyphs.mlab_source.dataset.modified()
# you would typically use this function the following way in your main :
figure = mlab.gcf()
mlab.clf()
# define your points
pts = ...
# define scalars or they will be defined to None by default
s = len(pts)*[1]
glyphs = mlab.points3d(pts[:,0], pts[:,1], pts[:,2], s, scale_factor=1, mode='cube')
glyph_points = glyphs.glyph.glyph_source.glyph_source.output.points.to_array()
picker = figure.on_mouse_pick(picker, button='Left')
picker.tolerance = 0.01
mlab.show()
Inspired from this example : https://docs.enthought.com/mayavi/mayavi/auto/example_select_red_balls.html

Categories

Resources