This tutorial demonstrates how to leverage the Application for Extracting and Exploring Analysis Ready Samples (AppEEARS) Application Programming Interface (API) quality service to decode the quality data associated with NASA's Land Processes Distributed Active Archive Center (LP DAAC) products. Presented as a Jupyter Notebook, users can follow the step-by-step instructions to learn how to access and use the quality web service for decoding Moderate Resolution Imaging Spectroradiometer (MODIS) data products from the LP DAAC data archive.
Background
Quality information for LP DAAC data products is important to consider when determining the usability and usefulness of a data product for a particular science application. However, this information has been notoriously difficult for users to access. Quality information is often stored as an integer value that requires the user to decode into binary strings. In order to interpret the binary string, users must map the unique combinations of bits found in separate subsets of the binary string (i.e. bit-fields) to quality tables that characterize the particular quality attribute associated with each bit-field. This process is difficult for most users and is often passed over.
NASA's LP DAAC has added a quality web service to its AppEEARS service API to alleviate the difficult process of decoding and interpreting quality layers for tiled MODIS and Web-Enabled Landsat Data (WELD) products. The quality web service will decode an integer value for a data product/layer and return the quality attribute information associated with that particular integer value. Additionally, users can determine if a quality layer exists for a particular data product/layer as well as get details about individual quality layers.
Get Started
%autosave 0
Autosave disabled
Let's start by importing the required packages.
import requests
import pandas as pd
Next, assign the AppEEARS services API URL to a variable
SERVICES_URL = 'https://lpdaacsvc.cr.usgs.gov/services/appeears-api/'
Note: The AppEEARS services API page contains all of the AppEEARS services accompanied by their documentation.
Get All of the Data Layers that Contain Associated Quality Layers
To know what layers have a quality layer associated with them, users can get a print out of the number of layers with associated quality layers.
qualityLayers = requests.get('{}/quality?format=json'.format(SERVICES_URL)).json()
#print(qualityLayers)
'Quality information was found for {0} data layers!'.format(len(qualityLayers))
'Quality information was found for 1227 data layers!'
The request returns a list of dictionaries that can be indexed to filter the information returned.
qualityLayers[218:222]
[{'Layer': 'Nadir_Reflectance_Band2',
'ProductAndVersion': 'MCD43A4.006',
'QualityLayers': ['BRDF_Albedo_Band_Mandatory_Quality_Band2'],
'QualityProductAndVersion': 'MCD43A4.006'},
{'Layer': 'Nadir_Reflectance_Band3',
'ProductAndVersion': 'MCD43A4.006',
'QualityLayers': ['BRDF_Albedo_Band_Mandatory_Quality_Band3'],
'QualityProductAndVersion': 'MCD43A4.006'},
{'Layer': 'Nadir_Reflectance_Band4',
'ProductAndVersion': 'MCD43A4.006',
'QualityLayers': ['BRDF_Albedo_Band_Mandatory_Quality_Band4'],
'QualityProductAndVersion': 'MCD43A4.006'},
{'Layer': 'Nadir_Reflectance_Band5',
'ProductAndVersion': 'MCD43A4.006',
'QualityLayers': ['BRDF_Albedo_Band_Mandatory_Quality_Band5'],
'QualityProductAndVersion': 'MCD43A4.006'}]
The list of products contained in qualityLayers is long, and trying to find the product/layer of interest via indexing can be inefficient. Instead, let's use a python expression, known as a list comprehension, to get all of the layers in MYD13A2.006 that have quality layers associated with them.
productQuality_A = [q for q in qualityLayers if q['ProductAndVersion'] == 'MYD13A2.006']
productQuality_A
[{'Layer': '_1_km_16_days_EVI',
'ProductAndVersion': 'MYD13A2.006',
'QualityLayers': ['_1_km_16_days_VI_Quality'],
'QualityProductAndVersion': 'MYD13A2.006'},
{'Layer': '_1_km_16_days_NDVI',
'ProductAndVersion': 'MYD13A2.006',
'QualityLayers': ['_1_km_16_days_VI_Quality'],
'QualityProductAndVersion': 'MYD13A2.006'},
{'Layer': '_1_km_16_days_blue_reflectance',
'ProductAndVersion': 'MYD13A2.006',
'QualityLayers': ['_1_km_16_days_pixel_reliability'],
'QualityProductAndVersion': 'MYD13A2.006'},
{'Layer': '_1_km_16_days_MIR_reflectance',
'ProductAndVersion': 'MYD13A2.006',
'QualityLayers': ['_1_km_16_days_pixel_reliability'],
'QualityProductAndVersion': 'MYD13A2.006'},
{'Layer': '_1_km_16_days_NIR_reflectance',
'ProductAndVersion': 'MYD13A2.006',
'QualityLayers': ['_1_km_16_days_pixel_reliability'],
'QualityProductAndVersion': 'MYD13A2.006'},
{'Layer': '_1_km_16_days_red_reflectance',
'ProductAndVersion': 'MYD13A2.006',
'QualityLayers': ['_1_km_16_days_pixel_reliability'],
'QualityProductAndVersion': 'MYD13A2.006'}]
If users do not want to use indexing or list comprehensions (the previous two steps), they may use the product parameter in the quality service URL to get all of the layers in MYD13A2.006 that have associated quality layers.
productQuality_B = requests.get('{}/quality/MYD13A2.006?format=json'.format(SERVICES_URL)).json()
productQuality_B
[{'Layer': '_1_km_16_days_EVI',
'ProductAndVersion': 'MYD13A2.006',
'QualityLayers': ['_1_km_16_days_VI_Quality'],
'QualityProductAndVersion': 'MYD13A2.006'},
{'Layer': '_1_km_16_days_NDVI',
'ProductAndVersion': 'MYD13A2.006',
'QualityLayers': ['_1_km_16_days_VI_Quality'],
'QualityProductAndVersion': 'MYD13A2.006'},
{'Layer': '_1_km_16_days_blue_reflectance',
'ProductAndVersion': 'MYD13A2.006',
'QualityLayers': ['_1_km_16_days_pixel_reliability'],
'QualityProductAndVersion': 'MYD13A2.006'},
{'Layer': '_1_km_16_days_MIR_reflectance',
'ProductAndVersion': 'MYD13A2.006',
'QualityLayers': ['_1_km_16_days_pixel_reliability'],
'QualityProductAndVersion': 'MYD13A2.006'},
{'Layer': '_1_km_16_days_NIR_reflectance',
'ProductAndVersion': 'MYD13A2.006',
'QualityLayers': ['_1_km_16_days_pixel_reliability'],
'QualityProductAndVersion': 'MYD13A2.006'},
{'Layer': '_1_km_16_days_red_reflectance',
'ProductAndVersion': 'MYD13A2.006',
'QualityLayers': ['_1_km_16_days_pixel_reliability'],
'QualityProductAndVersion': 'MYD13A2.006'}]
productQuality_B[0]['QualityLayers'][0]
'_1_km_16_days_VI_Quality'
Note: In both cases above, EVI (_1_km_16_days_EVI) and NDVI (_1_km_16_days_NDVI) have the same quality layer (_1_km_16_days_VI_Quality). This is the case for many of the MODIS products. However, there are occasions when the layers within products have their own associated quality layers (e.g., MOD/MYD11 products).
Get the Quality Layer Description for MYD13A2.006
Information within quality layers is often stored as bit-packed integers. This means that in order to get the actual quality information, one must convert the integer to binary and parse the binary string into bit-fields. Bit-fields are distinct combinations of bits representing quality categories that give an indication as to the usability and usefulness of the data product. Let's get the quality layer description for MYD13A2.006 by adding the quality layer parameter to the quality service URL.
qualityLayerInfo = requests.get('{}/quality/MYD13A2.006/_1_km_16_days_VI_Quality?format=json'.format(SERVICES_URL)).json() #qualityLayerInfo pd.DataFrame(qualityLayerInfo)
Acceptable | Description | Name | ProductAndVersion | QualityLayer | Value | |
---|---|---|---|---|---|---|
0 | True | VI produced, good quality | MODLAND | MYD13A2.006 | _1_km_16_days_VI_Quality | 0 |
1 | False | VI produced, but check other QA | MODLAND | MYD13A2.006 | _1_km_16_days_VI_Quality | 1 |
2 | False | Pixel produced, but most probably cloudy | MODLAND | MYD13A2.006 | _1_km_16_days_VI_Quality | 2 |
3 | False | Pixel not produced due to other reasons than c... | MODLAND | MYD13A2.006 | _1_km_16_days_VI_Quality | 3 |
4 | None | Highest quality | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 0 |
5 | None | Lower quality | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 1 |
6 | None | Decreasing quality (0010) | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 2 |
7 | None | Decreasing quality (0011) | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 3 |
8 | None | Decreasing quality (0100) | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 4 |
9 | None | Decreasing quality (0101) | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 5 |
10 | None | Decreasing quality (0110) | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 6 |
11 | None | Decreasing quality (0111) | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 7 |
12 | None | Decreasing quality (1000) | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 8 |
13 | None | Decreasing quality (1001) | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 9 |
14 | None | Decreasing quality (1010) | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 10 |
15 | None | Decreasing quality (1011) | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 11 |
16 | None | Lowest quality | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 12 |
17 | None | Quality so low that it is not useful | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 13 |
18 | None | L1B data faulty | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 14 |
19 | None | Not useful for any other reason/not processed | VI Usefulness | MYD13A2.006 | _1_km_16_days_VI_Quality | 15 |
20 | None | Climatology | Aerosol Quantity | MYD13A2.006 | _1_km_16_days_VI_Quality | 0 |
21 | None | Low | Aerosol Quantity | MYD13A2.006 | _1_km_16_days_VI_Quality | 1 |
22 | None | Average | Aerosol Quantity | MYD13A2.006 | _1_km_16_days_VI_Quality | 2 |
23 | None | High | Aerosol Quantity | MYD13A2.006 | _1_km_16_days_VI_Quality | 3 |
24 | None | No | Adjacent cloud detected | MYD13A2.006 | _1_km_16_days_VI_Quality | 0 |
25 | None | Yes | Adjacent cloud detected | MYD13A2.006 | _1_km_16_days_VI_Quality | 1 |
26 | None | No | Atmosphere BRDF Correction | MYD13A2.006 | _1_km_16_days_VI_Quality | 0 |
27 | None | Yes | Atmosphere BRDF Correction | MYD13A2.006 | _1_km_16_days_VI_Quality | 1 |
28 | None | No | Mixed Clouds | MYD13A2.006 | _1_km_16_days_VI_Quality | 0 |
29 | None | Yes | Mixed Clouds | MYD13A2.006 | _1_km_16_days_VI_Quality | 1 |
30 | None | Shallow ocean | Land/Water Mask | MYD13A2.006 | _1_km_16_days_VI_Quality | 0 |
31 | None | Land (Nothing else but land) | Land/Water Mask | MYD13A2.006 | _1_km_16_days_VI_Quality | 1 |
32 | None | Ocean coastlines and lake shorelines | Land/Water Mask | MYD13A2.006 | _1_km_16_days_VI_Quality | 2 |
33 | None | Shallow inland water | Land/Water Mask | MYD13A2.006 | _1_km_16_days_VI_Quality | 3 |
34 | None | Ephemeral water | Land/Water Mask | MYD13A2.006 | _1_km_16_days_VI_Quality | 4 |
35 | None | Deep inland water | Land/Water Mask | MYD13A2.006 | _1_km_16_days_VI_Quality | 5 |
36 | None | Moderate or continental ocean | Land/Water Mask | MYD13A2.006 | _1_km_16_days_VI_Quality | 6 |
37 | None | Deep ocean | Land/Water Mask | MYD13A2.006 | _1_km_16_days_VI_Quality | 7 |
38 | None | No | Possible snow/ice | MYD13A2.006 | _1_km_16_days_VI_Quality | 0 |
39 | None | Yes | Possible snow/ice | MYD13A2.006 | _1_km_16_days_VI_Quality | 1 |
40 | None | No | Possible shadow | MYD13A2.006 | _1_km_16_days_VI_Quality | 0 |
41 | None | Yes | Possible shadow | MYD13A2.006 | _1_km_16_days_VI_Quality | 1 |
Note: The table above contains the description for each bit-field in MYD13A2.006's quality layer (i.e., _1_km_16_days_VI_Quality). For more information of quality layer descriptions, please consult with the product pages.
Decode Quality Value
Say that we have extracted a pixel value from the EVI layer (i.e., _1_km_16_days_EVI) of product MYD13A2.006. We can use the quality service to decode the associated quality value for the pixel by inserting the pixel value from the quality layer (_1_km_16_days_VI_Quality) in the quality service URL. We see that our pixel value from the quality layer equals 4160 and that our EVI value equals 0.3091.
qualityIntDecoder = requests.get('{}/quality/MYD13A2.006/_1_km_16_days_VI_Quality/4160?format=json'.format(SERVICES_URL)).json()
qualityIntDecoder
{'Adjacent cloud detected': {'bits': '0b0', 'description': 'No'},
'Aerosol Quantity': {'bits': '0b01', 'description': 'Low'},
'Atmosphere BRDF Correction': {'bits': '0b0', 'description': 'No'},
'Binary Representation': '0b0001000001000000',
'Land/Water Mask': {'bits': '0b010',
'description': 'Ocean coastlines and lake shorelines'},
'MODLAND': {'bits': '0b00', 'description': 'VI produced, good quality'},
'Mixed Clouds': {'bits': '0b0', 'description': 'No'},
'Possible shadow': {'bits': '0b0', 'description': 'No'},
'Possible snow/ice': {'bits': '0b0', 'description': 'No'},
'VI Usefulness': {'bits': '0b0000', 'description': 'Highest quality'}}
Display qualityIntDecoder as Pandas dataframe
pd.DataFrame(qualityIntDecoder)
Binary Representation | MODLAND | VI Usefulness | Aerosol Quantity | Adjacent cloud detected | Atmosphere BRDF Correction | Mixed Clouds | Land/Water Mask | Possible snow/ice | Possible shadow | |
---|---|---|---|---|---|---|---|---|---|---|
bits | 0b0001000001000000 | 0b00 | 0b0000 | 0b01 | 0b0 | 0b0 | 0b0 | 0b010 | 0b0 | 0b0 |
description | 0b0001000001000000 | VI produced, good quality | Highest quality | Low | No | No | No | Ocean coastlines and lake shorelines | No | No |
The above code snippet returns the decoded quality information for value 4160, however, the order in which the quality categories are displayed does not match the order in which the bit-fields are assembled in the binary string. To correct this, the same service call can be used, but the manner in which we bring the request into python changes.
import json
from collections import OrderedDict
qualityIntDecoder = json.loads(
requests.get('{}/quality/MYD13A2.006/_1_km_16_days_VI_Quality/4160?format=json'.format(SERVICES_URL)).text,
object_pairs_hook=OrderedDict)
Now let's print out the decoded quality value in its proper order.
#qualityIntDecoder
pd.DataFrame(qualityIntDecoder)
Binary Representation | MODLAND | VI Usefulness | Aerosol Quantity | Adjacent cloud detected | Atmosphere BRDF Correction | Mixed Clouds | Land/Water Mask | Possible snow/ice | Possible shadow | |
---|---|---|---|---|---|---|---|---|---|---|
bits | 0b0001000001000000 | 0b00 | 0b0000 | 0b01 | 0b0 | 0b0 | 0b0 | 0b010 | 0b0 | 0b0 |
description | 0b0001000001000000 | VI produced, good quality | Highest quality | Low | No | No | No | Ocean coastlines and lake shorelines | No | No |
We can see in the MODLAND column that this is a good quality pixel and the Land/Water Flag column indicates that this pixel is likely near a lake shoreline (given that the pixel extracted was from tile h10v04).
Finally, we can take the decoded python object (a python dictionary) and filter it based on the bit-field description.
qualityIntDecoder['MODLAND']
OrderedDict([('bits', '0b00'), ('description', 'VI produced, good quality')])
Relevant Data and Tools
Product | Long Name |
---|---|
MOD13A1.006 | MODIS/Terra Vegetation Indices 16-Day L3 Global 500 m SIN Grid |
MYD13A2.006 | MODIS/Aqua Vegetation Indices 16-Day L3 Global 1 km SIN Grid |
AppEEARS | The Application for Extracting and Exploring Analysis Ready Samples |
Jupyter | Python tutorial showing how to work with quality data associated with LP DAAC products (Notebook) |