|
12 | 12 | from matplotlib.image import imread
|
13 | 13 | from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
14 | 14 | from matplotlib.figure import Figure
|
15 |
| -from matplotlib.testing.decorators import cleanup |
| 15 | +from matplotlib.testing.decorators import cleanup, image_comparison |
16 | 16 | from matplotlib import pyplot as plt
|
17 | 17 | from matplotlib import collections
|
18 | 18 | from matplotlib import path
|
| 19 | +from matplotlib import transforms as mtransforms |
19 | 20 |
|
20 | 21 |
|
21 | 22 | @cleanup
|
@@ -155,6 +156,136 @@ def test_long_path():
|
155 | 156 | fig.savefig(buff, format='png')
|
156 | 157 |
|
157 | 158 |
|
| 159 | +@image_comparison(baseline_images=['agg_filter'], |
| 160 | + extensions=['png'], remove_text=True) |
| 161 | +def test_agg_filter(): |
| 162 | + def smooth1d(x, window_len): |
| 163 | + # copied from http://www.scipy.org/Cookbook/SignalSmooth |
| 164 | + |
| 165 | + s = np.r_[2*x[0] - x[window_len:1:-1], x, 2*x[-1] - x[-1:-window_len:-1]] |
| 166 | + w = np.hanning(window_len) |
| 167 | + y = np.convolve(w/w.sum(), s, mode='same') |
| 168 | + return y[window_len-1:-window_len+1] |
| 169 | + |
| 170 | + def smooth2d(A, sigma=3): |
| 171 | + window_len = max(int(sigma), 3)*2 + 1 |
| 172 | + A1 = np.array([smooth1d(x, window_len) for x in np.asarray(A)]) |
| 173 | + A2 = np.transpose(A1) |
| 174 | + A3 = np.array([smooth1d(x, window_len) for x in A2]) |
| 175 | + A4 = np.transpose(A3) |
| 176 | + |
| 177 | + return A4 |
| 178 | + |
| 179 | + class BaseFilter(object): |
| 180 | + def prepare_image(self, src_image, dpi, pad): |
| 181 | + ny, nx, depth = src_image.shape |
| 182 | + #tgt_image = np.zeros([pad*2+ny, pad*2+nx, depth], dtype="d") |
| 183 | + padded_src = np.zeros([pad*2 + ny, pad*2 + nx, depth], dtype="d") |
| 184 | + padded_src[pad:-pad, pad:-pad, :] = src_image[:, :, :] |
| 185 | + |
| 186 | + return padded_src # , tgt_image |
| 187 | + |
| 188 | + def get_pad(self, dpi): |
| 189 | + return 0 |
| 190 | + |
| 191 | + def __call__(self, im, dpi): |
| 192 | + pad = self.get_pad(dpi) |
| 193 | + padded_src = self.prepare_image(im, dpi, pad) |
| 194 | + tgt_image = self.process_image(padded_src, dpi) |
| 195 | + return tgt_image, -pad, -pad |
| 196 | + |
| 197 | + class OffsetFilter(BaseFilter): |
| 198 | + def __init__(self, offsets=None): |
| 199 | + if offsets is None: |
| 200 | + self.offsets = (0, 0) |
| 201 | + else: |
| 202 | + self.offsets = offsets |
| 203 | + |
| 204 | + def get_pad(self, dpi): |
| 205 | + return int(max(*self.offsets)/72.*dpi) |
| 206 | + |
| 207 | + def process_image(self, padded_src, dpi): |
| 208 | + ox, oy = self.offsets |
| 209 | + a1 = np.roll(padded_src, int(ox/72.*dpi), axis=1) |
| 210 | + a2 = np.roll(a1, -int(oy/72.*dpi), axis=0) |
| 211 | + return a2 |
| 212 | + |
| 213 | + class GaussianFilter(BaseFilter): |
| 214 | + "simple gauss filter" |
| 215 | + |
| 216 | + def __init__(self, sigma, alpha=0.5, color=None): |
| 217 | + self.sigma = sigma |
| 218 | + self.alpha = alpha |
| 219 | + if color is None: |
| 220 | + self.color = (0, 0, 0) |
| 221 | + else: |
| 222 | + self.color = color |
| 223 | + |
| 224 | + def get_pad(self, dpi): |
| 225 | + return int(self.sigma*3/72.*dpi) |
| 226 | + |
| 227 | + def process_image(self, padded_src, dpi): |
| 228 | + #offsetx, offsety = int(self.offsets[0]), int(self.offsets[1]) |
| 229 | + tgt_image = np.zeros_like(padded_src) |
| 230 | + aa = smooth2d(padded_src[:, :, -1]*self.alpha, |
| 231 | + self.sigma/72.*dpi) |
| 232 | + tgt_image[:, :, -1] = aa |
| 233 | + tgt_image[:, :, :-1] = self.color |
| 234 | + return tgt_image |
| 235 | + |
| 236 | + class DropShadowFilter(BaseFilter): |
| 237 | + def __init__(self, sigma, alpha=0.3, color=None, offsets=None): |
| 238 | + self.gauss_filter = GaussianFilter(sigma, alpha, color) |
| 239 | + self.offset_filter = OffsetFilter(offsets) |
| 240 | + |
| 241 | + def get_pad(self, dpi): |
| 242 | + return max(self.gauss_filter.get_pad(dpi), |
| 243 | + self.offset_filter.get_pad(dpi)) |
| 244 | + |
| 245 | + def process_image(self, padded_src, dpi): |
| 246 | + t1 = self.gauss_filter.process_image(padded_src, dpi) |
| 247 | + t2 = self.offset_filter.process_image(t1, dpi) |
| 248 | + return t2 |
| 249 | + |
| 250 | + fig = plt.figure() |
| 251 | + ax = fig.add_subplot(111) |
| 252 | + |
| 253 | + # draw lines |
| 254 | + l1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], "bo-", |
| 255 | + mec="b", mfc="w", lw=5, mew=3, ms=10, label="Line 1") |
| 256 | + l2, = ax.plot([0.1, 0.5, 0.9], [0.5, 0.2, 0.7], "ro-", |
| 257 | + mec="r", mfc="w", lw=5, mew=3, ms=10, label="Line 1") |
| 258 | + |
| 259 | + gauss = DropShadowFilter(4) |
| 260 | + |
| 261 | + for l in [l1, l2]: |
| 262 | + |
| 263 | + # draw shadows with same lines with slight offset. |
| 264 | + |
| 265 | + xx = l.get_xdata() |
| 266 | + yy = l.get_ydata() |
| 267 | + shadow, = ax.plot(xx, yy) |
| 268 | + shadow.update_from(l) |
| 269 | + |
| 270 | + # offset transform |
| 271 | + ot = mtransforms.offset_copy(l.get_transform(), ax.figure, |
| 272 | + x=4.0, y=-6.0, units='points') |
| 273 | + |
| 274 | + shadow.set_transform(ot) |
| 275 | + |
| 276 | + # adjust zorder of the shadow lines so that it is drawn below the |
| 277 | + # original lines |
| 278 | + shadow.set_zorder(l.get_zorder() - 0.5) |
| 279 | + shadow.set_agg_filter(gauss) |
| 280 | + shadow.set_rasterized(True) # to support mixed-mode renderers |
| 281 | + |
| 282 | + ax.set_xlim(0., 1.) |
| 283 | + ax.set_ylim(0., 1.) |
| 284 | + |
| 285 | + ax.xaxis.set_visible(False) |
| 286 | + ax.yaxis.set_visible(False) |
| 287 | + |
| 288 | + |
158 | 289 | if __name__ == "__main__":
|
159 | 290 | import nose
|
160 | 291 | nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
|
0 commit comments