How to join/connect/group multiple objects in Mayavi2 - python

I would like to combine multiple Mayavi objects into a single "grouped" object so that I control all of their properties together. For example I created the following bi-convex lens shape by combining 3 built-in surfaces (two spheres and one cylinder). Now I would like to assign uniform properties (specularity, ambient color, etc) to all of the constituent surfaces at a time (not individually). Also, I would like to translate/rotate the lens as a whole. I am not sure how to accomplish this.
Here is the bi-convex lens created in Mayavi (code given below):
As it can be seen in the following figure, the above lens is composed of three surfaces:
Here is the code for building the bi-convex lens:
import numpy as np
from mayavi import mlab
from mayavi.sources.builtin_surface import BuiltinSurface
from mayavi.modules.surface import Surface
from mayavi.filters.transform_data import TransformData
def lensUsingMayaviBuiltinSphere(radius=0.5, semiDiam=0.25, thickness=0.9):
"""
Render a bi-convex lens
"""
engine = mlab.get_engine()
sag = radius - np.sqrt(radius**2 - semiDiam**2)
cyl_height = thickness - 2.0*sag # thickness of the cylinder in between
# Create Mayavi data sources -- sphere_h1_src, sphere_h2_src, cylinder_src
# half 1: source = sphere_h1_src
sphere_h1_src = BuiltinSurface()
engine.add_source(sphere_h1_src)
sphere_h1_src.source = 'sphere'
sphere_h1_src.data_source.radius = radius
sphere_h1_src.data_source.center = np.array([ 0., 0., -np.sqrt(radius**2 - semiDiam**2) + cyl_height/2.0])
sphere_h1_src.data_source.end_phi = np.rad2deg(np.arcsin(semiDiam/radius)) #60.0
sphere_h1_src.data_source.end_theta = 360.0
sphere_h1_src.data_source.phi_resolution = 300
sphere_h1_src.data_source.theta_resolution = 300
# half 2: source = sphere_h2_src
sphere_h2_src = BuiltinSurface()
engine.add_source(sphere_h2_src)
sphere_h2_src.source = 'sphere'
sphere_h2_src.data_source.radius = radius
sphere_h2_src.data_source.center = np.array([ 0., 0., np.sqrt(radius**2 - semiDiam**2) - cyl_height/2.0])
sphere_h2_src.data_source.start_phi = 180.0 - np.rad2deg(np.arcsin(semiDiam/radius))
sphere_h2_src.data_source.end_phi = 180.0
sphere_h2_src.data_source.end_theta = 360.0
sphere_h2_src.data_source.phi_resolution = 300
sphere_h2_src.data_source.theta_resolution = 300
# cylinder source data in between
cylinder_src = BuiltinSurface()
engine.add_source(cylinder_src)
cylinder_src.source = 'cylinder'
cylinder_src.data_source.center = np.array([ 0., 0., 0.])
if cyl_height > 0:
cylinder_src.data_source.height = cyl_height
else:
cylinder_src.data_source.height = 0.0
cylinder_src.data_source.radius = semiDiam
cylinder_src.data_source.capping = False
cylinder_src.data_source.resolution = 50
# Add transformation filter to align cylinder length along z-axis
transform_data_filter = TransformData()
engine.add_filter(transform_data_filter, cylinder_src)
Rt_c = [ 1.0000, 0.0000, 0.0000, 0.00,
0.0000, 0.0000, -1.0000, 0.00,
0.0000, 1.0000, 0.0000, 0.00,
0.0000, 0.0000, 0.0000, 1.00]
transform_data_filter.transform.matrix.__setstate__({'elements': Rt_c})
transform_data_filter.widget.set_transform(transform_data_filter.transform)
transform_data_filter.filter.update()
transform_data_filter.widget.enabled = False # disable the rotation control further.
# Add surface modules to each source
right_surface = Surface()
engine.add_filter(right_surface, sphere_h1_src)
left_surface = Surface()
engine.add_filter(left_surface, sphere_h2_src)
cyl_surface = Surface()
engine.add_filter(cyl_surface, transform_data_filter)
fig = mlab.figure()
# Add lens
lensUsingMayaviBuiltinSphere(radius=2, semiDiam=1.2)
mlab.show()

I don't know of a way to combine sources in the way you are looking for. I think in fact that is probably impossible since under the hood the BuiltinSurface object has specific vtk sources that are not what you want. It should however be possible to simply use a different source that gives what you want. In this case you could generate a biconvex lens with mlab.mesh:
a,c,h=3,1,.2
phi,theta = np.mgrid[0:2*np.pi:np.pi/250, 0:2*np.pi:np.pi/250]
x=a*np.cos(theta)*np.sin(phi)
y=a*np.sin(theta)*np.sin(phi)
z=c*np.cos(phi)+(h*(-1)**(np.cos(phi)<0))
mlab.mesh(x,y,z,color=(1,1,1)
mlab.show()
One minor difference is that this surface is smooth. This is the nature of sampling a single surface --i.e., this result is a direct consequence of what your question asks to do. If this is an important feature of your figure, I would suggest an entirely different approach: wrap the 3 sources in a class and have the event handler update the relevant attributes on all three.

Based on the code by #aestrivex, here is one way of getting the desired output (lens with sharp edges). Note that this is not a solution for connecting multiple Mayavi objects.
import numpy as np
from mayavi import mlab
# Control parameters
# r is the semi-diameter of the lens
# c controls the center thickness of the lens
# h controls the curvature of the surfaces (lesser the value more the curvature)
r, c, h = 3, .75, .9
delta_phi = np.pi/250.0 # phi == azimuth (0 <= phi <= pi)
delta_theta = np.pi/100.0 # theta == zenith (0 <= theta <= pi)
phi, theta = np.mgrid[0:2.0*np.pi + delta_phi:delta_phi,0:np.pi + delta_theta:delta_theta]
# The Exact threshold values for masking tz, txy will change depending upon the
# sampling of theta. txy is always slightly less than tz. tz should be around 0.3
tz, txy = 0.279, 0.275
x = r*np.sin(theta)*np.cos(phi)*(np.abs(np.cos(theta)) > txy)
y = r*np.sin(theta)*np.sin(phi)*(np.abs(np.cos(theta)) > txy)
z = c*np.cos(theta)*(h**(-1)*( np.abs(np.cos(theta)) > tz))
mlab.mesh(x,y,z,color=(1,1,1))
mlab.show()
And here is the output:

Related

Matplotlib circle vertices out of range

I'm trying to test each red point for inclusion with the blue circle. However, the path for my circle has some strange values which is what I believe is causing the inclusion test to not work as intended.
The axis list in the code below represent the max & min for the longitude and latitude respectively. Given that the circle is plotted at the right location I expect its path to have vertices within that range which is not the case.
Where am I going wrong?
from matplotlib.patches import Ellipse
import matplotlib.path as mpltPath
axis = [4.7469287189121001, 5.0340994897259534, 52.282706941081258, 52.432452803031282]
unitX = (axis[1]-axis[0])/10
unitY = (axis[3]-axis[2])/10
fig, ax = plt.subplots(figsize=(8, 6))
for i, s in enumerate(housing_prices_shapes['2015']):
ax.plot(s[:,0], s[:,1], linewidth=0.5, c='0.5')
circle = Ellipse(housing_prices_shapes['2015'][0][0], width=unitX, height=unitY, edgecolor='b', facecolor='None')
ax.add_patch(circle)
listings_coordinates = airbnb_prices['2015'][["longitude", "latitude"]]
path_temp = circle.get_path()
transform = circle.get_transform()
new_path = transform.transform_path(path_temp)
path = mpltPath.Path(new_path.vertices)
flag = path.contains_points(listings_coordinates)
ax.scatter(listings_coordinates['longitude'].values, listings_coordinates['latitude'].values, c='r', s=0.5)
Each value used to create the circle prints as follow:
print(housing_prices_shapes['2015'][0][0], unitX, unitY)
[ 4.94147517 52.3670552 ] 0.028717077081385333 0.01497458619500236
The path variable which I expect to be in the same range as the longitude and latitude print as this, which is way off:
print(new_path.vertices)
array([[ 374.41773395, 221.41011283],
[ 380.33706714, 221.41011283],
[ 386.01475666, 223.12842659],
[ 390.2003573 , 226.18661544],
[ 394.38595794, 229.24480429],
[ 396.73773395, 233.39318067],
[ 396.73773395, 237.71811283],
[ 396.73773395, 242.04304498],
[ 394.38595794, 246.19142136],
[ 390.2003573 , 249.24961022],
[ 386.01475666, 252.30779907],
[ 380.33706714, 254.02611283],
[ 374.41773395, 254.02611283],
[ 368.49840076, 254.02611283],
[ 362.82071123, 252.30779907],
[ 358.63511059, 249.24961022],
[ 354.44950995, 246.19142136],
[ 352.09773395, 242.04304498],
[ 352.09773395, 237.71811283],
[ 352.09773395, 233.39318067],
[ 354.44950995, 229.24480429],
[ 358.63511059, 226.18661544],
[ 362.82071123, 223.12842659],
[ 368.49840076, 221.41011283],
[ 374.41773395, 221.41011283],
[ 374.41773395, 221.41011283]])
And of course no points are flagged as True:
print(any(flag))
False
As ImportanceOfBeingErnest noted in a comment, you shouldn't transform your ellipse path. Well, using the untransformed path wouldn't directly be useful either; you could probably make use of circle.get_verts().
But let me cut through your Gordian Knot: why not explicitly test for falling inside your ellipse? The equation of an ellipse with center (x0,y0) and semi-axes of length a and b is
(x-x0)^2/a^2 + (y-y0)^2/b^2 = 1
and it's really simple to see that the inside of the ellipse is then defined by the inequality
(x-x0)^2/a^2 + (y-y0)^2/b^2 < 1
(it's easy to see this for a circle, and you can think of an ellipse as a circle that went through a linear transform along one of its axes).
So use logical indexing to find which points are inside your ellipse! The only thing you need to watch out for is that the parameters passed to Ellipse are 2*a and 2*b:
points = airbnb_prices['2015'][['longitude', 'latitude']] # shape (N,2)
center = housing_prices_shapes['2015'][0][0] # shape (2,) broadcasts to (N,2)
a = unitX / 2 # scalar
b = unitY / 2 # scalar
# make use of broadcasting while we're at it
flag = ((points-center)**2 / np.array([a,b])**2).sum(axis=1) < 1
Now flag is a shape-(N,) logical array, i.e. the same shape and size as expected from your original call to contains_points.

Rotate an image around its x-axis

I need to rotate an image around its x-axis (or y-axis). I can easily create such an animation with avisynth, but now I need to implement that effect with Python's moviepy module. I can easily rotate an image with the following script but need some clues how to rotate it in 2D or 3D.
from moviepy.editor import *
clip = ImageClip('my_image.jpg')
rotated_clip = (clip.add_mask()
.fx(vfx.resize, width=300, height=300)
.fx(vfx.rotate, lambda t: 90*t, expand=False)
.set_duration(5))
final_clip = CompositeVideoClip([rotated_clip.set_pos("center")], size=(800,800), bg_color=3*[255])
final_clip.write_videofile("test.mp4", fps=25, codec="libx264")
Here is the avisynth script that actually generated that example image. Please note, it does require "QUAD" plugin.
function stars(clip c, int r) {
c.Overlay(x=rand(c.width),y=rand(c.height),BlankClip(c,width=1,height=1,color=$030301*rand(85)))
(r==0)? last : stars(r-1)
Trim(0,-1).Loop(c.Framecount, 0, 0)
}
width= 800
height=600
length=100000
Tcolor=$000040
Bcolor=$000018
StackVertical(BlankClip(length=length,width=2,height=1,color=TColor,pixel_type="RGB32"),BlankClip(length=length,width=2,height=1,color=BColor)).BilinearResize(width,2*height,src_top=0,src_height=2).Crop(0,height/2,0,-height/2).Stars(width*height/3072)
ImageSource("path_to_image.png", start=0, end=total_time, fps=300, pixel_type="RGB32")
#BlankClip(length=length,FPS=25,width=640,height=480,color=$000018,pixel_type="RGB32")
#ColorBars()
HALFCYCLE=10 # Frames in 1 HALF rotation (spinning clip)
NSPIN = 1 # Number of HALF rotations in spinning clip
NSTILL = 10 # Frames in STILL clip
V = 0.2 # Tilt/Yaw
tim = PI / HALFCYCLE
ScriptClip("""
c=last
t=tim*current_frame
t1x= 0.5 - 0.5 * cos(t) # BOTH Left
t2x= 0.5 + 0.5 * cos(t) # BOTH Right
#
t1y= 0.0 + V * sin(t) # ] both Top's opposite sign
t2y= 0.0 - V * sin(t) # ]
t3y= 1.0 + V * sin(t) # [ both Bottoms opposite sign
t4y= 1.0 - V * sin(t) # [
ResetMask
quad(t1x,t1y, t2x,t2y, t2x,t3y, t1x,t4y, normal=true)
#Overlay(c,last,mask=last.ShowAlpha())
""")
SPIN=Trim(0,-(NSPIN*HALFCYCLE +1)) # Spinning clip, + 1 to complete last spin
STILL=SPIN.Trim(SPIN.FrameCount-1,-1).Loop(NSTILL,0,0)
SPIN2=Trim((NSPIN%2 ==0)?0:HALFCYCLE,-(NSPIN*HALFCYCLE +1))
SPIN ++ STILL ++ SPIN2
Return Last
One way to do this is to use Vapory, another library by the author of MoviePy, which facilitates the operation of POV-Ray via Python. You can create a rectangle within a 3D scene and rotate it around whatever axis you like, saving frames into a MoviePy clip at intervals.
MoviePy + Vapory code
from moviepy.editor import concatenate, ImageClip, VideoClip
from vapory import *
img_path = './baseball.png'
img_clip = ImageClip(img_path)
W, H = img_clip.w, img_clip.h
AR = 1.0*W/H
# Set rotation rate by defining the period (in seconds) for 360 deg. revolution
t_rev = 2.0
t_half = t_rev/2.0 # The time required for a half revolution
t_still = 0.8 # How long (in seconds) to hold the half rotated image still
# Static POV-Ray objects
cam = Camera('location', [ 0, 0, -1],
'look_at', [ 0, 0, 0])
light = LightSource([0, 0, -1]) # Light at camera location
bg = Background('color', [0, 0, 0]) # Black background
def scene(t):
""" Returns the scene at time 't' (in seconds) """
s = Scene(camera = cam, objects = [light, bg])
# Add POV-Ray box with image textured on it
s = s.add_objects([
Box([0, 0, 0],
[W, H, 0],
Texture(Pigment(ImageMap('"{}"'.format(img_path), 'once')),
Finish('ambient', 1.0)),
'translate', [-0.5, -0.5, 0],
'scale', [AR, 1, 0],
'rotate', [0, (360/t_rev)*t, 0])]) # Can change axis of rotation here
return s
def make_frame(t):
return scene(t).render(width=W, height=H, antialiasing=0.1)
still_1 = VideoClip(make_frame).to_ImageClip(t=0).set_duration(t_still)
half_1 = VideoClip(make_frame).subclip(0, t_half)
still_2 = VideoClip(make_frame).to_ImageClip(t=t_half).set_duration(t_still)
half_2 = VideoClip(make_frame).subclip(t_half, t_rev)
final_clip = concatenate([still_1, half_1, still_2, half_2])
final_clip.write_gif("./baseball_rot.gif", fps=15)
Output GIF
Other thoughts:
The main things you might want to change are img_path, t_rev (the time for a full 360 degree revolution), t_still, and the output frame rate.
I removed one column of pixels from your example image to get it down to an even width (150 px). It's not important if you only want to make GIFs, but if you want to produce an x264-encoded MP4, you should probably use mod2 dimensions.
It seems like overkill to use a ray tracer for this problem, but it's the first working solution I came up with. I wanted to represent the image as a 2D rectangle in a 3D scene, where I could simply specify an angle of rotation and the 3D library would handle the rest.
It should be possible to solve this problem using a projective transform from scikit-image, as in this MoviePy example. Note specifically, the trapzWarp function near the middle of that code listing.

Fast b-spline algorithm with numpy/scipy

I need to compute bspline curves in python. I looked into scipy.interpolate.splprep and a few other scipy modules but couldn't find anything that readily gave me what I needed. So i wrote my own module below. The code works fine, but it is slow (test function runs in 0.03s, which seems like a lot considering i'm only asking for 100 samples with 6 control vertices).
Is there a way to simplify the code below with a few scipy module calls, which presumably would speed it up? And if not, what could i do to my code to improve its performance?
import numpy as np
# cv = np.array of 3d control vertices
# n = number of samples (default: 100)
# d = curve degree (default: cubic)
# closed = is the curve closed (periodic) or open? (default: open)
def bspline(cv, n=100, d=3, closed=False):
# Create a range of u values
count = len(cv)
knots = None
u = None
if not closed:
u = np.arange(0,n,dtype='float')/(n-1) * (count-d)
knots = np.array([0]*d + range(count-d+1) + [count-d]*d,dtype='int')
else:
u = ((np.arange(0,n,dtype='float')/(n-1) * count) - (0.5 * (d-1))) % count # keep u=0 relative to 1st cv
knots = np.arange(0-d,count+d+d-1,dtype='int')
# Simple Cox - DeBoor recursion
def coxDeBoor(u, k, d):
# Test for end conditions
if (d == 0):
if (knots[k] <= u and u < knots[k+1]):
return 1
return 0
Den1 = knots[k+d] - knots[k]
Den2 = knots[k+d+1] - knots[k+1]
Eq1 = 0;
Eq2 = 0;
if Den1 > 0:
Eq1 = ((u-knots[k]) / Den1) * coxDeBoor(u,k,(d-1))
if Den2 > 0:
Eq2 = ((knots[k+d+1]-u) / Den2) * coxDeBoor(u,(k+1),(d-1))
return Eq1 + Eq2
# Sample the curve at each u value
samples = np.zeros((n,3))
for i in xrange(n):
if not closed:
if u[i] == count-d:
samples[i] = np.array(cv[-1])
else:
for k in xrange(count):
samples[i] += coxDeBoor(u[i],k,d) * cv[k]
else:
for k in xrange(count+d):
samples[i] += coxDeBoor(u[i],k,d) * cv[k%count]
return samples
if __name__ == "__main__":
import matplotlib.pyplot as plt
def test(closed):
cv = np.array([[ 50., 25., -0.],
[ 59., 12., -0.],
[ 50., 10., 0.],
[ 57., 2., 0.],
[ 40., 4., 0.],
[ 40., 14., -0.]])
p = bspline(cv,closed=closed)
x,y,z = p.T
cv = cv.T
plt.plot(cv[0],cv[1], 'o-', label='Control Points')
plt.plot(x,y,'k-',label='Curve')
plt.minorticks_on()
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.xlim(35, 70)
plt.ylim(0, 30)
plt.gca().set_aspect('equal', adjustable='box')
plt.show()
test(False)
The two images below shows what my code returns with both closed conditions:
So after obsessing a lot about my question, and much research, i finally have my answer. Everything is available in scipy , and i'm putting my code here so hopefully someone else can find this useful.
The function takes in an array of N-d points, a curve degree, a periodic state (opened or closed) and will return n samples along that curve. There are ways to make sure the curve samples are equidistant but for the time being i'll focus on this question, as it is all about speed.
Worthy of note: I can't seem to be able to go beyond a curve of 20th degree. Granted, that's overkill already, but i figured it's worth mentioning.
Also worthy of note: on my machine the code below can calculate 100,000 samples in 0.017s
import numpy as np
import scipy.interpolate as si
def bspline(cv, n=100, degree=3, periodic=False):
""" Calculate n samples on a bspline
cv : Array ov control vertices
n : Number of samples to return
degree: Curve degree
periodic: True - Curve is closed
False - Curve is open
"""
# If periodic, extend the point array by count+degree+1
cv = np.asarray(cv)
count = len(cv)
if periodic:
factor, fraction = divmod(count+degree+1, count)
cv = np.concatenate((cv,) * factor + (cv[:fraction],))
count = len(cv)
degree = np.clip(degree,1,degree)
# If opened, prevent degree from exceeding count-1
else:
degree = np.clip(degree,1,count-1)
# Calculate knot vector
kv = None
if periodic:
kv = np.arange(0-degree,count+degree+degree-1)
else:
kv = np.clip(np.arange(count+degree+1)-degree,0,count-degree)
# Calculate query range
u = np.linspace(periodic,(count-degree),n)
# Calculate result
return np.array(si.splev(u, (kv,cv.T,degree))).T
To test it:
import matplotlib.pyplot as plt
colors = ('b', 'g', 'r', 'c', 'm', 'y', 'k')
cv = np.array([[ 50., 25.],
[ 59., 12.],
[ 50., 10.],
[ 57., 2.],
[ 40., 4.],
[ 40., 14.]])
plt.plot(cv[:,0],cv[:,1], 'o-', label='Control Points')
for d in range(1,21):
p = bspline(cv,n=100,degree=d,periodic=True)
x,y = p.T
plt.plot(x,y,'k-',label='Degree %s'%d,color=colors[d%len(colors)])
plt.minorticks_on()
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.xlim(35, 70)
plt.ylim(0, 30)
plt.gca().set_aspect('equal', adjustable='box')
plt.show()
Results for both opened or periodic curves:
ADDENDUM
As of scipy-0.19.0 there is a new scipy.interpolate.BSpline function that can be used.
import numpy as np
import scipy.interpolate as si
def scipy_bspline(cv, n=100, degree=3, periodic=False):
""" Calculate n samples on a bspline
cv : Array ov control vertices
n : Number of samples to return
degree: Curve degree
periodic: True - Curve is closed
"""
cv = np.asarray(cv)
count = cv.shape[0]
# Closed curve
if periodic:
kv = np.arange(-degree,count+degree+1)
factor, fraction = divmod(count+degree+1, count)
cv = np.roll(np.concatenate((cv,) * factor + (cv[:fraction],)),-1,axis=0)
degree = np.clip(degree,1,degree)
# Opened curve
else:
degree = np.clip(degree,1,count-1)
kv = np.clip(np.arange(count+degree+1)-degree,0,count-degree)
# Return samples
max_param = count - (degree * (1-periodic))
spl = si.BSpline(kv, cv, degree)
return spl(np.linspace(0,max_param,n))
Testing for equivalency:
p1 = bspline(cv,n=10**6,degree=3,periodic=True) # 1 million samples: 0.0882 sec
p2 = scipy_bspline(cv,n=10**6,degree=3,periodic=True) # 1 million samples: 0.0789 sec
print np.allclose(p1,p2) # returns True
Giving optimization tips without profiling data is a bit like shooting in the dark. However, the function coxDeBoor seems to be called very often. This is where I would start optimizing.
Function calls in Python are expensive. You should try to replace the coxDeBoor recursion with iteration to avoid excessive function calls. Some general information how to do this can be found in answers to this question. As stack/queue you can use collections.deque.

How to plot 3D ellipsoid with Mayavi

I would like to plot diffusion tensors(ellipsoid) in diffusion MRI. The data have three Eigenvalues of the corresponding diffusion tensor. I want to draw an 3D Ellipsoid with its semi-axes lengths corresponding to those three Eigenvalues.
How to do it with Mayavi?
Google brought me here and to the answer. I found how to render an ellipsoid here: https://github.com/spyke/spyke/blob/master/demo/mayavi_test.py and combined it with the arrow from here https://stackoverflow.com/a/20109619/2389450 to produce something like: http://imageshack.com/a/img673/7664/YzbTHY.png
Cheers,
Max
Code:
from mayavi.api import Engine
from mayavi.sources.api import ParametricSurface
from mayavi.modules.api import Surface
from mayavi import mlab
from tvtk.tools import visual
import numpy as np
def Arrow_From_A_to_B(x1, y1, z1, x2, y2, z2,scale=None):
ar1=visual.arrow(x=x1, y=y1, z=z1)
ar1.length_cone=0.4
arrow_length=np.sqrt((x2-x1)**2+(y2-y1)**2+(z2-z1)**2)
if scale is None:
ar1.actor.scale=[arrow_length, arrow_length, arrow_length]
else:
ar1.actor.scale=scale
ar1.pos = ar1.pos/arrow_length
ar1.axis = [x2-x1, y2-y1, z2-z1]
return ar1
engine = Engine()
engine.start()
scene = engine.new_scene()
scene.scene.disable_render = True # for speed
visual.set_viewer(scene)
surfaces = []
for i in range(2):
source = ParametricSurface()
source.function = 'ellipsoid'
engine.add_source(source)
surface = Surface()
source.add_module(surface)
actor = surface.actor # mayavi actor, actor.actor is tvtk actor
#actor.property.ambient = 1 # defaults to 0 for some reason, ah don't need it, turn off scalar visibility instead
actor.property.opacity = 0.7
actor.property.color = (0,0,1) # tuple(np.random.rand(3))
actor.mapper.scalar_visibility = False # don't colour ellipses by their scalar indices into colour map
actor.property.backface_culling = True # gets rid of weird rendering artifact when opacity is < 1
actor.property.specular = 0.1
#actor.property.frontface_culling = True
actor.actor.orientation = np.array([1,0,0]) * 360 # in degrees
actor.actor.origin = np.array([0,0,0])
actor.actor.position = np.array([0,0,0])
actor.actor.scale = np.array([ 0.26490647, 0.26490647, 0.92717265])
actor.enable_texture=True
actor.property.representation = ['wireframe', 'surface'][i]
surfaces.append(surface)
Arrow_From_A_to_B(0,0,0, 0.26490647, 0, 0,np.array([0.26490647,0.4,0.4]))
Arrow_From_A_to_B(0,0,0, 0, 0.26490647, 0,np.array([0.4,0.26490647,0.4]))
Arrow_From_A_to_B(0,0,0, 0, 0, 0.92717265,np.array([0.4,0.4,0.92717265]))
source.scene.background = (1.0,1.0,1.0)
scene.scene.disable_render = False # now turn it on
# set the scalars, this has to be done some indeterminate amount of time
# after each surface is created, otherwise the scalars get overwritten
# later by their default of 1.0
for i, surface in enumerate(surfaces):
vtk_srcs = mlab.pipeline.get_vtk_src(surface)
print('len(vtk_srcs) = %d' % len(vtk_srcs))
vtk_src = vtk_srcs[0]
try: npoints = len(vtk_src.point_data.scalars)
except TypeError:
print('hit the TypeError on surface i=%d' % i)
npoints = 2500
vtk_src.point_data.scalars = np.tile(i, npoints)
# on pick, find the ellipsoid with origin closest to the picked coord,
# then check if that coord falls within that nearest ellipsoid, and if
# so, print out the ellispoid id, or pop it up in a tooltip
mlab.show()

Plot periodic trajectories

I have some data of a particle moving in a corridor with closed boundary conditions.
Plotting the trajectory leads to a zig-zag trajectory.
I would like to know how to prevent plot() from connecting the points where the particle comes back to the start. Some thing like in the upper part of the pic, but without "."
The first idea I had was to find the index where the numpy array a[:-1]-a[1:] becomes positive and then plot from 0 to that index. But how would I get the index of the first occurrence of a positive element of a[:-1]-a[1:]?
Maybe there are some other ideas.
I'd go a different approach. First, I'd determine the jump points not by looking at the sign of the derivative, as probably the movement might go up or down, or even have some periodicity in it. I'd look at those points with the biggest derivative.
Second, an elegant approach to have breaks in a plot line is to mask one value on each jump. Then matplotlib will make segments automatically. My code is:
import pylab as plt
import numpy as np
xs = np.linspace(0., 100., 1000.)
data = (xs*0.03 + np.sin(xs) * 0.1) % 1
plt.subplot(2,1,1)
plt.plot(xs, data, "r-")
#Make a masked array with jump points masked
abs_d_data = np.abs(np.diff(data))
mask = np.hstack([ abs_d_data > abs_d_data.mean()+3*abs_d_data.std(), [False]])
masked_data = np.ma.MaskedArray(data, mask)
plt.subplot(2,1,2)
plt.plot(xs, masked_data, "b-")
plt.show()
And gives us as result:
The disadvantage of course is that you lose one point at each break - but with the sampling rate you seem to have I guess you can trade this in for simpler code.
To find where the particle has crossed the upper boundary, you can do something like this:
>>> import numpy as np
>>> a = np.linspace(0, 10, 50) % 5
>>> a = np.linspace(0, 10, 50) % 5 # some sample data
>>> np.nonzero(np.diff(a) < 0)[0] + 1
array([25, 49])
>>> a[24:27]
array([ 4.89795918, 0.10204082, 0.30612245])
>>> a[48:]
array([ 4.79591837, 0. ])
>>>
np.diff(a) calculates the discrete difference of a, while np.nonzero finds where the condition np.diff(a) < 0 is negative, i.e., the particle has moved downward.
To avoid the connecting line you will have to plot by segments.
Here's a quick way to plot by segments when the derivative of a changes sign:
import numpy as np
a = np.linspace(0, 20, 50) % 5 # similar to Micheal's sample data
x = np.arange(50) # x scale
indices = np.where(np.diff(a) < 0)[0] + 1 # the same as Micheal's np.nonzero
for n, i in enumerate(indices):
if n == 0:
plot(x[:i], a[:i], 'b-')
else:
plot(x[indices[n - 1]:i], a[indices[n - 1]:i], 'b-')
Based on Thorsten Kranz answer a version which adds points to the original data when the 'y' crosses the period. This is important if the density of data-points isn't very high, e.g. np.linspace(0., 100., 100) vs. the original np.linspace(0., 100., 1000). The x position of the curve transitions are linear interpolated. Wrapped up in a function its:
import numpy as np
def periodic2plot(x, y, period=np.pi*2.):
indexes = np.argwhere(np.abs(np.diff(y))>.5*period).flatten()
index_shift = 0
for i in indexes:
i += index_shift
index_shift += 3 # in every loop it adds 3 elements
if y[i] > .5*period:
x_transit = np.interp(period, np.unwrap(y[i:i+2], period=period), x[i:i+2])
add = np.ma.array([ period, 0., 0.], mask=[0,1,0])
else:
# interpolate needs sorted xp = np.unwrap(y[i:i+2], period=period)
x_transit = np.interp(0, np.unwrap(y[i:i+2], period=period)[::-1], x[i:i+2][::-1])
add = np.ma.array([ 0., 0., period], mask=[0,1,0])
x_add = np.ma.array([x_transit]*3, mask=[0,1,0])
x = np.ma.hstack((x[:i+1], x_add, x[i+1:]))
y = np.ma.hstack((y[:i+1], add, y[i+1:]))
return x, y
The code for comparison to the original answer of Thorsten Kranz with lower data-points density.
import matplotlib.pyplot as plt
x = np.linspace(0., 100., 100)
y = (x*0.03 + np.sin(x) * 0.1) % 1
#Thorsten Kranz: Make a masked array with jump points masked
abs_d_data = np.abs(np.diff(y))
mask = np.hstack([np.abs(np.diff(y))>.5, [False]])
masked_y = np.ma.MaskedArray(y, mask)
# Plot
plt.figure()
plt.plot(*periodic2plot(x, y, period=1), label='This answer')
plt.plot(x, masked_y, label='Thorsten Kranz')
plt.autoscale(enable=True, axis='both', tight=True)
plt.legend(loc=1)
plt.tight_layout()

Categories

Resources