Skip to content

Commit

Permalink
Merge pull request #60277 from merydian/fix-60205
Browse files Browse the repository at this point in the history
Fix QgsRasterLayer and QgsRasterBlock as_numpy methods
  • Loading branch information
rouault authored Jan 27, 2025
2 parents 9d8f5e7 + 106e5aa commit 2bef21d
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 19 deletions.
21 changes: 14 additions & 7 deletions python/PyQt6/core/__init__.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -569,11 +569,15 @@ try:
raise ValueError(f"The raster block data type '{str(self.dataType())}' is not compatible with NumPy arrays.")
src_array = _numpy.frombuffer(self.data(), dtype=raster_dtype)
src_array = src_array.reshape((self.height(), self.width()))
if not self.hasNoDataValue() or not use_masking:
return src_array
else:
no_data_value = self.noDataValue() if isinstance(self.noDataValue(), raster_dtype) else 0
if use_masking:
if not self.hasNoDataValue():
# Default to 0 as noDataValue if none is set
no_data_value = 0
else:
no_data_value = self.noDataValue()
return _numpy.ma.masked_equal(src_array, no_data_value)
else:
return src_array

QgsRasterBlock.as_numpy = _raster_block_as_numpy

Expand All @@ -586,7 +590,10 @@ try:
src_array = block.as_numpy(use_masking=use_masking)
arrays.append(src_array)

return _numpy.array(arrays) # This converts any maskedArrays to numpy.array
if use_masking:
return _numpy.ma.stack(arrays, axis=0)
else:
return _numpy.array(arrays)

QgsRasterLayer.as_numpy = _raster_layer_as_numpy

Expand Down Expand Up @@ -646,7 +653,7 @@ try:


QgsGeometry.as_numpy = _qgsgeometry_as_numpy

except ModuleNotFoundError:
def _raster_block_as_numpy(self, use_masking:bool = True):
raise QgsNotSupportedException('QgsRasterBlock.as_numpy is not available, numpy is not installed on the system')
Expand All @@ -657,7 +664,7 @@ except ModuleNotFoundError:
raise QgsNotSupportedException('QgsRasterLayer.as_numpy is not available, numpy is not installed on the system')

QgsRasterLayer.as_numpy = _raster_layer_as_numpy

def _geometry_as_numpy(self):
raise QgsNotSupportedException('QgsGeometry.as_numpy is not available, numpy is not installed on the system')

Expand Down
17 changes: 12 additions & 5 deletions python/core/__init__.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -579,11 +579,15 @@ try:
raise ValueError(f"The raster block data type '{str(self.dataType())}' is not compatible with NumPy arrays.")
src_array = _numpy.frombuffer(self.data(), dtype=raster_dtype)
src_array = src_array.reshape((self.height(), self.width()))
if not self.hasNoDataValue() or not use_masking:
return src_array
else:
no_data_value = self.noDataValue() if isinstance(self.noDataValue(), raster_dtype) else 0
if use_masking:
if not self.hasNoDataValue():
# Default to 0 as noDataValue if none is set
no_data_value = 0
else:
no_data_value = self.noDataValue()
return _numpy.ma.masked_equal(src_array, no_data_value)
else:
return src_array

QgsRasterBlock.as_numpy = _raster_block_as_numpy

Expand All @@ -596,7 +600,10 @@ try:
src_array = block.as_numpy(use_masking=use_masking)
arrays.append(src_array)

return _numpy.array(arrays) # This converts any maskedArrays to numpy.array
if use_masking:
return _numpy.ma.stack(arrays, axis=0)
else:
return _numpy.array(arrays)

QgsRasterLayer.as_numpy = _raster_layer_as_numpy

Expand Down
4 changes: 3 additions & 1 deletion tests/src/python/test_qgsrasterblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ def testQgsRasterBlock(self):
self.assertTrue((block.as_numpy() == expected_array).all())

# test with noDataValue set
block.setNoDataValue(0)
block.setNoDataValue(-999)
data = numpy.array([[numpy.nan, 2], [4, 4]])
expected_masked_array = numpy.ma.masked_array(data, mask=numpy.isnan(data))
self.assertTrue((block.as_numpy() == expected_masked_array).all())
self.assertTrue(numpy.ma.isMaskedArray(block.as_numpy()))
self.assertTrue(block.as_numpy().fill_value == -999)

# test with noDataValue set and use_masking == False
self.assertTrue((block.as_numpy(use_masking=False) == expected_array).all())
Expand Down
11 changes: 5 additions & 6 deletions tests/src/python/test_qgsrasterlayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import os
from shutil import copyfile

import numpy
import numpy as np
from osgeo import gdal
from qgis.PyQt.QtCore import QFileInfo, QSize, QTemporaryDir
Expand Down Expand Up @@ -1488,20 +1489,20 @@ def test_read_xml_crash(self):
def test_as_numpy(self):
layer = QgsRasterLayer(self.rpath, "raster")
arrays = layer.as_numpy()
self.assertEqual(type(arrays[5]), np.ndarray)
self.assertTrue(numpy.ma.isMaskedArray(arrays[0]))
self.assertEqual(arrays.shape, (9, 200, 200))
self.assertEqual(arrays[0].dtype, np.int8)

# test with bands parameter
arrays = layer.as_numpy(bands=[1, 3])
self.assertEqual(type(arrays[0]), np.ndarray)
self.assertTrue(numpy.ma.isMaskedArray(arrays[0]))
self.assertEqual(arrays.shape, (2, 200, 200))
self.assertEqual(arrays[0].dtype, np.int8)

path = os.path.join(unitTestDataPath("raster"), "rgb_with_mask.tif")
layer = QgsRasterLayer(path, QFileInfo(path).baseName())
arrays = layer.as_numpy()
self.assertEqual(type(arrays[0]), np.ndarray)
self.assertTrue(numpy.ma.isMaskedArray(arrays[0]))
self.assertEqual(arrays.shape, (4, 150, 162))
self.assertEqual(arrays[0].dtype, np.int8)

Expand All @@ -1510,9 +1511,7 @@ def test_as_numpy(self):
)
layer = QgsRasterLayer(path, QFileInfo(path).baseName())
arrays = layer.as_numpy()
self.assertEqual(
type(arrays[0]), np.ndarray
) # All maskedArrays are converted to numpy.array
self.assertTrue(numpy.ma.isMaskedArray(arrays[0]))
self.assertEqual(arrays.shape, (1, 4, 4))
self.assertEqual(arrays[0].dtype, np.float64)

Expand Down

0 comments on commit 2bef21d

Please sign in to comment.