Get rolling count of masked pixels in Google Earth Engine

countgoogle-earth-enginemaskingtime series

Using the Global Forecast System from NOAA, which provides relative humidity (RH) estimates several hours in to the future (up to 16 days to be exact). I create a binary of the RH, call it leaf wetness, where >=90% is 1, otherwise, 0.

I am trying to come up with a way to count which pixels are greater than or equal to 90% RH for different lengths of time. The count tracks continuous state of >=90%, so if a pixel drops below 90% (would be a 0 in a binary mask), the tracker changes back to 0 and starts at one next time >=90%. I made a small mock data set to kind of illustrate what I mean. The columns "1", "2", "3", are pixels in binary images of the image collection (and their matching pixels in the new images) and the images (layers) of the collection are for the hours of the forecast. The 1s & 0s between the hour and the "|||" is that pixel's value at that hour. The rolling counts are on the right side of the "|||". Those would each be represented in their own layers that keep counts based on continuous 1 in the binary collection. The starting collection has as many images as hours to be forecast and the value in that starting collection are binary. I want the new images, one for each hour as well, to be the counts of continuity.

Time  1 2 3 ||| 1 2 3 
00:00 1 0 1 ||| 1 0 1 = a new layer added to collection to represent the 1-hour counts  
01:00 1 1 1 ||| 2 1 2 = a new layer added to collection to represent the 2-hour counts
02:00 1 1 0 ||| 3 2 0 = a new layer added to collection to represent the 3-hour counts
03:00 1 1 1 ||| 4 3 1 = a new layer added to collection to represent the 4-hour counts
04:00 1 0 0 ||| 5 0 0 = a new layer added to collection to represent the 5-hour counts
05:00 1 1 1 ||| 6 1 1 = a new layer added to collection to represent the 6-hour counts

In the example data above, the "pixel" (column) labelled "1" is always one, so the count layers keep accumulating by one. But in the "pixel" 2, if I check at 3am, it has had 3 continuous hours of 1, however by 4am, due to the pixel being 0 at that time, the layer for counting continuous positive values (which represents wetness for my needs) would have a 0 and would start at 1 next time there is another 1 for that pixel in the binary, in this case, 5am.

I have a script that can give me a cumulative count for a specified length of time. I don't know how to make it return to 0 and restart when a 1 occurs in a future hour. I hope that can be the basis for what I need.

var cou_h = ['Henderson'];
var st_c = ['North Carolina'];
var cobo4 = ee.FeatureCollection("TIGER/2016/Counties").filter(ee.Filter.inList('NAME', cou_h)).geometry();
var st_nc = ee.FeatureCollection("TIGER/2018/States").filter(ee.Filter.inList('NAME', st_c));
var hend = cobo4.intersection(st_nc);

// If you want to specify a particular forecast from the series, 
// use 'forecast_hours' or 'forecast_time'. For example:
var colltrh = ee.ImageCollection("NOAA/GFS0P25")
                 .select(['temperature_2m_above_ground','relative_humidity_2m_above_ground'])       
                 .filter(ee.Filter.eq('creation_time',ee.Date(0).update(2022,6,1,0,0,0).millis()))
                 //.filter(ee.Filter.eq('forecast_hours', 1))
                 .map(function(im){return im.clip(hend)});

// RH threshold function
var pos_leaf_wetness = function(im){
  var plw = im.select('relative_humidity_2m_above_ground').gt(89).rename('leaf_wetness90');
  return im.addBands(plw);
}

// Add layers to collection
var colltrh = colltrh.map(pos_leaf_wetness);

// Just lookin'...
print(colltrh,'thr');
Map.addLayer(colltrh,null,'tr');


var hours_m = 120
var timeStep = 1

// A time series to keep rolling counts of the positive thresholded values?
var timeSeries = ee.ImageCollection(
  ee.List.sequence(0, hours_m, timeStep)
    .map(function (hourly) {
      var start = ee.Date(0).update(2022,6,1,0,0,0).advance(hourly, 'hours')
      var end = start.advance(timeStep, 'hours')
      var counting = colltrh.select('leaf_wetness90')
        .filterDate(start, end)
        .sum();
      return counting
        .set('system:time_start', start.millis())
        .set('empty', counting.bandNames().size().eq(0))
    })
  );

// Lookin around some more.
print(timeSeries,'timeseries');
Map.addLayer(timeSeries,null,'ts');

https://code.earthengine.google.com/badab7054765bb90ecda441762485bd9

Best Answer

You can iterate() your collection, inspect the last value to do the accumulation.

var date = '2022-06-01'
var humidityThreshold = 90 
var hours = 30

var collection = ee.ImageCollection("NOAA/GFS0P25")
  .select('relative_humidity_2m_above_ground')
  .filter(ee.Filter.gte('forecast_time', ee.Date(date).millis()))
  .filter(ee.Filter.lte('forecast_time', ee.Date(date).advance(hours, 'hours').millis()))
  .filter(ee.Filter.eq('forecast_hours', 1))
  .sort('forecast_time')
  .map(function (image) {
    return image
      .gte(humidityThreshold)
      .copyProperties(image, image.propertyNames())
  })
  
var counts = ee.ImageCollection(collection.iterate(
  function (image, acc) {
    image = ee.Image(image)
    acc = ee.ImageCollection(acc)
    var last = ee.Image(acc.get('last'))
    var count = last.add(1)
      .where(image.not(), ee.Image(0))
      .rename('count')
      .byte()
    return acc.merge(ee.ImageCollection([count]))
      .set('last', count)
  },
  ee.ImageCollection([])
    .set('last', ee.Image(0))
))

https://code.earthengine.google.com/77c0591d0f1762bad030877bb7a905e1

Related Question