1

I am trying to classify a Sentinel 2 image composite with some added index bands using Random Forest supervised classification on a collection of sample points generated from a source Dynamic World V1 composite for the purpose of further accuracy check. However, when I try to refer to the classified image in my code in any way, I get an error message like this one:

Image (Error) Property 'B1' of feature '0' is missing.

or this one:

Classified Image: Layer error: Property 'B1' of feature '0' is missing.

My script: https://code.earthengine.google.com/e63926c051dc7ccaa7bcf336c7a09ef2

var dwCollection = ee.ImageCollection("GOOGLE/DYNAMICWORLD/V1"),
    s2ImageVisParam = {"opacity":1,"bands":["B4","B3","B2"],"min":154,"max":1302,"gamma":1},
    studyArea = 
    /* color: #d63000 */
    /* shown: false */
    /* displayProperties: [
      {
        "type": "rectangle"
      }
    ] */
    ee.Geometry.Polygon(
        [[[38.61152953602074, 54.5100725973329],
          [38.61152953602074, 54.41928157332435],
          [38.783190912973865, 54.41928157332435],
          [38.783190912973865, 54.5100725973329]]], null, false);

var start_date = "2023-09-01";
var end_date = "2023-10-01";

//Study area Dynamic World image
var dw_study_area = dwCollection.filterBounds(studyArea)
  .filterDate(start_date, end_date)
  .select('label')
  .median().clip(studyArea);
//The type of the 'label' band is Double, should be Int for sampling
var label_int = dw_study_area.select('label').toInt().rename('label_int');
dw_study_area = dw_study_area.addBands(label_int).select('label_int');

var dwVisParams = {
  min: 0,
  max: 8,
  palette: ['#419bdf', '#397d49', '#88b053', '#7a87c6', '#e49635', '#dfc35a', '#c4281b', '#a59b8f',
    '#b39fe1']
};
Map.centerObject(studyArea, 11);
Map.addLayer(dw_study_area, dwVisParams, "Dynamic World Image");

//Sampling of 100 random points per each DW landscape class
var sample_points = dw_study_area.stratifiedSample({
  numPoints: 100,
  classBand: 'label_int',
  scale: 10
});

//Sentinel 2 image cloud and cloud shadow masking
function maskCloudAndShadowsSR(image) {
  var cloudProb = image.select('MSK_CLDPRB');
  var snowProb = image.select('MSK_SNWPRB');
  var cloud = cloudProb.lt(10);
  var scl = image.select('SCL'); 
  var shadow = scl.eq(3); // 3 = cloud shadow
  var cirrus = scl.eq(10); // 10 = cirrus
  // Cloud probability less than 10% or cloud shadow classification
  var mask = cloud.and(cirrus.neq(1)).and(shadow.neq(1));
  return image.updateMask(mask);
}

//Getting a Sentinel 2 composite with additional indices
var s2_collection = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
  .filterBounds(studyArea)
  .filterDate(start_date, end_date)
  .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
  .map(maskCloudAndShadowsSR)
  .select('B.*');
var area_s2_composite = s2_collection.median().clip(studyArea);
var ndvi = area_s2_composite.normalizedDifference(['B8', 'B4']).rename(['NDVI']);
var ndbi = area_s2_composite.normalizedDifference(['B11', 'B8']).rename(['NDBI']);
var mndwi = area_s2_composite.normalizedDifference(['B3', 'B11']).rename(['MNDWI']); 
var bsi = area_s2_composite.expression(
    '(( X + Y ) - (A + B)) /(( X + Y ) + (A + B)) ', {
      'X': area_s2_composite.select('B11'), //swir1
      'Y': area_s2_composite.select('B4'),  //red
      'A': area_s2_composite.select('B8'), // nir
      'B': area_s2_composite.select('B2'), // blue
  }).rename('BSI');
area_s2_composite = area_s2_composite.addBands(ndvi).addBands(ndbi).addBands(mndwi).addBands(bsi);
print(area_s2_composite);
Map.addLayer(area_s2_composite, s2ImageVisParam, "Sentinel 2 Image");

//Splitting the sample points collection into a training and validation part
sample_points = sample_points.randomColumn();
var split = 0.7;  //70% for training, 30% for validation
var training_points = sample_points.filter(ee.Filter.lt('random', split));
var validation_points = sample_points.filter(ee.Filter.gte('random', split));

//Filtering possible training points with Null property values
print(training_points);
var training_points_no_nulls = training_points.filter(
  ee.Filter.notNull(training_points.first().propertyNames()));
print(training_points_no_nulls);  //NO NULL PROPERTIES

//Random Forest classifier creation and training
var trained = ee.Classifier.smileRandomForest(50)
  .train({
    features: training_points_no_nulls,
    classProperty: 'label_int',
    inputProperties: area_s2_composite.bandNames()
  });

//Image classification
var classified = area_s2_composite.classify(trained).select('classification');
print(classified);

Map.addLayer(classified, dwVisParams, "Classified Image");

If I remove the B1 band from the Sentinel 2 image, the interpreter says that the B2 property is missing, after removing the B2 (and the indices calculation part), the same repeats for the B3, etc., so it seems that all of them are "missing" in some way.

Prior to asking here for help, I have found here a number of posts on similar issues and performed some checks, but, according to the print() listings, both my sample collection and S2 composite contain complete and valid data.

Above all, I am totally missing the point of identifying the lack of source image bands in the classified image: there just shouldn't be any of them when I call the .select() method, leaving only the 'classification' band!

My sample points contain the 'label_int' property used for classification. Filtering possible training points with Null property values shows that all points are valid and there is nothing to filter. The use of the validation points collection instead of the training points doesn't affect the error. Removing the cloud / cloud shadow mask doesn't affect the error.

Clicking on the image in the Inspector mode shows all bands in the source S2 composite having their values for different points.

1 Answer 1

0

All bands in the image you want to classify must have corresponding properties in your training data collection. In this case, you can simply combine the Dynamic World label band with your composite when sampling:

var sample_points = dw_study_area.select('label_int')
  .addBands(area_s2_composite)
  .stratifiedSample({
    numPoints: 100,
    classBand: 'label_int',
    scale: 10,
    tileScale: 16
  })

https://code.earthengine.google.com/110f58bff163f88a586a7566e44d7c16

1
  • It seems that I was too focused on proportional representation of the Dynamic World land cover classes and completely ignored the need to have the source image bands for training. I added them, and now it works properly. Thank you very much! Commented Jan 9 at 12:04

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.