As Kevin Reid pointed out, the problem here is that the region reduction by min
and/or max
is resulting in null
, which can occur when all the pixels in the region are masked. An alternative to reducing by count
(as suggested) you can also pre-calculate min and max, add them as image properties and then filter on them to remove images from your collection that are null
. This way you do not have to reduce the images an additional time for count
. Here is an example script that illustrates the problem and provides a solution.
Code Editor script
/**
* @license
* Copyright 2019 Google LLC.
* SPDX-License-Identifier: Apache-2.0
*/
// #############################################################################
// ### PROBLEM DEMO ###
// #############################################################################
// Define ROI.
var roi = ee.Geometry.Polygon(
[[[-0.06057661456907226, 40.842476672518494],
[-0.06057661456907226, 40.53424659065251],
[0.36789018230592774, 40.53424659065251],
[0.36789018230592774, 40.842476672518494]]], null, false);
// Import a sample image - set status.
var img = ee.Image('LANDSAT/LC08/C01/T1_SR/LC08_198032_20130813')
.set('status', 'noMask');
// Make a copy that is all masked - set status.
var imgAllMasked = img.gt(1e7).selfMask().set('status', 'allMask');
// Put images together in an image collection.
var l8collection = ee.ImageCollection.fromImages([img, imgAllMasked]);
// Define a function to add NDVI using NIR (B5) and the red band (B4).
var getNDVI = function(img){
return img.addBands(img.normalizedDifference(['B5','B4']).rename('NDVI'));
};
// Map NDVI function over the image collection.
var l8ndvi = l8collection.map(getNDVI);
// Subset just the 'allMask' image for testing with expression.
var allMasked = l8ndvi.filter('status == "allMask"').first();
// Define a combined reducer for min and max - one pass through the data.
var minMaxReducer = ee.Reducer.min().combine({
reducer2: ee.Reducer.max(),
sharedInputs: true
});
// Calculate regional min and max for the `allMasked` image.
var minMax = allMasked.reduceRegion(
{reducer: minMaxReducer, geometry:roi, bestEffort:true, scale: 30});
// Get the regional min and max NDVI values from the dictionary.
var min = minMax.get('NDVI_min');
var max = minMax.get('NDVI_max');
print('allMaskDemo min calc:', min); // null
print('allMaskDemo max calc:', max); // null
// Try using the expression function with null inputs; error occurs.
var allMaskDemo = allMasked.expression("(NDVI-min)/(max-min)", {
"NDVI": allMasked,
"max": ee.Number(max),
"min": ee.Number(min),
});
print('allMaskDemo index calc:', allMaskDemo);
// #############################################################################
// ### SOLUTION ###
// #############################################################################
// Need to filter out all of the images that have null min or max region
// reductions. Map a function over the image collection that adds the min and
// max region reductions as image properties and then filter out the ones that
// are null.
// Calculate regional min and max and add as image properties.
l8ndvi = l8ndvi.map(function(img) {
var minMax = img.reduceRegion(
{reducer: minMaxReducer, geometry:roi, bestEffort:true, scale: 30});
return img.set({
roi_min: minMax.get('NDVI_min'),
roi_max: minMax.get('NDVI_max')
});
});
// Filter out min and max region reductions that are null.
var filtered = l8ndvi
.filterMetadata('roi_min', 'not_equals', null)
.filterMetadata('roi_max', 'not_equals', null);
// Create VCI index.
var vci = filtered.map(function(img){
var id = img.id();
var max = img.getNumber('roi_max');
var Index = img.expression("(NDVI-min)/(max-min)", {
"NDVI": img.select('NDVI'),
"min" : img.getNumber('roi_min'),
"max" : img.getNumber('roi_max'),
})
.rename('VCI')
.copyProperties(img,['system:time_start','system:time_end']);
return Index;
});
print('After filtering null min and max:', vci);