Lab 9 - Thresholds

Objective: Learn to apply thresholds and Boolean operators to create categorical classifications from continuous data.

Introduction

In this lab, we will extend the skills acquired in Lab 8 - Band Arithmetic - NDVI, to create categorized images using logical operators based on a threshold value. After learning how to manipulate images using band arithmetic to yield continuous values, such as the Normalized Difference Vegetation Index (NDVI), we will now focus on categorizing these continuous values into distinct classes. Utilizing Google Earth Engine's suite of Boolean and conditional operators, the lab will guide you through classifying NDVI values to distinguish between vegetated and non-vegetated areas.

Specifically, we will:

  1. Explore the difference between continuous and categorical data, emphasizing their applications in remote sensing.
  2. Implement a basic threshold to create a binary map separating vegetated and non-vegetated regions.
  3. Use advanced techniques like .where and remap to create a more nuanced categorized image, distinguishing between water, non-forest, and forest areas.

Background: Continuous vs. Categorical Data

Continuous Data: This type of data is measured on a continuous scale and can take on any value within a given range. In the context of remote sensing, NDVI is a good example of continuous data. The NDVI values can range from -1 to 1, offering a high level of granularity. Statistical methods like mean, standard deviation, and regression are suitable for analyzing continuous data.

Categorical Data: Categorical data falls into distinct categories and is often represented by labels or numbers with no mathematical meaning. In remote sensing, this would involve taking continuous data like NDVI and categorizing it into groups, such as "vegetated" and "non-vegetated," based on certain thresholds. Frequency tables, chi-square tests, and contingency tables are used for analyzing categorical data.

Logical Operators for Categorization

We will use Google Earth Engine to implement thresholds and categorize NDVI values. Logical operators like 'greater than' (gt), 'less than' (lt), and 'equal to' (eq) will be employed to separate the NDVI image into two categories: "no vegetation" or "vegetation." This simplification can be invaluable for certain types of analyses, like determining the proportion of a city that is vegetated.

Part 1: Creating an NDVI Image

Let's create a Sentinel-2 map of NDVI near Seattle, Washington, USA. Enter the code below in a new script.

// Create an NDVI image using Sentinel 2.
var seaPoint = ee.Geometry.Point(-122.2040, 47.6221);
var seaImage = ee.ImageCollection('COPERNICUS/S2')
    .filterBounds(seaPoint)
    .filterDate('2020-08-15', '2020-10-01')
    .first();

var seaNDVI = seaImage.normalizedDifference(['B8', 'B4']);

// And map it.
Map.centerObject(seaPoint, 10);
var vegPalette = ['red', 'white', 'green'];
Map.addLayer(seaNDVI,
    {
        min: -1,
        max: 1,
        palette: vegPalette
    },
    'NDVI Seattle');

Inspect the image. We can see that vegetated areas are darker green while non-vegetated locations are white and water is pink. If we use the Inspector to query our image, we can see that parks and other forested areas have an NDVI of about 0.5. Thus, it would make sense to define areas with NDVI values greater than 0.5 as forested, and those below that threshold as not forested.

Part 2: Implementing a Threshold

Now let's define that value as a threshold and use it to threshold our vegetated areas.

// Implement a threshold.
var seaVeg = seaNDVI.gt(0.5);

// Map the threshold.
Map.addLayer(seaVeg,
    {
        min: 0,
        max: 1,
        palette: ['white', 'green']
    },
    'Non-forest vs. Forest');

The gt method is from the family of Boolean operators—that is, gt is a function that performs a test in each pixel and returns the value 1 if the test evaluates to true, and 0 otherwise. Here, for every pixel in the image, it tests whether the NDVI value is greater than 0.5. When this condition is met, the layer seaVeg gets the value 1. When the condition is false, it receives the value 0.

Use the Inspector tool to explore this new layer. If you click on a green location, that NDVI should be greater than 0.5. If you click on a white pixel, the NDVI value should be equal to or less than 0.5.

Other operators in this Boolean family include less than (lt), less than or equal to (lte), equal to (eq), not equal to (neq), and greater than or equal to (gte) and more.

Part 3: Using .where

A binary map classifying NDVI is very useful. However, there are situations where you may want to split your image into more than two bins. Earth Engine provides a tool, the where method, that conditionally evaluates to true or false within each pixel depending on the outcome of a test. This is analogous to an if statement seen commonly in other languages. However, we avoid using the JavaScript if statement to perform this logic when programming for Earth Engine. Importantly, JavaScript if commands are not calculated on Google's servers, and can create serious problems when running your code—in effect, the servers try to ship all of the information to be executed to your own computer's browser, which is very underequipped for such enormous tasks. Instead, we use the where clause for conditional logic.

Suppose instead of splitting the forested areas from the non-forested areas in our NDVI, we want to split the image into likely water, non-forested, and forested areas. We can use where and thresholds of -0.1 and 0.5. We will start by creating an image using ee.Image. We then clip the new image to cover the same area as our seaNDVI layer.

// Implement .where.
// Create a starting image with all values = 1.
var seaWhere = ee.Image(1)
    // Use clip to constrain the size of the new image.
    .clip(seaNDVI.geometry());

// Make all NDVI values less than -0.1 equal 0.
seaWhere = seaWhere.where(seaNDVI.lte(-0.1), 0);

// Make all NDVI values greater than 0.5 equal 2.
seaWhere = seaWhere.where(seaNDVI.gte(0.5), 2);

// Map our layer that has been divided into three classes.
Map.addLayer(seaWhere,
    {
        min: 0,
        max: 2,
        palette: ['blue', 'white', 'green']
    },
    'Water, Non-forest, Forest');

There are a few exciting things to note about this code that you may not have seen before. First, we're not defining a new variable for each where call. As a result, we can perform many calls without creating a new variable each time and needing to keep track of them. Second, when we started the starting image, we set the value to 1. This means we could easily set the bottom and top values with one where clause each. Finally, while we did not do it here, we can combine multiple where clauses using and or or. For example, we could identify pixels with an intermediate level of NDVI using seaNDVI.gte(-0.1).and(seaNDVI.lt(0.5)).

Part 4: Remapping Values

Finally, let's do some Remapping that takes specific values in an image and assigns them a different value. This is particularly useful for categorical datasets. Let's use the remap method to change the values for our seaWhere layer. Note that since we're changing the middle value to be the largest, we'll also need to adjust our palette.

// Implement remapping.
// Remap the values from the seaWhere layer.
var seaRemap = seaWhere.remap([0, 1, 2], // Existing values.
    [9, 11, 10]); // Remapped values.

Map.addLayer(seaRemap,
    {
        min: 9,
        max: 11,
        palette: ['blue', 'green', 'white']
    },
    'Remapped Values');

Use the inspector to compare values between our original seaWhere (displayed as Water, Non-Forest, Forest) and the seaRemap, marked as "Remapped Values." Click on a forested area to see that the Remapped Values should be 10, instead of 2.

📧 Lab Submission

Submit lab via email.

Subject: Lab 9 - Thresholds - [Your Name]

Submit the URL to your code with well-commented code that explains the different aspects of this lab. (50 points)