95th percentile greenest pixel quality mosaic – Sentinel 2 – instead of max

google-earth-enginemosaicndvisentinel-2soil

I am trying to combine the approaches suggested in the answers to this post – Google Engine – Make a greenest pixel composite for Sentinel 2 – to make a quality mosaic, based on the 95th percentile NDVI value, in order to reduce errors from using the max NDVI.

I've attempted to make a function to define the 95th percentile NDVI value using a reducer, then filter the image collection for values below this threshold. Then I make a qualityMosaic, based on the filtered image collection.

I suspect there are several problems here, possibly I am mixing functions for image vs image collection, and possibly it is not doing what I intend – i.e filtering by 95th percentile ndvi values for EACH pixel within the image collection. The image collection is the same after I apply my ndvi filter function, and the output image seems to be the same as a greenest-pixel composite.

My ultimate aim here is to give a good representation of both bare soil, and have the greenest values for the surrounding vegetation. You can see I get some strange effects for bare soil using greenest pixel quality mosaic – this is what I'm hoping to improve by using the 95th percentile.

how to represent bare soil in a greenest pixel composite?

// Area of interest
var x1 = 5.8;   //West longitude bound
var x2 = 6.45;   //East longitude bound
var y1 = 61.7;  //North latitude bound
var y2 = 61.4;  //South latitude bound

var area =ee.Geometry.Polygon([[x1, y1], [x2, y1], [x2, y2], [x1, y2]]);

//////////////// Functions ////////////////
//add NDVI
function addNDVI(image) {
  var ndvi = image.normalizedDifference(['B8', 'B4']);
  return image.addBands(ndvi.rename('ndvi'));
}

//filter by 95th percentile
function filter_ndvi95(image){
  var lte_ndvi95 = image.select('ndvi').lte(ndvi_95);
  return image
}

//////////////// Analysis ////////////////
var filtered = ee.ImageCollection("COPERNICUS/S2").
        filterDate('2020-01-01', '2020-12-31').
        filterBounds(area);

var with_ndvi = filtered.map(addNDVI);
print(with_ndvi)
var ndvi_95 = with_ndvi.reduce(ee.Reducer.percentile([95]));

//filter image collection by NDVI 95th percentile 
var filtered_ndvi95 = with_ndvi.map(filter_ndvi95);
print(filtered_ndvi95)

// create greenest pixel quality mosaic
var greenest = filtered_ndvi95.qualityMosaic('ndvi').clip(area);

//////////////// User Interface ////////////////
var rgb_vis = {min: 0, max: 3000, gamma: 1.5, bands: ['B4', 'B3', 'B2']};
Map.addLayer(greenest, rgb_vis, 'RGB (greenest pixel)');
Map.centerObject(area);

Best Answer

Your original script has some errors so it is not functioning as you intend. This part of your script returns the original image because you returned image and not lte_ndvi95. There are other issues here that would cause your script to error which I will address later, but that is primary reason why you don't see any changes in the resulting mosaic.

//filter by 95th percentile
function filter_ndvi95(image){
  var lte_ndvi95 = image.select('ndvi').lte(ndvi_95);
  return image
}

From the way your script is written, I assume you are trying to:

  1. Get the 95th percentile NDVI value for the image
  2. Mask out pixels that are above the 95th percentile for that specific image.
  3. Return the image with all pixels that have NDVI values above the 95th percentile removed

Here is the edited function that accomplishes those goals:

function filter_ndvi95(image){
  // Get the 95th percentile NDVI value for image
  var ndvi95 = ee.Number(image.reduceRegion(ee.Reducer.percentile([95]),area).get('ndvi'))
  
  // Create a mask selecting all values less than the 95th percentile
  var lte95_mask = image.select('ndvi').lte(ndvi95)
  
  // Update the image removing all values greater than the 95th percentile
  image = image.updateMask(lte95_mask)
  
  return image
}

You can see from an image of the unfiltered image minus the filtered image that the function is working as written and that there are some differences between the filtered and unfiltered pixels (darker red means more different) but not all pixels are different.

Unfiltered subtract Filtered

However, I'm not sure that this is what you want, since the pixels being removed are more likely to be areas of high vegetation, which doesn't necessarily make your bare soil more clear. See the 2 clippings below.

Unfiltered

Unfiltered

Filtered

Filtered

If you are looking to make cleaner mosaic images for visual purposes, I think that there are better methods out there. However, if you are trying to do analysis on NDVI, or band information, it would be helpful to know what question you are trying to solve for to be able to point you in the right direction. For example, if you were simply looking for the 95th highest NDVI value for every pixel in an ee.ImageCollection, that could be easily accomplished by using unfiltered.reduce(ee.Reducer.percentile([95])).

Full code: https://code.earthengine.google.com/4ef3e5d83ebb5694cca6064585b114b4

Related Question