1

From the following code I would like to mask the edges of my sentinel 1 composites.

@Daniel provided the code.

var roi = Map.getBounds(true)
var startDate = ee.Date('2015-01-01')
var endDate = ee.Date('2015-03-01')
var deltaDays = 16

var myCollection = ee.ImageCollection('COPERNICUS/S1_GRD')
    .filter(ee.Filter.eq('instrumentMode', 'IW'))
    .filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VH'))
    .filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VV'))
    .filter(ee.Filter.eq('orbitProperties_pass', 'DESCENDING'))
    .filterBounds(roi);

var days = endDate.difference(startDate, 'days')
var composites = ee.ImageCollection(
  ee.List.sequence(0, days.subtract(1), deltaDays)
    .map(function (offsetDays) {
      var start = startDate.advance(offsetDays, 'days')
      var end = start.advance(deltaDays, 'days')
      var masked = ee.Image([ee.Image(), ee.Image()]).rename(['VV', 'VH'])
      var composite = myCollection
        .filterDate(start, end)
        .median()
      var empty = composite.bandNames().size().eq(0)
      composite = masked.addBands(composite, null, true)
      return composite
        .addBands(
          composite.expression('c.VV - c.VH', {c: composite})
            .rename('VV-VH')
        )
        .select(['VV', 'VH', 'VV-VH'])
        .set('start', start.format('yyyy-MM-dd')) // Include dates of composite
        .set('end', end.format('yyyy-MM-dd'))
        .set('empty', empty)
        .clip(roi)
    })
  )
  .filterMetadata('empty', 'equals', 0) // Drop empty images
  .toList(1000)

I tried this but it did not bring me to any solutions:

exports.maskEdge = function(img) {
  var mask = img.select(0).unitScale(-25, 5).multiply(255).toByte().connectedComponents(ee.Kernel.rectangle(1,1), 100);
  return img.updateMask(mask.select(0)).set('system:time_start', img.get('system:time_start'));  
};

2 Answers 2

2

I usually use the angle band to mask the sides of the scenes, only allowing angles between 31 and 45 degrees. There's often some noise at the beginning and end of each track, so I mask that out too. This might lead to gaps in your composites though, so you might want to skip that in some cases.

function maskBorder(image) {
  var totalSlices = ee.Number(image.get('totalSlices'))
  var sliceNumber = ee.Number(image.get('sliceNumber'))
  var middleSlice = ee.Image(sliceNumber.gt(1).and(sliceNumber.lt(totalSlices)))
  var mask = image.select(['VV', 'VH']).mask().reduce(ee.Reducer.min()).floor()
  var pixelsToMask = mask.not()
    .fastDistanceTransform(128, 'pixels').sqrt()
  var metersToMask = pixelsToMask  
    .multiply(ee.Image.pixelArea().sqrt())    
    .rename('metersToMask')
  var notBorder = metersToMask.gte(500).and(pixelsToMask.gt(2))
  var angle = image.select('angle')
  return image
      .updateMask(
          angle.gt(31).and(angle.lt(45))
          .and(middleSlice.or(notBorder))
      )    
}

https://code.earthengine.google.com/97698941ca6e15516ae890a0c2676e0c

6
  • Unrelated to this, but you might want to convert to gamma 0, to get a smother composite. code.earthengine.google.com/97698941ca6e15516ae890a0c2676e0c Jan 21, 2020 at 7:10
  • thank you. But for some reason, I still have the edges in my roi: ``` var roi = ee.Geometry.Polygon( [[[-69.98350047919075,-12.690380458271608], [-69.98350047919075,-12.643150122426537], [-70.05250835272591,-12.643150122426537], [-70.05250835272591,-12.690380458271608]]], null, false); ``` And yes, I know about the visualization smother. But I need these data for a f/nf classifiction so in linear values. Jan 21, 2020 at 13:51
  • You have a gap between scenes, with no data at all. So masking edges won't do much to fix that? code.earthengine.google.com/593f27c6210183f2a3d5fb0cc900cc94 Jan 21, 2020 at 17:33
  • On gamma0: You can convert to gamma0 (treat Earth as an ellipsoid, not flat), then convert it linear for your classification. Jan 21, 2020 at 17:35
  • To get more data, try switching between ascending/descending orbits, or increase your 'deltaDays'. Your ROI seems to have more data for descending orbits. code.earthengine.google.com/0e5a0b4ae62d9f7194331189c686c5d7 Jan 21, 2020 at 17:48
1

I have been struggling with this same issue today, the solution above wasn't really satisfactory for our use case as it filters out too much data and is too imprecise. I came up with the following solution in python, hopefully it will be useful to someone else:

def maskBorder(img, buffer_area=5, pct_min_value=0.5, band='VV'):
    """
    Function that masks artifacts on the border of an Earth Engine Image if they are below a certain value. Can 
    also be used on a collection by doing: collection = collection.map(maskBorder)

    Args:
    img: Earth Engine Image
    buffer_area: How many pixels from the border should be considered part of the border.
    pct_min_value: Minimum percentile value below which to filter out outliers.
    band: Which band to use for checking if the value is an outlier.

    Returns:
    Returns the same image withdef maskBorder(img, buffer_area=5, pct_min_value=0.5, band='VV'):
"""
Function that masks artifacts on the border of an Earth Engine Image if they are below a certain value. Can 
also be used on a collection by doing: collection = collection.map(maskBorder)

Args:
    img: Earth Engine Image
    buffer_area: How many pixels from the border should be considered part of the border.
    pct_min_value: Minimum percentile value below which to filter out outliers.
    band: Which band to use for checking if the value is an outlier.

Returns:
    Returns the same image with border artifacts masked.
"""

# The minimum number of pixels around a certain pixel
min_pixel_count = (2*buffer_area+1)*(2*buffer_area+1)

# Mask for determining which pixels are on the border.
mask1 = img.reduceNeighborhood(reducer=ee.Reducer.count(), kernel=ee.Kernel.square(buffer_area)).select(f'{band}_count').lt(min_pixel_count)

# Minimum value of a VV pixel at the border (trying to filter out low outliers)
min_value = img.reduceRegion(reducer=ee.Reducer.percentile([pct_min_value]),  geometry=img.geometry().bounds(), 
                             scale=img.select(band).projection().nominalScale(), bestEffort=True).get(band)

# Mask for masking values below the minimum value
mask2 = img.select(band).lt(ee.Number(min_value))

# Mask out pixels that are both on the border and below the minimum value
mask = mask2.And(mask1).Not()

# Update the image mask
img = img.updateMask(mask)
return img border artifacts masked.
    """
    
    # The minimum number of pixels around a certain pixel
    min_pixel_count = (2*buffer_area+1)*(2*buffer_area+1)
    
    # Mask for determining which pixels are on the border.
    mask1 = img.reduceNeighborhood(reducer=ee.Reducer.count(), kernel=ee.Kernel.square(buffer_area)).select(f'{band}_count').lt(min_pixel_count)
    
    # Minimum value of a VV pixel at the border (trying to filter out low outliers)
    min_value = img.reduceRegion(reducer=ee.Reducer.percentile([pct_min_value]),  geometry=img.geometry().bounds(), 
                             scale=img.select(band).projection().nominalScale(), bestEffort=True).get(band)
    
    # Mask for masking values below the minimum value
    mask2 = img.select(band).lt(ee.Number(min_value))
    
    # Mask out pixels that are both on the border and below the minimum value
    mask = mask2.And(mask1).Not()
    
    # Update the image mask
    img = img.updateMask(mask)
    return img

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.