%run NBCONFIG.ipynb
Populating the interactive namespace from numpy and matplotlib
pyparty
provides some basic Grid classes, the goals of which are to provide a gridding/tiling system for creating particles and background. As with other aspects of pyparty, grids can be created from barebones:
http://stackoverflow.com/questions/19586828/drawing-grid-pattern-in-matplotlib
pyparty
Grids wrap numpy.meshgrid() and provide a basic API for human-natural interaction. For example, grids retain information on their spacing, tiles, centers, corners etc... Before looking at the Grid classes in general, let's look at the builtin CartesianGrid of the canvas class. First, let's conjur up some random circles. Canvas has a special constructor for this:
from pyparty import Canvas, splot, showim
c=Canvas.random_circles(n=15, background='honeydew', pcolor='random')
Canvas' grid can be accessed as an attribute:
c.show(grid=True)
c.grid
CartesianGrid (512 X 512) at 0xb80fd7c: X --> 15 divisions (34.1 pixels / div) Y --> 15 divisions (34.1 pixels / div)
A variety of show() keywords can be used to change grid properties. Any of these keywords will automatically set grid=True. These are:
gstyle is only available for patchshow()
ax1, ax2, ax3, ax4 = splot(2,2, figsize=(10,10))
c.show(ax1, gcolor='blue', title='standard')
c.show(ax2, gcolor='blue', gunder=True, title='grid under')
c.patchshow(ax3, gcolor='orange', gunder=True, title='patches grid under')
c.patchshow(ax4, gcolor='orange', gstyle='--', title='patches gstyle = --');
We can change the tiling via the spacing or points parameters, for both the x and y dimensions. Spacing sets the width of each grid tile; points let the users fix the grid to a preset number of spaces. At any time, the reset_grid() method will restore the default resolution and spacing.
from pyparty import splot
ax1, ax2 = splot(1,2, figsize=(10,10))
c.grid.xdiv=5
c.show(ax1, gcolor='black', title='5 "X" divisions')
c.reset_grid()
c.grid.yspacing=20
c.patchshow(ax2, gcolor=(1,0,0), title='20 pixels per "Y" division')
c.reset_grid();
The "grid" keyword to show() takes a variety of arguments:
axs = splot(2,2)
c.show(axs[0], grid='centers', title='centers')
c.show(axs[1], grid='corners', title='corners')
c.show(axs[2], grid='hlines', title='hlines')
c.show(axs[3], grid='vlines', title='vlines')
# Zoom in to for clarity
for ax in axs:
ax.set_xlim(-2,37)
ax.set_ylim(37,-2)
There's not much magic going on here, becuase centers, corners etc.. are all accessible indicies in the underlying grid. We could access them directly and use them as masks on any ndarray.
#Make a blank 512x512 array (ones=white)
blank = np.ones((512,512,3))
#Get the indicies of corners, centers
blank[c.grid.centers] = (1,0,0) #r
blank[c.grid.hlines] = (0,1,0) #g
blank[c.grid.corners] = (0,0,1) #b
imshow(blank)
plt.xlim(-1,104)
plt.ylim(104,-1);
A major motivator for the canvas grid is to map particles to the grid points. Canvas selectively promotes a few very common grid attributes for convienence. These are:
All other grid attributes must be accessed directly on the grid. Important ones include:
As shown above, grid feature attribute (centers/edges etc...) are returned as masks. For particle mapping it is advantageous to return the coordinates of these points instead. For example, a set of (x,y) pairs corresponding to the grid centers. This is done through the c.gpairs() method:
from skimage.data import moon, lena
cnew=Canvas(background=moon())
cnew.grid.xdiv=10
cnew.grid.ydiv=20
for (cx, cy) in cnew.gpairs('centers'):
cnew.add('circle', radius=10, center=(cx,cy), color='orange')
for idx, (cx, cy) in enumerate( cnew.gpairs('corners') ):
cnew.add('ellipse', xradius=10, yradius=30, center=(cx,cy), color='white', phi=45*idx)
cnew.patchshow(gcolor='yellow', gstyle='--');
No handlers could be found for logger "pyparty.utils"
pyparty
implements 3 different Grid classes (presented in order of class heirarchy)
Grids were developed for two primary purposes:
While canvas implements an implicit instance of a CartesianGrid; it is mostly for mapping particles. The other Grids are more flexible for creating interesting backgrounds. Let's start by looking at Grid(). Unlike the TiledGrid or the CartesianGrid, Grid will plot as a continuous 2d function. The underlying array is stored in the .zz attribute:
from pyparty.tools.grids import Grid, TiledGrid
from pyparty import showim
ax1, ax2 = splot(1,2)
g = Grid()
tg = TiledGrid(xpoints=20, ypoints=20)
showim(g.zz, ax1)
showim(tg.zz, ax2)
print g
print tg
Grid (512 X 512) at 0xd4a008c: X --> 10 divisions (51.1 pixels / div) Y --> 10 divisions (51.1 pixels / div) TiledGrid (512 X 512) at 0xc28041c: X --> 10 divisions (51.1 pixels / div) Y --> 10 divisions (51.1 pixels / div)
TiledGrid() is merely a Grid() which forces integer rounding of its underlying mesh; hence, creating bins or tiles. To understand how grids work, let's first review numpy's meshgrid functionality to contextualize some of the common concepts.
This section will give some background into numpy meshgrids; for a full explanation, consult the numpy API
Every grid first starts by making a mesh that returns two arrays, XX and YY. Given the mesh, one applies a 2-dimensional function onto the mesh, which returns a single array (ZZ). ZZ is what is finally plotted. To illustrate this process, I'll go through a simple example using only the numpy API. First we generate our XX and YY mesh:
x=np.linspace(0,1,512)
y=np.linspace(0,1,512)
XX, YY = np.meshgrid(x,y)
ax1, ax2 = splot(1,2)
showim(XX, ax1, 'jet')
showim(YY, ax2, 'jet');
Notice that XX and YY are orthogonal. In effect, we can define an arbitrary function that takes in (XX, YY) as parameters, and returns ZZ. A very simple function would merely throw out YY and return just XX. This function I will call justXX. Another simple function would merely add XX and YY; it shall be called add_em:
def justXX(xx, yy):
return xx
def add_em(xx, yy):
return xx+yy
ax1, ax2 = splot(1,2)
showim(justXX(XX,YY), ax1)
showim(add_em(XX,YY), ax2);
While justXX is equivalent to plotting XX, add_em results in diagonal fade, as you'd expect from adding two orthogonal arrays.
Grid() internally stores an arbitrary ZZ function (eg justXX) in its zfcn attribute. zfcn is arbitrary EXCEPT that must have two positional arguments: XX, YY.
Because it's very common to want to be able to generate a backdrop of changing gradiant, Grid() accepts a fade parameter that will call an implicit zfcn that performs combinations of horizontal, vertical and diagonal gradients (ie, all combinations of the above examples), and has a reverse option
ax1, ax2 = splot(1,2)
tg1 = TiledGrid(style='d')
tg2 = TiledGrid(style='d', reverse=True)
showim(tg1.zz, ax1, 'jet')
showim(tg2.zz, ax2, 'jet');
The fade zfunction simplifies the process of creating constant gradients in the horizontal, vertical or diagonal direction. The TiledGrid() behaves similarly to the standard Grid(), except that its zfunction will bin data (through integer rounding) to user-set x and y spacing.
Note: instead of reversing the zfcn, we also could have just used the reversed colormap, bone_r.
Let's define a custom z function that takes the cos of XX and sin of YY and set to both a Grid() and TiledGrid() to observe the difference:
ax1, ax2 = splot(1,2)
def ztrigfcn(xx, yy, power=2):
return abs(np.cos(xx**power) + np.sin(yy**power))
tg.set_zfcn(ztrigfcn)
g.set_zfcn(ztrigfcn)
showim(g.zz, ax1, 'Blues')
showim(tg.zz,ax2, 'Blues');
Notice that the function is applied on the grid BEFORE tiling. If you convert to integer during your zfunction, you will implictly create a tiled grid. Probably best to avoid this!
Tiling is achieved by passing a zfcn that returns integer arrays. I would advise that you just use the TiledGrid() or CartesianGrid() rather than defining your own Tiling zfcn; however, an example of a tiling zfcn is shown below
def zint(xx, yy):
return xx.astype(int) + yy.astype(int)
g_foo = Grid()
g_foo.set_zfcn(zint)
showim(g_foo.zz, 'YlGn', title="Tiling through zfunction");
A grid can directly be passed into a canvas as the background:
ax1, ax2 = splot(1,2)
c.background = g
c.show(ax1, 'Blues_r')
c.reset_background()
c.background = tg1
c.show(ax2, 'jet', bgonly=True);
Grid also has limited support for blending background. The blend_bg() method merely performs a weighted sum between g.zz and an image. It also does some house keeping, like converting the image 1-channel gray, as well as cropping the dimensions if the image is larger than the grid. This is controlled through the weight parameter (0 : pure bg, 1 : pure mesh function)
from skimage.data import lena
ax1, ax2 = splot(1,2)
g=Grid(style='h')
shadow_lena = g.blend_bg( lena(), weight=0.75 )
g.set_zfcn(ztrigfcn, power=2.25)
trippy_lena = g.blend_bg( lena(), weight=.25 )
showim(shadow_lena, ax1, 'gray', title='horiz. shadow')
showim(trippy_lena, ax2, 'YlOrRd_r', title='trig pattern');
Notice that this does not actually warp the image by the zfunction; it merely merges the image and the zfunction effect. One drawback is that 3-channel images like lena are convert to 8-bit, and not restored. In future versions, this may be updated so that her original coloring can be restored. These images could easily be set to the background of a Canvas.
Grids also has methods for accessing individiual tiles in the grid, as well as the diagonals. These are currently experimental as they break from the API set forth by grid / hlines / vlines. In any case, they are currently functional
Tiles are generated through grid.as_tiles(key=False, sort=True), or through the grid.tiles property, which access as_tiles() with default values. Tiles are returned as lists of masks which must be index in a loop. Let's show how to loop over tiles and paint every other tile blue:
tiles = c.grid.tiles
c = Canvas( background=moon() )
img = c.image
eventiles = tiles[::2]
# Paint every other tile blue
for tile in eventiles:
img[tile] = (0,0,1)
showim(img);
If you need to access tiles, use as_tiles(key='flat' / '2d'); tiles will then be returned as a dictionary of flattened (0,1,2,3..) or 2d ( (0,0), (0,1) ...) keys. By default, sort order is preserved, meaning collections.OrderedDict is called. This comes standard in Python 2.7 or higher. Disable sorting via as_tiles(key=..., sort=False)
tflat = c.grid.as_tiles(key='flat') # or key=True
t2d = c.grid.as_tiles(key='2d')
for i in range(5):
print tflat.keys()[i], t2d.keys()[i]
0 (0, 0) 1 (0, 1) 2 (0, 2) 3 (0, 3) 4 (0, 4)
Now we can access tiles in an intuitive manner
tile5 = tflat[5]
tile16 = t2d[(9,9)] #index starts at 0
img[tile5] = (1,0,0) #red
img[tile16] = (1,1,0) #yellow
showim(img);
Tiles merely connected regions between grid corners. In other words, the center of the tile is grid.centers. Therefore, mapping particles to centers will center them in tiles.
c.background = img
oddtiles = tflat.keys()[1::2]
for idx, (cx, cy) in enumerate(c.gpairs('centers')):
if idx in oddtiles:
c.add('circle', radius=10, center=(cx,cy))
c.show();
Grid diagonals must be indexed the same manner as tiles; that is, through looping.
img = c.image
for d in c.grid.dlines:
img[d] = (1,0,0) #red
for nd in c.grid.dlines_neg:
img[nd] = (0,1,0) #green
showim(img);
Tiles and diagonal lines will be better integrated into the Canvas and Grid API in soon.