Open In Colab   Open in Kaggle

Monitoring and Mapping Wildfires Using Satellite Data#

Content creators: Brittany Engle, Diana Cadillo

Content reviewers: Yuhan Douglas Rao, Abigail Bodner, Jenna Pearson, Chi Zhang, Ohad Zivan

Content editors: Zane Mitrevica, Natalie Steinemann, Jenna Pearson, Chi Zhang, Ohad Zivan

Production editors: Wesley Banfield, Jenna Pearson, Chi Zhang, Ohad Zivan

Our 2023 Sponsors: NASA TOPS and Google DeepMind

# @title Project Background

from ipywidgets import widgets
from IPython.display import YouTubeVideo
from IPython.display import IFrame
from IPython.display import display


class PlayVideo(IFrame):
    def __init__(self, id, source, page=1, width=400, height=300, **kwargs):
        self.id = id
        if source == "Bilibili":
            src = f"https://player.bilibili.com/player.html?bvid={id}&page={page}"
        elif source == "Osf":
            src = f"https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render"
        super(PlayVideo, self).__init__(src, width, height, **kwargs)


def display_videos(video_ids, W=400, H=300, fs=1):
    tab_contents = []
    for i, video_id in enumerate(video_ids):
        out = widgets.Output()
        with out:
            if video_ids[i][0] == "Youtube":
                video = YouTubeVideo(
                    id=video_ids[i][1], width=W, height=H, fs=fs, rel=0
                )
                print(f"Video available at https://youtube.com/watch?v={video.id}")
            else:
                video = PlayVideo(
                    id=video_ids[i][1],
                    source=video_ids[i][0],
                    width=W,
                    height=H,
                    fs=fs,
                    autoplay=False,
                )
                if video_ids[i][0] == "Bilibili":
                    print(
                        f"Video available at https://www.bilibili.com/video/{video.id}"
                    )
                elif video_ids[i][0] == "Osf":
                    print(f"Video available at https://osf.io/{video.id}")
            display(video)
        tab_contents.append(out)
    return tab_contents


video_ids = [('Youtube', '2CDVn6O_q34'), ('Bilibili', 'BV1GP411k7XA')]
tab_contents = display_videos(video_ids, W=730, H=410)
tabs = widgets.Tab()
tabs.children = tab_contents
for i in range(len(tab_contents)):
    tabs.set_title(i, video_ids[i][0])
display(tabs)
# @title Tutorial slides
# @markdown These are the slides for the videos in all tutorials today
from IPython.display import IFrame
link_id = "8w5da"

In this project, you will be working with wildfire remote sensing data from Sentinel 2 to extract Burn Perimeters using multiple Burn Indices and other relevant information related to wildfires. By integrating this data with information from other global databases, you will have the opportunity to explore the connections between wildfires and their impact on both the ecosystem and society.

Project Template#

Project Template

Note: The dashed boxes are socio-economic questions.

Data Exploration Notebook#

Project Setup#

# installs
# !pip install gdal
# !pip install pandas
# !pip install geopandas
# imports
import random
import numpy
import matplotlib.pyplot as plt
import pandas as pd
import geopandas as gpd
import os
import matplotlib

Satellite Data#

Global Wildfire Information System (GWIS) is a global, joint initiative created by the GEO and the Copernicus Work Programs. The goal of this program is to bring together sources at a regional and international level to provide a global, comprehensive understanding of fire regimes and their effects.

The Globfire dataset uses the MODIS burned area product (MCD64A1) to determine the amount of burnt area per event (Artés et al., 2019). The MCD64A1 product that Globfire is built on top of combines imagery from the Terra and Aqua satellites with thermal anomalies to provide burnt area information (Website, MODIS C6 BA User Guide & User Guide).

Sentinel 2 Launched by the European Space Agency in June of 2015 (S2-A) and March of 2017 (S2-B), the Copernicus Sentinel-2 mission combines two polar-orbiting satellites to monitor variability in land surface conditions. Together, they have a global revisit time of roughly 5 days.

In the provided Project Template, we load the following datasets:

Global Wildfire Information Systems: Climate Action Large Wildfire Dataset#

The Climate Action Large Wildfire Dataset is a filtered version of the Globfire dataset (created by GWIS, which you can access here).

The resolution of this dataset is 500m. It has been pre-filtered to include only fires that are Class F or greater. Per the National Wildfire Coordinating Group, a Class F fire is defined as a fire that is 1,000 acres or greater, but less than 5,000 acres (note that a 2000m square region is roughly 1000 acres). For this dataset, all fires greater than 1,000 acres are included. Additional columns indicating area (acres), landcover number and landcover description, and country in which they are located within, were added. The landcover number and description were added using the Copernicus Global Land Cover Layers: CGLS-LC100 Collection 3 User Guide.

Table Information: ID: Globfire fire ID (unique to each fire)

  1. IDate: Globfire Initial (start) date of the fire

  2. FDate: Globfire Final (end) date of the fire

  3. Continent: Location of the fire

  4. Area_acres: size of fire in acres

  5. Landcover: land cover value from ESA, if the landcover of the fire cover is greater than 51%, then it is labeled as that landcover

  6. LC_descrip: land cover description from ESA

  7. Country: country the fire is located in

# code to retrieve and load the data
url_climateaction = "~/shared/Data/Projects/Wildfires/ClimateAction_countries.shp"
Dataset = gpd.read_file(url_climateaction)  # need to update to OSF and pooch.retrieve
---------------------------------------------------------------------------
CPLE_OpenFailedError                      Traceback (most recent call last)
File fiona/ogrext.pyx:136, in fiona.ogrext.gdal_open_vector()

File fiona/_err.pyx:291, in fiona._err.exc_wrap_pointer()

CPLE_OpenFailedError: /home/runner/shared/Data/Projects/Wildfires/ClimateAction_countries.shp: No such file or directory

During handling of the above exception, another exception occurred:

DriverError                               Traceback (most recent call last)
Cell In[5], line 3
      1 # code to retrieve and load the data
      2 url_climateaction = "~/shared/Data/Projects/Wildfires/ClimateAction_countries.shp"
----> 3 Dataset = gpd.read_file(url_climateaction)  # need to update to OSF and pooch.retrieve

File /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages/geopandas/io/file.py:297, in _read_file(filename, bbox, mask, rows, engine, **kwargs)
    294     else:
    295         path_or_bytes = filename
--> 297     return _read_file_fiona(
    298         path_or_bytes, from_bytes, bbox=bbox, mask=mask, rows=rows, **kwargs
    299     )
    301 else:
    302     raise ValueError(f"unknown engine '{engine}'")

File /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages/geopandas/io/file.py:338, in _read_file_fiona(path_or_bytes, from_bytes, bbox, mask, rows, where, **kwargs)
    335     reader = fiona.open
    337 with fiona_env():
--> 338     with reader(path_or_bytes, **kwargs) as features:
    339         crs = features.crs_wkt
    340         # attempt to get EPSG code

File /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages/fiona/env.py:457, in ensure_env_with_credentials.<locals>.wrapper(*args, **kwds)
    454     session = DummySession()
    456 with env_ctor(session=session):
--> 457     return f(*args, **kwds)

File /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages/fiona/__init__.py:292, in open(fp, mode, driver, schema, crs, encoding, layer, vfs, enabled_drivers, crs_wkt, allow_unsupported_drivers, **kwargs)
    289     path = parse_path(fp)
    291 if mode in ("a", "r"):
--> 292     colxn = Collection(
    293         path,
    294         mode,
    295         driver=driver,
    296         encoding=encoding,
    297         layer=layer,
    298         enabled_drivers=enabled_drivers,
    299         allow_unsupported_drivers=allow_unsupported_drivers,
    300         **kwargs
    301     )
    302 elif mode == "w":
    303     colxn = Collection(
    304         path,
    305         mode,
   (...)
    314         **kwargs
    315     )

File /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages/fiona/collection.py:243, in Collection.__init__(self, path, mode, driver, schema, crs, encoding, layer, vsi, archive, enabled_drivers, crs_wkt, ignore_fields, ignore_geometry, include_fields, wkt_version, allow_unsupported_drivers, **kwargs)
    241 if self.mode == "r":
    242     self.session = Session()
--> 243     self.session.start(self, **kwargs)
    244 elif self.mode in ("a", "w"):
    245     self.session = WritingSession()

File fiona/ogrext.pyx:588, in fiona.ogrext.Session.start()

File fiona/ogrext.pyx:143, in fiona.ogrext.gdal_open_vector()

DriverError: /home/runner/shared/Data/Projects/Wildfires/ClimateAction_countries.shp: No such file or directory

We can now visualize the content of the dataset.

# code to print the shape, array names, etc of the dataset
Dataset.head()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[6], line 2
      1 # code to print the shape, array names, etc of the dataset
----> 2 Dataset.head()

NameError: name 'Dataset' is not defined
# plot the dataset
Dataset.plot()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[7], line 2
      1 # plot the dataset
----> 2 Dataset.plot()

NameError: name 'Dataset' is not defined

Great work! Now you are all set to address the questions you are interested in! Good luck!

Sentinel-2#

Each of the two satellites contain a Multi-Spectral Instrument (MSI) that houses 13 spectral bands, at various resolutions. The MSI uses a push-broom acquisition technique and measures in the Visible and Near Infrared (NIR) and the Short Wave Infrared (SWIR) domains. There are:

  • Four bands with 10m resolution: B2-Blue (490 nm), B3- Green (560 nm), B4- Red (665 nm) and Band 8- Near Infrared (NIR) (842 nm).

  • Six bands with 20m resolution: B5, B6, B7 and B8A (705 nm, 740 nm, 783 nm and 865 nm respectively) Vegetation Red Edge bands, along with B11 and B12 (1610 nm and 2190 nm) SWIR bands. These bands are mostly used for vegetation characterisation, vegetation stress assessment and ice/cloud/snow detection.

  • Three additional bands: B1 - Coastal Aerosol (443 nm), B9- Water Vapor (945 nm), and B10-SWIR-Cirrus (1375 nm) which are typically used for corrections of the atmosphere and clouds.

*Sentinel-2

# view image
def showImage(Output):
    plt.imshow(Output)
    plt.show()
# data source-specific imports

# root folder location of where the imagery is currently saved
rootFolder = '/home/jovyan/shared/Data/Projects/Wildfires'

continet = "Asia"

# import pre images
# pre_fire_paths = glob.glob(rootFolder + continet + id +"/pre_fire_*.npy")
pre_fire_paths = [
    fileName
    for fileName in os.listdir(os.path.join(rootFolder, continet))
    if (fileName.endswith(".npy") & fileName.startswith("pre_fire_"))
]
pre_fires_numpy = [
    numpy.load(os.path.join(rootFolder, continet, x)).astype(int)
    for x in pre_fire_paths
]

# import post images
post_fire_paths = [
    fileName
    for fileName in os.listdir(os.path.join(rootFolder, continet))
    if (fileName.endswith(".npy") & fileName.startswith("post_fire_"))
]
post_fires_numpy = [
    numpy.load(os.path.join(rootFolder, continet, x)).astype(int)
    for x in post_fire_paths
]

# import Pre-SCL Mask for cloud coverage
scl_fire_paths = [
    fileName
    for fileName in os.listdir(os.path.join(rootFolder, continet))
    if (fileName.endswith(".npy") & fileName.startswith("scl_pre_fire_"))
]
scl_fires_numpy = [
    numpy.load(os.path.join(rootFolder, continet, x)) for x in scl_fire_paths
]

# import Post-SCL Mask for cloud coverage
scl_fires_post_paths = [
    fileName
    for fileName in os.listdir(os.path.join(rootFolder, continet))
    if (fileName.endswith(".npy") & fileName.startswith("scl_post_fire_"))
]
scl_fires_post_numpy = [
    numpy.load(os.path.join(rootFolder, continet, x)) for x in scl_fires_post_paths
]
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[9], line 12
      6 continet = "Asia"
      8 # import pre images
      9 # pre_fire_paths = glob.glob(rootFolder + continet + id +"/pre_fire_*.npy")
     10 pre_fire_paths = [
     11     fileName
---> 12     for fileName in os.listdir(os.path.join(rootFolder, continet))
     13     if (fileName.endswith(".npy") & fileName.startswith("pre_fire_"))
     14 ]
     15 pre_fires_numpy = [
     16     numpy.load(os.path.join(rootFolder, continet, x)).astype(int)
     17     for x in pre_fire_paths
     18 ]
     20 # import post images

FileNotFoundError: [Errno 2] No such file or directory: '/home/jovyan/shared/Data/Projects/Wildfires/Asia'
# print list of pre_fires
print("\n".join(pre_fire_paths))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[10], line 2
      1 # print list of pre_fires
----> 2 print("\n".join(pre_fire_paths))

NameError: name 'pre_fire_paths' is not defined
# print list of post_fire
print("\n".join(post_fire_paths))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[11], line 2
      1 # print list of post_fire
----> 2 print("\n".join(post_fire_paths))

NameError: name 'post_fire_paths' is not defined
# print list of SCL
print("\n".join(scl_fire_paths))
print("\n".join(scl_fires_post_paths))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[12], line 2
      1 # print list of SCL
----> 2 print("\n".join(scl_fire_paths))
      3 print("\n".join(scl_fires_post_paths))

NameError: name 'scl_fire_paths' is not defined
# view pre-fire satellite image that was taken right before the fire start date
showImage(
    numpy.clip(pre_fires_numpy[2][:, :, [3, 2, 1]] / 10000 * 3.5, 0, 1)
)  # RGB bands for Sentinel 2 are Bands: 4,3,2
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[13], line 3
      1 # view pre-fire satellite image that was taken right before the fire start date
      2 showImage(
----> 3     numpy.clip(pre_fires_numpy[2][:, :, [3, 2, 1]] / 10000 * 3.5, 0, 1)
      4 )  # RGB bands for Sentinel 2 are Bands: 4,3,2

NameError: name 'pre_fires_numpy' is not defined
# view post-fire satellite image that was taken right before the fire start date
showImage(
    numpy.clip(post_fires_numpy[0][:, :, [3, 2, 1]] / 10000 * 3.5, 0, 1)
)  # RGB bands for Sentinel 2 are Bands: 4,3,2
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[14], line 3
      1 # view post-fire satellite image that was taken right before the fire start date
      2 showImage(
----> 3     numpy.clip(post_fires_numpy[0][:, :, [3, 2, 1]] / 10000 * 3.5, 0, 1)
      4 )  # RGB bands for Sentinel 2 are Bands: 4,3,2

NameError: name 'post_fires_numpy' is not defined

Sentinel-2: Vegetation Health & Burnt Areas#

Sentinel-2 Imagery - Vegetation Health & Burnt Areas

Continents included:

  • Asia

  • Africa

  • Austrailia

  • Europe

  • North America

  • South America

Vegetation Health: Vegetation indices define and monitor the health of vegetation using the radiance values of the visible and near-infrared channels. The Normalized Difference Vegetation Index (NDVI) is used to measure “greenness” of vegetation. As one of the most widely used vegetation indexes, the NDVI takes advantage of how strongly Chlorophyll absorbs visible light and how well the leafs cellular structure reflects near-infrared. Its values range from +1.0 to -1.0, with areas of sand, rock, and snow typically having a value of 0.1 or less. Shrubs and spare vegetation are roughly 0.2 to 0.5, while higher NDVI values (0.6 to 0.9) indicate dense vegetation that is typically found in tropical forests.

The NDVI can also be used to create the Vegetation Condition Index (VCI). The VCI depends on the current NDVI along with the extreme NDVI values within the dataset (NDVImax and NDVImin). Specifically, $\(VCI = \frac{NDVI-NDVImin}{NDVImax-NDVImin}\times 100\%\)$

This number is then compared to the threshold to determine the drought severity. For this project, up to 3 months of pre-fire imagery will be used to determine current drought conditions.

Burnt Area Severity: Burn severity describes how the intensity of the fire affects the functioning of the ecosystem in which it occurred. The degree to which it alters the ecosystem is typically found using a burn index, which then (typically) classes the severity of the fire as: unburned, low severity, moderate severity, or high severity. One of the most common burn indices is the Normalized Burn Ratio (NBR). This index is designed to highlight burnt areas in fire zones. The formula is similar to NDVI, except that the formula combines the use of both near infrared (NIR) and shortwave infrared (SWIR) wavelengths. Healthy vegetation shows a very high reflectance in the NIR, and low reflectance in the SWIR portion of the spectrum - the opposite of what is seen in areas devastated by fire. Recently burnt areas demonstrate low reflectance in the NIR and high reflectance in the SWIR. The difference between the spectral responses of healthy vegetation and burnt areas reach their peak in the NIR and the SWIR regions of the spectrum. The difference between normalized burn ratios before and after a fire is called the dNBR, and is one of many useful indices.

Specifically, the dNBR isolates the burned areas from the unburned areas, and subtracts the pre-fire imagery from the post-fire imagery. This removes any unchanged, and thus unburned, pixels as their values result in near zero. The results of the dNBR are based on burn severity, and correspond to the gradient of burn severity for every pixel. The dNBR has an unscaled range of -2.0 to +2.0 with burned areas tending to show more positively.

SCL Mask Importing the SCL in order to mask out clouds form imagery

# compute SCL Mask to 0s and 1s, masking out clouds and bad pixels
def computeSCLMask(image):
    rImage, cImage = image.shape
    sclOutput = numpy.zeros((rImage, cImage))
    for x in range(cImage):
        for y in range(rImage):
            sclOutput[y, x] = 1 if image[y, x] in [0, 1, 3, 8, 9, 11] else 0

    return sclOutput
# create Pre-fire and post-fire SCL masks
print(scl_fires_numpy[5])
pre_SCL_Mask = computeSCLMask(scl_fires_numpy[5])
post_SCL_Mask = computeSCLMask(scl_fires_post_numpy[0])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[16], line 2
      1 # create Pre-fire and post-fire SCL masks
----> 2 print(scl_fires_numpy[5])
      3 pre_SCL_Mask = computeSCLMask(scl_fires_numpy[5])
      4 post_SCL_Mask = computeSCLMask(scl_fires_post_numpy[0])

NameError: name 'scl_fires_numpy' is not defined
# view SCL imagery for image closests to the start fire date
# -------  Save SCL as colored image based on SCL classification

# No Data (0) = black
# Saturated / Defective (1) = red
# Dark Area Pixels (2) = chocolate
# Cloud Shadows (3) = brown
# Vegetation (4) = lime
# Bare Soils (5) = yellow
# Water (6) = blue
# Clouds low probability / Unclassified (7) = aqua
# clouds medium probability (8) = darkgrey
# Clouds high probability (9) light grey
# Cirrus (10) = deepskyblue
# Snow / Ice (11) = magenta
#  colors: https://matplotlib.org/3.1.1/gallery/color/named_colors.html#sphx-glr-gallery-color-named-colors-py


def showSCL(image):
    cmap = matplotlib.colors.ListedColormap(
        [
            "black",
            "red",
            "chocolate",
            "brown",
            "lime",
            "yellow",
            "blue",
            "aqua",
            "darkgrey",
            "lightgrey",
            "deepskyblue",
            "magenta",
        ]
    )
    plt.imshow(image, cmap=cmap)
    plt.show()


showSCL(scl_fires_numpy[5])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[17], line 40
     36     plt.imshow(image, cmap=cmap)
     37     plt.show()
---> 40 showSCL(scl_fires_numpy[5])

NameError: name 'scl_fires_numpy' is not defined

Dataset-specific Functions: NDVI#

# this code computes the NDVI for an image
"The NDVI can be computed on any image (pre or post)."
"Compute the NDVI on the pre-fire image"

# compute NDVI
def computeNDVI(image, mask):
    r, c, ch = image.shape
    ndviOutput = numpy.zeros((r, c))
    for x in range(c):
        for y in range(r):
            if (image[y, x, 7] == 0 and image[y, x, 3] == 0) or mask[y, x] == 1:
                ndviOutput[y, x] = numpy.nan
            else:
                ndviOutput[y, x] = (image[y, x, 7] - image[y, x, 3]) / (
                    image[y, x, 7] + image[y, x, 3]
                )

    return ndviOutput
# TA Code
computeNDVI_value = computeNDVI(pre_fires_numpy[2], pre_SCL_Mask)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[19], line 2
      1 # TA Code
----> 2 computeNDVI_value = computeNDVI(pre_fires_numpy[2], pre_SCL_Mask)

NameError: name 'pre_fires_numpy' is not defined
# plot NDVI without remap
showImage(computeNDVI_value)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[20], line 2
      1 # plot NDVI without remap
----> 2 showImage(computeNDVI_value)

NameError: name 'computeNDVI_value' is not defined
# use this code to remap the NDVI to specific colors for values
def remapNDVI(NDVI):
    remapped = numpy.zeros((NDVI.shape[0], NDVI.shape[1]))
    for x in range(remapped.shape[0]):
        for y in range(remapped.shape[1]):
            if numpy.isnan(NDVI[x, y]):
                remapped[x, y] = numpy.nan
            elif NDVI[x, y] <= -0.2:
                remapped[x, y] = 1
            elif NDVI[x, y] <= 0:
                remapped[x, y] = 2
            elif NDVI[x, y] <= 0.1:
                remapped[x, y] = 3
            elif NDVI[x, y] <= 0.2:
                remapped[x, y] = 4
            elif NDVI[x, y] <= 0.3:
                remapped[x, y] = 5
            elif NDVI[x, y] <= 0.4:
                remapped[x, y] = 6
            elif NDVI[x, y] <= 0.5:
                remapped[x, y] = 7
            elif NDVI[x, y] <= 0.6:
                remapped[x, y] = 8
            elif NDVI[x, y] <= 0.7:
                remapped[x, y] = 9
            elif NDVI[x, y] <= 0.8:
                remapped[x, y] = 10
            elif NDVI[x, y] <= 0.9:
                remapped[x, y] = 11
            elif NDVI[x, y] <= 1:
                remapped[x, y] = 12
            else:
                remapped[x, y] = 13
    return remapped
# TA Code
NDVI_remap = remapNDVI(computeNDVI_value)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[22], line 2
      1 # TA Code
----> 2 NDVI_remap = remapNDVI(computeNDVI_value)

NameError: name 'computeNDVI_value' is not defined
# view remapped NDVI
def showNDVI(image):
    cmap = matplotlib.colors.ListedColormap(
        [
            "#000000",
            "#a50026",
            "#d73027",
            "#f46d43",
            "#fdae61",
            "#fee08b",
            "#ffffbf",
            "#d9ef8b",
            "#a6d96a",
            "#66bd63",
            "#1a9850",
            "#006837",
        ]
    )
    plt.imshow(image, cmap=cmap)
    plt.show()


showNDVI(NDVI_remap)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[23], line 23
     19     plt.imshow(image, cmap=cmap)
     20     plt.show()
---> 23 showNDVI(NDVI_remap)

NameError: name 'NDVI_remap' is not defined

Dataset-specific Functions: VCI#

# compute the SCL mask for all the SCLs and apply it to the pre_fire_NDVIs
pre_fires_scl = [computeSCLMask(x) for x in scl_fires_numpy]
pre_fires_NDVI = [computeNDVI(x[0], x[1]) for x in zip(pre_fires_numpy, pre_fires_scl)]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[24], line 2
      1 # compute the SCL mask for all the SCLs and apply it to the pre_fire_NDVIs
----> 2 pre_fires_scl = [computeSCLMask(x) for x in scl_fires_numpy]
      3 pre_fires_NDVI = [computeNDVI(x[0], x[1]) for x in zip(pre_fires_numpy, pre_fires_scl)]

NameError: name 'scl_fires_numpy' is not defined
# compute for VCI
def computeVCI(for_ndvi_image, ndvi_dataset):
    rImage, cImage = for_ndvi_image.shape
    vciOutput = numpy.zeros((rImage, cImage))
    ndvi_dataset.append(for_ndvi_image)
    for x in range(cImage):
        for y in range(rImage):
            pixels = [z[y, x] for z in ndvi_dataset]
            filtered_pixels = [f for f in pixels if not numpy.isnan(f)]
            if len(filtered_pixels) == 0 or len(filtered_pixels) == 1:
                vciOutput[y, x] = 1
            elif numpy.isnan(for_ndvi_image[y, x]):
                vciOutput[y, x] = 1
            else:
                max_val = max(filtered_pixels)
                min_val = min(filtered_pixels)
                if max_val == min_val:
                    vciOutput[y, x] = 1
                else:
                    vciOutput[y, x] = (for_ndvi_image[y, x] - min_val) / (
                        max_val - min_val
                    )

    return vciOutput

VCI Drought Threshold#

droughtthreshold.png

# compute the VCI for the last pre-fire to view the drought over the time period
last_pre_fire_NDVI = pre_fires_NDVI.pop(2)
last_pre_fire_vci = computeVCI(last_pre_fire_NDVI, pre_fires_NDVI)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[26], line 2
      1 # compute the VCI for the last pre-fire to view the drought over the time period
----> 2 last_pre_fire_NDVI = pre_fires_NDVI.pop(2)
      3 last_pre_fire_vci = computeVCI(last_pre_fire_NDVI, pre_fires_NDVI)

NameError: name 'pre_fires_NDVI' is not defined
# view the non-thresholded VCI
showImage(last_pre_fire_vci)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[27], line 2
      1 # view the non-thresholded VCI
----> 2 showImage(last_pre_fire_vci)

NameError: name 'last_pre_fire_vci' is not defined
# map specific colors to values to show the severity of the droughts
def remapVCI(vci):
    remapped = numpy.zeros(vci.shape)
    for x in range(remapped.shape[0]):
        for y in range(remapped.shape[1]):
            if vci[x, y] < 0.35:
                remapped[x, y] = 1
            elif vci[x, y] <= 0.50:
                remapped[x, y] = 2
            else:
                remapped[x, y] = 3
    return remapped
# define the VCI mapped/thresholded values
def showVCI(vci_image):
    cmap = matplotlib.colors.ListedColormap(["red", "yellow", "green"])
    plt.imshow(remapVCI(vci_image), cmap=cmap)
    plt.show()
# view the mapped VCI values
showVCI(last_pre_fire_vci)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[30], line 2
      1 # view the mapped VCI values
----> 2 showVCI(last_pre_fire_vci)

NameError: name 'last_pre_fire_vci' is not defined

Dataset-specific Functions: NBR and dNBR Code#

# this code creates the NBR for each image then uses the NBR to create the dNBR. It can easily be updated for other burnt area indices


def computeFireMasks(pre_fire, post_fire):

    rows, columns, channels = pre_fire.shape
    nbrPost = numpy.zeros((rows, columns))
    nbrPre = numpy.zeros((rows, columns))
    dnbr = numpy.zeros((rows, columns))

    for x in range(columns):
        for y in range(rows):
            nbrPost[y, x] = (post_fire[y, x, 7] - post_fire[y, x, 11]) / (
                post_fire[y, x, 7] + post_fire[y, x, 11]
            )
            nbrPre[y, x] = (pre_fire[y, x, 7] - pre_fire[y, x, 11]) / (
                pre_fire[y, x, 7] + pre_fire[y, x, 11]
            )
            dnbr[y, x] = nbrPre[y, x] - nbrPost[y, x]

    return dnbr
# TA Code
# run computeFireMasks
dnbr = computeFireMasks(pre_fires_numpy[2], post_fires_numpy[0])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[32], line 3
      1 # TA Code
      2 # run computeFireMasks
----> 3 dnbr = computeFireMasks(pre_fires_numpy[2], post_fires_numpy[0])

NameError: name 'pre_fires_numpy' is not defined
# this code applies a threshold to the dNBR to show the level of burn intensity (unburned, low severity, moderate severity, or high severity)


def remapDNBR(dnbr):
    remapped = numpy.zeros((dnbr.shape[0], dnbr.shape[1]))
    for x in range(remapped.shape[0]):
        for y in range(remapped.shape[1]):
            if numpy.isnan(dnbr[x, y]):
                remapped[x, y] = numpy.nan
            elif dnbr[x, y] <= -0.251:
                remapped[x, y] = 1
            elif dnbr[x, y] <= -0.101:
                remapped[x, y] = 2
            elif dnbr[x, y] <= 0.099:
                remapped[x, y] = 3
            elif dnbr[x, y] <= 0.269:
                remapped[x, y] = 4
            elif dnbr[x, y] <= 0.439:
                remapped[x, y] = 5
            elif dnbr[x, y] <= 0.659:
                remapped[x, y] = 6
            elif dnbr[x, y] <= 1.3:
                remapped[x, y] = 7
            else:
                remapped[x, y] = 8
    return remapped
# TA Code
dnbr_remapped = remapDNBR(dnbr)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[34], line 2
      1 # TA Code
----> 2 dnbr_remapped = remapDNBR(dnbr)

NameError: name 'dnbr' is not defined

dNBR Threshold#

threshold.png

# this code takes the above function (remapDNBR) where the dNBR threshold has been applied to the image'
# and applies a color coded map to each threshold as shown in the image above'


def showDNBR(dnbr):
    cmap = matplotlib.colors.ListedColormap(
        [
            "blue",
            "teal",
            "green",
            "yellow",
            "orange",
            "red",
            "purple",
        ]
    )
    plt.imshow(remapDNBR(dnbr), cmap=cmap)


showDNBR(dnbr_remapped)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[35], line 20
      6     cmap = matplotlib.colors.ListedColormap(
      7         [
      8             "blue",
   (...)
     15         ]
     16     )
     17     plt.imshow(remapDNBR(dnbr), cmap=cmap)
---> 20 showDNBR(dnbr_remapped)

NameError: name 'dnbr_remapped' is not defined

Further Reading#

Socio-economic effects of wildfires

  • Zhao, J. et al. (2020). Quantifying the Effects of Vegetation Restorations on the Soil Erosion Export and Nutrient Loss on the Loess Plateau. Front. Plant Sci. 11 https://doi.org/10.3389/fpls.2020.573126

  • Amanda K. Hohner, Charles C. Rhoades, Paul Wilkerson, Fernando L. Rosario-Ortiz (2019). Wildfires alter forest watersheds and threaten drinking water quality. Accounts of Chemical Research. 52: 1234-1244. https://doi.org/10.1021/acs.accounts.8b00670

  • Alan Buis (2021). The Climate Connections of a Record Fire Year in the U.S. West. Link

  • Ian P. Davies ,Ryan D. Haugo,James C. Robertson,Phillip S. Levin (2018). The unequal vulnerability of communities of color to wildfire.PLoS ONE 13(11): e0205825. https://doi.org/10.1371/journal.pone.0205825

Wildfires and forest ecosystems