1

I am currently trying to adapt the scan line correction (SLC) for Landsat 7 on my timeseries. My code is based on the stack exchange question how to do the SLC, but I am not managing to apply it as a loop.

For the SLC-function, there are always two images of the ImageCollection needed, one as a source and the other one to fill the gaps induced by the SL failure. I know how to map over an ImageCollection, but I do not understand, how to always also cast the next image in the collection.

So if my Collection has 6 images, I want to apply this sequence to my loop, and always get an image as a source and the next image in line as the filling.

var list = ee.List.sequence(0,5);
var corrected = ee.ImageCollection.fromImages(
  list.map(function (i) {
  var source  = ee.Image(listOfImages.get(i))
  var fill    = ee.Image(listOfImages.get(i.add(1)))
  var result  = GapFill(source, fill, 10, true);
 return ee.Image(result);
}));

How can get the next image in a loop? Obviously .add or a + is not working.

Here is the full code

var area =     ee.Geometry.Polygon(
        [[[35.715690058580556, -6.013865604001415],
          [35.715690058580556, -6.190699019077679],
          [35.971122187486806, -6.190699019077679],
          [35.971122187486806, -6.013865604001415]]], null, false);
// Set study area as map center.
Map.centerObject(area,11);

// Create an empty image into which to paint the features, cast to byte. 
var empty = ee.Image().byte();
// Paint all the polygon edges with the same number and width, display.
var outline = empty.paint({
  featureCollection: area,
  color: 1,
  width: 3
});
Map.addLayer(outline, {palette: '000000'}, 'Kiteto & Kongwa');


///////////////////////////////////////////////////////////////////////////////////7
/* Apply the USGS L7 Phase-2 Gap filling protocol, using a single kernel size. */
///////////////////////////////////////////////////////////////////////////////////7
var MIN_SCALE = 1/3;
var MAX_SCALE = 3;
var MIN_NEIGHBORS = 144;
var GapFill = function(src, fill, kernelSize, upscale) {
  var kernel = ee.Kernel.square(kernelSize * 30, "meters", false)

  // Find the pixels common to both scenes.
  var common = src.mask().and(fill.mask())
  var fc = fill.updateMask(common)
  var sc = src.updateMask(common)
  //Map.addLayer(common.select(0).mask(common.select(0)), {palette:['000000']}, 'common mask (both exist)', false)

  // Find the primary scaling factors with a regression.
  // Interleave the bands for the regression.  This assumes the bands have the same names.
  var regress = fc.addBands(sc)

  regress = regress.select(regress.bandNames().sort())
  print(regress,'regress')
  var ratio = 5

  if(upscale) {
    var fit = regress
      .reduceResolution(ee.Reducer.median(), false, 500)
      .reproject(regress.select(0).projection().scale(ratio, ratio))
      .reduceNeighborhood(ee.Reducer.linearFit().forEach(src.bandNames()), kernel, null, false)
      .unmask()
      .reproject(regress.select(0).projection().scale(ratio, ratio))
  } else {

    var fit = regress
      .reduceNeighborhood(ee.Reducer.linearFit().forEach(src.bandNames()), kernel, null, false)
  }

  var offset = fit.select(".*_offset")
  var scale = fit.select(".*_scale")

  //Map.addLayer(scale.select('Blue_scale'), {min:-2, max:2}, 'scale B1', false)

  // Find the secondary scaling factors using just means and stddev
  var Reducer = ee.Reducer.mean().combine(ee.Reducer.stdDev(), null, true)

  if(upscale) {
    var src_stats = src
      .reduceResolution(ee.Reducer.median(), false, 500)
      .reproject(regress.select(0).projection().scale(ratio, ratio))
      .reduceNeighborhood(Reducer, kernel, null, false)
      .reproject(regress.select(0).projection().scale(ratio, ratio))

    var fill_stats = fill
      .reduceResolution(ee.Reducer.median(), false, 500)
      .reproject(regress.select(0).projection().scale(ratio, ratio))
      .reduceNeighborhood(Reducer, kernel, null, false)
      .reproject(regress.select(0).projection().scale(ratio, ratio))
  } else {
    var src_stats = src
      .reduceNeighborhood(Reducer, kernel, null, false)

    var fill_stats = fill
      .reduceNeighborhood(Reducer, kernel, null, false)
  }

  var scale2 = src_stats.select(".*stdDev").divide(fill_stats.select(".*stdDev"))
  var offset2 = src_stats.select(".*mean").subtract(fill_stats.select(".*mean").multiply(scale2))

  var invalid = scale.lt(MIN_SCALE).or(scale.gt(MAX_SCALE))
 // Map.addLayer(invalid.select(0).mask(invalid.select(0)), {palette:['550000']}, 'invalid1', false)
  scale = scale.where(invalid, scale2)
  offset = offset.where(invalid, offset2)

  // When all else fails, just use the difference of means as an offset.  
  var invalid2 = scale.lt(MIN_SCALE).or(scale.gt(MAX_SCALE))
 // Map.addLayer(invalid2.select(0).mask(invalid2.select(0)), {palette:['552020']}, 'invalid2', false)
  scale = scale.where(invalid2, 1)
  offset = offset.where(invalid2, src_stats.select(".*mean").subtract(fill_stats.select(".*mean")))

  // Apply the scaling and mask off pixels that didn't have enough neighbors.
  var count = common.reduceNeighborhood(ee.Reducer.count(), kernel, null, true, "boxcar")
  var scaled = fill.multiply(scale).add(offset)
      .updateMask(count.gte(MIN_NEIGHBORS))

  return src.unmask(scaled, true)
}

//////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// Define coefficients supplied by Roy et al. (2016) for translating ETM+
// surface reflectance to OLI surface reflectance.

// Define function to get and rename bands of interest from ETM+.
function renameETM(img) {
  return img.select(
        ['B1', 'B2', 'B3', 'B4', 'B5', 'B7', 'pixel_qa'],
        ['Blue', 'Green', 'Red', 'NIR', 'SWIR1', 'SWIR2', 'pixel_qa']
  );
}
// Define function to mask out clouds and cloud shadows.
function fmask(img) {
  var cloudShadowBitMask = 1 << 3;
  var cloudsBitMask = 1 << 5;
  var qa = img.select('pixel_qa');
  var mask = qa.bitwiseAnd(cloudShadowBitMask).eq(0)
    .and(qa.bitwiseAnd(cloudsBitMask).eq(0));
  return img.updateMask(mask);
}
// Define function to prepare ETM+ images.
function prepETM(img) {
  var orig = img;
  img = renameETM(img);
  img = fmask(img);
  return ee.Image(img.copyProperties(orig, orig.propertyNames()));
}
// ################################################################
// ### APPLICATION ###
// ################################################################
var etmCol = ee.ImageCollection('LANDSAT/LE07/C01/T1_SR')
                    .filterDate('2003-05-30', '2005-05-30')
// Define a collection filter.
var colFilter = ee.Filter.and(
  ee.Filter.bounds(area),
ee.Filter.lt('CLOUD_COVER', 05),        
  ee.Filter.lt('GEOMETRIC_RMSE_MODEL', 10),
ee.Filter.or(
    ee.Filter.eq('IMAGE_QUALITY', 9),
    ee.Filter.eq('IMAGE_QUALITY_OLI', 9)
  ));

// Filter collections and prepare them for merging
var l7images = etmCol.filter(colFilter).map(prepETM);
print(l7images,'Landsat 7');
var listOfImages = l7images.toList(l7images.size());
//Example of how the SLC works
var source    = ee.Image(listOfImages.get(0))
var fill      = ee.Image(listOfImages.get(1))
Map.addLayer(fill,    {min:400, max:2500, gamma: 2.222 , bands:["Red", "Green", "Blue"]}, "1", true)
Map.addLayer(source,  {min:400, max:2500, gamma: 2.222 , bands:["Red", "Green", "Blue"]}, "2", true)
var result = GapFill(source, fill, 10, false);
Map.addLayer(result.clip(area),  {min:400, max:2500, gamma: 2.222, bands:["Red", "Green", "Blue"]}, "filled", true)


var list = ee.List.sequence(0,5);
var corrected = ee.ImageCollection.fromImages(
  list.map(function (i) {
  var source  = ee.Image(listOfImages.get(i))
  var fill    = ee.Image(listOfImages.get(i.add(1)))
  var result  = GapFill(source, fill, 10, true);
 return ee.Image(result);
}));

1 Answer 1

1

Without spending time getting the whole script working, I can answer the specific question about getting the next i. When you map over an ee.List, you have to explicitly cast the value. In your case, add i = ee.Number(i) to your mapped function.

var corrected = ee.ImageCollection.fromImages(
  list.map(function (i) {
    i = ee.Number(i)
    var source  = ee.Image(listOfImages.get(i))
    var fill    = ee.Image(listOfImages.get(i.add(1)))
    var result  = GapFill(source, fill, 10, true)
 return ee.Image(result)

}));

1
  • The problem, however, is that you are creating a loop inside a map function. This generates the error: "arguments cannot be used in client-side operations". The result should have a way of mapping over a collection in the server side.
    – salixtreks
    Commented Feb 4, 2022 at 22:20

Your Answer

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

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