diff --git a/doc/platforms_supported.rst b/doc/platforms_supported.rst index c861ec5..e01b5e4 100644 --- a/doc/platforms_supported.rst +++ b/doc/platforms_supported.rst @@ -85,6 +85,9 @@ have been included in Pyspectral. * - Sentinel-2B msi - `rsr_msi_Sentinel-2B.h5` - ESA-Sentinel-MSI_ + * - Sentinel-2C msi + - `rsr_msi_Sentinel-2C.h5` + - ESA-Sentinel-MSI_ * - NOAA-20 viirs - `rsr_viirs_NOAA-20.h5` - NESDIS_ diff --git a/pyspectral/etc/pyspectral.yaml b/pyspectral/etc/pyspectral.yaml index a6f3aeb..06d0aca 100644 --- a/pyspectral/etc/pyspectral.yaml +++ b/pyspectral/etc/pyspectral.yaml @@ -154,10 +154,12 @@ download_from_internet: True # Arctica-M-N1-msu-gsa: # path: /path/to/original/Arctica_M_N1_SRF.xlsx -# Sentinel-2A-msi: -# path: /path/to/original/sentinel-2a/msi/data/S2-SRF_COPE-GSEG-EOPG-TN-15-0007_3.0.xlsx -# Sentinel-2B-msi: -# path: /path/to/original/sentinel-2b/msi/data/S2-SRF_COPE-GSEG-EOPG-TN-15-0007_3.0.xlsx +#Sentinel-2A-msi: +# path: /path/to/COPE-GSEG-EOPG-TN-15-0007-Sentinel-2_Spectral_Response_Functions_2024-4.0.xlsx +#Sentinel-2B-msi: +# path: /path/to/COPE-GSEG-EOPG-TN-15-0007-Sentinel-2_Spectral_Response_Functions_2024-4.0.xlsx +#Sentinel-2C-msi: +# path: /path/to/COPE-GSEG-EOPG-TN-15-0007-Sentinel-2_Spectral_Response_Functions_2024-4.0.xlsx # Himawari-8-ahi: # path: /path/to/original/ahi/data diff --git a/pyspectral/utils.py b/pyspectral/utils.py index 3ffe149..e74d9d2 100644 --- a/pyspectral/utils.py +++ b/pyspectral/utils.py @@ -82,6 +82,7 @@ 'EOS-Terra': 'modis', 'Sentinel-2A': 'msi', 'Sentinel-2B': 'msi', + 'Sentinel-2C': 'msi', 'Arctica-M-N1': 'msu-gsa', 'Electro-L-N2': 'msu-gs', 'Sentinel-3A': ['olci', 'slstr'], @@ -107,10 +108,11 @@ 'avhrr-2': 'avhrr/2', 'avhrr-3': 'avhrr/3'} -HTTP_PYSPECTRAL_RSR = "https://zenodo.org/records/12743289/files/pyspectral_rsr_data.tgz" +HTTP_PYSPECTRAL_RSR = "https://zenodo.org/records/13833977/files/pyspectral_rsr_data.tgz" RSR_DATA_VERSION_FILENAME = "PYSPECTRAL_RSR_VERSION" -RSR_DATA_VERSION = "v1.3.2" +RSR_DATA_VERSION = "v1.4.0" + ATM_CORRECTION_LUT_VERSION = {} ATM_CORRECTION_LUT_VERSION['antarctic_aerosol'] = {'version': 'v1.0.1', diff --git a/rsr_convert_scripts/README.rst b/rsr_convert_scripts/README.rst index d45bebf..c822e4b 100644 --- a/rsr_convert_scripts/README.rst +++ b/rsr_convert_scripts/README.rst @@ -95,8 +95,8 @@ Terra files have names like this: ``rsr.1.oobd.det`` %> python msi_reader.py -The original Sentinel-2 A&B MSI spectral responses. Filenames look like this -``S2-SRF_COPE-GSEG-EOPG-TN-15-0007_3.0.xlsx`` +The original Sentinel-2 A,B, and C MSI spectral responses. Filenames look like this +``COPE-GSEG-EOPG-TN-15-0007-Sentinel-2_Spectral_Response_Functions_2024-4.0.xlsx`` .. code:: diff --git a/rsr_convert_scripts/msi_reader.py b/rsr_convert_scripts/msi_reader.py index ab4ac3d..4ee1d60 100644 --- a/rsr_convert_scripts/msi_reader.py +++ b/rsr_convert_scripts/msi_reader.py @@ -29,47 +29,34 @@ import os import numpy as np -from xlrd import open_workbook +import pandas as pd from pyspectral.raw_reader import InstrumentRSR from pyspectral.utils import convert2hdf5 as tohdf5 LOG = logging.getLogger(__name__) - -MSI_BAND_NAMES = {} -MSI_BAND_NAMES['S2A'] = {'S2A_SR_AV_B1': 'B01', - 'S2A_SR_AV_B2': 'B02', - 'S2A_SR_AV_B3': 'B03', - 'S2A_SR_AV_B4': 'B04', - 'S2A_SR_AV_B5': 'B05', - 'S2A_SR_AV_B6': 'B06', - 'S2A_SR_AV_B7': 'B07', - 'S2A_SR_AV_B8': 'B08', - 'S2A_SR_AV_B8A': 'B8A', - 'S2A_SR_AV_B9': 'B09', - 'S2A_SR_AV_B10': 'B10', - 'S2A_SR_AV_B11': 'B11', - 'S2A_SR_AV_B12': 'B12'} -MSI_BAND_NAMES['S2B'] = {'S2B_SR_AV_B1': 'B01', - 'S2B_SR_AV_B2': 'B02', - 'S2B_SR_AV_B3': 'B03', - 'S2B_SR_AV_B4': 'B04', - 'S2B_SR_AV_B5': 'B05', - 'S2B_SR_AV_B6': 'B06', - 'S2B_SR_AV_B7': 'B07', - 'S2B_SR_AV_B8': 'B08', - 'S2B_SR_AV_B8A': 'B8A', - 'S2B_SR_AV_B9': 'B09', - 'S2B_SR_AV_B10': 'B10', - 'S2B_SR_AV_B11': 'B11', - 'S2B_SR_AV_B12': 'B12'} - -SHEET_HEADERS = {'Spectral Responses (S2A)': 'S2A', - 'Spectral Responses (S2B)': 'S2B'} - -PLATFORM_SHORT_NAME = {'Sentinel-2A': 'S2A', - 'Sentinel-2B': 'S2B'} +MSI_BAND_NAMES = {"B01": "B1", + "B02": "B2", + "B03": "B3", + "B04": "B4", + "B05": "B5", + "B06": "B6", + "B07": "B7", + "B08": "B8", + "B8A": "B8A", + "B09": "B9", + "B10": "B10", + "B11": "B11", + "B12": "B12"} + +SHEET_HEADERS = {"S2A": "Spectral Responses (S2A)", + "S2B": "Spectral Responses (S2B)", + "S2C": "Spectral Responses (S2C)"} + +PLATFORM_SHORT_NAME = {"Sentinel-2A": "S2A", + "Sentinel-2B": "S2B", + "Sentinel-2C": "S2C"} class MsiRSR(InstrumentRSR): @@ -79,56 +66,34 @@ def __init__(self, bandname, platform_name): """Read the Sentinel-2 MSI relative spectral responses for all channels.""" super(MsiRSR, self).__init__(bandname, platform_name) - self.instrument = 'msi' + self.instrument = "msi" + self.platform_name = platform_name + self.short_plat = PLATFORM_SHORT_NAME[platform_name] self._get_options_from_config() - LOG.debug("Filename: %s", str(self.path)) + LOG.debug(f"Filename: {self.path}") if os.path.exists(self.path): - self._load() + self._load(platform_name) else: - raise IOError("Couldn't find an existing file for this band: " + - str(self.bandname)) + raise IOError(f"Couldn't find an existing file for this band: {str(self.bandname)}") def _load(self, scale=0.001): """Load the Sentinel-2 MSI relative spectral responses.""" - with open_workbook(self.path) as wb_: - for sheet in wb_.sheets(): - if sheet.name not in SHEET_HEADERS.keys(): - continue - - plt_short_name = PLATFORM_SHORT_NAME.get(self.platform_name) - if plt_short_name != SHEET_HEADERS.get(sheet.name): - continue - - wvl = sheet.col_values(0, 1) - for idx in range(1, sheet.row_len(0)): - ch_name = MSI_BAND_NAMES[plt_short_name].get(str(sheet.col_values(idx, 0, 1)[0])) - if ch_name != self.bandname: - continue + bname = MSI_BAND_NAMES.get(self.bandname) + df = pd.read_excel(self.path, engine='openpyxl', sheet_name=SHEET_HEADERS[self.short_plat]) + wvl = np.array(df['SR_WL']) + resp = np.array(df[f"{self.short_plat}_SR_AV_{bname}"]) - resp = sheet.col_values(idx, 1) - resp = np.array(resp) - resp = np.where(resp == '', 0, resp).astype('float32') - mask = np.less_equal(resp, 0.00001) - wvl0 = np.ma.masked_array(wvl, mask=mask) - wvl_mask = np.ma.masked_outside(wvl, wvl0.min() - 2, wvl0.max() + 2) + mask = np.less_equal(resp, 0.00001) + wvl0 = np.ma.masked_array(wvl, mask=mask) + wvl_mask = np.ma.masked_outside(wvl, wvl0.min() - 2, wvl0.max() + 2) - wvl = wvl_mask.compressed() - resp = np.ma.masked_array(resp, mask=wvl_mask.mask).compressed() - self.rsr = {'wavelength': wvl / 1000., 'response': resp} - - break - - break + wvl = wvl_mask.compressed() + resp = np.ma.masked_array(resp, mask=wvl_mask.mask).compressed() + self.rsr = {"wavelength": wvl / 1000., "response": resp} if __name__ == "__main__": - bands = MSI_BAND_NAMES['S2A'].values() - bands.sort() - for platform_name in ['Sentinel-2A', ]: - tohdf5(MsiRSR, platform_name, bands) - - bands = MSI_BAND_NAMES['S2B'].values() - bands.sort() - for platform_name in ['Sentinel-2B', ]: - tohdf5(MsiRSR, platform_name, bands) + + for plat_name in ["Sentinel-2A", "Sentinel-2B", "Sentinel-2C"]: + tohdf5(MsiRSR, plat_name, sorted(MSI_BAND_NAMES))