Link to the notebook.
IPython is my favorite tool ever, so matplotlib has to be my favorite plotting lib. I heard that matplotlib doesn't look nice or at least not Web-2.1-stylish [1]: that is not true, but you have to write some code to make the plots stylish. My result actually isn't that pretty, I'm sure if my friend the desginer would help, we could make an awesome pie-chart.
I just want to show that the tools are there.
BTW the matplotlib xkcd-style inspired me to try this.
IPython is my favorite tool ever, so matplotlib has to be my favorite plotting lib. I heard that matplotlib doesn't look nice or at least not Web-2.1-stylish [1]: that is not true, but you have to write some code to make the plots stylish. My result actually isn't that pretty, I'm sure if my friend the desginer would help, we could make an awesome pie-chart.
I just want to show that the tools are there.
BTW the matplotlib xkcd-style inspired me to try this.
In [22]:
%pylab inline
%config InlineBackend.figure_format = 'png'
import matplotlib.font_manager as fm
import matplotlib.colors as mc
from matplotlib.artist import Artist
import sys
sys.version
Out[22]:
Helpers
These nice helpers are from demo_agg_filter.py, I only added those we need. We will treat these as black box, I havent studied them, but I show how to write a simple filter. Show helpers.
In [2]:
class FilteredArtistList(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()
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')
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
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 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)
return a2
class GaussianFilter(BaseFilter):
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/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
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
Axes customizers
In [3]:
def dark_edges(ax):
# Iterate over the patches in the axes
for patch in ax.patches:
# Get the facecolor of the patch
ec = patch.get_facecolor()
# Make that color a bit darker and set it as edge color
patch.set_edgecolor(tuple(x * 0.7 for x in ec[:3]) + (ec[3],))
patch.set_linewidth(1.4)
def change_fonts(ax):
# Load a font family
prop = fm.FontProperties(family=['Arial'], size=20)
# Set it to all texts in the axes
for text in ax.texts:
text.set_fontproperties(prop)
def shade_patches(ax):
# Set our custom shadow_filter for all patches in the axes
for patch in ax.patches:
patch.set_agg_filter(shadow_filter)
My shadow filter
The shadow filter is quite simple and demonstrates how to write agg filters for matplotlib. I'll explain it below.
In [4]:
obj = None
def shadow_filter(image, dpi):
global obj
# Get the shape of the image
nx, ny, depth = image.shape
# Create a mash grid
xx, yy = numpy.mgrid[0:nx, 0:ny]
# Draw a circular "shadow"
circle = (xx + nx * 4) ** 2 + (yy + ny) ** 2
# Normalize
circle -= circle.min()
circle = circle / circle.max()
# Steepness
value = circle.clip(0.3, 0.6) + 0.4
saturation = 1 - circle.clip(0.7, 0.8)
# Normalize
saturation -= saturation.min() - 0.1
saturation = saturation / saturation.max()
# Convert the rgb part (without alpha) to hsv
hsv = mc.rgb_to_hsv(image[:,:,:3])
# Multiply the value of hsv image with the shadow
hsv[:,:,2] = hsv[:,:,2] * value
# Highlights with saturation
hsv[:,:,1] = hsv[:,:,1] * saturation
# Copy the hsv back into the image (we haven't touched alpha)
image[:,:,:3] = mc.hsv_to_rgb(hsv)
# the return values are: new_image, offset_x, offset_y
return image, 0, 0
Plot the pie with all customizations
In [5]:
# We create a nice large figure
figure(1, figsize=(12,12))
ax = axes([0.1, 0.1, 0.8, 0.8])
labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
fracs = [15, 30, 45, 10]
# Explode in a regular fashion (the circle is still "round")
expl = [x / 1000 for x in fracs]
# We draw the whole pie
ax.pie(
fracs,
explode=expl,
labels=labels,
autopct='%1.1f%%',
startangle=90,
colors=[
'#FF8A8A',
'#86BCFF',
'#33FDC0',
'#FFFFAA'
]
)
# We draw all the the patches again,
# this time with the drop-shadow filter
shadow = FilteredArtistList(
ax.patches,
DropShadowFilter(
36,
offsets=(-5,-7),
alpha=0.4
)
)
# This tells the axes to draw the drop-shadow
ax.add_artist(shadow)
# Replace the black edges by darkened edges
dark_edges(ax)
# Add a shade to the patches
shade_patches(ax)
# Change the font
change_fonts(ax)
How the shadow filter works
NumPy is fast, but only if it hasn't to return to python and native code is running. So how do you draw a shadow (gradient in photoshop)? You need things you can multipy with your image, they're called mesh-grids.
In [6]:
xx, yy = numpy.mgrid[0:10, 0:10]
In [7]:
xx
Out[7]:
In [8]:
yy
Out[8]:
It is basicly the y and x coordinate but expanded to the whole image that you can easily multiply these.
Lets make a circular shadow (gradient):
Lets make a circular shadow (gradient):
In [9]:
circle = (xx - 5) ** 2 + (yy - 5) ** 2
circle
Out[9]:
In [10]:
imshow(circle, cmap = cm.Greys_r);
We can change the center of the circle:
In [11]:
circle = (xx) ** 2 + (yy) ** 2
imshow(circle, cmap = cm.Greys_r);
We can change the brightness of the circle:
In [12]:
circle = (xx - 5) ** 2 + (yy - 5) ** 2 - 20
circle = circle.clip(0)
imshow(circle, cmap = cm.Greys_r);
In [13]:
circle = (xx - 5) ** 2 + (yy - 5) ** 2
circle = circle.clip(0, 20)
imshow(circle, cmap = cm.Greys_r);
If the center of the circle is far enough away, the shadow looses the roundness, please forgive me that it is still called circle. :-)
In [14]:
circle = (xx+100) ** 2 + (yy+100) ** 2
imshow(circle, cmap = cm.Greys_r);
Clipping makes the shadow steeper:
In [15]:
circle = (xx+50) ** 2 + (yy+100) ** 2
# Normalize the gradient
circle -= circle.min()
circle = circle / circle.max()
# Steepness
circle = circle.clip(0.3, 0.7)
imshow(circle, cmap = cm.Greys_r);
Now we create a red image. We initialze all zeros and set the red channel to 1. In image[:, :, 0] the first : means all rows, the second means all columns and 0 means the first rgb channel -> red.
In [16]:
image = zeros((10, 10, 3))
image[:, :, 0] = 1
In [17]:
imshow(image);
In numpy x[] = means replace. So we replace the red channel of all pixels image[:, :, 0] with the red channel of all pixels times circle.
In [18]:
image[:, :, 0] = image[:, :, 0] * circle
imshow(image);
