1

I want to make supervised classification of Sentinel-2 data-set. The algorithm is:

// Define periods of analysis
var startdate1 = '2017-10-01';
var enddate1 = '2017-11-30';

var startdate2 = '2018-04-01';
var enddate2 = '2018-05-31';

var startdate3 = '2018-06-01';
var enddate3 = '2018-07-31';


// Define geograpic domain
var geometry = Knezha.geometry();


///////////////////////////////////////////////////////////////////////////
// Preprocess Sentinel-2 data
///////////////////////////////////////////////////////////////////////////

// cloud function to remove clouds
 function maskS2clouds(image){
  var qa = image.select('QA60');

  // Bits 10 and 11 are clouds and cirrus, respectively.
  var cloudBitMask = 1 << 10;
  var cirrusBitMask = 1 << 11;

  // Both flags should be set to zero, indicating clear conditions.
  var mask = qa.bitwiseAnd(cloudBitMask).eq(0)
      .and(qa.bitwiseAnd(cirrusBitMask).eq(0));

  return image.updateMask(mask).divide(10000);
}

//////////////////PERIOD1-OCT-NOV 2017////////////////////////////////////

// filter Sentinel-2 data
var Sentinel2_period1 = S2.filterBounds(geometry).filterDate(startdate1, enddate1)
                  // Pre-filter to get less cloudy granules.
                  .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 10));
// remove the clouds
var S2_nocloud_period1 = Sentinel2_period1.map(maskS2clouds);
// take the median
var st2median_period1 = S2_nocloud_period1.median();
// select only bands B2-B8, B11 and B12
var S2_period1 = st2median_period1.select('B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B11', 'B12');


//////////////////PERIOD2-APR-MAY 2018////////////////////////////////////

// filter Sentinel-2 data
var Sentinel2_period2 = S2.filterBounds(geometry).filterDate(startdate2, enddate2)
                  // Pre-filter to get less cloudy granules.
                  .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 10));
// remove the clouds
var S2_nocloud_period2 = Sentinel2_period2.map(maskS2clouds);
// take the median
var st2median_period2 = S2_nocloud_period2.median();
// select only bands B2-B8, B11 and B12
var S2_period2 = st2median_period2.select('B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B11', 'B12')

//////////////////PERIOD3-JUN-JUL 2018////////////////////////////////////

// filter Sentinel-2 data
var Sentinel2_period3 = S2.filterBounds(geometry).filterDate(startdate3, enddate3)
                  // Pre-filter to get less cloudy granules.
                  .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 10));
// remove the clouds
var S2_nocloud_period3 = Sentinel2_period3.map(maskS2clouds);
// take the median
var st2median_period3 = S2_nocloud_period3.median();
// select only bands B2-B8, B11 and B12
var S2_period3 = st2median_period3.select('B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B11', 'B12');


// Combine the bands from the three periods
// into one image and clip over geometry
var input_img_S2 = S2_period1.addBands([S2_period2, S2_period3])
                .rename(['P1_B2', 'P1_B3', 'P1_B4', 'P1_B5', 'P1_B6', 'P1_B7', 'P1_B8', 'P1_B11',
                'P1_B12', 'P2_B2', 'P2_B3', 'P2_B4', 'P2_B5', 'P2_B6', 'P2_B7', 'P2_B8', 'P2_B11',
                'P2_B12', 'P3_B2', 'P3_B3', 'P3_B4', 'P3_B5', 'P3_B6', 'P3_B7', 'P3_B8', 'P3_B11',
                'P3_B12'])
                .clip(geometry);


// Add to the map
var visParams = {bands: ['P1_B8', 'P2_B8', 'P3_B8'], max: 0.5}
Map.addLayer(input_img_S2, visParams, 'Sentinel-2 data', false);
Map.addLayer(S2_period1, {bands: ['B4', 'B3', 'B2'], max: 0.3}, 'median RBG period 1', false);
Map.addLayer(S2_period2, {bands: ['B4', 'B3', 'B2'], max: 0.3}, 'median RBG period 2', false);
Map.addLayer(S2_period3, {bands: ['B4', 'B3', 'B2'], max: 0.3}, 'median RBG period 3', false);

//////////////////////////////////////////////////////////////////////////
// Now the real classification
//////////////////////////////////////////////////////////////////////////

// Extract spectral data from the input image for training points
var training = input_img_S2.sampleRegions({
  // Get the sample from the point FeatureCollection.
  collection: training_points,
  // We'll classify on 'Level_2"
  properties: ['Level_2'],
  // Set the scale to get Sentinel-2 pixels in the polygons.
  scale: 20,
  tileScale: 4
});

// Make a Random Forest classifier (10 trees) and train it.
var classifier = ee.Classifier.randomForest(10)
    .train(training, 'Level_2');

// Classify the image with the trained classifier
var classified = input_img_S2.classify(classifier);

// Create a palette to display the classes.
var palette =['#deebab', '#aeee98', '#8cb3d1', '#e3df51',
              '#cb50c7', '#8b8c53', '#8cce21', '#1f8a1d',
              '#5c305e', '#074ec9'];

// Add classification to the map
Map.addLayer(classified, {min: 0, max: 9, palette: palette}, 'Classification', false);


//////////////////////////////////////////////////////////////////////////
// Accuracy assessment
//////////////////////////////////////////////////////////////////////////

// Create 1000 random points in the val polygons.
var randomPoints = ee.FeatureCollection.randomPoints(val_poly, 1000, 0, 1)

// Property Level_2 not attached to points, so we need
// Spatial join
var randomPoints_new = val_poly.map(function(feat){
  feat = ee.Feature(feat);
  var covertype = feat.get('Level_2');
  var randomPoints_filtered = randomPoints.filterBounds(feat.geometry()).map(function(point){
    return ee.Feature(point).set('Level_2', covertype);
  });
  return randomPoints_filtered;
}).flatten();

// Extract spectral data from the input image for validation points
var validation = input_img_S2.sampleRegions({
  // Get the sample from the point FeatureCollection.
  collection: randomPoints_new,
  // We'll classify on 'Level_2"
  properties: ['Level_2'],
  // Set the scale to get Sentinel-2 pixels at the points.
  scale: 20,
  tileScale: 4
})
.filter(ee.Filter.neq('P1_B2', null));

// Classify the validation data.
var validated = validation.classify(classifier);

// Get a confusion matrix representing expected accuracy.
var testAccuracy = validated.errorMatrix('Level_2', 'classification');

// Еxport error matrix
var exportAccuracy = ee.Feature(null, {matrix: testAccuracy.array()})
Export.table.toDrive({
  collection: ee.FeatureCollection(exportAccuracy),
  description: 'Error_matrix',
  fileFormat: 'CSV'
});

Everything seems to be successful until I came to the accuracy assessment. When I run the entire script, including the accuracy assessment part, I get this error:

Error: User memory limit exceeded.

I reduced the number of validation points to only 1000, just for initial experiment. I actually would prefer something like 100 000 points for the assessment. But I get memory limit exceeded even with 1000 points.

Maybe the problem is not in the number of validation points at all. But it is definitely in the accuracy assessment because I was able to export the classification raster to Google Drive and the problems start when I continue from there onwards.

What do I need to change?

Link to the code: https://code.earthengine.google.com/6a0b69b4a0442253639a667ff856fb17

Links to the vector assets: https://code.earthengine.google.com/?asset=users/petarkirilov/Zlatia_test_site_34

https://code.earthengine.google.com/?asset=users/petarkirilov/training_points

https://code.earthengine.google.com/?asset=users/petarkirilov/validation_polygons

2 Answers 2

1

Just for the sake of completeness, I share my own experience here:

Check that all your features share the same geometry type.

I faced the same memory issue during the evaluation of classification over a small FeatureCollection of size 2000 polygons. The problem was solved after I realized that a number of features in my FeatureCollection are not as expected of type Polygon but of type GeometryCollection.

My guess is that during the operations of filtering/clipping of FeatureCollection, some changes may occur to the geometry of features. This was the root cause of my memory issue in classification.

0

As described here, wherever you have tileScale: 4, try increasing it to tileScale: 8 and if that doesn't work, try tileScale: 16. You may also want to try exporting some of those intermediate tables toAsset(), then picking up the exported asset and continuing the computation.

1
  • 1
    Increasing 'tileScale' to 8 or 16 did not solve the problem. However, exporting one of the intermediate tables 'toAsset()' worked. I exported the 'randomPoints_new' table which was produced after the spatial join operations. Then, the new table was imported and used in the extracting of spectral data from the input image i.e. constructing the final validation sample. The error matrix was exported successfully to Drive. All this was possible even with 'tileScale' set to 4. Thanks a lot. Updated code: code.earthengine.google.com/615c826cb82ff11075d24079faaf87a1
    – ABC
    Commented Jun 17, 2019 at 7:53

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.