Repository: ASFHyP3/asf-tools (language: python)

Issue:
Fix release workflow, fix spelling
Some pre-release fixes for #90 


Discussion / hints:

Fix patch:
diff --git a/.github/queries/asssociated-pr.query.yml b/.github/queries/associated-pr.query.yml
similarity index 100%
rename from .github/queries/asssociated-pr.query.yml
rename to .github/queries/associated-pr.query.yml
diff --git a/.github/workflows/distribute.yml b/.github/workflows/distribute.yml
new file mode 100644
index 00000000..e4394109
--- /dev/null
+++ b/.github/workflows/distribute.yml
@@ -0,0 +1,55 @@
+name: Distribute to PyPI
+
+on:
+  push:
+    tags:
+      - v*
+
+jobs:
+  distribute:
+    runs-on: ubuntu-latest
+    outputs:
+      SDIST_VERSION: ${{ steps.build.outputs.version }}
+    steps:
+      - uses: actions/checkout@v2
+
+      - uses: actions/setup-python@v1
+        with:
+          python-version: 3.8
+
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          python -m pip install --upgrade setuptools wheel "setuptools-scm[toml]"
+
+      - name: Build distribuion
+        id: build
+        run: |
+          git fetch origin +refs/tags/*:refs/tags/*
+          export SDIST_VERSION=$(python setup.py --version)
+          echo "::set-output name=version::${SDIST_VERSION}"
+          python setup.py sdist bdist_wheel
+
+      - name: upload to PyPI.org
+        uses: pypa/gh-action-pypi-publish@master
+        with:
+          user: __token__
+          password: ${{ secrets.TOOLS_PYPI_PAK }}
+
+  verify-distribution:
+    runs-on: ubuntu-latest
+    needs: distribute
+    steps:
+      - uses: actions/checkout@v2
+
+      - uses: conda-incubator/setup-miniconda@v2
+        with:
+          mamba-version: "*"
+          python-version: 3.8
+          activate-environment: asf-tools
+          environment-file: environment.yml
+
+      - name: Pip install ${{ needs.distribute.outputs.SDIST_VERSION }}
+        shell: bash -l {0}
+        run: |
+          python -m pip install asf_tools==${{ needs.distribute.outputs.SDIST_VERSION }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 5fa6297a..d392d4e7 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -5,7 +5,6 @@ on:
     tags:
       - v*
 
-
 jobs:
   finish:
     runs-on: ubuntu-latest
@@ -18,6 +17,7 @@ jobs:
       - name: Build release asset names
         id: release_assets
         run: |
+          echo ::set-output name=version_tag::${GITHUB_REF#refs/tags/}
           echo ::set-output name=ARC_FILE::ASF_ArcGIS_Toolbox_$(echo ${GITHUB_REF#refs/tags/}).zip
 
       - name: Zip ArcGIS Toolbox
@@ -26,7 +26,7 @@ jobs:
           args: zip -qq -j -r ${{ steps.release_assets.outputs.ARC_FILE }} ArcGIS-toolbox
 
       - name: Create Release
-        uses: docker://antonyurchenko/git-release:latest
+        uses: docker://antonyurchenko/git-release:v3.5.0
         env:
           GITHUB_TOKEN: ${{ secrets.TOOLS_BOT_PAK }}
           ALLOW_TAG_PREFIX: "true"
@@ -35,13 +35,14 @@ jobs:
           args: |
             ${{ steps.release_assets.outputs.ARC_FILE }}
 
-      - name: Trigger rebuild of HyP3 docs
+      - name: Bump ASF Tools version in HyP3 docs
         uses: benc-uk/workflow-dispatch@v1.1
         with:
-          workflow: Deploy to Github.io
+          workflow: Update ASF Tools version
           token: ${{ secrets.TOOLS_BOT_PAK }}
-          repo: ASFHyP3/ASFHyP3
+          repo: ASFHyP3/hyp3-docs
           ref: main
+          inputs: '{"asf_tools_version": "${{ steps.release_assets.outputs.version_tag }}"}'
 
       - name: Attempt fast-forward develop from main
         run: |
diff --git a/.trufflehog.txt b/.trufflehog.txt
index 16879013..f424c2c3 100644
--- a/.trufflehog.txt
+++ b/.trufflehog.txt
@@ -1,2 +1,3 @@
 .*gitleaks.toml$
 .*\.pyt\.xml$
+.*water-extent-map-on-demand.ipynb$
diff --git a/ArcGIS-toolbox/ASF_Tools.LogDiff.pyt.xml b/ArcGIS-toolbox/ASF_Tools.LogDiff.pyt.xml
index 0fbc96a1..95cf68ca 100644
--- a/ArcGIS-toolbox/ASF_Tools.LogDiff.pyt.xml
+++ b/ArcGIS-toolbox/ASF_Tools.LogDiff.pyt.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<metadata xml:lang="en"><Esri><CreaDate>20191114</CreaDate><CreaTime>15052200</CreaTime><ArcGISFormat>1.0</ArcGISFormat><ArcGISstyle>North American Profile of ISO19115 2003</ArcGISstyle><SyncOnce>TRUE</SyncOnce><ModDate>20201002</ModDate><ModTime>14504300</ModTime><scaleRange><minScale>150000000</minScale><maxScale>5000</maxScale></scaleRange><ArcGISProfile>NAP</ArcGISProfile><DataProperties><itemProps><imsContentType export="False"/></itemProps></DataProperties></Esri><tool name="LogDiff" displayname="Calculate Log Difference" toolboxalias="ASF Tools" xmlns=""><arcToolboxHelpPath>c:\program files\arcgis\pro\Resources\Help\gp</arcToolboxHelpPath><parameters><param name="date2" displayname="Comparison raster (i.e. most recent SAR acquisition)" type="Required" direction="Input" datatype="Raster Dataset" expression="date2"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Navigate to the raster that will be compared to the reference raster, or enter the full path by typing or pasting. You can also drag and drop a raster from the Catalog window.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;For comparisons through time, select the most recent raster as the comparison raster.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;ArcGIS Pro tip: Select the raster file as the input, not Band_1. While Pro allows you to navigate to the band level, it is not a valid input for this tool.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="date1" displayname="Reference raster (i.e. oldest SAR acquisition)" type="Required" direction="Input" datatype="Raster Dataset" expression="date1"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Navigate to the reference raster to be compared against, or enter the full path by typing or pasting. You can also drag and drop a raster from the Catalog window.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;For comparisons through time, select the earlier acquisition of the two images to be the reference raster.&lt;/SPAN&gt;&lt;/P&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;ArcGIS Pro tip: Select the raster file as the input, not Band_1. While Pro allows you to navigate to the band level, it is not a valid input for this tool.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;P&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="outdir" displayname="Destination folder for output file" type="Required" direction="Input" datatype="Workspace" expression="outdir"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Navigate to the destination directory where the output will be saved.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The default value is the directory where the most recent raster (the later acquisition date) is located, but you can navigate to a different directory or amend the path as desired.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input reference raster after the default directory populates, the output directory parameter does not automatically update. Delete the outdated filename and press tab or click away from the field to reset the output directory to reflect the new input reference raster.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="outname" displayname="File name for output log difference file (including valid raster file extension)" type="Required" direction="Input" datatype="String" expression="outname"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Enter the filename for the output difference raster. You must include a valid raster file extension (such as .tif) if you are not saving the raster to a geodatabase.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The default value is comprised of the base filename of the earlier acquisition followed by the base filename of the later acquisition, tagged with _LogDiff, and including a .tif file extension.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input rasters after the default filename populates, the output filename parameter does not automatically update. Delete the outdated filename and press tab or click away from the field to reset the filename to reflect the new input rasters.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="out_yn" displayname="Add output to map" type="Required" direction="Input" datatype="Boolean" expression="out_yn"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Select this option to automatically add the output raster to the map. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;This option is selected by default. Remove the check if you do not want the output raster added to the map.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param></parameters><summary>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This tool calculates the log difference between two rasters. It was designed to work with a 2-point time series of &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Radiometric Terrain Corrected (RTC) SAR products&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; in amplitude scale, but can be used to look for differences between any two single-band rasters. Note that the threshold values expected for significant change will be different if the input rasters are in power or amplitude, and the input rasters must both be in the same scale. Avoid using this approach with datasets in dB scale, which is already in a log scale.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;The output directory and filename are automatically populated using the input raster parameters. You can amend the auto-populated outputs or set them to something completely different if desired. Note that if you change your input files, the default values will not automatically change. You can reset the auto-populated outputs based on the new parameters by deleting the default string and pressing tab or clicking away from the field.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</summary><usage>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This tool calculates the log difference between two rasters. It was designed to calculate differences through time between two &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Radiometric Terrain Corrected (RTC) SAR products&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; in amplitude scale, but can be used to look for differences between any two single-band rasters. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Negative values indicate a decrease in pixel value. For RTC images, these pixels have lower detected radar backscatter on the second date than the first. By default, the pixels of strongest negative change are displayed in black.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Positive values indicate an increase in pixel value. For RTC images, these pixels have higher detected radar backscatter on the second date than the first. By default, the pixels of strongest positive change are displayed in white.&lt;/SPAN&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 20;"&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Consider classifying the output image to highlight areas of the most extreme change. In the Symbology tab of the Raster Properties, select Classified and compute the histogram when prompted. Select the number of classes you want to delineate and click the Classify button to adjust the break points in the range of values. Note that values close to 0 (either positive or negative) underwent little change.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</usage><scriptExamples><scriptExample><title>ASF_Tools.pyt LogDiff</title><para>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This is the code for the LogDiff tool class within the ASF_Tools.pyt script.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</para><code>class LogDiff(object):
+<metadata xml:lang="en"><Esri><CreaDate>20191114</CreaDate><CreaTime>15052200</CreaTime><ArcGISFormat>1.0</ArcGISFormat><ArcGISstyle>North American Profile of ISO19115 2003</ArcGISstyle><SyncOnce>TRUE</SyncOnce><ModDate>20201002</ModDate><ModTime>14504300</ModTime><scaleRange><minScale>150000000</minScale><maxScale>5000</maxScale></scaleRange><ArcGISProfile>NAP</ArcGISProfile><DataProperties><itemProps><imsContentType export="False"/></itemProps></DataProperties></Esri><tool name="LogDiff" displayname="Calculate Log Difference" toolboxalias="ASF Tools" xmlns=""><arcToolboxHelpPath>c:\program files\arcgis\pro\Resources\Help\gp</arcToolboxHelpPath><parameters><param name="date2" displayname="Comparison raster (i.e. most recent SAR acquisition)" type="Required" direction="Input" datatype="Raster Dataset" expression="date2"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Navigate to the raster that will be compared to the reference raster, or enter the full path by typing or pasting. You can also drag and drop a raster from the Catalog window.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;For comparisons through time, select the most recent raster as the comparison raster.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;ArcGIS Pro tip: Select the raster file as the input, not Band_1. While Pro allows you to navigate to the band level, it is not a valid input for this tool.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="date1" displayname="Reference raster (i.e. oldest SAR acquisition)" type="Required" direction="Input" datatype="Raster Dataset" expression="date1"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Navigate to the reference raster to be compared against, or enter the full path by typing or pasting. You can also drag and drop a raster from the Catalog window.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;For comparisons through time, select the earlier acquisition of the two images to be the reference raster.&lt;/SPAN&gt;&lt;/P&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;ArcGIS Pro tip: Select the raster file as the input, not Band_1. While Pro allows you to navigate to the band level, it is not a valid input for this tool.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;P&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="outdir" displayname="Destination folder for output file" type="Required" direction="Input" datatype="Workspace" expression="outdir"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Navigate to the destination directory where the output will be saved.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The default value is the directory where the most recent raster (the later acquisition date) is located, but you can navigate to a different directory or amend the path as desired.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input reference raster after the default directory populates, the output directory parameter does not automatically update. Delete the outdated filename and press tab or click away from the field to reset the output directory to reflect the new input reference raster.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="outname" displayname="File name for output log difference file (including valid raster file extension)" type="Required" direction="Input" datatype="String" expression="outname"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Enter the filename for the output difference raster. You must include a valid raster file extension (such as .tif) if you are not saving the raster to a geodatabase.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The default value is comprised of the base filename of the earlier acquisition followed by the base filename of the later acquisition, tagged with _LogDiff, and including a .tif file extension.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input rasters after the default filename populates, the output filename parameter does not automatically update. Delete the outdated filename and press tab or click away from the field to reset the filename to reflect the new input rasters.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="out_yn" displayname="Add output to map" type="Required" direction="Input" datatype="Boolean" expression="out_yn"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Select this option to automatically add the output raster to the map. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;This option is selected by default. Remove the check if you do not want the output raster added to the map.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param></parameters><summary>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This tool calculates the log difference between two rasters. It was designed to work with a 2-point time series of &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Radiometric Terrain Corrected (RTC) SAR products&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; in amplitude scale, but can be used to look for differences between any two single-band rasters. Note that the threshold values expected for significant change will be different if the input rasters are in power or amplitude, and the input rasters must both be in the same scale. Avoid using this approach with datasets in dB scale, which is already in a log scale.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;The output directory and filename are automatically populated using the input raster parameters. You can amend the auto-populated outputs or set them to something completely different if desired. Note that if you change your input files, the default values will not automatically change. You can reset the auto-populated outputs based on the new parameters by deleting the default string and pressing tab or clicking away from the field.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</summary><usage>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This tool calculates the log difference between two rasters. It was designed to calculate differences through time between two &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Radiometric Terrain Corrected (RTC) SAR products&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; in amplitude scale, but can be used to look for differences between any two single-band rasters. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Negative values indicate a decrease in pixel value. For RTC images, these pixels have lower detected radar backscatter on the second date than the first. By default, the pixels of strongest negative change are displayed in black.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Positive values indicate an increase in pixel value. For RTC images, these pixels have higher detected radar backscatter on the second date than the first. By default, the pixels of strongest positive change are displayed in white.&lt;/SPAN&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 20;"&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Consider classifying the output image to highlight areas of the most extreme change. In the Symbology tab of the Raster Properties, select Classified and compute the histogram when prompted. Select the number of classes you want to delineate and click the Classify button to adjust the break points in the range of values. Note that values close to 0 (either positive or negative) underwent little change.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</usage><scriptExamples><scriptExample><title>ASF_Tools.pyt LogDiff</title><para>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This is the code for the LogDiff tool class within the ASF_Tools.pyt script.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</para><code>class LogDiff(object):
     def __init__(self):
 
         """Calculates the Log Difference between two RTC products"""
diff --git a/ArcGIS-toolbox/ASF_Tools.RGBDecomp.pyt.xml b/ArcGIS-toolbox/ASF_Tools.RGBDecomp.pyt.xml
index a86ccfb9..27fe2a44 100644
--- a/ArcGIS-toolbox/ASF_Tools.RGBDecomp.pyt.xml
+++ b/ArcGIS-toolbox/ASF_Tools.RGBDecomp.pyt.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<metadata xml:lang="en"><Esri><CreaDate>20200918</CreaDate><CreaTime>13534800</CreaTime><ArcGISFormat>1.0</ArcGISFormat><SyncOnce>TRUE</SyncOnce><ModDate>20201002</ModDate><ModTime>15000700</ModTime><scaleRange><minScale>150000000</minScale><maxScale>5000</maxScale></scaleRange><ArcGISProfile>NAP</ArcGISProfile><DataProperties><itemProps><imsContentType export="False"/></itemProps></DataProperties></Esri><tool name="RGBDecomp" displayname="RGB Decomposition" toolboxalias="ASF Tools" xmlns=""><arcToolboxHelpPath>c:\program files\arcgis\pro\Resources\Help\gp</arcToolboxHelpPath><parameters><param name="indir" displayname="Input directory containing dual-pol RTC data" type="Required" direction="Input" datatype="Workspace" expression="indir"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Navigate to the directory for an &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC product generated with HyP3&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, or other directory containing the co- and cross-pol datasets. You can also enter the path by typing or pasting it, or by dragging the folder from the Catalog window.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The two input rasters (co-pol and dual-pol) must be in the same folder, with filenames ending in XX.tif where XX is the polarization (VV, VH, HV, HH). There can only be one co-pol and one cross-pol raster in the input directory, and the datasets must be in either amplitude or power scale.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="scale" displayname="Scale of input RTC (Amplitude or Power)" type="Required" direction="Input" datatype="String" expression="Power | Amplitude"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Select the scale of the input datasets from the dropdown menu (either Amplitude or Power).&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;If the input directory is an &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC product folder generated using HyP3&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, this parameter will automatically populate based on the directory name, but you can set it manually if the directory does not follow the HyP3 product naming convention.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input directory after this parameter is populated, it does not automatically update to match the new input directory information. Delete the outdated parameter and press tab or click away from the field to reset it to reflect the new input directory if necessary.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="pol" displayname="Primary polarization (V or H)" type="Required" direction="Input" datatype="String" expression="V | H"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Select the primary polarization from the dropdown menu (either V for VV/VH datasets or H for HH/HV datasets).&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;If the input directory is an &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC product folder generated using HyP3&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, this parameter will automatically populate based on the directory name, but you can set it manually if the directory does not follow the HyP3 product naming convention.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input directory after this parameter is populated, it does not automatically update to match the new input directory information. Delete the outdated parameter and press tab or click away from the field to reset it to reflect the new input directory if necessary.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="rb_thresh_db" displayname="Threshold cutoff value for red/blue in dB (default: -24.0 dB)" type="Required" direction="Input" datatype="Double" expression="rb_thresh_db"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Set the red/blue threshold (dB).&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The default value is -24 dB, which works well for most Sentinel-1 IW datasets, but you can adjust the threshold if desired. Suitable values typically fall in the range of -19 to -25 dB.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="outdir" displayname="Output directory for new RGB file" type="Required" direction="Input" datatype="Workspace" expression="outdir"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Navigate to the destination directory where the output will be saved. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;By default this is set to be the same as the input directory. You can amend the path, or click the folder icon to navigate to a different output directory if desired.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input directory after the default directory populates, the output directory parameter does not automatically update. Delete the outdated directory and press tab or click away from the field to reset the output directory to reflect the new input directory.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="outname" displayname="Filename for new RGB raster" type="Required" direction="Input" datatype="String" expression="outname"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Enter the filename for the output raster, including the .tif extension.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;By default, this parameter is set to add an _RGB.tif tag to the input directory name. You can amend the name, or enter an entirely different filename if desired.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input directory after this parameter is populated, it does not automatically update to match the new input directory information. Delete the outdated filename and press tab or click away from the field to reset the parameter to reflect the new input directory if necessary.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="out_yn" displayname="Add output to map" type="Required" direction="Input" datatype="Boolean" expression="out_yn"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;P&gt;&lt;SPAN&gt;Select this option to automatically add the output raster to the map. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;This option is selected by default. Remove the check if you do not want the output raster added to the map.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;</dialogReference></param></parameters><summary>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This tool generates an RGB Decomposition raster from a dual-pol RTC dataset. It was designed for use with Sentinel-1 RTC products generated with HyP3, including the &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;On Demand RTC products you can request in Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, but can be used with any dual-pol RTC dataset that is in either power or amplitude scale. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The parameters are populated with default values once you select the input directory containing the RTC datasets. You can amend any of the auto-populated outputs or set them to something completely different if desired. Note that if you change your input directory, the default values will not automatically change. You can reset the auto-populated outputs based on the new parameters by deleting the default string and pressing tab or clicking away from the field.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;For best results when viewing the output image, set the symbology to be an RGB Composite stretched to Minimum-Maximum with no gamma stretch applied. The default symbology is generally an RGB Composite stretched to a Percent Clip with a gamma stretch applied.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</summary><usage>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This tool generates an RGB Decomposition raster from a dual-pol RTC dataset. It was designed for use with Sentinel-1 RTC products generated with HyP3, including the &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;On Demand RTC products you can request in Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, but can be used with any dual-pol dataset that is in either power or amplitude scale. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The two input rasters (co-pol and dual-pol) must be in the same folder, with filenames ending in XX.tif where XX is the polarization (VV, VH, HV, HH). There can only be one co-pol and one cross-pol raster in the input directory. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The parameters will be set to default values once the input directory is selected. If the input directory is an &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC product folder generated using HyP3&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, all of the parameters will be automatically populated based on the information contained in the directory name. If you are not using a product directory generated by HyP3, or if the directory name has been changed, you will need to set the scale and primary polarization parameters manually. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The red/blue threshold can be adjusted if desired. The default is -24 dB, and the appropriate value range is generally -19 to -25 dB. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The output directory defaults to the input directory, and the output name defaults to the name of the directory tagged with _RGB.tif. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;You can amend any of the auto-populated outputs or set them to something completely different if desired. Note that if you change your input directory, the default values will not automatically change. You can reset the auto-populated outputs based on the new parameters by deleting the default string and pressing tab or clicking away from the field. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;----- &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;For best results when viewing the output image, set the symbology to be an RGB Composite stretched to Minimum-Maximum with no gamma stretch applied. The default symbology is generally an RGB Composite stretched to a Percent Clip with a gamma stretch applied.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;In products generated using this tool, vegetated areas will generally appear more green, water (or desert sand) blue, and other areas such as urban landscapes more red. Where there are stronger cross-pol returns, indicating more volume scatter, the image will be more dominated by the green band, while areas with more surface scatter will have a greater contribution from the blue and red bands. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;A href="https://github.com:443/ASFHyP3/hyp3-lib/blob/develop/docs/rgb_decomposition.md" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Additional information&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; about interpreting RGB Decomposition images and the processing steps used to generate the color values is available.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</usage><scriptExamples><scriptExample><title>RGB Decomposition</title><code>class RGBDecomp(object):
+<metadata xml:lang="en"><Esri><CreaDate>20200918</CreaDate><CreaTime>13534800</CreaTime><ArcGISFormat>1.0</ArcGISFormat><SyncOnce>TRUE</SyncOnce><ModDate>20201002</ModDate><ModTime>15000700</ModTime><scaleRange><minScale>150000000</minScale><maxScale>5000</maxScale></scaleRange><ArcGISProfile>NAP</ArcGISProfile><DataProperties><itemProps><imsContentType export="False"/></itemProps></DataProperties></Esri><tool name="RGBDecomp" displayname="RGB Decomposition" toolboxalias="ASF Tools" xmlns=""><arcToolboxHelpPath>c:\program files\arcgis\pro\Resources\Help\gp</arcToolboxHelpPath><parameters><param name="indir" displayname="Input directory containing dual-pol RTC data" type="Required" direction="Input" datatype="Workspace" expression="indir"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Navigate to the directory for an &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC product generated with HyP3&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, or other directory containing the co- and cross-pol datasets. You can also enter the path by typing or pasting it, or by dragging the folder from the Catalog window.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The two input rasters (co-pol and dual-pol) must be in the same folder, with filenames ending in XX.tif where XX is the polarization (VV, VH, HV, HH). There can only be one co-pol and one cross-pol raster in the input directory, and the datasets must be in either power, amplitude, or dB scale.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="scale" displayname="Scale of input RTC (power, amplitude, or dB)" type="Required" direction="Input" datatype="String" expression="Power | Amplitude | dB"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Select the scale of the input datasets from the dropdown menu (either Power, Amplitude, or dB).&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;If the input directory is an &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC product folder generated using HyP3&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, this parameter will automatically populate based on the directory name, but you can set it manually if the directory does not follow the HyP3 product naming convention.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input directory after this parameter is populated, it does not automatically update to match the new input directory information. Delete the outdated parameter and press tab or click away from the field to reset it to reflect the new input directory if necessary.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="pol" displayname="Primary polarization (V or H)" type="Required" direction="Input" datatype="String" expression="V | H"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Select the primary polarization from the dropdown menu (either V for VV/VH datasets or H for HH/HV datasets).&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;If the input directory is an &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC product folder generated using HyP3&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, this parameter will automatically populate based on the directory name, but you can set it manually if the directory does not follow the HyP3 product naming convention.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input directory after this parameter is populated, it does not automatically update to match the new input directory information. Delete the outdated parameter and press tab or click away from the field to reset it to reflect the new input directory if necessary.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="rb_thresh_db" displayname="Threshold cutoff value for red/blue in dB (default: -24.0 dB)" type="Required" direction="Input" datatype="Double" expression="rb_thresh_db"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Set the red/blue threshold (dB).&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The default value is -24 dB, which works well for most Sentinel-1 IW datasets, but you can adjust the threshold if desired. Suitable values typically fall in the range of -19 to -25 dB.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="outdir" displayname="Output directory for new RGB file" type="Required" direction="Input" datatype="Workspace" expression="outdir"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Navigate to the destination directory where the output will be saved. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;By default this is set to be the same as the input directory. You can amend the path, or click the folder icon to navigate to a different output directory if desired.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input directory after the default directory populates, the output directory parameter does not automatically update. Delete the outdated directory and press tab or click away from the field to reset the output directory to reflect the new input directory.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="outname" displayname="Filename for new RGB raster" type="Required" direction="Input" datatype="String" expression="outname"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Enter the filename for the output raster, including the .tif extension.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;By default, this parameter is set to add an _RGB.tif tag to the input directory name. You can amend the name, or enter an entirely different filename if desired.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input directory after this parameter is populated, it does not automatically update to match the new input directory information. Delete the outdated filename and press tab or click away from the field to reset the parameter to reflect the new input directory if necessary.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="out_yn" displayname="Add output to map" type="Required" direction="Input" datatype="Boolean" expression="out_yn"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;P&gt;&lt;SPAN&gt;Select this option to automatically add the output raster to the map. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;This option is selected by default. Remove the check if you do not want the output raster added to the map.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;</dialogReference></param></parameters><summary>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This tool generates an RGB Decomposition raster from a dual-pol RTC dataset. It was designed for use with Sentinel-1 RTC products generated with HyP3, including the &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;On Demand RTC products you can request in Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, but can be used with any dual-pol RTC dataset that is in either power, amplitude, or dB scale. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The parameters are populated with default values once you select the input directory containing the RTC datasets. You can amend any of the auto-populated outputs or set them to something completely different if desired. Note that if you change your input directory, the default values will not automatically change. You can reset the auto-populated outputs based on the new parameters by deleting the default string and pressing tab or clicking away from the field.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;For best results when viewing the output image, set the symbology to be an RGB Composite stretched to Minimum-Maximum with no gamma stretch applied. The default symbology is generally an RGB Composite stretched to a Percent Clip with a gamma stretch applied.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</summary><usage>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This tool generates an RGB Decomposition raster from a dual-pol RTC dataset. It was designed for use with Sentinel-1 RTC products generated with HyP3, including the &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;On Demand RTC products you can request in Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, but can be used with any dual-pol dataset that is in either power, amplitude, or dB scale. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The two input rasters (co-pol and dual-pol) must be in the same folder, with filenames ending in XX.tif where XX is the polarization (VV, VH, HV, HH). There can only be one co-pol and one cross-pol raster in the input directory. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The parameters will be set to default values once the input directory is selected. If the input directory is an &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC product folder generated using HyP3&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, all of the parameters will be automatically populated based on the information contained in the directory name. If you are not using a product directory generated by HyP3, or if the directory name has been changed, you will need to set the scale and primary polarization parameters manually. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The red/blue threshold can be adjusted if desired. The default is -24 dB, and the appropriate value range is generally -19 to -25 dB. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The output directory defaults to the input directory, and the output name defaults to the name of the directory tagged with _RGB.tif. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;You can amend any of the auto-populated outputs or set them to something completely different if desired. Note that if you change your input directory, the default values will not automatically change. You can reset the auto-populated outputs based on the new parameters by deleting the default string and pressing tab or clicking away from the field. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;----- &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;For best results when viewing the output image, set the symbology to be an RGB Composite stretched to Minimum-Maximum with no gamma stretch applied. The default symbology is generally an RGB Composite stretched to a Percent Clip with a gamma stretch applied.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;In products generated using this tool, vegetated areas will generally appear more green, water (or desert sand) blue, and other areas such as urban landscapes more red. Where there are stronger cross-pol returns, indicating more volume scatter, the image will be more dominated by the green band, while areas with more surface scatter will have a greater contribution from the blue and red bands. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;A href="https://github.com:443/ASFHyP3/hyp3-lib/blob/develop/docs/rgb_decomposition.md" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Additional information&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; about interpreting RGB Decomposition images and the processing steps used to generate the color values is available.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</usage><scriptExamples><scriptExample><title>RGB Decomposition</title><code>class RGBDecomp(object):
     def __init__(self):
 
         """Generates an RGB image from co- and cross-pol RTC data"""
@@ -21,13 +21,13 @@
         # Second parameter: scale of input dataset
         scale = arcpy.Parameter(
             name="scale",
-            displayName="Scale of input RTC (Amplitude or Power)",
+            displayName="Scale of input RTC (Power, Amplitude, or dB)",
             datatype="GPString",
             parameterType="Required",
             direction="Input")
 
         scale.filter.type = "ValueList"
-        scale.filter.list = ["Power", "Amplitude"]
+        scale.filter.list = ["Power", "Amplitude", "dB"]
 
         # Third parameter: Primary polarization
         pol = arcpy.Parameter(
@@ -100,7 +100,7 @@
                     arcpy.AddMessage("Spatial Analyst Extension has been checked out.")
                 else:
                     arcpy.AddMessage("Spatial Analyst Extension is not available for use.")
-        except Exception:
+        except Exception:  # noqa: B902
             arcpy.AddMessage(
                 "Spatial Analyst extension is not available for use. Check your licensing to make sure you have "
                 "access to this extension.")
@@ -121,6 +121,8 @@
                 insc = 'Amplitude'
             elif inscale == 'p':
                 insc = 'Power'
+            elif inscale == 'd':
+                insc = 'dB'
             else:
                 insc = ''
             if not parameters[1].altered:
@@ -216,13 +218,18 @@
             xps = arcpy.sa.Square(xp)
             arcpy.AddMessage("Input scale is amplitude. Converted to power scale.")
 
+        elif scale == 'dB':
+            cps = arcpy.sa.Power(10, arcpy.sa.Divide(cp, 10))
+            xps = arcpy.sa.Power(10, arcpy.sa.Divide(xp, 10))
+            arcpy.AddMessage("Input scale is dB. Converted to power scale.")
+
         elif scale == 'Power':
             cps = cp
             xps = xp
             arcpy.AddMessage("Input scale is power. No conversion necessary.")
 
         else:
-            arcpy.AddError("Input scale must be set to Amplitude or Power.")
+            arcpy.AddError("Input scale must be set to Power Amplitude, or dB.")
             sys.exit(0)
 
         arcpy.AddMessage("Input rasters have been defined. Running pixel cleanup routine...")
@@ -242,12 +249,12 @@
         remap_mb = "0 %s 1;%s 100000 0" % (rb_thresh, rb_thresh)
         mb = arcpy.sa.Reclassify(xp0, "VALUE", remap_mb, "DATA")
 
-        # mr = xp0 &gt; k
+        # mr = xp0 > k
         remap_mr = "0 %s 0;%s 100000 1" % (rb_thresh, rb_thresh)
         mr = arcpy.sa.Reclassify(xp0, "VALUE", remap_mr, "DATA")
 
-        # mx = SXP &gt; 0
-        mx = arcpy.sa.Con(xp0, "1", "0", "VALUE &gt; 0")
+        # mx = SXP > 0
+        mx = arcpy.sa.Con(xp0, "1", "0", "VALUE > 0")
 
         arcpy.AddMessage("Spatial masks generated. Deriving red and blue components of surface scatter...")
 
@@ -276,9 +283,9 @@
         band_list = []
 
         # Remove negative values and convert each band to an integer raster
-        ar = arcpy.sa.Int(arcpy.sa.Con(ir, "255", ir, "VALUE &gt; 255"))
-        ag = arcpy.sa.Int(arcpy.sa.Con(ig, "255", ig, "VALUE &gt; 255"))
-        ab = arcpy.sa.Int(arcpy.sa.Con(ib, "255", ib, "VALUE &gt; 255"))
+        ar = arcpy.sa.Int(arcpy.sa.Con(ir, "255", ir, "VALUE > 255"))
+        ag = arcpy.sa.Int(arcpy.sa.Con(ig, "255", ig, "VALUE > 255"))
+        ab = arcpy.sa.Int(arcpy.sa.Con(ib, "255", ib, "VALUE > 255"))
 
         # Save bands as GeoTIFF rasters in the scratch folder
         arpath = os.path.join(scratchpath, "ar.tif")
@@ -315,11 +322,28 @@
                 "Option to add output layer to map was not selected. "
                 "Output can be added manually if desired: %s" % outpath)
 
+        """
+        # Delete temporary files
+        arcpy.AddMessage("Cleaning up...")
+
+        # The deletion of the temp folder could be set as an option, if there's any chance that users might
+        # want to have access to the individual color bands. I don't think that's likely, though.
+
+        # There is not currently a mechanism for getting rid of the temp folder.
+        # The arcpy.DeleteManagement approach doesn't work from within a tool, so that's not an option.
+        # There seems to be a residual hold on at least one of the saved input bands for the RGB Composite,
+        # so using shutil doesn't work (it won't delete a directory if a file contained in the directory
+        # still has a file lock applied). The lock is probably ArcGIS-driven.
+        # Not sure how best to proceed to get rid of the temp folder once the process is complete.
+
+        arcpy.AddMessage("Temporary files have been deleted.")
+        """
+
         # Check In Spatial Analyst Extension
         status = arcpy.CheckInExtension("Spatial")
         arcpy.AddMessage("The Spatial Analyst Extension is in %s status." % status)
 
-        return</code><para>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This is the code for the RGBDecomp tool class within the ASF_Tools.pyt script&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</para></scriptExample></scriptExamples></tool><dataIdInfo><idCitation><resTitle>RGB Decomposition</resTitle></idCitation><idAbs>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This tool generates an RGB Decomposition raster from a dual-pol RTC dataset. It was designed for use with RTC products generated with HyP3, including the &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;On Demand RTC products you can request in Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, but can be used with any dual-pol RTC dataset that is in either power or amplitude scale. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The parameters are populated with default values once you select the input directory containing the RTC datasets. You can amend any of the auto-populated outputs or set them to something completely different if desired. Note that if you change your input directory, the default values will not automatically change. You can reset the auto-populated outputs based on the new parameters by deleting the default string and pressing tab or clicking away from the field.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;For best results when viewing the output image, set the symbology to be an RGB Composite stretched to Minimum-Maximum with no gamma stretch applied. The default symbology is generally an RGB Composite stretched to a Percent Clip with a gamma stretch applied.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</idAbs><searchKeys><keyword>ASF</keyword><keyword>Alaska Satellite Facility</keyword><keyword>SAR</keyword><keyword>Synthetic Aperture Radar</keyword><keyword>RGB Decomposition</keyword></searchKeys><idCredit>Alaska Satellite Facility</idCredit><dataLang><languageCode value="eng"/></dataLang><dataChar><CharSetCd value="004"/></dataChar><idPoC xmlns=""><rpIndName>Heidi Kristenson</rpIndName><rpOrgName>Alaska Satellite Facility</rpOrgName><rpPosName>GIS Specialist</rpPosName><role><RoleCd value="011"/></role></idPoC></dataIdInfo><distInfo><distributor><distorFormat><formatName>ArcToolbox Tool</formatName></distorFormat></distributor></distInfo><mdHrLv><ScopeCd value="005"/></mdHrLv><mdContact xmlns=""><rpIndName>Heidi Kristenson</rpIndName><rpOrgName>Alaska Satellite Facility</rpOrgName><rpPosName>GIS Specialist</rpPosName><role><RoleCd value="011"/></role><editorSave>False</editorSave><displayName>Heidi Kristenson</displayName></mdContact><Binary><Thumbnail><Data EsriPropertyType="PictureX">/9j/4AAQSkZJRgABAQEAHgAeAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK
+        return</code><para>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This is the code for the RGBDecomp tool class within the ASF_Tools.pyt script&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</para></scriptExample></scriptExamples></tool><dataIdInfo><idCitation><resTitle>RGB Decomposition</resTitle></idCitation><idAbs>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This tool generates an RGB Decomposition raster from a dual-pol RTC dataset. It was designed for use with RTC products generated with HyP3, including the &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;On Demand RTC products you can request in Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, but can be used with any dual-pol RTC dataset that is in power, amplitude, or dB scale. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The parameters are populated with default values once you select the input directory containing the RTC datasets. You can amend any of the auto-populated outputs or set them to something completely different if desired. Note that if you change your input directory, the default values will not automatically change. You can reset the auto-populated outputs based on the new parameters by deleting the default string and pressing tab or clicking away from the field.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;For best results when viewing the output image, set the symbology to be an RGB Composite stretched to Minimum-Maximum with no gamma stretch applied. The default symbology is generally an RGB Composite stretched to a Percent Clip with a gamma stretch applied.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</idAbs><searchKeys><keyword>ASF</keyword><keyword>Alaska Satellite Facility</keyword><keyword>SAR</keyword><keyword>Synthetic Aperture Radar</keyword><keyword>RGB Decomposition</keyword></searchKeys><idCredit>Alaska Satellite Facility</idCredit><dataLang><languageCode value="eng"/></dataLang><dataChar><CharSetCd value="004"/></dataChar><idPoC xmlns=""><rpIndName>Heidi Kristenson</rpIndName><rpOrgName>Alaska Satellite Facility</rpOrgName><rpPosName>GIS Specialist</rpPosName><role><RoleCd value="011"/></role></idPoC></dataIdInfo><distInfo><distributor><distorFormat><formatName>ArcToolbox Tool</formatName></distorFormat></distributor></distInfo><mdHrLv><ScopeCd value="005"/></mdHrLv><mdContact xmlns=""><rpIndName>Heidi Kristenson</rpIndName><rpOrgName>Alaska Satellite Facility</rpOrgName><rpPosName>GIS Specialist</rpPosName><role><RoleCd value="011"/></role><editorSave>False</editorSave><displayName>Heidi Kristenson</displayName></mdContact><Binary><Thumbnail><Data EsriPropertyType="PictureX">/9j/4AAQSkZJRgABAQEAHgAeAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK
 CwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQU
 FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAD/AUoDASIA
 AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA
diff --git a/ArcGIS-toolbox/ASF_Tools.ScaleConversion.pyt.xml b/ArcGIS-toolbox/ASF_Tools.ScaleConversion.pyt.xml
index e114f7f8..d43bf904 100644
--- a/ArcGIS-toolbox/ASF_Tools.ScaleConversion.pyt.xml
+++ b/ArcGIS-toolbox/ASF_Tools.ScaleConversion.pyt.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<metadata xml:lang="en"><Esri><CreaDate>20191111</CreaDate><CreaTime>16262800</CreaTime><ArcGISFormat>1.0</ArcGISFormat><ArcGISstyle>North American Profile of ISO19115 2003</ArcGISstyle><SyncOnce>TRUE</SyncOnce><ModDate>20201002</ModDate><ModTime>15065100</ModTime><scaleRange><minScale>150000000</minScale><maxScale>5000</maxScale></scaleRange><ArcGISProfile>NAP</ArcGISProfile><DataProperties><itemProps><imsContentType export="False"/></itemProps></DataProperties></Esri><tool name="ScaleConversion" displayname="Scale Conversion (Power, Amplitude, dB)" toolboxalias="ASF Tools" xmlns=""><arcToolboxHelpPath>c:\program files\arcgis\pro\Resources\Help\gp</arcToolboxHelpPath><parameters><param name="p_inpath" displayname="Raster file to be converted" type="Required" direction="Input" datatype="Raster Dataset" expression="p_inpath"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Browse to the raster file to be converted by clicking on the folder icon, or type or paste in the full path. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;You can also drag and drop a raster from the Catalog window.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;ArcGIS Pro tip: Select the raster file as the input, not Band_1. While Pro allows you to navigate to the band level, it is not a valid input for this tool.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="p_inscale" displayname="Scale of input raster (power or amplitude)" type="Required" direction="Input" datatype="String" expression="Power | Amplitude"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Select either Power or Amplitude from the dropdown menu. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;The selection must match the actual scale of the values in the input raster. &lt;/SPAN&gt;&lt;SPAN&gt;If the input raster is a standard RTC product generated with HyP3, such as the&lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt; &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;On Demand RTC products you can request in Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, this field will be automatically populated based upon the filename. For input rasters that do not use the HyP3 naming convention, this parameter must be set manually. &lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;The output values will be incorrect if the input scale is not accurately defined.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;For &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Sentinel-1 RTC products&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; generated by HyP3, the filename includes a 6-letter options tag just before the product ID near the end of the filename. The second letter indicates the scale: p for power, a for amplitude (i.e. g&lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;a&lt;/SPAN&gt;&lt;SPAN&gt;uned would be a gamma-nought amplitude product, while s&lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;p&lt;/SPAN&gt;&lt;SPAN&gt;uned would be a sigma-nought power product).   &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;A href="https://search.asf.alaska.edu:443/#/?flightDirs=&amp;amp;amp;amp;amp;amp;dataset=ALOS&amp;amp;amp;amp;amp;amp;subtypes=ALOS$$&amp;amp;amp;amp;amp;amp;productTypes=ALOS$$RTC_LOW_RES,RTC_HI_RES&amp;amp;amp;amp;amp;amp;beamModes=ALOS$$&amp;amp;amp;amp;amp;amp;polarizations=ALOS$$" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;PALSAR-1 RTC&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; products available from &lt;/SPAN&gt;&lt;A href="https://search.asf.alaska.edu:443/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;ASF's Data Search - Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; are in power scale. &lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="p_outscale" displayname="Scale of output raster (power, amplitude or dB)" type="Required" direction="Input" datatype="String" expression="Power | Amplitude | dB"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Select the desired output scale from the dropdown menu, which includes Power, Amplitude and dB (decibels).&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="p_outdir" displayname="Destination folder for output raster" type="Required" direction="Input" datatype="Workspace" expression="p_outdir"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Browse to or type in the path to the directory for the output product. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The default path is to the same directory that contains the input raster, but you can amend this path as desired or browse to a completely different directory.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input raster after the default directory populates, the output directory parameter does not automatically update. Delete the outdated directory path and press tab or click away from the field to reset the directory to reflect the new input raster path.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="p_outname" displayname="File name for output raster (must include valid raster file extension)" type="Required" direction="Input" datatype="String" expression="p_outname"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Enter the filename for the output raster, including the file extension if you are not saving the raster to a geodatabase.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The default filename is the same as the base name of the input raster, with the output scale added as a tag and a .tif extension included. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input raster and/or output scale parameters after the default name populates, it does not automatically upadate. Delete the outdated filename and press tab or click away from the field to reset the filename default to reflect the new parameters.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="out_yn" displayname="Add output to map" type="Required" direction="Input" datatype="Boolean" expression="out_yn"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Select this option to automatically add the output raster to the map. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;This option is selected by default. Remove the check if you do not want the output raster added to the map.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param></parameters><summary>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN&gt;This tool converts SAR imagery from one scale into another. The scales most commonly used with SAR data are power, amplitude and dB. &lt;/SPAN&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN&gt;The output is a new raster dataset, with the pixel values in the designated output scale.&lt;/SPAN&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN STYLE="font-style:italic;"&gt;The output directory and filename are automatically populated using the source raster and output scale parameters. You can amend the auto-populated outputs or set them to something completely different if desired. Note that if you change your input file or output scale, the default values will not automatically change. You can reset the auto-populated outputs based on the new parameters by deleting the default string and pressing tab or clicking away from the field.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</summary><usage>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;While designed to work with RTC imagery generated with HyP3, including the &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;On Demand RTC products you can request in Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, this tool will convert any raster with pixel values in either amplitude or power scale into power, amplitude, or dB.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The user selects the file to be converted, declares the input scale and the desired output scale, and defines the output directory and desired filename. Note that a valid raster file extension must be included in the filename (such as .tif) unless you are saving the raster to a geodatabase.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;If you are using a &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Sentinel-1 RTC product&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; generated by ASF's HyP3 processing platform, the tool will recognize the input scale based on the file name. If you are using a different type of input file, or if the name has been changed, you must define the input scale manually. &lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;If the input scale is not set correctly, the output values will be incorrect. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;For &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Sentinel-1 RTC products&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; generated by ASF, the filename includes a 6-letter options tag just before the product ID near the end of the filename. The second letter indicates the scale: p for power, a for amplitude (i.e. g&lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;a&lt;/SPAN&gt;&lt;SPAN&gt;uned would be a gamma-nought amplitude product, while s&lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;p&lt;/SPAN&gt;&lt;SPAN&gt;uned would be a sigma-nought power product).  &lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;A href="https://search.asf.alaska.edu:443/" target="/?flightDirs=&amp;amp;amp;amp;dataset=ALOS&amp;amp;amp;amp;subtypes=ALOS$$&amp;amp;amp;amp;productTypes=ALOS$$RTC_LOW_RES,RTC_HI_RES&amp;amp;amp;amp;beamModes=ALOS$$&amp;amp;amp;amp;polarizations=ALOS$$" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;ALOS PALSAR-1 RTC&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; products available from &lt;/SPAN&gt;&lt;A href="http://search.asf.alaska.edu:80/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;ASF's Data Search - Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; are in power scale. &lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that the power scale tends to display very dark RTC images with the default Percent Clip stretch used by most GIS software, due to the range of values being mostly very close to zero with a handful of much higher values. Amplitude values, which are the square root of power values, tend to give a better default visual display. Decibel (dB) values, which are 10 x Log10 of the power values, differentiate the very dark values (such as water pixels on a calm day) even more clearly from the rest of the pixels. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;The stretch symbology settings of any of these scales can be adjusted to result in a pleasing visual display of the image without having to generate a new raster layer. Consider using the Standard Deviation or Sigmoid stretch options, or using Min-Max with a set maximum (such as 0.3 for power values). Conversion is more important if you want to run analyses using pixel values of a particular scale. Because the dB scale is logarithmic, take care when using dB values for statistical analysis; it may not always be appropriate.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 20;"&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</usage><scriptExamples><scriptExample><title>ASF_Tools.pyt ScaleConversion</title><code>class ScaleConversion(object):
+<metadata xml:lang="en"><Esri><CreaDate>20191111</CreaDate><CreaTime>16262800</CreaTime><ArcGISFormat>1.0</ArcGISFormat><ArcGISstyle>North American Profile of ISO19115 2003</ArcGISstyle><SyncOnce>TRUE</SyncOnce><ModDate>20201002</ModDate><ModTime>15065100</ModTime><scaleRange><minScale>150000000</minScale><maxScale>5000</maxScale></scaleRange><ArcGISProfile>NAP</ArcGISProfile><DataProperties><itemProps><imsContentType export="False"/></itemProps></DataProperties></Esri><tool name="ScaleConversion" displayname="Scale Conversion (Power, Amplitude, dB)" toolboxalias="ASF Tools" xmlns=""><arcToolboxHelpPath>c:\program files\arcgis\pro\Resources\Help\gp</arcToolboxHelpPath><parameters><param name="p_inpath" displayname="Raster file to be converted" type="Required" direction="Input" datatype="Raster Dataset" expression="p_inpath"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Browse to the raster file to be converted by clicking on the folder icon, or type or paste in the full path. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;You can also drag and drop a raster from the Catalog window.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;ArcGIS Pro tip: Select the raster file as the input, not Band_1. While Pro allows you to navigate to the band level, it is not a valid input for this tool.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="p_inscale" displayname="Scale of input raster (power, amplitude, or dB)" type="Required" direction="Input" datatype="String" expression="Power | Amplitude | dB"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Select Power, Amplitude, or dB from the dropdown menu. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;The selection must match the actual scale of the values in the input raster. &lt;/SPAN&gt;&lt;SPAN&gt;If the input raster is a standard  &lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt; &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC product generated with HyP3&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, such as the &lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt; &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/using/vertex/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;On Demand RTC products you can request in Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, this field will be automatically populated based upon the filename. For input rasters that do not use the HyP3 naming convention, this parameter must be set manually. &lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;The output values will be incorrect if the input scale is not accurately defined.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;For &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Sentinel-1 RTC products&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; generated by HyP3, the filename includes a 6-letter options tag just before the product ID near the end of the filename. The second letter indicates the scale: p for power, a for amplitude, d for dB (i.e. g&lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;a&lt;/SPAN&gt;&lt;SPAN&gt;uned would be a gamma-nought amplitude product, while s&lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;p&lt;/SPAN&gt;&lt;SPAN&gt;uned would be a sigma-nought power product). &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;A href="https://search.asf.alaska.edu:443/#/?flightDirs=&amp;amp;amp;amp;amp;amp;dataset=ALOS&amp;amp;amp;amp;amp;amp;subtypes=ALOS$$&amp;amp;amp;amp;amp;amp;productTypes=ALOS$$RTC_LOW_RES,RTC_HI_RES&amp;amp;amp;amp;amp;amp;beamModes=ALOS$$&amp;amp;amp;amp;amp;amp;polarizations=ALOS$$" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;ALOS PALSAR RTC&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; products available from &lt;/SPAN&gt;&lt;A href="https://search.asf.alaska.edu:443/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;ASF's Data Search - Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; are in power scale. &lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="p_outscale" displayname="Scale of output raster (power, amplitude or dB)" type="Required" direction="Input" datatype="String" expression="Power | Amplitude | dB"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Select the desired output scale from the dropdown menu, which includes Power, Amplitude and dB (Decibel).&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="p_outdir" displayname="Destination folder for output raster" type="Required" direction="Input" datatype="Workspace" expression="p_outdir"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Browse to or type in the path to the directory for the output product. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The default path is to the same directory that contains the input raster, but you can amend this path as desired or browse to a completely different directory.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input raster after the default directory populates, the output directory parameter does not automatically update. Delete the outdated directory path and press tab or click away from the field to reset the directory to reflect the new input raster path.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="p_outname" displayname="File name for output raster (must include valid raster file extension)" type="Required" direction="Input" datatype="String" expression="p_outname"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Enter the filename for the output raster, including the file extension if you are not saving the raster to a geodatabase.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The default filename is the same as the base name of the input raster, with the output scale added as a tag and a .tif extension included. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that if you change the input raster and/or output scale parameters after the default name populates, it does not automatically update. Delete the outdated filename and press tab or click away from the field to reset the filename default to reflect the new parameters.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param><param name="out_yn" displayname="Add output to map" type="Required" direction="Input" datatype="Boolean" expression="out_yn"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Select this option to automatically add the output raster to the map. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;This option is selected by default. Remove the check if you do not want the output raster added to the map.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference></param></parameters><summary>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN&gt;This tool converts SAR imagery from one scale into another. The scales most commonly used with SAR data are power, amplitude and dB. &lt;/SPAN&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN&gt;The output is a new raster dataset, with the pixel values in the designated output scale.&lt;/SPAN&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN STYLE="font-style:italic;"&gt;The output directory and filename are automatically populated using the source raster and output scale parameters. You can amend the auto-populated outputs or set them to something completely different if desired. Note that if you change your input file or output scale, the default values will not automatically change. You can reset the auto-populated outputs based on the new parameters by deleting the default string and pressing tab or clicking away from the field.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</summary><usage>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;While designed to work with RTC imagery generated with HyP3, including the &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;On Demand RTC products you can request in Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, this tool will convert any raster with pixel values in power, amplitude, or dB scale into any other of those three scales.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;The user selects the file to be converted, declares the input scale and the desired output scale, and defines the output directory and desired filename. Note that a valid raster file extension must be included in the filename (such as .tif) unless you are saving the raster to a geodatabase.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;If you are using a &lt;/SPAN&gt;&lt;A href="https://https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Sentinel-1 RTC product&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; generated by ASF's HyP3 processing platform, the tool will recognize the input scale based on the file name. If you are using a different type of input file, or if the name has been changed, you must define the input scale manually. &lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;If the input scale is not set correctly, the output values will be incorrect. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;For &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Sentinel-1 RTC products&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; generated by ASF, the fl includes a 6-letter options tag just before the product ID near the end of the filename. The second letter indicates the scale: p ifor power, a for enameamplitude, d for dB (i.e. g&lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;a&lt;/SPAN&gt;&lt;SPAN&gt;uned would be a gamma-nought amplitude product, while s&lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;p&lt;/SPAN&gt;&lt;SPAN&gt;uned would be a sigma-nought power product).  &lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;A href="https://search.asf.alaska.edu:443/" target="/?flightDirs=&amp;amp;amp;amp;dataset=ALOS&amp;amp;amp;amp;subtypes=ALOS$$&amp;amp;amp;amp;productTypes=ALOS$$RTC_LOW_RES,RTC_HI_RES&amp;amp;amp;amp;beamModes=ALOS$$&amp;amp;amp;amp;polarizations=ALOS$$" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;ALOS PALSAR RTC&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; products available from &lt;/SPAN&gt;&lt;A href="http://search.asf.alaska.edu:80/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;ASF's Data Search - Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; are in power scale. &lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;Note that the power scale tends to display very dark RTC images with the default Percent Clip stretch used by most GIS software, due to the range of values being mostly very close to zero with a handful of much higher values. Amplitude values, which are the square root of power values, tend to give a better default visual display. Decibel (dB) values, which are 10 x Log10 of the power values, differentiate the very dark values (such as water pixels on a calm day) even more clearly from the rest of the pixels. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;The stretch symbology settings of any of these scales can be adjusted to result in a pleasing visual display of the image without having to generate a new raster layer. Consider using the Standard Deviation or Sigmoid stretch options, or using Min-Max with a set maximum (such as 0.3 for power values). Conversion is more important if you want to run analyses using pixel values of a particular scale. Because the dB scale is logarithmic, take care when using dB values for statistical analysis; it may not always be appropriate.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 20;"&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</usage><scriptExamples><scriptExample><title>ASF_Tools.pyt ScaleConversion</title><code>class ScaleConversion(object):
     def __init__(self):
 
-        """Converts RTC products from power to amplitude"""
+        """Converts RTC products between Power, Amplitude, and dB scales"""
         self.label = "Scale Conversion (Power, Amplitude, dB)"
-        self.description = "This tool converts RTC products from Power or Amplitude scale to a different scale " \
+        self.description = "This tool converts RTC products from Power, Amplitude, or dB scale to a different scale " \
                            "(Power, Amplitude or dB)."
         self.canRunInBackground = True
 
@@ -22,18 +22,18 @@
         # Second parameter: input scale
         p_inscale = arcpy.Parameter(
             name="p_inscale",
-            displayName="Scale of input raster (power or amplitude)",
+            displayName="Scale of input raster (power, amplitude, or dB)",
             datatype="GPString",
             parameterType="Required",
             direction="Input")
 
         p_inscale.filter.type = "ValueList"
-        p_inscale.filter.list = ["Power", "Amplitude"]
+        p_inscale.filter.list = ["Power", "Amplitude", "dB"]
 
         # Third parameter: output scale
         p_outscale = arcpy.Parameter(
             name="p_outscale",
-            displayName="Scale of output raster (power, amplitude or dB)",
+            displayName="Scale of output raster (power, amplitude, or dB)",
             datatype="GPString",
             parameterType="Required",
             direction="Input")
@@ -102,6 +102,17 @@
                 out_t10 = arcpy.sa.Times(out_log10, 10)
                 out_t10.save(self.outpath)
 
+        elif self.inscale == 'dB':
+            if self.outscale == 'Power':
+                out_d10 = arcpy.sa.Divide(self.inpath, 10)
+                out_exp = arcpy.sa.Power(10, out_d10)
+                out_exp.save(self.outpath)
+
+            elif self.outscale == 'Amplitude':
+                out_d20 = arcpy.sa.Divide(self.inpath, 20)
+                out_exp = arcpy.sa.Power(10, out_d20)
+                out_exp.save(self.outpath)
+
         else:
             arcpy.AddMessage('Parameters entered incorrectly; no conversion performed.')
 
@@ -120,7 +131,7 @@
                     arcpy.AddMessage("Spatial Analyst Extension has been checked out.")
                 else:
                     arcpy.AddMessage("Spatial Analyst Extension is not available for use.")
-        except Exception:
+        except Exception:  # noqa: B902
             arcpy.AddMessage(
                 "Spatial Analyst extension is not available for use. Check your licensing to make sure you have "
                 "access to this extension.")
@@ -147,6 +158,8 @@
                 insc = 'Amplitude'
             elif inscale == 'p':
                 insc = 'Power'
+            elif inscale == 'd':
+                insc = 'dB'
             else:
                 insc = ''
             if not parameters[1].altered:
diff --git a/ArcGIS-toolbox/ASF_Tools.UnzipFiles.pyt.xml b/ArcGIS-toolbox/ASF_Tools.UnzipFiles.pyt.xml
index b12be5e6..b1dc9762 100644
--- a/ArcGIS-toolbox/ASF_Tools.UnzipFiles.pyt.xml
+++ b/ArcGIS-toolbox/ASF_Tools.UnzipFiles.pyt.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<metadata xml:lang="en"><Esri><CreaDate>20190110</CreaDate><CreaTime>11564900</CreaTime><ArcGISFormat>1.0</ArcGISFormat><ArcGISstyle>North American Profile of ISO19115 2003</ArcGISstyle><SyncOnce>TRUE</SyncOnce><ModDate>20201002</ModDate><ModTime>15111700</ModTime><scaleRange><minScale>150000000</minScale><maxScale>5000</maxScale></scaleRange><ArcGISProfile>NAP</ArcGISProfile><DataProperties><itemProps><imsContentType export="False"/></itemProps></DataProperties></Esri><tool name="UnzipFiles" displayname="Unzip Files" toolboxalias="ASF Tools" xmlns=""><arcToolboxHelpPath>c:\program files\arcgis\pro\Resources\Help\gp</arcToolboxHelpPath><parameters><param name="ziplocation" displayname="Location of .zip files" type="Required" direction="Input" datatype="Workspace" expression="ziplocation"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Click the browse icon to navigate to and select the directory folder containing the downloaded .zip files. For most Windows users, this will be the default Downloads folder.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Note that ALL files with a .zip extension in the ziplocation directory will be extracted. Make sure that you want all of the .zip files available in the ziplocation directory to be extracted to the same destination.&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;To set default values for this parameter, click on the Tool Help button at the bottom of the tool dialog box to display instructions (refer to Python Reference section).&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference><pythonReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;If you would like to set a default value for this parameter, you may edit the ASF_Tools.pyt script to add in your default download folder.&lt;/SPAN&gt;&lt;/P&gt;&lt;OL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Right-click on the ASF_Tools.pyt toolbox in ArcCatalog (or the Catalog window in ArcMap) and select Edit...&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Scroll down to ziplocation.value (line 43) and enter your download folder path between the single quotes (refer to comments in the script for an example)&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Save the script (select Save from the File menu in the script editor), then close the editor window&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Double-click the ASF_Tools.pyt toolbox in ArcCatalog (or the Catalog window in ArcMap) to expand the toolbox, and open the Unzip Files tool&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;If you have double-clicked the toolbox and the change is not reflected in the tool dialog, close and re-open your ArcGIS application (ArcMap or ArcCatalog)&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/OL&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</pythonReference></param><param name="outlocation" displayname="Destination folder for extracted products" type="Required" direction="Input" datatype="Workspace" expression="outlocation"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Click the browse icon to navigate to and select the destination directory for the extracted product folders.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Note that each .zip file has a folder inside to keep all of the files for that product together, so the resulting extractions will be a set of folders (one for each product) in the destination directory.&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;To set default values for this parameter, click on the Tool Help button at the bottom of the tool dialog box to display instructions (refer to Python Reference section).&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference><pythonReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;If you would like to set a default value for this parameter, you may edit the ASF_Tools.pyt script to add in your default destination folder.&lt;/SPAN&gt;&lt;/P&gt;&lt;OL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Right-click on the ASF_Tools.pyt toolbox in ArcCatalog (or the Catalog window in ArcMap) and select Edit...&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Scroll down to outlocation.value (line 57) and enter your download folder path between the single quotes (refer to comments in the script for an example)&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Save the script (select Save from the File menu in the script editor), then close the editor window&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Double-click the ASF_Tools.pyt toolbox in ArcCatalog (or the Catalog window in ArcMap) to expand the toolbox, and open the Unzip Files tool&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;If you have double-clicked the toolbox and the change is not reflected in the tool dialog, close and re-open your ArcGIS application (ArcMap or ArcCatalog)&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/OL&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</pythonReference></param></parameters><summary>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This tool unzips ASF products and saves the extracted folders in the desired destination folder. This is particularly useful if you are downloading multiple &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC files from Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; and would like to extract them to a product folder.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;All .zip files in the download folder will be extracted&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Each product will have its own folder within the destination directory (when extracting ASF files, or other zip files that have a single root directory containing all the files within the zip archive)&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;The original .zip files in the download folder will each be deleted once file extraction is complete&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;font-weight:bold;"&gt;NOTE: &lt;/SPAN&gt;&lt;SPAN STYLE="font-style:italic;"&gt;If you have downloaded GRD or SLC products and will be using the SNAP Sentinel-1 Toolbox to process the products, &lt;/SPAN&gt;&lt;SPAN STYLE="font-style:italic;font-weight:bold;"&gt;do not &lt;/SPAN&gt;&lt;SPAN STYLE="font-style:italic;"&gt;unzip the product downloads using this tool. S1TBX is designed to work within the .zip folder structure, and this tool deletes the .zip files once they are extracted.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</summary><usage>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Many ASF products are served as zip files, including &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Sentinel-1 RTC products&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; generated using the On-Demand functionality in &lt;/SPAN&gt;&lt;A href="https://search.asf.alaska.edu:443/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;. Each product zip file contains another folder inside, so when the files are extracted, the individual component product files will still be contained within a uniquely-named product folder. Because the zip file and folder names are often long, standard Windows zip utilities may encounter errors if the full product pathway contains more characters than is allowed.  &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;This tool offers a quick and easy way to extract multiple zip files to the desired destination folder and delete the original zip files from the download folder. While designed to help manage ASF downloads, this tool can be used to extract any collection of zip files.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;If you have downloaded Sentinel-1 GRD or SLC files, and will be using the SNAP Sentinel-1 Toolbox to process the images, you will need to retain the .zip files. If you still want to use this tool to unzip the product files, make sure you've copied the original zip files to another folder first, because the .zip files will be deleted once extracted.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</usage><scriptExamples><scriptExample><title>ASF_Tools.pyt UnzipFiles</title><para>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This is the code for the UnzipFiles tool class within the ASF_Tools.pyt script.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</para><code>class UnzipFiles(object):
+<metadata xml:lang="en"><Esri><CreaDate>20190110</CreaDate><CreaTime>11564900</CreaTime><ArcGISFormat>1.0</ArcGISFormat><ArcGISstyle>North American Profile of ISO19115 2003</ArcGISstyle><SyncOnce>TRUE</SyncOnce><ModDate>20201002</ModDate><ModTime>15111700</ModTime><scaleRange><minScale>150000000</minScale><maxScale>5000</maxScale></scaleRange><ArcGISProfile>NAP</ArcGISProfile><DataProperties><itemProps><imsContentType export="False"/></itemProps></DataProperties></Esri><tool name="UnzipFiles" displayname="Unzip Files" toolboxalias="ASF Tools" xmlns=""><arcToolboxHelpPath>c:\program files\arcgis\pro\Resources\Help\gp</arcToolboxHelpPath><parameters><param name="ziplocation" displayname="Location of .zip files" type="Required" direction="Input" datatype="Workspace" expression="ziplocation"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Click the browse icon to navigate to and select the directory folder containing the downloaded .zip files. For most Windows users, this will be the default Downloads folder.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Note that ALL files with a .zip extension in the ziplocation directory will be extracted. Make sure that you want all of the .zip files available in the ziplocation directory to be extracted to the same destination.&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;To set default values for this parameter, click on the Tool Help button at the bottom of the tool dialog box to display instructions (refer to Python Reference section).&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference><pythonReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;If you would like to set a default value for this parameter, you may edit the ASF_Tools.pyt script to add in your default download folder.&lt;/SPAN&gt;&lt;/P&gt;&lt;OL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Right-click on the ASF_Tools.pyt toolbox in ArcCatalog (or the Catalog window in ArcMap) and select Edit...&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Scroll down to ziplocation.value (line 43) and enter your download folder path between the single quotes (refer to comments in the script for an example)&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Save the script (select Save from the File menu in the script editor), then close the editor window&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Double-click the ASF_Tools.pyt toolbox in ArcCatalog (or the Catalog window in ArcMap) to expand the toolbox, and open the Unzip Files tool&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;If you have double-clicked the toolbox and the change is not reflected in the tool dialog, close and re-open your ArcGIS application (ArcMap or ArcCatalog)&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/OL&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</pythonReference></param><param name="outlocation" displayname="Destination folder for extracted products" type="Required" direction="Input" datatype="Workspace" expression="outlocation"><dialogReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Click the browse icon to navigate to and select the destination directory for the extracted product folders.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Note that each .zip file has a folder inside to keep all of the files for that product together, so the resulting extractions will be a set of folders (one for each product) in the destination directory.&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;"&gt;To set default values for this parameter, click on the Tool Help button at the bottom of the tool dialog box to display instructions (refer to Python Reference section).&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</dialogReference><pythonReference>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;If you would like to set a default value for this parameter, you may edit the ASF_Tools.pyt script to add in your default destination folder.&lt;/SPAN&gt;&lt;/P&gt;&lt;OL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Right-click on the ASF_Tools.pyt toolbox in ArcCatalog (or the Catalog window in ArcMap) and select Edit...&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Scroll down to outlocation.value (line 57) and enter your download folder path between the single quotes (refer to comments in the script for an example)&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Save the script (select Save from the File menu in the script editor), then close the editor window&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Double-click the ASF_Tools.pyt toolbox in ArcCatalog (or the Catalog window in ArcMap) to expand the toolbox, and open the Unzip Files tool&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;If you have double-clicked the toolbox and the change is not reflected in the tool dialog, close and re-open your ArcGIS application (ArcMap or ArcCatalog)&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/OL&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</pythonReference></param></parameters><summary>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This tool unzips ASF products and saves the extracted folders in the desired destination folder. This is particularly useful if you are downloading multiple &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC files from Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; and would like to extract them to a product folder.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;All .zip files in the download folder will be extracted&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;Each product will have its own folder within the destination directory (when extracting ASF files, or other zip files that have a single root directory containing all the files within the zip archive)&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN&gt;The original .zip files in the download folder will each be deleted once file extraction is complete&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN STYLE="font-style:italic;font-weight:bold;"&gt;NOTE: &lt;/SPAN&gt;&lt;SPAN STYLE="font-style:italic;"&gt;If you have downloaded GRD or SLC products and will be using the SNAP Sentinel-1 Toolbox to process the products, &lt;/SPAN&gt;&lt;SPAN STYLE="font-style:italic;font-weight:bold;"&gt;do not &lt;/SPAN&gt;&lt;SPAN STYLE="font-style:italic;"&gt;unzip the product downloads using this tool. S1TBX is designed to work within the .zip folder structure, and this tool deletes the .zip files once they are extracted.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</summary><usage>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Many ASF products are served as zip files, including &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Sentinel-1 RTC products&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; generated using the On-Demand functionality in &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/using/vertex/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;. Each product zip file contains another folder inside, so when the files are extracted, the individual component product files will still be contained within a uniquely-named product folder. Because the zip file and folder names are often long, standard Windows zip utilities may encounter errors if the full product pathway contains more characters than is allowed.  &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;This tool offers a quick and easy way to extract multiple zip files to the desired destination folder and delete the original zip files from the download folder. While designed to help manage ASF downloads, this tool can be used to extract any collection of zip files.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;If you have downloaded Sentinel-1 GRD or SLC files, and will be using the SNAP Sentinel-1 Toolbox to process the images, you will need to retain the .zip files. If you still want to use this tool to unzip the product files, make sure you've copied the original zip files to another folder first, because the .zip files will be deleted once extracted.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</usage><scriptExamples><scriptExample><title>ASF_Tools.pyt UnzipFiles</title><para>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This is the code for the UnzipFiles tool class within the ASF_Tools.pyt script.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</para><code>class UnzipFiles(object):
     def __init__(self):
 
         """Unzips ASF products downloaded from Vertex or HyP3."""
diff --git a/ArcGIS-toolbox/ASF_Tools.pyt b/ArcGIS-toolbox/ASF_Tools.pyt
index 5a3a3c3c..8a0b40f6 100644
--- a/ArcGIS-toolbox/ASF_Tools.pyt
+++ b/ArcGIS-toolbox/ASF_Tools.pyt
@@ -109,9 +109,9 @@ class UnzipFiles(object):
 class ScaleConversion(object):
     def __init__(self):
 
-        """Converts RTC products from power to amplitude"""
+        """Converts RTC products between Power, Amplitude, and dB scales"""
         self.label = "Scale Conversion (Power, Amplitude, dB)"
-        self.description = "This tool converts RTC products from Power or Amplitude scale to a different scale " \
+        self.description = "This tool converts RTC products from Power, Amplitude, or dB scale to a different scale " \
                            "(Power, Amplitude or dB)."
         self.canRunInBackground = True
 
@@ -129,18 +129,18 @@ class ScaleConversion(object):
         # Second parameter: input scale
         p_inscale = arcpy.Parameter(
             name="p_inscale",
-            displayName="Scale of input raster (power or amplitude)",
+            displayName="Scale of input raster (power, amplitude, or dB)",
             datatype="GPString",
             parameterType="Required",
             direction="Input")
 
         p_inscale.filter.type = "ValueList"
-        p_inscale.filter.list = ["Power", "Amplitude"]
+        p_inscale.filter.list = ["Power", "Amplitude", "dB"]
 
         # Third parameter: output scale
         p_outscale = arcpy.Parameter(
             name="p_outscale",
-            displayName="Scale of output raster (power, amplitude or dB)",
+            displayName="Scale of output raster (power, amplitude, or dB)",
             datatype="GPString",
             parameterType="Required",
             direction="Input")
@@ -209,6 +209,17 @@ class ScaleConversion(object):
                 out_t10 = arcpy.sa.Times(out_log10, 10)
                 out_t10.save(self.outpath)
 
+        elif self.inscale == 'dB':
+            if self.outscale == 'Power':
+                out_d10 = arcpy.sa.Divide(self.inpath, 10)
+                out_exp = arcpy.sa.Power(10, out_d10)
+                out_exp.save(self.outpath)
+
+            elif self.outscale == 'Amplitude':
+                out_d20 = arcpy.sa.Divide(self.inpath, 20)
+                out_exp = arcpy.sa.Power(10, out_d20)
+                out_exp.save(self.outpath)
+
         else:
             arcpy.AddMessage('Parameters entered incorrectly; no conversion performed.')
 
@@ -227,7 +238,7 @@ class ScaleConversion(object):
                     arcpy.AddMessage("Spatial Analyst Extension has been checked out.")
                 else:
                     arcpy.AddMessage("Spatial Analyst Extension is not available for use.")
-        except Exception:
+        except Exception:  # noqa: B902
             arcpy.AddMessage(
                 "Spatial Analyst extension is not available for use. Check your licensing to make sure you have "
                 "access to this extension.")
@@ -254,6 +265,8 @@ class ScaleConversion(object):
                 insc = 'Amplitude'
             elif inscale == 'p':
                 insc = 'Power'
+            elif inscale == 'd':
+                insc = 'dB'
             else:
                 insc = ''
             if not parameters[1].altered:
@@ -395,7 +408,7 @@ class ReclassifyRTC(object):
                     arcpy.AddMessage("Spatial Analyst Extension has been checked out.")
                 else:
                     arcpy.AddMessage("Spatial Analyst Extension is not available for use.")
-        except Exception:
+        except Exception:  # noqa: B902
             arcpy.AddMessage(
                 "Spatial Analyst extension is not available for use. Check your licensing to make sure you have "
                 "access to this extension.")
@@ -546,7 +559,7 @@ class LogDiff(object):
                     arcpy.AddMessage("Spatial Analyst Extension has been checked out.")
                 else:
                     arcpy.AddMessage("Spatial Analyst Extension is not available for use.")
-        except Exception:
+        except Exception:  # noqa: B902
             arcpy.AddMessage(
                 "Spatial Analyst extension is not available for use. "
                 "Check your licensing to make sure you have access to this extension.")
@@ -643,13 +656,13 @@ class RGBDecomp(object):
         # Second parameter: scale of input dataset
         scale = arcpy.Parameter(
             name="scale",
-            displayName="Scale of input RTC (Amplitude or Power)",
+            displayName="Scale of input RTC (Power, Amplitude, or dB)",
             datatype="GPString",
             parameterType="Required",
             direction="Input")
 
         scale.filter.type = "ValueList"
-        scale.filter.list = ["Power", "Amplitude"]
+        scale.filter.list = ["Power", "Amplitude", "dB"]
 
         # Third parameter: Primary polarization
         pol = arcpy.Parameter(
@@ -722,7 +735,7 @@ class RGBDecomp(object):
                     arcpy.AddMessage("Spatial Analyst Extension has been checked out.")
                 else:
                     arcpy.AddMessage("Spatial Analyst Extension is not available for use.")
-        except Exception:
+        except Exception:  # noqa: B902
             arcpy.AddMessage(
                 "Spatial Analyst extension is not available for use. Check your licensing to make sure you have "
                 "access to this extension.")
@@ -743,6 +756,8 @@ class RGBDecomp(object):
                 insc = 'Amplitude'
             elif inscale == 'p':
                 insc = 'Power'
+            elif inscale == 'd':
+                insc = 'dB'
             else:
                 insc = ''
             if not parameters[1].altered:
@@ -838,13 +853,18 @@ class RGBDecomp(object):
             xps = arcpy.sa.Square(xp)
             arcpy.AddMessage("Input scale is amplitude. Converted to power scale.")
 
+        elif scale == 'dB':
+            cps = arcpy.sa.Power(10, arcpy.sa.Divide(cp, 10))
+            xps = arcpy.sa.Power(10, arcpy.sa.Divide(xp, 10))
+            arcpy.AddMessage("Input scale is dB. Converted to power scale.")
+
         elif scale == 'Power':
             cps = cp
             xps = xp
             arcpy.AddMessage("Input scale is power. No conversion necessary.")
 
         else:
-            arcpy.AddError("Input scale must be set to Amplitude or Power.")
+            arcpy.AddError("Input scale must be set to Power, Amplitude, or dB.")
             sys.exit(0)
 
         arcpy.AddMessage("Input rasters have been defined. Running pixel cleanup routine...")
diff --git a/ArcGIS-toolbox/ASF_Tools.pyt.xml b/ArcGIS-toolbox/ASF_Tools.pyt.xml
index 8f0b93dd..ac9f414f 100644
--- a/ArcGIS-toolbox/ASF_Tools.pyt.xml
+++ b/ArcGIS-toolbox/ASF_Tools.pyt.xml
@@ -1,5 +1,10 @@
 <?xml version="1.0"?>
-<metadata xml:lang="en"><Esri><CreaDate>20191111</CreaDate><CreaTime>16262600</CreaTime><ArcGISFormat>1.0</ArcGISFormat><ArcGISstyle>North American Profile of ISO19115 2003</ArcGISstyle><SyncOnce>TRUE</SyncOnce><ModDate>20191202</ModDate><ModTime>152850</ModTime><scaleRange><minScale>150000000</minScale><maxScale>5000</maxScale></scaleRange><ArcGISProfile>NAP</ArcGISProfile><DataProperties><itemProps><imsContentType export="False">006</imsContentType></itemProps></DataProperties></Esri><toolbox name="ASF_Tools" alias="ASF Tools"><arcToolboxHelpPath>c:\program files (x86)\arcgis\desktop10.6\Help\gp</arcToolboxHelpPath><toolsets/></toolbox><dataIdInfo><idCitation xmlns=""><resTitle>ASF_Tools</resTitle><citId xmlns=""><identCode>ASF_Tools_191114</identCode></citId><date><createDate>2019-11-14T00:00:00</createDate></date></idCitation><idPurp>This Python Toolbox contains geoprocessing tools for working with Synthetic Aperture Radar data in ArcGIS. It was developed in ArcGIS Desktop 10.6.1, and has been tested with ArcGIS Desktop 10.6.1 and 10.7.1 and ArcGIS Pro 2.4.2, 2.5.X and 2.6.X. Compatibility with earlier versions is not guaranteed.</idPurp><idCredit>Alaska Satellite Facility</idCredit><idAbs>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This Python Toolbox contains geoprocessing tools for working with Synthetic Aperture Radar (SAR) data in ArcGIS Desktop. &lt;/SPAN&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN&gt;The tools were developed for use with Radiometric Terrain Corrected (RTC) datasets produced by ASF using the HyP3 platform. Sentinel-1 RTC products generated using HyP3 are now available &lt;/SPAN&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;on demand from ASF's Data Search - Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; portal at no cost to the user. In many cases, however, the tools may be used with calibrated SAR images (in amplitude or power scale) of any origin. Some of the tools may also be useful for non-SAR single-band raster datasets.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;Unzip Files tool&lt;/SPAN&gt;&lt;SPAN&gt;: assists in file management when downloading .zip files from ASF. This tool could be used to extract to a specified location any zip files with an additional internal directory containing the individual files. &lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;Scale Conversion tool&lt;/SPAN&gt;&lt;SPAN&gt;: allows easy conversion of pixel values in RTC rasters from power or amplitude scale into power, amplitude or dB scale. This is an application specific to SAR data, but does not require a HyP3 output; it could be used with any calibrated SAR dataset using either a power or amplitude scale. *REQUIRES SPATIAL ANALYST EXTENSION*&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;Reclassify RTC tool&lt;/SPAN&gt;&lt;SPAN&gt;: generates a raster that includes only those pixels below a user-defined threshold value, and is designed for identifying water pixels. This tool could be used for any application where the user is interested in identifying values below a given threshold. *REQUIRES SPATIAL ANALYST EXTENSION"&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;Log Difference tool&lt;/SPAN&gt;&lt;SPAN&gt;: compares two rasters by calculating the log difference on a pixel-by-pixel basis to identify areas where backscatter values have changed over time. This tool could be used to compare any two single-band raster layers that only include positive pixel values; it is not specific to SAR data. *REQUIRES SPATIAL ANALYST EXTENSION*&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;RGB Decomposition tool&lt;/SPAN&gt;&lt;SPAN&gt;: generates an RGB image using the co- and cross-polarized datasets from an RTC product. Input datasets can be in either amplitude or power scale, and primary polarization can be either V (VV/VH) or H (HH/HV). *REQUIRES SPATIAL ANALYST EXTENSION*&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;To use the tools that require the Spatial Analyst Extension, your installation of ArcGIS Desktop must have licensing that covers this extension. To check your extension, select Customize from the ArcGIS menu and click Extensions.... Check the box next to Spatial Analyst. If that extension is not available to be checked, you will not be able to use the tools requiring this extension.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Any of the tools that generate an output raster have the option to automatically add the output to the map. This option is applied by default, but can be turned off if desired by removing the check next to the "Add output to map" option in the dialog box.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;=====&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;RTC Resources from ASF&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;A href="https://asfhyp3.github.io:443/using/vertex/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC On-Demand Processing for Sentinel-1&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; is now available from ASF's Data Search - Vertex portal. Simply search for scenes and submit them for processing, and they will be processed using ASF's HyP3 cloud computing platform. You can then download them for use.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;When using RTC products generated by HyP3, the GeoTIFFs containing the actual backscatter data are tagged with the polarization (VV, VH, HV, or HH). These are the raster datasets to use with these geoprocessing tools. Several other products are included in the HyP3 product zip file; consult the README textfile included in the zip file for more information. Each raster included in the zip file also has ArcGIS-compliant metadata providing additional raster-specific information, which can be viewed in the Item Description in the ArcMap Catalog window or the Description tab in ArcCatalog or ArcGIS Pro.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;There are &lt;/SPAN&gt;&lt;A href="https://search.asf.alaska.edu:443/#/?flightDirs=&amp;amp;dataset=ALOS&amp;amp;subtypes=ALOS$$&amp;amp;productTypes=ALOS$$RTC_LOW_RES,RTC_HI_RES&amp;amp;beamModes=ALOS$$&amp;amp;polarizations=ALOS$$" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC products for the PALSAR-1 mission&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; (2006-2011) available for download at &lt;/SPAN&gt;&lt;A href="http://search.asf.alaska.edu:80/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;ASF's Data Search - Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;. These products are in power scale. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;A href="http://asf.alaska.edu:80/data-recipes/data-recipe-tutorials" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Instructions for generating RTC products&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; using other software packages are available in Data Recipe format from ASF: &lt;/SPAN&gt;&lt;A href="https://www.asf.alaska.edu/data-recipes/data-recipe-tutorials/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;asf.alaska.edu/data-recipes/data-recipe-tutorials&lt;/SPAN&gt;&lt;/A&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;A href="https://www.asf.alaska.edu/contact/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Contact ASF&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; if you have questions or comments about this toolbox or other products and services offered by ASF.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</idAbs><idPoC xmlns=""><rpIndName>Heidi Kristenson</rpIndName><rpOrgName>Alaska Satellite Facility</rpOrgName><rpPosName>GIS Specialist</rpPosName><role><RoleCd value="011"/></role><rpCntInfo xmlns=""><cntAddress addressType=""><eMailAdd>hjkristenson@alaska.edu</eMailAdd></cntAddress></rpCntInfo><displayName>Heidi Kristenson</displayName></idPoC><searchKeys><keyword>ASF</keyword><keyword>Alaska Satellite Facility</keyword><keyword>SAR</keyword><keyword>Synthetic Aperture Radar</keyword><keyword>ArcGIS Desktop</keyword><keyword>Python Toolbox</keyword></searchKeys><dataChar><CharSetCd value="004"/></dataChar><productKeys xmlns=""><keyword>Synthetic Aperture Radar</keyword></productKeys><dataLang><languageCode value="eng"/><countryCode value="US"/></dataLang><resConst><Consts xmlns=""><useLimit>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Note that many of the tools require the Spatial Analyst extension. If your ArcGIS installation does not include licensing for Spatial Analyst, you will not be able to use all of the tools included in the toolbox.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</useLimit></Consts></resConst><tpCat><TopicCatCd value="007"/></tpCat><tpCat><TopicCatCd value="008"/></tpCat></dataIdInfo><distInfo xmlns=""><distributor xmlns=""><distorFormat xmlns=""><formatName>ArcToolbox Toolbox</formatName><formatVer>10.6.1</formatVer></distorFormat><distorCont xmlns=""><rpIndName>Heidi Kristenson</rpIndName><rpOrgName>Alaska Satellite Facility</rpOrgName><rpPosName>GIS Specialist</rpPosName><role><RoleCd value="011"/></role><rpCntInfo><cntAddress addressType=""><eMailAdd>hjkristenson@alaska.edu</eMailAdd></cntAddress></rpCntInfo><displayName>Heidi Kristenson</displayName><displayName>Heidi Kristenson</displayName></distorCont></distributor><distFormat xmlns=""><formatName>ArcGIS Python Toolbox</formatName><formatVer>10.6.1</formatVer></distFormat></distInfo><mdHrLv><ScopeCd value="013"/></mdHrLv><mdChar><CharSetCd value="004"/></mdChar><mdContact xmlns=""><rpIndName>Heidi Kristenson</rpIndName><rpOrgName>Alaska Satellite Facility</rpOrgName><rpPosName>GIS Specialist</rpPosName><role><RoleCd value="011"/></role><rpCntInfo xmlns=""><cntAddress addressType=""><eMailAdd>hjkristenson@alaska.edu</eMailAdd></cntAddress></rpCntInfo></mdContact><Binary><Thumbnail><Data EsriPropertyType="PictureX">/9j/4AAQSkZJRgABAQEAkACQAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK
+<metadata xml:lang="en"><Esri><CreaDate>20191111</CreaDate><CreaTime>16262600</CreaTime><ArcGISFormat>1
+    .0</ArcGISFormat><ArcGISstyle>North American Profile of ISO19115 2003</ArcGISstyle><SyncOnce>TRUE
+</SyncOnce><ModDate>20191202</ModDate><ModTime>152850</ModTime><scaleRange><minScale>150000000</minScale><maxScale>
+    5000</maxScale></scaleRange><ArcGISProfile>NAP</ArcGISProfile><DataProperties><itemProps><imsContentType
+        export="False">006</imsContentType></itemProps></DataProperties></Esri><toolbox name="ASF_Tools"
+                                                                                        alias="ASF Tools"><arcToolboxHelpPath>c:\program files (x86)\arcgis\desktop10.6\Help\gp</arcToolboxHelpPath><toolsets/></toolbox><dataIdInfo><idCitation xmlns=""><resTitle>ASF_Tools</resTitle><citId xmlns=""><identCode>ASF_Tools_191114</identCode></citId><date><createDate>2019-11-14T00:00:00</createDate></date></idCitation><idPurp>This Python Toolbox contains geoprocessing tools for working with Synthetic Aperture Radar data in ArcGIS. It was developed in ArcGIS Desktop 10.6.1, and has been tested with ArcGIS Desktop 10.6.1 and 10.7.1 and ArcGIS Pro 2.4.2, 2.5.X and 2.6.X. Compatibility with earlier versions is not guaranteed.</idPurp><idCredit>Alaska Satellite Facility</idCredit><idAbs>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;This Python Toolbox contains geoprocessing tools for working with Synthetic Aperture Radar (SAR) data in ArcGIS Desktop. &lt;/SPAN&gt;&lt;/P&gt;&lt;P STYLE="margin:1 1 1 0;"&gt;&lt;SPAN&gt;The tools were developed for use with Radiometric Terrain Corrected (RTC) datasets produced by ASF using the HyP3 platform. &lt;/SPAN&gt;&lt;SPAN STYLE="font-weight:bold;"&gt; &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Sentinel-1 RTC products generated using HyP3&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; are now available &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/using/vertex/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;on demand from ASF's Data Search - Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; portal at no cost to the user. In many cases, however, the tools may be used with calibrated SAR images (in amplitude, power, or db scale) of any origin. Some of the tools may also be useful for non-SAR single-band raster datasets.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;Unzip Files tool&lt;/SPAN&gt;&lt;SPAN&gt;: assists in file management when downloading .zip files from ASF. This tool could be used to extract to a specified location any zip files with an additional internal directory containing the individual files. &lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;Scale Conversion tool&lt;/SPAN&gt;&lt;SPAN&gt;: allows easy conversion of pixel values in RTC rasters from power, amplitude, or dB scale into power, amplitude or dB scale. This is an application specific to SAR data, but does not require a HyP3 output; it could be used with any calibrated SAR dataset using either a power, amplitude, or dB scale. *REQUIRES SPATIAL ANALYST EXTENSION*&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;Reclassify RTC tool&lt;/SPAN&gt;&lt;SPAN&gt;: generates a raster that includes only those pixels below a user-defined threshold value, and is designed for identifying water pixels. This tool could be used for any application where the user is interested in identifying values below a given threshold. *REQUIRES SPATIAL ANALYST EXTENSION"&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;Log Difference tool&lt;/SPAN&gt;&lt;SPAN&gt;: compares two rasters by calculating the log difference on a pixel-by-pixel basis to identify areas where backscatter values have changed over time. This tool could be used to compare any two single-band raster layers that only include positive pixel values; it is not specific to SAR data. *REQUIRES SPATIAL ANALYST EXTENSION*&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;LI&gt;&lt;P&gt;&lt;SPAN STYLE="font-weight:bold;"&gt;RGB Decomposition tool&lt;/SPAN&gt;&lt;SPAN&gt;: generates an RGB image using the co- and cross-polarized datasets from an RTC product. Input datasets can be in either amplitude, power, or dB scale, and primary polarization can be either V (VV/VH) or H (HH/HV). *REQUIRES SPATIAL ANALYST EXTENSION*&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;To use the tools that require the Spatial Analyst Extension, your installation of ArcGIS Desktop must have licensing that covers this extension. To check your extension, select Customize from the ArcGIS menu and click Extensions.... Check the box next to Spatial Analyst. If that extension is not available to be checked, you will not be able to use the tools requiring this extension.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Any of the tools that generate an output raster have the option to automatically add the output to the map. This option is applied by default, but can be turned off if desired by removing the check next to the "Add output to map" option in the dialog box.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;=====&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;RTC Resources from ASF&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC On-Demand Processing for Sentinel-1&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; is now available from &lt;/SPAN&gt;&lt;A href="https://hyp3-docs.asf.alaska.edu/using/vertex/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;ASF's Data Search - Vertex portal&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;. Simply search for scenes and submit them for processing, and they will be processed using ASF's HyP3 cloud computing platform. You can then download them for use.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;When using RTC products generated by HyP3, the GeoTIFFs containing the actual backscatter data are tagged with the polarization (VV, VH, HV, or HH). These are the raster datasets to use with these geoprocessing tools. Several other products are included in the HyP3 product zip file; consult the README textfile included in the zip file for more information. Each raster included in the zip file also has ArcGIS-compliant metadata providing additional raster-specific information, which can be viewed in the Item Description in the ArcMap Catalog window or the Description tab in ArcCatalog or ArcGIS Pro.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;There are &lt;/SPAN&gt;&lt;A href="https://search.asf.alaska.edu:443/#/?flightDirs=&amp;amp;dataset=ALOS&amp;amp;subtypes=ALOS$$&amp;amp;productTypes=ALOS$$RTC_LOW_RES,RTC_HI_RES&amp;amp;beamModes=ALOS$$&amp;amp;polarizations=ALOS$$" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;RTC products for the PALSAR-1 mission&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; (2006-2011) available for download at &lt;/SPAN&gt;&lt;A href="http://search.asf.alaska.edu:80/" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;ASF's Data Search - Vertex&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;. These products are in power scale. &lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;A href="http://asf.alaska.edu:80/data-recipes/data-recipe-tutorials" target="_blank" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Instructions for generating RTC products&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; using other software packages are available in Data Recipe format from ASF: &lt;/SPAN&gt;&lt;A href="https://www.asf.alaska.edu/data-recipes/data-recipe-tutorials/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;asf.alaska.edu/data-recipes/data-recipe-tutorials&lt;/SPAN&gt;&lt;/A&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;-----&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;A href="https://www.asf.alaska.edu/contact/" STYLE="text-decoration:underline;"&gt;&lt;SPAN&gt;Contact ASF&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; if you have questions or comments about this toolbox or other products and services offered by ASF.&lt;/SPAN&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</idAbs><idPoC xmlns=""><rpIndName>Heidi Kristenson</rpIndName><rpOrgName>Alaska Satellite Facility</rpOrgName><rpPosName>GIS Specialist</rpPosName><role><RoleCd value="011"/></role><rpCntInfo xmlns=""><cntAddress addressType=""><eMailAdd>hjkristenson@alaska.edu</eMailAdd></cntAddress></rpCntInfo><displayName>Heidi Kristenson</displayName></idPoC><searchKeys><keyword>ASF</keyword><keyword>Alaska Satellite Facility</keyword><keyword>SAR</keyword><keyword>Synthetic Aperture Radar</keyword><keyword>ArcGIS Desktop</keyword><keyword>Python Toolbox</keyword></searchKeys><dataChar><CharSetCd value="004"/></dataChar><productKeys xmlns=""><keyword>Synthetic Aperture Radar</keyword></productKeys><dataLang><languageCode value="eng"/><countryCode value="US"/></dataLang><resConst><Consts xmlns=""><useLimit>&lt;DIV STYLE="text-align:Left;"&gt;&lt;DIV&gt;&lt;DIV&gt;&lt;P&gt;&lt;SPAN&gt;Note that many of the tools require the Spatial Analyst extension. If your ArcGIS installation does not include licensing for Spatial Analyst, you will not be able to use all of the tools included in the toolbox.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN /&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;/DIV&gt;</useLimit></Consts></resConst><tpCat><TopicCatCd value="007"/></tpCat><tpCat><TopicCatCd value="008"/></tpCat></dataIdInfo><distInfo xmlns=""><distributor xmlns=""><distorFormat xmlns=""><formatName>ArcToolbox Toolbox</formatName><formatVer>10.6.1</formatVer></distorFormat><distorCont xmlns=""><rpIndName>Heidi Kristenson</rpIndName><rpOrgName>Alaska Satellite Facility</rpOrgName><rpPosName>GIS Specialist</rpPosName><role><RoleCd value="011"/></role><rpCntInfo><cntAddress addressType=""><eMailAdd>hjkristenson@alaska.edu</eMailAdd></cntAddress></rpCntInfo><displayName>Heidi Kristenson</displayName><displayName>Heidi Kristenson</displayName></distorCont></distributor><distFormat xmlns=""><formatName>ArcGIS Python Toolbox</formatName><formatVer>10.6.1</formatVer></distFormat></distInfo><mdHrLv><ScopeCd value="013"/></mdHrLv><mdChar><CharSetCd value="004"/></mdChar><mdContact xmlns=""><rpIndName>Heidi Kristenson</rpIndName><rpOrgName>Alaska Satellite Facility</rpOrgName><rpPosName>GIS Specialist</rpPosName><role><RoleCd value="011"/></role><rpCntInfo xmlns=""><cntAddress addressType=""><eMailAdd>hjkristenson@alaska.edu</eMailAdd></cntAddress></rpCntInfo></mdContact><Binary><Thumbnail><Data EsriPropertyType="PictureX">/9j/4AAQSkZJRgABAQEAkACQAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK
 CwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQU
 FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCABjAMADASIA
 AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA
diff --git a/ArcGIS-toolbox/README.md b/ArcGIS-toolbox/README.md
index 4ec075c5..fffce17c 100644
--- a/ArcGIS-toolbox/README.md
+++ b/ArcGIS-toolbox/README.md
@@ -1,7 +1,10 @@
 ArcGIS Toolbox
 ==============
-The [ASF_Tools ArcGIS Python Toolbox](https://asf.alaska.edu/how-to/data-tools/gis-tools/) can be used with either ArcGIS Desktop or ArcGIS Pro, and contains tools that perform geoprocessing tasks useful for working with Synthetic Aperture Radar (SAR) data. The tools were designed to be used with [Sentinel-1 Radiometric Terrain Corrected (RTC) SAR datasets](https://asfhyp3.github.io/using/vertex), such as those available on-demand using ASF's [Data Search - Vertex](https://search.asf.alaska.edu/) portal, but several of the tools have the potential to be used with a variety of rasters, including non-SAR datasets.
 
+[![DOI](https://zenodo.org/badge/295506894.svg)](https://zenodo.org/badge/latestdoi/295506894)
+
+The [ASF_Tools ArcGIS Python Toolbox](https://asf.alaska.edu/how-to/data-tools/gis-tools/) can be used with either ArcGIS Desktop or ArcGIS Pro, and contains tools that perform geoprocessing tasks useful for working with Synthetic Aperture Radar (SAR) data. The tools were designed to be used with [Sentinel-1 Radiometric Terrain Corrected (RTC) SAR datasets](https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/), such as those available on-demand using ASF's [Data Search - Vertex](https://hyp3-docs.asf.alaska.edu/using/vertex/) portal, but several of the tools have the potential to be used with a variety of rasters, including non-SAR datasets.
+https://hyp3-docs.asf.alaska.edu/using/vertex/
 The Toolbox is distributed as a zipped archive including the .pyt Toolbox script and associated .xml files. There is an XML file for the toolbox itself and one for each of the tools it contains. These XML files contain the metadata displayed in the item descriptions and tool help windows in ArcGIS, and must be kept in the same directory as the Python Toolbox (.pyt) file, or the information they contain will no longer be accessible.
 
 ### Toolbox Contents
@@ -10,7 +13,7 @@ The Toolbox is distributed as a zipped archive including the .pyt Toolbox script
 This tool assists in file management when downloading .zip files from ASF. It could be used to extract to a specified location any zip files with an additional internal directory containing the individual files. The tool deletes the original zip files once they are extracted, and is especially helpful when dealing with file paths that are so long that they are beyond the maximum allowed in default Windows unzip utilities.
 
 #### Scale Conversion Tool 
-This tool converts pixel values in calibrated SAR datasets (such as RTC rasters) from power or amplitude scale into power, amplitude or dB scale. This is an application specific to SAR data values/scales.
+This tool converts pixel values in calibrated SAR datasets (such as RTC rasters) from power, amplitude or dB scale into power, amplitude or dB scale. This is an application specific to SAR data values/scales.
 
 #### Reclassify RTC Tool
 This tool generates a raster that includes only those pixels below a user-defined threshold value, and is designed for isolating water pixels. While intended for RTC files in dB scale, this tool could be used for any application where the user is interested in generating a spatial mask for values below a given threshold in a single-band raster.
@@ -19,7 +22,7 @@ This tool generates a raster that includes only those pixels below a user-define
 This tool compares two rasters by calculating the log difference on a pixel-by-pixel basis to identify areas where backscatter values have changed over time. While intended for RTC files in amplitude scale, this tool could be used to compare the pixel values of any two single-band rasters, as long as there are no negative values (NoData values will be returned for pixels with a negative number in either of the datasets).
 
 #### RGB Decomposition Tool
-This tool generates an RGB image using the co- and cross-polarized datasets from an RTC product. Input datasets can be in either amplitude or power scale, and the primary polarization can be either vertical (VV/VH) or horizontal (HH/HV). [Additional documentation](https://github.com/ASFHyP3/hyp3-lib/blob/develop/docs/rgb_decomposition.md) is available regarding the calculations used and the interpretation of these false-color images.
+This tool generates an RGB image using the co- and cross-polarized datasets from an RTC product. Input datasets can be in power, amplitude or dB scale, and the primary polarization can be either vertical (VV/VH) or horizontal (HH/HV). [Additional documentation](https://github.com/ASFHyP3/hyp3-lib/blob/develop/docs/rgb_decomposition.md) is available regarding the calculations used and the interpretation of these false-color images.
 
 ### Prerequisites
 Users must have either ArcGIS Desktop (ArcMap) or ArcGIS Pro installed and licensed on their computer. The Toolbox has been tested with Desktop versions 10.6.1 and 10.7.1 and Pro versions 2.4.2, 2.5.x and 2.6.1, but it may work with earlier versions as well.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 851d86fa..6cc7cecb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [PEP 440](https://www.python.org/dev/peps/pep-0440/) 
 and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.3.0](https://github.com/ASFHyP3/asf-tools/compare/v0.2.0...v0.3.0)
+
+### Added
+* `asf_tools.water_map` and an associated `water_map` entrypoint for making
+  water extent maps using a multi-mode Expectation Maximization approach and refined using Fuzzy Logic
+* `asf_tools.hand` sub package containing:
+  * `asf_tools.hand.calculate` and an associated `calculate_hand` entrypoint for calculating Height Above 
+    Nearest Drainage (HAND) from the Copernicus GLO-30 DEM
+  * `asf_tools.hand.prepare` to prepare a raster from the Copernicus GLO-30 DEM derived Global HAND tiles
+* `asf_tools.dem` to prepare a virtual raster (VRT) mosaic of the Copernicus GLO-30 DEM tiles
+* `expectation_maximization_threshold` in `asf_tools.threshold` to calculate water threshold value
+  using a multi-mode Expectation Maximization approach
+* `tile_array` and `untile_array` in `asf_tools.tile` to transform a numpy array into a set of tiles
+* `convert_scale` in `asf_tools.raster` to transform calibrated raster between decibel, power, and amplitude scales
+
+### Changed
+* ASF_Tools ArcGIS toolbox now accepts inputs in dB (decibel) scale for the RGBDecomp and ScaleConversion tools. 
+
 ## [0.2.0](https://github.com/ASFHyP3/asf-tools/compare/v0.1.1...v0.2.0)
 
 ### Added
diff --git a/README.md b/README.md
index fefe0c40..74b27369 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,8 @@ ASF Tools
 **_A collection of tools for working with higher-level SAR products_**
 -----
 
+[![DOI](https://zenodo.org/badge/295506894.svg)](https://zenodo.org/badge/latestdoi/295506894)
+
 ## ArcGIS Toolbox
 The [ArcGIS Python Toolbox](ArcGIS-toolbox) can be used with either ArcGIS Desktop or 
 ArcGIS Pro, and contains tools that perform geoprocessing tasks useful for working
diff --git a/asf_tools/README.md b/asf_tools/README.md
index 379c6d9c..c80acc47 100644
--- a/asf_tools/README.md
+++ b/asf_tools/README.md
@@ -1,4 +1,4 @@
-## ASF Tools for Python
+# ASF Tools for Python
 
 [![PyPI license](https://img.shields.io/pypi/l/asf_tools.svg)](https://pypi.python.org/pypi/asf_tools/)
 [![PyPI pyversions](https://img.shields.io/pypi/pyversions/asf_tools.svg)](https://pypi.python.org/pypi/asf_tools/)
@@ -6,6 +6,8 @@
 [![Conda version](https://img.shields.io/conda/vn/conda-forge/asf_tools)](https://anaconda.org/conda-forge/asf_tools)
 [![Conda platforms](https://img.shields.io/conda/pn/conda-forge/asf_tools)](https://anaconda.org/conda-forge/asf_tools)
 
+[![DOI](https://zenodo.org/badge/295506894.svg)](https://zenodo.org/badge/latestdoi/295506894)
+
 `asf_tools` is a Python package for working with Synthetic Aperture Radar (SAR) data.
 It was designed for working with datasets generated by
 [HyP3](https://hyp3-docs.asf.alaska.edu/), but several of the tools have the
@@ -14,13 +16,18 @@ potential to be used with a variety of rasters, including non-SAR datasets.
 
 ## Install
 
-`asf_tools` can be installed via [Anaconda/Miniconda](https://docs.conda.io/en/latest/index.html):
+In order to easily manage dependencies, we recommend using dedicated project
+environments via [Anaconda/Miniconda](https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html).
+It is also possible to use [Python virtual environments](https://docs.python.org/3/tutorial/venv.html),
+but installation of non-python dependencies (e.g., `gdal`) can be challenging. 
+
+`asf_tools` can be installed into a conda environment with
 
 ```
 conda install -c conda-forge asf_tools
 ```
 
-Or using [`pip`](https://pypi.org/project/asf_tools/):
+or into a virtual environment with
 
 ```
 python -m pip install asf_tools
@@ -30,23 +37,64 @@ python -m pip install asf_tools
 
 ### Local Resolution Weighted Composite
 
-The `make_composite` tool allows you to create a local-resolution-weighted composite from a set of Sentinel-1 RTC
-products ([D. Small, 2012](https://doi.org/10.1109/IGARSS.2012.6350465)). It is intended to be used with RTC products generated by [ASF HyP3](https://hyp3-docs.asf.alaska.edu/using/vertex).
+The `make_composite` tool allows you to create a local-resolution-weighted composite
+from a set of Sentinel-1 RTC products ([D. Small, 2012](https://doi.org/10.1109/IGARSS.2012.6350465)).
+It is intended to be used with RTC products generated by [ASF HyP3](https://hyp3-docs.asf.alaska.edu/using/vertex).
 
-You will need to request RTC products using the `Include Scattering Area` option, then download and unzip them into an empty directory.
+You will need to request RTC products using the `Include Scattering Area` option,
+then download and unzip them into an empty directory.
 
-To generate a composite of the co-polarization images, navigate to the directory containing the unzipped RTC products and run:
+To generate a composite of the co-polarization images, navigate to the directory
+containing the unzipped RTC products and run:
 ```
 make_composite VV-composite */*VV.tif
 ```
 
-To generate a composite of the cross-polarization images, navigate to the directory containing the unzipped RTC products and run:
+To generate a composite of the cross-polarization images, navigate to the directory
+containing the unzipped RTC products and run:
 ```
 make_composite VH-composite */*VH.tif
 ```
 
 #### Usage Tip
-Because the imagery has been radiometrically terrain corrected (RTC), geometric and radiometric distortions have been removed from the files to be composited.  One the strong points of LRW composites is that you combine both ascending and descending datatakes into a single product.  In this manner no layover or shadow masks are required - what is shadowed on an ascending pass is visible in a descending pass and vice-versa.  Thus, not only is it possible to combine ascending and descending, but it is highly encouraged.  Using many datatakes from both the ascending and descending satellite passes will make the best composites possible.
+Because the imagery has been radiometrically terrain corrected (RTC), geometric and
+radiometric distortions have been removed from the files to be composited. One the
+strong points of LRW composites is that you combine both ascending and descending
+datatakes into a single product. In this manner no layover or shadow masks are
+required - what is shadowed on an ascending pass is visible in a descending pass
+and vice-versa. Thus, not only is it possible to combine ascending and descending,
+but it is highly encouraged. Using many datatakes from both the ascending and
+descending satellite passes will make the best composites possible.
 
 #### About Local Resolution Weighting (LRW)
-In an LRW composite, each satellite pass contributes to creating the output pixels.  The amount of this contribution is scaled by the inverse of the scattering area used during terrain correction (thus the need for requesting the area map option of HyP3 RTC).  The inverse of the surface scattering area, also referred to as local resolution, is multiplied by each pixel's backscatter value.  The results of all of the images covering any single pixel are then summed. This total is then divided by the sum of the weights used to get the output average backscatter.
+In an LRW composite, each satellite pass contributes to creating the output pixels.
+The amount of this contribution is scaled by the inverse of the scattering area used
+during terrain correction (thus the need for requesting the area map option of HyP3 RTC).
+The inverse of the surface scattering area, also referred to as local resolution,
+is multiplied by each pixel's backscatter value. The results of all of the images
+covering any single pixel are then summed. This total is then divided by the sum
+of the weights used to get the output average backscatter.
+
+### Water extent mapping
+
+The `water_map` tool allows you to create a surface water extent map from a Sentinel-1
+dual-pol (VV+VH) RTC product. It is intended to be used with RTC products generated by
+[ASF HyP3](https://hyp3-docs.asf.alaska.edu/using/vertex).
+
+Additionally, a HAND (height above nearest drainage) GeoTIFF that is pixel aligned to
+the RTC images is required, and preferably derived from the same DEM used to correct
+the RTC images -- the quality of the HAND used is directly tied to the quality of the
+output water extent map.
+
+To make a water extent map, run:
+```
+water_map [OUT_RASTER] [VV_RASTER] [VH_RASTER] [HAND_RASTER]
+```
+
+For more information and to see the options available, see:
+```
+water_map --help
+```
+
+For details on the algorithm see the `asf_tools.water_map.make_water_map` docstring.
+<!-- FIXME: Link to API reference on our docs page! -->
diff --git a/asf_tools/composite.py b/asf_tools/composite.py
index 3bb5c7e9..a937bd90 100755
--- a/asf_tools/composite.py
+++ b/asf_tools/composite.py
@@ -8,16 +8,17 @@
 the number of rasters contributing to each composite pixel is created.
 
 References:
-    David Small, 2012: https://doi.org/10.1109/IGARSS.2012.6350465
+    David Small, 2012: <https://doi.org/10.1109/IGARSS.2012.6350465>
 """
 
 import argparse
 import logging
 import os
 import sys
+from pathlib import Path
 from statistics import multimode
 from tempfile import NamedTemporaryFile, TemporaryDirectory
-from typing import List
+from typing import List, Union
 
 import numpy as np
 from osgeo import gdal, osr
@@ -185,7 +186,7 @@ def read_as_array(raster: str, band: int = 1) -> np.array:
     return data
 
 
-def write_cog(file_name: str, data: np.ndarray, transform: List[float], epsg_code: int,
+def write_cog(file_name: Union[str, Path], data: np.ndarray, transform: List[float], epsg_code: int,
               dtype=gdal.GDT_Float32, nodata_value=None):
     """Creates a Cloud Optimized GeoTIFF
 
@@ -213,7 +214,7 @@ def write_cog(file_name: str, data: np.ndarray, transform: List[float], epsg_cod
 
         driver = gdal.GetDriverByName('COG')
         options = ['COMPRESS=LZW', 'OVERVIEW_RESAMPLING=AVERAGE', 'NUM_THREADS=ALL_CPUS', 'BIGTIFF=YES']
-        driver.CreateCopy(file_name, temp_geotiff, options=options)
+        driver.CreateCopy(str(file_name), temp_geotiff, options=options)
 
         del temp_geotiff  # How to close w/ gdal
     return file_name
diff --git a/asf_tools/dem.py b/asf_tools/dem.py
new file mode 100644
index 00000000..9ee9b9ad
--- /dev/null
+++ b/asf_tools/dem.py
@@ -0,0 +1,43 @@
+"""Prepare a Copernicus GLO-30 DEM virtual raster (VRT) covering a given geometry"""
+from pathlib import Path
+from typing import Union
+
+from osgeo import gdal, ogr
+from shapely.geometry.base import BaseGeometry
+
+from asf_tools import vector
+from asf_tools.util import GDALConfigManager
+
+DEM_GEOJSON = '/vsicurl/https://asf-dem-west.s3.amazonaws.com/v2/cop30.geojson'
+
+gdal.UseExceptions()
+ogr.UseExceptions()
+
+
+def prepare_dem_vrt(vrt: Union[str, Path], geometry: Union[ogr.Geometry, BaseGeometry]):
+    """Create a DEM mosaic VRT covering a given geometry
+
+    The DEM mosaic is assembled from the Copernicus GLO-30 DEM tiles that intersect the geometry.
+
+    Note: `asf_tools` does not currently support geometries that cross the antimeridian.
+
+    Args:
+        vrt: Path for the output VRT file
+        geometry: Geometry in EPSG:4326 (lon/lat) projection for which to prepare a DEM mosaic
+
+    """
+    with GDALConfigManager(GDAL_DISABLE_READDIR_ON_OPEN='EMPTY_DIR'):
+        if isinstance(geometry, BaseGeometry):
+            geometry = ogr.CreateGeometryFromWkb(geometry.wkb)
+
+        min_lon, max_lon, _, _ = geometry.GetEnvelope()
+        if min_lon < -160. and max_lon > 160.:
+            raise ValueError(f'asf_tools does not currently support geometries that cross the antimeridian: {geometry}')
+
+        tile_features = vector.get_features(DEM_GEOJSON)
+        if not vector.get_property_values_for_intersecting_features(geometry, tile_features):
+            raise ValueError(f'Copernicus GLO-30 DEM does not intersect this geometry: {geometry}')
+
+        dem_file_paths = vector.intersecting_feature_properties(geometry, tile_features, 'file_path')
+
+        gdal.BuildVRT(str(vrt), dem_file_paths)
diff --git a/asf_tools/hand/__init__.py b/asf_tools/hand/__init__.py
new file mode 100644
index 00000000..a269442d
--- /dev/null
+++ b/asf_tools/hand/__init__.py
@@ -0,0 +1,8 @@
+from asf_tools.hand.calculate import calculate_hand_for_basins, make_copernicus_hand
+from asf_tools.hand.prepare import prepare_hand_vrt
+
+__all__ = [
+    'calculate_hand_for_basins',
+    'make_copernicus_hand',
+    'prepare_hand_vrt',
+]
diff --git a/asf_tools/hand/calculate.py b/asf_tools/hand/calculate.py
new file mode 100644
index 00000000..c0eb966f
--- /dev/null
+++ b/asf_tools/hand/calculate.py
@@ -0,0 +1,177 @@
+"""Calculate Height Above Nearest Drainage (HAND) from the Copernicus GLO-30 DEM"""
+import argparse
+import logging
+import sys
+import warnings
+from pathlib import Path
+from tempfile import NamedTemporaryFile
+from typing import Optional, Union
+
+import astropy.convolution
+import fiona
+import numpy as np
+import rasterio.crs
+import rasterio.mask
+from pysheds.grid import Grid
+from shapely.geometry import GeometryCollection, shape
+
+from asf_tools.composite import write_cog
+from asf_tools.dem import prepare_dem_vrt
+
+log = logging.getLogger(__name__)
+
+
+def fill_nan(array: np.ndarray) -> np.ndarray:
+    """Replace NaNs with values interpolated from their neighbors
+
+    Replace NaNs with values interpolated from their neighbors using a 2D Gaussian
+    kernel, see: https://docs.astropy.org/en/stable/convolution/#using-astropy-s-convolution-to-replace-bad-data
+    """
+    kernel = astropy.convolution.Gaussian2DKernel(x_stddev=3)  # kernel x_size=8*stddev
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore")
+
+        array = astropy.convolution.interpolate_replace_nans(
+            array, kernel, convolve=astropy.convolution.convolve
+        )
+
+    return array
+
+
+def calculate_hand(dem_array, dem_affine: rasterio.Affine, dem_crs: rasterio.crs.CRS, basin_mask,
+                   acc_thresh: Optional[int] = 100):
+    """Calculate the Height Above Nearest Drainage (HAND)
+
+     Calculate the Height Above Nearest Drainage (HAND) using pySHEDS library. Because HAND
+     is tied to watershed boundaries (hydrobasins), clipped/cut basins will produce weird edge
+     effects, and incomplete basins should be masked out. For watershed boundaries,
+     see: https://www.hydrosheds.org/page/hydrobasins
+
+     This involves:
+        * Filling depressions (regions of cells lower than their surrounding neighbors)
+            in the Digital Elevation Model (DEM)
+        * Resolving un-drainable flats
+        * Determine the flow direction using the ESRI D8 routing scheme
+        * Determine flow accumulation (number of upstream cells)
+        * Create a drainage mask using the accumulation threshold `acc_thresh`
+        * Calculating HAND
+
+    In the HAND calculation, NaNs inside the basin filled using `fill_nan`
+
+    Args:
+        dem_array: DEM to calculate HAND for
+        dem_crs: DEM Coordinate Reference System (CRS)
+        dem_affine: DEM Affine geotransform
+        basin_mask: Array of booleans indicating wither an element should be masked out (à la Numpy Masked Arrays:
+            https://numpy.org/doc/stable/reference/maskedarray.generic.html#what-is-a-masked-array)
+        acc_thresh: Accumulation threshold for determining the drainage mask.
+            If `None`, the mean accumulation value is used
+    """
+
+    grid = Grid()
+    grid.add_gridded_data(dem_array, data_name='dem', affine=dem_affine, crs=dem_crs.to_dict(), mask=~basin_mask)
+
+    log.info('Filling depressions')
+    grid.fill_depressions('dem', out_name='flooded_dem')
+    if np.isnan(grid.flooded_dem).any():
+        log.debug('NaNs encountered in flooded DEM; filling.')
+        grid.flooded_dem = fill_nan(grid.flooded_dem)
+
+    log.info('Resolving flats')
+    grid.resolve_flats('flooded_dem', out_name='inflated_dem')
+    if np.isnan(grid.inflated_dem).any():
+        log.debug('NaNs encountered in inflated DEM; replacing NaNs with original DEM values')
+        grid.inflated_dem[np.isnan(grid.inflated_dem)] = dem_array[np.isnan(grid.inflated_dem)]
+
+    log.info('Obtaining flow direction')
+    grid.flowdir(data='inflated_dem', out_name='dir', apply_mask=True)
+    if np.isnan(grid.dir).any():
+        log.debug('NaNs encountered in flow direction; filling.')
+        grid.dir = fill_nan(grid.dir)
+
+    log.info('Calculating flow accumulation')
+    grid.accumulation(data='dir', out_name='acc')
+    if np.isnan(grid.acc).any():
+        log.debug('NaNs encountered in accumulation; filling.')
+        grid.acc = fill_nan(grid.acc)
+
+    if acc_thresh is None:
+        acc_thresh = grid.acc.mean()
+
+    log.info(f'Calculating HAND using accumulation threshold of {acc_thresh}')
+    hand = grid.compute_hand('dir', 'inflated_dem', grid.acc > acc_thresh, inplace=False)
+    if np.isnan(hand).any():
+        log.debug('NaNs encountered in HAND; filling.')
+        hand = fill_nan(hand)
+
+    # ensure non-basin is masked after fill_nan
+    hand[basin_mask] = np.nan
+
+    return hand
+
+
+def calculate_hand_for_basins(out_raster:  Union[str, Path], geometries: GeometryCollection,
+                              dem_file: Union[str, Path]):
+    """Calculate the Height Above Nearest Drainage (HAND) for watershed boundaries (hydrobasins).
+
+    For watershed boundaries, see: https://www.hydrosheds.org/page/hydrobasins
+
+    Args:
+        out_raster: HAND GeoTIFF to create
+        geometries: watershed boundary (hydrobasin) polygons to calculate HAND over
+        dem_file: DEM raster covering (containing) `geometries`
+    """
+    with rasterio.open(dem_file) as src:
+        basin_mask, basin_affine_tf, basin_window = rasterio.mask.raster_geometry_mask(
+            src, geometries, all_touched=True, crop=True, pad=True, pad_width=1
+        )
+        basin_array = src.read(1, window=basin_window)
+
+        hand = calculate_hand(basin_array, basin_affine_tf, src.crs, basin_mask)
+
+        write_cog(str(out_raster), hand, transform=basin_affine_tf.to_gdal(), epsg_code=src.crs.to_epsg())
+
+
+def make_copernicus_hand(out_raster:  Union[str, Path], vector_file: Union[str, Path]):
+    """Copernicus GLO-30 Height Above Nearest Drainage (HAND)
+
+    Make a Height Above Nearest Drainage (HAND) GeoTIFF from the Copernicus GLO-30 DEM
+    covering the watershed boundaries (hydrobasins) defined in a vector file.
+
+    For watershed boundaries, see: https://www.hydrosheds.org/page/hydrobasins
+
+    Args:
+        out_raster: HAND GeoTIFF to create
+        vector_file: Vector file of watershed boundary (hydrobasin) polygons to calculate HAND over
+    """
+    with fiona.open(vector_file) as vds:
+        geometries = GeometryCollection([shape(feature['geometry']) for feature in vds])
+
+    with NamedTemporaryFile(suffix='.vrt', delete=False) as dem_vrt:
+        prepare_dem_vrt(dem_vrt.name, geometries)
+        calculate_hand_for_basins(out_raster, geometries, dem_vrt.name)
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description=__doc__,
+        epilog='For watershed boundaries, see: https://www.hydrosheds.org/page/hydrobasins',
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+    )
+    parser.add_argument('out_raster', type=Path,
+                        help='HAND GeoTIFF to create')
+    parser.add_argument('vector_file', type=Path,
+                        help='Vector file of watershed boundary (hydrobasin) polygons to calculate HAND over. '
+                             'Vector file Must be openable by GDAL, see: https://gdal.org/drivers/vector/index.html')
+
+    parser.add_argument('-v', '--verbose', action='store_true', help='Turn on verbose logging')
+    args = parser.parse_args()
+
+    level = logging.DEBUG if args.verbose else logging.INFO
+    logging.basicConfig(stream=sys.stdout, format='%(asctime)s - %(levelname)s - %(message)s', level=level)
+    log.debug(' '.join(sys.argv))
+    log.info(f'Calculating HAND for {args.vector_file}')
+
+    make_copernicus_hand(args.out_raster, args.vector_file)
+
+    log.info(f'HAND GeoTIFF created successfully: {args.out_raster}')
diff --git a/asf_tools/hand/prepare.py b/asf_tools/hand/prepare.py
new file mode 100644
index 00000000..bf338f7f
--- /dev/null
+++ b/asf_tools/hand/prepare.py
@@ -0,0 +1,74 @@
+"""Prepare a Height Above Nearest Drainage (HAND) virtual raster (VRT) covering a given geometry"""
+from pathlib import Path
+from tempfile import NamedTemporaryFile
+from typing import Union
+
+from osgeo import gdal, ogr
+from rasterio.enums import Resampling
+from shapely.geometry import shape
+from shapely.geometry.base import BaseGeometry
+
+from asf_tools import vector
+from asf_tools.composite import get_epsg_code
+from asf_tools.util import GDALConfigManager
+
+HAND_GEOJSON = '/vsicurl/https://asf-hand-data.s3.amazonaws.com/cop30-hand.geojson'
+
+gdal.UseExceptions()
+ogr.UseExceptions()
+
+
+def prepare_hand_vrt(vrt: Union[str, Path], geometry: Union[ogr.Geometry, BaseGeometry]):
+    """Prepare a HAND mosaic VRT covering a given geometry
+
+    Prepare a Height Above Nearest Drainage (HAND) virtual raster (VRT) covering a given geometry.
+    The Height Above Nearest Drainage (HAND) mosaic is assembled from the HAND tiles that intersect
+    the geometry, using a HAND derived from the Copernicus GLO-30 DEM.
+
+    Note: `asf_tools` does not currently support geometries that cross the antimeridian.
+
+    Args:
+        vrt: Path for the output VRT file
+        geometry: Geometry in EPSG:4326 (lon/lat) projection for which to prepare a HAND mosaic
+
+    """
+    with GDALConfigManager(GDAL_DISABLE_READDIR_ON_OPEN='EMPTY_DIR'):
+        if isinstance(geometry, BaseGeometry):
+            geometry = ogr.CreateGeometryFromWkb(geometry.wkb)
+
+        min_lon, max_lon, _, _ = geometry.GetEnvelope()
+        if min_lon < -160. and max_lon > 160.:
+            raise ValueError(f'asf_tools does not currently support geometries that cross the antimeridian: {geometry}')
+
+        tile_features = vector.get_features(HAND_GEOJSON)
+        if not vector.get_property_values_for_intersecting_features(geometry, tile_features):
+            raise ValueError(f'Copernicus GLO-30 HAND does not intersect this geometry: {geometry}')
+
+        hand_file_paths = vector.intersecting_feature_properties(geometry, tile_features, 'file_path')
+
+        gdal.BuildVRT(str(vrt), hand_file_paths)
+
+
+def prepare_hand_for_raster(hand_raster: Union[str, Path], source_raster: Union[str, Path],
+                            resampling_method: str = 'lanczos'):
+    """Create a HAND raster pixel-aligned to a source raster
+
+    Args:
+        hand_raster: Path for the output HAND raster
+        source_raster: Path for the source raster
+        resampling_method: Name of the resampling method to use. For available methods, see:
+            https://gdal.org/programs/gdalwarp.html#cmdoption-gdalwarp-r
+    """
+    info = gdal.Info(str(source_raster), format='json')
+
+    hand_geometry = shape(info['wgs84Extent'])
+    hand_bounds = [info['cornerCoordinates']['upperLeft'][0],
+                   info['cornerCoordinates']['lowerRight'][1],
+                   info['cornerCoordinates']['lowerRight'][0],
+                   info['cornerCoordinates']['upperLeft'][1]]
+
+    with NamedTemporaryFile(suffix='.vrt', delete=False) as hand_vrt:
+        prepare_hand_vrt(hand_vrt.name, hand_geometry)
+        gdal.Warp(str(hand_raster), hand_vrt.name, dstSRS=f'EPSG:{get_epsg_code(info)}',
+                  outputBounds=hand_bounds, width=info['size'][0], height=info['size'][1],
+                  resampleAlg=Resampling[resampling_method].value)
diff --git a/asf_tools/raster.py b/asf_tools/raster.py
new file mode 100644
index 00000000..d5a25cf5
--- /dev/null
+++ b/asf_tools/raster.py
@@ -0,0 +1,60 @@
+import logging
+import warnings
+from pathlib import Path
+from typing import Literal, Union
+
+import numpy as np
+from osgeo import gdal
+
+gdal.UseExceptions()
+log = logging.getLogger(__name__)
+
+
+def convert_scale(array: Union[np.ndarray, np.ma.MaskedArray], in_scale: Literal['db', 'amplitude', 'power'],
+                  out_scale: Literal['db', 'amplitude', 'power']) -> Union[np.ndarray, np.ma.MaskedArray]:
+    """Convert calibrated raster scale between db, amplitude and power"""
+    if in_scale == out_scale:
+        warnings.warn(f'Nothing to do! {in_scale} is same as {out_scale}.')
+        return array
+
+    log10 = np.ma.log10 if isinstance(array, np.ma.MaskedArray) else np.log10
+
+    if in_scale == 'db':
+        if out_scale == 'power':
+            return 10 ** (array / 10)
+        if out_scale == 'amplitude':
+            return 10 ** (array / 20)
+
+    if in_scale == 'amplitude':
+        if out_scale == 'power':
+            return array ** 2
+        if out_scale == 'db':
+            return 10 * log10(array ** 2)
+
+    if in_scale == 'power':
+        if out_scale == 'amplitude':
+            return np.sqrt(array)
+        if out_scale == 'db':
+            return 10 * log10(array)
+
+    raise ValueError(f'Cannot convert raster of scale {in_scale} to {out_scale}')
+
+
+def read_as_masked_array(raster: Union[str, Path], band: int = 1) -> np.ma.MaskedArray:
+    """Reads data from a raster image into memory, masking invalid and NoData values
+
+    Args:
+        raster: The file path to a raster image
+        band: The raster band to read
+
+    Returns:
+        data: The raster pixel data as a numpy MaskedArray
+    """
+    log.debug(f'Reading raster values from {raster}')
+    ds = gdal.Open(str(raster))
+    band = ds.GetRasterBand(band)
+    data = np.ma.masked_invalid(band.ReadAsArray())
+    nodata = band.GetNoDataValue()
+    if nodata is not None:
+        return np.ma.masked_values(data, nodata)
+    return data
diff --git a/asf_tools/threshold.py b/asf_tools/threshold.py
new file mode 100644
index 00000000..a05662c7
--- /dev/null
+++ b/asf_tools/threshold.py
@@ -0,0 +1,141 @@
+# Turned off flake8 because we haven't refactored 3rd party provided functions
+# flake8: noqa
+import numpy as np
+
+
+def _make_histogram(image):
+    image = image.flatten()
+    indices = np.nonzero(np.isnan(image))
+    image[indices] = 0
+    indices = np.nonzero(np.isinf(image))
+    image[indices] = 0
+    del indices
+    size = image.size
+    maximum = int(np.ceil(np.amax(image)) + 1)
+    histogram = np.zeros((1, maximum))
+    for i in range(0, size):
+        floor_value = np.floor(image[i]).astype(np.uint8)
+        if 0 < floor_value < maximum - 1:
+            temp1 = image[i] - floor_value
+            temp2 = 1 - temp1
+            histogram[0, floor_value] = histogram[0, floor_value] + temp1
+            histogram[0, floor_value - 1] = histogram[0, floor_value - 1] + temp2
+    histogram = np.convolve(histogram[0], [1, 2, 3, 2, 1])
+    histogram = histogram[2:(histogram.size - 3)]
+    histogram /= np.sum(histogram)
+    return histogram
+
+
+def _make_distribution(m, v, g, x):
+    x = x.flatten()
+    m = m.flatten()
+    v = v.flatten()
+    g = g.flatten()
+    y = np.zeros((len(x), m.shape[0]))
+    for i in range(0, m.shape[0]):
+        d = x - m[i]
+        amp = g[i] / np.sqrt(2 * np.pi * v[i])
+        y[:, i] = amp * np.exp(-0.5 * (d * d) / v[i])
+    return y
+
+
+def expectation_maximization_threshold(tile: np.ndarray, number_of_classes: int = 3) -> float:
+    """Water threshold Calculation using a multi-mode Expectation Maximization Approach
+
+    Thresholding works best when backscatter tiles are provided on a decibel scale
+    to get Gaussian distribution that is scaled to a range of 0-255, and performed
+    on a small tile that is likely to have a transition between water and not water.
+
+
+    Args:
+        tile: array of backscatter values for a tile from an RTC raster
+        number_of_classes: classify the tile into this many classes. Typically, three
+            classes capture: (1) urban and bright slopes, (2) average brightness farmland,
+            and (3) water, as is often seen in the US Midwest.
+
+    Returns:
+        threshold: threshold value that can be used to create a water extent map
+    """
+
+    image_copy = tile.copy()
+    image_copy2 = np.ma.filled(tile.astype(float), np.nan)  # needed for valid posterior_lookup keys
+    image = tile.flatten()
+    minimum = np.amin(image)
+    image = image - minimum + 1
+    maximum = np.amax(image)
+
+    histogram = _make_histogram(image)
+    nonzero_indices = np.nonzero(histogram)[0]
+    histogram = histogram[nonzero_indices]
+    histogram = histogram.flatten()
+    class_means = (
+            (np.arange(number_of_classes) + 1) * maximum /
+            (number_of_classes + 1)
+    )
+    class_variances = np.ones(number_of_classes) * maximum
+    class_proportions = np.ones(number_of_classes) * 1 / number_of_classes
+    sml = np.mean(np.diff(nonzero_indices)) / 1000
+    iteration = 0
+    while True:
+        class_likelihood = _make_distribution(
+            class_means, class_variances, class_proportions, nonzero_indices
+        )
+        sum_likelihood = np.sum(class_likelihood, 1) + np.finfo(
+            class_likelihood[0][0]).eps
+        log_likelihood = np.sum(histogram * np.log(sum_likelihood))
+        for j in range(0, number_of_classes):
+            class_posterior_probability = (
+                    histogram * class_likelihood[:, j] / sum_likelihood
+            )
+            class_proportions[j] = np.sum(class_posterior_probability)
+            class_means[j] = (
+                    np.sum(nonzero_indices * class_posterior_probability)
+                    / class_proportions[j]
+            )
+            vr = (nonzero_indices - class_means[j])
+            class_variances[j] = (
+                    np.sum(vr * vr * class_posterior_probability)
+                    / class_proportions[j] + sml
+            )
+            del class_posterior_probability, vr
+        class_proportions += 1e-3
+        class_proportions /= np.sum(class_proportions)
+        class_likelihood = _make_distribution(
+            class_means, class_variances, class_proportions, nonzero_indices
+        )
+        sum_likelihood = np.sum(class_likelihood, 1) + np.finfo(
+            class_likelihood[0, 0]).eps
+        del class_likelihood
+        new_log_likelihood = np.sum(histogram * np.log(sum_likelihood))
+        del sum_likelihood
+        if (new_log_likelihood - log_likelihood) < 0.000001:
+            break
+        iteration += 1
+    del log_likelihood, new_log_likelihood
+    class_means = class_means + minimum - 1
+    s = image_copy.shape
+    posterior = np.zeros((s[0], s[1], number_of_classes))
+    posterior_lookup = dict()
+    for i in range(0, s[0]):
+        for j in range(0, s[1]):
+            pixel_val = image_copy2[i, j]
+            if pixel_val in posterior_lookup:
+                for n in range(0, number_of_classes):
+                    posterior[i, j, n] = posterior_lookup[pixel_val][n]
+            else:
+                posterior_lookup.update({pixel_val: [0] * number_of_classes})
+                for n in range(0, number_of_classes):
+                    x = _make_distribution(
+                        class_means[n], class_variances[n], class_proportions[n],
+                        image_copy[i, j]
+                    )
+                    posterior[i, j, n] = x * class_proportions[n]
+                    posterior_lookup[pixel_val][n] = posterior[i, j, n]
+
+    sorti = np.argsort(class_means)
+    xvec = np.arange(class_means[sorti[0]], class_means[sorti[1]], step=.05)
+    x1 = _make_distribution(class_means[sorti[0]], class_variances[sorti[0]], class_proportions[sorti[0]], xvec)
+    x2 = _make_distribution(class_means[sorti[1]], class_variances[sorti[1]], class_proportions[sorti[1]], xvec)
+    dx = np.abs(x1 - x2)
+
+    return xvec[np.argmin(dx)]
diff --git a/asf_tools/tile.py b/asf_tools/tile.py
new file mode 100644
index 00000000..1cf2f310
--- /dev/null
+++ b/asf_tools/tile.py
@@ -0,0 +1,126 @@
+from typing import Tuple, Union
+
+import numpy as np
+
+
+def tile_array(array: Union[np.ndarray, np.ma.MaskedArray], tile_shape: Tuple[int, int] = (200, 200),
+               pad_value: float = None) -> Union[np.ndarray, np.ma.MaskedArray]:
+    """Tile a 2D numpy array
+
+    Turn a 2D numpy array like:
+        >>> array = [[0, 0, 1, 1],
+        ...          [0, 0, 1, 1],
+        ...          [2, 2, 3, 3],
+        ...          [2, 2, 3, 3]]
+        >>> array.shape
+        (4, 4)
+
+    into a tiled array like:
+        >>> tiles = tiled_array(array, 2, 2)
+        >>> print(tiles)
+        [[[0, 0],
+          [0, 0]],
+         [[1, 1],
+          [1, 1]],
+         [[2, 2],
+          [2, 2]],
+         [[3, 3],
+          [3, 3]]]
+        >>> tiles.shape
+        (4, 2, 2)
+
+    Args:
+        array: 2D array to tile
+        tile_shape: the shape of each tile
+        pad_value: right-bottom pad `a` with `pad` as needed so `a` is evenly divisible into tiles
+
+    Returns:
+        the tiled array
+    """
+    array_rows, array_columns = array.shape
+    tile_rows, tile_columns = tile_shape
+
+    # CREDIT: https://twitter.com/LizzUltee/status/1379508448262512641
+    rpad = -array_rows % tile_rows
+    cpad = -array_columns % tile_columns
+
+    if (rpad or cpad) and pad_value is None:
+        raise ValueError(f'Cannot evenly tile a {array.shape} array into ({tile_rows},{tile_columns}) tiles')
+
+    if rpad or cpad:
+        padded_array = np.pad(array, ((0, rpad), (0, cpad)), constant_values=pad_value)
+        if isinstance(array, np.ma.MaskedArray):
+            mask = np.pad(array.mask, ((0, rpad), (0, cpad)), constant_values=True)
+            padded_array = np.ma.MaskedArray(padded_array, mask=mask)
+    else:
+        padded_array = array
+
+    tile_list = []
+    for rows in np.vsplit(padded_array, range(tile_rows, array_rows, tile_rows)):
+        tile_list.extend(np.hsplit(rows, range(tile_columns, array_columns, tile_columns)))
+
+    dstack = np.ma.dstack if isinstance(array, np.ma.MaskedArray) else np.dstack
+    tiled = np.moveaxis(dstack(tile_list), -1, 0)
+
+    return tiled
+
+
+def untile_array(tiled_array: Union[np.ndarray, np.ma.MaskedArray], array_shape: Tuple[int, int]) \
+        -> Union[np.ndarray, np.ma.MaskedArray]:
+    """Untile a tiled array into a 2D numpy array
+
+    This is the reverse of `tile_array` and will turn a tiled array like:
+        >>> tiled_array = [[[0,0],
+        ...                 [0,0]],
+        ...                [[1,1],
+        ...                 [1,1]],
+        ...                [[2,2],
+        ...                 [2,2]],
+        ...                [[3,3],
+        ...                 [3,3]]]
+        >>> tiled_array.shape
+        (4, 2, 2)
+
+    into a 2D array like:
+        >>> array = untile_array(tiled_array)
+        >>> print(array)
+        [[0, 0, 1, 1],
+         [0, 0, 1, 1],
+         [2, 2, 3, 3],
+         [2, 2, 3, 3]]
+        >>> array.shape
+        (4, 4)
+
+    Args:
+        tiled_array: a tiled array
+        array_shape: shape to untile the array to. If array_shape's size is smaller
+            than the tiled array, `untile_array` will subset the tiled array assuming
+            bottom right padding was added when tiling.
+
+    Returns:
+        the untiled array
+    """
+    _, tile_rows, tile_columns = tiled_array.shape
+    array_rows, array_columns = array_shape
+
+    untiled_rows = int(np.ceil(array_rows / tile_rows))
+    untiled_columns = int(np.ceil(array_columns / tile_columns))
+
+    untiled = np.zeros((untiled_rows*tile_rows, untiled_columns*tile_columns), dtype=tiled_array.dtype)
+
+    if (array_size := array_rows * array_columns) > tiled_array.size:
+        raise ValueError(
+            f'array_shape {array_shape} will result in an array bigger than the tiled array:'
+            f' {array_size} > {tiled_array.size}'
+        )
+
+    for ii in range(untiled_rows):
+        for jj in range(untiled_columns):
+            untiled[ii*tile_rows:(ii+1)*tile_rows, jj*tile_columns:(jj+1)*tile_columns] = \
+                tiled_array[ii * untiled_columns + jj, :, :]
+
+    if isinstance(tiled_array, np.ma.MaskedArray):
+        untiled_mask = untile_array(tiled_array.mask, untiled.shape)
+        untiled = np.ma.MaskedArray(untiled, mask=untiled_mask)
+
+    return untiled[:array_rows, :array_columns]
diff --git a/asf_tools/util.py b/asf_tools/util.py
new file mode 100644
index 00000000..c1d10e60
--- /dev/null
+++ b/asf_tools/util.py
@@ -0,0 +1,25 @@
+from osgeo import gdal
+
+gdal.UseExceptions()
+
+
+class GDALConfigManager:
+    """Context manager for setting GDAL config options temporarily"""
+    def __init__(self, **options):
+        """
+        Args:
+            **options: GDAL Config `option=value` keyword arguments.
+        """
+        self.options = options.copy()
+        self._previous_options = {}
+
+    def __enter__(self):
+        for key in self.options:
+            self._previous_options[key] = gdal.GetConfigOption(key)
+
+        for key, value in self.options.items():
+            gdal.SetConfigOption(key, value)
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        for key, value in self._previous_options.items():
+            gdal.SetConfigOption(key, value)
diff --git a/asf_tools/vector.py b/asf_tools/vector.py
new file mode 100644
index 00000000..a6707499
--- /dev/null
+++ b/asf_tools/vector.py
@@ -0,0 +1,27 @@
+from pathlib import Path
+from typing import Iterator, List, Union
+
+from osgeo import ogr
+
+ogr.UseExceptions()
+
+
+def get_features(vector_path: Union[str, Path]) -> List[ogr.Feature]:
+    ds = ogr.Open(str(vector_path))
+    layer = ds.GetLayer()
+    return [feature for feature in layer]
+
+
+def get_property_values_for_intersecting_features(geometry: ogr.Geometry, features: Iterator) -> bool:
+    for feature in features:
+        if feature.GetGeometryRef().Intersects(geometry):
+            return True
+
+
+def intersecting_feature_properties(geometry: ogr.Geometry, features: Iterator,
+                                    feature_property: str) -> List[str]:
+    property_values = []
+    for feature in features:
+        if feature.GetGeometryRef().Intersects(geometry):
+            property_values.append(feature.GetField(feature_property))
+    return property_values
diff --git a/asf_tools/water_map.py b/asf_tools/water_map.py
new file mode 100644
index 00000000..0fb7817d
--- /dev/null
+++ b/asf_tools/water_map.py
@@ -0,0 +1,333 @@
+"""Generate surface water maps from Sentinel-1 RTC products
+
+Create a surface water extent map from a dual-pol Sentinel-1 RTC product and
+a HAND image. The HAND image must be pixel-aligned (same extent and size) to
+the RTC images. The water extent maps are created using an adaptive Expectation
+Maximization thresholding approach and refined using Fuzzy Logic.
+"""
+
+import argparse
+import logging
+import sys
+from pathlib import Path
+from typing import Optional, Tuple, Union
+
+import numpy as np
+import skfuzzy as fuzz
+from osgeo import gdal
+from skimage import measure
+
+from asf_tools.composite import get_epsg_code, write_cog
+from asf_tools.hand.prepare import prepare_hand_for_raster
+from asf_tools.raster import read_as_masked_array
+from asf_tools.threshold import expectation_maximization_threshold as em_threshold
+from asf_tools.tile import tile_array, untile_array
+
+log = logging.getLogger(__name__)
+
+
+def mean_of_subtiles(tiles: np.ndarray) -> np.ndarray:
+    sub_tile_shape = (tiles.shape[1] // 2, tiles.shape[2] // 2)
+    sub_tiles_mean = np.zeros((tiles.shape[0], 4))
+    for ii, tile in enumerate(tiles):
+        sub_tiles = tile_array(tile.filled(0), tile_shape=sub_tile_shape)
+        sub_tiles_mean[ii, :] = sub_tiles.mean(axis=(1, 2))
+    return sub_tiles_mean
+
+
+def select_hand_tiles(tiles: np.ndarray, hand_threshold: float, hand_fraction: float) -> np.ndarray:
+    tile_indexes = np.arange(tiles.shape[0])
+
+    tiles = np.ma.masked_greater_equal(tiles, hand_threshold)
+    percent_valid_pixels = np.sum(~tiles.mask, axis=(1, 2)) / (tiles.shape[1] * tiles.shape[2])
+
+    return tile_indexes[percent_valid_pixels > hand_fraction]
+
+
+def select_backscatter_tiles(backscatter_tiles: np.ndarray, hand_candidates: np.ndarray) -> np.ndarray:
+    tile_indexes = np.arange(backscatter_tiles.shape[0])
+
+    sub_tile_means = mean_of_subtiles(backscatter_tiles)
+    sub_tile_means_std = sub_tile_means.std(axis=1)
+    tile_medians = np.ma.median(backscatter_tiles, axis=(1, 2))
+    tile_variance = sub_tile_means_std / tile_medians  # OK
+
+    low_mean_threshold = np.ma.median(tile_medians[hand_candidates])
+    low_mean_candidates = tile_indexes[tile_medians < low_mean_threshold]
+
+    potential_candidates = np.intersect1d(hand_candidates, low_mean_candidates)
+
+    for variance_threshold in np.percentile(tile_variance, np.arange(5, 96)[::-1]):
+        variance_candidates = tile_indexes[tile_variance > variance_threshold]
+        selected = np.intersect1d(variance_candidates, potential_candidates)
+        sort_index = np.argsort(sub_tile_means_std[selected])[::-1]
+        if len(selected) >= 5:
+            return selected[sort_index][:5]
+    return np.array([])
+
+
+def determine_em_threshold(tiles: np.ndarray, scaling: float) -> float:
+    thresholds = []
+    for ii in range(tiles.shape[0]):
+        test_tile = (np.around(tiles[ii, :, :] * scaling)).astype(int)
+        thresholds.append(em_threshold(test_tile) / scaling)
+
+    return np.median(np.sort(thresholds)[:4])
+
+
+def calculate_slope_magnitude(array: np.ndarray, pixel_size) -> np.ndarray:
+    dx, dy = np.gradient(array)
+    magnitude = np.sqrt(dx**2, dy**2) / pixel_size
+    slope = np.arctan(magnitude) / np.pi * 180.
+    return slope
+
+
+def determine_membership_limits(
+        array: np.ndarray, mask_percentile: float = 90., std_range: float = 3.0) -> Tuple[float, float]:
+    array = np.ma.masked_values(array, 0.)
+    array = np.ma.masked_greater(array, np.percentile(array, mask_percentile))
+    lower_limit = np.ma.median(array)
+    upper_limit = lower_limit + std_range * array.std() + 5.0
+    return lower_limit, upper_limit
+
+
+def min_max_membership(array: np.ndarray, lower_limit: float, upper_limit: float, resolution: float) -> np.ndarray:
+    possible_values = np.arange(array.min(), array.max(), resolution)
+    activation = fuzz.zmf(possible_values, lower_limit, upper_limit)
+    membership = fuzz.interp_membership(possible_values, activation, array)
+    return membership
+
+
+def segment_area_membership(segments: np.ndarray, min_area: int = 3, max_area: int = 10) -> np.ndarray:
+    segment_areas = np.bincount(segments.ravel())
+
+    possible_areas = np.arange(min_area, max_area + 1)
+    activation = 1 - fuzz.zmf(possible_areas, min_area, max_area)
+
+    segment_membership = np.zeros_like(segments)
+
+    segments_above_threshold = np.squeeze((segment_areas > max_area).nonzero())
+    segments_above_threshold = np.delete(segments_above_threshold, (segments_above_threshold == 0).nonzero())
+    np.putmask(segment_membership, np.isin(segments, segments_above_threshold), 1)
+
+    for area in possible_areas:
+        mask = np.isin(segments, (segment_areas == area).nonzero())
+        np.putmask(segment_membership, mask, fuzz.interp_membership(possible_areas, activation, area))
+    return segment_membership
+
+
+def remove_small_segments(segments: np.ndarray, min_area: int = 3) -> np.ndarray:
+    valid_segments = segments != 0
+
+    segment_areas = np.bincount(segments.ravel())
+    segments_below_threshold = (segment_areas < min_area).nonzero()
+    np.putmask(valid_segments, np.isin(segments, segments_below_threshold), False)
+
+    return valid_segments
+
+
+def fuzzy_refinement(initial_map: np.ndarray, gaussian_array: np.ndarray, hand_array: np.ndarray, pixel_size: float,
+                     gaussian_thresholds: Tuple[float, float], membership_threshold: float = 0.45) -> np.ndarray:
+    water_map = np.ones_like(initial_map)
+
+    water_segments = measure.label(initial_map, connectivity=2)
+    water_segment_membership = segment_area_membership(water_segments)
+    water_map &= ~np.isclose(water_segment_membership, 0.)
+
+    gaussian_membership = min_max_membership(gaussian_array, gaussian_thresholds[0], gaussian_thresholds[1], 0.005)
+    water_map &= ~np.isclose(gaussian_membership, 0.)
+
+    hand_lower_limit, hand_upper_limit = determine_membership_limits(hand_array)
+    hand_membership = min_max_membership(hand_array, hand_lower_limit, hand_upper_limit, 0.1)
+    water_map &= ~np.isclose(hand_membership, 0.)
+
+    hand_slopes = calculate_slope_magnitude(hand_array, pixel_size)
+    slope_membership = min_max_membership(hand_slopes, 0., 15., 0.1)
+    water_map &= ~np.isclose(slope_membership, 0.)
+
+    water_map_weights = (gaussian_membership + hand_membership + slope_membership + water_segment_membership) / 4.
+    water_map &= water_map_weights >= membership_threshold
+
+    return water_map
+
+
+def make_water_map(out_raster: Union[str, Path], vv_raster: Union[str, Path], vh_raster: Union[str, Path],
+                   hand_raster: Optional[Union[str, Path]] = None, tile_shape: Tuple[int, int] = (100, 100),
+                   max_vv_threshold: float = -15.5, max_vh_threshold: float = -23.0,
+                   hand_threshold: float = 15., hand_fraction: float = 0.8, membership_threshold: float = 0.45):
+    """Creates a surface water extent map from a Sentinel-1 RTC product
+
+    Create a surface water extent map from a dual-pol Sentinel-1 RTC product and
+    a HAND image. The HAND image must be pixel-aligned (same extent and size) to
+    the RTC images. The water extent maps are created using an adaptive Expectation
+    Maximization thresholding approach and refined with Fuzzy Logic.
+
+    The input images are broken into a set of corresponding tiles with a shape of
+    `tile_shape`, and a set of tiles are selected from the VH RTC
+    image that contain water boundaries to determine an appropriate water threshold.
+     Candidate tiles must meet these criteria:
+    * `hand_fraction` of pixels within a tile must have HAND pixel values lower
+      than `hand_threshold`
+    * The median backscatter value for the tile must be lower than an average tiles'
+      backscatter values
+    * The tile must have a high variance -- high variance is considered initially to
+      be a variance in the 95th percentile of the tile variances, but progressively
+      relaxed to the 5th percentile if there not at least 5 candidate tiles.
+
+    The 5 VH tiles with the highest variance are selected for thresholding and a
+    water threshold value is determined using an Expectation Maximization approach.
+    If there were not enough candidate tiles or the threshold is too high,
+    `max_vh_threshold` and/or `max_vv_threshold` will be used instead.
+
+    From the initial threshold-based water extent maps, Fuzzy Logic is used to remove
+    spurious false detections and improve the water extent map quality. The fuzzy logic
+    uses these indicators for the presence of water:
+    * radar cross section in a pixel relative to the determined detection threshold
+    * the height above nearest drainage (HAND)
+    * the surface slope, which is derived from the HAND data
+    * the size of the detected water feature
+
+    For each indicator, a Z-shaped activation function is used to determine pixel membership.
+    The membership maps are combined to form the final water extent map. Pixels classified
+    as water pixels will:
+    * have non-zero membership in all of the indicators, and
+    * have an average membership above the `membership_threshold` value.
+
+    Finally, the VV and VH water masks will be combined to include all water pixels
+    from both masks, and the combined water map will be written to `out_raster`.
+
+    Args:
+        out_raster: Water map GeoTIFF to create
+        vv_raster: Sentinel-1 RTC GeoTIFF, in power scale, with VV polarization
+        vh_raster: Sentinel-1 RTC GeoTIFF, in power scale, with VH polarization
+        hand_raster: Height Above Nearest Drainage (HAND) GeoTIFF aligned to the RTC rasters
+        tile_shape: shape (height, width) in pixels to tile the image to
+        max_vv_threshold: Maximum threshold value to use for `vv_raster` in decibels (db)
+        max_vh_threshold:  Maximum threshold value to use for `vh_raster` in decibels (db)
+        hand_threshold: The maximum height above nearest drainage in meters to consider
+            a pixel valid
+        hand_fraction: The minimum fraction of valid HAND pixels required in a tile for
+            thresholding
+        membership_threshold: The average membership to the fuzzy indicators required for a water pixel
+    """
+    if tile_shape[0] % 2 or tile_shape[1] % 2:
+        raise ValueError(f'tile_shape {tile_shape} requires even values.')
+
+    info = gdal.Info(str(vh_raster), format='json')
+
+    out_transform = info['geoTransform']
+    out_epsg = get_epsg_code(info)
+
+    if hand_raster is None:
+        hand_raster = str(out_raster).replace('.tif', '_HAND.tif')
+        log.info(f'Extracting HAND data to: {hand_raster}')
+        prepare_hand_for_raster(hand_raster, vh_raster)
+
+    log.info(f'Determining HAND memberships from {hand_raster}')
+    hand_array = read_as_masked_array(hand_raster)
+    hand_tiles = tile_array(hand_array, tile_shape=tile_shape, pad_value=np.nan)
+
+    hand_candidates = select_hand_tiles(hand_tiles, hand_threshold, hand_fraction)
+    log.debug(f'Selected HAND tile candidates {hand_candidates}')
+
+    selected_tiles = None
+    water_extent_maps = []
+    for max_db_threshold, raster, pol in ((max_vh_threshold, vh_raster, 'VH'), (max_vv_threshold, vv_raster, 'VV')):
+        log.info(f'Creating initial {pol} water extent map from {raster}')
+        array = read_as_masked_array(raster)
+        tiles = tile_array(array, tile_shape=tile_shape, pad_value=0.)
+        # Masking less than zero only necessary for old HyP3/GAMMA products which sometimes returned negative powers
+        tiles = np.ma.masked_less_equal(tiles, 0.)
+        if selected_tiles is None:
+            selected_tiles = select_backscatter_tiles(tiles, hand_candidates)
+            log.info(f'Selected tiles {selected_tiles} from {raster}')
+
+        with np.testing.suppress_warnings() as sup:
+            sup.filter(RuntimeWarning)  # invalid value and divide by zero encountered in log10
+            tiles = np.log10(tiles) + 30.  # linear power scale -> Gaussian scale optimized for thresholding
+        max_gaussian_threshold = max_db_threshold / 10. + 30.  # db -> Gaussian scale optimized for thresholding
+        if selected_tiles.size:
+            scaling = 256 / (np.mean(tiles) + 3 * np.std(tiles))
+            gaussian_threshold = determine_em_threshold(tiles[selected_tiles, :, :], scaling)
+            threshold_db = 10. * (gaussian_threshold - 30.)
+            log.info(f'Threshold determined to be {threshold_db} db')
+            if gaussian_threshold > max_gaussian_threshold:
+                log.warning(f'Threshold too high! Using maximum threshold {max_db_threshold} db')
+                gaussian_threshold = max_gaussian_threshold
+        else:
+            log.warning(f'Tile selection did not converge! using default threshold {max_db_threshold} db')
+            gaussian_threshold = max_gaussian_threshold
+
+        gaussian_array = untile_array(tiles, array.shape)
+        water_map = np.ma.masked_less_equal(gaussian_array, gaussian_threshold).mask
+        water_map &= ~array.mask
+
+        write_cog(str(out_raster).replace('.tif', f'_{pol}_initial.tif'), water_map, transform=out_transform,
+                  epsg_code=out_epsg, dtype=gdal.GDT_Byte, nodata_value=False)
+
+        log.info(f'Refining initial {pol} water extent map using Fuzzy Logic')
+        array = np.ma.masked_where(~water_map, array)
+        gaussian_lower_limit = np.log10(np.ma.median(array)) + 30.
+
+        water_map = fuzzy_refinement(
+            water_map, gaussian_array, hand_array, pixel_size=out_transform[1],
+            gaussian_thresholds=(gaussian_lower_limit, gaussian_threshold), membership_threshold=membership_threshold
+        )
+        water_map &= ~array.mask
+
+        write_cog(str(out_raster).replace('.tif', f'_{pol}_fuzzy.tif'), water_map, transform=out_transform,
+                  epsg_code=out_epsg, dtype=gdal.GDT_Byte, nodata_value=False)
+
+        water_extent_maps.append(water_map)
+
+    log.info('Combining Fuzzy VH and VV extent map')
+    combined_water_map = np.logical_or(*water_extent_maps)
+
+    combined_segments = measure.label(combined_water_map, connectivity=2)
+    combined_water_map = remove_small_segments(combined_segments)
+
+    write_cog(out_raster, combined_water_map, transform=out_transform,
+              epsg_code=out_epsg, dtype=gdal.GDT_Byte, nodata_value=False)
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description=__doc__,
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter
+    )
+
+    parser.add_argument('out_raster', help='Water map GeoTIFF to create')
+    # FIXME: Decibel RTCs would be real nice.
+    parser.add_argument('vv_raster',
+                        help='Sentinel-1 RTC GeoTIFF raster, in power scale, with VV polarization')
+    parser.add_argument('vh_raster',
+                        help='Sentinel-1 RTC GeoTIFF raster, in power scale, with VH polarization')
+
+    parser.add_argument('--hand-raster',
+                        help='Height Above Nearest Drainage (HAND) GeoTIFF aligned to the RTC rasters. '
+                             'If not specified, HAND data will be extracted from a Copernicus GLO-30 DEM based HAND.')
+    parser.add_argument('--tile-shape', type=int, nargs=2, default=(100, 100),
+                        help='shape (height, width) in pixels to tile the image to')
+    parser.add_argument('--max-vv-threshold', type=float, default=-15.5,
+                        help='Maximum threshold value to use for `vv_raster` in decibels (db)')
+    parser.add_argument('--max-vh-threshold', type=float, default=-23.0,
+                        help='Maximum threshold value to use for `vh_raster` in decibels (db)')
+    parser.add_argument('--hand-threshold', type=float, default=15.,
+                        help='The maximum height above nearest drainage in meters to consider a pixel valid')
+    parser.add_argument('--hand-fraction', type=float, default=0.8,
+                        help='The minimum fraction of valid HAND pixels required in a tile for thresholding')
+    parser.add_argument('--membership-threshold', type=float, default=0.45,
+                        help='The average membership to the fuzzy indicators required for a water pixel')
+
+    parser.add_argument('-v', '--verbose', action='store_true', help='Turn on verbose logging')
+    args = parser.parse_args()
+
+    level = logging.DEBUG if args.verbose else logging.INFO
+    logging.basicConfig(stream=sys.stdout, format='%(asctime)s - %(levelname)s - %(message)s', level=level)
+    log.debug(' '.join(sys.argv))
+
+    make_water_map(args.out_raster, args.vv_raster, args.vh_raster, args.hand_raster, args.tile_shape,
+                   args.max_vv_threshold, args.max_vh_threshold, args.hand_threshold, args.hand_fraction,
+                   args.membership_threshold)
+
+    log.info(f'Water map created successfully: {args.out_raster}')
diff --git a/conda-env.yml b/environment.yml
similarity index 75%
rename from conda-env.yml
rename to environment.yml
index 2ac3189f..49dce638 100644
--- a/conda-env.yml
+++ b/environment.yml
@@ -4,6 +4,7 @@ channels:
   # - esri  # windows only
   - nodefaults
 dependencies:
+  - python>=3.8
   - pip
   # For packaging, and testing
   # - arcpy  # windows only
@@ -18,9 +19,16 @@ dependencies:
   - pytest-console-scripts
   - pytest-cov
   # For running
+  - astropy
+  - fiona
+  - gdal>=3.3
   - numpy
-  - gdal>=3.2.0
+  - pysheds
+  - rasterio
+  - scikit-fuzzy
+  - scikit-image
+  - shapely
   - pip:
     # for packaging and testing
     - s3pypi
-    - safety
+    - gdal-utils
diff --git a/postBuild b/postBuild
new file mode 100644
index 00000000..5b0aba61
--- /dev/null
+++ b/postBuild
@@ -0,0 +1,6 @@
+#!/bin/bash
+# This file is used by Binder to install this repo into the binder environment
+#    https://mybinder.readthedocs.io/en/latest/using/config_files.html#postbuild-run-code-after-installing-the-environment
+# Notably, providing an environment.yml file means pip files (requirements.txt and setup.py) are ignored
+
+python -m pip install -e .[develop]
diff --git a/prototype/osl-env.yml b/prototype/osl-env.yml
new file mode 100644
index 00000000..8f744aaa
--- /dev/null
+++ b/prototype/osl-env.yml
@@ -0,0 +1,21 @@
+name: asf-tools
+channels:
+  - conda-forge
+  - nodefaults
+dependencies:
+  - python>=3.8
+  - pip
+  # For running
+  - asf_search>=0.4
+  - astropy
+  - fiona
+  - gdal>=3.2.0
+  - hyp3_sdk>=1.1
+  - jupyter
+  - kernda
+  - numpy
+  - pysheds
+  - rasterio
+  - scikit-fuzzy
+  - scikit-image
+  - shapely
diff --git a/prototype/osl-readme.md b/prototype/osl-readme.md
new file mode 100644
index 00000000..93940a45
--- /dev/null
+++ b/prototype/osl-readme.md
@@ -0,0 +1,66 @@
+# Creating an `asf_tools` prototype environment in OpenSARlab
+
+<https://opensarlab.asf.alaska.edu/>
+
+**NOTE:** OpenSARlab was [upgraded on June 10, 2021](https://github.com/ASFOpenSARlab/opensarlab-docs/blob/master/OpenSARlab_release_notes/OpenSARlab_Release_Notes_June_2021.ipynb)
+with much better support for conda environments. This significantly simplifies setting
+up development environments and these instructions have changed accordingly.
+
+## Initial (one-time) setup of an `asf_tools` development environment
+
+```bash
+git clone https://github.com/ASFHyP3/asf-tools.git
+cd asf-tools
+
+conda env create -f prototype/osl-env.yml
+conda activate asf-tools
+
+# install an editable/develop version of the `asf_tools` python package
+python -m pip install -e .
+
+# test `asf_tools` is installed:
+python -c 'import asf_tools; print(asf_tools.__version__)'
+```
+
+Refresh your browser and the new environment should be available!
+
+Now you can:
+* Create a new notebook using the `asf-tools` kernel
+  
+  ![new_notebook_kernel](https://user-images.githubusercontent.com/7882693/121728495-ce9af180-ca99-11eb-84eb-8114b7ce1183.png)
+
+* Change the kernel of an existing notebook to the `asf-tools` kernel
+  
+  ![change_kernel](https://user-images.githubusercontent.com/7882693/121728676-0efa6f80-ca9a-11eb-959d-656c9376a8d7.png)
+
+### Keeping in sync with changes upstream
+
+*Note:* These instructions assume you're working from the head/root of the asf-tools repository.
+
+In the future, as dependencies are added or removed, you can update the conda environment with
+```bash
+conda env update -f prototype/osl-env.yml
+```
+and you should *not* need to restart OpenSARlab for the environment updates to take effect.
+
+As `asf_tools` itself is developed, you may need to reinstall the editable version
+if the package structure has significantly changed, or entrypoints have been added/removed.
+```bash
+python -m pip install -e .
+```
+
+In general, any time you pull changes it's a good idea to run both of the above commands.
+
+## Run the example `water-extent-map.ipynb`
+
+Once you've completed the above one-time setup, you can run through the example notebook,
+which will be located in the `prototype/` directory:
+* `water-extent-map.ipynb` -- Run the water map code directly. This allows you to
+  edit the code and see the changes reflected.
+* `water-extent-map-on-demand.ipynb` -- Request water map products from the custom HyP3-watermap deployment
+   using either ASF Search Vertex or the `asf_search` Python package, and the HyP3 SDK.
+
+  ![prototype notebooks](https://user-images.githubusercontent.com/7882693/122486645-f7cded00-cf85-11eb-8c94-2ddd63961059.png)
+
+
+*Note: Make sure you change the notebook kernel for each of these notebooks to the `asf-tools` kernel!*
diff --git a/prototype/water-extent-map-on-demand.ipynb b/prototype/water-extent-map-on-demand.ipynb
new file mode 100644
index 00000000..19c4d0e9
--- /dev/null
+++ b/prototype/water-extent-map-on-demand.ipynb
@@ -0,0 +1,334 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# On Demand water maps via HyP3-watermap\n",
+    "\n",
+    "This notebook will leverage either [ASF Search -- Vertex](https://search.asf.alaska.edu/#/) or the\n",
+    "[asf_search](https://github.com/asfadmin/Discovery-asf_search) Python package, and the\n",
+    "[HyP3 SDK](https://hyp3-docs.asf.alaska.edu/using/sdk/), to request On Demand surface water extent maps\n",
+    "from the custom [hyp3-watermap](https://hyp3-watermap.asf.alaska.edu) HyP3 deployment.\n",
+    "\n",
+    "Water maps are generated from Sentinel-1 SLCs or GRDs by:\n",
+    "1. Applying Radiometric Terrain Correction (RTC)\n",
+    "2. Creating initial VV- and VH-based water maps using a thresholding approach\n",
+    "3. Refining the initial VV- and VH-based water maps using fuzzy logic\n",
+    "4. Combining the refined VV- and VH-based water maps into a final water map\n",
+    "\n",
+    "For more information on the methods, or to modify the water map methods and process them locally, see the\n",
+    "[water-extent-map.ipynb](water-extent-map.ipynb) notebook.\n",
+    "\n",
+    "## 0. Initial setup\n",
+    "\n",
+    "Import and setup some helper functions for this notebook."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "import ipywidgets as widgets\n",
+    "from IPython.display import display\n",
+    "\n",
+    "def wkt_input():\n",
+    "    wkt = widgets.Textarea(\n",
+    "        placeholder='WKT of search area',\n",
+    "        value='POLYGON((-91.185 36.6763,-86.825 36.6763,-86.825 38.9176,-91.185 38.9176,-91.185 36.6763))',\n",
+    "        layout=widgets.Layout(width='100%'),\n",
+    "    )\n",
+    "    display(wkt)\n",
+    "    return wkt\n",
+    "\n",
+    "def file_ids_input():\n",
+    "    file_ids = widgets.Textarea(\n",
+    "        placeholder='copy-paste Sentinel-1 granule names or file ids here (One granule or id per line)',\n",
+    "        layout=widgets.Layout(width='100%', height='12em'),\n",
+    "    )\n",
+    "    display(file_ids)\n",
+    "    return file_ids"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 1. Search for Sentinel-1 scenes to process\n",
+    "\n",
+    "You can search for Sentinel-1 scenes with either [ASF Search -- Vertex](https://search.asf.alaska.edu/#/) or the\n",
+    "[asf_search](https://github.com/asfadmin/Discovery-asf_search) Python package. Vertex provides an interactive,\n",
+    "feature rich experience, while `asf_search` allows searching programmatically and mirrors the vertex interface\n",
+    "as best it can. Section 1.1 describes using Vertex and Section 1.2 describes using `asf_search`.\n",
+    "\n",
+    "*Note: only 1.1 or 1.2 needs to be executed to run this notebook.*\n",
+    "\n",
+    "### 1.1 Search for  Sentinel-1 scenes in Vertex\n",
+    "\n",
+    "Requesting water map products from the custom HyP3-watermap deployment looks very similar to\n",
+    "[requesting On Demand RTC products](https://storymaps.arcgis.com/stories/2ead3222d2294d1fae1d11d3f98d7c35),\n",
+    "**except** instead of adding scenes to your On Demand queue, you'll:\n",
+    "1. add the scenes to your Downloads cart\n",
+    "   ![add to cart](https://user-images.githubusercontent.com/7882693/122344682-85acc800-cef3-11eb-8337-5a356b722c12.png)\n",
+    "\n",
+    "\n",
+    "2. open the Downloads Cart and select \"Copy File Ids\", and\n",
+    "   ![image](https://user-images.githubusercontent.com/7882693/122345160-04096a00-cef4-11eb-8c27-892329293e4d.png)\n",
+    "\n",
+    "\n",
+    "3. paste the file ids into the text area that will appear below the next cell.\n",
+    "\n",
+    "**Note:** Water maps currently require the Sentinel-1 source granules to be SLCs (preferred) or High-Res GRDs,\n",
+    "acquired using the IW beam mode, with both VV and VH polarizations. You can use the\n",
+    "[example search](https://search.asf.alaska.edu/#/?beamModes=IW&polarizations=VV%2BVH&productTypes=SLC&zoom=6.190&center=-91.993,33.963&polygon=POLYGON((-91.185%2036.6763,-86.825%2036.6763,-86.825%2038.9176,-91.185%2038.9176,-91.185%2036.6763))&start=2021-05-30T00:00:00Z&resultsLoaded=true&granule=S1A_IW_GRDH_1SDV_20210607T234810_20210607T234835_038241_04834F_4BB6-GRD_HD&end=2021-06-07T23:59:59Z)\n",
+    "or jump-start your search in Vertex (with the required parameters already set) by following [this link](https://search.asf.alaska.edu/#/?dataset=Sentinel-1&productTypes=SLC&beamModes=IW&polarizations=VV%2BVH)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "file_ids = file_ids_input()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "all_granules = [f.strip().split('-')[0] for f in file_ids.value.splitlines()]\n",
+    "display(sorted(all_granules))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.2 Search for Sentinel-1 scenes with `asf_search`\n",
+    "\n",
+    "We'll use the geographic search functionality of `asf_search` to perform a search over an Area of\n",
+    "Interest (AOI) represented as [Well-Known Text (WKT)](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry).\n",
+    "\n",
+    "You can use the example WKT, or copy and paste your AOI's WKT, in the text area that will appear below the next cell."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "wkt = wkt_input()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Water maps currently require the Sentinel-1 source granules to be SLCs (preferred) or High-Res GRDs,\n",
+    "acquired using the IW beam mode, with both VV and VH polarizations. The next cell performs a search over your AOI,\n",
+    "with these parameters set.\n",
+    "\n",
+    "*Note: You will likely want to edit the `start` and `end` parameters.*"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import asf_search\n",
+    "from asf_search.constants import SENTINEL1, SLC, IW, VV_VH\n",
+    "\n",
+    "search_results = asf_search.geo_search(\n",
+    "    platform=[SENTINEL1],\n",
+    "    processingLevel=[SLC],\n",
+    "    beamMode=[IW],\n",
+    "    polarization=[VV_VH],\n",
+    "    intersectsWith=wkt.value,\n",
+    "    start='2021-05-30',\n",
+    "    end='2021-06-08',\n",
+    ")\n",
+    "all_granules = {result.properties['sceneName'] for result in search_results}\n",
+    "display(sorted(all_granules))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 2. Request water maps from HyP3-watermap\n",
+    "\n",
+    "### 2.1 Connect to the HyP3-watermap deployment\n",
+    "\n",
+    "Use the HyP3 SDK to connect to the custom deployment with your [NASA Earthdata login](https://urs.earthdata.nasa.gov/)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import hyp3_sdk\n",
+    "\n",
+    "hyp3_watermap = hyp3_sdk.HyP3('https://hyp3-watermap.asf.alaska.edu', prompt=True)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 2.2 Specify the custom water map parameters\n",
+    "\n",
+    "Below is a dictionary representation of the possible customization options for a water-map job.\n",
+    "Importantly, this definition will be applied to each granule in our search results, so these\n",
+    "options will be used with each job we submit.\n",
+    "\n",
+    "You may change any or all of them, and in particular, you will likely want to use the\n",
+    "`name` parameter to group each \"batch\" of jobs together and easily find them later."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "job_definition = {\n",
+    "    'name': 'water-map-example',\n",
+    "    'job_type': 'WATER_MAP',\n",
+    "    'job_parameters': {\n",
+    "        'resolution': 30,\n",
+    "        'speckle_filter': True,\n",
+    "        'max_vv_threshold': -15.5,\n",
+    "        'max_vh_threshold': -23.0,\n",
+    "        'hand_threshold': 15.0,\n",
+    "        'hand_fraction': 0.8,\n",
+    "        'membership_threshold': 0.45,\n",
+    "    }\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 2.3 Submit the jobs to the custom HyP3-watermap deployment\n",
+    "\n",
+    "Using the job definition as defined above (make sure you run the cell!), this will submit a job for\n",
+    "each granule in the search results."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import copy\n",
+    "\n",
+    "prepared_jobs = []\n",
+    "for granule in all_granules:\n",
+    "    job = copy.deepcopy(job_definition)\n",
+    "    job['job_parameters']['granules'] = [granule]\n",
+    "    prepared_jobs.append(job)\n",
+    "\n",
+    "jobs = hyp3_watermap.submit_prepared_jobs(prepared_jobs)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Once the jobs are submitted, you can watch for them to complete (it will take ~30 min for all jobs to finish)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "jobs = hyp3_watermap.watch(jobs)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Or, you can come back later and find your jobs by name, and make sure they're finished"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "jobs = hyp3_watermap.find_jobs(name='water-map-example')\n",
+    "jobs = hyp3_watermap.watch(jobs)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Once all jobs are complete, you can download the products for each successful job"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "jobs.download_files('data/')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Notes on viewing/evaluating the water map products\n",
+    "\n",
+    "* All GeoTIFFs in the RTC products are Cloud-Optimized, including the water map files `*_WM.tif`, and will have overviews/pyramids.\n",
+    "\n",
+    "  **This means the `*_WM.tif`'s appear to have a significantly higher water extent than they do in reality until you zoom in.**"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "asf-tools",
+   "language": "python",
+   "name": "conda-env-.local-asf-tools-py"
+  },
+  "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.9.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/prototype/water-extent-map.ipynb b/prototype/water-extent-map.ipynb
new file mode 100644
index 00000000..e441a019
--- /dev/null
+++ b/prototype/water-extent-map.ipynb
@@ -0,0 +1,204 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# HyP3 prototype water extent map product\n",
+    "\n",
+    "This notebook mimics the workflow a HyP3 Water Map plugin will follow to create an On Demand surface water extent map. It is intended demonstrate *how* HyP3 goes from an On Demand request to a product as well as provide a platform for testing/tuning/refinement ot the water extent map product.\n",
+    "\n",
+    "Importantly, step 2 will become the plugin's primary interface, and the options provided to the `make_water_map` function are able to be exposed to users. Step 1 is a required data marshaling step that will be handled transparently inside the container.\n",
+    "\n",
+    "If you'd like an overview of the water extent mapping process, run the following cell to get the `make_water_map` documentation, otherwise, skip to step 1."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from asf_tools.water_map import make_water_map\n",
+    "\n",
+    "make_water_map?"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 1. Generate input dual-pol RTC product\n",
+    "\n",
+    "Water extent mapping requires Radiometrically Terrain Corrected (RTC) Sentinel-1 scenes in order to accurately connect backscatter features to the input HAND data. The next few cells will use HyP3's Python SDK to request an RTC product from HyP3 for the Sentinel-1 granule (scene):\n",
+    "\n",
+    "[S1A_IW_GRDH_1SDV_20210413T235641_20210413T235706_037439_0469D0_3F2B](https://search.asf.alaska.edu/#/?zoom=6.964&center=-90.509,40.296&resultsLoaded=true&granule=S1A_IW_GRDH_1SDV_20210413T235641_20210413T235706_037439_0469D0_3F2B-METADATA_GRD_HD&searchType=List%20Search&searchList=S1A_IW_GRDH_1SDV_20210413T235641_20210413T235706_037439_0469D0_3F2B)\n",
+    "\n",
+    "### 1.1 Connect to HyP3"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import logging\n",
+    "import shutil\n",
+    "import hyp3_sdk as sdk\n",
+    "\n",
+    "logging.basicConfig(level=logging.INFO)\n",
+    "\n",
+    "hyp3 = sdk.HyP3(prompt=True)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.2  Submit an On Demand request for the input RTC product\n",
+    "\n",
+    "Currently, the RTC options provided to `hyp3.submit_rtc_job` as specified are expected/required by `make_water_map` and are not recommended to be changed, except for:\n",
+    "\n",
+    "* `speckle_filter` -- Apply an Enhanced Lee speckle filter.\n",
+    "\n",
+    "* `dem_matching` -- Coregisters SAR data to the DEM, rather than using dead reckoning based on orbit files. See the [Terrain Correction](https://hyp3-docs.asf.alaska.edu/guides/rtc_product_guide/#terrain-correction) in the RTC Product Guide for more information.\n",
+    "\n",
+    "* `include_dem` -- include a GeoTIF of the Copernicus DEM pixel aligned to the RTC products. This GeoTIFF isn't used by `make_water_map` but was used to create the RTC product and the HAND data\n",
+    "\n",
+    "* `include_rgb` -- include a false-color RGB decomposition for dual-pol granules. This GeoTIFF isn't used by `make_water_map` but may help you [interpret the RTC image](https://github.com/ASFHyP3/hyp3-lib/blob/develop/docs/rgb_decomposition.md#interpreting-the-images) and evaluate the water extent map\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "granule = 'S1A_IW_GRDH_1SDV_20210413T235641_20210413T235706_037439_0469D0_3F2B'\n",
+    "\n",
+    "job = hyp3.submit_rtc_job(\n",
+    "    granule, name='water-extent-example',\n",
+    "    radiometry='gamma0', scale='power', resolution=30, dem_name='copernicus',\n",
+    "    include_dem=True, include_rgb=True,\n",
+    "    speckle_filter=True, dem_matching=False\n",
+    ")\n",
+    "print(job)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.3 Wait for the RTC product to be generated\n",
+    "\n",
+    "This will take $\\approx$ 25 minutes to complete."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "job = hyp3.watch(job)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.4 Download the RTC product\n",
+    "\n",
+    "RTC products are downloaded as Zip files and then extracted. The GeoTIFF for the VV and VH polarizations are selected from the (unzipped) product folder."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "product_zip = job.download_files()[0]\n",
+    "shutil.unpack_archive(product_zip)\n",
+    "vv_raster = product_zip.parent / product_zip.stem / f'{product_zip.stem}_VV.tif'\n",
+    "vh_raster = product_zip.parent / product_zip.stem / f'{product_zip.stem}_VH.tif'\n",
+    "print(f'VV raster: {vv_raster}')\n",
+    "print(f'VH raster: {vv_raster}')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 2. Generate the water extent map\n",
+    "\n",
+    "Now that we have marshalled all the required input data, we can generate the surface water extent map!\n",
+    "\n",
+    "**Note:** this will take $\\approx$ 30 minutes to complete."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from asf_tools.water_map import make_water_map\n",
+    "\n",
+    "water_extent_raster = 'water_extent.tif'\n",
+    "make_water_map(water_extent_raster, vv_raster, vh_raster, tile_shape=(100, 100),\n",
+    "               max_vv_threshold=-15.5, max_vh_threshold=-23., hand_threshold=15., hand_fraction=0.8)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "`make_water_map` in [water_map.py](../asf_tools/water_map.py) is the primary \"science\" interface for generating the water maps. It *should* include all the necessary options for tuning the product -- if it doesn't, and you'd like other things exposed, either:\n",
+    "* Ask us to expose it by:\n",
+    "  * [Chatting with us in Gitter](https://gitter.im/ASFHyP3/community)\n",
+    "  * [Opening an issue](https://github.com/ASFHyP3/asf-tools/issues/new/choose)\n",
+    "\n",
+    "* [Fork](https://github.com/ASFHyP3/hyp3-sdk/fork) and propose changes in a Pull Request. See our [contributing guidelines](https://hyp3-docs.asf.alaska.edu/contributing/).\n",
+    "\n",
+    "For more info on the `make_water_map` function, run the cell below."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from asf_tools.water_map import make_water_map\n",
+    "make_water_map?"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "asf-tools",
+   "language": "python",
+   "name": "conda-env-.local-asf-tools-py"
+  },
+  "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.9.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/setup.py b/setup.py
index bcfe0446..2ef85200 100644
--- a/setup.py
+++ b/setup.py
@@ -31,8 +31,15 @@
     python_requires='~=3.8',
 
     install_requires=[
+        'astropy',
+        'fiona',
+        'gdal>=3.3',
         'numpy',
-        'gdal>=3.2.0',
+        'pysheds',
+        'scikit-fuzzy',
+        'scikit-image',
+        'shapely',
+        'rasterio',
     ],
 
     extras_require={
@@ -41,6 +48,7 @@
             'flake8-import-order',
             'flake8-blind-except',
             'flake8-builtins',
+            'gdal-utils',
             'pytest',
             'pytest-cov',
             'pytest-console-scripts',
@@ -52,6 +60,8 @@
     entry_points={
         'console_scripts': [
             'make_composite = asf_tools.composite:main',
+            'water_map = asf_tools.water_map:main',
+            'calculate_hand = asf_tools.hand.calculate:main',
         ]
     },
 


Test patch:
diff --git a/.github/workflows/test-and-build.yml b/.github/workflows/test-and-build.yml
deleted file mode 100644
index ca0b24ad..00000000
--- a/.github/workflows/test-and-build.yml
+++ /dev/null
@@ -1,164 +0,0 @@
-name: Test and build
-
-on:
-  push:
-    branches:
-      - main
-      - develop
-  pull_request:
-    branches:
-      - main
-      - develop
-
-env:
-  S3_PYPI_HOST: hyp3-pypi-west.s3-website-us-west-2.amazonaws.com
-  S3_PYPI_BUCKET: hyp3-pypi-west
-  AWS_REGION: us-west-2
-  AWS_ACCESS_KEY_ID: ${{ secrets.V2_AWS_ACCESS_KEY_ID }}
-  AWS_SECRET_ACCESS_KEY: ${{ secrets.V2_AWS_SECRET_ACCESS_KEY }}
-
-jobs:
-  pytest:
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        python-version: [3.8, 3.9]
-
-    steps:
-      - uses: actions/checkout@v2
-
-      - uses: conda-incubator/setup-miniconda@v2
-        with:
-          auto-update-conda: true
-          python-version: ${{ matrix.python-version }}
-          activate-environment: asf-tools
-          environment-file: conda-env.yml
-
-      - name: Pytest in conda environment
-        shell: bash -l {0}
-        run: |
-          python -m pip install .[develop]
-          pytest --cov=asf_tools
-
-      - name: Safety analysis of conda environment
-        shell: bash -l {0}
-        run: |
-          python -m pip freeze | safety check --full-report --stdin
-          conda list --export | awk -F '=' '/^[^#]/ {print $1 "==" $2}' | safety check --full-report --stdin
-
-
-  package:
-    runs-on: ubuntu-latest
-    needs: pytest
-    outputs:
-      SDIST_VERSION:  ${{ steps.build.outputs.version }}
-    steps:
-      - uses: actions/checkout@v2
-        with:
-          fetch-depth: 0
-          token: ${{ secrets.TOOLS_BOT_PAK }}
-
-      - name: Get associated PR
-        if: github.ref == 'refs/heads/main'
-        uses: helaili/github-graphql-action@2.0.1
-        env:
-          GITHUB_TOKEN: ${{ secrets.TOOLS_BOT_PAK }}
-        with:
-          query: .github/queries/asssociated-pr.query.yml
-          outputFile: pr.json
-          owner: ASFHyP3
-          name: asf-tools
-          sha: ${{ github.sha }}
-
-      - name: Get PR labels
-        if: github.ref == 'refs/heads/main'
-        uses: helaili/github-graphql-action@2.0.1
-        env:
-          GITHUB_TOKEN: ${{ secrets.TOOLS_BOT_PAK }}
-        with:
-          query: .github/queries/pr-labels.query.yml
-          outputFile: labels.json
-          owner: ASFHyP3
-          name: asf-tools
-
-      - name: Upload a Build Artifact
-        if: github.ref == 'refs/heads/main'
-        uses: actions/upload-artifact@v2
-        with:
-          name: query-responces
-          path: '*.json'
-
-      - uses: actions/setup-python@v1
-        with:
-          python-version: 3.8
-
-      - name: Install dependencies
-        run: |
-          python -m pip install --upgrade pip
-          python -m pip install --upgrade setuptools wheel twine s3pypi "setuptools-scm[toml]" importlib_metadata
-          python -m pip install bump2version
-
-      - name: Tag version
-        if: github.ref == 'refs/heads/main'
-        run: |
-          git fetch origin +refs/tags/*:refs/tags/*
-          git config user.email "UAF-asf-apd@alaska.edu"
-          git config user.name "tools-bot"
-
-          LABEL_QUERY='.data.repository.pullRequest.labels.nodes[].name'
-          SELECT='select(. == "major" or . == "minor" or . == "patch")'
-          BUMP_PART=$(jq --raw-output  "${LABEL_QUERY} | ${SELECT}" labels.json | sort | head -1)
-
-          PR_QUERY='.data.repository.commit.associatedPullRequests.edges[0].node.title'
-          TAG_MSG=$(jq --raw-output "${PR_QUERY}"  pr.json)
-
-          bump2version --current-version $(git describe --abbrev=0) \
-              --tag --tag-message "${TAG_MSG}" "${BUMP_PART}"
-
-          git push --tags
-          echo "Tagged version $(git describe --abbrev=0) and pushed back to repo"
-
-      - uses: aws-actions/configure-aws-credentials@v1
-        with:
-          aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }}
-          aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }}
-          aws-region: ${{ env.AWS_REGION }}
-
-      - name: Build distribuion and upload to S3-PyPI
-        id: build
-        run: |
-          git fetch origin +refs/tags/*:refs/tags/*
-          export SDIST_VERSION=$(python setup.py --version)
-          echo "::set-output name=version::${SDIST_VERSION}"
-          python setup.py sdist bdist_wheel
-          echo "Uploading version ${SDIST_VERSION} to ${S3_PYPI_BUCKET}"
-          s3pypi --bucket ${S3_PYPI_BUCKET} --private --force --verbose
-
-      - name: Upload to PyPI.org
-        if: github.ref == 'refs/heads/master'
-        uses: pypa/gh-action-pypi-publish@master
-        with:
-          user: __token__
-          password: ${{ secrets.TOOLS_PYPI_PAK }}
-
-
-  verify-packaging:
-    runs-on: ubuntu-latest
-    needs: package
-    steps:
-      - uses: actions/checkout@v2
-
-      - uses: conda-incubator/setup-miniconda@v2
-        with:
-          auto-update-conda: true
-          python-version: 3.8
-          activate-environment: asf-tools
-          environment-file: conda-env.yml
-
-      - name: Pip install ${{ needs.package.outputs.SDIST_VERSION }}
-        shell: bash -l {0}
-        run: |
-          export SDIST_VERSION=${{ needs.package.outputs.SDIST_VERSION }}
-          python -m pip install asf_tools==${SDIST_VERSION} \
-              --trusted-host "${S3_PYPI_HOST}" \
-              --extra-index-url "http://${S3_PYPI_HOST}"
diff --git a/.github/workflows/tag.yml b/.github/workflows/test-and-tag.yml
similarity index 69%
rename from .github/workflows/tag.yml
rename to .github/workflows/test-and-tag.yml
index 692ee7ab..d613250a 100644
--- a/.github/workflows/tag.yml
+++ b/.github/workflows/test-and-tag.yml
@@ -1,13 +1,42 @@
-name: Tag new version
+name: Test and tag
 
 on:
   push:
     branches:
       - main
+      - develop
+  pull_request:
+    branches:
+      - main
+      - develop
 
 jobs:
+  pytest:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.8, 3.9]
+
+    steps:
+      - uses: actions/checkout@v2
+
+      - uses: conda-incubator/setup-miniconda@v2
+        with:
+          mamba-version: "*"
+          python-version: ${{ matrix.python-version }}
+          activate-environment: asf-tools
+          environment-file: environment.yml
+
+      - name: Pytest in conda environment
+        shell: bash -l {0}
+        run: |
+          python -m pip install .[develop]
+          pytest --cov=asf_tools
+
   tag:
     runs-on: ubuntu-latest
+    needs: pytest
+    if: github.ref == 'refs/heads/main'
     outputs:
       SDIST_VERSION:  ${{ steps.build.outputs.version }}
     steps:
@@ -21,7 +50,7 @@ jobs:
         env:
           GITHUB_TOKEN: ${{ secrets.TOOLS_BOT_PAK }}
         with:
-          query: .github/queries/asssociated-pr.query.yml
+          query: .github/queries/associated-pr.query.yml
           outputFile: pr.json
           owner: ASFHyP3
           name: asf-tools
@@ -37,15 +66,15 @@ jobs:
           owner: ASFHyP3
           name: asf-tools
 
-      - name: Upload a Build Artifact
+      - name: Upload query artifacts for debugging
         uses: actions/upload-artifact@v2
         with:
-          name: query-responses
+          name: query-responces
           path: '*.json'
 
       - uses: actions/setup-python@v1
         with:
-          python-version: 3.7
+          python-version: 3.8
 
       - name: Install dependencies
         run: |
diff --git a/tests/conftest.py b/tests/conftest.py
index e69de29b..a4825eaa 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -0,0 +1,39 @@
+from pathlib import Path
+
+import numpy as np
+import pytest
+
+
+def pytest_addoption(parser):
+    parser.addoption('--integration', action='store_true', default=False, dest='integration',
+                     help='enable integration tests')
+
+
+def pytest_collection_modifyitems(config, items):
+    if not config.getoption('--integration'):
+        integration_skip = pytest.mark.skip(reason='Integration tests not requested; skipping.')
+        for item in items:
+            if 'integration' in item.keywords:
+                item.add_marker(integration_skip)
+
+
+@pytest.fixture(scope='session')
+def raster_tiles():
+    tiles_file = Path(__file__).parent / 'data' / 'em_tiles.npz'
+    tile_data = np.load(tiles_file)
+    tiles = np.ma.MaskedArray(tile_data['tiles'], mask=tile_data['mask'])
+    return np.log10(tiles) + 30
+
+
+@pytest.fixture(scope='session')
+def thresholds():
+    thresholds_file = Path(__file__).parent / 'data' / 'em_thresholds.npz'
+    thresholds_data = np.load(thresholds_file)
+    return thresholds_data['thresholds']
+
+
+@pytest.fixture(scope='session')
+def hand_candidates():
+    hand_file = Path(__file__).parent / 'data' / 'hand_candidates.npz'
+    hand_data = np.load(hand_file)
+    return hand_data['hand_candidates']
diff --git a/tests/data/em_thresholds.npz b/tests/data/em_thresholds.npz
new file mode 100644
index 00000000..5dc70f87
Binary files /dev/null and b/tests/data/em_thresholds.npz differ
diff --git a/tests/data/em_tiles.npz b/tests/data/em_tiles.npz
new file mode 100644
index 00000000..d22538ba
Binary files /dev/null and b/tests/data/em_tiles.npz differ
diff --git a/tests/data/hand_candidates.npz b/tests/data/hand_candidates.npz
new file mode 100644
index 00000000..96914682
Binary files /dev/null and b/tests/data/hand_candidates.npz differ
diff --git a/tests/test_dem.py b/tests/test_dem.py
new file mode 100644
index 00000000..fe742783
--- /dev/null
+++ b/tests/test_dem.py
@@ -0,0 +1,68 @@
+import json
+
+import pytest
+from osgeo import gdal, ogr
+
+from asf_tools import dem
+
+gdal.UseExceptions()
+
+
+def test_prepare_dem_vrt_no_coverage():
+    geojson = {
+        'type': 'Point',
+        'coordinates': [0, 0],
+    }
+    geometry = ogr.CreateGeometryFromJson(json.dumps(geojson))
+    with pytest.raises(ValueError):
+        dem.prepare_dem_vrt('foo', geometry)
+
+
+def test_prepare_dem_vrt(tmp_path):
+    dem_vrt = tmp_path / 'dem.tif'
+    geojson = {
+        'type': 'Polygon',
+        'coordinates': [[
+            [0.4, 10.16],
+            [0.4, 10.86],
+            [0.6, 10.86],
+            [0.6, 10.16],
+            [0.4, 10.16],
+        ]],
+    }
+    geometry = ogr.CreateGeometryFromJson(json.dumps(geojson))
+
+    dem.prepare_dem_vrt(str(dem_vrt), geometry)
+    assert dem_vrt.exists()
+
+    info = gdal.Info(str(dem_vrt), format='json')
+    assert info['geoTransform'] == \
+           [-0.0001388888888889, 0.0002777777777778, 0.0, 11.00013888888889, 0.0, -0.0002777777777778]
+    assert info['size'] == [3600, 3600]
+
+
+def test_prepare_dem_geotiff_antimeridian(tmp_path):
+    dem_vrt = tmp_path / 'dem.vrt'
+    geojson = {
+        'type': 'MultiPolygon',
+        'coordinates': [
+            [[
+                [179.5, 51.4],
+                [179.5, 51.6],
+                [180.0, 51.6],
+                [180.0, 51.4],
+                [179.5, 51.4],
+            ]],
+            [[
+                [-180.0, 51.4],
+                [-180.0, 51.6],
+                [-179.5, 51.6],
+                [-179.5, 51.4],
+                [-180.0, 51.4],
+            ]],
+        ],
+    }
+    geometry = ogr.CreateGeometryFromJson(json.dumps(geojson))
+
+    with pytest.raises(ValueError):
+        dem.prepare_dem_vrt(str(dem_vrt), geometry)
diff --git a/tests/test_entrypoints.py b/tests/test_entrypoints.py
index 53364f3d..e25f172a 100644
--- a/tests/test_entrypoints.py
+++ b/tests/test_entrypoints.py
@@ -1,3 +1,13 @@
-def test_asf_tools(script_runner):
+def test_make_composite(script_runner):
     ret = script_runner.run('make_composite', '-h')
     assert ret.success
+
+
+def test_water_map(script_runner):
+    ret = script_runner.run('water_map', '-h')
+    assert ret.success
+
+
+def test_make_hand(script_runner):
+    ret = script_runner.run('calculate_hand', '-h')
+    assert ret.success
diff --git a/tests/test_hand.py b/tests/test_hand.py
new file mode 100644
index 00000000..bde24c2a
--- /dev/null
+++ b/tests/test_hand.py
@@ -0,0 +1,85 @@
+import json
+
+import pytest
+from osgeo import gdal, ogr
+from osgeo_utils.gdalcompare import find_diff
+
+from asf_tools import hand
+
+HAND_BASINS = '/vsicurl/https://hyp3-testing.s3-us-west-2.amazonaws.com/' \
+              'asf-tools/hand/hybas_af_lev12_v1c_firstpoly.geojson'
+GOLDEN_HAND = '/vsicurl/https://hyp3-testing.s3-us-west-2.amazonaws.com/' \
+              'asf-tools/hand/hybas_af_lev12_v1c_firstpoly.tif'
+
+gdal.UseExceptions()
+
+
+@pytest.mark.integration
+def test_make_copernicus_hand(tmp_path):
+
+    out_hand = tmp_path / 'hand.tif'
+    hand.make_copernicus_hand(out_hand, HAND_BASINS)
+
+    assert out_hand.exists()
+
+    diffs = find_diff(str(GOLDEN_HAND), str(out_hand))
+    assert diffs == 0
+
+
+def test_prepare_hand_vrt_no_coverage():
+    geojson = {
+        'type': 'Point',
+        'coordinates': [0, 0],
+    }
+    geometry = ogr.CreateGeometryFromJson(json.dumps(geojson))
+    with pytest.raises(ValueError):
+        hand.prepare_hand_vrt('foo', geometry)
+
+
+def test_prepare_hand_vrt(tmp_path):
+    hand_vrt = tmp_path / 'hand.tif'
+    geojson = {
+        'type': 'Polygon',
+        'coordinates': [[
+            [0.4, 10.16],
+            [0.4, 10.86],
+            [0.6, 10.86],
+            [0.6, 10.16],
+            [0.4, 10.16],
+        ]],
+    }
+    geometry = ogr.CreateGeometryFromJson(json.dumps(geojson))
+
+    hand.prepare_hand_vrt(str(hand_vrt), geometry)
+    assert hand_vrt.exists()
+
+    info = gdal.Info(str(hand_vrt), format='json')
+    assert info['geoTransform'] == \
+           [-0.0001388888888889, 0.0002777777777778, 0.0, 11.00013888888889, 0.0, -0.0002777777777778]
+    assert info['size'] == [3600, 3600]
+
+
+def test_prepare_hand_vrt_antimeridian():
+    geojson = {
+        'type': 'MultiPolygon',
+        'coordinates': [
+            [[
+                [179.5, 51.4],
+                [179.5, 51.6],
+                [180.0, 51.6],
+                [180.0, 51.4],
+                [179.5, 51.4],
+            ]],
+            [[
+                [-180.0, 51.4],
+                [-180.0, 51.6],
+                [-179.5, 51.6],
+                [-179.5, 51.4],
+                [-180.0, 51.4],
+            ]],
+        ],
+    }
+    geometry = ogr.CreateGeometryFromJson(json.dumps(geojson))
+
+    with pytest.raises(ValueError):
+        hand.prepare_hand_vrt('foo', geometry)
diff --git a/tests/test_raster.py b/tests/test_raster.py
new file mode 100644
index 00000000..7cf53ace
--- /dev/null
+++ b/tests/test_raster.py
@@ -0,0 +1,53 @@
+import numpy as np
+import pytest
+
+from asf_tools import raster
+
+
+def test_convert_scale():
+    c = raster.convert_scale(np.array([-10, -5, 0, 5, 10]), 'amplitude', 'power')
+    assert np.allclose(c, np.array([100, 25, 0, 25, 100]))
+
+    c = raster.convert_scale(np.array([-10, -5, 0, 5, 10]), 'amplitude', 'db')
+    assert np.allclose(c, np.array([20., 13.97940009, -np.inf, 13.97940009, 20.]))
+
+    c = raster.convert_scale(np.array([-1, 0, 1, 4, 9]), 'power', 'amplitude')
+    assert np.isnan(c[0])
+    assert np.allclose(c[1:], np.array([0, 1, 2, 3]))
+
+    c = raster.convert_scale(np.array([-1, 0, 1, 4, 9]), 'power', 'db')
+    assert np.isnan(c[0])
+    assert np.allclose(c[1:], np.array([-np.inf, 0., 6.02059991, 9.54242509]), )
+
+    c = raster.convert_scale(np.array([np.nan, -np.inf, 0., 6.02059991, 9.54242509]), 'db', 'power')
+    assert np.isnan(c[0])
+    assert np.allclose(c[1:], np.array([0, 1, 4, 9]))
+
+    c = raster.convert_scale(np.array([-np.inf, -20., 0., 13.97940009, 20.]), 'db', 'amplitude')
+    assert np.allclose(c, np.array([0., 0.1, 1., 5., 10.]))
+
+    a = np.array([-10, -5, 0, 5, 10])
+    with pytest.raises(ValueError):
+        _ = raster.convert_scale(a, 'power', 'foo')
+    with pytest.raises(ValueError):
+        _ = raster.convert_scale(a, 'bar', 'amplitude')
+
+    with pytest.warns(UserWarning):
+        assert np.allclose(raster.convert_scale(a, 'amplitude', 'amplitude'), np.array([-10, -5, 0, 5, 10]))
+    with pytest.warns(UserWarning):
+        assert np.allclose(raster.convert_scale(a, 'power', 'power'), np.array([-10, -5, 0, 5, 10]))
+    with pytest.warns(UserWarning):
+        assert np.allclose(raster.convert_scale(a, 'db', 'db'), np.array([-10, -5, 0, 5, 10]))
+
+
+def test_convert_scale_masked_arrays():
+    masked_array = np.ma.MaskedArray([-1, 0, 1, 4, 9], mask=[False, False, False, False, False])
+    c = raster.convert_scale(masked_array, 'power', 'db')
+    assert np.allclose(c.mask, [True, True, False, False, False])
+    assert np.allclose(
+        c, np.ma.MaskedArray([np.nan, -np.inf, 0., 6.02059991, 9.54242509], mask=[True, True, False, False, False])
+    )
+
+    a = raster.convert_scale(c, 'db', 'power')
+    assert np.allclose(a.mask, [True, True, False, False, False])
+    assert np.allclose(a, np.ma.MaskedArray([-1, 0, 1, 4, 9], mask=[True, True, False, False, False]))
diff --git a/tests/test_threshold.py b/tests/test_threshold.py
new file mode 100644
index 00000000..b1257887
--- /dev/null
+++ b/tests/test_threshold.py
@@ -0,0 +1,10 @@
+import numpy as np
+
+from asf_tools.threshold import expectation_maximization_threshold
+
+
+def test_determine_em_threshold(raster_tiles, thresholds):
+    scaling = 8.732284197109262
+    test_tiles = (np.around(raster_tiles * scaling)).astype(int)
+    for tile, threshold in zip(test_tiles, thresholds):
+        assert np.isclose(expectation_maximization_threshold(tile), threshold)
diff --git a/tests/test_tile.py b/tests/test_tile.py
new file mode 100644
index 00000000..ea87ff66
--- /dev/null
+++ b/tests/test_tile.py
@@ -0,0 +1,129 @@
+import numpy as np
+import pytest
+
+from asf_tools import tile
+
+
+def test_tile_array():
+    a = np.array([[0, 0, 1, 1],
+                  [0, 0, 1, 1],
+                  [2, 2, 3, 3],
+                  [2, 2, 3, 3]])
+
+    tiled = tile.tile_array(a, tile_shape=(2, 2))
+    assert tiled.shape == (4, 2, 2)
+    assert np.all(tiled[0, :, :] == np.array([[0, 0], [0, 0]]))
+    assert np.all(tiled[-1, :, :] == np.array([[3, 3], [3, 3]]))
+
+    with pytest.raises(ValueError):
+        tile.tile_array(a, tile_shape=(3, 3))
+
+    tiled = tile.tile_array(a, tile_shape=(3, 3), pad_value=4)
+    assert tiled.shape == (4, 3, 3)
+    assert np.all(tiled[0, :, :] == np.array([[0, 0, 1], [0, 0, 1], [2, 2, 3]]))
+    assert np.all(tiled[-1, :, :] == np.array([[3, 4, 4], [4, 4, 4], [4, 4, 4]]))
+
+    tiled = tile.tile_array(a, tile_shape=(2, 3), pad_value=4)
+    assert tiled.shape == (4, 2, 3)
+    assert np.all(tiled[0, :, :] == np.array([[0, 0, 1], [0, 0, 1]]))
+    assert np.all(tiled[-1, :, :] == np.array([[3, 4, 4], [3, 4, 4]]))
+
+    tiled = tile.tile_array(a, tile_shape=(3, 2), pad_value=4)
+    assert tiled.shape == (4, 3, 2)
+    assert np.all(tiled[0, :, :] == np.array([[0, 0], [0, 0], [2, 2]]))
+    assert np.all(tiled[-1, :, :] == np.array([[3, 3], [4, 4], [4, 4]]))
+
+
+def test_tile_masked_array():
+    a = np.array([[0, 0, 1, 1],
+                  [0, 0, 1, 1],
+                  [2, 2, 3, 3],
+                  [2, 2, 3, 3]])
+
+    with pytest.raises(AttributeError):
+        _ = tile.tile_array(a, tile_shape=(2, 2)).mask
+
+    m = np.array([[False, False, False, True],
+                  [False, False, False, False],
+                  [False, False, False, False],
+                  [False, False, False, True]])
+
+    ma = np.ma.MaskedArray(a, mask=m)
+    tiled = tile.tile_array(ma, tile_shape=(2, 2))
+
+    assert tiled.shape == (4, 2, 2)
+    assert isinstance(tiled, np.ma.MaskedArray)
+    assert np.all(
+        tiled.mask == np.array([[[False, False],
+                                 [False, False]],
+                                [[False, True],
+                                 [False, False]],
+                                [[False, False],
+                                 [False, False]],
+                                [[False, False],
+                                 [False, True]]])
+    )
+
+    tiled = tile.tile_array(ma, tile_shape=(3, 3), pad_value=4)
+    assert isinstance(tiled, np.ma.MaskedArray)
+    assert tiled.shape == (4, 3, 3)
+    assert np.all(np.ma.getdata(tiled[0, :, :]) == np.array([[0, 0, 1], [0, 0, 1], [2, 2, 3]]))
+    assert np.all(
+        tiled[0, :, :].mask == np.array([[False, False, False], [False, False, False], [False, False, False]])
+    )
+    assert np.all(np.ma.getdata(tiled[-1, :, :]) == np.array([[3, 4, 4], [4, 4, 4], [4, 4, 4]]))
+    assert np.all(
+        tiled[-1, :, :].mask == np.array([[True, True, True], [True, True, True], [True, True, True]])
+    )
+
+
+def test_untile_array():
+    a = np.array([[0, 0, 1, 1, 2, 2],
+                  [0, 0, 1, 1, 2, 2],
+                  [3, 3, 4, 4, 5, 5],
+                  [3, 3, 4, 4, 5, 5],
+                  [6, 6, 7, 7, 8, 8],
+                  [6, 6, 7, 7, 8, 8],
+                  ])
+
+    assert np.all(a == tile.untile_array(tile.tile_array(a, tile_shape=(2, 2)), array_shape=a.shape))
+    assert np.all(a == tile.untile_array(tile.tile_array(a, tile_shape=(4, 4), pad_value=9), array_shape=a.shape))
+    assert np.all(a == tile.untile_array(tile.tile_array(a, tile_shape=(2, 4), pad_value=9), array_shape=a.shape))
+    assert np.all(a == tile.untile_array(tile.tile_array(a, tile_shape=(4, 2), pad_value=9), array_shape=a.shape))
+
+    with pytest.raises(ValueError):
+        tile.untile_array(tile.tile_array(a, tile_shape=(4, 4)), array_shape=(9, 9))
+
+    with pytest.raises(ValueError):
+        tile.untile_array(tile.tile_array(a, tile_shape=(2, 4), pad_value=9), array_shape=(6, 9))
+
+    # array shape will subset some of the padding that was required to tile `a` with `tile_shape`
+    assert np.all(
+            np.pad(a, ((0, 0), (0, 2)), constant_values=9)
+            == tile.untile_array(tile.tile_array(a, tile_shape=(2, 4), pad_value=9), array_shape=(6, 8))
+    )
+
+
+def test_untile_masked_array():
+    a = np.array([[0, 0, 1, 1],
+                  [0, 0, 1, 1],
+                  [2, 2, 3, 3],
+                  [2, 2, 3, 3]])
+
+    with pytest.raises(AttributeError):
+        _ = tile.untile_array(tile.tile_array(a, tile_shape=(2, 2)), array_shape=a.shape).mask
+
+    m = np.array([[False, False, False, True],
+                  [False, False, False, False],
+                  [False, False, False, False],
+                  [False, False, False, True]])
+
+    ma = np.ma.MaskedArray(a, mask=m)
+    untiled = tile.untile_array(tile.tile_array(ma.copy(), tile_shape=(2, 2)), array_shape=a.shape)
+
+    assert np.all(ma == untiled)
+    assert np.all(ma.mask == untiled.mask)
+
+    untiled = tile.untile_array(tile.tile_array(ma.copy(), tile_shape=(3, 3), pad_value=4), array_shape=a.shape)
+    assert np.all(ma == untiled)
+    assert np.all(ma.mask == untiled.mask)
diff --git a/tests/test_vector.py b/tests/test_vector.py
new file mode 100644
index 00000000..37c32faf
--- /dev/null
+++ b/tests/test_vector.py
@@ -0,0 +1,62 @@
+import json
+
+from osgeo import ogr
+
+from asf_tools import dem, hand, vector
+
+
+def test_intersects_feature():
+    for vector_file in (dem.DEM_GEOJSON, hand.prepare.HAND_GEOJSON):
+        features = vector.get_features(vector_file)
+
+        geojson = {
+            'type': 'Point',
+            'coordinates': [169, -45],
+        }
+        geometry = ogr.CreateGeometryFromJson(json.dumps(geojson))
+        assert vector.get_property_values_for_intersecting_features(geometry, features)
+
+        geojson = {
+            'type': 'Point',
+            'coordinates': [0, 0],
+        }
+        geometry = ogr.CreateGeometryFromJson(json.dumps(geojson))
+        assert not vector.get_property_values_for_intersecting_features(geometry, features)
+
+
+def test_get_intersecting_feature_properties():
+    dem_tile_features = vector.get_features(dem.DEM_GEOJSON)
+
+    geojson = {
+        'type': 'Point',
+        'coordinates': [0, 0],
+    }
+    geometry = ogr.CreateGeometryFromJson(json.dumps(geojson))
+    assert vector.intersecting_feature_properties(geometry, dem_tile_features, 'file_path') == []
+
+    geojson = {
+        'type': 'Point',
+        'coordinates': [169, -45],
+    }
+    geometry = ogr.CreateGeometryFromJson(json.dumps(geojson))
+    assert vector.intersecting_feature_properties(geometry, dem_tile_features, 'file_path') == [
+        '/vsicurl/https://copernicus-dem-30m.s3.amazonaws.com/'
+        'Copernicus_DSM_COG_10_S46_00_E169_00_DEM/Copernicus_DSM_COG_10_S46_00_E169_00_DEM.tif'
+    ]
+
+    geojson = {
+        'type': 'MultiPoint',
+        'coordinates': [[0, 0], [169, -45], [-121.5, 73.5]]
+    }
+    geometry = ogr.CreateGeometryFromJson(json.dumps(geojson))
+    assert vector.intersecting_feature_properties(geometry, dem_tile_features, 'file_path') == [
+        '/vsicurl/https://copernicus-dem-30m.s3.amazonaws.com/'
+        'Copernicus_DSM_COG_10_S46_00_E169_00_DEM/Copernicus_DSM_COG_10_S46_00_E169_00_DEM.tif',
+        '/vsicurl/https://copernicus-dem-30m.s3.amazonaws.com/'
+        'Copernicus_DSM_COG_10_N73_00_W122_00_DEM/Copernicus_DSM_COG_10_N73_00_W122_00_DEM.tif',
+    ]
+
+
+def test_get_features():
+    for vector_file in (dem.DEM_GEOJSON, hand.prepare.HAND_GEOJSON):
+        assert len(vector.get_features(vector_file)) == 26445
diff --git a/tests/test_water_map.py b/tests/test_water_map.py
new file mode 100644
index 00000000..b55282ec
--- /dev/null
+++ b/tests/test_water_map.py
@@ -0,0 +1,50 @@
+import numpy as np
+import pytest
+from osgeo_utils.gdalcompare import find_diff
+
+from asf_tools import water_map
+from asf_tools.composite import read_as_array
+from asf_tools.tile import tile_array
+
+
+def test_determine_em_threshold(raster_tiles):
+    scaling = 8.732284197109262
+    threshold = water_map.determine_em_threshold(raster_tiles, scaling)
+    assert np.isclose(threshold, 27.482176801248677)
+
+
+@pytest.mark.integration
+def test_select_hand_tiles(hand_candidates):
+    hand_geotif = '/vsicurl/https://hyp3-testing.s3-us-west-2.amazonaws.com/asf-tools/water-map/20200603_HAND.tif'
+    hand_array = read_as_array(str(hand_geotif))
+    hand_tiles = np.ma.masked_invalid(tile_array(hand_array, tile_shape=(100, 100), pad_value=np.nan))
+
+    selected_tiles = water_map.select_hand_tiles(hand_tiles, 15., 0.8)
+    assert np.all(selected_tiles == hand_candidates)
+
+
+@pytest.mark.integration
+def test_select_backscatter_tiles(hand_candidates):
+    backscatter_geotif = '/vsicurl/https://hyp3-testing.s3-us-west-2.amazonaws.com/asf-tools/water-map/20200603_VH.tif'
+    backscatter_array = np.ma.masked_invalid(read_as_array(backscatter_geotif))
+    backscatter_tiles = np.ma.masked_less_equal(tile_array(backscatter_array, tile_shape=(100, 100), pad_value=0.), 0.)
+
+    tile_indexes = water_map.select_backscatter_tiles(backscatter_tiles, hand_candidates)
+    assert np.all(tile_indexes == np.array([771, 1974, 2397, 1205, 2577]))
+
+
+@pytest.mark.integration
+def test_make_water_map(tmp_path):
+    vv_geotif = '/vsicurl/https://hyp3-testing.s3-us-west-2.amazonaws.com/asf-tools/water-map/20200603_VV.tif'
+    vh_geotif = '/vsicurl/https://hyp3-testing.s3-us-west-2.amazonaws.com/asf-tools/water-map/20200603_VH.tif'
+    hand_geotif = '/vsicurl/https://hyp3-testing.s3-us-west-2.amazonaws.com/asf-tools/water-map/20200603_HAND.tif'
+
+    out_water_map = tmp_path / 'water_map.tif'
+    water_map.make_water_map(out_water_map, vv_geotif, vh_geotif, hand_geotif)
+
+    assert out_water_map.exists()
+
+    golden_water_map = '/vsicurl/https://hyp3-testing.s3-us-west-2.amazonaws.com/asf-tools/water-map/' \
+                       'fuzzy-water-map.tif'
+    diffs = find_diff(golden_water_map, str(out_water_map))
+    assert diffs == 0