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 dc75f7d

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 82a4b24 commit dc75f7d
Copy full SHA for dc75f7d

File tree

Expand file treeCollapse file tree

1 file changed

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

1 file changed

+49
-33
lines changed

‎lib/mpl_toolkits/mplot3d/axes3d.py

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

1672-
def get_normals(polygons):
1673-
"""
1674-
Takes a list of polygons and return an array of their normals
1675-
"""
1676-
v1 = np.empty((len(polygons), 3))
1677-
v2 = np.empty((len(polygons), 3))
1678-
for poly_i, ps in enumerate(polygons):
1679-
# pick three points around the polygon at which to find the
1680-
# normal doesn't vectorize because polygons is jagged
1681-
i1, i2, i3 = 0, len(ps)//3, 2*len(ps)//3
1682-
v1[poly_i, :] = ps[i1, :] - ps[i2, :]
1683-
v2[poly_i, :] = ps[i2, :] - ps[i3, :]
1684-
return np.cross(v1, v2)
1685-
16861672
# note that the striding causes some polygons to have more coordinates
16871673
# than others
16881674
polyc = art3d.Poly3DCollection(polys, *args, **kwargs)
16891675

16901676
if fcolors is not None:
16911677
if shade:
1692-
colset = self._shade_colors(colset, get_normals(polys), lightsource)
1678+
colset = self._shade_colors(
1679+
colset, self._generate_normals(polys), lightsource)
16931680
polyc.set_facecolors(colset)
16941681
polyc.set_edgecolors(colset)
16951682
elif cmap:
@@ -1702,7 +1689,8 @@ def get_normals(polygons):
17021689
polyc.set_norm(norm)
17031690
else:
17041691
if shade:
1705-
colset = self._shade_colors(color, get_normals(polys), lightsource)
1692+
colset = self._shade_colors(
1693+
color, self._generate_normals(polys), lightsource)
17061694
else:
17071695
colset = color
17081696
polyc.set_facecolors(colset)
@@ -1713,18 +1701,45 @@ def get_normals(polygons):
17131701
return polyc
17141702

17151703
def _generate_normals(self, polygons):
1716-
'''
1717-
Generate normals for polygons by using the first three points.
1718-
This normal of course might not make sense for polygons with
1719-
more than three points not lying in a plane.
1720-
'''
1704+
"""
1705+
Takes a list of polygons and return an array of their normals.
1706+
1707+
Uses three points equally spaced around the polygon.
1708+
This normal of course might not make sense for polygons with more than
1709+
three points not lying in a plane, but it's a plausible and fast
1710+
approximation.
1711+
1712+
Parameters
1713+
----------
1714+
polygons: list of (M_i, 3) array_like, or (..., M, 3) array_like
1715+
A sequence of polygons to compute normals for, which can have
1716+
varying numbers of vertices. If the polygons all have the same
1717+
number of vertices and array is passed, then the operation will
1718+
be vectorized.
17211719
1722-
normals = []
1723-
for verts in polygons:
1724-
v1 = np.array(verts[0]) - np.array(verts[1])
1725-
v2 = np.array(verts[2]) - np.array(verts[0])
1726-
normals.append(np.cross(v1, v2))
1727-
return normals
1720+
Returns
1721+
-------
1722+
normals: (..., 3) array_like
1723+
A normal vector estimated for the polygon.
1724+
1725+
"""
1726+
if isinstance(polygons, np.ndarray):
1727+
# optimization: polygons all have the same number of points, so can
1728+
# vectorize
1729+
n = polygons.shape[-2]
1730+
i1, i2, i3 = 0, n//3, 2*n//3
1731+
v1 = polygons[..., i1, :] - polygons[..., i2, :]
1732+
v2 = polygons[..., i2, :] - polygons[..., i3, :]
1733+
else:
1734+
# The subtraction doesn't vectorize because polygons is jagged.
1735+
v1 = np.empty((len(polygons), 3))
1736+
v2 = np.empty((len(polygons), 3))
1737+
for poly_i, ps in enumerate(polygons):
1738+
n = len(ps)
1739+
i1, i2, i3 = 0, n//3, 2*n//3
1740+
v1[poly_i, :] = ps[i1, :] - ps[i2, :]
1741+
v2[poly_i, :] = ps[i2, :] - ps[i3, :]
1742+
return np.cross(v1, v2)
17281743

17291744
def _shade_colors(self, color, normals, lightsource=None):
17301745
'''
@@ -1971,9 +1986,7 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None,
19711986
polyc.set_norm(norm)
19721987
else:
19731988
if shade:
1974-
v1 = verts[:, 0, :] - verts[:, 1, :]
1975-
v2 = verts[:, 1, :] - verts[:, 2, :]
1976-
normals = np.cross(v1, v2)
1989+
normals = self._generate_normals(verts)
19771990
colset = self._shade_colors(color, normals, lightsource)
19781991
else:
19791992
colset = color
@@ -2020,9 +2033,9 @@ def _3d_extend_contour(self, cset, stride=5):
20202033
botverts[0][i2],
20212034
botverts[0][i1]])
20222035

2023-
v1 = np.array(topverts[0][i1]) - np.array(topverts[0][i2])
2024-
v2 = np.array(topverts[0][i1]) - np.array(botverts[0][i1])
2025-
normals.append(np.cross(v1, v2))
2036+
# all polygons have 4 vertices, so vectorize
2037+
polyverts = np.array(polyverts)
2038+
normals = self._generate_normals(polyverts)
20262039

20272040
colors = self._shade_colors(color, normals)
20282041
colors2 = self._shade_colors(color, normals)
@@ -2451,6 +2464,9 @@ def bar3d(self, x, y, z, dx, dy, dz, color=None,
24512464
(xi + dxi, yi + dyi, zi + dzi), (xi + dxi, yi, zi + dzi)),
24522465
])
24532466

2467+
# can vectorize, all polygons have four vertices
2468+
polys = np.array(polys)
2469+
24542470
facecolors = []
24552471
if color is None:
24562472
color = [self._get_patches_for_fill.get_next_color()]

0 commit comments

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