import os import sys import json import glob import re from collections import OrderedDict import grass.script as gs # Semantic label can have any form. Explanatory metadata can be stored # separately. It is suggested to follow some standard e.g. remote # sensing band names should be STAC common names, see # https://stacspec.org/ # https://github.com/radiantearth/stac-spec/blob/master/extensions/eo/README.md#band-object class SemanticLabelReaderError(Exception): pass class SemanticLabelReader: """Semantic label reader""" def __init__(self): self._json_files = glob.glob( os.path.join(os.environ["GISBASE"], "etc", "i.band.library", "*.json") ) if not self._json_files: raise SemanticLabelReaderError("No semantic label definitions found") self._read_config() def _read_config(self): """Read configuration""" self.config = dict() for json_file in self._json_files: try: with open(json_file) as fd: config = json.load(fd, object_pairs_hook=OrderedDict) except json.decoder.JSONDecodeError as e: raise SemanticLabelReaderError( "Unable to parse '{}': {}".format(json_file, e) ) # check if configuration is valid self._check_config(config) self.config[os.path.basename(json_file)] = config @staticmethod def _check_config(config): """Check if config is valid :todo: check shortcut uniqueness :param dict config: configuration to be validated """ for items in config.values(): for item in ("shortcut", "bands"): if item not in items.keys(): raise SemanticLabelReaderError( "Invalid band definition: <{}> is missing".format(item) ) if len(items["bands"]) < 1: raise SemanticLabelReaderError( "Invalid band definition: no bands defined" ) @staticmethod def _print_label_extended(label, item): """Print label specific metadata :param str label: label identifier :param str item: items to be printed out """ def print_kv(k, v, indent): if isinstance(v, OrderedDict): print("{}{}:".format(" " * indent * 2, k)) for ki, vi in v.items(): print_kv(ki, vi, indent * 2) else: print("{}{}: {}".format(" " * indent * 2, k, v)) indent = 4 print("{}label: {}".format(" " * indent, label)) for k, v in item[label].items(): print_kv(k, v, indent) def _print_label(self, semantic_label=None, tag=None): sys.stdout.write(semantic_label) if tag: sys.stdout.write(" {}".format(tag)) sys.stdout.write(os.linesep) def print_info(self, shortcut=None, band=None, semantic_label=None, extended=False): """Prints semantic label information to stdout. Can be filtered by semantic label identifier. :param str shortcut: shortcut to filter (eg. S2) or None :param str band: band (eg. 1) or None :param str semantic_label: semantic_label filter (eg. S2_8A) or None :param bool extended: print also extended metadata """ if semantic_label: try: shortcut, band = semantic_label.split("_") except ValueError: shortcut = semantic_label band = None found = False for root in self.config.values(): for item in root.values(): try: if shortcut and re.match(shortcut, item["shortcut"]) is None: continue except re.error as e: raise SemanticLabelReaderError("Invalid pattern: {}".format(e)) found = True if band and band not in item["bands"]: raise SemanticLabelReaderError( "Band <{}> not found in <{}>".format(band, shortcut) ) # print generic information if extended: for subitem in item.keys(): if subitem == "bands": # bands item is processed bellow continue print("{}: {}".format(subitem, item[subitem])) # print detailed band information if band: self._print_label_extended(band, item["bands"]) else: for iband in item["bands"]: self._print_label_extended(iband, item["bands"]) else: # basic information only if band: self._print_label( semantic_label=item["shortcut"], tag=item["bands"][band].get("tag"), ) else: for iband in item["bands"]: self._print_label( semantic_label=item["shortcut"], tag=item["bands"][iband].get("tag"), ) # print warning when defined shortcut not found if not found: gs.warning( "Metadata for semantic label <{}> not found".format(semantic_label) ) def find_file(self, semantic_label): """Find file by semantic label. Match is case-insensitive. :param str semantic_label: semantic label identifier to search for (eg. S2_1) :return str: file basename if found or None """ try: shortcut, band = semantic_label.split("_") except ValueError: # raise SemanticLabelReaderError("Invalid band identifier <{}>".format( # semantic_label # )) shortcut = None for filename, config in self.config.items(): for root in config.keys(): if ( shortcut and config[root]["shortcut"].upper() == shortcut.upper() and band.upper() in map(lambda x: x.upper(), config[root]["bands"].keys()) ): return filename return None def get_bands(self): """Get list of band identifiers. :return list: list of valid band identifiers """ bands = [] for root in self.config.values(): for item in root.values(): for band in item["bands"]: bands.append("{}_{}".format(item["shortcut"], band)) return bands