geowatch.utils.util_bands module

Collected information about satellite bands from https://github.com/stac-extensions/eo/

This should be mostly independent of the data source used. (eg Google Cloud vs element84 AWS, Collection-1 vs Collection-2) No guarantees that every value will be indentical, but close enough for heuristics.

Each constant is a list of ‘eo:bands’ Band Object dicts. They are sorted in the same order in which they appear if the bands come stacked in a single image, or in lexicographic order if the bands come in separate images.

Coverage of the catalogs is inconsistent. Where necessary, info has been filled in by hand.

Notes

Sentinal 2 Band Table

Band Resolution Central Wavelength Description B1 60 m 443 nm Ultra blue (Coastal and Aerosol) B2 10 m 490 nm Blue B3 10 m 560 nm Green B4 10 m 665 nm Red B5 20 m 705 nm Visible and Near Infrared (VNIR) B6 20 m 740 nm Visible and Near Infrared (VNIR) B7 20 m 783 nm Visible and Near Infrared (VNIR) B8 10 m 842 nm Visible and Near Infrared (VNIR) B8a 20 m 865 nm Visible and Near Infrared (VNIR) B9 60 m 940 nm Short Wave Infrared (SWIR) B10 60 m 1375 nm Short Wave Infrared (SWIR) B11 20 m 1610 nm Short Wave Infrared (SWIR) B12 20 m 2190 nm Short Wave Infrared (SWIR)

Landsat 8 Band Table

Band Resolution Central Wavelength Description 1 30 m 430 nm Coastal aerosol 2 30 m 450 nm Blue 3 30 m 530 nm Green 4 30 m 640 nm Red 5 30 m 850 nm Near Infrared (NIR) 6 30 m 1570 nm SWIR 1 7 30 m 2110 nm SWIR 2 8 15 m 500 nm Panchromatic 9 30 m 1360 nm Cirrus 10 100 m 10600 nm Thermal Infrared (TIRS) 1 11 100 m 11500 nm Thermal Infrared (TIRS) 2

Worldview 3 MUL Band Table

Band Resolution Central Wavelength Description 1 1.38 m 400 nm Coastal aerosol 2 1.38 m 450 nm Blue 3 1.38 m 510 nm Green 4 1.38 m 585 nm Yellow 5 1.38 m 630 nm Red 6 1.38 m 705 nm Red edge 7 1.38 m 770 nm Near-IR1 8 1.38 m 860 nm Near-IR2

Worldview 3 PAN Band Table

1 0.34 m 450-800 nm Panchromatic

References

https://gis.stackexchange.com/questions/290796/how-to-edit-the-metadata-for-individual-bands-of-a-multiband-raster-preferably https://gisgeography.com/sentinel-2-bands-combinations/ https://earth.esa.int/eogateway/missions/worldview-3 https://www.usgs.gov/faqs/what-are-band-designations-landsat-satellites?qt-news_science_products=0#qt-news_science_products .. [MODIS-SPEC] https://modis.gsfc.nasa.gov/about/specifications.php .. [SentinelHub] https://apps.sentinel-hub.com/eo-browser/?zoom=15&lat=42.87425&lng=-73.83164&themeId=DEFAULT-THEME&visualizationUrl=https%3A%2F%2Fservices.sentinel-hub.com%2Fogc%2Fwms%2Fbd86bcc0-f318-402b-a145-015f85b9427e&datasetId=S2L2A&fromTime=2022-02-16T00%3A00%3A00.000Z&toTime=2022-02-16T23%3A59%3A59.999Z&layerId=4-FALSE-COLOR-URBAN

geowatch.utils.util_bands.dicts_contain(d_list, dsub_list)[source]
geowatch.utils.util_bands.SENTINEL2 = [{'center_wavelength': 0.4439, 'common_name': 'coastal', 'full_width_half_max': 0.027, 'gsd': 60, 'name': 'B01'}, {'center_wavelength': 0.4966, 'common_name': 'blue', 'full_width_half_max': 0.098, 'gsd': 10, 'name': 'B02'}, {'center_wavelength': 0.56, 'common_name': 'green', 'full_width_half_max': 0.045, 'gsd': 10, 'name': 'B03'}, {'center_wavelength': 0.6645, 'common_name': 'red', 'full_width_half_max': 0.038, 'gsd': 10, 'name': 'B04'}, {'center_wavelength': 0.7039, 'full_width_half_max': 0.019, 'gsd': 20, 'name': 'B05'}, {'center_wavelength': 0.7402, 'full_width_half_max': 0.018, 'gsd': 20, 'name': 'B06'}, {'center_wavelength': 0.7825, 'full_width_half_max': 0.028, 'gsd': 20, 'name': 'B07'}, {'center_wavelength': 0.8351, 'common_name': 'nir', 'full_width_half_max': 0.145, 'gsd': 10, 'name': 'B08'}, {'center_wavelength': 0.8648, 'full_width_half_max': 0.033, 'gsd': 20, 'name': 'B8A'}, {'center_wavelength': 0.945, 'full_width_half_max': 0.026, 'gsd': 60, 'name': 'B09'}, {'center_wavelength': 1.3735, 'common_name': 'cirrus', 'full_width_half_max': 0.075, 'gsd': 60, 'name': 'B10'}, {'center_wavelength': 1.6137, 'common_name': 'swir16', 'full_width_half_max': 0.143, 'gsd': 20, 'name': 'B11'}, {'center_wavelength': 2.22024, 'common_name': 'swir22', 'full_width_half_max': 0.242, 'gsd': 20, 'name': 'B12'}]

This band info is taken from the sentinelhub AWS catalog. It will need to be updated to match RGD’s when that is STAC-compliant.

References

https://www.element84.com/earth-search/ https://docs.sentinel-hub.com/api/latest/data/landsat-8/ https://landsat.gsfc.nasa.gov/satellites/landsat-8/ https://planetarycomputer.microsoft.com/dataset/landsat-c2-l2

Example

>>> from pystac_client import Client
>>> # for Collection 1
>>> cat = Client.open('https://earth-search.aws.element84.com/v0')
>>> search = cat.search(bbox=[-110, 39.5, -105, 40.5], max_items=1, collections=['landsat-8-l1-c1'])
>>> # for Collection 2
>>> # cat = Client.open('https://earth-search.aws.element84.com/v1')
>>> # search = cat.search(bbox=[-110, 39.5, -105, 40.5], max_items=1, collections=['landsat-ot-l1'])
>>> i = list(search.items())[0]
>>> # one image for all bands
>>> bands = [v.to_dict()['eo:bands'][0] for k,v in i.assets.items() if k.startswith('B') and (k != 'BQA')]
>>>
>>> from geowatch.utils.util_bands import *
>>> assert dicts_contain(LANDSAT8, bands)
geowatch.utils.util_bands.LANDSAT8 = [{'alias': ['aerosol'], 'center_wavelength': 0.48, 'common_name': 'coastal', 'full_width_half_max': 0.02, 'gsd': 30, 'name': 'B1'}, {'center_wavelength': 0.44, 'common_name': 'blue', 'full_width_half_max': 0.06, 'gsd': 30, 'name': 'B2'}, {'center_wavelength': 0.56, 'common_name': 'green', 'full_width_half_max': 0.06, 'gsd': 30, 'name': 'B3'}, {'center_wavelength': 0.65, 'common_name': 'red', 'full_width_half_max': 0.04, 'gsd': 30, 'name': 'B4'}, {'alias': ['nir08'], 'center_wavelength': 0.86, 'common_name': 'nir', 'full_width_half_max': 0.03, 'gsd': 30, 'name': 'B5'}, {'center_wavelength': 1.6, 'common_name': 'swir16', 'full_width_half_max': 0.08, 'gsd': 30, 'name': 'B6'}, {'center_wavelength': 2.2, 'common_name': 'swir22', 'full_width_half_max': 0.2, 'gsd': 30, 'name': 'B7'}, {'center_wavelength': 0.59, 'common_name': 'pan', 'full_width_half_max': 0.18, 'gsd': 15, 'name': 'B8'}, {'center_wavelength': 1.37, 'common_name': 'cirrus', 'full_width_half_max': 0.02, 'gsd': 30, 'name': 'B9'}, {'alias': ['tir1'], 'center_wavelength': 10.9, 'common_name': 'lwir11', 'full_width_half_max': 0.8, 'gsd': 100, 'name': 'B10', 'notes': 'thermal-ir'}, {'alias': ['tir2'], 'center_wavelength': 12, 'common_name': 'lwir12', 'full_width_half_max': 1, 'gsd': 100, 'name': 'B11', 'notes': 'thermal-ir'}]

This band info is taken from the USGS Landsat catalog.

This is for Collection-2 Level-1; may be slightly different from Collection-1 Level-1 (RGD’s current source)

Example

>>> # not compatible with pystac_client for some reason
>>> import requests
>>> item = requests.get(('https://landsatlook.usgs.gov/sat-api/collections'
>>>                      '/landsat-c2l1/items/LE07_L1TP_026043_20210518_20210518_02_RT')).json()
>>> assets = item['assets']
>>> keys = sorted(k for k in assets.keys() if 'B' in k)
>>> bands = [assets[k]['eo:bands'][0] for k in keys]
>>>
>>> from geowatch.utils.util_bands import *
>>> assert dicts_contain(LANDSAT7, bands)
geowatch.utils.util_bands.LANDSAT7 = [{'center_wavelength': 0.49, 'common_name': 'blue', 'gsd': 30, 'name': 'B1'}, {'center_wavelength': 0.56, 'common_name': 'green', 'gsd': 30, 'name': 'B2'}, {'center_wavelength': 0.66, 'common_name': 'red', 'gsd': 30, 'name': 'B3'}, {'center_wavelength': 0.84, 'common_name': 'nir08', 'gsd': 30, 'name': 'B4'}, {'center_wavelength': 1.65, 'common_name': 'swir16', 'gsd': 30, 'name': 'B5'}, {'center_wavelength': 11.45, 'common_name': 'tir', 'gsd': 30, 'name': 'B6'}, {'center_wavelength': 11.45, 'common_name': 'tir', 'gsd': 30, 'name': 'B6'}, {'center_wavelength': 2.22, 'common_name': 'swir22', 'gsd': 30, 'name': 'B7'}, {'center_wavelength': 0.71, 'common_name': 'pan', 'gsd': 30, 'name': 'B8'}]

This band info is taken from the IARPA T&E STAC catalog.

Example

>>> # xdoctest: +SKIP
>>> # requires the api_key for this catalog
>>> from pystac_client import Client
>>> catalog = Client.open('https://api.smart-stac.com/', headers={"x-api-key": api_key})
>>> search = catalog.search(collections=['worldview-nitf'], bbox=[128.662489, 37.659517, 128.676673, 37.664560])
>>> items = list(search.items())
>>> props = [i.to_dict()['properties'] for i in items]
>>>
>>> wv01 = [p for p in props if p['mission'] == 'WV01']
>>> assert np.unique([p['instruments'] for p in wv01]) == ['panchromatic']
>>> wv01_pan = [p for p in wv01 if p['instruments'] == ['panchromatic']][0]['eo:bands']
>>>
>>> wv02 = [p for p in props if p['mission'] == 'WV02']
>>> assert np.all(np.unique([p['instruments'] for p in wv02]) == ['panchromatic', 'vis-multi'])
>>> assert np.all(np.unique([len(p['eo:bands']) for p in wv02]) == [1, 4, 8])
>>> wv02_pan = [p for p in wv02 if p['instruments'] == ['panchromatic']][0]['eo:bands']
>>> wv02_ms = [p['eo:bands'] for p in wv02 if p['instruments'] == ['vis-multi']]
>>> wv02_ms4 = [p for p in wv02_ms if len(p) == 4][0]
>>> wv02_ms8 = [p for p in wv02_ms if len(p) == 8][0]
>>>
>>> wv03 = [p for p in props if p['mission'] == 'WV03']
>>> assert np.all(np.unique([p['instruments'] for p in wv03]) == ['panchromatic', 'vis-multi'])
>>> assert np.all(np.unique([len(p['eo:bands']) for p in wv03]) == [1, 8])
>>> wv03_pan = [p for p in wv03 if p['instruments'] == ['panchromatic']][0]['eo:bands']
>>> wv03_ms = [p['eo:bands'] for p in wv03 if p['instruments'] == ['vis-multi']]
>>> wv03_ms8 = [p for p in wv03_ms if len(p) == 8][0]
>>>
>>> # not sure if this must be true, but it is
>>> assert wv02_pan == wv03_pan
>>> assert wv02_ms8 == wv03_ms8
>>>
>>> from geowatch.utils.util_bands import *
>>> assert dicts_contain(WORLDVIEW1_PAN, wv01_pan)
>>> assert dicts_contain(WORLDVIEW2_PAN, wv02_pan)
>>> assert dicts_contain(WORLDVIEW2_MS4, wv02_ms4)
>>> assert dicts_contain(WORLDVIEW2_MS8, wv02_ms8)
>>> assert dicts_contain(WORLDVIEW3_PAN, wv03_pan)
>>> assert dicts_contain(WORLDVIEW3_MS8, wv03_ms8)
geowatch.utils.util_bands.PLANETSCOPE_8BAND = [{'common_name': 'coastal', 'name': 'B1'}, {'common_name': 'blue', 'name': 'B2'}, {'common_name': 'green1', 'name': 'B3'}, {'common_name': 'green', 'name': 'B4'}, {'common_name': 'yellow', 'name': 'B5'}, {'common_name': 'red', 'name': 'B6'}, {'common_name': 'red-edge', 'name': 'B7'}, {'common_name': 'nir', 'name': 'B8'}]

TODO

fix wv doctest

geowatch.utils.util_bands.ALL_BANDS = [{'center_wavelength': 0.4439, 'common_name': 'coastal', 'full_width_half_max': 0.027, 'gsd': 60, 'name': 'B01'}, {'center_wavelength': 0.4966, 'common_name': 'blue', 'full_width_half_max': 0.098, 'gsd': 10, 'name': 'B02'}, {'center_wavelength': 0.56, 'common_name': 'green', 'full_width_half_max': 0.045, 'gsd': 10, 'name': 'B03'}, {'center_wavelength': 0.6645, 'common_name': 'red', 'full_width_half_max': 0.038, 'gsd': 10, 'name': 'B04'}, {'center_wavelength': 0.7039, 'full_width_half_max': 0.019, 'gsd': 20, 'name': 'B05'}, {'center_wavelength': 0.7402, 'full_width_half_max': 0.018, 'gsd': 20, 'name': 'B06'}, {'center_wavelength': 0.7825, 'full_width_half_max': 0.028, 'gsd': 20, 'name': 'B07'}, {'center_wavelength': 0.8351, 'common_name': 'nir', 'full_width_half_max': 0.145, 'gsd': 10, 'name': 'B08'}, {'center_wavelength': 0.8648, 'full_width_half_max': 0.033, 'gsd': 20, 'name': 'B8A'}, {'center_wavelength': 0.945, 'full_width_half_max': 0.026, 'gsd': 60, 'name': 'B09'}, {'center_wavelength': 1.3735, 'common_name': 'cirrus', 'full_width_half_max': 0.075, 'gsd': 60, 'name': 'B10'}, {'center_wavelength': 1.6137, 'common_name': 'swir16', 'full_width_half_max': 0.143, 'gsd': 20, 'name': 'B11'}, {'center_wavelength': 2.22024, 'common_name': 'swir22', 'full_width_half_max': 0.242, 'gsd': 20, 'name': 'B12'}, {'alias': ['aerosol'], 'center_wavelength': 0.48, 'common_name': 'coastal', 'full_width_half_max': 0.02, 'gsd': 30, 'name': 'B1'}, {'center_wavelength': 0.44, 'common_name': 'blue', 'full_width_half_max': 0.06, 'gsd': 30, 'name': 'B2'}, {'center_wavelength': 0.56, 'common_name': 'green', 'full_width_half_max': 0.06, 'gsd': 30, 'name': 'B3'}, {'center_wavelength': 0.65, 'common_name': 'red', 'full_width_half_max': 0.04, 'gsd': 30, 'name': 'B4'}, {'alias': ['nir08'], 'center_wavelength': 0.86, 'common_name': 'nir', 'full_width_half_max': 0.03, 'gsd': 30, 'name': 'B5'}, {'center_wavelength': 1.6, 'common_name': 'swir16', 'full_width_half_max': 0.08, 'gsd': 30, 'name': 'B6'}, {'center_wavelength': 2.2, 'common_name': 'swir22', 'full_width_half_max': 0.2, 'gsd': 30, 'name': 'B7'}, {'center_wavelength': 0.59, 'common_name': 'pan', 'full_width_half_max': 0.18, 'gsd': 15, 'name': 'B8'}, {'center_wavelength': 1.37, 'common_name': 'cirrus', 'full_width_half_max': 0.02, 'gsd': 30, 'name': 'B9'}, {'alias': ['tir1'], 'center_wavelength': 10.9, 'common_name': 'lwir11', 'full_width_half_max': 0.8, 'gsd': 100, 'name': 'B10', 'notes': 'thermal-ir'}, {'alias': ['tir2'], 'center_wavelength': 12, 'common_name': 'lwir12', 'full_width_half_max': 1, 'gsd': 100, 'name': 'B11', 'notes': 'thermal-ir'}, {'center_wavelength': 0.49, 'common_name': 'blue', 'gsd': 30, 'name': 'B1'}, {'center_wavelength': 0.56, 'common_name': 'green', 'gsd': 30, 'name': 'B2'}, {'center_wavelength': 0.66, 'common_name': 'red', 'gsd': 30, 'name': 'B3'}, {'center_wavelength': 0.84, 'common_name': 'nir08', 'gsd': 30, 'name': 'B4'}, {'center_wavelength': 1.65, 'common_name': 'swir16', 'gsd': 30, 'name': 'B5'}, {'center_wavelength': 11.45, 'common_name': 'tir', 'gsd': 30, 'name': 'B6'}, {'center_wavelength': 11.45, 'common_name': 'tir', 'gsd': 30, 'name': 'B6'}, {'center_wavelength': 2.22, 'common_name': 'swir22', 'gsd': 30, 'name': 'B7'}, {'center_wavelength': 0.71, 'common_name': 'pan', 'gsd': 30, 'name': 'B8'}, {'center_wavelength': 0.65, 'common_name': 'panchromatic', 'name': 'PAN'}, {'center_wavelength': 0.625, 'common_name': 'panchromatic', 'name': 'PAN'}, {'center_wavelength': 0.48, 'common_name': 'blue', 'name': 'B2'}, {'center_wavelength': 0.545, 'common_name': 'green', 'name': 'B3'}, {'center_wavelength': 0.66, 'common_name': 'red', 'name': 'B5'}, {'center_wavelength': 0.833, 'common_name': 'near-ir1', 'name': 'B7'}, {'center_wavelength': 0.425, 'common_name': 'coastal', 'name': 'B1'}, {'center_wavelength': 0.48, 'common_name': 'blue', 'name': 'B2'}, {'center_wavelength': 0.545, 'common_name': 'green', 'name': 'B3'}, {'center_wavelength': 0.605, 'common_name': 'yellow', 'name': 'B4'}, {'center_wavelength': 0.66, 'common_name': 'red', 'name': 'B5'}, {'center_wavelength': 0.725, 'common_name': 'red-edge', 'name': 'B6'}, {'center_wavelength': 0.833, 'common_name': 'near-ir1', 'name': 'B7'}, {'center_wavelength': 0.95, 'common_name': 'near-ir2', 'name': 'B8'}, {'center_wavelength': 0.625, 'common_name': 'panchromatic', 'name': 'PAN'}, {'center_wavelength': 0.425, 'common_name': 'coastal', 'name': 'B1'}, {'center_wavelength': 0.48, 'common_name': 'blue', 'name': 'B2'}, {'center_wavelength': 0.545, 'common_name': 'green', 'name': 'B3'}, {'center_wavelength': 0.605, 'common_name': 'yellow', 'name': 'B4'}, {'center_wavelength': 0.66, 'common_name': 'red', 'name': 'B5'}, {'center_wavelength': 0.725, 'common_name': 'red-edge', 'name': 'B6'}, {'center_wavelength': 0.833, 'common_name': 'near-ir1', 'name': 'B7'}, {'center_wavelength': 0.95, 'common_name': 'near-ir2', 'name': 'B8'}]

WIP Collect synonyms for allowed common_names values (not enforced by STAC) TODO do we even need to conform to this? Should we only collect “true” synonyms like {‘pan’: ‘panchromatic’} ?

Example

>>> from geowatch.utils.util_bands import *
>>> import itertools
>>> names = set(b.get('common_name', '') for b in ALL_BANDS)
>>> accounted_names = set(EO_COMMONNAMES.keys()).union(
>>>     set(itertools.chain.from_iterable(EO_COMMONNAMES.values())))
>>> todo = names.difference(accounted_names)
>>> # not sure what to do with these
>>> print(todo)
{'', 'tir'}

References

https://github.com/stac-extensions/eo/blob/main/json-schema/schema.json#L151

geowatch.utils.util_bands.EO_COMMONNAMES = {'blue': [], 'cirrus': [], 'coastal': [], 'green': [], 'lwir': [], 'lwir11': [], 'lwir12': [], 'nir': ['near-ir1', 'near-ir2'], 'nir08': [], 'nir09': [], 'pan': ['panchromatic'], 'red': [], 'rededge': ['red-edge'], 'swir16': [], 'swir22': [], 'yellow': []}

WIP Bands that are used to observe targets on the ground This is just a rough first pass

Example

>>> from geowatch.utils.util_bands import *
>>> assert GROUND.issubset(set(EO_COMMONNAMES.keys()))
geowatch.utils.util_bands.GROUND = {'blue', 'coastal', 'green', 'nir', 'nir08', 'nir09', 'pan', 'red', 'rededge', 'yellow'}

These band fields can be accessed as python objects as well using pystac

Example

>>> from pystac.extensions.eo import Band
>>> from geowatch.utils.util_bands import *
>>> for band in ALL_BANDS:
>>>     band.pop('gsd', None)  # pystac doesn't support this yet
>>>     b = Band.create(**band)
geowatch.utils.util_bands.specialized_index_bands(bands=None, coco_img=None, symbolic=False)[source]

Ported from code from by (Yongquan Zhao on 26 April 2017)

References

https://mail.google.com/mail/u/1/#chat/space/AAAAE5jpxTc

# Example: # >>> # xdoctest: +REQUIRES(module:sympy) # >>> from geowatch.utils.util_bands import * # NOQA # >>> symbolic = True # >>> indexes = specialized_index_bands(coco_img=None, symbolic=symbolic) # >>> import sympy as sym # >>> for key, index in indexes.items(): # >>> print(‘===============’) # >>> print(‘key = {!r}’.format(key)) # >>> print(’nOrig {}’.format(key)) # >>> print(index) # >>> print(’nSimplified {}’.format(key)) # >>> print(sym.simplify(index))

geowatch.utils.util_bands.specialized_index_bands2(delayed=None)[source]

Ported from code from by (Yongquan Zhao on 26 April 2017)

References

https://mail.google.com/mail/u/1/#chat/space/AAAAE5jpxTc