Bezier MxN Surface in matrix form( with Python and numpy) - python

I am trying to rewrite slow method("bezier_surf_eval") for bezier surface with quick one(in matrix form "bezier_surface_M").
Using this formula:
Were [N] and [M] an bezier basis matrix (4x2 degree, because of 5x3 control points),
[U], [W] is surface uv params, [B] - control points
What i do wrong?
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d.axes3d import Axes3D
from scipy.special import comb
def bern(i, d, t):
return comb(d, i) * (t ** (d - i)) * (1 - t) ** i
def bezier_matrix(d):
return np.array([[(-1) ** (i - j) * comb(j, i) * comb(d, j) for i in range(d + 1)] for j in range(d + 1)], int)
BM = [bezier_matrix(i) for i in range(16)]
poles5x3 = np.array([
[[1.8, -0.3, 0.], [1.8, 0.13, 0.1], [1.8, 0.5, 0.]],
[[2., -0.3, 0.06], [2.1, 0.1, 0.1], [2.1, 0.5, 0.1]],
[[2.3, -0.3, 0.1], [2.3, 0.13, 0.2], [2.3, 0.5, 0.1]],
[[2.4, -0.3, 0.1], [2.5, 0.1, 0.15], [2.5, 0.5, 0.1]],
[[2.6, -0.3, 0.], [2.6, 0.1, 0.1], [2.5, 0.5, 0.]]])
def bezier_surf_eval(poles, u, v, deg_u, deg_v):
count_u, count_v = deg_u + 1, deg_v + 1
co = [0, 0, 0]
for i in range(count_u):
for j in range(count_v):
bu = bern(i, deg_u, u)
bv = bern(j, deg_v, v)
pole_co = poles[i][j]
m = bu * bv
co[0] += pole_co[0] * m
co[1] += pole_co[1] * m
co[2] += pole_co[2] * m
return co
def bezier_surface_slow(poles, resol=(16, 16)):
params_u = np.linspace(0, 1, resol[0])
params_v = np.linspace(0, 1, resol[1])
count_u, count_v, _ = poles.shape
du, dv = count_u - 1, count_v - 1
cps = poles.tolist()
coords = np.empty((*resol, 3), float)
for vi, v in enumerate(params_v):
for ui, u in enumerate(params_u):
coords[ui, vi] = bezier_surf_eval(cps, u, v, deg_u=du, deg_v=dv)
return np.array(coords, np.float32)
def bezier_surface_M(cps: np.ndarray, resol=(16, 16)):
u = np.linspace(0, 1, resol[0])
v = np.linspace(0, 1, resol[1])
count_u, count_v, _ = cps.shape
deg_u, deg_v = count_u - 1, count_v - 1
u_vec = np.array([u ** i for i in range(count_u)])
v_vec = np.array([v ** i for i in range(count_v)])
BM_u, BM_v = BM[deg_u], BM[deg_v]
return u_vec.T.dot(BM_u).dot(cps).dot(BM_v.T).dot(v_vec)
fig: Figure = plt.figure(figsize=(7, 7))
ax: Axes3D = fig.add_subplot(111, projection='3d')
# ax.scatter(px, py, pz)
# --------------
resol = 16, 16
co = bezier_surface_slow(poles5x3, resol=resol)
x, y, z = co.T
ax.plot_surface(x, y, z, cmap=cm.gray, linewidth=1, antialiased=False)
# --------------
resol = 16, 16
co = bezier_surface_M(poles5x3, resol=resol)
x, y, z = co.T
ax.plot_surface(x, y, z, cmap=cm.gray, linewidth=1, antialiased=False)
plt.show()

Looks like i solved it.
Problem was in bad order for matrices (in method "bezier_surface_M") for dot product. Multiplication with per-axis matrices with correct orders do the job.
Replaced:
u_vec.T.dot(BM_u).dot(cps).dot(BM_v.T).dot(v_vec)
With:
cps_x = cps[:, :, 0]
cps_y = cps[:, :, 1]
cps_z = cps[:, :, 2]
m1 = u_vec.T.dot(BM_u)
m2 = BM_v.T.dot(v_vec)
x = m1.dot(cps_x).dot(m2)
y = m1.dot(cps_y).dot(m2)
z = m1.dot(cps_z).dot(m2)
Maybe it can be optimized with np.tensorproduct or something like that, but i`m not as good with numpy and matrices math.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d.axes3d import Axes3D
from scipy.special import comb
def bezier_matrix(d):
return np.array([[(-1) ** (i - j) * comb(j, i) * comb(d, j) for i in range(d + 1)] for j in range(d + 1)], int)
BM = [bezier_matrix(i) for i in range(16)]
poles5x3 = np.array([
[[1.8, -0.3, 0.], [1.8, 0.13, 0.1], [1.8, 0.5, 0.]],
[[2., -0.3, 0.06], [2.1, 0.1, 0.1], [2.1, 0.5, 0.1]],
[[2.3, -0.3, 0.1], [2.3, 0.13, 0.2], [2.3, 0.5, 0.1]],
[[2.4, -0.3, 0.1], [2.5, 0.1, 0.15], [2.5, 0.5, 0.1]],
[[2.6, -0.3, 0.], [2.6, 0.1, 0.1], [2.5, 0.5, 0.]]])
def bezier_surface_M(cps: np.ndarray, resol=(16, 16)):
u, v = np.linspace(0, 1, resol[0]), np.linspace(0, 1, resol[1])
count_u, count_v, _ = cps.shape
deg_u, deg_v = count_u - 1, count_v - 1
u_vec = np.array([u ** i for i in range(count_u)])
v_vec = np.array([v ** i for i in range(count_v)])
BM_u, BM_v = BM[deg_u], BM[deg_v]
cps_x = cps[:, :, 0]
cps_y = cps[:, :, 1]
cps_z = cps[:, :, 2]
m1 = u_vec.T.dot(BM_u)
m2 = BM_v.T.dot(v_vec)
x = m1.dot(cps_x).dot(m2)
y = m1.dot(cps_y).dot(m2)
z = m1.dot(cps_z).dot(m2)
return x, y, z
fig: Figure = plt.figure(figsize=(7, 7))
ax: Axes3D = fig.add_subplot(111, projection='3d')
resol = 32, 32
# --------------
x, y, z = bezier_surface_M(poles5x3, resol=resol)
ax.plot_surface(x, y, z, cmap=cm.gray, linewidth=1, antialiased=False)
plt.show()

Related

Scatter color map based on a list different from x and y (Python)

Imagine you have the following:
import matplotlib.pyplot as plt
x = [0.8, 0.4, 0.6, 1, 1.5, 1.8, 2.0, 0.5, 1.3, 0.1]
y = [0.5, 0.12, 0.45, 0.98, 1.31, 1.87, 1.0, 0.11, 1.45, 0.67]
r = [x[i]/y[i] for i in range(len(x))]
fig, ax = plt.subplots(1,1, tight_layout=True, figsize=(10,10))
ax.subplot(x,y,cmap=?)
Now I would like to plot this and have a color map. However, the colors of the points are given by the values of r. How do I do this?
Thank you in advance.
Here's how you do it
import matplotlib.pyplot as plt
x = [0.8, 0.4, 0.6, 1, 1.5, 1.8, 2.0, 0.5, 1.3, 0.1]
y = [0.5, 0.12, 0.45, 0.98, 1.31, 1.87, 1.0, 0.11, 1.45, 0.67]
r = [x[i]/y[i] for i in range(len(x))]
fig, ax = plt.subplots(1,1, tight_layout=True, figsize=(10,10))
ax.scatter(x, y, c=r)
You can also change the default colormap
ax.scatter(x, y, c=r, cmap='viridis')
The complete color map reference
You can use MatPlotLib's pyplot.scatter, which takes 2 arrays (x values, y values) as required arguments. You can also supply a 3rd array, c, of the same length of x and y that sets the color value of each point.
In your case:
ax.scatter(x=x,y=y,c=r)
easy as that!

Solving equation with for loops python

I have arrays like this:
x = np.array([-1,-1,-1,1,-1,-1])
weight = np.array([[0.5,-0.5,0.5,-0.5,0.5,-0.5],
[-0.5,0.5,-0.5,0.5,-0.5,0.5],
[0.5,0.5,0.5,0.5,0.5,0.5]])
print(weight.shape)
bias=np.array([2, 2, 2])
print(bias)
weight = np.transpose(weight)
weight
You can run the above code which results to arrays bias and weight_ham and x:
bias = [2 2 2]
weight = array([[ 0.5, -0.5, 0.5],
[-0.5, 0.5, 0.5],
[ 0.5, -0.5, 0.5],
[-0.5, 0.5, 0.5],
[ 0.5, -0.5, 0.5],
[-0.5, 0.5, 0.5]])
x = array([-1, -1, -1, 1, -1, -1])
Now i want to calculate this equation:
the y_in array should be like this:
y_in = np.zeros((1, len(bias)))
What i don't understand is how can i compute that equation with for loop since i'm not really familiar with how should i write for loops.
if you didn't understand the equation you can see this example below:
I don't understand why you are required to use loops when you are already working with numpy, however the correct way would be:
>>> np.add(bias, np.dot(x[None, :], weight)).flatten()
array([1., 3., 0.])
But if you want loops:
y = []
for index_1, b in enumerate(bias):
sum_ = b
for index_2, val in enumerate(x):
sum_ += x[index_2] * weight[index_2, index_1]
y.append(sum_)
>>> y
[1.0, 3.0, 0.0]
# OR
>>> [b + sum(x_val * w for x_val, w in zip(x, weight[:,i])) for i, b in enumerate(bias)]
[1.0, 3.0, 0.0]
Posting answer for your screenshot problem. You can use the same code for your original problem:
x = np.array([1,1,-1,-1])
weight = np.array([[0.5,-0.5,-0.5,-0.5],
[-0.5,-0.5,-0.5,0.5],
])
bias=np.array([2, 2])
weight = np.transpose(weight)
One Liner:
np.add(bias, np.dot(weight.T, x))
Using Loop:
y_arr = []
for j in range(weight.shape[1]):
y = (bias[j] + np.dot(weight[:,j].T, x))
y_arr.append(y)
y_arr = np.array(y_arr)
y_arr:
array([3., 1.])

Finding maximum per row when condition is met

I'm having the following relatively simple problem. I have two arrays storing x and y coordinates per timestep, e.g.
x = [[0, 1, 2, 3], [0.1, 1.1, 2.1, 3.1]]
y = [[0.5, 0.5, 0.5, 0.5], [0.51, 0.52, 0.49, 0.53]]
in which 2 timesteps are represented (2 rows). What I would like is to find the maximum y coordinate per row when the condition x >= 1 and x <= 2.5 is met.
How can I define a function which returns an array of 2 columns with just the max(y) per row when the spatial x condition is met?
I've tried np.where without luck. The result the function should return is:
[0.5, 0.52]
You can use numpy's mask function. The mask function 'masks' the true values, so the conditions are flipped.
import numpy as np
x = [[0, 1, 2, 3], [0.1, 1.1, 2.1, 3.1]]
y = [[0.5, 0.5, 0.5, 0.5], [0.51, 0.52, 0.49, 0.53]]
x = np.array(x)
y = np.array(y)
y_masked = np.ma.masked_where((x>2.5) | (x<1), y)
result = np.max(y_masked, axis = 1)
print(result)
Not very pretty, but using pure Python (no numpy) you could combine zip, filter, and max:
>>> x = [[0,1,2,3],[0.1,1.1,2.1,3.1]]
>>> y = [[0.5,0.5,0.5,0.5],[0.51,0.52,0.49,0.53]]
>>> [max(filter(lambda t: 1.0 <= t[0] <= 2.5, zip(rx, ry)), key=lambda t: t[1])[1]
... for rx, ry in zip(x, y)]
...
[0.5, 0.52]
Or a bit shorter, using a list comprehension to filter and reverse order of the tuple so max can use natural ordering:
>>> [max((y, x) for (x, y) in zip(rx, ry) if 1.0 <= x <= 2.5)[0]
... for rx, ry in zip(x, y)]
...
[0.5, 0.52]
As you were suggesting a Numpy solution:
import numpy as np
x = np.array([[0, 1, 2, 3], [0.1, 1.1, 2.1, 3.1]])
y = np.array([[0.5, 0.5, 0.5, 0.5], [0.51, 0.52, 0.49, 0.53]])
print([np.max(y[i][(x[i] >= 1) & (x[i] <= 2.5)]) for i in range(len(x))])
gives
[0.5, 0.52]

how to plot arrow on two matrix(one is theta, another is length)

I have two numpy arrays already, one is the theta, another is the length:
for example,
x = np.array([[1, 2, 3], [4, 5, 6]], np.int32) #length of the arrow
y = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]], np.float) #theta of the arrow
then I want to plot arrow on this 3*2 image (assume this is a 2*3 pixels image),each arrow on each pixel, I expect 6 arrows, but do not know how to plot, many thanks
The simplest way to use quiver is to calculate u,v coordinates for the arrows using length times cos and sine of the angle.
import matplotlib.pyplot as plt
import numpy as np
x = np.array([[1, 2, 3], [4, 5, 6]], np.int32) # length of the arrow
y = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]], np.float) # theta of the arrow
u, v = x * (np.cos(y), np.sin(y))
fig, ax = plt.subplots()
q = ax.quiver(u, v)
plt.xlim(-0.5, len(x[0]) - 0.5)
plt.ylim(-0.5, len(x) - 0.5)
plt.xticks(range(len(x[0])))
plt.yticks(range(len(x)))
plt.show()

Adding values to bars of subplots

I have a subplot bar chart coded as:
axes = df.plot.bar(yerr=df1, figsize=(10,8), legend=False,
title='Bar chart',grid=1, subplots=True, layout (5,1),xticks=None)
Is there an easy way to modify the code so that to see numerical values from dataframe df on the top of each bar?
UPDATE: with the code below still no values:
df = DataFrame(np.zeros((5, 3)))
df.index=['[1,3)','[3, 4)','[4, 5)','[5, 6)','[7]']
df.columns=['cat1','cat2','cat3']
df.iloc[0,:]= np.array( [0.4, 0.3, 0.2])
df.iloc[1,:]= np.array( [0, 0.1, 0.9])
df.iloc[2,:]= np.array( [0.3, 0.1, 0.3])
df.iloc[3,:]= np.array( [0, 0, 0.2])
df.iloc[4,:]= np.array( [0.0, 0, 0.9])
se_df = DataFrame(np.zeros((5, 3)))
se_df.index=['[1,3)','[3, 4)','[4, 5)','[5, 6)','[7]']
se_df.columns=['cat1','cat2','cat3']
se_df.iloc[0,:]= np.array( [0.1, 0.2, 0.002])
se_df.iloc[1,:]= np.array( [0.003, 0.02, 0.008])
se_df.iloc[2,:]= np.array( [0.006, 0.03, 0.0002])
se_df.iloc[3,:]= np.array( [0.001, 0, 0.0001])
se_df.iloc[4,:]= np.array( [0.0001, 0, 0.0002])
df1=df.transpose()
se_df1=se_df.transpose()
axes = df1.plot.bar(yerr=se_df1, figsize=(10,8), legend=False,
title='Title',grid=1, subplots=True, layout=(5,1),xticks=None)
for n,i in enumerate(axes, 1):
for rec, label in zip(i.patches,df.loc[:, n].astype(str)):
height = rec.get_height()
i.text(rec.get_x() + rec.get_width() / 2, height - 5, label,
ha = 'center', va='bottom', color='w', weight='bold')
plt.tight_layout()
Could you please indicate what am I doing wrong?
Update with your new code:
df = pd.DataFrame(np.zeros((5, 3)))
df.index=['[1,3)','[3, 4)','[4, 5)','[5, 6)','[7]']
df.columns=['cat1','cat2','cat3']
df.iloc[0,:]= np.array( [0.4, 0.3, 0.2])
df.iloc[1,:]= np.array( [0, 0.1, 0.9])
df.iloc[2,:]= np.array( [0.3, 0.1, 0.3])
df.iloc[3,:]= np.array( [0, 0, 0.2])
df.iloc[4,:]= np.array( [0.0, 0, 0.9])
se_df = pd.DataFrame(np.zeros((5, 3)))
se_df.index=['[1,3)','[3, 4)','[4, 5)','[5, 6)','[7]']
se_df.columns=['cat1','cat2','cat3']
se_df.iloc[0,:]= np.array( [0.1, 0.2, 0.002])
se_df.iloc[1,:]= np.array( [0.003, 0.02, 0.008])
se_df.iloc[2,:]= np.array( [0.006, 0.03, 0.0002])
se_df.iloc[3,:]= np.array( [0.001, 0, 0.0001])
se_df.iloc[4,:]= np.array( [0.0001, 0, 0.0002])
df1=df.transpose()
se_df1=se_df.transpose()
naxes = df1.plot.bar(yerr=se_df1, figsize=(10,8), legend=False,
title='Title',grid=1, subplots=True, layout=(5,1),xticks=None)
for n,i in enumerate(naxes, 1):
for rec, label in zip(i[0].patches,df1.iloc[:, n-1].astype(str)):
height = rec.get_height()
i[0].text(rec.get_x() + rec.get_width() / 2, height * .8, label,
ha = 'center', va='bottom', color='w', weight='bold')
plt.tight_layout()
Let's use:
np.random.seed(20)
df = pd.DataFrame({'Chart':[1,2,3,4]*3,'x':[1,2,3]*4,'y':np.random.randint(0,50,12)})
df_chart = df.set_index(['x','Chart'])['y'].unstack()
naxes = df_chart.plot.bar(subplots=True, figsize=(15,10), grid=1, yerr=df.groupby(['Chart'])['y'].transform('std'))
for n,i in enumerate(naxes, 1):
for rec, label in zip(i.patches,df_chart.loc[:, n].astype(str)):
height = rec.get_height()
i.text(rec.get_x() + rec.get_width() / 2, height - 5, label,
ha = 'center', va='bottom', color='w', weight='bold')
plt.tight_layout()
Output:

Categories

Resources