def _compute_lst(landsat_ds, mtl_dict, water_bodies_mask): # brightness temperature (band 10) radio_rescale = mtl_dict['RADIOMETRIC_RESCALING'] thermal_constants = mtl_dict['TIRS_THERMAL_CONSTANTS'] bt_arr = brightness_temp.brightness_temp( landsat_ds['tirs'].values, radio_rescale['RADIANCE_MULT_BAND_10'], radio_rescale['RADIANCE_ADD_BAND_10'], thermal_constants['K1_CONSTANT_BAND_10'], thermal_constants['K2_CONSTANT_BAND_10']) - 273.15 ndvi_arr = _compute_ndvi(landsat_ds) min_ndvi = ndvi_arr.min() pv_arr = np.square((ndvi_arr - min_ndvi) / (ndvi_arr.max() - min_ndvi)) eps_arr = np.zeros_like(pv_arr) eps_arr[water_bodies_mask] = eps_water eps_arr[(ndvi_arr < ndvi_soil) & ~water_bodies_mask] = eps_soil mid_cond = (ndvi_arr >= ndvi_soil) & (ndvi_arr < ndvi_veg) & ~water_bodies_mask pv_mid_arr = pv_arr[mid_cond] eps_arr[mid_cond] = eps_veg * pv_mid_arr + eps_soil * (1 - pv_mid_arr) + C eps_arr[(ndvi_arr >= ndvi_veg) & ~water_bodies_mask] = eps_veg # land surface temperature (putting it all together) # lst_arr = bt_arr / (1 + (lambd * bt_arr / rho) * np.log(eps_arr)) lst_arr = bt_arr / (1 + (lambd * bt_arr / rho) * np.log(eps_arr)) # correct for potential infinities arising from divisions by zero # (landsat 8's nodata) # lst_arr[~landsat_mask] = landsat_meta['nodata'] # return np.nan_to_num(lst_arr, LANDSAT_NODATA) return lst_arr
def _convert(arr: numpy.ndarray, band: str, metadata: Dict) -> numpy.ndarray: """Convert DN to TOA or Temp.""" if band in ["1", "2", "3", "4", "5", "6", "7", "8", "9"]: # OLI multi_reflect = metadata["RADIOMETRIC_RESCALING"].get( f"REFLECTANCE_MULT_BAND_{band}" ) add_reflect = metadata["RADIOMETRIC_RESCALING"].get( f"REFLECTANCE_ADD_BAND_{band}" ) sun_elev = metadata["IMAGE_ATTRIBUTES"]["SUN_ELEVATION"] arr = 10000 * reflectance.reflectance( arr, multi_reflect, add_reflect, sun_elev, src_nodata=0 ) elif band in ["10", "11"]: # TIRS multi_rad = metadata["RADIOMETRIC_RESCALING"].get(f"RADIANCE_MULT_BAND_{band}") add_rad = metadata["RADIOMETRIC_RESCALING"].get(f"RADIANCE_ADD_BAND_{band}") k1 = metadata["TIRS_THERMAL_CONSTANTS"].get(f"K1_CONSTANT_BAND_{band}") k2 = metadata["TIRS_THERMAL_CONSTANTS"].get(f"K2_CONSTANT_BAND_{band}") arr = brightness_temp.brightness_temp(arr, multi_rad, add_rad, k1, k2) # TODO # elif band == "QA": return arr
def test_brightness_temp(img, ML, AL, K1, K2): L = img.astype(np.float32) * ML + AL src_nodata = 0.0 L[img == src_nodata] = np.nan Output = K2 / np.log((K1 / L) + 1) Result = brightness_temp.brightness_temp(img, ML, AL, K1, K2, src_nodata=0) np.testing.assert_array_equal(Output, Result)
def test_brightness_temperature2(test_data): tif, tif_meta, tif_output, tif_shape, tif_output_meta, mtl = test_data band = 11 M = toa_utils._load_mtl_key( mtl, ['L1_METADATA_FILE', 'RADIOMETRIC_RESCALING', 'RADIANCE_MULT_BAND_'], band) A = toa_utils._load_mtl_key( mtl, ['L1_METADATA_FILE', 'RADIOMETRIC_RESCALING', 'RADIANCE_ADD_BAND_'], band) K1 = toa_utils._load_mtl_key( mtl, ['L1_METADATA_FILE', 'TIRS_THERMAL_CONSTANTS', 'K1_CONSTANT_BAND_'], band) K2 = toa_utils._load_mtl_key( mtl, ['L1_METADATA_FILE', 'TIRS_THERMAL_CONSTANTS', 'K2_CONSTANT_BAND_'], band) assert isinstance(M, float) assert isinstance(A, float) assert isinstance(K1, float) assert isinstance(K2, float) BT = brightness_temp.brightness_temp(tif, M, A, K1, K2, src_nodata=0) assert BT.dtype == np.float32 assert flex_compare(tif_output, BT)
def landsat_min_max_worker(band, address, metadata, pmin=2, pmax=98, width=1024, height=1024): """Retrieve histogram percentage cut for a Landsat-8 scene. Attributes ---------- address : Landsat band AWS address band : Landsat band number metadata : Landsat metadata pmin : Histogram minimum cut (default: 2) pmax : Histogram maximum cut (default: 98) width : int, optional (default: 1024) Pixel width for the decimated read. height : int, optional (default: 1024) Pixel height for the decimated read. Returns ------- out : list, int returns a list of the min/max histogram cut values. """ if int(band) > 9: # TIRS multi_rad = metadata['RADIOMETRIC_RESCALING'].get( 'RADIANCE_MULT_BAND_{}'.format(band)) add_rad = metadata['RADIOMETRIC_RESCALING'].get( 'RADIANCE_ADD_BAND_{}'.format(band)) k1 = metadata['TIRS_THERMAL_CONSTANTS'].get( 'K1_CONSTANT_BAND_{}'.format(band)) k2 = metadata['TIRS_THERMAL_CONSTANTS'].get( 'K2_CONSTANT_BAND_{}'.format(band)) with rasterio.open('{}_B{}.TIF'.format(address, band)) as src: arr = src.read(indexes=1, out_shape=(height, width)).astype(src.profile['dtype']) arr = brightness_temp.brightness_temp(arr, multi_rad, add_rad, k1, k2) else: multi_reflect = metadata['RADIOMETRIC_RESCALING'].get( 'REFLECTANCE_MULT_BAND_{}'.format(band)) add_reflect = metadata['RADIOMETRIC_RESCALING'].get( 'REFLECTANCE_ADD_BAND_{}'.format(band)) sun_elev = metadata['IMAGE_ATTRIBUTES']['SUN_ELEVATION'] with rasterio.open('{}_B{}.TIF'.format(address, band)) as src: arr = src.read(indexes=1, out_shape=(height, width)).astype(src.profile['dtype']) arr = 10000 * reflectance.reflectance(arr, multi_reflect, add_reflect, sun_elev, src_nodata=0) return np.percentile(arr[arr > 0], (pmin, pmax)).astype(np.int).tolist()
def dn_to_toa(arr: numpy.ndarray, band: str, metadata: Dict) -> numpy.ndarray: """Convert DN to TOA or Temp. Args: arr (numpy.ndarray): Digital Number array values. band (str): Landsat 8 band's name. metadata (str): Landsat MTL metadata. Returns: numpy.ndarray: DN coverted to TOA or Temperature. """ if band in ["B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9"]: # OLI multi_reflect = metadata["RADIOMETRIC_RESCALING"].get( f"REFLECTANCE_MULT_BAND_{band[1:]}") add_reflect = metadata["RADIOMETRIC_RESCALING"].get( f"REFLECTANCE_ADD_BAND_{band[1:]}") sun_elev = metadata["IMAGE_ATTRIBUTES"]["SUN_ELEVATION"] arr = 10000 * reflectance.reflectance( arr, multi_reflect, add_reflect, sun_elev, src_nodata=0) arr = arr.astype("uint16") elif band in ["B10", "B11"]: # TIRS multi_rad = metadata["RADIOMETRIC_RESCALING"].get( f"RADIANCE_MULT_BAND_{band[1:]}") add_rad = metadata["RADIOMETRIC_RESCALING"].get( f"RADIANCE_ADD_BAND_{band[1:]}") k1 = metadata["TIRS_THERMAL_CONSTANTS"].get( f"K1_CONSTANT_BAND_{band[1:]}") k2 = metadata["TIRS_THERMAL_CONSTANTS"].get( f"K2_CONSTANT_BAND_{band[1:]}") arr = brightness_temp.brightness_temp(arr, multi_rad, add_rad, k1, k2) return arr
def test_brightness_temp_wrong_shape(img, ML, AL, K1, K2): with pytest.raises(ValueError): brightness_temp.brightness_temp(img, ML, AL, K1, K2, src_nodata=0)
def dn2toa(self, platform, mtl_file=None, wavelengths=None): """This method converts digital numbers to top of atmosphere reflectance, like described here: https://www.usgs.gov/land-resources/nli/landsat/using-usgs-landsat-level-1-data-product :param platform: image platform, possible Platform.Landsat[5, 7, 8] or Platform.Sentinel2 (<enum 'Platform'>). :param mtl_file: path to Landsat MTL file that holds the band specific rescale factors (str). :param wavelengths: like ["Blue", "Green", "Red", "NIR", "SWIR1", "TIRS", "SWIR2"] for Landsat-5 (list of str). """ if platform in [ Platform.Landsat5, Platform.Landsat7, Platform.Landsat8, ]: if mtl_file is None: raise AttributeError( f"'mtl_file' has to be set if platform is {platform}.") else: # get rescale factors from mtl file mtl = toa_utils._load_mtl( str(mtl_file)) # no obvious reason not to call this metadata = mtl["L1_METADATA_FILE"] sun_elevation = metadata["IMAGE_ATTRIBUTES"]["SUN_ELEVATION"] toa = [] for idx, b in enumerate( self._lookup_bands(platform, wavelengths)): if (platform == Platform.Landsat8 and b in ["10", "11"]) or (platform != Platform.Landsat8 and b.startswith("6")): if platform == Platform.Landsat8: thermal_conversion_constant1 = metadata[ "TIRS_THERMAL_CONSTANTS"][ f"K1_CONSTANT_BAND_{b}"] thermal_conversion_constant2 = metadata[ "TIRS_THERMAL_CONSTANTS"][ f"K2_CONSTANT_BAND_{b}"] else: thermal_conversion_constant1 = metadata[ "THERMAL_CONSTANTS"][f"K1_CONSTANT_BAND_{b}"] thermal_conversion_constant2 = metadata[ "THERMAL_CONSTANTS"][f"K2_CONSTANT_BAND_{b}"] multiplicative_rescaling_factors = metadata[ "RADIOMETRIC_RESCALING"][f"RADIANCE_MULT_BAND_{b}"] additive_rescaling_factors = metadata[ "RADIOMETRIC_RESCALING"][f"RADIANCE_ADD_BAND_{b}"] # rescale thermal bands toa.append( brightness_temp.brightness_temp( self.__arr[idx, :, :], ML=multiplicative_rescaling_factors, AL=additive_rescaling_factors, K1=thermal_conversion_constant1, K2=thermal_conversion_constant2, )) continue # rescale reflectance bands multiplicative_rescaling_factors = metadata[ "RADIOMETRIC_RESCALING"][f"REFLECTANCE_MULT_BAND_{b}"] additive_rescaling_factors = metadata[ "RADIOMETRIC_RESCALING"][f"REFLECTANCE_ADD_BAND_{b}"] toa.append( reflectance.reflectance( self.__arr[idx, :, :], MR=multiplicative_rescaling_factors, AR=additive_rescaling_factors, E=sun_elevation, )) self.__arr = np.array(np.stack(toa, axis=0)) elif platform == Platform.Sentinel2: self.__arr = self.__arr.astype(np.float32) / 10000.0 else: raise AttributeError( f"Cannot convert dn2toa. Platform {platform} not supported [Landsat-5, Landsat-7, Landsat-8, " f"Sentinel-2]. ") self.__update_dataset(self.dataset.crs, self.dataset.transform, nodata=self.dataset.nodata)
def tile(sceneid, tile_x, tile_y, tile_z, rgb=(4, 3, 2), tilesize=256, pan=False): """Create mercator tile from Landsat-8 data. Attributes ---------- sceneid : str Landsat sceneid. For scenes after May 2017, sceneid have to be LANDSAT_PRODUCT_ID. tile_x : int Mercator tile X index. tile_y : int Mercator tile Y index. tile_z : int Mercator tile ZOOM level. rgb : tuple, int, optional (default: (4, 3, 2)) Bands index for the RGB combination. tilesize : int, optional (default: 256) Output image size. pan : boolean, optional (default: False) If True, apply pan-sharpening. Returns ------- data : numpy ndarray mask: numpy array """ if not isinstance(rgb, tuple): rgb = tuple((rgb, )) scene_params = utils.landsat_parse_scene_id(sceneid) meta_data = utils.landsat_get_mtl(sceneid).get('L1_METADATA_FILE') landsat_address = '{}/{}'.format(LANDSAT_BUCKET, scene_params['key']) wgs_bounds = toa_utils._get_bounds_from_metadata( meta_data['PRODUCT_METADATA']) if not utils.tile_exists(wgs_bounds, tile_z, tile_x, tile_y): raise TileOutsideBounds( 'Tile {}/{}/{} is outside image bounds'.format( tile_z, tile_x, tile_y)) mercator_tile = mercantile.Tile(x=tile_x, y=tile_y, z=tile_z) tile_bounds = mercantile.xy_bounds(mercator_tile) ms_tile_size = int(tilesize / 2) if pan else tilesize addresses = ['{}_B{}.TIF'.format(landsat_address, band) for band in rgb] _tiler = partial(utils.tile_band_worker, bounds=tile_bounds, tilesize=ms_tile_size, nodata=0) with futures.ThreadPoolExecutor(max_workers=3) as executor: data, masks = zip(*list(executor.map(_tiler, addresses))) data = np.concatenate(data) mask = np.all(masks, axis=0).astype(np.uint8) * 255 if pan: pan_address = '{}_B8.TIF'.format(landsat_address) matrix_pan, mask = utils.tile_band_worker(pan_address, tile_bounds, tilesize, nodata=0) w, s, e, n = tile_bounds pan_transform = transform.from_bounds(w, s, e, n, tilesize, tilesize) vis_transform = pan_transform * Affine.scale(2.) data = pansharpen(data, vis_transform, matrix_pan, pan_transform, np.int16, 'EPSG:3857', 'EPSG:3857', 0.2, method='Brovey', src_nodata=0) sun_elev = meta_data['IMAGE_ATTRIBUTES']['SUN_ELEVATION'] for bdx, band in enumerate(rgb): if int(band) > 9: # TIRS multi_rad = meta_data['RADIOMETRIC_RESCALING'].get( 'RADIANCE_MULT_BAND_{}'.format(band)) add_rad = meta_data['RADIOMETRIC_RESCALING'].get( 'RADIANCE_ADD_BAND_{}'.format(band)) k1 = meta_data['TIRS_THERMAL_CONSTANTS'].get( 'K1_CONSTANT_BAND_{}'.format(band)) k2 = meta_data['TIRS_THERMAL_CONSTANTS'].get( 'K2_CONSTANT_BAND_{}'.format(band)) data[bdx] = brightness_temp.brightness_temp( data[bdx], multi_rad, add_rad, k1, k2) else: multi_reflect = meta_data['RADIOMETRIC_RESCALING'].get( 'REFLECTANCE_MULT_BAND_{}'.format(band)) add_reflect = meta_data['RADIOMETRIC_RESCALING'].get( 'REFLECTANCE_ADD_BAND_{}'.format(band)) data[bdx] = 10000 * reflectance.reflectance( data[bdx], multi_reflect, add_reflect, sun_elev) return data, mask
def tile(sceneid, tile_x, tile_y, tile_z, bands=("4", "3", "2"), tilesize=256, pan=False, **kwargs): """ Create mercator tile from Landsat-8 data. Attributes ---------- sceneid : str Landsat sceneid. For scenes after May 2017, sceneid have to be LANDSAT_PRODUCT_ID. tile_x : int Mercator tile X index. tile_y : int Mercator tile Y index. tile_z : int Mercator tile ZOOM level. bands : tuple, str, optional (default: ("4", "3", "2")) Bands index for the RGB combination. tilesize : int, optional (default: 256) Output image size. pan : boolean, optional (default: False) If True, apply pan-sharpening. kwargs: dict, optional These will be passed to the 'rio_tiler.utils._tile_read' function. Returns ------- data : numpy ndarray mask: numpy array """ if not isinstance(bands, tuple): bands = tuple((bands, )) for band in bands: if band not in LANDSAT_BANDS: raise InvalidBandName( "{} is not a valid Landsat band name".format(band)) scene_params = _landsat_parse_scene_id(sceneid) meta_data = _landsat_get_mtl(sceneid).get("L1_METADATA_FILE") landsat_address = "{}/{}".format(LANDSAT_BUCKET, scene_params["key"]) wgs_bounds = toa_utils._get_bounds_from_metadata( meta_data["PRODUCT_METADATA"]) if not utils.tile_exists(wgs_bounds, tile_z, tile_x, tile_y): raise TileOutsideBounds("Tile {}/{}/{} is outside image bounds".format( tile_z, tile_x, tile_y)) mercator_tile = mercantile.Tile(x=tile_x, y=tile_y, z=tile_z) tile_bounds = mercantile.xy_bounds(mercator_tile) def _tiler(band): address = "{}_B{}.TIF".format(landsat_address, band) if band == "QA": nodata = 1 else: nodata = 0 return utils.tile_read(address, bounds=tile_bounds, tilesize=tilesize, nodata=nodata, **kwargs) with futures.ThreadPoolExecutor(max_workers=MAX_THREADS) as executor: data, masks = zip(*list(executor.map(_tiler, bands))) data = np.concatenate(data) mask = np.all(masks, axis=0).astype(np.uint8) * 255 if pan: pan_address = "{}_B8.TIF".format(landsat_address) matrix_pan, mask = utils.tile_read(pan_address, tile_bounds, tilesize, nodata=0) data = utils.pansharpening_brovey(data, matrix_pan, 0.2, matrix_pan.dtype) sun_elev = meta_data["IMAGE_ATTRIBUTES"]["SUN_ELEVATION"] for bdx, band in enumerate(bands): if band in ["1", "2", "3", "4", "5", "6", "7", "8", "9"]: # OLI multi_reflect = meta_data["RADIOMETRIC_RESCALING"].get( "REFLECTANCE_MULT_BAND_{}".format(band)) add_reflect = meta_data["RADIOMETRIC_RESCALING"].get( "REFLECTANCE_ADD_BAND_{}".format(band)) data[bdx] = 10000 * reflectance.reflectance( data[bdx], multi_reflect, add_reflect, sun_elev) elif band in ["10", "11"]: # TIRS multi_rad = meta_data["RADIOMETRIC_RESCALING"].get( "RADIANCE_MULT_BAND_{}".format(band)) add_rad = meta_data["RADIOMETRIC_RESCALING"].get( "RADIANCE_ADD_BAND_{}".format(band)) k1 = meta_data["TIRS_THERMAL_CONSTANTS"].get( "K1_CONSTANT_BAND_{}".format(band)) k2 = meta_data["TIRS_THERMAL_CONSTANTS"].get( "K2_CONSTANT_BAND_{}".format(band)) data[bdx] = brightness_temp.brightness_temp( data[bdx], multi_rad, add_rad, k1, k2) return data, mask
def _landsat_stats( band, address_prefix, metadata, overview_level=None, max_size=1024, percentiles=(2, 98), dst_crs=CRS({"init": "EPSG:4326"}), histogram_bins=10, histogram_range=None, ): """ Retrieve landsat dataset statistics. Attributes ---------- band : str Landsat band number address_prefix : str A Landsat AWS S3 dataset prefix. metadata : dict Landsat metadata overview_level : int, optional Overview (decimation) level to fetch. max_size: int, optional Maximum size of dataset to retrieve (will be used to calculate the overview level to fetch). percentiles : tulple, optional Percentile or sequence of percentiles to compute, which must be between 0 and 100 inclusive (default: (2, 98)). dst_crs: CRS or dict Target coordinate reference system (default: EPSG:4326). histogram_bins: int, optional Defines the number of equal-width histogram bins (default: 10). histogram_range: tuple or list, optional The lower and upper range of the bins. If not provided, range is simply the min and max of the array. Returns ------- out : dict (percentiles), min, max, stdev, histogram for each band, e.g. { "4": { 'pc': [15, 121], 'min': 1, 'max': 162, 'std': 27.22067722127997, 'histogram': [ [102934, 135489, 20981, 13548, 11406, 8799, 7351, 5622, 2985, 662] [1., 17.1, 33.2, 49.3, 65.4, 81.5, 97.6, 113.7, 129.8, 145.9, 162.] ] } } """ src_path = "{}_B{}.TIF".format(address_prefix, band) with rasterio.open(src_path) as src: levels = src.overviews(1) width = src.width height = src.height bounds = transform_bounds(src.crs, dst_crs, *src.bounds, densify_pts=21) if len(levels): if overview_level: decim = levels[overview_level] else: # determine which zoom level to read for ii, decim in enumerate(levels): if max(width // decim, height // decim) < max_size: break else: decim = 1 warnings.warn("Dataset has no overviews, reading the full dataset", NoOverviewWarning) out_shape = (height // decim, width // decim) if band == "QA": nodata = 1 else: nodata = 0 vrt_params = dict(nodata=nodata, add_alpha=False, src_nodata=nodata, init_dest_nodata=False) with WarpedVRT(src, **vrt_params) as vrt: arr = vrt.read(out_shape=out_shape, indexes=[1], masked=True) if band in ["1", "2", "3", "4", "5", "6", "7", "8", "9"]: # OLI multi_reflect = metadata["RADIOMETRIC_RESCALING"].get( "REFLECTANCE_MULT_BAND_{}".format(band)) add_reflect = metadata["RADIOMETRIC_RESCALING"].get( "REFLECTANCE_ADD_BAND_{}".format(band)) sun_elev = metadata["IMAGE_ATTRIBUTES"]["SUN_ELEVATION"] arr = 10000 * reflectance.reflectance( arr, multi_reflect, add_reflect, sun_elev, src_nodata=0) elif band in ["10", "11"]: # TIRS multi_rad = metadata["RADIOMETRIC_RESCALING"].get( "RADIANCE_MULT_BAND_{}".format(band)) add_rad = metadata["RADIOMETRIC_RESCALING"].get( "RADIANCE_ADD_BAND_{}".format(band)) k1 = metadata["TIRS_THERMAL_CONSTANTS"].get( "K1_CONSTANT_BAND_{}".format(band)) k2 = metadata["TIRS_THERMAL_CONSTANTS"].get( "K2_CONSTANT_BAND_{}".format(band)) arr = brightness_temp.brightness_temp(arr, multi_rad, add_rad, k1, k2) params = {} if histogram_bins: params.update(dict(bins=histogram_bins)) if histogram_range: params.update(dict(range=histogram_range)) stats = {band: utils._stats(arr, percentiles=percentiles, **params)} return { "bounds": { "value": bounds, "crs": dst_crs.to_string() if isinstance(dst_crs, CRS) else dst_crs, }, "statistics": stats, }
def tile(sceneid, tile_x, tile_y, tile_z, bands=("4", "3", "2"), tilesize=256, pan=False): """ Create mercator tile from Landsat-8 data. Attributes ---------- sceneid : str Landsat sceneid. For scenes after May 2017, sceneid have to be LANDSAT_PRODUCT_ID. tile_x : int Mercator tile X index. tile_y : int Mercator tile Y index. tile_z : int Mercator tile ZOOM level. bands : tuple, str, optional (default: ("4", "3", "2")) Bands index for the RGB combination. tilesize : int, optional (default: 256) Output image size. pan : boolean, optional (default: False) If True, apply pan-sharpening. Returns ------- data : numpy ndarray mask: numpy array """ if not isinstance(bands, tuple): bands = tuple((bands, )) for band in bands: if band not in LANDSAT_BANDS: raise InvalidBandName( "{} is not a valid Landsat band name".format(band)) scene_params = _landsat_parse_scene_id(sceneid) meta_data = _landsat_get_mtl(sceneid).get("L1_METADATA_FILE") landsat_address = "{}/{}".format(LANDSAT_BUCKET, scene_params["key"]) wgs_bounds = toa_utils._get_bounds_from_metadata( meta_data["PRODUCT_METADATA"]) if not utils.tile_exists(wgs_bounds, tile_z, tile_x, tile_y): raise TileOutsideBounds("Tile {}/{}/{} is outside image bounds".format( tile_z, tile_x, tile_y)) mercator_tile = mercantile.Tile(x=tile_x, y=tile_y, z=tile_z) tile_bounds = mercantile.xy_bounds(mercator_tile) ms_tile_size = int(tilesize / 2) if pan else tilesize addresses = ["{}_B{}.TIF".format(landsat_address, band) for band in bands] _tiler = partial(utils.tile_read, bounds=tile_bounds, tilesize=ms_tile_size, nodata=0) with futures.ThreadPoolExecutor(max_workers=MAX_THREADS) as executor: data, masks = zip(*list(executor.map(_tiler, addresses))) data = np.concatenate(data) mask = np.all(masks, axis=0).astype(np.uint8) * 255 if pan: pan_address = "{}_B8.TIF".format(landsat_address) matrix_pan, mask = utils.tile_read(pan_address, tile_bounds, tilesize, nodata=0) w, s, e, n = tile_bounds pan_transform = transform.from_bounds(w, s, e, n, tilesize, tilesize) vis_transform = pan_transform * Affine.scale(2.0) data = pansharpen( data, vis_transform, matrix_pan, pan_transform, np.int16, "EPSG:3857", "EPSG:3857", 0.2, method="Brovey", src_nodata=0, ) sun_elev = meta_data["IMAGE_ATTRIBUTES"]["SUN_ELEVATION"] for bdx, band in enumerate(bands): if int(band) > 9: # TIRS multi_rad = meta_data["RADIOMETRIC_RESCALING"].get( "RADIANCE_MULT_BAND_{}".format(band)) add_rad = meta_data["RADIOMETRIC_RESCALING"].get( "RADIANCE_ADD_BAND_{}".format(band)) k1 = meta_data["TIRS_THERMAL_CONSTANTS"].get( "K1_CONSTANT_BAND_{}".format(band)) k2 = meta_data["TIRS_THERMAL_CONSTANTS"].get( "K2_CONSTANT_BAND_{}".format(band)) data[bdx] = brightness_temp.brightness_temp( data[bdx], multi_rad, add_rad, k1, k2) else: multi_reflect = meta_data["RADIOMETRIC_RESCALING"].get( "REFLECTANCE_MULT_BAND_{}".format(band)) add_reflect = meta_data["RADIOMETRIC_RESCALING"].get( "REFLECTANCE_ADD_BAND_{}".format(band)) data[bdx] = 10000 * reflectance.reflectance( data[bdx], multi_reflect, add_reflect, sun_elev) return data, mask
def landsat8_tile(sceneid, tile_x, tile_y, tile_z, bands=("4", "3", "2"), tilesize=256, pan=False, percents="", **kwargs): """ Create mercator tile from Landsat-8 data. Attributes ---------- sceneid : str Landsat sceneid. For scenes after May 2017, sceneid have to be LANDSAT_PRODUCT_ID. tile_x : int Mercator tile X index. tile_y : int Mercator tile Y index. tile_z : int Mercator tile ZOOM level. bands : tuple, str, optional (default: ("4", "3", "2")) Bands index for the RGB combination. tilesize : int, optional (default: 256) Output image size. pan : boolean, optional (default: False) If True, apply pan-sharpening. kwargs: dict, optional These will be passed to the 'rio_tiler.utils._tile_read' function. Returns ------- data : numpy ndarray mask: numpy array """ if not isinstance(bands, tuple): bands = tuple((bands, )) for band in bands: if band not in LANDSAT_BANDS: raise InvalidBandName( "{} is not a valid Landsat band name".format(band)) scene_params = landsat8._landsat_parse_scene_id(sceneid) meta_data = landsat8._landsat_get_mtl(sceneid).get("L1_METADATA_FILE") landsat_address = "{}/{}".format(LANDSAT_BUCKET, scene_params["key"]) wgs_bounds = toa_utils._get_bounds_from_metadata( meta_data["PRODUCT_METADATA"]) addresses = ["{}_B{}.TIF".format(landsat_address, band) for band in bands] values = [] percents = percents.split(',') i = 0 for address in addresses: with rasterio.open(address) as src: if int(percents[i]) != 0 and int(percents[i + 1]) != 100: overviews = src.overviews(1) if len(overviews) > 0: d = src.read( out_shape=(1, int(src.height / overviews[len(overviews) - 1]), int(src.width / overviews[len(overviews) - 1]))) else: d = src.read() dflatten = numpy.array(d.flatten()) p_start, p_end = numpy.percentile(dflatten[dflatten > 0], (int(percents[i]), (int(percents[i + 1])))) values.append([p_start, p_end]) else: values.append([None, None]) i += 2 if not utils.tile_exists(wgs_bounds, tile_z, tile_x, tile_y): # raise TileOutsideBounds( # "Tile {}/{}/{} is outside image bounds".format(tile_z, tile_x, tile_y) # ) return None, None mercator_tile = mercantile.Tile(x=tile_x, y=tile_y, z=tile_z) tile_bounds = mercantile.xy_bounds(mercator_tile) def _tiler(band): address = "{}_B{}.TIF".format(landsat_address, band) if band == "QA": nodata = 1 else: nodata = 0 return utils.tile_read(address, bounds=tile_bounds, tilesize=tilesize, nodata=nodata, **kwargs) with futures.ThreadPoolExecutor(max_workers=MAX_THREADS) as executor: data, masks = zip(*list(executor.map(_tiler, bands))) mask = numpy.all(masks, axis=0).astype(numpy.uint8) * 255 new_data = list(data) has_modification = False for ds in range(0, len(new_data)): if values[ds][0] is not None and values[ds][1] is not None: has_modification = True new_data[ds] = rescale_intensity(new_data[ds], in_range=(values[ds][0], values[ds][1]), out_range=(0, 255)) if has_modification == True: data = numpy.array(new_data).astype(numpy.uint8) data = numpy.concatenate(data) if pan: pan_address = "{}_B8.TIF".format(landsat_address) matrix_pan, mask = utils.tile_read(pan_address, tile_bounds, tilesize, nodata=0) data = utils.pansharpening_brovey(data, matrix_pan, 0.2, matrix_pan.dtype) sun_elev = meta_data["IMAGE_ATTRIBUTES"]["SUN_ELEVATION"] for bdx, band in enumerate(bands): if band in ["1", "2", "3", "4", "5", "6", "7", "8", "9"]: # OLI multi_reflect = meta_data["RADIOMETRIC_RESCALING"].get( "REFLECTANCE_MULT_BAND_{}".format(band)) add_reflect = meta_data["RADIOMETRIC_RESCALING"].get( "REFLECTANCE_ADD_BAND_{}".format(band)) data[bdx] = 10000 * reflectance.reflectance( data[bdx], multi_reflect, add_reflect, sun_elev) elif band in ["10", "11"]: # TIRS multi_rad = meta_data["RADIOMETRIC_RESCALING"].get( "RADIANCE_MULT_BAND_{}".format(band)) add_rad = meta_data["RADIOMETRIC_RESCALING"].get( "RADIANCE_ADD_BAND_{}".format(band)) k1 = meta_data["TIRS_THERMAL_CONSTANTS"].get( "K1_CONSTANT_BAND_{}".format(band)) k2 = meta_data["TIRS_THERMAL_CONSTANTS"].get( "K2_CONSTANT_BAND_{}".format(band)) data[bdx] = brightness_temp.brightness_temp( data[bdx], multi_rad, add_rad, k1, k2) return data, mask