Skip to content

Commit

Permalink
r.incora
Browse files Browse the repository at this point in the history
  • Loading branch information
anikaweinmann committed Jan 31, 2022
1 parent bf338d7 commit ee2c835
Show file tree
Hide file tree
Showing 13 changed files with 1,264 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[flake8]

# E501 line too long (83 > 79 characters)
# F821 undefined name '_'

exclude = .git
max-line-length = 88
per-file-ignores =
./v.incora.training_data/v.incora.training_data.py: F821, E501
./r.incora.postproc/r.incora.postproc.py: F821
./r.incora.change/r.incora.change.py: F821
25 changes: 25 additions & 0 deletions .github/workflows/flake8.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Python Flake8 Code Quality

on:
- push
- pull_request

jobs:
flake8:
runs-on: ubuntu-20.04

steps:
- uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8

- name: Install
run: |
python -m pip install --upgrade pip
pip install flake8==3.8.4
- name: Run Flake8
run: |
flake8 --config=.flake8 --count --statistics --show-source --jobs=$(nproc) .
17 changes: 17 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
MODULE_TOPDIR = ../..

PGM = r.incora

# note: to deactivate a module, just place a file "DEPRECATED" into the subdir
ALL_SUBDIRS := ${sort ${dir ${wildcard */.}}}
DEPRECATED_SUBDIRS := ${sort ${dir ${wildcard */DEPRECATED}}}
RM_SUBDIRS := bin/ docs/ etc/ scripts/
SUBDIRS_1 := $(filter-out $(DEPRECATED_SUBDIRS), $(ALL_SUBDIRS))
SUBDIRS := $(filter-out $(RM_SUBDIRS), $(SUBDIRS_1))

include $(MODULE_TOPDIR)/include/Make/Dir.make

default: parsubdirs htmldir

install: installsubdirs
$(INSTALL_DATA) $(PGM).html $(INST_DIR)/docs/html/
7 changes: 7 additions & 0 deletions r.incora.change/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
MODULE_TOPDIR = ../..

PGM = r.incora.change

include $(MODULE_TOPDIR)/include/Make/Script.make

default: script
22 changes: 22 additions & 0 deletions r.incora.change/r.incora.change.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<h2>DESCRIPTION</h2>

<em>r.incora.change</em> runs a change detection based on two input maps. The
nomenclature is optimized for the incora project.

<h2>SEE ALSO</h2>

<em>
<a href="r.change.stats">r.sample.category</a>
<a href="r.change.info">r.buffer</a>
<a href="r.mapcalc">r.mapcalc</a>
</em>

<h2>AUTHORS</h2>

Guido Riembauer, mundialis, riembauer at mundialis.de


<!--
<p>
<i>Last changed: $Date$</i>
-->
248 changes: 248 additions & 0 deletions r.incora.change/r.incora.change.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
#!/usr/bin/env python3

############################################################################
#
# MODULE: r.incora.change
# AUTHOR(S): Guido Riembauer
# PURPOSE: Runs r.change.stats and does post processing for incora
# COPYRIGHT: (C) 2020-2022 by mundialis GmbH & Co. KG and the GRASS
# Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
#############################################################################

# %Module
# % description: Runs r.change.stats and does post processing for incora.
# % keyword: raster
# % keyword: classification
# % keyword: change detection
# %end

# %option G_OPT_R_INPUT
# % key: input
# % label: Two input raster maps to calculate the change detection on
# % description: It is assumed that the second raster is the later one
# %end

# %option G_OPT_R_OUTPUT
# % key: output_cd
# % required: no
# % multiple: no
# % label: Name of total output change detection map
# %end

# %option G_OPT_R_OUTPUT
# % key: output_water
# % required: no
# % multiple: no
# % label: Name of output change detection map of water areas
# %end

# %option G_OPT_R_OUTPUT
# % key: output_bu
# % required: no
# % multiple: no
# % label: Name of output change detection map of built-up areas
# %end

# %option G_OPT_R_OUTPUT
# % key: output_forest
# % required: no
# % multiple: no
# % label: Name of output change detection map of forest areas
# %end

# %option G_OPT_R_OUTPUT
# % key: output_lowveg
# % required: no
# % multiple: no
# % label: Name of output change detection map of low vegetation areas
# %end

# %option G_OPT_R_OUTPUT
# % key: output_bare
# % required: no
# % multiple: no
# % label: Name of output change detection map of bare soil areas
# %end

# %option G_OPT_R_OUTPUT
# % key: output_agr
# % required: no
# % multiple: no
# % label: Name of output change detection map of agriculture areas
# %end

# %option
# % key: minsize
# % type: string
# % required: no
# % multiple: no
# % answer: 1.0
# % label: Minimum size of changed areas (in ha)
# % description: Smaller areas will be deleted from the output raster(s)
# %end

# %option
# % key: mode_winsize
# % type: integer
# % required: no
# % multiple: no
# % answer: 3
# % label: Size of mode filter if -f flag is set
# % description: -f flag must be set
# %end

# %option
# % key: gain_winsize
# % type: integer
# % required: no
# % multiple: no
# % answer: 4
# % label: Window size of information gain filter in r.change.info
# %end

# %option
# % key: gain_thresh
# % type: string
# % required: no
# % multiple: no
# % answer: 0.5
# % label: Threshold for the information gain map
# % description: All areas < gain_thresh are omitted from the output maps
# %end

# %flag
# % key: f
# % description: Filter change detection product using a mode filter of size window_size
# %end

import atexit
import os
import grass.script as grass

# initialize global vars
rm_rasters = []


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="raster")["file"]:
grass.run_command("g.remove", type="raster", name=rmrast, **kwargs)


def main():

global rm_rasters
# parameters
input = options["input"].split(",")
if len(input) != 2:
grass.fatal(_("Input must consist of two raster maps"))
if options["output_cd"]:
outrast_cd = options["output_cd"]
else:
outrast_cd = "cd_rast_%s" % os.getpid()
rm_rasters.append(outrast_cd)

output_agr = options["output_agr"]
output_forest = options["output_forest"]
output_lowveg = options["output_lowveg"]
output_water = options["output_water"]
output_bu = options["output_bu"]
output_bare = options["output_bare"]

if not grass.find_program("r.change.stats", "--help"):
grass.fatal(_("The 'r.change.stats' module was not found, install it first:") +
"\n" +
"g.extension r.change.stats url=path/to/module")
if not grass.find_program("r.change.info", "--help"):
grass.fatal(
_("The 'r.change.info' module was not found, install it first:")
+ "\n g.extension r.change.info")
cd_temprast = "cd_tempraster_%s" % os.getpid()
rm_rasters.append(cd_temprast)
cd_params = {
"input": input,
"output": cd_temprast,
"flags": "cl"
}
if flags["f"]:
cd_params["window_size"] = options["mode_winsize"]
cd_params["flags"] += "f"
grass.message(_("Calculating change detection..."))
grass.run_command("r.change.stats", **cd_params)
output_list = [output_forest, output_lowveg, output_water, output_bu,
output_bare, output_agr]
values_list = ["10", "20", "30", "40", "50", "60"]
output_used = []
values_used = []
for idx, item in enumerate(output_list):
if len(item) > 0:
output_used.append(item)
values_used.append(values_list[idx])

grass.message(_("Calculating Information Gain..."))
gainmap = "gainmap_%s" % os.getpid()
rm_rasters.append(gainmap)
steps = int(options["gain_winsize"])/2
grass.run_command("r.change.info", input=input, method="gain1",
size=options["gain_winsize"], step=steps,
output=gainmap, quiet=True)
if len(output_used) > 0:
tempraster_1 = "%s_tmp1_%s" % (item, os.getpid())
rm_rasters.append(tempraster_1)
# correct the outrast_cd raster with the information gain
eq = f"{outrast_cd} = if({gainmap}>{options['gain_thresh']},{cd_temprast},0)"
grass.run_command("r.mapcalc", expression=eq, quiet=True)
# this binary raster contains where changes occured
expression_1 = "%s = if(%s > %s && %s != 0, 1, null())" % (
tempraster_1, gainmap, options["gain_thresh"],
outrast_cd)
grass.run_command("r.mapcalc", expression=expression_1, quiet=True)
for idx, item in enumerate(output_used):
grass.message(_("Calculating change raster %s..." % item))
tempraster_2 = "%s_tmp2_%s" % (item, os.getpid())
rm_rasters.append(tempraster_2)
# this raster contains where changes occured and one of the input
# rasters contains the respective class (1 = map1, 2 = map2)
expression_2 = f"{tempraster_2} = if({tempraster_1} == 1 && " \
f"{input[0]} == {values_used[idx]},1, " \
f"if({tempraster_1} == 1 && {input[1]} == {values_used[idx]}," \
f"2,null()))"
grass.run_command("r.mapcalc", expression=expression_2, quiet=True)
# omit areas smaller < threshold
grass.run_command(
"r.reclass.area",
input=tempraster_2,
output=item,
value=options["minsize"],
mode="greater",
method="reclass",
quiet=True,
)
grass.message(_("Generated output maps:"))
if options["output_cd"]:
grass.message(_(f"<{outrast_cd}>"))
for item in output_used:
grass.message(_(f"<{item}>"))


if __name__ == "__main__":
options, flags = grass.parser()
atexit.register(cleanup)
main()
62 changes: 62 additions & 0 deletions r.incora.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>GRASS GIS manual: r.incora toolset</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<link rel="stylesheet" href="grassdocs.css" type="text/css">
</head>
<body bgcolor="white">
<div id="container">

<a href="https://grass.osgeo.org/grass-stable/manuals/index.html"><img src="grass_logo.png" alt="GRASS logo"></a>
<hr class="header">

<h2>NAME</h2>

<em><b>r.incora</b></em> - Toolset for Incora landcover classification.


<h2>KEYWORDS</h2>
<a href="https://grass.osgeo.org/grass-stable/manuals/raster.html">raster</a>, <a href="https://grass.osgeo.org/grass-stable/manuals/topic_classification.html">classification</a>

<!-- meta page description: Toolset for download and processing time series of Sentinel products -->
<h2>DESCRIPTION</h2>

The <em>r.incora</em> toolset consists of currently three modules.
<p>

<i>Processing modules:</i>
<dl>
<dt><a href="v.incora.training_data.html">v.incora.training_data</a></dt>
<dd>creates a vector map containing training points from a set of rules
containing the output classes: forest, low vegetation, water, built-up,
bare soil and agriculture.</dd>
<dt><a href="r.incora.postproc.html">r.incora.postproc</a></dt>
<dd> uses post processing to deal with mixed pixels identified
in <a href="v.incora.training_data">v.incora.training_data</a>. Mixed pixels
(class 70) are removed from the map. The gaps are then filled using
<a href="https://grass.osgeo.org/grass80/manuals/r.grow.distance.html">r.grow.distance</a>.</dd>
<dt><a href="r.incora.change.html">r.incora.change</a></dt>
<dd>runs a change detection based on two input maps. The
nomenclature is optimized for the incora project.</dd>
</dl>

<h2>REQUIREMENTS</h2>

The following GRASS GIS Addons are required:

<ul>
<li><a href="https://grass.osgeo.org/grass80/manuals/addons/r.sample.category.html">r.sample.category</a></li>
<li><a href="https://github.com/mundialis/r.change.stats.html">r.change.stats</a></li>
<li><a href="https://grass.osgeo.org/grass80/manuals/addons/r.change.info.html">r.change.info</a></li>
</ul>

<h2>AUTHORS</h2>

Anika Weinmann and Guido Riembauer, <a href="https://www.mundialis.de/">mundialis</a>


<!--
<p>
<i>Last changed: $Date$</i>
-->
Loading

0 comments on commit ee2c835

Please sign in to comment.