From: <lee...@us...> - 2009-08-14 18:11:13
|
Revision: 7488 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=7488&view=rev Author: leejjoon Date: 2009-08-14 18:11:05 +0000 (Fri, 14 Aug 2009) Log Message: ----------- add support for image filtering in agg backend Modified Paths: -------------- trunk/matplotlib/CHANGELOG trunk/matplotlib/lib/matplotlib/artist.py trunk/matplotlib/lib/matplotlib/backend_bases.py trunk/matplotlib/lib/matplotlib/backends/backend_agg.py trunk/matplotlib/lib/matplotlib/backends/backend_mixed.py trunk/matplotlib/lib/matplotlib/colors.py trunk/matplotlib/lib/matplotlib/text.py Added Paths: ----------- trunk/matplotlib/examples/pylab_examples/demo_agg_filter.py Modified: trunk/matplotlib/CHANGELOG =================================================================== --- trunk/matplotlib/CHANGELOG 2009-08-14 13:56:44 UTC (rev 7487) +++ trunk/matplotlib/CHANGELOG 2009-08-14 18:11:05 UTC (rev 7488) @@ -1,3 +1,6 @@ +2009-08-14 Add support for image filtering for agg back end. See the example + demo_agg_filter.py. -JJL + 2009-08-09 AnnotationBbox added. Similar to Annotation, but works with OffsetBox instead of Text. See the example demo_annotation_box.py. -JJL Added: trunk/matplotlib/examples/pylab_examples/demo_agg_filter.py =================================================================== --- trunk/matplotlib/examples/pylab_examples/demo_agg_filter.py (rev 0) +++ trunk/matplotlib/examples/pylab_examples/demo_agg_filter.py 2009-08-14 18:11:05 UTC (rev 7488) @@ -0,0 +1,312 @@ +import matplotlib.pyplot as plt + +import numpy as np +import scipy.ndimage as NI +import matplotlib.cm as cm +import matplotlib.mlab as mlab + + +class BaseFilter(object): + 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 __call__(self, im, dpi): + pad = self.get_pad(dpi) + padded_src = self.prepare_image(im, dpi, pad) + 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 get_pad(self, dpi): + return max(*self.offsets) + + def process_image(self, padded_src, dpi): + ox, oy = self.offsets + a1 = np.roll(padded_src, ox, axis=1) + a2 = np.roll(a1, -oy, axis=0) + return a2 + +class GaussianFilter(BaseFilter): + "simple gauss filter" + def __init__(self, sigma, alpha=0.5, color=None): + self.sigma = sigma + self.alpha = alpha + if color is None: + self.color=(0, 0, 0) + else: + self.color=color + + def get_pad(self, dpi): + return int(self.sigma*3) + + + def process_image(self, padded_src, dpi): + #offsetx, offsety = int(self.offsets[0]), int(self.offsets[1]) + tgt_image = np.zeros_like(padded_src) + tgt_image[:,:,-1] = NI.gaussian_filter(padded_src[:,:,-1]*self.alpha, + self.sigma) + tgt_image[:,:,:-1] = self.color + return tgt_image + +class DropShadowFilter(BaseFilter): + def __init__(self, sigma, alpha=0.3, color=None, offsets=None): + self.gauss_filter = GaussianFilter(sigma, alpha, color) + self.offset_filter = OffsetFilter(offsets) + + def get_pad(self, dpi): + return max(self.gauss_filter.get_pad(dpi), + self.offset_filter.get_pad(dpi)) + + def process_image(self, padded_src, dpi): + t1 = self.gauss_filter.process_image(padded_src, dpi) + t2 = self.offset_filter.process_image(t1, dpi) + return t2 + + +from matplotlib.colors import LightSource + +class LightFilter(BaseFilter): + "simple gauss filter" + def __init__(self, sigma, fraction=0.5): + self.gauss_filter = GaussianFilter(sigma, alpha=1) + self.light_source = LightSource() + self.fraction = fraction + #hsv_min_val=0.5,hsv_max_val=0.9, + # hsv_min_sat=0.1,hsv_max_sat=0.1) + def get_pad(self, dpi): + return self.gauss_filter.get_pad(dpi) + + def process_image(self, padded_src, dpi): + t1 = self.gauss_filter.process_image(padded_src, dpi) + elevation = t1[:,:,3] + rgb = 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 + + + +class GrowFilter(BaseFilter): + "enlarge the area" + def __init__(self, pixels, color=None): + self.pixels = pixels + if color is None: + self.color=(1, 1, 1) + else: + self.color=color + + def __call__(self, im, dpi): + pad = self.pixels + ny, nx, depth = im.shape + new_im = np.empty([pad*2+ny, pad*2+nx, depth], dtype="d") + alpha = new_im[:,:,3] + alpha.fill(0) + alpha[pad:-pad, pad:-pad] = im[:,:,-1] + alpha2 = NI.grey_dilation(alpha, size=(self.pixels, self.pixels)) + new_im[:,:,-1] = alpha2 + new_im[:,:,:-1] = self.color + offsetx, offsety = -pad, -pad + + return new_im, offsetx, offsety + + +from matplotlib.artist import Artist + +class FilteredArtistList(Artist): + """ + A simple container to draw filtered artist. + """ + def __init__(self, artist_list, filter): + self._artist_list = artist_list + self._filter = filter + Artist.__init__(self) + + def draw(self, renderer): + renderer.start_rasterizing() + renderer.start_filter() + for a in self._artist_list: + a.draw(renderer) + renderer.stop_filter(self._filter) + renderer.stop_rasterizing() + + + +import matplotlib.transforms as mtransforms + +def filtered_text(ax): + # mostly copied from contour_demo.py + + # prepare image + delta = 0.025 + x = np.arange(-3.0, 3.0, delta) + y = np.arange(-2.0, 2.0, delta) + X, Y = np.meshgrid(x, y) + Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0) + Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1) + # difference of Gaussians + Z = 10.0 * (Z2 - Z1) + + + # draw + im = ax.imshow(Z, interpolation='bilinear', origin='lower', + cmap=cm.gray, extent=(-3,3,-2,2)) + 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, + fmt='%1.1f', + fontsize=11) + + # change clable color to black + for t in cl: + t.set_color("k") + + # Add white glows to improve visibility of labels. + white_glows = FilteredArtistList(cl, GrowFilter(3)) + ax.add_artist(white_glows) + white_glows.set_zorder(cl[0].get_zorder()-0.1) + + ax.xaxis.set_visible(False) + ax.yaxis.set_visible(False) + + +def drop_shadow_line(ax): + # copyed 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") + + + gauss = DropShadowFilter(2) + + 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) + shadow.update_from(l) + + # offset transform + ot = mtransforms.offset_copy(l.get_transform(), ax.figure, + x=4.0, y=-6.0, units='points') + + shadow.set_transform(ot) + + + # adjust zorder of the shadow lines so that it is drawn below the + # original lines + shadow.set_zorder(l.get_zorder()-0.5) + shadow.set_agg_filter(gauss) + shadow.set_rasterized(True) # to support mixed-mode renderers + + + + ax.set_xlim(0., 1.) + ax.set_ylim(0., 1.) + + ax.xaxis.set_visible(False) + ax.yaxis.set_visible(False) + + + + +def drop_shadow_patches(ax): + # copyed from barchart_demo.py + N = 5 + menMeans = (20, 35, 30, 35, 27) + + ind = np.arange(N) # the x locations for the groups + width = 0.35 # the width of the bars + + rects1 = ax.bar(ind, menMeans, width, color='r', ec="w", lw=2) + + womenMeans = (25, 32, 34, 20, 25) + rects2 = ax.bar(ind+width+0.1, womenMeans, width, color='y', ec="w", lw=2) + + #gauss = GaussianFilter(1.5, offsets=(1,1), ) + gauss = DropShadowFilter(1.5, offsets=(1,1), ) + shadow = FilteredArtistList(rects1+rects2, gauss) + ax.add_artist(shadow) + shadow.set_zorder(rects1[0].get_zorder()-0.1) + + ax.set_xlim(ind[0]-0.5, ind[-1]+1.5) + ax.set_ylim(0, 40) + + ax.xaxis.set_visible(False) + ax.yaxis.set_visible(False) + + +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(8) + for p in pies[0]: + p.set_agg_filter(light_filter) + p.set_rasterized(True) # to support mixed-mode renderers + p.set(ec="none", + lw=2) + + gauss = DropShadowFilter(3, offsets=(3,4), alpha=0.7) + shadow = FilteredArtistList(pies[0], gauss) + ax.add_artist(shadow) + shadow.set_zorder(pies[0][0].get_zorder()-0.1) + + +if 1: + + plt.figure(1, 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) + + ax = plt.subplot(224) + ax.set_aspect(1) + light_filter_pie(ax) + ax.set_frame_on(True) + + plt.show() + + Modified: trunk/matplotlib/lib/matplotlib/artist.py =================================================================== --- trunk/matplotlib/lib/matplotlib/artist.py 2009-08-14 13:56:44 UTC (rev 7487) +++ trunk/matplotlib/lib/matplotlib/artist.py 2009-08-14 18:11:05 UTC (rev 7488) @@ -36,7 +36,15 @@ if artist.get_rasterized(): renderer.start_rasterizing() + if artist.get_agg_filter() is not None: + renderer.start_filter() + + def after(artist, renderer): + + if artist.get_agg_filter() is not None: + renderer.stop_filter(artist.get_agg_filter()) + if artist.get_rasterized(): renderer.stop_rasterizing() @@ -78,7 +86,8 @@ self._picker = None self._contains = None self._rasterized = None - + self._agg_filter = None + self.eventson = False # fire events only if eventson self._oid = 0 # an observer id self._propobservers = {} # a dict from oids to funcs @@ -548,6 +557,7 @@ gc.set_clip_path(None) def get_rasterized(self): + "return True if the artist is to be rasterized" return self._rasterized def set_rasterized(self, rasterized): @@ -563,6 +573,17 @@ self._rasterized = rasterized + def get_agg_filter(self): + "return filter function to be used for agg filter" + return self._agg_filter + + def set_agg_filter(self, filter_func): + """ + set agg_filter fuction. + + """ + self._agg_filter = filter_func + def draw(self, renderer, *args, **kwargs): 'Derived classes drawing method' if not self.get_visible(): return Modified: trunk/matplotlib/lib/matplotlib/backend_bases.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backend_bases.py 2009-08-14 13:56:44 UTC (rev 7487) +++ trunk/matplotlib/lib/matplotlib/backend_bases.py 2009-08-14 18:11:05 UTC (rev 7488) @@ -409,12 +409,36 @@ return cbook.strip_math(s) def start_rasterizing(self): + """ + Used in MixedModeRenderer. Switch to the raster renderer. + """ pass def stop_rasterizing(self): + """ + Used in MixedModeRenderer. Switch back to the vector renderer + and draw the contents of the raster renderer as an image on + the vector renderer. + """ pass + def start_filter(self): + """ + Used in AggRenderer. Switch to a temporary renderer for image + filtering effects. + """ + pass + def stop_filter(self, filter_func): + """ + Used in AggRenderer. Switch back to the original renderer. + The contents of the temporary renderer is processed with the + *filter_func* and is drawn on the original renderer as an + image. + """ + pass + + class GraphicsContextBase: """ An abstract base class that provides color, line styles, etc... Modified: trunk/matplotlib/lib/matplotlib/backends/backend_agg.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_agg.py 2009-08-14 13:56:44 UTC (rev 7487) +++ trunk/matplotlib/lib/matplotlib/backends/backend_agg.py 2009-08-14 18:11:05 UTC (rev 7488) @@ -57,22 +57,33 @@ self.height = height if __debug__: verbose.report('RendererAgg.__init__ width=%s, height=%s'%(width, height), 'debug-annoying') self._renderer = _RendererAgg(int(width), int(height), dpi, debug=False) + self._filter_renderers = [] + if __debug__: verbose.report('RendererAgg.__init__ _RendererAgg done', 'debug-annoying') + + self._update_methods() + self.mathtext_parser = MathTextParser('Agg') + + self.bbox = Bbox.from_bounds(0, 0, self.width, self.height) + if __debug__: verbose.report('RendererAgg.__init__ done', + 'debug-annoying') + + def draw_markers(self, *kl, **kw): + # for filtering to work with rastrization, methods needs to be wrapped. + # maybe there is better way to do it. + return self._renderer.draw_markers(*kl, **kw) + + def _update_methods(self): #self.draw_path = self._renderer.draw_path # see below - self.draw_markers = self._renderer.draw_markers + #self.draw_markers = self._renderer.draw_markers self.draw_path_collection = self._renderer.draw_path_collection self.draw_quad_mesh = self._renderer.draw_quad_mesh self.draw_gouraud_triangle = self._renderer.draw_gouraud_triangle self.draw_image = self._renderer.draw_image self.copy_from_bbox = self._renderer.copy_from_bbox self.tostring_rgba_minimized = self._renderer.tostring_rgba_minimized - self.mathtext_parser = MathTextParser('Agg') - self.bbox = Bbox.from_bounds(0, 0, self.width, self.height) - if __debug__: verbose.report('RendererAgg.__init__ done', - 'debug-annoying') - def draw_path(self, gc, path, transform, rgbFace=None): """ Draw the path @@ -165,6 +176,7 @@ d /= 64.0 return w, h, d + def draw_tex(self, gc, x, y, s, prop, angle): # todo, handle props, angle, origins size = prop.get_size_in_points() @@ -271,7 +283,58 @@ else: self._renderer.restore_region(region) + def start_filter(self): + """ + Start filtering. It simply create a new canvas (the old one is saved). + """ + self._filter_renderers.append(self._renderer) + self._renderer = _RendererAgg(int(self.width), int(self.height), + self.dpi) + self._update_methods() + def stop_filter(self, post_processing): + """ + Save the plot in the current canvas as a image and apply + the *post_processing* function. + + def post_processing(image, dpi): + # ny, nx, depth = image.shape + # image (numpy array) has RGBA channels and has a depth of 4. + ... + # create a new_image (numpy array of 4 channels, size can be + # different). The resulting image may have offsets from + # lower-left corner of the original image + return new_image, offset_x, offset_y + + The saved renderer is restored and the returned image from + post_processing is plotted (using draw_image) on it. + """ + + from matplotlib._image import fromarray + + width, height = int(self.width), int(self.height) + + buffer, bounds = self._renderer.tostring_rgba_minimized() + + l, b, w, h = bounds + + + self._renderer = self._filter_renderers.pop() + self._update_methods() + + if w > 0 and h > 0: + img = npy.fromstring(buffer, npy.uint8) + img, ox, oy = post_processing(img.reshape((h, w, 4)) / 255., + self.dpi) + image = fromarray(img, 1) + image.flipud_out() + + gc = self.new_gc() + self._renderer.draw_image(gc, + l+ox, height - b - h +oy, + image) + + def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance Modified: trunk/matplotlib/lib/matplotlib/backends/backend_mixed.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_mixed.py 2009-08-14 13:56:44 UTC (rev 7487) +++ trunk/matplotlib/lib/matplotlib/backends/backend_mixed.py 2009-08-14 18:11:05 UTC (rev 7488) @@ -58,6 +58,7 @@ finalize flipy get_canvas_width_height get_image_magnification get_texmanager get_text_width_height_descent new_gc open_group option_image_nocomposite points_to_pixels strip_math + start_filter stop_filter """.split() def _set_current_renderer(self, renderer): self._renderer = renderer Modified: trunk/matplotlib/lib/matplotlib/colors.py =================================================================== --- trunk/matplotlib/lib/matplotlib/colors.py 2009-08-14 13:56:44 UTC (rev 7487) +++ trunk/matplotlib/lib/matplotlib/colors.py 2009-08-14 18:11:05 UTC (rev 7488) @@ -1021,6 +1021,19 @@ RGBA values are returned, which can then be used to plot the shaded image with imshow. """ + + rgb0 = cmap((data-data.min())/(data.max()-data.min())) + rgb1 = self.shade_rgb(rgb0, elevation=data) + rgb0[:,:,0:3] = rgb1 + return rgb0 + + def shade_rgb(self,rgb, elevation, fraction=1.): + """ + Take the input RGB array (ny*nx*3) adjust their color values + to given the impression of a shaded relief map with a + specified light source using the elevation (ny*nx). + A new RGB array ((ny*nx*3)) is returned. + """ # imagine an artificial sun placed at infinity in # some azimuth and elevation position illuminating our surface. The parts of # the surface that slope toward the sun should brighten while those sides @@ -1029,7 +1042,7 @@ az = self.azdeg*np.pi/180.0 alt = self.altdeg*np.pi/180.0 # gradient in x and y directions - dx, dy = np.gradient(data) + dx, dy = np.gradient(elevation) slope = 0.5*np.pi - np.arctan(np.hypot(dx, dy)) aspect = np.arctan2(dx, dy) intensity = np.sin(alt)*np.sin(slope) + np.cos(alt)*np.cos(slope)*np.cos(-az -\ @@ -1037,9 +1050,9 @@ # rescale to interval -1,1 # +1 means maximum sun exposure and -1 means complete shade. intensity = (intensity - intensity.min())/(intensity.max() - intensity.min()) - intensity = 2.*intensity - 1. + intensity = (2.*intensity - 1.)*fraction # convert to rgb, then rgb to hsv - rgb = cmap((data-data.min())/(data.max()-data.min())) + #rgb = cmap((data-data.min())/(data.max()-data.min())) hsv = rgb_to_hsv(rgb[:,:,0:3]) # modify hsv values to simulate illumination. hsv[:,:,1] = np.where(np.logical_and(np.abs(hsv[:,:,1])>1.e-10,intensity>0),\ @@ -1053,5 +1066,4 @@ hsv[:,:,1:] = np.where(hsv[:,:,1:]<0.,0,hsv[:,:,1:]) hsv[:,:,1:] = np.where(hsv[:,:,1:]>1.,1,hsv[:,:,1:]) # convert modified hsv back to rgb. - rgb[:,:,0:3] = hsv_to_rgb(hsv) - return rgb + return hsv_to_rgb(hsv) Modified: trunk/matplotlib/lib/matplotlib/text.py =================================================================== --- trunk/matplotlib/lib/matplotlib/text.py 2009-08-14 13:56:44 UTC (rev 7487) +++ trunk/matplotlib/lib/matplotlib/text.py 2009-08-14 18:11:05 UTC (rev 7488) @@ -18,6 +18,8 @@ from matplotlib.transforms import Affine2D, Bbox from matplotlib.lines import Line2D +from matplotlib.artist import allow_rasterization + import matplotlib.nxutils as nxutils def _process_text_args(override, fontdict=None, **kwargs): @@ -501,6 +503,7 @@ self._bbox_patch.draw(renderer) + @allow_rasterization def draw(self, renderer): """ Draws the :class:`Text` object to the given *renderer*. @@ -1727,6 +1730,7 @@ self.arrow.set_clip_box(self.get_clip_box()) + @allow_rasterization def draw(self, renderer): """ Draw the :class:`Annotation` object to the given *renderer*. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |