Source code for grass.pygrass.raster

import ctypes
import numpy as np

#
# import GRASS modules
#
from grass.script import fatal
from grass.exceptions import OpenError

import grass.lib.gis as libgis
import grass.lib.raster as libraster
import grass.lib.rowio as librowio

libgis.G_gisinit("")

# flake8: noqa: E402
# import pygrass modules
from grass.pygrass.errors import must_be_open
from grass.pygrass.gis.region import Region
from grass.pygrass import utils

# import raster classes
from grass.pygrass.raster.abstract import RasterAbstractBase
from grass.pygrass.raster.raster_type import TYPE as RTYPE, RTYPE_STR
from grass.pygrass.raster.buffer import Buffer
from grass.pygrass.raster.segment import Segment
from grass.pygrass.raster.rowio import RowIO

# flake8: qa

WARN_OVERWRITE = "Raster map <{0}> already exists and will be overwritten"

test_raster_name = "Raster_test_map"


[docs]class RasterRow(RasterAbstractBase): """Raster_row_access": Inherits: "Raster_abstract_base" and implements the default row access of the Rast library. * Implements row access using row id * The get_row() method must accept a Row object as argument that will be used for value storage, so no new buffer will be allocated * Implements sequential writing of rows * Implements indexed value read only access using the [row][col] operator * Implements the [row] read method that returns a new Row object * Writing is limited using the put_row() method which accepts a Row as argument * No mathematical operation like __add__ and stuff for the Raster object (only for rows), since r.mapcalc is more sophisticated and faster * Raises IndexError if [row] is out of range Examples: >>> elev = RasterRow(test_raster_name) >>> elev.exist() True >>> elev.is_open() False >>> elev.open() >>> elev.is_open() True >>> elev.has_cats() True >>> elev.mode 'r' >>> elev.mtype 'CELL' >>> elev.num_cats() 16 >>> elev.info.range (11, 44) >>> elev.info.cols 4 >>> elev.info.rows 4 Editing the history >>> elev.hist.read() >>> elev.hist.title = "A test map" >>> elev.hist.write() >>> elev.hist.title 'A test map' >>> elev.hist.keyword 'This is a test map' >>> attrs = list(elev.hist) >>> attrs[0] ('name', 'Raster_test_map') >>> attrs[2] ('mtype', '') Each Raster map have an attribute call ``cats`` that allow user to interact with the raster categories. >>> elev.cats # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE [('A', 11, None), ('B', 12, None), ... ('P', 44, None)] >>> elev.cats.labels() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'n', 'O', 'P'] >>> elev.cats[0] ('A', 11, None) >>> elev.cats[2] ('C', 13, None) >>> elev.cats[0] = ('AA', 11) >>> elev.cats[1] = ('BB', 12) >>> elev.cats.write() >>> elev.cats.read() >>> elev.cats[0] ('AA', 11, None) >>> elev.cats[1] ('BB', 12, None) Open a raster map using the *with statement*: >>> with RasterRow(test_raster_name) as elev: ... for row in elev: ... row Buffer([11, 21, 31, 41], dtype=int32) Buffer([12, 22, 32, 42], dtype=int32) Buffer([13, 23, 33, 43], dtype=int32) Buffer([14, 24, 34, 44], dtype=int32) >>> elev.is_open() False """ def __init__(self, name, mapset="", *args, **kargs): super().__init__(name, mapset, *args, **kargs) # mode = "r", method = "row",
[docs] @must_be_open def get_row(self, row, row_buffer=None): """Private method that return the row using the read mode call the `Rast_get_row` C function. :param row: the number of row to obtain :type row: int :param row_buffer: Buffer object instance with the right dim and type :type row_buffer: Buffer >>> elev = RasterRow(test_raster_name) >>> elev.open() >>> elev[0] Buffer([11, 21, 31, 41], dtype=int32) >>> elev.get_row(0) Buffer([11, 21, 31, 41], dtype=int32) """ if row_buffer is None: row_buffer = Buffer((self._cols,), self.mtype) libraster.Rast_get_row(self._fd, row_buffer.p, row, self._gtype) return row_buffer
[docs] @must_be_open def put_row(self, row): """Private method to write the row sequentially. :param row: a Row object to insert into raster :type row: Buffer object """ libraster.Rast_put_row(self._fd, row.p, self._gtype)
[docs] def open(self, mode=None, mtype=None, overwrite=None): """Open the raster if exist or created a new one. :param str mode: Specify if the map will be open with read or write mode ('r', 'w') :param str type: If a new map is open, specify the type of the map(`CELL`, `FCELL`, `DCELL`) :param bool overwrite: Use this flag to set the overwrite mode of existing raster maps if the map already exist, automatically check the type and set: * self.mtype Set all the privite, attributes: * self._fd; * self._gtype * self._rows and self._cols """ self.mode = mode or self.mode self.mtype = mtype or self.mtype self.overwrite = overwrite if overwrite is not None else self.overwrite if self.mode == "r": if self.exist(): self.info.read() self.cats.mtype = self.mtype self.cats.read() self.hist.read() self._fd = libraster.Rast_open_old(self.name, self.mapset) self._gtype = libraster.Rast_get_map_type(self._fd) self.mtype = RTYPE_STR[self._gtype] else: str_err = _("The map does not exist, I can't open in 'r' mode") raise OpenError(str_err) elif self.mode == "w": if self.exist(): if not self.overwrite: str_err = _( "Raster map <{0}> already exists and will be not overwritten" ) raise OpenError(str_err.format(self)) if self._gtype is None: raise OpenError(_("Raster type not defined")) self._fd = libraster.Rast_open_new(self.name, self._gtype) else: msg = "Open mode: %r not supported, valid mode are: r, w" raise OpenError(msg) # read rows and cols from the active region self._rows = libraster.Rast_window_rows() self._cols = libraster.Rast_window_cols()
[docs]class RasterRowIO(RasterRow): """Raster_row_cache_access": The same as "Raster_row_access" but uses the ROWIO library for cached row access """ def __init__(self, name, *args, **kargs): self.rowio = RowIO() super().__init__(name, *args, **kargs)
[docs] def open(self, mode=None, mtype=None, overwrite=False): """Open the raster if exist or created a new one. :param mode: specify if the map will be open with read or write mode ('r', 'w') :type mode: str :param type: if a new map is open, specify the type of the map(`CELL`, `FCELL`, `DCELL`) :type type: str :param overwrite: use this flag to set the overwrite mode of existing raster maps :type overwrite: bool """ super().open(mode, mtype, overwrite) self.rowio.open(self._fd, self._rows, self._cols, self.mtype)
[docs] @must_be_open def close(self): """Function to close the raster""" self.rowio.release() libraster.Rast_close(self._fd) # update rows and cols attributes self._rows = None self._cols = None self._fd = None
[docs] @must_be_open def get_row(self, row, row_buffer=None): """This method returns the row using: * the read mode and * `rowcache` method :param row: the number of row to obtain :type row: int :param row_buffer: Specify the Buffer object that will be instantiate :type row_buffer: Buffer object >>> elev = RasterRowIO(test_raster_name) >>> elev.open('r') >>> for row in elev: ... row Buffer([11, 21, 31, 41], dtype=int32) Buffer([12, 22, 32, 42], dtype=int32) Buffer([13, 23, 33, 43], dtype=int32) Buffer([14, 24, 34, 44], dtype=int32) >>> elev.close() """ if row_buffer is None: row_buffer = Buffer((self._cols,), self.mtype) rowio_buf = librowio.Rowio_get(ctypes.byref(self.rowio.c_rowio), row) ctypes.memmove(row_buffer.p, rowio_buf, self.rowio.row_size) return row_buffer
[docs]class RasterSegment(RasterAbstractBase): """Raster_segment_access": Inherits "Raster_abstract_base" and uses the segment library for cached randomly reading and writing access. * Implements the [row][col] operator for read and write access using Segment_get() and Segment_put() functions internally * Implements row read and write access with the [row] operator using Segment_get_row() Segment_put_row() internally * Implements the get_row() and put_row() method using Segment_get_row() Segment_put_row() internally * Implements the flush_segment() method * Implements the copying of raster maps to segments and vice verse * Overwrites the open and close methods * No mathematical operation like __add__ and stuff for the Raster object (only for rows), since r.mapcalc is more sophisticated and faster """ def __init__(self, name, srows=64, scols=64, maxmem=100, *args, **kargs): self.segment = Segment(srows, scols, maxmem) super().__init__(name, *args, **kargs) def _get_mode(self): return self._mode def _set_mode(self, mode): if mode and mode.lower() not in {"r", "w", "rw"}: str_err = _("Mode type: {0} not supported ('r', 'w','rw')") raise ValueError(str_err.format(mode)) self._mode = mode mode = property( fget=_get_mode, fset=_set_mode, doc="Set or obtain the opening mode of raster" ) def __setitem__(self, key, row): """Return the row of Raster object, slice allowed.""" if isinstance(key, slice): # Get the start, stop, and step from the slice return [self.put_row(ii, row) for ii in range(*key.indices(len(self)))] if isinstance(key, tuple): x, y = key return self.put(x, y, row) if isinstance(key, int): if key < 0: # Handle negative indices key += self._rows if key >= self._rows: raise IndexError(_("Index out of range: %r.") % key) return self.put_row(key, row) msg = "Invalid argument type." raise TypeError(msg)
[docs] @must_be_open def map2segment(self): """Transform an existing map to segment file.""" row_buffer = Buffer((self._cols), self.mtype) for row in range(self._rows): libraster.Rast_get_row(self._fd, row_buffer.p, row, self._gtype) self.segment.put_row(row, row_buffer)
[docs] @must_be_open def segment2map(self): """Transform the segment file to a map.""" row_buffer = Buffer((self._cols), self.mtype) for row in range(self._rows): row_buffer = self.segment.get_row(row, row_buffer) libraster.Rast_put_row(self._fd, row_buffer.p, self._gtype)
[docs] @must_be_open def get_row(self, row, row_buffer=None): """Return the row using the `segment.get_row` method :param row: specify the row number :type row: int :param row_buffer: specify the Buffer object that will be instantiate :type row_buffer: Buffer object >>> elev = RasterRowIO(test_raster_name) >>> elev.open('r') >>> for row in elev: ... row Buffer([11, 21, 31, 41], dtype=int32) Buffer([12, 22, 32, 42], dtype=int32) Buffer([13, 23, 33, 43], dtype=int32) Buffer([14, 24, 34, 44], dtype=int32) >>> elev.close() >>> with RasterSegment(test_raster_name) as elev: ... for row in elev: ... row Buffer([11, 21, 31, 41], dtype=int32) Buffer([12, 22, 32, 42], dtype=int32) Buffer([13, 23, 33, 43], dtype=int32) Buffer([14, 24, 34, 44], dtype=int32) """ if row_buffer is None: row_buffer = Buffer((self._cols), self.mtype) return self.segment.get_row(row, row_buffer)
[docs] @must_be_open def put_row(self, row, row_buffer): """Write the row using the `segment.put_row` method :param row: a Row object to insert into raster :type row: Buffer object Input and output must have the same type in case of row copy >>> map_a = RasterSegment(test_raster_name) >>> map_b = RasterSegment(test_raster_name + "_segment") >>> map_a.open('r') >>> map_b.open('w', mtype="CELL", overwrite=True) >>> for row in range(map_a.info.rows): ... map_b[row] = map_a[row] + 1000 >>> map_a.close() >>> map_b.close() >>> map_b = RasterSegment(test_raster_name + "_segment") >>> map_b.open("r") >>> for row in map_b: ... row Buffer([1011, 1021, 1031, 1041], dtype=int32) Buffer([1012, 1022, 1032, 1042], dtype=int32) Buffer([1013, 1023, 1033, 1043], dtype=int32) Buffer([1014, 1024, 1034, 1044], dtype=int32) >>> map_b.close() """ self.segment.put_row(row, row_buffer)
[docs] @must_be_open def get(self, row, col): """Return the map value using the `segment.get` method :param row: Specify the row number :type row: int :param col: Specify the column number :type col: int >>> elev = RasterSegment(test_raster_name) >>> elev.open('r') >>> for i in range(4): ... elev.get(i,i) 11 22 33 44 >>> elev.close() >>> with RasterSegment(test_raster_name) as elev: ... elev.get(0,0) ... elev.get(1,1) ... elev.get(2,2) ... elev.get(3,3) 11 22 33 44 """ return self.segment.get(row, col)
[docs] @must_be_open def put(self, row, col, val): """Write the value to the map using the `segment.put` method :param row: Specify the row number :type row: int :param col: Specify the column number :type col: int :param val: Specify the value that will be write to the map cell :type val: value >>> map_a = RasterSegment(test_raster_name) >>> map_b = RasterSegment(test_raster_name + "_segment") >>> map_a.open('r') >>> map_b.open('w', mtype="FCELL", overwrite=True) >>> for row in range(map_a.info.rows): ... for col in range(map_a.info.cols): ... value = map_a.get(row,col) ... map_b.put(row,col,value + 100) >>> map_a.close() >>> map_b.close() >>> map_b = RasterSegment(test_raster_name + "_segment") >>> map_b.open("r") >>> for row in map_b: ... row Buffer([111., 121., 131., 141.], dtype=float32) Buffer([112., 122., 132., 142.], dtype=float32) Buffer([113., 123., 133., 143.], dtype=float32) Buffer([114., 124., 134., 144.], dtype=float32) >>> map_b.close() """ self.segment.val.value = val self.segment.put(row, col)
[docs] def open(self, mode=None, mtype=None, overwrite=None): """Open the map, if the map already exist: determine the map type and copy the map to the segment files; else, open a new segment map. :param mode: specify if the map will be open with read, write or read/write mode ('r', 'w', 'rw') :type mode: str :param mtype: specify the map type, valid only for new maps: CELL, FCELL, DCELL :type mtype: str :param overwrite: use this flag to set the overwrite mode of existing raster maps :type overwrite: bool """ # read rows and cols from the active region self._rows = libraster.Rast_window_rows() self._cols = libraster.Rast_window_cols() self.mode = mode or self.mode self.mtype = mtype or self.mtype self.overwrite = overwrite if overwrite is not None else self.overwrite if self.exist(): self.info.read() self.cats.mtype = self.mtype self.cats.read() self.hist.read() if (self.mode in {"w", "rw"}) and self.overwrite is False: str_err = _("Raster map <{0}> already exists. Use overwrite.") fatal(str_err.format(self)) # We copy the raster map content into the segments if self.mode in {"rw", "r"}: self._fd = libraster.Rast_open_old(self.name, self.mapset) self._gtype = libraster.Rast_get_map_type(self._fd) self.mtype = RTYPE_STR[self._gtype] # initialize the segment, I need to determine the mtype of the # map # before to open the segment self.segment.open(self) self.map2segment() self.segment.flush() self.cats.read() self.hist.read() if self.mode == "rw": # warning(_(WARN_OVERWRITE.format(self))) # Close the file descriptor and open it as new again libraster.Rast_close(self._fd) self._fd = libraster.Rast_open_new(self.name, self._gtype) # Here we simply overwrite the existing map without content copying elif self.mode == "w": # warning(_(WARN_OVERWRITE.format(self))) self._gtype = RTYPE[self.mtype]["grass type"] self.segment.open(self) self._fd = libraster.Rast_open_new(self.name, self._gtype) else: if self.mode == "r": str_err = _("Raster map <{0}> does not exist") raise OpenError(str_err.format(self.name)) self._gtype = RTYPE[self.mtype]["grass type"] self.segment.open(self) self._fd = libraster.Rast_open_new(self.name, self._gtype)
[docs] @must_be_open def close(self, rm_temp_files=True): """Close the map, copy the segment files to the map. :param rm_temp_files: if True all the segments file will be removed :type rm_temp_files: bool """ if self.mode in {"w", "rw"}: self.segment.flush() self.segment2map() if rm_temp_files: self.segment.close() else: self.segment.release() libraster.Rast_close(self._fd) # update rows and cols attributes self._rows = None self._cols = None self._fd = None
[docs]def random_map_only_columns(mapname, mtype, overwrite=True, factor=100): region = Region() random_map = RasterRow(mapname) rng = np.random.default_rng() row_buf = Buffer( (region.cols,), mtype, buffer=(rng.random(region.cols) * factor).data, ) random_map.open("w", mtype, overwrite) for _ in range(region.rows): random_map.put_row(row_buf) random_map.close() return random_map
[docs]def random_map(mapname, mtype, overwrite=True, factor=100): region = Region() random_map = RasterRow(mapname) random_map.open("w", mtype, overwrite) rng = np.random.default_rng() for _ in range(region.rows): row_buf = Buffer( (region.cols,), mtype, buffer=(rng.random(region.cols) * factor).data, ) random_map.put_row(row_buf) random_map.close() return random_map
[docs]def raster2numpy(rastname, mapset=""): """Return a numpy array from a raster map :param str rastname: the name of raster map :parar str mapset: the name of mapset containing raster map """ with RasterRow(rastname, mapset=mapset, mode="r") as rast: return np.array(rast)
[docs]def raster2numpy_img(rastname, region, color="ARGB", array=None): """Convert a raster map layer into a string with 32Bit ARGB, 24Bit RGB or 8Bit Gray little endian encoding. Return a numpy array from a raster map of type uint8 that contains the colored map data as 32 bit ARGB, 32Bit RGB or 8 bit image :param rastname: The name of raster map :type rastname: string :param region: The region to be used for raster map reading :type region: grass.pygrass.gis.region.Region :param color: "ARGB", "RGB", "GRAY1", "GRAY2" ARGB -> 32Bit RGB with alpha channel (0xAARRGGBB) RGB -> 32Bit RGB (0xffRRGGBB) GRAY1 -> grey scale formular: .33R+ .5G+ .17B GRAY2 -> grey scale formular: .30R+ .59G+ .11B :type color: String :param array: A numpy array (optional) to store the image, the array needs to setup as follows: array = np.ndarray((region.rows*region.cols*scale), np.uint8) scale = 4 in case of ARGB and RGB or scale = 1 in case of Gray scale :type array: numpy.ndarray :return: A numpy array of size rows*cols*4 in case of ARGB, RGB and rows*cols*1 in case of gray scale Attention: This function will change the computational raster region of the current process while running. """ from copy import deepcopy region_orig = deepcopy(region) # Set the raster region region.set_raster_region() scale = 1 color_mode = 1 if color.upper() == "ARGB": scale = 4 color_mode = 1 elif color.upper() == "RGB": scale = 4 color_mode = 2 elif color.upper() == "GRAY1": scale = 1 color_mode = 3 elif color.upper() == "GRAY2": scale = 1 color_mode = 4 if array is None: array = np.ndarray((region.rows * region.cols * scale), np.uint8) libraster.Rast_map_to_img_str( rastname, color_mode, array.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)) ) # Restore the raster region region_orig.set_raster_region() return array
[docs]def numpy2raster(array, mtype, rastname, overwrite=False): """Save a numpy array to a raster map :param obj array: a numpy array :param obj mtype: the datatype of array :param str rastername: the name of output map :param bool overwrite: True to overwrite existing map """ reg = Region() if (reg.rows, reg.cols) != array.shape: msg = "Region and array are different: %r != %r" raise TypeError(msg % ((reg.rows, reg.cols), array.shape)) with RasterRow(rastname, mode="w", mtype=mtype, overwrite=overwrite) as new: newrow = Buffer((array.shape[1],), mtype=mtype) for row in array: newrow[:] = row[:] new.put_row(newrow)
if __name__ == "__main__": import doctest from grass.pygrass.modules import Module Module("g.region", n=40, s=0, e=40, w=0, res=10) Module( "r.mapcalc", expression="%s = row() + (10 * col())" % (test_raster_name), overwrite=True, ) Module( "r.support", map=test_raster_name, title="A test map", history="Generated by r.mapcalc", description="This is a test map", ) cats = """11:A 12:B 13:C 14:D 21:E 22:F 23:G 24:H 31:I 32:J 33:K 34:L 41:M 42:n 43:O 44:P""" Module("r.category", rules="-", map=test_raster_name, stdin_=cats, separator=":") doctest.testmod() mset = utils.get_mapset_raster(test_raster_name, mapset="") if mset: # Remove the generated vector map, if exists Module("g.remove", flags="f", type="raster", name=test_raster_name) mset = utils.get_mapset_raster(test_raster_name + "_segment", mapset="") if mset: # Remove the generated raster map, if exists Module("g.remove", flags="f", type="raster", name=test_raster_name + "_segment")