content: # SimpleCV Feature library
  #
  # Tools return basic features in feature sets
  # #    x = 0.00
  #     y = 0.00
  #     _mMaxX = None
  #     _mMaxY = None
  #     _mMinX = None
  #     _mMinY = None
  #     _mWidth = None
  #     _mHeight = None
  #     _mSrcImgW = None
  #     mSrcImgH = None
  
  
  #load system libraries
  from SimpleCV.base import *
  from SimpleCV.Color import *
  import copy
  
  
  class FeatureSet(list):
      """
      **SUMMARY**
  
      FeatureSet is a class extended from Python's list which has special functions so that it is useful for handling feature metadata on an image.
  
      In general, functions dealing with attributes will return numpy arrays, and functions dealing with sorting or filtering will return new FeatureSets.
  
      **EXAMPLE**
  
      >>> image = Image("/path/to/image.png")
      >>> lines = image.findLines()  #lines are the feature set
      >>> lines.draw()
      >>> lines.x()
      >>> lines.crop()
      """
      def __getitem__(self,key):
          """
          **SUMMARY**
  
          Returns a FeatureSet when sliced. Previously used to
          return list. Now it is possible to use FeatureSet member
          functions on sub-lists
  
          """
          if type(key) is types.SliceType: #Or can use 'try:' for speed
              return FeatureSet(list.__getitem__(self, key))
          else:
              return list.__getitem__(self,key)
  
      def __getslice__(self, i, j):
          """
          Deprecated since python 2.0, now using __getitem__
          """
          return self.__getitem__(slice(i,j))
  
      def count(self):
          '''
          This function returns the length / count of the all the items in the FeatureSet
          '''
  
          return len(self)
  
      def draw(self, color = Color.GREEN,width=1, autocolor = False, alpha=-1):
          """
          **SUMMARY**
  
          Call the draw() method on each feature in the FeatureSet.
  
          **PARAMETERS**
          
          * *color* - The color to draw the object. Either an BGR tuple or a member of the :py:class:`Color` class.
          * *width* - The width to draw the feature in pixels. A value of -1 usually indicates a filled region.
          * *autocolor* - If true a color is randomly selected for each feature.
          
  
          **RETURNS**
  
          Nada. Nothing. Zilch.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> feats = img.findBlobs()
          >>> feats.draw(color=Color.PUCE, width=3)
          >>> img.show()
  
          """
          for f in self:
              if(autocolor):
                  color = Color().getRandom()
              if alpha != -1:
                  f.draw(color=color,width=width,alpha=alpha)
              else:
                  f.draw(color=color,width=width)
  
      def show(self, color = Color.GREEN, autocolor = False,width=1):
          """
          **EXAMPLE**
  
          This function will automatically draw the features on the image and show it.
          It is a basically a shortcut function for development and is the same as:
  
          **PARAMETERS**
  
          * *color* - The color to draw the object. Either an BGR tuple or a member of the :py:class:`Color` class.
          * *width* - The width to draw the feature in pixels. A value of -1 usually indicates a filled region.
          * *autocolor* - If true a color is randomly selected for each feature.
  
          **RETURNS**
  
          Nada. Nothing. Zilch.
  
  
          **EXAMPLE**
          >>> img = Image("logo")
          >>> feat = img.findBlobs()
          >>> if feat: feat.draw()
          >>> img.show()
  
          """
          self.draw(color, width, autocolor)
          self[-1].image.show()
  
  
      def reassignImage(self, newImg):
          """
          **SUMMARY**
  
          Return a new featureset where the features are assigned to a new image.
  
          **PARAMETERS**
  
          * *img* - the new image to which to assign the feature.
  
          .. Warning::
            THIS DOES NOT PERFORM A SIZE CHECK. IF YOUR NEW IMAGE IS NOT THE EXACT SAME SIZE YOU WILL CERTAINLY CAUSE ERRORS.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> img2 = img.invert()
          >>> l = img.findLines()
          >>> l2 = img.reassignImage(img2)
          >>> l2.show()
  
          """
          retVal = FeatureSet()
          for i in self:
              retVal.append(i.reassign(newImg))
          return retVal
  
      def x(self):
          """
          **SUMMARY**
  
          Returns a numpy array of the x (horizontal) coordinate of each feature.
  
          **RETURNS**
  
          A numpy array.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> feats = img.findBlobs()
          >>> xs = feats.x()
          >>> print xs
  
          """
          return np.array([f.x for f in self])
  
      def y(self):
          """
          **SUMMARY**
  
          Returns a numpy array of the y (vertical) coordinate of each feature.
  
          **RETURNS**
  
          A numpy array.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> feats = img.findBlobs()
          >>> xs = feats.y()
          >>> print xs
  
          """
          return np.array([f.y for f in self])
  
      def coordinates(self):
          """
          **SUMMARY**
  
          Returns a 2d numpy array of the x,y coordinates of each feature.  This
          is particularly useful if you want to use Scipy's Spatial Distance module
  
          **RETURNS**
  
          A numpy array of all the positions in the featureset.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> feats = img.findBlobs()
          >>> xs = feats.coordinates()
          >>> print xs
  
  
          """
          return np.array([[f.x, f.y] for f in self])
  
      def center(self):
          return self.coordinates()
  
      def area(self):
          """
          **SUMMARY**
  
          Returns a numpy array of the area of each feature in pixels.
  
          **RETURNS**
  
          A numpy array of all the positions in the featureset.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> feats = img.findBlobs()
          >>> xs = feats.area()
          >>> print xs
  
          """
          return np.array([f.area() for f in self])
  
      def sortArea(self):
          """
          **SUMMARY**
  
          Returns a new FeatureSet, with the largest area features first.
  
          **RETURNS**
  
          A featureset sorted based on area.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> feats = img.findBlobs()
          >>> feats = feats.sortArea()
          >>> print feats[-1] # biggest blob
          >>> print feats[0] # smallest blob
  
          """
          return FeatureSet(sorted(self, key = lambda f: f.area()))
  
      def sortX(self):
          """
          **SUMMARY**
  
          Returns a new FeatureSet, with the smallest x coordinates features first.
  
          **RETURNS**
  
          A featureset sorted based on area.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> feats = img.findBlobs()
          >>> feats = feats.sortX()
          >>> print feats[-1] # biggest blob
          >>> print feats[0] # smallest blob
  
          """
          return FeatureSet(sorted(self, key = lambda f: f.x))
  
      def sortY(self):
          """
          **SUMMARY**
  
          Returns a new FeatureSet, with the smallest y coordinates features first.
  
          **RETURNS**
  
          A featureset sorted based on area.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> feats = img.findBlobs()
          >>> feats = feats.sortY()
          >>> print feats[-1] # biggest blob
          >>> print feats[0] # smallest blob
  
          """
          return FeatureSet(sorted(self, key = lambda f: f.y)) 
  
      def distanceFrom(self, point = (-1, -1)):
          """
          **SUMMARY**
  
          Returns a numpy array of the distance each Feature is from a given coordinate.
          Default is the center of the image.
  
          **PARAMETERS**
  
          * *point* - A point on the image from which we will calculate distance.
  
          **RETURNS**
  
          A numpy array of distance values.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> feats = img.findBlobs()
          >>> d = feats.distanceFrom()
          >>> d[0]  #show the 0th blobs distance to the center.
  
          **TO DO**
  
          Make this accept other features to measure from.
  
          """
          if (point[0] == -1 or point[1] == -1 and len(self)):
              point = self[0].image.size()
  
          return spsd.cdist(self.coordinates(), [point])[:,0]
  
      def sortDistance(self, point = (-1, -1)):
          """
          **SUMMARY**
  
          Returns a sorted FeatureSet with the features closest to a given coordinate first.
          Default is from the center of the image.
  
          **PARAMETERS**
  
          * *point* - A point on the image from which we will calculate distance.
  
          **RETURNS**
  
          A numpy array of distance values.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> feats = img.findBlobs()
          >>> d = feats.sortDistance()
          >>> d[-1].show()  #show the 0th blobs distance to the center.
  
  
          """
          return FeatureSet(sorted(self, key = lambda f: f.distanceFrom(point)))
  
      def distancePairs(self):
          """
          **SUMMARY**
  
          Returns the square-form of pairwise distances for the featureset.
          The resulting N x N array can be used to quickly look up distances
          between features.
  
          **RETURNS**
  
          A NxN np matrix of distance values.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> feats = img.findBlobs()
          >>> d = feats.distancePairs()
          >>> print d
  
          """
          return spsd.squareform(spsd.pdist(self.coordinates()))
  
      def angle(self):
          """
          **SUMMARY**
  
          Return a numpy array of the angles (theta) of each feature.
          Note that theta is given in degrees, with 0 being horizontal.
  
          **RETURNS**
  
          An array of angle values corresponding to the features.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> l = img.findLines()
          >>> angs = l.angle()
          >>> print angs
  
  
          """
          return np.array([f.angle() for f in self])
  
      def sortAngle(self, theta = 0):
          """
          Return a sorted FeatureSet with the features closest to a given angle first.
          Note that theta is given in radians, with 0 being horizontal.
  
          **RETURNS**
  
          An array of angle values corresponding to the features.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> l = img.findLines()
          >>> l = l.sortAngle()
          >>> print angs
  
          """
          return FeatureSet(sorted(self, key = lambda f: abs(f.angle() - theta)))
  
      def length(self):
          """
          **SUMMARY**
  
          Return a numpy array of the length (longest dimension) of each feature.
  
          **RETURNS**
  
          A numpy array of the length, in pixels, of eatch feature object.
  
          **EXAMPLE**
  
          >>> img = Image("Lenna")
          >>> l = img.findLines()
          >>> lengt = l.length()
          >>> lengt[0] # length of the 0th element.
  
          """
  
          return np.array([f.length() for f in self])
  
      def sortLength(self):
          """
          **SUMMARY**
  
          Return a sorted FeatureSet with the longest features first.
  
          **RETURNS**
  
          A sorted FeatureSet.
  
          **EXAMPLE**
  
          >>> img = Image("Lenna")
          >>> l = img.findLines().sortLength()
          >>> lengt[-1] # length of the 0th element.
  
          """
          return FeatureSet(sorted(self, key = lambda f: f.length()))
  
      def meanColor(self):
          """
          **SUMMARY**
  
          Return a numpy array of the average color of the area covered by each Feature.
  
          **RETURNS**
  
          Returns an array of RGB triplets the correspond to the mean color of the feature.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> kp = img.findKeypoints()
          >>> c = kp.meanColor()
  
  
          """
          return np.array([f.meanColor() for f in self])
  
      def colorDistance(self, color = (0, 0, 0)):
          """
          **SUMMARY**
  
          Return a numpy array of the distance each features average color is from
          a given color tuple (default black, so colorDistance() returns intensity)
  
          **PARAMETERS**
  
          * *color* - The color to calculate the distance from.
  
          **RETURNS**
  
          The distance of the average color for the feature from given color as a numpy array.
  
          **EXAMPLE**
  
          >>> img = Image("lenna")
          >>> circs = img.findCircle()
          >>> d = circs.colorDistance(color=Color.BLUE)
          >>> print d
  
          """
          return spsd.cdist(self.meanColor(), [color])[:,0]
  
      def sortColorDistance(self, color = (0, 0, 0)):
          """
          Return a sorted FeatureSet with features closest to a given color first.
          Default is black, so sortColorDistance() will return darkest to brightest
          """
          return FeatureSet(sorted(self, key = lambda f: f.colorDistance(color)))
  
      def filter(self, filterarray):
          """
          **SUMMARY**
  
          Return a FeatureSet which is filtered on a numpy boolean array.  This
          will let you use the attribute functions to easily screen Features out
          of return FeatureSets.
  
          **PARAMETERS**
  
          * *filterarray* - A numpy array, matching  the size of the feature set,
            made of Boolean values, we return the true values and reject the False value.
  
          **RETURNS**
  
          The revised feature set.
  
          **EXAMPLE**
  
          Return all lines < 200px
  
          >>> my_lines.filter(my_lines.length() < 200) # returns all lines < 200px
          >>> my_blobs.filter(my_blobs.area() > 0.9 * my_blobs.length**2) # returns blobs that are nearly square
          >>> my_lines.filter(abs(my_lines.angle()) < numpy.pi / 4) #any lines within 45 degrees of horizontal
          >>> my_corners.filter(my_corners.x() - my_corners.y() > 0) #only return corners in the upper diagonal of the image
  
          """
          return FeatureSet(list(np.array(self)[np.array(filterarray)]))
  
      def width(self):
          """
          **SUMMARY**
  
          Returns a nparray which is the width of all the objects in the FeatureSet.
  
          **RETURNS**
  
          A numpy array of width values.
  
  
          **EXAMPLE**
  
          >>> img = Image("NotLenna")
          >>> l = img.findLines()
          >>> l.width()
  
          """
          return np.array([f.width() for f in self])
  
      def height(self):
          """
          Returns a nparray which is the height of all the objects in the FeatureSet
  
          **RETURNS**
  
          A numpy array of width values.
  
  
          **EXAMPLE**
  
          >>> img = Image("NotLenna")
          >>> l = img.findLines()
          >>> l.height()
  
          """
          return np.array([f.height() for f in self])
  
      def crop(self):
          """
          **SUMMARY**
  
          Returns a nparray with the cropped features as SimpleCV image.
  
          **RETURNS**
  
          A SimpleCV image cropped to each image.
  
          **EXAMPLE**
  
          >>> img = Image("Lenna")
          >>> blobs = img.findBlobs(128)
          >>> for b in blobs:
          >>>   newImg = b.crop()
          >>>   newImg.show()
          >>>   time.sleep(1)
  
          """
          return np.array([f.crop() for f in self])
  
      def inside(self,region):
          """
          **SUMMARY**
  
          Return only the features inside the region. where region can be a bounding box,
          bounding circle, a list of tuples in a closed polygon, or any other featutres.
  
          **PARAMETERS**
  
          * *region*
  
            * A bounding box - of the form (x,y,w,h) where x,y is the upper left corner
            * A bounding circle of the form (x,y,r)
            * A list of x,y tuples defining a closed polygon e.g. ((x,y),(x,y),....)
            * Any two dimensional feature (e.g. blobs, circle ...)
  
          **RETURNS**
  
          Returns a featureset of features that are inside the region.
  
          **EXAMPLE**
  
          >>> img = Image("Lenna")
          >>> blobs = img.findBlobs()
          >>> b = blobs[-1]
          >>> lines = img.findLines()
          >>> inside = lines.inside(b)
  
          **NOTE**
  
          This currently performs a bounding box test, not a full polygon test for speed.
  
  
          """
          fs = FeatureSet()
          for f in self:
              if(f.isContainedWithin(region)):
                  fs.append(f)
          return fs
  
  
      def outside(self,region):
          """
          **SUMMARY**
  
          Return only the features outside the region. where region can be a bounding box,
          bounding circle, a list of tuples in a closed polygon, or any other featutres.
  
          **PARAMETERS**
  
          * *region*
  
            * A bounding box - of the form (x,y,w,h) where x,y is the upper left corner
            * A bounding circle of the form (x,y,r)
            * A list of x,y tuples defining a closed polygon e.g. ((x,y),(x,y),....)
            * Any two dimensional feature (e.g. blobs, circle ...)
  
          **RETURNS**
  
          Returns a featureset of features that are outside the region.
  
  
          **EXAMPLE**
  
          >>> img = Image("Lenna")
          >>> blobs = img.findBlobs()
          >>> b = blobs[-1]
          >>> lines = img.findLines()
          >>> outside = lines.outside(b)
  
          **NOTE**
  
          This currently performs a bounding box test, not a full polygon test for speed.
  
          """
          fs = FeatureSet()
          for f in self:
              if(f.isNotContainedWithin(region)):
                  fs.append(f)
          return fs
  
      def overlaps(self,region):
          """
          **SUMMARY**
  
          Return only the features that overlap or the region. Where region can be a bounding box,
          bounding circle, a list of tuples in a closed polygon, or any other featutres.
  
          **PARAMETERS**
  
          * *region*
  
            * A bounding box - of the form (x,y,w,h) where x,y is the upper left corner
            * A bounding circle of the form (x,y,r)
            * A list of x,y tuples defining a closed polygon e.g. ((x,y),(x,y),....)
            * Any two dimensional feature (e.g. blobs, circle ...)
  
          **RETURNS**
  
          Returns a featureset of features that overlap the region.
  
          **EXAMPLE**
  
          >>> img = Image("Lenna")
          >>> blobs = img.findBlobs()
          >>> b = blobs[-1]
          >>> lines = img.findLines()
          >>> outside = lines.overlaps(b)
  
          **NOTE**
  
          This currently performs a bounding box test, not a full polygon test for speed.
  
          """
          fs = FeatureSet()
          for f in self:
              if( f.overlaps(region) ):
                  fs.append(f)
          return fs
  
      def above(self,region):
          """
          **SUMMARY**
  
          Return only the features that are above a  region. Where region can be a bounding box,
          bounding circle, a list of tuples in a closed polygon, or any other featutres.
  
          **PARAMETERS**
  
          * *region*
  
            * A bounding box - of the form (x,y,w,h) where x,y is the upper left corner
            * A bounding circle of the form (x,y,r)
            * A list of x,y tuples defining a closed polygon e.g. ((x,y),(x,y),....)
            * Any two dimensional feature (e.g. blobs, circle ...)
  
          **RETURNS**
  
          Returns a featureset of features that are above the region.
  
          **EXAMPLE**
  
          >>> img = Image("Lenna")
          >>> blobs = img.findBlobs()
          >>> b = blobs[-1]
          >>> lines = img.findLines()
          >>> outside = lines.above(b)
  
          **NOTE**
  
          This currently performs a bounding box test, not a full polygon test for speed.
  
          """
          fs = FeatureSet()
          for f in self:
              if(f.above(region)):
                  fs.append(f)
          return fs
  
      def below(self,region):
          """
          **SUMMARY**
  
          Return only the features below the region. where region can be a bounding box,
          bounding circle, a list of tuples in a closed polygon, or any other featutres.
  
          **PARAMETERS**
  
          * *region*
  
            * A bounding box - of the form (x,y,w,h) where x,y is the upper left corner
            * A bounding circle of the form (x,y,r)
            * A list of x,y tuples defining a closed polygon e.g. ((x,y),(x,y),....)
            * Any two dimensional feature (e.g. blobs, circle ...)
  
          **RETURNS**
  
          Returns a featureset of features that are below the region.
  
          **EXAMPLE**
  
          >>> img = Image("Lenna")
          >>> blobs = img.findBlobs()
          >>> b = blobs[-1]
          >>> lines = img.findLines()
          >>> inside = lines.below(b)
  
          **NOTE**
  
          This currently performs a bounding box test, not a full polygon test for speed.
  
          """
          fs = FeatureSet()
          for f in self:
              if(f.below(region)):
                  fs.append(f)
          return fs
  
      def left(self,region):
          """
          **SUMMARY**
  
          Return only the features left of the region. where region can be a bounding box,
          bounding circle, a list of tuples in a closed polygon, or any other featutres.
  
          **PARAMETERS**
  
          * *region*
  
            * A bounding box - of the form (x,y,w,h) where x,y is the upper left corner
            * A bounding circle of the form (x,y,r)
            * A list of x,y tuples defining a closed polygon e.g. ((x,y),(x,y),....)
            * Any two dimensional feature (e.g. blobs, circle ...)
  
          **RETURNS**
  
          Returns a featureset of features that are left of the region.
  
          **EXAMPLE**
  
          >>> img = Image("Lenna")
          >>> blobs = img.findBlobs()
          >>> b = blobs[-1]
          >>> lines = img.findLines()
          >>> left = lines.left(b)
  
          **NOTE**
  
          This currently performs a bounding box test, not a full polygon test for speed.
  
          """
          fs = FeatureSet()
          for f in self:
              if(f.left(region)):
                  fs.append(f)
          return fs
  
      def right(self,region):
          """
          **SUMMARY**
  
          Return only the features right of the region. where region can be a bounding box,
          bounding circle, a list of tuples in a closed polygon, or any other featutres.
  
          **PARAMETERS**
  
          * *region*
  
            * A bounding box - of the form (x,y,w,h) where x,y is the upper left corner
            * A bounding circle of the form (x,y,r)
            * A list of x,y tuples defining a closed polygon e.g. ((x,y),(x,y),....)
            * Any two dimensional feature (e.g. blobs, circle ...)
  
          **RETURNS**
  
          Returns a featureset of features that are right of the region.
  
          **EXAMPLE**
  
          >>> img = Image("Lenna")
          >>> blobs = img.findBlobs()
          >>> b = blobs[-1]
          >>> lines = img.findLines()
          >>> right = lines.right(b)
  
          **NOTE**
  
          This currently performs a bounding box test, not a full polygon test for speed.
  
          """
          fs = FeatureSet()
          for f in self:
              if(f.right(region)):
                  fs.append(f)
          return fs
  
      def onImageEdge(self, tolerance=1):
          """
          **SUMMARY**
  
          The method returns a feature set of features that are on or "near" the edge of
          the image. This is really helpful for removing features that are edge effects.
  
          **PARAMETERS**
  
          * *tolerance* - the distance in pixels from the edge at which a feature
            qualifies as being "on" the edge of the image.
  
          **RETURNS**
  
          Returns a featureset of features that are on the edge of the image.
  
          **EXAMPLE**
  
          >>> img = Image("./sampleimages/EdgeTest1.png")
          >>> blobs = img.findBlobs()
          >>> es = blobs.onImageEdge()
          >>> es.draw(color=Color.RED)
          >>> img.show()
  
          """
          fs = FeatureSet()
          for f in self:
              if(f.onImageEdge(tolerance)):
                  fs.append(f)
          return fs
  
      def notOnImageEdge(self, tolerance=1):
          """
          **SUMMARY**
  
          The method returns a feature set of features that are not on or "near" the edge of
          the image. This is really helpful for removing features that are edge effects.
  
          **PARAMETERS**
  
          * *tolerance* - the distance in pixels from the edge at which a feature
            qualifies as being "on" the edge of the image.
  
          **RETURNS**
  
          Returns a featureset of features that are not on the edge of the image.
  
          **EXAMPLE**
  
          >>> img = Image("./sampleimages/EdgeTest1.png")
          >>> blobs = img.findBlobs()
          >>> es = blobs.notOnImageEdge()
          >>> es.draw(color=Color.RED)
          >>> img.show()
  
          """
          fs = FeatureSet()
          for f in self:
              if(f.notOnImageEdge(tolerance)):
                  fs.append(f)
          return fs
  
  
      def topLeftCorners(self):
          """
          **SUMMARY**
  
          This method returns the top left corner of each feature's bounding box.
  
          **RETURNS**
  
          A numpy array of x,y position values.
  
          **EXAMPLE**
  
          >>> img = Image("./sampleimages/EdgeTest1.png")
          >>> blobs = img.findBlobs()
          >>> tl = img.topLeftCorners()
          >>> print tl[0]
          """
          return np.array([f.topLeftCorner() for f in self])
  
  
  
      def bottomLeftCorners(self):
          """
          **SUMMARY**
  
          This method returns the bottom left corner of each feature's bounding box.
  
          **RETURNS**
  
          A numpy array of x,y position values.
  
          **EXAMPLE**
  
          >>> img = Image("./sampleimages/EdgeTest1.png")
          >>> blobs = img.findBlobs()
          >>> bl = img.bottomLeftCorners()
          >>> print bl[0]
  
          """
          return np.array([f.bottomLeftCorner() for f in self])
  
      def topLeftCorners(self):
          """
          **SUMMARY**
  
          This method returns the top left corner of each feature's bounding box.
  
          **RETURNS**
  
          A numpy array of x,y position values.
  
          **EXAMPLE**
  
          >>> img = Image("./sampleimages/EdgeTest1.png")
          >>> blobs = img.findBlobs()
          >>> tl = img.bottomLeftCorners()
          >>> print tl[0]
  
          """
          return np.array([f.topLeftCorner() for f in self])
  
  
      def topRightCorners(self):
          """
          **SUMMARY**
  
          This method returns the top right corner of each feature's bounding box.
  
          **RETURNS**
  
          A numpy array of x,y position values.
  
          **EXAMPLE**
  
          >>> img = Image("./sampleimages/EdgeTest1.png")
          >>> blobs = img.findBlobs()
          >>> tr = img.topRightCorners()
          >>> print tr[0]
  
          """
          return np.array([f.topRightCorner() for f in self])
  
  
  
      def bottomRightCorners(self):
          """
          **SUMMARY**
  
          This method returns the bottom right corner of each feature's bounding box.
  
          **RETURNS**
  
          A numpy array of x,y position values.
  
          **EXAMPLE**
  
          >>> img = Image("./sampleimages/EdgeTest1.png")
          >>> blobs = img.findBlobs()
          >>> br = img.bottomRightCorners()
          >>> print br[0]
  
          """
          return np.array([f.bottomRightCorner() for f in self])
  
      def aspectRatios(self):
          """
          **SUMMARY**
  
          Return the aspect ratio of all the features in the feature set, For our purposes
          aspect ration is max(width,height)/min(width,height).
  
          **RETURNS**
  
          A numpy array of the aspect ratio of the features in the featureset.
  
          **EXAMPLE**
  
          >>> img = Image("OWS.jpg")
          >>> blobs = img.findBlobs(128)
          >>> print blobs.aspectRatio()
  
          """
          return np.array([f.aspectRatio() for f in self])
  
      def cluster(self,method="kmeans",properties=None,k=3):
          """
          
          **SUMMARY**
  
          This function clusters the blobs in the featureSet based on the properties. Properties can be "color", "shape" or "position" of blobs.
          Clustering is done using K-Means or Hierarchical clustering(Ward) algorithm.
  
          **PARAMETERS**
          
          * *properties* - It should be a list with any combination of "color", "shape", "position". properties = ["color","position"]. properties = ["position","shape"]. properties = ["shape"]
          * *method* - if method is "kmeans", it will cluster using K-Means algorithm, if the method is "hierarchical", no need to spicify the number of clusters
          * *k* - The number of clusters(kmeans).
          
  
          **RETURNS**
  
          A list of featureset, each being a cluster itself.
  
          **EXAMPLE**
  
            >>> img = Image("lenna")
            >>> blobs = img.findBlobs()
            >>> clusters = blobs.cluster(method="kmeans",properties=["color"],k=5)
            >>> for i in clusters:
            >>>     i.draw(color=Color.getRandom(),width=5)
            >>> img.show()
          
          """
          try :
              from sklearn.cluster import KMeans, Ward
              from sklearn import __version__
          except :
              logger.warning("install scikits-learning package")
              return
          X = [] #List of feature vector of each blob
          if not properties:
              properties = ['color','shape','position']
          if k > len(self):
              logger.warning("Number of clusters cannot be greater then the number of blobs in the featureset")
              return
          for i in self:
              featureVector = []
              if 'color' in properties:
                  featureVector.extend(i.mAvgColor)
              if 'shape' in properties:
                  featureVector.extend(i.mHu)
              if 'position' in properties:
                  featureVector.extend(i.extents())
              if not featureVector :
                  logger.warning("properties parameter is not specified properly")
                  return
              X.append(featureVector)
  
          if method == "kmeans":
              
              # Ignore minor version numbers.
              sklearn_version = re.search(r'\d+\.\d+', __version__).group()
              
              if (float(sklearn_version) > 0.11):
                  k_means = KMeans(init='random', n_clusters=k, n_init=10).fit(X)
              else:
                  k_means = KMeans(init='random', k=k, n_init=10).fit(X)
              KClusters = [ FeatureSet([]) for i in range(k)]
              for i in range(len(self)):
                  KClusters[k_means.labels_[i]].append(self[i])
              return KClusters
  
          if method == "hierarchical":
              ward = Ward(n_clusters=int(sqrt(len(self)))).fit(X) #n_clusters = sqrt(n)
              WClusters = [ FeatureSet([]) for i in range(int(sqrt(len(self))))]
              for i in range(len(self)):
                  WClusters[ward.labels_[i]].append(self[i])
              return WClusters
  
      @property
      def image(self):
          if not len(self):
              return None
          return self[0].image
  
      @image.setter
      def image(self, i):
          for f in self:
              f.image = i
  
  ### ----------------------------------------------------------------------------
  ### ----------------------------------------------------------------------------
  ### ----------------------------FEATURE CLASS-----------------------------------
  ### ----------------------------------------------------------------------------
  ### ----------------------------------------------------------------------------
  class Feature(object):
      """
      **SUMMARY**
  
      The Feature object is an abstract class which real features descend from.
      Each feature object has:
  
      * a draw() method,
      * an image property, referencing the originating Image object
      * x and y coordinates
      * default functions for determining angle, area, meanColor, etc for FeatureSets
      * in the Feature class, these functions assume the feature is 1px
  
      """
      x = 0.00
      y = 0.00
      _mMaxX = None
      _mMaxY = None
      _mMinX = None
      _mMinY = None
      _mWidth = None
      _mHeight = None
      _mSrcImgW = None
      _mSrcImgH = None
  
      # This is 2.0 refactoring
      mBoundingBox = None # THIS SHALT BE TOP LEFT (X,Y) THEN W H i.e. [X,Y,W,H]
      mExtents = None # THIS SHALT BE [MAXX,MINX,MAXY,MINY]
      points = None  # THIS SHALT BE (x,y) tuples in the ORDER [(TopLeft),(TopRight),(BottomLeft),(BottomRight)]
  
      image = "" #parent image
      #points = []
      #boundingBox = []
  
      def __init__(self, i, at_x, at_y, points):
          #THE COVENANT IS THAT YOU PROVIDE THE POINTS IN THE SPECIFIED FORMAT AND ALL OTHER VALUES SHALT FLOW
          self.x = at_x
          self.y = at_y
          self.image = i
          self.points = points
          self._updateExtents(new_feature=True)
  
      def reassign(self, img):
          """
          **SUMMARY**
  
          Reassign the image of this feature and return an updated copy of the feature.
  
          **PARAMETERS**
  
          * *img* - the new image to which to assign the feature.
  
          .. Warning::
            THIS DOES NOT PERFORM A SIZE CHECK. IF YOUR NEW IMAGE IS NOT THE EXACT SAME SIZE YOU WILL CERTAINLY CAUSE ERRORS.
  
          **EXAMPLE**
  
  