This notebook will demonstrate how one can compute the particles directly from an image, and store them as an instance of Particle Manager. There's a nice tutorial for analysis of connected components without pyparty in the latter half of this tutorial:
You'll notice that they use masking in many cases to isolate regions of the image/array that correspond to labels. In this guide, I'll do this as well, and then show you how pyparty can be used to completely extract the particles without the need to rely on masking and direct image manipulation.
Particle labeling is available through skimage.morphology (via scipy.ndimage). First let's make some test data
Configure notebook style (see NBCONFIG.ipynb), add imports and paths. The %run magic used below requires IPython 2.0 or higher.
from __future__ import division
%run NBCONFIG.ipynb
Populating the interactive namespace from numpy and matplotlib
c=Canvas()
c.available()
(('simple:', ['bezier', 'circle', 'ellipse', 'line']), ('n-circle:', ['dimer', 'tetramer', 'trimer']), ('polygon:', ['polygon', 'rectangle', 'square', 'triangle']))
c.add('dimer', overlap=.3) #Default to image center (256, 256)
c.add('dimer', center=(100,100), phi=25.)
c.add('circle', center=(100,400), phi=60)
c.add('ellipse', center=(400,100))
c.add('triangle', center=(200,300), length=50)
c.add('ellipse', center=(400, 400), yradius=5, xradius=50)
print c.particles
c.show();
NAME PTYPE CENTER PHI 0 dimer_0 dimer (256, 256) 0.0 1 dimer_1 dimer (100, 100) 25.0 2 circle_0 circle (100, 400) 60.0 3 ellipse_0 ellipse (400, 100) 0.0 4 triangle_0 triangle (200, 300) 0.0 5 ellipse_1 ellipse (400, 400) 0.0
Let's try out the label function (image must be binary)
from skimage.morphology import label
ax1, ax2, ax3 = splot(1, 3, figsize=(10,8))
# We can access grayimage immediately from canvas
labels = label(c.grayimage)
showim(c.image, ax1, title='RGB')
showim(c.grayimage, 'gray', ax2, title='Grayscale')
showim(labels, 'jet', ax3, title='skimage.morphology labels');
::Skip to next section if not interested and just want to know how to use it::
Notice that labels is just an array; it doesn't have any concept the "particles". We can see that there are 7 unique labels (although 0 corresponds to background), but this is all mixed up in an ndarray
print labels.shape
print np.unique(labels)
labels
(512, 512) [0 1 2 3 4 5 6]
array([[0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], ..., [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0]])
Since pyparty stores the rr_cc indicies of each particle, we can look at their values in the new labeled image.
print ' '.join(['INDEX', 'NAME', 'CSTART', 'CEND', 'CENTER'])+'\n'
for idx, p in enumerate(c.particles):
print idx, p.name, labels[p.rr_cc][0:3], labels[p.rr_cc][-3:], p.center
INDEX NAME CSTART CEND CENTER 0 dimer_0 [3 3 3] [3 3 3] (256, 256) 1 dimer_1 [1 1 1] [1 1 1] (100, 100) 2 circle_0 [5 5 5] [5 5 5] (100, 400) 3 ellipse_0 [2 2 2] [2 2 2] (400, 100) 4 triangle_0 [4 4 4] [4 4 4] (200, 300) 5 ellipse_1 [6 6 6] [6 6 6] (400, 400)
Thus, we maintain control over which labels have been assigned to which particle. For example, we can see that dimer_1 has two different colors assigned to it, and these particles were labeled first and second, while ellipse 4 was labeled last.
It would be nice if we could immediately retrieve a set of corresponding labels to the particles and indeed we can in pyparty! In fact, this functionality is a major motivator for building pyparty
The from_labels method streamlines the process espoused above. Since all of the particles in the image are already stored, we can return a binary image of just the particles using pbinary. Alternatively, we can tell tell Canvas to use its internally stored thresholding function. In this case, pbinary is what we want, as its guaranteed to show all of the particles. A later example will show a case in which we want to use the thresholding function.
ax1, ax2 = splot(1,2)
c.show('pbinary', ax1, title='pbinary')
showim(c.binaryimage, ax2, 'gray', title='%s' % c.threshfcn);
print 'Canvas thresholding function is "%s()"' % c.threshfcn
Canvas thresholding function is "img2bool()"
Let's go ahead an make a new canvas; the from_labels method will analyze the binarized image from pbinary. We will then try to extract the labels from this background. Since we know the background is black, we can exclude it from the labels using the bgexclude keyword.
clab = c.from_labels(exclude='black')
clab.show(title='Particles from labels')
clab.particles[0:5]
01-30 01:21:40 WARNING pyparty.tools.manager: pmin < 10 may result in errors in some particle descriptors
NAME PTYPE CENTER PHI 0 label_1 nd_label (91, 118) 0.0 1 label_2 nd_label (100, 400) 0.0 2 label_3 nd_label (108, 81) 0.0 3 label_4 nd_label (256, 256) 0.0 4 label_5 nd_label (300, 200) 0.0
The particles are retrived. They have new colors (since default colors are random) and are now of ptype = nd_label.
Any keyword for skimage.morphology.label() is valid for canvas.from_labels() (except "background" which has been replaced by "bgexclude"); with additional keywords:
We can recover the same color schema of our original labels by a colormap.
ax1, ax2, ax3 = splot(1, 3, figsize=(12,8))
clab = c.from_labels(prefix='FOOPREFIX', colorbynum=True, exclude='black')
clab.show(ax1, title='Black to Yellow Ascending')
clab.show('jet', ax2, title="1-Channel coloring")
clab.background='black'
clab.show('jet', ax3, title='Original Coloring');
clab.particles[0:5]
01-30 01:21:41 WARNING pyparty.tools.manager: pmin < 10 may result in errors in some particle descriptors
NAME PTYPE CENTER PHI 0 FOOPREFIX_1 nd_label (91, 118) 0.0 1 FOOPREFIX_2 nd_label (100, 400) 0.0 2 FOOPREFIX_3 nd_label (108, 81) 0.0 3 FOOPREFIX_4 nd_label (256, 256) 0.0 4 FOOPREFIX_5 nd_label (300, 200) 0.0
This allows us to recover the original color scheme used in labeling the raw image. Notice that one of the dimers is now stored as two nd_label particles. It's the same color ast he background, so appear invisible.
len(c), len(clab), 'Dimer labeled as 2 particles'
(6, 7, 'Dimer labeled as 2 particles')
Now I can do anything I want with the particles. For example, let's sort by eccentricity (largest to smallest).
clab.sortby('eccentricity', inplace=True, ascending=False)
clab.eccentricity
array([ 0.99553441, 0.8725733 , 0.82286498, 0.04213593, 0. , 0. , 0. ])
Let's load a real image, and set an adaptive threshold function. I happen to know that a guassian local adaptive threshold (skimage.filter.threshold_adaptive) works well with a blocksize of 199. I will use the canvas set_threshfcn() wrapper to implement this.
from pyparty.data import lena_who
ax1, ax2 = splot(1,2)
c=Canvas(background=lena_who())
c.show('pbinary', ax1, title='No particles; pbinary is blank');
c.set_threshfcn('adaptive', block_size=199, method='gaussian')
showim(c.binarybackground, ax2, 'gray', title='Adaptive threshold');
c.from_labels() offers a lot of options for customizability. First, let's check out just the options wrapped into the underlying scikit.morophology.label function (http://scikit-image.org/docs/dev/api/skimage.morphology.html#label). These are:
A prior cell already showed the addition pyparty
keywords (prefix, colorbynum etc...).
Unlike the first example, our current canvas has ZERO PARTICLES. Thus, if we used "pbinary", we'd get a black background. Instead, we need to get the labels using the adaptive threshold method seen above. This is done through the pbinary keyword to from_labels()
#This cell may take a bit to load
ax1, ax2, ax3, ax4 = splot(2,2, figsize=(15,15))
c1=c.from_labels(pbinary=False) #background = None, neighbors = 4
c2=c.from_labels(neighbors=8, pbinary=False) #background = none
c3=c.from_labels(exclude=0, pbinary=False) # neighbors = 4
c4=c.from_labels(exclude=1, pbinary=False) # neighbors = 4
c1.show(ax1, title='%s particles; no background; 4 neighbors' % len(c1))
c2.show(ax2, title='%s particles; no background; 8 neighbors' % len(c2))
c3.show(ax3, title='%s particles; black bg cut; 4 neighbors' % len(c3))
c4.show(ax4, title='%s particles; white bg cut; 4 neighbors' % len(c4));
01-30 01:21:43 WARNING pyparty.tools.manager: pmin < 10 may result in errors in some particle descriptors 01-30 01:22:01 WARNING pyparty.tools.manager: pmin < 10 may result in errors in some particle descriptors 01-30 01:22:14 WARNING pyparty.tools.manager: pmin < 10 may result in errors in some particle descriptors 01-30 01:22:22 WARNING pyparty.tools.manager: pmin < 10 may result in errors in some particle descriptors
The colors are random; to shade, use the colorbynum keyword as shown above. More options for controlling label colors are coming soon.
When we do not specicfy a background pixel, the entire image is labeled. When a background is specified, notice that the original image is underlaid in the output. We can control what background the labels are overlaid upon using the bgout keyword.
ax1, ax2 = splot(1,2, figsize=(15,15))
c1=c.from_labels(pbinary=False, exclude='black', bgout='white')
c2=c.from_labels(pbinary=False, exclude='black', bgout=c.graybackground)
c1.show(ax1, title='Labels on a white background')
c2.show(ax2, title='Labels on a gray image');
01-30 01:22:34 WARNING pyparty.tools.manager: pmin < 10 may result in errors in some particle descriptors 01-30 01:22:42 WARNING pyparty.tools.manager: pmin < 10 may result in errors in some particle descriptors 01-30 01:22:50 WARNING pyparty.utils: background color has been converted (1-channel to 3-channel RGB)
Two other important keywords are the pmin and pmax that specify a pixel size range. By default, pmin=2 pixels and pmax has no upper bound.
ax1, ax2 = splot(1,2, figsize=(15,15))
c1=c.from_labels(exclude='black', pmax=1000, pbinary=False)
c2=c.from_labels(exclude='black', pmin=5000, pbinary=False)
c1.show(ax1, title='Small labels (< 1000 pixels)')
c2.show(ax2, title='Large labels (> 5000 pixels)');
01-30 01:22:52 WARNING pyparty.tools.manager: pmin < 10 may result in errors in some particle descriptors
pmin / pmax will reduce the labeling time; however, filtering can always be done on the particles directly. For example, let's keep only the largest particles:
#Sort particles by largest area; keep top 3
c2.sortby('area', inplace=True, ascending=False) #large to small
c2.particles=c2.particles[0:3]
# To set attributes on indivdual particles, access plist, not particles
c2.plist[0].color = 'r'
c2.plist[1].color = 'b'
c2.plist[2].color = 'w'
#Superimpose on a black background
c2.background = 'black'
c2.show();
Particles labeled from an image should behave identically to intrinsic pyparty shapes. For example, changing the orientation (this looks like it may be buggy; will fix soon)
def rotate(p):
p.phi = p.phi + 45.0
return p
c2.pmap(rotate)
c2.show();
The logging warning about pmin < 10 is a bug that affects descriptors on very small particles. For example, the area of 4-pixel object.
c.fromlabels(binary=False) Setting to False will use the grayimage for labels. Because the gray image has color variablity, most labels will single pixels (only pixels with identical colors AND connected will share labels). This results in O(n^2) slowdown, since a 512 x 512 grayimage can have up to 262144 pixels. While scikit's labeling will still find them quickly, pyparty
will take a long time to store that many particles. If you do need to use labels on a grayimage; it's best to use skimage.morphology.labels directly.