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.

------------------------------------------------------------------------------
Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day 
trial. Simplify your report design, integration and deployment - and focus on 
what you do best, core application coding. Discover what's new with 
Crystal Reports now.  http://p.sf.net/sfu/bobj-july
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to