Filter out cloudy scenes in Landsat/4/5

Hi, I’m trying out EO-learn modules and I’ve come across some problems filtering out cloudy scenes from the landsat 4/5 collection. What parameter should I pass instead of CLM, which is for Sentinel-2 :

filter_task = SimpleFilterTask((FeatureType.MASK, 'CLM '), MaxCCPredicate(maxcc=0.05))

Hi! Landsat 4-5 TM collections do not have a cloud mask band. However, the tiles do have cloud coverage information available, so you can filter scenes by using the maxCC parameter in your requests. If you want to do filtering in the script, there’s an evalscript for cloud masks available for Landsat 8 here, and it should be possible to modify it for Landsat 4-5 TM by changing the bands.

Really easy to modify and it works perfectly! Thank you so much, Monja!

I have modified the script to work for Landsat 4/5, but I can’t seem to extract the dataMask and the cloud_mask correctly using this code:

evalscript = """

function setup() {
    return {
        input: [{
            bands: ["B01", "B02", "B03", "B07", "dataMask"]
        }],
        output: [
            { id:"cloud_mask", bands:1},
            { id:"bands", bands:3,sampleType: 'FLOAT32'},
            { id:"mask", bands:1}
        ]
    }
}

function evaluatePixel(samples) {
  let val = [samples.B03, samples.B02, samples.B01, samples.dataMask];
  let cloud = create_mask(samples.B01,samples.B03, samples.B07);
  return {bands:val, cloud_mask:cloud, mask:samples.dataMask}
}

function create_mask(b1,b3,b7){
    var abs = Math.abs;
    var ceiling = Math.ceil;
    var cos = Math.cos;
    var exp = Math.exp;
    var floor = Math.floor;
    var log = Math.log;
    var sin = Math.sin;
    var sqrt = Math.sqrt;
    var truncate = Math.trunc;
    var round=Math.round;
    var band1 = round(b1*65535);
    var band3 = round(b3*65535);
    var band7 = round(b7*65535)
    let cloud_val;
    let output_first = 2.16246741593412 - 0.796409165054949*band3 + 0.971776520302587*sqrt(abs(0.028702220187686*band7*band1 + 0.971297779812314*sin(band1))) + 0.0235599298084993*floor(0.995223926146334*sqrt(abs(0.028702220187686*band7*band1 + 0.971297779812314*sin(band1))) + 0.00477607385366598*abs(0.028702220187686*band7*band1 + 0.971297779812314*sin(band1))) - 0.180030905136552*cos(band3) + 0.0046635498889134*abs(0.028702220187686*band7*band1 + 0.971297779812314*sin(band1));

    let output_second = band7;

    if (output_first< output_second)
    {
    return(cloud_val =  0);
    }
    else
    {
    return(cloud_val =  1);
    }
}



"""
input_task = SentinelHubEvalscriptTask(
    features=[(FeatureType.DATA, 'bands'),(FeatureType.MASK, 'cloud_mask'),(FeatureType.MASK, 'mask')],
    evalscript=evalscript,
    data_collection=DataCollection.LANDSAT45_L2,
    resolution=30,
    maxcc=0.4,
    time_difference=datetime.timedelta(hours=2),
    config=config
)
add_valmask = AddValidDataMaskTask(predicate=ValidDataPredicate(), valid_data_feature='VALID_DATA')

I only get this for example:

Hi @owl.egodinho,

It turns out there is a mistake in your evalscript, which is quite hard to find. In evaluatePixel function instead of a line:

return {bands:val, cloud_mask:cloud, mask:samples.dataMask}

you should write:

return {bands:val, cloud_mask:[cloud], mask:[samples.dataMask]}

Then masks will be returned correctly. Basically, every item in the return dictionary should be a list of values, even if you are requesting a single-channel feature.

Hi @maleksandrov! Thank you, that was indeed the problem! I made a few modifications, here is the full working evalscript for anyone interested in using it:

//VERSION=3

function evaluatePixel(sample) {
    return{
    truecolor: [sample.B03, sample.B02, sample.B01, sample.dataMask],
    mask: [sample.dataMask],
    cloud_mask: [create_mask(sample.B01,sample.B03, sample.B07)]
    
    }
    
}

function setup() {
  return {
    input: [{
      bands: [
        "B01",
        "B02",
        "B03",
        "B07",
        "dataMask"
      ]
    }],
    output: [{id:'truecolor',
      bands: 3,
      sampleType: 'FLOAT32'
    },
    {id:'mask',
      bands: 1,
      sampleType: 'INT8'
    },
    {id:'cloud_mask',
      bands: 1,
      sampleType: 'INT8'
    }]
  }
}

function create_mask(b1,b3,b7){
    var abs = Math.abs;
    var ceiling = Math.ceil;
    var cos = Math.cos;
    var exp = Math.exp;
    var floor = Math.floor;
    var log = Math.log;
    var sin = Math.sin;
    var sqrt = Math.sqrt;
    var truncate = Math.trunc;
    var round=Math.round;
    var band1 = round(b1*65535);
    var band3 = round(b3*65535);
    var band7 = round(b7*65535)
    let cloud_val;
    let output_first = 2.16246741593412 - 0.796409165054949*band3 + 0.971776520302587*sqrt(abs(0.028702220187686*band7*band1 + 0.971297779812314*sin(band1))) + 0.0235599298084993*floor(0.995223926146334*sqrt(abs(0.028702220187686*band7*band1 + 0.971297779812314*sin(band1))) + 0.00477607385366598*abs(0.028702220187686*band7*band1 + 0.971297779812314*sin(band1))) - 0.180030905136552*cos(band3) + 0.0046635498889134*abs(0.028702220187686*band7*band1 + 0.971297779812314*sin(band1));

    let output_second = band7;

    if (output_first< output_second)
    {
    return(cloud_val =  0);
    }
    else
    {
    return(cloud_val =  1);
    }
}
1 Like