简体   繁体   中英

Transform 3D points to 2D plot

I have a data-set of 3D points (x,y,z) projected onto a plane and i'd like to transform them into a simple 2D plot by looking at the points from an orthogonal direction to that plane. Any python explanation are much appreciated!

数据集

You can use this:

import pylab
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
fig = pylab.figure()
ax = fig.add_subplot(111, projection = '3d')
x = y = z = [1, 2, 3]
sc = ax.scatter(x,y,z)

#####################    
x2, y2, _ = proj3d.proj_transform(1, 1, 1, ax.get_proj())
print x2, y2   # project 3d data space to 2d data space
print ax.transData.transform((x2, y2))  # convert 2d space to screen space
#####################
def on_motion(e):
   # move your mouse to (1,1,1), and e.xdata, e.ydata will be the same as x2, y2
   print e.x, e.y, e.xdata, e.ydata  
fig.canvas.mpl_connect('motion_notify_event', on_motion)
pylab.show()

Depending on how the data were projected, and how perfectly planar they are, one way could be to use a PCA

Example on a fabricated dataset (please, next time, provide such a fabricated dataset. It is better, because I did mine by surmising how yours may look).

import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.decomposition import PCA

# Fabrication of the dataset : a bunch of 10000 random points distributed in a ring (like Saturn's rings)
radius=np.random.uniform(100,200,(10000,))
angles=np.random.uniform(0,2*np.pi,(10000,))
x1=radius*np.cos(angles)
y1=radius*np.sin(angles)
# Just to see how the original data look like
plt.figure()
plt.scatter(x1,y1)
plt.show()

在此处输入图像描述

# Note that those are "secret" data. 
# We are not supposed to know how the 2D data are
# What we will work on are 3D data fabricated as follows:
# generate 3D data, that are those, on a plane
# with some noise
# we just use vectors (1,1,1) and (1,-1,1) as generator of the plane. 
# So a planar point (x,y) will be sent do a
# 3d point x(1,1,1)+y(1,-1,1).
# Plus some noise
xyz=x1.reshape(-1,1)@[[1,1,1]] + y1.reshape(-1,1)@[[1,-1,1]] + np.random.normal(0,2,(10000,3))

fig=plt.figure()
ax = fig.add_subplot(111, projection = '3d')
ax.scatter(xyz[:,0],xyz[:,1],xyz[:,2])
plt.show()

我们将处理这些数据

So, that is the data we will work on. 3D data that are mainly on a plane. We want the 2D dataset of that plane. But, of course, we can't access to x1 , y1 , since we pretend to know only of xyz

pca=PCA(n_components=2)
xy=pca.fit_transform(xyz)
# xy are the projection on the best possible plane
# of xyz data.
plt.figure()
plt.scatter(xy[:,0], xy[:,1])
plt.show()

在此处输入图像描述

You may also know what are the axis of this plane

pca.components_
#array([[-0.70692992,  0.02117576, -0.70696653],
#       [ 0.01489184,  0.99977576,  0.01505521]])

So, roughly (-√2/2,0,-√2/2) and (0,1,0). Not the same axis we've used (1,1,1) and (1,-1,1). But see that one basis generate the other: (1,1,1) is -√2(-√2/2,0,-√2/2)+(0,1,0). And (1,-1,1) is -√2(-√2/2,0,-√2/2)-(0,1,0).

Or, the other way round: (-√2/2,0,-√2/2) = -√2/4(1,1,1)-√2/4(1,-1,1); (0,1,0)=½(1,1,1)-½(1,-1,1)

So, it is the same plane. Just not the same axis in that plane, but that is normal: nothing in a 3D data of planar points can tell how the 3D data were built.

Note that this method is well suited if 3D data are a little bit noisy. If not, you could achieve the same result with simple Gram-Schmidt method. Choosing extreme points

Starting from another xyz without the noise

# same xyz but without the noise
xyzClean=x1.reshape(-1,1)@[[1,1,1]] + y1.reshape(-1,1)@[[1,-1,1]]

# One (randomly chosen. So why not the 1st) point
# of the dataset
m0=xyzClean[0]
# Choose m1 so that it is further from m0 as possible
dm0=((xyzClean-m0)**2).sum(axis=1)
idx1=np.argmax(dm0)
m1=xyzClean[idx1]
# Choose m2 as far as both m0 and m1 as possible
dm1=((xyzClean-m1)**2).sum(axis=1)
idx2=np.argmax(np.minimum(dm0,dm1))
m2=xyzClean[idx2]

# Gram-Schmidt process to get two orthogonal 
# vectors from origin m0
v1=m1-m0
v1=v1/np.sqrt(v1@v1) # normalization
v2=(m2-m0) - ((m2-m0)@v1)*v1
v2=v2/np.sqrt(v2@v2)
# v1=[ 0.70700705, -0.01679433,  0.70700705]
# v2=[0.01187538, 0.99985897, 0.01187538]
# Note that 1.39721978*v1+1.02360973*v2
 = (1,1,1)
# And 1.43080844*v1-0.9761082*v2 = (1,-1,1)
# So, again, same plane
# The advantage of having orthogonal basis, is that
# projection on this basis is now easy
projDataV1 = xyzClean@v1
projDataV2 = xyzClean@v2

plt.figure()
plt.scatter(projDataV1, projDataV2)
plt.show()

在此处输入图像描述

That second method is well suited if you have no noise, and your 3d data are exactly planar. Not that the 1st one wouldn't work (it would work anytime). But this one is faster. And could be even faster, if instead of selecting m0, m1 and m2 as far as possible from each other, I had just selected them "far enough" from each other.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM