GeoTensor numpy API¶
- Author: Gonzalo Mateo-García
This tutorial shows how to operate with the numpy API with GeoTensor objects. The GeoTensor class is a subclass of np.ndarray that stores the spatial affine transform, the coordinate reference system and no data value (called fill_value_default). When a GeoTensor is sliced, its transform attribute is shifted accordingly. Additionally its transform is also shifted if the GeoTensor is padded. GeoTensors are restricted to be 2D, 3D or 4D arrays and their two last dimensions are assumed to be the y and x spatial axis.
As a subclass of np.ndarray, operations with GeoTensor objects work similar than operations with np.ndarrays. However, there are some restrictions that we have implemented to keep consistency with the GeoTensor concept. If you need to use the numpy implementation you can access the bare numpy object with the .values attribute. Below there's a list with restrictions on numpy operations:
- Slicing a
GeoTensoris more restrictive than anumpyarray. It only allows to slice withlists, numbers orslices. In particular the spatial dimensions can only be sliced withslices. Slicing for inplace modification is not restricted (i.e. you can slice with boolean arrays to modify certain values of the object). - Binary operations (such as add, multiply etc) check for
GeoTensorinputs if they have thesame_extent; that is, sametransformcrsand spatial dimensions (widthandheight). squeeze,expand_dimsandtransposemake sure spatial dimensions (last two axes) are not modified and kept at the end of the array.concatenateandstackmake sure all operatedGeoTensors havesame_extentandshape.concatenatedoes not allow to concatenate on the spatial dims.- Reductions (such as
np.meanornp.all) returnGeoTensorobject if the spatial dimensions are preserved andnp.ndarrayor scalars otherwise.
Load some sample data¶
from georeader import plot
from georeader.geotensor import GeoTensor
from georeader import save
import os
import numpy as np
s2url = "https://huggingface.co/datasets/isp-uv-es/WorldFloodsv2/resolve/main/test/S2/EMSR264_18MIANDRIVAZODETAIL_DEL_v2.tif"
filelocal = os.path.basename(s2url)
if not os.path.exists(filelocal):
s2img = GeoTensor.load_file(s2url)
save.save_tiled_geotiff(s2img, filelocal)
else:
s2img = GeoTensor.load_file(filelocal)
# Set nodata value to zero
s2img.fill_value_default = 0
s2img
Padding and slicing as with numpy arrays¶
transform_before = s2img.transform
s2img = s2img.pad(((0,0), (25, 25), (10, 10)))
s2img
Padding changes transform
assert s2img.transform != transform_before, "adding changes transform"
s2img.transform, transform_before
rgb = (s2img[[3,2,1]] / 3_500).clip(0,1)
plot.show(rgb)
validmask and invalidmask methods¶
invalids = np.all(s2img.invalidmask(),axis=0)
assert isinstance(invalids, GeoTensor), "invalids is of class GeoTensor!"
plot.plot_segmentation_mask(invalids, interpretation_array=["valid", "invalid"],
color_array=["C0","C1"])
Slicing propagates the affine transform¶
rgb_slice = rgb[:, 20:300, 50:340]
rgb_slice
assert rgb_slice.transform != rgb.transform, f"Different transform"
rgb_slice.transform, rgb.transform
Slicing with step¶
rgb_slice_2 = rgb[:, slice(20,300,2), slice(50,340)]
rgb_slice_2
plot.show(rgb_slice_2)
Reversing¶
rgb_slice_rev = rgb[:, slice(300,20,-1), slice(50,340)]
rgb_slice_rev
plot.show(rgb_slice_rev)
np.mean and other reduction ops return GeoTensor¶
When reduction axis are not the spatial axis, the returned object is a GeoTensor
import numpy as np
greyimg = np.mean(rgb, axis=0)
greyimg
plot.show(greyimg, add_colorbar_next_to=True)
When reducing over the spatial axis, return object is np.ndarray:
average_reflectance_band = np.mean(rgb, axis=(-2,-1))
average_reflectance_band
vectorized ufuncs¶
greyimg_sin = np.sin(greyimg)
greyimg_sin
Ops of GeoTensor with numbers and np.ndarray¶
clouds = greyimg >= .9
plot.plot_segmentation_mask(clouds, interpretation_array=["clear","cloud"],
color_array=["C0", "white"])
cloud_percentage = np.mean(clouds)
cloud_percentage
Example: normalize by bands¶
import matplotlib.pyplot as plt
average_reflectance_band = np.mean(rgb, axis=(-2,-1), keepdims=True)
std_reflectance = np.mean(rgb, axis=(-2,-1), keepdims=True)
rgb_norm = (rgb - average_reflectance_band) / std_reflectance
fig, ax = plt.subplots(1, 3, figsize=(15, 5),sharey=True, tight_layout=True)
for i in range(3):
plot.show(rgb_norm[i], add_colorbar_next_to=True,ax=ax[i], title=f"B{i+2}")
Computing RS indexes¶
b11 = s2img[11].astype(np.float64)
b3 = s2img[2].astype(np.float64)
mndwi = (b11 - b3) / (b11 + b3)
plot.show(mndwi, add_colorbar_next_to=True)
water = mndwi < 0
land_water_clouds = GeoTensor(np.ones(clouds.shape, dtype=np.uint8),
fill_value_default=0,
crs=clouds.crs,
transform=clouds.transform)
land_water_clouds[water] = 2
land_water_clouds[clouds] = 3
land_water_clouds[invalids] = 0
plot.plot_segmentation_mask(land_water_clouds,
interpretation_array=["invalids","clear","water","cloud"],
color_array=["#000000","#c66d43","#437cc6","#eeeeee"])
.values attribute returns a view of the object as np.ndarray¶
clouds.values
Apply function that expects np.ndarray objects¶
We can pass a GeoTensor to any function that expects np.ndarray. Depending on the function the return type may be a GeoTensor or a np.ndarray. If the return type is np.ndarray we can wrap the array with array_as_geotensor method to convert back to GeoTensor object with the same spatial info.
from scipy import ndimage as ndi
greyimg_smoothed = ndi.gaussian_filter(greyimg, sigma=5, cval=0, mode="reflect")
greyimg_smoothed
greyimg_smoothed = rgb.array_as_geotensor(greyimg_smoothed)
plot.show(greyimg_smoothed, add_colorbar_next_to=True)
Licence¶
The georeader package is published under a GNU Lesser GPL v3 licence
georeader tutorials and notebooks are released under a Creative Commons non-commercial licence.
If you find this work useful please cite:
@article{portales-julia_global_2023,
title = {Global flood extent segmentation in optical satellite images},
volume = {13},
issn = {2045-2322},
doi = {10.1038/s41598-023-47595-7},
number = {1},
urldate = {2023-11-30},
journal = {Scientific Reports},
author = {Portalés-Julià, Enrique and Mateo-García, Gonzalo and Purcell, Cormac and Gómez-Chova, Luis},
month = nov,
year = {2023},
pages = {20316},
}