Sentinel-2 images distortions, artifacts, clouds - How to remove?

Hi guys,

I use sentinel hub request builder (Requests Builder (sentinel-hub.com)) batch API to get satellite imagery for the whole of Germany for 2017 and 2020. I create a cloud-free mosaic using the script below between the dates 15. May and 15. September of each year. Otherwise, my settings are

  • tiling grid: 20km
  • resolution: 10
  • output image format: TIFF
  • COG output = true

The image for 2020 (on the right) looks fine, the one for 2017 looks severely distorted (see an exemplary part of one satellite image below). I now change the timeframe to 01.03. - 01.11. for 2017, but the clouds remain and many distortions (e.g., the black spots) exist. Does anyone know what is going on here and how I can fix it?

//VERSION=3

function setup() {
  return {
    input: [{
      bands: ["B04", "B03", "B02", "CLM", "SCL"],  //Requests required bands, s2cloudless mask and scene classification layer
      units: "DN"
    }],
    output: {
      bands: 3,
      sampleType: SampleType.UINT8
    },
    mosaicking: "ORBIT"
  };
}

function filterScenes (scenes, inputMetadata) {
  return scenes.filter(function (scene) {
    return scene.date.getTime()>=(inputMetadata.to.getTime()-8*30*24*3600*1000); //Defines the time range, e.g. from 1st June until 31st October counts 5 months with 30 days, 24 hours...
  });
}

function getValue(values) {
  values.sort( function(a,b) {return a - b;} );
  return getFirstQuartile(values);
}

function getFirstQuartile(sortedValues) {
  var index = Math.floor(sortedValues.length / 4);
  return sortedValues[index];
}
function getDarkestPixel(sortedValues) {
  return sortedValues[0]; // darkest pixel
}

function validate (samples) {
  var scl = samples.SCL;
  var clm = samples.CLM;

  if (clm === 1 || clm === 255) {
        return false;
  } else if (scl === 1) { // SC_SATURATED_DEFECTIVE
        return false;
  } else if (scl === 3) { // SC_CLOUD_SHADOW
        return false;
  } else if (scl === 8) { // SC_CLOUD_MEDIUM_PROBA
        return false;
  } else if (scl === 9) { // SC_CLOUD_HIGH_PROBA
        return false;
  } else if (scl === 10) { // SC_THIN_CIRRUS
        return false;
  } else if (scl === 11) { // SC_SNOW_ICE
    return false;
  }  else {
  return true;
  }
}

const visualizer = new HighlightCompressVisualizer(0.0, 0.2)

function evaluatePixel(samples, scenes) {
  var clo_b02 = []; var clo_b03 = []; var clo_b04 = [];
  var clo_b02_invalid = []; var clo_b03_invalid = []; var clo_b04_invalid = [];
  var a = 0; var a_invalid = 0;

  for (var i = 0; i < samples.length; i++) {
    var sample = samples[i];
    if (sample.B02 > 0 && sample.B03 > 0 && sample.B04 > 0 ) {
      var isValid = validate(sample);

      if (isValid) {
        clo_b02[a] = sample.B02;
        clo_b03[a] = sample.B03;
        clo_b04[a] = sample.B04;
        a = a + 1;
      } else {
        clo_b02_invalid[a_invalid] = sample.B02;
        clo_b03_invalid[a_invalid] = sample.B03;
        clo_b04_invalid[a_invalid] = sample.B04;
        a_invalid = a_invalid + 1;
      }
    }
  }

  var rValue;
  var gValue;
  var bValue;

  if (a > 0) {
    rValue = getValue(clo_b04);
    gValue = getValue(clo_b03);
    bValue = getValue(clo_b02);
  } else if (a_invalid > 0) {
    rValue = getValue(clo_b04_invalid);
    gValue = getValue(clo_b03_invalid);
    bValue = getValue(clo_b02_invalid);
  } else {
    rValue = 0;
    gValue = 0;
    bValue = 0;
  }
  return [visualizer.process(rValue / 10000) * 255, visualizer.process(gValue / 10000) * 255, visualizer.process(bValue / 10000) * 255]
}

Hi Felix,

Thanks for the message, it looks like you may have just found a particularly cloudy part of Germany. Do you have an AOI that you can share with me? Then, I can do some investigations on our side.

As I’m sure you know, the cloud mask used in the evalscript is based on the Scene classification data, generated by the Sen2Cor processor, and sometimes it can be unreliable.

EDIT: sorry, I have just noticed you are also using the S2 cloudless mask in your evalscript too. Have you tested your evalscript with just S2 Cloudless, and just the Scene Classification Layer? You can try this using just Process API on this location so you don’t need to run Batch API over your whole area again.