reader.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import os
  2. import sys
  3. import json
  4. import glob
  5. import re
  6. from collections import OrderedDict
  7. import grass.script as gs
  8. # Semantic label can have any form. Explanatory metadata can be stored
  9. # separately. It is suggested to follow some standard e.g. remote
  10. # sensing band names should be STAC common names, see
  11. # https://stacspec.org/
  12. # https://github.com/radiantearth/stac-spec/blob/master/extensions/eo/README.md#band-object
  13. class SemanticLabelReaderError(Exception):
  14. pass
  15. class SemanticLabelReader:
  16. """Semantic label reader"""
  17. def __init__(self):
  18. self._json_files = glob.glob(
  19. os.path.join(os.environ["GISBASE"], "etc", "i.band.library", "*.json")
  20. )
  21. if not self._json_files:
  22. raise SemanticLabelReaderError("No semantic label definitions found")
  23. self._read_config()
  24. def _read_config(self):
  25. """Read configuration"""
  26. self.config = dict()
  27. for json_file in self._json_files:
  28. try:
  29. with open(json_file) as fd:
  30. config = json.load(fd, object_pairs_hook=OrderedDict)
  31. except json.decoder.JSONDecodeError as e:
  32. raise SemanticLabelReaderError(
  33. "Unable to parse '{}': {}".format(json_file, e)
  34. )
  35. # check if configuration is valid
  36. self._check_config(config)
  37. self.config[os.path.basename(json_file)] = config
  38. @staticmethod
  39. def _check_config(config):
  40. """Check if config is valid
  41. :todo: check shortcut uniqueness
  42. :param dict config: configuration to be validated
  43. """
  44. for items in config.values():
  45. for item in ("shortcut", "bands"):
  46. if item not in items.keys():
  47. raise SemanticLabelReaderError(
  48. "Invalid band definition: <{}> is missing".format(item)
  49. )
  50. if len(items["bands"]) < 1:
  51. raise SemanticLabelReaderError(
  52. "Invalid band definition: no bands defined"
  53. )
  54. @staticmethod
  55. def _print_label_extended(label, item):
  56. """Print label specific metadata
  57. :param str label: label identifier
  58. :param str item: items to be printed out
  59. """
  60. def print_kv(k, v, indent):
  61. if isinstance(v, OrderedDict):
  62. print("{}{}:".format(" " * indent * 2, k))
  63. for ki, vi in v.items():
  64. print_kv(ki, vi, indent * 2)
  65. else:
  66. print("{}{}: {}".format(" " * indent * 2, k, v))
  67. indent = 4
  68. print("{}label: {}".format(" " * indent, label))
  69. for k, v in item[label].items():
  70. print_kv(k, v, indent)
  71. def _print_label(self, semantic_label=None, tag=None):
  72. sys.stdout.write(semantic_label)
  73. if tag:
  74. sys.stdout.write(" {}".format(tag))
  75. sys.stdout.write(os.linesep)
  76. def print_info(self, shortcut=None, band=None, semantic_label=None, extended=False):
  77. """Prints semantic label information to stdout.
  78. Can be filtered by semantic label identifier.
  79. :param str shortcut: shortcut to filter (eg. S2) or None
  80. :param str band: band (eg. 1) or None
  81. :param str semantic_label: semantic_label filter (eg. S2_8A) or None
  82. :param bool extended: print also extended metadata
  83. """
  84. if semantic_label:
  85. try:
  86. shortcut, band = semantic_label.split("_")
  87. except ValueError:
  88. shortcut = semantic_label
  89. band = None
  90. found = False
  91. for root in self.config.values():
  92. for item in root.values():
  93. try:
  94. if shortcut and re.match(shortcut, item["shortcut"]) is None:
  95. continue
  96. except re.error as e:
  97. raise SemanticLabelReaderError("Invalid pattern: {}".format(e))
  98. found = True
  99. if band and band not in item["bands"]:
  100. raise SemanticLabelReaderError(
  101. "Band <{}> not found in <{}>".format(band, shortcut)
  102. )
  103. # print generic information
  104. if extended:
  105. for subitem in item.keys():
  106. if subitem == "bands":
  107. # bands item is processed bellow
  108. continue
  109. print("{}: {}".format(subitem, item[subitem]))
  110. # print detailed band information
  111. if band:
  112. self._print_label_extended(band, item["bands"])
  113. else:
  114. for iband in item["bands"]:
  115. self._print_label_extended(iband, item["bands"])
  116. else:
  117. # basic information only
  118. if band:
  119. self._print_label(
  120. semantic_label=item["shortcut"],
  121. tag=item["bands"][band].get("tag"),
  122. )
  123. else:
  124. for iband in item["bands"]:
  125. self._print_label(
  126. semantic_label=item["shortcut"],
  127. tag=item["bands"][iband].get("tag"),
  128. )
  129. # print warning when defined shortcut not found
  130. if not found:
  131. gs.warning(
  132. "Metadata for semantic label <{}> not found".format(semantic_label)
  133. )
  134. def find_file(self, semantic_label):
  135. """Find file by semantic label.
  136. Match is case-insensitive.
  137. :param str semantic_label: semantic label identifier to search for (eg. S2_1)
  138. :return str: file basename if found or None
  139. """
  140. try:
  141. shortcut, band = semantic_label.split("_")
  142. except ValueError:
  143. # raise SemanticLabelReaderError("Invalid band identifier <{}>".format(
  144. # semantic_label
  145. # ))
  146. shortcut = None
  147. for filename, config in self.config.items():
  148. for root in config.keys():
  149. if (
  150. shortcut
  151. and config[root]["shortcut"].upper() == shortcut.upper()
  152. and band.upper()
  153. in map(lambda x: x.upper(), config[root]["bands"].keys())
  154. ):
  155. return filename
  156. return None
  157. def get_bands(self):
  158. """Get list of band identifiers.
  159. :return list: list of valid band identifiers
  160. """
  161. bands = []
  162. for root in self.config.values():
  163. for item in root.values():
  164. for band in item["bands"]:
  165. bands.append("{}_{}".format(item["shortcut"], band))
  166. return bands