diff --git a/autotest/gdrivers/vrtprocesseddataset.py b/autotest/gdrivers/vrtprocesseddataset.py
index eea8907b0ced..5d2bf3eb8792 100755
--- a/autotest/gdrivers/vrtprocesseddataset.py
+++ b/autotest/gdrivers/vrtprocesseddataset.py
@@ -1198,6 +1198,102 @@ def test_vrtprocesseddataset_trimming_errors(tmp_vsimem):
)
+###############################################################################
+# Test expressions
+
+
+@pytest.mark.parametrize(
+ "expression,src,expected,error",
+ [
+ pytest.param(
+ "return [ALL_BANDS[1], ALL_BANDS[2]]",
+ np.array([[[1, 2]], [[3, 4]], [[5, 6]]]),
+ np.array([[[3, 4]], [[5, 6]]]),
+ None,
+ id="multiple bands in, multiple bands out (1)",
+ ),
+ pytest.param(
+ "return [ALL_BANDS]",
+ np.array([[[1, 2]], [[3, 4]], [[5, 6]]]),
+ np.array([[[1, 2]], [[3, 4]], [[5, 6]]]),
+ None,
+ id="multiple bands in, multiple bands out (2)",
+ ),
+ pytest.param(
+ "B1",
+ np.array([[[1, 2]], [[3, 4]], [[5, 6]]]),
+ np.array([[1, 2]]),
+ None,
+ id="multiple bands in, single band out (1)",
+ ),
+ pytest.param(
+ "ALL_BANDS[0]",
+ np.array([[[1, 2]], [[3, 4]], [[5, 6]]]),
+ np.array([[1, 2]]),
+ None,
+ id="multiple bands in, single band out (2)",
+ ),
+ pytest.param(
+ "return [B1];",
+ np.array([[[1, 2]], [[3, 4]], [[5, 6]]]),
+ np.array([[1, 2]]),
+ None,
+ id="multiple bands in, single band out (3)",
+ ),
+ pytest.param(
+ "return [B1, B2]",
+ np.array([[[1, 2]], [[3, 4]], [[5, 6]]]),
+ np.array([[1, 2]]),
+ "returned 2 values but 1 output band",
+ id="return wrong number of bands",
+ ),
+ pytest.param(
+ "return [ALL_BANDS, B2]",
+ np.array([[[1, 2]], [[3, 4]], [[5, 6]]]),
+ np.array([[1, 2]]),
+ "must return a vector or a list of scalars",
+ id="return wrong number of bands",
+ ),
+ ],
+)
+def test_vrtprocesseddataset_expression(tmp_vsimem, expression, src, expected, error):
+
+ src_filename = tmp_vsimem / "src.tif"
+ with gdal.GetDriverByName("GTiff").Create(src_filename, 2, 1, 3) as src_ds:
+ src_ds.WriteArray(src)
+ src_ds.SetGeoTransform([0, 1, 0, 0, 0, 1])
+
+ expected_output_bands = 1 if len(expected.shape) == 2 else expected.shape[0]
+
+ output_band_xml = "".join(
+ f""""""
+ for i in range(expected_output_bands)
+ )
+
+ ds = gdal.Open(
+ f"""
+
+ {src_filename}
+
+
+
+ Expression
+ {expression}
+
+
+ {output_band_xml}
+
+ """
+ )
+
+ if error:
+ with pytest.raises(Exception, match=error):
+ result = ds.ReadAsArray()
+ else:
+ result = ds.ReadAsArray()
+ np.testing.assert_equal(result, expected)
+
+
###############################################################################
# Test that serialization (for example due to statistics computation) properly
# works
diff --git a/frmts/vrt/vrtprocesseddataset.cpp b/frmts/vrt/vrtprocesseddataset.cpp
index b8cd27b76915..700665066012 100644
--- a/frmts/vrt/vrtprocesseddataset.cpp
+++ b/frmts/vrt/vrtprocesseddataset.cpp
@@ -436,9 +436,11 @@ CPLErr VRTProcessedDataset::Init(const CPLXMLNode *psTree,
if (nCurrentBandCount != nBands)
{
- CPLError(CE_Failure, CPLE_AppDefined,
- "Number of output bands of last step is not consistent with "
- "number of VRTProcessedRasterBand's");
+ CPLError(
+ CE_Failure, CPLE_AppDefined,
+ "Number of output bands of last step (%d) is not consistent with "
+ "number of VRTProcessedRasterBand's (%d)",
+ nCurrentBandCount, nBands);
return CE_Failure;
}
diff --git a/frmts/vrt/vrtprocesseddatasetfunctions.cpp b/frmts/vrt/vrtprocesseddatasetfunctions.cpp
index f6fb184fb1f6..6db4d0c03451 100644
--- a/frmts/vrt/vrtprocesseddatasetfunctions.cpp
+++ b/frmts/vrt/vrtprocesseddatasetfunctions.cpp
@@ -14,9 +14,13 @@
#include "cpl_string.h"
#include "vrtdataset.h"
+#include
+
#include
+#include
#include
#include