NumPy API¶
The Workflows Array
type mimics a NumPy ndarray, supporting vectorized operations, broadcasting, and advanced indexing with the same syntax as NumPy. Workflows also contains a workflows.numpy submodule with equivalent versions of over 175 NumPy routines.
You can access a Workflows Array
from Image.ndarray
and ImageCollection.ndarray
. You can also construct one from a local NumPy array or list, as long as it’s relatively small (< 10MiB when JSON-serialized as a list):
>>> import descarteslabs.workflows as wf
>>> import numpy as np
>>> np_arr = np.array([[1.1, 2.2, 4.4], [8.8, 9.9, 10.0]])
>>> wf.Array.from_numpy(np_arr)
<descarteslabs.workflows.types.array.array_.Array at 0x...>
>>> imgs = wf.ImageCollection.from_id("sentinel-2:L1C", start_datetime="2018-01-01", end_datetime="2018-03-01")
>>> imgs.ndarray
<descarteslabs.workflows.types.array.masked_array.MaskedArray at 0x...>
>>> arr = wf.Array([[1, 2, 4], [8, 9, 10]])
>>> arr
<descarteslabs.workflows.types.array.array_.Array at 0x...>
Slicing¶
Arrays support the most of the indexing syntax from NumPy:
>>> arr = wf.Array([[1, 2, 4],
... [8, 9, 10]])
>>> arr[0]
<descarteslabs.workflows.types.array.array_.Array at 0x...>
>>> arr[0].compute()
array([1, 2, 4])
>>> arr[0, 0]
<descarteslabs.workflows.types.primitives.number.Int at 0x...>
>>> arr[0, 0].compute()
1
>>> arr[:, [0, 2]]
<descarteslabs.workflows.types.array.array_.Array at 0x...>
>>> arr[:, [0, 2]].compute()
array([[ 1, 4],
[ 8, 10]])
>>> arr[np.newaxis, 0, 1].compute()
<descarteslabs.workflows.types.array.array_.Array at 0x...>
>>> arr[np.newaxis, 0, 1].compute()
array([2])
>>> arr[..., 0]
<descarteslabs.workflows.types.array.array_.Array at 0x...>
>>> arr[..., 0].compute()
array([1, 8])
>>> (arr > 3)
<descarteslabs.workflows.types.array.array_.Array at 0x...>
>>> (arr > 3).compute()
array([[False, False, True],
[ True, True, True]])
>>> arr[arr > 3]
<descarteslabs.workflows.types.array.array_.Array at 0x...>
>>> arr[arr > 3].compute()
array([ 4, 8, 9, 10])
Array
supports:
- Slicing by integers and slices:
x[0, :5]
- Slicing by lists/arrays of integers:
x[[1, 2, 4]]
- Slicing by lists/arrays of booleans:
x[[False, True, True, False, True]]
- Slicing one
Array
with anArray
of bools:x[x > 0]
- Slicing one
Array
with a zero or one-dimensionalArray
of ints:a[b]
The only unsupported indexing operations are:
- Lists or arrays in multiple axes (
x[[1, 2, 3], [3, 2, 1]]
) - Slicing with a multi-dimensional
Array
of ints
Operations and ufuncs¶
The workflows.numpy submodule contains equivalents of most of the NumPy ufuncs (elementwise numerical operators like np.add
, np.sqrt
, etc.), and many other routines, including parts of submodules like np.linalg
and np.ma
. You can use them on proxy types (Array
, Int
, Float
, Bool
) as well as NumPy arrays and Python scalars (int
, float
). They always return proxy types.
Additionally, you can use the actual NumPy version of any of these on a Workflows Array
; internally, NumPy will just dispatch to the Workflows version:
>>> import numpy as np
>>> arr = wf.Array.from_numpy(np.arange(4))
>>> wf.numpy.square(arr)
<descarteslabs.workflows.types.array.array_.Array at 0x7ff153cfead0>
>>> wf.numpy.square(arr).compute()
array([0, 1, 4, 9])
>>> np.square(arr) # still returns Workflows array, even though using the NumPy function
<descarteslabs.workflows.types.array.array_.Array at 0x7ff153cfead0>
>>> np.square(arr).compute()
array([0, 1, 4, 9])
Interacting with Imagery¶
Arrays and raster objects (Image
and ImageCollection
) are not directly interoperable (arr + img
won’t work, for example).
However, you can access the array from a raster object with the ndarray
field. And you can turn an Array
back into an Image
or ImageCollection
with Array.to_imagery
. Note that Array.to_imagery
always returns an ImageCollection
even if the Array
is only 3D. If you are expecting an Image
, you can index into the result like my_col[0]
:
>>> imgs = wf.ImageCollection.from_id("sentinel-2:L1C", start_datetime="2018-01-01", end_datetime="2018-03-01")
>>> rgb = imgs.pick_bands("red green blue")
>>> spectral_target = [0.2, 0.3, 0.4] # per-band spectral targets
>>> spectral_target_arr = wf.Array(spectral_target)
>>> delta = rgb.ndarray - spectral_target[None, :, None, None]
>>> # ^ must expand to 4 dimensions to align with the ImageCollection's 4D Array
>>> delta_std = delta.std(axis=0) # std deviation over axis 0, aka `axis="images"`
>>> delta_img = delta_std.to_imagery()[0] # no properties/bandinfo given
>>> delta_img.compute(wf.map.geocontext())
ImageResult:
* ndarray: MaskedArray<shape=(3, 135, 398), dtype=float64>
* properties:
* bandinfo: 'band_1', 'band_2', 'band_3'
* geocontext: 'geometry', 'resolution', 'crs', 'bounds', ...
Array.to_imagery
will create empty metadata for the raster object if you don’t pass any in, defaulting to empty properties and bands named band_1
through band_N
. But when appropriate, you can pass in specific metadata:
>>> delta_img_with_metadata = delta_std.to_imagery({"foo": "bar"}, {"red_d": {}, "green_d": {}, "blue_d": {}})[0]
>>> delta_img_with_metadata.compute(wf.map.geocontext())
ImageResult:
* ndarray: MaskedArray<shape=(3, 135, 398), dtype=float64>
* properties: 'foo'
* bandinfo: 'red_d', 'green_d', 'blue_d'
* geocontext: 'geometry', 'resolution', 'crs', 'bounds', ...