content: from SimpleCV.base import *
  from SimpleCV.Features.Features import Feature, FeatureSet
  from SimpleCV.Color import Color
  from SimpleCV.ImageClass import Image
  from SimpleCV.Features.Detection import ShapeContextDescriptor
  import math
  import scipy.stats as sps
  
  class Blob(Feature):
      """
      **SUMMARY**
  
      A blob is a typicall a cluster of pixels that form a feature or unique
      shape that allows it to be distinguished from the rest of the image
      Blobs typically are computed very quickly so they are used often to
      find various items in a picture based on properties.  Typically these
      things like color, shape, size, etc.   Since blobs are computed quickly
      they are typically used to narrow down search regions in an image, where
      you quickly find a blob and then that blobs region is used for more
      computational intensive type image processing.
  
      **EXAMPLE**
  
      >>> img = Image("lenna")
      >>> blobs = img.findBlobs()
      >>> blobs[-1].draw()
      >>> img.show()
  
      **SEE ALSO**
      :py:meth:`findBlobs`
      :py:class:`BlobMaker`
      :py:meth:`findBlobsFromMask`
  
      """
      seq = '' #the cvseq object that defines this blob
      mContour = [] # the blob's outer perimeter as a set of (x,y) tuples
      mConvexHull = [] # the convex hull contour as a set of (x,y) tuples
      mMinRectangle = [] #the smallest box rotated to fit the blob
      # mMinRectangle[0] = centroid (x,y)
      # mMinRectangle[1] = (w,h)
      # mMinRectangle[2] = angle
  
      #mBoundingBox = [] #get W/H and X/Y from this
      mHu = [] # The seven Hu Moments
      mPerimeter = 0 # the length of the perimeter in pixels
      mArea = 0 # the area in pixels
      m00 = 0
      m01 = 0
      m10 = 0
      m11 = 0
      m20 = 0
      m02 = 0
      m21 = 0
      m12 = 0
      mContourAppx = None
      mLabel = "" # A user label
      mLabelColor = [] # what color to draw the label
      mAvgColor = []#The average color of the blob's area.
      #mImg =  '' #Image()# the segmented image of the blob
      #mHullImg = '' # Image() the image from the hull.
      #mMask = '' #Image()# A mask of the blob area
      #xmHullMask = '' #Image()#A mask of the hull area ... we may want to use this for the image mask.
      mHoleContour = []  # list of hole contours
      #mVertEdgeHist = [] #vertical edge histogram
      #mHortEdgeHist = [] #horizontal edge histgram
      pickle_skip_properties = set(
          ('mImg', 'mHullImg', 'mMask', 'mHullMask'))
  
      def __init__(self):
          self._scdescriptors = None
          self.mContour = []
          self.mConvexHull = []
          self.mMinRectangle = [-1,-1,-1,-1,-1] #angle from this
          self.mContourAppx = []
          self.mHu = [-1,-1,-1,-1,-1,-1,-1]
          self.mPerimeter = 0
          self.mArea = 0
          self.m00 = 0
          self.m01 = 0
          self.m10 = 0
          self.m11 = 0
          self.m20 = 0
          self.m02 = 0
          self.m21 = 0
          self.m12 = 0
          self.mLabel = "UNASSIGNED"
          self.mLabelColor = []
          self.mAvgColor = [-1,-1,-1]
          self.image = None
          self.mHoleContour = []
          self.points = []
          #TODO
          # I would like to clean up the Hull mask parameters
          # it seems to me that we may want the convex hull to be
          # the default way we calculate for area.
  
      def __getstate__(self):
          skip = self.pickle_skip_properties
          newdict = {}
          for k,v in self.__dict__.items():
              if k in skip:
                  continue
              else:
                  newdict[k] = v
          return newdict
  
  
      def __setstate__(self, mydict):
          iplkeys = []
          for k in mydict:
              if re.search("__string", k):
                  iplkeys.append(k)
              else:
                  self.__dict__[k] = mydict[k]
  
          #once we get all the metadata loaded, go for the bitmaps
          for k in iplkeys:
              realkey = k[:-len("__string")]
              self.__dict__[realkey] = cv.CreateImageHeader((self.width(), self.height()), cv.IPL_DEPTH_8U, 1)
              cv.SetData(self.__dict__[realkey], mydict[k])
  
      def perimeter(self):
          """
          **SUMMARY**
  
          This function returns the perimeter as an integer number of pixel lengths.
  
          **RETURNS**
  
          Integer
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> print blobs[-1].perimeter()
  
          """
  
          return self.mPerimeter
  
      def hull(self):
          """
          **SUMMARY**
  
          This function returns the convex hull points as a list of x,y tuples.
  
          **RETURNS**
  
          A list of x,y tuples.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> print blobs[-1].hull()
  
          """
          return self.mConvexHull
  
      def contour(self):
          """
          **SUMMARY**
  
          This function returns the contour points as a list of x,y tuples.
  
          **RETURNS**
  
          A list of x,y tuples.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> print blobs[-1].contour()
  
          """
  
          return self.mContour
  
      def meanColor(self):
          """
          **SUMMARY**
  
          This function returns a tuple representing the average color of the blob.
  
          **RETURNS**
  
          A RGB triplet of the average blob colors.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> print blobs[-1].meanColor()
  
          """
          #print self.mBoundingBox
          hack = (self.mBoundingBox[0],self.mBoundingBox[1],self.mBoundingBox[2],self.mBoundingBox[3])
          cv.SetImageROI(self.image.getBitmap(),hack)
          #may need the offset paramete
          avg = cv.Avg(self.image.getBitmap(),self.mMask._getGrayscaleBitmap())
          cv.ResetImageROI(self.image.getBitmap())
  
          return tuple(reversed(avg[0:3]))
  
      def area(self):
          """
          **SUMMARY**
  
          This method returns the area of the blob in terms of the number of
          pixels inside the contour.
  
          **RETURNS**
  
          An integer of the area of the blob in pixels.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> print blobs[-1].area()
          >>> print blobs[0].area()
  
          """
          return(self.mArea)
  
  
      def minRect(self):
          """
          Returns the corners for the smallest rotated rectangle to enclose the blob.
          The points are returned as a list of  (x,y) tupples.
          """
          #if( self.mMinRectangle[1][0] < self.mMinRectangle[1][1]):
          ang = self.mMinRectangle[2]
          #else:
          #    ang =  90 + self.mMinRectangle[2]
          ang = 2*pi*(float(ang)/360.00)
          tx = self.minRectX()
          ty = self.minRectY()
          w = self.minRectWidth()/2.0
          h = self.minRectHeight()/2.0
          derp = np.matrix([[cos(ang),-1*sin(ang),tx],[sin(ang),cos(ang),ty],[0,0,1]])
          # [ cos a , -sin a, tx ]
          # [ sin a , cos a , ty ]
          # [ 0     , 0     ,  1 ]
          tl = np.matrix([-1.0*w,h,1.0]) #Kat gladly supports homo. coordinates.
          tr = np.matrix([w,h,1.0])
          bl = np.matrix([-1.0*w,-1.0*h,1.0])
          br = np.matrix([w,-1.0*h,1.0])
          tlp = derp*tl.transpose()
          trp = derp*tr.transpose()
          blp = derp*bl.transpose()
          brp = derp*br.transpose()
          return( (float(tlp[0,0]),float(tlp[1,0])),(float(trp[0,0]),float(trp[1,0])),(float(blp[0,0]),float(blp[1,0])),(float(brp[0,0]),float(brp[1,0])) )
  
      def drawRect(self,layer=None,color=Color.DEFAULT,width=1,alpha=128):
          """
          **SUMMARY**
  
          Draws the bounding rectangle for the blob.
  
          **PARAMETERS**
  
          * *color* - The color to render the blob's box.
          * *alpha* - The alpha value of the rendered blob 0 = transparent 255 = opaque.
          * *width* - The width of the drawn blob in pixels
          * *layer* - if layer is not None, the blob is rendered to the layer versus the source image.
  
          **RETURNS**
  
          Returns None, this operation works on the supplied layer or the source image.
  
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> blobs[-1].drawRect(color=Color.RED, width=-1,alpha=128)
          >>> img.show()
  
          """
          if( layer is None ):
              layer = self.image.dl()
  
          if( width < 1 ):
              layer.rectangle(self.topLeftCorner(),(self.width(),self.height()),color,width,filled=True,alpha=alpha)
          else:
              layer.rectangle(self.topLeftCorner(),(self.width(),self.height()),color,width,filled=False,alpha=alpha)
  
  
      def drawMinRect(self,layer=None,color=Color.DEFAULT,width=1,alpha=128):
          """
          **SUMMARY**
  
          Draws the minimum bounding rectangle for the blob. The minimum bounding rectangle is the smallest
          rotated rectangle that can enclose the blob.
  
          **PARAMETERS**
  
          * *color* - The color to render the blob's box.
          * *alpha* - The alpha value of the rendered blob 0 = transparent 255 = opaque.
          * *width* - The width of the drawn blob in pixels
          * *layer* - If layer is not None, the blob is rendered to the layer versus the source image.
  
          **RETURNS**
  
          Returns none, this operation works on the supplied layer or the source image.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> for b in blobs:
          >>>      b.drawMinRect(color=Color.RED, width=-1,alpha=128)
          >>> img.show()
  
          """
          if( layer is None ):
              layer = self.image.dl()
          (tl,tr,bl,br) = self.minRect()
          layer.line(tl,tr,color,width=width,alpha=alpha,antialias = False)
          layer.line(bl,br,color,width=width,alpha=alpha,antialias = False)
          layer.line(tl,bl,color,width=width,alpha=alpha,antialias = False)
          layer.line(tr,br,color,width=width,alpha=alpha,antialias = False)
  
      def angle(self):
          """
          **SUMMARY**
  
          This method returns the angle between the horizontal and the minimum enclosing
          rectangle of the blob. The minimum enclosing rectangle IS NOT not the bounding box.
          Use the bounding box for situations where you need only an approximation of the objects
          dimensions. The minimum enclosing rectangle is slightly harder to maninpulate but
          gives much better information about the blobs dimensions.
  
          **RETURNS**
  
          Returns the angle between the minimum bounding rectangle and the horizontal.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> blob[-1].angle()
  
          """
          #return self.mMinRectangle[2]+90.00
          retVal = 0.00
          if self.mMinRectangle[1][0] < self.mMinRectangle[1][1]:
              retVal = self.mMinRectangle[2]
          else:
              retVal = 90.00 + self.mMinRectangle[2]
          retVal = retVal + 90.00
          if( retVal > 90.00 ):
              retVal = -180.00 + retVal
          return retVal
  
      def minRectX(self):
          """
          **SUMMARY**
  
          This is the x coordinate of the centroid for the minimum bounding rectangle
  
          **RETURNS**
  
          An integer that is the x position of the centrod of the minimum bounding rectangle.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> print blobs[-1].minRectX()
  
          """
          return(self.mMinRectangle[0][0])
  
      def minRectY(self):
          """
          **SUMMARY**
  
          This is the y coordinate of the centroid for the minimum bounding rectangle
  
          **RETURNS**
  
          An integer that is the y position of the centrod of the minimum bounding rectangle.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> print blobs[-1].minRectY()
  
          """
          return(self.mMinRectangle[0][1])
  
      def minRectWidth(self):
          """
          **SUMMARY**
  
          This is the width of the minimum bounding rectangle for the blob.
  
          **RETURNS**
  
          An integer that is the width of the minimum bounding rectangle for this blob.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> print blobs[-1].minRectWidth()
  
          """
          return(self.mMinRectangle[1][0])
  
      def minRectHeight(self):
          """
          **SUMMARY**
  
          This is the height, in pixels, of the minimum bounding rectangle.
  
          **RETURNS**
  
          An integer that is the height of the minimum bounding rectangle for this blob.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> print blobs[-1].minRectHeight()
  
  
          """
          return(self.mMinRectangle[1][1])
  
      def rectifyMajorAxis(self,axis=0):
          """
          **SUMMARY**
  
          Rectify the blob image and the contour such that the major
          axis is aligned to either horizontal=0 or vertical=1. This is to say, we take the blob,
          find the longest axis, and rotate the blob such that the axis is either vertical or horizontal.
  
          **PARAMETERS**
  
          * *axis* - if axis is zero we rotate the blobs to fit along the vertical axis, otherwise we use the horizontal axis.
  
          **RETURNS**
  
          This method works in place, i.e. it rotates the blob's internal data structures. This method is experimetnal.
          Use at your own risk.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> blobs[-2].mImg.show()
          >>> blobs[-2].rectifyToMajorAxis(1)
          >>> blobs[-2].mImg.show()
  
          """
          finalRotation = self.angle()
          w = self.minRectWidth()
          h = self.minRectHeight()
  
          if( w > h ):
              finalRotation = finalRotation
  
          if(axis > 0 ):
              finalRotation = finalRotation - 90
  
          self.rotate(finalRotation)
          return None
  
      def rotate(self,angle):
          """
          **SUMMARY**
  
          Rotate the blob given an  angle in degrees. If you use this method
          most of the blob elements will
          be rotated in place , however, this will "break" drawing back to the original image.
          To draw the blob create a new layer and draw to that layer. Positive rotations
          are counter clockwise.
  
          **PARAMETERS**
  
          * *angle* - A floating point angle in degrees. Positive is anti-clockwise.
  
          **RETURNS**
  
          .. Warning:
            Nothing. All rotations are performed in place. This modifies the blob's data
            and will break any image write back capabilities.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> blobs[-2].mImg.show()
          >>> blobs[-2].rotate(90)
          >>> blobs[-2].mImg.show()
  
          """
          #FIXME: This function should return a blob
          theta = 2*np.pi*(angle/360.0)
          mode = ""
          point =(self.x,self.y)
          self.mImg = self.mImg.rotate(angle,mode,point)
          self.mHullImg = self.mHullImg.rotate(angle,mode,point)
          self.mMask = self.mMask.rotate(angle,mode,point)
          self.mHullMask = self.mHullMask.rotate(angle,mode,point)
  
          self.mContour = map(lambda x:
                              (x[0]*np.cos(theta)-x[1]*np.sin(theta),
                               x[0]*np.sin(theta)+x[1]*np.cos(theta)),
                               self.mContour)
          self.mConvexHull = map(lambda x:
                                 (x[0]*np.cos(theta)-x[1]*np.sin(theta),
                                  x[0]*np.sin(theta)+x[1]*np.cos(theta)),
                                 self.mConvexHull)
  
          if( self.mHoleContour is not None):
              for h in self.mHoleContour:
                  h = map(lambda x:
                      (x[0]*np.cos(theta)-x[1]*np.sin(theta),
                       x[0]*np.sin(theta)+x[1]*np.cos(theta)),
                       h)
  
  
      def drawAppx(self, color = Color.HOTPINK,width=-1,alpha=-1,layer=None):
          if( self.mContourAppx is None or len(self.mContourAppx)==0 ):
              return
  
          if not layer:
              layer = self.image.dl()
  
          if width < 1:
              layer.polygon(self.mContourAppx,color,width,True,True,alpha)
          else:
              layer.polygon(self.mContourAppx,color,width,False,True,alpha)
  
      def draw(self, color = Color.GREEN, width=-1, alpha=-1, layer=None):
          """
          **SUMMARY**
  
          Draw the blob, in the given color, to the appropriate layer
  
          By default, this draws the entire blob filled in, with holes.  If you
          provide a width, an outline of the exterior and interior contours is drawn.
  
          **PARAMETERS**
  
          * *color* -The color to render the blob as a color tuple.
          * *alpha* - The alpha value of the rendered blob 0=transparent 255=opaque.
          * *width* - The width of the drawn blob in pixels, if -1 then filled then the polygon is filled.
          * *layer* - A source layer, if layer is not None, the blob is rendered to the layer versus the source image.
  
          **RETURNS**
  
          This method either works on the original source image, or on the drawing layer provided.
          The method does not modify object itself.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> blobs[-2].draw(color=Color.PUCE,width=-1,alpha=128)
          >>> img.show()
  
          """
          if not layer:
              layer = self.image.dl()
  
          if width == -1:
              #copy the mask into 3 channels and multiply by the appropriate color
              maskred = cv.CreateImage(cv.GetSize(self.mMask._getGrayscaleBitmap()), cv.IPL_DEPTH_8U, 1)
              maskgrn = cv.CreateImage(cv.GetSize(self.mMask._getGrayscaleBitmap()), cv.IPL_DEPTH_8U, 1)
              maskblu = cv.CreateImage(cv.GetSize(self.mMask._getGrayscaleBitmap()), cv.IPL_DEPTH_8U, 1)
  
              maskbit = cv.CreateImage(cv.GetSize(self.mMask._getGrayscaleBitmap()), cv.IPL_DEPTH_8U, 3)
  
              cv.ConvertScale(self.mMask._getGrayscaleBitmap(), maskred, color[0] / 255.0)
              cv.ConvertScale(self.mMask._getGrayscaleBitmap(), maskgrn, color[1] / 255.0)
              cv.ConvertScale(self.mMask._getGrayscaleBitmap(), maskblu, color[2] / 255.0)
  
              cv.Merge(maskblu, maskgrn, maskred, None, maskbit)
  
              masksurface = Image(maskbit).getPGSurface()
              masksurface.set_colorkey(Color.BLACK)
              if alpha != -1:
                  masksurface.set_alpha(alpha)
              layer._mSurface.blit(masksurface, self.topLeftCorner()) #KAT HERE
          else:
              self.drawOutline(color, alpha, width, layer)
              self.drawHoles(color, alpha, width, layer)
  
  
      def drawOutline(self, color=Color.GREEN, alpha=255, width=1, layer=None):
          """
          **SUMMARY**
  
          Draw the blob contour the provided layer -- if no layer is provided, draw
          to the source image.
  
  
          **PARAMETERS**
  
          * *color* - The color to render the blob.
          * *alpha* - The alpha value of the rendered poly.
          * *width* - The width of the drawn blob in pixels, -1 then the polygon is filled.
          * *layer* - if layer is not None, the blob is rendered to the layer versus the source image.
  
  
          **RETURNS**
  
          This method either works on the original source image, or on the drawing layer provided.
          The method does not modify object itself.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> blobs[-2].drawOutline(color=Color.GREEN,width=3,alpha=128)
          >>> img.show()
  
  
          """
  
          if( layer is None ):
              layer = self.image.dl()
  
          if( width < 0 ):
              #blit the blob in
              layer.polygon(self.mContour,color,filled=True,alpha=alpha)
          else:
              lastp = self.mContour[0] #this may work better.... than the other
              for nextp in self.mContour[1::]:
                  layer.line(lastp,nextp,color,width=width,alpha=alpha,antialias = False)
                  lastp = nextp
              layer.line(self.mContour[0],self.mContour[-1],color,width=width,alpha=alpha, antialias = False)
  
  
      def drawHoles(self, color=Color.GREEN, alpha=-1, width=-1, layer=None):
          """
          **SUMMARY**
  
          This method renders all of the holes (if any) that are present in the blob.
  
          **PARAMETERS**
  
          * *color* - The color to render the blob's holes.
          * *alpha* - The alpha value of the rendered blob hole.
          * *width* - The width of the drawn blob hole in pixels, if w=-1 then the polygon is filled.
          * *layer* - If layer is not None, the blob is rendered to the layer versus the source image.
  
          **RETURNS**
  
          This method either works on the original source image, or on the drawing layer provided.
          The method does not modify object itself.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs(128)
          >>> blobs[-1].drawHoles(color=Color.GREEN,width=3,alpha=128)
          >>> img.show()
  
          """
          if(self.mHoleContour is None):
              return
          if( layer is None ):
              layer = self.image.dl()
  
          if( width < 0 ):
              #blit the blob in
              for h in self.mHoleContour:
                  layer.polygon(h,color,filled=True,alpha=alpha)
          else:
              for h in self.mHoleContour:
                  lastp = h[0] #this may work better.... than the other
                  for nextp in h[1::]:
                      layer.line((int(lastp[0]),int(lastp[1])),(int(nextp[0]),int(nextp[1])),color,width=width,alpha=alpha,antialias = False)
                      lastp = nextp
                  layer.line(h[0],h[-1],color,width=width,alpha=alpha, antialias = False)
  
      def drawHull(self, color=Color.GREEN, alpha=-1, width=-1, layer=None ):
          """
          **SUMMARY**
  
          Draw the blob's convex hull to either the source image or to the
          specified layer given by layer.
  
          **PARAMETERS**
  
          * *color* - The color to render the blob's convex hull as an RGB triplet.
          * *alpha* - The alpha value of the rendered blob.
          * *width* - The width of the drawn blob in pixels, if w=-1 then the polygon is filled.
          * *layer* - if layer is not None, the blob is rendered to the layer versus the source image.
  
          **RETURNS**
  
          This method either works on the original source image, or on the drawing layer provided.
          The method does not modify object itself.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs(128)
          >>> blobs[-1].drawHull(color=Color.GREEN,width=3,alpha=128)
          >>> img.show()
  
          """
          if( layer is None ):
              layer = self.image.dl()
  
          if( width < 0 ):
              #blit the blob in
              layer.polygon(self.mConvexHull,color,filled=True,alpha=alpha)
          else:
              lastp = self.mConvexHull[0] #this may work better.... than the other
              for nextp in self.mConvexHull[1::]:
                  layer.line(lastp,nextp,color,width=width,alpha=alpha,antialias = False)
                  lastp = nextp
              layer.line(self.mConvexHull[0],self.mConvexHull[-1],color,width=width,alpha=alpha, antialias = False)
  
  
      #draw the actual pixels inside the contour to the layer
      def drawMaskToLayer(self, layer = None, offset=(0,0)):
          """
          **SUMMARY**
  
          Draw the actual pixels of the blob to another layer. This is handy if you
          want to examine just the pixels inside the contour.
  
          **PARAMETERS**
  
          * *layer* - A drawing layer upon which to apply the mask.
          * *offset* -  The offset from the top left corner where we want to place the mask.
  
          **RETURNS**
  
          This method either works on the original source image, or on the drawing layer provided.
          The method does not modify object itself.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs(128)
          >>> dl = DrawingLayer((img.width,img.height))
          >>> blobs[-1].drawMaskToLayer(layer = dl)
          >>> dl.show()
  
          """
          if( layer is not None ):
              layer = self.image.dl()
  
          mx = self.mBoundingBox[0]+offset[0]
          my = self.mBoundingBox[1]+offset[1]
          layer.blit(self.mImg,coordinates=(mx,my))
          return None
  
      def isSquare(self, tolerance = 0.05, ratiotolerance = 0.05):
          """
          **SUMMARY**
  
          Given a tolerance, test if the blob is a rectangle, and how close its
          bounding rectangle's aspect ratio is to 1.0.
  
          **PARAMETERS**
  
          * *tolerance* - A percentage difference between an ideal rectangle and our hull mask.
          * *ratiotolerance* - A percentage difference of the aspect ratio of our blob and an ideal square.
  
          **RETURNS**
  
          Boolean True if our object falls within tolerance, false otherwise.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs(128)
          >>> if(blobs[-1].isSquare() ):
          >>>     print "it is hip to be square."
  
          """
          if self.isRectangle(tolerance) and abs(1 - self.aspectRatio()) < ratiotolerance:
              return True
          return False
  
  
      def isRectangle(self, tolerance = 0.05):
          """
          **SUMMARY**
  
          Given a tolerance, test the blob against the rectangle distance to see if
          it is rectangular.
  
          **PARAMETERS**
  
          * *tolerance* - The percentage difference between our blob and its idealized bounding box.
  
          **RETURNS**
  
          Boolean True if the blob is withing the rectangle tolerage, false otherwise.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs(128)
          >>> if(blobs[-1].isRecangle() ):
          >>>     print "it is hip to be square."
  
          """
          if self.rectangleDistance() < tolerance:
              return True
          return False
  
      def rectangleDistance(self):
          """
          **SUMMARY**
  
          This compares the hull mask to the bounding rectangle.  Returns the area
          of the blob's hull as a fraction of the bounding rectangle.
  
          **RETURNS**
  
          The number of pixels in the blobs hull mask over the number of pixels in its bounding box.
  
          """
          blackcount, whitecount = self.mHullMask.histogram(2)
          return abs(1.0 - float(whitecount) / (self.minRectWidth() * self.minRectHeight()))
  
  
      def isCircle(self, tolerance = 0.05):
          """
          **SUMMARY**
  
          Test circle distance against a tolerance to see if the blob is circlular.
  
          **PARAMETERS**
  
          * *tolerance* - the percentage difference between our blob and an ideal circle.
  
          **RETURNS**
  
          True if the feature is within tolerance for being a circle, false otherwise.
  
          """
          if self.circleDistance() < tolerance:
              return True
          return False
  
      def circleDistance(self):
          """
          **SUMMARY**
  
          Compare the hull mask to an ideal circle and count the number of pixels
          that deviate as a fraction of total area of the ideal circle.
  
          **RETURNS**
  
          The difference, as a percentage, between the hull of our blob and an idealized
          circle of our blob.
  
          """
          w = self.mHullMask.width
          h = self.mHullMask.height
  
          idealcircle = Image((w,h))
          radius = min(w,h) / 2
          idealcircle.dl().circle((w/2, h/2), radius, filled= True, color=Color.WHITE)
          idealcircle = idealcircle.applyLayers()
          netdiff = (idealcircle - self.mHullMask) + (self.mHullMask - idealcircle)
          numblack, numwhite = netdiff.histogram(2)
          return float(numwhite) / (radius * radius * np.pi)
  
      def centroid(self):
          """
          **SUMMARY**
  
          Return the centroid (mass-determined center) of the blob. Note that this is differnt from the bounding box center.
  
          **RETURNS**
  
          An (x,y) tuple that is the center of mass of the blob.
  
          **EXAMPLE**
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> img.drawCircle((blobs[-1].x,blobs[-1].y),10,color=Color.RED)
          >>> img.drawCircle((blobs[-1].centroid()),10,color=Color.BLUE)
          >>> img.show()
  
          """
          return (self.m10 / self.m00, self.m01 / self.m00)
  
      def radius(self):
          """
          **SUMMARY**
  
          Return the radius, the avg distance of each contour point from the centroid
          """
          return float(np.mean(spsd.cdist(self.mContour, [self.centroid()])))
  
      def hullRadius(self):
          """
          **SUMMARY**
  
          Return the radius of the convex hull contour from the centroid
          """
          return float(np.mean(spsd.cdist(self.mConvexHull, [self.centroid()])))
  
      @LazyProperty
      def mImg(self):
          #NOTE THAT THIS IS NOT PERFECT - ISLAND WITH A LAKE WITH AN ISLAND WITH A LAKE STUFF
          retVal = cv.CreateImage((self.width(),self.height()),cv.IPL_DEPTH_8U,3)
          cv.Zero(retVal)
          bmp = self.image.getBitmap()
          mask = self.mMask.getBitmap()
          tl = self.topLeftCorner()
          cv.SetImageROI(bmp,(tl[0],tl[1], self.width(),self.height()))
          cv.Copy(bmp,retVal,mask)
          cv.ResetImageROI(bmp)
          return Image(retVal)
  
      @LazyProperty
      def mMask(self):
          # TODO: FIX THIS SO THAT THE INTERIOR CONTOURS GET SHIFTED AND DRAWN
  
          #Alas - OpenCV does not provide an offset in the fillpoly method for
          #the cv bindings (only cv2 -- which I am trying to avoid). Have to
          #manually do the offset for the ROI shift.
  
          retVal = cv.CreateImage((self.width(),self.height()),cv.IPL_DEPTH_8U,1)
          cv.Zero(retVal)
          l,t = self.topLeftCorner()
  
          # construct the exterior contour - these are tuples
  
          cv.FillPoly(retVal,[[(p[0] - l, p[1] - t) for p in self.mContour]],(255,255,255),8)
  
          #construct the hole contoursb
          holes = []
          if self.mHoleContour is not None:
              for h in self.mHoleContour: # -- these are lists
                  holes.append([(h2[0]-l,h2[1]-t) for h2 in h])
  
              cv.FillPoly(retVal,holes,(0,0,0),8)
          return Image(retVal)
  
  
      @LazyProperty
      def mHullImg(self):
          retVal = cv.CreateImage((self.width(),self .height()),cv.IPL_DEPTH_8U,3)
          cv.Zero(retVal)
          bmp = self.image.getBitmap()
          mask = self.mHullMask.getBitmap()
          tl = self.topLeftCorner()
          cv.SetImageROI(bmp,(tl[0],tl[1], self.width(),self.height()))
          cv.Copy(bmp,retVal,mask)
          cv.ResetImageROI(bmp)
          return Image(retVal)
  
  
      @LazyProperty
      def mHullMask(self):
          retVal = cv.CreateImage((self.width(),self.height()),cv.IPL_DEPTH_8U,3)
          cv.Zero(retVal)
          #Alas - OpenCV does not provide an offset in the fillpoly method for
          #the cv bindings (only cv2 -- which I am trying to avoid). Have to
          #manually do the offset for the ROI shift.
          thull = []
          l,t = self.topLeftCorner()
          cv.FillPoly(retVal,[[(p[0] - l, p[1] - t) for p in self.mConvexHull]],(255,255,255),8)
          return Image(retVal)
  
  
      def hullImage(self):
          """
          **SUMMARY**
  
          The convex hull of a blob is the shape that would result if you snapped a rubber band around
          the blob. So if you had the letter "C" as your blob the convex hull would be the letter "O."
          This method returns an image where the source image around the convex hull of the blob is copied
          ontop a black background.
  
          **RETURNS**
          Returns a SimpleCV Image of the convex hull, cropped to fit.
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> blobs[-1].hullImage().show()
  
          """
          return self.mHullImg
  
      def hullMask(self):
          """
          **SUMMARY**
  
          The convex hull of a blob is the shape that would result if you snapped a rubber band around
          the blob. So if you had the letter "C" as your blob the convex hull would be the letter "O."
          This method returns an image where the area of the convex hull is white and the rest of the image
          is black. This image is cropped to the size of the blob.
  
          **RETURNS**
  
          Returns a binary SimpleCV image of the convex hull mask, cropped to fit the blob.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> blobs = img.findBlobs()
          >>> blobs[-1].hullMask().show()
  
          """
          return self.mHullMask
  
      def blobImage(self):
          """
          **SUMMARY**
  
  