web-dev-qa-db-ja.com

Pythonを使用した3Dの2DサーフェスからのポイントのDelaunay三角形分割?

3Dポイントのコレクションがあります。これらのポイントは一定のレベル(z = 0,1、...、7)でサンプリングされます。画像で明確にする必要があります。

point collection

これらのポイントは、Xと呼ばれる_(N, 3)_という形状の乱雑なndarrayにあります。上記のプロットは、以下を使用して作成されます。

_import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

X = load('points.npy')
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_wireframe(X[:,0], X[:,1], X[:,2])
ax.scatter(X[:,0], X[:,1], X[:,2])
plt.draw()
_

代わりに、このオブジェクトの表面のみを三角形化して、表面をプロットしたいと思います。ただし、このオブジェクトの凸包は、微妙な形状情報が失われるため、確認したくありません。

私はax.plot_trisurf(X[:,0], X[:,1], X[:,2])を試しましたが、これは次の混乱を引き起こします:

mess

何か助けは?

データの例

問題を表す3Dデータを生成するスニペットは次のとおりです。

_import numpy as np
X = []
for i in range(8):
    t = np.linspace(0,2*np.pi,np.random.randint(30,50))
    for j in range(t.shape[0]):
        # random circular objects...
        X.append([
            (-0.05*(i-3.5)**2+1)*np.cos(t[j])+0.1*np.random.Rand()-0.05,
            (-0.05*(i-3.5)**2+1)*np.sin(t[j])+0.1*np.random.Rand()-0.05,
            i
        ])
X = np.array(X)
_

元の画像のサンプルデータ

これが元のデータへのペーストビンです:

http://Pastebin.com/YBZhJcsV

以下は、定数zに沿ったスライスです。

enter no image description here

21
Matt Hancock

アップデート3

更新2で説明した具体的な例を次に示します。視覚化のためのmayaviがない場合は、インストールすることをお勧めします edm経由edm install mayavi pyqt matplotlibを使用します。

3Dに積み重ねられたおもちゃの2D輪郭

enter image description here

等高線-> 3Dサーフェス

enter image description here

図を生成するコード

from matplotlib import path as mpath
from mayavi import mlab
import numpy as np


def make_star(amplitude=1.0, rotation=0.0):
    """ Make a star shape
    """
    t = np.linspace(0, 2*np.pi, 6) + rotation
    star = np.zeros((12, 2))
    star[::2] = np.c_[np.cos(t), np.sin(t)]
    star[1::2] = 0.5*np.c_[np.cos(t + np.pi / 5), np.sin(t + np.pi / 5)]
    return amplitude * star

def make_stars(n_stars=51, z_diff=0.05):
    """ Make `2*n_stars-1` stars stacked in 3D
    """
    amps = np.linspace(0.25, 1, n_stars)
    amps = np.r_[amps, amps[:-1][::-1]]
    rots = np.linspace(0, 2*np.pi, len(amps))
    zamps = np.linspace
    stars = []
    for i, (amp, rot) in enumerate(Zip(amps, rots)):
        star = make_star(amplitude=amp, rotation=rot)
        height = i*z_diff
        z = np.full(len(star), height)
        star3d = np.c_[star, z]
        stars.append(star3d)
    return stars

def polygon_to_boolean(points, xvals, yvals):
    """ Convert `points` to a boolean indicator mask
    over the specified domain
    """
    x, y = np.meshgrid(xvals, yvals)
    xy = np.c_[x.flatten(), y.flatten()]
    mask = mpath.Path(points).contains_points(xy).reshape(x.shape)
    return x, y, mask

def plot_contours(stars):
    """ Plot a list of stars in 3D
    """
    n = len(stars)

    for i, star in enumerate(stars):
        x, y, z = star.T
        mlab.plot3d(*star.T)
        #ax.plot3D(x, y, z, '-o', c=(0, 1-i/n, i/n))
        #ax.set_xlim(-1, 1)
        #ax.set_ylim(-1, 1)
    mlab.show()



if __name__ == '__main__':

    # Make and plot the 2D contours
    stars3d = make_stars()
    plot_contours(stars3d)

    xvals = np.linspace(-1, 1, 101)
    yvals = np.linspace(-1, 1, 101)

    volume = np.dstack([
        polygon_to_boolean(star[:,:2], xvals, yvals)[-1]
        for star in stars3d
    ]).astype(float)

    mlab.contour3d(volume, contours=[0.5])
    mlab.show()

アップデート2

私は今これを次のように行います:

  1. 各Zスライスのパスが閉じていて単純であることを利用し、matplotlib.pathを使用して輪郭の内側と外側のポイントを決定します。このアイデアを使用して、各スライスの輪郭をブール値の画像に変換し、ブール値のボリュームに結合します。
  2. 次に、 skimage 's marching_cubes メソッドを使用して、視覚化のために表面の三角形分割を取得します。

メソッドの例を次に示します。データは少し異なると思いますが、結果ははるかにきれいで、切断されたサーフェスや穴のあるサーフェスを処理できることがわかります。

元の答え

わかりました、これが私が思いついた解決策です。それは私のデータがほぼ球形であり、zで均一にサンプリングされていることに大きく依存します。他のコメントのいくつかは、より堅牢なソリューションに関する詳細情報を提供します。私のデータはほぼ球形球形なので、データポイントの球座標変換から方位角と天頂角を三角測量します。

import numpy as np
import matplotlib.pyplot as plt 
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.tri as mtri

X = np.load('./mydatars.npy')
# My data points are strictly positive. This doesn't work if I don't center about the Origin.
X -= X.mean(axis=0)

rad = np.linalg.norm(X, axis=1)
zen = np.arccos(X[:,-1] / rad)
azi = np.arctan2(X[:,1], X[:,0])

tris = mtri.Triangulation(zen, azi)

fig = plt.figure()
ax  = fig.add_subplot(111, projection='3d')
ax.plot_trisurf(X[:,0], X[:,1], X[:,2], triangles=tris.triangles, cmap=plt.cm.bone)
plt.show()

上記のPastebinのサンプルデータを使用すると、次の結果が得られます。

no thanks


13
Matt Hancock

形状情報が失われる可能性があるため、凸包を使用したくないとおっしゃっていたようですね。 scipy.spatial.ConvexHull を使用していますが、「ジッター球形」のサンプルデータに対してかなりうまく機能する簡単なソリューションがあります。他の人に役立つ場合に備えて、とにかくここでそれを共有すると思いました。

from matplotlib.tri import triangulation
from scipy.spatial import ConvexHull

# compute the convex hull of the points
cvx = ConvexHull(X)

x, y, z = X.T

# cvx.simplices contains an (nfacets, 3) array specifying the indices of
# the vertices for each simplical facet
tri = Triangulation(x, y, triangles=cvx.simplices)

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.hold(True)
ax.plot_trisurf(tri, z)
ax.plot_wireframe(x, y, z, color='r')
ax.scatter(x, y, z, color='r')

plt.draw()

enter image description here

この例では、サンプルデータが多かれ少なかれ凸面上にあるため、かなりうまくいきます。おそらく、もっと難しいサンプルデータを作成できますか?トロイダル面は、凸包法が明らかに失敗する良いテストケースです。

点群から任意の3Dサーフェスをマッピングすることは本当に難しい問題です。これが 関連する質問 であり、役立つリンクがいくつか含まれています。

10
ali_m