Example #1
0
def main():
    # Get the domain XML file from the command line arguments
    if len(sys.argv) < 2:
        print 'Usage: detect_flood_modis.py domain.xml'
        sys.exit(0)

    cmt.ee_authenticate.initialize()

    # Fetch data set information
    domain = cmt.domain.Domain()
    domain.load_xml(sys.argv[1])

    # Display the Landsat and MODIS data for the data set
    cmt.util.gui_util.visualizeDomain(domain)

    #
    # import cmt.modis.adaboost
    # cmt.modis.adaboost.adaboost_learn()         # Adaboost training
    # #cmt.modis.adaboost.adaboost_dem_learn(None) # Adaboost DEM stats collection
    # raise Exception('DEBUG')

    waterMask = ee.Image("MODIS/MOD44W/MOD44W_005_2000_02_24").select(
        ['water_mask'], ['b1'])
    addToMap(waterMask.mask(waterMask), {
        'min': 0,
        'max': 1
    }, 'Permanent Water Mask', False)

    # For each of the algorithms
    for a in range(len(ALGORITHMS)):
        # Run the algorithm on the data and get the results
        try:
            (alg, result) = detect_flood(domain, ALGORITHMS[a])
            if result is None:
                continue

            # These lines are needed for certain data sets which EE is not properly masking!!!
            # result = result.mask(domain.skybox_nir.image.reduce(ee.Reducer.allNonZero()))
            # result = result.mask(domain.skybox.image.reduce(ee.Reducer.allNonZero()))

            # Get a color pre-associated with the algorithm, then draw it on the map
            color = get_algorithm_color(ALGORITHMS[a])
            addToMap(result.mask(result), {
                'min': 0,
                'max': 1,
                'opacity': 0.5,
                'palette': '000000, ' + color
            }, alg, False)

            # Compare the algorithm output to the ground truth and print the results
            if domain.ground_truth:
                cmt.util.evaluation.evaluate_approach_thread(
                    functools.partial(evaluation_function, alg=ALGORITHMS[a]),
                    result, domain.ground_truth, domain.bounds,
                    is_algorithm_fractional(ALGORITHMS[a]))
        except Exception, e:
            print('Caught exception running algorithm: ' +
                  get_algorithm_name(ALGORITHMS[a]) + '\n' + str(e) + '\n')
Example #2
0
def grow_regions(sensor, thresholded, thresholds):
    """???????"""
    REGION_GROWTH_RANGE = 20
    neighborhood_kernel = ee.Kernel.square(1, "pixels", False)
    loose_thresholded = sensor.image.select([sensor.band_names[0]]).lte(thresholds[0])
    for i in range(1, len(sensor.band_names)):
        loose_thresholded = loose_thresholded.And(sensor.image.select([sensor.band_names[i]]).lte(thresholds[i]))
    addToMap(loose_thresholded, {"min": 0, "max": 1}, "Loose", False)
    for i in range(REGION_GROWTH_RANGE):
        thresholded = thresholded.convolve(neighborhood_kernel).And(loose_thresholded)
    return thresholded
Example #3
0
def main():
    # Get the domain XML file from the command line arguments
    if len(sys.argv) < 2:
        print 'Usage: detect_flood_radar.py domain.xml'
        sys.exit(0)

    cmt.ee_authenticate.initialize()

    # Fetch data set information
    domain = cmt.domain.Domain()
    domain.load_xml(sys.argv[1])

    # Display radar and ground truth
    cmt.util.gui_util.visualizeDomain(domain)

    waterMask = ee.Image("MODIS/MOD44W/MOD44W_005_2000_02_24").select(
        ['water_mask'], ['b1'])
    addToMap(waterMask.mask(waterMask), {
        'min': 0,
        'max': 1
    }, 'Permanent Water Mask', False)

    # print im.image.getDownloadUrl({'name' : 'sar', 'region':ee.Geometry.Rectangle(-91.23, 32.88, -91.02, 33.166).toGeoJSONString(), 'scale': 6.174})

    # For each of the algorithms
    for a in range(len(ALGORITHMS)):
        try:
            # Run the algorithm on the data and get the results
            alg = ALGORITHMS[a]
            result = detect_flood(domain, alg)

            # Needed for certain images which did not mask properly from maps engine
            # result = result.mask(domain.get_radar().image.reduce(ee.Reducer.allNonZero()))

            # Get a color pre-associated with the algorithm, then draw it on the map
            color = get_algorithm_color(alg)
            addToMap(result.mask(result), {
                'min': 0,
                'max': 1,
                'opacity': 0.5,
                'palette': '000000, ' + color
            }, get_algorithm_name(alg), False)

            # Compare the algorithm output to the ground truth and print the results
            if domain.ground_truth is not None:
                cmt.util.evaluation.evaluate_approach_thread(
                    functools.partial(evaluation_function, alg=alg), result,
                    domain.ground_truth, domain.bounds)
        except Exception, e:
            print('Caught exception running algorithm: ' +
                  get_algorithm_name(ALGORITHMS[a]) + '\n' + str(e) + '\n')
Example #4
0
def grow_regions(sensor, thresholded, thresholds):
    '''???????'''
    REGION_GROWTH_RANGE = 20
    neighborhood_kernel = ee.Kernel.square(1, 'pixels', False)
    loose_thresholded = sensor.image.select([sensor.band_names[0]
                                             ]).lte(thresholds[0])
    for i in range(1, len(sensor.band_names)):
        loose_thresholded = loose_thresholded.And(
            sensor.image.select([sensor.band_names[i]]).lte(thresholds[i]))
    addToMap(loose_thresholded, {'min': 0, 'max': 1}, 'Loose', False)
    for i in range(REGION_GROWTH_RANGE):
        thresholded = thresholded.convolve(neighborhood_kernel).And(
            loose_thresholded)
    return thresholded
def main():
  # Get the domain XML file from the command line arguments
  if len(sys.argv) < 2:
      print 'Usage: detect_flood_modis.py domain.xml'
      sys.exit(0)
  
  cmt.ee_authenticate.initialize()
  
  # Fetch data set information
  domain = cmt.domain.Domain()
  domain.load_xml(sys.argv[1])
  
  # Display the Landsat and MODIS data for the data set
  cmt.util.gui_util.visualizeDomain(domain)
  
  #
  # import cmt.modis.adaboost
  # cmt.modis.adaboost.adaboost_learn()         # Adaboost training
  # #cmt.modis.adaboost.adaboost_dem_learn(None) # Adaboost DEM stats collection
  # raise Exception('DEBUG')
  
  waterMask = ee.Image("MODIS/MOD44W/MOD44W_005_2000_02_24").select(['water_mask'], ['b1'])
  addToMap(waterMask.mask(waterMask), {'min': 0, 'max': 1}, 'Permanent Water Mask', False)
  
  # For each of the algorithms
  for a in range(len(ALGORITHMS)):
      # Run the algorithm on the data and get the results
      try:
          (alg, result) = detect_flood(domain, ALGORITHMS[a])
          if result is None:
              continue
  
          # These lines are needed for certain data sets which EE is not properly masking!!!
          # result = result.mask(domain.skybox_nir.image.reduce(ee.Reducer.allNonZero()))
          # result = result.mask(domain.skybox.image.reduce(ee.Reducer.allNonZero()))
  
          # Get a color pre-associated with the algorithm, then draw it on the map
          color = get_algorithm_color(ALGORITHMS[a])
          addToMap(result.mask(result), {'min': 0, 'max': 1, 'opacity': 0.5, 'palette': '000000, ' + color},
                   alg, False)
  
          # Compare the algorithm output to the ground truth and print the results
          if domain.ground_truth:
              cmt.util.evaluation.evaluate_approach_thread(functools.partial(
                  evaluation_function, alg=ALGORITHMS[a]), result, domain.ground_truth, domain.bounds,
                  is_algorithm_fractional(ALGORITHMS[a]))
      except Exception, e:
          print('Caught exception running algorithm: ' + get_algorithm_name(ALGORITHMS[a]) + '\n' +
                str(e) + '\n')
Example #6
0
def threshold(domain, historical_domain=None):
    '''An implementation of the paper:
           Matgen, Hostache, Schumann, et. al. "Towards an automated SAR-based flood monitoring system:
           Lessons learned from two case studies." Physics and Chemistry of the Earth, 2011.
           
        TODO: ????
    '''

    sensor = domain.get_radar()  # Get the first radar sensor available
    hist = RadarHistogram(domain, sensor)

    thresholds = hist.get_thresholds()  # Get thresholds for each band

    # For each band, get pixels less than a threshold
    results = []
    for c in range(len(thresholds)):
        ch = sensor.band_names[c]
        results.append(sensor.image.select([ch], [ch]).lte(thresholds[c]))

    #?????
    result_image = results[0]
    for c in range(1, len(results)):
        result_image = result_image.addBands(results[c],
                                             [sensor.band_names[c]])
    addToMap(result_image, {'min': 0, 'max': 1}, 'Color Image', False)

    # TODO: Compare water pixels to expected distribution
    #       take difference of two image, remove pixels that aren't below
    #       original non region-growing threshold and don't change by at
    #       least fixed amount

    #?????
    result_image = results[0].select([sensor.band_names[0]], ['b1'])
    for c in range(1, len(results)):
        result_image = result_image.And(results[c])

    #????
    growing_thresholds = hist.find_loose_thresholds()
    result_image = grow_regions(sensor, result_image, growing_thresholds)

    #hist.show_histogram() # This is useful for debugging

    return result_image
def main():
    # Get the domain XML file from the command line arguments
    if len(sys.argv) < 2:
        print 'Usage: detect_flood_radar.py domain.xml'
        sys.exit(0)
    
    cmt.ee_authenticate.initialize()
    
    # Fetch data set information
    domain = cmt.domain.Domain()
    domain.load_xml(sys.argv[1])
    
    # Display radar and ground truth
    cmt.util.gui_util.visualizeDomain(domain)
    
    waterMask = ee.Image("MODIS/MOD44W/MOD44W_005_2000_02_24").select(['water_mask'], ['b1'])
    addToMap(waterMask.mask(waterMask), {'min': 0, 'max': 1}, 'Permanent Water Mask', False)
    
    # print im.image.getDownloadUrl({'name' : 'sar', 'region':ee.Geometry.Rectangle(-91.23, 32.88, -91.02, 33.166).toGeoJSONString(), 'scale': 6.174})
    
    # For each of the algorithms
    for a in range(len(ALGORITHMS)):
        try:
            # Run the algorithm on the data and get the results
            alg = ALGORITHMS[a]
            result = detect_flood(domain, alg)
    
            # Needed for certain images which did not mask properly from maps engine
            # result = result.mask(domain.get_radar().image.reduce(ee.Reducer.allNonZero()))
    
            # Get a color pre-associated with the algorithm, then draw it on the map
            color = get_algorithm_color(alg)
            addToMap(result.mask(result), {'min': 0, 'max': 1, 'opacity': 0.5, 'palette': '000000, ' + color},
                     get_algorithm_name(alg), False)
    
            # Compare the algorithm output to the ground truth and print the results
            if domain.ground_truth is not None:
                cmt.util.evaluation.evaluate_approach_thread(functools.partial(
                    evaluation_function, alg=alg), result, domain.ground_truth, domain.bounds)
        except Exception, e:
            print('Caught exception running algorithm: ' + get_algorithm_name(ALGORITHMS[a]) + '\n' +
                  str(e) + '\n')
Example #8
0
def threshold(domain, historical_domain=None):
    """An implementation of the paper:
           Matgen, Hostache, Schumann, et. al. "Towards an automated SAR-based flood monitoring system:
           Lessons learned from two case studies." Physics and Chemistry of the Earth, 2011.
           
        TODO: ????
    """

    sensor = domain.get_radar()  # Get the first radar sensor available
    hist = RadarHistogram(domain, sensor)

    thresholds = hist.get_thresholds()  # Get thresholds for each band

    # For each band, get pixels less than a threshold
    results = []
    for c in range(len(thresholds)):
        ch = sensor.band_names[c]
        results.append(sensor.image.select([ch], [ch]).lte(thresholds[c]))

    # ?????
    result_image = results[0]
    for c in range(1, len(results)):
        result_image = result_image.addBands(results[c], [sensor.band_names[c]])
    addToMap(result_image, {"min": 0, "max": 1}, "Color Image", False)

    # TODO: Compare water pixels to expected distribution
    #       take difference of two image, remove pixels that aren't below
    #       original non region-growing threshold and don't change by at
    #       least fixed amount

    # ?????
    result_image = results[0].select([sensor.band_names[0]], ["b1"])
    for c in range(1, len(results)):
        result_image = result_image.And(results[c])

    # ????
    growing_thresholds = hist.find_loose_thresholds()
    result_image = grow_regions(sensor, result_image, growing_thresholds)

    # hist.show_histogram() # This is useful for debugging

    return result_image
Example #9
0
def sar_martinis(domain, cr_method=False):
    '''Compute a global threshold via histogram splitting on selected subregions'''
    
    sensor     = domain.get_radar()
    radarImage = sensor.image

    # Many papers reccomend a median type filter to remove speckle noise.
    
    # 1: Divide up the image into a grid of tiles, X
    
    # Divide up the region into a grid of subregions
    MAX_BOXES_PER_SIDE = 12 # Cap the number of boxes at 144
    DESIRED_BOX_SIZE_METERS = 3000
    boxList, boxSizeMeters  = divideUpBounds(domain.bounds, DESIRED_BOX_SIZE_METERS, MAX_BOXES_PER_SIDE)
    
    # Extract the center point from each box
    centersList = map(getBoundsCenter, boxList)
    
    # SENTINEL = 12m/pixel
    KERNEL_SIZE = 13 # Each box will be covered by a 13x13 pixel kernel
    metersPerPixel = boxSizeMeters / KERNEL_SIZE
    print 'Using metersPerPixel: ' + str(metersPerPixel)
    
    avgKernel   = ee.Kernel.square(KERNEL_SIZE, 'pixels', True); # <-- EE fails if this is in meters!

    # Select the radar layer we want to work in
    if 'water_detect_radar_channel' in domain.algorithm_params:
        channelName = domain.algorithm_params['water_detect_radar_channel']
    else: # Just use the first radar channel
        channelName = sensor.band_names[0]   
    
    # Rescale the input data so the statistics are not dominated by very bright pixels
    GRAY_MAX  = 255
    grayLayer = applyCutlerLinearLogScale(radarImage.select([channelName]), domain.bounds)
    #addToMap(grayLayer, {'min': 0, 'max': GRAY_MAX,  'opacity': 1.0, 'palette': GRAY_PALETTE}, 'grayLayer',   False)
    
    
    # Compute the global mean, then make a constant image out of it.
    globalMean      = grayLayer.reduceRegion(ee.Reducer.mean(), domain.bounds, metersPerPixel)
    globalMeanImage = ee.Image.constant(globalMean.getInfo()[channelName])
    
    print 'global mean = ' + str(globalMean.getInfo()[channelName])
    
    
    # Compute mean and standard deviation across the entire image
    meanImage          = grayLayer.convolve(avgKernel)
    graysSquared       = grayLayer.pow(ee.Image(2))
    meansSquared       = meanImage.pow(ee.Image(2))
    meanOfSquaredImage = graysSquared.convolve(avgKernel)
    meansDiff          = meanOfSquaredImage.subtract(meansSquared)
    stdImage           = meansDiff.sqrt()
    
    # Debug plots
    #addToMap(meanImage, {'min': 3000, 'max': 70000,  'opacity': 1.0, 'palette': GRAY_PALETTE}, 'Mean',   False)
    #addToMap(stdImage,  {'min': 3000, 'max': 200000, 'opacity': 1.0, 'palette': GRAY_PALETTE}, 'StdDev', False)
    #addToMap(meanImage, {'min': 0, 'max': GRAY_MAX, 'opacity': 1.0, 'palette': GRAY_PALETTE}, 'Mean',   False)
    #addToMap(stdImage,  {'min': 0, 'max': 40,       'opacity': 1.0, 'palette': GRAY_PALETTE}, 'StdDev', False)
        
    # Compute these two statistics across the entire image
    CV = meanImage.divide(stdImage).reproject(       "EPSG:4326", None, metersPerPixel)
    R  = meanImage.divide(globalMeanImage).reproject("EPSG:4326", None, metersPerPixel)
    
    
    # 2: Prune to a reduced set of tiles X'
    
    # Parameters which control which sub-regions will have their histograms analyzed
    # - These are strongly influenced by the smoothing kernel size!!!
    MIN_CV = 0.7
    MAX_CV = 1.3
    MAX_R  = 1.1
    MIN_R  = 0.5
    
    # Debug plots
    addToMap(CV, {'min': 0, 'max': 4.0, 'opacity': 1.0, 'palette': GRAY_PALETTE}, 'CV', False)
    addToMap(R,  {'min': 0, 'max': 4.0, 'opacity': 1.0, 'palette': GRAY_PALETTE}, 'R',  False)
    
    if cr_method:
        MIN_CR = 0.10
    
        # sar_griefeneder reccomends replacing CV with CR = (std / gray value range), min value 0.05
        imageMin  = grayLayer.reduceRegion(ee.Reducer.min(), domain.bounds, metersPerPixel).getInfo()[channelName]
        imageMax  = grayLayer.reduceRegion(ee.Reducer.max(), domain.bounds, metersPerPixel).getInfo()[channelName]
        grayRange = imageMax - imageMin
        CR = stdImage.divide(grayRange)
    
        #addToMap(CR, {'min': 0, 'max': 0.3, 'opacity': 1.0, 'palette': GRAY_PALETTE}, 'CR', False)
    
    
    # Filter out pixels based on computed statistics
    t1 = CV.gte(MIN_CV)
    t2 = CV.lte(MAX_CV)
    t3 = R.gte(MIN_R)
    t4 = R.lte(MAX_R)
    if cr_method:
        temp = CR.gte(MIN_CR).And(t3).And(t4)
    else:
        temp = t1.And(t2).And(t3).And(t4)
    X_prime = temp.reproject("EPSG:4326", None, metersPerPixel)
    addToMap(X_prime.mask(X_prime),  {'min': 0, 'max': 1, 'opacity': 1.0, 'palette': TEAL_PALETTE}, 'X_prime',  False)
       
    # 3: Prune again to a final set of tiles X''
    
    # Further pruning happens here but for now we are skipping it and using
    #  everything that got by the filter.  This would speed local computation.
    # - This is equivalent to using a large number for N'' in the original paper
    #    (which does not suggest a value for N'')
    X_doublePrime = X_prime
    
    
    # 4: For each tile, compute the optimal threshold
    
    # Assemble all local gray values at each point ?
    localPixelLists = grayLayer.neighborhoodToBands(avgKernel)
        
    maskWrapper = ee.ImageCollection([X_doublePrime]);
    collection  = ee.ImageCollection([localPixelLists]);
    
    # Extract the point data at from each sub-region!
    
    localThresholdList = []
    usedPointList      = []
    rejectedPointList  = []
    for loc in centersList:
        
        try:
            thisLoc = ee.Geometry.Point(loc[1], loc[0])
        
            # If the mask for this location is invalid, skip this location
            maskValue = maskWrapper.getRegion(thisLoc, metersPerPixel);
            maskValue = maskValue.getInfo()[1][4] # TODO: Not the best way to grab the value!
            if not maskValue:
                rejectedPointList.append(thisLoc)
                continue
        
            # Otherwise pull down all the pixel values surrounding this center point
            
            pointData = collection.getRegion(thisLoc, metersPerPixel)
            pixelVals = pointData.getInfo()[1][4:] # TODO: Not the best way to grab the value!
    
            # TODO: Can EE handle making a histogram around this region or do we need to do this ourselves?
            #pointData = localPixelLists.reduceRegion(thisRegion, ee.Reducer.histogram(), SAMPLING_SCALE);
            #print pointData.getInfo()
            #print pixelVals
            #__show_histogram(pixelVals)
            #plt.bar(range(len(pixelVals)), pixelVals)
            
            # Compute a histogram from the pixels (TODO: Do this with EE!)
            NUM_BINS = 256
            hist, binEdges = numpy.histogram(pixelVals, NUM_BINS)
            binCenters = numpy.divide(numpy.add(binEdges[:NUM_BINS], binEdges[1:]), 2.0)
            
            # Compute a split on the histogram
            splitVal = histogram.splitHistogramKittlerIllingworth(hist, binCenters)
            print "Computed local threshold = " + str(splitVal)
            localThresholdList.append(splitVal)
            usedPointList.append(thisLoc)
            
            #plt.bar(binCenters, hist)
            #plt.show()
        except Exception,e:
            print 'Failed to compute a location:'
            print str(e)
Example #10
0
            #plt.show()
        except Exception,e:
            print 'Failed to compute a location:'
            print str(e)
       
    numUsedPoints   = len(usedPointList)
    numUnusedPoints = len(rejectedPointList)

    if (numUsedPoints > 0):
        usedPointListEE = ee.FeatureCollection(ee.Feature(usedPointList[0]))
        for i in range(1,numUsedPoints):
            temp = ee.FeatureCollection(ee.Feature(usedPointList[i]))
            usedPointListEE = usedPointListEE.merge(temp)       

        usedPointsDraw = usedPointListEE.draw('00FF00', 8)
        addToMap(usedPointsDraw, {}, 'Used PTs', False)
        
    if (numUnusedPoints > 0):
        unusedPointListEE = ee.FeatureCollection(ee.Feature(rejectedPointList[0]))
        for i in range(1,numUnusedPoints):
            temp = ee.FeatureCollection(ee.Feature(rejectedPointList[i]))
            unusedPointListEE = unusedPointListEE.merge(temp)       

        unusedPointsDraw = unusedPointListEE.draw('FF0000', 8)
        addToMap(unusedPointsDraw, {}, 'Unused PTs', False)
    
    
    # 5: Use the individual thresholds to compute a global threshold 
    
    computedThreshold = numpy.median(localThresholdList) # Nothing fancy going on here!
    
Example #11
0
def Lake_Level_Run(lake, date = None, enddate = None, results_dir = None, fai=False, ndti=False, \
                   update_function = None, complete_function = None):
    if date is None:
        start_date = ee.Date('1984-01-01')
        end_date = ee.Date('2030-01-01')
    elif enddate != None and date != None:
        start_date = ee.Date(date)
        end_date = ee.Date(enddate)
        if dt.strptime(date, '%Y-%m-%d') > dt.strptime(enddate, '%Y-%m-%d'):
            print "Date range invalid: Start date is after end date. Please adjust date range and retry."
            return
        elif dt.strptime(date, '%Y-%m-%d') == dt.strptime(enddate, '%Y-%m-%d'):
            print "Date range invalid: Start date is same as end date. Please adjust date range and retry."
            return
    else:
        start_date = ee.Date(date)
        end_date = start_date.advance(1.0, 'month')

    # start_date = ee.Date('2011-06-01') # lake high
    # start_date = ee.Date('1993-07-01') # lake low
    # start_date = ee.Date('1993-06-01') # lake low but some jet streams

    # --- This is the database containing all the lake locations!
    # all_lakes = ee.FeatureCollection('ft:13s-6qZDKWXsLOWyN7Dap5o6Xuh2sehkirzze29o3', "geometry").toList(1000000)

    if lake is not None:
        all_lakes = ee.FeatureCollection(
            'ft:1igNpJRGtsq2RtJuieuV0DwMg5b7nU8ZHGgLbC7iq',
            "geometry").filterMetadata(u'LAKE_NAME', u'equals',
                                       lake).toList(1000000)
        if all_lakes.size() == 0:
            print 'Lake not found in database. Ending process...'
    else:
        # bounds = ee.Geometry.Rectangle(-125.29, 32.55, -114.04, 42.02)
        # all_lakes = ee.FeatureCollection('ft:13s-6qZDKWXsLOWyN7Dap5o6Xuh2sehkirzze29o3', "geometry").filterBounds(bounds).toList(1000000)
        all_lakes = ee.FeatureCollection(
            'ft:1igNpJRGtsq2RtJuieuV0DwMg5b7nU8ZHGgLbC7iq',
            "geometry").toList(1000000)
        # .filterMetadata(u'AREA_SKM', u'less_than', 300.0).toList(100000)#.filterMetadata(
        # u'LAT_DEG', u'less_than',   42.02).filterMetadata( u'LAT_DEG', u'greater_than', 32.55).filterMetadata(
        # u'LONG_DEG', u'less_than', -114.04).filterMetadata(u'LONG_DEG', u'greater_than', -125.29).toList(1000000)
        # pprint(ee.Feature(all_lakes.get(0)).getInfo())

    # display individual image from a date
    if enddate != None and date != None:
        # Create output directory
        if not os.path.exists(results_dir):
            os.makedirs(results_dir)

        # Fetch ee information for all of the lakes we loaded from the database
        all_lakes_local = all_lakes.getInfo()
        for i in range(len(all_lakes_local)):  # For each lake...
            ee_lake = ee.Feature(all_lakes.get(i))  # Get this one lake
            # Spawn a processing thread for this lake
            LakeThread((all_lakes_local[i], ee_lake, start_date, end_date, results_dir, fai, ndti, \
                        functools.partial(update_function, i, len(all_lakes_local))))

        # Wait in this loop until all of the LakeThreads have stopped
        while True:
            thread_lock.acquire()
            if total_threads == 0:
                thread_lock.release()
                break
            thread_lock.release()
            time.sleep(0.1)
    elif date:
        from cmt.mapclient_qt import centerMap, addToMap
        lake = all_lakes.get(0).getInfo()
        ee_lake = ee.Feature(all_lakes.get(0))
        ee_bounds = ee_lake.geometry().buffer(1000)
        collection = get_image_collection(ee_bounds, start_date, end_date)
        landsat = ee.Image(collection.first())
        #pprint(landsat.getInfo())
        center = ee_bounds.centroid().getInfo()['coordinates']
        centerMap(center[0], center[1], 11)
        addToMap(landsat, {'bands': ['B3', 'B2', 'B1']}, 'Landsat 3,2,1 RGB')
        addToMap(landsat, {'bands': ['B7', 'B5', 'B4']}, 'Landsat 7,5,4 RGB',
                 False)
        addToMap(landsat, {'bands': ['B6']}, 'Landsat 6', False)
        clouds = detect_clouds(landsat)
        water = detect_water(landsat, clouds)
        addToMap(clouds.mask(clouds), {'opacity': 0.5}, 'Cloud Mask')
        addToMap(water.mask(water), {
            'opacity': 0.5,
            'palette': '00FFFF'
        }, 'Water Mask')
        addToMap(ee.Feature(ee_bounds))
        # print count_water_and_clouds(ee_bounds, landsat).getInfo()

    # compute water levels in all images of area
    else:
        # Create output directory
        if not os.path.exists(results_dir):
            os.makedirs(results_dir)

        # Fetch ee information for all of the lakes we loaded from the database
        all_lakes_local = all_lakes.getInfo()
        for i in range(len(all_lakes_local)):  # For each lake...
            ee_lake = ee.Feature(all_lakes.get(i))  # Get this one lake
            # Spawn a processing thread for this lake
            LakeThread((all_lakes_local[i], ee_lake, start_date, end_date, results_dir, \
                    functools.partial(update_function, i, len(all_lakes_local))))

        # Wait in this loop until all of the LakeThreads have stopped
        while True:
            thread_lock.acquire()
            if total_threads == 0:
                thread_lock.release()
                break
            thread_lock.release()
            time.sleep(0.1)
    if complete_function != None:
        complete_function()
        print "Operation completed."
def sar_martinis(domain, cr_method=False):
    '''Compute a global threshold via histogram splitting on selected subregions'''

    sensor = domain.get_radar()
    radarImage = sensor.image

    # Many papers reccomend a median type filter to remove speckle noise.

    # 1: Divide up the image into a grid of tiles, X

    # Divide up the region into a grid of subregions
    MAX_BOXES_PER_SIDE = 12  # Cap the number of boxes at 144
    DESIRED_BOX_SIZE_METERS = 3000
    boxList, boxSizeMeters = divideUpBounds(domain.bounds,
                                            DESIRED_BOX_SIZE_METERS,
                                            MAX_BOXES_PER_SIDE)

    # Extract the center point from each box
    centersList = map(getBoundsCenter, boxList)

    # SENTINEL = 12m/pixel
    KERNEL_SIZE = 13  # Each box will be covered by a 13x13 pixel kernel
    metersPerPixel = boxSizeMeters / KERNEL_SIZE
    print 'Using metersPerPixel: ' + str(metersPerPixel)

    avgKernel = ee.Kernel.square(KERNEL_SIZE, 'pixels', True)
    # <-- EE fails if this is in meters!

    # Select the radar layer we want to work in
    if 'water_detect_radar_channel' in domain.algorithm_params:
        channelName = domain.algorithm_params['water_detect_radar_channel']
    else:  # Just use the first radar channel
        channelName = sensor.band_names[0]

    # Rescale the input data so the statistics are not dominated by very bright pixels
    GRAY_MAX = 255
    grayLayer = applyCutlerLinearLogScale(radarImage.select([channelName]),
                                          domain.bounds)
    #addToMap(grayLayer, {'min': 0, 'max': GRAY_MAX,  'opacity': 1.0, 'palette': GRAY_PALETTE}, 'grayLayer',   False)

    # Compute the global mean, then make a constant image out of it.
    globalMean = grayLayer.reduceRegion(ee.Reducer.mean(), domain.bounds,
                                        metersPerPixel)
    globalMeanImage = ee.Image.constant(globalMean.getInfo()[channelName])

    print 'global mean = ' + str(globalMean.getInfo()[channelName])

    # Compute mean and standard deviation across the entire image
    meanImage = grayLayer.convolve(avgKernel)
    graysSquared = grayLayer.pow(ee.Image(2))
    meansSquared = meanImage.pow(ee.Image(2))
    meanOfSquaredImage = graysSquared.convolve(avgKernel)
    meansDiff = meanOfSquaredImage.subtract(meansSquared)
    stdImage = meansDiff.sqrt()

    # Debug plots
    #addToMap(meanImage, {'min': 3000, 'max': 70000,  'opacity': 1.0, 'palette': GRAY_PALETTE}, 'Mean',   False)
    #addToMap(stdImage,  {'min': 3000, 'max': 200000, 'opacity': 1.0, 'palette': GRAY_PALETTE}, 'StdDev', False)
    #addToMap(meanImage, {'min': 0, 'max': GRAY_MAX, 'opacity': 1.0, 'palette': GRAY_PALETTE}, 'Mean',   False)
    #addToMap(stdImage,  {'min': 0, 'max': 40,       'opacity': 1.0, 'palette': GRAY_PALETTE}, 'StdDev', False)

    # Compute these two statistics across the entire image
    CV = meanImage.divide(stdImage).reproject("EPSG:4326", None,
                                              metersPerPixel)
    R = meanImage.divide(globalMeanImage).reproject("EPSG:4326", None,
                                                    metersPerPixel)

    # 2: Prune to a reduced set of tiles X'

    # Parameters which control which sub-regions will have their histograms analyzed
    # - These are strongly influenced by the smoothing kernel size!!!
    MIN_CV = 0.7
    MAX_CV = 1.3
    MAX_R = 1.1
    MIN_R = 0.5

    # Debug plots
    addToMap(CV, {
        'min': 0,
        'max': 4.0,
        'opacity': 1.0,
        'palette': GRAY_PALETTE
    }, 'CV', False)
    addToMap(R, {
        'min': 0,
        'max': 4.0,
        'opacity': 1.0,
        'palette': GRAY_PALETTE
    }, 'R', False)

    if cr_method:
        MIN_CR = 0.10

        # sar_griefeneder reccomends replacing CV with CR = (std / gray value range), min value 0.05
        imageMin = grayLayer.reduceRegion(
            ee.Reducer.min(), domain.bounds,
            metersPerPixel).getInfo()[channelName]
        imageMax = grayLayer.reduceRegion(
            ee.Reducer.max(), domain.bounds,
            metersPerPixel).getInfo()[channelName]
        grayRange = imageMax - imageMin
        CR = stdImage.divide(grayRange)

        #addToMap(CR, {'min': 0, 'max': 0.3, 'opacity': 1.0, 'palette': GRAY_PALETTE}, 'CR', False)

    # Filter out pixels based on computed statistics
    t1 = CV.gte(MIN_CV)
    t2 = CV.lte(MAX_CV)
    t3 = R.gte(MIN_R)
    t4 = R.lte(MAX_R)
    if cr_method:
        temp = CR.gte(MIN_CR).And(t3).And(t4)
    else:
        temp = t1.And(t2).And(t3).And(t4)
    X_prime = temp.reproject("EPSG:4326", None, metersPerPixel)
    addToMap(X_prime.mask(X_prime), {
        'min': 0,
        'max': 1,
        'opacity': 1.0,
        'palette': TEAL_PALETTE
    }, 'X_prime', False)

    # 3: Prune again to a final set of tiles X''

    # Further pruning happens here but for now we are skipping it and using
    #  everything that got by the filter.  This would speed local computation.
    # - This is equivalent to using a large number for N'' in the original paper
    #    (which does not suggest a value for N'')
    X_doublePrime = X_prime

    # 4: For each tile, compute the optimal threshold

    # Assemble all local gray values at each point ?
    localPixelLists = grayLayer.neighborhoodToBands(avgKernel)

    maskWrapper = ee.ImageCollection([X_doublePrime])
    collection = ee.ImageCollection([localPixelLists])

    # Extract the point data at from each sub-region!

    localThresholdList = []
    usedPointList = []
    rejectedPointList = []
    for loc in centersList:

        try:
            thisLoc = ee.Geometry.Point(loc[1], loc[0])

            # If the mask for this location is invalid, skip this location
            maskValue = maskWrapper.getRegion(thisLoc, metersPerPixel)
            maskValue = maskValue.getInfo()[1][
                4]  # TODO: Not the best way to grab the value!
            if not maskValue:
                rejectedPointList.append(thisLoc)
                continue

            # Otherwise pull down all the pixel values surrounding this center point

            pointData = collection.getRegion(thisLoc, metersPerPixel)
            pixelVals = pointData.getInfo()[1][
                4:]  # TODO: Not the best way to grab the value!

            # TODO: Can EE handle making a histogram around this region or do we need to do this ourselves?
            #pointData = localPixelLists.reduceRegion(thisRegion, ee.Reducer.histogram(), SAMPLING_SCALE);
            #print pointData.getInfo()
            #print pixelVals
            #__show_histogram(pixelVals)
            #plt.bar(range(len(pixelVals)), pixelVals)

            # Compute a histogram from the pixels (TODO: Do this with EE!)
            NUM_BINS = 256
            hist, binEdges = numpy.histogram(pixelVals, NUM_BINS)
            binCenters = numpy.divide(
                numpy.add(binEdges[:NUM_BINS], binEdges[1:]), 2.0)

            # Compute a split on the histogram
            splitVal = histogram.splitHistogramKittlerIllingworth(
                hist, binCenters)
            print "Computed local threshold = " + str(splitVal)
            localThresholdList.append(splitVal)
            usedPointList.append(thisLoc)

            #plt.bar(binCenters, hist)
            #plt.show()
        except Exception, e:
            print 'Failed to compute a location:'
            print str(e)
            #plt.show()
        except Exception, e:
            print 'Failed to compute a location:'
            print str(e)

    numUsedPoints = len(usedPointList)
    numUnusedPoints = len(rejectedPointList)

    if (numUsedPoints > 0):
        usedPointListEE = ee.FeatureCollection(ee.Feature(usedPointList[0]))
        for i in range(1, numUsedPoints):
            temp = ee.FeatureCollection(ee.Feature(usedPointList[i]))
            usedPointListEE = usedPointListEE.merge(temp)

        usedPointsDraw = usedPointListEE.draw('00FF00', 8)
        addToMap(usedPointsDraw, {}, 'Used PTs', False)

    if (numUnusedPoints > 0):
        unusedPointListEE = ee.FeatureCollection(
            ee.Feature(rejectedPointList[0]))
        for i in range(1, numUnusedPoints):
            temp = ee.FeatureCollection(ee.Feature(rejectedPointList[i]))
            unusedPointListEE = unusedPointListEE.merge(temp)

        unusedPointsDraw = unusedPointListEE.draw('FF0000', 8)
        addToMap(unusedPointsDraw, {}, 'Unused PTs', False)

    # 5: Use the individual thresholds to compute a global threshold

    computedThreshold = numpy.median(
        localThresholdList)  # Nothing fancy going on here!
# Fetch data set information
domain = cmt.domain.Domain()
domain.load_xml(sys.argv[1])

# Display the Landsat and MODIS data for the data set
cmt.util.gui_util.visualizeDomain(domain)

#
# import cmt.modis.adaboost
# cmt.modis.adaboost.adaboost_learn()         # Adaboost training
# #cmt.modis.adaboost.adaboost_dem_learn(None) # Adaboost DEM stats collection
# raise Exception('DEBUG')

waterMask = ee.Image("MODIS/MOD44W/MOD44W_005_2000_02_24").select(['water_mask'], ['b1'])
addToMap(waterMask.mask(waterMask), {'min': 0, 'max': 1}, 'Permanent Water Mask', False)

# For each of the algorithms
for a in range(len(ALGORITHMS)):
    # Run the algorithm on the data and get the results
    try:
        (alg, result) = detect_flood(domain, ALGORITHMS[a])
        if result is None:
            continue

        # These lines are needed for certain data sets which EE is not properly masking!!!
        # result = result.mask(domain.skybox_nir.image.reduce(ee.Reducer.allNonZero()))
        # result = result.mask(domain.skybox.image.reduce(ee.Reducer.allNonZero()))

        # Get a color pre-associated with the algorithm, then draw it on the map
        color = get_algorithm_color(ALGORITHMS[a])
         #u'LAT_DEG', u'less_than',   42.02).filterMetadata( u'LAT_DEG', u'greater_than', 32.55).filterMetadata(
         #u'LONG_DEG', u'less_than', -114.04).filterMetadata(u'LONG_DEG', u'greater_than', -125.29).toList(1000000)
    #pprint(ee.Feature(all_lakes.get(0)).getInfo())

# display individual image from a date
if args.date:
    from cmt.mapclient_qt import centerMap, addToMap
    lake       = all_lakes.get(0).getInfo()
    ee_lake    = ee.Feature(all_lakes.get(0))
    ee_bounds  = ee_lake.geometry().buffer(1000)
    collection = get_image_collection(ee_bounds, start_date, end_date)
    landsat    = ee.Image(collection.first())
    #pprint(landsat.getInfo())
    center = ee_bounds.centroid().getInfo()['coordinates']
    centerMap(center[0], center[1], 11)
    addToMap(landsat, {'bands': ['B3', 'B2', 'B1']}, 'Landsat 3,2,1 RGB')
    addToMap(landsat, {'bands': ['B7', 'B5', 'B4']}, 'Landsat 7,5,4 RGB', False)
    addToMap(landsat, {'bands': ['B6'            ]}, 'Landsat 6',         False)
    clouds = detect_clouds(landsat)
    water  = detect_water(landsat, clouds)
    addToMap(clouds.mask(clouds), {'opacity' : 0.5}, 'Cloud Mask')
    addToMap(water.mask(water), {'opacity' : 0.5, 'palette' : '00FFFF'}, 'Water Mask')
    addToMap(ee.Feature(ee_bounds))
    #print count_water_and_clouds(ee_bounds, landsat).getInfo()
    
# compute water levels in all images of area
else:
    # Create output directory
    if not os.path.exists(args.results_dir):
        os.makedirs(args.results_dir)
        
Example #16
0
            # plt.show()
        except Exception, e:
            print "Failed to compute a location:"
            print str(e)

    numUsedPoints = len(usedPointList)
    numUnusedPoints = len(rejectedPointList)

    if numUsedPoints > 0:
        usedPointListEE = ee.FeatureCollection(ee.Feature(usedPointList[0]))
        for i in range(1, numUsedPoints):
            temp = ee.FeatureCollection(ee.Feature(usedPointList[i]))
            usedPointListEE = usedPointListEE.merge(temp)

        usedPointsDraw = usedPointListEE.draw("00FF00", 8)
        addToMap(usedPointsDraw, {}, "Used PTs", False)

    if numUnusedPoints > 0:
        unusedPointListEE = ee.FeatureCollection(ee.Feature(rejectedPointList[0]))
        for i in range(1, numUnusedPoints):
            temp = ee.FeatureCollection(ee.Feature(rejectedPointList[i]))
            unusedPointListEE = unusedPointListEE.merge(temp)

        unusedPointsDraw = unusedPointListEE.draw("FF0000", 8)
        addToMap(unusedPointsDraw, {}, "Unused PTs", False)

    # 5: Use the individual thresholds to compute a global threshold

    computedThreshold = numpy.median(localThresholdList)  # Nothing fancy going on here!

    print "Computed global threshold = " + str(computedThreshold)
def Lake_Level_Run(lake, date = None, enddate = None, results_dir = None, fai=False, ndti=False, \
                   update_function = None, complete_function = None):
    if date is None:
        start_date = ee.Date('1984-01-01')
        end_date   = ee.Date('2030-01-01')
    elif enddate != None and date != None:
        start_date = ee.Date(date)
        end_date   = ee.Date(enddate)
        if dt.strptime(date,'%Y-%m-%d') > dt.strptime(enddate,'%Y-%m-%d'):
            print "Date range invalid: Start date is after end date. Please adjust date range and retry."
            return
        elif dt.strptime(date,'%Y-%m-%d') == dt.strptime(enddate,'%Y-%m-%d'):
            print "Date range invalid: Start date is same as end date. Please adjust date range and retry."
            return
    else:
        start_date = ee.Date(date)
        end_date = start_date.advance(1.0, 'month')

    # start_date = ee.Date('2011-06-01') # lake high
    # start_date = ee.Date('1993-07-01') # lake low
    # start_date = ee.Date('1993-06-01') # lake low but some jet streams

    # --- This is the database containing all the lake locations!
    # all_lakes = ee.FeatureCollection('ft:13s-6qZDKWXsLOWyN7Dap5o6Xuh2sehkirzze29o3', "geometry").toList(1000000)

    if lake is not None:
        all_lakes = ee.FeatureCollection('ft:1igNpJRGtsq2RtJuieuV0DwMg5b7nU8ZHGgLbC7iq', "geometry").filterMetadata(u'LAKE_NAME', u'equals', lake).toList(1000000)
        if all_lakes.size() == 0:
            print 'Lake not found in database. Ending process...'
    else:
        # bounds = ee.Geometry.Rectangle(-125.29, 32.55, -114.04, 42.02)
        # all_lakes = ee.FeatureCollection('ft:13s-6qZDKWXsLOWyN7Dap5o6Xuh2sehkirzze29o3', "geometry").filterBounds(bounds).toList(1000000)
        all_lakes = ee.FeatureCollection('ft:1igNpJRGtsq2RtJuieuV0DwMg5b7nU8ZHGgLbC7iq', "geometry").toList(1000000)
        # .filterMetadata(u'AREA_SKM', u'less_than', 300.0).toList(100000)#.filterMetadata(
        # u'LAT_DEG', u'less_than',   42.02).filterMetadata( u'LAT_DEG', u'greater_than', 32.55).filterMetadata(
        # u'LONG_DEG', u'less_than', -114.04).filterMetadata(u'LONG_DEG', u'greater_than', -125.29).toList(1000000)
        # pprint(ee.Feature(all_lakes.get(0)).getInfo())

    # display individual image from a date
    if enddate != None and date != None:
        # Create output directory
        if not os.path.exists(results_dir):
            os.makedirs(results_dir)

        # Fetch ee information for all of the lakes we loaded from the database
        all_lakes_local = all_lakes.getInfo()
        for i in range(len(all_lakes_local)):  # For each lake...
            ee_lake = ee.Feature(all_lakes.get(i))  # Get this one lake
            # Spawn a processing thread for this lake
            LakeThread((all_lakes_local[i], ee_lake, start_date, end_date, results_dir, fai, ndti, \
                        functools.partial(update_function, i, len(all_lakes_local))))

        # Wait in this loop until all of the LakeThreads have stopped
        while True:
            thread_lock.acquire()
            if total_threads == 0:
                thread_lock.release()
                break
            thread_lock.release()
            time.sleep(0.1)
    elif date:
        from cmt.mapclient_qt import centerMap, addToMap
        lake       = all_lakes.get(0).getInfo()
        ee_lake    = ee.Feature(all_lakes.get(0))
        ee_bounds  = ee_lake.geometry().buffer(1000)
        collection = get_image_collection(ee_bounds, start_date, end_date)
        landsat    = ee.Image(collection.first())
        #pprint(landsat.getInfo())
        center = ee_bounds.centroid().getInfo()['coordinates']
        centerMap(center[0], center[1], 11)
        addToMap(landsat, {'bands': ['B3', 'B2', 'B1']}, 'Landsat 3,2,1 RGB')
        addToMap(landsat, {'bands': ['B7', 'B5', 'B4']}, 'Landsat 7,5,4 RGB', False)
        addToMap(landsat, {'bands': ['B6'            ]}, 'Landsat 6',         False)
        clouds = detect_clouds(landsat)
        water = detect_water(landsat, clouds)
        addToMap(clouds.mask(clouds), {'opacity' : 0.5}, 'Cloud Mask')
        addToMap(water.mask(water), {'opacity' : 0.5, 'palette' : '00FFFF'}, 'Water Mask')
        addToMap(ee.Feature(ee_bounds))
        # print count_water_and_clouds(ee_bounds, landsat).getInfo()

    # compute water levels in all images of area
    else:
        # Create output directory
        if not os.path.exists(results_dir):
            os.makedirs(results_dir)

        # Fetch ee information for all of the lakes we loaded from the database
        all_lakes_local = all_lakes.getInfo()
        for i in range(len(all_lakes_local)):  # For each lake...
            ee_lake = ee.Feature(all_lakes.get(i))  # Get this one lake
            # Spawn a processing thread for this lake
            LakeThread((all_lakes_local[i], ee_lake, start_date, end_date, results_dir, \
                    functools.partial(update_function, i, len(all_lakes_local))))

        # Wait in this loop until all of the LakeThreads have stopped
        while True:
            thread_lock.acquire()
            if total_threads == 0:
                thread_lock.release()
                break
            thread_lock.release()
            time.sleep(0.1)
    if complete_function != None:
        complete_function()
        print "Operation completed."
Example #18
0
def dnns_revised(domain, b):
    '''Dynamic Nearest Neighbor Search with revisions to improve performance on our test data'''
    
    # One issue with this algorithm is that its large search range slows down even Earth Engine!
    # - With a tiny kernel size, everything is relative to the region average which seems to work pretty well.
    # Another problem is that we don't have a good way of identifying 'Definately land' pixels like we do for water.
    
    # Parameters
    KERNEL_SIZE = 1 # The original paper used a 100x100 pixel box = 25,000 meters!
    PURELAND_THRESHOLD = 3500 # TODO: Vary by domain?
    PURE_WATER_THRESHOLD_RATIO = 0.1
    
    # Set up two square kernels of the same size
    # - These kernels define the search range for nearby pure water and land pixels
    kernel            = ee.Kernel.square(KERNEL_SIZE, 'pixels', False)
    kernel_normalized = ee.Kernel.square(KERNEL_SIZE, 'pixels', True)
    
    composite_image = b['b1'].addBands(b['b2']).addBands(b['b6'])
    
    # Compute (b2 - b1) < threshold, a simple water detection algorithm.  Treat the result as "pure water" pixels.
    pureWaterThreshold = float(domain.algorithm_params['modis_diff_threshold']) * PURE_WATER_THRESHOLD_RATIO
    pureWater = modis_diff(domain, b, pureWaterThreshold)
    
    # Compute the mean value of pure water pixels across the entire region, then store in a constant value image.
    AVERAGE_SCALE_METERS = 30 # This value seems to have no effect on the results
    averageWater      = safe_get_info(pureWater.mask(pureWater).multiply(composite_image).reduceRegion(ee.Reducer.mean(), domain.bounds, AVERAGE_SCALE_METERS))
    averageWaterImage = ee.Image([averageWater['sur_refl_b01'], averageWater['sur_refl_b02'], averageWater['sur_refl_b06']])
    
    # For each pixel, compute the number of nearby pure water pixels
    pureWaterCount = pureWater.convolve(kernel)
    # Get mean of nearby pure water (b1,b2,b6) values for each pixel with enough pure water nearby
    MIN_PURE_NEARBY = 10
    averageWaterLocal = pureWater.multiply(composite_image).convolve(kernel).multiply(pureWaterCount.gte(MIN_PURE_NEARBY)).divide(pureWaterCount)
    # For pixels that did not have enough pure water nearby, just use the global average water value
    averageWaterLocal = averageWaterLocal.add(averageWaterImage.multiply(averageWaterLocal.Not()))

   
    # Use simple diff method to select pure land pixels
    #LAND_THRESHOLD   = 2000 # TODO: Move to domain selector
    pureLand             = b['b2'].subtract(b['b1']).gte(PURELAND_THRESHOLD).select(['sur_refl_b02'], ['b1']) # Rename sur_refl_b02 to b1
    averagePureLand      = safe_get_info(pureLand.mask(pureLand).multiply(composite_image).reduceRegion(ee.Reducer.mean(), domain.bounds, AVERAGE_SCALE_METERS))
    averagePureLandImage = ee.Image([averagePureLand['sur_refl_b01'], averagePureLand['sur_refl_b02'], averagePureLand['sur_refl_b06']])
    pureLandCount        = pureLand.convolve(kernel)        # Get nearby pure land count for each pixel
    averagePureLandLocal = pureLand.multiply(composite_image).convolve(kernel).multiply(pureLandCount.gte(MIN_PURE_NEARBY)).divide(pureLandCount)
    averagePureLandLocal = averagePureLandLocal.add(averagePureLandImage.multiply(averagePureLandLocal.Not())) # For pixels that did not have any pure land nearby, use mean


    # Implement equations 10 and 11 from the paper
    oneOverSix   = b['b1'].divide(b['b6'])
    twoOverSix   = b['b2'].divide(b['b6'])
    eqTenLeft    = oneOverSix.subtract( averageWaterLocal.select('sur_refl_b01').divide(b['b6']) )
    eqElevenLeft = twoOverSix.subtract( averageWaterLocal.select('sur_refl_b02').divide(b['b6']) )
    
    # For each pixel, grab all the ratios from nearby pixels
    nearbyPixelsOneOverSix = oneOverSix.neighborhoodToBands(kernel) # Each of these images has one band per nearby pixel
    nearbyPixelsTwoOverSix = twoOverSix.neighborhoodToBands(kernel)
    nearbyPixelsOne        = b['b1'].neighborhoodToBands(kernel)
    nearbyPixelsTwo        = b['b2'].neighborhoodToBands(kernel)
    nearbyPixelsSix        = b['b6'].neighborhoodToBands(kernel)
    
    # Find which nearby pixels meet the EQ 10 and 11 criteria
    eqTenMatches        = ( nearbyPixelsOneOverSix.gt(eqTenLeft   ) ).And( nearbyPixelsOneOverSix.lt(oneOverSix) )
    eqElevenMatches     = ( nearbyPixelsTwoOverSix.gt(eqElevenLeft) ).And( nearbyPixelsTwoOverSix.lt(twoOverSix) )
    nearbyLandPixels    = eqTenMatches.And(eqElevenMatches)
    
    # Find the average of the nearby matching pixels
    numNearbyLandPixels = nearbyLandPixels.reduce(ee.Reducer.sum())
    meanNearbyBandOne   = nearbyPixelsOne.multiply(nearbyLandPixels).reduce(ee.Reducer.sum()).divide(numNearbyLandPixels)
    meanNearbyBandTwo   = nearbyPixelsTwo.multiply(nearbyLandPixels).reduce(ee.Reducer.sum()).divide(numNearbyLandPixels)
    meanNearbyBandSix   = nearbyPixelsSix.multiply(nearbyLandPixels).reduce(ee.Reducer.sum()).divide(numNearbyLandPixels)


    # Pack the results into a three channel image for the whole region
    meanNearbyLand = meanNearbyBandOne.addBands(meanNearbyBandTwo).addBands(meanNearbyBandSix)
    meanNearbyLand = meanNearbyLand.multiply(numNearbyLandPixels.gte(MIN_PURE_NEARBY)).add( averagePureLandImage.multiply(numNearbyLandPixels.lt(MIN_PURE_NEARBY)) )

    addToMap(numNearbyLandPixels,  {'min': 0, 'max': 400, }, 'numNearbyLandPixels', False)
    addToMap(meanNearbyLand,       {'min': 0, 'max': 3000, 'bands': ['sum', 'sum_1', 'sum_2']}, 'meanNearbyLand', False)

    
    # Compute the water fraction: (land - b) / (land - water)
    landDiff  = averagePureLandLocal.subtract(composite_image)
    waterDiff = averageWaterLocal.subtract(composite_image)
    typeDiff  = averagePureLandLocal.subtract(averageWaterLocal)
    #water_vector   = (averageLandLocal.subtract(b)).divide(averageLandLocal.subtract(averageWaterLocal))
    landDist  = landDiff.expression("b('sur_refl_b01')*b('sur_refl_b01') + b('sur_refl_b02') *b('sur_refl_b02') + b('sur_refl_b06')*b('sur_refl_b06')").sqrt();
    waterDist = waterDiff.expression("b('sur_refl_b01')*b('sur_refl_b01') + b('sur_refl_b02') *b('sur_refl_b02') + b('sur_refl_b06')*b('sur_refl_b06')").sqrt();
    typeDist  = typeDiff.expression("b('sur_refl_b01')*b('sur_refl_b01') + b('sur_refl_b02') *b('sur_refl_b02') + b('sur_refl_b06')*b('sur_refl_b06')").sqrt();
       
    #waterOff = landDist.divide(waterDist.add(landDist)) 
    waterOff = landDist.divide(typeDist) # TODO: Improve this math, maybe full matrix treatment?

    # Set pure water to 1, pure land to 0
    waterOff = waterOff.subtract(pureLand.multiply(waterOff))
    waterOff = waterOff.add(pureWater.multiply(ee.Image(1.0).subtract(waterOff)))
    
    # TODO: Better way of filtering out low fraction pixels.
    waterOff = waterOff.multiply(waterOff)
    waterOff = waterOff.gt(0.6)
    
    #addToMap(fraction,       {'min': 0, 'max': 1},   'fraction', False)
    addToMap(pureWater,      {'min': 0, 'max': 1},   'pure water', False)
    addToMap(pureLand,       {'min': 0, 'max': 1},   'pure land', False)
    addToMap(pureWaterCount, {'min': 0, 'max': 100}, 'pure water count', False)
    addToMap(pureLandCount,  {'min': 0, 'max': 100}, 'pure land count', False)
    
    
    #addToMap(numNearbyLandPixels,  {'min': 0, 'max': 400, }, 'numNearbyLandPixels', False)
    #addToMap(meanNearbyLand,       {'min': 0, 'max': 3000, 'bands': ['sum', 'sum_1', 'sum_2']}, 'meanNearbyLand', False)
    addToMap(averageWaterImage,    {'min': 0, 'max': 3000, 'bands': ['constant', 'constant_1', 'constant_2']}, 'average water', False)
    addToMap(averagePureLandImage, {'min': 0, 'max': 3000, 'bands': ['constant', 'constant_1', 'constant_2']}, 'average pure land',  False)
    addToMap(averageWaterLocal,    {'min': 0, 'max': 3000, 'bands': ['sur_refl_b01', 'sur_refl_b02', 'sur_refl_b06']}, 'local water ref', False)
    addToMap(averagePureLandLocal, {'min': 0, 'max': 3000, 'bands': ['sur_refl_b01', 'sur_refl_b02', 'sur_refl_b06']}, 'local pure land ref',  False)
    
    
    return waterOff.select(['sur_refl_b01'], ['b1']) # Rename sur_refl_b02 to b1