I am trying to visualize water interference by matplotlib and numpy. But after the caculation is finished, the animation fps is too slow. I have found several ways to solve it but they do not work. I think the caculation process may not be that time costing and I have no idea why the fps is so low.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import matplotlib.animation as animation
"""
1.parameter setup
"""
L, sep, N, k, cut = 6, 2, 500, 5*2, 0.8 # 0.5倍寬度, 波源與原點距離, 每邊分割數量, 角波數, z軸範圍
fps, frn = 24, 50 # 每秒影格數量, 影格總數
x = np.linspace(-2*L, 2*L, N) # x axis
y = np.linspace(-L, L, N) # y axis
X, Y = np.meshgrid(x, y) # 2D matrix
j = complex(0, 1) # 根號 -1
"""
2.設定計算振幅的函數, 計算每個位置的振幅並存入陣列
"""
# 自訂函式, 計算每個位置對應的振幅
"""
def func(x, y, t):
r1 = np.sqrt(x**2 + y**2) # 點波源1
for i in range(250):
r1[i] = y[i]+L
r2 = np.sqrt((x+sep)**2 + y**2) # 點波源2
z = np.exp(j*k*r1)/k*3
for i in range(250, 499):
z[i] = np.exp(j*k*(r1[249]+r1[i]))/k*3
z_real = np.real(z*np.exp(-j*t))
return z_real # 回傳實部
"""
def func(x, y, t, n, a, b):
"""
r0 = np.sqrt((x-6)**2 + y**2)
r1 = np.sqrt((x-5)**2 + y**2)
...
r11 = np.sqrt((x+5)**2 + y**2)
r12 = np.sqrt((x+6)**2 + y**2)
"""
for i in range(n):
r = np.sqrt((x-a[i])**2 + (y-b[i])**2)
if(i == 0):
z = np.exp(j*k*r)/r
else:
z += np.exp(j*k*r)/r
return np.real(z*np.exp(-j*t)) # 回傳實部
Z = np.zeros((N, N, frn)) # 儲存振幅用的2維陣列
T = np.linspace(0, 2*np.pi, frn) # 儲存時間用的1維陣列
# user input
n=int(input("輸入波源數量:"))
print('點波源位置輸入格式: x , y')
a=[]
b=[]
for i in range(n):
temp=input(f"位置{i}:").split(',')
a.append(int(temp[0]))
b.append(int(temp[1]))
# 計算每個時刻每個位置對應的振幅, 有加cut效果較佳
for i in range(frn):
Z[:, :, i] = func(X, Y, T[i], n,a,b).clip(-cut, cut)
"""
3.繪圖
"""
fig = plt.figure(figsize=(7, 6), dpi=100) # 開啟繪圖視窗
ax = fig.gca()
ax.set_aspect(1.0) # 使圖片長寬變成1:1
# 以某個預設的colormap為基底, 修改成對應到 -cut ~ +cut 的colormap
mappable = plt.cm.ScalarMappable(cmap=plt.cm.jet)
mappable.set_array(np.arange(-cut, cut, 0.1))
# 在圖片右側加上color bar, 高度與圖片相同
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
plt.colorbar(mappable, cax=cax)
# 自訂函式, 先移除前一張圖, 再畫出下一張圖
def update(frame_number):
plot[0] = ax.contourf(X, Y, Z[:, :, frame_number], cmap=mappable.cmap, norm=mappable.norm)
# t = 0 的圖片
plot = [ax.contourf(X, Y, Z[:, :, 0], cmap=mappable.cmap, norm=mappable.norm)]
# 產生動畫, 目標為繪圖物件fig, 使用自訂函式update更新圖片, 圖片總數為frn, 時間間隔為interal, 單位為ms
ani = animation.FuncAnimation(fig, update, frn, interval=1000/fps)
plt.show() # 顯示圖片
# ani.save('TwoSourcesInterference2D.gif', writer='imagemagick', fps=fps) # 儲存圖片
I am expected to speed up the animation to about 12fps.
Related
The code below creates a Scatter plot from X and based on values of w,b, creates lines over X.
I have tried a couple of combinations such as:
fig.canvas.draw()
fig.canvas.flush_events()
plt.clf
plt.cla
But they either seem to plot multiple lines over the plot or Delete the figure / axes.
Is it possible to plot the Scatter plot only once but the Lines keep changing based on w,b?.
Below is the code that I have used:
from sklearn import datasets
import matplotlib.pyplot as plt
import numpy as np
import time
from IPython.display import display, clear_output
def get_hyperplane_value(x, w, b, offset):
'''
Generate Hyperplane for the plot
'''
return (-w[0] * x + b + offset) / w[1]
def plot_now(ax, W,b):
'''
Visualise the results
'''
x0_1 = np.amin(X[:, 0])
x0_2 = np.amax(X[:, 0])
x1_1 = get_hyperplane_value(x0_1, W, b, 0)
x1_2 = get_hyperplane_value(x0_2, W, b, 0)
x1_1_m = get_hyperplane_value(x0_1, W, b, -1)
x1_2_m = get_hyperplane_value(x0_2, W, b, -1)
x1_1_p = get_hyperplane_value(x0_1, W, b, 1)
x1_2_p = get_hyperplane_value(x0_2, W, b, 1)
ax.plot([x0_1, x0_2], [x1_1, x1_2], "y--")
ax.plot([x0_1, x0_2], [x1_1_m, x1_2_m], "k")
ax.plot([x0_1, x0_2], [x1_1_p, x1_2_p], "k")
x1_min = np.amin(X[:, 1])
x1_max = np.amax(X[:, 1])
ax.set_ylim([x1_min - 3, x1_max + 3])
ax.scatter(X[:, 0], X[:, 1], marker="o", c = y)
return ax
X, y = datasets.make_blobs(n_samples=50, n_features=2, centers=2, cluster_std=1.05, random_state=40)
y = np.where(y == 0, -1, 1)
fig = plt.figure(figsize = (7,7))
ax = fig.add_subplot(1, 1, 1)
for i in range(50):
W = np.random.randn(2)
b = np.random.randn()
ax.cla()
ax = plot_now(ax, W, b)
display(fig)
clear_output(wait = True)
plt.pause(0.25)
It appears to me that you are trying to animate a figure, so you should use FuncAnimation. The basic principle with animations is that you initialize your lines, and later update the values.
from sklearn import datasets
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
def get_hyperplane_value(x, w, b, offset):
'''
Generate Hyperplane for the plot
'''
return (-w[0] * x + b + offset) / w[1]
def get_weights_bias(i):
W = np.random.randn(2)
b = np.random.randn()
return W, b
def plot_now(i):
# retrieve weights and bias at iteration i
W, b = get_weights_bias(i)
x0_1 = np.amin(X[:, 0])
x0_2 = np.amax(X[:, 0])
x1_1 = get_hyperplane_value(x0_1, W, b, 0)
x1_2 = get_hyperplane_value(x0_2, W, b, 0)
x1_1_m = get_hyperplane_value(x0_1, W, b, -1)
x1_2_m = get_hyperplane_value(x0_2, W, b, -1)
x1_1_p = get_hyperplane_value(x0_1, W, b, 1)
x1_2_p = get_hyperplane_value(x0_2, W, b, 1)
line1.set_data([x0_1, x0_2], [x1_1, x1_2])
line2.set_data([x0_1, x0_2], [x1_1_m, x1_2_m])
line3.set_data([x0_1, x0_2], [x1_1_p, x1_2_p])
x1_min = np.amin(X[:, 1])
x1_max = np.amax(X[:, 1])
ax.set_ylim([x1_min - 3, x1_max + 3])
X, y = datasets.make_blobs(n_samples=50, n_features=2, centers=2, cluster_std=1.05, random_state=40)
y = np.where(y == 0, -1, 1)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
plt.scatter(X[:, 0], X[:, 1], marker="o", c = y) # ax.scatter
# initialize empty lines
line1, = ax.plot([], [], "y--")
line2, = ax.plot([], [], "k")
line3, = ax.plot([], [], "k")
# create an animation with 10 frames
anim = FuncAnimation(fig, plot_now, frames=range(10), repeat=False)
plt.show()
The task:
I am trying to interpolate a vector field on a regular grid, i.e.:
The issue:
I am using the RegularGridInterpolator from scipy to do this. However, it seems that the resulting vector field is turned with respect to the original:
Anyone knows why?
Python code to reproduce example:
from scipy.interpolate import RegularGridInterpolator
import matplotlib.pyplot as plt
import numpy as np
# ORIGINAL
# Number of points (NxN)
N = 50
# Boundaries
ymin = -2.; ymax = 2.
xmin = -2.; xmax = 2.
# Create Meshgrid
x = np.linspace(xmin,xmax, N)
y = np.linspace(ymin,ymax, N)
xx, yy = np.meshgrid(x, y)
# Vector Field
Fx = np.cos(xx + 2*yy)
Fy = np.sin(xx - 2*yy)
# Plot vector field
fig, ax = plt.subplots()
ax.quiver(x, y, Fx, Fy)
plt.title("Original")
plt.show()
# REDUCED
# Number of points (NxN)
N = 10
# Boundaries
ymin = -2.; ymax = 2.
xmin = -2.; xmax = 2.
# Create Meshgrid
x = np.linspace(xmin,xmax, N)
y = np.linspace(ymin,ymax, N)
xx, yy = np.meshgrid(x, y)
# Vector Field
Fx = np.cos(xx + 2*yy)
Fy = np.sin(xx - 2*yy)
# Plot vector field
fig, ax = plt.subplots()
ax.quiver(x, y, Fx, Fy)
plt.title("Reduced")
plt.show()
# INTERPOLATED VERSION BASED ON REDUCED
# Iterpolate
my_interpolating_function_x = RegularGridInterpolator((x, y), Fx)
my_interpolating_function_y = RegularGridInterpolator((x, y), Fy)
# Create Meshgrid
N = 50
x = np.linspace(xmin,xmax, N)
y = np.linspace(ymin,ymax, N)
grid = np.meshgrid(x, y)
new_points = np.vstack(list(map(np.ravel, grid))).T
# Interpolate
F_x_inter = my_interpolating_function_x(new_points)
F_y_inter = my_interpolating_function_y(new_points)
# reshape
F_x_inter = np.reshape(F_x_inter,(50,50))
F_y_inter = np.reshape(F_y_inter,(50,50))
#plot
fig, ax = plt.subplots()
ax.quiver(x, y, F_x_inter, F_y_inter)
plt.title("Interpolated")
plt.show()
I want to animate julia sets. Anyways everything works so far,
I only need to change the axis labeling. When plotting my values the
x- and y axis are both showing the 500x500 linspace. Id rather like to see
the [-1,1]x[-1,1] Intervall ive defined the linspace on. How could I change that?
thank you :)
code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# Parameters and Test-Function
f = lambda z: z ** 2 - 0.1+0.651*1j
N = 1000
R = 2
def pre_greyscale(f, c, N, R):
if np.abs(c) > R:
return 0
else:
for i in range(0, N):
c = f(c)
if np.abs(c) > R:
return i + 1
return N
# fig1 = plt.figure()
real = np.linspace(-1, 1, num=500)
imaginary = np.linspace(-1, 1, num=500)
pre_image = np.empty(shape=(real.size, imaginary.size))
for k, r in enumerate(real):
for p, i in enumerate(imaginary):
pre_image[p, k] = pre_greyscale(f, r + i * 1j, N, R)
def animate(m):
image = np.empty(shape=(real.size, imaginary.size))
for k in range(0, 500):
for p in range(0, 500):
if pre_image[p, k] <= m:
image[p, k] = 1 - pre_image[p, k] / m
# else:
# image[k, p] = 0
# mat = plt.imshow(image, cmap='gray')
# plt.show()
return image
imagelist = [animate(x) for x in range(N)]
fig = plt.figure() # make figure
# Initialize imshow
im = plt.imshow(imagelist[0], cmap=plt.get_cmap('gray'), vmin=0, vmax=1)
# function to update figure
def updatefig(j):
# set the data in the axesimage object
im.set_array(imagelist[j])
# return the artists set
return [im]
# kick off the animation
ani = FuncAnimation(fig, updatefig, frames=N,
interval=20, blit=True)
ani.save('fractal2.gif', writer='pillow')
Adding the extent parameter to plt.imshow will set the correct labels:
# Initialize imshow
im = plt.imshow(imagelist[0], cmap=plt.get_cmap('gray'), vmin=0, vmax=1, extent=[-1,1,-1,1])
I was able to simulate a mass-spring system under damped oscillations. However, I wanted to add a subplot of position vs time and another subplot velocity vs position (phase path) so that I will be having three animations. How can I add them? The source code that I used is shown below
Update: I tried adding the first subplot position vs time but I cannot get the desired curve for it.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
#Constants
w = np.pi #angular frequency
b = np.pi * 0.2 #damping parameter
#Function that implements rk4 integration
def rk4(t, x, f, dt):
dx1 = f(t, x)*dt
dx2 = f(t+0.5*dt, x+0.5*dx1)*dt
dx3 = f(t+0.5*dt, x+0.5*dx2)*dt
dx4 = f(t+dt, x+dx3)*dt
return x+dx1/6.0+dx2/3.0+dx3/3.0+dx4/6.0
#Function that returns dX/dt for the linearly damped oscillator
def dXdt(t, X):
x = X[0]
vx = X[1]
ax = -2*b*vx - w**2*x
return np.array([vx, ax])
#Initialize Variables
x0 = 5.0 #Initial x position
vx0 = 1.0 #Initial x Velocity
#Setting time array for graph visualization
dt = 0.05
N = 200
x = np.zeros(N)
vx = np.zeros(N)
y = []
# integrate equations of motion using rk4;
# X is a vector that contains the positions and velocities being integrated
X = np.array([x0, vx0])
for i in range(N):
x[i] = X[0]
vx[i] = X[1]
y.append(0)
# update the vector X to the next time step
X = rk4(i*dt, X, dXdt, dt)
fig, (ax1, ax2) = plt.subplots(2,1, figsize=(8,6))
fig.suptitle(r' Damped Oscillation with $\beta$$\approx$' + str(round(b,2)) + r' and $\omega$$\approx$'
+ str(round(w,2)), fontsize=16)
line1, = ax1.plot([], [], lw=10,c="blue",ls="-",ms=50,marker="s",mfc="gray",fillstyle="none",mec="black",markevery=2)
line2, = ax2.plot([], [], lw=2, color='r')
time_template = '\nTime = %.1fs'
time_text = ax1.text(0.1, 0.9, '', transform=ax1.transAxes)
for ax in [ax1, ax2]:
ax1.set_xlim(1.2*min(x), 1.2*max(x))
ax2.set_ylim(1.2*min(x), 1.2*max(x),1)
ax2.set_xlim(0, N*dt)
ax1.set_yticklabels([])
def init():
line1.set_data([], [])
line2.set_data([], [])
time_text.set_text('')
return line1, line2, time_text
def animate(i):
thisx1 = [x[i],0]
thisy1 = [y[i],0]
thisx2 = [i*dt,0]
thisy2 = [x[i],0]
line1.set_data(thisx1, thisy1)
line2.set_data(thisx2, thisy2)
time_text.set_text(time_template % (i*dt))
return line1, line2, time_text
ani = animation.FuncAnimation(fig, animate, np.arange(1, N),
interval=50, blit=True, init_func=init,repeat=False)
After some minors changes to your initial code, the most noteworthy being:
thisx1 = [x[i],0]
thisy1 = [y[i],0]
thisx2 = [i*dt,0]
thisy2 = [x[i],0]
line1.set_data(thisx1, thisy1)
line2.set_data(thisx2, thisy2)
# should be written like this
line1.set_data([x[i],0], [y[i],0])
line2.set_data(t[:i], x[:i])
line3.set_data(x[:i], vx[:i])
The working version, with phase space plot in green, is as follows:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
#Constants
w = np.pi #angular frequency
b = np.pi * 0.2 #damping parameter
#Function that implements rk4 integration
def rk4(t, x, f, dt):
dx1 = f(t, x)*dt
dx2 = f(t+0.5*dt, x+0.5*dx1)*dt
dx3 = f(t+0.5*dt, x+0.5*dx2)*dt
dx4 = f(t+dt, x+dx3)*dt
return x+dx1/6.0+dx2/3.0+dx3/3.0+dx4/6.0
#Function that returns dX/dt for the linearly damped oscillator
def dXdt(t, X):
x = X[0]
vx = X[1]
ax = -2*b*vx - w**2*x
return np.array([vx, ax])
#Initialize Variables
x0 = 5.0 #Initial x position
vx0 = 1.0 #Initial x Velocity
#Setting time array for graph visualization
dt = 0.05
N = 200
t = np.linspace(0,N*dt,N,endpoint=False)
x = np.zeros(N)
vx = np.zeros(N)
y = np.zeros(N)
# integrate equations of motion using rk4;
# X is a vector that contains the positions and velocities being integrated
X = np.array([x0, vx0])
for i in range(N):
x[i] = X[0]
vx[i] = X[1]
# update the vector X to the next time step
X = rk4(i*dt, X, dXdt, dt)
fig, (ax1, ax2, ax3) = plt.subplots(3,1, figsize=(8,12))
fig.suptitle(r' Damped Oscillation with $\beta$$\approx$' + str(round(b,2)) + r' and $\omega$$\approx$'
+ str(round(w,2)), fontsize=16)
line1, = ax1.plot([], [], lw=10,c="blue",ls="-",ms=50,marker="s",mfc="gray",fillstyle="none",mec="black",markevery=2)
line2, = ax2.plot([], [], lw=1, color='r')
line3, = ax3.plot([], [], lw=1, color='g')
time_template = '\nTime = %.1fs'
time_text = ax1.text(0.1, 0.9, '', transform=ax1.transAxes)
ax1.set_xlim(1.2*min(x), 1.2*max(x))
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax2.set_ylim(1.2*min(x), 1.2*max(x),1)
ax2.set_xlim(0, N*dt)
ax2.set_xlabel('t')
ax2.set_ylabel('x')
ax3.set_xlim(1.2*min(x), 1.2*max(x),1)
ax3.set_ylim(1.2*min(vx), 1.2*max(vx),1)
ax3.set_xlabel('x')
ax3.set_ylabel('vx')
def init():
line1.set_data([], [])
line2.set_data([], [])
line3.set_data([], [])
time_text.set_text('')
return line1, line2, line3, time_text
def animate(i):
line1.set_data([x[i],0], [y[i],0])
line2.set_data(t[:i], x[:i])
line3.set_data(x[:i], vx[:i])
time_text.set_text(time_template % (i*dt))
return line1, line2, line3, time_text
ani = animation.FuncAnimation(fig, animate, np.arange(1, N),
interval=50, blit=True, init_func=init,repeat=False)
ani.save('anim.gif')
and gives:
I want to fit a plane to some data points and draw it. My current code is this:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
points = [(1.1,2.1,8.1),
(3.2,4.2,8.0),
(5.3,1.3,8.2),
(3.4,2.4,8.3),
(1.5,4.5,8.0)]
xs, ys, zs = zip(*points)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xs, ys, zs)
point = np.array([0.0, 0.0, 8.1])
normal = np.array([0.0, 0.0, 1.0])
d = -point.dot(normal)
xx, yy = np.meshgrid([-5,10], [-5,10])
z = (-normal[0] * xx - normal[1] * yy - d) * 1. /normal[2]
ax.plot_surface(xx, yy, z, alpha=0.2, color=[0,1,0])
ax.set_xlim(-10,10)
ax.set_ylim(-10,10)
ax.set_zlim( 0,10)
plt.show()
which results in the following:
As you can see at the moment I create the plane manually. How can I calculate it? I guess it is possible with scipy.optimize.minimize somehow. The kind of error function is not that important to me at the moment. I think least squares (vertical point-plane-distance) would be fine. It would be cool if one of you could show me how to do it.
Oh, the idea just came to my mind. It's quite easy. :-)
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import scipy.optimize
import functools
def plane(x, y, params):
a = params[0]
b = params[1]
c = params[2]
z = a*x + b*y + c
return z
def error(params, points):
result = 0
for (x,y,z) in points:
plane_z = plane(x, y, params)
diff = abs(plane_z - z)
result += diff**2
return result
def cross(a, b):
return [a[1]*b[2] - a[2]*b[1],
a[2]*b[0] - a[0]*b[2],
a[0]*b[1] - a[1]*b[0]]
points = [(1.1,2.1,8.1),
(3.2,4.2,8.0),
(5.3,1.3,8.2),
(3.4,2.4,8.3),
(1.5,4.5,8.0)]
fun = functools.partial(error, points=points)
params0 = [0, 0, 0]
res = scipy.optimize.minimize(fun, params0)
a = res.x[0]
b = res.x[1]
c = res.x[2]
xs, ys, zs = zip(*points)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xs, ys, zs)
point = np.array([0.0, 0.0, c])
normal = np.array(cross([1,0,a], [0,1,b]))
d = -point.dot(normal)
xx, yy = np.meshgrid([-5,10], [-5,10])
z = (-normal[0] * xx - normal[1] * yy - d) * 1. /normal[2]
ax.plot_surface(xx, yy, z, alpha=0.2, color=[0,1,0])
ax.set_xlim(-10,10)
ax.set_ylim(-10,10)
ax.set_zlim( 0,10)
plt.show()
Sorry for asking unnecessarily.
Another way is with a straight forward least squares solution.
The equation for a plane is: ax + by + c = z. So set up matrices like this with all your data:
x_0 y_0 1
A = x_1 y_1 1
...
x_n y_n 1
And
a
x = b
c
And
z_0
B = z_1
...
z_n
In other words: Ax = B. Now solve for x which are your coefficients. But since (I assume) you have more than 3 points, the system is over-determined so you need to use the left pseudo inverse. So the answer is:
a
b = (A^T A)^-1 A^T B
c
And here is some simple Python code with an example:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
N_POINTS = 10
TARGET_X_SLOPE = 2
TARGET_y_SLOPE = 3
TARGET_OFFSET = 5
EXTENTS = 5
NOISE = 5
# create random data
xs = [np.random.uniform(2*EXTENTS)-EXTENTS for i in range(N_POINTS)]
ys = [np.random.uniform(2*EXTENTS)-EXTENTS for i in range(N_POINTS)]
zs = []
for i in range(N_POINTS):
zs.append(xs[i]*TARGET_X_SLOPE + \
ys[i]*TARGET_y_SLOPE + \
TARGET_OFFSET + np.random.normal(scale=NOISE))
# plot raw data
plt.figure()
ax = plt.subplot(111, projection='3d')
ax.scatter(xs, ys, zs, color='b')
# do fit
tmp_A = []
tmp_b = []
for i in range(len(xs)):
tmp_A.append([xs[i], ys[i], 1])
tmp_b.append(zs[i])
b = np.matrix(tmp_b).T
A = np.matrix(tmp_A)
fit = (A.T * A).I * A.T * b
errors = b - A * fit
residual = np.linalg.norm(errors)
print "solution:"
print "%f x + %f y + %f = z" % (fit[0], fit[1], fit[2])
print "errors:"
print errors
print "residual:"
print residual
# plot plane
xlim = ax.get_xlim()
ylim = ax.get_ylim()
X,Y = np.meshgrid(np.arange(xlim[0], xlim[1]),
np.arange(ylim[0], ylim[1]))
Z = np.zeros(X.shape)
for r in range(X.shape[0]):
for c in range(X.shape[1]):
Z[r,c] = fit[0] * X[r,c] + fit[1] * Y[r,c] + fit[2]
ax.plot_wireframe(X,Y,Z, color='k')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()
Thanks #Ben for sharing! Since np.matrix is deprecated, I edited your code so it works with np arrays
import matplotlib.pyplot as plt
import numpy as np
from numpy.linalg import inv
# Pass the function array of points, shape (3, X)
def plane_from_points(points):
# Create this matrix correctly without transposing it later?
A = np.array([
points[0,:],
points[1,:],
np.ones(points.shape[1])
]).T
b = np.array([points[2, :]]).T
# fit = (A.T * A).I * A.T * b
fit = np.dot(np.dot(inv(np.dot(A.T, A)), A.T), b)
# errors = b - np.dot(A, fit)
# residual = np.linalg.norm(errors)
return fit
N_POINTS = 10
TARGET_X_SLOPE = 2
TARGET_y_SLOPE = 3
TARGET_OFFSET = 5
EXTENTS = 5
NOISE = 3
# create random data
xs = [np.random.uniform(2*EXTENTS)-EXTENTS for i in range(N_POINTS)]
ys = [np.random.uniform(2*EXTENTS)-EXTENTS for i in range(N_POINTS)]
zs = []
for i in range(N_POINTS):
zs.append(xs[i]*TARGET_X_SLOPE + \
ys[i]*TARGET_y_SLOPE + \
TARGET_OFFSET + np.random.normal(scale=NOISE))
points = np.array([xs, ys, zs])
# plot raw data
plt.figure()
ax = plt.subplot(111, projection='3d')
ax.scatter(xs, ys, zs, color='b')
fit = plane_from_points(points)
# plot plane
xlim = ax.get_xlim()
ylim = ax.get_ylim()
X,Y = np.meshgrid(np.arange(xlim[0], xlim[1]),
np.arange(ylim[0], ylim[1]))
Z = np.zeros(X.shape)
for r in range(X.shape[0]):
for c in range(X.shape[1]):
Z[r,c] = fit[0] * X[r,c] + fit[1] * Y[r,c] + fit[2]
ax.plot_wireframe(X,Y,Z, color='k')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()
I'm surprised nobody has mentioned lsq_linear. There you can more or less directly plug in the data points and get the plane coefficients out:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
points = np.array([[1.1,2.1,8.1],
[3.2,4.2,8.0],
[5.3,1.3,8.2],
[3.4,2.4,8.3],
[1.5,4.5,8.0]])
A = np.hstack((points[:,:2], np.ones((len(xs),1))))
b = points[:,2]
res = scipy.optimize.lsq_linear(A, b)
assert res.success
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xs, ys, zs)
XnY = np.linspace(-5,10,10)
X, Y = np.meshgrid(XnY, XnY)
Z = res.x[0] * X + res.x[1] * Y + res.x[2]
surf = ax.plot_surface(X, Y, Z, alpha=0.2, color=[0,1,0])
ax.set_xlim(-5,10)
ax.set_ylim(-5,10)
ax.set_zlim( 0,10)
plt.show()