2

I would like to perform raster calculation with logical operators but I get this error. How can I fix this issue? In QGIS raster calculator this is the statement: ( "cloud_mask_prefire@1" > 0 OR "cloud_mask_postfire@1" > 0 OR ( ( "Band 3 (GREEN) pre-fire@1" - "Band 8 (NIR) pre-fire@1" ) / ( "Band 3 (GREEN) pre-fire@1" + "Band 8 (NIR) pre-fire@1" ) ) >= 0.0 )

water_cloud_mask = np.logical_or(cloud_mask_prefire_Data>0, cloud_mask_postfire_Data>0, ((B3_prefire_Data-B8_prefire_Data)/(B3_prefire_Data+B8_prefire_Data))>0.0)

Error:

C:\ProgramData\Anaconda3\lib\site-packages\ipykernel_launcher.py:3: RuntimeWarning: invalid value encountered in greater This is separate from the ipykernel package so we can avoid doing imports until

1 Answer 1

1

A few things to note here:

1) The warning (note that it is not an error) is most likely raised because you have either np.nan or np.inf values in one (or several) of your arrays. However, comparing arrays with invalid values will produce a result array despite of raising a warning. Take the following example:

import numpy as np
a = np.array([3, 1, 7, np.nan, 5])
b = np.array([0, 6, 2, 5, 4])
>>> a > b
__main__:1: RuntimeWarning: invalid value encountered in greater
array([True, False, True, False, True])  # returned an array (despite the warning) where np.nan > 5 is False

A quick and dirty solution is to just make numpy ignore this warning by using the numpy.seterr() function before doing the comparison:

np.seterr(invalid='ignore')

A better approach (in my opinion) is leveraging numpy masked arrays.

mask = np.isnan(a) | np.isinf(a)  # this will make sense later
a = np.ma.array(a, mask=mask)

>>> a
masked_array(data=[3.0, 1.0, 7.0, --, 5.0],  # note the masked value
             mask=[False, False, False,  True, False],
       fill_value=1e+20)

Calling a > b will keep raising the warning but you can use the numpy.ma functions, which will take care of this.

>>> test = np.ma.greater(a, b)
>>> test
masked_array(data=[True, False, True, --, True],
             mask=[False, False, False,  True, False],
       fill_value=True)

Finally, it is for you to decide what the masked values (only one in this example) will be. As you have a boolean array you can only choose between True and False.

>>> test.filled(False)
array([ True, False,  True, False,  True])
>>> test.filled(True)
array([ True, False,  True,  True,  True])

2) The np.logical_or() function takes two arrays as input to perform the element-wise comparison. However, you are passing another array as the third argument, where the third argument is:

out : ndarray, None, or tuple of ndarray and None, optional A location into which the result is stored. If provided, it must have a shape that the inputs broadcast to. If not provided or None, a freshly-allocated array is returned. A tuple (possible only as a keyword argument) must have length equal to the number of outputs.

This means the function is not doing what you are expecting it to do. You would have to nest another np.logical_or() function inside the first one if you want this to work. You could also refer to one of the possible solutions presented in this answer, or:

3) In your case, you can replace the np.logical_or() function with the bitwise OR operator (i.e. |). This might make your code more readable. Here is an illustrarion using our previous arrays:

>>> np.logical_or(b > 5, b < 2)
array([ True, True, False, False, False])
>>> (b > 5) | (b < 2)  # note the parentheses
array([ True, True, False, False, False])

One big advantage of this is that you can do as many OR operations as you want without having to nest multiple functions, which I believe is a big plus from a code readability perspective.

c = np.array([4, 5, 2, 9, 4])

>>> np.logical_or(c > 5, np.logical(b > 5, b < 2))
array([ True, True, False, True, False])
>>> (c > 5) | ((b > 5) | (b < 2))
array([ True, True, False, True, False])

Taking into account these things, your code could look something like:

# do this for your four arrays
mask = np.isnan(cloud_mask_prefire_Data) | np.isinf(cloud_mask_prefire_Data)
cloud_mask_prefire_Data = np.ma.array(cloud_mask_prefire_Data, mask=mask)

# ...
# ...

# do the comparisons (I wrote them separately but feel free to change that if you want)
comp1 = np.ma.greater(cloud_mask_prefire_Data, 0)
comp2 = np.ma.greater(cloud_mask_postfire_Data, 0)
comp3 = np.ma.greater((B3_prefire_Data - B8_prefire_Data) /  B3_prefire_Data + B8_prefire_Data), 0)

water_cloud_mask = comp1 | comp2 | comp3
water_cloud_mask = water_cloud_mask.filled(True)  # change to False if you consider it is more suitable

One nice thing about masked arrays is that they will also deal with divisions by zero, something that might happen in your case.

As I don't have access to your data, I could not test the code. It is possible that it needs some small adjustments. However, I hope this provides you with a starting point to migrate the QGIS statement to Python.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.