Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

earth and related environmental sciences

Radar Vegetation Index

Monitor vegetation crop growth using Sentinel-1 GRD data

Eurac Research
ESA EOPF Zarr Logo

Radar Vegetation IndexΒΆ

πŸš€ Launch in JupyterHub

Run this notebook interactively with all dependencies pre-installed

PrefaceΒΆ

The original notebooks used as a reference for this work are the following:

  1. Copernicus Data Space Ecosystem example available here.

  2. Sentinel Hub example, available here

The example has been adapted to use the data provided by the EOPF Sentinel Zarr Samples project instead of the openEO API or SentinelHub API.

IntroductionΒΆ

This notebook exmplains how to compute two different vegetation indexes using Sentinel-1 Ground Range Detected (GRD) data. They can be used to monitor the vegetation health condition or crop growth using SAR data.
We will use the Sentinel-1 GRD collection available within the EOPF Sentinel Zarr Samples STAC API, showing how to efficiently retrieve small Areas Of Interest (AOIs) from the data in SAR geometry.

Upgrade and install libraries:

Requirement already satisfied: xarray in /home/mclaus@eurac.edu/micromamba/envs/eopf-zarr/lib/python3.11/site-packages (2025.1.1)
Collecting xarray
  Using cached xarray-2025.10.1-py3-none-any.whl.metadata (12 kB)
Collecting xvec
  Using cached xvec-0.5.1-py3-none-any.whl.metadata (3.2 kB)
Requirement already satisfied: numpy>=1.26 in /home/mclaus@eurac.edu/micromamba/envs/eopf-zarr/lib/python3.11/site-packages (from xarray) (1.26.4)
Requirement already satisfied: packaging>=24.1 in /home/mclaus@eurac.edu/micromamba/envs/eopf-zarr/lib/python3.11/site-packages (from xarray) (25.0)
Requirement already satisfied: pandas>=2.2 in /home/mclaus@eurac.edu/micromamba/envs/eopf-zarr/lib/python3.11/site-packages (from xarray) (2.3.3)
Requirement already satisfied: pyproj>=3.0.0 in /home/mclaus@eurac.edu/micromamba/envs/eopf-zarr/lib/python3.11/site-packages (from xvec) (3.7.2)
Requirement already satisfied: shapely>=2.0b1 in /home/mclaus@eurac.edu/micromamba/envs/eopf-zarr/lib/python3.11/site-packages (from xvec) (2.1.2)
Collecting cf_xarray>=0.9.2 (from xvec)
  Using cached cf_xarray-0.10.9-py3-none-any.whl.metadata (16 kB)
Collecting xproj>=0.2.0 (from xvec)
  Using cached xproj-0.2.1-py3-none-any.whl.metadata (3.0 kB)
Requirement already satisfied: python-dateutil>=2.8.2 in /home/mclaus@eurac.edu/micromamba/envs/eopf-zarr/lib/python3.11/site-packages (from pandas>=2.2->xarray) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /home/mclaus@eurac.edu/micromamba/envs/eopf-zarr/lib/python3.11/site-packages (from pandas>=2.2->xarray) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /home/mclaus@eurac.edu/micromamba/envs/eopf-zarr/lib/python3.11/site-packages (from pandas>=2.2->xarray) (2025.2)
Requirement already satisfied: certifi in /home/mclaus@eurac.edu/micromamba/envs/eopf-zarr/lib/python3.11/site-packages (from pyproj>=3.0.0->xvec) (2025.11.12)
Requirement already satisfied: six>=1.5 in /home/mclaus@eurac.edu/micromamba/envs/eopf-zarr/lib/python3.11/site-packages (from python-dateutil>=2.8.2->pandas>=2.2->xarray) (1.17.0)
Using cached xarray-2025.10.1-py3-none-any.whl (1.4 MB)
Using cached xvec-0.5.1-py3-none-any.whl (784 kB)
Using cached cf_xarray-0.10.9-py3-none-any.whl (76 kB)
Using cached xproj-0.2.1-py3-none-any.whl (23 kB)
Installing collected packages: xarray, xproj, cf_xarray, xvec
  Attempting uninstall: xarray
    Found existing installation: xarray 2025.1.1
    Uninstalling xarray-2025.1.1:
      Successfully uninstalled xarray-2025.1.1
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4/4 [xvec][32m1/4 [xproj]
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
xcube-eopf 0.3.0 requires xcube-core>=1.11.0, which is not installed.
xcube-stac 1.1.1 requires libgdal-jp2openjpeg, which is not installed.
xcube-stac 1.1.1 requires xcube-core>=1.11.0, which is not installed.
openeo 0.46.0 requires xarray<2025.01.2,>=0.12.3, but you have xarray 2025.10.1 which is incompatible.
Successfully installed cf_xarray-0.10.9 xarray-2025.10.1 xproj-0.2.1 xvec-0.5.1
Note: you may need to restart the kernel to use updated packages.

Import ModulesΒΆ

<xarray.core.options.set_options at 0x7f22a703df50>

Data AccessΒΆ

We use the STAC API to query for the necessary data, specifying also the Sentinel-1 orbit direction.

items found: 37

We can extract the path to the Zarr products from the list of STAC Items

Open a single product:

Loading...

Spatial SlicingΒΆ

Since we want to work on a small area of interest, but the data is not on a regular grid, we will have to understand the data structure to perform the most efficient spatial slicing of the data.
To perform the spatial slice, we will use Ground Control Points (GCPs).

Loading...

As we presented in a previous notebook, it is possible to interpolate the GCPs data to match the GRD dimensions and assign the latitude and longitude values to the GRD.

We can then create a boolean mask, showing where the coordinates fall within our AOI, which is used to perform the spatial slicing.

If you have enough resources, this step might take around 1 minute. If not, it might also crash your Python kernel. In the latter case, skip to the next proposed method, which is less resource intensive.

Efficient Spatial SlicingΒΆ

The previosuly presented procedure is inefficient, since it firsty interpolates the GCPs and then compares each pixel with the coordinates of our AOI.
We can speed up this process by comparing immediately the low resolution GCPs with our AOI, get the corresponding slice in SAR geometry (using the azimuth_time and ground_range dimensions).
Finally interpolate the coordinates only for the smaller extent we extracted.

We firstly define a helper function to compare the dimension of the GCPs subset and get the necessary indexes for spatial slicing:

We now perform the more efficient spatial slicing:

CPU times: user 448 ms, sys: 146 ms, total: 594 ms
Wall time: 618 ms
Loading...

CalibrationΒΆ

Get the Οƒ0\sigma_0 look up table and calibrate the data:

Loading...

GeocodingΒΆ

Regular GridΒΆ

Since we plan to analyze values from multiple dates as a single datacube, we need to geocode the data on a regular grid to align it and stack it together.
The following helper function creates a regularly spaced coordinates grid from the lat/lon bounds:

InterpolationΒΆ

We can now use the scipy library to perform the data interpolation from the irregular to the common regular grid:

CPU times: user 2.32 s, sys: 430 ms, total: 2.75 s
Wall time: 4.93 s
Loading...

Visualize Geocoded ResultΒΆ

<Figure size 640x480 with 2 Axes>

Radar Vegetation IndexΒΆ

Now that we have our geocoded GRD data, we can compute the vegetation indexes:

  1. Radar Vegetation Index (RVI) by Nasirzadehdizaji et al.: RVI=4ΟƒVH0ΟƒVV0+ΟƒVH0RVI = \frac{4\sigma^0_{VH}}{\sigma^0_{VV}+\sigma^0_{VH}}

  2. Dual-pol Radar Vegetation Index for GRD data (DpRVI) by Bhogapurapu et al.: DpRVI=q(q+3)(q+1)2DpRVI = \frac{q(q+3)}{(q+1)^2} where q=ΟƒVH0ΟƒVV0q = \frac{\sigma^0_{VH}}{\sigma^0_{VV}}

Time Series AnalysisΒΆ

We were able to compute the indexes for a single step in time. However a single view is not enough to characterize the vegetation behaviour in the long term.
Therefore, we have to do the same processing for all the products we selected via the STAC API.

Extract min/max values for the longitude and latitude to build a common regular grid

Geocode and combine the time series of sigma nought data into a single datacube:

Combine data from adjacent GRD scenes for the same day (coming from different Zarr products):

Loading...

Store the result locally, so that in case the Kernel crashes it will be possible to start again from the next cell, skipping the pre-processing steps.

<xarray.backends.zarr.ZarrStore at 0x7f228417c540>

Compute the vegetation indexes:

Spatial AggregationΒΆ

A sample geoJSON file containing the field of three different crop fields boundaries provides us the location of interest to perform our analysis:

Loading...

We can visualize the overlay the field boundaries with the geocoded backscatter:

<GeoAxes: title={'center': 'time = 2017-05-03'}, xlabel='x', ylabel='y'>
<Figure size 1600x800 with 2 Axes>

Now we can use the Xvec library to aggregate our radar vegetation index values over the specific fields:

And finally plot the resulting time series. We will notice that the two variations of the radar vegetation index assume really similar values, where the main difference is that the DpRVI is scaled between 0 and 1, whereas the RVI is not.

<Figure size 1000x300 with 3 Axes>
<Figure size 1000x300 with 3 Axes>

SummaryΒΆ

This notebook showed a complete Sentinel-1 GRD pipeline: from the data access to the final time series analysis.

Some important remarks:

  • Sentinel-1 GRD data has to be calibrated and geocoded before using it in this kind of analysis.

  • This notebook proposes an efficient way to crop and geocode the data, which is highly relevant for small areas and long time series.