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': {...}}