0

Can you advise me on how to correctly use .stratifiedSample to prep input for .smileRandomForest, using Google Earth Engine (GEE) Python API?

I'm trying to create training data with .stratifiedSample. I'm sampling from a layer with four classes, labeled 0-3. Then I want to use that output to train a .smileRandomForest classifier, and then classify the input image with the trained classifier.

The code runs, but the .classify step produces an image object that I can't visualize and which is difficult to examine (by which I mean .bandNames() and .display() don't work on it). I assume a step before that (sampling or training the classifier) is not set up correctly, but I'm not sure how.

My code is below:

#Imports
import numpy
import ee  
import geemap
#ee.Authenticate()
ee.Initialize()

#Constants
i_date = '2024-02-10'
f_date = '2024-03-01'
lon = -80.205595
lat = 25.748059
geometry = ee.Geometry({
    'type': 'Polygon',
    'coordinates': [[            #lat, lon
        [-80.267610, 25.796023], #Top left, 
        [-80.263518, 25.730725], #Lower left, 
        [-80.121574, 25.730725], #Lower right, 
        [-80.121574, 25.810281], #Top right
    ]],
})
geometry_bounds = geometry.bounds()

# Define functions

def addNDWI(image):
    ndwi = (image.select('B3').subtract(image.select('B8A'))).divide(image.select('B3').add(image.select('B8A'))).rename('NDWI_water')
    return image.addBands(ndwi)

def addMNDWI(image):
    mndwi = (image.select('B3').subtract(image.select('B11'))).divide(image.select('B3').add(image.select('B11'))).rename('MNDWI_water')
    return image.addBands(mndwi)

def addAWEI(image):   
    awei = (((image.select('B2').add(image.select('B3').multiply(ee.Number(2.5)))).subtract(
             (image.select('B8A').add(image.select('B11'))).multiply(ee.Number(1.5))).subtract(
             image.select('B12').multiply(ee.Number(.25)))).divide(
             image.select('B2').add(image.select('B3')).add(image.select('B8A')).add(image.select('B11')).add(image.select('B12')))).rename('AWEI_water')
    return image.addBands(awei)

def reclass_band(image, bandname, threshold, newname):
    bandtoreclass = image.select(bandname)
    reclassed = bandtoreclass.where(bandtoreclass.lt(threshold), 0).where(bandtoreclass.gte(threshold), 1).rename(newname)
    return image.addBands(reclassed)

def tally(image):
    tallied = (image.select('NDWI_reclass')).add(image.select('MNDWI_reclass')).add(image.select('AWEI_reclass')).toUint8().rename('Ensemble_Vote')
    return image.addBands(tallied)

# Get imagery (Sentinel 2)
s2_filtered = ee.ImageCollection('COPERNICUS/S2_SR').filterBounds(geometry).filterDate(i_date, f_date)
s2_clipped_collection = ee.ImageCollection(s2_filtered.map(lambda image: image.clip(geometry)))

# Add spectral indices
s2_ndwi_added = s2_clipped_collection.map(addNDWI) 
s2_mndwi_added = s2_ndwi_added.map(addMNDWI)
s2_just_ndwi_mndwi_awei = s2_mndwi_added.map(addAWEI)

# Reclass indices to binary
s2_ndwi_reclass_added = s2_just_ndwi_mndwi_awei.map(lambda image: reclass_band(image, 'NDWI_water', 0, 'NDWI_reclass'))
s2_mndwi_reclass_added = s2_ndwi_reclass_added.map(lambda image: reclass_band(image, 'MNDWI_water', 0, 'MNDWI_reclass'))
s2_awei_reclass_added = s2_mndwi_reclass_added.map(lambda image: reclass_band(image, 'AWEI_water', 0, 'AWEI_reclass'))
s2_B8A_reclass_added = s2_awei_reclass_added.map(lambda image: reclass_band(image, 'B8A', 850, 'B8A_NIR_reclass'))
s2_B12_reclass_added = s2_B8A_reclass_added.map(lambda image: reclass_band(image, 'B12', 800, 'B12_SWIR_reclass'))

# Calculate ensemble vote (0-3, how many indices agree a pixel is water)
s2_tallied = s2_B12_reclass_added.map(tally)

# Get one image to test the sampling method on.
s2_List_b=ee.ImageCollection(s2_tallied).toList(s2_tallied.size());
s2_single_Image_b=ee.Image(ee.List(s2_List_b).get(1));

### The following does not produce expected output:

# Collect stratified random points in the four ensemble classes (0-3)
stratified = s2_single_Image_b.stratifiedSample(
      numPoints=1000,
      classBand='Ensemble_Vote',
      geometries=True
    )

# Attempt to create classifier, train using points collected from the ensemble layer, and then classify an image using the trained classifier
RF_classifier = ee.Classifier.smileRandomForest(numberOfTrees=30)
Training_RF_classifier = RF_classifier.train(
    features=stratified,  
    classProperty="Ensemble_Vote", 
    inputProperties=s2_single_Image_b,
)
classifiedImage = s2_single_Image_b.classify(Training_RF_classifier)

# none of these produce information:
band_names = classifiedImage.bandNames()
display('Band names:', band_names)
classifiedImage.propertyNames()

# View
m = geemap.Map(center=[lat, lon], zoom=13)
viz2 = {'min': 0, 'max': 3, 'palette': ['34a116', '185bba', 'fc0398','0000FF']}
m.addLayer(classifiedImage.select('Ensemble_Vote'), viz2, 'Classified')
m

#The .addLayer produces EEException: Classifier.train, argument 'inputProperties': Invalid type.
#Expected type: List<String>.
#Actual type: Image<...

1 Answer 1

0

As the error messagbe says: you've provided an incorrect argument to Classifier.train(). The inputProperties argument is expected to be a list of property names. That's not what you used. You probably want image.bandNames() instead.

Training_RF_classifier = RF_classifier.train(
    features=stratified,  
    classProperty="Ensemble_Vote", 
    inputProperties=s2_single_Image_b.bandNames(),
)
1
  • Thank you for answering and explaining. This fixed the .train output (which now produces a "Classifier.train" object that can be examined). However, calling the fixed classifier on an image still produces the same odd output (<ee.image.Image object at 0x000001E2C3A95710>) that can't be examined or displayed. Is something else wrong? Commented Jun 5 at 17:23

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.