-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
r.trees.thresholds: addon to propose thresholds for creation of train…
…ing data and postprocessing
- Loading branch information
1 parent
2209d24
commit 2f185cb
Showing
3 changed files
with
252 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
MODULE_TOPDIR = ../../.. | ||
|
||
PGM = r.trees.thresholds | ||
|
||
include $(MODULE_TOPDIR)/include/Make/Script.make | ||
|
||
default: script |
27 changes: 27 additions & 0 deletions
27
grass-gis-addons/m.analyse.trees/r.trees.thresholds/r.trees.thresholds.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<h2>DESCRIPTION</h2> | ||
|
||
<em>r.trees.thresholds</em> proposes thresholds for NDVI and NIR as these | ||
depend on the respective flight conditions. | ||
|
||
|
||
<h2>EXAMPLES</h2> | ||
|
||
<h3>Proposing thresholds for trainingdata generation and postprocessing</h3> | ||
|
||
<div class="code"><pre> | ||
r.trees.thresholds forest=fnk forest_column=code2020 \ | ||
nir_raster=top_nir_02 ndvi_raster=top_ndvi_02 \ | ||
ndsm=ndsm ndsm_threshold=4 | ||
</pre></div> | ||
|
||
<h2>SEE ALSO</h2> | ||
|
||
<em> | ||
<a href="m.analyse.trees.html">m.analyse.trees</a>, | ||
<a href="https://grass.osgeo.org/grass-stable/manuals/r.geomorphon.html">r.quantile</a>, | ||
</em> | ||
|
||
<h2>AUTHOR</h2> | ||
|
||
<p>Anika Weinmann, <a href="https://www.mundialis.de/">mundialis</a>, weinmann at mundialis.de</p> | ||
<p>Victoria-Leandra Brunn, <a href="https://www.mundialis.de/">mundialis</a>, brunn at mundialis.de</p> |
218 changes: 218 additions & 0 deletions
218
grass-gis-addons/m.analyse.trees/r.trees.thresholds/r.trees.thresholds.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
#!/usr/bin/env python3 | ||
|
||
############################################################################ | ||
# | ||
# MODULE: r.trees.thresholds | ||
# | ||
# AUTHOR(S): Victoria-Leandra Brunn, Anika Weinmann | ||
# | ||
# PURPOSE: Proposes thresholds for NDVI and NIR as these depend on the | ||
# respective flight conditions | ||
# | ||
# COPYRIGHT: (C) 2024 by mundialis and the GRASS Development Team | ||
# | ||
# This program is free software under the GNU General Public | ||
# License (>=v2). Read the file COPYING that comes with GRASS | ||
# for details. | ||
# | ||
############################################################################# | ||
|
||
# %Module | ||
# % description: Proposes thresholds for NDVI and NIR as these depend on the respective flight conditions. | ||
# % keyword: raster | ||
# % keyword: statistics | ||
# % keyword: threshold | ||
# % keyword: machine learning | ||
# % keyword: trees analysis | ||
# %end | ||
|
||
# %option G_OPT_V_INPUT | ||
# % key: forest | ||
# % label: Name of the vector map with forest inside e.g. FNK | ||
# % answer: ndsm | ||
# % guisection: Input | ||
# %end | ||
|
||
# %option G_OPT_DB_COLUMN | ||
# % key: forest_column | ||
# % label: Name of the column in the forest vector map to filter only forest e.g. for FNK code_2020 | ||
# % guisection: Input | ||
# % required: no | ||
# %end | ||
|
||
# %option | ||
# % key: forest_values | ||
# % label: Values of the forest_column which to select from | ||
# % answer: 400,410,420,431,432,502 | ||
# % multiple: yes | ||
# % guisection: Input | ||
# % required: no | ||
# %end | ||
|
||
# %option G_OPT_R_INPUT | ||
# % key: nir_raster | ||
# % label: Name of the NIR raster | ||
# % answer: top_nir_02 | ||
# % guisection: Input | ||
# %end | ||
|
||
# %option G_OPT_R_INPUT | ||
# % key: ndvi_raster | ||
# % label: Name of the NDVI raster | ||
# % answer: top_ndvi_02 | ||
# % guisection: Input | ||
# %end | ||
|
||
# %option G_OPT_R_INPUT | ||
# % key: ndsm | ||
# % label: Name of the nDSM raster | ||
# % answer: ndsm | ||
# % guisection: Input | ||
# %end | ||
|
||
# %option | ||
# % key: ndsm_threshold | ||
# % type: double | ||
# % required: yes | ||
# % label: nDSM threshold for potential trees | ||
# % answer: 1 | ||
# % guisection: Parameters | ||
# %end | ||
|
||
|
||
import atexit | ||
import os | ||
import sys | ||
import grass.script as grass | ||
from grass.pygrass.utils import get_lib_path | ||
|
||
# initialize global vars | ||
rm_rasters = [] | ||
rm_vectors = [] | ||
tmp_mask_old = None | ||
PID = os.getpid() | ||
|
||
def cleanup(): | ||
nuldev = open(os.devnull, "w") | ||
kwargs = {"flags": "f", "quiet": True, "stderr": nuldev} | ||
for rmrast in rm_rasters: | ||
if grass.find_file(name=rmrast, element="cell")["file"]: | ||
grass.run_command("g.remove", type="raster", name=rmrast, **kwargs) | ||
for rmv in rm_vectors: | ||
if grass.find_file(name=rmv, element="vector")["file"]: | ||
grass.run_command("g.remove", type="vector", name=rmv, **kwargs) | ||
grass.del_temp_region() | ||
|
||
|
||
def get_percentiles(rast, percentiles=list(range(1, 100, 1))): | ||
perc_out = grass.parse_command( | ||
"r.quantile", | ||
input=rast, | ||
percentiles=percentiles, | ||
quiet=True, | ||
) | ||
return { | ||
float(k.split(":")[1]): float(k.split(":")[2]) | ||
for k in perc_out | ||
} | ||
|
||
|
||
def main(): | ||
global rm_rasters, tmp_mask_old, rm_vectors | ||
|
||
path = get_lib_path(modname="m.analyse.trees", libname="analyse_trees_lib") | ||
if path is None: | ||
grass.fatal("Unable to find the analyse trees library directory") | ||
sys.path.append(path) | ||
try: | ||
from analyse_trees_lib import ( | ||
set_nprocs, | ||
test_memory, | ||
) | ||
except Exception: | ||
grass.fatal("m.analyse.trees library is not installed") | ||
|
||
grass.message(_("Preparing input data...")) | ||
if grass.find_file(name="MASK", element="cell")["file"]: | ||
tmp_mask_old = f"tmp_mask_old_{PID}" | ||
grass.run_command( | ||
"g.rename", raster=f"MASK,{tmp_mask_old}", quiet=True | ||
) | ||
|
||
vect_map = options["forest"] | ||
nir = options["nir_raster"] | ||
ndvi = options["ndvi_raster"] | ||
ndsm = options["ndsm"] | ||
ndsm_threshold = float(options["ndsm_threshold"]) | ||
forest_column = options["forest_column"] | ||
forest_values = options["forest_values"].split(",") | ||
|
||
grass.use_temp_region() | ||
|
||
# extract forest if "forest_column" is set | ||
forest = vect_map | ||
if forest_column: | ||
forest = f"foest_{PID}" | ||
forest_where = " or ".join([f"{forest_column}='{val}'" for val in forest_values]) | ||
rm_vectors.append(forest) | ||
grass.run_command( | ||
"v.extract", | ||
input=vect_map, | ||
output=forest, | ||
where=f"{forest_where}", | ||
quiet=True, | ||
) | ||
|
||
# get percentile of trees in the forests over the height | ||
grass.run_command("g.region", vector=forest, align=nir) | ||
grass.run_command("r.mask", vector=forest) | ||
ndsm_percentiles = get_percentiles(ndsm) | ||
ndsm_perc = 0 | ||
for ndsm_percentile, ndsm in ndsm_percentiles.items(): | ||
if ndsm < ndsm_threshold: | ||
ndsm_perc = ndsm_percentile | ||
else: | ||
break | ||
|
||
# percentiles for NDVI range | ||
ndsm_perc_step = ndsm_perc/2. if ndsm_perc < 20 else 10 | ||
min_perc = ndsm_perc - ndsm_perc_step | ||
max_perc = ndsm_perc + ndsm_perc_step | ||
percs = [min_perc if min_perc > 0 else 0, ndsm_perc, ndsm_perc + 1, max_perc] | ||
|
||
# get threshold for NDVI | ||
ndvi_percentiles = get_percentiles(ndvi, percs) | ||
ndvi_min = ndvi_percentiles[min_perc] | ||
ndvi_val = (ndvi_percentiles[ndsm_perc] + ndvi_percentiles[ndsm_perc + 1]) / 2. | ||
ndvi_max = ndvi_percentiles[max_perc] | ||
|
||
# percentiles for NIR range | ||
ndsm_perc_step = ndsm_perc/2. if ndsm_perc < 20 else 10 | ||
min_perc_nir = ndsm_perc | ||
max_perc_nir = ndsm_perc + 2 * ndsm_perc_step | ||
ndsm_perc_nir = ndsm_perc + 1 * ndsm_perc_step | ||
percs_nir = [min_perc_nir if min_perc_nir > 0 else 0, ndsm_perc_nir, ndsm_perc_nir + 1, max_perc_nir] | ||
|
||
# get threshold for NIR | ||
nir_percentiles = get_percentiles(nir, percs_nir) | ||
nir_min = nir_percentiles[min_perc_nir] | ||
nir_val = (nir_percentiles[ndsm_perc_nir] + nir_percentiles[ndsm_perc_nir + 1]) / 2. | ||
nir_max = nir_percentiles[max_perc_nir] | ||
sys.stdout.write( | ||
f"Proposal for NDVI threshold in the range of [{ndvi_min}, {ndvi_max}]; e.g.:\n" | ||
) | ||
sys.stdout.write(f"<NDVI_THRES={ndvi_val}>\n") | ||
sys.stdout.write("Proposal for NIR threshold:\n") | ||
sys.stdout.write( | ||
f"Proposal for NIR threshold in the range of [{nir_min}, {nir_max}]; e.g.:\n" | ||
) | ||
sys.stdout.write(f"<NIR_THRES={nir_val}>\n") | ||
|
||
# import pdb; pdb.set_trace() | ||
grass.run_command("r.mask", flags="r") | ||
|
||
|
||
if __name__ == "__main__": | ||
options, flags = grass.parser() | ||
atexit.register(cleanup) | ||
main() |