From 21135785edd35597498b4dd93de2479d1f88b3fe Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 1 Aug 2019 23:36:21 +0200 Subject: [PATCH] Cleanup demo_agg_filter. --- examples/misc/demo_agg_filter.py | 134 +++++++++++-------------------- lib/matplotlib/tests/test_agg.py | 70 +++++++--------- 2 files changed, 75 insertions(+), 129 deletions(-) diff --git a/examples/misc/demo_agg_filter.py b/examples/misc/demo_agg_filter.py index 9dfc45655c3b..80dde60de314 100644 --- a/examples/misc/demo_agg_filter.py +++ b/examples/misc/demo_agg_filter.py @@ -4,18 +4,17 @@ =============== """ -import matplotlib.pyplot as plt -import numpy as np import matplotlib.cm as cm +import matplotlib.pyplot as plt import matplotlib.transforms as mtransforms from matplotlib.colors import LightSource from matplotlib.artist import Artist +import numpy as np def smooth1d(x, window_len): # copied from http://www.scipy.org/Cookbook/SignalSmooth - s = np.r_[2*x[0] - x[window_len:1:-1], x, 2*x[-1] - x[-1:-window_len:-1]] w = np.hanning(window_len) y = np.convolve(w/w.sum(), s, mode='same') @@ -23,78 +22,64 @@ def smooth1d(x, window_len): def smooth2d(A, sigma=3): - - window_len = max(int(sigma), 3)*2 + 1 - A1 = np.array([smooth1d(x, window_len) for x in np.asarray(A)]) - A2 = np.transpose(A1) - A3 = np.array([smooth1d(x, window_len) for x in A2]) - A4 = np.transpose(A3) - - return A4 + window_len = max(int(sigma), 3) * 2 + 1 + A = np.apply_along_axis(smooth1d, 0, A, window_len) + A = np.apply_along_axis(smooth1d, 1, A, window_len) + return A class BaseFilter: - def prepare_image(self, src_image, dpi, pad): - ny, nx, depth = src_image.shape - # tgt_image = np.zeros([pad*2+ny, pad*2+nx, depth], dtype="d") - padded_src = np.zeros([pad*2 + ny, pad*2 + nx, depth], dtype="d") - padded_src[pad:-pad, pad:-pad, :] = src_image[:, :, :] - - return padded_src # , tgt_image def get_pad(self, dpi): return 0 + def process_image(padded_src, dpi): + raise NotImplementedError("Should be overridden by subclasses") + def __call__(self, im, dpi): pad = self.get_pad(dpi) - padded_src = self.prepare_image(im, dpi, pad) + padded_src = np.pad(im, [(pad, pad), (pad, pad), (0, 0)], "constant") tgt_image = self.process_image(padded_src, dpi) return tgt_image, -pad, -pad class OffsetFilter(BaseFilter): - def __init__(self, offsets=None): - if offsets is None: - self.offsets = (0, 0) - else: - self.offsets = offsets + + def __init__(self, offsets=(0, 0)): + self.offsets = offsets def get_pad(self, dpi): - return int(max(*self.offsets)/72.*dpi) + return int(max(self.offsets) / 72 * dpi) def process_image(self, padded_src, dpi): ox, oy = self.offsets - a1 = np.roll(padded_src, int(ox/72.*dpi), axis=1) - a2 = np.roll(a1, -int(oy/72.*dpi), axis=0) + a1 = np.roll(padded_src, int(ox / 72 * dpi), axis=1) + a2 = np.roll(a1, -int(oy / 72 * dpi), axis=0) return a2 class GaussianFilter(BaseFilter): - "simple gauss filter" + """Simple Gaussian filter.""" - def __init__(self, sigma, alpha=0.5, color=None): + def __init__(self, sigma, alpha=0.5, color=(0, 0, 0)): self.sigma = sigma self.alpha = alpha - if color is None: - self.color = (0, 0, 0) - else: - self.color = color + self.color = color def get_pad(self, dpi): - return int(self.sigma*3/72.*dpi) + return int(self.sigma*3 / 72 * dpi) def process_image(self, padded_src, dpi): - # offsetx, offsety = int(self.offsets[0]), int(self.offsets[1]) - tgt_image = np.zeros_like(padded_src) - aa = smooth2d(padded_src[:, :, -1]*self.alpha, - self.sigma/72.*dpi) - tgt_image[:, :, -1] = aa - tgt_image[:, :, :-1] = self.color + tgt_image = np.empty_like(padded_src) + tgt_image[:, :, :3] = self.color + tgt_image[:, :, 3] = smooth2d(padded_src[:, :, 3] * self.alpha, + self.sigma / 72 * dpi) return tgt_image class DropShadowFilter(BaseFilter): - def __init__(self, sigma, alpha=0.3, color=None, offsets=None): + + def __init__(self, sigma, alpha=0.3, color=(0, 0, 0), offsets=(0, 0)): self.gauss_filter = GaussianFilter(sigma, alpha, color) self.offset_filter = OffsetFilter(offsets) @@ -109,7 +94,6 @@ def process_image(self, padded_src, dpi): class LightFilter(BaseFilter): - "simple gauss filter" def __init__(self, sigma, fraction=0.5): self.gauss_filter = GaussianFilter(sigma, alpha=1) @@ -123,47 +107,36 @@ def process_image(self, padded_src, dpi): t1 = self.gauss_filter.process_image(padded_src, dpi) elevation = t1[:, :, 3] rgb = padded_src[:, :, :3] - + alpha = padded_src[:, :, 3:] rgb2 = self.light_source.shade_rgb(rgb, elevation, fraction=self.fraction) - - tgt = np.empty_like(padded_src) - tgt[:, :, :3] = rgb2 - tgt[:, :, 3] = padded_src[:, :, 3] - - return tgt + return np.concatenate([rgb2, alpha], -1) class GrowFilter(BaseFilter): - "enlarge the area" + """Enlarge the area.""" - def __init__(self, pixels, color=None): + def __init__(self, pixels, color=(1, 1, 1)): self.pixels = pixels - if color is None: - self.color = (1, 1, 1) - else: - self.color = color + self.color = color def __call__(self, im, dpi): - ny, nx, depth = im.shape - alpha = np.pad(im[..., -1], self.pixels, "constant") - alpha2 = np.clip(smooth2d(alpha, self.pixels/72.*dpi) * 5, 0, 1) + alpha = np.pad(im[..., 3], self.pixels, "constant") + alpha2 = np.clip(smooth2d(alpha, self.pixels / 72 * dpi) * 5, 0, 1) new_im = np.empty((*alpha2.shape, 4)) - new_im[:, :, -1] = alpha2 - new_im[:, :, :-1] = self.color + new_im[:, :, :3] = self.color + new_im[:, :, 3] = alpha2 offsetx, offsety = -self.pixels, -self.pixels return new_im, offsetx, offsety class FilteredArtistList(Artist): - """ - A simple container to draw filtered artist. - """ + """A simple container to filter multiple artists at once.""" def __init__(self, artist_list, filter): + super().__init__() self._artist_list = artist_list self._filter = filter - Artist.__init__(self) def draw(self, renderer): renderer.start_rasterizing() @@ -188,15 +161,13 @@ def filtered_text(ax): # draw ax.imshow(Z, interpolation='bilinear', origin='lower', - cmap=cm.gray, extent=(-3, 3, -2, 2)) + cmap=cm.gray, extent=(-3, 3, -2, 2), aspect='auto') levels = np.arange(-1.2, 1.6, 0.2) CS = ax.contour(Z, levels, origin='lower', linewidths=2, extent=(-3, 3, -2, 2)) - ax.set_aspect("auto") - # contour label cl = ax.clabel(CS, levels[1::2], # label every second level inline=1, @@ -223,17 +194,14 @@ def drop_shadow_line(ax): # copied from examples/misc/svg_filter_line.py # draw lines - l1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], "bo-", - mec="b", mfc="w", lw=5, mew=3, ms=10, label="Line 1") - l2, = ax.plot([0.1, 0.5, 0.9], [0.5, 0.2, 0.7], "ro-", - mec="r", mfc="w", lw=5, mew=3, ms=10, label="Line 1") + l1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], "bo-") + l2, = ax.plot([0.1, 0.5, 0.9], [0.5, 0.2, 0.7], "ro-") gauss = DropShadowFilter(4) for l in [l1, l2]: # draw shadows with same lines with slight offset. - xx = l.get_xdata() yy = l.get_ydata() shadow, = ax.plot(xx, yy) @@ -288,7 +256,6 @@ def light_filter_pie(ax): fracs = [15, 30, 45, 10] explode = (0, 0.05, 0, 0) pies = ax.pie(fracs, explode=explode) - ax.patch.set_visible(True) light_filter = LightFilter(9) for p in pies[0]: @@ -305,21 +272,12 @@ def light_filter_pie(ax): if __name__ == "__main__": - plt.figure(figsize=(6, 6)) - plt.subplots_adjust(left=0.05, right=0.95) - - ax = plt.subplot(221) - filtered_text(ax) - - ax = plt.subplot(222) - drop_shadow_line(ax) - - ax = plt.subplot(223) - drop_shadow_patches(ax) + fix, axs = plt.subplots(2, 2) - ax = plt.subplot(224) - ax.set_aspect(1) - light_filter_pie(ax) - ax.set_frame_on(True) + filtered_text(axs[0, 0]) + drop_shadow_line(axs[0, 1]) + drop_shadow_patches(axs[1, 0]) + light_filter_pie(axs[1, 1]) + axs[1, 1].set_frame_on(True) plt.show() diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index e576f8ed71f8..b9905ff58cea 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -81,79 +81,69 @@ def test_long_path(): @image_comparison(['agg_filter.png'], remove_text=True) def test_agg_filter(): def smooth1d(x, window_len): - s = np.r_[2*x[0] - x[window_len:1:-1], - x, - 2*x[-1] - x[-1:-window_len:-1]] + # copied from http://www.scipy.org/Cookbook/SignalSmooth + s = np.r_[ + 2*x[0] - x[window_len:1:-1], x, 2*x[-1] - x[-1:-window_len:-1]] w = np.hanning(window_len) y = np.convolve(w/w.sum(), s, mode='same') return y[window_len-1:-window_len+1] def smooth2d(A, sigma=3): - window_len = max(int(sigma), 3)*2 + 1 - A1 = np.array([smooth1d(x, window_len) for x in np.asarray(A)]) - A2 = np.transpose(A1) - A3 = np.array([smooth1d(x, window_len) for x in A2]) - A4 = np.transpose(A3) - - return A4 + window_len = max(int(sigma), 3) * 2 + 1 + A = np.apply_along_axis(smooth1d, 0, A, window_len) + A = np.apply_along_axis(smooth1d, 1, A, window_len) + return A class BaseFilter: - def prepare_image(self, src_image, dpi, pad): - ny, nx, depth = src_image.shape - padded_src = np.zeros([pad*2 + ny, pad*2 + nx, depth], dtype="d") - padded_src[pad:-pad, pad:-pad, :] = src_image[:, :, :] - - return padded_src # , tgt_image def get_pad(self, dpi): return 0 + def process_image(padded_src, dpi): + raise NotImplementedError("Should be overridden by subclasses") + def __call__(self, im, dpi): pad = self.get_pad(dpi) - padded_src = self.prepare_image(im, dpi, pad) + padded_src = np.pad(im, [(pad, pad), (pad, pad), (0, 0)], + "constant") tgt_image = self.process_image(padded_src, dpi) return tgt_image, -pad, -pad class OffsetFilter(BaseFilter): - def __init__(self, offsets=None): - if offsets is None: - self.offsets = (0, 0) - else: - self.offsets = offsets + + def __init__(self, offsets=(0, 0)): + self.offsets = offsets def get_pad(self, dpi): - return int(max(*self.offsets)/72.*dpi) + return int(max(self.offsets) / 72 * dpi) def process_image(self, padded_src, dpi): ox, oy = self.offsets - a1 = np.roll(padded_src, int(ox/72.*dpi), axis=1) - a2 = np.roll(a1, -int(oy/72.*dpi), axis=0) + a1 = np.roll(padded_src, int(ox / 72 * dpi), axis=1) + a2 = np.roll(a1, -int(oy / 72 * dpi), axis=0) return a2 class GaussianFilter(BaseFilter): - "simple gauss filter" + """Simple Gaussian filter.""" - def __init__(self, sigma, alpha=0.5, color=None): + def __init__(self, sigma, alpha=0.5, color=(0, 0, 0)): self.sigma = sigma self.alpha = alpha - if color is None: - self.color = (0, 0, 0) - else: - self.color = color + self.color = color def get_pad(self, dpi): - return int(self.sigma*3/72.*dpi) + return int(self.sigma*3 / 72 * dpi) def process_image(self, padded_src, dpi): - tgt_image = np.zeros_like(padded_src) - aa = smooth2d(padded_src[:, :, -1]*self.alpha, - self.sigma/72.*dpi) - tgt_image[:, :, -1] = aa - tgt_image[:, :, :-1] = self.color + tgt_image = np.empty_like(padded_src) + tgt_image[:, :, :3] = self.color + tgt_image[:, :, 3] = smooth2d(padded_src[:, :, 3] * self.alpha, + self.sigma / 72 * dpi) return tgt_image class DropShadowFilter(BaseFilter): - def __init__(self, sigma, alpha=0.3, color=None, offsets=None): + + def __init__(self, sigma, alpha=0.3, color=(0, 0, 0), offsets=(0, 0)): self.gauss_filter = GaussianFilter(sigma, alpha, color) self.offset_filter = OffsetFilter(offsets) @@ -166,8 +156,7 @@ def process_image(self, padded_src, dpi): t2 = self.offset_filter.process_image(t1, dpi) return t2 - fig = plt.figure() - ax = fig.add_subplot(111) + fig, ax = plt.subplots() # draw lines l1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], "bo-", @@ -180,7 +169,6 @@ def process_image(self, padded_src, dpi): for l in [l1, l2]: # draw shadows with same lines with slight offset. - xx = l.get_xdata() yy = l.get_ydata() shadow, = ax.plot(xx, yy)