{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "provenance": [], "authorship_tag": "ABX9TyMengzOvSvUK3zBUlhx+XFx", "include_colab_link": true }, "kernelspec": { "name": "python3", "display_name": "Python 3" }, "language_info": { "name": "python" } }, "cells": [ { "cell_type": "markdown", "metadata": { "id": "view-in-github", "colab_type": "text" }, "source": [ "<a href=\"https://colab.research.google.com/github/alex-pakalniskis/gisc606-spring2023/blob/main/lab1/GISC606_Lab1.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" ] }, { "cell_type": "markdown", "source": [ "# GISC 606 Lab 1: Normalized Burn Ratio (NBR)\n", "\n", "## Overview\n", "* Part 1: Assess burn scars with satellite imagery and open-source GIS tools\n", "* Part 2: Assigned readings and literature search\n", "* Your Deliverable\n", "\n", "## Part 1\n", "Assess burn scars with satellite imagery\n", "> Note: This tutorial was originally developed by Esri's Learn Team. You can find the official maintained version at [this location](https://learn.arcgis.com/en/projects/assess-burn-scars-with-satellite-imagery/). Additionally you can find other tutorials in the [tutorial gallery](https://learn.arcgis.com/en/gallery/).\n", "\n", "\n", "You'll use Linux command line tools and Python to display multispectral imagery data and calculate a Normalized Burn Ratio spectral index. No prior Linux and minimal prior Python experience is required (I've provided all the code).\n" ], "metadata": { "id": "JwuvPnjcKmQi" } }, { "cell_type": "code", "source": [ "# Install dtrx, an extraction utility that simplifies user experience. \n", "# In a more real-life scenario you wouldn't necessarily do this step every time but only once. \n", "# In Google Colab, we get a fresh computer each time so need to reinstall each time. \n", "# https://pypi.org/project/dtrx/ \n", "!pip install dtrx" ], "metadata": { "id": "9C7bEH9BeBRh" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Download the lab data from Esri\n", "# We're only using the provided imagery, so will throw away the ArcGIS Pro project and other related files.\n", "# https://man7.org/linux/man-pages/man1/wget.1.html\n", "!wget -c https://www.arcgis.com/sharing/rest/content/items/1bc2bc1305b447fa939b937a8867114f/data -O MontanaFires" ], "metadata": { "id": "t2iu16W7f7VX" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Extract the compressed MontanaFires file into an uncompressed folder\n", "!dtrx MontanaFires" ], "metadata": { "id": "B6L6xKo0eBT-" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Move a nested subdirectory in the extracted MontanaFires.1 directory to a new location \n", "# https://man7.org/linux/man-pages/man1/mv.1.html\n", "!mv MontanaFires.1/commondata/raster_data raster_data" ], "metadata": { "id": "aqztpMJ8eBWs" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Clean up your workspace by removing unnecessary folders and files\n", "# https://man7.org/linux/man-pages/man1/rm.1.html\n", "!rm MontanaFires\n", "!rm -rf MontanaFires.1" ], "metadata": { "id": "7cDw8AsifhN9" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Display the contents of `raster_data` directory\n", "# https://man7.org/linux/man-pages/man1/ls.1.html\n", "!ls raster_data/" ], "metadata": { "id": "sdcHGpvDfhQj" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Install dependencies for rasterio then install rasterio\n", "!sudo apt-get install python-numpy gdal-bin libgdal-dev\n", "!pip install rasterio" ], "metadata": { "id": "rcTwMbtffhSv" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Check the version of rasterio installed via command line\n", "!rio --version" ], "metadata": { "id": "45JynjQGfhVF" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Use rasterio command line tool to display image metadata for 2014 pre-fire imagery\n", "!rio info raster_data/G_2014.tif --indent 2 --verbose" ], "metadata": { "id": "N7mUnZtEfhXO" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Do the same thing for the 2015 post-fire imagery but save to a JSON file\n", "!rio info raster_data/G_2015.tif --indent 2 --verbose > G_2015.json" ], "metadata": { "id": "jQpwerZCfhZ0" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Let's switch to Python now\n", "# Notice how we stop using ! since we're no longer trying to invoke Linux command line tools\n", "# We're importing the `json` library and opening the JSON file we just created with rio\n", "import json\n", "\n", "with open(\"G_2015.json\") as f:\n", " info_2015 = json.load(f)" ], "metadata": { "id": "1HX6glnPXaF0" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Let's display the JSON \"dictionary keys\"\n", "info_2015.keys()" ], "metadata": { "id": "RCiqzunSXix0" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# And we can display a specific key value\n", "info_2015[\"descriptions\"]" ], "metadata": { "id": "RFlNi7E4Xi0c" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Determine the CRS of 2015 imagery data here\n" ], "metadata": { "id": "Dr-oMQLwXi3C" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Question 1 (double-click this cell and write your answer below the question)\n", "### Part A\n", "What are the band names for the 2015 imagery data?\n", "* aka `info_2015[\"descriptions\"]`\n", "\n", "### Part B\n", "Using the blank code cell above, determine the CRS of the 2015 imagery data. You can copy+paste the preceeding code cell, but make sure to replace \"description\" with \"crs\"." ], "metadata": { "id": "mWX06_n7W_vF" } }, { "cell_type": "code", "source": [ "# Import `rasterio` and some support libraries for performing array computation (multispectral images are big arrays) and visualizing data\n", "import rasterio\n", "from matplotlib import pyplot as plt\n", "from rasterio.plot import show, show_hist\n", "import numpy as np" ], "metadata": { "id": "BK6gJRd2f7dz" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Load the data set into rasterio\n", "data_14 = rasterio.open(\"raster_data/G_2014.tif\")\n", "data_15 = rasterio.open(\"raster_data/G_2015.tif\")" ], "metadata": { "id": "nWg8F9taifcB" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Plot one of the bands\n", "plt.imshow(data_15.read(3), cmap=\"pink\")\n", "plt.show()" ], "metadata": { "id": "mYhk647aife5" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Apply super basic contrast stretching\n", "img=data_15.read(3)\n", "vmin, vmax = np.nanpercentile(img, (5,95)) # 5-95% stretch\n", "img_plt = plt.imshow(img, cmap='pink', vmin=vmin, vmax=vmax)\n", "plt.show()" ], "metadata": { "id": "kEu4joIPdMN4" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Convenience function to normalize data to 0-1 scale\n", "def normalize(array):\n", " \"\"\"Normalizes numpy arrays into scale 0.0 - 1.0\"\"\"\n", " array_min, array_max = array.min(), array.max()\n", " return ((array - array_min)/(array_max - array_min))" ], "metadata": { "id": "JMP_bS59js7H" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Load RGB data from 2015 into memory, normalize, then display\n", "red = data_15.read(3)\n", "green = data_15.read(2)\n", "blue = data_15.read(1)\n", "\n", "redn = normalize(red)\n", "greenn = normalize(green)\n", "bluen = normalize(blue)\n", "\n", "rgb = np.dstack((redn, greenn, bluen))\n", "plt.imshow(rgb)" ], "metadata": { "id": "v0lFA5rQelZS" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Calculate NBR for 2014 and display the result\n", "band_5_14 = data_14.read(5)\n", "band_7_14 = data_14.read(7)\n", "\n", "n5 = normalize(band_5_14)\n", "n7 = normalize(band_7_14)\n", "\n", "nbr_14 = (n5 - n7) / (n5 + n7)\n", "\n", "plt.imshow(nbr_14, cmap=\"gray\")\n", "plt.show()" ], "metadata": { "id": "4lvwgOXwjs-z" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Calculate NBR for 2015 and display the result\n", "band_5_15 = data_15.read(5)\n", "band_7_15 = data_15.read(7)\n", "\n", "n5 = normalize(band_5_15)\n", "n7 = normalize(band_7_15)\n", "\n", "nbr_15 = (n5 - n7) / (n5 + n7)\n", "\n", "\n", "\n", "plt.imshow(nbr_15, cmap=\"gray\")\n", "plt.show()" ], "metadata": { "id": "00hHWDOkjtCG" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Derive NBR difference then plot it\n", "nbr_delta = nbr_15 - nbr_14\n", "\n", "plt.imshow(nbr_delta, cmap=\"RdYlGn\")\n", "plt.show()" ], "metadata": { "id": "wBCljM3QjtFW" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Convenience function to apply standard deviation contrast stretch to an array\n", "import math\n", "# https://stackoverflow.com/questions/59813350/create-new-raster-tif-from-standard-deviation-stretched-bands-works-with-dst\n", "def std_stretch_data(data, n=2):\n", " \"\"\"Applies an n-standard deviation stretch to data.\"\"\"\n", "\n", " # Get the mean and n standard deviations.\n", " mean, d = data.mean(), data.std() * n\n", "\n", " # Calculate new min and max as integers. Make sure the min isn't\n", " # smaller than the real min value, and the max isn't larger than\n", " # the real max value.\n", " new_min = math.floor(max(mean - d, data.min()))\n", " new_max = math.ceil(min(mean + d, data.max()))\n", "\n", " # Convert any values smaller than new_min to new_min, and any\n", " # values larger than new_max to new_max.\n", " data = np.clip(data, new_min, new_max)\n", "\n", " # Scale the data.\n", " data = (data - data.min()) / (new_max - new_min)\n", " return data\n" ], "metadata": { "id": "NCoNW_WLlvuw" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Apply standard deviation contrast stretching to enhance NBR difference raster then display\n", "std_stretched = std_stretch_data(nbr_delta)\n", "plt.imshow(std_stretched, cmap=\"RdBu\")\n", "plt.show()" ], "metadata": { "id": "lXuyvu-Elvxm" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Display stretched data and a corresponding histogram\n", "fig, (axrgb, axhist) = plt.subplots(1, 2, figsize=(14,7))\n", "show(std_stretched, ax=axrgb, cmap=\"RdBu\")\n", "show_hist(std_stretched, bins=50, histtype='stepfilled', lw=0.0, stacked=False, alpha=0.3, ax=axhist)\n", "plt.show()" ], "metadata": { "id": "GPyfKIurjtIf" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Save NBR difference raster to file\n", "with rasterio.open(\n", " \"nbr_delta.tif\", \n", " \"w\", \n", " driver=\"GTiff\", \n", " height=data_15.shape[0], \n", " width=data_15.shape[1], \n", " dtype=red.dtype, \n", " count=1, \n", " crs='+proj=latlong', \n", " transform=data_15.transform\n", " ) as dst:\n", " dst.write(nbr_delta, 1)" ], "metadata": { "id": "mXzoNkvGlxMk" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Display directory contents\n", "!ls" ], "metadata": { "id": "CWCn6bUTlSJ8" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Question 2:\n", "Please use `rio` to display information about the `nbr_delta.tif` data set to your command line output. Use the empty code cell below this question." ], "metadata": { "id": "60q1G2OnUraF" } }, { "cell_type": "code", "source": [], "metadata": { "id": "mvkiOY2QUrrI" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Part 2\n", "### Assigned readings\n", "* [Mapping Burns and Natural Reforestation using Thematic Mapper Data by Lopez Garcia, M., and V. Caselles](https://www.researchgate.net/publication/246761915_Mapping_burns_and_natural_reforestation_using_Thematic_Mapper_data)\n", "* [Landscape Assessment: Remote Sensing of Severity, the Normalized Burn Ratio; and Ground Measure of Severity, the Composite Burn Index by Key, C. and N. Benson, N](https://www.researchgate.net/publication/241687027_Landscape_Assessment_Ground_measure_of_severity_the_Composite_Burn_Index_and_Remote_sensing_of_severity_the_Normalized_Burn_Ratio)\n", "\n", "## Your Deliverable\n", "### Part 1\n", "Save a copy of your Notebook in the GitHub repository you created during Lab 0. \n", "* Click File > Download > Download .ipynb\n", "* Navigate to your GitHub repo (created during Lab 0) and make sure that you're logged into GitHub\n", "* Click Add file > Upload files\n", "* Select the .ipynb file you saved\n", "* Paste the link to your uploaded file in the GitHub issue you opened last week in this repo: https://github.com/alex-pakalniskis/gisc606-spring2023\n", "\n", "\n", "\n", "### Part 2\n", "Please write a 2-4 sentence summary (paraphrase in your own words) about each article then compile your results into JSON format. Follow the steps below to submit your JSON data.\n", "> Note: The steps may contain links with additional explanations to provide you with missing context.\n", "\n", "1. Login to your GitHub account if you aren't already logged in\n", "1. Navigate to [https://github.com/alex-pakalniskis/gisc606-spring2023](https://github.com/alex-pakalniskis/gisc606-spring2023)\n", "1. [Create a branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-and-deleting-branches-within-your-repository#creating-a-branch-using-the-branch-dropdown) of `alex-pakalniskis/gisc606-spring2023` from `main` branch using the branch dropdown\n", " 1. Title your branch `lab 1: your csulb email without @csulb.edu`, i.e. mine would be `lab 1: alex.pakalniskis`\n", "1. Create a file called `your-email-without-@csulb.edu.json`, i.e. mine would be `alex-pakalniskis.json` in the `gisc606-spring/lab1` subdirectory.\n", "1. Copy the contents of Part 2's submission template (see below) into `your-email-without-@csulb.edu.json` then update with your information. \n", "1. [Merge your changes](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request#creating-the-pull-request) into `main` branch by opening a pull request\n", "1. [Request a review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) from @alex-pakalniskis\n", "\n", "#### Part 2 Submission Template\n", "\n", "``` json\n", "{\n", " \"your_name\": \"Alex Pakalniskis\",\n", " \"your_csulb_email\": \"alex.pakalniskis@csulb.edu\",\n", " \"articles\": [\n", " {\n", " \"title\": \"Mapping Burns and Natural Reforestation using Thematic Mapper Data\",\n", " \"authors\": \"Lopez Garcia, M., and V. Caselles\",\n", " \"link\": \"https://www.researchgate.net/publication/246761915_Mapping_burns_and_natural_reforestation_using_Thematic_Mapper_data\",\n", " \"your_summary\": \"In two to four sentences, paraphrase the content of the article (your own words).\"\n", " },\n", " {\n", " \"title\": \"Landscape Assessment: Remote Sensing of Severity, the Normalized Burn Ratio; and Ground Measure of Severity, the Composite Burn Index.\",\n", " \"authors\": \"Key, C. and N. Benson, N\",\n", " \"link\": \"https://www.researchgate.net/publication/241687027_Landscape_Assessment_Ground_measure_of_severity_the_Composite_Burn_Index_and_Remote_sensing_of_severity_the_Normalized_Burn_Ratio\",\n", " \"your_summary\": \"In two to four sentences, paraphrase the content of the article (your own words).\"\n", " }\n", " ]\n", "}\n", "```\n", "\n" ], "metadata": { "id": "gswRD-5DK7n1" } } ] }