717 lines
26 KiB
Plaintext
717 lines
26 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "dae926b2-fa06-4f00-b18d-7f74e92ce676",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Sentinel-1 RTC Gamma0 data <img align=\"right\" src=\"../../resources/csiro_easi_logo.png\">\n",
|
|
"\n",
|
|
"#### Index\n",
|
|
"- [Overview](#Overview)\n",
|
|
"- [Setup (imports, defaults, dask, odc)](#Setup)\n",
|
|
"- [Example query](#Example-query)\n",
|
|
"- [Product definition](#Product-definition)\n",
|
|
"- [Quality layer](#Quality-layer)\n",
|
|
"- [Create and apply a good quality pixel mask](#Create-and-apply-a-good-quality-pixel-mask)\n",
|
|
"- [Plot and browse the data](#Plot-and-browse-the-data)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "35b91cdb-2c5a-48c0-a0bf-7de2c2637bc5",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Overview\n",
|
|
"\n",
|
|
"This notebook demonstrates how to load and use Sentinel-1 Radiometric Terrain Corrected (RTC) Gamma0 data generated in EASI.\n",
|
|
"\n",
|
|
"These _analysis ready data_ S1 gamma-0 backscatter data are processed from Sentinel-1 GRD scenes using the [SNAP-10 Toolbox](https://step.esa.int/main/download/snap-download/) with Graph Processing Tool (GPT) xml receipes. See the [RTC Gamma0 product variants](#RTC-Gamma0-product-variants) section for further details.\n",
|
|
"\n",
|
|
"For most uses we recommend the smoothed 20 m product (`sentinel1_grd_gamma0_20m`).\n",
|
|
"We can process the 10 m products (`sentinel1_grd_gamma0_10m`, `sentinel1_grd_gamma0_10m_unsmooth`) and other variants on request."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "5e5120c6-e943-46a9-a4d8-c5b759586e62",
|
|
"metadata": {},
|
|
"source": [
|
|
"#### Using Sentinel-1 backscatter data\n",
|
|
"\n",
|
|
"An excellent introduction and overview to using SAR data is provided in the [CEOS Laymans SAR Interpretation Guide](https://ceos.org/ard/files/Laymans_SAR_Interpretation_Guide_3.0.pdf). This guide has also been converted to a set of set of Jupyter notebooks that you can download from https://github.com/AMA-Labs/cal-notebooks/tree/main/examples/SAR.\n",
|
|
"\n",
|
|
"Synthetic Aperture Radar operates in the microwave range of the electromagnetic spectrum as an active pulse sent by the satellite and scattered by features on the Earth's surface. The return signal from the surface is measured at the satellite in terms of the signal intensity, phase and polarisation compared to the signal that was sent.\n",
|
|
"\n",
|
|
"The SAR instrument on the Sentinel-1 satellites operate in the C-band at approximately 5.6 cm wavelength. This means that it can \"see\" objects of about this size and larger, and smaller objects are relatively transparent. This makes Sentinel-1 more sensitive to tree canopies, sparse and low biomass vegetation, and surface water (smooth and wind affected).\n",
|
|
"\n",
|
|
"> The SAR signal responds to the orientation and scattering from surface features of comparable size or larger than the wavelength.\n",
|
|
"> - A bright backscatter value typically means the surface was orientated perpendicular to the signal incidence angle and most of the signal was reflected back to the satellite (direct backscatter)\n",
|
|
"> - A dark backscatter value means most of the signal was reflected away from the satellite (forward scattering) and typically responds to a smooth surface (relative to the wavelength) such as calm water or bare soil\n",
|
|
"> - Rough surfaces (relative to the wavelength) result in diffuse scattering where some of the signal is returned to the satellite.\n",
|
|
"> - Complex surfaces may result in volume scattering (scattering within a tree canopy) or double-bounce scattering (perpendicular objects such as buildings and structures)\n",
|
|
"> - The relative backscatter values of co-polarisation (VV) and cross-polarisation (VH) measurements can provide information on the scattering characteristics of the surface features.\n",
|
|
"\n",
|
|
"Using Sentinel-1 backscatter data requires interpretation of the data for different surface features, including as these features change spatially or in time. It may also be necessary to carefully consider the incidence angle of the SAR signal relative to the surface features using the _incidence_angle_ band or the satellite direction metadata (descending = north to south; ascending = south to north)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "69f88789-9098-4669-b783-57d8982e9762",
|
|
"metadata": {
|
|
"jp-MarkdownHeadingCollapsed": true
|
|
},
|
|
"source": [
|
|
"#### Units and conversions\n",
|
|
"The `sentinel1_grd_gamma0_*` data are given in _Intensity_ (or backscatter _power_) units. Intensity can be converted to _decibel (dB)_ or _amplitude_, and vice-versa, with the following equations. Practical _Xarray_ examples are given below.\n",
|
|
"\n",
|
|
"Intensity to/from dB:\n",
|
|
"```\n",
|
|
" dB = 10 * log10(intensity) + K\n",
|
|
"intensity = 10^((dB-K)/10)\n",
|
|
"\n",
|
|
"where K is a calibration factor, which for Sentinel-1 is 0 dB.\n",
|
|
"```\n",
|
|
"\n",
|
|
"Intensity to/from Amplitude:\n",
|
|
"```\n",
|
|
"intensity = amplitude * amplitude\n",
|
|
"amplitude = sqrt(intensity)\n",
|
|
"```\n",
|
|
"\n",
|
|
"Additional reference: https://forum.step.esa.int/t/what-stage-of-processing-requires-the-linear-to-from-db-command"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "dd5c0ff4-e1a2-4adf-98d1-1ca5b9f3126d",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Set up"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "4cb7c012-719e-4f13-a206-36f271c6a91c",
|
|
"metadata": {},
|
|
"source": [
|
|
"#### Imports"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "1c848c17-2e7c-4b68-9682-b8994644f521",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Common imports and settings\n",
|
|
"import os, sys, re\n",
|
|
"from pathlib import Path\n",
|
|
"from IPython.display import Markdown\n",
|
|
"import pandas as pd\n",
|
|
"pd.set_option(\"display.max_rows\", None)\n",
|
|
"import xarray as xr\n",
|
|
"import numpy as np\n",
|
|
"\n",
|
|
"# Datacube\n",
|
|
"import datacube\n",
|
|
"from datacube.utils.aws import configure_s3_access\n",
|
|
"import odc.geo.xr # https://github.com/opendatacube/odc-geo\n",
|
|
"from datacube.utils import masking # https://github.com/opendatacube/datacube-core/blob/develop/datacube/utils/masking.py\n",
|
|
"from dea_tools.plotting import display_map # https://github.com/GeoscienceAustralia/dea-notebooks/tree/develop/Tools\n",
|
|
"\n",
|
|
"# Basic plots\n",
|
|
"%matplotlib inline\n",
|
|
"# import matplotlib.pyplot as plt\n",
|
|
"# plt.rcParams['figure.figsize'] = [12, 8]\n",
|
|
"\n",
|
|
"# Holoviews\n",
|
|
"# https://holoviz.org/tutorial/Composing_Plots.html\n",
|
|
"# https://holoviews.org/user_guide/Composing_Elements.html\n",
|
|
"import hvplot.xarray\n",
|
|
"import panel as pn"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "bde8313b-887d-4529-bd57-89f740ff6380",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# EASI defaults\n",
|
|
"# These are convenience functions so that the notebooks in this repository work in all EASI deployments\n",
|
|
"\n",
|
|
"# The `git.Repo()` part returns the local directory that easi-notebooks has been cloned into\n",
|
|
"# If using the `easi-tools` functions from another path, replace `repo` with your local path to `easi-notebooks` directory\n",
|
|
"try:\n",
|
|
" import git\n",
|
|
" repo = git.Repo('.', search_parent_directories=True).working_tree_dir # Path to this cloned local directory\n",
|
|
"except (ImportError, git.InvalidGitRepositoryError):\n",
|
|
" repo = Path.home() / 'easi-notebooks' # Reasonable default\n",
|
|
" if not repo.is_dir():\n",
|
|
" raise RuntimeError('To use `easi-tools` please provide the local path to `https://github.com/csiro-easi/easi-notebooks`')\n",
|
|
"if repo not in sys.path:\n",
|
|
" sys.path.append(str(repo)) # Add the local path to `easi-notebooks` to python\n",
|
|
"\n",
|
|
"from easi_tools import EasiDefaults\n",
|
|
"from easi_tools import initialize_dask, xarray_object_size, heading"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "d2916064-8680-44a2-b537-0ecdd0b3e05b",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"source": [
|
|
"#### EASI defaults\n",
|
|
"\n",
|
|
"These default values are configured for each EASI instance. They help us to use the same training notebooks in each EASI instance. You may find some of the functions convenient for your work or you can easily override the values in your copy of this notebook."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "44d6583e-d2f5-4587-b035-6814d1313740",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"easi = EasiDefaults()\n",
|
|
"\n",
|
|
"family = 'sentinel-1'\n",
|
|
"product = easi.product(family) # 'sentinel1_grd_gamma0_20m'\n",
|
|
"display(Markdown(f'Default {family} product for \"{easi.name}\": [{product}]({easi.explorer}/products/{product})'))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "bb66366a-f2d5-461a-a34f-7eed5a5296fb",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"source": [
|
|
"#### Dask cluster\n",
|
|
"\n",
|
|
"Using a local _Dask_ cluster is a good habit to get into. It can simplify loading and processing of data in many cases, and it provides a dashboard that shows the loading/processing progress.\n",
|
|
"\n",
|
|
"To learn more about _Dask_ see the set of [dask notebooks](https://github.com/csiro-easi/easi-notebooks/tree/main/html#dask-tutorials)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "02f9e778-755c-4ff2-a99f-ad5c48438665",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Local cluster\n",
|
|
"cluster, client = initialize_dask(workers=4)\n",
|
|
"display(client)\n",
|
|
"\n",
|
|
"# Or use Dask Gateway - this may take a few minutes\n",
|
|
"# cluster, client = initialize_dask(use_gateway=True, workers=4)\n",
|
|
"# display(client)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "a746582e-be79-448c-a9c4-7a2ae2d703ce",
|
|
"metadata": {},
|
|
"source": [
|
|
"#### ODC database\n",
|
|
"\n",
|
|
"Connect to the ODC database. Configure the environment and low-level tools to read from AWS buckets."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "0e2232ee-f0a5-43fc-a6c6-d422b437cd69",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"dc = datacube.Datacube()\n",
|
|
"\n",
|
|
"# Access AWS \"requester-pays\" buckets\n",
|
|
"# This is necessary for reading data from most third-party AWS S3 buckets such as for Landsat and Sentinel-2\n",
|
|
"configure_s3_access(aws_unsigned=False, requester_pays=True, client=client);"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "1801db2b-e24e-45fa-85d4-e153ebcc8f6f",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Example query\n",
|
|
"\n",
|
|
"Change any of the parameters in the `query` object below to adjust the location, time, projection, or spatial resolution of the returned datasets.\n",
|
|
"\n",
|
|
"Use the Explorer interface to check the temporal and spatial coverage for each product."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "2a04d875-20d4-4d72-a16d-e76b7f48161c",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Explorer link\n",
|
|
"display(Markdown(f'See: {easi.explorer}/products/{product}'))\n",
|
|
"\n",
|
|
"# EASI defaults\n",
|
|
"display(Markdown(f'#### Location: {easi.location}'))\n",
|
|
"latitude_range = easi.latitude\n",
|
|
"longitude_range = easi.longitude\n",
|
|
"time_range = easi.time\n",
|
|
"\n",
|
|
"# Or set your own latitude / longitude\n",
|
|
"# Australia GWW\n",
|
|
"# latitude_range = (-33, -32.6)\n",
|
|
"# longitude_range = (120.5, 121)\n",
|
|
"# time_range = ('2020-01-01', '2020-01-31')\n",
|
|
"\n",
|
|
"query = {\n",
|
|
" 'product': product, # Product name\n",
|
|
" 'x': longitude_range, # \"x\" axis bounds\n",
|
|
" 'y': latitude_range, # \"y\" axis bounds\n",
|
|
" 'time': time_range, # Any parsable date strings\n",
|
|
"}\n",
|
|
"\n",
|
|
"# Convenience function to display the selected area of interest\n",
|
|
"display_map(longitude_range, latitude_range)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "9fefeef8-0c21-49c2-b86f-c68e5178a7ac",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"source": [
|
|
"## Load data"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "2f4c724f-636a-4833-a165-adb053acaa07",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Target xarray parameters\n",
|
|
"# - Select a set of measurements to load\n",
|
|
"# - output CRS and resolution\n",
|
|
"# - Usually we group input scenes on the same day to a single time layer (groupby)\n",
|
|
"# - Select a reasonable Dask chunk size (this should be adjusted depending on the\n",
|
|
"# spatial and resolution parameters you choose\n",
|
|
"load_params = {\n",
|
|
" 'group_by': 'solar_day', # Scene grouping\n",
|
|
" 'dask_chunks': {'latitude':2048, 'longitude':2048}, # Dask chunks\n",
|
|
"}\n",
|
|
"\n",
|
|
"# Load data\n",
|
|
"data = dc.load(**(query | load_params))\n",
|
|
"display(xarray_object_size(data))\n",
|
|
"display(data)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "be474a1a-b5f1-40e0-b35a-5b9c9f1a15bd",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# When happy with the shape and size of chunks, persist() the result\n",
|
|
"data = data.persist()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "e3d86506-39ba-4f02-ac6e-2d1d27119668",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Conversion and helper functions"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "99eb8a2c-fe00-44b7-bd39-250ab3d323cc",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# These functions use numpy, which should be satisfactory for most notebooks.\n",
|
|
"# Calculations for larger or more complex arrays may require Xarray's \"ufunc\" capability.\n",
|
|
"# https://docs.xarray.dev/en/stable/examples/apply_ufunc_vectorize_1d.html\n",
|
|
"#\n",
|
|
"# Apply numpy.log10 to the DataArray\n",
|
|
"# log10_data = xr.apply_ufunc(np.log10, data)\n",
|
|
"\n",
|
|
"def intensity_to_db(da: 'xr.DataArray', K=0):\n",
|
|
" \"\"\"Return an array converted to dB values\"\"\"\n",
|
|
" xx = da.where(da > 0, np.nan) # Set values <= 0 to NaN\n",
|
|
" xx = 10*np.log10(xx) + K\n",
|
|
" xx.attrs.update({\"units\": \"dB\"})\n",
|
|
" return xx\n",
|
|
"\n",
|
|
"def db_to_intensity(da: 'xr.DataArray', K=0):\n",
|
|
" \"\"\"Return an array converted to intensity values\"\"\"\n",
|
|
" xx = np.power(10, (da-K)/10.0)\n",
|
|
" xx.attrs.update({\"units\": \"intensity\"})\n",
|
|
" return xx\n",
|
|
"\n",
|
|
"def select_valid_time_layers(ds: 'xarray', percent: float = 5):\n",
|
|
" \"\"\"Select time layers that have at least a given percentage of valid data (e.g., >=5%)\n",
|
|
"\n",
|
|
" Example usage:\n",
|
|
" selected = select_valid_time_layers(ds, percent=5)\n",
|
|
" filtered == ds.sel(time=selected)\n",
|
|
" \"\"\"\n",
|
|
" spatial_dims = ds.odc.spatial_dims\n",
|
|
" return ds.count(dim=spatial_dims).values / (ds.sizes[spatial_dims[0]]*ds.sizes[spatial_dims[1]]) >= (percent/100.0)\n",
|
|
"\n",
|
|
"# Examples to check that the intensity to/from dB functions work as expected\n",
|
|
"# xx = data.vv.isel(time=0,latitude=np.arange(0, 5),longitude=np.arange(0, 5))\n",
|
|
"# xx[0] = 0\n",
|
|
"# xx[1] = -0.001\n",
|
|
"# display(xx.values)\n",
|
|
"# yy = intensity_to_db(xx)\n",
|
|
"# display(yy.values)\n",
|
|
"# zz = db_to_intensity(yy)\n",
|
|
"# display(zz.values)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "57bcf24c-99bb-4986-9afd-3a6e63b9a8cc",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# hvPlot convenience functions\n",
|
|
"def make_image(ds: 'xarray', frame_height=300, **kwargs):\n",
|
|
" \"\"\"Return a Holoviews DynamicMap (image) object that can be displayed or combined\"\"\"\n",
|
|
" spatial_dims = ds.odc.spatial_dims\n",
|
|
" defaults = dict(\n",
|
|
" cmap=\"Greys_r\",\n",
|
|
" y = spatial_dims[0], x = spatial_dims[1],\n",
|
|
" groupby = 'time',\n",
|
|
" rasterize = True,\n",
|
|
" geo = True,\n",
|
|
" robust = True,\n",
|
|
" frame_height = frame_height,\n",
|
|
" clabel = ds.attrs.get('units', None),\n",
|
|
" )\n",
|
|
" defaults.update(**kwargs)\n",
|
|
" return ds.hvplot.image(**defaults)\n",
|
|
"\n",
|
|
"def rgb_image(ds: 'xarray', frame_height=300, **kwargs):\n",
|
|
" \"\"\"Return a Holoviews DynamicMap (RBG image) object that can be displayed or combined\"\"\"\n",
|
|
" spatial_dims = ds.odc.spatial_dims\n",
|
|
" defaults = dict(\n",
|
|
" bands='band',\n",
|
|
" y = spatial_dims[0], x = spatial_dims[1],\n",
|
|
" groupby = 'time',\n",
|
|
" rasterize = True,\n",
|
|
" geo = True,\n",
|
|
" robust = True,\n",
|
|
" frame_height = frame_height,\n",
|
|
" )\n",
|
|
" defaults.update(**kwargs)\n",
|
|
" return ds.hvplot.rgb(**defaults)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "91c113df-101a-49d2-911e-5a83e77ad14a",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Optional time layer filter\n",
|
|
"\n",
|
|
"selected = select_valid_time_layers(data.vv, 10) # Exclude time layers with less than 10% valid data\n",
|
|
"data = data.sel(time=selected).persist()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "961295e1-675b-4dda-92ba-c44b15506de8",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Add db values to the dataset\n",
|
|
"\n",
|
|
"data['vh_db'] = intensity_to_db(data.vh).persist()\n",
|
|
"data['vv_db'] = intensity_to_db(data.vv).persist()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "5fc56259-2e20-48f8-bab5-13ef56993bf9",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"source": [
|
|
"## Plot the data\n",
|
|
"\n",
|
|
"> Note the different data ranges for plotting (`clim`) between `vv`, `vh`, _intensity_ and _dB_.\n",
|
|
"\n",
|
|
"- Stronger co-polarisation (VV) indicates direct backscatter while stronger cross-polarisation (VH) may indicate a complex surface or volume scattering.\n",
|
|
"- Intensity data are linear-scaled so can tend to disciminate across a range of backscatter returns.\n",
|
|
"- Decibel data are log-scaled so can tend to discriminate high and low backscatter returns."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "a7630dec-2f78-41b5-b1b4-0eb32206c120",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# VV and VH (intensity and dB) and Angle hvPlots\n",
|
|
"\n",
|
|
"vv_plot = make_image(data.vv, clim=(0, 0.5), title='VV (intensity)')\n",
|
|
"vh_plot = make_image(data.vh, clim=(0, 0.1), title='VH (intensity)')\n",
|
|
"ia_plot = make_image(data.angle, title='Incidence angle')\n",
|
|
"\n",
|
|
"vv_db_plot = make_image(data.vv_db, clim=(-30, -3), title='VV (dB)')\n",
|
|
"vh_db_plot = make_image(data.vh_db, clim=(-30, -1), title='VH (dB)')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "6531e1f9-d8c0-45bd-aeaa-f7e1c95bbc7d",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Arrange plots with linked axes and time slider. Adjust browser window width if required.\n",
|
|
"\n",
|
|
"layout = pn.panel(\n",
|
|
" (vv_plot + vh_plot + ia_plot + vv_db_plot + vh_db_plot).cols(3),\n",
|
|
" widget_location='top',\n",
|
|
")\n",
|
|
"print(layout) # Helpful to see how the hvplot is constructed\n",
|
|
"layout"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "77fbc6c5-4e0e-40c2-8c8f-82757ae0909b",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Plot a histogram of the dB data\n",
|
|
"\n",
|
|
"A histogram can help separate water from land features. Here we show a histogram for the _VH (db)_ channel for all time layers.\n",
|
|
"- If the histogram shows two clear peaks then a value between the peaks could be used as a water / land threshold\n",
|
|
"- If not then try selected time layers, a different area of interest, or other channels or combinations."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "2a122cfe-f2bd-4b63-983c-c625e6849f5e",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# vals, bins, hist_plot = data.vh_db.plot.hist(bins=np.arange(-30, 0, 1), color='red') # Matplotlib\n",
|
|
"hist_plot = data.vh_db.hvplot.hist(bins=np.arange(-30, 0, 1), color='red', title='Combined times', height=400) # hvPlot\n",
|
|
"\n",
|
|
"print(hist_plot) # Helpful to see how the hvplot is constructed\n",
|
|
"hist_plot"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "1bbb8282-3b77-43ce-a774-88b031fb94a3",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Make an RGB image\n",
|
|
"\n",
|
|
"A common strategy to create an RGB colour composite image for SAR data from two channels is to use the ratio of the channels to represent the third colour. Here we choose\n",
|
|
"\n",
|
|
"To create an RGB colour composite image we can use the ratio of VH and VV to represent a third channel. Here we choose\n",
|
|
"- Red = VH ... complex scattering\n",
|
|
"- Green = VV ... direct scattering\n",
|
|
"- Blue = VH/VV ... relatively more complex than direct"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "bd2da45d-a1db-4d75-b725-706d0fa252c9",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Add the vh/vv band to represent 'blue'\n",
|
|
"data['vh_vv'] = data.vh / data.vv\n",
|
|
"\n",
|
|
"# Scale the measurements by their median so they have a similar range for visualization\n",
|
|
"spatial_dims = data.odc.spatial_dims\n",
|
|
"data['vh_scaled'] = data.vh / data.vh.median(dim=spatial_dims).persist()\n",
|
|
"data['vv_scaled'] = data.vv / data.vv.median(dim=spatial_dims).persist()\n",
|
|
"data['vh_vv_scaled'] = data.vh_vv / data.vh_vv.median(dim=spatial_dims).persist()\n",
|
|
"\n",
|
|
"# odc-geo function\n",
|
|
"rgb_data = data.odc.to_rgba(bands=['vh_scaled','vv_scaled','vh_vv_scaled'], vmin=0, vmax=2)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "a8834880-fb2d-42ec-bc01-f1b37ac97038",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# As subplots\n",
|
|
"# rgb_plot = rgb_image(\n",
|
|
"# rgb_data,\n",
|
|
"# ).layout().cols(4)\n",
|
|
"\n",
|
|
"# As movie. Select \"loop\" and use \"-\" button to adjust the speed to allow for rendering. After a few cycles the images should play reasonably well.\n",
|
|
"rgb_plot = rgb_image(\n",
|
|
" rgb_data,\n",
|
|
" precompute = True,\n",
|
|
" widget_type='scrubber', widget_location='bottom',\n",
|
|
" frame_height = 500,\n",
|
|
")\n",
|
|
"\n",
|
|
"print(rgb_plot) # Helpful to see how the hvplot is constructed\n",
|
|
"rgb_plot"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "fd1df15e-31e8-4ad8-9940-e28bd6822441",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Export to Geotiffs\n",
|
|
"\n",
|
|
"Recall that to write a dask dataset to a file requires the dataset to be `.compute()`ed. This may result in a large memory increase on your JupyterLab node if the area of interest is large enough, which in turn may kill the kernel. If so then skip this step, choose a smaller area or find a different way to export data."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "1347d080-74d5-491a-bb45-45252fc385ea",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Make a directory to save outputs to\n",
|
|
"target = Path.home() / 'output'\n",
|
|
"if not target.exists(): target.mkdir()\n",
|
|
"\n",
|
|
"def write_band(ds, varname):\n",
|
|
" \"\"\"Write the variable name of the xarray dataset to a Geotiff file for each time layer\"\"\"\n",
|
|
" for i in range(len(ds.time)):\n",
|
|
" date = ds[varname].isel(time=i).time.dt.strftime('%Y%m%d').data\n",
|
|
" fname = f'{target}/example_sentinel-1_{varname}_{date}.tif'\n",
|
|
" single = ds[varname].isel(time=i).compute()\n",
|
|
" single.odc.write_cog(\n",
|
|
" fname=fname,\n",
|
|
" overwrite=True,\n",
|
|
" )\n",
|
|
" print(f'Wrote: {fname}')\n",
|
|
" \n",
|
|
"write_band(data, 'vv')\n",
|
|
"write_band(data, 'vh')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "7ed2add5-12ad-4327-801b-9e60b3baf031",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Appendix"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "776027ff-c4ef-4d34-962f-b2a33ba09a60",
|
|
"metadata": {},
|
|
"source": [
|
|
"### RTC Gamma0 product variants\n",
|
|
"\n",
|
|
"The set of products listed here differ by the selection and configuration of processing steps and options. The set of SNAP operators conform with [CEOS Analysis Ready Data](https://ceos.org/ard/) specifications for _normalised radar backscatter_.\n",
|
|
"\n",
|
|
"S1 gamma-0 backscatter data are processed from Sentinel-1 GRD scenes using the [SNAP-10 Toolbox](https://step.esa.int/main/download/snap-download/) with Graph Processing Tool (GPT) xml receipes (available on request).\n",
|
|
"\n",
|
|
"| | sentinel1_grd_gamma0_20m | sentinel1_grd_gamma0_10m | sentinel1_grd_gamma0_10m_unsmooth |\n",
|
|
"|--|--|--|--|\n",
|
|
"| **DEM** | | | |\n",
|
|
"| copernicus_dem_30 | Y | Y | Y |\n",
|
|
"| Scene to DEM extent multiplier| 3.0 | 3.0 | 3.0 |\n",
|
|
"| **SNAP operator** | | | |\n",
|
|
"| Apply-Orbit-File | Y | Y | Y |\n",
|
|
"| ThermalNoiseRemoval | Y | Y | Y |\n",
|
|
"| Remove-GRD-Border-Noise | Y | Y | Y |\n",
|
|
"| Calibration | Y | Y | Y |\n",
|
|
"| SetNoDataValue | Y | Y | Y |\n",
|
|
"| Terrain-Flattening | Y | Y | Y |\n",
|
|
"| Speckle-Filter | Y | Y | N |\n",
|
|
"| Multilook | Y | Y | N |\n",
|
|
"| Terrain-Correction | Y | Y | Y |\n",
|
|
"| **Output** | | | |\n",
|
|
"| Projection | WGS84, epsg:4326 | WGS84, epsg:4326 | WGS84, epsg:4326 |\n",
|
|
"| Pixel resolution | 20 m | 10 m | 10 m |\n",
|
|
"| Pixel alignment</br>_PixelIsArea = top-left_ | PixelIsArea | PixelIsArea | PixelIsArea |"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "ef7c0753-3ba2-4736-acf0-3f52ca36bd17",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "folium",
|
|
"language": "python",
|
|
"name": "folium"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.12.7"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|