Source code for satpy.tests.reader_tests.test_seviri_l1b_calibration

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2017-2018 Satpy developers
#
# This file is part of satpy.
#
# satpy 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.
#
# satpy 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
# satpy.  If not, see <http://www.gnu.org/licenses/>.
"""Unittesting the native msg reader."""

import unittest
from datetime import datetime

import numpy as np
import pytest
import xarray as xr

from satpy.readers.seviri_base import SEVIRICalibrationAlgorithm, SEVIRICalibrationHandler

COUNTS_INPUT = xr.DataArray(
    np.array([[377.,  377.,  377.,  376.,  375.],
              [376.,  375.,  376.,  374.,  374.],
              [374.,  373.,  373.,  374.,  374.],
              [347.,  345.,  345.,  348.,  347.],
              [306.,  306.,  307.,  307.,  308.]], dtype=np.float32)
)

RADIANCES_OUTPUT = xr.DataArray(
    np.array([[66.84162903,  66.84162903,  66.84162903,  66.63659668,
               66.4315567],
              [66.63659668,  66.4315567,  66.63659668,  66.22652435,
               66.22652435],
              [66.22652435,  66.02148438,  66.02148438,  66.22652435,
               66.22652435],
              [60.69055939,  60.28048706,  60.28048706,  60.89559937,
               60.69055939],
              [52.28409576,  52.28409576,  52.48912811,  52.48912811,
               52.69416809]], dtype=np.float32)
)

GAIN = 0.20503567620766011
OFFSET = -10.456819486590666

CAL_TYPE1 = 1
CAL_TYPE2 = 2
CAL_TYPEBAD = -1
CHANNEL_NAME = "IR_108"
PLATFORM_ID = 323  # Met-10

TBS_OUTPUT1 = xr.DataArray(
    np.array([[269.29684448,  269.29684448,  269.29684448,  269.13296509,
               268.96871948],
              [269.13296509,  268.96871948,  269.13296509,  268.80422974,
               268.80422974],
              [268.80422974,  268.63937378,  268.63937378,  268.80422974,
               268.80422974],
              [264.23751831,  263.88912964,  263.88912964,  264.41116333,
               264.23751831],
              [256.77682495,  256.77682495,  256.96743774,  256.96743774,
               257.15756226]], dtype=np.float32)
)

TBS_OUTPUT2 = xr.DataArray(
    np.array([[268.94519043,  268.94519043,  268.94519043,  268.77984619,
               268.61422729],
              [268.77984619,  268.61422729,  268.77984619,  268.44830322,
               268.44830322],
              [268.44830322,  268.28204346,  268.28204346,  268.44830322,
               268.44830322],
              [263.84396362,  263.49285889,  263.49285889,  264.01898193,
               263.84396362],
              [256.32858276,  256.32858276,  256.52044678,  256.52044678,
               256.71188354]], dtype=np.float32)
)

VIS008_SOLAR_IRRADIANCE = 73.1807

VIS008_RADIANCE = xr.DataArray(
    np.array([[0.62234485,  0.59405649,  0.59405649,  0.59405649,  0.59405649],
             [0.59405649,  0.62234485,  0.62234485,  0.59405649,  0.62234485],
             [0.76378691,  0.79207528,  0.79207528,  0.76378691,  0.79207528],
             [3.30974245,  3.33803129,  3.33803129,  3.25316572,  3.47947311],
             [7.52471399,  7.83588648,  8.2602129,  8.57138538,  8.99571133]],
             dtype=np.float32)
)

VIS008_REFLECTANCE = xr.DataArray(
    np.array([[2.739768, 2.615233, 2.615233, 2.615233, 2.615233],
              [2.615233, 2.739768, 2.739768, 2.615233, 2.739768],
              [3.362442, 3.486977, 3.486977, 3.362442, 3.486977],
              [14.570578, 14.695117, 14.695117, 14.321507, 15.317789],
              [33.126278, 34.49616, 36.364185, 37.73407, 39.60209]],
             dtype=np.float32)
)


[docs] class TestSEVIRICalibrationAlgorithm(unittest.TestCase): """Unit Tests for SEVIRI calibration algorithm."""
[docs] def setUp(self): """Set up the SEVIRI Calibration algorithm for testing.""" self.algo = SEVIRICalibrationAlgorithm( platform_id=PLATFORM_ID, scan_time=datetime(2020, 8, 15, 13, 0, 40) )
[docs] def test_convert_to_radiance(self): """Test the conversion from counts to radiances.""" result = self.algo.convert_to_radiance(COUNTS_INPUT, GAIN, OFFSET) xr.testing.assert_allclose(result, RADIANCES_OUTPUT) assert result.dtype == np.float32
[docs] def test_ir_calibrate(self): """Test conversion from radiance to brightness temperature.""" result = self.algo.ir_calibrate(RADIANCES_OUTPUT, CHANNEL_NAME, CAL_TYPE1) xr.testing.assert_allclose(result, TBS_OUTPUT1, rtol=1E-5) assert result.dtype == np.float32 result = self.algo.ir_calibrate(RADIANCES_OUTPUT, CHANNEL_NAME, CAL_TYPE2) xr.testing.assert_allclose(result, TBS_OUTPUT2, rtol=1E-5) with pytest.raises(NotImplementedError): self.algo.ir_calibrate(RADIANCES_OUTPUT, CHANNEL_NAME, CAL_TYPEBAD)
[docs] def test_vis_calibrate(self): """Test conversion from radiance to reflectance.""" result = self.algo.vis_calibrate(VIS008_RADIANCE, VIS008_SOLAR_IRRADIANCE) xr.testing.assert_allclose(result, VIS008_REFLECTANCE) assert result.sun_earth_distance_correction_applied assert result.dtype == np.float32
[docs] class TestSeviriCalibrationHandler: """Unit tests for SEVIRI calibration handler."""
[docs] def test_init(self): """Test initialization of the calibration handler.""" with pytest.raises(ValueError, match="Invalid calibration mode: INVALID. Choose one of (.*)"): SEVIRICalibrationHandler( platform_id=None, channel_name=None, coefs=None, calib_mode="invalid", scan_time=None )
[docs] def _get_calibration_handler(self, calib_mode="NOMINAL", ext_coefs=None): """Provide a calibration handler.""" return SEVIRICalibrationHandler( platform_id=324, channel_name="IR_108", coefs={ "coefs": { "NOMINAL": { "gain": 10, "offset": -1 }, "GSICS": { "gain": 20, "offset": -2 }, "EXTERNAL": ext_coefs or {} }, "radiance_type": 1 }, calib_mode=calib_mode, scan_time=None )
[docs] def test_calibrate_exceptions(self): """Test exceptions raised by the calibration handler.""" calib = self._get_calibration_handler() with pytest.raises(ValueError, match="Invalid calibration invalid for channel IR_108"): calib.calibrate(None, "invalid")
[docs] @pytest.mark.parametrize( ("calib_mode", "ext_coefs", "expected"), [ ("NOMINAL", {}, (10, -1)), ("GSICS", {}, (20, -40)), ("GSICS", {"gain": 30, "offset": -3}, (30, -3)), ("NOMINAL", {"gain": 30, "offset": -3}, (30, -3)) ] ) def test_get_gain_offset(self, calib_mode, ext_coefs, expected): """Test selection of gain and offset.""" calib = self._get_calibration_handler(calib_mode=calib_mode, ext_coefs=ext_coefs) coefs = calib.get_gain_offset() assert coefs == expected
[docs] class TestFileHandlerCalibrationBase: """Base class for file handler calibration tests.""" platform_id = 324 gains_nominal = np.arange(1, 13) offsets_nominal = np.arange(-1, -13, -1) # No GSICS coefficients for VIS channels -> set to zero gains_gsics = [0, 0, 0, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 0] offsets_gsics = [0, 0, 0, -0.4, -0.5, -0.6, -0.7, -0.8, -0.9, -1.0, -1.1, 0] radiance_types = 2 * np.ones(12) scan_time = datetime(2020, 1, 1) external_coefs = { "VIS006": {"gain": 10, "offset": -10}, "IR_108": {"gain": 20, "offset": -20}, "HRV": {"gain": 5, "offset": -5} } spectral_channel_ids = {"VIS006": 1, "IR_108": 9, "HRV": 12} expected = { "VIS006": { "counts": { "NOMINAL": xr.DataArray( [[0, 10], [100, 255]], dims=("y", "x") ) }, "radiance": { "NOMINAL": xr.DataArray( [[np.nan, 9], [99, 254]], dims=("y", "x") ), "GSICS": xr.DataArray( [[np.nan, 9], [99, 254]], dims=("y", "x") ), "EXTERNAL": xr.DataArray( [[np.nan, 90], [990, 2540]], dims=("y", "x") ) }, "reflectance": { "NOMINAL": xr.DataArray( [[np.nan, 41.88985], [460.7884, 1182.2247]], dims=("y", "x") ), "EXTERNAL": xr.DataArray( [[np.nan, 418.89853], [4607.8843, 11822.249]], dims=("y", "x") ) } }, "IR_108": { "counts": { "NOMINAL": xr.DataArray( [[0, 10], [100, 255]], dims=("y", "x") ) }, "radiance": { "NOMINAL": xr.DataArray( [[np.nan, 81], [891, 2286]], dims=("y", "x") ), "GSICS": xr.DataArray( [[np.nan, 8.19], [89.19, 228.69]], dims=("y", "x") ), "EXTERNAL": xr.DataArray( [[np.nan, 180], [1980, 5080]], dims=("y", "x") ) }, "brightness_temperature": { "NOMINAL": xr.DataArray( [[np.nan, 279.82318], [543.2585, 812.77167]], dims=("y", "x") ), "GSICS": xr.DataArray( [[np.nan, 189.20985], [285.53293, 356.06668]], dims=("y", "x") ), "EXTERNAL": xr.DataArray( [[np.nan, 335.14236], [758.6249, 1262.7567]], dims=("y", "x") ), } }, "HRV": { "counts": { "NOMINAL": xr.DataArray( [[0, 10], [100, 255]], dims=("y", "x") ) }, "radiance": { "NOMINAL": xr.DataArray( [[np.nan, 108], [1188, 3048]], dims=("y", "x") ), "GSICS": xr.DataArray( [[np.nan, 108], [1188, 3048]], dims=("y", "x") ), "EXTERNAL": xr.DataArray( [[np.nan, 45], [495, 1270]], dims=("y", "x") ) }, "reflectance": { "NOMINAL": xr.DataArray( [[np.nan, 415.26767], [4567.944, 11719.775]], dims=("y", "x") ), "EXTERNAL": xr.DataArray( [[np.nan, 173.02817], [1903.31, 4883.2397]], dims=("y", "x") ) } } }
[docs] @pytest.fixture(name="counts") def counts(self): """Provide fake image counts.""" return xr.DataArray( [[0, 10], [100, 255]], dims=("y", "x") )
[docs] def _get_expected( self, channel, calibration, calib_mode, use_ext_coefs ): if use_ext_coefs: return self.expected[channel][calibration]["EXTERNAL"] return self.expected[channel][calibration][calib_mode]