def test_sentinel_id_invalid(): """ Should raise an error with invalid sceneid """ scene = 'S2A_tile_20170323_17SNC' with pytest.raises(InvalidSentinelSceneId): utils.sentinel_parse_scene_id(scene)
def tile(sceneid, tile_x, tile_y, tile_z, rgb=('04', '03', '02'), tilesize=256): """Create mercator tile from Sentinel-2 data. Attributes ---------- sceneid : str Sentinel-2 sceneid. tile_x : int Mercator tile X index. tile_y : int Mercator tile Y index. tile_z : int Mercator tile ZOOM level. rgb : tuple, str, optional (default: ('04', '03', '02')) Bands index for the RGB combination. tilesize : int, optional (default: 256) Output image size. Returns ------- data : numpy ndarray mask: numpy array """ if not isinstance(rgb, tuple): rgb = tuple((rgb, )) scene_params = utils.sentinel_parse_scene_id(sceneid) sentinel_address = '{}/{}'.format(SENTINEL_BUCKET, scene_params['key']) sentinel_preview = '{}/preview.jp2'.format(sentinel_address) with rasterio.open(sentinel_preview) as src: wgs_bounds = transform_bounds(*[src.crs, 'epsg:4326'] + list(src.bounds), densify_pts=21) 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) addresses = ['{}/B{}.jp2'.format(sentinel_address, band) for band in rgb] _tiler = partial(utils.tile_band_worker, bounds=tile_bounds, tilesize=tilesize, nodata=0) with futures.ThreadPoolExecutor(max_workers=3) as executor: data, masks = zip(*list(executor.map(_tiler, addresses))) mask = np.all(masks, axis=0).astype(np.uint8) * 255 return np.concatenate(data), mask
def bounds(sceneid): """Retrieve image bounds. Attributes ---------- sceneid : str Sentinel-2 sceneid. Returns ------- out : dict dictionary with image bounds. """ scene_params = utils.sentinel_parse_scene_id(sceneid) sentinel_address = '{}/{}'.format(SENTINEL_BUCKET, scene_params['key']) with rasterio.open('{}/preview.jp2'.format(sentinel_address)) as src: wgs_bounds = transform_bounds(*[src.crs, 'epsg:4326'] + list(src.bounds), densify_pts=21) info = {'sceneid': sceneid} info['bounds'] = list(wgs_bounds) return info
def area( scene, bbox, expression, expression_range=[-1, 1], bbox_crs="epsg:4326", out_crs="epsg:3857", ): """Area handler.""" max_img_size = 512 bands = tuple(set(re.findall(r"b(?P<bands>[0-9]{1,2})", expression))) scene_params = sentinel_parse_scene_id(scene) sentinel_address = f'{SENTINEL_BUCKET}/{scene_params["key"]}' scene_info = sentinel2_get_info(os.path.basename(SENTINEL_BUCKET), scene_params["key"], request_pays=True) addresses = [f"{sentinel_address}/B{band}.jp2" for band in bands] _worker = partial( get_area, bbox=bbox, max_img_size=max_img_size, bbox_crs=bbox_crs, out_crs=out_crs, ) with futures.ThreadPoolExecutor(max_workers=3) as executor: data = np.concatenate(list(executor.map(_worker, addresses))) if not np.any(data): raise Exception("No valid data in array") mask = np.all(data != 0, axis=0).astype(np.uint8) * 255 ctx = {} for bdx, b in enumerate(bands): ctx["b{}".format(b)] = data[bdx] ratio = np.nan_to_num(ne.evaluate(expression, local_dict=ctx)) ratio = np.where( mask, linear_rescale(ratio, in_range=expression_range, out_range=[0, 255]), 0).astype(np.uint8) img = array_to_img(ratio, mask, get_colormap(name="cfastie")) ndvi = b64_encode_img(img, "jpeg") date = (scene_params["acquisitionYear"] + "-" + scene_params["acquisitionMonth"] + "-" + scene_params["acquisitionDay"]) return { "ndvi": ndvi, "date": date, "sat": scene_info["sat"], "scene": scene, "cloud": scene_info["cloud_coverage"], }
def metadata(sceneid, pmin=2, pmax=98): """ Retrieve image bounds and histogram info. Attributes ---------- sceneid : str Sentinel-2 sceneid. pmin : int, optional, (default: 2) Histogram minimum cut. pmax : int, optional, (default: 98) Histogram maximum cut. Returns ------- out : dict dictionary with image bounds and bands histogram cuts. """ scene_params = utils.sentinel_parse_scene_id(sceneid) sentinel_address = "{}/{}".format(SENTINEL_BUCKET, scene_params["key"]) with rasterio.open("{}/preview.jp2".format(sentinel_address)) as src: wgs_bounds = transform_bounds(*[src.crs, "epsg:4326"] + list(src.bounds), densify_pts=21) info = {"sceneid": sceneid, "bounds": list(wgs_bounds)} bands = [ "01", "02", "03", "04", "05", "06", "07", "08", "8A", "09", "10", "11", "12", ] addresses = [ "{}/preview/B{}.jp2".format(sentinel_address, band) for band in bands ] _min_max_worker = partial(utils.band_min_max_worker, pmin=pmin, pmax=pmax) with futures.ThreadPoolExecutor(max_workers=2) as executor: responses = list(executor.map(_min_max_worker, addresses)) info["rgbMinMax"] = dict(zip(bands, responses)) return info
def point(scene, coordinates, expression): """Point handler.""" bands = tuple(set(re.findall(r"b(?P<bands>[0-9]{1,2})", expression))) scene_params = sentinel_parse_scene_id(scene) sentinel_address = f'{SENTINEL_BUCKET}/{scene_params["key"]}' scene_info = sentinel2_get_info(os.path.basename(SENTINEL_BUCKET), scene_params["key"], request_pays=True) addresses = [f"{sentinel_address}/B{band}.jp2" for band in bands] def worker(address): """Worker.""" with rasterio.open(address) as band: lon_srs, lat_srs = warp.transform("EPSG:4326", band.crs, [coordinates[0]], [coordinates[1]]) point = list(band.sample([(lon_srs[0], lat_srs[0])]))[0] return point[0] try: with futures.ThreadPoolExecutor(max_workers=3) as executor: data = list(executor.map(worker, addresses)) ctx = {} for bdx, b in enumerate(bands): ctx["b{}".format(b)] = data[bdx] ratio = np.nan_to_num(ne.evaluate(expression, local_dict=ctx)) except IndexError: ratio = 0.0 date = (scene_params["acquisitionYear"] + "-" + scene_params["acquisitionMonth"] + "-" + scene_params["acquisitionDay"]) return { "ndvi": ratio, "date": date, "sat": scene_info["sat"], "scene": scene, "cloud": scene_info["cloud_coverage"], }
def metadata(sceneid, pmin=2, pmax=98): """Retrieve image bounds and histogram info. Attributes ---------- sceneid : str Sentinel-2 sceneid. pmin : int, optional, (default: 2) Histogram minimum cut. pmax : int, optional, (default: 98) Histogram maximum cut. Returns ------- out : dict dictionary with image bounds and bands histogram cuts. """ scene_params = utils.sentinel_parse_scene_id(sceneid) sentinel_address = '{}/{}'.format(SENTINEL_BUCKET, scene_params['key']) with rasterio.open('{}/preview.jp2'.format(sentinel_address)) as src: wgs_bounds = transform_bounds(*[src.crs, 'epsg:4326'] + list(src.bounds), densify_pts=21) info = {'sceneid': sceneid, 'bounds': list(wgs_bounds)} bands = [ '01', '02', '03', '04', '05', '06', '07', '08', '8A', '09', '10', '11', '12' ] addresses = [ '{}/preview/B{}.jp2'.format(sentinel_address, band) for band in bands ] _min_max_worker = partial(utils.band_min_max_worker, pmin=pmin, pmax=pmax) with futures.ThreadPoolExecutor(max_workers=2) as executor: responses = list(executor.map(_min_max_worker, addresses)) info['rgbMinMax'] = dict(zip(bands, responses)) return info
def test_sentinel_id_valid_strip(): """ Should work as expected (parse sentinel scene id) """ scene = 'S2A_tile_20170323_07SNC_0' expected_content = { 'acquisitionDay': '23', 'acquisitionMonth': '03', 'acquisitionYear': '2017', 'key': 'tiles/7/S/NC/2017/3/23/0', 'lat': 'NC', 'num': '0', 'satellite': 'A', 'sensor': '2', 'sq': 'S', 'utm': '07' } assert utils.sentinel_parse_scene_id(scene) == expected_content
def tile(sceneid, tile_x, tile_y, tile_z, rgb=('04', '03', '02'), r_bds=(0, 16000), g_bds=(0, 16000), b_bds=(1, 16000), tilesize=256): """Create mercator tile from Sentinel-2 data and encodes it in base64. Attributes ---------- sceneid : str Sentinel-2 sceneid. 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: ('04', '03', '02')) Bands index for the RGB combination. r_bds : tuple, int, optional (default: (0, 16000)) First band (red) DN min and max values (DN * 10,000) used for the linear rescaling. g_bds : tuple, int, optional (default: (0, 16000)) Second band (green) DN min and max values (DN * 10,000) used for the linear rescaling. b_bds : tuple, int, optional (default: (0, 16000)) Third band (blue) DN min and max values (DN * 10,000) used for the linear rescaling. tilesize : int, optional (default: 256) Output image size. Returns ------- out : numpy ndarray (type: uint8) """ scene_params = utils.sentinel_parse_scene_id(sceneid) sentinel_address = '{}/{}'.format(SENTINEL_BUCKET, scene_params['key']) sentinel_preview = '{}/preview.jp2'.format(sentinel_address) with rasterio.open(sentinel_preview) as src: wgs_bounds = transform_bounds( *[src.crs, 'epsg:4326'] + list(src.bounds), densify_pts=21) 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) # define a list of bands Min and Max Values (form input) histo_cuts = dict(zip(rgb, [r_bds, g_bds, b_bds])) addresses = ['{}/B{}.jp2'.format(sentinel_address, band) for band in rgb] _tiler = partial(utils.tile_band_worker, bounds=tile_bounds, tilesize=tilesize) with futures.ThreadPoolExecutor(max_workers=3) as executor: out = np.stack(executor.map(_tiler, addresses)) for bdx, band in enumerate(rgb): # Rescale Intensity to byte (1->255) with 0 being NoData out[bdx] = np.where( out[bdx] > 0, utils.linear_rescale(out[bdx], in_range=histo_cuts.get(band), out_range=[1, 255]), 0) return out.astype(np.uint8)