Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

library: Add imagery Python library module to grass.script #3756

Merged
merged 22 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion python/grass/script/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ include $(MODULE_TOPDIR)/include/Make/Python.make

DSTDIR = $(ETC)/python/grass/script

MODULES = core db raster raster3d vector array setup task utils
MODULES = core db imagery raster raster3d vector array setup task utils

PYFILES := $(patsubst %,$(DSTDIR)/%.py,$(MODULES) __init__)
PYCFILES := $(patsubst %,$(DSTDIR)/%.pyc,$(MODULES) __init__)
Expand Down
1 change: 1 addition & 0 deletions python/grass/script/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from .core import *
from .db import *
from .imagery import *
ninsbl marked this conversation as resolved.
Show resolved Hide resolved
from .raster import *
from .raster3d import *
from .vector import *
Expand Down
121 changes: 121 additions & 0 deletions python/grass/script/imagery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""
Imagery related functions to be used in Python scripts.

Usage:

::

import grass.script as gs

gs.imagery.group_to_dict(imagery_group)
ninsbl marked this conversation as resolved.
Show resolved Hide resolved
...

(C) 2024 by Stefan Blumentrath 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.

.. sectionauthor:: Stefan Blumentrath
"""

from grass.exceptions import CalledModuleError

from .core import read_command, warning
from .raster import raster_info


def group_to_dict(
imagery_group_name,
subgroup=None,
dict_key="semantic_labels",
fill_semantic_label=True,
full_info=True,
env=None,
):
"""Create a dictionary to represent an imagery group with metadata.

Defined by the dict_key option, the dictionary uses either the names
wenzeslaus marked this conversation as resolved.
Show resolved Hide resolved
of the raster maps ("map_names"), their row indices in the group
("indices") or their associated semantic_labels ("semantic_labels") as keys.
The default is to use semantic_labels as keys. Note that map metadata
of the maps in the group have to be read to get the semantic label,
in addition to the group file. The same metadata is read when the
"full_info" should be returned.

The function can also operate on the level of subgroups. In case a
non-existing (or empty sub-group) is requested a warning is printed
and an empty dictionary is returned (following the behavior of i.group).

Example:

>>> run_command("g.copy", raster="lsat7_2000_10,lsat7_2000_10")
>>> run_command("r.support", raster="lsat7_2000_10", semantic_label="L8_1")
>>> run_command("g.copy", raster="lsat7_2000_20,lsat7_2000_20")
>>> run_command("r.support", raster="lsat7_2000_20", semantic_label="L8_2")
>>> run_command("g.copy", raster="lsat7_2000_30,lsat7_2000_30")
>>> run_command("r.support", raster="lsat7_2000_30", semantic_label="L8_3")
>>> run_command("i.group", group="L8_group",
>>> input="lsat7_2000_10,lsat7_2000_20,lsat7_2000_30")
>>> group_to_dict("L8_group", full_info=False) # doctest: +ELLIPSIS
{"L8_1": "lsat7_2000_10", ... "L8_3": "lsat7_2000_30"}
>>> run_command("g.remove", flags="f", type="group", name="L8_group")
>>> run_command("g.remove", flags="f", type="raster",
>>> name="lsat7_2000_10,lsat7_2000_20,lsat7_2000_30")

:param str table: imagery_group_name: Name of the imagery group to process (or None)
:param str dict_key: What to use as key for dictionary. Can bei either
"semantic_labels" (default), "map_names" or "indices"
:param env: environment

:return: dictionary with maps and their semantic labels (or row indices in the
imagery group)
ninsbl marked this conversation as resolved.
Show resolved Hide resolved
:rtype: dict
"""
group_dict = {}
try:
maps_in_group = (
read_command(
"i.group",
group=imagery_group_name,
subgroup=subgroup,
flags="g",
quiet=True,
env=env,
)
.strip()
.split()
)
except CalledModuleError as cme:
raise cme
ninsbl marked this conversation as resolved.
Show resolved Hide resolved

if subgroup and not maps_in_group:
warning(
_("Empty result returned for subgroup <{sg}> in group <{g}>").format(
sg=subgroup, g=imagery_group_name
)
)

for idx, raster_map in enumerate(maps_in_group):
raster_map_info = None
if full_info or dict_key == "semantic_labels":
raster_map_info = raster_info(raster_map, env=env)

if dict_key == "indices":
key = str(idx + 1)
val = raster_map_info or raster_map
elif dict_key == "map_names":
key = raster_map
val = raster_map_info or str(idx + 1)
elif dict_key == "semantic_labels":
key = raster_map_info["semantic_label"]
if not key or key == '"none"':
warning(
_(
"Raster map {m} in group <{g}> does not have a semantic label."
).format(m=raster_map, g=imagery_group_name)
)
if fill_semantic_label:
key = str(idx + 1)
ninsbl marked this conversation as resolved.
Show resolved Hide resolved
val = raster_map_info if full_info else raster_map
group_dict[key] = val
return group_dict
101 changes: 101 additions & 0 deletions python/grass/script/testsuite/test_imagery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from grass.gunittest.case import TestCase
from grass.gunittest.main import test

import grass.script as gs


class TestImageryGroupToDict(TestCase):
"""Tests function `group_to_dict` that returns raster maps
from an imagery group and their metadata."""

@classmethod
def setUpClass(cls):
cls.bands = [1, 2, 3]
cls.raster_maps = [f"lsat7_2002_{band}0" for band in cls.bands]
cls.group = "L8_group"
cls.subgroup = "L8_group_subgroup"
# Create input maps with label and group
for band in cls.bands:
cls.runModule(
"g.copy", raster=[f"lsat7_2002_{band}0", f"lsat7_2002_{band}0"]
)
cls.runModule(
"r.support", map=f"lsat7_2002_{band}0", semantic_label=f"L8_{band}"
)
cls.runModule("i.group", group=cls.group, input=cls.raster_maps)

@classmethod
def tearDownClass(cls):
cls.runModule("g.remove", type="raster", name=cls.raster_maps, flags="f")
cls.runModule("g.remove", type="group", name=cls.group, flags="f")

def test_basic_group_label_keys(self):
ref_dict = {f"L8_{band}": f"lsat7_2002_{band}0" for band in self.bands}
group_info = gs.imagery.group_to_dict(self.group, full_info=False)
# Check that a dict is returned
self.assertIsInstance(group_info, dict)
self.assertListEqual(list(ref_dict.keys()), list(group_info.keys()))
self.assertListEqual(
list(ref_dict.values()), [val.split("@")[0] for val in group_info.values()]
)

def test_basic_group_map_keys(self):
ref_dict = {f"lsat7_2002_{band}0": f"L8_{band}" for band in self.bands}
group_info = gs.imagery.group_to_dict(
self.group, dict_key=None, full_info=False
)
# Check that a dict is returned
self.assertIsInstance(group_info, dict)
self.assertListEqual(
list(ref_dict.keys()), [key.split("@")[0] for key in group_info.keys()]
)
self.assertListEqual(list(ref_dict.values()), list(group_info.values()))

def test_full_info_group_label_keys(self):
group_info = gs.imagery.group_to_dict(self.group, full_info=True)
# Check that a dict is returned
self.assertIsInstance(group_info, dict)
self.assertListEqual(
[f"L8_{band}" for band in self.bands], list(group_info.keys())
)
for band in self.bands:
# Take some metadata keys from raster_info
for metadata_key in [
"north",
"nsres",
"cols",
"datatype",
"map",
"date",
"semantic_label",
"comments",
]:
self.assertIn(metadata_key, group_info[f"L8_{band}"])

def test_full_info_group_label_keys_subgroup(self):
self.runModule(
"i.group", group=self.group, subgroup=self.subgroup, input=self.raster_maps
)
group_info = gs.imagery.group_to_dict(self.group, full_info=True)
# Check that a dict is returned
self.assertIsInstance(group_info, dict)
self.assertListEqual(
[f"L8_{band}" for band in self.bands], list(group_info.keys())
)
for band in self.bands:
# Take some metadata keys from raster_info
for metadata_key in [
"north",
"nsres",
"cols",
"datatype",
"map",
"date",
"semantic_label",
"comments",
]:
self.assertIn(metadata_key, group_info[f"L8_{band}"])


if __name__ == "__main__":
test()
Loading