应用由单独的固定点确定的旋转矩阵 - python

Apply rotation matrix determined by separate fixed point - python

我正在对一组点应用旋转矩阵,目的是使这些点沿水平轴对齐。使用下面,我要调整的xy点记录在xy

我希望使用 X_RefY_Ref 以及 X_FixedY_Fixed 之间的角度来转换点。我也希望转换点,以便 X_RefY_Ref 在旋转完成后位于 0,0

旋转点目前不为此进行调整。我不确定是否应该在旋转之前或之后考虑参考点。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
import pandas as pd

df = pd.DataFrame({  
    'Period' : ['1','1','1','1','2','2','2','2'],        
    'Label' : ['A','B','C','D','A','B','C','D'],                             
    'x' : [2.0,3.0,3.0,2.0,2.0,3.0,3.0,1.0],
    'y' : [2.0,3.0,-1.0,0.0,2.0,3.0,-1.0,1.0],     
    'X_Ref' : [1,1,1,1,2,2,2,2],
    'Y_Ref' : [1,1,1,1,0,0,0,0],        
    'X_Fixed' : [0,0,0,0,0,0,0,0],
    'Y_Fixed' : [0,0,0,0,2,2,2,2],                
    })

np.random.seed(1)

xy = df[['x','y']].values
Ref = df[['X_Ref','Y_Ref']].values
Fix = df[['X_Fixed','Y_Fixed']].values

fig, ax = plt.subplots()

plot_kws = {'alpha': 0.75,
        'edgecolor': 'white',
        'linewidths': 0.75}

ax.scatter(xy[:, 0], xy[:, 1], **plot_kws)
ax.scatter(Ref[:, 0], Ref[:, 1], marker = 'x')
ax.scatter(Fix[:, 0], Fix[:, 1], marker = '+')

pca = PCA(2)

# Fit the PCA object, but do not transform the data
pca.fit(xy)

# pca.components_ : array, shape (n_components, n_features)
# cos theta
ct = pca.components_[0, 0]
# sin theta
st = pca.components_[0, 1]

# One possible value of theta that lies in [0, pi]
t = np.arccos(ct)

# If t is in quadrant 1, rotate CLOCKwise by t
if ct > 0 and st > 0:
    t *= -1
# If t is in Q2, rotate COUNTERclockwise by the complement of theta
elif ct < 0 and st > 0:
    t = np.pi - t
# If t is in Q3, rotate CLOCKwise by the complement of theta
elif ct < 0 and st < 0:
    t = -(np.pi - t)
# If t is in Q4, rotate COUNTERclockwise by theta, i.e., do nothing
elif ct > 0 and st < 0:
    pass

# Manually build the ccw rotation matrix
rotmat = np.array([[np.cos(t), -np.sin(t)], 
                   [np.sin(t),  np.cos(t)]])

# Apply rotation to each row of 'm'. The output (m2)
# will be the rotated FIFA input coordinates.
m2 = (rotmat @ xy.T).T

# Center the rotated point cloud at (0, 0)
m2 -= m2.mean(axis=0)

初始分发期 1:

计划分发期 1:

初始分配期 2:

计划分配期 2:

你的问题不清楚,因为如果你绘制已经计算出的m2,问题中提到的“预期轮换”已经可以实现:

fig, ax = plt.subplots()
ax.scatter(m2[:, 0], m2[:, 1], **plot_kws)

输出:

但是你在问题中也提到了以下内容:

The rotation angle is determined by the angle between X_Ref,Y_Ref and X_Fixed,Y_Fixed.

这是一个完全不同的场景。您可以通过计算两点之间的 arctan 来计算两点之间的角度,而无需使用 PCA。这可以使用 numpy.arctan 完成,如下所示:

t = np.arctan((Y_Fixed - Y_Ref/ X_Fixed - X_Ref))

这里 (X_Fixed, Y_Fixed) 和 (X_Ref, Y_Ref) 假设为两个点。

对于数据框中的每一行,您可以根据 (X_Fixed、Y_Fixed) 之间的角度旋转后计算 xy 值和 (X_Ref, Y_Ref) 在该特定行中。这可以使用以下代码片段完成;

def rotate_points(row):
    t = np.arctan((row['Y_Fixed'] - row['Y_Ref']/ row['X_Fixed'] - row['X_Ref']))
    rotmat = np.array([[np.cos(t), -np.sin(t)], 
                   [np.sin(t),  np.cos(t)]])
    xy = row[['x','y']].values
    rotated = rotmat @ xy
    return rotated

df['rotated_x'] = df.apply(lambda row: rotate_points(row)[0], axis = 1)
df['rotated_y'] = df.apply(lambda row: rotate_points(row)[1], axis = 1)

您的数据框现在看起来像这样,并在右侧添加了两个新列:

+----+----------+---------+-----+-----+---------+---------+-----------+-----------+-------------+-------------+-------------+
|    |   Period | Label   |   x |   y |   X_Ref |   Y_Ref |   X_Fixed |   Y_Fixed | Direction   |   rotated_x |   rotated_y |
|----+----------+---------+-----+-----+---------+---------+-----------+-----------+-------------+-------------+-------------|
|  0 |        1 | A       |  -1 |   1 |       1 |       3 |        -2 |         0 | Left        |   -1.34164  |    0.447214 |
|  1 |        1 | B       |   0 |   4 |       1 |       3 |        -2 |         0 | Left        |   -1.78885  |    3.57771  |
|  2 |        1 | C       |   2 |   2 |       1 |       3 |        -2 |         0 | Left        |    0.894427 |    2.68328  |
|  3 |        1 | D       |   2 |   3 |       1 |       3 |        -2 |         0 | Left        |    0.447214 |    3.57771  |
|  4 |        2 | E       |   2 |   4 |       1 |       3 |        -2 |         0 | Right       |    0        |    4.47214  |
|  5 |        2 | F       |   1 |   4 |       1 |       3 |        -2 |         0 | Right       |   -0.894427 |    4.02492  |
|  6 |        2 | G       |   3 |   5 |       1 |       3 |        -2 |         0 | Right       |    0.447214 |    5.81378  |
|  7 |        2 | H       |   0 |   2 |       1 |       3 |        -2 |         0 | Right       |   -0.894427 |    1.78885  |
+----+----------+---------+-----+-----+---------+---------+-----------+-----------+-------------+-------------+-------------+

现在您已经根据需要旋转了 xy 点。

更新:

根据修改后的问题,您可以在绘图中的 (0,0) 处添加参考点,如下所示:

fig, ax = plt.subplots()
ax.scatter(m2[:, 0], m2[:, 1], **plot_kws)
ax.scatter(list(np.repeat(0, len(Ref))), list(np.repeat(0, len(Ref))) , **plot_kws)
plt.show()

输出:

如果我了解您要实现的目标,则无需任何 PCA。我会使用复数,这看起来更简单:

编辑

之前翻译的步骤顺序有点小错误。此编辑将更正它并使用您的新数据集,包括在不同时期更改 ref/fixed 点。

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame({  
    'Period' : ['1','1','1','1','2','2','2','2'],        
    'Label' : ['A','B','C','D','A','B','C','D'],                             
    'x' : [2.0,3.0,3.0,2.0,2.0,3.0,3.0,1.0],
    'y' : [2.0,3.0,-1.0,0.0,2.0,3.0,-1.0,1.0],     
    'X_Ref' : [1,1,1,1,2,2,2,2],
    'Y_Ref' : [1,1,1,1,0,0,0,0],        
    'X_Fixed' : [0,0,0,0,0,0,0,0],
    'Y_Fixed' : [0,0,0,0,2,2,2,2],                
    })

首先,将fixed/ref指向复数:

for f in ['Ref', 'Fixed']:
    df[f] = df['X_'+f] + 1j*df['Y_'+f]
    df.drop(['X_'+f, 'Y_'+f], axis=1, inplace=True)

计算旋转(请注意,它是与您在问题中陈述的角度相反的角度,以符合您的预期结果):

df['angle'] = - np.angle(df['Ref'] - df['Fixed'])

计算每个点的旋转(包括ref/fixed):

df['rotated'] = (df['x'] + 1j*df["y"]) * np.exp(1j*df['angle'])
for f in ['Ref', 'Fixed']:
    df[f+'_Rotated'] = df[f] * np.exp(1j*df['angle'])

将您的数据集围绕“参考”点居中:

df['translation'] = - df['Ref_Rotated']
df['NewPoint'] = df['rotated'] + df['translation']
for f in ['Ref', 'Fixed']:
    df[f+'_Transformed'] = df[f+'_Rotated'] + df['translation']

恢复笛卡尔坐标:

df['x2'] = np.real(df['NewPoint'])
df['y2'] = np.imag(df['NewPoint'])
for f in ['Ref', 'Fixed']:
    df['NewX_'+f] = np.real(df[f+'_Transformed'])
    df['NewY_'+f] = np.imag(df[f+'_Transformed'])

然后绘制您喜欢的任何时间段的输出:

output = df[['Period', 'Label', 'x2', 'y2', 'NewX_Ref', 'NewY_Ref', 'NewX_Fixed', 'NewY_Fixed']]
output.set_index('Period', inplace=True)

fig, ax = plt.subplots()

plot_kws = {'alpha': 0.75,
        'edgecolor': 'white',
        'linewidths': 0.75}

plt.xlim(-5,5)
plt.ylim(-5,5)


period = '1'

ax.scatter(output.loc[period, 'NewX_Ref'], output.loc[period, 'NewY_Ref'])
ax.scatter(output.loc[period, 'NewX_Fixed'], output.loc[period, 'NewY_Fixed'])
ax.scatter(output.loc[period, 'x2'], output.loc[period, 'y2'], **plot_kws, marker = '+')

plt.gca().set_aspect('equal', adjustable='box')
plt.show()

第 1 期的结果:

第 2 期的结果: