From 9f66076b2fd1420099ae0c4c95083e284774d1c8 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 2 Jan 2025 11:30:26 -0700 Subject: [PATCH 01/14] Poly3DCollection masking speedups --- lib/mpl_toolkits/mplot3d/art3d.py | 136 ++++++++++++++++++----------- lib/mpl_toolkits/mplot3d/proj3d.py | 21 +++++ 2 files changed, 104 insertions(+), 53 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index deb0ca34302c..f249a5b260dc 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -75,7 +75,7 @@ def get_dir_vector(zdir): def _viewlim_mask(xs, ys, zs, axes): """ - Return original points with points outside the axes view limits masked. + Return the mask of the points outside the axes view limits. Parameters ---------- @@ -86,8 +86,8 @@ def _viewlim_mask(xs, ys, zs, axes): Returns ------- - xs_masked, ys_masked, zs_masked : np.ma.array - The masked points. + mask : np.array + The mask of the points. """ mask = np.logical_or.reduce((xs < axes.xy_viewLim.xmin, xs > axes.xy_viewLim.xmax, @@ -95,10 +95,7 @@ def _viewlim_mask(xs, ys, zs, axes): ys > axes.xy_viewLim.ymax, zs < axes.zz_viewLim.xmin, zs > axes.zz_viewLim.xmax)) - xs_masked = np.ma.array(xs, mask=mask) - ys_masked = np.ma.array(ys, mask=mask) - zs_masked = np.ma.array(zs, mask=mask) - return xs_masked, ys_masked, zs_masked + return mask class Text3D(mtext.Text): @@ -1062,16 +1059,36 @@ def get_vector(self, segments3d): return self._get_vector(segments3d) def _get_vector(self, segments3d): - """Optimize points for projection.""" - if len(segments3d): - xs, ys, zs = np.vstack(segments3d).T - else: # vstack can't stack zero arrays. - xs, ys, zs = [], [], [] - ones = np.ones(len(xs)) - self._vec = np.array([xs, ys, zs, ones]) + """Optimize points for projection. - indices = [0, *np.cumsum([len(segment) for segment in segments3d])] - self._segslices = [*map(slice, indices[:-1], indices[1:])] + 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 arrray, 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 " + + "shape {}".format(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: + 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] + assert self._invalid_vertices is False or \ + self._invalid_vertices.shape == self._faces.shape[:-1] def set_verts(self, verts, closed=True): """ @@ -1133,52 +1150,65 @@ def do_3d_projection(self): self._facecolor3d = self._facecolors if self._edge_is_mapped: self._edgecolor3d = self._edgecolors + + + needs_masking = self._invalid_vertices is not False + 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 + with np.errstate(invalid='ignore', divide='ignore'): + pfaces = proj3d._proj_transform_vectors(self._faces, self.axes.M) + if self._axlim_clip: - xs, ys, zs = _viewlim_mask(*self._vec[0:3], self.axes) - if self._vec.shape[0] == 4: # Will be 3 (xyz) or 4 (xyzw) - w_masked = np.ma.masked_where(zs.mask, self._vec[3]) - vec = np.ma.array([xs, ys, zs, w_masked]) - else: - vec = np.ma.array([xs, ys, zs]) - else: - vec = self._vec - txs, tys, tzs = proj3d._proj_transform_vec(vec, self.axes.M) - xyzlist = [(txs[sl], tys[sl], tzs[sl]) for sl in self._segslices] + 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) != len(xyzlist): - cface = cface.repeat(len(xyzlist), axis=0) - if len(cedge) != len(xyzlist): + 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(len(xyzlist), axis=0) - - if xyzlist: - # sort by depth (furthest drawn first) - z_segments_2d = sorted( - ((self._zsortfunc(zs.data), np.ma.column_stack([xs, ys]), fc, ec, idx) - for idx, ((xs, ys, zs), fc, ec) - in enumerate(zip(xyzlist, cface, cedge))), - key=lambda x: x[0], reverse=True) - - _, segments_2d, self._facecolors2d, self._edgecolors2d, idxs = \ - zip(*z_segments_2d) - else: - segments_2d = [] - self._facecolors2d = np.empty((0, 4)) - self._edgecolors2d = np.empty((0, 4)) - idxs = [] + cedge = cedge.repeat(num_faces, axis=0) + + face_z = self._zsortfunc(pzs, axis=-1) + if needs_masking: + face_z = face_z.data + face_order = np.argsort(face_z, axis=-1)[::-1] + faces_2d = pfaces[face_order, :, :2] if self._codes3d is not None: - codes = [self._codes3d[idx] for idx in idxs] - PolyCollection.set_verts_and_codes(self, segments_2d, codes) + 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: + 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) + + self._facecolors2d = cface[face_order] + if len(self._edgecolor3d) == len(cface): + self._edgecolors2d = cedge[face_order] else: - PolyCollection.set_verts(self, segments_2d, self._closed) - - if len(self._edgecolor3d) != len(cface): self._edgecolors2d = self._edgecolor3d # Return zorder value @@ -1186,11 +1216,11 @@ def do_3d_projection(self): zvec = np.array([[0], [0], [self._sort_zpos], [1]]) ztrans = proj3d._proj_transform_vec(zvec, self.axes.M) return ztrans[2][0] - elif tzs.size > 0: + elif pzs.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(tzs) + return np.min(pzs) else: return np.nan diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index 923bd32c9ce0..eb59af3bd69e 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -144,6 +144,27 @@ def _proj_transform_vec(vec, M): return txs, tys, tzs +def _proj_transform_vectors(vecs, M): + """Vectorized version of ``_proj_transform_vec``. + Parameters + ---------- + vecs : ... x 3 np.ndarray + Input vectors + M : 4 x 4 np.ndarray + Projection matrix + """ + vecs_shape = vecs.shape + vecs = vecs.reshape(-1, 3).T + + vecs_pad = np.empty((vecs.shape[0] + 1,) + vecs.shape[1:]) + vecs_pad[:-1] = vecs + vecs_pad[-1] = 1 + product = np.dot(M, vecs_pad) + tvecs = product[:3] / product[3] + + return tvecs.T.reshape(vecs_shape) + + def _proj_transform_vec_clip(vec, M, focal_length): vecw = np.dot(M, vec.data) w = vecw[3] From 94c6ca7058fafdb356e125a7444e44a055dea0f4 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 2 Jan 2025 11:54:25 -0700 Subject: [PATCH 02/14] 3d text masking --- lib/mpl_toolkits/mplot3d/art3d.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index f249a5b260dc..cd8f2541bf61 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -179,14 +179,13 @@ def set_3d_properties(self, z=0, zdir='z', axlim_clip=False): @artist.allow_rasterization def draw(self, renderer): if self._axlim_clip: - xs, ys, zs = _viewlim_mask(self._x, self._y, self._z, self.axes) - position3d = np.ma.row_stack((xs, ys, zs)).ravel().filled(np.nan) + mask = _viewlim_mask(self._x, self._y, self._z, self.axes) + pos3d = np.ma.array((self._x, self._y, self._z), + dtype=float, mask=mask).filled(np.nan) else: - xs, ys, zs = self._x, self._y, self._z - position3d = np.asanyarray([xs, ys, zs]) + pos3d = np.asanyarray([self._x, self._y, self._z]) - proj = proj3d._proj_trans_points( - [position3d, position3d + self._dir_vec], self.axes.M) + 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)) From 606e76f23f2d9185be07d5a67916eb819e47cdc6 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 2 Jan 2025 12:22:27 -0700 Subject: [PATCH 03/14] Single 3d object masking --- lib/mpl_toolkits/mplot3d/art3d.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index cd8f2541bf61..75e84d0b5082 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -309,7 +309,9 @@ def get_data_3d(self): @artist.allow_rasterization def draw(self, renderer): if self._axlim_clip: - xs3d, ys3d, zs3d = _viewlim_mask(*self._verts3d, self.axes) + mask = _viewlim_mask(*self._verts3d, self.axes) + xs3d, ys3d, zs3d = np.ma.array(self._verts3d, + dtype=float, mask=mask).filled(np.nan) else: xs3d, ys3d, zs3d = self._verts3d xs, ys, zs, tis = proj3d._proj_transform_clip(xs3d, ys3d, zs3d, @@ -527,7 +529,9 @@ def get_path(self): def do_3d_projection(self): s = self._segment3d if self._axlim_clip: - xs, ys, zs = _viewlim_mask(*zip(*s), self.axes) + mask = _viewlim_mask(*zip(*s), self.axes) + xs, ys, zs = np.ma.array(zip(*s), + dtype=float, mask=mask).filled(np.nan) else: xs, ys, zs = zip(*s) vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs, @@ -583,7 +587,9 @@ def set_3d_properties(self, path, zs=0, zdir='z', axlim_clip=False): def do_3d_projection(self): s = self._segment3d if self._axlim_clip: - xs, ys, zs = _viewlim_mask(*zip(*s), self.axes) + mask = _viewlim_mask(*zip(*s), self.axes) + xs, ys, zs = np.ma.array(zip(*s), + dtype=float, mask=mask).filled(np.nan) else: xs, ys, zs = zip(*s) vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs, From b48cdcfb015b90d6a01999a9b51d4cef77a099f1 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 2 Jan 2025 14:24:24 -0700 Subject: [PATCH 04/14] Line3D collection and Path3DCollection masking --- lib/mpl_toolkits/mplot3d/art3d.py | 41 ++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 75e84d0b5082..ed916ef443d5 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -448,22 +448,27 @@ def do_3d_projection(self): """ Project the points according to renderer matrix. """ - segments = self._segments3d + segments = np.asanyarray(self._segments3d) + + mask = False + if np.ma.isMA(segments): + mask = segments.mask + if self._axlim_clip: - all_points = np.ma.vstack(segments) - masked_points = np.ma.column_stack([*_viewlim_mask(*all_points.T, - self.axes)]) - segment_lengths = [np.shape(segment)[0] for segment in segments] - segments = np.split(masked_points, np.cumsum(segment_lengths[:-1])) - xyslist = [proj3d._proj_trans_points(points, self.axes.M) - for points in segments] - segments_2d = [np.ma.column_stack([xs, ys]) for xs, ys, zs in xyslist] + viewlim_mask = _viewlim_mask(segments[..., 0], + segments[..., 1], + segments[..., 2], + self.axes) + if np.any(viewlim_mask): + # broadcast mask to 3D + viewlim_mask = viewlim_mask[..., np.newaxis].repeat(3, axis=-1) + 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 - minz = 1e9 - for xs, ys, zs in xyslist: - minz = min(minz, min(zs)) + minz = min(xyzs[..., 2].min(), 1e9) return minz @@ -853,11 +858,17 @@ def set_depthshade(self, depthshade): 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: - xs, ys, zs = _viewlim_mask(*self._offsets3d, self.axes) + 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: - xs, ys, zs = self._offsets3d - vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs, + 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 From 0bf8b7b6cb21e29a046bfcc04143bb277ce9a055 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 2 Jan 2025 17:33:50 -0700 Subject: [PATCH 05/14] Patch3DCollection masking --- lib/mpl_toolkits/mplot3d/art3d.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index ed916ef443d5..7ed243a658fe 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -708,14 +708,18 @@ def set_3d_properties(self, zs, zdir, axlim_clip=False): def do_3d_projection(self): if self._axlim_clip: - xs, ys, zs = _viewlim_mask(*self._offsets3d, self.axes) + 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 - super().set_offsets(np.ma.column_stack([vxs, vys])) + 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) From fdcc0d86b8b2c1e1b2c57357c97434ba837b02ee Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 2 Jan 2025 17:34:13 -0700 Subject: [PATCH 06/14] simplify projection --- lib/mpl_toolkits/mplot3d/proj3d.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index eb59af3bd69e..440baabfb114 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -133,15 +133,10 @@ def _ortho_transformation(zfront, zback): def _proj_transform_vec(vec, M): vecw = np.dot(M, vec.data) - w = vecw[3] - txs, tys, tzs = vecw[0]/w, vecw[1]/w, vecw[2]/w - if np.ma.isMA(vec[0]): # we check each to protect for scalars - txs = np.ma.array(txs, mask=vec[0].mask) - if np.ma.isMA(vec[1]): - tys = np.ma.array(tys, mask=vec[1].mask) - if np.ma.isMA(vec[2]): - tzs = np.ma.array(tzs, mask=vec[2].mask) - return txs, tys, tzs + ts = vecw[0:3]/vecw[3] + if np.ma.isMA(vec): + ts = np.ma.array(ts, mask=vec.mask) + return ts[0], ts[1], ts[2] def _proj_transform_vectors(vecs, M): From e0ff2087afdfe329f362dd5d5353ff42fd6acf0c Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 2 Jan 2025 17:44:52 -0700 Subject: [PATCH 07/14] Fix Poly3Dcollection autoscaling --- lib/mpl_toolkits/mplot3d/axes3d.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 45d38a346a69..8ec63aba3349 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2892,7 +2892,9 @@ def add_collection3d(self, col, zs=0, zdir='z', autolim=True, *, self.auto_scale_xyz(*np.array(col._segments3d).transpose(), had_data=had_data) elif isinstance(col, art3d.Poly3DCollection): - self.auto_scale_xyz(*col._vec[:-1], had_data=had_data) + self.auto_scale_xyz(col._faces[..., 0], + col._faces[..., 1], + col._faces[..., 2], had_data=had_data) elif isinstance(col, art3d.Patch3DCollection): pass # FIXME: Implement auto-scaling function for Patch3DCollection From 80a5a95efacb235ed600319d968cfc5e36753b3a Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 2 Jan 2025 17:55:15 -0700 Subject: [PATCH 08/14] Collection3D masking --- lib/mpl_toolkits/mplot3d/art3d.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 7ed243a658fe..d7c7c822f4e2 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -402,7 +402,8 @@ def do_3d_projection(self): """Project the points according to renderer matrix.""" vs_list = [vs for vs, _ in self._3dverts_codes] if self._axlim_clip: - vs_list = [np.ma.row_stack(_viewlim_mask(*vs.T, self.axes)).T + 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) From d18c52bc922008dc31f5b9f04f1f8c2016da7d51 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 2 Jan 2025 17:55:59 -0700 Subject: [PATCH 09/14] fix Line3D masking --- lib/mpl_toolkits/mplot3d/art3d.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index d7c7c822f4e2..dfa025a83f4f 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -309,7 +309,10 @@ def get_data_3d(self): @artist.allow_rasterization def draw(self, renderer): if self._axlim_clip: - mask = _viewlim_mask(*self._verts3d, self.axes) + 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) else: From 17823098f9e47ce06b58b4c803620405ef37dcfa Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 2 Jan 2025 20:05:23 -0700 Subject: [PATCH 10/14] Fix tests linting --- lib/mpl_toolkits/mplot3d/art3d.py | 40 ++++++++++++------ lib/mpl_toolkits/mplot3d/proj3d.py | 4 +- .../test_axes3d/surface3d_masked.png | Bin 41837 -> 41724 bytes lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 2 +- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index dfa025a83f4f..8133b1a330ef 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -467,12 +467,16 @@ def do_3d_projection(self): # broadcast mask to 3D viewlim_mask = viewlim_mask[..., np.newaxis].repeat(3, axis=-1) mask = mask | viewlim_mask - xyzs = np.ma.array(proj3d._proj_transform_vectors(segments, self.axes.M), mask=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 - minz = min(xyzs[..., 2].min(), 1e9) + if len(xyzs) > 0: + minz = min(xyzs[..., 2].min(), 1e9) + else: + minz = np.nan return minz @@ -872,7 +876,8 @@ def do_3d_projection(self): 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)) + mask = np.broadcast_to(mask, + (len(self._offsets3d), *self._offsets3d[0].shape)) xyzs = np.ma.array(self._offsets3d, mask=mask) else: xyzs = self._offsets3d @@ -1083,13 +1088,14 @@ def get_vector(self, segments3d): return self._get_vector(segments3d) def _get_vector(self, segments3d): - """Optimize points for projection. + """ + 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 arrray, then it should + 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): @@ -1175,8 +1181,7 @@ def do_3d_projection(self): if self._edge_is_mapped: self._edgecolor3d = self._edgecolors - - needs_masking = self._invalid_vertices is not False + needs_masking = np.any(self._invalid_vertices) num_faces = len(self._faces) mask = self._invalid_vertices @@ -1207,13 +1212,19 @@ def do_3d_projection(self): else: cedge = cedge.repeat(num_faces, axis=0) - face_z = self._zsortfunc(pzs, axis=-1) + 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] - faces_2d = pfaces[face_order, :, :2] - if self._codes3d is not None: + 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 @@ -1221,7 +1232,7 @@ def do_3d_projection(self): codes = [self._codes3d[idx] for idx in face_order] PolyCollection.set_verts_and_codes(self, faces_2d, codes) else: - if needs_masking: + if needs_masking and len(faces_2d) > 0: invalid_vertices_2d = np.broadcast_to( mask[face_order, :, None], faces_2d.shape) @@ -1229,8 +1240,11 @@ def do_3d_projection(self): faces_2d, mask=invalid_vertices_2d) PolyCollection.set_verts(self, faces_2d, self._closed) - self._facecolors2d = cface[face_order] - if len(self._edgecolor3d) == len(cface): + 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 diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index 440baabfb114..34a03969c961 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -140,7 +140,9 @@ def _proj_transform_vec(vec, M): def _proj_transform_vectors(vecs, M): - """Vectorized version of ``_proj_transform_vec``. + """ + Vectorized version of ``_proj_transform_vec``. + Parameters ---------- vecs : ... x 3 np.ndarray diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_masked.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_masked.png index df893f9c843f810cb0142b8e95e568c4ab2b4552..9e5af36ffbfc9d8c1817120be703b6ee9ef8affb 100644 GIT binary patch literal 41724 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 diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 0852a797c946..7067576e358a 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -996,7 +996,7 @@ def test_poly3dcollection_verts_validation(): art3d.Poly3DCollection(poly) # should be Poly3DCollection([poly]) poly = np.array(poly, dtype=float) - with pytest.raises(ValueError, match=r'list of \(N, 3\) array-like'): + with pytest.raises(ValueError, match=r'MxNx3 array'): art3d.Poly3DCollection(poly) # should be Poly3DCollection([poly]) From a700fe9df80d65f64fe6e8ee9a7b962324345573 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Sun, 5 Jan 2025 17:29:48 -0700 Subject: [PATCH 11/14] 3d speedups whats new --- doc/users/next_whats_new/3d_speedups.rst | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 doc/users/next_whats_new/3d_speedups.rst diff --git a/doc/users/next_whats_new/3d_speedups.rst b/doc/users/next_whats_new/3d_speedups.rst new file mode 100644 index 000000000000..70e80bfbdccb --- /dev/null +++ b/doc/users/next_whats_new/3d_speedups.rst @@ -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. From 52a06a660a83407d1841a89c6eb74844a19b8510 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Tue, 7 Jan 2025 11:49:00 -0700 Subject: [PATCH 12/14] Code review updates --- lib/mpl_toolkits/mplot3d/art3d.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 8133b1a330ef..208d9286015e 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -178,12 +178,10 @@ def set_3d_properties(self, z=0, zdir='z', axlim_clip=False): @artist.allow_rasterization def draw(self, renderer): + pos3d = np.array([self._x, self._y, self._z], dtype=float) 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), - dtype=float, mask=mask).filled(np.nan) - else: - pos3d = np.asanyarray([self._x, self._y, self._z]) + pos3d[mask] = np.nan proj = proj3d._proj_trans_points([pos3d, pos3d + self._dir_vec], self.axes.M) dx = proj[0][1] - proj[0][0] @@ -465,7 +463,8 @@ def do_3d_projection(self): self.axes) if np.any(viewlim_mask): # broadcast mask to 3D - viewlim_mask = viewlim_mask[..., np.newaxis].repeat(3, axis=-1) + 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) @@ -1100,8 +1099,8 @@ def _get_vector(self, segments3d): """ if isinstance(segments3d, np.ndarray): if segments3d.ndim != 3 or segments3d.shape[-1] != 3: - raise ValueError("segments3d must be a MxNx3 array, but got " + - "shape {}".format(segments3d.shape)) + 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) @@ -1109,6 +1108,8 @@ def _get_vector(self, segments3d): self._faces = segments3d self._invalid_vertices = False else: + # 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) @@ -1117,8 +1118,6 @@ def _get_vector(self, segments3d): segments[i, :len(face)] = face self._faces = segments self._invalid_vertices = np.arange(max_verts) >= num_verts[:, None] - assert self._invalid_vertices is False or \ - self._invalid_vertices.shape == self._faces.shape[:-1] def set_verts(self, verts, closed=True): """ @@ -1184,11 +1183,7 @@ def do_3d_projection(self): 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 - with np.errstate(invalid='ignore', divide='ignore'): - pfaces = proj3d._proj_transform_vectors(self._faces, self.axes.M) + pfaces = proj3d._proj_transform_vectors(self._faces, self.axes.M) if self._axlim_clip: viewlim_mask = _viewlim_mask(self._faces[..., 0], self._faces[..., 1], From 68b8a6c3b5b8ecb2594fa4428d571b24fd86ac5f Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Wed, 8 Jan 2025 09:24:04 -0700 Subject: [PATCH 13/14] Ignore div0 errors on masked vertices --- lib/mpl_toolkits/mplot3d/art3d.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 208d9286015e..44b27ee2aa46 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -1183,7 +1183,11 @@ def do_3d_projection(self): needs_masking = np.any(self._invalid_vertices) num_faces = len(self._faces) mask = self._invalid_vertices - pfaces = proj3d._proj_transform_vectors(self._faces, self.axes.M) + + # Some faces might contain masked vertices, so we want to ignore any + # errors that those might cause + 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], From 4fc37454625b58a1820c00b7c70e32ee7f2f7099 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Mon, 27 Jan 2025 14:40:59 -0700 Subject: [PATCH 14/14] code review updates --- lib/mpl_toolkits/mplot3d/art3d.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 44b27ee2aa46..88c2ae625c00 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -87,7 +87,7 @@ def _viewlim_mask(xs, ys, zs, axes): Returns ------- mask : np.array - The mask of the points. + The mask of the points as a bool array. """ mask = np.logical_or.reduce((xs < axes.xy_viewLim.xmin, xs > axes.xy_viewLim.xmax, @@ -178,10 +178,12 @@ def set_3d_properties(self, z=0, zdir='z', axlim_clip=False): @artist.allow_rasterization def draw(self, renderer): - pos3d = np.array([self._x, self._y, self._z], dtype=float) if self._axlim_clip: mask = _viewlim_mask(self._x, self._y, self._z, self.axes) - pos3d[mask] = np.nan + 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]