Indices#

Indices calculated from multispectral satellite imagery are powerful ways to quantitatively analyze these data. They take advantage of different spectral properties of materials to differentiate between them. Many of these indices can be calculated with simple arithmetic operations. So now that our data are in xarray.Dataset’s it’s fairly easy to calculate them.

NDVI#

As an example, let’s load two example scenes from the Brumadinho tailings dam disaster:

import xlandsat as xls
import matplotlib.pyplot as plt

path_before = xls.datasets.fetch_brumadinho_before()
path_after = xls.datasets.fetch_brumadinho_after()

before = xls.load_scene(path_before)
after = xls.load_scene(path_after)

We can calculate the NDVI for these scenes to see if we can isolate the effect of the flood following the dam collapse:

before = before.assign(
    ndvi=(before.nir - before.red) / (before.nir + before.red),
)
after = after.assign(
    ndvi=(after.nir - after.red) / (after.nir + after.red),
)

# Set some metadata for xarray to find
before.ndvi.attrs["long_name"] = "normalized difference vegetation index"
before.ndvi.attrs["units"] = "dimensionless"
after.ndvi.attrs["long_name"] = "normalized difference vegetation index"
after.ndvi.attrs["units"] = "dimensionless"

after
<xarray.Dataset>
Dimensions:   (easting: 400, northing: 300)
Coordinates:
  * easting   (easting) float64 5.844e+05 5.844e+05 ... 5.963e+05 5.964e+05
  * northing  (northing) float64 -2.232e+06 -2.232e+06 ... -2.223e+06 -2.223e+06
Data variables:
    blue      (northing, easting) float16 0.0686 0.07043 ... 0.05823 0.0564
    green     (northing, easting) float16 0.1027 0.09839 ... 0.07593 0.07043
    red       (northing, easting) float16 0.09778 0.09778 ... 0.06799 0.06177
    nir       (northing, easting) float16 0.2988 0.2715 0.2881 ... 0.2637 0.251
    swir1     (northing, easting) float16 0.2311 0.2274 0.2316 ... 0.1608 0.142
    swir2     (northing, easting) float16 0.145 0.1442 0.144 ... 0.09961 0.08655
    ndvi      (northing, easting) float16 0.5073 0.4705 0.4907 ... 0.5903 0.605
Attributes: (12/19)
    Conventions:                CF-1.8
    title:                      Landsat 8 scene from 2019-01-30 (path/row=218...
    digital_object_identifier:  https://doi.org/10.5066/P9OGBGM6
    origin:                     Image courtesy of the U.S. Geological Survey
    landsat_product_id:         LC08_L2SP_218074_20190130_20200829_02_T1
    processing_level:           L2SP
    ...                         ...
    ellipsoid:                  WGS84
    date_acquired:              2019-01-30
    scene_center_time:          12:57:09.1851220Z
    wrs_path:                   218
    wrs_row:                    74
    mtl_file:                   GROUP = LANDSAT_METADATA_FILE\n  GROUP = PROD...

And now we can make pseudo-color plots of the NDVI:

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 12))

# Limit the scale to [-1, +1] so the plots are easier to compare
before.ndvi.plot(ax=ax1, vmin=-1, vmax=1, cmap="RdBu_r")
after.ndvi.plot(ax=ax2, vmin=-1, vmax=1, cmap="RdBu_r")

ax1.set_title(f"Before: {before.attrs['title']}")
ax2.set_title(f"After: {after.attrs['title']}")

ax1.set_aspect("equal")
ax2.set_aspect("equal")

plt.show()
_images/indices_2_0.png

Finally, we can calculate the change in NDVI from one scene to the other by taking the difference:

ndvi_change = before.ndvi - after.ndvi
ndvi_change.name = "ndvi_change"
ndvi_change.attrs["long_name"] = (
    f"NDVI change between {before.attrs['date_acquired']} and "
    f"{after.attrs['date_acquired']}"
)
ndvi_change
<xarray.DataArray 'ndvi_change' (northing: 300, easting: 370)>
array([[ 0.05908 ,  0.06323 ,  0.0542  , ...,  0.004395, -0.009766,
         0.07324 ],
       [ 0.0498  ,  0.07764 ,  0.07495 , ...,  0.0957  ,  0.012695,
         0.003906],
       [-0.010254,  0.11743 ,  0.0747  , ...,  0.03125 ,  0.01807 ,
         0.04004 ],
       ...,
       [-0.000977,  0.01123 ,  0.000977, ...,  0.00928 ,  0.01367 ,
         0.00708 ],
       [ 0.00879 ,  0.02344 ,  0.01318 , ...,  0.006836,  0.00586 ,
         0.001221],
       [-0.01221 ,  0.02637 ,  0.006836, ...,  0.01343 ,  0.01221 ,
         0.0105  ]], dtype=float16)
Coordinates:
  * easting   (easting) float64 5.844e+05 5.844e+05 ... 5.954e+05 5.955e+05
  * northing  (northing) float64 -2.232e+06 -2.232e+06 ... -2.223e+06 -2.223e+06
Attributes:
    long_name:  NDVI change between 2019-01-14 and 2019-01-30

Did you notice?

The keen-eyed among you may have noticed that the number of points along the "easting" dimension has decreased. This is because xarray only makes the calculations for pixels where the two scenes coincide. In this case, there was an East-West shift between scenes but our calculations take that into account.

Now lets plot it:

fig, ax = plt.subplots(1, 1, figsize=(10, 6))
ndvi_change.plot(ax=ax, vmin=-1, vmax=1, cmap="PuOr")
ax.set_aspect("equal")
plt.show()
_images/indices_4_0.png

There’s some noise in the cloudy areas of both scenes in the northeast but otherwise this plots highlights the area affected by flooding from the dam collapse in bright red at the center.