I have the following code where I calculate the greenest pixel composite for the months 5-9 from Landsat 7.

var geometry = 
    /* color: #d63000 */
    /* shown: false */
        [[[20.81272957288212, 39.87257138492846],
          [20.79075691663212, 39.8098319474331],
          [20.935639118780557, 39.798226782067786],
          [20.95555183850712, 39.860976816642435]]]);
var mask7 = function(image) {
  var qa = image.select('BQA');
  var cloud = qa.bitwiseAnd(1 << 4)
                  .and(qa.bitwiseAnd(1 << 6))
                  .or(qa.bitwiseAnd(1 << 8));
  var mask2 = image.mask().reduce(ee.Reducer.min());
  return image
       .select(['B3', 'B4','B5'], ['Red', 'NIR','SWIR'])
       .set('system:time_start', image.get('system:time_start'));

var dataset = ee.ImageCollection("LANDSAT/LE07/C01/T1_TOA")

var NDVI_l = function(image) {
  var ndvi = image.normalizedDifference(['NIR', 'Red']).rename('NDVI');
  return image.addBands(ndvi);

var NDVI_col = dataset

var greenest = NDVI_col.qualityMosaic('NDVI');


Map.addLayer(greenest.clip(geometry),{min:-1, max:1,  'palette': ['red','yellow', 'green']}, 'Greenest pixel composite');

However, I want the following:

  1. to calculate the period between the earliest greenup (from the greenest composite) per year and the latest browning (lowest NDVI pixel composite that I do no know how to calculate) per year (and for the period 1999-2018).
  2. to create an image collection with these periods (so an image per year with the maximum values)


  1. how can I calculate a composite of the latest lowest pixel NDVI values per year?
  2. how can I extract the period between the highest and lowest NDVIs per year?
  3. how can I extract the NDVI images between the earliest greenup and latest browning events per year?

Perhaps I need something like the following for the annual intervals?

var years = ee.List.sequence(1999, 2018);

var byyear = ee.ImageCollection.fromImages(
      years.map(function (y) {
        return greenest.filter(ee.Filter.calendarRange(y, y, 'year'))
                   .set('year', y);

2 Answers 2


You can get the min/max values and day of year of each by using the numInputs option on the min and max reducers to bring along the corresponding day of year (from a date band). Example using Sentinel-2:

// Mask clouds, compute NDVI and add a DOY band.
function prepare(image) {
  var qa = image.select('QA60')

  // Bits 10 and 11 are clouds and cirrus, respectively.
  var cloudBitMask = 1 << 10;
  var cirrusBitMask = 1 << 11;

  // Both flags should be set to zero, indicating clear conditions.
  var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(

  var doy = ee.Image.constant(image.date().getRelative('day', 'year')).int().rename('doy')

  // Return the masked and scaled data, without the QA bands.
  return image.updateMask(mask).normalizedDifference(["B5", "B4"]).rename("ndvi")
      .copyProperties(image, ["system:time_start"])

// Find the max NDVI and the corresponding DOY and then the min NDVI/DOY after the max date.
function minMax(year) {
  var date = ee.Date.fromYMD(year, 1, 1)
  var collection = ee.ImageCollection('COPERNICUS/S2')
    .filterDate(date, date.advance(1, 'year'))
  // Find the max NDVI and get the DOY that goes along with it.
  var max = collection.reduce(ee.Reducer.max(2)).rename('maxNDVI', 'maxDOY')
  // Find the min NDVI value AFTER the max DOY.
  var min = collection.map(function(image) {
    return image.updateMask(image.select('doy').gt(max.select('maxDOY')))
  .rename('minNDVI', 'minDOY')
  return max.addBands(min)

var results = ee.List.sequence(2016, 2020).map(minMax)
var resultsCollection = ee.ImageCollection.fromImages(results)

You can do any math you want the min/max NDVI/DOY by mapping a function to do simple band math after that.

Full code here: https://code.earthengine.google.com/9efbf16cda7c9a7c1c9730d3f877dc0c

  • thank you @Noel, it is very useful! I do play around with the code trying to adapt it with Landsat images and I get the error : Layer 1: Layer error: ImageCollection.reduce: Need 2 bands for Reducer.max, <ImageCollection> has 0. Is it because I use the NDVI collection for the reducer?
    – geo_dd
    Aug 24, 2021 at 7:20
  • Without seeing the modifications you made, it's hard to guess what went wrong. The cloud masking for Landsat is different than for Sentinel, so probably something there. You can use the CloudMasking examples in the code editor examples to see how it's done for different landsat datasets. Aug 24, 2021 at 11:00
  • apologies, I forgot to attach the link the code, here it is: code.earthengine.google.com/923bc7169f5330003268921194e82ed0 . I think that the masking is ok though.
    – geo_dd
    Aug 24, 2021 at 12:23
  • 1
    You removed the line that added the doy band, and then later selected only the NDVI band , which also removed the doy band. code.earthengine.google.com/e937e6c06ef464fbde7c6e7e8bd99846 Aug 24, 2021 at 16:03

Here's a solution to obtain the desired output for the last two questions in the OP. There are two main tricks in the procedure: 1) create an image that is an inverse of the NDVI (to get the browniest pixel) and 2) create images based on the date of each image to calculate the date difference. I modified your NDVI_l function, as well as the function used to obtain the byyear var.

var NDVI_l = function(image) {
  // Create image with ndvi
  var ndvi = image.normalizedDifference(['NIR', 'Red']).rename('NDVI');
  // Create image with date info
  var date = image.metadata('system:time_start', 'date')
                  .divide(1000 * 60 * 60 * 24);// To obtain date in days
  // Create image with the inverse of ndvi
  var invNdvi = ee.Image.constant(1).divide(ndvi).rename('InvNDVI');
  // Add all bands to the original image
  var temp = ndvi.addBands(date.addBands(invNdvi));
  return image.addBands(temp);

// Create sequence of years to evaluate
var years = ee.List.sequence(1999, 2018);

var byyear = ee.ImageCollection.fromImages(
      years.map(function (y) {
        // Get images for the year of interest and map NDVI_l
        var imCol =  dataset.filter(ee.Filter.calendarRange(y, y, 'year'))
        // Make quality mosaic based on NDVI value (priorizing high NDVI)
        var greenest = imCol.qualityMosaic('NDVI');
        // Make quality mosaic based on InvNDVI value (priorizing low NDVI)
        var brownest = imCol.qualityMosaic('InvNDVI');
        // Get date difference as absolute value and rename
        var dateDiff = greenest.select('date').subtract(brownest.select('date'))
        // Select only NDVI bands and rename
        greenest = greenest.select('NDVI').rename('NDVImax');
        brownest = brownest.select('NDVI').rename('NDVImin');
        // Return NDVImax, NDVImin and dateDiff
        var resul = greenest.addBands(brownest.addBands(dateDiff));
        resul = resul.set('year', ee.Number(y));
        return resul;

print('byyear', byyear);
  • thank you for your time! I am having a look now at your sugestion and I have 2 questions. 1: does the inverse (browning) image relies on the actual values of lowest NDVI or you just created it based on the greenest? 2. will the date diference give me the change between the greenest and browning instead of the images with the actual NDVI values for the period between the greenest and browning?
    – geo_dd
    Aug 16, 2021 at 17:36
  • 1
    I'll try to answer each question. 1) the browning image takes as input all the NDVI images inside the collection (not only the greenest), that is why its calculated on the dataset collection. 2) the date difference image gives you the date difference (in days) between the lowest (browniest) and highest (greeniest) NDVI for each pixel. Aug 16, 2021 at 17:58
  • Thank you @Jonathan V. Solórzano. It is clear to me now. Thank you!
    – geo_dd
    Aug 16, 2021 at 18:09
  • However, apart from the date difference (in days) between the lowest (browniest) and highest (greeniest) NDVI for each pixel, I need the actual max NDVI for this between greenest and browniest period. How can I extract this?
    – geo_dd
    Aug 16, 2021 at 18:31
  • Hmm, you could probably do that by masking the image collection (dataset) with the date bands. Something like: var filtCol = dataset.map(function(image){ return image.updateMask(image.gte(greenest.select('date)))}) and then doing the qualityMosaic. However, for doing this you should find a way to identify which date is prior and posterior, in order to apply the updateMask accordingly as .gte and lte, respectively. Aug 16, 2021 at 20:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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