-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Significantly improve tight layout performance for cartopy axes #21935
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
136de27
to
c7501dd
Compare
5f15a05
to
a5a704e
Compare
Added a simple test that |
I can also see the origin of this bug: The matplotlib/lib/matplotlib/artist.py Lines 340 to 364 in 1a74793
While |
a5a704e
to
f53d451
Compare
f53d451
to
51bde9e
Compare
51bde9e
to
fc1a386
Compare
I've improved this PR in a force push:
I also made the following additional changes:
The latter points also resolve a second inefficiency: Currently, matplotlib/lib/matplotlib/axes/_base.py Lines 4616 to 4634 in 710fce3
and 2) includes I've also verified that, as before, import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig = plt.figure(figsize=(4, 2))
ax1 = fig.add_subplot(131)
ax2 = fig.add_subplot(132, projection='polar')
ax3 = fig.add_subplot(133, projection='hammer')
for ax in (ax1, ax2, ax3):
print(ax)
for a in (ax.patch, tuple(ax.spines.values())[0]):
clip_box = a.get_clip_box()
b1 = clip_box is not None and np.all(clip_box.extents == ax.bbox.extents)
clip_path = a.get_clip_path()
b2 = clip_path is not None and clip_path._patch is ax.patch
print(a, '\nclip on?', a.get_clip_on(), '\naxes clip box?', b1, '\naxes clip path?', b2, '\nFULLY CLIPPED?', a._fully_clipped_to_axes())
print() Results: AxesSubplot(0.125,0.11;0.227941x0.77)
Rectangle(xy=(0, 0), width=1, height=1, angle=0)
clip on? True
axes clip box? False
axes clip path? False
FULLY CLIPPED? False
Spine
clip on? True
axes clip box? False
axes clip path? False
FULLY CLIPPED? False
PolarAxesSubplot(0.398529,0.11;0.227941x0.77)
Wedge(center=(0.5, 0.5), r=0.5, theta1=0, theta2=360, width=None)
clip on? True
axes clip box? False
axes clip path? False
FULLY CLIPPED? False
Spine
clip on? True
axes clip box? False
axes clip path? False
FULLY CLIPPED? False
HammerAxesSubplot(0.672059,0.11;0.227941x0.77)
Circle(xy=(0.5, 0.5), radius=0.5)
clip on? True
axes clip box? False
axes clip path? False
FULLY CLIPPED? False
Spine
clip on? True
axes clip box? False
axes clip path? False
FULLY CLIPPED? False |
fc1a386
to
ce3ff1f
Compare
ce3ff1f
to
9ef9d01
Compare
This all looks good, but are we sure it has similar performance to before? https://github.com/matplotlib/mpl-bench has benchmarking mechanisms, in particular you could use the constrained_layout and tight_layout tests? |
Here are some very quick benchmarks with 2-column figures of map projections (thanks for pointing me to In principle this fits as a matplotlib PR, but the speedup does seem to be cartopy-specific. I get essentially zero speedup for 1) non-rectilinear matplotlib axes and 2) by omitting the Cartopy JPG testTest code: import time
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import proplot as pplt
fig, axs = plt.subplots(ncols=2, figsize=(8, 4), subplot_kw={'projection': ccrs.Stereographic()})
t1 = time.time()
lon = np.linspace(0, 360, 3000)
lat = np.linspace(-60, 60, 500)
state = np.random.RandomState(51423)
data = state.rand(len(lat) - 1, len(lon) - 1)
for ax in axs:
ax.coastlines()
ax.pcolormesh(lon, lat, data, transform=ccrs.PlateCarree())
t2 = time.time()
print('Plot time:', t2 - t1)
fig.savefig('test.jpg', dpi=300, bbox_inches=None)
# fig.savefig('test.jpg', dpi=300, bbox_inches='tight')
t3 = time.time()
print('Save time:', t3 - t2) Results with Plot time: 5.106376886367798
Save time: 4.24917197227478 Results with Plot time: 5.4417970180511475
Save time: 5.905539035797119 Results with Plot time: 5.133382081985474
Save time: 10.721824884414673 Cartopy PDF testTest code: import time
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import proplot as pplt
fig, axs = plt.subplots(ncols=2, figsize=(8, 4), subplot_kw={'projection': ccrs.Stereographic()})
t1 = time.time()
lon = np.linspace(0, 360, 100)
lat = np.linspace(-60, 60, 100)
state = np.random.RandomState(51423)
data = state.rand(len(lat) - 1, len(lon) - 1)
for ax in axs:
ax.coastlines()
ax.pcolormesh(lon, lat, data, transform=ccrs.PlateCarree())
t2 = time.time()
print('Plot time:', t2 - t1)
fig.savefig('test.pdf')
t3 = time.time()
print('Save time:', t3 - t2) Results with Plot time: 0.28498291969299316
Save time: 2.3959097862243652 Results with Plot time: 0.24462389945983887
Save time: 2.699641227722168 Results with Plot time: 0.2555239200592041
Save time: 3.705993890762329 Matplotlib JPG testTest code: import time
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import proplot as pplt
fig, axs = plt.subplots(ncols=2, figsize=(8, 4), subplot_kw={'projection': 'hammer'})
t1 = time.time()
lon = np.linspace(0, 360, 500)
lat = np.linspace(-60, 60, 500)
state = np.random.RandomState(51423)
data = state.rand(len(lat) - 1, len(lon) - 1)
for ax in axs:
ax.pcolormesh(lon, lat, data)
t2 = time.time()
print('Plot time:', t2 - t1)
fig.savefig('test.jpg', dpi=300, bbox_inches='tight')
t3 = time.time()
print('Save time:', t3 - t2) Results with 0.04705810546875
Save time: 12.776510953903198 Results with Plot time: 0.05183219909667969
Save time: 11.54852819442749 Results with Plot time: 0.053359031677246094
Save time: 11.422415018081665 |
if (artist.get_visible() and artist.get_in_layout())] | ||
# always include types that do not internally implement clipping | ||
# to axes. may have clip_on set to True and clip_box equivalent | ||
# to ax.bbox but then ignore these properties during draws. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is .... a fun fact.
This PR is adapted from SciTools/cartopy#1956 following our discussion there.
Summary
This PR significantly improves the speed of
Axes.get_tightbbox()
for non-rectilinear axes (in particular,matplotlib's PolarAxes and cartopy's GeoAxescartopyGeoAxes
with transformed coordinates) by preventing unnecessary and expensiveget_window_extent()
computations. In the presence of complex, high-resolution artists, it can result in a 2x improvement to the draw time for cartopyGeoAxes
when "tight layout" is enabled.There may be a better way to implement this -- looking forward to everyone's thoughts.
Details
To prevent unnecessary and expensive
get_window_extent()
computations,Axes.get_tightbbox()
skips artists withclip_on
set to True and whoseclip_box.extents
are equivalent toax.bbox.extents
. However, while all axes artists are clipped byTransformedPatchPath(ax.patch)
by default, only artists drawn inside rectilinear projections are clipped byax.bbox
(see the below example).This PR replaces
Artist._get_clipping_extent_bbox()
withArtist._is_axes_clipped()
. Now,Axes.get_tightbbox()
includesax.patch.get_window_extent()
in its computation, and skips all artists clipped by eitherax.bbox
orax.patch
(i.e., artists for whichArtist._is_axes_clipped()
returns True).This PR also removes the computation of the intersection of
clip_box
andclip_path
, under the assumption that the independent test ofclip_path
covers those instances, but perhaps that should be added back.Example
Here is an example with a cartopy
GeoAxes
:Performance before:
Performance after:
And of course the performance difference is larger the larger the dataset.
Checklist
Tests and Styling
pytest
passes).flake8-docstrings
and runflake8 --docstring-convention=all
).Documentation
doc/users/next_whats_new/
(follow instructions in README.rst there).doc/api/next_api_changes/
(follow instructions in README.rst there).