How to create a grid according to the orientation of a polygon - python-3.x

How can I make a grid according to the orientation of a polygon with some interval, where the polygon has always 4 point data with different orientation, for example my polygon looks like this :
x1 = np.array([50,0,150,200,50])
y1 = np.array([10,-50,-60,0,10])
and I want to make grid like this :

You can use a interpolate function for each coordinate:
from scipy.interpolate import interp2d
x = np.array([0, 1], dtype=np.float)
y = np.array([0, 1], dtype=np.float)
Now we need to create the interpolate function so that on the small unit square (0,1)x(0,1), we get the result we want.
zx = np.array([[0, 150],[50, 200]])
fx = interp2d(x, y, zx)
fx(0.5, 0.5)
Do the same for zy to get the y coordinate inside your polygon.

You can create a LineCollection of "grid lines" by subdividing the polygon edges into equidistant parts as shown below. In the function grid, nx and ny are the number of lines to create per dimension of the polygon.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
from matplotlib.collections import LineCollection
def grid(x,y, nx, ny, **kwargs):
def verts(a,b,c,d,n):
l1x = np.linspace(x[a],x[b], n)
l1y = np.linspace(y[a],y[b], n)
l2x = np.linspace(x[d],x[c], n)
l2y = np.linspace(y[d],y[c], n)
return np.stack((np.c_[l1x, l2x], np.c_[l1y, l2y]), axis=-1)
v = np.concatenate((verts(0,1,2,3,ny), verts(1,2,3,4,nx)), axis=0)
return LineCollection(v, **kwargs)
x1 = np.array([50,0,150,200,50])
y1 = np.array([10,-50,-60,0,10])
fig, ax = plt.subplots()
ax.add_collection(grid(x1,y1,5,6, color="gray"))
rect=Polygon(np.c_[x1,y1], edgecolor="C0", linewidth=2, facecolor="none", zorder=3)
ax.add_patch(rect)
ax.autoscale()
plt.show()

Related

matplotlib draw a contour line on a colorbar plot

I used below code to generate the colorbar plot of an image:
plt.imshow(distance)
cb = plt.colorbar()
plt.savefig(generate_filename("test_images.png"))
cb.remove()
The image looks likes this:
I want to draw a single contour line on this image where the signed distance value is equal to 0. I checked the doc of pyplot.contour but it needs a X and Y vector that represents the coordinates and a Z that represents heights. Is there a method to generate X, Y, and Z? Or is there a better function to achieve this? Thanks!
If you leave out X and Y, by default, plt.contour uses the array indices (in this case the range 0-1023 in both x and y).
To only draw a contour line at a given level, you can use levels=[0]. The colors= parameter can fix one or more colors. Optionally, you can draw a line on the colorbar to indicate the value of the level.
import matplotlib.pyplot as plt
import numpy as np
from scipy import ndimage # to smooth a test image
# create a test image with similar properties as the given one
np.random.seed(20221230)
distance = np.pad(np.random.randn(1001, 1001), (11, 11), constant_values=-0.02)
distance = ndimage.filters.gaussian_filter(distance, 100)
distance -= distance.min()
distance = distance / distance.max() * 0.78 - 0.73
plt.imshow(distance)
cbar = plt.colorbar()
level = 0
color = 'red'
plt.contour(distance, levels=[level], colors=color)
cbar.ax.axhline(level, color=color) # show the level on the colorbar
plt.show()
Reference: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contour.html
You can accomplish this by setting the [levels] parameter in contour([X, Y,] Z, [levels], **kwargs).
You can draw contour lines at the specified levels by giving an array that is in increasing order.
import matplotlib.pyplot as plt
import numpy as np
x = y = np.arange(-3.0, 3.0, 0.02)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-X ** 2 - Y ** 2)
Z2 = np.exp(-(X - 1) ** 2 - (Y - 1) ** 2)
Z3 = np.exp(-(X + 1) ** 2 - (Y + 1) ** 2)
Z = (Z1 - Z2 - Z3) * 2
fig, ax = plt.subplots()
im = ax.imshow(Z, interpolation='gaussian',
origin='lower', extent=[-4, 4, -4, 4],
vmax=abs(Z).max(), vmin=-abs(Z).max())
plt.colorbar(im)
CS = ax.contour(X, Y, Z, levels=[0.9], colors='black')
ax.clabel(CS, fmt='%1.1f', fontsize=12)
plt.show()
Result (levels=[0.9]):

How to get the plot of 3D geometry with equal axes in python using matplotlib?

I am facing a problem to plot the geometry in the python using matplotlib. I would like to have a plot which can have the equal lenth in all three axes (X, Y, Z). I have written below code but it does not show any equal axes in the obtained geometry.
How can I get the plot with equal axes?
def plotting(x, y, z, figname):
fig = plt.figure(figsize = (50,50))
ax = plt.axes(projection='3d')
ax.grid()
ax.scatter(x, y, z, c = 'r', s = 50)
ax.set_title(figname)
ax.set_xlabel('x', labelpad=20)
ax.set_ylabel('y', labelpad=20)
ax.set_zlabel('z', labelpad=20)
Matplotlib makes this very difficult. One way you could "achieve" that is by setting the same limits to xlim, ylim, zlim:
import numpy as np
import matplotlib.pyplot as plt
n = 1000
t = np.random.uniform(0, 2*np.pi, n)
p = np.random.uniform(0, 2*np.pi, n)
x = (4 + np.cos(t)) * np.cos(p)
y = (1.5 + np.cos(t)) * np.sin(p)
z = np.sin(t)
fig = plt.figure()
ax = fig.add_subplot(projection="3d")
ax.scatter(x, y, z)
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
ax.set_zlim(-4, 4)
plt.show()
Otherwise, your best bet is to use a different plotting library for 3D plots. Plotly allows to easily set equal aspect ratio. K3D-Jupyter and Mayavi uses equal aspect ratio by default.

Heat map for Irregularly Spaced Data with No Interpolation

I would like to plot a heatmap where the input data is not in the typical rectangularly spaced grid. Here is some sample data:
import numpy as np
xmin = 6
xmax= 12
ymin = 0
x = np.linspace(xmin, xmax, 100)
ymax = x**2
final = []
for i in range(len(ymax)):
yrange = np.linspace(0, ymax[i], 100)
for j in range(len(yrange)):
intensity = np.random.rand()
final.append([x[i], yrange[j], intensity])
data_for_plotting = np.asarray(final) # (10000, 3) shaped array
I would like to plot intensity (in the colorbar) as a function of (x,y) which represents the position and I would like to do this without interpolation.
Here is my solution which uses matplotlib's griddata and linear interpolation.
import matplotlib.pyplot as plt
from matplotlib.mlab import griddata
total_length = 100
x1 = np.linspace(min(data_for_plotting[:,0]), max(data_for_plotting[:,0]), total_length)
y1 = np.linspace(min(data_for_plotting[:,1]), max(data_for_plotting[:,1]), total_length)
z1 = griddata(data_for_plotting[:,0], data_for_plotting[:,1], data_for_plotting[:,2], x1, y1, interp='linear')
p=plt.pcolormesh(x1, y1, z1, vmin = 0. , vmax=1.0, cmap='viridis')
clb = plt.colorbar(p)
plt.show()
I am looking for an alternate solution without interpolation as I would like to see the smallest unit of measurement in my x and y position (pixel size/rectangle). Based on the sample data given above I expect the height of the pixel to increase for large values of x.
I'm unsure what matplotlib.mlab.griddata is about. Maybe some very old version?
You could use scipy.interpolate.griddata which needs its parameters in a slightly different format. method='nearest' switches off the interpolation (default method='linear').
Here is how it could look with your test data (see griddata's documentation for more explanation and examples):
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
import numpy as np
xmin = 6
xmax = 12
ymin = 0
x = np.linspace(xmin, xmax, 100)
ymax = x ** 2
final = []
for i in range(len(ymax)):
yrange = np.linspace(0, ymax[i], 100)
for j in range(len(yrange)):
intensity = np.random.rand()
final.append([x[i], yrange[j], intensity])
data_for_plotting = np.asarray(final) # (10000, 3) shaped array
total_length = 100
x1 = np.linspace(min(data_for_plotting[:, 0]), max(data_for_plotting[:, 0]), total_length)
y1 = np.linspace(min(data_for_plotting[:, 1]), max(data_for_plotting[:, 1]), total_length)
grid_x, grid_y = np.meshgrid(x1, y1)
z1 = griddata(data_for_plotting[:, :2], data_for_plotting[:, 2], (grid_x, grid_y), method='nearest')
img = plt.imshow(z1, extent=[x1[0], x1[-1], y1[0], y1[-1]], origin='lower',
vmin=0, vmax=1, cmap='inferno', aspect='auto')
cbar = plt.colorbar(img)
plt.show()
An alernative, is to create one rectangle for each of the prolonged pixels. Beware that this can be a rather slow operation. If really needed, one could create a pcolormesh for each column.
import matplotlib.pyplot as plt
from matplotlib.cm import ScalarMappable
import numpy as np
# ... create x and data_for_plotting as before
fig, ax = plt.subplots()
cmap = plt.get_cmap('inferno')
norm = plt.Normalize(0, 1)
x_step = x[1] - x[0]
y_step = 0
for i, (xi, yi, intensity_i) in enumerate(data_for_plotting):
if i + 1 < len(data_for_plotting) and data_for_plotting[i + 1, 0] == xi: # when False, the last y_step is reused
y_step = data_for_plotting[i + 1, 1] - yi
ax.add_artist(plt.Rectangle((xi, yi), x_step, y_step, color=cmap(norm(intensity_i))))
cbar = plt.colorbar(ScalarMappable(cmap=cmap, norm=norm))
ax.set_xlim(x[0], x[-1])
ax.set_ylim(0, data_for_plotting[:, 1].max())
plt.tight_layout()
plt.show()

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

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