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