Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
3D plotting performance improvements#29397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
9f66076
94c6ca7
606e76f
b48cdcf
0bf8b7b
fdcc0d8
e0ff208
80a5a95
d18c52b
1782309
a700fe9
52a06a6
68b8a6c
4fc3745
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
3D performance improvements | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
Draw time for 3D plots has been improved, especially for surface and wireframe | ||
plots. Users should see up to a 10x speedup in some cases. This should make | ||
interacting with 3D plots much more responsive. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -75,7 +75,7 @@ | ||
def _viewlim_mask(xs, ys, zs, axes): | ||
scottshambaugh marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
""" | ||
Returnthe mask of thepoints outside the axes view limits. | ||
Parameters | ||
---------- | ||
@@ -86,19 +86,16 @@ | ||
Returns | ||
------- | ||
mask: np.array | ||
Themask of thepoints as a bool array. | ||
""" | ||
mask = np.logical_or.reduce((xs < axes.xy_viewLim.xmin, | ||
xs > axes.xy_viewLim.xmax, | ||
ys < axes.xy_viewLim.ymin, | ||
ys > axes.xy_viewLim.ymax, | ||
zs < axes.zz_viewLim.xmin, | ||
zs > axes.zz_viewLim.xmax)) | ||
return mask | ||
class Text3D(mtext.Text): | ||
@@ -182,14 +179,13 @@ | ||
@artist.allow_rasterization | ||
def draw(self, renderer): | ||
if self._axlim_clip: | ||
mask = _viewlim_mask(self._x, self._y, self._z, self.axes) | ||
pos3d = np.ma.array([self._x, self._y, self._z], | ||
mask=mask, dtype=float).filled(np.nan) | ||
else: | ||
pos3d = np.array([self._x, self._y, self._z], dtype=float) | ||
proj = proj3d._proj_trans_points([pos3d, pos3d + self._dir_vec], self.axes.M) | ||
dx = proj[0][1] - proj[0][0] | ||
dy = proj[1][1] - proj[1][0] | ||
angle = math.degrees(math.atan2(dy, dx)) | ||
@@ -313,7 +309,12 @@ | ||
@artist.allow_rasterization | ||
def draw(self, renderer): | ||
if self._axlim_clip: | ||
mask = np.broadcast_to( | ||
_viewlim_mask(*self._verts3d, self.axes), | ||
(len(self._verts3d), *self._verts3d[0].shape) | ||
) | ||
xs3d, ys3d, zs3d = np.ma.array(self._verts3d, | ||
dtype=float, mask=mask).filled(np.nan) | ||
scottshambaugh marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
else: | ||
xs3d, ys3d, zs3d = self._verts3d | ||
xs, ys, zs, tis = proj3d._proj_transform_clip(xs3d, ys3d, zs3d, | ||
@@ -404,7 +405,8 @@ | ||
"""Project the points according to renderer matrix.""" | ||
vs_list = [vs for vs, _ in self._3dverts_codes] | ||
if self._axlim_clip: | ||
vs_list = [np.ma.array(vs, mask=np.broadcast_to( | ||
_viewlim_mask(*vs.T, self.axes), vs.shape)) | ||
for vs in vs_list] | ||
xyzs_list = [proj3d.proj_transform(*vs.T, self.axes.M) for vs in vs_list] | ||
self._paths = [mpath.Path(np.ma.column_stack([xs, ys]), cs) | ||
@@ -450,22 +452,32 @@ | ||
""" | ||
Project the points according to renderer matrix. | ||
""" | ||
segments = np.asanyarray(self._segments3d) | ||
mask = False | ||
if np.ma.isMA(segments): | ||
mask = segments.mask | ||
if self._axlim_clip: | ||
viewlim_mask = _viewlim_mask(segments[..., 0], | ||
segments[..., 1], | ||
segments[..., 2], | ||
self.axes) | ||
if np.any(viewlim_mask): | ||
# broadcast mask to 3D | ||
viewlim_mask = np.broadcast_to(viewlim_mask[..., np.newaxis], | ||
(*viewlim_mask.shape, 3)) | ||
mask = mask | viewlim_mask | ||
xyzs = np.ma.array(proj3d._proj_transform_vectors(segments, self.axes.M), | ||
mask=mask) | ||
segments_2d = xyzs[..., 0:2] | ||
LineCollection.set_segments(self, segments_2d) | ||
# FIXME | ||
if len(xyzs) > 0: | ||
minz = min(xyzs[..., 2].min(), 1e9) | ||
else: | ||
minz = np.nan | ||
return minz | ||
@@ -531,7 +543,9 @@ | ||
def do_3d_projection(self): | ||
s = self._segment3d | ||
if self._axlim_clip: | ||
mask = _viewlim_mask(*zip(*s), self.axes) | ||
xs, ys, zs = np.ma.array(zip(*s), | ||
scottshambaugh marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
dtype=float, mask=mask).filled(np.nan) | ||
else: | ||
xs, ys, zs = zip(*s) | ||
vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs, | ||
@@ -587,7 +601,9 @@ | ||
def do_3d_projection(self): | ||
s = self._segment3d | ||
if self._axlim_clip: | ||
mask = _viewlim_mask(*zip(*s), self.axes) | ||
xs, ys, zs = np.ma.array(zip(*s), | ||
scottshambaugh marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
dtype=float, mask=mask).filled(np.nan) | ||
else: | ||
xs, ys, zs = zip(*s) | ||
vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs, | ||
@@ -701,14 +717,18 @@ | ||
def do_3d_projection(self): | ||
if self._axlim_clip: | ||
mask = _viewlim_mask(*self._offsets3d, self.axes) | ||
xs, ys, zs = np.ma.array(self._offsets3d, mask=mask) | ||
else: | ||
xs, ys, zs = self._offsets3d | ||
vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs, | ||
self.axes.M, | ||
self.axes._focal_length) | ||
self._vzs = vzs | ||
if np.ma.isMA(vxs): | ||
super().set_offsets(np.ma.column_stack([vxs, vys])) | ||
else: | ||
super().set_offsets(np.column_stack([vxs, vys])) | ||
if vzs.size > 0: | ||
return min(vzs) | ||
@@ -851,11 +871,18 @@ | ||
self.stale = True | ||
def do_3d_projection(self): | ||
mask = False | ||
for xyz in self._offsets3d: | ||
if np.ma.isMA(xyz): | ||
mask = mask | xyz.mask | ||
if self._axlim_clip: | ||
mask = mask | _viewlim_mask(*self._offsets3d, self.axes) | ||
mask = np.broadcast_to(mask, | ||
(len(self._offsets3d), *self._offsets3d[0].shape)) | ||
xyzs = np.ma.array(self._offsets3d, mask=mask) | ||
else: | ||
xyzs = self._offsets3d | ||
vxs, vys, vzs, vis = proj3d._proj_transform_clip(*xyzs, | ||
self.axes.M, | ||
self.axes._focal_length) | ||
# Sort the points based on z coordinates | ||
@@ -1062,16 +1089,37 @@ | ||
return self._get_vector(segments3d) | ||
def _get_vector(self, segments3d): | ||
""" | ||
Optimize points for projection. | ||
Parameters | ||
---------- | ||
segments3d : NumPy array or list of NumPy arrays | ||
List of vertices of the boundary of every segment. If all paths are | ||
of equal length and this argument is a NumPy array, then it should | ||
be of shape (num_faces, num_vertices, 3). | ||
""" | ||
if isinstance(segments3d, np.ndarray): | ||
if segments3d.ndim != 3 or segments3d.shape[-1] != 3: | ||
raise ValueError("segments3d must be a MxNx3 array, but got " | ||
f"shape {segments3d.shape}") | ||
if isinstance(segments3d, np.ma.MaskedArray): | ||
self._faces = segments3d.data | ||
self._invalid_vertices = segments3d.mask.any(axis=-1) | ||
else: | ||
self._faces = segments3d | ||
self._invalid_vertices = False | ||
else: | ||
scottshambaugh marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
# Turn the potentially ragged list into a numpy array for later speedups | ||
# If it is ragged, set the unused vertices per face as invalid | ||
num_faces = len(segments3d) | ||
num_verts = np.fromiter(map(len, segments3d), dtype=np.intp) | ||
max_verts = num_verts.max(initial=0) | ||
segments = np.empty((num_faces, max_verts, 3)) | ||
for i, face in enumerate(segments3d): | ||
segments[i, :len(face)] = face | ||
self._faces = segments | ||
self._invalid_vertices = np.arange(max_verts) >= num_verts[:, None] | ||
def set_verts(self, verts, closed=True): | ||
""" | ||
@@ -1133,64 +1181,85 @@ | ||
self._facecolor3d = self._facecolors | ||
if self._edge_is_mapped: | ||
self._edgecolor3d = self._edgecolors | ||
needs_masking = np.any(self._invalid_vertices) | ||
num_faces = len(self._faces) | ||
mask = self._invalid_vertices | ||
# Some faces might contain masked vertices, so we want to ignore any | ||
# errors that those might cause | ||
Comment on lines +1189 to +1190 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Should these errors be handled within the ContributorAuthor
| ||
with np.errstate(invalid='ignore', divide='ignore'): | ||
pfaces = proj3d._proj_transform_vectors(self._faces, self.axes.M) | ||
if self._axlim_clip: | ||
viewlim_mask = _viewlim_mask(self._faces[..., 0], self._faces[..., 1], | ||
self._faces[..., 2], self.axes) | ||
if np.any(viewlim_mask): | ||
needs_masking = True | ||
mask = mask | viewlim_mask | ||
pzs = pfaces[..., 2] | ||
if needs_masking: | ||
pzs = np.ma.MaskedArray(pzs, mask=mask) | ||
# This extra fuss is to re-order face / edge colors | ||
cface = self._facecolor3d | ||
cedge = self._edgecolor3d | ||
if len(cface) !=num_faces: | ||
cface = cface.repeat(num_faces, axis=0) | ||
if len(cedge) !=num_faces: | ||
if len(cedge) == 0: | ||
cedge = cface | ||
else: | ||
cedge = cedge.repeat(num_faces, axis=0) | ||
if len(pzs) > 0: | ||
face_z = self._zsortfunc(pzs, axis=-1) | ||
else: | ||
face_z = pzs | ||
if needs_masking: | ||
face_z = face_z.data | ||
face_order = np.argsort(face_z, axis=-1)[::-1] | ||
if len(pfaces) > 0: | ||
faces_2d = pfaces[face_order, :, :2] | ||
else: | ||
faces_2d = pfaces | ||
if self._codes3d is not None and len(self._codes3d) > 0: | ||
if needs_masking: | ||
segment_mask = ~mask[face_order, :] | ||
faces_2d = [face[mask, :] for face, mask | ||
in zip(faces_2d, segment_mask)] | ||
codes = [self._codes3d[idx] for idx in face_order] | ||
PolyCollection.set_verts_and_codes(self, faces_2d, codes) | ||
else: | ||
if needs_masking and len(faces_2d) > 0: | ||
invalid_vertices_2d = np.broadcast_to( | ||
mask[face_order, :, None], | ||
faces_2d.shape) | ||
faces_2d = np.ma.MaskedArray( | ||
faces_2d, mask=invalid_vertices_2d) | ||
PolyCollection.set_verts(self, faces_2d, self._closed) | ||
if len(cface) > 0: | ||
self._facecolors2d = cface[face_order] | ||
else: | ||
self._facecolors2d = cface | ||
if len(self._edgecolor3d) == len(cface) and len(cedge) > 0: | ||
self._edgecolors2d = cedge[face_order] | ||
else: | ||
self._edgecolors2d = self._edgecolor3d | ||
# Return zorder value | ||
if self._sort_zpos is not None: | ||
zvec = np.array([[0], [0], [self._sort_zpos], [1]]) | ||
ztrans = proj3d._proj_transform_vec(zvec, self.axes.M) | ||
return ztrans[2][0] | ||
elifpzs.size > 0: | ||
# FIXME: Some results still don't look quite right. | ||
# In particular, examine contourf3d_demo2.py | ||
# with az = -54 and elev = -45. | ||
return np.min(pzs) | ||
else: | ||
return np.nan | ||
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.