Imagery Broadcasting and Alignment Rules¶
In Workflows, numbers, Images
, and ImageCollections
are all interoperable, even though they contain different amounts of data. For example, adding a number to an Image
adds it to every pixel in the Image
. Like NumPy and other array computing libraries, Workflows imagery objects support vectorized operations and broadcasting: when using an operator like addition, lower-dimensional data is first “stretched” (broadcast) to match up with high-dimensional data, then the operator is applied element-wise (vectorized) between all pixels in the images.
Broadly, the following statements apply when combining imagery:
- Scalars broadcast to everything
- Images broadcast to ImageCollections
- 1-band imagery broadcasts to N-band imagery
- N-band imagery must have the same band names to interoperate
- ImageCollections must be the same length to interoperate
- Imagery operations are aligned by band name
Some common examples of combining imagery:
>>> imagecollection = wf.ImageCollection.from_id("sentinel-2:L1C")
>>> img = imagecollection[0]
>>>
>>> img + 1 # adds 1 to every pixel
>>> img + img # adds corresponding pixels (equivalent to `img * 2`)
>>> img + img.pick_bands("red") # adds corresponding pixel from the red band to every band
>>> img.pick_bands("red green") + img.pick_bands("red green") # adds red band to red band, and green band to green band
>>> img.pick_bands("red green") + img.pick_bands("green red") # same as above---bands are matched up by name
>>>
>>> imagecollection + 1 # adds 1 to every pixel in every Image
>>> imagecollection + imagecollection # adds corresp. pixels in corresp. Images (equivalent to `imagecollection * 2`)
>>> imagecollection + imagecollection.pick_bands("red") # adds corresp. pixel from the red band to every band, Image by Image
>>> imagecollection + img # adds `img` to every Image
>>> imagecollection + img.pick_bands("red") # adds `img`'s red band to every band in every Image
Image
and ImageCollection
objects have three attributes that may be broadcast: .ndarray
, .properties
and .bandinfo
(for more information about these attributes and how to access them, see the Proxy Objects section). The following sections cover in detail how Workflows handles combining and broadcasting these attributes.
Broadcasting ndarrays¶
For an Image
the .ndarray
property is a 3-dimensional NumPy array (with axes corresponding to: bands, y pixels, and x pixels). For an ImageCollection
it is 4-dimensional (with axes corresponding to: images, bands, y pixels, and x pixels). When combining ndarrays
, if broadcasting is necessary, it is done according to the NumPy broadcasting guide.
Incorrect ndarray broadcasting¶
Combining imagery objects with different shapes is an invalid operation. For example, you cannot combine two ImageCollections
with different numbers of bands (eg. the .ndarray
shapes (3, 4, 512, 512)
and (3, 2, 512, 512)
cannot be broadcasted together).
You do not need to worry about having different shapes in the x pixels and y pixels dimensions. Since every raster is loaded with the same geocontext, those dimensions will always be the same.
Broadcasting properties¶
The .properties
attribute of an Image
is a dictionary; for an ImageCollection
it is a list of dictionaries. Combining properties is done by taking the intersection of dictionaries. In the following example, the .properties
dictionary of my_image
is intersected with every property dictionary of my_imagecollection
(ie. my_image.properties
is broadcasted to the length of my_imagecollection.properties
). The resulting list of properties contains only keys where every dictionary combination had the same value.
>>> my_image.properties.inspect(ctx)
{"color": "red", "size": "large"}
>>> my_imagecollection.properties.inspect(ctx)
[{"color": "red", "size":"small"}, {"color":"blue", "size": "large"}]
>>> result = my_image + my_imagecollection
>>> result.properties.inspect(ctx)
[{"color": "red"}, {"size": "large"}]
Broadcasting bandinfo¶
For both Image
and ImageCollection
, the .bandinfo
attribute is a dictionary. Bandinfos can only be combined if the imagery objects have the same number of bands with the same names (with one exception, see below). If two imagery objects have the same bands but in different orders, when they are combined, the order of the first operand is perserved. The actual combining of bandinfo is performed in the same way as when combining properties: the intersection is taken of the two band’s dictionaries. In the following example the .bandinfo
of my_image
is broadcasted to the .bandinfo
of my_imagecollection
and combined accordingly.
>>> my_image.bandinfo.inspect(ctx)
{'red': {'color':'Red', 'product': 'sentinel-2:L1C'}}
>>> my_imagecollection.bandinfo.inspect(ctx)
{'red': {'color':'Red', 'product': 'sentinel-2:L1C'}, 'blue': {'color':'Blue', 'product': 'sentinel-2:L1C'}}
>>> result = my_image + my_imagecollection
>>> result.bandinfo.inspect(ctx)
{'red': {'product': 'sentinel-2:L1C'}, 'blue': {'product': 'sentinel-2:L1C'}}
The one exception to the above combination rules happens when two objects each have a single band. In this case, the objects can be combined regardless of band name. The resulting imagery object’s band name will be of the format <first_bandname>_<op_name>_<second_bandname>
where op_name
is the name of the operation performed to combine the two bands.
>>> first = img.pick_bands("red")
>>> second = img.pick_bands("blue")
>>> result = first + second
>>> result.bandinfo.inspect(ctx)
{'red_add_blue': {...}}