Delaunay triangularization of Polyhedron (Python) - python

I'm trying to get the Delaunay Triangulation of a polyhedron in python so that I can calculate the centroid. I see that there is a Delaunay function in scipy.spatial and that it works in n-dimensions. The trouble is that the documentation shows 2D use and gives me no indication of what to do with higher dimensions. Being able to decompose this object into an array would probably solve this issue for me, but I don't know how to do that.
The problem I'm running into is that I do not know how to verify that this is working correctly as it is outputting an object. I can find nothing on Google about how to graph a polyhedron or how to use this object that scipy is spitting back.
If I do
import numpy as np
from scipy.spatial import Delaunay
points = np.array([[0,0,0],[1,0,0],[1,1,0],[1,0,1],[1,1,1],[0,1,0],[0,1,1],[0,0,1]])
Delaunay(points)
I really would just like to be able to get back the coordinates of these tetrahedrons so that I can calculate the centroids of the polyhedrons. It would also be really nice if I were able to graph the tesselated polyhedron too. I saw in MATLAB that I can do this with a fuction called trimesn, and I found one from matplotlib but it seems to be really different and its documentation is not great.
from matplotlib.collections import TriMesh TriMesh.__doc__
u'\n Class for the efficient drawing of a triangular mesh using\n
Gouraud shading.\n\n A triangular mesh is a
:class:`~matplotlib.tri.Triangulation`\n object.\n '

What tess = Delaunay(pts) returns is an object of the Delanauy class. You can check the tetrahedrons as tess.simplices. It has different attributes and methods. In 2D, for example, it can plot you triangulation, convex hull and Voronoi tesselation.
Regarding the visualization of the final collection of tetrahedrons I didn't find a straightforward way of doing it, but I managed to get a working script. Check the code below.
from __future__ import division
import numpy as np
from scipy.spatial import Delaunay
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
from itertools import combinations
def plot_tetra(tetra, pts, color="green", alpha=0.1, lc="k", lw=1):
combs = combinations(tetra, 3)
for comb in combs:
X = pts[comb, 0]
Y = pts[comb, 1]
Z = pts[comb, 2]
verts = [zip(X, Y, Z)]
triangle = Poly3DCollection(verts, facecolors=color, alpha=0.1)
lines = Line3DCollection(verts, colors=lc, linewidths=lw)
ax.add_collection3d(triangle)
ax.add_collection3d(lines)
pts = np.array([
[0,0,0],
[1,0,0],
[1,1,0],
[1,0,1],
[1,1,1],
[0,1,0],
[0,1,1],
[0,0,1]])
tess = Delaunay(pts)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for k, tetra in enumerate(tess.simplices):
color = plt.cm.Accent(k/(tess.nsimplex - 1))
plot_tetra(tetra, pts, color=color, alpha=0.1, lw=0.5, lc="k")
ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], c='k')
plt.savefig("Delaunay.png", dpi=600)
plt.show()
The resulting image is

You do not need the Delaunay triangulation to compute the centroid of a polyhedron.
The centroid is a weighted sum of tetrahedra centroids, where the weight is the volume of each tetrahedron.
You do not need to partition the polyhedron into tetrahedra.
First, triangulate the faces of the polyhedron, i.e., quads get partitioned
into two coplanar triangles, etc.
Next, pick an arbitrary point p in space, say, the origin.
Now, for each triangle face (a,b,c), compute the signed volume of the tetrahedron
(p,a,b,c). This works if all triangles are oriented counterclockwise.
The signed volume takes care of everything via cancellation.
Use the signed volume as the weight
to multiply the tetrahedra centroids.
Tetrahedra signed volume is explained in Chapter 1 of my book,
"Computational Geometry in C."

Related

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.

Finding image peaks using ndimage.maximum_filter and skimage.peak_local_max

I am trying to find some relative maximums of a given image. I understand that there are two possible methods, the first is using scipy.ndimage.maximum_filter() and the second using skimage.feature.peak_local_max().
In order to compare both methods I have modified an example from skimage shown here in order to compare the peaks found.
from scipy import ndimage as ndi
import matplotlib.pyplot as plt
from skimage.feature import peak_local_max
from skimage import data, img_as_float
im = img_as_float(data.coins())
# use ndimage to find the coordinates of maximum peaks
image_max = ndi.maximum_filter(im, size=20) == im
j, i = np.where(image_max)
coordinates_2 = np.array(zip(i,j))
# use skimage to find the coordinates of local maxima
coordinates = peak_local_max(im, min_distance=20)
# display results
fig, axes = plt.subplots(1, 2, figsize=(8, 3), sharex=True, sharey=True)
ax = axes.ravel()
ax[0].imshow(im, cmap=plt.cm.gray)
ax[0].plot(coordinates_2[:, 0], coordinates_2[:, 1], 'r.')
ax[0].axis('off')
ax[0].set_title('Maximum filter')
ax[1].imshow(im, cmap=plt.cm.gray)
ax[1].autoscale(False)
ax[1].plot(coordinates[:, 1], coordinates[:, 0], 'r.')
ax[1].axis('off')
ax[1].set_title('Peak local max')
fig.tight_layout()
plt.show()
This gives the next peaks for each method:
I understand that the parameter size for maximum_filter is not equivalent to the min_distance from peak_local_max, but I'd like to know if there is a method in which both give the same result. Is that possible?
Some related question on stackoverflow are:
Get coordinates of local maxima in 2D array above certain value
Peak detection in a 2D array
have you been able to come up with a solution?
I think one step into the direction is simply setting size=41 in the maximum filter.
This gives me rather similar, though not identical results.
The idea behind that is that peak_local_max looks for peaks in a region specified by 2 * min_distance + 1 (Source: Documentation).
Most of the additional peaks identified by ndi.maximum_filter are close to the boundary, but there also two additional peaks in the middle of the picture (additional peaks are marked in blue).
Peaks
At assume that peak_local_max employs some logic to strip boundary peaks and peaks that are close to other peak. Most likely based on the value of the peak.

Boundary points from a set of coordinates

I have a set of lat,long points, and from this points I'd like to extract the points that form the boundaries, I've used convexhull, but for my purpouse is not enough as convehull just returns the most distant points that form the polygon where all the points fit, I need ALL the points that form the peremiter, something like the image I've attached. What could I do? Is there some kind of package ready to use instead of implement any spatial algorithm?
Thanks
You must use a package for convex polygons. Here is an example:
import alphashape
import matplotlib.pyplot as plt
points = put your points here (can be array)!
alpha = 0.95 * alphashape.optimizealpha(points)
hull = alphashape.alphashape(points, alpha)
hull_pts = hull.exterior.coords.xy
fig, ax = plt.subplots()
ax.scatter(hull_pts[0], hull_pts[1], color='red')
Use Concave hull (Alpha shape) instead.
Assuming that you have all points (latitudes and longitudes) in two lists LATS, LONGS respectively, this python snippet cand do the trick. hullPoint will have the set of points that can draw the convex hull.
import numpy as np
from scipy.spatial import ConvexHull
allPoints=np.column_stack((LATS,LONGS))
hullPoints = ConvexHull(allPoints)

Projection of 3D convex hull onto xy plane with a colour map

What I'd like at the end is the smoothed colour map with contours plotted on top of it. The idea is to preserve as much as possible information from the 3D convex hull.
The problem is that the code I developed so far doesn't work for all the inputs.
Example
If I set tricontourf() integer parameter let say to 8 and provide 10 input files I will get 8 plots which are OK but 2 will be solid colour.
Next if I change parameter to 9 I'll get 7 good and 3 odd. Some of the good ones from the first step are now wrong!
Ideally I'd like to have this parameter fixed at ~25 so the colour map is smoothed.
Have look at the pictures:
This is wrong, int parameter = 9
this is what I want but smoother, int parameter 8
What is important to me is to have triangulation based on the convex hull.
import matplotlib.pyplot as plt
import numpy as np
import sys, os, time, math
from scipy.spatial import ConvexHull
from matplotlib.tri import Triangulation
import matplotlib.cm as cm
# get covex hull data and save them to an array
cvx = []
dataX = []
for filename in sys.argv[1:]:
X = np.genfromtxt(filename,delimiter="", skip_header=2)
dataX.append(X)
hull = ConvexHull(X)
cvx.append(hull)
for idx,filename in enumerate(sys.argv[1:]):
# start plotting data
x, y, z = dataX[idx].T
# triangulation based on a convex hull
simpl = cvx[idx].simplices
tri = Triangulation(x, y, triangles=simpl)
# plot lines (triangles)
plt.triplot(tri, color='k')
# plot contour lines based on convex hull facets
plt.tricontour(x, y, z, 5, linewidths=0.5, colors='k', triangles=simpl)
# plot colour map
plt.tricontourf(x, y, z, 8, cmap=plt.cm.rainbow, triangles=simpl)
plt.show()

How to plot complex numbers (Argand Diagram) using matplotlib

I'd like to create an Argand Diagram from a set of complex numbers using matplotlib.
Are there any pre-built functions to help me do this?
Can anyone recommend an approach?
Image by LeonardoG, CC-SA-3.0
I'm not sure exactly what you're after here...you have a set of complex numbers, and want to map them to the plane by using their real part as the x coordinate and the imaginary part as y?
If so you can get the real part of any python imaginary number with number.real and the imaginary part with number.imag. If you're using numpy, it also provides a set of helper functions numpy.real and numpy.imag etc. which work on numpy arrays.
So for instance if you had an array of complex numbers stored something like this:
In [13]: a = n.arange(5) + 1j*n.arange(6,11)
In [14]: a
Out[14]: array([ 0. +6.j, 1. +7.j, 2. +8.j, 3. +9.j, 4.+10.j])
...you can just do
In [15]: fig,ax = subplots()
In [16]: ax.scatter(a.real,a.imag)
This plots dots on an argand diagram for each point.
edit: For the plotting part, you must of course have imported matplotlib.pyplot via from matplotlib.pyplot import * or (as I did) use the ipython shell in pylab mode.
To follow up #inclement's answer; the following function produces an argand plot that is centred around 0,0 and scaled to the maximum absolute value in the set of complex numbers.
I used the plot function and specified solid lines from (0,0). These can be removed by replacing ro- with ro.
def argand(a):
import matplotlib.pyplot as plt
import numpy as np
for x in range(len(a)):
plt.plot([0,a[x].real],[0,a[x].imag],'ro-',label='python')
limit=np.max(np.ceil(np.absolute(a))) # set limits for axis
plt.xlim((-limit,limit))
plt.ylim((-limit,limit))
plt.ylabel('Imaginary')
plt.xlabel('Real')
plt.show()
For example:
>>> a = n.arange(5) + 1j*n.arange(6,11)
>>> from argand import argand
>>> argand(a)
produces:
EDIT:
I have just realised there is also a polar plot function:
for x in a:
plt.polar([0,angle(x)],[0,abs(x)],marker='o')
If you prefer a plot like the one below
one type of plot
or this one second type of plot
you can do this simply by these two lines (as an example for the plots above):
z=[20+10j,15,-10-10j,5+15j] # array of complex values
complex_plane2(z,1) # function to be called
by using a simple jupyter code from here
https://github.com/osnove/other/blob/master/complex_plane.py
I have written it for my own purposes. Even better it it helps to others.
To get that:
You can use:
cmath.polar to convert a complex number to polar rho-theta coordinates. In the code below this function is first vectorized in order to process an array of complex numbers instead of a single number, this is just to prevent the use an explicit loop.
A pyplot axis with its projection type set to polar. Plot can be done using pyplot.stem or pyplot.scatter.
In order to plot horizontal and vertical lines for Cartesian coordinates there are two possibilities:
Add a Cartesian axis and plot Cartesian coordinates. This solution is described in this question. I don't think it's an easy solution as the Cartesian axis won't be centered, nor it will have the correct scaling factor.
Use the polar axis, and translate Cartesian coordinates for projections into polar coordinates. This is the solution I used to plot the graph above. To not clutter the graph I've shown only one point with its projected Cartesian coordinates.
Code used for the plot above:
from cmath import pi, e, polar
from numpy import linspace, vectorize, sin, cos
from numpy.random import rand
from matplotlib import pyplot as plt
# Arrays of evenly spaced angles, and random lengths
angles = linspace(0, 2*pi, 12, endpoint=False)
lengths = 3*rand(*angles.shape)
# Create an array of complex numbers in Cartesian form
z = lengths * e ** (1j*angles)
# Convert back to polar form
vect_polar = vectorize(polar)
rho_theta = vect_polar(z)
# Plot numbers on polar projection
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.stem(rho_theta[1], rho_theta[0])
# Get a number, find projections on axes
n = 11
rho, theta = rho_theta[0][n], rho_theta[1][n]
a = cos(theta)
b = sin(theta)
rho_h, theta_h = abs(a)*rho, 0 if a >= 0 else -pi
rho_v, theta_v = abs(b)*rho, pi/2 if b >= 0 else -pi/2
# Plot h/v lines on polar projection
ax.plot((theta_h, theta), (rho_h, rho), c='r', ls='--')
ax.plot((theta, theta_v), (rho, rho_v), c='g', ls='--')
import matplotlib.pyplot as plt
from numpy import *
'''
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`
This draws the axis for argand diagram
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`
'''
r = 1
Y = [r*exp(1j*theta) for theta in linspace(0,2*pi, 200)]
Y = array(Y)
plt.plot(real(Y), imag(Y), 'r')
plt.ylabel('Imaginary')
plt.xlabel('Real')
plt.axhline(y=0,color='black')
plt.axvline(x=0, color='black')
def argand(complex_number):
'''
This function takes a complex number.
'''
y = complex_number
x1,y1 = [0,real(y)], [0, imag(y)]
x2,y2 = [real(y), real(y)], [0, imag(y)]
plt.plot(x1,y1, 'r') # Draw the hypotenuse
plt.plot(x2,y2, 'r') # Draw the projection on real-axis
plt.plot(real(y), imag(y), 'bo')
[argand(r*exp(1j*theta)) for theta in linspace(0,2*pi,100)]
plt.show()
https://github.com/QuantumNovice/Matplotlib-Argand-Diagram/blob/master/argand.py

Categories

Resources