├── .gitignore ├── .travis.yml ├── CHANGES.txt ├── MANIFEST.in ├── README.md ├── demo_img ├── capehorn_no_color_hist.png ├── greenland_no_color_hist.png ├── portland_same_color_hist.png ├── toa_capehorn_no_color.gif ├── toa_greenland_no_color.gif ├── toa_greenland_no_color_small.gif ├── toa_portland_same_color.gif └── toa_portland_same_color_small.gif ├── requirements-dev.txt ├── requirements.txt ├── rio_toa ├── __init__.py ├── brightness_temp.py ├── radiance.py ├── reflectance.py ├── scripts │ ├── __init__.py │ └── cli.py ├── sun_utils.py └── toa_utils.py ├── setup.py └── tests ├── data ├── LC80100202015018LGN00_MTL.json ├── LC80100202015018LGN00_MTL.txt ├── LC80430302016140LGN00_MTL.json ├── LC80460282016177LGN00_MTL.json ├── LC81060712016134LGN00_B3.TIF ├── LC81060712016134LGN00_MTL.json ├── LC81060712016134LGN00_MTL.txt ├── LC81390452014295LGN00_MTL.json ├── LC82290902015304LGN00_MTL.json ├── mtltest_LC80100202015018LGN00_MTL.txt ├── path164sundata.json ├── tiny_LC80100202015018LGN00_B1.TIF ├── tiny_LC80100202015018LGN00_B1_refl.TIF ├── tiny_LC80460282016177LGN00_B11.TIF ├── tiny_LC80460282016177LGN00_B2.TIF ├── tiny_LC80460282016177LGN00_B2_refl.TIF ├── tiny_LC80460282016177LGN00_B3.TIF ├── tiny_LC80460282016177LGN00_B4.TIF ├── tiny_LC80460282016177LGN00_rgb_refl.TIF ├── tiny_LC81390452014295LGN00_B10.TIF ├── tiny_LC81390452014295LGN00_B5.TIF ├── tiny_LC81390452014295LGN00_B5_radl.TIF ├── tiny_LC81390452014295LGN00_B5_refl.TIF └── tiny_LLC80460282016177LGN00_B11_bt.TIF ├── expected ├── bt.tif ├── ref1.tif └── ref2.tif ├── test_brightness_temp.py ├── test_cli.py ├── test_radiance.py ├── test_reflectance.py ├── test_sun_utils.py └── test_toa_utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | # OS X 57 | .DS_Store 58 | 59 | # Pyenv 60 | .python-version 61 | 62 | .*.swp 63 | 64 | docs/notebooks/.ipynb_checkpoints/* 65 | .hypothesis 66 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | cache: 4 | directories: 5 | - ~/.cache/pip 6 | env: 7 | global: 8 | - PIP_WHEEL_DIR=$HOME/.cache/pip/wheels 9 | - PIP_FIND_LINKS=file://$HOME/.cache/pip/wheels 10 | addons: 11 | apt: 12 | packages: 13 | - libgdal1h 14 | - gdal-bin 15 | - libgdal-dev 16 | - libatlas-dev 17 | - libatlas-base-dev 18 | - gfortran 19 | python: 20 | - "2.7" 21 | - "3.4" 22 | - "3.5" 23 | before_install: 24 | - "pip install -U pip" 25 | - "pip install wheel" 26 | install: 27 | - "pip wheel numpy" 28 | - "pip install --use-wheel numpy" 29 | - "pip wheel -r requirements.txt" 30 | - "pip install --use-wheel -r requirements.txt" 31 | - "pip wheel -r requirements-dev.txt" 32 | - "pip install --use-wheel -r requirements-dev.txt" 33 | - "pip install -e .[test]" 34 | script: 35 | - py.test --cov rio_toa --cov-report term-missing 36 | after_success: 37 | - coveralls 38 | 39 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | CHANGES 2 | 3 | 0.1.1 (2016-05-26) 4 | ------------------ 5 | - make matplotlib an optional runtime dependency 6 | 7 | 0.1.0 (2016-05-25) 8 | ------------------ 9 | - Initial release 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rio-toa 2 | [![Build Status](https://travis-ci.org/mapbox/rio-toa.svg?branch=master)](https://travis-ci.org/mapbox/rio-toa) 3 | [![Coverage Status](https://coveralls.io/repos/github/mapbox/rio-toa/badge.svg?branch=the-sunangle-also-rises)](https://coveralls.io/github/mapbox/rio-toa?branch=the-sunangle-also-rises) 4 | 5 | Top Of Atmosphere (TOA) calculations for Landsat 8 6 | 7 | ## Before/After 8 | ##### Savissivik, Greenland 2016-02-29 9 | ![toa_greenland_gif](demo_img/toa_greenland_no_color_small.gif) 10 | ###### Pre-TOA vs Post-TOA Histograms 11 | ![greenland_hist](demo_img/greenland_no_color_hist.png) 12 | ##### Portland OR, USA 2016-06-25 13 | ![toa_portland_gif](demo_img/toa_portland_same_color_small.gif) 14 | ###### Pre-TOA vs Post-TOA Histograms 15 | ![portland_hist](demo_img/portland_same_color_hist.png) 16 | ##### Cape Horn, Argentina 2015-08-07 17 | ![toa_caphorn_gif](demo_img/toa_capehorn_no_color.gif ) 18 | ###### Pre-TOA vs Post-TOA Histograms 19 | ![portland_hist](demo_img/capehorn_no_color_hist.png) 20 | 21 | ## Install 22 | 23 | We highly recommend installing in a virtualenv. Once activated, 24 | ``` 25 | pip install -U pip 26 | pip install rio-toa 27 | ``` 28 | Or install from source 29 | ``` 30 | git clone https://github.com/mapbox/rio-toa.git 31 | cd rio-toa 32 | pip install -U pip 33 | pip install -r requirements-dev.txt 34 | pip install -e . 35 | ``` 36 | ## Python API 37 | ### `rio_toa.radiance` 38 | The `radiance` module calculates top of atmosphere radiance of Landsat 8 as outlined here: http://landsat.usgs.gov/Landsat8_Using_Product.php. 39 | 40 | #### 1. `radiance` 41 | The `radiance.randiance` function accepts the following as inputs: 42 | 43 | - numpy 2D array (single band) 44 | - ML (multiplicative rescaling factor from scene mtl) 45 | - AL (additive rescaling factor from scene mtl) 46 | 47 | and outputs: 48 | - numpy 2D array (single band) 49 | 50 | ``` 51 | >>> from rio_toa import radiance 52 | >>> from rio_toa import toa_utils 53 | ... 54 | >>> toa = radiance.radiance(tif, ML, AL, src_nodata=0) 55 | >>> toa_rescaled = toa_utils.rescale(toa, rescale_factor, dst_dtype) 56 | 57 | ``` 58 | #### 2.`calculate_landsat_radiance` 59 | ``` 60 | >>> from rio_toa import reflectance 61 | ... 62 | >>> radiance.calculate_landsat_radiance(src_path, src_mtl, dst_path, 63 | creation_options, band_number, dst_dtype, processes) 64 | ``` 65 | 66 | ====== 67 | ### `rio_toa.reflectance` 68 | The `reflectance` module calculates top of atmosphere reflectance of Landsat 8 as outlined here: http://landsat.usgs.gov/Landsat8_Using_Product.php. 69 | 70 | #### 1. `reflectance` 71 | The `reflectance.reflectance` function accepts the following as inputs: 72 | - numpy ndarray 73 | - MR (multiplicative rescaling factor from scene mtl), numpy ndarray 74 | - AR (additive rescaling factor from scene mtl), numpy ndarray 75 | - E (sun elevation angle from mtl or per pixel), numpy ndarray 76 | 77 | and outputs: 78 | - numpy ndarray 79 | 80 | ``` 81 | >>> from rio_toa import reflectance 82 | >>> from rio_toa import toa_utils 83 | ... 84 | >>> toa = reflectance.reflectance(img, MR, AR, E, src_nodata=0) 85 | >>> toa_rescaled = toa_utils.rescale(toa, rescale_factor, dst_dtype) 86 | ``` 87 | 88 | #### 2. `calculate_landsat_reflectance` 89 | ``` 90 | >>> from rio_toa import reflectance 91 | ... 92 | >>> reflectance.calculate_landsat_reflectance(list(src_paths), src_mtl, dst_path, 93 | rescale_factor, creation_options, list(band_numbers), 94 | dst_dtype, processes, pixel_sunangle) 95 | ``` 96 | - per pixel solar angles could be used instead of the scene center solar angle: 97 | This option requires the additional ['DATE_ACQUIRED'] and ['SCENE_CENTER_TIME'] from mtl files. 98 | 99 | ``` 100 | >>> from rio_toa import reflectance 101 | >>> from rio_toa import sun_utils 102 | >>> import riomucho 103 | >>> from rasterio import warp 104 | >>> from rasterio.coords import BoundingBox 105 | ... 106 | >>> date_collected = mtl['L1_METADATA_FILE']['PRODUCT_METADATA']['DATE_ACQUIRED'] 107 | >>> time_collected_utc = mtl['L1_METADATA_FILE']['PRODUCT_METADATA']['SCENE_CENTER_TIME'] 108 | >>> bboxes = [BoundingBox( 109 | *warp.transform_bounds( 110 | src_crs', 111 | {'init': u'epsg:4326'}, 112 | *open_files[i].window_bounds(window))) 113 | for i in range(data.shape[0])] 114 | >>> E_stack = riomucho.utils.array_stack( 115 | [sun_utils.sun_elevation( 116 | bbox, 117 | data.shape[1:], 118 | 'date_collected', 119 | 'time_collected_utc')[np.newaxis, :] 120 | for bbox in bboxes]) 121 | ... 122 | >>> output = toa_utils.rescale(reflectance( 123 | data, 124 | M_stack, 125 | A_stack, 126 | E_stack, 127 | 'src_nodata'), 128 | rescale_factor, dst_dtype) 129 | ``` 130 | ### `rio_toa.brightness_temp` 131 | The 'brightness_temp' module converts Landsat 8 TIRS band data from spectral radiance to brightness temperature as outlined here: http://landsat.usgs.gov/Landsat8_Using_Product.php. 132 | 133 | 134 | #### 1. `brightness_temp` 135 | The `brightness_temp.brightness_temp` function accepts the following as inputs: 136 | 137 | - numpy 2D array (single band) 138 | - ML (multiplicative rescaling factor from scene mtl) 139 | - AL (additive rescaling factor from scene mtl) 140 | - K1 (thermal conversion constant from the scene mtl) 141 | - K2 (thermal conversion constant from the scene mtl) 142 | 143 | and outputs: 144 | - numpy 2D array (single band) 145 | 146 | ``` 147 | >>> from rio_toa import brightness_temp 148 | >>> from rio_toa import toa_utils 149 | ... 150 | >>> bt = radiance.radiance(tif, ML, AL, K1, K2, src_nodata=0) 151 | >>> bt_rescaled= toa_utils.temp_rescale(toa, temp_scale) 152 | ``` 153 | #### 2.`calculate_landsat_brightness_temperature` 154 | ``` 155 | >>> from rio_toa import brightness_temp 156 | ... 157 | >>> brightness_temp.calculate_landsat_brightness_temperature(src_path, src_mtl, 158 | dst_path, temp_scale, 159 | creation_options, thermal_bidx, 160 | dst_dtype, processes) 161 | 162 | ``` 163 | 164 | ## `CLI` 165 | 166 | ### `radiance` 167 | 168 | ``` 169 | Usage: rio toa radiance [OPTIONS] SRC_PATH SRC_MTL DST_PATH 170 | 171 | Calculates Landsat8 Top of Atmosphere Radiance 172 | 173 | Options: 174 | --dst-dtype Output datatype. Default='float32' 175 | ['float32', 'float64', 'uint16', 'uint8'] 176 | -r, --rescale-factor Rescale post-TOA tifs to 55,000. 177 | Default=float(55000.0/2**16). 178 | Range: [float(55000.0/2**16), float(1.0)] 179 | -t, --readtemplate File path template. Default='.*/LC8.*\_B{b}.TIF' 180 | -j, --workers INTEGER 181 | -t, --readtemplate File path template. Default='.*/LC8.*\_B{b}.TIF' 182 | --l8-bidx INTEGER L8 Band that the src_path represents (Default is 183 | parsed from file name) 184 | -v, --verbose 185 | --co NAME=VALUE Driver specific creation options.See the 186 | documentation for the selected output driver for more 187 | information. 188 | --help Show this message and exit. 189 | ``` 190 | 191 | ### `reflectance` 192 | 193 | ``` 194 | Usage: rio toa reflectance [OPTIONS] SRC_PATH SRC_MTL DST_PATH 195 | 196 | Calculates Landsat8 Top of Atmosphere Reflectance 197 | 198 | Options: 199 | 200 | 201 | --dst-dtype Output datatype. Default='float32' 202 | ['float32', 'float64', 'uint16', 'uint8'] 203 | -r, --rescale-factor Rescale post-TOA tifs to 55,000. 204 | Default=float(55000.0/2**16). 205 | Range: [float(55000.0/2**16), float(1.0)] 206 | -t, --readtemplate File path template. Default='.*/LC8.*\_B{b}.TIF' 207 | -j, --workers INTEGER number of processes 208 | --l8-bidx INTEGER L8 Band that the src_path represents (default is 209 | parsed from file name) 210 | -v, --verbose Debugging mode 211 | -p, --pixel-sunangle Per pixel sun elevation 212 | --co NAME=VALUE Driver specific creation options.See the 213 | documentation for the selected output driver for more 214 | information. 215 | --help Show this message and exit. 216 | ``` 217 | 218 | 219 | ### `brighttemp` 220 | 221 | ``` 222 | Usage: rio toa brighttemp [OPTIONS] SRC_PATH SRC_MTL DST_PATH 223 | 224 | Calculates Landsat8 at-satellite brightness temperature TIRS band data can 225 | be converted from spectral radiance to brightness temperature using the 226 | thermal constants provided in the metadata file: 227 | 228 | Options: 229 | -d, --dst-dtype [float32|float64|uint16|uint8] 230 | Output data type 231 | -s, --temp_scale [K|F|C] Temperature scale [Default = K (Kelvin)] 232 | -t, --readtemplate TEXT File path template [Default 233 | ='.*/LC8.*\_B{b}.TIF'] 234 | -j, --workers INTEGER 235 | --thermal-bidx INTEGER L8 thermal band that the src_path 236 | represents(Default is parsed from file name) 237 | -v, --verbose 238 | --co NAME=VALUE Driver specific creation options.See the 239 | documentation for the selected output driver 240 | for more information. 241 | --help Show this message and exit. 242 | ``` 243 | 244 | ### `parsemtl` 245 | 246 | Takes a file or stdin MTL in txt format, and outputs a json-formatted MTL to stdout 247 | 248 | ``` 249 | Usage: rio toa parsemtl [OPTIONS] [MTL] 250 | 251 | Converts a Landsat 8 text MTL to JSON 252 | 253 | Options: 254 | --help Show this message and exit. 255 | ``` 256 | From a local `*_MTL.txt`: 257 | ``` 258 | rio toa parsemtl tests/data/LC81060712016134LGN00_MTL.txt 259 | ``` 260 | From stdin: 261 | ``` 262 | cat tests/data/LC81060712016134LGN00_MTL.txt | rio toa parsemtl 263 | ``` 264 | From stdin on `s3`: 265 | ``` 266 | aws s3 cp s3://landsat-pds/L8/106/071/LC81060712016134LGN00/LC81060712016134LGN00_MTL.txt - | rio toa parsemtl 267 | ``` 268 | -------------------------------------------------------------------------------- /demo_img/capehorn_no_color_hist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/demo_img/capehorn_no_color_hist.png -------------------------------------------------------------------------------- /demo_img/greenland_no_color_hist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/demo_img/greenland_no_color_hist.png -------------------------------------------------------------------------------- /demo_img/portland_same_color_hist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/demo_img/portland_same_color_hist.png -------------------------------------------------------------------------------- /demo_img/toa_capehorn_no_color.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/demo_img/toa_capehorn_no_color.gif -------------------------------------------------------------------------------- /demo_img/toa_greenland_no_color.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/demo_img/toa_greenland_no_color.gif -------------------------------------------------------------------------------- /demo_img/toa_greenland_no_color_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/demo_img/toa_greenland_no_color_small.gif -------------------------------------------------------------------------------- /demo_img/toa_portland_same_color.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/demo_img/toa_portland_same_color.gif -------------------------------------------------------------------------------- /demo_img/toa_portland_same_color_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/demo_img/toa_portland_same_color_small.gif -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | affine 2 | cligj 3 | coveralls>=0.4 4 | delocate 5 | enum34 6 | numpy>=1.8.0 7 | snuggs>=1.2 8 | pytest 9 | hypothesis 10 | raster-tester 11 | setuptools>=0.9.8 12 | wheel 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click 2 | rasterio 3 | rio-mucho>=0.2.1 4 | rio-color>=0.4 5 | -------------------------------------------------------------------------------- /rio_toa/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | __version__ = "0.3.0" 4 | 5 | log = logging.getLogger(__name__) 6 | log.addHandler(logging.NullHandler()) 7 | -------------------------------------------------------------------------------- /rio_toa/brightness_temp.py: -------------------------------------------------------------------------------- 1 | import json 2 | import numpy as np 3 | import rasterio as rio 4 | import collections 5 | from rasterio.coords import BoundingBox 6 | import riomucho 7 | from rasterio import warp 8 | 9 | from rio_toa import radiance 10 | from rio_toa import toa_utils 11 | from rio_toa import sun_utils 12 | 13 | 14 | def brightness_temp(img, ML, AL, K1, K2, src_nodata=0): 15 | """Calculate brightness temperature of Landsat 8 16 | as outlined here: http://landsat.usgs.gov/Landsat8_Using_Product.php 17 | 18 | T = K2 / np.log((K1 / L) + 1) 19 | 20 | and 21 | 22 | L = ML * Q + AL 23 | 24 | where: 25 | T = At-satellite brightness temperature (degrees kelvin) 26 | L = TOA spectral radiance (Watts / (m2 * srad * mm)) 27 | ML = Band-specific multiplicative rescaling factor from the metadata 28 | (RADIANCE_MULT_BAND_x, where x is the band number) 29 | AL = Band-specific additive rescaling factor from the metadata 30 | (RADIANCE_ADD_BAND_x, where x is the band number) 31 | Q = Quantized and calibrated standard product pixel values (DN) 32 | (ndarray img) 33 | K1 = Band-specific thermal conversion constant from the metadata 34 | (K1_CONSTANT_BAND_x, where x is the thermal band number) 35 | K2 = Band-specific thermal conversion constant from the metadata 36 | (K1_CONSTANT_BAND_x, where x is the thermal band number) 37 | 38 | 39 | Parameters 40 | ----------- 41 | img: ndarray 42 | array of input pixels 43 | ML: float 44 | multiplicative rescaling factor from scene metadata 45 | AL: float 46 | additive rescaling factor from scene metadata 47 | K1: float 48 | thermal conversion constant from scene metadata 49 | K2: float 50 | thermal conversion constant from scene metadata 51 | 52 | Returns 53 | -------- 54 | ndarray: 55 | float32 ndarray with shape == input shape 56 | """ 57 | L = radiance.radiance(img, ML, AL, src_nodata=0) 58 | L[img == src_nodata] = np.NaN 59 | 60 | T = K2 / np.log((K1 / L) + 1) 61 | 62 | return T 63 | 64 | 65 | def _brightness_temp_worker(data, window, ij, g_args): 66 | """rio mucho worker for brightness temperature. It reads input 67 | files and perform reflectance calculations on each window. 68 | 69 | Parameters 70 | ------------ 71 | open_files: list of rasterio open files 72 | window: tuples 73 | g_args: dictionary 74 | 75 | Returns 76 | --------- 77 | out: None 78 | Output is written to dst_path 79 | """ 80 | 81 | output = toa_utils.temp_rescale( 82 | brightness_temp( 83 | data[0], 84 | g_args['M'], 85 | g_args['A'], 86 | g_args['K1'], 87 | g_args['K2'], 88 | g_args['src_nodata']), 89 | g_args['temp_scale']) 90 | 91 | return output.astype(g_args['dst_dtype']) 92 | 93 | 94 | def calculate_landsat_brightness_temperature( 95 | src_path, src_mtl, dst_path, temp_scale, 96 | creation_options, band, dst_dtype, processes): 97 | 98 | """Parameters 99 | ------------ 100 | src_path: list 101 | list of src_paths(strings) 102 | src_mtl: string 103 | mtl file path 104 | dst_path: string 105 | destination file path 106 | rescale_factor: float [default] float(55000.0/2**16) 107 | rescale post-TOA tifs to 55,000 or to full 16-bit 108 | creation_options: dictionary 109 | rio.options.creation_options 110 | band: list 111 | list of integers 112 | dst_dtype: strings [default] uint16 113 | destination data dtype 114 | 115 | Returns 116 | --------- 117 | out: None 118 | Output is written to dst_path 119 | """ 120 | mtl = toa_utils._load_mtl(src_mtl) 121 | 122 | M = toa_utils._load_mtl_key(mtl, 123 | ['L1_METADATA_FILE', 124 | 'RADIOMETRIC_RESCALING', 125 | 'RADIANCE_MULT_BAND_'], 126 | band) 127 | A = toa_utils._load_mtl_key(mtl, 128 | ['L1_METADATA_FILE', 129 | 'RADIOMETRIC_RESCALING', 130 | 'RADIANCE_ADD_BAND_'], 131 | band) 132 | 133 | K1 = toa_utils._load_mtl_key(mtl, 134 | ['L1_METADATA_FILE', 135 | 'TIRS_THERMAL_CONSTANTS', 136 | 'K1_CONSTANT_BAND_'], 137 | band) 138 | K2 = toa_utils._load_mtl_key(mtl, 139 | ['L1_METADATA_FILE', 140 | 'TIRS_THERMAL_CONSTANTS', 141 | 'K2_CONSTANT_BAND_'], 142 | band) 143 | 144 | dst_dtype = np.__dict__[dst_dtype] 145 | 146 | with rio.open(src_path) as src: 147 | dst_profile = src.profile.copy() 148 | 149 | src_nodata = src.nodata 150 | 151 | for co in creation_options: 152 | dst_profile[co] = creation_options[co] 153 | 154 | dst_profile['dtype'] = dst_dtype 155 | 156 | global_args = { 157 | 'M': M, 158 | 'A': A, 159 | 'K1': K1, 160 | 'K2': K2, 161 | 'src_nodata': 0, 162 | 'temp_scale': temp_scale, 163 | 'dst_dtype': dst_dtype 164 | } 165 | 166 | with riomucho.RioMucho([src_path], 167 | dst_path, 168 | _brightness_temp_worker, 169 | options=dst_profile, 170 | global_args=global_args) as rm: 171 | 172 | rm.run(processes) 173 | -------------------------------------------------------------------------------- /rio_toa/radiance.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import rasterio 3 | import riomucho 4 | 5 | from rio_toa import toa_utils 6 | 7 | 8 | def radiance(img, ML, AL, src_nodata=0): 9 | """Calculate top of atmosphere radiance of Landsat 8 10 | as outlined here: http://landsat.usgs.gov/Landsat8_Using_Product.php 11 | 12 | L = ML * Q + AL 13 | 14 | where: 15 | L = TOA spectral radiance (Watts / (m2 * srad * mm)) 16 | ML = Band-specific multiplicative rescaling factor from the metadata 17 | (RADIANCE_MULT_BAND_x, where x is the band number) 18 | AL = Band-specific additive rescaling factor from the metadata 19 | (RADIANCE_ADD_BAND_x, where x is the band number) 20 | Q = Quantized and calibrated standard product pixel values (DN) 21 | (ndarray img) 22 | 23 | Parameters 24 | ----------- 25 | img: ndarray 26 | array of input pixels 27 | ML: float 28 | multiplicative rescaling factor from scene metadata 29 | AL: float 30 | additive rescaling factor from scene metadata 31 | 32 | Returns 33 | -------- 34 | ndarray: 35 | float32 ndarray with shape == input shape 36 | """ 37 | 38 | rs = ML * img.astype(np.float32) + AL 39 | if src_nodata is not None: 40 | rs[img == src_nodata] = 0.0 41 | 42 | return rs 43 | 44 | 45 | def _radiance_worker(data, window, ij, g_args): 46 | """ 47 | rio mucho worker for radiance 48 | TODO: integrate rescaling functionality for 49 | different output datatypes 50 | """ 51 | output = toa_utils.rescale( 52 | radiance( 53 | data[0], 54 | g_args['M'], 55 | g_args['A'], 56 | g_args['src_nodata']), 57 | g_args['rescale_factor'], 58 | g_args['dst_dtype'], 59 | clip=g_args['clip']) 60 | 61 | return output 62 | 63 | 64 | def calculate_landsat_radiance(src_path, src_mtl, dst_path, rescale_factor, 65 | creation_options, band, dst_dtype, processes, 66 | clip=True): 67 | """ 68 | Parameters 69 | ------------ 70 | src_path: strings 71 | src_mtl: string 72 | dst_path: string 73 | rescale_factor: float 74 | creation_options: dict 75 | bands: list 76 | dst_dtype: string 77 | processes: integer 78 | pixel_sunangle: boolean 79 | clip: boolean 80 | 81 | Returns 82 | --------- 83 | None 84 | Output is written to dst_path 85 | """ 86 | mtl = toa_utils._load_mtl(src_mtl) 87 | 88 | M = toa_utils._load_mtl_key(mtl, 89 | ['L1_METADATA_FILE', 90 | 'RADIOMETRIC_RESCALING', 91 | 'RADIANCE_MULT_BAND_'], 92 | band) 93 | A = toa_utils._load_mtl_key(mtl, 94 | ['L1_METADATA_FILE', 95 | 'RADIOMETRIC_RESCALING', 96 | 'RADIANCE_ADD_BAND_'], 97 | band) 98 | 99 | rescale_factor = toa_utils.normalize_scale(rescale_factor, dst_dtype) 100 | 101 | dst_dtype = np.__dict__[dst_dtype] 102 | 103 | with rasterio.open(src_path) as src: 104 | dst_profile = src.profile.copy() 105 | 106 | src_nodata = src.nodata 107 | 108 | for co in creation_options: 109 | dst_profile[co] = creation_options[co] 110 | 111 | dst_profile['dtype'] = dst_dtype 112 | 113 | global_args = { 114 | 'A': A, 115 | 'M': M, 116 | 'src_nodata': src_nodata, 117 | 'rescale_factor': rescale_factor, 118 | 'clip': clip, 119 | 'dst_dtype': dst_dtype 120 | } 121 | 122 | with riomucho.RioMucho([src_path], 123 | dst_path, 124 | _radiance_worker, 125 | options=dst_profile, 126 | global_args=global_args) as rm: 127 | 128 | rm.run(processes) 129 | -------------------------------------------------------------------------------- /rio_toa/reflectance.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import rasterio 3 | from rasterio.coords import BoundingBox 4 | from rasterio import warp 5 | import riomucho 6 | 7 | from rio_toa import toa_utils 8 | from rio_toa import sun_utils 9 | 10 | 11 | def reflectance(img, MR, AR, E, src_nodata=0): 12 | """Calculate top of atmosphere reflectance of Landsat 8 13 | as outlined here: http://landsat.usgs.gov/Landsat8_Using_Product.php 14 | 15 | R_raw = MR * Q + AR 16 | 17 | R = R_raw / cos(Z) = R_raw / sin(E) 18 | 19 | Z = 90 - E (in degrees) 20 | 21 | where: 22 | 23 | R_raw = TOA planetary reflectance, without correction for solar angle. 24 | R = TOA reflectance with a correction for the sun angle. 25 | MR = Band-specific multiplicative rescaling factor from the metadata 26 | (REFLECTANCE_MULT_BAND_x, where x is the band number) 27 | AR = Band-specific additive rescaling factor from the metadata 28 | (REFLECTANCE_ADD_BAND_x, where x is the band number) 29 | Q = Quantized and calibrated standard product pixel values (DN) 30 | E = Local sun elevation angle. The scene center sun elevation angle 31 | in degrees is provided in the metadata (SUN_ELEVATION). 32 | Z = Local solar zenith angle (same angle as E, but measured from the 33 | zenith instead of from the horizon). 34 | 35 | Parameters 36 | ----------- 37 | img: ndarray 38 | array of input pixels of shape (rows, cols) or (rows, cols, depth) 39 | MR: float or list of floats 40 | multiplicative rescaling factor from scene metadata 41 | AR: float or list of floats 42 | additive rescaling factor from scene metadata 43 | E: float or numpy array of floats 44 | local sun elevation angle in degrees 45 | 46 | Returns 47 | -------- 48 | ndarray: 49 | float32 ndarray with shape == input shape 50 | 51 | """ 52 | 53 | if np.any(E < 0.0): 54 | raise ValueError("Sun elevation must be nonnegative " 55 | "(sun must be above horizon for entire scene)") 56 | 57 | input_shape = img.shape 58 | 59 | if len(input_shape) > 2: 60 | img = np.rollaxis(img, 0, len(input_shape)) 61 | 62 | rf = ((MR * img.astype(np.float32)) + AR) / np.sin(np.deg2rad(E)) 63 | if src_nodata is not None: 64 | rf[img == src_nodata] = 0.0 65 | 66 | if len(input_shape) > 2: 67 | if np.rollaxis(rf, len(input_shape) - 1, 0).shape != input_shape: 68 | raise ValueError( 69 | "Output shape %s is not equal to input shape %s" 70 | % (rf.shape, input_shape)) 71 | else: 72 | return np.rollaxis(rf, len(input_shape) - 1, 0) 73 | else: 74 | return rf 75 | 76 | 77 | def _reflectance_worker(open_files, window, ij, g_args): 78 | """rio mucho worker for reflectance. It reads input 79 | files and perform reflectance calculations on each window. 80 | 81 | Parameters 82 | ------------ 83 | open_files: list of rasterio open files 84 | window: tuples 85 | g_args: dictionary 86 | 87 | Returns 88 | --------- 89 | out: None 90 | Output is written to dst_path 91 | 92 | """ 93 | data = riomucho.utils.array_stack([ 94 | src.read(window=window).astype(np.float32) 95 | for src in open_files 96 | ]) 97 | 98 | depth, rows, cols = data.shape 99 | 100 | if g_args['pixel_sunangle']: 101 | bbox = BoundingBox( 102 | *warp.transform_bounds( 103 | g_args['src_crs'], 104 | {'init': u'epsg:4326'}, 105 | *open_files[0].window_bounds(window))) 106 | 107 | E = sun_utils.sun_elevation( 108 | bbox, 109 | (rows, cols), 110 | g_args['date_collected'], 111 | g_args['time_collected_utc']).reshape(rows, cols, 1) 112 | 113 | else: 114 | # We're doing whole-scene (instead of per-pixel) sunangle: 115 | E = np.array([g_args['E'] for i in range(depth)]) 116 | 117 | output = toa_utils.rescale( 118 | reflectance( 119 | data, 120 | g_args['M'], 121 | g_args['A'], 122 | E, 123 | g_args['src_nodata']), 124 | g_args['rescale_factor'], 125 | g_args['dst_dtype'], 126 | clip=g_args['clip']) 127 | 128 | return output 129 | 130 | 131 | def calculate_landsat_reflectance(src_paths, src_mtl, dst_path, rescale_factor, 132 | creation_options, bands, dst_dtype, 133 | processes, pixel_sunangle, clip=True): 134 | """ 135 | Parameters 136 | ------------ 137 | src_paths: list of strings 138 | src_mtl: string 139 | dst_path: string 140 | rescale_factor: float 141 | creation_options: dict 142 | bands: list 143 | dst_dtype: string 144 | processes: integer 145 | pixel_sunangle: boolean 146 | clip: boolean 147 | 148 | Returns 149 | --------- 150 | None 151 | Output is written to dst_path 152 | """ 153 | mtl = toa_utils._load_mtl(src_mtl) 154 | metadata = mtl['L1_METADATA_FILE'] 155 | 156 | M = [metadata['RADIOMETRIC_RESCALING'] 157 | ['REFLECTANCE_MULT_BAND_{}'.format(b)] 158 | for b in bands] 159 | A = [metadata['RADIOMETRIC_RESCALING'] 160 | ['REFLECTANCE_ADD_BAND_{}'.format(b)] 161 | for b in bands] 162 | 163 | E = metadata['IMAGE_ATTRIBUTES']['SUN_ELEVATION'] 164 | date_collected = metadata['PRODUCT_METADATA']['DATE_ACQUIRED'] 165 | time_collected_utc = metadata['PRODUCT_METADATA']['SCENE_CENTER_TIME'] 166 | 167 | rescale_factor = toa_utils.normalize_scale(rescale_factor, dst_dtype) 168 | 169 | dst_dtype = np.__dict__[dst_dtype] 170 | 171 | for src_path in src_paths: 172 | with rasterio.open(src_path) as src: 173 | dst_profile = src.profile.copy() 174 | src_nodata = src.nodata 175 | 176 | for co in creation_options: 177 | dst_profile[co] = creation_options[co] 178 | 179 | dst_profile['dtype'] = dst_dtype 180 | 181 | global_args = { 182 | 'A': A, 183 | 'M': M, 184 | 'E': E, 185 | 'src_nodata': src_nodata, 186 | 'src_crs': dst_profile['crs'], 187 | 'dst_dtype': dst_dtype, 188 | 'rescale_factor': rescale_factor, 189 | 'clip': clip, 190 | 'pixel_sunangle': pixel_sunangle, 191 | 'date_collected': date_collected, 192 | 'time_collected_utc': time_collected_utc, 193 | 'bands': len(bands) 194 | } 195 | 196 | dst_profile.update(count=len(bands)) 197 | 198 | if len(bands) == 3: 199 | dst_profile.update(photometric='rgb') 200 | else: 201 | dst_profile.update(photometric='minisblack') 202 | 203 | with riomucho.RioMucho(list(src_paths), 204 | dst_path, 205 | _reflectance_worker, 206 | options=dst_profile, 207 | global_args=global_args, 208 | mode='manual_read') as rm: 209 | 210 | rm.run(processes) 211 | -------------------------------------------------------------------------------- /rio_toa/scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/rio_toa/scripts/__init__.py -------------------------------------------------------------------------------- /rio_toa/scripts/cli.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | 4 | import click 5 | from rasterio.rio.options import creation_options 6 | 7 | from rio_toa.radiance import calculate_landsat_radiance 8 | from rio_toa.reflectance import calculate_landsat_reflectance 9 | from rio_toa.brightness_temp import calculate_landsat_brightness_temperature 10 | from rio_toa.toa_utils import _parse_bands_from_filename, _parse_mtl_txt 11 | 12 | logger = logging.getLogger('rio_toa') 13 | 14 | 15 | @click.group('toa') 16 | def toa(): 17 | """Top of Atmosphere (TOA) correction for landsat 8 18 | """ 19 | pass 20 | 21 | 22 | @click.command('radiance') 23 | @click.argument('src_path', type=click.Path(exists=True)) 24 | @click.argument('src_mtl', type=click.Path(exists=True)) 25 | @click.argument('dst_path', type=click.Path(exists=False)) 26 | @click.option('--dst-dtype', 27 | type=click.Choice(['uint16', 'uint8']), 28 | default='uint16', 29 | help='Output data type') 30 | @click.option('--rescale-factor', '-r', type=float, 31 | default=None, 32 | help="Rescale TOA values by a multiplier. (Default: " 33 | "55000 for uint16, 215 for uint8, 1.0 for float32)") 34 | @click.option('--clip/--no-clip', default=True, 35 | help="Clip raw TOA values to constrain the domain to 0..1 " 36 | "(Default: True)") 37 | @click.option('--readtemplate', '-t', default=".*/LC8.*\_B{b}.TIF", 38 | help="File path template [Default ='.*/LC8.*\_B{b}.TIF']") 39 | @click.option('--workers', '-j', type=int, default=4) 40 | @click.option('--l8-bidx', default=0, type=int, 41 | help="L8 Band that the src_path represents" 42 | "(Default is parsed from file name)") 43 | @click.option('--verbose', '-v', is_flag=True, default=False) 44 | @click.pass_context 45 | @creation_options 46 | def radiance(ctx, src_path, src_mtl, dst_path, rescale_factor, 47 | readtemplate, verbose, creation_options, l8_bidx, 48 | dst_dtype, workers, clip): 49 | """Calculates Landsat8 Top of Atmosphere Radiance 50 | """ 51 | if verbose: 52 | logger.setLevel(logging.DEBUG) 53 | 54 | if l8_bidx == 0: 55 | l8_bidx = _parse_bands_from_filename([src_path], readtemplate)[0] 56 | 57 | calculate_landsat_radiance(src_path, src_mtl, dst_path, 58 | rescale_factor, creation_options, l8_bidx, 59 | dst_dtype, workers, clip) 60 | 61 | 62 | @click.command('reflectance') 63 | @click.argument('src_paths', nargs=-1, type=click.Path(exists=True)) 64 | @click.argument('src_mtl', type=click.Path(exists=True)) 65 | @click.argument('dst_path', type=click.Path(exists=False)) 66 | @click.option('--dst-dtype', 67 | type=click.Choice(['uint16', 'uint8', 'float32']), 68 | default='uint16', 69 | help='Output data type') 70 | @click.option('--rescale-factor', '-r', type=float, 71 | default=None, 72 | help="Rescale TOA values by a multiplier. (Default: " 73 | "55000 for uint16, 215 for uint8, 1.0 for float32)") 74 | @click.option('--clip/--no-clip', default=True, 75 | help="Clip raw TOA values to constrain the domain to 0..1 " 76 | "(Default: True)") 77 | @click.option('--readtemplate', '-t', default=".*/LC8.*\_B{b}.TIF", 78 | help="File path template [Default ='.*/LC8.*\_B{b}.TIF']") 79 | @click.option('--workers', '-j', type=int, default=4) 80 | @click.option('--l8-bidx', default=0, type=int, 81 | help="L8 Band that the src_path represents" 82 | "(Default is parsed from file name)") 83 | @click.option('--verbose', '-v', is_flag=True, default=False) 84 | @click.option('--pixel-sunangle', '-p', is_flag=True, default=False, 85 | help="Per pixel sun elevation") 86 | @click.pass_context 87 | @creation_options 88 | def reflectance(ctx, src_paths, src_mtl, dst_path, dst_dtype, 89 | rescale_factor, clip, readtemplate, workers, l8_bidx, 90 | verbose, creation_options, pixel_sunangle): 91 | """Calculates Landsat8 Top of Atmosphere Reflectance 92 | """ 93 | if verbose: 94 | logger.setLevel(logging.DEBUG) 95 | 96 | if l8_bidx == 0: 97 | l8_bidx = _parse_bands_from_filename(list(src_paths), readtemplate) 98 | 99 | calculate_landsat_reflectance(list(src_paths), src_mtl, dst_path, 100 | rescale_factor, creation_options, 101 | list(l8_bidx), dst_dtype, 102 | workers, pixel_sunangle, clip) 103 | 104 | 105 | @click.command('brighttemp') 106 | @click.argument('src_path', type=click.Path(exists=True)) 107 | @click.argument('src_mtl', type=click.Path(exists=True)) 108 | @click.argument('dst_path', type=click.Path(exists=False)) 109 | @click.option('--dst-dtype', '-d', 110 | type=click.Choice(['float32', 'float64', 'uint16', 'uint8']), 111 | default='float32', 112 | help='Output data type') 113 | @click.option('--temp-scale', '-s', 114 | type=click.Choice(['K', 'F', 'C']), 115 | default='K', 116 | help='Temperature scale [Default = K (Kelvin)]') 117 | @click.option('--readtemplate', '-t', default=".*/LC8.*\_B{b}.TIF", 118 | help="File path template [Default ='.*/LC8.*\_B{b}.TIF']") 119 | @click.option('--workers', '-j', type=int, default=4) 120 | @click.option('--thermal-bidx', default=0, type=int, 121 | help="L8 thermal band that the src_path represents" 122 | "(Default is parsed from file name)") 123 | @click.option('--verbose', '-v', is_flag=True, default=False) 124 | @click.pass_context 125 | @creation_options 126 | def brighttemp(ctx, src_path, src_mtl, dst_path, dst_dtype, 127 | temp_scale, readtemplate, workers, 128 | thermal_bidx, verbose, creation_options): 129 | """Calculates Landsat8 at-satellite brightness temperature. 130 | TIRS band data can be converted from spectral radiance 131 | to brightness temperature using the thermal 132 | constants provided in the metadata file: 133 | """ 134 | 135 | if verbose: 136 | logger.setLevel(logging.DEBUG) 137 | 138 | if thermal_bidx == 0: 139 | thermal_bidx = _parse_bands_from_filename([src_path], readtemplate)[0] 140 | 141 | calculate_landsat_brightness_temperature( 142 | src_path, src_mtl, dst_path, temp_scale, 143 | creation_options, thermal_bidx, dst_dtype, workers) 144 | 145 | 146 | @click.command('parsemtl') 147 | @click.argument('mtl', default='-', required=False) 148 | def parsemtl(mtl): 149 | """Converts a Landsat 8 text MTL 150 | to JSON 151 | """ 152 | try: 153 | mtl = str(click.open_file(mtl).read()) 154 | except IOError: 155 | mtl = str('\n'.join([inputtiles])) 156 | 157 | click.echo(json.dumps(_parse_mtl_txt(mtl))) 158 | 159 | 160 | toa.add_command(radiance) 161 | toa.add_command(reflectance) 162 | toa.add_command(brighttemp) 163 | toa.add_command(parsemtl) 164 | -------------------------------------------------------------------------------- /rio_toa/sun_utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | import datetime 3 | import numpy as np 4 | 5 | 6 | def parse_utc_string(collected_date, collected_time_utc): 7 | """ 8 | Given a string in the format: 9 | YYYY-MM-DD HH:MM:SS.SSSSSSSSZ 10 | Parse and convert into a datetime object 11 | Fractional seconds are ignored 12 | 13 | Parameters 14 | ----------- 15 | collected_date_utc: str 16 | Format: YYYY-MM-DD 17 | collected_time: str 18 | Format: HH:MM:SS.SSSSSSSSZ 19 | 20 | Returns 21 | -------- 22 | datetime object 23 | parsed scene center time 24 | """ 25 | utcstr = collected_date + ' ' + collected_time_utc 26 | 27 | if not re.match(r'\d{4}\-\d{2}\-\d{2}\ \d{2}\:\d{2}\:\d{2}\.\d+Z', 28 | utcstr): 29 | raise ValueError("%s is an invalid utc time" % utcstr) 30 | 31 | return datetime.datetime.strptime( 32 | utcstr.split(".")[0], 33 | "%Y-%m-%d %H:%M:%S") 34 | 35 | 36 | def time_to_dec_hour(parsedtime): 37 | """ 38 | Calculate the decimal hour from a datetime object 39 | 40 | Parameters 41 | ----------- 42 | parsedtime: datetime object 43 | 44 | Returns 45 | -------- 46 | decimal hour: float 47 | time in decimal hours 48 | """ 49 | return (parsedtime.hour + 50 | (parsedtime.minute / 60.0) + 51 | (parsedtime.second / 60.0 ** 2) 52 | ) 53 | 54 | 55 | def calculate_declination(d): 56 | """ 57 | Calculate the declination of the sun in radians based on a given day. 58 | As reference +23.26 degrees at the northern summer solstice, -23.26 59 | degrees at the southern summer solstice. 60 | See: https://en.wikipedia.org/wiki/Position_of_the_Sun#Calculations 61 | 62 | Parameters 63 | ----------- 64 | d: int 65 | days from midnight on January 1st 66 | 67 | Returns 68 | -------- 69 | declination in radians: float 70 | the declination on day d 71 | 72 | """ 73 | return np.arcsin( 74 | np.sin(np.deg2rad(23.45)) * 75 | np.sin(np.deg2rad(360. / 365.) * 76 | (d - 81)) 77 | ) 78 | 79 | 80 | def solar_angle(day, utc_hour, longitude): 81 | """ 82 | Given a day, utc decimal hour, and longitudes, compute the solar angle 83 | for these longitudes 84 | 85 | Parameters 86 | ----------- 87 | day: int 88 | days of the year with jan 1 as day = 1 89 | utc_hour: float 90 | decimal hour of the day in utc time to compute solar angle for 91 | longitude: ndarray or float 92 | longitude of the point(s) to compute solar angle for 93 | 94 | Returns 95 | -------- 96 | solar angle in degrees for these longitudes 97 | """ 98 | localtime = (longitude / 180.0) * 12 + utc_hour 99 | 100 | lstm = 15 * (localtime - utc_hour) 101 | 102 | B = np.deg2rad((360. / 365.) * (day - 81)) 103 | 104 | eot = (9.87 * 105 | np.sin(2 * B) - 106 | 7.53 * np.cos(B) - 107 | 1.5 * np.sin(B)) 108 | 109 | return 15 * (localtime + 110 | (4 * (longitude - lstm) + eot) / 60.0 - 12) 111 | 112 | 113 | def _calculate_sun_elevation(longitude, latitude, declination, day, utc_hour): 114 | """ 115 | Calculates the solar elevation angle 116 | https://en.wikipedia.org/wiki/Solar_zenith_angle 117 | 118 | Parameters 119 | ----------- 120 | longitude: ndarray or float 121 | longitudes of the point(s) to compute solar angle for 122 | latitude: ndarray or float 123 | latitudes of the point(s) to compute solar angle for 124 | declination: float 125 | declination of the sun in radians 126 | day: int 127 | days of the year with jan 1 as day = 1 128 | utc_hour: float 129 | decimal hour from a datetime object 130 | 131 | Returns 132 | -------- 133 | the solar elevation angle in degrees 134 | """ 135 | hour_angle = np.deg2rad(solar_angle(day, utc_hour, longitude)) 136 | 137 | latitude = np.deg2rad(latitude) 138 | 139 | return np.rad2deg(np.arcsin( 140 | np.sin(declination) * 141 | np.sin(latitude) + 142 | np.cos(declination) * 143 | np.cos(latitude) * 144 | np.cos(hour_angle) 145 | )) 146 | 147 | 148 | def _create_lnglats(shape, bbox): 149 | """ 150 | Creates a (lng, lat) array tuple with cells that respectively 151 | represent a longitude and a latitude at that location 152 | 153 | Parameters 154 | ----------- 155 | shape: tuple 156 | the shape of the arrays to create 157 | bbox: tuple or list 158 | the bounds of the arrays to create in [w, s, e, n] 159 | 160 | Returns 161 | -------- 162 | (lngs, lats): tuple of (rows, cols) shape ndarrays 163 | """ 164 | 165 | rows, cols = shape 166 | w, s, e, n = bbox 167 | xCell = (e - w) / float(cols) 168 | yCell = (n - s) / float(rows) 169 | 170 | lat, lng = np.indices(shape, dtype=np.float32) 171 | 172 | return ((lng * xCell) + w + (xCell / 2.0), 173 | (np.flipud(lat) * yCell) + s + (yCell / 2.0)) 174 | 175 | 176 | def sun_elevation(bounds, shape, date_collected, time_collected_utc): 177 | """ 178 | Given a raster's bounds + dimensions, calculate the 179 | sun elevation angle in degrees for each input pixel 180 | based on metadata from a Landsat MTL file 181 | 182 | Parameters 183 | ----------- 184 | bounds: BoundingBox 185 | bounding box of the input raster 186 | shape: tuple 187 | tuple of (rows, cols) or (depth, rows, cols) for input raster 188 | collected_date_utc: str 189 | Format: YYYY-MM-DD 190 | collected_time: str 191 | Format: HH:MM:SS.SSSSSSSSZ 192 | 193 | Returns 194 | -------- 195 | ndarray 196 | ndarray with shape = (rows, cols) with sun elevation 197 | in degrees calculated for each pixel 198 | """ 199 | utc_time = parse_utc_string(date_collected, time_collected_utc) 200 | 201 | if len(shape) == 3: 202 | _, rows, cols = shape 203 | else: 204 | rows, cols = shape 205 | 206 | lng, lat = _create_lnglats((rows, cols), 207 | list(bounds)) 208 | 209 | decimal_hour = time_to_dec_hour(utc_time) 210 | 211 | declination = calculate_declination(utc_time.timetuple().tm_yday) 212 | 213 | return _calculate_sun_elevation(lng, lat, declination, 214 | utc_time.timetuple().tm_yday, 215 | decimal_hour) 216 | -------------------------------------------------------------------------------- /rio_toa/toa_utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | 4 | import numpy as np 5 | 6 | 7 | def _parse_bands_from_filename(filenames, template): 8 | tomatch = re.compile(template.replace('{b}', '([0-9]+?)')) 9 | bands = [] 10 | for f in filenames: 11 | if not tomatch.match(f): 12 | raise ValueError('%s is not a valid template for %s' 13 | % (template, ', '.join(filenames))) 14 | bands.append(int(tomatch.findall(f)[0])) 15 | 16 | return bands 17 | 18 | 19 | def _load_mtl_key(mtl, keys, band=None): 20 | """ 21 | Loads requested metadata from a Landsat MTL dict 22 | 23 | Parameters 24 | ----------- 25 | mtl: dict 26 | parsed Landsat 8 MTL metadata 27 | keys: iterable 28 | a list containing arbitrary keys to load from the MTL 29 | band: int 30 | band number to join to last key value 31 | (ignored if not int) 32 | 33 | Returns 34 | -------- 35 | output: any value 36 | the value corresponding to the provided key in the MTL 37 | """ 38 | keys = list(keys) 39 | 40 | if isinstance(band, int): 41 | keys[-1] = '%s%s' % (keys[-1], band) 42 | 43 | # for each key, the mtl is windowed down by each key hash 44 | for k in keys: 45 | mtl = mtl[k] 46 | 47 | return mtl 48 | 49 | 50 | def _load_mtl(src_mtl): 51 | with open(src_mtl) as src: 52 | if src_mtl.split('.')[-1] == 'json': 53 | return json.loads(src.read()) 54 | else: 55 | return _parse_mtl_txt(src.read()) 56 | 57 | 58 | def _parse_mtl_txt(mtltxt): 59 | group = re.findall('.*\n', mtltxt) 60 | 61 | is_group = re.compile(r'GROUP\s\=\s.*') 62 | is_end = re.compile(r'END_GROUP\s\=\s.*') 63 | get_group = re.compile('\=\s([A-Z0-9\_]+)') 64 | 65 | output = [{ 66 | 'key': 'all', 67 | 'data': {} 68 | }] 69 | 70 | for g in map(str.lstrip, group): 71 | if is_group.match(g): 72 | output.append({ 73 | 'key': get_group.findall(g)[0], 74 | 'data': {} 75 | }) 76 | 77 | elif is_end.match(g): 78 | endk = output.pop() 79 | k = u'{}'.format(endk['key']) 80 | output[-1]['data'][k] = endk['data'] 81 | 82 | else: 83 | k, d = _parse_data(g) 84 | if k: 85 | k = u'{}'.format(k) 86 | output[-1]['data'][k] = d 87 | 88 | return output[0]['data'] 89 | 90 | 91 | def _cast_to_best_type(kd): 92 | key, data = kd[0] 93 | try: 94 | return key, int(data) 95 | except ValueError: 96 | try: 97 | return key, float(data) 98 | except ValueError: 99 | return key, u'{}'.format(data.strip('"')) 100 | 101 | 102 | def _parse_data(line): 103 | kd = re.findall(r'(.*)\s\=\s(.*)', line) 104 | 105 | if len(kd) == 0: 106 | return False, False 107 | else: 108 | return _cast_to_best_type(kd) 109 | 110 | 111 | def _get_bounds_from_metadata(product_metadata): 112 | corners = ['LL', 'LR', 'UR', 'UL'] 113 | lats = [product_metadata["CORNER_{}_LAT_PRODUCT".format(i)] 114 | for i in corners] 115 | lngs = [product_metadata["CORNER_{}_LON_PRODUCT".format(i)] 116 | for i in corners] 117 | 118 | return [min(lngs), min(lats), max(lngs), max(lats)] 119 | 120 | 121 | def rescale(arr, rescale_factor, dtype, clip=True): 122 | """Convert an array from 0..1 to dtype, scaling up linearly 123 | """ 124 | arr = arr.copy() # avoid mutating the original data 125 | if clip: 126 | arr[arr < 0.0] = 0.0 127 | arr[arr > 1.0] = 1.0 128 | arr *= rescale_factor 129 | 130 | # check overflow if destination is int/uint and not clipped 131 | if not clip and np.issubdtype(dtype, np.integer): 132 | if arr.max() > np.iinfo(dtype).max or \ 133 | arr.min() < np.iinfo(dtype).min: 134 | raise ValueError( 135 | "Cannot safely cast to {} without losing data" 136 | "; Reduce the --rescaling-factor or use --clip".format(dtype)) 137 | 138 | return arr.astype(dtype) 139 | 140 | 141 | def temp_rescale(arr, temp_scale): 142 | if temp_scale == 'K': 143 | return arr 144 | 145 | elif temp_scale == 'F': 146 | return arr * (9 / 5.0) - 459.67 147 | 148 | elif temp_scale == 'C': 149 | return arr - 273.15 150 | 151 | else: 152 | raise ValueError('%s is not a valid temperature scale' 153 | % (temp_scale)) 154 | 155 | 156 | def normalize_scale(rescale_factor, dtype): 157 | default_scales = { 158 | 'uint8': 255, 159 | 'uint16': 65535, 160 | 'float32': 1.0} 161 | 162 | if not rescale_factor: 163 | try: 164 | rescale_factor = default_scales[dtype] 165 | except KeyError: 166 | rescale_factor = 1.0 167 | 168 | return rescale_factor 169 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from setuptools import setup, find_packages 4 | from setuptools.extension import Extension 5 | 6 | # Parse the version from the fiona module. 7 | with open('rio_toa/__init__.py') as f: 8 | for line in f: 9 | if line.find("__version__") >= 0: 10 | version = line.split("=")[1].strip() 11 | version = version.strip('"') 12 | version = version.strip("'") 13 | break 14 | 15 | long_description = """""" 16 | 17 | 18 | def read(fname): 19 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 20 | 21 | setup(name='rio-toa', 22 | version=version, 23 | description=u"Top Of Atmosphere (TOA) calculations for Landsat 8", 24 | long_description=long_description, 25 | classifiers=[], 26 | keywords='', 27 | author=u"Damon Burgett", 28 | author_email='damon@mapbox.com', 29 | url='https://github.com/mapbox/rio-toa', 30 | license='BSD', 31 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), 32 | include_package_data=True, 33 | zip_safe=False, 34 | install_requires=["click", "rasterio", "rio-mucho"], 35 | extras_require={ 36 | 'test': ['pytest', 'hypothesis', 'pytest-cov', 'codecov']}, 37 | entry_points=""" 38 | [rasterio.rio_plugins] 39 | toa=rio_toa.scripts.cli:toa 40 | """ 41 | ) 42 | -------------------------------------------------------------------------------- /tests/data/LC80100202015018LGN00_MTL.json: -------------------------------------------------------------------------------- 1 | { 2 | "L1_METADATA_FILE": { 3 | "IMAGE_ATTRIBUTES": { 4 | "GROUND_CONTROL_POINTS_VERSION": 2, 5 | "EARTH_SUN_DISTANCE": 0.9838797, 6 | "GEOMETRIC_RMSE_MODEL": 15.073, 7 | "CLOUD_COVER": 19.74, 8 | "IMAGE_QUALITY_TIRS": 9, 9 | "SUN_AZIMUTH": 164.19023018, 10 | "SUN_ELEVATION": 11.10898916, 11 | "ROLL_ANGLE": -0.001, 12 | "GROUND_CONTROL_POINTS_MODEL": 45, 13 | "GEOMETRIC_RMSE_MODEL_X": 10.119, 14 | "GEOMETRIC_RMSE_MODEL_Y": 11.171, 15 | "IMAGE_QUALITY_OLI": 9 16 | }, 17 | "TIRS_THERMAL_CONSTANTS": { 18 | "K2_CONSTANT_BAND_10": 1321.08, 19 | "K2_CONSTANT_BAND_11": 1201.14, 20 | "K1_CONSTANT_BAND_10": 774.89, 21 | "K1_CONSTANT_BAND_11": 480.89 22 | }, 23 | "RADIOMETRIC_RESCALING": { 24 | "RADIANCE_MULT_BAND_7": 0.00052941, 25 | "RADIANCE_MULT_BAND_6": 0.0015707, 26 | "RADIANCE_MULT_BAND_5": 0.0063158, 27 | "RADIANCE_MULT_BAND_4": 0.010321, 28 | "RADIANCE_MULT_BAND_3": 0.012239, 29 | "RADIANCE_MULT_BAND_2": 0.013282, 30 | "RADIANCE_MULT_BAND_1": 0.012971, 31 | "RADIANCE_MULT_BAND_9": 0.0024684, 32 | "RADIANCE_MULT_BAND_8": 0.01168, 33 | "RADIANCE_ADD_BAND_9": -12.34186, 34 | "RADIANCE_ADD_BAND_8": -58.40173, 35 | "RADIANCE_MULT_BAND_11": 0.0, 36 | "RADIANCE_MULT_BAND_10": 0.0, 37 | "REFLECTANCE_ADD_BAND_9": -0.1, 38 | "REFLECTANCE_ADD_BAND_8": -0.1, 39 | "RADIANCE_ADD_BAND_1": -64.85281, 40 | "REFLECTANCE_ADD_BAND_6": -0.1, 41 | "RADIANCE_ADD_BAND_3": -61.19631, 42 | "RADIANCE_ADD_BAND_2": -66.41007, 43 | "RADIANCE_ADD_BAND_5": -31.57918, 44 | "RADIANCE_ADD_BAND_4": -51.60418, 45 | "RADIANCE_ADD_BAND_7": -2.64703, 46 | "RADIANCE_ADD_BAND_6": -7.85346, 47 | "REFLECTANCE_MULT_BAND_9": 2e-05, 48 | "REFLECTANCE_MULT_BAND_8": 2e-05, 49 | "REFLECTANCE_MULT_BAND_1": 2e-05, 50 | "REFLECTANCE_MULT_BAND_3": 2e-05, 51 | "REFLECTANCE_MULT_BAND_2": 2e-05, 52 | "REFLECTANCE_MULT_BAND_5": 2e-05, 53 | "REFLECTANCE_MULT_BAND_4": 2e-05, 54 | "REFLECTANCE_MULT_BAND_7": 2e-05, 55 | "REFLECTANCE_MULT_BAND_6": 2e-05, 56 | "REFLECTANCE_ADD_BAND_7": -0.1, 57 | "REFLECTANCE_ADD_BAND_5": -0.1, 58 | "REFLECTANCE_ADD_BAND_4": -0.1, 59 | "RADIANCE_ADD_BAND_11": 0.1, 60 | "RADIANCE_ADD_BAND_10": 0.1, 61 | "REFLECTANCE_ADD_BAND_3": -0.1, 62 | "REFLECTANCE_ADD_BAND_2": -0.1, 63 | "REFLECTANCE_ADD_BAND_1": -0.1 64 | }, 65 | "PRODUCT_METADATA": { 66 | "PANCHROMATIC_LINES": 16121, 67 | "NADIR_OFFNADIR": "NADIR", 68 | "DATA_TYPE": "L1T", 69 | "CORNER_LR_LAT_PRODUCT": 56.18273, 70 | "SCENE_CENTER_TIME": "15:10:22.4142571Z", 71 | "CORNER_LR_PROJECTION_Y_PRODUCT": 6231300.0, 72 | "CORNER_LL_PROJECTION_X_PRODUCT": 465000.0, 73 | "FILE_NAME_BAND_QUALITY": "LC80100202015018LGN00_BQA.TIF", 74 | "CORNER_UR_PROJECTION_Y_PRODUCT": 6473100.0, 75 | "CORNER_LL_PROJECTION_Y_PRODUCT": 6231300.0, 76 | "TARGET_WRS_ROW": 20, 77 | "BPF_NAME_TIRS": "LT8BPF20150106000000_20150131235959.03", 78 | "THERMAL_LINES": 8061, 79 | "CORNER_LR_PROJECTION_X_PRODUCT": 704400.0, 80 | "FILE_NAME_BAND_3": "LC80100202015018LGN00_B3.TIF", 81 | "CORNER_LL_LAT_PRODUCT": 56.22531, 82 | "FILE_NAME_BAND_1": "LC80100202015018LGN00_B1.TIF", 83 | "REFLECTIVE_LINES": 8061, 84 | "FILE_NAME_BAND_7": "LC80100202015018LGN00_B7.TIF", 85 | "FILE_NAME_BAND_6": "LC80100202015018LGN00_B6.TIF", 86 | "FILE_NAME_BAND_5": "LC80100202015018LGN00_B5.TIF", 87 | "FILE_NAME_BAND_4": "LC80100202015018LGN00_B4.TIF", 88 | "FILE_NAME_BAND_9": "LC80100202015018LGN00_B9.TIF", 89 | "FILE_NAME_BAND_8": "LC80100202015018LGN00_B8.TIF", 90 | "SPACECRAFT_ID": "LANDSAT_8", 91 | "ELEVATION_SOURCE": "GLS2000", 92 | "CPF_NAME": "L8CPF20150101_20150331.01", 93 | "CORNER_UR_LON_PRODUCT": -59.50678, 94 | "FILE_NAME_BAND_11": "LC80100202015018LGN00_B11.TIF", 95 | "CORNER_LR_LON_PRODUCT": -59.70643, 96 | "CORNER_UL_PROJECTION_X_PRODUCT": 465000.0, 97 | "PANCHROMATIC_SAMPLES": 15961, 98 | "SENSOR_ID": "OLI_TIRS", 99 | "FILE_NAME_BAND_2": "LC80100202015018LGN00_B2.TIF", 100 | "WRS_PATH": 10, 101 | "OUTPUT_FORMAT": "GEOTIFF", 102 | "FILE_NAME_BAND_10": "LC80100202015018LGN00_B10.TIF", 103 | "WRS_ROW": 20, 104 | "CORNER_UL_PROJECTION_Y_PRODUCT": 6473100.0, 105 | "BPF_NAME_OLI": "LO8BPF20150118150923_20150118182534.01", 106 | "TARGET_WRS_PATH": 10, 107 | "CORNER_UL_LON_PRODUCT": -63.59878, 108 | "THERMAL_SAMPLES": 7981, 109 | "METADATA_FILE_NAME": "LC80100202015018LGN00_MTL.txt", 110 | "REFLECTIVE_SAMPLES": 7981, 111 | "CORNER_UR_PROJECTION_X_PRODUCT": 704400.0, 112 | "RLUT_FILE_NAME": "L8RLUT20130211_20431231v09.h5", 113 | "CORNER_UL_LAT_PRODUCT": 58.3973, 114 | "CORNER_UR_LAT_PRODUCT": 58.35104, 115 | "CORNER_LL_LON_PRODUCT": -63.56448, 116 | "DATE_ACQUIRED": "2015-01-18" 117 | }, 118 | "PROJECTION_PARAMETERS": { 119 | "UTM_ZONE": 20, 120 | "GRID_CELL_SIZE_REFLECTIVE": 30.0, 121 | "MAP_PROJECTION": "UTM", 122 | "ORIENTATION": "NORTH_UP", 123 | "ELLIPSOID": "WGS84", 124 | "GRID_CELL_SIZE_THERMAL": 30.0, 125 | "DATUM": "WGS84", 126 | "GRID_CELL_SIZE_PANCHROMATIC": 15.0, 127 | "RESAMPLING_OPTION": "CUBIC_CONVOLUTION" 128 | }, 129 | "METADATA_FILE_INFO": { 130 | "ORIGIN": "Image courtesy of the U.S. Geological Survey", 131 | "LANDSAT_SCENE_ID": "LC80100202015018LGN00", 132 | "PROCESSING_SOFTWARE_VERSION": "LPGS_2.4.0", 133 | "FILE_DATE": "2015-01-18T19:30:44Z", 134 | "STATION_ID": "LGN", 135 | "REQUEST_ID": "0501501184561_00001" 136 | }, 137 | "MIN_MAX_PIXEL_VALUE": { 138 | "QUANTIZE_CAL_MAX_BAND_5": 65535, 139 | "QUANTIZE_CAL_MAX_BAND_4": 65535, 140 | "QUANTIZE_CAL_MAX_BAND_7": 65535, 141 | "QUANTIZE_CAL_MAX_BAND_6": 65535, 142 | "QUANTIZE_CAL_MAX_BAND_1": 65535, 143 | "QUANTIZE_CAL_MAX_BAND_3": 65535, 144 | "QUANTIZE_CAL_MAX_BAND_2": 65535, 145 | "QUANTIZE_CAL_MAX_BAND_9": 65535, 146 | "QUANTIZE_CAL_MAX_BAND_8": 65535, 147 | "QUANTIZE_CAL_MIN_BAND_9": 1, 148 | "QUANTIZE_CAL_MIN_BAND_8": 1, 149 | "QUANTIZE_CAL_MIN_BAND_7": 1, 150 | "QUANTIZE_CAL_MIN_BAND_6": 1, 151 | "QUANTIZE_CAL_MIN_BAND_5": 1, 152 | "QUANTIZE_CAL_MIN_BAND_4": 1, 153 | "QUANTIZE_CAL_MIN_BAND_3": 1, 154 | "QUANTIZE_CAL_MIN_BAND_2": 1, 155 | "QUANTIZE_CAL_MIN_BAND_1": 1, 156 | "QUANTIZE_CAL_MIN_BAND_11": 1, 157 | "QUANTIZE_CAL_MIN_BAND_10": 1, 158 | "QUANTIZE_CAL_MAX_BAND_11": 65535, 159 | "QUANTIZE_CAL_MAX_BAND_10": 65535 160 | }, 161 | "MIN_MAX_RADIANCE": { 162 | "RADIANCE_MINIMUM_BAND_6": -7.85188, 163 | "RADIANCE_MINIMUM_BAND_7": -2.64651, 164 | "RADIANCE_MINIMUM_BAND_4": -51.59386, 165 | "RADIANCE_MINIMUM_BAND_5": -31.57287, 166 | "RADIANCE_MINIMUM_BAND_2": -66.39679, 167 | "RADIANCE_MINIMUM_BAND_3": -61.18407, 168 | "RADIANCE_MINIMUM_BAND_1": -64.83984, 169 | "RADIANCE_MINIMUM_BAND_8": -58.39005, 170 | "RADIANCE_MINIMUM_BAND_9": -12.33939, 171 | "RADIANCE_MINIMUM_BAND_10": 0.1, 172 | "RADIANCE_MINIMUM_BAND_11": 0.1, 173 | "RADIANCE_MAXIMUM_BAND_10": 0.1, 174 | "RADIANCE_MAXIMUM_BAND_11": 0.1, 175 | "RADIANCE_MAXIMUM_BAND_8": 707.0697, 176 | "RADIANCE_MAXIMUM_BAND_9": 149.42291, 177 | "RADIANCE_MAXIMUM_BAND_1": 785.17297, 178 | "RADIANCE_MAXIMUM_BAND_2": 804.02673, 179 | "RADIANCE_MAXIMUM_BAND_3": 740.90375, 180 | "RADIANCE_MAXIMUM_BAND_4": 624.77179, 181 | "RADIANCE_MAXIMUM_BAND_5": 382.32916, 182 | "RADIANCE_MAXIMUM_BAND_6": 95.08179, 183 | "RADIANCE_MAXIMUM_BAND_7": 32.04765 184 | }, 185 | "MIN_MAX_REFLECTANCE": { 186 | "REFLECTANCE_MAXIMUM_BAND_8": 1.2107, 187 | "REFLECTANCE_MINIMUM_BAND_8": -0.09998, 188 | "REFLECTANCE_MAXIMUM_BAND_9": 1.2107, 189 | "REFLECTANCE_MINIMUM_BAND_9": -0.09998, 190 | "REFLECTANCE_MINIMUM_BAND_4": -0.09998, 191 | "REFLECTANCE_MINIMUM_BAND_5": -0.09998, 192 | "REFLECTANCE_MINIMUM_BAND_6": -0.09998, 193 | "REFLECTANCE_MINIMUM_BAND_7": -0.09998, 194 | "REFLECTANCE_MINIMUM_BAND_1": -0.09998, 195 | "REFLECTANCE_MINIMUM_BAND_2": -0.09998, 196 | "REFLECTANCE_MINIMUM_BAND_3": -0.09998, 197 | "REFLECTANCE_MAXIMUM_BAND_6": 1.2107, 198 | "REFLECTANCE_MAXIMUM_BAND_7": 1.2107, 199 | "REFLECTANCE_MAXIMUM_BAND_4": 1.2107, 200 | "REFLECTANCE_MAXIMUM_BAND_5": 1.2107, 201 | "REFLECTANCE_MAXIMUM_BAND_2": 1.2107, 202 | "REFLECTANCE_MAXIMUM_BAND_3": 1.2107, 203 | "REFLECTANCE_MAXIMUM_BAND_1": 1.2107 204 | } 205 | } 206 | } -------------------------------------------------------------------------------- /tests/data/LC80100202015018LGN00_MTL.txt: -------------------------------------------------------------------------------- 1 | GROUP = L1_METADATA_FILE 2 | GROUP = METADATA_FILE_INFO 3 | ORIGIN = "Image courtesy of the U.S. Geological Survey" 4 | REQUEST_ID = "0501501184561_00001" 5 | LANDSAT_SCENE_ID = "LC80100202015018LGN00" 6 | FILE_DATE = 2015-01-18T19:30:44Z 7 | STATION_ID = "LGN" 8 | PROCESSING_SOFTWARE_VERSION = "LPGS_2.4.0" 9 | END_GROUP = METADATA_FILE_INFO 10 | GROUP = PRODUCT_METADATA 11 | DATA_TYPE = "L1T" 12 | ELEVATION_SOURCE = "GLS2000" 13 | OUTPUT_FORMAT = "GEOTIFF" 14 | SPACECRAFT_ID = "LANDSAT_8" 15 | SENSOR_ID = "OLI_TIRS" 16 | WRS_PATH = 10 17 | WRS_ROW = 20 18 | NADIR_OFFNADIR = "NADIR" 19 | TARGET_WRS_PATH = 10 20 | TARGET_WRS_ROW = 20 21 | DATE_ACQUIRED = 2015-01-18 22 | SCENE_CENTER_TIME = 15:10:22.4142571Z 23 | CORNER_UL_LAT_PRODUCT = 58.39730 24 | CORNER_UL_LON_PRODUCT = -63.59878 25 | CORNER_UR_LAT_PRODUCT = 58.35104 26 | CORNER_UR_LON_PRODUCT = -59.50678 27 | CORNER_LL_LAT_PRODUCT = 56.22531 28 | CORNER_LL_LON_PRODUCT = -63.56448 29 | CORNER_LR_LAT_PRODUCT = 56.18273 30 | CORNER_LR_LON_PRODUCT = -59.70643 31 | CORNER_UL_PROJECTION_X_PRODUCT = 465000.000 32 | CORNER_UL_PROJECTION_Y_PRODUCT = 6473100.000 33 | CORNER_UR_PROJECTION_X_PRODUCT = 704400.000 34 | CORNER_UR_PROJECTION_Y_PRODUCT = 6473100.000 35 | CORNER_LL_PROJECTION_X_PRODUCT = 465000.000 36 | CORNER_LL_PROJECTION_Y_PRODUCT = 6231300.000 37 | CORNER_LR_PROJECTION_X_PRODUCT = 704400.000 38 | CORNER_LR_PROJECTION_Y_PRODUCT = 6231300.000 39 | PANCHROMATIC_LINES = 16121 40 | PANCHROMATIC_SAMPLES = 15961 41 | REFLECTIVE_LINES = 8061 42 | REFLECTIVE_SAMPLES = 7981 43 | THERMAL_LINES = 8061 44 | THERMAL_SAMPLES = 7981 45 | FILE_NAME_BAND_1 = "LC80100202015018LGN00_B1.TIF" 46 | FILE_NAME_BAND_2 = "LC80100202015018LGN00_B2.TIF" 47 | FILE_NAME_BAND_3 = "LC80100202015018LGN00_B3.TIF" 48 | FILE_NAME_BAND_4 = "LC80100202015018LGN00_B4.TIF" 49 | FILE_NAME_BAND_5 = "LC80100202015018LGN00_B5.TIF" 50 | FILE_NAME_BAND_6 = "LC80100202015018LGN00_B6.TIF" 51 | FILE_NAME_BAND_7 = "LC80100202015018LGN00_B7.TIF" 52 | FILE_NAME_BAND_8 = "LC80100202015018LGN00_B8.TIF" 53 | FILE_NAME_BAND_9 = "LC80100202015018LGN00_B9.TIF" 54 | FILE_NAME_BAND_10 = "LC80100202015018LGN00_B10.TIF" 55 | FILE_NAME_BAND_11 = "LC80100202015018LGN00_B11.TIF" 56 | FILE_NAME_BAND_QUALITY = "LC80100202015018LGN00_BQA.TIF" 57 | METADATA_FILE_NAME = "LC80100202015018LGN00_MTL.txt" 58 | BPF_NAME_OLI = "LO8BPF20150118150923_20150118182534.01" 59 | BPF_NAME_TIRS = "LT8BPF20150106000000_20150131235959.03" 60 | CPF_NAME = "L8CPF20150101_20150331.01" 61 | RLUT_FILE_NAME = "L8RLUT20130211_20431231v09.h5" 62 | END_GROUP = PRODUCT_METADATA 63 | GROUP = IMAGE_ATTRIBUTES 64 | CLOUD_COVER = 19.74 65 | IMAGE_QUALITY_OLI = 9 66 | IMAGE_QUALITY_TIRS = 9 67 | ROLL_ANGLE = -0.001 68 | SUN_AZIMUTH = 164.19023018 69 | SUN_ELEVATION = 11.10898916 70 | EARTH_SUN_DISTANCE = 0.9838797 71 | GROUND_CONTROL_POINTS_VERSION = 2 72 | GROUND_CONTROL_POINTS_MODEL = 45 73 | GEOMETRIC_RMSE_MODEL = 15.073 74 | GEOMETRIC_RMSE_MODEL_Y = 11.171 75 | GEOMETRIC_RMSE_MODEL_X = 10.119 76 | END_GROUP = IMAGE_ATTRIBUTES 77 | GROUP = MIN_MAX_RADIANCE 78 | RADIANCE_MAXIMUM_BAND_1 = 785.17297 79 | RADIANCE_MINIMUM_BAND_1 = -64.83984 80 | RADIANCE_MAXIMUM_BAND_2 = 804.02673 81 | RADIANCE_MINIMUM_BAND_2 = -66.39679 82 | RADIANCE_MAXIMUM_BAND_3 = 740.90375 83 | RADIANCE_MINIMUM_BAND_3 = -61.18407 84 | RADIANCE_MAXIMUM_BAND_4 = 624.77179 85 | RADIANCE_MINIMUM_BAND_4 = -51.59386 86 | RADIANCE_MAXIMUM_BAND_5 = 382.32916 87 | RADIANCE_MINIMUM_BAND_5 = -31.57287 88 | RADIANCE_MAXIMUM_BAND_6 = 95.08179 89 | RADIANCE_MINIMUM_BAND_6 = -7.85188 90 | RADIANCE_MAXIMUM_BAND_7 = 32.04765 91 | RADIANCE_MINIMUM_BAND_7 = -2.64651 92 | RADIANCE_MAXIMUM_BAND_8 = 707.06970 93 | RADIANCE_MINIMUM_BAND_8 = -58.39005 94 | RADIANCE_MAXIMUM_BAND_9 = 149.42291 95 | RADIANCE_MINIMUM_BAND_9 = -12.33939 96 | RADIANCE_MAXIMUM_BAND_10 = 0.10000 97 | RADIANCE_MINIMUM_BAND_10 = 0.10000 98 | RADIANCE_MAXIMUM_BAND_11 = 0.10000 99 | RADIANCE_MINIMUM_BAND_11 = 0.10000 100 | END_GROUP = MIN_MAX_RADIANCE 101 | GROUP = MIN_MAX_REFLECTANCE 102 | REFLECTANCE_MAXIMUM_BAND_1 = 1.210700 103 | REFLECTANCE_MINIMUM_BAND_1 = -0.099980 104 | REFLECTANCE_MAXIMUM_BAND_2 = 1.210700 105 | REFLECTANCE_MINIMUM_BAND_2 = -0.099980 106 | REFLECTANCE_MAXIMUM_BAND_3 = 1.210700 107 | REFLECTANCE_MINIMUM_BAND_3 = -0.099980 108 | REFLECTANCE_MAXIMUM_BAND_4 = 1.210700 109 | REFLECTANCE_MINIMUM_BAND_4 = -0.099980 110 | REFLECTANCE_MAXIMUM_BAND_5 = 1.210700 111 | REFLECTANCE_MINIMUM_BAND_5 = -0.099980 112 | REFLECTANCE_MAXIMUM_BAND_6 = 1.210700 113 | REFLECTANCE_MINIMUM_BAND_6 = -0.099980 114 | REFLECTANCE_MAXIMUM_BAND_7 = 1.210700 115 | REFLECTANCE_MINIMUM_BAND_7 = -0.099980 116 | REFLECTANCE_MAXIMUM_BAND_8 = 1.210700 117 | REFLECTANCE_MINIMUM_BAND_8 = -0.099980 118 | REFLECTANCE_MAXIMUM_BAND_9 = 1.210700 119 | REFLECTANCE_MINIMUM_BAND_9 = -0.099980 120 | END_GROUP = MIN_MAX_REFLECTANCE 121 | GROUP = MIN_MAX_PIXEL_VALUE 122 | QUANTIZE_CAL_MAX_BAND_1 = 65535 123 | QUANTIZE_CAL_MIN_BAND_1 = 1 124 | QUANTIZE_CAL_MAX_BAND_2 = 65535 125 | QUANTIZE_CAL_MIN_BAND_2 = 1 126 | QUANTIZE_CAL_MAX_BAND_3 = 65535 127 | QUANTIZE_CAL_MIN_BAND_3 = 1 128 | QUANTIZE_CAL_MAX_BAND_4 = 65535 129 | QUANTIZE_CAL_MIN_BAND_4 = 1 130 | QUANTIZE_CAL_MAX_BAND_5 = 65535 131 | QUANTIZE_CAL_MIN_BAND_5 = 1 132 | QUANTIZE_CAL_MAX_BAND_6 = 65535 133 | QUANTIZE_CAL_MIN_BAND_6 = 1 134 | QUANTIZE_CAL_MAX_BAND_7 = 65535 135 | QUANTIZE_CAL_MIN_BAND_7 = 1 136 | QUANTIZE_CAL_MAX_BAND_8 = 65535 137 | QUANTIZE_CAL_MIN_BAND_8 = 1 138 | QUANTIZE_CAL_MAX_BAND_9 = 65535 139 | QUANTIZE_CAL_MIN_BAND_9 = 1 140 | QUANTIZE_CAL_MAX_BAND_10 = 65535 141 | QUANTIZE_CAL_MIN_BAND_10 = 1 142 | QUANTIZE_CAL_MAX_BAND_11 = 65535 143 | QUANTIZE_CAL_MIN_BAND_11 = 1 144 | END_GROUP = MIN_MAX_PIXEL_VALUE 145 | GROUP = RADIOMETRIC_RESCALING 146 | RADIANCE_MULT_BAND_1 = 1.2971E-02 147 | RADIANCE_MULT_BAND_2 = 1.3282E-02 148 | RADIANCE_MULT_BAND_3 = 1.2239E-02 149 | RADIANCE_MULT_BAND_4 = 1.0321E-02 150 | RADIANCE_MULT_BAND_5 = 6.3158E-03 151 | RADIANCE_MULT_BAND_6 = 1.5707E-03 152 | RADIANCE_MULT_BAND_7 = 5.2941E-04 153 | RADIANCE_MULT_BAND_8 = 1.1680E-02 154 | RADIANCE_MULT_BAND_9 = 2.4684E-03 155 | RADIANCE_MULT_BAND_10 = 0.0000E+00 156 | RADIANCE_MULT_BAND_11 = 0.0000E+00 157 | RADIANCE_ADD_BAND_1 = -64.85281 158 | RADIANCE_ADD_BAND_2 = -66.41007 159 | RADIANCE_ADD_BAND_3 = -61.19631 160 | RADIANCE_ADD_BAND_4 = -51.60418 161 | RADIANCE_ADD_BAND_5 = -31.57918 162 | RADIANCE_ADD_BAND_6 = -7.85346 163 | RADIANCE_ADD_BAND_7 = -2.64703 164 | RADIANCE_ADD_BAND_8 = -58.40173 165 | RADIANCE_ADD_BAND_9 = -12.34186 166 | RADIANCE_ADD_BAND_10 = 0.10000 167 | RADIANCE_ADD_BAND_11 = 0.10000 168 | REFLECTANCE_MULT_BAND_1 = 2.0000E-05 169 | REFLECTANCE_MULT_BAND_2 = 2.0000E-05 170 | REFLECTANCE_MULT_BAND_3 = 2.0000E-05 171 | REFLECTANCE_MULT_BAND_4 = 2.0000E-05 172 | REFLECTANCE_MULT_BAND_5 = 2.0000E-05 173 | REFLECTANCE_MULT_BAND_6 = 2.0000E-05 174 | REFLECTANCE_MULT_BAND_7 = 2.0000E-05 175 | REFLECTANCE_MULT_BAND_8 = 2.0000E-05 176 | REFLECTANCE_MULT_BAND_9 = 2.0000E-05 177 | REFLECTANCE_ADD_BAND_1 = -0.100000 178 | REFLECTANCE_ADD_BAND_2 = -0.100000 179 | REFLECTANCE_ADD_BAND_3 = -0.100000 180 | REFLECTANCE_ADD_BAND_4 = -0.100000 181 | REFLECTANCE_ADD_BAND_5 = -0.100000 182 | REFLECTANCE_ADD_BAND_6 = -0.100000 183 | REFLECTANCE_ADD_BAND_7 = -0.100000 184 | REFLECTANCE_ADD_BAND_8 = -0.100000 185 | REFLECTANCE_ADD_BAND_9 = -0.100000 186 | END_GROUP = RADIOMETRIC_RESCALING 187 | GROUP = TIRS_THERMAL_CONSTANTS 188 | K1_CONSTANT_BAND_10 = 774.89 189 | K1_CONSTANT_BAND_11 = 480.89 190 | K2_CONSTANT_BAND_10 = 1321.08 191 | K2_CONSTANT_BAND_11 = 1201.14 192 | END_GROUP = TIRS_THERMAL_CONSTANTS 193 | GROUP = PROJECTION_PARAMETERS 194 | MAP_PROJECTION = "UTM" 195 | DATUM = "WGS84" 196 | ELLIPSOID = "WGS84" 197 | UTM_ZONE = 20 198 | GRID_CELL_SIZE_PANCHROMATIC = 15.00 199 | GRID_CELL_SIZE_REFLECTIVE = 30.00 200 | GRID_CELL_SIZE_THERMAL = 30.00 201 | ORIENTATION = "NORTH_UP" 202 | RESAMPLING_OPTION = "CUBIC_CONVOLUTION" 203 | END_GROUP = PROJECTION_PARAMETERS 204 | END_GROUP = L1_METADATA_FILE 205 | END 206 | -------------------------------------------------------------------------------- /tests/data/LC80430302016140LGN00_MTL.json: -------------------------------------------------------------------------------- 1 | {"L1_METADATA_FILE": {"IMAGE_ATTRIBUTES": {"GROUND_CONTROL_POINTS_VERIFY": 78, "GROUND_CONTROL_POINTS_VERSION": 4, "EARTH_SUN_DISTANCE": 1.0118752, "GEOMETRIC_RMSE_MODEL": 7.397, "CLOUD_COVER": 14.87, "IMAGE_QUALITY_TIRS": 7, "SUN_AZIMUTH": 140.61938851, "GEOMETRIC_RMSE_VERIFY": 3.663, "SUN_ELEVATION": 62.13510937, "TIRS_SSM_POSITION_STATUS": "ESTIMATED", "ROLL_ANGLE": -0.001, "CLOUD_COVER_LAND": 14.87, "GROUND_CONTROL_POINTS_MODEL": 268, "GEOMETRIC_RMSE_MODEL_X": 4.856, "GEOMETRIC_RMSE_MODEL_Y": 5.579, "TIRS_SSM_MODEL": "PRELIMINARY", "IMAGE_QUALITY_OLI": 9}, "TIRS_THERMAL_CONSTANTS": {"K2_CONSTANT_BAND_10": 1321.0789, "K2_CONSTANT_BAND_11": 1201.1442, "K1_CONSTANT_BAND_10": 774.8853, "K1_CONSTANT_BAND_11": 480.8883}, "RADIOMETRIC_RESCALING": {"RADIANCE_MULT_BAND_7": 0.00050052, "RADIANCE_MULT_BAND_6": 0.001485, "RADIANCE_MULT_BAND_5": 0.0059712, "RADIANCE_MULT_BAND_4": 0.0097576, "RADIANCE_MULT_BAND_3": 0.011571, "RADIANCE_MULT_BAND_2": 0.012557, "RADIANCE_MULT_BAND_1": 0.012263, "RADIANCE_MULT_BAND_9": 0.0023337, "RADIANCE_MULT_BAND_8": 0.011043, "RADIANCE_ADD_BAND_9": -11.66839, "RADIANCE_ADD_BAND_8": -55.21484, "RADIANCE_MULT_BAND_11": 0.0003342, "RADIANCE_MULT_BAND_10": 0.0003342, "REFLECTANCE_ADD_BAND_9": -0.1, "REFLECTANCE_ADD_BAND_8": -0.1, "RADIANCE_ADD_BAND_1": -61.3139, "REFLECTANCE_ADD_BAND_6": -0.1, "RADIANCE_ADD_BAND_3": -57.85693, "RADIANCE_ADD_BAND_2": -62.78618, "RADIANCE_ADD_BAND_5": -29.85596, "RADIANCE_ADD_BAND_4": -48.78822, "RADIANCE_ADD_BAND_7": -2.50259, "RADIANCE_ADD_BAND_6": -7.42491, "REFLECTANCE_MULT_BAND_9": 2e-05, "REFLECTANCE_MULT_BAND_8": 2e-05, "REFLECTANCE_MULT_BAND_1": 2e-05, "REFLECTANCE_MULT_BAND_3": 2e-05, "REFLECTANCE_MULT_BAND_2": 2e-05, "REFLECTANCE_MULT_BAND_5": 2e-05, "REFLECTANCE_MULT_BAND_4": 2e-05, "REFLECTANCE_MULT_BAND_7": 2e-05, "REFLECTANCE_MULT_BAND_6": 2e-05, "REFLECTANCE_ADD_BAND_7": -0.1, "REFLECTANCE_ADD_BAND_5": -0.1, "REFLECTANCE_ADD_BAND_4": -0.1, "RADIANCE_ADD_BAND_11": 0.1, "RADIANCE_ADD_BAND_10": 0.1, "REFLECTANCE_ADD_BAND_3": -0.1, "REFLECTANCE_ADD_BAND_2": -0.1, "REFLECTANCE_ADD_BAND_1": -0.1}, "PRODUCT_METADATA": {"PANCHROMATIC_LINES": 16021, "NADIR_OFFNADIR": "NADIR", "DATA_TYPE": "L1T", "CORNER_LR_LAT_PRODUCT": 42.11246, "SCENE_CENTER_TIME": "18:37:53.6526080Z", "CORNER_LR_PROJECTION_Y_PRODUCT": 4662300.0, "CORNER_LL_PROJECTION_X_PRODUCT": 240300.0, "FILE_NAME_BAND_QUALITY": "LC80430302016140LGN00_BQA.TIF", "CORNER_UR_PROJECTION_Y_PRODUCT": 4902600.0, "CORNER_LL_PROJECTION_Y_PRODUCT": 4662300.0, "TARGET_WRS_ROW": 30, "BPF_NAME_TIRS": "LT8BPF20160507073029_20160507073845.01", "THERMAL_LINES": 8011, "CORNER_LR_PROJECTION_X_PRODUCT": 477000.0, "FILE_NAME_BAND_3": "LC80430302016140LGN00_B3.TIF", "CORNER_LL_LAT_PRODUCT": 42.06984, "FILE_NAME_BAND_1": "LC80430302016140LGN00_B1.TIF", "REFLECTIVE_LINES": 8011, "FILE_NAME_BAND_7": "LC80430302016140LGN00_B7.TIF", "FILE_NAME_BAND_6": "LC80430302016140LGN00_B6.TIF", "FILE_NAME_BAND_5": "LC80430302016140LGN00_B5.TIF", "FILE_NAME_BAND_4": "LC80430302016140LGN00_B4.TIF", "FILE_NAME_BAND_9": "LC80430302016140LGN00_B9.TIF", "FILE_NAME_BAND_8": "LC80430302016140LGN00_B8.TIF", "SPACECRAFT_ID": "LANDSAT_8", "ELEVATION_SOURCE": "GLS2000", "CPF_NAME": "L8CPF20160401_20160630.03", "CORNER_UR_LON_PRODUCT": -117.28822, "FILE_NAME_BAND_11": "LC80430302016140LGN00_B11.TIF", "CORNER_LR_LON_PRODUCT": -117.27821, "CORNER_UL_PROJECTION_X_PRODUCT": 240300.0, "PANCHROMATIC_SAMPLES": 15781, "SENSOR_ID": "OLI_TIRS", "FILE_NAME_BAND_2": "LC80430302016140LGN00_B2.TIF", "WRS_PATH": 43, "OUTPUT_FORMAT": "GEOTIFF", "FILE_NAME_BAND_10": "LC80430302016140LGN00_B10.TIF", "WRS_ROW": 30, "CORNER_UL_PROJECTION_Y_PRODUCT": 4902600.0, "BPF_NAME_OLI": "LO8BPF20160519182319_20160519185709.01", "TARGET_WRS_PATH": 43, "CORNER_UL_LON_PRODUCT": -120.25176, "THERMAL_SAMPLES": 7891, "METADATA_FILE_NAME": "LC80430302016140LGN00_MTL.txt", "REFLECTIVE_SAMPLES": 7891, "CORNER_UR_PROJECTION_X_PRODUCT": 477000.0, "RLUT_FILE_NAME": "L8RLUT20150303_20431231v11.h5", "CORNER_UL_LAT_PRODUCT": 44.23034, "CORNER_UR_LAT_PRODUCT": 44.27628, "CORNER_LL_LON_PRODUCT": -120.13908, "DATE_ACQUIRED": "2016-05-19"}, "PROJECTION_PARAMETERS": {"UTM_ZONE": 11, "GRID_CELL_SIZE_REFLECTIVE": 30.0, "MAP_PROJECTION": "UTM", "ORIENTATION": "NORTH_UP", "ELLIPSOID": "WGS84", "GRID_CELL_SIZE_THERMAL": 30.0, "DATUM": "WGS84", "GRID_CELL_SIZE_PANCHROMATIC": 15.0, "RESAMPLING_OPTION": "CUBIC_CONVOLUTION"}, "METADATA_FILE_INFO": {"ORIGIN": "Image courtesy of the U.S. Geological Survey", "LANDSAT_SCENE_ID": "LC80430302016140LGN00", "PROCESSING_SOFTWARE_VERSION": "LPGS_2.6.2", "FILE_DATE": "2016-05-20T04:11:30Z", "STATION_ID": "LGN", "REQUEST_ID": "0501605194410_00035"}, "MIN_MAX_PIXEL_VALUE": {"QUANTIZE_CAL_MAX_BAND_5": 65535, "QUANTIZE_CAL_MAX_BAND_4": 65535, "QUANTIZE_CAL_MAX_BAND_7": 65535, "QUANTIZE_CAL_MAX_BAND_6": 65535, "QUANTIZE_CAL_MAX_BAND_1": 65535, "QUANTIZE_CAL_MAX_BAND_3": 65535, "QUANTIZE_CAL_MAX_BAND_2": 65535, "QUANTIZE_CAL_MAX_BAND_9": 65535, "QUANTIZE_CAL_MAX_BAND_8": 65535, "QUANTIZE_CAL_MIN_BAND_9": 1, "QUANTIZE_CAL_MIN_BAND_8": 1, "QUANTIZE_CAL_MIN_BAND_7": 1, "QUANTIZE_CAL_MIN_BAND_6": 1, "QUANTIZE_CAL_MIN_BAND_5": 1, "QUANTIZE_CAL_MIN_BAND_4": 1, "QUANTIZE_CAL_MIN_BAND_3": 1, "QUANTIZE_CAL_MIN_BAND_2": 1, "QUANTIZE_CAL_MIN_BAND_1": 1, "QUANTIZE_CAL_MIN_BAND_11": 1, "QUANTIZE_CAL_MIN_BAND_10": 1, "QUANTIZE_CAL_MAX_BAND_11": 65535, "QUANTIZE_CAL_MAX_BAND_10": 65535}, "MIN_MAX_RADIANCE": {"RADIANCE_MINIMUM_BAND_6": -7.42342, "RADIANCE_MINIMUM_BAND_7": -2.50209, "RADIANCE_MINIMUM_BAND_4": -48.77847, "RADIANCE_MINIMUM_BAND_5": -29.84999, "RADIANCE_MINIMUM_BAND_2": -62.77362, "RADIANCE_MINIMUM_BAND_3": -57.84536, "RADIANCE_MINIMUM_BAND_1": -61.30164, "RADIANCE_MINIMUM_BAND_8": -55.2038, "RADIANCE_MINIMUM_BAND_9": -11.66605, "RADIANCE_MINIMUM_BAND_10": 0.10033, "RADIANCE_MINIMUM_BAND_11": 0.10033, "RADIANCE_MAXIMUM_BAND_10": 22.0018, "RADIANCE_MAXIMUM_BAND_11": 22.0018, "RADIANCE_MAXIMUM_BAND_8": 668.48608, "RADIANCE_MAXIMUM_BAND_9": 141.26915, "RADIANCE_MAXIMUM_BAND_1": 742.32739, "RADIANCE_MAXIMUM_BAND_2": 760.15228, "RADIANCE_MAXIMUM_BAND_3": 700.47382, "RADIANCE_MAXIMUM_BAND_4": 590.67902, "RADIANCE_MAXIMUM_BAND_5": 361.46609, "RADIANCE_MAXIMUM_BAND_6": 89.89333, "RADIANCE_MAXIMUM_BAND_7": 30.29886}, "MIN_MAX_REFLECTANCE": {"REFLECTANCE_MAXIMUM_BAND_8": 1.2107, "REFLECTANCE_MINIMUM_BAND_8": -0.09998, "REFLECTANCE_MAXIMUM_BAND_9": 1.2107, "REFLECTANCE_MINIMUM_BAND_9": -0.09998, "REFLECTANCE_MINIMUM_BAND_4": -0.09998, "REFLECTANCE_MINIMUM_BAND_5": -0.09998, "REFLECTANCE_MINIMUM_BAND_6": -0.09998, "REFLECTANCE_MINIMUM_BAND_7": -0.09998, "REFLECTANCE_MINIMUM_BAND_1": -0.09998, "REFLECTANCE_MINIMUM_BAND_2": -0.09998, "REFLECTANCE_MINIMUM_BAND_3": -0.09998, "REFLECTANCE_MAXIMUM_BAND_6": 1.2107, "REFLECTANCE_MAXIMUM_BAND_7": 1.2107, "REFLECTANCE_MAXIMUM_BAND_4": 1.2107, "REFLECTANCE_MAXIMUM_BAND_5": 1.2107, "REFLECTANCE_MAXIMUM_BAND_2": 1.2107, "REFLECTANCE_MAXIMUM_BAND_3": 1.2107, "REFLECTANCE_MAXIMUM_BAND_1": 1.2107}}} 2 | -------------------------------------------------------------------------------- /tests/data/LC80460282016177LGN00_MTL.json: -------------------------------------------------------------------------------- 1 | {"L1_METADATA_FILE": {"IMAGE_ATTRIBUTES": {"GROUND_CONTROL_POINTS_VERIFY": 174, "GROUND_CONTROL_POINTS_VERSION": 4, "EARTH_SUN_DISTANCE": 1.0165183, "GEOMETRIC_RMSE_MODEL": 6.261, "CLOUD_COVER": 11.3, "IMAGE_QUALITY_TIRS": 7, "SUN_AZIMUTH": 139.32619154, "GEOMETRIC_RMSE_VERIFY": 3.619, "SUN_ELEVATION": 62.58246948, "TIRS_SSM_POSITION_STATUS": "ESTIMATED", "ROLL_ANGLE": -0.001, "CLOUD_COVER_LAND": 11.3, "GROUND_CONTROL_POINTS_MODEL": 479, "GEOMETRIC_RMSE_MODEL_X": 4.115, "GEOMETRIC_RMSE_MODEL_Y": 4.719, "TIRS_SSM_MODEL": "PRELIMINARY", "IMAGE_QUALITY_OLI": 9}, "TIRS_THERMAL_CONSTANTS": {"K2_CONSTANT_BAND_10": 1321.0789, "K2_CONSTANT_BAND_11": 1201.1442, "K1_CONSTANT_BAND_10": 774.8853, "K1_CONSTANT_BAND_11": 480.8883}, "RADIOMETRIC_RESCALING": {"RADIANCE_MULT_BAND_7": 0.00049596, "RADIANCE_MULT_BAND_6": 0.0014714, "RADIANCE_MULT_BAND_5": 0.0059168, "RADIANCE_MULT_BAND_4": 0.0096687, "RADIANCE_MULT_BAND_3": 0.011466, "RADIANCE_MULT_BAND_2": 0.012443, "RADIANCE_MULT_BAND_1": 0.012151, "RADIANCE_MULT_BAND_9": 0.0023124, "RADIANCE_MULT_BAND_8": 0.010942, "RADIANCE_ADD_BAND_9": -11.56203, "RADIANCE_ADD_BAND_8": -54.71159, "RADIANCE_MULT_BAND_11": 0.0003342, "RADIANCE_MULT_BAND_10": 0.0003342, "REFLECTANCE_ADD_BAND_9": -0.1, "REFLECTANCE_ADD_BAND_8": -0.1, "RADIANCE_ADD_BAND_1": -60.75506, "REFLECTANCE_ADD_BAND_6": -0.1, "RADIANCE_ADD_BAND_3": -57.32959, "RADIANCE_ADD_BAND_2": -62.21392, "RADIANCE_ADD_BAND_5": -29.58384, "RADIANCE_ADD_BAND_4": -48.34354, "RADIANCE_ADD_BAND_7": -2.47978, "RADIANCE_ADD_BAND_6": -7.35723, "REFLECTANCE_MULT_BAND_9": 2e-05, "REFLECTANCE_MULT_BAND_8": 2e-05, "REFLECTANCE_MULT_BAND_1": 2e-05, "REFLECTANCE_MULT_BAND_3": 2e-05, "REFLECTANCE_MULT_BAND_2": 2e-05, "REFLECTANCE_MULT_BAND_5": 2e-05, "REFLECTANCE_MULT_BAND_4": 2e-05, "REFLECTANCE_MULT_BAND_7": 2e-05, "REFLECTANCE_MULT_BAND_6": 2e-05, "REFLECTANCE_ADD_BAND_7": -0.1, "REFLECTANCE_ADD_BAND_5": -0.1, "REFLECTANCE_ADD_BAND_4": -0.1, "RADIANCE_ADD_BAND_11": 0.1, "RADIANCE_ADD_BAND_10": 0.1, "REFLECTANCE_ADD_BAND_3": -0.1, "REFLECTANCE_ADD_BAND_2": -0.1, "REFLECTANCE_ADD_BAND_1": -0.1}, "PRODUCT_METADATA": {"PANCHROMATIC_LINES": 15821, "NADIR_OFFNADIR": "NADIR", "DATA_TYPE": "L1T", "CORNER_LR_LAT_PRODUCT": 44.9402, "SCENE_CENTER_TIME": "18:55:50.7858220Z", "CORNER_LR_PROJECTION_Y_PRODUCT": 4978500.0, "CORNER_LL_PROJECTION_X_PRODUCT": 433800.0, "FILE_NAME_BAND_QUALITY": "LC80460282016177LGN00_BQA.TIF", "CORNER_UR_PROJECTION_Y_PRODUCT": 5215800.0, "CORNER_LL_PROJECTION_Y_PRODUCT": 4978500.0, "TARGET_WRS_ROW": 28, "BPF_NAME_TIRS": "LT8BPF20160620173313_20160620195437.01", "THERMAL_LINES": 7911, "CORNER_LR_PROJECTION_X_PRODUCT": 667500.0, "FILE_NAME_BAND_3": "LC80460282016177LGN00_B3.TIF", "CORNER_LL_LAT_PRODUCT": 44.95685, "FILE_NAME_BAND_1": "LC80460282016177LGN00_B1.TIF", "REFLECTIVE_LINES": 7911, "FILE_NAME_BAND_7": "LC80460282016177LGN00_B7.TIF", "FILE_NAME_BAND_6": "LC80460282016177LGN00_B6.TIF", "FILE_NAME_BAND_5": "LC80460282016177LGN00_B5.TIF", "FILE_NAME_BAND_4": "LC80460282016177LGN00_B4.TIF", "FILE_NAME_BAND_9": "LC80460282016177LGN00_B9.TIF", "FILE_NAME_BAND_8": "LC80460282016177LGN00_B8.TIF", "SPACECRAFT_ID": "LANDSAT_8", "ELEVATION_SOURCE": "GLS2000", "CPF_NAME": "L8CPF20160401_20160630.03", "CORNER_UR_LON_PRODUCT": -120.79369, "FILE_NAME_BAND_11": "LC80460282016177LGN00_B11.TIF", "CORNER_LR_LON_PRODUCT": -120.87699, "CORNER_UL_PROJECTION_X_PRODUCT": 433800.0, "PANCHROMATIC_SAMPLES": 15581, "SENSOR_ID": "OLI_TIRS", "FILE_NAME_BAND_2": "LC80460282016177LGN00_B2.TIF", "WRS_PATH": 46, "OUTPUT_FORMAT": "GEOTIFF", "FILE_NAME_BAND_10": "LC80460282016177LGN00_B10.TIF", "WRS_ROW": 28, "CORNER_UL_PROJECTION_Y_PRODUCT": 5215800.0, "BPF_NAME_OLI": "LO8BPF20160625083937_20160625102938.01", "TARGET_WRS_PATH": 46, "CORNER_UL_LON_PRODUCT": -123.87227, "THERMAL_SAMPLES": 7791, "METADATA_FILE_NAME": "LC80460282016177LGN00_MTL.txt", "REFLECTIVE_SAMPLES": 7791, "CORNER_UR_PROJECTION_X_PRODUCT": 667500.0, "RLUT_FILE_NAME": "L8RLUT20150303_20431231v11.h5", "CORNER_UL_LAT_PRODUCT": 47.09239, "CORNER_UR_LAT_PRODUCT": 47.07445, "CORNER_LL_LON_PRODUCT": -123.83931, "DATE_ACQUIRED": "2016-06-25"}, "PROJECTION_PARAMETERS": {"UTM_ZONE": 10, "GRID_CELL_SIZE_REFLECTIVE": 30.0, "MAP_PROJECTION": "UTM", "ORIENTATION": "NORTH_UP", "ELLIPSOID": "WGS84", "GRID_CELL_SIZE_THERMAL": 30.0, "DATUM": "WGS84", "GRID_CELL_SIZE_PANCHROMATIC": 15.0, "RESAMPLING_OPTION": "CUBIC_CONVOLUTION"}, "METADATA_FILE_INFO": {"ORIGIN": "Image courtesy of the U.S. Geological Survey", "LANDSAT_SCENE_ID": "LC80460282016177LGN00", "PROCESSING_SOFTWARE_VERSION": "LPGS_2.6.2", "FILE_DATE": "2016-06-27T01:43:28Z", "STATION_ID": "LGN", "REQUEST_ID": "0501606261080_00033"}, "MIN_MAX_PIXEL_VALUE": {"QUANTIZE_CAL_MAX_BAND_5": 65535, "QUANTIZE_CAL_MAX_BAND_4": 65535, "QUANTIZE_CAL_MAX_BAND_7": 65535, "QUANTIZE_CAL_MAX_BAND_6": 65535, "QUANTIZE_CAL_MAX_BAND_1": 65535, "QUANTIZE_CAL_MAX_BAND_3": 65535, "QUANTIZE_CAL_MAX_BAND_2": 65535, "QUANTIZE_CAL_MAX_BAND_9": 65535, "QUANTIZE_CAL_MAX_BAND_8": 65535, "QUANTIZE_CAL_MIN_BAND_9": 1, "QUANTIZE_CAL_MIN_BAND_8": 1, "QUANTIZE_CAL_MIN_BAND_7": 1, "QUANTIZE_CAL_MIN_BAND_6": 1, "QUANTIZE_CAL_MIN_BAND_5": 1, "QUANTIZE_CAL_MIN_BAND_4": 1, "QUANTIZE_CAL_MIN_BAND_3": 1, "QUANTIZE_CAL_MIN_BAND_2": 1, "QUANTIZE_CAL_MIN_BAND_1": 1, "QUANTIZE_CAL_MIN_BAND_11": 1, "QUANTIZE_CAL_MIN_BAND_10": 1, "QUANTIZE_CAL_MAX_BAND_11": 65535, "QUANTIZE_CAL_MAX_BAND_10": 65535}, "MIN_MAX_RADIANCE": {"RADIANCE_MINIMUM_BAND_6": -7.35576, "RADIANCE_MINIMUM_BAND_7": -2.47929, "RADIANCE_MINIMUM_BAND_4": -48.33387, "RADIANCE_MINIMUM_BAND_5": -29.57792, "RADIANCE_MINIMUM_BAND_2": -62.20148, "RADIANCE_MINIMUM_BAND_3": -57.31813, "RADIANCE_MINIMUM_BAND_1": -60.7429, "RADIANCE_MINIMUM_BAND_8": -54.70065, "RADIANCE_MINIMUM_BAND_9": -11.55972, "RADIANCE_MINIMUM_BAND_10": 0.10033, "RADIANCE_MINIMUM_BAND_11": 0.10033, "RADIANCE_MAXIMUM_BAND_10": 22.0018, "RADIANCE_MAXIMUM_BAND_11": 22.0018, "RADIANCE_MAXIMUM_BAND_8": 662.39319, "RADIANCE_MAXIMUM_BAND_9": 139.98155, "RADIANCE_MAXIMUM_BAND_1": 735.56146, "RADIANCE_MAXIMUM_BAND_2": 753.22394, "RADIANCE_MAXIMUM_BAND_3": 694.08942, "RADIANCE_MAXIMUM_BAND_4": 585.29529, "RADIANCE_MAXIMUM_BAND_5": 358.17154, "RADIANCE_MAXIMUM_BAND_6": 89.074, "RADIANCE_MAXIMUM_BAND_7": 30.02271}, "MIN_MAX_REFLECTANCE": {"REFLECTANCE_MAXIMUM_BAND_8": 1.2107, "REFLECTANCE_MINIMUM_BAND_8": -0.09998, "REFLECTANCE_MAXIMUM_BAND_9": 1.2107, "REFLECTANCE_MINIMUM_BAND_9": -0.09998, "REFLECTANCE_MINIMUM_BAND_4": -0.09998, "REFLECTANCE_MINIMUM_BAND_5": -0.09998, "REFLECTANCE_MINIMUM_BAND_6": -0.09998, "REFLECTANCE_MINIMUM_BAND_7": -0.09998, "REFLECTANCE_MINIMUM_BAND_1": -0.09998, "REFLECTANCE_MINIMUM_BAND_2": -0.09998, "REFLECTANCE_MINIMUM_BAND_3": -0.09998, "REFLECTANCE_MAXIMUM_BAND_6": 1.2107, "REFLECTANCE_MAXIMUM_BAND_7": 1.2107, "REFLECTANCE_MAXIMUM_BAND_4": 1.2107, "REFLECTANCE_MAXIMUM_BAND_5": 1.2107, "REFLECTANCE_MAXIMUM_BAND_2": 1.2107, "REFLECTANCE_MAXIMUM_BAND_3": 1.2107, "REFLECTANCE_MAXIMUM_BAND_1": 1.2107}}} 2 | -------------------------------------------------------------------------------- /tests/data/LC81060712016134LGN00_B3.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/LC81060712016134LGN00_B3.TIF -------------------------------------------------------------------------------- /tests/data/LC81060712016134LGN00_MTL.json: -------------------------------------------------------------------------------- 1 | { 2 | "L1_METADATA_FILE": { 3 | "IMAGE_ATTRIBUTES": { 4 | "GROUND_CONTROL_POINTS_VERIFY": 158, 5 | "GROUND_CONTROL_POINTS_VERSION": 4, 6 | "EARTH_SUN_DISTANCE": 1.0104922, 7 | "GEOMETRIC_RMSE_MODEL": 4.541, 8 | "CLOUD_COVER": 0.02, 9 | "IMAGE_QUALITY_TIRS": 7, 10 | "SUN_AZIMUTH": 40.31309714, 11 | "GEOMETRIC_RMSE_VERIFY": 3.01, 12 | "SUN_ELEVATION": 45.66897551, 13 | "TIRS_SSM_POSITION_STATUS": "ESTIMATED", 14 | "ROLL_ANGLE": -0.001, 15 | "CLOUD_COVER_LAND": 0.02, 16 | "GROUND_CONTROL_POINTS_MODEL": 418, 17 | "GEOMETRIC_RMSE_MODEL_X": 3.332, 18 | "GEOMETRIC_RMSE_MODEL_Y": 3.085, 19 | "TIRS_SSM_MODEL": "PRELIMINARY", 20 | "IMAGE_QUALITY_OLI": 9 21 | }, 22 | "TIRS_THERMAL_CONSTANTS": { 23 | "K2_CONSTANT_BAND_10": 1321.0789, 24 | "K2_CONSTANT_BAND_11": 1201.1442, 25 | "K1_CONSTANT_BAND_10": 774.8853, 26 | "K1_CONSTANT_BAND_11": 480.8883 27 | }, 28 | "RADIOMETRIC_RESCALING": { 29 | "RADIANCE_MULT_BAND_7": 0.00050189, 30 | "RADIANCE_MULT_BAND_6": 0.001489, 31 | "RADIANCE_MULT_BAND_5": 0.0059875, 32 | "RADIANCE_MULT_BAND_4": 0.0097844, 33 | "RADIANCE_MULT_BAND_3": 0.011603, 34 | "RADIANCE_MULT_BAND_2": 0.012592, 35 | "RADIANCE_MULT_BAND_1": 0.012296, 36 | "RADIANCE_MULT_BAND_9": 0.0023401, 37 | "RADIANCE_MULT_BAND_8": 0.011073, 38 | "RADIANCE_ADD_BAND_9": -11.70035, 39 | "RADIANCE_ADD_BAND_8": -55.36609, 40 | "RADIANCE_MULT_BAND_11": 0.0003342, 41 | "RADIANCE_MULT_BAND_10": 0.0003342, 42 | "REFLECTANCE_ADD_BAND_9": -0.1, 43 | "REFLECTANCE_ADD_BAND_8": -0.1, 44 | "RADIANCE_ADD_BAND_1": -61.48185, 45 | "REFLECTANCE_ADD_BAND_6": -0.1, 46 | "RADIANCE_ADD_BAND_3": -58.01541, 47 | "RADIANCE_ADD_BAND_2": -62.95817, 48 | "RADIANCE_ADD_BAND_5": -29.93774, 49 | "RADIANCE_ADD_BAND_4": -48.92186, 50 | "RADIANCE_ADD_BAND_7": -2.50945, 51 | "RADIANCE_ADD_BAND_6": -7.44524, 52 | "REFLECTANCE_MULT_BAND_9": 2e-05, 53 | "REFLECTANCE_MULT_BAND_8": 2e-05, 54 | "REFLECTANCE_MULT_BAND_1": 2e-05, 55 | "REFLECTANCE_MULT_BAND_3": 2e-05, 56 | "REFLECTANCE_MULT_BAND_2": 2e-05, 57 | "REFLECTANCE_MULT_BAND_5": 2e-05, 58 | "REFLECTANCE_MULT_BAND_4": 2e-05, 59 | "REFLECTANCE_MULT_BAND_7": 2e-05, 60 | "REFLECTANCE_MULT_BAND_6": 2e-05, 61 | "REFLECTANCE_ADD_BAND_7": -0.1, 62 | "REFLECTANCE_ADD_BAND_5": -0.1, 63 | "REFLECTANCE_ADD_BAND_4": -0.1, 64 | "RADIANCE_ADD_BAND_11": 0.1, 65 | "RADIANCE_ADD_BAND_10": 0.1, 66 | "REFLECTANCE_ADD_BAND_3": -0.1, 67 | "REFLECTANCE_ADD_BAND_2": -0.1, 68 | "REFLECTANCE_ADD_BAND_1": -0.1 69 | }, 70 | "PRODUCT_METADATA": { 71 | "PANCHROMATIC_LINES": 15581, 72 | "NADIR_OFFNADIR": "NADIR", 73 | "DATA_TYPE": "L1T", 74 | "CORNER_LR_LAT_PRODUCT": -16.95339, 75 | "SCENE_CENTER_TIME": "01:23:31.4516110Z", 76 | "CORNER_LR_PROJECTION_Y_PRODUCT": -1875300.0, 77 | "CORNER_LL_PROJECTION_X_PRODUCT": 464700.0, 78 | "FILE_NAME_BAND_QUALITY": "LC81060712016134LGN00_BQA.TIF", 79 | "CORNER_UR_PROJECTION_Y_PRODUCT": -1641600.0, 80 | "CORNER_LL_PROJECTION_Y_PRODUCT": -1875300.0, 81 | "TARGET_WRS_ROW": 71, 82 | "BPF_NAME_TIRS": "LT8BPF20160507073029_20160507073845.01", 83 | "THERMAL_LINES": 7791, 84 | "CORNER_LR_PROJECTION_X_PRODUCT": 694200.0, 85 | "FILE_NAME_BAND_3": "LC81060712016134LGN00_B3.TIF", 86 | "CORNER_LL_LAT_PRODUCT": -16.96127, 87 | "FILE_NAME_BAND_1": "LC81060712016134LGN00_B1.TIF", 88 | "REFLECTIVE_LINES": 7791, 89 | "FILE_NAME_BAND_7": "LC81060712016134LGN00_B7.TIF", 90 | "FILE_NAME_BAND_6": "LC81060712016134LGN00_B6.TIF", 91 | "FILE_NAME_BAND_5": "LC81060712016134LGN00_B5.TIF", 92 | "FILE_NAME_BAND_4": "LC81060712016134LGN00_B4.TIF", 93 | "FILE_NAME_BAND_9": "LC81060712016134LGN00_B9.TIF", 94 | "FILE_NAME_BAND_8": "LC81060712016134LGN00_B8.TIF", 95 | "SPACECRAFT_ID": "LANDSAT_8", 96 | "ELEVATION_SOURCE": "GLS2000", 97 | "CPF_NAME": "L8CPF20160401_20160630.02", 98 | "CORNER_UR_LON_PRODUCT": 130.8048, 99 | "FILE_NAME_BAND_11": "LC81060712016134LGN00_B11.TIF", 100 | "CORNER_LR_LON_PRODUCT": 130.82374, 101 | "CORNER_UL_PROJECTION_X_PRODUCT": 464700.0, 102 | "PANCHROMATIC_SAMPLES": 15301, 103 | "SENSOR_ID": "OLI_TIRS", 104 | "FILE_NAME_BAND_2": "LC81060712016134LGN00_B2.TIF", 105 | "WRS_PATH": 106, 106 | "OUTPUT_FORMAT": "GEOTIFF", 107 | "FILE_NAME_BAND_10": "LC81060712016134LGN00_B10.TIF", 108 | "WRS_ROW": 71, 109 | "CORNER_UL_PROJECTION_Y_PRODUCT": -1641600.0, 110 | "BPF_NAME_OLI": "LO8BPF20160513005835_20160513012938.01", 111 | "TARGET_WRS_PATH": 106, 112 | "CORNER_UL_LON_PRODUCT": 128.67188, 113 | "THERMAL_SAMPLES": 7651, 114 | "METADATA_FILE_NAME": "LC81060712016134LGN00_MTL.txt", 115 | "REFLECTIVE_SAMPLES": 7651, 116 | "CORNER_UR_PROJECTION_X_PRODUCT": 694200.0, 117 | "RLUT_FILE_NAME": "L8RLUT20150303_20431231v11.h5", 118 | "CORNER_UL_LAT_PRODUCT": -14.84854, 119 | "CORNER_UR_LAT_PRODUCT": -14.84169, 120 | "CORNER_LL_LON_PRODUCT": 128.66844, 121 | "DATE_ACQUIRED": "2016-05-13" 122 | }, 123 | "PROJECTION_PARAMETERS": { 124 | "UTM_ZONE": 52, 125 | "GRID_CELL_SIZE_REFLECTIVE": 30.0, 126 | "MAP_PROJECTION": "UTM", 127 | "ORIENTATION": "NORTH_UP", 128 | "ELLIPSOID": "WGS84", 129 | "GRID_CELL_SIZE_THERMAL": 30.0, 130 | "DATUM": "WGS84", 131 | "GRID_CELL_SIZE_PANCHROMATIC": 15.0, 132 | "RESAMPLING_OPTION": "CUBIC_CONVOLUTION" 133 | }, 134 | "METADATA_FILE_INFO": { 135 | "ORIGIN": "Image courtesy of the U.S. Geological Survey", 136 | "LANDSAT_SCENE_ID": "LC81060712016134LGN00", 137 | "PROCESSING_SOFTWARE_VERSION": "LPGS_2.6.2", 138 | "FILE_DATE": "2016-05-13T10:12:45Z", 139 | "STATION_ID": "LGN", 140 | "REQUEST_ID": "0501605130084_00012" 141 | }, 142 | "MIN_MAX_PIXEL_VALUE": { 143 | "QUANTIZE_CAL_MAX_BAND_5": 65535, 144 | "QUANTIZE_CAL_MAX_BAND_4": 65535, 145 | "QUANTIZE_CAL_MAX_BAND_7": 65535, 146 | "QUANTIZE_CAL_MAX_BAND_6": 65535, 147 | "QUANTIZE_CAL_MAX_BAND_1": 65535, 148 | "QUANTIZE_CAL_MAX_BAND_3": 65535, 149 | "QUANTIZE_CAL_MAX_BAND_2": 65535, 150 | "QUANTIZE_CAL_MAX_BAND_9": 65535, 151 | "QUANTIZE_CAL_MAX_BAND_8": 65535, 152 | "QUANTIZE_CAL_MIN_BAND_9": 1, 153 | "QUANTIZE_CAL_MIN_BAND_8": 1, 154 | "QUANTIZE_CAL_MIN_BAND_7": 1, 155 | "QUANTIZE_CAL_MIN_BAND_6": 1, 156 | "QUANTIZE_CAL_MIN_BAND_5": 1, 157 | "QUANTIZE_CAL_MIN_BAND_4": 1, 158 | "QUANTIZE_CAL_MIN_BAND_3": 1, 159 | "QUANTIZE_CAL_MIN_BAND_2": 1, 160 | "QUANTIZE_CAL_MIN_BAND_1": 1, 161 | "QUANTIZE_CAL_MIN_BAND_11": 1, 162 | "QUANTIZE_CAL_MIN_BAND_10": 1, 163 | "QUANTIZE_CAL_MAX_BAND_11": 65535, 164 | "QUANTIZE_CAL_MAX_BAND_10": 65535 165 | }, 166 | "MIN_MAX_RADIANCE": { 167 | "RADIANCE_MINIMUM_BAND_6": -7.44376, 168 | "RADIANCE_MINIMUM_BAND_7": -2.50894, 169 | "RADIANCE_MINIMUM_BAND_4": -48.91208, 170 | "RADIANCE_MINIMUM_BAND_5": -29.93175, 171 | "RADIANCE_MINIMUM_BAND_2": -62.94558, 172 | "RADIANCE_MINIMUM_BAND_3": -58.00381, 173 | "RADIANCE_MINIMUM_BAND_1": -61.46955, 174 | "RADIANCE_MINIMUM_BAND_8": -55.35501, 175 | "RADIANCE_MINIMUM_BAND_9": -11.69801, 176 | "RADIANCE_MINIMUM_BAND_10": 0.10033, 177 | "RADIANCE_MINIMUM_BAND_11": 0.10033, 178 | "RADIANCE_MAXIMUM_BAND_10": 22.0018, 179 | "RADIANCE_MAXIMUM_BAND_11": 22.0018, 180 | "RADIANCE_MAXIMUM_BAND_8": 670.3172, 181 | "RADIANCE_MAXIMUM_BAND_9": 141.65611, 182 | "RADIANCE_MAXIMUM_BAND_1": 744.36078, 183 | "RADIANCE_MAXIMUM_BAND_2": 762.23456, 184 | "RADIANCE_MAXIMUM_BAND_3": 702.39258, 185 | "RADIANCE_MAXIMUM_BAND_4": 592.297, 186 | "RADIANCE_MAXIMUM_BAND_5": 362.45624, 187 | "RADIANCE_MAXIMUM_BAND_6": 90.13957, 188 | "RADIANCE_MAXIMUM_BAND_7": 30.38186 189 | }, 190 | "MIN_MAX_REFLECTANCE": { 191 | "REFLECTANCE_MAXIMUM_BAND_8": 1.2107, 192 | "REFLECTANCE_MINIMUM_BAND_8": -0.09998, 193 | "REFLECTANCE_MAXIMUM_BAND_9": 1.2107, 194 | "REFLECTANCE_MINIMUM_BAND_9": -0.09998, 195 | "REFLECTANCE_MINIMUM_BAND_4": -0.09998, 196 | "REFLECTANCE_MINIMUM_BAND_5": -0.09998, 197 | "REFLECTANCE_MINIMUM_BAND_6": -0.09998, 198 | "REFLECTANCE_MINIMUM_BAND_7": -0.09998, 199 | "REFLECTANCE_MINIMUM_BAND_1": -0.09998, 200 | "REFLECTANCE_MINIMUM_BAND_2": -0.09998, 201 | "REFLECTANCE_MINIMUM_BAND_3": -0.09998, 202 | "REFLECTANCE_MAXIMUM_BAND_6": 1.2107, 203 | "REFLECTANCE_MAXIMUM_BAND_7": 1.2107, 204 | "REFLECTANCE_MAXIMUM_BAND_4": 1.2107, 205 | "REFLECTANCE_MAXIMUM_BAND_5": 1.2107, 206 | "REFLECTANCE_MAXIMUM_BAND_2": 1.2107, 207 | "REFLECTANCE_MAXIMUM_BAND_3": 1.2107, 208 | "REFLECTANCE_MAXIMUM_BAND_1": 1.2107 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /tests/data/LC81060712016134LGN00_MTL.txt: -------------------------------------------------------------------------------- 1 | GROUP = L1_METADATA_FILE 2 | GROUP = METADATA_FILE_INFO 3 | ORIGIN = "Image courtesy of the U.S. Geological Survey" 4 | REQUEST_ID = "0501605130084_00012" 5 | LANDSAT_SCENE_ID = "LC81060712016134LGN00" 6 | FILE_DATE = 2016-05-13T10:12:45Z 7 | STATION_ID = "LGN" 8 | PROCESSING_SOFTWARE_VERSION = "LPGS_2.6.2" 9 | END_GROUP = METADATA_FILE_INFO 10 | GROUP = PRODUCT_METADATA 11 | DATA_TYPE = "L1T" 12 | ELEVATION_SOURCE = "GLS2000" 13 | OUTPUT_FORMAT = "GEOTIFF" 14 | SPACECRAFT_ID = "LANDSAT_8" 15 | SENSOR_ID = "OLI_TIRS" 16 | WRS_PATH = 106 17 | WRS_ROW = 71 18 | NADIR_OFFNADIR = "NADIR" 19 | TARGET_WRS_PATH = 106 20 | TARGET_WRS_ROW = 71 21 | DATE_ACQUIRED = 2016-05-13 22 | SCENE_CENTER_TIME = "01:23:31.4516110Z" 23 | CORNER_UL_LAT_PRODUCT = -14.84854 24 | CORNER_UL_LON_PRODUCT = 128.67188 25 | CORNER_UR_LAT_PRODUCT = -14.84169 26 | CORNER_UR_LON_PRODUCT = 130.80480 27 | CORNER_LL_LAT_PRODUCT = -16.96127 28 | CORNER_LL_LON_PRODUCT = 128.66844 29 | CORNER_LR_LAT_PRODUCT = -16.95339 30 | CORNER_LR_LON_PRODUCT = 130.82374 31 | CORNER_UL_PROJECTION_X_PRODUCT = 464700.000 32 | CORNER_UL_PROJECTION_Y_PRODUCT = -1641600.000 33 | CORNER_UR_PROJECTION_X_PRODUCT = 694200.000 34 | CORNER_UR_PROJECTION_Y_PRODUCT = -1641600.000 35 | CORNER_LL_PROJECTION_X_PRODUCT = 464700.000 36 | CORNER_LL_PROJECTION_Y_PRODUCT = -1875300.000 37 | CORNER_LR_PROJECTION_X_PRODUCT = 694200.000 38 | CORNER_LR_PROJECTION_Y_PRODUCT = -1875300.000 39 | PANCHROMATIC_LINES = 15581 40 | PANCHROMATIC_SAMPLES = 15301 41 | REFLECTIVE_LINES = 7791 42 | REFLECTIVE_SAMPLES = 7651 43 | THERMAL_LINES = 7791 44 | THERMAL_SAMPLES = 7651 45 | FILE_NAME_BAND_1 = "LC81060712016134LGN00_B1.TIF" 46 | FILE_NAME_BAND_2 = "LC81060712016134LGN00_B2.TIF" 47 | FILE_NAME_BAND_3 = "LC81060712016134LGN00_B3.TIF" 48 | FILE_NAME_BAND_4 = "LC81060712016134LGN00_B4.TIF" 49 | FILE_NAME_BAND_5 = "LC81060712016134LGN00_B5.TIF" 50 | FILE_NAME_BAND_6 = "LC81060712016134LGN00_B6.TIF" 51 | FILE_NAME_BAND_7 = "LC81060712016134LGN00_B7.TIF" 52 | FILE_NAME_BAND_8 = "LC81060712016134LGN00_B8.TIF" 53 | FILE_NAME_BAND_9 = "LC81060712016134LGN00_B9.TIF" 54 | FILE_NAME_BAND_10 = "LC81060712016134LGN00_B10.TIF" 55 | FILE_NAME_BAND_11 = "LC81060712016134LGN00_B11.TIF" 56 | FILE_NAME_BAND_QUALITY = "LC81060712016134LGN00_BQA.TIF" 57 | METADATA_FILE_NAME = "LC81060712016134LGN00_MTL.txt" 58 | BPF_NAME_OLI = "LO8BPF20160513005835_20160513012938.01" 59 | BPF_NAME_TIRS = "LT8BPF20160507073029_20160507073845.01" 60 | CPF_NAME = "L8CPF20160401_20160630.02" 61 | RLUT_FILE_NAME = "L8RLUT20150303_20431231v11.h5" 62 | END_GROUP = PRODUCT_METADATA 63 | GROUP = IMAGE_ATTRIBUTES 64 | CLOUD_COVER = 0.02 65 | CLOUD_COVER_LAND = 0.02 66 | IMAGE_QUALITY_OLI = 9 67 | IMAGE_QUALITY_TIRS = 7 68 | TIRS_SSM_MODEL = "PRELIMINARY" 69 | TIRS_SSM_POSITION_STATUS = "ESTIMATED" 70 | ROLL_ANGLE = -0.001 71 | SUN_AZIMUTH = 40.31309714 72 | SUN_ELEVATION = 45.66897551 73 | EARTH_SUN_DISTANCE = 1.0104922 74 | GROUND_CONTROL_POINTS_VERSION = 4 75 | GROUND_CONTROL_POINTS_MODEL = 418 76 | GEOMETRIC_RMSE_MODEL = 4.541 77 | GEOMETRIC_RMSE_MODEL_Y = 3.085 78 | GEOMETRIC_RMSE_MODEL_X = 3.332 79 | GROUND_CONTROL_POINTS_VERIFY = 158 80 | GEOMETRIC_RMSE_VERIFY = 3.010 81 | END_GROUP = IMAGE_ATTRIBUTES 82 | GROUP = MIN_MAX_RADIANCE 83 | RADIANCE_MAXIMUM_BAND_1 = 744.36078 84 | RADIANCE_MINIMUM_BAND_1 = -61.46955 85 | RADIANCE_MAXIMUM_BAND_2 = 762.23456 86 | RADIANCE_MINIMUM_BAND_2 = -62.94558 87 | RADIANCE_MAXIMUM_BAND_3 = 702.39258 88 | RADIANCE_MINIMUM_BAND_3 = -58.00381 89 | RADIANCE_MAXIMUM_BAND_4 = 592.29700 90 | RADIANCE_MINIMUM_BAND_4 = -48.91208 91 | RADIANCE_MAXIMUM_BAND_5 = 362.45624 92 | RADIANCE_MINIMUM_BAND_5 = -29.93175 93 | RADIANCE_MAXIMUM_BAND_6 = 90.13957 94 | RADIANCE_MINIMUM_BAND_6 = -7.44376 95 | RADIANCE_MAXIMUM_BAND_7 = 30.38186 96 | RADIANCE_MINIMUM_BAND_7 = -2.50894 97 | RADIANCE_MAXIMUM_BAND_8 = 670.31720 98 | RADIANCE_MINIMUM_BAND_8 = -55.35501 99 | RADIANCE_MAXIMUM_BAND_9 = 141.65611 100 | RADIANCE_MINIMUM_BAND_9 = -11.69801 101 | RADIANCE_MAXIMUM_BAND_10 = 22.00180 102 | RADIANCE_MINIMUM_BAND_10 = 0.10033 103 | RADIANCE_MAXIMUM_BAND_11 = 22.00180 104 | RADIANCE_MINIMUM_BAND_11 = 0.10033 105 | END_GROUP = MIN_MAX_RADIANCE 106 | GROUP = MIN_MAX_REFLECTANCE 107 | REFLECTANCE_MAXIMUM_BAND_1 = 1.210700 108 | REFLECTANCE_MINIMUM_BAND_1 = -0.099980 109 | REFLECTANCE_MAXIMUM_BAND_2 = 1.210700 110 | REFLECTANCE_MINIMUM_BAND_2 = -0.099980 111 | REFLECTANCE_MAXIMUM_BAND_3 = 1.210700 112 | REFLECTANCE_MINIMUM_BAND_3 = -0.099980 113 | REFLECTANCE_MAXIMUM_BAND_4 = 1.210700 114 | REFLECTANCE_MINIMUM_BAND_4 = -0.099980 115 | REFLECTANCE_MAXIMUM_BAND_5 = 1.210700 116 | REFLECTANCE_MINIMUM_BAND_5 = -0.099980 117 | REFLECTANCE_MAXIMUM_BAND_6 = 1.210700 118 | REFLECTANCE_MINIMUM_BAND_6 = -0.099980 119 | REFLECTANCE_MAXIMUM_BAND_7 = 1.210700 120 | REFLECTANCE_MINIMUM_BAND_7 = -0.099980 121 | REFLECTANCE_MAXIMUM_BAND_8 = 1.210700 122 | REFLECTANCE_MINIMUM_BAND_8 = -0.099980 123 | REFLECTANCE_MAXIMUM_BAND_9 = 1.210700 124 | REFLECTANCE_MINIMUM_BAND_9 = -0.099980 125 | END_GROUP = MIN_MAX_REFLECTANCE 126 | GROUP = MIN_MAX_PIXEL_VALUE 127 | QUANTIZE_CAL_MAX_BAND_1 = 65535 128 | QUANTIZE_CAL_MIN_BAND_1 = 1 129 | QUANTIZE_CAL_MAX_BAND_2 = 65535 130 | QUANTIZE_CAL_MIN_BAND_2 = 1 131 | QUANTIZE_CAL_MAX_BAND_3 = 65535 132 | QUANTIZE_CAL_MIN_BAND_3 = 1 133 | QUANTIZE_CAL_MAX_BAND_4 = 65535 134 | QUANTIZE_CAL_MIN_BAND_4 = 1 135 | QUANTIZE_CAL_MAX_BAND_5 = 65535 136 | QUANTIZE_CAL_MIN_BAND_5 = 1 137 | QUANTIZE_CAL_MAX_BAND_6 = 65535 138 | QUANTIZE_CAL_MIN_BAND_6 = 1 139 | QUANTIZE_CAL_MAX_BAND_7 = 65535 140 | QUANTIZE_CAL_MIN_BAND_7 = 1 141 | QUANTIZE_CAL_MAX_BAND_8 = 65535 142 | QUANTIZE_CAL_MIN_BAND_8 = 1 143 | QUANTIZE_CAL_MAX_BAND_9 = 65535 144 | QUANTIZE_CAL_MIN_BAND_9 = 1 145 | QUANTIZE_CAL_MAX_BAND_10 = 65535 146 | QUANTIZE_CAL_MIN_BAND_10 = 1 147 | QUANTIZE_CAL_MAX_BAND_11 = 65535 148 | QUANTIZE_CAL_MIN_BAND_11 = 1 149 | END_GROUP = MIN_MAX_PIXEL_VALUE 150 | GROUP = RADIOMETRIC_RESCALING 151 | RADIANCE_MULT_BAND_1 = 1.2296E-02 152 | RADIANCE_MULT_BAND_2 = 1.2592E-02 153 | RADIANCE_MULT_BAND_3 = 1.1603E-02 154 | RADIANCE_MULT_BAND_4 = 9.7844E-03 155 | RADIANCE_MULT_BAND_5 = 5.9875E-03 156 | RADIANCE_MULT_BAND_6 = 1.4890E-03 157 | RADIANCE_MULT_BAND_7 = 5.0189E-04 158 | RADIANCE_MULT_BAND_8 = 1.1073E-02 159 | RADIANCE_MULT_BAND_9 = 2.3401E-03 160 | RADIANCE_MULT_BAND_10 = 3.3420E-04 161 | RADIANCE_MULT_BAND_11 = 3.3420E-04 162 | RADIANCE_ADD_BAND_1 = -61.48185 163 | RADIANCE_ADD_BAND_2 = -62.95817 164 | RADIANCE_ADD_BAND_3 = -58.01541 165 | RADIANCE_ADD_BAND_4 = -48.92186 166 | RADIANCE_ADD_BAND_5 = -29.93774 167 | RADIANCE_ADD_BAND_6 = -7.44524 168 | RADIANCE_ADD_BAND_7 = -2.50945 169 | RADIANCE_ADD_BAND_8 = -55.36609 170 | RADIANCE_ADD_BAND_9 = -11.70035 171 | RADIANCE_ADD_BAND_10 = 0.10000 172 | RADIANCE_ADD_BAND_11 = 0.10000 173 | REFLECTANCE_MULT_BAND_1 = 2.0000E-05 174 | REFLECTANCE_MULT_BAND_2 = 2.0000E-05 175 | REFLECTANCE_MULT_BAND_3 = 2.0000E-05 176 | REFLECTANCE_MULT_BAND_4 = 2.0000E-05 177 | REFLECTANCE_MULT_BAND_5 = 2.0000E-05 178 | REFLECTANCE_MULT_BAND_6 = 2.0000E-05 179 | REFLECTANCE_MULT_BAND_7 = 2.0000E-05 180 | REFLECTANCE_MULT_BAND_8 = 2.0000E-05 181 | REFLECTANCE_MULT_BAND_9 = 2.0000E-05 182 | REFLECTANCE_ADD_BAND_1 = -0.100000 183 | REFLECTANCE_ADD_BAND_2 = -0.100000 184 | REFLECTANCE_ADD_BAND_3 = -0.100000 185 | REFLECTANCE_ADD_BAND_4 = -0.100000 186 | REFLECTANCE_ADD_BAND_5 = -0.100000 187 | REFLECTANCE_ADD_BAND_6 = -0.100000 188 | REFLECTANCE_ADD_BAND_7 = -0.100000 189 | REFLECTANCE_ADD_BAND_8 = -0.100000 190 | REFLECTANCE_ADD_BAND_9 = -0.100000 191 | END_GROUP = RADIOMETRIC_RESCALING 192 | GROUP = TIRS_THERMAL_CONSTANTS 193 | K1_CONSTANT_BAND_10 = 774.8853 194 | K1_CONSTANT_BAND_11 = 480.8883 195 | K2_CONSTANT_BAND_10 = 1321.0789 196 | K2_CONSTANT_BAND_11 = 1201.1442 197 | END_GROUP = TIRS_THERMAL_CONSTANTS 198 | GROUP = PROJECTION_PARAMETERS 199 | MAP_PROJECTION = "UTM" 200 | DATUM = "WGS84" 201 | ELLIPSOID = "WGS84" 202 | UTM_ZONE = 52 203 | GRID_CELL_SIZE_PANCHROMATIC = 15.00 204 | GRID_CELL_SIZE_REFLECTIVE = 30.00 205 | GRID_CELL_SIZE_THERMAL = 30.00 206 | ORIENTATION = "NORTH_UP" 207 | RESAMPLING_OPTION = "CUBIC_CONVOLUTION" 208 | END_GROUP = PROJECTION_PARAMETERS 209 | END_GROUP = L1_METADATA_FILE 210 | END 211 | -------------------------------------------------------------------------------- /tests/data/LC81390452014295LGN00_MTL.json: -------------------------------------------------------------------------------- 1 | { 2 | "L1_METADATA_FILE": { 3 | "IMAGE_ATTRIBUTES": { 4 | "GROUND_CONTROL_POINTS_VERIFY": 72, 5 | "GROUND_CONTROL_POINTS_VERSION": 2, 6 | "EARTH_SUN_DISTANCE": 0.9953272, 7 | "GEOMETRIC_RMSE_MODEL": 6.255, 8 | "CLOUD_COVER": 0.38, 9 | "IMAGE_QUALITY_TIRS": 9, 10 | "SUN_AZIMUTH": 147.35570767, 11 | "SUN_ELEVATION": 52.12893938, 12 | "ROLL_ANGLE": -0.001, 13 | "GROUND_CONTROL_POINTS_MODEL": 328, 14 | "GEOMETRIC_RMSE_MODEL_X": 4.655, 15 | "GEOMETRIC_RMSE_MODEL_Y": 4.177, 16 | "GEOMETRIC_RMSE_VERIFY": 3.242, 17 | "IMAGE_QUALITY_OLI": 9 18 | }, 19 | "TIRS_THERMAL_CONSTANTS": { 20 | "K2_CONSTANT_BAND_10": 1321.08, 21 | "K2_CONSTANT_BAND_11": 1201.14, 22 | "K1_CONSTANT_BAND_10": 774.89, 23 | "K1_CONSTANT_BAND_11": 480.89 24 | }, 25 | "RADIOMETRIC_RESCALING": { 26 | "RADIANCE_MULT_BAND_7": 0.0005173, 27 | "RADIANCE_MULT_BAND_6": 0.0015348, 28 | "RADIANCE_MULT_BAND_5": 0.0061714, 29 | "RADIANCE_MULT_BAND_4": 0.010085, 30 | "RADIANCE_MULT_BAND_3": 0.011959, 31 | "RADIANCE_MULT_BAND_2": 0.012978, 32 | "RADIANCE_MULT_BAND_1": 0.012674, 33 | "RADIANCE_MULT_BAND_9": 0.0024119, 34 | "RADIANCE_MULT_BAND_8": 0.011413, 35 | "RADIANCE_ADD_BAND_9": -12.0596, 36 | "RADIANCE_ADD_BAND_8": -57.06607, 37 | "RADIANCE_MULT_BAND_11": 0.0003342, 38 | "RADIANCE_MULT_BAND_10": 0.0003342, 39 | "REFLECTANCE_ADD_BAND_9": -0.1, 40 | "REFLECTANCE_ADD_BAND_8": -0.1, 41 | "RADIANCE_ADD_BAND_1": -63.36962, 42 | "REFLECTANCE_ADD_BAND_6": -0.1, 43 | "RADIANCE_ADD_BAND_3": -59.79675, 44 | "RADIANCE_ADD_BAND_2": -64.89126, 45 | "RADIANCE_ADD_BAND_5": -30.85696, 46 | "RADIANCE_ADD_BAND_4": -50.42398, 47 | "RADIANCE_ADD_BAND_7": -2.5865, 48 | "RADIANCE_ADD_BAND_6": -7.67385, 49 | "REFLECTANCE_MULT_BAND_9": 2e-05, 50 | "REFLECTANCE_MULT_BAND_8": 2e-05, 51 | "REFLECTANCE_MULT_BAND_1": 2e-05, 52 | "REFLECTANCE_MULT_BAND_3": 2e-05, 53 | "REFLECTANCE_MULT_BAND_2": 2e-05, 54 | "REFLECTANCE_MULT_BAND_5": 2e-05, 55 | "REFLECTANCE_MULT_BAND_4": 2e-05, 56 | "REFLECTANCE_MULT_BAND_7": 2e-05, 57 | "REFLECTANCE_MULT_BAND_6": 2e-05, 58 | "REFLECTANCE_ADD_BAND_7": -0.1, 59 | "REFLECTANCE_ADD_BAND_5": -0.1, 60 | "REFLECTANCE_ADD_BAND_4": -0.1, 61 | "RADIANCE_ADD_BAND_11": 0.1, 62 | "RADIANCE_ADD_BAND_10": 0.1, 63 | "REFLECTANCE_ADD_BAND_3": -0.1, 64 | "REFLECTANCE_ADD_BAND_2": -0.1, 65 | "REFLECTANCE_ADD_BAND_1": -0.1 66 | }, 67 | "PRODUCT_METADATA": { 68 | "PANCHROMATIC_LINES": 15581, 69 | "NADIR_OFFNADIR": "NADIR", 70 | "DATA_TYPE": "L1T", 71 | "CORNER_LR_LAT_PRODUCT": 20.60778, 72 | "SCENE_CENTER_TIME": "04:37:48.7052949Z", 73 | "CORNER_LR_PROJECTION_Y_PRODUCT": 2279100.0, 74 | "CORNER_LL_PROJECTION_X_PRODUCT": 381900.0, 75 | "FILE_NAME_BAND_QUALITY": "LC81390452014295LGN00_BQA.TIF", 76 | "CORNER_UR_PROJECTION_Y_PRODUCT": 2512800.0, 77 | "CORNER_LL_PROJECTION_Y_PRODUCT": 2279100.0, 78 | "TARGET_WRS_ROW": 45, 79 | "BPF_NAME_TIRS": "LT8BPF20141022041924_20141022055753.01", 80 | "THERMAL_LINES": 7791, 81 | "CORNER_LR_PROJECTION_X_PRODUCT": 610500.0, 82 | "FILE_NAME_BAND_3": "LC81390452014295LGN00_B3.TIF", 83 | "CORNER_LL_LAT_PRODUCT": 20.60731, 84 | "FILE_NAME_BAND_1": "LC81390452014295LGN00_B1.TIF", 85 | "REFLECTIVE_LINES": 7791, 86 | "FILE_NAME_BAND_7": "LC81390452014295LGN00_B7.TIF", 87 | "FILE_NAME_BAND_6": "LC81390452014295LGN00_B6.TIF", 88 | "FILE_NAME_BAND_5": "LC81390452014295LGN00_B5.TIF", 89 | "FILE_NAME_BAND_4": "LC81390452014295LGN00_B4.TIF", 90 | "FILE_NAME_BAND_9": "LC81390452014295LGN00_B9.TIF", 91 | "FILE_NAME_BAND_8": "LC81390452014295LGN00_B8.TIF", 92 | "SPACECRAFT_ID": "LANDSAT_8", 93 | "ELEVATION_SOURCE": "GLS2000", 94 | "CPF_NAME": "L8CPF20141001_20141231.01", 95 | "CORNER_UR_LON_PRODUCT": 88.07598, 96 | "FILE_NAME_BAND_11": "LC81390452014295LGN00_B11.TIF", 97 | "CORNER_LR_LON_PRODUCT": 88.06044, 98 | "CORNER_UL_PROJECTION_X_PRODUCT": 381900.0, 99 | "PANCHROMATIC_SAMPLES": 15241, 100 | "SENSOR_ID": "OLI_TIRS", 101 | "FILE_NAME_BAND_2": "LC81390452014295LGN00_B2.TIF", 102 | "WRS_PATH": 139, 103 | "OUTPUT_FORMAT": "GEOTIFF", 104 | "FILE_NAME_BAND_10": "LC81390452014295LGN00_B10.TIF", 105 | "WRS_ROW": 45, 106 | "CORNER_UL_PROJECTION_Y_PRODUCT": 2512800.0, 107 | "BPF_NAME_OLI": "LO8BPF20141022042317_20141022060146.01", 108 | "TARGET_WRS_PATH": 139, 109 | "CORNER_UL_LON_PRODUCT": 85.85002, 110 | "THERMAL_SAMPLES": 7621, 111 | "METADATA_FILE_NAME": "LC81390452014295LGN00_MTL.txt", 112 | "REFLECTIVE_SAMPLES": 7621, 113 | "CORNER_UR_PROJECTION_X_PRODUCT": 610500.0, 114 | "RLUT_FILE_NAME": "L8RLUT20130211_20431231v09.h5", 115 | "CORNER_UL_LAT_PRODUCT": 22.71835, 116 | "CORNER_UR_LAT_PRODUCT": 22.71887, 117 | "CORNER_LL_LON_PRODUCT": 85.86664, 118 | "DATE_ACQUIRED": "2014-10-22" 119 | }, 120 | "PROJECTION_PARAMETERS": { 121 | "UTM_ZONE": 45, 122 | "GRID_CELL_SIZE_REFLECTIVE": 30.0, 123 | "MAP_PROJECTION": "UTM", 124 | "ORIENTATION": "NORTH_UP", 125 | "ELLIPSOID": "WGS84", 126 | "GRID_CELL_SIZE_THERMAL": 30.0, 127 | "DATUM": "WGS84", 128 | "GRID_CELL_SIZE_PANCHROMATIC": 15.0, 129 | "RESAMPLING_OPTION": "CUBIC_CONVOLUTION" 130 | }, 131 | "METADATA_FILE_INFO": { 132 | "ORIGIN": "Image courtesy of the U.S. Geological Survey", 133 | "LANDSAT_SCENE_ID": "LC81390452014295LGN00", 134 | "PROCESSING_SOFTWARE_VERSION": "LPGS_2.4.0", 135 | "FILE_DATE": "2014-10-22T08:42:59Z", 136 | "STATION_ID": "LGN", 137 | "REQUEST_ID": "0501410225527_00035" 138 | }, 139 | "MIN_MAX_PIXEL_VALUE": { 140 | "QUANTIZE_CAL_MAX_BAND_5": 65535, 141 | "QUANTIZE_CAL_MAX_BAND_4": 65535, 142 | "QUANTIZE_CAL_MAX_BAND_7": 65535, 143 | "QUANTIZE_CAL_MAX_BAND_6": 65535, 144 | "QUANTIZE_CAL_MAX_BAND_1": 65535, 145 | "QUANTIZE_CAL_MAX_BAND_3": 65535, 146 | "QUANTIZE_CAL_MAX_BAND_2": 65535, 147 | "QUANTIZE_CAL_MAX_BAND_9": 65535, 148 | "QUANTIZE_CAL_MAX_BAND_8": 65535, 149 | "QUANTIZE_CAL_MIN_BAND_9": 1, 150 | "QUANTIZE_CAL_MIN_BAND_8": 1, 151 | "QUANTIZE_CAL_MIN_BAND_7": 1, 152 | "QUANTIZE_CAL_MIN_BAND_6": 1, 153 | "QUANTIZE_CAL_MIN_BAND_5": 1, 154 | "QUANTIZE_CAL_MIN_BAND_4": 1, 155 | "QUANTIZE_CAL_MIN_BAND_3": 1, 156 | "QUANTIZE_CAL_MIN_BAND_2": 1, 157 | "QUANTIZE_CAL_MIN_BAND_1": 1, 158 | "QUANTIZE_CAL_MIN_BAND_11": 1, 159 | "QUANTIZE_CAL_MIN_BAND_10": 1, 160 | "QUANTIZE_CAL_MAX_BAND_11": 65535, 161 | "QUANTIZE_CAL_MAX_BAND_10": 65535 162 | }, 163 | "MIN_MAX_RADIANCE": { 164 | "RADIANCE_MINIMUM_BAND_6": -7.67231, 165 | "RADIANCE_MINIMUM_BAND_7": -2.58598, 166 | "RADIANCE_MINIMUM_BAND_4": -50.4139, 167 | "RADIANCE_MINIMUM_BAND_5": -30.85079, 168 | "RADIANCE_MINIMUM_BAND_2": -64.87829, 169 | "RADIANCE_MINIMUM_BAND_3": -59.78479, 170 | "RADIANCE_MINIMUM_BAND_1": -63.35695, 171 | "RADIANCE_MINIMUM_BAND_8": -57.05466, 172 | "RADIANCE_MINIMUM_BAND_9": -12.05719, 173 | "RADIANCE_MINIMUM_BAND_10": 0.10033, 174 | "RADIANCE_MINIMUM_BAND_11": 0.10033, 175 | "RADIANCE_MAXIMUM_BAND_10": 22.0018, 176 | "RADIANCE_MAXIMUM_BAND_11": 22.0018, 177 | "RADIANCE_MAXIMUM_BAND_8": 690.89893, 178 | "RADIANCE_MAXIMUM_BAND_9": 146.00558, 179 | "RADIANCE_MAXIMUM_BAND_1": 767.216, 180 | "RADIANCE_MAXIMUM_BAND_2": 785.63855, 181 | "RADIANCE_MAXIMUM_BAND_3": 723.95917, 182 | "RADIANCE_MAXIMUM_BAND_4": 610.48315, 183 | "RADIANCE_MAXIMUM_BAND_5": 373.58527, 184 | "RADIANCE_MAXIMUM_BAND_6": 92.90726, 185 | "RADIANCE_MAXIMUM_BAND_7": 31.31472 186 | }, 187 | "MIN_MAX_REFLECTANCE": { 188 | "REFLECTANCE_MAXIMUM_BAND_8": 1.2107, 189 | "REFLECTANCE_MINIMUM_BAND_8": -0.09998, 190 | "REFLECTANCE_MAXIMUM_BAND_9": 1.2107, 191 | "REFLECTANCE_MINIMUM_BAND_9": -0.09998, 192 | "REFLECTANCE_MINIMUM_BAND_4": -0.09998, 193 | "REFLECTANCE_MINIMUM_BAND_5": -0.09998, 194 | "REFLECTANCE_MINIMUM_BAND_6": -0.09998, 195 | "REFLECTANCE_MINIMUM_BAND_7": -0.09998, 196 | "REFLECTANCE_MINIMUM_BAND_1": -0.09998, 197 | "REFLECTANCE_MINIMUM_BAND_2": -0.09998, 198 | "REFLECTANCE_MINIMUM_BAND_3": -0.09998, 199 | "REFLECTANCE_MAXIMUM_BAND_6": 1.2107, 200 | "REFLECTANCE_MAXIMUM_BAND_7": 1.2107, 201 | "REFLECTANCE_MAXIMUM_BAND_4": 1.2107, 202 | "REFLECTANCE_MAXIMUM_BAND_5": 1.2107, 203 | "REFLECTANCE_MAXIMUM_BAND_2": 1.2107, 204 | "REFLECTANCE_MAXIMUM_BAND_3": 1.2107, 205 | "REFLECTANCE_MAXIMUM_BAND_1": 1.2107 206 | } 207 | } 208 | } -------------------------------------------------------------------------------- /tests/data/LC82290902015304LGN00_MTL.json: -------------------------------------------------------------------------------- 1 | {"L1_METADATA_FILE": {"IMAGE_ATTRIBUTES": {"GROUND_CONTROL_POINTS_VERIFY": 243, "GROUND_CONTROL_POINTS_VERSION": 2, "EARTH_SUN_DISTANCE": 0.9927846, "GEOMETRIC_RMSE_MODEL": 5.149, "CLOUD_COVER": 0.09, "IMAGE_QUALITY_TIRS": 9, "SUN_AZIMUTH": 51.34902132, "SUN_ELEVATION": 50.9018374, "TIRS_SSM_POSITION_STATUS": "NOMINAL", "ROLL_ANGLE": -0.001, "CLOUD_COVER_LAND": 0.09, "GROUND_CONTROL_POINTS_MODEL": 655, "GEOMETRIC_RMSE_MODEL_X": 3.705, "GEOMETRIC_RMSE_MODEL_Y": 3.576, "GEOMETRIC_RMSE_VERIFY": 2.44, "IMAGE_QUALITY_OLI": 9}, "TIRS_THERMAL_CONSTANTS": {"K2_CONSTANT_BAND_10": 1321.0789, "K2_CONSTANT_BAND_11": 1201.1442, "K1_CONSTANT_BAND_10": 774.8853, "K1_CONSTANT_BAND_11": 480.8883}, "RADIOMETRIC_RESCALING": {"RADIANCE_MULT_BAND_7": 0.00051995, "RADIANCE_MULT_BAND_6": 0.0015426, "RADIANCE_MULT_BAND_5": 0.006203, "RADIANCE_MULT_BAND_4": 0.010137, "RADIANCE_MULT_BAND_3": 0.012021, "RADIANCE_MULT_BAND_2": 0.013045, "RADIANCE_MULT_BAND_1": 0.012739, "RADIANCE_MULT_BAND_9": 0.0024243, "RADIANCE_MULT_BAND_8": 0.011472, "RADIANCE_ADD_BAND_9": -12.12145, "RADIANCE_ADD_BAND_8": -57.35875, "RADIANCE_MULT_BAND_11": 0.0003342, "RADIANCE_MULT_BAND_10": 0.0003342, "REFLECTANCE_ADD_BAND_9": -0.1, "REFLECTANCE_ADD_BAND_8": -0.1, "RADIANCE_ADD_BAND_1": -63.69463, "REFLECTANCE_ADD_BAND_6": -0.1, "RADIANCE_ADD_BAND_3": -60.10343, "RADIANCE_ADD_BAND_2": -65.22408, "RADIANCE_ADD_BAND_5": -31.01522, "RADIANCE_ADD_BAND_4": -50.6826, "RADIANCE_ADD_BAND_7": -2.59976, "RADIANCE_ADD_BAND_6": -7.7132, "REFLECTANCE_MULT_BAND_9": 2e-05, "REFLECTANCE_MULT_BAND_8": 2e-05, "REFLECTANCE_MULT_BAND_1": 2e-05, "REFLECTANCE_MULT_BAND_3": 2e-05, "REFLECTANCE_MULT_BAND_2": 2e-05, "REFLECTANCE_MULT_BAND_5": 2e-05, "REFLECTANCE_MULT_BAND_4": 2e-05, "REFLECTANCE_MULT_BAND_7": 2e-05, "REFLECTANCE_MULT_BAND_6": 2e-05, "REFLECTANCE_ADD_BAND_7": -0.1, "REFLECTANCE_ADD_BAND_5": -0.1, "REFLECTANCE_ADD_BAND_4": -0.1, "RADIANCE_ADD_BAND_11": 0.1, "RADIANCE_ADD_BAND_10": 0.1, "REFLECTANCE_ADD_BAND_3": -0.1, "REFLECTANCE_ADD_BAND_2": -0.1, "REFLECTANCE_ADD_BAND_1": -0.1}, "PRODUCT_METADATA": {"PANCHROMATIC_LINES": 15961, "NADIR_OFFNADIR": "NADIR", "DATA_TYPE": "L1T", "CORNER_LR_LAT_PRODUCT": -44.23612, "SCENE_CENTER_TIME": "14:11:51.6655513Z", "CORNER_LR_PROJECTION_Y_PRODUCT": -4902300.0, "CORNER_LL_PROJECTION_X_PRODUCT": 495900.0, "FILE_NAME_BAND_QUALITY": "LC82290902015304LGN00_BQA.TIF", "CORNER_UR_PROJECTION_Y_PRODUCT": -4662900.0, "CORNER_LL_PROJECTION_Y_PRODUCT": -4902300.0, "TARGET_WRS_ROW": 90, "BPF_NAME_TIRS": "LT8BPF20151029121006_20151113000000.02", "THERMAL_LINES": 7981, "CORNER_LR_PROJECTION_X_PRODUCT": 734700.0, "FILE_NAME_BAND_3": "LC82290902015304LGN00_B3.TIF", "CORNER_LL_LAT_PRODUCT": -44.27393, "FILE_NAME_BAND_1": "LC82290902015304LGN00_B1.TIF", "REFLECTIVE_LINES": 7981, "FILE_NAME_BAND_7": "LC82290902015304LGN00_B7.TIF", "FILE_NAME_BAND_6": "LC82290902015304LGN00_B6.TIF", "FILE_NAME_BAND_5": "LC82290902015304LGN00_B5.TIF", "FILE_NAME_BAND_4": "LC82290902015304LGN00_B4.TIF", "FILE_NAME_BAND_9": "LC82290902015304LGN00_B9.TIF", "FILE_NAME_BAND_8": "LC82290902015304LGN00_B8.TIF", "SPACECRAFT_ID": "LANDSAT_8", "ELEVATION_SOURCE": "GLS2000", "CPF_NAME": "L8CPF20151001_20151231.02", "CORNER_UR_LON_PRODUCT": -66.16249, "FILE_NAME_BAND_11": "LC82290902015304LGN00_B11.TIF", "CORNER_LR_LON_PRODUCT": -66.06098, "CORNER_UL_PROJECTION_X_PRODUCT": 495900.0, "PANCHROMATIC_SAMPLES": 15921, "SENSOR_ID": "OLI_TIRS", "FILE_NAME_BAND_2": "LC82290902015304LGN00_B2.TIF", "WRS_PATH": 229, "OUTPUT_FORMAT": "GEOTIFF", "FILE_NAME_BAND_10": "LC82290902015304LGN00_B10.TIF", "WRS_ROW": 90, "CORNER_UL_PROJECTION_Y_PRODUCT": -4662900.0, "BPF_NAME_OLI": "LO8BPF20151031133856_20151031151749.01", "TARGET_WRS_PATH": 229, "CORNER_UL_LON_PRODUCT": -69.0496, "THERMAL_SAMPLES": 7961, "METADATA_FILE_NAME": "LC82290902015304LGN00_MTL.txt", "REFLECTIVE_SAMPLES": 7961, "CORNER_UR_PROJECTION_X_PRODUCT": 734700.0, "RLUT_FILE_NAME": "L8RLUT20150303_20431231v11.h5", "CORNER_UL_LAT_PRODUCT": -42.11819, "CORNER_UR_LAT_PRODUCT": -42.0831, "CORNER_LL_LON_PRODUCT": -69.05138, "DATE_ACQUIRED": "2015-10-31"}, "PROJECTION_PARAMETERS": {"UTM_ZONE": 19, "GRID_CELL_SIZE_REFLECTIVE": 30.0, "MAP_PROJECTION": "UTM", "ORIENTATION": "NORTH_UP", "ELLIPSOID": "WGS84", "GRID_CELL_SIZE_THERMAL": 30.0, "DATUM": "WGS84", "GRID_CELL_SIZE_PANCHROMATIC": 15.0, "RESAMPLING_OPTION": "CUBIC_CONVOLUTION"}, "METADATA_FILE_INFO": {"ORIGIN": "Image courtesy of the U.S. Geological Survey", "LANDSAT_SCENE_ID": "LC82290902015304LGN00", "PROCESSING_SOFTWARE_VERSION": "LPGS_2.5.1", "FILE_DATE": "2015-10-31T21:44:46Z", "STATION_ID": "LGN", "REQUEST_ID": "0501510314792_00035"}, "MIN_MAX_PIXEL_VALUE": {"QUANTIZE_CAL_MAX_BAND_5": 65535, "QUANTIZE_CAL_MAX_BAND_4": 65535, "QUANTIZE_CAL_MAX_BAND_7": 65535, "QUANTIZE_CAL_MAX_BAND_6": 65535, "QUANTIZE_CAL_MAX_BAND_1": 65535, "QUANTIZE_CAL_MAX_BAND_3": 65535, "QUANTIZE_CAL_MAX_BAND_2": 65535, "QUANTIZE_CAL_MAX_BAND_9": 65535, "QUANTIZE_CAL_MAX_BAND_8": 65535, "QUANTIZE_CAL_MIN_BAND_9": 1, "QUANTIZE_CAL_MIN_BAND_8": 1, "QUANTIZE_CAL_MIN_BAND_7": 1, "QUANTIZE_CAL_MIN_BAND_6": 1, "QUANTIZE_CAL_MIN_BAND_5": 1, "QUANTIZE_CAL_MIN_BAND_4": 1, "QUANTIZE_CAL_MIN_BAND_3": 1, "QUANTIZE_CAL_MIN_BAND_2": 1, "QUANTIZE_CAL_MIN_BAND_1": 1, "QUANTIZE_CAL_MIN_BAND_11": 1, "QUANTIZE_CAL_MIN_BAND_10": 1, "QUANTIZE_CAL_MAX_BAND_11": 65535, "QUANTIZE_CAL_MAX_BAND_10": 65535}, "MIN_MAX_RADIANCE": {"RADIANCE_MINIMUM_BAND_6": -7.71166, "RADIANCE_MINIMUM_BAND_7": -2.59924, "RADIANCE_MINIMUM_BAND_4": -50.67246, "RADIANCE_MINIMUM_BAND_5": -31.00902, "RADIANCE_MINIMUM_BAND_2": -65.21103, "RADIANCE_MINIMUM_BAND_3": -60.09141, "RADIANCE_MINIMUM_BAND_1": -63.68189, "RADIANCE_MINIMUM_BAND_8": -57.34728, "RADIANCE_MINIMUM_BAND_9": -12.11903, "RADIANCE_MINIMUM_BAND_10": 0.10033, "RADIANCE_MINIMUM_BAND_11": 0.10033, "RADIANCE_MAXIMUM_BAND_10": 22.0018, "RADIANCE_MAXIMUM_BAND_11": 22.0018, "RADIANCE_MAXIMUM_BAND_8": 694.44238, "RADIANCE_MAXIMUM_BAND_9": 146.75443, "RADIANCE_MAXIMUM_BAND_1": 771.15088, "RADIANCE_MAXIMUM_BAND_2": 789.66791, "RADIANCE_MAXIMUM_BAND_3": 727.67224, "RADIANCE_MAXIMUM_BAND_4": 613.6142, "RADIANCE_MAXIMUM_BAND_5": 375.50128, "RADIANCE_MAXIMUM_BAND_6": 93.38376, "RADIANCE_MAXIMUM_BAND_7": 31.47532}, "MIN_MAX_REFLECTANCE": {"REFLECTANCE_MAXIMUM_BAND_8": 1.2107, "REFLECTANCE_MINIMUM_BAND_8": -0.09998, "REFLECTANCE_MAXIMUM_BAND_9": 1.2107, "REFLECTANCE_MINIMUM_BAND_9": -0.09998, "REFLECTANCE_MINIMUM_BAND_4": -0.09998, "REFLECTANCE_MINIMUM_BAND_5": -0.09998, "REFLECTANCE_MINIMUM_BAND_6": -0.09998, "REFLECTANCE_MINIMUM_BAND_7": -0.09998, "REFLECTANCE_MINIMUM_BAND_1": -0.09998, "REFLECTANCE_MINIMUM_BAND_2": -0.09998, "REFLECTANCE_MINIMUM_BAND_3": -0.09998, "REFLECTANCE_MAXIMUM_BAND_6": 1.2107, "REFLECTANCE_MAXIMUM_BAND_7": 1.2107, "REFLECTANCE_MAXIMUM_BAND_4": 1.2107, "REFLECTANCE_MAXIMUM_BAND_5": 1.2107, "REFLECTANCE_MAXIMUM_BAND_2": 1.2107, "REFLECTANCE_MAXIMUM_BAND_3": 1.2107, "REFLECTANCE_MAXIMUM_BAND_1": 1.2107}}} 2 | -------------------------------------------------------------------------------- /tests/data/mtltest_LC80100202015018LGN00_MTL.txt: -------------------------------------------------------------------------------- 1 | GROUP = L1_METADATA_FILE 2 | GROUP = METADATA_FILE_INFO 3 | ORIGIN = "Image courtesy of the U.S. Geological Survey" 4 | REQUEST_ID = "0501501184561_00001" 5 | LANDSAT_SCENE_ID = "LC80100202015018LGN00" 6 | PROCESSING_SOFTWARE_VERSION = "LPGS_2.4.0" 7 | END_GROUP = METADATA_FILE_INFO 8 | GROUP = PRODUCT_METADATA 9 | DATA_TYPE = "L1T" 10 | DATE_ACQUIRED = 2015-01-18 11 | SCENE_CENTER_TIME = 15:10:22.4142571Z 12 | END_GROUP = PRODUCT_METADATA 13 | END_GROUP = L1_METADATA_FILE 14 | END 15 | -------------------------------------------------------------------------------- /tests/data/tiny_LC80100202015018LGN00_B1.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LC80100202015018LGN00_B1.TIF -------------------------------------------------------------------------------- /tests/data/tiny_LC80100202015018LGN00_B1_refl.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LC80100202015018LGN00_B1_refl.TIF -------------------------------------------------------------------------------- /tests/data/tiny_LC80460282016177LGN00_B11.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LC80460282016177LGN00_B11.TIF -------------------------------------------------------------------------------- /tests/data/tiny_LC80460282016177LGN00_B2.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LC80460282016177LGN00_B2.TIF -------------------------------------------------------------------------------- /tests/data/tiny_LC80460282016177LGN00_B2_refl.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LC80460282016177LGN00_B2_refl.TIF -------------------------------------------------------------------------------- /tests/data/tiny_LC80460282016177LGN00_B3.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LC80460282016177LGN00_B3.TIF -------------------------------------------------------------------------------- /tests/data/tiny_LC80460282016177LGN00_B4.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LC80460282016177LGN00_B4.TIF -------------------------------------------------------------------------------- /tests/data/tiny_LC80460282016177LGN00_rgb_refl.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LC80460282016177LGN00_rgb_refl.TIF -------------------------------------------------------------------------------- /tests/data/tiny_LC81390452014295LGN00_B10.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LC81390452014295LGN00_B10.TIF -------------------------------------------------------------------------------- /tests/data/tiny_LC81390452014295LGN00_B5.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LC81390452014295LGN00_B5.TIF -------------------------------------------------------------------------------- /tests/data/tiny_LC81390452014295LGN00_B5_radl.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LC81390452014295LGN00_B5_radl.TIF -------------------------------------------------------------------------------- /tests/data/tiny_LC81390452014295LGN00_B5_refl.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LC81390452014295LGN00_B5_refl.TIF -------------------------------------------------------------------------------- /tests/data/tiny_LLC80460282016177LGN00_B11_bt.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/data/tiny_LLC80460282016177LGN00_B11_bt.TIF -------------------------------------------------------------------------------- /tests/expected/bt.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/expected/bt.tif -------------------------------------------------------------------------------- /tests/expected/ref1.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/expected/ref1.tif -------------------------------------------------------------------------------- /tests/expected/ref2.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/rio-toa/e94c75b6ca720eefbb88cc777cb69f6608485d3e/tests/expected/ref2.tif -------------------------------------------------------------------------------- /tests/test_brightness_temp.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | from affine import Affine 5 | import click 6 | from hypothesis import given 7 | import hypothesis.strategies as st 8 | from hypothesis.extra.numpy import arrays 9 | import numpy as np 10 | import pytest 11 | import rasterio as rio 12 | from rasterio.warp import reproject, Resampling 13 | 14 | from rio_toa import brightness_temp 15 | from rio_toa import toa_utils 16 | 17 | 18 | def affaux(up): 19 | return Affine(1, 0, 0, 0, -1, 0), Affine(up, 0, 0, 0, -up, 0) 20 | 21 | 22 | def upsample_array(bidx, up, fr, to): 23 | upBidx = np.empty( 24 | (bidx.shape[0] * up, bidx.shape[1] * up), dtype=bidx.dtype) 25 | 26 | reproject( 27 | bidx, upBidx, 28 | src_transform=fr, 29 | dst_transform=to, 30 | src_crs="EPSG:3857", 31 | dst_crs="EPSG:3857", 32 | resampling=Resampling.bilinear) 33 | 34 | return upBidx 35 | 36 | 37 | def flex_compare(r1, r2, thresh=10): 38 | upsample = 4 39 | r1 = r1[::upsample] 40 | r2 = r2[::upsample] 41 | toAff, frAff = affaux(upsample) 42 | r1 = upsample_array(r1, upsample, frAff, toAff) 43 | r2 = upsample_array(r2, upsample, frAff, toAff) 44 | tdiff = np.abs(r1.astype(np.float64) - r2.astype(np.float64)) 45 | click.echo('{0} values exceed the threshold difference ' 46 | 'with a max variance of {1}'.format( 47 | np.sum(tdiff > thresh), tdiff.max()), err=True) 48 | return not np.any(tdiff > thresh) 49 | 50 | 51 | # Testing brightness_temp python api 52 | @given(arrays(np.uint16, (3, 8, 8), 53 | elements=st.integers( 54 | min_value=1, 55 | max_value=np.iinfo('uint16').max)), 56 | st.floats(min_value=0.0, max_value=1.0), 57 | st.floats(min_value=0.1, max_value=1.0), 58 | st.floats(), 59 | st.floats()) 60 | def test_brightness_temp(img, ML, AL, K1, K2): 61 | L = img.astype(np.float32) * ML + AL 62 | src_nodata = 0.0 63 | L[img == src_nodata] = np.nan 64 | Output = K2 / np.log((K1 / L) + 1) 65 | Result = brightness_temp.brightness_temp(img, ML, AL, K1, K2, src_nodata=0) 66 | np.testing.assert_array_equal(Output, Result) 67 | 68 | 69 | # Testing brightness_temp python api 70 | @given(arrays(np.uint16, (3, 8, 8), 71 | elements=st.integers( 72 | min_value=1, 73 | max_value=np.iinfo('uint16').max)), 74 | st.text(min_size=1), 75 | st.floats(min_value=0.1, max_value=1.0), 76 | st.floats(), 77 | st.floats()) 78 | def test_brightness_temp_wrong_type(img, ML, AL, K1, K2): 79 | with pytest.raises(TypeError): 80 | brightness_temp.brightness_temp(img, ML, AL, K1, K2, src_nodata=0) 81 | 82 | 83 | # Testing brightness_temp python api 84 | @given(arrays(np.uint16, (3, 8, 8), 85 | elements=st.integers( 86 | min_value=1, 87 | max_value=np.iinfo('uint16').max)), 88 | st.floats(min_value=0.1, max_value=1.0), 89 | arrays(np.uint16, (3,), 90 | elements=st.integers( 91 | min_value=1, 92 | max_value=np.iinfo('uint16').max)), 93 | st.floats(), 94 | st.floats()) 95 | def test_brightness_temp_wrong_shape(img, ML, AL, K1, K2): 96 | with pytest.raises(ValueError): 97 | brightness_temp.brightness_temp(img, ML, AL, K1, K2, src_nodata=0) 98 | 99 | 100 | @pytest.fixture 101 | def test_var(): 102 | src_path = 'tests/data/tiny_LC80460282016177LGN00_B11.TIF' 103 | src_mtl = 'tests/data/LC80460282016177LGN00_MTL.json' 104 | dst_path = 'tests/data/tiny_LLC80460282016177LGN00_B11_bt.TIF' 105 | 106 | return src_path, src_mtl, dst_path 107 | 108 | 109 | @pytest.fixture 110 | def test_data(test_var): 111 | src_path, src_mtl, dst_path = test_var 112 | 113 | with rio.open(src_path, 'r') as src: 114 | tif = src.read(1) 115 | tif_meta = src.meta 116 | tif_shape = src.shape 117 | 118 | with rio.open(dst_path, 'r') as src: 119 | tif_output = src.read(1) 120 | tif_output_meta = src.meta 121 | 122 | with open(src_mtl, 'r') as src: 123 | mtl = json.loads(src.read()) 124 | 125 | return tif, tif_meta, tif_output, tif_shape, tif_output_meta, mtl 126 | 127 | 128 | def test_brightness_temperature2(test_data): 129 | tif, tif_meta, tif_output, tif_shape, tif_output_meta, mtl = test_data 130 | band = 11 131 | 132 | M = toa_utils._load_mtl_key(mtl, 133 | ['L1_METADATA_FILE', 134 | 'RADIOMETRIC_RESCALING', 135 | 'RADIANCE_MULT_BAND_'], 136 | band) 137 | A = toa_utils._load_mtl_key(mtl, 138 | ['L1_METADATA_FILE', 139 | 'RADIOMETRIC_RESCALING', 140 | 'RADIANCE_ADD_BAND_'], 141 | band) 142 | K1 = toa_utils._load_mtl_key(mtl, 143 | ['L1_METADATA_FILE', 144 | 'TIRS_THERMAL_CONSTANTS', 145 | 'K1_CONSTANT_BAND_'], 146 | band) 147 | K2 = toa_utils._load_mtl_key(mtl, 148 | ['L1_METADATA_FILE', 149 | 'TIRS_THERMAL_CONSTANTS', 150 | 'K2_CONSTANT_BAND_'], 151 | band) 152 | 153 | assert isinstance(M, float) 154 | assert isinstance(A, float) 155 | assert isinstance(K1, float) 156 | assert isinstance(K2, float) 157 | BT = brightness_temp.brightness_temp(tif, M, A, K1, K2, src_nodata=0) 158 | assert BT.dtype == np.float32 159 | assert flex_compare(tif_output, BT) 160 | 161 | 162 | def test_calculate_landsat_brightness_temperature(test_var, test_data, capfd): 163 | src_path, src_mtl, tif_output_stack = \ 164 | test_var[0], test_var[1], test_data[-3] 165 | dst_path = '/tmp/bt.tif' 166 | expected_path = 'tests/expected/bt.tif' 167 | temp_scale = 'F' 168 | creation_options = {} 169 | thermal_bidx = 11 170 | dst_dtype = 'float32' 171 | processes = 1 172 | 173 | brightness_temp.calculate_landsat_brightness_temperature(src_path, src_mtl, 174 | dst_path, temp_scale, 175 | creation_options, thermal_bidx, 176 | dst_dtype, processes) 177 | 178 | out, err = capfd.readouterr() 179 | assert os.path.exists(dst_path) 180 | 181 | with rio.open(dst_path) as created: 182 | with rio.open(expected_path) as expected: 183 | assert flex_compare(created.read(), expected.read()) 184 | 185 | 186 | def test_calculate_brightness_dst_dtype(test_var, capfd): 187 | src_path, src_mtl = test_var[0], test_var[1] 188 | dst_path = '/tmp/test_uint16.tif' 189 | temp_scale = 'K' 190 | creation_options = {} 191 | thermal_bidx = 11 192 | dst_dtype = 'uint16' 193 | processes = 1 194 | 195 | brightness_temp.calculate_landsat_brightness_temperature( 196 | src_path, src_mtl, dst_path, temp_scale, creation_options, 197 | thermal_bidx, dst_dtype, processes) 198 | 199 | assert os.path.exists(dst_path) 200 | 201 | with rio.open(dst_path) as created: 202 | assert created.meta['dtype'] == 'uint16' 203 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from click.testing import CliRunner 4 | import logging 5 | import pytest 6 | from click import UsageError 7 | import rasterio 8 | import json 9 | 10 | from rasterio.rio.options import creation_options 11 | from rio_toa.scripts.cli import radiance, reflectance, brighttemp, parsemtl 12 | 13 | 14 | def test_cli_radiance_default(tmpdir): 15 | output = str(tmpdir.join('toa_radiance.tif')) 16 | runner = CliRunner() 17 | result = runner.invoke( 18 | radiance, 19 | ['tests/data/LC81060712016134LGN00_B3.TIF', 20 | 'tests/data/LC81060712016134LGN00_MTL.json', 21 | output]) 22 | assert result.exit_code == 0 23 | assert os.path.exists(output) 24 | with rasterio.open(output) as out: 25 | assert out.count == 1 26 | assert out.dtypes[0] == rasterio.uint16 27 | 28 | 29 | def test_cli_radiance_good(tmpdir): 30 | output = str(tmpdir.join('toa_radiance.tif')) 31 | runner = CliRunner() 32 | result = runner.invoke( 33 | radiance, 34 | ['tests/data/tiny_LC80100202015018LGN00_B1.TIF', 35 | 'tests/data/LC80100202015018LGN00_MTL.json', 36 | output, '--readtemplate', '.*/tiny_LC8.*\_B{b}.TIF']) 37 | assert result.exit_code == 0 38 | assert os.path.exists(output) 39 | with rasterio.open(output) as out: 40 | assert out.count == 1 41 | assert out.dtypes[0] == rasterio.uint16 42 | 43 | 44 | def test_cli_radiance_fail(tmpdir): 45 | output = str(tmpdir.join('toa_radiance.tif')) 46 | runner = CliRunner() 47 | result = runner.invoke( 48 | radiance, 49 | ['tests/data/tiny_LC80100202015018LGN00_B1.TIF', 50 | 'tests/data/LC80100202015018LGN00_MTL.json', 51 | output, '.*/Fail_LC8.*\_B{b}.TIF']) 52 | assert result.exit_code != 0 53 | 54 | 55 | def test_cli_reflectance_default(tmpdir): 56 | output = str(tmpdir.join('toa_reflectance.tif')) 57 | runner = CliRunner() 58 | result = runner.invoke( 59 | reflectance, 60 | ['tests/data/LC81060712016134LGN00_B3.TIF', 61 | 'tests/data/LC81060712016134LGN00_MTL.json', 62 | output]) 63 | assert result.exit_code == 0 64 | with rasterio.open(output) as out: 65 | assert out.count == 1 66 | assert out.dtypes[0] == rasterio.uint16 67 | 68 | 69 | def test_cli_reflectance_good(tmpdir): 70 | output = str(tmpdir.join('toa_reflectance_readtemplate.TIF')) 71 | runner = CliRunner() 72 | result = runner.invoke( 73 | reflectance, 74 | ['tests/data/tiny_LC81390452014295LGN00_B5.TIF', 75 | 'tests/data/LC81390452014295LGN00_MTL.json', 76 | output, '--readtemplate', '.*/tiny_LC8.*\_B{b}.TIF']) 77 | assert result.exit_code == 0 78 | with rasterio.open(output) as out: 79 | assert out.count == 1 80 | assert out.dtypes[0] == rasterio.uint16 81 | 82 | 83 | def test_cli_reflectance_l8_bidx(tmpdir): 84 | output = str(tmpdir.join('toa_reflectance.tif')) 85 | runner = CliRunner() 86 | result = runner.invoke( 87 | reflectance, 88 | ['tests/data/tiny_LC81390452014295LGN00_B5.TIF', 89 | 'tests/data/LC81390452014295LGN00_MTL.json', 90 | output, '--l8-bidx', 'notint']) 91 | assert result.exit_code != 0 92 | assert 'Error: Invalid value for "--l8-bidx":' \ 93 | ' notint is not a valid integer\n' in result.output 94 | 95 | 96 | def test_cli_reflectance_fail(tmpdir): 97 | output = str(tmpdir.join('toa_reflectance_readtemplate.TIF')) 98 | runner = CliRunner() 99 | result = runner.invoke( 100 | reflectance, 101 | ['tests/data/tiny_LC81390452014295LGN00_B5.TIF', 102 | 'tests/data/LC81390452014295LGN00_MTL.json', 103 | output]) 104 | assert result.exit_code != 0 105 | 106 | 107 | def test_cli_reflectance_fail2(tmpdir): 108 | output = str(tmpdir.join('toa_reflectance_readtemplate.TIF')) 109 | runner = CliRunner() 110 | result = runner.invoke( 111 | reflectance, 112 | ['tests/data/tiny_LC81390452014295LGN00_B5.TIF', 113 | 'tests/data/LC81390452014295LGN00_MTL.json', 114 | output, '.*/Fail_LC8.*\_B{b}.TIF']) 115 | assert result.exit_code != 0 116 | 117 | 118 | def test_cli_reflectance_multiband_stack(tmpdir): 119 | output = str(tmpdir.join('toa_reflectance_multiband_stack.TIF')) 120 | runner = CliRunner() 121 | result = runner.invoke( 122 | reflectance, 123 | ['tests/data/tiny_LC80460282016177LGN00_B2.TIF', 124 | 'tests/data/tiny_LC80460282016177LGN00_B3.TIF', 125 | 'tests/data/tiny_LC80460282016177LGN00_B4.TIF', 126 | 'tests/data/LC80460282016177LGN00_MTL.json', 127 | output, '-t', '.*/tiny_LC8.*\_B{b}.TIF', 128 | '--dst-dtype', 'uint16']) 129 | assert len(os.listdir(str(tmpdir))) == 1 130 | assert result.exit_code == 0 131 | with rasterio.open(output) as out: 132 | assert out.count == 3 133 | assert out.dtypes[0] == rasterio.uint16 134 | 135 | 136 | def test_cli_brighttemp(tmpdir): 137 | output = str(tmpdir.join('toa_brightness_temp.TIF')) 138 | runner = CliRunner() 139 | result = runner.invoke(brighttemp, 140 | ['tests/data/tiny_LC81390452014295LGN00_B10.TIF', 141 | 'tests/data/LC81390452014295LGN00_MTL.json', 142 | output, '-t', '.*/tiny_LC8.*\_B{b}.TIF']) 143 | 144 | assert len(os.listdir(str(tmpdir))) == 1 145 | assert result.exit_code == 0 146 | 147 | 148 | def test_cli_brighttemp_thermal_bidx(tmpdir): 149 | output = str(tmpdir.join('toa_brightness_temp.TIF')) 150 | runner = CliRunner() 151 | result = runner.invoke(brighttemp, 152 | ['tests/data/tiny_LC81390452014295LGN00_B10.TIF', 153 | 'tests/data/LC81390452014295LGN00_MTL.json', 154 | output, '-t', '.*/tiny_LC8.*\_B{b}.TIF', 155 | '--thermal_bidx', '11']) 156 | 157 | assert len(os.listdir(str(tmpdir))) == 1 158 | assert result.exit_code == 0 159 | 160 | 161 | def test_cli_brighttemp_thermal_bidx(tmpdir): 162 | output = str(tmpdir.join('toa_brightness_temp.TIF')) 163 | runner = CliRunner() 164 | result = runner.invoke(brighttemp, 165 | ['tests/data/tiny_LC81390452014295LGN00_B10.TIF', 166 | 'tests/data/LC81390452014295LGN00_MTL.json', 167 | output, '-t', '.*/tiny_LC8.*\_B{b}.TIF', 168 | '-v']) 169 | 170 | assert len(os.listdir(str(tmpdir))) == 1 171 | assert result.exit_code == 0 172 | 173 | 174 | def test_cli_parsemtl_good(tmpdir): 175 | runner = CliRunner() 176 | result = runner.invoke( 177 | parsemtl, 178 | ['tests/data/mtltest_LC80100202015018LGN00_MTL.txt']) 179 | assert result.exit_code == 0 180 | assert json.loads(result.output) == dict( 181 | {"L1_METADATA_FILE": 182 | {"METADATA_FILE_INFO": 183 | {"ORIGIN": 184 | "Image courtesy of the U.S. Geological Survey", 185 | "LANDSAT_SCENE_ID": "LC80100202015018LGN00", 186 | "PROCESSING_SOFTWARE_VERSION": "LPGS_2.4.0", 187 | "REQUEST_ID": "0501501184561_00001"}, 188 | "PRODUCT_METADATA": 189 | {"SCENE_CENTER_TIME": "15:10:22.4142571Z", 190 | "DATE_ACQUIRED": "2015-01-18", 191 | "DATA_TYPE": "L1T"}}}) 192 | 193 | 194 | def test_cli_parsemtl_fail(tmpdir): 195 | runner = CliRunner() 196 | result = runner.invoke( 197 | parsemtl, 198 | ['tests/data/tiny_mtltest_LC80100202015018LGN00_MTL.txt']) 199 | assert result.exit_code != 0 200 | -------------------------------------------------------------------------------- /tests/test_radiance.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import numpy as np 4 | import rasterio as rio 5 | import riomucho 6 | import pytest 7 | from rasterio.coords import BoundingBox 8 | 9 | from rio_toa import toa_utils, sun_utils 10 | from rio_toa import radiance 11 | 12 | 13 | def test_radiance(): 14 | band = np.array([[0, 0, 0], 15 | [0, 1, 1], 16 | [1, 0, 1]]).astype('float32') 17 | 18 | ML = 0.2 19 | AL = -0.1 20 | 21 | assert np.array_equal(radiance.radiance(band, ML, AL), 22 | np.array([[0., 0., 0.], 23 | [0., 0.1, 0.1], 24 | [0.1, 0., 0.1]]).astype(np.float32)) 25 | 26 | 27 | def test_radiance_wrong_type(): 28 | band = np.array([[9931., 9872., 9939.], 29 | [0., 5000., 100.], 30 | [10000.1, 0., 100002.]]).astype('float32') 31 | 32 | with pytest.raises(TypeError): 33 | radiance.radiance(band, '45sldf', -0.1, 65.0) 34 | 35 | 36 | def test_radiance_wrong_shape(): 37 | band = np.array([[0, 0, 0], 38 | [0, 1, 1], 39 | [1, 0, 1]]).astype('float32') 40 | 41 | # wrong ML shape 42 | with pytest.raises(ValueError): 43 | radiance.radiance(band, np.array([[1, 2, 3], [4, 5, 6]]), 44 | -0.00000001) 45 | 46 | # wrong AL shape 47 | with pytest.raises(ValueError): 48 | radiance.radiance(band, 35.1, np.array([1, 3])) 49 | 50 | 51 | @pytest.fixture 52 | def test_var(): 53 | src_path = 'tests/data/tiny_LC81390452014295LGN00_B5.TIF' 54 | src_mtl = 'tests/data/LC81390452014295LGN00_MTL.json' 55 | dst_path = 'tests/data/tiny_LC81390452014295LGN00_B5_radl.TIF' 56 | 57 | return src_path, src_mtl, dst_path 58 | 59 | 60 | @pytest.fixture 61 | def test_data(test_var): 62 | src_path, src_mtl, dst_path = test_var 63 | 64 | with rio.open(src_path, 'r') as src: 65 | tif = src.read(1) 66 | tif_meta = src.meta 67 | tif_shape = src.shape 68 | 69 | with rio.open(dst_path, 'r') as src: 70 | tif_output = src.read(1) 71 | tif_output_meta = src.meta 72 | 73 | with open(src_mtl, 'r') as src: 74 | mtl = json.loads(src.read()) 75 | 76 | return tif, tif_meta, tif_output, tif_shape, tif_output_meta, mtl 77 | 78 | 79 | def test_calculate_radiance(test_data): 80 | tif, tif_meta, tif_output, tif_shape, tif_output_meta, mtl = test_data 81 | 82 | M = toa_utils._load_mtl_key(mtl, 83 | ['L1_METADATA_FILE', 84 | 'RADIOMETRIC_RESCALING', 85 | 'RADIANCE_MULT_BAND_'], 86 | 5) 87 | A = toa_utils._load_mtl_key(mtl, 88 | ['L1_METADATA_FILE', 89 | 'RADIOMETRIC_RESCALING', 90 | 'RADIANCE_ADD_BAND_'], 91 | 5) 92 | 93 | assert isinstance(M, float) 94 | toa = radiance.radiance(tif, M, A) 95 | toa_rescaled = toa_utils.rescale(toa, 255, np.uint8) 96 | scale = float(np.iinfo(np.uint16).max) / float(np.iinfo(np.uint8).max) 97 | tif_out_rescaled = np.clip( 98 | (tif_output / scale), 99 | 0, np.iinfo(np.uint8).max).astype(np.uint8) 100 | assert toa_rescaled.dtype == np.uint8 101 | assert np.min(tif_out_rescaled) == np.min(toa_rescaled) 102 | assert int(np.max(tif_out_rescaled)) == int(np.max(toa_rescaled)) 103 | 104 | 105 | def test_calculate_radiance2(test_data): 106 | tif, tif_meta, tif_output, tif_shape, tif_output_meta, mtl = test_data 107 | M = toa_utils._load_mtl_key(mtl, 108 | ['L1_METADATA_FILE', 109 | 'RADIOMETRIC_RESCALING', 110 | 'RADIANCE_MULT_BAND_'], 111 | 5) 112 | A = toa_utils._load_mtl_key(mtl, 113 | ['L1_METADATA_FILE', 114 | 'RADIOMETRIC_RESCALING', 115 | 'RADIANCE_ADD_BAND_'], 116 | 5) 117 | 118 | toa = toa_utils.rescale( 119 | radiance.radiance(tif, M, A), 120 | 55000, np.uint16) 121 | assert toa.dtype == np.uint16 122 | assert np.all(toa) < 1.5 123 | assert np.all(toa) >= 0.0 124 | 125 | 126 | def test_calculate_landsat_radiance(test_var, capfd): 127 | src_path, src_mtl = test_var[:2] 128 | dst_path = '/tmp/rad1.TIF' 129 | rescale_factor = 1.0 130 | creation_options = {} 131 | band = 5 132 | dst_dtype = 'uint8' 133 | processes = 1 134 | radiance.calculate_landsat_radiance( 135 | src_path, src_mtl, dst_path, 136 | rescale_factor, creation_options, band, 137 | dst_dtype, processes) 138 | out, err = capfd.readouterr() 139 | assert os.path.exists(dst_path) 140 | -------------------------------------------------------------------------------- /tests/test_reflectance.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | from affine import Affine 5 | import click 6 | import numpy as np 7 | import pytest 8 | import rasterio as rio 9 | from rasterio.coords import BoundingBox 10 | from rasterio.warp import reproject, Resampling 11 | 12 | from rio_toa import toa_utils, sun_utils 13 | from rio_toa import reflectance 14 | 15 | 16 | def affaux(up): 17 | return Affine(1, 0, 0, 0, -1, 0), Affine(up, 0, 0, 0, -up, 0) 18 | 19 | 20 | def upsample_array(bidx, up, fr, to): 21 | upBidx = np.empty( 22 | (bidx.shape[0] * up, bidx.shape[1] * up), dtype=bidx.dtype) 23 | 24 | reproject( 25 | bidx, upBidx, 26 | src_transform=fr, 27 | dst_transform=to, 28 | src_crs="EPSG:3857", 29 | dst_crs="EPSG:3857", 30 | resampling=Resampling.bilinear) 31 | 32 | return upBidx 33 | 34 | 35 | def flex_compare(r1, r2, thresh=10): 36 | upsample = 4 37 | r1 = r1[::upsample] 38 | r2 = r2[::upsample] 39 | toAff, frAff = affaux(upsample) 40 | r1 = upsample_array(r1, upsample, frAff, toAff) 41 | r2 = upsample_array(r2, upsample, frAff, toAff) 42 | tdiff = np.abs(r1.astype(np.float64) - r2.astype(np.float64)) 43 | click.echo('{0} values exceed the threshold' 44 | 'difference with a max variance of {1}'.format( 45 | np.sum(tdiff > thresh), tdiff.max()), err=True) 46 | return not np.any(tdiff > thresh) 47 | 48 | 49 | def test_reflectance(): 50 | band = np.array([[0, 0, 0], 51 | [0, 1, 1], 52 | [1, 0, 1]]).astype('float32') 53 | 54 | MR = 0.2 55 | AR = -0.1 56 | E = 90.0 57 | 58 | assert np.array_equal(reflectance.reflectance(band, MR, AR, E), 59 | np.array([[0., 0., 0.], 60 | [0., 0.1, 0.1], 61 | [0.1, 0., 0.1]]).astype(np.float32)) 62 | 63 | 64 | def test_reflectance_wrong_type(): 65 | band = np.array([[9931., 9872., 9939.], 66 | [0., 5000., 100.], 67 | [10000.1, 0., 100002.]]).astype('float32') 68 | 69 | with pytest.raises(TypeError): 70 | reflectance.reflectance(band, '45sldf', -0.1, 65.0) 71 | 72 | 73 | def test_reflectance_wrong_shape(): 74 | band = np.array([[0, 0, 0], 75 | [0, 1, 1], 76 | [1, 0, 1]]).astype('float32') 77 | 78 | # wrong sun elevation shape 79 | with pytest.raises(ValueError): 80 | reflectance.reflectance(band, 0.2, -0.00000001, 81 | np.array([[1, 2, 3], [4, 5, 6]])) 82 | 83 | with pytest.raises(ValueError): 84 | reflectance.reflectance(band, 35.1, np.array([1, 3]), 90.0) 85 | 86 | 87 | def test_reflectance_negative_elevation(): 88 | band = np.array([[0, 0, 0], 89 | [0, 2, 1], 90 | [2, 0, 1.00008]]).astype('float32') 91 | MR = 0.2 92 | AR = -0.1 93 | E = -90.0 94 | 95 | with pytest.raises(ValueError): 96 | reflectance.reflectance(band, MR, AR, E) 97 | 98 | 99 | @pytest.fixture 100 | def test_var(): 101 | src_path_b = 'tests/data/tiny_LC80460282016177LGN00_B2.TIF' 102 | src_path_g = 'tests/data/tiny_LC80460282016177LGN00_B3.TIF' 103 | src_path_r = 'tests/data/tiny_LC80460282016177LGN00_B4.TIF' 104 | src_mtl = 'tests/data/LC80460282016177LGN00_MTL.json' 105 | dst_path_single = 'tests/data/tiny_LC80460282016177LGN00_B2_refl.TIF' 106 | dst_path_stack = 'tests/data/tiny_LC80460282016177LGN00_rgb_refl.TIF' 107 | 108 | return src_path_b, src_path_g, src_path_r, \ 109 | src_mtl, dst_path_single, dst_path_stack 110 | 111 | 112 | @pytest.fixture 113 | def test_data(test_var): 114 | src_path_b, src_path_g, src_path_r, \ 115 | src_mtl, dst_path_single, dst_path_stack = test_var 116 | 117 | with rio.open(src_path_b, 'r') as src: 118 | tif_b = src.read(1) 119 | tif_meta = src.meta 120 | tif_shape = src.shape 121 | 122 | with rio.open(src_path_g, 'r') as src: 123 | tif_g = src.read(1) 124 | 125 | with rio.open(src_path_r, 'r') as src: 126 | tif_r = src.read(1) 127 | 128 | with rio.open(dst_path_single, 'r') as src: 129 | tif_output_single = src.read(1) 130 | tif_output_single_meta = src.meta 131 | 132 | with rio.open(dst_path_stack, 'r') as src: 133 | tif_output_stack = src.read(1) 134 | tif_output_stack_meta = src.meta 135 | 136 | with open(src_mtl, 'r') as src: 137 | mtl = json.loads(src.read()) 138 | 139 | return tif_b, tif_g, tif_r, tif_meta, tif_shape, \ 140 | tif_output_single, tif_output_single_meta, \ 141 | tif_output_stack, tif_output_stack_meta, mtl 142 | 143 | 144 | def test_calculate_reflectance(test_data): 145 | tif_b, tif_output_single, mtl = test_data[0], test_data[5], test_data[-1] 146 | 147 | M = toa_utils._load_mtl_key(mtl, 148 | ['L1_METADATA_FILE', 149 | 'RADIOMETRIC_RESCALING', 150 | 'REFLECTANCE_MULT_BAND_'], 151 | 5) 152 | A = toa_utils._load_mtl_key(mtl, 153 | ['L1_METADATA_FILE', 154 | 'RADIOMETRIC_RESCALING', 155 | 'REFLECTANCE_ADD_BAND_'], 156 | 5) 157 | E = toa_utils._load_mtl_key(mtl, 158 | ['L1_METADATA_FILE', 159 | 'IMAGE_ATTRIBUTES', 160 | 'SUN_ELEVATION']) 161 | 162 | assert (np.sin(np.radians(E)) <= 1) & (-1 <= np.sin(np.radians(E))) 163 | assert isinstance(M, float) 164 | toa = reflectance.reflectance(tif_b, M, A, E) 165 | toa_rescaled = toa_utils.rescale(toa, 55000.0, np.uint16, clip=False) 166 | assert toa_rescaled.dtype == np.uint16 167 | # Note, the test data was created under a rescaling code, hence the fuzziness 168 | diff = toa_rescaled[310:315, 310:315] - tif_output_single[310:315, 310:315] 169 | assert diff.max() <= 1 170 | 171 | def test_calculate_reflectance_uint8(test_data): 172 | tif_b, tif_output_single, mtl = test_data[0], test_data[5], test_data[-1] 173 | 174 | M = toa_utils._load_mtl_key(mtl, 175 | ['L1_METADATA_FILE', 176 | 'RADIOMETRIC_RESCALING', 177 | 'REFLECTANCE_MULT_BAND_'], 178 | 5) 179 | A = toa_utils._load_mtl_key(mtl, 180 | ['L1_METADATA_FILE', 181 | 'RADIOMETRIC_RESCALING', 182 | 'REFLECTANCE_ADD_BAND_'], 183 | 5) 184 | E = toa_utils._load_mtl_key(mtl, 185 | ['L1_METADATA_FILE', 186 | 'IMAGE_ATTRIBUTES', 187 | 'SUN_ELEVATION']) 188 | 189 | assert (np.sin(np.radians(E)) <= 1) & (-1 <= np.sin(np.radians(E))) 190 | assert isinstance(M, float) 191 | toa = reflectance.reflectance(tif_b, M, A, E) 192 | toa_rescaled = toa_utils.rescale(toa, 215, np.uint8) 193 | scale = float(np.iinfo(np.uint16).max) / float(np.iinfo(np.uint8).max) 194 | tif_out_rescaled = np.clip( 195 | (tif_output_single / scale), 196 | 0, np.iinfo(np.uint8).max).astype(np.uint8) 197 | assert toa_rescaled.dtype == np.uint8 198 | assert np.min(tif_out_rescaled) == np.min(toa_rescaled) 199 | 200 | 201 | def test_calculate_reflectance2(test_data): 202 | tif_b, tif_shape, mtl = test_data[0], test_data[4], test_data[-1] 203 | 204 | M = toa_utils._load_mtl_key(mtl, 205 | ['L1_METADATA_FILE', 206 | 'RADIOMETRIC_RESCALING', 207 | 'REFLECTANCE_MULT_BAND_'], 208 | 5) 209 | A = toa_utils._load_mtl_key(mtl, 210 | ['L1_METADATA_FILE', 211 | 'RADIOMETRIC_RESCALING', 212 | 'REFLECTANCE_ADD_BAND_'], 213 | 5) 214 | date_collected = toa_utils._load_mtl_key(mtl, 215 | ['L1_METADATA_FILE', 216 | 'PRODUCT_METADATA', 217 | 'DATE_ACQUIRED']) 218 | time_collected_utc = toa_utils._load_mtl_key(mtl, 219 | ['L1_METADATA_FILE', 220 | 'PRODUCT_METADATA', 221 | 'SCENE_CENTER_TIME']) 222 | bounds = BoundingBox(*toa_utils._get_bounds_from_metadata( 223 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA'])) 224 | E = sun_utils.sun_elevation(bounds, 225 | tif_shape, 226 | date_collected, 227 | time_collected_utc) 228 | toa = reflectance.reflectance(tif_b, M, A, E) 229 | toa_rescaled = toa_utils.rescale(toa, 55000.0, np.uint16) 230 | assert toa_rescaled.dtype == np.uint16 231 | assert np.all(toa_rescaled) < 1.5 232 | assert np.all(toa_rescaled) >= 0.0 233 | 234 | 235 | def test_calculate_landsat_reflectance(test_var, capfd): 236 | src_path, src_mtl = test_var[0], test_var[3] 237 | dst_path = '/tmp/ref1.TIF' 238 | rescale_factor = 1.0 239 | creation_options = {} 240 | band = 5 241 | dst_dtype = 'uint8' 242 | processes = 1 243 | pixel_sunangle = False 244 | reflectance.calculate_landsat_reflectance([src_path], src_mtl, dst_path, 245 | rescale_factor, creation_options, 246 | [band], dst_dtype, processes, 247 | pixel_sunangle) 248 | out, err = capfd.readouterr() 249 | assert os.path.exists(dst_path) 250 | 251 | 252 | def test_calculate_landsat_reflectance_single_pixel(test_var, capfd): 253 | src_path, src_mtl = test_var[0], test_var[3] 254 | dst_path = '/tmp/ref1.tif' 255 | expected_path = 'tests/expected/ref1.tif' 256 | rescale_factor = 1.0 257 | creation_options = {} 258 | band = 5 259 | dst_dtype = 'uint16' 260 | processes = 1 261 | pixel_sunangle = True 262 | 263 | reflectance.calculate_landsat_reflectance([src_path], src_mtl, dst_path, 264 | rescale_factor, creation_options, 265 | [band], dst_dtype, processes, 266 | pixel_sunangle) 267 | out, err = capfd.readouterr() 268 | 269 | with rio.open(dst_path) as created: 270 | with rio.open(expected_path) as expected: 271 | assert flex_compare(created.read(), expected.read()) 272 | 273 | 274 | def test_calculate_landsat_reflectance_stack_pixel(test_var, test_data, capfd): 275 | src_path, src_mtl, tif_output_stack = \ 276 | test_var[:3], test_var[3], test_data[-3] 277 | dst_path = '/tmp/ref2.tif' 278 | expected_path = 'tests/expected/ref2.tif' 279 | creation_options = {} 280 | dst_dtype = 'uint8' 281 | rescale_factor = 215 282 | processes = 1 283 | pixel_sunangle = True 284 | 285 | reflectance.calculate_landsat_reflectance(list(src_path), src_mtl, 286 | dst_path, rescale_factor, 287 | creation_options, [4, 3, 2], 288 | dst_dtype, processes, 289 | pixel_sunangle) 290 | 291 | out, err = capfd.readouterr() 292 | assert os.path.exists(dst_path) 293 | 294 | with rio.open(dst_path) as created: 295 | with rio.open(expected_path) as expected: 296 | assert flex_compare(created.read(), expected.read()) 297 | -------------------------------------------------------------------------------- /tests/test_sun_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import json 3 | 4 | from rio_toa import sun_utils, toa_utils 5 | from rasterio.coords import BoundingBox 6 | import datetime 7 | import numpy as np 8 | 9 | from rio_toa.sun_utils import ( 10 | parse_utc_string, time_to_dec_hour, calculate_declination, 11 | solar_angle, sun_elevation, _create_lnglats) 12 | 13 | 14 | def test_parse_utc_string(): 15 | assert parse_utc_string('2014-10-22', '04:37:48.7052949Z') == \ 16 | datetime.datetime(2014, 10, 22, 4, 37, 48) 17 | 18 | 19 | def test_time_to_dec_hour(): 20 | assert time_to_dec_hour( 21 | datetime.datetime(2014, 10, 22, 4, 37, 48)) == \ 22 | 4.630000000000001 23 | 24 | 25 | def test_declination(): 26 | d = 173 27 | lat = 21.6668 28 | assert np.rad2deg(calculate_declination(d)) > 0.0 29 | 30 | 31 | @pytest.fixture 32 | def test_data(): 33 | mtl1 = toa_utils._load_mtl('tests/data/LC81060712016134LGN00_MTL.json') 34 | mtl2 = toa_utils._load_mtl('tests/data/LC80430302016140LGN00_MTL.json') 35 | mtl3 = toa_utils._load_mtl('tests/data/LC82290902015304LGN00_MTL.json') 36 | mtl4 = toa_utils._load_mtl('tests/data/LC80100202015018LGN00_MTL.json') 37 | 38 | return mtl1, mtl2, mtl3, mtl4 39 | 40 | 41 | def test_make_lat_lngs_n(): 42 | lngs, lats = _create_lnglats((5, 5), [-120., 37., -119., 38.]) 43 | assert lats[0, 0] > lats[-1, 0] 44 | assert lngs[0, -1] > lngs[0, 0] 45 | 46 | 47 | def test_make_lat_lngs_s(): 48 | lngs, lats = _create_lnglats((5, 5), [-120., -38., -119., -37.]) 49 | assert lats[0, 0] > lats[-1, 0] 50 | assert lngs[0, -1] > lngs[0, 0] 51 | 52 | 53 | def test_sun_angle(test_data): 54 | # South, Summer 55 | mtl = test_data[0] 56 | mtl_sun = mtl['L1_METADATA_FILE']['IMAGE_ATTRIBUTES']['SUN_ELEVATION'] 57 | bbox = BoundingBox(*toa_utils._get_bounds_from_metadata( 58 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA'])) 59 | 60 | sunangles = sun_utils.sun_elevation( 61 | bbox, 62 | (100, 100), 63 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA']['DATE_ACQUIRED'], 64 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA']['SCENE_CENTER_TIME']) 65 | 66 | assert sunangles.max() > mtl_sun 67 | assert sunangles.min() < mtl_sun 68 | 69 | 70 | def test_sun_angle2(test_data): 71 | # North, Summer 72 | mtl = test_data[1] 73 | mtl_sun = mtl['L1_METADATA_FILE']['IMAGE_ATTRIBUTES']['SUN_ELEVATION'] 74 | bbox = BoundingBox(*toa_utils._get_bounds_from_metadata( 75 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA'])) 76 | 77 | sunangles = sun_utils.sun_elevation( 78 | bbox, 79 | (100, 100), 80 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA']['DATE_ACQUIRED'], 81 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA']['SCENE_CENTER_TIME']) 82 | 83 | assert np.mean(sunangles[0, :]) < np.mean(sunangles[-1, :]), "In N, Nrow should < Srow" 84 | assert sunangles.max() > mtl_sun 85 | assert sunangles.min() < mtl_sun 86 | 87 | 88 | def test_sun_angle3(test_data): 89 | # South, Winter 90 | mtl = test_data[2] 91 | mtl_sun = mtl['L1_METADATA_FILE']['IMAGE_ATTRIBUTES']['SUN_ELEVATION'] 92 | bbox = BoundingBox(*toa_utils._get_bounds_from_metadata( 93 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA'])) 94 | 95 | sunangles = sun_utils.sun_elevation( 96 | bbox, 97 | (100, 100), 98 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA']['DATE_ACQUIRED'], 99 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA']['SCENE_CENTER_TIME']) 100 | 101 | assert sunangles[49][49] - mtl_sun < 5 102 | 103 | 104 | def test_sun_angle4(test_data): 105 | # South, Winter 106 | mtl = test_data[3] 107 | mtl_sun = mtl['L1_METADATA_FILE']['IMAGE_ATTRIBUTES']['SUN_ELEVATION'] 108 | bbox = BoundingBox(*toa_utils._get_bounds_from_metadata( 109 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA'])) 110 | 111 | sunangles = sun_utils.sun_elevation( 112 | bbox, 113 | (100, 100), 114 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA']['DATE_ACQUIRED'], 115 | mtl['L1_METADATA_FILE']['PRODUCT_METADATA']['SCENE_CENTER_TIME']) 116 | 117 | assert sunangles[49][49] - mtl_sun < 5 118 | 119 | 120 | @pytest.fixture 121 | def sun_elev_test_data(): 122 | with open('tests/data/path164sundata.json') as dsrc: 123 | return json.loads(dsrc.read()) 124 | 125 | 126 | def test_sun_elev_calc(sun_elev_test_data): 127 | for d in sun_elev_test_data: 128 | pred_sun_el = sun_utils.sun_elevation( 129 | BoundingBox(*d['bbox']), 130 | (10, 10), 131 | d['date_acquired'], 132 | d['scene_center_time'] 133 | ) 134 | assert pred_sun_el.max() > d['mtl_sun_elevation'] 135 | assert pred_sun_el.min() < d['mtl_sun_elevation'] 136 | -------------------------------------------------------------------------------- /tests/test_toa_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | 4 | from rio_toa.toa_utils import ( 5 | _parse_bands_from_filename, 6 | _load_mtl_key, _load_mtl, rescale, 7 | temp_rescale) 8 | 9 | 10 | 11 | def test_parse_band_from_filename_default(): 12 | assert _parse_bands_from_filename(['data/LC81070352015122LGN00_B3.TIF'], 13 | '.*/LC8.*\_B{b}.TIF') == [3] 14 | 15 | 16 | def test_parse_band_from_filename_good(): 17 | assert _parse_bands_from_filename( 18 | ['tiny_LC81070352015122LGN00_B3.tif'], 19 | 'tiny_LC8.*_B{b}.tif') == [3] 20 | 21 | 22 | def test_parse_band_from_filename_bad(): 23 | with pytest.raises(ValueError): 24 | _parse_bands_from_filename( 25 | ['LC81070352015122LGN00_B3.tif'], 26 | 'LC8NOGOOD.*_B{b}.tif') 27 | 28 | 29 | def test_parse_band_from_filename_bad2(): 30 | with pytest.raises(ValueError): 31 | _parse_bands_from_filename( 32 | ['data/tiny_LC81070352015122LGN00_B3.tif'], 33 | '.*/LC8.*\_B{b}.TIF') 34 | 35 | 36 | def test_load_mtl(): 37 | src_mtl = 'tests/data/LC80100202015018LGN00_MTL.json' 38 | mtl = _load_mtl(src_mtl) 39 | assert isinstance(mtl, dict) 40 | 41 | 42 | def test_load_txt_mtl_1(): 43 | txtmtl = _load_mtl('tests/data/LC81060712016134LGN00_MTL.txt') 44 | jsonmtl = _load_mtl('tests/data/LC81060712016134LGN00_MTL.json') 45 | 46 | for k in jsonmtl['L1_METADATA_FILE'].keys(): 47 | assert k in txtmtl['L1_METADATA_FILE'] 48 | assert jsonmtl['L1_METADATA_FILE'][k] == txtmtl['L1_METADATA_FILE'][k] 49 | 50 | 51 | def test_load_txt_mtl_2(): 52 | txtmtl = _load_mtl('tests/data/LC80100202015018LGN00_MTL.txt') 53 | jsonmtl = _load_mtl('tests/data/LC80100202015018LGN00_MTL.json') 54 | 55 | for k in jsonmtl['L1_METADATA_FILE'].keys(): 56 | assert k in txtmtl['L1_METADATA_FILE'] 57 | assert jsonmtl['L1_METADATA_FILE'][k] == txtmtl['L1_METADATA_FILE'][k] 58 | 59 | 60 | def test_load_mtl_key(): 61 | mtl_test = {u'L1_METADATA_FILE': 62 | {u'IMAGE_ATTRIBUTES': 63 | {u'CLOUD_COVER': 19.74, 64 | u'SUN_AZIMUTH': 164.19023018}, 65 | u'TIRS_THERMAL_CONSTANTS': 66 | {u'K1_CONSTANT_BAND_10': 774.89, 67 | u'K1_CONSTANT_BAND_11': 480.89}, 68 | u'RADIOMETRIC_RESCALING': 69 | {u'RADIANCE_ADD_BAND_1': -64.85281, 70 | u'RADIANCE_MULT_BAND_1': 0.012971}}} 71 | 72 | keys = ['L1_METADATA_FILE', 'TIRS_THERMAL_CONSTANTS', 'K1_CONSTANT_BAND_'] 73 | K11 = _load_mtl_key(mtl_test, keys, band=11) 74 | assert K11 == 480.89 75 | 76 | keys2 = ['L1_METADATA_FILE', 'IMAGE_ATTRIBUTES', 'CLOUD_COVER'] 77 | clouds = _load_mtl_key(mtl_test, keys2, band=None) 78 | assert clouds == 19.74 79 | 80 | 81 | def test_rescale(): 82 | arr = np.array(np.linspace(0.0, 1.5, num=9).reshape(3, 3)) 83 | dtype = np.__dict__['uint16'] 84 | rescale_factor = 1.0 85 | rescaled_arr = rescale(arr, rescale_factor, dtype) 86 | mask = (rescaled_arr != np.iinfo(np.uint16).max) & (rescaled_arr != 1.0) 87 | 88 | assert np.all(rescaled_arr) <= np.iinfo(np.uint16).max 89 | assert np.all(rescaled_arr) >= 0.0 90 | assert np.array_equal(rescaled_arr[mask], arr[mask].astype(int)) 91 | assert rescaled_arr.dtype == 'uint16' 92 | 93 | 94 | def test_rescale2(): 95 | arr = np.array(np.linspace(0.0, 1.5, num=9).reshape(3, 3)) 96 | dtype = np.__dict__['uint8'] 97 | rescale_factor = 1.0 98 | rescaled_arr = rescale(arr, rescale_factor, dtype) 99 | mask = (rescaled_arr != np.iinfo(dtype).max) & (rescaled_arr != 1.0) 100 | 101 | assert np.all(rescaled_arr) <= np.iinfo(dtype).max 102 | assert np.all(rescaled_arr) >= 0.0 103 | assert np.array_equal(rescaled_arr[mask], arr[mask].astype(int)) 104 | assert rescaled_arr.dtype == 'uint8' 105 | 106 | 107 | def test_rescale_dtype(): 108 | arr = np.array(np.linspace(0.0, 1.5, num=9).reshape(3, 3)) 109 | dtype = np.__dict__['float32'] 110 | rescale_factor = 1.0 111 | rescaled_arr = rescale(arr, rescale_factor, dtype) 112 | assert rescaled_arr.dtype == dtype 113 | 114 | 115 | def test_rescale_clip(): 116 | arr = np.array(np.linspace(0.0, 1.5, num=9).reshape(3, 3)) 117 | dtype = np.__dict__['float32'] 118 | rescale_factor = 1.0 119 | rescaled_arr = rescale(arr, rescale_factor, dtype, clip=True) 120 | assert rescaled_arr.max() == 1.0 121 | rescaled_arr = rescale(arr, rescale_factor, dtype, clip=False) 122 | assert rescaled_arr.max() == 1.5 123 | 124 | 125 | def test_rescale_overflow(): 126 | arr = np.array(np.linspace(0.0, 1.5, num=9).reshape(3, 3)) 127 | dtype = np.__dict__['uint16'] 128 | rescale_factor = 65535 129 | rescaled_arr = rescale(arr, rescale_factor, dtype, clip=True) 130 | assert rescaled_arr.max() == 65535 131 | rescaled_arr = rescale(arr, rescale_factor, np.__dict__['float32'], clip=False) 132 | assert rescaled_arr.max() == 65535 * 1.5 133 | with pytest.raises(ValueError): 134 | # without clipping, this will overflow 135 | rescaled_arr = rescale(arr, rescale_factor, dtype, clip=False) 136 | 137 | 138 | def test_temp_rescale_K(): 139 | arr = np.array(np.linspace(0.0, 1.5, num=9).reshape(3, 3)) 140 | np.testing.assert_array_equal(arr, temp_rescale(arr, 'K')) 141 | 142 | 143 | def test_temp_rescale_F(): 144 | arr = np.array(np.linspace(0.0, 1.5, num=9).reshape(3, 3)) 145 | np.testing.assert_array_equal(arr * (9 / 5.0) - 459.67, 146 | temp_rescale(arr, 'F')) 147 | 148 | 149 | def test_temp_rescale_C(): 150 | arr = np.array(np.linspace(0.0, 1.5, num=9).reshape(3, 3)) 151 | np.testing.assert_array_equal(arr - 273.15, temp_rescale(arr, 'C')) 152 | 153 | 154 | def test_temp_rescale_error(): 155 | arr = np.array(np.linspace(0.0, 1.5, num=9).reshape(3, 3)) 156 | with pytest.raises(ValueError): 157 | temp_rescale(arr, 'FC') 158 | --------------------------------------------------------------------------------