#!/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 datetime as dt
import unittest
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=dt.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 = dt.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]