Skip to content

Commit 8704959

Browse files
committed
Python bindings: Add Dataset.ReadAsMaskedArray
1 parent 6a68918 commit 8704959

File tree

2 files changed

+124
-2
lines changed

2 files changed

+124
-2
lines changed

autotest/gcore/numpy_rw.py

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -983,13 +983,15 @@ def test_numpy_rw_band_read_as_array_getlasterrormsg():
983983
def test_numpy_rw_masked_array_1():
984984

985985
ds = gdal.Open("data/byte.tif")
986-
987986
band = ds.GetRasterBand(1)
988987

989988
masked_arr = band.ReadAsMaskedArray()
990-
991989
assert not numpy.any(masked_arr.mask)
992990

991+
ds_masked_arr = ds.ReadAsMaskedArray()
992+
assert not numpy.any(ds_masked_arr.mask)
993+
assert numpy.all(masked_arr == ds_masked_arr)
994+
993995

994996
def test_numpy_rw_masked_array_2():
995997

@@ -1007,6 +1009,90 @@ def test_numpy_rw_masked_array_2():
10071009

10081010
assert masked_arr.sum() == arr[mask == 255].sum()
10091011

1012+
ds_masked_arr = ds.ReadAsMaskedArray()
1013+
assert numpy.all(ds_masked_arr.mask == masked_arr.mask)
1014+
assert numpy.all(ds_masked_arr.data == masked_arr.data)
1015+
1016+
1017+
###############################################################################
1018+
# Test dataset read into a masked array
1019+
1020+
1021+
def test_numpy_rw_masked_array_3():
1022+
1023+
nx = 6
1024+
ny = 4
1025+
nz = 3
1026+
nodata = -999
1027+
1028+
ds = gdal.GetDriverByName("MEM").Create("", nx, ny, nz, eType=gdal.GDT_Int16)
1029+
for i in range(nz):
1030+
ds.GetRasterBand(i + 1).SetNoDataValue(nodata)
1031+
1032+
data = numpy.arange(nx * ny * nz).reshape(nz, ny, nx).astype(numpy.int16)
1033+
data[:, 1, 1] = nodata
1034+
ds.WriteArray(data)
1035+
1036+
masked_data = numpy.ma.masked_array(data, mask=data == nodata)
1037+
1038+
assert numpy.ma.allequal(masked_data, ds.ReadAsMaskedArray())
1039+
1040+
# read a single band
1041+
assert numpy.ma.allequal(masked_data[1, :, :], ds.ReadAsMaskedArray(band_list=[2]))
1042+
1043+
# read multiple bands
1044+
assert numpy.ma.allequal(
1045+
numpy.ma.stack((masked_data[2, :, :], masked_data[0, :, :])),
1046+
ds.ReadAsMaskedArray(band_list=[3, 1]),
1047+
)
1048+
1049+
# read a sub-window (square)
1050+
assert numpy.ma.allequal(
1051+
numpy.ma.stack((masked_data[2, 0:2, 1:3], masked_data[0, 0:2, 1:3])),
1052+
ds.ReadAsMaskedArray(band_list=[3, 1], xsize=2, ysize=2, xoff=1, yoff=0),
1053+
)
1054+
1055+
# read a sub-window (single pixel)
1056+
assert numpy.ma.allequal(
1057+
numpy.ma.stack((masked_data[2, 1:2, 1:2], masked_data[0, 1:2, 1:2])),
1058+
ds.ReadAsMaskedArray(band_list=[3, 1], xsize=1, ysize=1, xoff=1, yoff=1),
1059+
)
1060+
1061+
# read a sub-window (read 2x2 square into 4x4 square)
1062+
assert numpy.ma.allequal(
1063+
numpy.ma.repeat(
1064+
numpy.ma.repeat(
1065+
numpy.ma.stack((masked_data[2, 0:2, 1:3], masked_data[0, 0:2, 1:3])),
1066+
2,
1067+
axis=2,
1068+
),
1069+
2,
1070+
axis=1,
1071+
),
1072+
ds.ReadAsMaskedArray(
1073+
band_list=[3, 1], xsize=2, ysize=2, xoff=1, yoff=0, buf_xsize=4, buf_ysize=4
1074+
),
1075+
)
1076+
1077+
# read a sub-window (read 2x2 square into single pixel)
1078+
assert numpy.ma.allequal(
1079+
numpy.ma.mean(
1080+
numpy.ma.stack((masked_data[2, 0:2, 1:3], masked_data[0, 0:2, 1:3])),
1081+
axis=(1, 2),
1082+
keepdims=True,
1083+
).round(),
1084+
ds.ReadAsMaskedArray(
1085+
band_list=[3, 1],
1086+
xsize=2,
1087+
ysize=2,
1088+
xoff=1,
1089+
yoff=0,
1090+
buf_xsize=1,
1091+
buf_ysize=1,
1092+
resample_alg=gdal.GRIORA_Average,
1093+
),
1094+
)
1095+
10101096

10111097
###############################################################################
10121098
# Test type code mapping

swig/include/python/gdal_python.i

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,42 @@ CPLErr ReadRaster1( double xoff, double yoff, double xsize, double ysize,
11101110

11111111
%pythoncode %{
11121112

1113+
def ReadAsMaskedArray(self, xoff=0, yoff=0, xsize=None, ysize=None,
1114+
buf_xsize=None, buf_ysize=None, buf_type=None,
1115+
resample_alg=gdalconst.GRIORA_NearestNeighbour,
1116+
callback=None,
1117+
callback_data=None,
1118+
band_list=None):
1119+
"""
1120+
Read a window from raster bands into a NumPy masked array.
1121+
1122+
Parameters are the same as for :py:meth:`ReadAsArray`.
1123+
"""
1124+
1125+
import numpy as np
1126+
1127+
arr = self.ReadAsArray(xoff=xoff, yoff=yoff, xsize=xsize, ysize=ysize,
1128+
buf_xsize=buf_xsize, buf_ysize=buf_ysize, buf_type=buf_type,
1129+
resample_alg=resample_alg, band_list=band_list)
1130+
1131+
if band_list is None:
1132+
band_list = [i+1 for i in range(self.RasterCount)]
1133+
1134+
all_valid = all(self.GetRasterBand(band).GetMaskFlags() == GMF_ALL_VALID for band in band_list)
1135+
1136+
if all_valid:
1137+
return np.ma.masked_array(arr, False)
1138+
1139+
masks = [self.GetRasterBand(band).GetMaskBand().ReadAsArray(
1140+
xoff=xoff, yoff=yoff,
1141+
win_xsize=xsize, win_ysize=ysize,
1142+
buf_xsize=buf_xsize, buf_ysize=buf_ysize,
1143+
resample_alg=gdalconst.GRIORA_Mode) != 255
1144+
for band in band_list]
1145+
1146+
return np.ma.masked_array(arr, np.vstack(masks))
1147+
1148+
11131149
def ReadAsArray(self, xoff=0, yoff=0, xsize=None, ysize=None, buf_obj=None,
11141150
buf_xsize=None, buf_ysize=None, buf_type=None,
11151151
resample_alg=gdalconst.GRIORA_NearestNeighbour,

0 commit comments

Comments
 (0)