From 702025fd870c71135aced94e0cf8d83ae186dd57 Mon Sep 17 00:00:00 2001 From: Simon Proud Date: Thu, 17 Oct 2024 20:45:37 +0200 Subject: [PATCH] Update Landsat RSR reader, add support for Landsat-9 OLI, and TIRS aboard both -8 and -9 --- doc/platforms_supported.rst | 18 +++- pyspectral/etc/pyspectral.yaml | 10 +++ rsr_convert_scripts/oli_reader.py | 85 ------------------ rsr_convert_scripts/oli_tirs_reader.py | 117 +++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 88 deletions(-) delete mode 100644 rsr_convert_scripts/oli_reader.py create mode 100644 rsr_convert_scripts/oli_tirs_reader.py diff --git a/doc/platforms_supported.rst b/doc/platforms_supported.rst index e01b5e46..b583246f 100644 --- a/doc/platforms_supported.rst +++ b/doc/platforms_supported.rst @@ -95,8 +95,17 @@ have been included in Pyspectral. - `rsr_viirs_Suomi-NPP.h5` - GSICS_ * - Landsat-8 oli - - `rsr_oli_Landsat-8.h5` - - NASA-Landsat-OLI_ + - `rsr_oli_tirs_Landsat-8.h5` + - NASA-Landsat-8-OLI_ + * - Landsat-8 tirs + - `rsr_oli_tirs_Landsat-8.h5` + - NASA-Landsat-8-TIRS_ + * - Landsat-9 oli-2 + - `rsr_oli_tirs_Landsat-9.h5` + - NASA-Landsat-9-OLI_ + * - Landsat-9 tirs-2 + - `rsr_oli_tirs_Landsat-9.h5` + - NASA-Landsat-9-TIRS_ * - FY-3D mersi-2 - `rsr_mersi-2_FY-3D.h5` - CMA_ (Acquired via personal contact) @@ -125,7 +134,10 @@ have been included in Pyspectral. .. _ESA-Sentinel-OLCI: https://sentinel.esa.int/documents/247904/322304/OLCI+SRF+%28NetCDF%29/15cfd7a6-b7bc-4051-87f8-c35d765ae43a .. _ESA-Sentinel-SLSTR: https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-slstr/instrument/measured-spectral-response-function-data .. _ESA-Sentinel-MSI: https://earth.esa.int/documents/247904/685211/S2-SRF_COPE-GSEG-EOPG-TN-15-0007_3.0.xlsx -.. _NASA-Landsat-OLI: https://landsat.gsfc.nasa.gov/wp-content/uploads/2013/06/Ball_BA_RSR.v1.1-1.xlsx +.. _NASA-Landsat-8-OLI: https://landsat.gsfc.nasa.gov/wp-content/uploads/2014/09/Ball_BA_RSR.v1.2.xlsx +.. _NASA-Landsat-9-OLI: https://landsat.gsfc.nasa.gov/wp-content/uploads/2024/03/L9_OLI2_Ball_BA_RSR.v2-1.xlsx +.. _NASA-Landsat-8-TIRS: https://landsat.gsfc.nasa.gov/wp-content/uploads/2013/06/TIRS_Relative_Spectral_Responses.BA_.v1.xlsx +.. _NASA-Landsat-9-TIRS: https://landsat.gsfc.nasa.gov/wp-content/uploads/2021-10/L9_TIRS2_Relative_Spectral_Responses.BA.v1.0.xlsx .. _NESDIS: https://ncc.nesdis.noaa.gov/J1VIIRS/J1VIIRSSpectralResponseFunctions.php .. _CMA: http://www.cma.gov.cn/en2014/ .. _NWPSAF-MetImage: https://nwpsaf.eu/downloads/rtcoef_rttov12/ir_srf/rtcoef_metopsg_1_metimage_srf.html diff --git a/pyspectral/etc/pyspectral.yaml b/pyspectral/etc/pyspectral.yaml index 06d0aca6..df651f4e 100644 --- a/pyspectral/etc/pyspectral.yaml +++ b/pyspectral/etc/pyspectral.yaml @@ -136,6 +136,16 @@ download_from_internet: True # Envisat-aatsr: # path: /path/to/original/envisat/aatsr/data +# Landsat-8-oli_tirs: +# path: D:\sat_data\ +# oli: Ball_BA_RSR.v1.2.xlsx +# tirs: TIRS_Relative_Spectral_Responses.BA_.v1.xlsx + +# Landsat-9-oli_tirs: +# path: D:\sat_data\ +# oli: L9_OLI2_Ball_BA_RSR.v2-1.xlsx +# tirs: L9_TIRS2_Relative_Spectral_Responses.BA.v1.0.xlsx + # Sentinel-3A-slstr: # path: /path/to/original/sentinel-3a/slstr/data # ch1: SLSTR_FM02_S1_20150122.nc diff --git a/rsr_convert_scripts/oli_reader.py b/rsr_convert_scripts/oli_reader.py deleted file mode 100644 index b833b795..00000000 --- a/rsr_convert_scripts/oli_reader.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (c) 2017-2022 Pytroll developers -# -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Landsat-8 OLI reader.""" - -import logging -import os - -import numpy as np -from xlrd import open_workbook - -from pyspectral.raw_reader import InstrumentRSR -from pyspectral.utils import convert2hdf5 as tohdf5 - -LOG = logging.getLogger(__name__) - - -OLI_BAND_NAMES = {'CoastalAerosol': 'B1', - 'Blue': 'B2', - 'Green': 'B3', - 'Red': 'B4', - 'NIR': 'B5', - 'Cirrus': 'B9', - 'SWIR1': 'B6', - 'SWIR2': 'B7', - 'Pan': 'B8'} - - -class OliRSR(InstrumentRSR): - """Class for Landsat OLI RSR.""" - - def __init__(self, bandname, platform_name): - """Read the Landsat OLI relative spectral responses for all channels.""" - super(OliRSR, self).__init__(bandname, platform_name) - - self.instrument = 'oli' - self._get_options_from_config() - - LOG.debug("Filename: %s", str(self.path)) - if os.path.exists(self.path): - self._load() - else: - raise IOError("Couldn't find an existing file for this band: " + - str(self.bandname)) - - def _load(self, scale=0.001): - """Load the Landsat OLI relative spectral responses.""" - with open_workbook(self.path) as wb_: - for sheet in wb_.sheets(): - if sheet.name in ['Plot of AllBands', ]: - continue - ch_name = OLI_BAND_NAMES.get(sheet.name.strip()) - - if ch_name != self.bandname: - continue - - wvl = sheet.col_values(0, 2) - resp = sheet.col_values(1, 2) - - self.rsr = {'wavelength': np.array(wvl) / 1000., - 'response': np.array(resp)} - break - - -if __name__ == "__main__": - bands = OLI_BAND_NAMES.values() - bands.sort() - for platform_name in ['Landsat-8', ]: - tohdf5(OliRSR, platform_name, bands) diff --git a/rsr_convert_scripts/oli_tirs_reader.py b/rsr_convert_scripts/oli_tirs_reader.py new file mode 100644 index 00000000..f8d01166 --- /dev/null +++ b/rsr_convert_scripts/oli_tirs_reader.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017-2024 Pytroll developers +# +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Landsat-8/9 OLI/TIRS reader. +This reader generates spectral responses for OLI and TIRS instruments aboard Landsat-8 and -9. +We assume that the instruments are one combined instrument from the user perspective, called `oli_tirs` rather +than generating RSRs for the two instruments separately. +The original spectral response data can be found at the links below. +Landsat-8/OLI: https://landsat.gsfc.nasa.gov/wp-content/uploads/2014/09/Ball_BA_RSR.v1.2.xlsx +Landsat-9/OLI-2: https://landsat.gsfc.nasa.gov/wp-content/uploads/2024/03/L9_OLI2_Ball_BA_RSR.v2-1.xlsx +Landsat-8/TIRS: https://landsat.gsfc.nasa.gov/wp-content/uploads/2013/06/TIRS_Relative_Spectral_Responses.BA_.v1.xlsx +Landsat-9/TIRS-2: https://landsat.gsfc.nasa.gov/wp-content/uploads/2021-10/L9_TIRS2_Relative_Spectral_Responses.BA.v1.0.xlsx +""" + +import logging +import os + +import numpy as np +import pandas as pd + +from pyspectral.raw_reader import InstrumentRSR +from pyspectral.utils import convert2hdf5 as tohdf5 + +LOG = logging.getLogger(__name__) + +OLI_BAND_NAMES = {"B1": "CoastalAerosol", + "B2": "Blue", + "B3": "Green", + "B4": "Red", + "B5": "NIR", + "B6": "Cirrus", + "B7": "SWIR1", + "B8": "SWIR2", + "B9": "Pan"} + +TIRS_SHEETNAMES_L8 = {"B10": "TIRS BA RSR", + "B11": "TIRS BA RSR"} +TIRS_BAND_NAMES_L8 = {"B10": "TIRS1 10.8um band average", + "B11": "TIRS2 12.0um band average"} + +TIRS_SHEETNAMES_L9 = {"B10": "TIRS Band 10 BA RSR", + "B11": "TIRS Band 11 BA RSR"} +TIRS_BAND_NAMES_L9 = {"B10": "Band 10 Band=Average RSR", + "B11": "Band 11 Band-Average RSR"} + + +class OliRSR(InstrumentRSR): + """Class for Landsat OLI RSR.""" + + def __init__(self, bandname, platform_name): + """Read the Landsat OLI relative spectral responses for all channels.""" + super(OliRSR, self).__init__(bandname, platform_name) + self.instrument = "oli_tirs" + self._get_options_from_config() + self.band = bandname + opts = self.options[f"{platform_name}-{self.instrument}"] + if bandname in OLI_BAND_NAMES: + self.path = opts["path"] + opts["oli"] + elif bandname in TIRS_BAND_NAMES_L8: + self.path = opts["path"] + opts["tirs"] + else: + raise ValueError(f"Unknown band name: {bandname}") + + LOG.debug(f"Filename: {self.path}") + if os.path.exists(self.path): + self._load() + else: + raise IOError("Couldn't find an existing file for this band: " + + str(self.bandname)) + + def _load(self, scale=0.001): + """Load the Landsat OLI relative spectral responses.""" + if self.band in OLI_BAND_NAMES: + df = pd.read_excel(self.path, engine="openpyxl", sheet_name=OLI_BAND_NAMES[self.band]) + wvl = np.array(df["Wavelength"]) + resp = np.array(df["BA RSR [watts]"]) + else: + if self.platform_name == "Landsat-8": + sheet_name = TIRS_SHEETNAMES_L8[self.band] + band_name = TIRS_BAND_NAMES_L8[self.band] + elif self.platform_name == "Landsat-9": + sheet_name = TIRS_SHEETNAMES_L9[self.band] + band_name = TIRS_BAND_NAMES_L9[self.band] + df = pd.read_excel(self.path, engine="openpyxl", sheet_name=sheet_name) + + wvl = np.array(df["wavelength [um]"]) + resp = np.array(df[band_name]) + + # Cut unneeded points + pts = np.argwhere(resp > 0.002) + wvl = np.squeeze(wvl[pts]) + resp = np.squeeze(resp[pts]) + + self.rsr = {"wavelength": wvl, + "response": resp} + + +if __name__ == "__main__": + bands = sorted(OLI_BAND_NAMES.keys()) + sorted(TIRS_BAND_NAMES_L8.keys()) + for platform_name in ["Landsat-8", "Landsat-9"]: + tohdf5(OliRSR, platform_name, bands)