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 3e07fd4

Browse filesBrowse files
eric-wiesertimhoffm
authored andcommitted
MAINT: Unify calculation of normal vectors from polygons (#12136)
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 9c56881 commit 3e07fd4
Copy full SHA for 3e07fd4

File tree

Expand file treeCollapse file tree

1 file changed

+43
-32
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+43
-32
lines changed

‎lib/mpl_toolkits/mplot3d/axes3d.py

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

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

17051691
if fcolors is not None:
17061692
if shade:
17071693
colset = self._shade_colors(
1708-
colset, get_normals(polys), lightsource)
1694+
colset, self._generate_normals(polys), lightsource)
17091695
polyc.set_facecolors(colset)
17101696
polyc.set_edgecolors(colset)
17111697
elif cmap:
@@ -1719,7 +1705,7 @@ def get_normals(polygons):
17191705
else:
17201706
if shade:
17211707
colset = self._shade_colors(
1722-
color, get_normals(polys), lightsource)
1708+
color, self._generate_normals(polys), lightsource)
17231709
else:
17241710
colset = color
17251711
polyc.set_facecolors(colset)
@@ -1731,20 +1717,47 @@ def get_normals(polygons):
17311717

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

17491762
def _shade_colors(self, color, normals, lightsource=None):
17501763
"""
@@ -1991,9 +2004,7 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None,
19912004
polyc.set_norm(norm)
19922005
else:
19932006
if shade:
1994-
v1 = verts[:, 0, :] - verts[:, 1, :]
1995-
v2 = verts[:, 1, :] - verts[:, 2, :]
1996-
normals = np.cross(v1, v2)
2007+
normals = self._generate_normals(verts)
19972008
colset = self._shade_colors(color, normals, lightsource)
19982009
else:
19992010
colset = color
@@ -2040,9 +2051,9 @@ def _3d_extend_contour(self, cset, stride=5):
20402051
botverts[0][i2],
20412052
botverts[0][i1]])
20422053

2043-
v1 = np.array(topverts[0][i1]) - np.array(topverts[0][i2])
2044-
v2 = np.array(topverts[0][i1]) - np.array(botverts[0][i1])
2045-
normals.append(np.cross(v1, v2))
2054+
# all polygons have 4 vertices, so vectorize
2055+
polyverts = np.array(polyverts)
2056+
normals = self._generate_normals(polyverts)
20462057

20472058
colors = self._shade_colors(color, normals)
20482059
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.