Lab 9 - Thresholds

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

What You'll Learn

  • Understand the difference between continuous and categorical data
  • Apply threshold values to create binary (yes/no) classifications
  • Use Boolean operators (gt, lt, eq) for pixel-level tests
  • Create multi-class classifications using .where()
  • Reassign class values using .remap()

Building On Previous Learning

This lab directly extends Lab 8 where you calculated NDVI (a continuous index ranging from -1 to 1). Now you'll learn to convert that continuous data into categorical classes like "forest" vs "non-forest."

Why This Matters

Continuous data like NDVI is powerful for analysis, but many applications require categorical maps:

  • Land cover mapping: Converting NDVI into "vegetated" vs "non-vegetated"
  • Water detection: Thresholding NDWI to identify water bodies
  • Urban extraction: Using NDBI thresholds to map built-up areas
  • Burn severity: Classifying NBR into severity categories
  • Area calculation: You can only calculate the area of categorical classes

Thresholding is a simple but powerful form of image classification.

Before You Start

  • Prerequisites: Complete Lab 8 and revisit the lecture notes on threshold-based classification.
  • Estimated time: 60 minutes
  • Materials: Earth Engine access, your NDVI script from Lab 8, and documentation for threshold values you want to test.

Key Terms

Threshold
A cutoff value used to separate continuous data into categories (e.g., NDVI > 0.5 = forest).
Boolean Operator
A logical test that returns true (1) or false (0). Examples: greater than (gt), less than (lt), equal to (eq).
Continuous Data
Data that can take any value within a range (e.g., NDVI from -1 to 1, temperature from -40 to 50).
Categorical Data
Data that falls into distinct classes represented by labels or codes (e.g., 0=Water, 1=Forest, 2=Urban).
.where()
An Earth Engine method that conditionally assigns values based on a test - like an if/else statement for images.
.remap()
An Earth Engine method that changes pixel values from one set of values to another.

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.

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

Measured on a continuous scale, can take any value within a range.

  • Example: NDVI values from -1 to 1
  • Analysis: Mean, standard deviation, regression
  • Use: Detailed scientific analysis, change detection

Categorical Data

Falls into distinct categories represented by labels or codes.

  • Example: "Vegetated" vs "Non-vegetated"
  • Analysis: Frequency tables, area calculations
  • Use: Land cover maps, decision making, reporting

Boolean 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."

Operator Method Meaning Example
> .gt() Greater than ndvi.gt(0.5)
< .lt() Less than ndvi.lt(0)
>= .gte() Greater than or equal ndvi.gte(0.3)
<= .lte() Less than or equal ndvi.lte(-0.1)
== .eq() Equal to landcover.eq(1)
!= .neq() Not equal to landcover.neq(0)

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');

Understanding the code:

  • This is the same NDVI calculation from Lab 8
  • Sentinel-2: B8 = NIR, B4 = Red
  • NDVI ranges from -1 (water) to +1 (dense vegetation)

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.

Try It

Use the Inspector to click on different land covers. What NDVI values do you find for: water, urban areas, grass, and dense forest? Write these down - you'll use them to set thresholds.

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');

Understanding .gt():

  • Tests each pixel: "Is this value greater than 0.5?"
  • Returns 1 if true (green in our palette)
  • Returns 0 if false (white in our palette)
  • The result is a binary image with only two values

Expected Result

You should see a simplified map where green = forest (NDVI > 0.5) and white = non-forest (NDVI ≤ 0.5).

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:

  • .lt() - Less than
  • .lte() - Less than or equal to
  • .eq() - Equal to
  • .neq() - Not equal to
  • .gte() - Greater than or equal to

Part 3: Using .where() for Multiple Classes

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 the where method that conditionally evaluates to true or false within each pixel depending on the outcome of a test.

Important: Avoid JavaScript if Statements

JavaScript if statements run on your computer, not on Google's servers. Using them for pixel-level operations would require downloading the entire image to your browser - which would crash it! Always use .where() for conditional logic on images.

Let's split the image into three classes: water, non-forested, and forested areas using thresholds of -0.1 and 0.5:

// 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');

Understanding the logic:

  • Start with all pixels = 1 (this will become "non-forest")
  • Set pixels with NDVI ≤ -0.1 to 0 (water)
  • Set pixels with NDVI ≥ 0.5 to 2 (forest)
  • Remaining pixels stay at 1 (non-forest)

Class Scheme

Value Color Class NDVI Range
0 Blue Water ≤ -0.1
1 White Non-forest -0.1 to 0.5
2 Green Forest ≥ 0.5

Pro Tips

  • You can combine conditions with .and() and .or()
  • Example: seaNDVI.gte(-0.1).and(seaNDVI.lt(0.5))
  • Start with a default value and use .where() to override specific ranges

Part 4: Remapping Values

The remap method takes specific values in an image and assigns them different values. This is particularly useful for categorical datasets when you need to standardize class codes.

// 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');

Understanding remap():

  • First array: original values to find
  • Second array: new values to assign
  • 0 → 9, 1 → 11, 2 → 10

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.

When to use remap: Remap is useful when combining datasets with different class coding schemes, or when you need to assign classes values that match an external standard.

Check Your Understanding

  1. What is the difference between .gt(0.5) and .gte(0.5)?
  2. If you use ndvi.lt(0), what pixels will have a value of 1?
  3. Why should you avoid JavaScript if statements for image operations?
  4. If you wanted to create 5 classes instead of 3, how many .where() calls would you need?

Troubleshooting

Problem: The entire image is one color after thresholding

Solution: Your threshold value is probably wrong. Use the Inspector on your NDVI image to find actual values, then adjust your threshold accordingly.

Problem: "Geometry is required" error with .where()

Solution: When creating a constant image with ee.Image(1), you need to .clip() it to a geometry before using .where().

Problem: Remap doesn't seem to change anything

Solution: Make sure your "from" values exactly match the values in your image. Use Inspector to check what values actually exist.

Key Takeaways

  • Boolean operators (.gt(), .lt(), etc.) create binary images (0 and 1)
  • Thresholds convert continuous data into categorical classes
  • Use .where() for multi-class conditional logic, NOT JavaScript if
  • Use .remap() to reassign class values after classification
  • Always verify thresholds using the Inspector tool before finalizing

📋 Lab Submission

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)

Your code should include:

  • NDVI calculation
  • Binary threshold classification
  • Multi-class classification using .where()
  • Remapping demonstration
  • Clear comments explaining each step