Skip to content

Backport PR #19343 on branch v3.5.x (Enh improve agg chunks error) #21174

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 52 additions & 5 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,65 @@ def draw_path(self, gc, path, transform, rgbFace=None):
c = c[ii0:ii1]
c[0] = Path.MOVETO # move to end of last chunk
p = Path(v, c)
p.simplify_threshold = path.simplify_threshold
try:
self._renderer.draw_path(gc, p, transform, rgbFace)
except OverflowError as err:
raise OverflowError(
"Exceeded cell block limit (set 'agg.path.chunksize' "
"rcparam)") from err
msg = (
"Exceeded cell block limit in Agg.\n\n"
"Please reduce the value of "
f"rcParams['agg.path.chunksize'] (currently {nmax}) "
"or increase the path simplification threshold"
"(rcParams['path.simplify_threshold'] = "
f"{mpl.rcParams['path.simplify_threshold']:.2f} by "
"default and path.simplify_threshold = "
f"{path.simplify_threshold:.2f} on the input)."
)
raise OverflowError(msg) from None
else:
try:
self._renderer.draw_path(gc, path, transform, rgbFace)
except OverflowError as err:
raise OverflowError("Exceeded cell block limit (set "
"'agg.path.chunksize' rcparam)") from err
cant_chunk = ''
if rgbFace is not None:
cant_chunk += "- can not split filled path\n"
if gc.get_hatch() is not None:
cant_chunk += "- can not split hatched path\n"
if not path.should_simplify:
cant_chunk += "- path.should_simplify is False\n"
if len(cant_chunk):
msg = (
"Exceeded cell block limit in Agg, however for the "
"following reasons:\n\n"
f"{cant_chunk}\n"
"we can not automatically split up this path to draw."
"\n\nPlease manually simplify your path."
)

else:
inc_threshold = (
"or increase the path simplification threshold"
"(rcParams['path.simplify_threshold'] = "
f"{mpl.rcParams['path.simplify_threshold']} "
"by default and path.simplify_threshold "
f"= {path.simplify_threshold} "
"on the input)."
)
if nmax > 100:
msg = (
"Exceeded cell block limit in Agg. Please reduce "
"the value of rcParams['agg.path.chunksize'] "
f"(currently {nmax}) {inc_threshold}"
)
else:
msg = (
"Exceeded cell block limit in Agg. Please set "
"the value of rcParams['agg.path.chunksize'], "
f"(currently {nmax}) to be greater than 100 "
+ inc_threshold
)

raise OverflowError(msg) from None

def draw_mathtext(self, gc, x, y, s, prop, angle):
"""Draw mathtext using :mod:`matplotlib.mathtext`."""
Expand Down
89 changes: 84 additions & 5 deletions lib/matplotlib/tests/test_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@

from matplotlib import (
collections, path, pyplot as plt, transforms as mtransforms, rcParams)
from matplotlib.image import imread
from matplotlib.backends.backend_agg import RendererAgg
from matplotlib.figure import Figure
from matplotlib.image import imread
from matplotlib.path import Path
from matplotlib.testing.decorators import image_comparison
from matplotlib.transforms import IdentityTransform


def test_repeated_save_with_alpha():
Expand Down Expand Up @@ -72,10 +75,10 @@ def test_marker_with_nan():

def test_long_path():
buff = io.BytesIO()

fig, ax = plt.subplots()
np.random.seed(0)
points = np.random.rand(70000)
fig = Figure()
ax = fig.subplots()
points = np.ones(100_000)
points[::2] *= -1
ax.plot(points)
fig.savefig(buff, format='png')

Expand Down Expand Up @@ -251,3 +254,79 @@ def test_draw_path_collection_error_handling():
ax.scatter([1], [1]).set_paths(path.Path([(0, 1), (2, 3)]))
with pytest.raises(TypeError):
fig.canvas.draw()


@pytest.fixture
def chunk_limit_setup():
N = 100_000
dpi = 500
w = 5*dpi
h = 6*dpi

# just fit in the width
x = np.linspace(0, w, N)
# and go top-to-bottom
y = np.ones(N) * h
y[::2] = 0

idt = IdentityTransform()
# make a renderer
ra = RendererAgg(w, h, dpi)
# setup the minimal gc to draw a line
gc = ra.new_gc()
gc.set_linewidth(1)
gc.set_foreground('r')
# make a Path
p = Path(np.vstack((x, y)).T)
# effectively disable path simplification (but leaving it "on")
p.simplify_threshold = 0

return ra, gc, p, idt


def test_chunksize_hatch_fail(chunk_limit_setup):
ra, gc, p, idt = chunk_limit_setup

gc.set_hatch('/')

with pytest.raises(OverflowError, match='hatched path'):
ra.draw_path(gc, p, idt)


def test_chunksize_rgbFace_fail(chunk_limit_setup):
ra, gc, p, idt = chunk_limit_setup

with pytest.raises(OverflowError, match='filled path'):
ra.draw_path(gc, p, idt, (1, 0, 0))


def test_chunksize_no_simplify_fail(chunk_limit_setup):
ra, gc, p, idt = chunk_limit_setup
p.should_simplify = False
with pytest.raises(OverflowError, match="should_simplify is False"):
ra.draw_path(gc, p, idt)


def test_chunksize_zero(chunk_limit_setup):
ra, gc, p, idt = chunk_limit_setup
# set to zero to disable, currently defaults to 0, but lets be sure
rcParams['agg.path.chunksize'] = 0
with pytest.raises(OverflowError, match='Please set'):
ra.draw_path(gc, p, idt)


def test_chunksize_too_big_to_chunk(chunk_limit_setup):
ra, gc, p, idt = chunk_limit_setup
# set big enough that we do not try to chunk
rcParams['agg.path.chunksize'] = 1_000_000
with pytest.raises(OverflowError, match='Please reduce'):
ra.draw_path(gc, p, idt)


def test_chunksize_toobig_chunks(chunk_limit_setup):
ra, gc, p, idt = chunk_limit_setup
# small enough we will try to chunk, but big enough we will fail
# to render
rcParams['agg.path.chunksize'] = 90_000
with pytest.raises(OverflowError, match='Please reduce'):
ra.draw_path(gc, p, idt)
4 changes: 2 additions & 2 deletions lib/matplotlib/tests/test_simplification.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,8 @@ def test_start_with_moveto():

def test_throw_rendering_complexity_exceeded():
plt.rcParams['path.simplify'] = False
xx = np.arange(200000)
yy = np.random.rand(200000)
xx = np.arange(2_000_000)
yy = np.random.rand(2_000_000)
yy[1000] = np.nan

fig, ax = plt.subplots()
Expand Down
2 changes: 1 addition & 1 deletion src/_backend_agg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi)
rendererBase(),
rendererAA(),
rendererBin(),
theRasterizer(8192),
theRasterizer(32768),
lastclippath(NULL),
_fill_color(agg::rgba(1, 1, 1, 0))
{
Expand Down