I'm using Google Earth Engine Python API in Colab to carry out surface water delineation with Sentinel-1 data. The analysis uses Otsu thresholding with edge detection as described here: https://google-earth-engine.com/Aquatic-and-Hydrological-Applications/Surface-Water-Mapping/

The function I've written is intended to be mapped over an image collection; however, it returns dictionary reference errors when mapped. I've tested it on an individual image and it works with no issues. I'm guessing there is an issue with how dictionary values are being passed within the function, but I can't figure out where the error is coming from.

Here is a snippet of my code, illustrating the issue.

# Import libraries
import ee
import geemap
import os
import geemap.chart as chart
import matplotlib.pyplot as plt
import pandas as pd
import math
import numpy as np

# Import Sentinel-1 data as collection

roi = ee.Geometry.Rectangle(89.6206, 24.4842, 89.85,  23.769174)
# Start and end date definitions
startDate =ee.Date('2019-09-18')
endDate = ee.Date('2019-09-22')

# Data import, filter, and clip
s1Collection = (ee.ImageCollection('COPERNICUS/S1_GRD')
  .select(['VV', 'VH'])
  .map(lambda image: image.clip(roi))
  .map(lambda img: img.set('date', img.date().format('YYYY-MM-dd')))

# Otsu thresholding with edge detection.
# Adapted from: https://google-earth-engine.com/Aquatic-and-Hydrological-Applications/Surface-Water-Mapping/
# Adapted from: https://github.com/gee-community/geemap/blob/d8ba370caf712edec4e002653623fec930ec7312/examples/notebooks/otsu.ipynb

def otsu(histogram):
    counts = ee.Array(ee.Dictionary(histogram).get('histogram'))
    means = ee.Array(ee.Dictionary(histogram).get('bucketMeans'))
    size = means.length().get([0])
    total = counts.reduce(ee.Reducer.sum(), [0]).get([0])
    sum = means.multiply(counts).reduce(ee.Reducer.sum(), [0]).get([0])
    mean = sum.divide(total)

    indices = ee.List.sequence(1, size)

    # Compute between sum of squares, where each mean partitions the data.

    def func_xxx(i):
        aCounts = counts.slice(0, 0, i)
        aCount = aCounts.reduce(ee.Reducer.sum(), [0]).get([0])
        aMeans = means.slice(0, 0, i)
        aMean = (
            .reduce(ee.Reducer.sum(), [0])
        bCount = total.subtract(aCount)
        bMean = sum.subtract(aCount.multiply(aMean)).divide(bCount)
        return aCount.multiply(aMean.subtract(mean).pow(2)).add(

    bss = indices.map(func_xxx)

    # Return the mean value corresponding to the maximum BSS.
    return means.sort(bss).get([-1])

def edgeOtsu(image):

  # Define parameters for the adaptive thresholding.
  # Band for analysis
  band = 'VV'
  # Initial estimate of water/no-water for estimating the edges
  # Number of connected pixels to use for length calculation.
  # Length of edges to be considered water edges.
  # Buffer in meters to apply to edges.
  # Threshold for canny edge detection.
  # Sigma value for gaussian filter in canny edge detection.
  # Lower threshold for canny detection.

  # Get preliminary water.

  # Get projection information to convert buffer size to pixels.

  # Get canny edges.
        'image': binary,
        'threshold': cannyThreshold,
        'sigma': cannySigma

  # Process canny edges:
  # Get the edges and length of edges.
  # Mask short edges that can be noise.
  # Calculate the buffer in pixel size.
  # Buffer the edges using a dilation operation.
  # Mask areas not within the buffer .
  edgeImage= image.select(band).updateMask(bufferedEdges)

  localHistogram = ee.Dictionary(
            'reducer': ee.Reducer.histogram(255, 0.1),
            'geometry': edgeImage.geometry(),
            'scale': 90,
            'maxPixels' : 1e10,
            'bestEffort': True,
  threshold = otsu(localHistogram)
  date = image.date().format("YYYY-MM-DD")
  water = image.select(band).lt(threshold).selfMask().set({"date":date})
  return water.set({"threshold": threshold})
# When mapping the function I receive dictionary errors
water_images = s1Collection.map(edgeOtsu)
# If I apply the function to a single image I receive no errors
water_images = edgeOtsu(s1Collection.first())

1 Answer 1


FilterBounds includes any image whose footprint intersects your ROI in any way. That doesn't mean there are always valid pixels in your ROI, and if there are no valid pixels, then the output of a reduceRegion call will be null.

You have 2 images in your collection (you would have seen this if you'd printed it out), and one of them only barely intersects your ROI (you would have seen this if you'd displayed the results of the collection).

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.