Apply Operator with Pangeo#
The apply operator employ a process on the datacube that calculates new pixel values for each pixel, based on n other pixels.
Let’s start again with the same sample data from the Sentinel-2 STAC Collection, applying the filters directly in the stackstac.stack
call, to reduce the amount of data.
# STAC Catalogue Libraries
import pystac_client
import stackstac
# West, South, East, North
spatial_extent = [11.259613, 46.461019, 11.406212, 46.522237]
temporal_extent = ['2022-07-10T00:00:00Z','2022-07-13T00:00:00Z']
bands = ["red","green","blue"]
URL = "https://earth-search.aws.element84.com/v1"
catalog = pystac_client.Client.open(URL)
s2_items = catalog.search(
bbox=spatial_extent,
datetime=temporal_extent,
query=["eo:cloud_cover<50"],
collections=["sentinel-2-l2a"]
).item_collection()
s2_cube = stackstac.stack(s2_items,
bounds_latlon=spatial_extent,
assets=bands
)
s2_cube
/srv/conda/envs/notebook/lib/python3.11/site-packages/stackstac/prepare.py:408: UserWarning: The argument 'infer_datetime_format' is deprecated and will be removed in a future version. A strict version of it is now the default, see https://pandas.pydata.org/pdeps/0004-consistent-to-datetime-parsing.html. You can safely remove this argument.
times = pd.to_datetime(
<xarray.DataArray 'stackstac-0747b87a1b91037120a189227e8f5908' (time: 1, band: 3, y: 714, x: 1146)> dask.array<fetch_raster_window, shape=(1, 3, 714, 1146), dtype=float64, chunksize=(1, 1, 714, 1024), chunktype=numpy.ndarray> Coordinates: (12/54) * time (time) datetime64[ns] 2022-07-12... id (time) <U24 'S2B_32TPS_20220712_... * band (band) <U5 'red' 'green' 'blue' * x (x) float64 6.733e+05 ... 6.848e+05 * y (y) float64 5.155e+06 ... 5.148e+06 view:sun_azimuth float64 146.9 ... ... proj:shape object {10980} gsd int64 10 common_name (band) <U5 'red' 'green' 'blue' center_wavelength (band) float64 0.665 0.56 0.49 full_width_half_max (band) float64 0.038 0.045 0.098 epsg int64 32632 Attributes: spec: RasterSpec(epsg=32632, bounds=(673310.0, 5147750.0, 684770.0... crs: epsg:32632 transform: | 10.00, 0.00, 673310.00|\n| 0.00,-10.00, 5154890.00|\n| 0.0... resolution: 10.0
Visualize the RGB bands of our sample dataset:
s2_cube.isel(time=0).plot.imshow()
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
<matplotlib.image.AxesImage at 0x7ff48857fd90>

Apply an arithmetic formula#
We would like to improve the previous visualization, rescaling all the pixels between 0 and 1.
We can use apply
in combination with other math
processes.
s2_cube
<xarray.DataArray 'stackstac-0747b87a1b91037120a189227e8f5908' (time: 1, band: 3, y: 714, x: 1146)> dask.array<fetch_raster_window, shape=(1, 3, 714, 1146), dtype=float64, chunksize=(1, 1, 714, 1024), chunktype=numpy.ndarray> Coordinates: (12/54) * time (time) datetime64[ns] 2022-07-12... id (time) <U24 'S2B_32TPS_20220712_... * band (band) <U5 'red' 'green' 'blue' * x (x) float64 6.733e+05 ... 6.848e+05 * y (y) float64 5.155e+06 ... 5.148e+06 view:sun_azimuth float64 146.9 ... ... proj:shape object {10980} gsd int64 10 common_name (band) <U5 'red' 'green' 'blue' center_wavelength (band) float64 0.665 0.56 0.49 full_width_half_max (band) float64 0.038 0.045 0.098 epsg int64 32632 Attributes: spec: RasterSpec(epsg=32632, bounds=(673310.0, 5147750.0, 684770.0... crs: epsg:32632 transform: | 10.00, 0.00, 673310.00|\n| 0.00,-10.00, 5154890.00|\n| 0.0... resolution: 10.0
import xarray as xr
input_min = -0.1
input_max = 0.2
output_min = 0
output_max = 1
def rescale(x, input_min, input_max, output_min, output_max):
y = xr.where(x>=input_min, x, input_min)
y = xr.where(y<=input_max, y, input_max)
return ((y - input_min) / (input_max - input_min)) * (output_max - output_min) + output_min
scaled_data = rescale(s2_cube, input_min, input_max, output_min, output_max)
input_min = -0.1
input_max = 0.2
output_min = 0
output_max = 1
def rescale(x, input_min, input_max, output_min, output_max):
return ((x.where(x>=input_min, input_min).where(x<=input_max, input_max) - input_min) / (input_max - input_min)) * (output_max - output_min) + output_min
scaled_data = rescale(s2_cube, input_min, input_max, output_min, output_max)
scaled_data.load()
<xarray.DataArray 'stackstac-0747b87a1b91037120a189227e8f5908' (time: 1, band: 3, y: 714, x: 1146)> array([[[[0.218 , 0.20266667, 0.20733333, ..., 0.138 , 0.095 , 0.11733333], [0.252 , 0.24733333, 0.21933333, ..., 0.08866667, 0.07866667, 0.07566667], [0.206 , 0.16066667, 0.19733333, ..., 0.07 , 0.08 , 0.08066667], ..., [0.23933333, 0.29166667, 0.12533333, ..., 0.072 , 0.07833333, 0.072 ], [0.196 , 0.30666667, 0.16666667, ..., 0.07733333, 0.069 , 0.05133333], [0.178 , 0.28933333, 0.22066667, ..., 0.06766667, 0.05966667, 0.058 ]], [[0.21566667, 0.19466667, 0.21133333, ..., 0.152 , 0.14466667, 0.15633333], [0.226 , 0.21133333, 0.23 , ..., 0.13233333, 0.137 , 0.12533333], [0.21466667, 0.17 , 0.184 , ..., 0.13266667, 0.13966667, 0.121 ], ... [0.296 , 0.29533333, 0.15933333, ..., 0.146 , 0.16166667, 0.13133333], [0.24 , 0.326 , 0.19666667, ..., 0.15633333, 0.14 , 0.117 ], [0.24133333, 0.33133333, 0.21666667, ..., 0.14866667, 0.139 , 0.13066667]], [[0.162 , 0.13633333, 0.15033333, ..., 0.08933333, 0.08566667, 0.08533333], [0.16166667, 0.171 , 0.18 , ..., 0.06366667, 0.06866667, 0.07333333], [0.14033333, 0.117 , 0.14733333, ..., 0.076 , 0.08 , 0.074 ], ..., [0.19666667, 0.24166667, 0.10166667, ..., 0.073 , 0.08366667, 0.06866667], [0.12966667, 0.26733333, 0.11733333, ..., 0.07866667, 0.08233333, 0.07333333], [0.2 , 0.21466667, 0.183 , ..., 0.08666667, 0.07966667, 0.07533333]]]]) Coordinates: (12/54) * time (time) datetime64[ns] 2022-07-12... id (time) <U24 'S2B_32TPS_20220712_... * band (band) <U5 'red' 'green' 'blue' * x (x) float64 6.733e+05 ... 6.848e+05 * y (y) float64 5.155e+06 ... 5.148e+06 view:sun_azimuth float64 146.9 ... ... proj:shape object {10980} gsd int64 10 common_name (band) <U5 'red' 'green' 'blue' center_wavelength (band) float64 0.665 0.56 0.49 full_width_half_max (band) float64 0.038 0.045 0.098 epsg int64 32632
Visualize the result and see how apply
scaled the data resulting in a more meaningful visualization:
scaled_data.isel(time=0).plot.imshow()
<matplotlib.image.AxesImage at 0x7ff48857f910>
