Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add function to the SLSTR L1 reader to enable correction of VIS radiances. #1401

Merged
merged 10 commits into from
Nov 16, 2020
78 changes: 67 additions & 11 deletions satpy/readers/slstr_l1b.py
Expand Up @@ -17,6 +17,7 @@
# satpy. If not, see <http://www.gnu.org/licenses/>.
"""SLSTR L1b reader."""

import warnings
import logging
import os
import re
Expand All @@ -35,6 +36,28 @@
PLATFORM_NAMES = {'S3A': 'Sentinel-3A',
'S3B': 'Sentinel-3B'}

# These are the default channel adjustment factors.
# Defined in the product notice: S3.PN-SLSTR-L1.06
# https://www.eumetsat.int/website/wcm/idc/idcplg?IdcService=GET_FILE&dDocName=PDF_S3A_PN_SLSTR_L1_06&RevisionSelectionMethod=LatestReleased&Rendition=Web
CHANCALIB_FACTORS = {'S1_nadir': 1.0,
'S2_nadir': 1.0,
'S3_nadir': 1.0,
'S4_nadir': 1.0,
'S5_nadir': 1.12,
'S6_nadir': 1.2,
'S7_nadir': 1.0,
'S8_nadir': 1.0,
'S9_nadir': 1.0,
'S1_oblique': 1.0,
'S2_oblique': 1.0,
'S3_oblique': 1.0,
'S4_oblique': 1.0,
'S5_oblique': 1.15,
'S6_oblique': 1.26,
'S7_oblique': 1.0,
'S8_oblique': 1.0,
'S9_oblique': 1.0, }


class NCSLSTRGeo(BaseFileHandler):
"""Filehandler for geo info."""
Expand Down Expand Up @@ -80,9 +103,26 @@ def end_time(self):


class NCSLSTR1B(BaseFileHandler):
"""Filehandler for l1 SLSTR data."""

def __init__(self, filename, filename_info, filetype_info):
"""Filehandler for l1 SLSTR data.

By default, the calibration factors recommended by EUMETSAT are applied.
This is required as the SLSTR VIS channels are producing slightly incorrect
radiances that require adjustment.
Satpy uses the radiance corrections in S3.PN-SLSTR-L1.06, checked 26/10/2020.
User-supplied coefficients can be passed via the `user_calibration` kwarg
This should be a dict of channel names (such as `S1_nadir`, `S8_oblique`).

For example::
calib_dict = {'S1_nadir': 1.12}
scene = satpy.Scene(filenames,
reader='slstr-l1b',
reader_kwargs={'user_calib':: calib_dict})

Will multiply S1 nadir radiances by 1.12.
"""

def __init__(self, filename, filename_info, filetype_info,
user_calibration=None):
"""Initialize the SLSTR l1 data filehandler."""
super(NCSLSTR1B, self).__init__(filename, filename_info,
filetype_info)
Expand Down Expand Up @@ -113,6 +153,27 @@ def __init__(self, filename, filename_info, filetype_info):

self.platform_name = PLATFORM_NAMES[filename_info['mission_id']]
self.sensor = 'slstr'
if isinstance(user_calibration, dict):
self.usercalib = user_calibration
else:
self.usercalib = None

def _apply_radiance_adjustment(self, radiances):
"""Adjust SLSTR radiances with default or user supplied values."""
chan_name = self.channel + '_' + self.view
adjust_fac = None
if self.usercalib is not None:
# If user supplied adjustment, use it.
if chan_name in self.usercalib:
adjust_fac = self.usercalib[chan_name]
if adjust_fac is None:
if chan_name in CHANCALIB_FACTORS:
adjust_fac = CHANCALIB_FACTORS[chan_name]
else:
warnings.warn("Warning: No radiance adjustment supplied " +
"for channel " + chan_name)
return radiances
return radiances * adjust_fac

@staticmethod
def _cal_rad(rad, didx, solar_flux=None):
Expand All @@ -124,28 +185,23 @@ def _cal_rad(rad, didx, solar_flux=None):
def get_dataset(self, key, info):
"""Load a dataset."""
if (self.channel not in key['name'] or
self.stripe != key['stripe'].name or
self.stripe != key['stripe'].name or
self.view != key['view'].name):
return

logger.debug('Reading %s.', key['name'])
if key['calibration'] == 'brightness_temperature':
variable = self.nc['{}_BT_{}{}'.format(self.channel, self.stripe, self.view[0])]
else:
variable = self.nc['{}_radiance_{}{}'.format(self.channel, self.stripe, self.view[0])]

radiances = variable
radiances = self._apply_radiance_adjustment(variable)
units = variable.attrs['units']

if key['calibration'] == 'reflectance':
# TODO take into account sun-earth distance
solar_flux = self.cal[re.sub('_[^_]*$', '', key['name']) + '_solar_irradiances']
d_index = self.indices['detector_{}{}'.format(self.stripe, self.view[0])]
idx = 0 if self.view[0] == 'n' else 1 # 0: Nadir view, 1: oblique (check).

idx = 0 if self.view[0] == 'n' else 1 # 0: Nadir view, 1: oblique (check).
radiances.data = da.map_blocks(
self._cal_rad, radiances.data, d_index.data, solar_flux=solar_flux[:, idx].values)

radiances *= np.pi * 100
units = '%'

Expand Down
110 changes: 0 additions & 110 deletions satpy/tests/reader_tests/test_nc_slstr.py

This file was deleted.