Skip to content

Raster layer

Import
from pyqgis_wrapper.layer import RasterLayerBuilder, RasterLayerManager

RasterLayerBuilder()

Bases: pyqgis_wrapper.layer.base.LayerBuilder[qgis.core.QgsRasterLayer], pyqgis_wrapper.layer.raster_layer.RasterUtilsMixin

Class that handle layer creation. Either from an existing layer, from an array or from dimensions. A subset extent can be specified to write only a part of the provided raster. Additionnaly an offset can be applied to the array

The class is designed to mimic the chaining pattern of the VectorLayerBuilder with some difference : - user need to call save() first. - class does not handle memory file and write the raster in the tmp folder if no path is provided.

Source code in pyqgis_wrapper/layer/raster_layer.py
456
457
458
459
460
461
462
463
464
465
466
467
468
def __init__(self):
    """ """
    super().__init__()
    self._path = None
    self._writer = None
    self._provider = "gdal"
    self._layer_name = None
    self._extent = None
    self._n_cols = None
    self._n_rows = None
    self._dtype = None
    self._indexes = None
    self._no_data = None

resolution property writable

Return the raster resolution

Returns:

Type Description
int

raster resolution

save(path=None, layer_name=None, extension='tif', provider='gdal', overwrite=False)

Construct the file that'll store the raster layer.

Parameters:

Name Type Description Default
path typing.Optional[typing.Union[str, pathlib.Path]]

Output file to write to, if the path is a folder and layer name is None then a random name will be generated, defaults to None

None
layer_name typing.Optional[str]

Name of the file. If the path is a file then the layer name will be discarded, defaults to None

None
extension str

Extension of the file, defaults to "tif" Accepted extensions : 'tif','gpkg','bag','bil','bmp','bt','byn','ecw','ers','gdb','gen','grd', 'gsb','gtx','hdr','img','jp2','kro','lbl','map','mbtiles','mpr','mrf', 'nc','ntf','pgm','pix','rgb','rst','rsw','sdat','ter','vrt','xml'

'tif'
provider str

Provider used to load the QgsRasterLayer, defaults to "gdal"

'gdal'
overwrite bool

If True will erase existing file, defaults to False

False
Source code in pyqgis_wrapper/layer/raster_layer.py
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
def save(
    self,
    path: Optional[Union[str, Path]] = None,
    layer_name: Optional[str] = None,
    extension: str = "tif",
    provider: str = "gdal",
    overwrite: bool = False,
) -> RasterLayerBuilder:
    """
    Construct the file that'll store the raster layer.

    :param path: Output file to write to, if the path
    is a folder and layer name is None
    then a random name will be generated, defaults to None
    :type path: Optional[Union[str, Path]], optional
    :param layer_name: Name of the file. If the path is a file then
    the layer name will be discarded, defaults to None
    :type layer_name: Optional[str], optional
    :param extension: Extension of the file, defaults to "tif"
    Accepted extensions :
        'tif','gpkg','bag','bil','bmp','bt','byn','ecw','ers','gdb','gen','grd',
        'gsb','gtx','hdr','img','jp2','kro','lbl','map','mbtiles','mpr','mrf',
        'nc','ntf','pgm','pix','rgb','rst','rsw','sdat','ter','vrt','xml'
    :type extension: str, optional
    :param provider:  Provider used to load the QgsRasterLayer, defaults to "gdal"
    :type provider: str, optional
    :param overwrite:If True will erase existing file, defaults to False
    :type overwrite: bool, optional
    """
    self.provider = provider

    self._path, extension = self._validate_output(path, layer_name, extension)

    if not overwrite and Path(self._path).exists():
        raise FileExistsError("Overwrite is set to False and file exists.")
    else:
        self._writer = QgsRasterFileWriter(outputUrl=self._path)

    return self

from_layer(layer, extent=None, band=None, no_data=None)

Fetch metatdata needed for constructing another raster from tthe provided one.

Parameters:

Name Type Description Default
layer qgis.core.QgsRasterLayer

Provided raster to copy

required
extent typing.Optional[qgis.core.QgsRectangle]

Extent of the raster that'll be copied. If not provided the whole raster will be copied, defaults to None

None
band typing.Optional[typing.Union[typing.List[int], int, slice]]

Band that'll be copied. If not provided all bands will be copied, defaults to None

None
no_data typing.Optional[typing.Union[float, int]]

No data value. If not provided will be infered from src.

None

Returns:

Type Description
RasterLayerBuilder

self

Source code in pyqgis_wrapper/layer/raster_layer.py
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
def from_layer(
    self,
    layer: QgsRasterLayer,
    extent: Optional[QgsRectangle] = None,
    band: Optional[Union[List[int], int, slice]] = None,
    no_data: Optional[Union[float, int]] = None,
) -> RasterLayerBuilder:
    """
    Fetch metatdata needed for constructing another raster from tthe provided one.

    :param layer: Provided raster to copy
    :type layer: QgsRasterLayer
    :param extent: Extent of the raster that'll be copied.
    If not provided the whole raster will be copied, defaults to None
    :type extent: Optional[QgsRectangle], optional
    :param band: Band that'll be copied.
    If not provided all bands will be copied, defaults to None
    :type Optional[Union[List[int], int, slice]], optional
    :param no_data: No data value. If not provided will be infered from src.
    :type no_data: Optional[Union[float, int]] , optional
    :return: self
    :rtype: RasterLayerBuilder
    """
    if self._writer is None:
        raise RuntimeError(
            "RasterLayerBuilder has no file to work on.Call `save()` first."
        )
    if not isinstance(layer, QgsRasterLayer):
        raise TypeError(
            f"Parameter layer expected a QgsRasterLayer, got '{type(layer)}'."
        )
    else:
        if not layer.isValid():
            raise ValueError(f"{layer} is not a valid QgsRasterLayer.")

    self._src_layer = layer
    self.set_crs(self._src_layer.crs())
    self._resolve_extent(extent)
    self._indexes = self._resolve_bands(self._src_layer, band=band)
    self._dtype = self._resolve_band_type(
        self._src_layer.dataProvider().dataType(1)
    )
    self._resolve_no_data(no_data)

    return self

configure(crs, dtype, resolution, extent, band=None, array=None, no_data=None)

Configure the builder to create either: - an empty raster from the given dimension - a raster with the provided layer

Uint8 numpy dtype is not supported in QGIS and is converted to Byte When exporting the created raster.as_numpy() you should cast it to uint8 manually if you provided an uint8 array.

Parameters:

Name Type Description Default
crs typing.Union[str, int, qgis.core.QgsCoordinateReferenceSystem]

Crs used for the raster projection

required
dtype typing.Union[qgis.core.Qgis.DataType, int, numpy.dtype]

Expected integer or DataType

  • 'Byte': 1,
  • 'UInt16': 2,
  • 'Int16': 3,
  • 'UInt32': 4,
  • 'Int32': 5,
  • 'Float32': 6,
  • 'Float64': 7,
  • 'CInt16': 8,
  • 'CInt32': 9,
  • 'CFloat32': 10,
  • 'CFloat64': 11,
  • 'ARGB32': 12,
  • 'ARGB32_Premultiplied': 13,
  • 'Int8': 14
required
res typing.Union[int, float]

Resolution of the raster layer

required
extent qgis.core.QgsRectangle

Extent used for the raster layer. It is assumed that the extent will match the array size. (n_row = extent.height / resolution)

required
band typing.Optional[int]

Specify the number of band in the newly created raster, defaults to None. If None then all array bands will be used or a single band raster will be created from the specified dim. Gdal band count is used (start to 1).

None
array typing.Optional[numpy.ndarray]

Array to write into the file, defaults to None Array can in the shape of (band, row, col) or (row, col, band)

None

Returns:

Type Description
RasterLayerBuilder

self

Source code in pyqgis_wrapper/layer/raster_layer.py
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
def configure(
    self,
    crs: Union[str, int, QgsCoordinateReferenceSystem],
    dtype: Union[Qgis.DataType, int, np.dtype],
    resolution: Union[int, float],
    extent: QgsRectangle,
    band: Optional[int] = None,
    array: Optional[np.ndarray] = None,
    no_data: Optional[Union[float, int]] = None,
) -> RasterLayerBuilder:
    """
    Configure the builder to create either:
     - an empty raster from the given dimension
     - a raster with the provided layer

    Uint8 numpy dtype is not supported in QGIS and is converted to Byte
    When exporting the created raster.as_numpy() you should cast it
    to uint8 manually if you provided an uint8 array.

    :param crs: Crs used for the raster projection
    :type crs: Union[str, int, QgsCoordinateReferenceSystem]
    :param dtype: Expected integer or DataType

     - 'Byte': 1,
     - 'UInt16': 2,
     - 'Int16': 3,
     - 'UInt32': 4,
     - 'Int32': 5,
     - 'Float32': 6,
     - 'Float64': 7,
     - 'CInt16': 8,
     - 'CInt32': 9,
     - 'CFloat32': 10,
     - 'CFloat64': 11,
     - 'ARGB32': 12,
     - 'ARGB32_Premultiplied': 13,
     - 'Int8': 14
    :type dtype: Union[Qgis.DataType, int, np.dtype]

    :param res: Resolution of the raster layer
    :type res: Union[int, float]
    :param extent: Extent used for the raster layer.
    It is assumed that the extent will match the array size. (n_row = extent.height / resolution)
    :type extent: QgsRectangle
    :param band: Specify the number of band in the newly created raster, defaults to None.
     If None then all array bands will be used or a single band raster
     will be created from the specified dim.
     Gdal band count is used (start to 1).
    :type band: Optional[Union[List[int], int, slice]], optional
    :param array: Array to write into the file, defaults to None
     Array can in the shape of (band, row, col) or (row, col, band)
    :type array: Optional[np.ndarray], optional
    :return: self
    :rtype: RasterLayerBuilder
    """
    if self._writer is None:
        raise RuntimeError(
            "RasterLayerBuilder has no file to work on. Call `save()` first."
        )

    self._array = array
    self.set_crs(crs)
    self._dtype = self._resolve_band_type(dtype)
    self.resolution = resolution
    self._resolve_extent(extent)
    self._no_data = no_data
    if array is not None:
        self._from_array(array, band)
    else:
        self._from_dimension(band)

    return self

build(x_offset=0, y_offset=0)

Build the raster with the provided info

The x and y off set will only be used in case an array was provided. For the given dimension if the array is smaller than the raster then the offset will be used to place the array inside the raster the array to specify witch parts will be write in the raster.

Parameters:

Name Type Description Default
x_offset int

Rows offset, defaults to 0

0
y_offset int

Columns offset, defaults to 0

0

Returns:

Type Description
RasterLayerBuilder Note: The offset paramater might be deleted. The intended case was for it to create a big raster and only populate a part of it and then later call the RasterLayerManager to update the rest. But it is easier and more logical to compute an empty raster and directly call the RasterLayerManager to update parts by parts. We'll see at usage.

self

Source code in pyqgis_wrapper/layer/raster_layer.py
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
def build(self, x_offset: int = 0, y_offset: int = 0) -> RasterLayerBuilder:
    """
    Build the raster with the provided info

    The x and y off set will only be used in case an array was provided.
    For the given dimension if the array is smaller than the raster
    then the offset will be used to place the array inside the raster
    the array to specify witch parts will be write in the raster.
    :param x_offset: Rows offset, defaults to 0
    :type x_offset: int, optional
    :param y_offset: Columns offset, defaults to 0
    :type y_offset: int, optional
    :return: self
    :rtype: RasterLayerBuilder

    Note: The offset paramater might be deleted.
    The intended case was for it to create a big raster and only populate a part of it
    and then later call the RasterLayerManager to update the rest.
    But it is easier and more logical to compute an empty raster and directly call
    the RasterLayerManager to update parts by parts.
    We'll see at usage.

    """
    self._validate_parameters()

    if self._src_layer is not None:
        self._copy_layer()
    elif self._array is not None:
        self._write_array(x_offset, y_offset)
    else:
        # Needed to keep a reference ?
        provider = self._create_provider()  # noqa: F841

    self._load_layer()

    return self

RasterLayerManager()

Regroups all the rasterLayerManager - array: read/update array in a raster file - pixel: read a specific pixel value - metadata: set/get crs, no data and band name - summary: histogram and descriptive statistics

Source code in pyqgis_wrapper/layer/raster_layer.py
1440
1441
1442
1443
1444
def __init__(self):
    self._array = RasterArrayManager()
    self._pixel = RasterPixelManager()
    self._metadata = RasterMetadataManager()
    self._summary = RasterBandSummary()

RasterUtilsMixin

Handle the edition mode for the raster data provider

As provider.isEditable seems to be True by default i keep an internal switch to handle edit_session Not sure if needed but QGIS doc says it should be used (might be because of an older behavior)

extent_mapping(raster, extent)

Map given extent to raster grid. Return x & y offset in pixel units, aligned extent and height & with of aligned extent in pixel units.

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster layer.

required
extent qgis.core.QgsRectangle

Given extent.

required

Returns:

Type Description
Tuple[int, int, QgsRectangle, Tuple[int, int]]

x_offset, y_offset, aligned extent, height and with of aligned extent in pixel units.

Raises:

Type Description
ExtentError

Raise error if extent out of raster extent.

Source code in pyqgis_wrapper/layer/raster_layer.py
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
def extent_mapping(
    self, raster: QgsRasterLayer, extent: QgsRectangle
) -> Tuple[Tuple[int, int], QgsRectangle, Tuple[int, int]]:
    """Map given extent to raster grid.
    Return x & y offset in pixel units, aligned extent and height & with of aligned extent in pixel units.

    :param raster: Raster layer.
    :type raster: QgsRasterLayer
    :param extent: Given extent.
    :type extent: QgsRectangle
    :raises ExtentError: Raise error if extent out of raster extent.
    :return: x_offset, y_offset, aligned extent, height and with of aligned extent in pixel units.
    :rtype: Tuple[int, int, QgsRectangle, Tuple[int, int]]
    """

    resx = raster.rasterUnitsPerPixelX()
    resy = raster.rasterUnitsPerPixelY()
    r_extent = raster.extent()
    r_xmin = r_extent.xMinimum()
    r_ymin = r_extent.yMinimum()
    r_xmax = r_extent.xMaximum()
    r_ymax = r_extent.yMaximum()

    xmin = extent.xMinimum()
    ymin = extent.yMinimum()
    xmax = extent.xMaximum()
    ymax = extent.yMaximum()

    if xmin < r_xmin or ymin < r_ymin or xmax > r_xmax or ymax > r_ymax:
        raise ExtentError(
            "Given extent is laying totally or partially outside the raster extent."
        )

    eps = 1e-9

    xoff = floor((extent.xMinimum() - r_xmin) / resx + eps)
    yoff = floor((r_ymax - extent.yMaximum()) / resy + eps)

    xend = ceil((extent.xMaximum() - r_xmin) / resx - eps)
    yend = ceil((r_ymax - extent.yMinimum()) / resy - eps)

    width = xend - xoff
    height = yend - yoff

    aligned_xmin = r_xmin + xoff * resx
    aligned_xmax = r_xmin + xend * resx
    aligned_ymax = r_ymax - yoff * resy
    aligned_ymin = r_ymax - yend * resy

    aligned_extent = QgsRectangle(
        aligned_xmin,
        aligned_ymin,
        aligned_xmax,
        aligned_ymax,
    )

    return (xoff, yoff), aligned_extent, (height, width)

RasterLayerBaseManager()

Bases: abc.ABC, pyqgis_wrapper.layer.raster_layer.RasterUtilsMixin

Base handler for QgsRasterLayer

Source code in pyqgis_wrapper/layer/raster_layer.py
962
963
def __init__(self):
    self.logger = create_child_logger(__name__)

RasterArrayManager()

Bases: pyqgis_wrapper.layer.raster_layer.RasterLayerBaseManager

Handles raster array operation : - update an array or a part of it with another one - read the array in the raster

Source code in pyqgis_wrapper/layer/raster_layer.py
962
963
def __init__(self):
    self.logger = create_child_logger(__name__)

update(raster, array, band=None, extent=None)

Update a QgsRasterLayer with an array of shape (H, W, C). C should be channels corresponding to provided bands in same order.

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster to update

required
array typing.Union[numpy.ndarray, numpy.ma.MaskedArray]

Array that'll be used for the update

required
band typing.Optional[typing.Union[typing.List[int], int, slice]]

Band where the array will be wrote. All extra band will be dismissed The array third dimension will be written in the order of the provided band.

None
extent typing.Optional[qgis.core.QgsRectangle]

If the array dimensions is smaller than the raster this extent will be used to write to the correct raster part. Any extra data will be discarded, defaults to None

None

Returns:

Type Description
QgsRasterLayer

updated QgsRasterLayer

Source code in pyqgis_wrapper/layer/raster_layer.py
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
def update(
    self,
    raster: QgsRasterLayer,
    array: Union[np.ndarray, np.ma.MaskedArray],
    band: Optional[Union[List[int], int, slice]] = None,
    extent: Optional[QgsRectangle] = None,
) -> QgsRasterLayer:
    """
    Update a QgsRasterLayer with an array of shape (H, W, C). C should be channels corresponding to provided bands in same order.

    :param raster: Raster to update
    :type raster: QgsRasterLayer
    :param array: Array that'll be used for the update
    :type array: Union[np.ndarray, np.ma.MaskedArray]
    :param band: Band where the array will be wrote.
     All extra band will be dismissed
     The array third dimension will be written in the order of the provided band.
    :type band: Optional[Union[List[int], int, slice]]
    :param extent: If the array dimensions is smaller than the raster
     this extent will be used to write
     to the correct raster part.
     Any extra data will be discarded, defaults to None
    :type extent: Optional[QgsRectangle], optional

    :return: updated QgsRasterLayer
    :rtype: QgsRasterLayer
    """
    if array.ndim == 2:
        array = np.atleast_3d(array)
    elif array.ndim != 3:
        raise ValueError(
            f"Given array should have 3 dimensions (H, W, C),  got {array.shape}."
        )

    indexes = self._resolve_bands(raster, band)
    indexes = self._validate_indexes(indexes, array)
    dtype = self._resolve_band_type(array.dtype)
    if dtype != raster.dataProvider().dataType(1):
        raise InvalidType(
            f"Array dtype ({dtype}) does not match"
            f" the raster dtype ({raster.dataProvider().dataType(1)})."
        )
    if extent is None:
        extent = raster.extent()

    (x_offset, y_offset), aligned_extent, (h, w) = self.extent_mapping(
        raster, extent
    )

    rows, cols, channels = array.shape
    if rows != h or cols != w:
        raise ArrayEditionError(
            f"Target extent accept arrays of shape ({h}, {w}, X). Provided array is ({rows}, {cols}, {channels})."
        )

    self._write_array(
        raster.dataProvider(),
        array,
        indexes,
        aligned_extent,
        x_offset,
        y_offset,
    )  # might need to use raster extent instead

    # self._update_renderer(raster)

    return raster

read(raster, band=None, extent=None)

Read a raster or a raster part as an numpy array of shape (H, W, C).

If provided extent is not aligned on raster grid it will extend the extent to a larger aligned extent.

If any no data value are set in the raster the array will be masked.

Uint8 numpy dtype is not supported in QGIS and is converted to Byte When exporting the created raster.as_numpy() you should cast it to uint8 manually if you provided an uint8 array.

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster to read

required
band typing.Optional[typing.Union[typing.List[int], int, slice]]

Band to read, it can be an int, a list of int or a slice (GDAL band count), defaults to None If None all the bands will be used.

None
extent typing.Optional[qgis.core.QgsRectangle]

Subset extent to use if not the whole array is wanted, defaults to None

None

Returns:

Type Description
Union[np.ndarray, np.ma.MaskedArray]

Raster array

Source code in pyqgis_wrapper/layer/raster_layer.py
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
def read(
    self,
    raster: QgsRasterLayer,
    band: Optional[Union[List[int], int, slice]] = None,
    extent: Optional[QgsRectangle] = None,
) -> Union[np.ndarray, np.ma.MaskedArray]:
    """
    Read a raster or a raster part as an numpy array of shape (H, W, C).

    If provided extent is not aligned on raster grid it will extend the extent to a larger aligned extent.

    If any no data value are set in the raster the array will be masked.

    Uint8 numpy dtype is not supported in QGIS and is converted to Byte
    When exporting the created raster.as_numpy() you should cast it
    to uint8 manually if you provided an uint8 array.

    :param raster: Raster to read
    :type raster: QgsRasterLayer
    :param band: Band to read, it can be an int,
     a list of int or a slice (GDAL band count), defaults to None
     If None all the bands will be used.
    :type band: Optional[Union[List[int], int, slice]], optional
    :param extent: Subset extent to use if not the whole array is wanted, defaults to None
    :type extent: Optional[QgsRectangle], optional

    :return: Raster array
    :rtype: Union[np.ndarray, np.ma.MaskedArray]
    """
    indexes = self._resolve_bands(raster, band=band)

    if extent is None:
        extent = raster.extent()

    _, aligned_extent, (h, w) = self.extent_mapping(raster, extent)
    use_mask = self._resolve_mask(raster, indexes)
    arrays = []
    if len(indexes) == raster.bandCount() and extent == raster.extent():
        return raster.as_numpy(use_mask).transpose(1, 2, 0)
    else:
        for index in indexes:
            block = raster.dataProvider().block(index, aligned_extent, w, h)
            arrays.append(block.as_numpy(use_mask))

        return np.ma.stack(arrays, axis=2)

RasterPixelManager()

Bases: pyqgis_wrapper.layer.raster_layer.RasterLayerBaseManager

Handles pixel operation

Source code in pyqgis_wrapper/layer/raster_layer.py
962
963
def __init__(self):
    self.logger = create_child_logger(__name__)

read(raster, point, band=None)

Read a pixel from a given raster and output his value.

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster to read from

required
point typing.Union[qgis.core.QgsPointXY, qgis.core.QgsPoint]

Point where the pixel to read is laying

required
band typing.Optional[typing.Union[int, typing.List[int], slice]]

Band to search the pixel on. If None all the bands will be retrieved, defaults to None

None

Returns:

Type Description
Union[int, List[int]]

Pixels values as list if multiple bands provided or int if a single band is provided.

Source code in pyqgis_wrapper/layer/raster_layer.py
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
def read(
    self,
    raster: QgsRasterLayer,
    point: Union[QgsPointXY, QgsPoint],
    band: Optional[Union[int, List[int], slice]] = None,
) -> Union[int, List[int]]:
    """
    Read a pixel from a given raster and output his value.

    :param raster: Raster to read from
    :type raster: QgsRasterLayer
    :param point: Point where the pixel to read is laying
    :type point: Union[QgsPointXY, QgsPoint]
    :param band: Band to search the pixel on.
    If None all the bands will be retrieved, defaults to None
    :type band: Optional[int], optional

    :return: Pixels values as list if multiple bands
    provided or int if a single band is provided.
    :rtype: Union[int, List[int]]
    """
    results = []
    if isinstance(point, QgsPoint):
        point = QgsPointXY(point.x(), point.y())

    indexes = self._resolve_bands(raster, band)

    for index in indexes:
        results.append(raster.dataProvider().sample(point, index)[0])

    if len(results) == 1:
        return results[0]
    else:
        return results

RasterMetadataManager()

Bases: pyqgis_wrapper.layer.raster_layer.RasterLayerBaseManager

Handles raster metadata such as applying a CRS (does not reproject) or applying no data.

Source code in pyqgis_wrapper/layer/raster_layer.py
962
963
def __init__(self):
    self.logger = create_child_logger(__name__)

get_crs(raster)

The provider CRS and the layer CRS might be different if it was reassigned

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster to check

required

Returns:

Type Description
Tuple[QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem]

Return the crs of the layer aswell as the crs of his provider.

Source code in pyqgis_wrapper/layer/raster_layer.py
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
def get_crs(
    self, raster: QgsRasterLayer
) -> Tuple[QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem]:
    """
    The provider CRS and the layer CRS might be different if it was reassigned

    :param raster: Raster to check
    :type raster: QgsRasterLayer

    :return: Return the crs of the layer aswell as the crs of his provider.
    :rtype: Tuple[QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem]
    """
    return raster.crs(), raster.dataProvider().crs()

set_crs(raster, crs)

This does not erase the original provider CRS. It is only usefull to work on this data in case the CRS was incorrectly set.

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster to assign the CRS to

required
crs typing.Union[qgis.core.QgsCoordinateReferenceSystem, str, int]

CRS to use

required

Returns:

Type Description
bool

Return True if the CRS changed

Source code in pyqgis_wrapper/layer/raster_layer.py
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
def set_crs(
    self, raster: QgsRasterLayer, crs: Union[QgsCoordinateReferenceSystem, str, int]
) -> bool:
    """
    This does not erase the original provider CRS.
    It is only usefull to work on this data in case the CRS was incorrectly set.

    :param raster: Raster to assign the CRS to
    :type raster: QgsRasterLayer
    :param crs: CRS to use
    :type crs: Union[QgsCoordinateReferenceSystem, str, int]

    :return: Return True if the CRS changed
    :rtype: bool
    """
    crs = CRSHandler.build_crs(crs)
    raster.setCrs(crs)
    if raster.crsChanged:
        return True
    else:
        return False

get_src_no_data(raster, band=1)

Get the source no data value if set.

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster to check

required
band int

Band to check start at 1, defaults to 1

1

Returns:

Type Description
Union[float, None]

No data value or None if not set

Source code in pyqgis_wrapper/layer/raster_layer.py
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
def get_src_no_data(
    self, raster: QgsRasterLayer, band: int = 1
) -> Union[float, None]:
    """
    Get the source no data value if set.

    :param raster: Raster to check
    :type raster: QgsRasterLayer
    :param band: Band to check start at 1, defaults to 1
    :type band: int, optional

    :return: No data value or None if not set
    :rtype: Union[float, None]
    """
    if raster.dataProvider().sourceHasNoDataValue(band):
        return raster.dataProvider().sourceNoDataValue(band)
    else:
        return None

set_src_no_data(raster, band=1, value=None, activate=True)

Set the source no data value or toggle it on/off if no value is provided. Beware : toggling it off does not add it back to statistics computations. If another no data value was set prior it'll not be shown in statistics again. I do not know how to remvoe cached statistics if it's the issue.

No data will not be shown in masked numpy array if toggled on.

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster to set the no data on

required
band int

Band to set the no data on, defaults to 1

1
value typing.Optional[float]

description, defaults to None

None
activate bool

description, defaults to True

True
Source code in pyqgis_wrapper/layer/raster_layer.py
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
def set_src_no_data(
    self,
    raster: QgsRasterLayer,
    band: int = 1,
    value: Optional[float] = None,
    activate: bool = True,
):
    """
    Set the source no data value or toggle it on/off if no value is provided.
    Beware : toggling it off does not add it back to statistics computations.
    If another no data value was set prior it'll not be shown in statistics again.
    I do not know how to remvoe cached statistics if it's the issue.

    No data will not be shown in masked numpy array if toggled on.

    :param raster: Raster to set the no data on
    :type raster: QgsRasterLayer
    :param band: Band to set the no data on, defaults to 1
    :type band: int, optional
    :param value: _description_, defaults to None
    :type value: Optional[float], optional
    :param activate: _description_, defaults to True
    :type activate: bool, optional
    """
    if value is not None:
        raster.dataProvider().setNoDataValue(band, value)
        raster.dataProvider().setUseSourceNoDataValue(band, True)
    else:
        raster.dataProvider().setUseSourceNoDataValue(band, activate)

    raster.dataProvider().reload()  # Not sure if needed
    raster.dataProvider().reloadData()  # Not sure which one to use

get_usr_no_data(raster, band=1)

Get the user no data value if set. Contrary to src no data, user no data can be multiple

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster to check

required
band int

Band to check start at 1, defaults to 1

1

Returns:

Type Description
Union[List[float], None]

No data value or None if not set

Source code in pyqgis_wrapper/layer/raster_layer.py
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
def get_usr_no_data(
    self, raster: QgsRasterLayer, band: int = 1
) -> Union[List[float], None]:
    """
    Get the user no data value if set.
    Contrary to src no data, user no data can be multiple

    :param raster: Raster to check
    :type raster: QgsRasterLayer
    :param band: Band to check start at 1, defaults to 1
    :type band: int, optional

    :return: No data value or None if not set
    :rtype: Union[List[float], None]
    """
    user_no_data = raster.dataProvider().userNoDataValues(band)
    if bool(user_no_data):
        min, max = user_no_data[0].min(), user_no_data[0].max()
        return [min, max]
    else:
        return None

set_usr_no_data(raster, band=1, value=None)

Set the user no data value or remove it if no value is provided. User no data can handle range of value (ex : 252 to 255) Any new no data provided will erase the ones already used. User no data seems to work on statistics without reloading them No data will not be shown in masked numpy array.

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster to set the no data on

required
band int

Band to set the no data on, defaults to 1

1
value typing.Optional[typing.Union[int, typing.Sequence[int]]]

description, defaults to None

None
activate (bool, optional)

description, defaults to True

required
Source code in pyqgis_wrapper/layer/raster_layer.py
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
def set_usr_no_data(
    self,
    raster: QgsRasterLayer,
    band: int = 1,
    value: Optional[Union[int, Sequence[int]]] = None,
):
    """
    Set the user no data value or remove it if no value is provided.
    User no data can handle range of value (ex : 252 to 255)
    Any new no data provided will erase the ones already used.
    User no data seems to work on statistics without reloading them
    No data will not be shown in masked numpy array.

    :param raster: Raster to set the no data on
    :type raster: QgsRasterLayer
    :param band: Band to set the no data on, defaults to 1
    :type band: int, optional
    :param value: _description_, defaults to None
    :type value: Optional[float], optional
    :param activate: _description_, defaults to True
    :type activate: bool, optional
    """
    if value is not None:
        if isinstance(value, int):
            no_data = [QgsRasterRange(value, value)]
        elif isinstance(value, (tuple, list, set)):
            no_data = sorted(value)
            no_data = [QgsRasterRange(no_data[0], no_data[1])]
    else:
        no_data = []

    raster.dataProvider().setUserNoDataValue(band, no_data)
    raster.dataProvider().reload()  # Not sure if needed
    raster.dataProvider().reloadData()

get_band_name(raster)

Fetch the band names if the bands have no name it'll return an empty string.

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

raster to check

required

Returns:

Type Description
Dict[int,str]

Band names with the key as index (GDAL like)

Source code in pyqgis_wrapper/layer/raster_layer.py
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
def get_band_name(self, raster: QgsRasterLayer) -> Dict[int, str]:
    """
    Fetch the band names if the bands have no name it'll return an empty string.

    :param raster: raster to check
    :type raster: QgsRasterLayer

    :return: Band names with the key as index (GDAL like)
    :rtype: Dict[int,str]
    """
    names = {}

    for band in range(1, raster.bandCount() + 1):
        name = raster.bandName(band)
        if ":" in name:
            names[band] = name.split(f"Band {band}: ")[1]
        else:
            names[band] = name.split(f"Band {band}")[1]

    return names

set_band_name(raster, names)

Set band names for the raster band.

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster with the bands to name

required
names typing.Dict[int, str]

Expect a dict with keys as band index (GDAL like) and values as band names

required
Source code in pyqgis_wrapper/layer/raster_layer.py
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
def set_band_name(self, raster: QgsRasterLayer, names: Dict[int, str]):
    """
    Set band names for the raster band.

    :param raster: Raster with the bands to name
    :type raster: QgsRasterLayer
    :param names: Expect a dict with keys as band index (GDAL like)
    and values as band names
    :type names: Dict[int, str]
    """
    dataset = gdal.Open(raster.dataProvider().dataSourceUri(), gdal.GA_Update)
    _ = self._resolve_bands(raster, list(names.keys()))

    if dataset:
        for band, name in names.items():
            band = dataset.GetRasterBand(band)
            band.SetDescription(name)

    dataset.FlushCache()
    dataset = None
    raster.dataProvider().reloadData()

RasterBandSummary()

Bases: pyqgis_wrapper.layer.raster_layer.RasterLayerBaseManager

Handles raster statistics at band level.

Source code in pyqgis_wrapper/layer/raster_layer.py
962
963
def __init__(self):
    self.logger = create_child_logger(__name__)

histogram(raster, band=1, bins=0, min=None, max=None, extent=QgsRectangle(), sample_size=0)

Return the pixel frequency and the bin edges of the histogram for the band.

The returned value can be used to plot the pixel frequency using : plt.bar(bin_edges[:-1], frequency, width=(bin_edges[1]-bin_edges[0]), align='edge')

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster to compute the histogram on

required
band int

Band to compute the histogram on (GDAL like), defaults to 1

1
bins int

Number of bins, if 0 it will create bins of width one, defaults to 0

0
min typing.Optional[float]

Minimal value to include, defaults to None

None
max typing.Optional[float]

Maximal value to include, defaults to None

None
extent qgis.core.QgsRectangle

Optional subset extent. If empty the whole raster will be included, defaults to QgsRectangle()

qgis.core.QgsRectangle()
sample_size int

Number of pixel to compute the histogram on. If 0 it'll use all pixels, defaults to 0

0

Returns:

Type Description
Tuple[np.ndarray, np.ndarray]

Pixel frequency and bin_edges

Source code in pyqgis_wrapper/layer/raster_layer.py
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
def histogram(
    self,
    raster: QgsRasterLayer,
    band: int = 1,
    bins: int = 0,
    min: Optional[float] = None,
    max: Optional[float] = None,
    extent: QgsRectangle = QgsRectangle(),
    sample_size: int = 0,
) -> Tuple[np.ndarray, np.ndarray]:
    """
    Return the pixel frequency and the bin
    edges of the histogram for the band.

    The returned value can be used to plot the pixel frequency using :
    plt.bar(bin_edges[:-1], frequency, width=(bin_edges[1]-bin_edges[0]), align='edge')

    :param raster: Raster to compute the histogram on
    :type raster: QgsRasterLayer
    :param band: Band to compute the histogram on (GDAL like), defaults to 1
    :type band: int, optional
    :param bins: Number of bins, if 0 it will create
     bins of width one, defaults to 0
    :type bins: int, optional
    :param min: Minimal value to include, defaults to None
    :type min: Optional[float], optional
    :param max: Maximal value to include, defaults to None
    :type max: Optional[float], optional
    :param extent: Optional subset extent.
     If empty the whole raster will be included, defaults to QgsRectangle()
    :type max: Optional[QgsRectangle], optional
    :param sample_size: Number of pixel to compute the histogram on.
     If 0 it'll use all pixels, defaults to 0
    :type sample_size: int, optional

    :return: Pixel frequency and bin_edges
    :rtype: Tuple[np.ndarray, np.ndarray]
    """
    hist = raster.dataProvider().histogram(
        band, bins, min, max, extent, sample_size
    )
    frequency = (hist.histogramVector / np.sum(hist.histogramVector)) * 100
    bin_edges = np.linspace(hist.minimum, hist.maximum, hist.binCount + 1)

    return frequency, bin_edges

statistics(raster, band=1, extent=QgsRectangle(), sample_size=0)

Compute base statistics on the raster band.

All the returned statistics exclude user no data. Source no data seems to be excluded only if it was set before the first statistics computation.

Parameters:

Name Type Description Default
raster qgis.core.QgsRasterLayer

Raster to compute statistics on

required
band int

Band to compute statistics on, defaults to 1

1
extent typing.Optional[qgis.core.QgsRectangle]

Optional subset extent. If empty the whole raster will be included, defaults to QgsRectangle()

qgis.core.QgsRectangle()
sample_size int

Number of pixel to compute the histogram on. If 0 it'll use all pixels, defaults to 0

0

Returns:

Type Description
Tuple[float, float, float, float, int]

Statistics in the following order: min, max, mean, stddev, pixel count

Source code in pyqgis_wrapper/layer/raster_layer.py
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
def statistics(
    self,
    raster: QgsRasterLayer,
    band: int = 1,
    extent: Optional[QgsRectangle] = QgsRectangle(),
    sample_size: int = 0,
) -> Tuple[float, float, float, float, int]:
    """
    Compute base statistics on the raster band.

    All the returned statistics exclude user no data.
    Source no data seems to be excluded only
    if it was set before the first statistics computation.

    :param raster: Raster to compute statistics on
    :type raster: QgsRasterLayer
    :param band: Band to compute statistics on, defaults to 1
    :type band: int, optional
    :param extent: Optional subset extent.
     If empty the whole raster will be included, defaults to QgsRectangle()
    :type max: Optional[QgsRectangle], optional
    :param sample_size: Number of pixel to compute the histogram on.
     If 0 it'll use all pixels, defaults to 0
    :type sample_size: int, optional

    :return: Statistics in the following order:
     min, max, mean, stddev, pixel count
    :rtype: Tuple[float, float, float, float, int]
    """
    stats = raster.dataProvider().bandStatistics(
        band, QgsRasterBandStats.All, extent, sample_size
    )

    return (
        round(stats.minimumValue, 2),
        round(stats.maximumValue, 2),
        round(stats.mean, 2),
        round(stats.stdDev, 2),
        stats.elementCount,
    )

DataTypeValidator

Valid the data type in the raster band and output a formated data type DataType are mainly used for raster data. Vector data use QVariant or QMetaType

numpy_dtype_to_qgis_dtype(value) staticmethod

Map numpy dtype to qgis dtype

Parameters:

Name Type Description Default
value numpy.dtype

numpy dtype to map

required

Returns:

Type Description
Qgis.DataType

Qgis dtype

Source code in pyqgis_wrapper/layer/raster_layer.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@staticmethod
def numpy_dtype_to_qgis_dtype(value: np.dtype) -> Qgis.DataType:
    """
    Map numpy dtype to qgis dtype

    :param value: numpy dtype to map
    :type value: np.dtype

    :return: Qgis dtype
    :rtype: Qgis.DataType
    """
    return {
        "uint8": Qgis.DataType.Byte,
        "byte": Qgis.DataType.Byte,
        "int8": Qgis.DataType.Int8,
        "uint16": Qgis.DataType.UInt16,
        "int16": Qgis.DataType.Int16,
        "uint32": Qgis.DataType.UInt32,
        "int32": Qgis.DataType.Int32,
        "float32": Qgis.DataType.Float32,
        "float64": Qgis.DataType.Float64,
        "complex64": Qgis.DataType.CFloat32,
        "complex128": Qgis.DataType.CFloat64,
    }[str(value)]

is_valid(value) staticmethod

Check if the given integer or DataType is valid.

Parameters:

Name Type Description Default
value typing.Union[int, qgis.core.Qgis.DataType, numpy.dtype]

Value to check

required

Returns:

Type Description
bool

True if valid otherwise raise an error

Source code in pyqgis_wrapper/layer/raster_layer.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
@staticmethod
def is_valid(value: Union[int, Qgis.DataType, np.dtype]) -> bool:
    """
    Check if the given integer or DataType is valid.

    :param value: Value to check
    :type value: Union[int, Qgis.DataType, np.dtype]

    :return: True if valid otherwise raise an error
    :rtype: bool
    """
    if isinstance(value, np.dtype):
        value = DataTypeValidator.numpy_dtype_to_qgis_dtype(value).value
    elif isinstance(value, Qgis.DataType):
        value = value.value
    elif isinstance(value, int):
        pass
    else:
        raise InvalidType(
            "Invalid dtype, Expected int, Qgis.DataType or np.dtype."
            f"Got {type(value)}"
        )
    exists = value in [dt.value for dt in Qgis.DataType]
    if exists:
        if value != 0:
            return exists

    raise ValueError(
        f"{value} is not a valid Qgis.DataType."
        f" Expected : \n {DataTypeValidator.valid_types()}"
    )

valid_type(value) staticmethod

Return a valid DataType

Uint8 numpy dtype is not supported in QGIS and is converted to Byte When exporting raster.as_numpy() you should cast it to uint8 manually

Parameters:

Name Type Description Default
value typing.Union[int, qgis.core.Qgis.DataType, numpy.dtype]

Value to check

required

Returns:

Type Description
Qgis.DataType

Valid datatype

Source code in pyqgis_wrapper/layer/raster_layer.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
@staticmethod
def valid_type(value: Union[int, Qgis.DataType, np.dtype]) -> Qgis.DataType:
    """
    Return a valid DataType

    Uint8 numpy dtype is not supported in QGIS and is converted to Byte
    When exporting raster.as_numpy() you should cast it to uint8 manually

    :param value: Value to check
    :type value: Union[int, Qgis.DataType, np.dtype]

    :return: Valid datatype
    :rtype: Qgis.DataType
    """
    if isinstance(value, np.dtype):
        value = DataTypeValidator.numpy_dtype_to_qgis_dtype(value)

    if isinstance(value, Qgis.DataType):
        return value
    elif isinstance(value, int):
        return Qgis.DataType(value)

valid_types() staticmethod

Return a dictionary of valid Qgis.DataType names and their integer values sorted by their values.

Source code in pyqgis_wrapper/layer/raster_layer.py
145
146
147
148
149
150
151
152
153
154
155
@staticmethod
def valid_types() -> dict:
    """
    Return a dictionary of valid Qgis.DataType names and their integer
    values sorted by their values.
    """
    return {
        dt.name: dt.value
        for dt in sorted(Qgis.DataType, key=lambda d: d.value)
        if dt.value != 0
    }