AG真人百家乐官方网站

Skip to main content
NSF NEON, Operated by Battelle

Main navigation

  • AG真人百家乐官方网站 Us
    • Overview
      • Spatial and Temporal Design
      • History
    • Vision and Management
    • Advisory Groups
      • Science, Technology & Education Advisory Committee
      • Technical Working Groups (TWGs)
    • FAQ
    • Contact Us
      • Contact NEON Biorepository
      • Field Offices
    • User Accounts
    • Staff
    • Code of Conduct

    AG真人百家乐官方网站 Us

  • Data & Samples
    • Data Portal
      • Spatial Data & Maps
    • Data Themes
      • Biogeochemistry
      • Ecohydrology
      • Land Cover and Processes
      • Organisms, Populations, and Communities
    • Samples & Specimens
      • Discover and Use NEON Samples
        • Sample Types
        • Sample Repositories
        • Megapit and Distributed Initial Characterization Soil Archives
      • Sample Processing
      • Sample Quality
    • Collection Methods
      • Protocols & Standardized Methods
      • Airborne Remote Sensing
        • Flight Box Design
        • Flight Schedules and Coverage
        • Daily Flight Reports
          • AOP Flight Report Sign Up
        • Camera
        • Imaging Spectrometer
        • Lidar
      • Automated Instruments
        • Site Level Sampling Design
        • Sensor Collection Frequency
        • Instrumented Collection Types
          • Meteorology
          • Phenocams
          • Soil Sensors
          • Ground Water
          • Surface Water
      • Observational Sampling
        • Site Level Sampling Design
        • Sampling Schedules
        • Observation Types
          • Aquatic Organisms
            • Aquatic Microbes
            • Fish
            • Macroinvertebrates & Zooplankton
            • Periphyton, Phytoplankton, and Aquatic Plants
          • Terrestrial Organisms
            • Birds
            • Ground Beetles
            • Mosquitoes
            • Small Mammals
            • Soil Microbes
            • Terrestrial Plants
            • Ticks
          • Hydrology & Geomorphology
            • Discharge
            • Geomorphology
          • Biogeochemistry
          • DNA Sequences
          • Pathogens
          • Sediments
          • Soils
            • Soil Descriptions
        • Optimizing the Observational Sampling Designs
    • Data Notifications
    • Data Guidelines and Policies
      • Acknowledging and Citing NEON
      • Publishing Research Outputs
      • Usage Policies
    • Data Management
      • Data Availability
      • Data Formats and Conventions
      • Data Processing
      • Data Quality
      • Data Product Bundles
      • Data Product Revisions and Releases
        • Release 2021
        • Release 2022
        • Release 2023
        • Release 2024
        • Release-2025
      • NEON and Google
      • Externally Hosted Data

    Data & Samples

  • Field Sites
    • AG真人百家乐官方网站 Field Sites and Domains
    • Explore Field Sites

    Field Sites

  • Impact
    • Observatory Blog
    • Case Studies
    • Papers & Publications
    • Newsroom
      • NEON in the News
      • Newsletter Archive
      • Newsletter Sign Up

    Impact

  • Resources
    • Getting Started with NEON Data & Resources
    • Documents and Communication Resources
      • Papers & Publications
      • Outreach Materials
    • Code Hub
      • Code Resources Guidelines
      • Code Resources Submission
    • Learning Hub
      • Science Videos
      • Tutorials
      • Workshops & Courses
      • Teaching Modules
    • Research Support Services
      • Field Site Coordination
      • Letters of Support
      • Mobile Deployment Platforms
      • Permits and Permissions
      • AOP Flight Campaigns
      • Research Support FAQs
      • Research Support Projects
    • Funding Opportunities

    Resources

  • Get Involved
    • Advisory Groups
      • Science, Technology & Education Advisory Committee
      • Technical Working Groups
    • Upcoming Events
    • NEON Ambassador Program
      • Exploring NEON-Derived Data Products Workshop Series
    • Research and Collaborations
      • Environmental Data Science Innovation and Inclusion Lab
      • Collaboration with DOE BER User Facilities and Programs
      • EFI-NEON Ecological Forecasting Challenge
      • NEON Great Lakes User Group
      • NEON Science Summit
      • NCAR-NEON-Community Collaborations
        • NCAR-NEON Community Steering Committee
    • Community Engagement
      • How Community Feedback Impacts NEON Operations
    • Science Seminars and Data Skills Webinars
      • Past Years
    • Work Opportunities
      • Careers
      • Seasonal Fieldwork
      • Internships
        • Intern Alumni
    • Partners

    Get Involved

  • My Account
  • Search

Search

Learning Hub

  • Science Videos
  • Tutorials
  • Workshops & Courses
  • Teaching Modules

Breadcrumb

  1. Resources
  2. Learning Hub
  3. Tutorials
  4. Relating Reflectance Indices to Flux Data

Tutorial

Relating Reflectance Indices to Flux Data

Authors: [Claire K. Lunch, Chris Florian]

Last Updated: Apr 10, 2025

THIS TUTORIAL USES PRE-PREPARED DATA PROVIDED IN PERSON. CHECK BACK FOR AN UPDATED TUTORIAL INCLUDING INSTRUCTIONS FOR DATA WRANGLING.

Flux footprints represent an estimate of the region of land surface contributing to the fluxes estimated by eddy covariance. The exact footprint varies from moment to moment, due to changes in wind speed and direction, so footprints are typically presented as proportional estimates of the contribution from each point over the relevant time period. See for details and considerations about footprint calculations.

Because they represent regions of land around the flux tower, flux footprints are ideally suited to be used in combination with remotely sensed data. In this tutorial, we attempt to use flux footprints together with NDVI to estimate net ecosystem exchange (NEE), but the general principles used here can be applied to many data types in the remote sensing and eddy covariance suites (for example, canopy water content and latent heat fluxes, canopy height increment and cumulative carbon uptake, etc).

Background: NDVI in the flux footprint

Let's say we want to use reflectance data to scale up NEE, by extrapolating flux data across a larger landscape.

NEON remote sensing flights follow a detailed schedule, as described on the flight schedule page. Although flights are generally carried out at a similar time of year at each site over time, weather conditions can shift the exact date, and local weather and phenology may result in flights capturing slightly different conditions on the ground in different years. And of course, local conditions each year may result in different vegetation and soil conditions even at the same time of year and same phenological stage.

The flux footprints enable us to greatly improve the accuracy of the relationship between reflectance and fluxes - instead of guessing at the appropriate region around the tower to use for the comparison, we can use the region identified by the footprint, and weighted by the relative contribution of each pixel to the footprint.

To begin to bring together these data sources, in this tutorial, we will examine the relationship between NDVI within the flux footprint and NEE on the day(s) the remote sensing flight took place. In order to give ourselves the best odds of detecting differences, we'll focus on grasslands, where NDVI is a more reliable indicator of photosynthetic activity than, say, evergreen forests. The sites used here span a
large latitude range across the Great Plains.

Objectives

After completing this activity, you will be able to:

  • Examine rasters of NEON flux footprint and NDVI data
  • Calculate the weighted average NDVI within the footprint
  • Explore the relationship between NDVI and NEE

Things You鈥檒l Need To Complete This Tutorial

  • The dataset provided on flash drives, containing flux data, footprint rasters, and NDVI rasters
  • The tutorial is written in R, but if you are more comfortable in Python, you can follow the narrative sections and translate the code sections into Python. The functions carried out by the raster package in R can be done with rasterio in Python.

Install Packages

  • R raster: install.packages("raster")
  • Python rasterio: pip install rasterio

Additional Resources

  • NEON
  • Download and Explore tutorial
  • Introduction to NEON flux data

Set Up Environment and Load Data

First install and load the necessary packages.

## 
## # install and load raster package
## # if raster is already installed, only the library() command
## # needs to be run
## install.packages("raster")
library(raster)
## 

Data

For this tutorial, we will need three data sets from two NEON data products: NDVI is published in Vegetation indices (DP3.30026.001), and NEE and footprints are published in Eddy covariance (DP4.00200.001). These datasets have been pre-prepared and provided on flash drives. The flux data for all sites are in one file, flux_allSites.csv. The NDVI and footprint data are in paired .grd and .gri files, one pair for each site and flight date. Finally, the flight_dates.csv file contains the date of roughly the middle of the flight campaign, to help you line up the comparable flux data with the NDVI data.

Four sites are included, three of them with data from two years. The sites, from south to north, are Konza Prairie Biological Station (KONZ), Central Plains Experimental Range (CPER), Northern Great Plains Research Laboratory (NOGP), and Dakota Coteau Field Station (DCFS).

If you're working in R, you may want to create an R Project and add the data folder to it, to keep the file paths simple. The code below assumes this structure; otherwise, you'll simply have to modify the file paths.

Start by loading the flux data and flight dates:

flux.all <- read.csv("~/data/flux_allSites.csv")
flight.dates <- read.csv("~/data/flight_dates.csv")

Let's start by viewing the data files for a single site and year, to get familiar with the data. We'll use KONZ July 2020.

Load the footprint and NDVI data. The .grd and .gri files both contain information, but loading the data only requires pointing to the .grd files.

## 
foot.KONZ <- raster("~/data/footKONZ2020-07.grd")
ndvi.KONZ <- raster("~/data/ndviKONZ2020-07.grd")
## 
## # if using Python and rasterio:
## footKONZ = rasterio.open("/data/footKONZ2020-07.grd")
## ndviKONZ = rasterio.open("/data/ndviKONZ2020-07.grd")
## 

Explore Data from One Site

Let's start by viewing the NDVI data. The plot() function will recognize the data type and create a nice image:

plot(ndvi.KONZ)

plot() also works for the flux footprints, but for these data I prefer the look of a contour plot, which shows the boundaries of different levels of influence on the flux:

filledContour(foot.KONZ, col=topo.colors(20))

To get a better look at the region contributing the most to the flux, we can trim the raster down to pixels with a value greater than 0.0005, using the calc() and trim() functions from the raster package. First we set all pixels with a low value to zero, then trim the raster to exclude those pixels:

footAdj <- calc(foot.KONZ, 
                 fun=function(x){ x[x < 0.0005] <- 0; 
                 return(x)})
footMost <- trim(footAdj, values=0)
filledContour(footMost, col=topo.colors(20))

We can also overlay the NDVI and footprint data, to see where the footprint falls on the landscape. In this case we'll use the plot() function for clearer visuals:

plot(ndvi.KONZ)
plot(foot.KONZ, add=T,
     col=terrain.colors(5, alpha=0.5))

Next step: calculate mean NDVI from the sampled area, and then NDVI weighted by the contribution of the pixel to the fluxes. Starting with mean NDVI, we'll use the cellStats() function from the raster package.

cellStats(ndvi.KONZ, stat="mean", na.rm=T)

## [1] 0.7722162

To get NDVI weighted by the footprint, we need to make the two rasters comparable. Currently their boundaries don't match, and neither does their resolution. Again, the raster package has methods for this: crop() to crop the NDVI raster down to cover the same area as the footprint, and resample() to adjust the footprint raster to the resolution of the NDVI raster.

ndvi.KONZ <- crop(ndvi.KONZ, extent(foot.KONZ))
foot.KONZ <- resample(foot.KONZ, ndvi.KONZ)

In the footprint raster, each pixel's value is its proportional contribution to the flux. But now that we've resampled it to a different resolution, the sum of the pixels is no longer 1. We need to re-level the pixel values to bring them back to proportional values:

foot.KONZ <- foot.KONZ/cellStats(foot.KONZ, 
                                 stat="sum", 
                                 na.rm=T)

Now we can calculate the weighted NDVI, multiplying each NDVI pixel by its footprint weight and then using the cellStats() function as we did above:

prop.weight <- foot.KONZ*ndvi.KONZ
cellStats(prop.weight, stat="sum", 
          na.rm=T)

## [1] 0.7992741

Here we can see NDVI within the flux footprint is a little higher than NDVI across the entire region.

Let's take a look at the fluxes from the same time. We'll subset the flux.all table based on the site, and the dates from the flight.dates table. Let's include the date in the table, plus one on either side, since it's rare that the flights are completed in a single day (see Assumptions section below for discussion about the precise data collection dates).

flux.all$timeBgn <- as.POSIXct(flux.all$timeBgn, 
                               format="%Y-%m-%d %H:%M:%S",
                               tz="GMT")
flux.KONZ <- flux.all[which(flux.all$siteID=="KONZ" & 
                      flux.all$timeBgn>=as.POSIXct("2020-07-11", 
                                                   tz="GMT") &
                      flux.all$timeBgn<as.POSIXct("2020-07-14", 
                                                   tz="GMT")),]
plot(flux.KONZ$data.fluxCo2.nsae.flux~flux.KONZ$timeBgn, 
     pch=20, xlab="Date", ylab="Net ecosystem exchange")

Now we have fluxes and weighted NDVI from one site, but this doesn't tell us much by itself! We need context from other sites and years.

Calculate Weighted NDVI and Fluxes Across Sites

In the next two code chunks, we'll write a function to make the weighted NDVI calculations above, and then loop over the sites and dates in the dataset. I encourage you to try out writing the function and the loop yourself, especially if you're working on developing your coding skills. But if you're not familiar with how to do this, you can copy the code as written below.

Function to calculate footprint-weighted NDVI:

foot.weighted <- function(ndvi.raster, foot.raster) {

  ndvi.foot <- crop(ndvi.raster, extent(foot.raster))
  foot.ndvi <- raster::resample(foot.raster, ndvi.foot)
  foot.ndvi <- foot.ndvi/cellStats(foot.ndvi, stat="sum", na.rm=T)
  comb <- foot.ndvi*ndvi.foot
  w.ndvi <- cellStats(comb, stat="sum", na.rm=T)
  return(w.ndvi)
  
}

Loop over the rasters of all sites and dates, reading in both the NDVI and footprint rasters and applying the weighting function. If your data aren't in a project folder, or your folder structure differs, you'll need to adjust the file paths at the start of the for loop.

flight.dates$month <- substring(flight.dates$FlightDate, 1, 7)
ndvi.w <- character()
for(i in unique(flight.dates$Site)) {
  
  ffls <- list.files("~/data", "foot", full.names=T)
  afls <- list.files("~/data", "ndvi", full.names=T)
  ffls <- grep(".grd$", ffls, value=T)
  afls <- grep(".grd$", afls, value=T)

  flight.dates.i <- flight.dates[which(flight.dates$Site==i),]
  
  for(j in unique(flight.dates.i$month)) {
    
    footfl <- grep(i, ffls, value=T)
    footfl <- grep(j, footfl, value=T)
    if(length(footfl)==0) {next}
    nfl <- grep(i, afls, value=T)
    nfl <- grep(j, nfl, value=T)
    
    foot <- raster(footfl)
    ndvi <- raster(nfl)
    
    nw <- foot.weighted(ndvi, foot)
    ndvi.w <- rbind(ndvi.w, c(i, j, nw))
    
  }
  
}

ndvi.w <- data.frame(ndvi.w)
names(ndvi.w) <- c('site','month','ndvi')
ndvi.w$ndvi <- as.numeric(ndvi.w$ndvi)

ndvi.w

##   site   month      ndvi
## 1 KONZ 2019-05 0.7561873
## 2 KONZ 2020-07 0.7992741
## 3 NOGP 2019-07 0.7307495
## 4 NOGP 2021-06 0.4495142
## 5 CPER 2020-06 0.2578832
## 6 CPER 2021-06 0.3197897
## 7 DCFS 2020-06 0.7258271

Now we have a table of footprint-weighted NDVI for every site and month of data collection. Great! All we need now is flux data to compare it to.

We'll again loop over the sites and dates in the dataset, but this time we'll subset the flux dataset to the date of the flight, plus one day on either side, and calculate the daily net ecosystem exchange. Again, I encourage you to give this a try on your own, but the code I used to do it is in the next code chunk.

ndvi.w$flux <- NA
flight.dates$FlightDate <- as.POSIXct(flight.dates$FlightDate, 
                                      tz="GMT")
for(i in 1:nrow(flight.dates)) {
  
  flux.sub <- flux.all[which(flux.all$siteID==
                               flight.dates$Site[i] & 
                        flux.all$timeBgn>=
                          I(flight.dates$FlightDate[i]-86400) &
                        flux.all$timeBgn<
                          I(flight.dates$FlightDate[i]+86400)),]
  fl <- mean(flux.sub$data.fluxCo2.nsae.flux, na.rm=T)
  ndvi.w$flux[which(ndvi.w$site==flight.dates$Site[i] & 
                      ndvi.w$month==flight.dates$month[i])] <- fl
  
}

# convert to units of grams of carbon per meter squared per day
ndvi.w$flux <- ndvi.w$flux*1e-6*12.011*86400

Now plot NEE against weighted NDVI:

plot(ndvi.w$flux~ndvi.w$ndvi, 
     xlab="NDVI", ylab="NEE",
     pch=20)

To get a little more insight, let's plot the site code for each data point:

plot(ndvi.w$flux~ndvi.w$ndvi, 
     xlab="NDVI", ylab="NEE",
     type="n", pch=20)
text(ndvi.w$ndvi, ndvi.w$flux,
     labels=ndvi.w$site, cex=0.5)

We can see that increasing NDVI is associated with increased carbon uptake. But we calculated this using overall NEE, which includes respiration. Depending on our analysis, it might be more appropriate to try to build a model to predict uptake, and to model respiration separately.

To do this properly, we'd need to use flux partitioning methods, but we can get a very crude idea of what the results might look like by re-calculating our NEE estimate using only the negative values from each day. We'll re-use the flux calculation for loop above:

ndvi.w$dayflux <- NA
for(i in 1:nrow(flight.dates)) {
  
  flux.sub <- flux.all[which(flux.all$siteID==
                               flight.dates$Site[i] & 
                        flux.all$timeBgn>=
                          I(flight.dates$FlightDate[i]-86400) &
                        flux.all$timeBgn<
                          I(flight.dates$FlightDate[i]+86400)),]
  fl <- mean(flux.sub$data.fluxCo2.nsae.flux
             [which(flux.sub$data.fluxCo2.nsae.flux<0)], na.rm=T)
  ndvi.w$dayflux[which(ndvi.w$site==flight.dates$Site[i] & 
                      ndvi.w$month==flight.dates$month[i])] <- fl
  
}

ndvi.w$dayflux <- ndvi.w$dayflux*1e-6*12.011*86400

And plot carbon uptake as above:

plot(ndvi.w$dayflux~ndvi.w$ndvi, 
     xlab="NDVI", ylab="Daytime NEE",
     type="n", pch=20)
text(ndvi.w$ndvi, ndvi.w$dayflux,
     labels=ndvi.w$site, cex=0.5)

The relationship is much tighter when we consider only net uptake values.

Assumptions, Simplifications, and Further Possibilities

As mentioned, it's best to model GPP and respiration separately, and we would expect NDVI to have a much stronger relationship with GPP than respiration. But that's not the only way our analysis here is limited relative to what's possible.

To simplify our calculations of which fluxes correspond to the NDVI values, I provided the flight.dates table, which contains a single date for each site and year. In reality, the flights can span several days, which aren't always consecutive. In addition to simplifying the flight info, the flux footprints in the provided dataset are averaged over all the days when the plane was overhead.

The data product includes data tiles containing the flight date when each pixel was collected. The expanded package of the flux data product, includes flux footprints for every half-hourly flux calculation interval. It is possible to use these datasets to calculate the contribution to the flux of every NDVI pixel at the time it was observed. However, this would be a very elaborate calculation, and before going down that road, consider the scientific question you're trying to answer. Given what you know about uncertainties in fluxes, uncertainties in NDVI, and the relative rates of change of NDVI and NEE over time, is your question sensitive to this level of detail?

Considering sensitivity raises another question: How important were the flux footprints to our analysis? How different would the results be if we simply calculated NDVI for, say, a one kilometer radius around the tower? This is an easy question to answer, with only a small modification to the code above. Give it a try, and again, consider the types of analyses that might demand different levels of precision.

Finally, note that in our dataset, we had three pairs of repeat measurements at the same site. In general, we would expect much better model predictions within a site than between sites. And we restricted ourselves to Great Plains grasslands. How would we expect this exercise to differ if we expanded into other site types?

Get Lesson Code

flux-footprint-NDVI.R

Questions?

If you have questions or comments on this content, please contact us.

Contact Us
NSF NEON, Operated by Battelle

Follow Us:

Join Our Newsletter

Get updates on events, opportunities, and how NEON is being used today.

Subscribe Now

Footer

  • AG真人百家乐官方网站 Us
  • Newsroom
  • Contact Us
  • Terms & Conditions
  • Careers
  • Code of Conduct

Copyright © Battelle, 2025

The National Ecological Observatory Network is a major facility fully funded by the U.S. National Science Foundation.

Any opinions, findings and conclusions or recommendations expressed in this material do not necessarily reflect the views of the U.S. National Science Foundation.