Error in random forest classification for the GEDI datasets in GEE

classificationgoogle-earth-enginegoogle-earth-engine-javascript-apirandom forest

I am a beginner in GEE. I am trying to do the random forest classification for the GEDI datasets (rh13 and rh99) in Google Earth Engine. I want to do the classification of each point of the GEDI datasets and then interpolate for all the images (because the GEDI datasets are data points).

I'm getting this error and do not how to fix it:

Classified: Layer error: Property 'Relative Height' of feature '0_0'
is missing

var Paracou = 
/* color: #d63000 */
/* displayProperties: [
  {
    "type": "rectangle"
  }
] */
ee.Geometry.Polygon(
    [[[-52.992228448173776, 5.3639624330339855],
      [-52.992228448173776, 5.216962579558439],
      [-52.898158013603464, 5.216962579558439],
      [-52.898158013603464, 5.3639624330339855]]], null, false);
var qualityMask = function(im) {
  return im.updateMask(im.select('quality_flag').eq(1))
      .updateMask(im.select('degrade_flag').eq(0));
};
var img = ee.ImageCollection('LARSE/GEDI/GEDI02_A_002_MONTHLY')
                  .filterBounds(Paracou)
                  .map(qualityMask)
                  .select('rh13','rh99')
                  .median();
var gediVis = {
  min: 1,
  max: 60,
};

var img = img.clip(Paracou);
Map.centerObject(Paracou,12);
Map.addLayer(img, gediVis, 'rh13-99');
print(img.getInfo());

var points = img.sample({
  'region': Paracou,
  'scale' : 30,
  'numPixels' : 200000,
  'seed' : 0,
  'geometries': true
});
Map.addLayer(points,{},'training',true);
print(points.size().getInfo());
print(points.first().getInfo());
//SPLIT AND TRAINING
// Use these bands for prediction
var bands = ['rh13','rh99'];

var label = 'Relative Height';

var sample = img.select(bands).sampleRegions({
    collection: points,
    properties : [label],
    scale : 30
  });
var sample = sample.randomColumn();
var split = 0.7;
var training = sample.filter(ee.Filter.lt('random',split));
var validation = sample.filter(ee.Filter.gte('random',split));
print(training.first().getInfo());
print(validation.first().getInfo());
var classifier = ee.Classifier.smileRandomForest(10).train(training,label,bands);
// Classification
var result = img.select(bands).classify(classifier);
Map.addLayer(result.randomVisualizer(),{},'classified');

https://code.earthengine.google.com/37d6104d659f4dc63f36baccde372a16

Best Answer

I don't follow how you want this to work. What are the classes you want in your classification? Nevertheless, the reason for your error is this:

var label = 'Relative Height'
var sample = img.select(bands).sampleRegions({
  collection: points,
  properties: [label],
  scale: 30
})

The points collection doesn't contain any Relative Height property. Even if it did, I don't think this quite makes sense.

You could maybe try something like the below. Note that I've never used the GEDI data, and have very limited knowledge of it. So take this approach with a grain of salt. I'd very much appreciate someone with more knowledge on this to jump in and comment.

I'm extracting reference data from the GEDI collection, create Landsat 8/Sentinel 1 composites for the corresponding month, and sample it for training data. A classifier is trained, then finally applied to a composite for a date range of your choosing.

var aoi = ee.Geometry.Polygon([[
  [-52.992228448173776, 5.3639624330339855],
  [-52.992228448173776, 5.216962579558439],
  [-52.898158013603464, 5.216962579558439],
  [-52.898158013603464, 5.3639624330339855]
]]).buffer(1e4)

var heightBand = 'rh95'

var gedi = ee.ImageCollection('LARSE/GEDI/GEDI02_A_002_MONTHLY')
  .filterBounds(aoi)
  .map(function (image) {
    return image
      .updateMask(image.select('quality_flag').eq(1))
      .updateMask(image.select('degrade_flag').eq(0))
      .select(heightBand)
  })
var referenceData = gedi
  .map(toReferenceData)  
  .filterMetadata('count', 'not_equals', 0)
var trainingData = referenceData
  .map(toTrainingData)
  .flatten()

var composite = toComposite('2021-01-01', '2022-01-01')
var classifier = ee.Classifier.smileRandomForest(25)
  .train(trainingData, heightBand, composite.bandNames())
  .setOutputMode('REGRESSION')
var classification = composite.classify(classifier)


print('trainingData', trainingData)
print('classifier', classifier.explain())
Map.addLayer(referenceData.flatten(), null, 'reference data', false)
Map.addLayer(gedi.mosaic().clip(aoi), {bands: heightBand, min: 0, max: 40, palette: '#fffdcd, #e1cd73, #aaac20, #5f920c, #187328, #144b2a, #172313'}, heightBand)
Map.addLayer(composite, {bands: 'red,green,blue', min: 0, max: 2000, gamma: 1.6}, 'optical')
Map.addLayer(composite, {bands: 'VV,VH,ratio_VV_VH', min: [-20, -25, 3], max: [0, -5, 14]}, 'sar')
Map.addLayer(classification, {min: 0, max: 40, palette: '#fffdcd, #e1cd73, #aaac20, #5f920c, #187328, #144b2a, #172313'}, 'height')
Map.centerObject(aoi, 12)


function toReferenceData(gedi) {
  var referenceData = gedi
    .sample({
      region: aoi,
      scale : 25,
      numPixels : 200000,
      geometries: true
    })
    .map(function (feature) {
      // Can only use ints as classes
      return feature.set('rh99', feature.getNumber('rh99').round())
    })
  return referenceData
    .set({
      year: gedi.getNumber('year'),
      month: gedi.getNumber('month'),
      count: referenceData.size()
    })
}


function toTrainingData(referenceData) {
  var year = referenceData.getNumber('year')
  var month = referenceData.getNumber('month')
  var fromDate = ee.Date.fromYMD(year, month, 1)
  var toDate = fromDate.advance(1, 'month')
  var composite = toComposite(fromDate, toDate)
  return composite.sampleRegions({
    collection: referenceData,
    properties : [heightBand],
    scale : 30
  })  
}


function toComposite(fromDate, toDate) {
  return toOpticalComposite(fromDate, toDate)
    .addBands(toSarComposite(fromDate, toDate))
}

function toSarComposite(fromDate, toDate) {  
  var composite = ee.ImageCollection('COPERNICUS/S1_GRD_FLOAT')
    .filter(ee.Filter.and(
      ee.Filter.bounds(aoi),
      ee.Filter.date(fromDate, toDate),
      ee.Filter.eq('instrumentMode', 'IW'),
      ee.Filter.listContains('transmitterReceiverPolarisation', 'VV'),
      ee.Filter.listContains('transmitterReceiverPolarisation', 'VH')
    ))
    .map(function (image) {
      return maskBorder(
        toDb(
          toGamma0(image)
        )
      ).select(['VV', 'VH'])
    })
    .median()
  return composite
    .addBands(
      composite.select('VV').divide(composite.select('VH')).rename('ratio_VV_VH')
    )
    

  function toGamma0(image) {
    var gamma0 = image.expression('i/(cos(angle * pi / 180))', {
      'i': image.select(['VV', 'VH']),
      'angle': image.select('angle'),
      'pi': Math.PI
    })
    return image.addBands(gamma0, null, true)    
  }
  
  function toDb(image) {
    return image.addBands(
      image.select(['VV', 'VH']).log10().multiply(10), null, true
    )    
  }
    
  function maskBorder(image) {
    var angle = image.select('angle')
    return image
      .updateMask(
        angle.gt(31).and(angle.lt(45))
      )    
  }    
}


function toOpticalComposite(fromDate, toDate) {
  return ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
    .filterDate(fromDate, toDate)
    .filterBounds(aoi)
    .map(function (image) {
      var qa = image.select('QA_PIXEL')
      var cloudShadow = bitwiseExtract(qa, 4)
      var snow = bitwiseExtract(qa, 5).rename('snow')
      var cloud = bitwiseExtract(qa, 6).not().rename('cloud')
      return image
        .updateMask(
          cloudShadow.not()
            .and(snow.not())
            .and(cloud.not())
        )
        .select(
          ['SR_B2', 'SR_B3', 'SR_B4', 'SR_B5', 'SR_B6', 'SR_B7'],
          ['blue', 'green', 'red', 'nir', 'swir1', 'swir2']
        )
        .multiply(2.75e-05)
        .add(-0.2)
        .multiply(10000)
        .int16()
    })
    .median()
}

function bitwiseExtract(value, fromBit, toBit) {
  if (toBit === undefined)
    toBit = fromBit
  var maskSize = ee.Number(1).add(toBit).subtract(fromBit)
  var mask = ee.Number(1).leftShift(maskSize).subtract(1)
  return value.rightShift(fromBit).bitwiseAnd(mask)
}

https://code.earthengine.google.com/52f8f4ee6d2ab759e4f0fd1acb1ea2a9

Related Question