I am exploring Sentinel-2 time-series NDVI with Google Earth Engine. In another post I learned how to plot a NDVI time series chart.

NDVI time series chart

I know that there are many smoothing methods for reducing NDVI noise, as per this article. I would like to apply one of them directly in GEE (not R), for example the Whittaker filter.

Can I use any of these methods in GEE? How?

2 Answers 2


Here is an example of how you can do this using Moving Window Smoothing, taken from https://www.youtube.com/watch?v=ncb5_zbvnaU&t=85s by Ujaval Gandhi of Spatial Thoughts:

var start = '2021-01-01'
var end = '2022-01-01'

var point = ee.Geometry.Point(-156.85136, 59.41592)
var patch = point.buffer(50).bounds()

// Get S2 data 
var S2_data = ee.ImageCollection('COPERNICUS/S2_SR')
    .filterBounds(patch).filterDate(start, end);

print('no. of S2 images', S2_data.size());

// Function to add a NDVI band to an image
function addNDVI(image) {
  var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
  return image.addBands(ndvi);

// Function to mask clouds
function maskS2clouds(image) {
  var qa = image.select('QA60')
  var cloudBitMask = 1 << 10;
  var cirrusBitMask = 1 << 11;
  var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
  return image.updateMask(mask).divide(10000)
      .copyProperties(image, ["system:time_start"])

//  Make S2 Image Collection 

var originalCollection = S2_data
  .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
  print ('no. of cloud-free S2 images', originalCollection.size())

// Moving-Window Smoothing

// Specify the time-window
var days = 15;

// Convert to milliseconds 
var millis = ee.Number(days).multiply(1000*60*60*24);

// We use a 'save-all join' to find all images 
// that are within the time-window

// The join will add all matching images into a
// new property called 'images'
var join = ee.Join.saveAll({
  matchesKey: 'images'

// This filter will match all images that are captured
// within the specified day of the source image
var diffFilter = ee.Filter.maxDifference({
  difference: millis,
  leftField: 'system:time_start', 
  rightField: 'system:time_start'

var joinedCollection = join.apply({
  primary: originalCollection, 
  secondary: originalCollection, 
  condition: diffFilter

print('Joined Collection', joinedCollection);

// Each image in the joined collection will contain
// matching images in the 'images' property
// Extract and return the mean of matched images
var extractAndComputeMean = function(image) {
  var matchingImages = ee.ImageCollection.fromImages(image.get('images'));
  var meanImage = matchingImages.reduce(
  return ee.Image(image).addBands(meanImage)

var smoothedCollection = ee.ImageCollection(

print('Smoothed Collection', smoothedCollection)

// Define the chart and print it to the console.

// Display a time-series chart
var chart_NDVI = ui.Chart.image.series({
  imageCollection: smoothedCollection.select(['ndvi', 'ndvi_moving_average']),
  region: patch,
  reducer: ee.Reducer.mean(),
  scale: 20
      title: 'NDVI Time Series',
      interpolateNulls: false,
      vAxis: {title: 'NDVI', viewWindow: {min: 0, max: 1}},
      hAxis: {title: '', format: 'YYYY-MM'},
      lineWidth: 1,
      pointSize: 4,
      series: {
        0: {color: '#66c2a4', lineDashStyle: [1, 1], pointSize: 2}, // Original NDVI
        1: {color: '#238b45', lineWidth: 2 }, // Smoothed NDVI


Image Convolutions is an option I would recommend using to achieve linear smoothing.

To perform linear convolutions on images, use image.convolve(). The only argument to convolve is an ee.Kernel which is specified by a shape and the weights in the kernel. Each pixel of the image output by convolve() is the linear combination of the kernel values and the input image pixels covered by the kernel. The kernels are applied to each band individually. For example, you might want to use a low-pass (smoothing) kernel to remove high-frequency information.

Sample Code

// Load and display an image.
var image = ee.Image('LANDSAT/LC08/C01/T1_TOA/LC08_044034_20140318');
Map.setCenter(-121.9785, 37.8694, 11);
Map.addLayer(image, {bands: ['B5', 'B4', 'B3'], max: 0.5}, 'input image');

// Define a boxcar or low-pass kernel.
var boxcar = ee.Kernel.square({
radius: 7, units: 'pixels', normalize: true

// Smooth the image by convolving with the boxcar kernel.
var smooth = image.convolve(boxcar);
Map.addLayer(smooth, {bands: ['B5', 'B4', 'B3'], max: 0.5}, 'smoothed');

Figure 1. Landsat 8 image convolved with a smoothing kernel. San Francisco Bay area, California, USA.

enter image description here

Figure 2. Landsat 8 image convolved with a Laplacian edge detection kernel. San Francisco Bay area, California, USA.

enter image description here

  • 4
    Actually the question is related to temporal filtering, and the convolve() filter is applied just "in the space of an image" Nov 28, 2018 at 16:31

Your Answer

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

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