def makeContours(xcoordinates,ycoordinates,width,height,binsize): 

    getLngLat = utilities.makeGetLngLat(metadata)
    getMeters = utilities.makeGetMeters(metadata)

    # make the 2d histogram
    clusterdata, xedges, yedges = numpy.histogram2d(xcoordinates, ycoordinates, bins=(int(width/binsize), int(height/binsize)), range=((0, width), (0, height)))
    if len(xcoordinates) == 0:
        clusterdata = numpy.zeros((int(width/binsize), int(height/binsize)), dtype=numpy.dtype(float))

    # make contours for the three levels; the contour polygons are expressed in pixel-index coordinates (not lng/lat or meters)
    contoursMin = utilities.contours(clusterdata, xedges, yedges, 0.5, interpolate=True, smooth=True)
    cutLevel50 = utilities.cutLevel(clusterdata, 50.0)
    contours50 = utilities.contours(clusterdata, xedges, yedges, cutLevel50, interpolate=True, smooth=True)
    cutLevel95 = utilities.cutLevel(clusterdata, 95.0)
    contours95 = utilities.contours(clusterdata, xedges, yedges, cutLevel95, interpolate=True, smooth=True)

    # construct output data that includes the polygons in lng,lat coordinates, circumferences in meters, and areas in meters^2
    clusterData = {"contoursMin": [], "contours50": [], "contours95": [],
                        "numberOfLabeledPixels": len(xcoordinates), "cutLevel50": cutLevel50, "cutLevel95": cutLevel95}

    for polygon in contoursMin:

        lnglatPolygon = utilities.convert(polygon, getLngLat)
        metersPolygon = utilities.convert(lnglatPolygon, getMeters)

        data = {"rowcolpolygon": polygon, "lnglatpolygon": lnglatPolygon, "areaInPixels": numpy.abs(utilities.area(polygon)), "circumferenceInMeters": numpy.abs(utilities.circumference(metersPolygon)), "areaInMeters": numpy.abs(utilities.area(metersPolygon)), "centroidInLngLat": utilities.centroid(lnglatPolygon)}
        clusterData["contoursMin"].append(data)

    for polygon in contours50:

        lnglatPolygon = utilities.convert(polygon, getLngLat)
        metersPolygon = utilities.convert(lnglatPolygon, getMeters)

        data = {"rowcolpolygon": polygon, "lnglatpolygon": lnglatPolygon, "areaInPixels": numpy.abs(utilities.area(polygon)), "circumferenceInMeters": numpy.abs(utilities.circumference(metersPolygon)), "areaInMeters": numpy.abs(utilities.area(metersPolygon)), "centroidInLngLat": utilities.centroid(lnglatPolygon)}
        clusterData["contours50"].append(data)

    for polygon in contours95:

        lnglatPolygon = utilities.convert(polygon, getLngLat)
        metersPolygon = utilities.convert(lnglatPolygon, getMeters)

        data = {"rowcolpolygon": polygon, "lnglatpolygon": lnglatPolygon, "areaInPixels": numpy.abs(utilities.area(polygon)), "circumferenceInMeters": numpy.abs(utilities.circumference(metersPolygon)), "areaInMeters": numpy.abs(utilities.area(metersPolygon)), "centroidInLngLat": utilities.centroid(lnglatPolygon)}
        clusterData["contours95"].append(data)

    return clusterData
Ejemplo n.º 2
0
        alphadata = numpy.reshape(alphadata, rgbDouble.shape[1] * rgbDouble.shape[2])

        arrayToPng = ArrayToPng()
        arrayToPng.putdata(rgbDouble.shape[1], rgbDouble.shape[2], reddata, greendata, bluedata, alphadata, flipy=False, onePixelBeyondBorder=False)

        attrib = {"version": "1.1", "width": "100%", "height": "100%", "preserveAspectRatio": "xMidYMin meet"}
        attrib["viewBox"] = "0 0 %d %d" % (rgbDouble.shape[2], rgbDouble.shape[1])
        attrib["style"] = "fill: none; stroke: black; stroke-linejoin: miter; stroke-width: 2; text-anchor: middle;"
        attrib["font-family"] = "DejaVu Sans Condensed, DejaVu Sans, Lucida Sans Unicode, Lucida Sans, Helvetica, Sans, sans-serif"
        attrib["font-weight"] = "normal"

        imagePlot = SVG.svg(SVG.g(), **attrib)
        imageGroup = imagePlot[0]
        imageGroup.append(SVG.image(**{defs.XLINK_HREF: "data:image/png;base64," + arrayToPng.b64encode(), "x": "0", "y": "0", "width": repr(rgbDouble.shape[1]), "height": repr(rgbDouble.shape[2])}))

    getLngLat = utilities.makeGetLngLat(imageValue["metadata"])
    wavelengths = imageValue["metadata"]["bandWavelength"]
    bandNames = imageValue["metadata"]["bandNames"]

    tableRows = []
    for clusterKey in sorted(imageValue):
        if clusterKey != "metadata":
            cluster = imageValue[clusterKey]["contours95"][0]
            clump = cluster["other"]
            selectedObjects.append(cluster)

            if makingImage:
                thePath = ["L{},{}".format(x, y) for x, y in cluster["rowcolpolygon"]]
                thePath[0] = "M" + thePath[0][1:]
                thePath.append("Z")
                imageGroup.append(SVG.path(d=" ".join(thePath), style="stroke: #ff0000; stroke-width: 1.5; line-join: round;"))
Ejemplo n.º 3
0
def main(metadata,mask,imgArray):


    imgMean = numpy.mean(imgArray,axis=0)                           # find the mean of each band

    imgCov = numpy.cov(imgArray.T - imgMean.reshape(-1,1))          # Find covariance
                                                    
    imgPval, imgPvec = numpy.linalg.eig(imgCov)   # The eigenvalues and eigenvectors of the covariance matrix
    indexList = numpy.argsort(-imgPval)           # Sort principal components
    imgPval = imgPval[indexList]
    imgPvec = imgPvec[:,indexList]        

    imgProj = numpy.dot(imgPvec.T,imgArray.T - imgMean.reshape(-1,1)) # Projection of original data on PCs
    imgMdist = numpy.sum(numpy.square(imgProj.T) / imgPval,axis=1)    # Mahalanobis distance

    # sort distances and select top k1 std devs above
    # take k1 = 6
    # Assume distances have log-normal distribution,
    # so find k1-sigma extreme point in log distribution, and transform back

    k1 = 6
    lmean = numpy.mean(numpy.log(imgMdist))
    lsd = numpy.std(numpy.log(imgMdist))
    npix = numpy.size(imgMdist)
    dtol = numpy.exp(lmean + k1*lsd)
    imgFlag = (imgMdist > dtol)
    nsubpix = numpy.sum(imgFlag)

    raredict = {}
    if nsubpix > 2:
       
        # Similarity matrix (cosine)
        imgNorm = numpy.sqrt(numpy.sum(imgProj[:,imgFlag]**2,axis=0))
        imgDot = numpy.dot(imgProj[:,imgFlag].T,imgProj[:,imgFlag])
        simmat = imgDot / numpy.outer(imgNorm,imgNorm)  
        
        # Proximity matrix (taxi-cab metric)
        ny,nx = numpy.mgrid[0:mask.shape[0],0:mask.shape[1]-2]
        ny = numpy.reshape(ny,(-1,),'F')
        nx = numpy.reshape(nx,(-1,),'F')
        imgRna = ny[imgFlag]
        imgCna = nx[imgFlag]
        
        proxmat = numpy.abs(imgRna - imgRna.reshape(-1,1)) + numpy.abs(imgCna - imgCna.reshape(-1,1))

        # Thresholds
        k2 = .95
        k3 = 3
        k4 = 5

        spfmat = numpy.logical_and(numpy.abs(simmat) > k2, proxmat < k3)

        imgRct = numpy.sum(spfmat,axis=0)
        imgCct = numpy.sum(spfmat,axis=1)
                
        imgRall = imgRna[imgRct >= k4]
        imgCall = imgCna[imgCct >= k4]

        if (imgCall.size > 3):
            img3Find = mask.shape[0]*imgCall + imgRall
            imgMdistRpc = imgMdist[img3Find]

            # Get the original spectra of the rare pixels
            imageRPC = imgArray[img3Find]

            proxmat2 = proxmat[imgRct>=k4,:]
            proxmat2 = proxmat2[:,imgCct>=k4]

            proxmat2[proxmat2 != 1] = 0
            sprox = numpy.sum(proxmat2==1)
            if (sprox > 0):
                clout = clump(proxmat2)
                plmap = numpy.hstack((numpy.reshape(numpy.array(imgRall),(-1,1)),numpy.reshape(numpy.array(imgCall),(-1,1)),imgMdistRpc.reshape(-1,1),numpy.array(clout[1].reshape(-1,1),dtype=int),imageRPC))
                plmap = plmap[numpy.argsort(plmap[:,3]),:]
                plmap2 = numpy.hstack((plmap,numpy.zeros((plmap.shape[0],2))))
                for x in numpy.unique(plmap[:,3]):
                    plmean = numpy.mean(plmap[plmap[:,3]==x,2])
                    plsd   = numpy.var(plmap[plmap[:,3]==x,2],ddof=1)
                    plrpcmean = numpy.mean(plmap[plmap[:,3]==x,4:],axis=0) # average radiance
                                                                           # for a color
                    plmap2[plmap[:,3]==x,-2] = plmean
                    plmap2[plmap[:,3]==x,-1] = plsd
                    plmap2[plmap[:,3]==x,4:plmap2.shape[1]-2] = plrpcmean
                plmap2 = plmap2[numpy.lexsort((plmap2[:,0],plmap2[:,1],-plmap2[:,-2])),:]

                getLngLat = utilities.makeGetLngLat(metadata)
                lnglatPolygon = utilities.convert(plmap2[:,:2].tolist(), getLngLat)
                for x in numpy.unique(plmap2[:,3]):
                    raredict['%d' % x] = {"color": x,
                                        "row,column": plmap2[plmap2[:,3]==x,:2].tolist(),
                                        "longitude,latitude": [[lnglatPolygon[ll][0],lnglatPolygon[ll][1]] for ll in numpy.arange(plmap2.shape[0])[plmap2[:,3]==x]],
                                        "mdist": plmap2[plmap2[:,3]==x,2].tolist(),
                                        "mdistmean": plmap2[plmap2[:,3]==x,-2][0], # these values are identical, so just take one of them
                                        "mdistvar": plmap2[plmap2[:,3]==x,-1][0],
                                        "colorSpectrum": plmap2[plmap2[:,3]==x,4:plmap2.shape[1]-2].tolist()[0]
                                       }
    return raredict
Ejemplo n.º 4
0
def main(filename, metadata, mask, bands, start, end, iscan, trueRowsStart, nTrueRows, trueColsStart, nTrueCols, FCUT = .08, PCUTBLOB = .001, PCUTCHI = .01, MINCATALOGUEDSIZE = 0):
    START = start; END = end
    tstart = datetime.datetime.now()
    imageBands = sorted(bands.keys())
    windowRadius = 1
    truePositive = 0
    falsePositive = 0
    taggedPixels = 0
    imageArray = numpy.zeros((bands[imageBands[0]].shape + (len(bands),)  ))
    maxScore = len(imageBands)/PCUTBLOB

    # Attempt to remove some problematic bands, in particular, ones with 'stripes'.
    bandToRemove = []
    sys.stderr.write('Number of Bands: %i\n'%len(imageBands))
    sys.stderr.write('Evaluate Whether Columns are reasonable\n')
    for i, band in enumerate(imageBands):
        imageArray[:,:,i] = bands[band]             
        if config.blobspectra.VALIDATECOLUMNS:
          ok = True
          bandrange = numpy.ma.max(bands[band].data) - numpy.ma.min(bands[band].data)
          sys.stderr.write('For band %i bandrange is %f\n'%(i,bandrange))
          sys.stderr.write('At %s\n'%(repr(numpy.unravel_index(numpy.ma.argmax(bands[band].data), bands[band].data.shape))))
          maxpix = numpy.unravel_index(numpy.ma.argmax(bands[band].data), bands[band].data.shape)
          sys.stderr.write('Value at max %d\n'%bands[band][maxpix])
          for j in range(1, bands[band].shape[1] - 1):
            if (numpy.ma.max(bands[band].data[:, j]) -  numpy.ma.min(bands[band].data[:, j])) /  bandrange < .01:
              if True:
                sys.stderr.write('%i %f %f\n'%(j, numpy.ma.max(bands[band].data[:,j]),numpy.ma.min(bands[band].data[:,j])))
              ok = False
          if not ok:
            sys.stderr.write('Remove band %i because of un-natural radiance distribution in Cols\n' % i)
            bandToRemove.append(i)

    if config.blobspectra.VALIDATEROWS:
      sys.stderr.write('Evaluate Whether Rows are reasonable\n')
      for i, band in enumerate(imageBands):
          ok = True
          bandrange = numpy.ma.max(bands[band].data) - numpy.ma.min(bands[band].data)
          for j in range(1, bands[band].shape[0] - 1 ):
            if (numpy.ma.max(bands[band].data[j,:]) -  numpy.ma.min(bands[band].data[j,:])) /  bandrange < .01:
              ok = False
          if not ok:
            sys.stderr.write('Remove band %i because of un-natural radiance distribution in Rows\n' % i)
            bandToRemove.append(i)
           
    imageArray = numpy.delete(imageArray, bandToRemove, 2)
    sys.stderr.write('Shape %s\n'%repr(imageArray.shape))
    try:
      sys.stderr.write('Sample %s\n'%repr(imageArray[400,400,:]))
    except:
      pass
    nPixels = imageArray.shape[0] * imageArray.shape[1]
    sys.stderr.write("this is the size of the image: " + str(imageArray.shape) + '\n')
    NimageRows = imageArray.shape[0]
    sys.stderr.write("This is the number of pixels per band in the image: " + str(imageArray.shape[0] * imageArray.shape[1]) + '\n')
    sys.stderr.write("This is the total number of pixels in the image: " + str(imageArray.size) + '\n')
    # This is the number of pixels that are non-zero. Here, a pixel is a 3-index object: x,y,wavelength. Dividing
    # this number by the number of bands ought to be pretty close to the number of unmasked geospatial pixels in this portion.
    sys.stderr.write("This is the total number of non-zero pixels: " + str((imageArray.reshape(-1, ) > 0).sum()) + '\n')
    # numpy.prod(imageArray,2) multiplies the radiances across all wavelengths together. This construct will add up
    # number of geospatial pixels for which at least one band is zero.
    sys.stderr.write("This is the total number of zero pixels: " + str((numpy.prod(imageArray, 2) == 0).sum()) + '\n')
    imageIndices = numpy.arange(imageArray.shape[0] * imageArray.shape[1]).reshape((imageArray.shape[0], imageArray.shape[1]))

    nbands = imageArray.shape[2]
    # (ij)th element of ny yields i. (ij)th element of nx yields j
    ny, nx = numpy.mgrid[0 : mask.shape[0], 0 : mask.shape[1]]
    globalMean = numpy.mean(imageArray.reshape(-1, ))
    sys.stderr.write('global mean: %f\n'%globalMean)
    sys.stderr.write('global var : %f\n'%numpy.var(imageArray.reshape(-1,)))
    if globalMean <= 5  :
      sys.stderr.write('=====> Insufficient radiance in image. Returning!\n')
      return
    if numpy.var(imageArray.reshape(-1,)) < 200**2 :
      sys.stderr.write('=====> Insufficient variability in image. Returning!\n')
      return
    globalMean = globalMean * mask.sum() / (mask.shape[0] * mask.shape[1]) #This line is because the original mean 
                                                                              #over all pixels, not just the non-zeros in the mask

    #################################
    #####STANDARD DEVIATION WINDOW###
    #################################

    #Calculate the mean and standard deviation of 3-by-3 pixel windows
    boxStandardDev = numpy.zeros(imageArray.shape)
    boxMean = numpy.zeros(imageArray.shape)
    nBoxPixels = (1 + 2 * windowRadius) ** 2
    for i in xrange(nbands):
        box = rolling_window(imageArray[:, :, i], 1)
        boxsq = rolling_window(imageArray[:, :, i] ** 2, 1)
        boxStandardDev[:, :, i] = numpy.sqrt(boxsq / nBoxPixels - (box / nBoxPixels) ** 2)
        boxMean[:, :, i] = box / nBoxPixels
        boxStdZero = numpy.where(boxStandardDev[:,:,i] == 0)

    #################################
    ####CREATE STANDARD DEV MASK#####
    #################################

    #Create a boolean array (mask) with TRUE entries where standard deviation is below FCUT * max standard dev of each band
    zfilter = numpy.zeros(imageArray.shape)
    for i in xrange(nbands):
        #sys.stderr.write('%s\n'%repr(numpy.histogram((numpy.ma.masked_array(boxStandardDev[:,:,i], mask=mask)).compressed(), 100)))
        zfilter[:, :, i] = boxStandardDev[:, :, i] < FCUT * numpy.max(boxStandardDev[:, :, i])
    zfilterAll = numpy.ma.all(zfilter, 2)
    zfilterAll = numpy.ma.masked_array(zfilterAll, mask=mask)
    sys.stderr.write('Unmasked pixels %i\n'%zfilterAll.count())
    sys.stderr.write('Pixels in chromatically homogenous region %s\n'%repr(zfilterAll.sum()))
    #Combine this mask with the original image shape mask
    #sdMask = numpy.ma.masked_array(zfilterAll.astype('uint8'), mask | ~zfilterAll)
    sdMask = zfilterAll

    ######################################
    #####CONNECTED COMPONENTS LABELING####
    ######################################
 
    #This step uses a connected components labeling algorithm.
    #It is applied to the binary mask generated in the previous step,
    #and labels each connected group of pixels with a separate integer.
    #Note that the background pixels are also labeled. If, as is most often the case,
    #the background is connected, it takes on the value 0.      
    connected_regions, num_features = find_regions(numpy.ma.masked_array(sdMask, mask=mask))
    sys.stderr.write('Using find_regions \n')
    #connected_regions, num_features = ndimage.label(sdMask)
    #sys.stderr.write('Using scipy %i\n'%num_features)
    connected_regions[numpy.prod(imageArray, 2) == 0] = sorted(numpy.unique(connected_regions))[-1] + 1
    regions_labels = sorted(numpy.unique(connected_regions))
    #
    # background (i.e. region 0) will be green, masked out (radiance=0) areas will be red per
    # the rgb convention used here.
    #connectedPlot.save("connectedPlot.png", "PNG", options="optimize")
    ######################################
    #####BLOB STATISTICS##################
    ######################################
    regions = {}
    regions["bandNames"] = imageBands

    
    if num_features == 0:
      sys.stderr.write('=====> No features identified in this image. Returning!\n')
      return
    #Calcuate the mean and standard deviation of each connected, across each band.
    #The arrays are placed in a dictionary which contains the row-column coordinates,
    #along with the mean and standard deviation for each band.

    #In most cases, the background of the image will be simply connected. In rare cases, a feature from the initial sdMask
    #may cut the image, in which case there will be more than one background. Here, the x-y coordinates of each region are
    #rigorously checked against the background (which is False in the sdMask) x-y coordinates.

    nyx_background = set(map(tuple,numpy.hstack((ny[sdMask == False].reshape(-1, 1),nx[sdMask == False].reshape(-1, 1)))))
    sys.stderr.write('Number of background pixels %i\n'%len(nyx_background))
    regions = defaultdict(dict)
    background_regions = defaultdict(dict)   

    #The bad pixel (i.e., zero radiance because information was missing) has the last label, which gets avoided here 
    for i,label in enumerate(regions_labels[:-1]):
        #First assign pixels which are "background", i.e., did not pass the FCUT, to a separate dictionary
        regionindices = connected_regions == label
        regionrows = ny[regionindices].reshape(-1,1)
        regioncols = nx[regionindices].reshape(-1,1)
        regionpixels = set(map(tuple, numpy.hstack((regionrows, regioncols))))        
        if regionpixels <= nyx_background:
            background_regions[label]["yxCoordinates"] = numpy.hstack((regionrows , regioncols))
            background_regions[label]["mean"] = numpy.mean(imageArray[regionindices , :] , axis=0)
            background_regions[label]["standard_deviation"] = numpy.std(imageArray[regionindices , :] , axis=0)            
            background_regions[label]["mySubBlobs"] = [[label , regionindices.sum()]] #a record is kept of the background region
            background_regions[label]["radiances"] = imageArray[regionindices , :]
        else:
            regions[label]["yxCoordinates"] = numpy.hstack((regionrows , regioncols))
            regions[label]["mean"] = numpy.mean(imageArray[regionindices , :] , axis = 0)
            regions[label]["background"] = []
            try:
              regions[label]["mean"] = regions[label]["mean"] * globalMean / numpy.mean(regions[label]["mean"])
            except:
              sys.stderr.write('Fail to normalize this mean for region %i\n'%label)
              sys.stderr.write(repr(regions[label]["mean"]))
            regions[label]["standard_deviation"] = numpy.std(imageArray[regionindices , :] , axis=0)
            regions[label]["mySubBlobs"] = [[label , regionindices.sum()]] #a record is kept of all blobs and their respective sizes
            if regions[label]["mySubBlobs"][0][1] > 1:
              regions[label]["standard_deviation"] = numpy.mean(boxStandardDev[regionindices,:], axis=0)
            else:
              regions[label]["standard_deviation"] = boxStandardDev[regionindices,:].reshape(-1,)
    regionlabels = regions.keys()
    sys.stderr.write('non background region labels %i\n'%len(regionlabels))

    singletonRad = 0
    for key in background_regions.keys():
        if singletonRad == 0:
            singletonRad = background_regions[key]["radiances"]
            singletonYX = background_regions[key]["yxCoordinates"]
        else:
            singletonRad = numpy.vstack((singletonRad,background_regions[key]["radiances"]))
            singletonYX = numpy.vstack((singletonYX,background_regions[key]["yxCoordinates"]))


    #This is where the merging of blobs (hierarchical clustering) takes place.
    #First test whether regions which made the initial cut (where sdMask=True)
    #can be grouped together.
    #Start out by using the mean and standard dev of blobs to construct a t-test statistic.  
    #If this statistic is above a threshold PCUT, the blobs will be merged. The second blob
    #is always appended to the first. 

    #For regions below 3 pixels, use a chi-squared test in place of t-test to determined
    #whether pixels should be merged to a blob.
    regionsBlobbed = defaultdict(dict)                         #regions will be appended to this dictionary as they are merged with other regions 
    regionsUnblobbed = copy.deepcopy(regions)   #regions will be removed from this dictioanry when they have been merged to another region

    merge_keys = copy.copy(regions.keys())

    unmerged_regions = sorted([x for x in merge_keys if regions[x]["yxCoordinates"].shape[0] > MINCATALOGUEDSIZE]) 
    sys.stderr.write('Merging %i valid among a total of %i multi-cell blobs first\n'%(len(unmerged_regions), len(merge_keys)))
    merged_regions = []
    merge_count = 0
    for i, band1 in enumerate(sorted([x for x in merge_keys if regions[x]["yxCoordinates"].shape[0] > MINCATALOGUEDSIZE])):
        if band1 in unmerged_regions:
            unmerged_regions.remove(band1)
            regionsBlobbed[band1] = copy.deepcopy(regions[band1])
            del regionsUnblobbed[band1] 
            n1 = sum([subblob[1] for subblob in regions[band1]['mySubBlobs']])
            for j, band2 in enumerate(unmerged_regions):
                n2 = sum([subblob[1] for subblob in regions[band2]['mySubBlobs']])
                blobbed_test_stat, test_stat, n1n2 = calc_tdist(regions[band1]["mean"], regions[band2]["mean"], regions[band1]["standard_deviation"],regions[band2]["standard_deviation"], n1, n2)
                blobbed_test_stat = numpy.min(test_stat)
                if blobbed_test_stat > PCUTBLOB / len(regions[band1]["mean"]):
                    #sys.stderr.write('%s, %s, %s \n'%(repr(blobbed_test_stat), repr(test_stat), repr(n1n2)))
                    merged_regions.append(band2)
                    merge_count += 1
                    regionsBlobbed[band1]["yxCoordinates"] = numpy.vstack((regionsBlobbed[band1]["yxCoordinates"], regions[band2]["yxCoordinates"]))
                    regionsBlobbed[band1]["mySubBlobs"].append([band2,regions[band2]["yxCoordinates"].shape[0]]) 
            for merged in merged_regions:
                unmerged_regions.remove(merged)
                merge_count -= 1
                del regionsUnblobbed[merged]
            merged_regions = []
    
    sys.stderr.write('Number of Blobs after blob-to-blob merging %i Number of SubBlobs %i\n'%(len(regionsBlobbed), numpy.sum([len(regionsBlobbed[b]["mySubBlobs"]) for b in regionsBlobbed.keys()])))
    sys.stderr.write('Spread in pixels associated with blobs:\n')
    blobsizes = []
    for k in regionsBlobbed.keys():
      npixels = regionsBlobbed[k]["yxCoordinates"].size/2
      if npixels != 1:
       sys.stderr.write('largish blob %i %s\n'%(k, repr(regionsBlobbed[k]["mySubBlobs"])))
       sys.stderr.write('%d\n'%(regionsBlobbed[k]["yxCoordinates"].size/2.0))
      blobsizes.append(regionsBlobbed[k]["yxCoordinates"].size/2)
    blobdist = numpy.histogram(blobsizes)
    sys.stderr.write('%s\n'%repr(blobdist))
    sys.stderr.write('Number of Pixels in Merged blobs: %i\n'%numpy.array(blobsizes).sum())
    regions = []

    merged_blobs = numpy.zeros(sdMask.shape)
    for band in regionsBlobbed.keys():
        rows = regionsBlobbed[band]["yxCoordinates"][:,0]
        cols = regionsBlobbed[band]["yxCoordinates"][:,1]
        merged_blobs[rows, cols] = band
    ############################
    #Re-group blob statistics###
    ############################

    blobMeans = []
    blobStandardDevs = []
    blobbedRegionKeys = sorted(regionsBlobbed.keys())
    for blob in blobbedRegionKeys:
        blobMeans.append(regionsBlobbed[blob]["mean"])
        blobStandardDevs.append(regionsBlobbed[blob]["standard_deviation"])
        try:
          if blobStandardDevs[-1][-1] == 0.0:
             row = regionsBlobbed[blob]['yxCoordinates'][0][0]
             col = regionsBlobbed[blob]['yxCoordinates'][0][1]
        except:
           sys.stderr.write('############\n')
           sys.stderr.write('Shape of blobStanardDevs %s\n'%repr((numpy.array(blobStandardDevs)).shape))
           sys.stderr.write('%s\n'%repr(regionsBlobbed[blob]))
           sys.stderr.write('%s\n'%repr(blobStandardDevs[-1][-1]))
    blobMeans = numpy.array(blobMeans)
    try:
      blobMeansMean = numpy.mean(blobMeans,axis=1)
    except:
      sys.stderr.write('blobmeans: %s\n'%repr(blobMeans))
    if len(blobMeans)==0:
      return

    blobStandardDevs = numpy.array(blobStandardDevs)

    sys.stderr.write('Done blob-blob merging\n')
    ###################################################################
    ######MERGE UNBLOBBED, PASSED FCUT REGIONS-Chi Squared Test#######
    ###################################################################

    if len(regionsUnblobbed.keys()) > 0: 
      #Merge any blobs (pixels that passed initial FCUT) which are smaller than MINCATALOGUEDSIZE
      #based on result of a chi-squared test
      unBlobbedMeans = []
      unBlobbedRegionKeys = sorted(regionsUnblobbed.keys())       #Avoid first key, which are the pixels that do not pass FCUT
      for unblobbed in unBlobbedRegionKeys:
          unBlobbedMeans.append(regionsUnblobbed[unblobbed]["mean"])  #Table the mean of the unblobbed regions
      unBlobbedMeans = numpy.array(unBlobbedMeans)

      unBlobbedChiSq = numpy.zeros((unBlobbedMeans.shape[0],blobMeans.shape[0]))
      for i in numpy.arange(blobMeans.shape[0]):
          #This is the vectorized argument to be used in the chi-squared test
          try:
            a=(unBlobbedMeans.T/numpy.mean(unBlobbedMeans, axis=1)).T
            b=blobMeans[i,:]/blobMeansMean[i]
            unBlobbedChiSq[:,i] = numpy.sum(numpy.square( ((a-b)*globalMean)/blobStandardDevs[i,:]), axis=1)
          except:
            sys.stderr.write('Unable to perform this chi-squared test ' + repr(unBlobbedMeans) + '\n')
            
      unblobbed_test_stat = calc_chisq(unBlobbedChiSq, unBlobbedMeans.shape[1])  
      unblobbed_test_stat_max = numpy.max(unblobbed_test_stat, axis=1) #Take max

      #Table the label of the unblobbed region with the label of the blobbed region where the unblobbed-blobbed chi-squared test has its max value
      unblobbed_to_merge_with = numpy.hstack((numpy.array(unBlobbedRegionKeys).reshape(-1,1),numpy.array(blobbedRegionKeys)[numpy.argmax(unblobbed_test_stat,axis=1)].reshape(-1,1)))

      #Find out which unblobbed regions pass the chi-squared test
      unblobbed_to_merge_with = unblobbed_to_merge_with[unblobbed_test_stat_max > PCUTCHI,:] 

      #Do the merging of the unblobbed regions that passed chi-squared test with appropriate blobbed region
      for unblobbed in unblobbed_to_merge_with:
          regionsBlobbed[unblobbed[1]]["yxCoordinates"] = numpy.vstack((regionsBlobbed[unblobbed[1]]["yxCoordinates"],regionsUnblobbed[unblobbed[0]]["yxCoordinates"]))    
          regionsBlobbed[unblobbed[1]]["mySubBlobs"].append([unblobbed[0],regionsUnblobbed[unblobbed[0]]["yxCoordinates"].shape[0]])
          del regionsUnblobbed[unblobbed[0]]         
       

    #################################################################
    ##     MERGE UNBLOBBED, DID NOT PASS FCUT                      ##
    #Do the chi-squared test again, but this time with the pixels  ##
    #which did not pass the initial FCUT                           ##
    #################################################################
    sys.stderr.write('Merge Single Pixels with high local SD to known blobs\n')
    sdNotMask = sdMask==False
    #
    #sdPlot = Image.fromarray(numpy.array(255*sdNotMask,dtype=numpy.uint8))
    #sdPlot.save("sdNotMask.png","PNG",options="optimize")
    #

    unBlobbedMeans = [] 
    for key in background_regions.keys():
        unBlobbedMeans = background_regions[key]["radiances"]
        unBlobbedChiSq = numpy.zeros((unBlobbedMeans.shape[0], blobMeans.shape[0]))
        # this construct normalizes each bg pixel by its mean over all bands.
        a = (unBlobbedMeans.T/numpy.mean(unBlobbedMeans, axis=1)).T
        b = blobMeans / blobMeansMean[:, None]
        unBlobbedChiSq = numpy.apply_along_axis(chisq, 1, a, b, blobStandardDevs, globalMean)
        best_chisquared = numpy.min(unBlobbedChiSq, axis = 1)
        unblobbed_test_stat = calc_chisq(best_chisquared, unBlobbedMeans.shape[1])
        #unblobbed_test_stat_max = numpy.max(unblobbed_test_stat, axis = 1)
        bgcoord = background_regions[key]["yxCoordinates"]
        background_coords = imageIndices[bgcoord[:, 0], bgcoord[:, 1]]
        # this variable has two rows, the first is a flattened index into the image raster,
        #                             the second is the best chi-squared match to a blob.
        unblobbed_to_merge_with = numpy.hstack((background_coords.reshape(-1, 1), \
                                  numpy.array(blobbedRegionKeys)[numpy.argmin(unBlobbedChiSq, axis = 1)].reshape(-1, 1)))
        # Background pixels will be merged if they have a good enough match.
        unblobbed_to_merge_with = unblobbed_to_merge_with[unblobbed_test_stat > PCUTCHI, :]
        # this construct yields an N x 2 array  where N is the number of pixels in the image.
        # the array is a list of the row, column index of the ith pixel (counting across and then down).
        singletonCoords = numpy.hstack((ny.reshape(-1, 1), nx.reshape(-1, 1)))
        blobsToUpdate = numpy.unique(unblobbed_to_merge_with[:, 1])
        testregionsBlobbed = {}
        for blob in blobsToUpdate:
          testregionsBlobbed[blob] = {}
          pixelsToAdd = unblobbed_to_merge_with[unblobbed_to_merge_with[:, 1] == blob][:,0]
          try:
            regionsBlobbed[blob]["yxCoordinates"] = numpy.vstack((regionsBlobbed[blob]["yxCoordinates"], singletonCoords[pixelsToAdd, : ]))          
            #testregionsBlobbed[blob]["yxCoordinates"] = numpy.vstack((regionsBlobbed[blob]["yxCoordinates"], singletonCoords[pixelsToAdd, : ]))          
          except:
            sys.stderr.write('%s\n'%repr(blob))
            sys.stderr.write('pixelstoadd %s\n'%repr(pixelsToAdd))
            sys.stderr.write('shapes: %s %s \n'%(regionsBlobbed[blob]["yxCoordinates"].shape, singletonCoords[pixelsToAdd,:].shape))
            sys.stderr.write('singleton coordinates %s\n'%repr(singletonCoords[pixelsToAdd,:]))
        for unblobbed in unblobbed_to_merge_with:
            regionsBlobbed[unblobbed[1]]["mySubBlobs"].append(["background pixel " + str(unblobbed[0]),1])
            regionsBlobbed[unblobbed[1]]["background"].append(1)

        background_regions[key]["yxCoordinates"] = numpy.delete(background_regions[key]["yxCoordinates"], numpy.arange(unblobbed_test_stat.size)[unblobbed_test_stat > PCUTCHI],0)
        background_regions[key]["radiances"] = numpy.delete(background_regions[key]["radiances"], numpy.arange(unblobbed_test_stat.size)[unblobbed_test_stat > PCUTCHI],0)
        background_regions[key]["mySubBlobs"][0][1] -= (unblobbed_test_stat > PCUTCHI).sum()


    sys.stderr.write('Done Merging Single High SD Pixels\n')   

    ##################################
    #   UNMATCHED BACKGROUND PIXELS  #
    ##################################

    singletonRad = 0
    for key in background_regions.keys():
        if singletonRad == 0:
            singletonRad = background_regions[key]["radiances"]
            singletonYX = background_regions[key]["yxCoordinates"]
        else:
            singletonRad = numpy.vstack((singletonRad,background_regions[key]["radiances"]))
            singletonYX = numpy.vstack((singletonYX,background_regions[key]["yxCoordinates"]))

    removeZeroSingletons = numpy.where( numpy.prod(singletonRad, 1) == 0 )
    if len(removeZeroSingletons[0]) > 0:
      singletonRad = numpy.delete(singletonRad, removeZeroSingletons, 0)
      singletonyx = numpy.delete(singletonYX, removeZeroSingletons, 0)
    singletonRadMeans = numpy.mean(singletonRad, axis = 0)
    singletonRad = singletonRad * globalMean / singletonRadMeans


    ##################################
    # TRY TO FIND BINARY BLOB MIXES ##    
    ##################################
    #for key in background_regions.keys():
    #  singleton

    sys.stderr.write('Cluster locally hight SD pixels that differ from blobs with one another\n')
    ##################################
    #   CLUSTER UNMERGED PIXELS      #
    ##################################
    #binlabelStart = int(sorted(regionsBlobbed.keys())[-1])
    binlabelStart = num_features + 1
    if len(singletonRad) > 0:
      start = 0
      chunk = 10000
      mergeSingletons = {}
      total = 0
      while total < len(singletonRad):
        end = min(len(singletonRad), start + chunk)
        mergeSingletons.update(mergevecs(singletonRad[start:end,:]))
        total +=  (end - start)
        start = start + chunk
      for i, tlabel in mergeSingletons.items():
          binlabel = binlabelStart + i
          #tlabel = mergeSingletons[i]
          regionsBlobbed[binlabel]["radiances"] = singletonRad[tlabel,:]
          regionsBlobbed[binlabel]["yxCoordinates"] = singletonYX[tlabel,:]
          regionsBlobbed[binlabel]["mySubBlobs"] = [["background pixels " + str(binlabel), len(tlabel)]]
          try:
            regionsBlobbed[binlabel]["background"].append(len(tlabel))
          except:
            regionsBlobbed[binlabel]["background"] = [len(tlabel)]
          regionsBlobbed[binlabel]["mean"] = numpy.mean(singletonRad[tlabel,:], axis=0)
          regionsBlobbed[binlabel]["standard_deviation"] = numpy.std(singletonRad[tlabel,:], axis=0)

   

    merged_blobs = numpy.zeros(sdMask.shape)
    for band in regionsBlobbed.keys():
        for i in xrange(regionsBlobbed[band]["yxCoordinates"].shape[0]):
            coord = regionsBlobbed[band]["yxCoordinates"]
            merged_blobs[regionsBlobbed[band]["yxCoordinates"][i,0],regionsBlobbed[band]["yxCoordinates"][i,1]] = band

    sys.stderr.write('Done Categorizing Pixels. Fill out catalog\n')
    imageArray[:, :, 4] = numpy.array(255. * imageArray[:, :, 4] / imageArray[:, :, 4].max(), dtype=numpy.uint8)
    catalog = {}
    candregions = []
    # no need to distinguish between the two categories at this point.
    regionsBlobbed.update(regionsUnblobbed)
    finalregions = regionsBlobbed.keys()
    finalregions.sort()
    ncand = 0
    select = selectioncriteria.selectioncriteria[config.blobspectra.SELECTIONCRITERIA]
    paramval = selectioncriteria.scanvals[iscan]
    select = tuple([x.replace('SCAN', str(paramval)) for x in select])
    if len(select) == 1:
      select = (select[0], "True")
    nLevel0 = 0
    sys.stderr.write('Evaluating %i Regions\n'%len(finalregions))
    nEvaluated = 0
    for iregion in range(len(finalregions)):
        nEvaluated += 1
        select = selectioncriteria.selectioncriteria[config.blobspectra.SELECTIONCRITERIA]    
        select = tuple([x.replace('SCAN', str(paramval)) for x in select])
        if len(select) == 1:
          select = (select[0], "True")
        region = finalregions[iregion]
        spectraldistances = []
        regionsBlobbed[region]["mean"] = regionsBlobbed[region]["mean"] * globalMean / numpy.mean(regionsBlobbed[region]["mean"])
        catalog[region] = {'color' : region, 'nsubBlobs' : len(regionsBlobbed[region]["mySubBlobs"])}

        blobsizes = [sblob[1] for sblob in regionsBlobbed[region]["mySubBlobs"]]
        nsingleton = 0
        nblobs = 0       
        nsingleton = str(regionsBlobbed[region]["mySubBlobs"]).count('background')
        if 'background' in regionsBlobbed[region].keys():
          singletonpixels = sum(regionsBlobbed[region]['background'])
          nbgadded = len(regionsBlobbed[region]['background'])
        else:
          singletonpixels = 0
          nbgadded = 0
        nblobs = len(regionsBlobbed[region]["mySubBlobs"]) + singletonpixels - nbgadded
        catalog[region]['nsubBlobs'] = nblobs
        try:
          if catalog[region]['nsubBlobs']==nsingleton:
            catalog[region]['meanNonSingletonSize'] = 0.0
          else:
            catalog[region]['meanNonSingletonSize'] = (numpy.sum(blobsizes) - nsingleton)/ float(catalog[region]['nsubBlobs'] - nsingleton)
        except:
          catalog[region]['meanNonSingletonSize'] = 0.0
        catalog[region]['singletons'] = nsingleton
        catalog[region]['meanBlobSize'] = numpy.mean(blobsizes)
        catalog[region]['sdBlobSize' ] = numpy.std(blobsizes)
        catalog[region]['nNonSingletonBlobs'] = catalog[region]['nsubBlobs'] - catalog[region]['singletons']
        saveSelect0 = select[0]
        #sys.stderr.write('Evaluating Region %i %d %d %d\n'%(nEvaluated, catalog[region]['meanNonSingletonSize'], catalog[region]['meanBlobSize'], catalog[region]['nNonSingletonBlobs']))
        if eval(select[0]):
          nLevel0 += 1         
          n1 = max(2, sum(blobsizes))
          for j in finalregions:
                if 'mean' in regionsBlobbed[j].keys():                  
                  regionsBlobbed[j]["mean"] = regionsBlobbed[j]["mean"] * globalMean / numpy.mean(regionsBlobbed[j]["mean"])
                  n2 = max(2, sum([subblob[1] for subblob in regionsBlobbed[j]['mySubBlobs']]))
                  blobbed_test_stat, test_stat, n1n2 = calc_tdist(regionsBlobbed[region]["mean"],regionsBlobbed[j]["mean"],regionsBlobbed[region]["standard_deviation"],regionsBlobbed[j]["standard_deviation"], n1, n2)
                  blobbed_test_stat = numpy.min(test_stat)
                  if numpy.isnan(blobbed_test_stat):
                    blobbed_test_stat = -numpy.inf
                elif 'radiances' in regionsBlobbed[j].keys():
                  if (regionsBlobbed[j]['radiances']==0).all():                
                    blobbed_test_stat = -numpy.inf
                  else:
                    regionindices = connected_regions == j
                    jmean = numpy.mean(imageArray[regionindices , :] , axis=0)
                    regionsBlobbed[j]["mean"] = jmean * globalMean / numpy.mean(jmean)
                    regionsBlobbed[j]["standard_deviation"] = numpy.std(imageArray[regionindices , :] , axis=0)
                    n2 = sum([subblob[1] for subblob in regionsBlobbed[j]['mySubBlobs']])
                    blobbed_test_stat, test_stat, n1n2 = calc_tdist(regionsBlobbed[region]["mean"],regionsBlobbed[j]["mean"],regionsBlobbed[region]["standard_deviation"],regionsBlobbed[j]["standard_deviation"], n1, n2)
                    blobbed_test_stat = numpy.min(test_stat)
                else:
                   blobbed_test_stat = -numpy.inf
                if j==region:
                  spectraldistances.append( -numpy.inf)
                else:
                  spectraldistances.append(blobbed_test_stat)
          spectralneighborind = numpy.argmax(spectraldistances)
          spectralneighbor = finalregions[spectralneighborind]
          catalog[region]['ClosestSpectralAlternate'] = (spectralneighbor, spectraldistances[spectralneighborind] * nbands / PCUTBLOB)
        else:
          # Failing the Level 0 cut forces Level 1 to fail
          select = ("True", "False")
          catalog[region]['ClosestSpectralAlternate'] = (0, numpy.inf)
        if eval(select[1]):
            candregions.append(region)
            nyx = regionsBlobbed[region]["yxCoordinates"]
            overlaprows = numpy.in1d(nyx[:,0], range(trueRowsStart, trueRowsStart + nTrueRows))
            overlapcols = numpy.in1d(nyx[:,1], range(trueColsStart, trueColsStart + nTrueCols))          
            Noverlap = sum(overlaprows & overlapcols)
            truePositive += Noverlap
            falsePositive += (numpy.sum(blobsizes) - Noverlap)
            taggedPixels += numpy.sum(blobsizes)
            key = filename + '-' + str(region)
            if not config.blobspectra.KNOWNTRUTH:
              key = 'KEY'
              score = '%5.6f'%((maxScore - catalog[region]['ClosestSpectralAlternate'][1])/maxScore)
              sys.stderr.write('%d %s\n'%(maxScore, repr(catalog[region]['ClosestSpectralAlternate'])))
              idcand = '%s'%repr(catalog[region]['color'])
              image = '%s'%os.path.basename(os.environ["map_input_file"])
              #sys.stderr.write('%s\n'%os.path.basename(os.environ["map_input_file"]))
              #for key in catalog[region].keys():
              #  sys.stderr.write('%s %s\n'%(repr(key), repr(catalog[region][key])))
              conv = utilities.makeGetLngLat(metadata)
              for pixel in nyx:
                latit, longit = conv(pixel[1], START + pixel[0])
                sys.stderr.write('%s %d %d %d %d %s\n'%(mask[pixel[0], pixel[1]], START + pixel[0], pixel[1], conv(0, 0)[0], conv(0, 0)[1], score))
                #sys.stderr.write('%6.3f %6.3f\n'%(latit, longit))
                #sys.stderr.write('%d %i %d %d\n'%(catalog[region]['meanNonSingletonSize'], catalog[region]['singletons'], catalog[region]['sdBlobSize'], catalog[region]['nNonSingletonBlobs']))
                outrec = ','.join((image, idcand, '%6.3f'%longit, '%6.3f'%latit, '%i'%(START + pixel[0]), '%i'%(pixel[1]), score, '%10f'%float(catalog[region]['meanBlobSize'])))
                binaryhadoop.emit(sys.stdout, key, outrec, encoding = binaryhadoop.TYPEDBYTES_JSON)
            if sum(overlaprows & overlapcols) > 0:
              sys.stderr.write('%s\n'%repr(saveSelect0))
              sys.stderr.write('%s\n'%repr(eval(saveSelect0)))
              sys.stderr.write('%s\n'%repr(catalog[region]))
              sys.stderr.write('tested with %i %i %i %i\n'%(trueRowsStart, nTrueRows, trueColsStart, nTrueCols))
              sys.stderr.write('%s\n'%repr(nyx[:,1]))
              sys.stderr.write('%s\n'%repr(range(trueRowsStart, trueRowsStart + nTrueRows)))
        else:
          catalog.pop(region)
            
    merged_blobs = numpy.zeros(sdMask.shape)
    for band in regionsBlobbed.keys():
        rows = regionsBlobbed[band]["yxCoordinates"][:,0]
        cols = regionsBlobbed[band]["yxCoordinates"][:,1]
        if band in candregions:
          merged_blobs[rows, cols] = band
        else:
          merged_blobs[rows, cols] = 0
    trueNegative = (nTrueRows * nTrueCols) - truePositive
    falseNegative = nPixels- taggedPixels - trueNegative
    key = str(paramval)
    if taggedPixels > 0:
      fp = float(falsePositive)/taggedPixels
    else:
      fp = 0.0
    if (truePositive + trueNegative) > 0:    
      eff = float(truePositive)/(truePositive + trueNegative)
    else:
      eff = 0.0
    sys.stderr.write('Confusion Matrix - TP: %d FP: %d TN: %d FN: %d Eff: %5.3f FPfrac:%5.3f \n'%(truePositive, falsePositive, trueNegative, falseNegative, eff, fp))
    val = '%d, %d, %d, %d, %5.3f, %5.3f'%(truePositive, falsePositive, trueNegative, falseNegative, eff, fp)
    if config.blobspectra.KNOWNTRUTH:
      outrec = int(truePositive), int(falsePositive), int(trueNegative), int(falseNegative), float(eff), float(fp)
      binaryhadoop.emit(sys.stdout, key, outrec, encoding = binaryhadoop.TYPEDBYTES_JSON)
Ejemplo n.º 5
0
def doEverything(metadata, mask, originalBlock, numSetColors, numBands, bandToIndexLookup):
    globalStart = time.time()

    if numSetColors < numBands:
        heartbeat("Image should have {} bands, but only {} were set\n".format(numBands, numSetColors))
        return

    if cameraName != "ALI":
        heartbeat("Reducing its dimensionality to 26\n")
        windowsOfBandsToTake = range(0, 27) + range(43, 46) + range(58, 67) + range(77, 92) + range(115, 139)
        reducedBlock = originalBlock[windowsOfBandsToTake,:,:]
        bandsToTake = []
        reducedBlock2 = numpy.zeros((reducedBlock.shape[0]/3, reducedBlock.shape[1], reducedBlock.shape[2]), dtype=numpy.double)
        for i in xrange(reducedBlock2.shape[0]):
            bandsToTake.append(windowsOfBandsToTake[3*i + 1])
            reducedBlock2[i] = reducedBlock[3*i:3*(i+1),:,:].mean(axis=0)
        del reducedBlock
    else:
        bandsToTake = numpy.argsort(metadata["bandNames"])
        reducedBlock2 = originalBlock

    heartbeat("Improving the mask\n")
    betterMask = mask > 0
    for i in xrange(reducedBlock2.shape[0]):
        numpy.logical_and(betterMask, reducedBlock2[i,:,:] > 0.0, betterMask)
    del mask

    shrinkmask = betterMask > 0
    for roll in -2, -1, 1, 2:
        for axis in 0, 1:
            numpy.logical_and(shrinkmask, numpy.roll(betterMask, roll, axis=axis) > 0, shrinkmask)

    heartbeat("Taking logarithm\n")
    oldsettings = numpy.seterr(divide="ignore", invalid="ignore")
    block = numpy.log(reducedBlock2)
    numpy.seterr(**oldsettings)

    heartbeat("Reducing image to a bag of pixels\n")
    bag = block.view()
    bag.shape = (block.shape[0], block.shape[1] * block.shape[2])
    del block

    bagMask = betterMask.view()
    bagMask.shape = (originalBlock.shape[1] * originalBlock.shape[2])
    bag = bag[:, bagMask]
    del bagMask

    projectionMatrix = numpy.matrix([[1 if j == i else -1 if j == i + 1 else 0 for j in xrange(reducedBlock2.shape[0])] for i in xrange(reducedBlock2.shape[0] - 1)])
    projectionInverse = projectionMatrix.I

    heartbeat("Projecting the bag onto the color-only basis\n")
    projected = numpy.array(numpy.dot(projectionMatrix, bag))
    del bag

    heartbeat("Casting the projected bag onto the image shape\n")
    projectedBlock = numpy.empty((reducedBlock2.shape[0] - 1, reducedBlock2.shape[1], reducedBlock2.shape[2]), dtype=numpy.double)
    for i in xrange(reducedBlock2.shape[0] - 1):
        projectedBlock[i,betterMask] = projected[i,:]

    heartbeat("Detecting edges\n")
    # 5x5 Kroon without integer-rounding: http://www.k-zone.nl/Kroon_DerivativePaper.pdf
    Gx = numpy.array([[ 0.0007,  0.0052,  0.0370,  0.0052,  0.0007],
                      [ 0.0037,  0.1187,  0.2589,  0.1187,  0.0037],
                      [ 0.0,     0.0,     0.0,     0.0,     0.0],
                      [-0.0037, -0.1187, -0.2589, -0.1187, -0.0037],
                      [-0.0007, -0.0052, -0.0370, -0.0052, -0.0007]])
    Gy = Gx.T

    startTime = time.time()
    convBlock = numpy.zeros((projectedBlock.shape[1], projectedBlock.shape[2]), numpy.double)
    for index in xrange(projectedBlock.shape[0]):
        heartbeat("    {} {} {}\n".format(index, time.time() - startTime, convBlock.max()))
        convGx2 = numpy.power(convolve(projectedBlock[index,:,:], Gx)[2:projectedBlock.shape[1]+2, 2:projectedBlock.shape[2]+2], 2)
        convGy2 = numpy.power(convolve(projectedBlock[index,:,:], Gy)[2:projectedBlock.shape[1]+2, 2:projectedBlock.shape[2]+2], 2)
        convBlock = convBlock + convGx2
        convBlock = convBlock + convGy2
    convBlock = numpy.sqrt(convBlock)

    heartbeat("Edges took {} seconds to detect\n".format(time.time() - startTime))

    heartbeat("Optimizing GMM\n")
    numGMMcomponents = 20
    startTime = time.time()

    if projected.shape[1] < 10 * numGMMcomponents or projected.shape[1] < 10 * projected.shape[0]:
        heartbeat("There are only {} points; skipping (number of GMM components is {} and number of dimensions in the space is {})\n".format(projected.shape[1], numGMMcomponents, projected.shape[0]))
        return

    attempts = 0
    done = False
    while not done:
        try:
            if 10000 < projected.shape[1]:
                randomSelection = projected[:,random.sample(xrange(projected.shape[1]), 10000)]
                model = MoG(randomSelection, numGMMcomponents)
                model.em(10)
                heartbeat("     time for first pass: {} seconds\n".format(time.time() - startTime))

                randomSelection = projected[:,random.sample(xrange(projected.shape[1]), 10000)]
                model = MoG(randomSelection, numGMMcomponents, means=model.means, covs=model.covs, mixprops=model.mixprops)
                model.em(10)
                heartbeat("     time for second pass: {} seconds\n".format(time.time() - startTime))

                randomSelection = projected[:,random.sample(xrange(projected.shape[1]), 10000)]
                model = MoG(randomSelection, numGMMcomponents, means=model.means, covs=model.covs, mixprops=model.mixprops)
                model.em(10)
                heartbeat("     time for third pass: {} seconds\n".format(time.time() - startTime))

                model = MoG(projected, numGMMcomponents, means=model.means, covs=model.covs, mixprops=model.mixprops)
                model.em(5)
                done = True

            else:
                heartbeat("     skipping three-pass subfit because the dataset is small\n")

                model = MoG(projected, numGMMcomponents)
                model.em(5)
                done = True

        except numpy.linalg.linalg.LinAlgError:
            attempts += 1
            if attempts > 4:
                heartbeat("    could not fit in 4 attempts; giving up\n")
                return

    heartbeat("GMM took {} seconds to optimize\n".format(time.time() - startTime))

    heartbeat("Scoring all pixels with GMM\n")
    startTime = time.time()
    scores = logsumexp(model.compute_posteriors(projected, reinit=True, normalize=False, logscale=True), 0)

    scoresBlock = numpy.zeros((reducedBlock2.shape[1], reducedBlock2.shape[2]), dtype=numpy.double)
    scoresBlock[betterMask] = scores
    del scores

    themin, themax = numpy.percentile(convBlock[shrinkmask], [0.5, 99.5])
    convNorm = (convBlock[shrinkmask] - themin)/(themax - themin)
    convBlockNorm = (convBlock - themin)/(themax - themin)

    themin, themax = numpy.percentile(scoresBlock[shrinkmask], [0.5, 99.5])
    scoresNorm = 1.0 - (scoresBlock[shrinkmask] - themin)/(themax - themin)
    scoresBlockNorm = 1.0 - (scoresBlock - themin)/(themax - themin)
    rawscoresmin, rawscoresmax = themin, themax
    del scoresBlock
    del scoresNorm

    selection = numpy.logical_and(scoresBlockNorm > 1.0, shrinkmask)
    indexes = zip(*numpy.nonzero(selection))

    scoredBag = numpy.argmax(model.compute_posteriors(projected, reinit=True), axis=0)
    heartbeat("GMM took {} seconds to score all pixels (a few times in different ways)\n".format(time.time() - startTime))

    heartbeat("Blurring scores for bucket-fill to spread better\n")
    startTime = time.time()
    spot = numpy.array([[math.exp(-((i - 2)**2 + (j - 2)**2)/2.0/1.0**2) for i in xrange(5)] for j in xrange(5)])
    spot = spot / spot.sum()
    blurScores = convolve(scoresBlockNorm[:,:], spot)[2:scoresBlockNorm.shape[0]+2, 2:scoresBlockNorm.shape[1]+2]
    heartbeat("Time to blur image: {} seconds\n".format(time.time() - startTime))

    heartbeat("Building the KD-tree\n")
    startTime = time.time()
    kdtree = KDTree(projected)
    dynamicRange = (lambda x: x[1] - x[0])(numpy.percentile(projected, [1, 99]))
    heartbeat("KD-tree took {} seconds to build\n".format(time.time() - startTime))

    heartbeat("Performing bucket-fill searches\n")
    startTime = time.time()

    clumps = set()
    assigned = numpy.empty((projectedBlock.shape[1], projectedBlock.shape[2]), dtype=numpy.dtype(object))
    considered = numpy.zeros((projectedBlock.shape[1], projectedBlock.shape[2]), dtype=numpy.dtype(bool))

    Clump.assigned = assigned
    Clump.projectedBlock = projectedBlock
    Clump.projectionInverse = projectionInverse
    Clump.metadata = metadata
    Clump.kdtree = kdtree
    Clump.bandsToTake = bandsToTake
    Clump.projected = projected
    Clump.convBlockNorm = convBlockNorm
    Clump.model = model
    Clump.rawscoresmin = rawscoresmin
    Clump.rawscoresmax = rawscoresmax
    Clump.dynamicRange = dynamicRange
    Clump.blurBlock = None
    Clump.scoresBlockNorm = scoresBlockNorm
    Clump.blurScores = blurScores

    for index, (x, y) in enumerate(indexes):
        if index % 100 == 0:
            heartbeat("    {} {}\n".format(float(index)/len(indexes), time.time() - startTime))
        queue = [(x, y)]
        clump = Clump((x, y))
        while len(queue) > 0:
            i, j = queue.pop()

            if i >= 0 and j >= 0 and i < shrinkmask.shape[0] and j < shrinkmask.shape[1] and shrinkmask[i, j]:
                if not considered[i, j]:
                    if blurScores[i, j] > 1.0:
                        clump.add((i, j))
                        assigned[i, j] = clump

                        if clump.size() > Clump.maxSize:
                            heartbeat("         clump is too big!\n")
                            break

                        newQueue = []
                        for ii in -1, 0, 1:
                            for jj in -1, 0, 1:
                                if ii != jj:
                                    newQueue.append((i + ii, j + jj))

                        queue = newQueue + queue

                elif assigned[i, j] is not None and assigned[i, j] != clump:
                    heartbeat("         merging clumps\n")
                    clump = assigned[i, j].mergeIn(clump)
                    if clump.size() > Clump.maxSize:
                        heartbeat("             ... into one that was too big!\n")
                        break

                considered[i, j] = True

        if not clump.isEmpty() and clump.size() <= Clump.maxSize:
            heartbeat("     new clump with size {}\n".format(clump.size()))
            clumps.add(clump)

    heartbeat("bucket-fill search took {} seconds\n".format(time.time() - startTime))

    heartbeat("Calculating attributes of the image and clumps\n")
    startTime = time.time()

    def gmmSpectrum(index):
        unnormalized = numpy.exp(numpy.array(numpy.dot(projectionInverse, model.means[:,index].T))[0])
        return dict(zip(list(numpy.array(metadata["bandNames"])[bandsToTake]), unnormalized / unnormalized.sum()))

    def fullSpectrumAt(indexes):
        unnormalized = numpy.zeros(originalBlock.shape[0], dtype=numpy.double)
        for i, j in indexes:
            unnormalized += originalBlock[:,i,j]
        return dict(zip(metadata["bandNames"], unnormalized / len(indexes)))

    output = {"metadata": metadata}
    getLngLat = utilities.makeGetLngLat(metadata)
    getMeters = utilities.makeGetMeters(metadata)

    wavelengths = [metadata["bandWavelength"][x] for x in numpy.array(metadata["bandNames"])[bandsToTake]]
    clusterNumber = 0
    for clump in clumps:
        heartbeat("     do clump {}\n".format(clump.indexes))

        border1 = clump.border()
        borderPoint1 = numpy.zeros(projectedBlock.shape[0], dtype=numpy.double)
        for i, j in clump.indexes:
            borderPoint1 = borderPoint1 + projectedBlock[:, i, j]
        borderPoint1 = borderPoint1 / len(border1)

        border2 = clump.border(list(clump.indexes) + list(border1))
        borderPoint2 = numpy.zeros(projectedBlock.shape[0], dtype=numpy.double)
        for i, j in clump.indexes:
            borderPoint2 = borderPoint2 + projectedBlock[:, i, j]
        borderPoint2 = borderPoint2 / len(border2)

        numSeeds = len(clump.seeds)
        numPixels = clump.size()
        seeds = sorted((int(i), int(j)) for i, j in clump.seeds)
        indexes = sorted((int(i), int(j)) for i, j in clump.indexes)
        border1 = sorted((int(i), int(j)) for i, j in border1)
        border2 = sorted((int(i), int(j)) for i, j in border2)
        mean = list(clump.mean())
        meanSeeds = list(clump.meanSeeds())
        stdev = clump.stdev()
        specMean = clump.spectrumOf(clump.mean())
        specMeanSeeds = clump.spectrumOf(clump.meanSeeds())
        borderSpec1 = clump.spectrumOf(borderPoint1)
        borderSpec2 = clump.spectrumOf(borderPoint2)
        edgeScore1 = clump.edginess(border1)
        edgeScore2 = clump.edginess(border2)
        gmmScoreMean = clump.gmmscoreOf(clump.mean())
        gmmScoreMeanSeeds = clump.gmmscoreOf(clump.meanSeeds())
        fullSpectrum = fullSpectrumAt(clump.indexes)
        fullSpectrumSeeds = fullSpectrumAt(clump.seeds)
        fullSpectrumBorder1 = fullSpectrumAt(border1)
        fullSpectrumBorder2 = fullSpectrumAt(border2)

        r200 = float(clump.density(clump.meanSeeds(), 0.200))
        r500 = float(clump.density(clump.meanSeeds(), 0.500))

        if r200 > 0.0 and r500 > 0.0 and gmmScoreMeanSeeds > 1.42 and gmmScoreMeanSeeds - gmmScoreMean > 0.12 and math.log10(r200) < -2.78 and math.log10(r500) < -1.30 and stdev > 0.067 and edgeScore2 < 0.61:
            clusterName = "cluster_{}".format(clusterNumber)
            output[clusterName] = {}
            clusterNumber += 1

            output[clusterName]["contours95"] = [{}]
            c95 = output[clusterName]["contours95"][0]

            x0 = numpy.mean([x for x, y in border2])
            y0 = numpy.mean([y for x, y in border2])
            order = numpy.argsort([math.atan2(y - y0, x - x0) for x, y in border2])
            c95["rowcolpolygon"] = [(int(x), int(y)) for x, y in numpy.array(border2)[order]]
            c95["lnglatpolygon"] = [getLngLat(x, y) for x, y in numpy.array(border2)[order]]

            c95["centroidInLngLat"] = getLngLat(x0, y0)
            c95["areaInPixels"] = numPixels

            pixelLength = abs(getMeters(*getLngLat(x0 + 0.5, y0))[0] - getMeters(*getLngLat(x0 - 0.5, y0))[0])
            pixelHeight = abs(getMeters(*getLngLat(x0, y0 + 0.5))[1] - getMeters(*getLngLat(x0, y0 - 0.5))[1])
            c95["areaInMeters"] = numPixels * pixelLength * pixelHeight
            c95["circumferenceInMeters"] = 0.5*(pixelLength + pixelHeight) * len(border1)

            rawGMMscore = float(logsumexp(model.compute_posteriors(numpy.array([clump.meanSeeds()]).T, reinit=True, normalize=False, logscale=True), 0)[0])
            rawKNNscore = float(r500)

            c95["score"] = rawGMMscore, rawKNNscore

            c95["other"] = {
                "numSeeds": numSeeds,
                "numPixels": numPixels,
                "seeds": seeds,
                "indexes": indexes,
                "border1": border1,
                "border2": border2,
                "mean": mean,
                "meanSeeds": meanSeeds,
                "stdev": stdev,
                "specMean": specMean,
                "specMeanSeeds": specMeanSeeds,
                "borderSpec1": borderSpec1,
                "borderSpec2": borderSpec2,
                "edgeScore1": edgeScore1,
                "edgeScore2": edgeScore2,
                "gmmScoreMean": gmmScoreMean,
                "gmmScoreMeanSeeds": gmmScoreMeanSeeds,
                "fullSpectrum": fullSpectrum,
                "fullSpectrumSeeds": fullSpectrumSeeds,
                "fullSpectrumBorder1": fullSpectrumBorder1,
                "fullSpectrumBorder2": fullSpectrumBorder2,
                "r200": r200,
                "r500": r500}

    binaryhadoop.emit(sys.stdout, metadata["originalDirName"], output, encoding=binaryhadoop.TYPEDBYTES_JSON)

    heartbeat("Calculating attributes took {} seconds\n".format(time.time() - startTime))

    totalTime = time.time() - globalStart
    heartbeat("Time to do everything: {} sec, which is {} min\n".format(totalTime, totalTime/60.0))