Calculate proportion of study area covered by image on Google Earth Engine using a pixel count

I want to calculate the proportion of a study area that is covered by (non null) pixels within an image. I have seen examples that do similar counts (here, here, and here), and followed code from here, but it doesn't seem to give the correct answer.

// get aoi feature
var aoi_ext = ee.Geometry.Polygon(
[[-3.0,53.0],
[-2.5,53.0],
[-2.5,53.5],
[-3.0,53.5],
[-3.0,53.0]]
);

// Get image collection from Sentinel 2
var collection = ee.ImageCollection("COPERNICUS/S2_SR")
.filterBounds(aoi_ext) // just images intersecting with this
.filterDate('2017-05-01', '2018-06-01') // %Y-%m-%d
.select(['B2', 'B3', 'B4', 'B5', 'QA60'])
.map(function(image) { return image.clip(aoi_ext); }) // clip here to mask extent
;

print('no. images', collection.size());

// Look at first image
var img1 = ee.Image(collection.first());
print('image 1', img1);

// Display image
Map.addLayer(img1, {bands:['B2'], min: 0, max: 3000}, 'image 1');
Map.centerObject(aoi_ext);

// calculate number of all pixels in aoi

// get an image of aoi
var aoi_img = ee.Image(1).clip(aoi_ext);

// count pixels in aoi image
var totPixels = ee.Number(aoi_img.reduceRegion({
reducer: ee.Reducer.count(),
scale: 10,
maxPixels: 999999999,
}).values().get(0));

// Count the non zero/null pixels in the image within the aoi
var actPixels = ee.Number(img1.select('B2').reduceRegion({
reducer: ee.Reducer.count(),
scale: 10,
geometry: aoi_ext, // need it here
maxPixels: 999999999,
}).values().get(0));

// calculate the perc of cover
var pcPix = actPixels.divide(totPixels).multiply(100);

print('pixels in image:', actPixels, 'pixels in aoi', totPixels);

print('% cover', pcPix);

Looking at the display, I would estimate that about 15% was covered. With other images, it also clearly does not do the calculation I expect. I also tried masking the image with the result from a .gte() call, but it gives the same result.

You should restructure the code a bit. Unmask does (in your case) not unmask pixels outside the image extent. Thus, your variable totPixels is not right. Make an unbounded constant image, and apply reduceRegion with the crs and scale argument to calculate the number of total pixels with the same projection as your image:

// calculate number of all pixels in aoi

// count pixels in aoi image
var totPixels = ee.Number(ee.Image(1).reduceRegion({
reducer: ee.Reducer.count(),
geometry: aoi_ext,
scale: 10,
maxPixels: 999999999,
}).values().get(0));

// Count the non zero/null pixels in the image within the aoi
var actPixels = ee.Number(img1.select('B2').reduceRegion({
reducer: ee.Reducer.count(),
scale: 10,
geometry: aoi_ext,
maxPixels: 999999999,
}).values().get(0));

// calculate the perc of cover
var pcPix = actPixels.divide(totPixels).multiply(100);

Link example, with an addition to map it over the full collection. Two additional notes: clipping is generally not necesary and when working with Sentinel-2 imagery, always consider first making daily composites. Then, you'll have unbounded composite images, which will work with the previous example.

• Thanks. There's a typo in your code here (carried through from the complete example) in totPix, crs should read "crs: img1.select('B2').projection()" so that it works with above example. Also, just to clarify, it's the clip that isn't somehow? extending beyond the image in my example? Unmask refers to the previous example.
– CCID
Apr 5 '20 at 7:36