3

I have been working with the tutorials in GEE on supervised and unsupervised classification, but I haven't come across any examples on the use of separability measures such as Jeffreys-Matusita, transformed divergence or B-distance for instance.

Here's my code to run a classification using CART()

// Add a clipping function to clip the image collection

var clipper = function(image){
  return image.clip(WG);
};

// Import raw Landsat 5 scenes and clip them over your study area
var l5filtered = ee.ImageCollection('LANDSAT/LT05/C01/T1').filterDate('2008-01-01','2012-12-31').map(clipper);

// Obtain a cloud-free composite of the scenes filtered
var cloudfree = ee.Algorithms.Landsat.simpleComposite({
  collection: l5filtered,
  asFloat: true
  });
Map.addLayer(cloudfree, {bands: ['B4','B3','B2'], max: 0.3}, 'composite');

// Specify the bands of the Landsat composite to be used as predictors (p)
var predictionBands = ['B1','B2','B3','B4','B5','B6','B7'];

// Step 2: Creating training data manually
// Draw a polygon around a bare ground area and import it as a featureCollection
// Click New property and import the new 'class' and give it a value of 0
// Similarly vegetation and water features were created

// Merging the three featureCollections to obtain a single featureCollection
var trainingFeatures = bare.merge(vegetation).merge(water);

// Note - we use .sampleRegions, if we have a featureCollection and we want to sample an image
// given a featureCollection

var classifierTraining = cloudfree.select(predictionBands)
                                  .sampleRegions({
                                    collection: trainingFeatures,
                                    properties : ['class'],
                                    scale: 30
                                  });

// Let's instantiate a CART and train it
var classifier = ee.Classifier.cart().train({
                   features: classifierTraining,
                   classProperty: 'class',
                   inputProperties: predictionBands
});

// Classify the image
var classified = cloudfree.select(predictionBands).classify(classifier);
Map.addLayer(classified, {min:0, max:2, palette: ['red','green','blue']}, 'classified');

// ACCURACY ASSESSMENT
// Step 1: Partition the set of known values into training and testing sets

// Adding a randomColumn of values ranging from 0 to 1
var trainingTesting = classifierTraining.randomColumn();

var trainingSet = trainingTesting.filter(ee.Filter.lt('random',0.6));
var testingSet = trainingTesting.filter(ee.Filter.gte('random',0.6));

// Now run the classifier only with the trainingSet
var trained = ee.Classifier.cart().train({
  features: trainingSet,
  classProperty: 'class',
  inputProperties: predictionBands
});

// Now classify the testData and obtain a Confusion matrix
var confusionMatrix = ee.ConfusionMatrix(testingSet.classify(trained)
                                                   .errorMatrix({
                                                     actual: 'class',
                                                     predicted: 'classification'
                                                   }));

// Now print the ConfusionMatrix and expand the object to inspect the matrix()
// One can also obtain basic descriptive statistics from the confusionMatrix
print('Confusion matrix:', confusionMatrix);
print('Overall Accuracy:', confusionMatrix.accuracy());
print('Producers Accuracy:', confusionMatrix.producersAccuracy());
print('Consumers Accuracy:', confusionMatrix.consumersAccuracy());

Unsure of how to calculate separability measures. I have shared the assets necessary. Here's the code link as well - https://code.earthengine.google.com/cb156ba6f71b6448f08bf106162c6373

0

The code you posted is not reproducible, so I'm starting from the classification example found in this introductory repo.

It's also not clear what you mean by the "use" of separability measures. I'm assuming that means you use them to assess how good of a job you've done creating a training dataset. For that reason, what you need here is training data, which is obtained through:

// Function to cloud mask from the pixel_qa band of Landsat 8 SR data.
function maskL8sr(image) {
  // Bits 3 and 5 are cloud shadow and cloud, respectively.
  var cloudShadowBitMask = 1 << 3;
  var cloudsBitMask = 1 << 5;
  // Get the pixel QA band.
  var qa = image.select('pixel_qa');
  // Both flags should be set to zero, indicating clear conditions.
  var mask = qa.bitwiseAnd(cloudShadowBitMask).eq(0)
      .and(qa.bitwiseAnd(cloudsBitMask).eq(0));
  // Return the masked image, scaled to TOA reflectance, without the QA bands.
  return image.updateMask(mask).divide(10000)
      .select("B[0-9]*")
      .copyProperties(image, ["system:time_start"]);
}

// Map the function over one year of data.
var collection = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR')
    .filterDate('2016-01-01', '2016-12-31')
    .map(maskL8sr)

var composite = collection.median();

// Display the results.
Map.addLayer(composite, {bands: ['B4', 'B3', 'B2'], min: 0, max: 0.3});

// Get some pre-made demonstration labels.
var labels = ee.FeatureCollection('users/nclinton/demo_landcover_labels');
Map.addLayer(labels, {}, 'labels');
// print(labels.first()); // Just to check.

var bands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'];

var training = composite.select(bands).sampleRegions({
  collection: labels, 
  properties: ['landcover'], 
  scale: 30
});
// print(training.size()) // N=98

That's just the first part of the classification example. Now, to check separability of the classes, here is a whole long list of distance measures, taken mostly from the excellent textbook by Schowengerdt:

// Compute per-class statistics.

// First, turn the prediction bands into vectors.
training = training.map(function(f) {
  // return f.set('vec', f.toArray(bands));
  return f.set('vec', f.toDictionary(bands).values(bands));
})

// Make a list of objects where each object corresponds to a class.
// Each object stores its label and a list of all the spectral
// vectors that belong to that class.
var lists = ee.List(training.reduceColumns({
  reducer: ee.Reducer.toList().group(0, 'class'), 
  selectors: ['landcover', 'vec']
}).get('groups'));

// Map over the lists to compute means and covariances.
// Note that the mean reducer works on arrays, and the 
// covariance reducer works on a list of 1D arrays.
lists = lists.map(function(obj) {
  var array = ee.Array(ee.Dictionary(obj).get('list'));
  var list = ee.List(ee.Dictionary(obj).get('list')).map(function(l) {
    return ee.Array(l)
  });
  var mean = array.reduce(ee.Reducer.mean(), [0]);
  // Watch out with this reducer.  It is an approximate solution.
  var covariance = list.reduce(ee.Reducer.covariance());
  return ee.Dictionary(obj).combine({
    mean: mean.transpose(),
    covariance: covariance
  })
});
print(lists)

// Implement lots of distance measures.
var classes = ee.List.sequence(0, 2);

var block = classes.map(function(i) {
  return classes.map(function(j) {
    var mean_i = ee.Array(ee.Dictionary(lists.get(i)).get('mean'));
    var mean_j = ee.Array(ee.Dictionary(lists.get(j)).get('mean'));
    return mean_i.subtract(mean_j).reduce('sum', [0]).project([0])
  })
})
print('block', block)

var euclidean = classes.map(function(i) {
  return classes.map(function(j) {
    var mean_i = ee.Array(ee.Dictionary(lists.get(i)).get('mean'));
    var mean_j = ee.Array(ee.Dictionary(lists.get(j)).get('mean'));
    var diff = mean_i.subtract(mean_j).project([0]);
    return diff.dotProduct(diff).sqrt();
  })
})
print('euclidean', euclidean)

var angular = classes.map(function(i) {
  return classes.map(function(j) {
    var mean_i = ee.Array(ee.Dictionary(lists.get(i)).get('mean')).project([0]);
    var mean_j = ee.Array(ee.Dictionary(lists.get(j)).get('mean')).project([0]);
    return mean_i.dotProduct(mean_j)
        .divide(mean_i.dotProduct(mean_i).sqrt())
        .divide(mean_j.dotProduct(mean_j).sqrt())
        .acos()
  })
})
print('angular', angular)

var mahalanobis = classes.map(function(i) {
  return classes.map(function(j) {
    var mean_i = ee.Array(ee.Dictionary(lists.get(i)).get('mean'));
    var mean_j = ee.Array(ee.Dictionary(lists.get(j)).get('mean'));
    var sigma_i = ee.Array(ee.Dictionary(lists.get(i)).get('covariance'));
    var sigma_j = ee.Array(ee.Dictionary(lists.get(j)).get('covariance'));
    return mean_i.subtract(mean_j).transpose() // 1x6
        .matrixMultiply(sigma_i.add(sigma_j).divide(2).matrixInverse()) // 6x6
        .matrixMultiply(mean_i.subtract(mean_j))
        .sqrt()
        .get([0, 0])
  })
})
print('mahalanobis', mahalanobis)

var bhattacharyya = classes.map(function(i) {
  return classes.map(function(j) {
    var mean_i = ee.Array(ee.Dictionary(lists.get(i)).get('mean'));
    var mean_j = ee.Array(ee.Dictionary(lists.get(j)).get('mean'));
    var sigma_i = ee.Array(ee.Dictionary(lists.get(i)).get('covariance'));
    var sigma_j = ee.Array(ee.Dictionary(lists.get(j)).get('covariance'));
    var mh = mean_i.subtract(mean_j).transpose()
        .matrixMultiply(sigma_i.add(sigma_j).divide(2).matrixInverse())
        .matrixMultiply(mean_i.subtract(mean_j))
        .get([0, 0])
        .sqrt();
    var t2 = sigma_i.add(sigma_j).divide(2).matrixDeterminant()
        .divide(sigma_i.matrixDeterminant().sqrt())
        .divide(sigma_j.matrixDeterminant().sqrt())
        .log()
        .divide(2);
    return mh.divide(8).add(t2)
  })
})
print('bhattacharyya', bhattacharyya)

var jm = ee.Array(bhattacharyya).multiply(-1).exp()
    .multiply(-1).add(1).multiply(2).sqrt();
print('jm', jm)
  • Thank you so much for this! This is super helpful :) – Vijay Ramesh May 25 at 12:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.