Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 5e6e837

Browse filesBrowse files
committed
MAINT: Unify calculation of normal vectors from polygons
This combines `get_normals` and `_generate_normals`, and eliminates all other calls to np.cross. `get_normals` and `_generate_normals` were profiled, and it was found that vectorizing `np.cross` like in `get_normals` was faster: ```python import numpy as np def get_normals(polygons): v1 = np.empty((len(polygons), 3)) v2 = np.empty((len(polygons), 3)) for poly_i, ps in enumerate(polygons): # pick three points around the polygon at which to find the # normal doesn't vectorize because polygons is jagged i1, i2, i3 = 0, len(ps)//3, 2*len(ps)//3 v1[poly_i, :] = ps[i1, :] - ps[i2, :] v2[poly_i, :] = ps[i2, :] - ps[i3, :] return np.cross(v1, v2) def _generate_normals(self, polygons): normals = [] for verts in polygons: v1 = np.array(verts[0]) - np.array(verts[1]) v2 = np.array(verts[2]) - np.array(verts[0]) normals.append(np.cross(v1, v2)) return np.array(normals) polygons = [ np.random.rand(np.random.randint(10, 1000), 3) for i in range(100) ] %timeit _generate_normals(polygons) # 3.14 ms ± 255 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit get_normals(polygons) # 452 µs ± 4.33 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) ```
1 parent 3e90025 commit 5e6e837
Copy full SHA for 5e6e837

File tree

Expand file treeCollapse file tree

1 file changed

+44
-33
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+44
-33
lines changed

‎lib/mpl_toolkits/mplot3d/axes3d.py

Copy file name to clipboardExpand all lines: lib/mpl_toolkits/mplot3d/axes3d.py
+44-33Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,28 +1686,14 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None,
16861686
if fcolors is not None:
16871687
colset.append(fcolors[rs][cs])
16881688

1689-
def get_normals(polygons):
1690-
"""
1691-
Takes a list of polygons and return an array of their normals
1692-
"""
1693-
v1 = np.empty((len(polygons), 3))
1694-
v2 = np.empty((len(polygons), 3))
1695-
for poly_i, ps in enumerate(polygons):
1696-
# pick three points around the polygon at which to find the
1697-
# normal doesn't vectorize because polygons is jagged
1698-
i1, i2, i3 = 0, len(ps)//3, 2*len(ps)//3
1699-
v1[poly_i, :] = ps[i1, :] - ps[i2, :]
1700-
v2[poly_i, :] = ps[i2, :] - ps[i3, :]
1701-
return np.cross(v1, v2)
1702-
17031689
# note that the striding causes some polygons to have more coordinates
17041690
# than others
17051691
polyc = art3d.Poly3DCollection(polys, *args, **kwargs)
17061692

17071693
if fcolors is not None:
17081694
if shade:
17091695
colset = self._shade_colors(
1710-
colset, get_normals(polys), lightsource)
1696+
colset, self._generate_normals(polys), lightsource)
17111697
polyc.set_facecolors(colset)
17121698
polyc.set_edgecolors(colset)
17131699
elif cmap:
@@ -1721,7 +1707,7 @@ def get_normals(polygons):
17211707
else:
17221708
if shade:
17231709
colset = self._shade_colors(
1724-
color, get_normals(polys), lightsource)
1710+
color, self._generate_normals(polys), lightsource)
17251711
else:
17261712
colset = color
17271713
polyc.set_facecolors(colset)
@@ -1732,21 +1718,48 @@ def get_normals(polygons):
17321718
return polyc
17331719

17341720
def _generate_normals(self, polygons):
1735-
'''
1736-
Generate normals for polygons by using the first three points.
1737-
This normal of course might not make sense for polygons with
1738-
more than three points not lying in a plane.
1721+
"""
1722+
Takes a list of polygons and return an array of their normals.
17391723
17401724
Normals point towards the viewer for a face with its vertices in
17411725
counterclockwise order, following the right hand rule.
1742-
'''
17431726
1744-
normals = []
1745-
for verts in polygons:
1746-
v1 = np.array(verts[1]) - np.array(verts[0])
1747-
v2 = np.array(verts[2]) - np.array(verts[0])
1748-
normals.append(np.cross(v1, v2))
1749-
return normals
1727+
Uses three points equally spaced around the polygon.
1728+
This normal of course might not make sense for polygons with more than
1729+
three points not lying in a plane, but it's a plausible and fast
1730+
approximation.
1731+
1732+
Parameters
1733+
----------
1734+
polygons: list of (M_i, 3) array_like, or (..., M, 3) array_like
1735+
A sequence of polygons to compute normals for, which can have
1736+
varying numbers of vertices. If the polygons all have the same
1737+
number of vertices and array is passed, then the operation will
1738+
be vectorized.
1739+
1740+
Returns
1741+
-------
1742+
normals: (..., 3) array_like
1743+
A normal vector estimated for the polygon.
1744+
1745+
"""
1746+
if isinstance(polygons, np.ndarray):
1747+
# optimization: polygons all have the same number of points, so can
1748+
# vectorize
1749+
n = polygons.shape[-2]
1750+
i1, i2, i3 = 0, n//3, 2*n//3
1751+
v1 = polygons[..., i1, :] - polygons[..., i2, :]
1752+
v2 = polygons[..., i2, :] - polygons[..., i3, :]
1753+
else:
1754+
# The subtraction doesn't vectorize because polygons is jagged.
1755+
v1 = np.empty((len(polygons), 3))
1756+
v2 = np.empty((len(polygons), 3))
1757+
for poly_i, ps in enumerate(polygons):
1758+
n = len(ps)
1759+
i1, i2, i3 = 0, n//3, 2*n//3
1760+
v1[poly_i, :] = ps[i1, :] - ps[i2, :]
1761+
v2[poly_i, :] = ps[i2, :] - ps[i3, :]
1762+
return np.cross(v1, v2)
17501763

17511764
def _shade_colors(self, color, normals, lightsource=None):
17521765
'''
@@ -1993,9 +2006,7 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None,
19932006
polyc.set_norm(norm)
19942007
else:
19952008
if shade:
1996-
v1 = verts[:, 0, :] - verts[:, 1, :]
1997-
v2 = verts[:, 1, :] - verts[:, 2, :]
1998-
normals = np.cross(v1, v2)
2009+
normals = self._generate_normals(verts)
19992010
colset = self._shade_colors(color, normals, lightsource)
20002011
else:
20012012
colset = color
@@ -2042,9 +2053,9 @@ def _3d_extend_contour(self, cset, stride=5):
20422053
botverts[0][i2],
20432054
botverts[0][i1]])
20442055

2045-
v1 = np.array(topverts[0][i1]) - np.array(topverts[0][i2])
2046-
v2 = np.array(topverts[0][i1]) - np.array(botverts[0][i1])
2047-
normals.append(np.cross(v1, v2))
2056+
# all polygons have 4 vertices, so vectorize
2057+
polyverts = np.array(polyverts)
2058+
normals = self._generate_normals(polyverts)
20482059

20492060
colors = self._shade_colors(color, normals)
20502061
colors2 = self._shade_colors(color, normals)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.