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] pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy