How to do color code the 3D scatter plot according to time of occurrence in an animated graph? - python-3.x

I have developed a code to create an animated scatter graph.
About the dataset, I have the X,Y,Z coordinate of each point and each event point are assigned a value (M) and each happened at a specific time (t).
I have the size of each point to be proportional to their value (i.e., M), now I want to add the color to each point so that it also shows the time of occurrence. I know I have to use .set_color(c) but c value expects a tuple value. I tried to normalize the values of the time to map the color from this post. However, there is something that I miss because the code is not working to color the points with related time. I would appreciate it if someone could share their experiences?
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
from IPython.display import HTML # Animation on jupyter lab
from matplotlib.animation import PillowWriter # For GIF animation
#####Data Generation####
# Space Coordinate
X = np.random.random((100,)) * 255 * 2 - 255
Y = np.random.random((100,)) * 255 * 2 - 255
Z = np.random.random((100,)) * 255 * 2 - 255
# Magnitude of each point
# M = np.random.random((100,))*-1+0.5
M = np.random.randint(1,70, size=100)
# Time
t = np.sort(np.random.random((100,))*10)
#ID each point should be color coded. Moreover, each point belongs to a cluster `ID`
ID = np.sort(np.round([np.random.random((100,))*5]))
x = []
y = []
z = []
m = []
def update_lines(i):
# for i in range (df_IS["EASTING [m]"].size):
dx = X[i]
dy = Y[i]
dz = Z[i]
dm = M[i]
# text.set_text("{:d}: [{:.0f}] Mw[{:.2f}]".format(ID[i], t[i],ID[i])) # for debugging
x.append(dx)
y.append(dy)
z.append(dz)
m.append(dm)
graph._offsets3d = (x, y, z)
graph.set_sizes(m)
return graph,
fig = plt.figure(figsize=(5, 5))
ax = fig.add_subplot(111, projection="3d")
graph = ax.scatter(X, Y, Z, s=M, color='orange') # s argument here
text = fig.text(0, 1, "TEXT", va='top') # for debugging
ax.set_xlim3d(X.min(), X.max())
ax.set_ylim3d(Y.min(), Y.max())
ax.set_zlim3d(Z.min(), Z.max())
# Creating the Animation object
ani = animation.FuncAnimation(fig, update_lines, frames=100, interval=500, blit=False, repeat=False)
# plt.show()
ani.save('test3Dscatter.gif', writer='pillow')
plt.close()
HTML(ani.to_html5_video())

You need to change "Color" to "cmap" so that you are able to call set of colors, see below:
graph = ax.scatter(X, Y, Z, s=M, cmap='jet') #jet is similar to rainbow

Related

How can I convert XYZ point cloud to binary mask image

I want to convert a set of point cloud (X, Y, Z) to a binary mask image using python. The problem is that these points are float and out of range of 0-255. To more specific, the points are related to an object (rectangle or ellipsoid), so I should make a binary image based on Z dimension, to specify the rectangle, for example, as 0 number and other points as 1 number in binary mask.
Can anyone give me some ideas to achieve my goal?
My point is like this array:
[[-1.56675167e+01 1.59539632e+01 1.15432026e-02]
[-1.26066835e+01 6.48645007e+00 1.15510724e-02]
[-1.18854252e+01 1.71767061e+01 1.15392632e-02]
...
[-1.45721083e+01 1.39116935e+01 -9.86438582e-04]
[-1.42607847e+01 1.28141373e+01 -1.73514791e-03]
[-1.48834319e+01 1.50092497e+01 7.59929187e-04]]
I was tried to get such binary mask that was answered in this example ():
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.path import Path
from descartes import PolygonPatch
import alphashape
from shapely.geometry import Point, Polygon
def poly2mask():
# First of all, I separated the contour of the polygon and get vertices
# in the border
hull = alphashape.alphashape(surface_org, 0.) # convex hull
poly = PolygonPatch(hull, alpha=0.2, edgecolor='#999999')
vertices = poly.get_path().vertices
x = vertices[:, 0] * 10
y = vertices[:, 1] * 10
vertices_ls = list(zip(x, y))
width, height = 120, 120
poly_path = Path(vertices_ls, closed=True)
x, y = np.mgrid[:height, :width]
coors = np.hstack((x.reshape(-1, 1), y.reshape(-1, 1)))
mask = poly_path.contains_points(coors)
mask = mask.reshape(height, width)
#print(mask)
plt.imshow(mask)
plt.ylim(-200, 200)
plt.xlim(-200, 200)
plt.show()
The image would look like this:
enter image description here

Find coordinate on curve

I have plotted curve created by a list with several values. How to find out the x-coordinate that correspond with y-coordinate 0.04400918? This value is not exactly included in the list that describes the curve. Thank you very much.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # 3d graph
from mpl_toolkits.mplot3d import proj3d # 3d graph
import matplotlib.pylab as pl
fig=pl.figure()
ax = Axes3D(fig)
x=[0.02554897, 0.02587839, 0.02623991, 0.02663096, 0.02704882, 0.02749103, 0.02795535, 0.02844018, 0.02894404, 0.02946527, 0.03000235]
y=[0.04739086, 0.0460989, 0.04481555, 0.04354088, 0.04227474, 0.04101689, 0.03976702, 0.03852497, 0.03729052, 0.0360633, 0.03484293]
z=[1.05764017e-18, 1.57788964e-18, 2.00281370e-18, 2.40500994e-18, 2.80239565e-18, 3.19420769e-18, 3.58001701e-18, 3.96024361e-18, 4.33484911e-18, 4.70364652e-18, 5.06672528e-18]
y_point=0.04400918
ax.plot3D(x,y,z)
plt.show()
Here is a specific resolution for your problem.
Some works have already been done for solving line-plane equation. This topic explains how to solve it. Even better, this snippet implements a solution.
For now, we only need to adapt it to our problem.
The first step is to find all the time the line is crossing the plan. To do that, we will iterate over the y dataset and collect all consecutive values when y_point is between them:
lines = []
for i in range(len(y) - 1):
if y[i] >= y_point and y_point >= y[i+1]:
lines.append([[x[i], y[i], z[i]], [x[i+1], y[i+1], z[i+1]]])
Then, for all of these lines, we will solve the intersection equation with the plane. We will use the function provided in sources above.
Finally, we will plot the results
Full code:
# Modules
import numpy as np
import matplotlib.pyplot as plt
# Data
x = [0.02554897, 0.02587839, 0.02623991, 0.02663096, 0.02704882, 0.02749103, 0.02795535, 0.02844018, 0.02894404, 0.02946527, 0.03000235]
y = [0.04739086, 0.0460989, 0.04481555, 0.04354088, 0.04227474, 0.04101689, 0.03976702, 0.03852497, 0.03729052, 0.0360633, 0.03484293]
z = [1.05764017e-18, 1.57788964e-18, 2.00281370e-18, 2.40500994e-18, 2.80239565e-18, 3.19420769e-18, 3.58001701e-18, 3.96024361e-18, 4.33484911e-18, 4.70364652e-18, 5.06672528e-18]
y_point = 0.04400918
# Source: https://rosettacode.org/wiki/Find_the_intersection_of_a_line_with_a_plane#Python
# Resolve intersection
def LinePlaneCollision(planeNormal, planePoint, rayDirection, rayPoint, epsilon=1e-6):
ndotu = planeNormal.dot(rayDirection)
if abs(ndotu) < epsilon:
raise RuntimeError("no intersection or line is within plane")
w = rayPoint - planePoint
si = -planeNormal.dot(w) / ndotu
Psi = w + si * rayDirection + planePoint
return Psi
# For all line, apply the solving process
def solveAllPoints(lines, y_point):
collision_points = []
for line in lines:
# Define plane
planeNormal = np.array([0, 1, 0]) # Plane normal (e.g. y vector)
planePoint = np.array([0, y_point, 0]) # Any point on the plane
# Define ray
rayDirection = line[1] - line[0] # Line direction
rayPoint = line[0] # Any point of the line
# Append point
collision_points.append(LinePlaneCollision(planeNormal, planePoint, rayDirection, rayPoint))
return collision_points
# Find all consecutive Y points crossing the plane.
# This function is only working for the given problem (intersection of the line
# with 1 plan defined by a normal vector = [0,1,0])
def getCrossingLines(y_point, x, y, z):
lines = []
for i in range(len(y) - 1):
if y[i] >= y_point and y_point >= y[i+1]:
lines.append([[x[i], y[i], z[i]], [x[i+1], y[i+1], z[i+1]]])
return np.array(lines)
# Get coordinates for drawing our plane
# Related topic: https://stackoverflow.com/questions/53115276/matplotlib-how-to-draw-a-vertical-plane-in-3d-figure
def getXYZPlane(x, y, z):
xs = np.linspace(min(x), max(x), 100)
zs = np.linspace(min(z), max(z), 100)
X, Z = np.meshgrid(xs, zs)
Y = np.array([y_point for _ in X])
return X, Y, Z
# Create plot
plt3d = plt.figure().gca(projection='3d')
ax = plt.gca()
# Draw data line
ax.plot3D(x,y,z)
# Plot plan
X, Y, Z = getXYZPlane(x, y, z)
ax.plot_surface(X, Y, Z)
# Draw crossing points (lines-planes)
lines = getCrossingLines(y_point, x, y , z)
for pt in solveAllPoints(lines, y_point):
ax.scatter(pt[0], pt[1], pt[2], color='green')
plt.show()
Output

Draw curves with triple colors and width by using matplotlib and LineCollection [duplicate]

The figure above is a great artwork showing the wind speed, wind direction and temperature simultaneously. detailedly:
The X axes represent the date
The Y axes shows the wind direction(Southern, western, etc)
The variant widths of the line were stand for the wind speed through timeseries
The variant colors of the line were stand for the atmospheric temperature
This simple figure visualized 3 different attribute without redundancy.
So, I really want to reproduce similar plot in matplotlib.
My attempt now
## Reference 1 http://stackoverflow.com/questions/19390895/matplotlib-plot-with-variable-line-width
## Reference 2 http://stackoverflow.com/questions/17240694/python-how-to-plot-one-line-in-different-colors
def plot_colourline(x,y,c):
c = plt.cm.jet((c-np.min(c))/(np.max(c)-np.min(c)))
lwidths=1+x[:-1]
ax = plt.gca()
for i in np.arange(len(x)-1):
ax.plot([x[i],x[i+1]], [y[i],y[i+1]], c=c[i],linewidth = lwidths[i])# = lwidths[i])
return
x=np.linspace(0,4*math.pi,100)
y=np.cos(x)
lwidths=1+x[:-1]
fig = plt.figure(1, figsize=(5,5))
ax = fig.add_subplot(111)
plot_colourline(x,y,prop)
ax.set_xlim(0,4*math.pi)
ax.set_ylim(-1.1,1.1)
Does someone has a more interested way to achieve this? Any advice would be appreciate!
Using as inspiration another question.
One option would be to use fill_between. But perhaps not in the way it was intended. Instead of using it to create your line, use it to mask everything that is not the line. Under it you can have a pcolormesh or contourf (for example) to map color any way you want.
Look, for instance, at this example:
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d
def windline(x,y,deviation,color):
y1 = y-deviation/2
y2 = y+deviation/2
tol = (y2.max()-y1.min())*0.05
X, Y = np.meshgrid(np.linspace(x.min(), x.max(), 100), np.linspace(y1.min()-tol, y2.max()+tol, 100))
Z = X.copy()
for i in range(Z.shape[0]):
Z[i,:] = c
#plt.pcolormesh(X, Y, Z)
plt.contourf(X, Y, Z, cmap='seismic')
plt.fill_between(x, y2, y2=np.ones(x.shape)*(y2.max()+tol), color='w')
plt.fill_between(x, np.ones(x.shape) * (y1.min() - tol), y2=y1, color='w')
plt.xlim(x.min(), x.max())
plt.ylim(y1.min()-tol, y2.max()+tol)
plt.show()
x = np.arange(100)
yo = np.random.randint(20, 60, 21)
y = interp1d(np.arange(0, 101, 5), yo, kind='cubic')(x)
dv = np.random.randint(2, 10, 21)
d = interp1d(np.arange(0, 101, 5), dv, kind='cubic')(x)
co = np.random.randint(20, 60, 21)
c = interp1d(np.arange(0, 101, 5), co, kind='cubic')(x)
windline(x, y, d, c)
, which results in this:
The function windline accepts as arguments numpy arrays with x, y , a deviation (like a thickness value per x value), and color array for color mapping. I think it can be greatly improved by messing around with other details but the principle, although not perfect, should be solid.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.linspace(0,4*np.pi,10000) # x data
y = np.cos(x) # y data
r = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [lambda x: 1-x/(2*np.pi), 0]) # red
g = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [lambda x: x/(2*np.pi), lambda x: -x/(2*np.pi)+2]) # green
b = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [0, lambda x: x/(2*np.pi)-1]) # blue
a = np.ones(10000) # alpha
w = x # width
fig, ax = plt.subplots(2)
ax[0].plot(x, r, color='r')
ax[0].plot(x, g, color='g')
ax[0].plot(x, b, color='b')
# mysterious parts
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
# mysterious parts
rgba = list(zip(r,g,b,a))
lc = LineCollection(segments, linewidths=w, colors=rgba)
ax[1].add_collection(lc)
ax[1].set_xlim(0,4*np.pi)
ax[1].set_ylim(-1.1,1.1)
fig.show()
I notice this is what I suffered.

Axis label missing

I am trying to create a 3D plot but I am having trouble with the z-axis label. It simply doesn't appear in the graph. How do I amend this? The code is as follows
# Gamma vs Current step 2
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
h = np.arange(0.1,5.1,0.1)
gamma = np.arange(0.1,5.1,0.1)
sigmaz_hgam = np.array([.009998,.03988,.08878,.15403
,.230769,.312854,.394358,.4708311,.539697879,.6,.6518698
,.696033486,.73345752165,.7651390123,.792,.814845635
,.8343567,.851098499,.865535727,.8780487,.8889486,.89848986
,.906881,.914295,.9208731,.9267338,.93197569,.93668129
,.9409202379,.94475138,.951383,.9542629,.956895,.959309
,.961526,.9635675,.96545144,.9671934,.968807,.97030539
,.9716983,.972995,.974206,.975337,.97639567,.977387,.978318
,.97919266,.98,.9807902])
mu = 1
sigmaz_hgam = mu*sigmaz_hgam
# creates an empty list for current values to be stored in
J1 = []
for i in range(sigmaz_hgam.size):
expec_sz = sigmaz_hgam[i]
J = 4*gamma[i]*(mu-expec_sz)
J1.append(J.real)
#print(J)
This part of the code is what is used to graph out which is where the problem lies
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
x = h
y = gamma
z = J1
ax.plot(x, y, z, label='Dephasing Model')
ax.legend()
ax.set_xlabel('h', fontsize=10)
ax.set_ylabel('$\gamma$')
ax.yaxis._axinfo['label']['space_factor'] = 3.0
for t in ax.zaxis.get_major_ticks(): t.label.set_fontsize(10)
# disable auto rotation
ax.zaxis.set_rotate_label(False)
ax.set_zlabel('J', fontsize=10, rotation = 0)
plt.show()
On my version of Matplotlib (2.0.2), on a Mac, I see the label (which is there – most of it is just being cropped out of the image in your case).
You could try to reduce the padding between the ticks and the label:
ax.zaxis.labelpad = 0

Trapezoidal wave in Python

How do I generate a trapezoidal wave in Python?
I looked into the modules such as SciPy and NumPy, but in vain. Is there a module such as the scipy.signal.gaussian which returns an array of values representing the Gaussian function wave?
I generated this using the trapezoidal kernel of Astropy,
Trapezoid1DKernel(30,slope=1.0)
. I want to implement this in Python without using Astropy.
While the width and the slope are sufficient to define a triangular signal, you would need a third parameter for a trapezoidal signal: the amplitude.
Using those three parameters, you can easily adjust the scipy.signal.sawtooth function to give you a trapeziodal shape by truncating and offsetting the triangular shaped function.
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
def trapzoid_signal(t, width=2., slope=1., amp=1., offs=0):
a = slope*width*signal.sawtooth(2*np.pi*t/width, width=0.5)/4.
a[a>amp/2.] = amp/2.
a[a<-amp/2.] = -amp/2.
return a + amp/2. + offs
t = np.linspace(0, 6, 501)
plt.plot(t,trapzoid_signal(t, width=2, slope=2, amp=1.), label="width=2, slope=2, amp=1")
plt.plot(t,trapzoid_signal(t, width=4, slope=1, amp=0.6), label="width=4, slope=1, amp=0.6")
plt.legend( loc=(0.25,1.015))
plt.show()
Note that you may also like to define a phase, depeding on the use case.
In order to define a single pulse, you might want to modify the function a bit and supply an array which ranges over [0,width].
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
def trapzoid_signal(t, width=2., slope=1., amp=1., offs=0):
a = slope*width*signal.sawtooth(2*np.pi*t/width, width=0.5)/4.
a += slope*width/4.
a[a>amp] = amp
return a + offs
for w,s,a in zip([2,5], [2,1], [1,0.6]):
t = np.linspace(0, w, 501)
l = "width={}, slope={}, amp={}".format(w,s,a)
plt.plot(t,trapzoid_signal(t, width=w, slope=s, amp=a), label=l)
plt.legend( loc="upper right")
plt.show()
From the SciPy website it looks like this isn't included (they currently have sawtooth and square, but not trapezoid). As a generalised version of the C example the following will do what you want,
import numpy as np
import matplotlib.pyplot as plt
def trapezoidalWave(xin, width=1., slope=1.):
x = xin%(4*width)
if (x <= width):
# Ascending line
return x*slope;
elif (x <= 2.*width):
# Top horizontal line
return width*slope
elif (x <= 3.*width):
# Descending line
return 3.*width*slope - x*slope
elif (x <= 4*width):
# Bottom horizontal line
return 0.
x = np.linspace(0.,20,1000)
for i in x:
plt.plot(i, trapezoidalWave(i), 'k.')
plt.plot(i, trapezoidalWave(i, 1.5, 2.), 'r.')
plt.show()
which looks like,
This can be done more elegantly with Heaviside functions which allow you to use NumPy arrays,
import numpy as np
import matplotlib.pyplot as plt
def H(x):
return 0.5 * (np.sign(x) + 1)
def trapWave(xin, width=1., slope=1.):
x = xin%(4*width)
y = ((H(x)-H(x-width))*x*slope +
(H(x-width)-H(x-2.*width))*width*slope +
(H(x-2.*width)-H(x-3.*width))*(3.*width*slope - x*slope))
return y
x = np.linspace(0.,20,1000)
plt.plot(x, trapWave(x))
plt.plot(x, trapWave(x, 1.5, 2.))
plt.show()
For this example, the Heaviside version is about 20 times faster!
The below example shows how to do that to get points and show scope.
Equation based on reply: Equation for trapezoidal wave equation
import math
import numpy as np
import matplotlib.pyplot as plt
def get_wave_point(x, a, m, l, c):
# Equation from: https://stackoverflow.com/questions/11041498/equation-for-trapezoidal-wave-equation
# a/pi(arcsin(sin((pi/m)x+l))+arccos(cos((pi/m)x+l)))-a/2+c
# a is the amplitude
# m is the period
# l is the horizontal transition
# c is the vertical transition
point = a/math.pi*(math.asin(math.sin((math.pi/m)*x+l))+math.acos(math.cos((math.pi/m)*x+l)))-a/2+c
return point
print('Testing wave')
x = np.linspace(0., 10, 1000)
listofpoints = []
for i in x:
plt.plot(i, get_wave_point(i, 5, 2, 50, 20), 'k.')
listofpoints.append(get_wave_point(i, 5, 2, 50, 20))
print('List of points : {} '.format(listofpoints))
plt.show()
The whole credit goes to #ImportanceOfBeingErnest . I am just revising some edits to his code which just made my day.
from scipy import signal
import matplotlib.pyplot as plt
from matplotlib import style
import numpy as np
def trapzoid_signal(t, width=2., slope=1., amp=1., offs=0):
a = slope*width*signal.sawtooth(2*np.pi*t/width, width=0.5)/4.
a += slope*width/4.
a[a>amp] = amp
return a + offs
for w,s,a in zip([32],[1],[0.0322]):
t = np.linspace(0, w, 34)
plt.plot(t,trapzoid_signal(t, width=w, slope=s, amp=a))
plt.show()
The result:
I'll throw a very late hat into this ring, namely, a function using only numpy that produces a single (symmetric) trapezoid at a desired location, with all the usual parameters. Also posted here
import numpy as np
def trapezoid(x, center=0, slope=1, width=1, height=1, offset=0):
"""
For given array x, returns a (symmetric) trapezoid with plateau at y=h (or -h if
slope is negative), centered at center value of "x".
Note: Negative widths and heights just converted to 0
Parameters
----------
x : array_like
array of x values at which the trapezoid should be evaluated
center : float
x coordinate of the center of the (symmetric) trapezoid
slope : float
slope of the sides of the trapezoid
width : float
width of the plateau of the trapezoid
height : float
(positive) vertical distance between the base and plateau of the trapezoid
offset : array_like
vertical shift (either single value or the same shape as x) to add to y before returning
Returns
-------
y : array_like
y value(s) of trapezoid with above parameters, evaluated at x
"""
# ---------- input checking ----------
if width < 0: width = 0
if height < 0: height = 0
x = np.asarray(x)
slope_negative = slope < 0
slope = np.abs(slope) # Do all calculations with positive slope, invert at end if necessary
# ---------- Calculation ----------
y = np.zeros_like(x)
mask_left = x - center < -width/2.0
mask_right = x - center > width/2.0
y[mask_left] = slope*(x[mask_left] - center + width/2.0)
y[mask_right] = -slope*(x[mask_right] - center - width/2.0)
y += height # Shift plateau up to y=h
y[y < 0] = 0 # cut off below zero (so that trapezoid flattens off at "offset")
if slope_negative: y = -y # invert non-plateau
return y + offset
Which outputs something like
import matplotlib.pyplot as plt
plt.style.use("seaborn-colorblind")
x = np.linspace(-5,5,1000)
for i in range(1,4):
plt.plot(x,trapezoid(x, center=0, slope=1, width=i, height=i, offset = 0), label=f"width = height = {i}\nslope=1")
plt.plot(x,trapezoid(x, center=0, slope=-1, width=2.5, height=1, offset = 0), label=f"width = height = 1.5,\nslope=-1")
plt.ylim((-2.5,3.5))
plt.legend(frameon=False, loc='lower center', ncol=2)
Example output:

Resources