From bcca61026377ac1bede90ff8f65cf33572f811bb Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Wed, 1 Jan 2025 23:33:26 -0700 Subject: [PATCH 1/3] Faster Path creation --- lib/matplotlib/collections.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 2e72926cbe5d..a4946178e5a5 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -1246,11 +1246,12 @@ def set_verts(self, verts, closed=True): verts_pad = np.concatenate((verts, verts[:, :1]), axis=1) # Creating the codes once is much faster than having Path do it # separately each time by passing closed=True. - codes = np.empty(verts_pad.shape[1], dtype=mpath.Path.code_type) - codes[:] = mpath.Path.LINETO - codes[0] = mpath.Path.MOVETO - codes[-1] = mpath.Path.CLOSEPOLY - self._paths = [mpath.Path(xy, codes) for xy in verts_pad] + example_path = mpath.Path(verts_pad[0], closed=True) + # Looking up the values once speeds up the iteration a bit + _make_path = mpath.Path._fast_from_codes_and_verts + codes = example_path.codes + self._paths = [_make_path(xy, codes, internals_from=example_path) + for xy in verts_pad] return self._paths = [] From 240c041792e938c726af22b64f01d69eeec48a4c Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Wed, 1 Jan 2025 21:40:14 -0700 Subject: [PATCH 2/3] 3d calculation speedups _to_unmasked_float_array updates Linting and tweaks mean zsort small perf gain linting Fix tests Linting and tweaks mean zsort small perf gain linting Fix tests Update lib/matplotlib/collections.py Co-authored-by: Oscar Gustafsson remove Path __slots__ Code review suggestions --- lib/matplotlib/cbook.py | 4 ++-- lib/matplotlib/collections.py | 13 ++++++------- lib/matplotlib/path.py | 1 - lib/matplotlib/tests/test_transforms.py | 2 +- lib/mpl_toolkits/mplot3d/art3d.py | 2 +- lib/mpl_toolkits/mplot3d/proj3d.py | 3 +-- 6 files changed, 11 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index b18b935c8ead..9b26842a766c 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1340,9 +1340,9 @@ def _to_unmasked_float_array(x): values are converted to nans. """ if hasattr(x, 'mask'): - return np.ma.asarray(x, float).filled(np.nan) + return np.ma.asanyarray(x, float).filled(np.nan) else: - return np.asarray(x, float) + return np.asanyarray(x, float) def _check_1d(x): diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index a4946178e5a5..a3f245bbc2c8 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -1242,15 +1242,14 @@ def set_verts(self, verts, closed=True): return # Fast path for arrays - if isinstance(verts, np.ndarray) and len(verts.shape) == 3: + if isinstance(verts, np.ndarray) and len(verts.shape) == 3 and verts.size: verts_pad = np.concatenate((verts, verts[:, :1]), axis=1) - # Creating the codes once is much faster than having Path do it - # separately each time by passing closed=True. - example_path = mpath.Path(verts_pad[0], closed=True) - # Looking up the values once speeds up the iteration a bit + # It's faster to create the codes and internal flags once in a + # template path and reuse them. + template_path = mpath.Path(verts_pad[0], closed=True) + codes = template_path.codes _make_path = mpath.Path._fast_from_codes_and_verts - codes = example_path.codes - self._paths = [_make_path(xy, codes, internals_from=example_path) + self._paths = [_make_path(xy, codes, internals_from=template_path) for xy in verts_pad] return diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index 5f5a0f3de423..4784410f0f5e 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -76,7 +76,6 @@ class Path: made up front in the constructor that will not change when the data changes. """ - code_type = np.uint8 # Path codes diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index 96e78b6828f8..83ea260c434e 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -988,7 +988,7 @@ def test_transformed_path(): atol=1e-15) # Changing the path does not change the result (it's cached). - path.points = [(0, 0)] * 4 + path._vertices = [(0, 0)] * 4 assert_allclose(trans_path.get_fully_transformed_path().vertices, [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)], atol=1e-15) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 88c2ae625c00..26c3c5a28e20 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -1367,7 +1367,7 @@ def _zalpha(colors, zs): # Should really normalize against the viewing depth. if len(colors) == 0 or len(zs) == 0: return np.zeros((0, 4)) - norm = Normalize(min(zs), max(zs)) + norm = Normalize(np.min(zs), np.max(zs)) sats = 1 - norm(zs) * 0.7 rgba = np.broadcast_to(mcolors.to_rgba_array(colors), (len(zs), 4)) return np.column_stack([rgba[:, :3], rgba[:, 3] * sats]) diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index 34a03969c961..87c59ae05714 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -164,8 +164,7 @@ def _proj_transform_vectors(vecs, M): def _proj_transform_vec_clip(vec, M, focal_length): vecw = np.dot(M, vec.data) - w = vecw[3] - txs, tys, tzs = vecw[0] / w, vecw[1] / w, vecw[2] / w + txs, tys, tzs = vecw[0:3] / vecw[3] if np.isinf(focal_length): # don't clip orthographic projection tis = np.ones(txs.shape, dtype=bool) else: From c6b9214f0c7ec2ab495b494f5b83df133f7ec7ff Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Tue, 7 Jan 2025 11:57:56 -0700 Subject: [PATCH 3/3] Remove test --- lib/matplotlib/tests/test_transforms.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index 83ea260c434e..9981c54f9025 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -987,12 +987,6 @@ def test_transformed_path(): [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)], atol=1e-15) - # Changing the path does not change the result (it's cached). - path._vertices = [(0, 0)] * 4 - assert_allclose(trans_path.get_fully_transformed_path().vertices, - [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)], - atol=1e-15) - def test_transformed_patch_path(): trans = mtransforms.Affine2D()