Measuring crop health

Keywords: data used; sentinel-2, band index; NDVI, interactive, analysis; time series, agriculture


While crops are growing, fields may look visually similar. However, health or growth rates from these fields can be quite different, leading to variability and unpredictability in revenue. Identifying underperforming crops can have two benefits:

  • Ability to scout for disease damage.

  • Ability to investigate poor performing fields and undertake management action such as soil testing or targeted fertilising to improve yield.

Digital Earth Africa use case

Satellite imagery can be used to measure plant health over time and identify any changes in growth patterns between otherwise similar fields. Sentinel-2’s 20-metre resolution makes it ideal for understanding the health of large fields.

The Normalised Difference Vegetation Index (NDVI) describes the difference between visible and near-infrared reflectance of vegetation cover. This index estimates the density of green on an area of land and can be used to track the health and growth of crops as they mature. Comparing the NDVI of two similar planting areas will help to identify any anomalies in growth patterns.


In this example, data from Sentinel-2 is used to assess crop growing patterns over two years. The worked example below takes users through the code required to:

  1. Create a time series data cube over croplands.

  2. Select multiple areas for comparison.

  3. Create graphs to identify crop performance trends over two years.

  4. Interpret the results.

Getting started

To run this analysis, run all the cells in the notebook, starting with the “Load packages and apps” cell.

Load packages and apps

This notebook works via two functions, which are referred to as apps: load_crophealth_data and run_crophealth_app. The apps allow the majority of the analysis code to be stored in another file, making the notebook easy to use and run. To view the code behind the apps, open the file.

%matplotlib inline

import datacube
from import load_crophealth_data
from import run_crophealth_app

Analysis parameters

The following cell sets important parameters for the analysis. There are four parameters that control where the data will be loaded:

  • lat: The central latitude to analyse (e.g. 14.789064).

  • lon: The central longitude to analyse (e.g. -17.065202).

  • buffer: The number of square degrees to load around the central latitude and longitude. For reasonable loading times, set this as 0.1 or lower.

  • date: The most recent date to show data for. The app will automatically load all data available for the two years prior to this date. (e.g. '2020-08-01' shows data from August 2018 to August 2020).

These can be changed in the cell below, noting that the DE Africa Explorer can be used to check whether Sentinel-2 data is available over the selected area.

Suggested areas

Here are some suggestions for areas to look at. To view one of these areas, copy and paste the parameter values into the cell below, then run the notebook.

Croplands, Senegal

lat = 14.789064
lon = -17.065202
buffer = 0.005

Aviv Coffee Farm, Tanzania

lat = -10.6979
lon = 35.2635
buffer = 0.003

Croplands, Western Kenya

lat = -0.483689
lon = 34.193792
buffer = 0.005
# Define the area of interest for the analysis
lat = 14.789064
lon = -17.065202
buffer = 0.005
date = '2020-08-01'

Load the data

The load_crophealth_data() command performs several key steps:

  • identify all available Sentinel-2 data in the case-study area over the two years prior to the set date

  • remove any bad quality pixels

  • keep images where more than half of the image contains good quality pixels

  • calculate the NDVI from the red and near infrared bands

  • return the collated data for analysis

The cleaned and collated data is stored in the dataset object. As the command runs, feedback will be provided below the cell, including information on the number of cleaned images loaded from the satellite.

The function takes four arguments: lat, lon, and buffer, date. These determine the area of interest adn date range that the function loads, and can be changed in the previous cell.

Please be patient. The load is complete when the cell status goes from [*] to [number].

dataset = load_crophealth_data(lat, lon, buffer, date)
Using pixel quality parameters for Sentinel 2
Finding datasets
Counting good quality pixels for each time step
Filtering to 97 out of 146 time steps with at least 50.0% good quality pixels
Applying pixel quality/cloud mask
Loading 97 time steps

Run the crop health app

The run_crophealth_app() command launches an interactive map. Drawing polygons within the red boundary (which represents the area covered by the loaded data) will result in plots of the average NDVI in that area. Draw polygons by clicking the ⬟ symbol in the app.

The function works by taking the loaded data dataset as an argument, as well as the lat, lon, and buffer parameters used to define the spatial extent.

Note: data points will only appear for images where more than 50% of the pixels were classified as good quality. This may cause trend lines on the average NDVI plot to appear disconnected. Available data points will be marked with the * symbol.

run_crophealth_app(dataset, lat, lon, buffer)

Drawing conclusions

Here are some questions to think about:

  • What are some factors that might explain differences or similarities across different sections of the study area?

  • Are there any noticable patterns across the two years of data? Could these correspond to specific events such as planting or harvesting?

Additional information

License: The code in this notebook is licensed under the Apache License, Version 2.0. Digital Earth Africa data is licensed under the Creative Commons by Attribution 4.0 license.

Contact: If you need assistance, please post a question on the Open Data Cube Slack channel or on the GIS Stack Exchange using the open-data-cube tag (you can view previously asked questions here). If you would like to report an issue with this notebook, you can file one on Github.

Compatible datacube version:


Last Tested:

from datetime import datetime'%Y-%m-%d')