def sar_martinis(domain, cr_method=False):
    
    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)
       
    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)
    
    finalWaterClass = grayLayer.lte(computedThreshold)

    #addToMap(finalWaterClass.mask(finalWaterClass),  {'min': 0, 'max': 1, 'opacity': 0.6, 'palette': RED_PALETTE}, 'mirtinis class',  False)
    
    # Rename the channel to what the evaluation function requires
    finalWaterClass = finalWaterClass.select([channelName], ['b1'])
    
    return finalWaterClass
Using a non-string key as argument to commands raises an error.
dataset/ETHPy150Open nasa/CrisisMappingToolkit/cmt/radar/martinis.py/sar_martinis--crossover with--dataset/ETHPy150Open kuri65536/python-for-android/python-modules/twisted/twisted/test/test_memcache.py/MemCacheTestCase.test_unicodeKey