Filter_bands() VS band()

I’m trying (with python) to mask cloudy pixels from terrascope LAI product following the openEO cookbook.

No problem if I strictly follow the cookbook example use the .band() to select the classification layer and build a mask:

import openeo
# load data from openo
con  = openeo.connect("https://openeo.vito.be").authenticate_oidc(provider_id="egi")

# Load data cube from TERRASCOPE_S2_NDVI_V2 collection.
datacube = con.load_collection("TERRASCOPE_S2_LAI_V2",
                               spatial_extent={"west": 5.60, "south": 50.42, "east": 6.3, "north": 50.7},
                               temporal_extent=["2022-07-01", "2022-08-15"],
                               bands=["LAI_10M","SCENECLASSIFICATION_20M"])
# get classification band
SCL = datacube.band("SCENECLASSIFICATION_20M")
LAI = datacube.band("LAI_10M")
# masking
mask = ~ ((SCL == 4) | (SCL == 5))
LAI_masked = LAI.mask(mask)

But the .band() is not documented in the openEO hub and I tried to use .filterbands()… And I’m getting an error when using the mask I built with .filter_bands(), meaning that the two processes results in different outputs. Do you have an explanation?

# get classification band
SCL = datacube.filter_bands(bands = ["SCENECLASSIFICATION_20M"])
LAI = datacube.filter_bands(bands = ["LAI_10M"])

# masking
mask = ~ ((SCL == 4) | (SCL == 5))
#Error: OperatorException: Unsupported unary operator 'not' (band math mode=False)

band is a convenience function of the Python client and not a process implemented by the back-end. As such it is not listed on the openEO Hub. It also does a bit more than just filter_bands as it then allows the “easy” way to formulate band math in the client. The filter_bands just returns a new filtered data cube and as such, you can’t directly use it for math. What you could likely do is SCL = datacube.filter_bands(bands = "SCENECLASSIFICATION_20M"]).band("SCENECLASSIFICATION_20M")

Thanks!

I wandering how to guess that because both function are supposed to produce openeo.rest.datacube.DataCube objects?

In addition to what Matthias said, you could easily check how the first process graph looks like with:

import openeo
# load data from openo
con  = openeo.connect("https://openeo.vito.be").authenticate_oidc(provider_id="egi")

# Load data cube from TERRASCOPE_S2_NDVI_V2 collection.
datacube = con.load_collection("TERRASCOPE_S2_LAI_V2",
                               spatial_extent={"west": 5.60, "south": 50.42, "east": 6.3, "north": 50.7},
                               temporal_extent=["2022-07-01", "2022-08-15"],
                               bands=["LAI_10M","SCENECLASSIFICATION_20M"])
# get classification band
SCL = datacube.band("SCENECLASSIFICATION_20M")
LAI = datacube.band("LAI_10M")
# masking
mask = ~ ((SCL == 4) | (SCL == 5))
LAI_masked = LAI.mask(mask)
LAI_masked = LAI_masked.save_result(format='netCDF')
LAI_masked.create_job(title='LAI_masked_band')

which results in:

looking at the details, we can notice that the python client translates the lines:

SCL = datacube.band("SCENECLASSIFICATION_20M")
mask = ~ ((SCL == 4) | (SCL == 5))

into a reduce_dimension process, with the provided formula:

Finally, the difference with using filter_bands is in the dimensions of the output datacube:
.bands() translates into a reduce_dimension + array_element, so the output datacube won’t have the bands dimension any more (has been reduced). Instead, filter_bands takes just one or some bands and keeps the bands dimension.
I guess that the Python client complains since it doesn’t know which of the bands has to use for the
mask = ~ ((SCL == 4) | (SCL == 5)) , since filter_bands output can have multiple bands.

@stefaan.lippens what do you think?

1 Like

Great explanations! Thanks :smile:

Indeed, as noted above, the .bands() method is convenience functionality (currently a feature only available in the openeo Python client, called “band math”) to build a reduce_dimension operation along the “band” dimension, using standard math operators. E.g.

red = cube.band("red")
green = cube.band("green")
result = 2 * red - 5 * green

will translate to something like (pseudo code):

reduce_dimension(
    dimension="bands",
    reducer=subtract(multiply(array_element(cube, "red"), 2), multiply(array_element(cube, "green"))))
)

Obviously, the math notation version is easier to understand.

.filter_bands() however is a standard openEO process to filter out specific bands (one or more) from a cube. It does not allow the same “band math” functionality, because it does not imply a reduce_dimension along the band dimension.
However, on the output of .filter_bands(), just like a normal DataCube, you still can apply mathematical operators. E.g.

cube = cube.filter_bands(["red", "green"])
cube = 2.5 * cube

This does not translate to reduce_dimension, but a “pixel-wise” apply, (scaling all pixel values with factor 2.5 in this case).

I hope this makes some things clear