From 35e910fa7b59aececc002534d7014d50e32f92d8 Mon Sep 17 00:00:00 2001 From: Gerrit Holl Date: Tue, 22 Mar 2022 10:54:16 +0100 Subject: [PATCH 01/34] Refactor areadefinition in VIIRS unit tests --- satpy/tests/compositor_tests/test_viirs.py | 72 ++++++++-------------- 1 file changed, 27 insertions(+), 45 deletions(-) diff --git a/satpy/tests/compositor_tests/test_viirs.py b/satpy/tests/compositor_tests/test_viirs.py index 820bd40d38..b59f934a74 100644 --- a/satpy/tests/compositor_tests/test_viirs.py +++ b/satpy/tests/compositor_tests/test_viirs.py @@ -27,14 +27,9 @@ class TestVIIRSComposites: """Test various VIIRS-specific composites.""" - def test_load_composite_yaml(self): - """Test loading the yaml for this sensor.""" - from satpy.composites.config_loader import load_compositor_configs_for_sensors - load_compositor_configs_for_sensors(['viirs']) - - def test_histogram_dnb(self): - """Test the 'histogram_dnb' compositor.""" - from satpy.composites.viirs import HistogramDNB + @pytest.fixture + def area(self): + """Return fake area for use with DNB tests.""" rows = 5 cols = 10 area = AreaDefinition( @@ -43,11 +38,21 @@ def test_histogram_dnb(self): 'lat_0': 0.0}, cols, rows, (-20037508.34, -10018754.17, 20037508.34, 10018754.17)) + return area + + def test_load_composite_yaml(self): + """Test loading the yaml for this sensor.""" + from satpy.composites.config_loader import load_compositor_configs_for_sensors + load_compositor_configs_for_sensors(['viirs']) + + def test_histogram_dnb(self, area): + """Test the 'histogram_dnb' compositor.""" + from satpy.composites.viirs import HistogramDNB comp = HistogramDNB('histogram_dnb', prerequisites=('dnb',), standard_name='toa_outgoing_radiance_per_' 'unit_wavelength') - dnb = np.zeros((rows, cols)) + 0.25 + dnb = np.zeros(area.shape) + 0.25 dnb[3, :] += 0.25 dnb[4:, :] += 0.5 dnb = da.from_array(dnb, chunks=25) @@ -55,7 +60,7 @@ def test_histogram_dnb(self): dims=('y', 'x'), attrs={'name': 'DNB', 'area': area}) # data changes by row, sza changes by col for testing - sza = np.zeros((rows, cols)) + 70.0 + sza = np.zeros(area.shape) + 70.0 sza[:, 3] += 20.0 sza[:, 4:] += 45.0 sza = da.from_array(sza, chunks=25) @@ -71,29 +76,21 @@ def test_histogram_dnb(self): unique_values = np.unique(data) np.testing.assert_allclose(unique_values, [0.5994, 0.7992, 0.999], rtol=1e-3) - def test_adaptive_dnb(self): + def test_adaptive_dnb(self, area): """Test the 'adaptive_dnb' compositor.""" from satpy.composites.viirs import AdaptiveDNB - rows = 5 - cols = 10 - area = AreaDefinition( - 'test', 'test', 'test', - {'proj': 'eqc', 'lon_0': 0.0, - 'lat_0': 0.0}, - cols, rows, - (-20037508.34, -10018754.17, 20037508.34, 10018754.17)) comp = AdaptiveDNB('adaptive_dnb', prerequisites=('dnb',), standard_name='toa_outgoing_radiance_per_' 'unit_wavelength') - dnb = np.zeros((rows, cols)) + 0.25 + dnb = np.zeros(area.shape) + 0.25 dnb[3, :] += 0.25 dnb[4:, :] += 0.5 dnb = da.from_array(dnb, chunks=25) c01 = xr.DataArray(dnb, dims=('y', 'x'), attrs={'name': 'DNB', 'area': area}) - sza = np.zeros((rows, cols)) + 70.0 + sza = np.zeros(area.shape) + 70.0 sza[:, 3] += 20.0 sza[:, 4:] += 45.0 sza = da.from_array(sza, chunks=25) @@ -108,36 +105,28 @@ def test_adaptive_dnb(self): data = res.compute() np.testing.assert_allclose(data.data, 0.999, rtol=1e-4) - def test_hncc_dnb(self): + def test_hncc_dnb(self, area): """Test the 'hncc_dnb' compositor.""" from satpy.composites.viirs import NCCZinke - rows = 5 - cols = 10 - area = AreaDefinition( - 'test', 'test', 'test', - {'proj': 'eqc', 'lon_0': 0.0, - 'lat_0': 0.0}, - cols, rows, - (-20037508.34, -10018754.17, 20037508.34, 10018754.17)) comp = NCCZinke('hncc_dnb', prerequisites=('dnb',), standard_name='toa_outgoing_radiance_per_' 'unit_wavelength') - dnb = np.zeros((rows, cols)) + 0.25 + dnb = np.zeros(area.shape) + 0.25 dnb[3, :] += 0.25 dnb[4:, :] += 0.5 dnb = da.from_array(dnb, chunks=25) c01 = xr.DataArray(dnb, dims=('y', 'x'), attrs={'name': 'DNB', 'area': area}) - sza = np.zeros((rows, cols)) + 70.0 + sza = np.zeros(area.shape) + 70.0 sza[:, 3] += 20.0 sza[:, 4:] += 45.0 sza = da.from_array(sza, chunks=25) c02 = xr.DataArray(sza, dims=('y', 'x'), attrs={'name': 'solar_zenith_angle', 'area': area}) - lza = np.zeros((rows, cols)) + 70.0 + lza = np.zeros(area.shape) + 70.0 lza[:, 3] += 20.0 lza[:, 4:] += 45.0 lza = da.from_array(lza, chunks=25) @@ -161,23 +150,16 @@ def test_hncc_dnb(self): @pytest.mark.parametrize("dnb_units", ["W m-2 sr-1", "W cm-2 sr-1"]) @pytest.mark.parametrize("saturation_correction", [False, True]) - def test_erf_dnb(self, dnb_units, saturation_correction): + def test_erf_dnb(self, dnb_units, saturation_correction, area): """Test the 'dynamic_dnb' or ERF DNB compositor.""" from satpy.composites.viirs import ERFDNB - rows = 5 - cols = 10 - area = AreaDefinition( - 'test', 'test', 'test', - {'proj': 'eqc', 'lon_0': 0.0, - 'lat_0': 0.0}, - cols, rows, - (-20037508.34, -10018754.17, 20037508.34, 10018754.17)) comp = ERFDNB('dynamic_dnb', prerequisites=('dnb',), saturation_correction=saturation_correction, standard_name='toa_outgoing_radiance_per_' 'unit_wavelength') - dnb = np.zeros((rows, cols)) + 0.25 + dnb = np.zeros(area.shape) + 0.25 + cols = area.shape[1] dnb[2, :cols // 2] = np.nan dnb[3, :] += 0.25 dnb[4:, :] += 0.5 @@ -187,14 +169,14 @@ def test_erf_dnb(self, dnb_units, saturation_correction): c01 = xr.DataArray(dnb, dims=('y', 'x'), attrs={'name': 'DNB', 'area': area, 'units': dnb_units}) - sza = np.zeros((rows, cols)) + 70.0 + sza = np.zeros(area.shape) + 70.0 sza[:, 3] += 20.0 sza[:, 4:] += 45.0 sza = da.from_array(sza, chunks=25) c02 = xr.DataArray(sza, dims=('y', 'x'), attrs={'name': 'solar_zenith_angle', 'area': area}) - lza = np.zeros((rows, cols)) + 70.0 + lza = np.zeros(area.shape) + 70.0 lza[:, 3] += 20.0 lza[:, 4:] += 45.0 lza = da.from_array(lza, chunks=25) From 5220ddba6e67e526b2ebd4cce93370fae62124c1 Mon Sep 17 00:00:00 2001 From: Gerrit Holl Date: Tue, 22 Mar 2022 11:01:27 +0100 Subject: [PATCH 02/34] Refactor common code into fixtures For the VIIRS composites unit tests, refactor common code into fixtures. --- satpy/tests/compositor_tests/test_viirs.py | 105 ++++++++------------- 1 file changed, 40 insertions(+), 65 deletions(-) diff --git a/satpy/tests/compositor_tests/test_viirs.py b/satpy/tests/compositor_tests/test_viirs.py index b59f934a74..74550332aa 100644 --- a/satpy/tests/compositor_tests/test_viirs.py +++ b/satpy/tests/compositor_tests/test_viirs.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2018 Satpy developers +# Copyright (c) 2018, 2022 Satpy developers # # This file is part of satpy. # @@ -40,18 +40,9 @@ def area(self): (-20037508.34, -10018754.17, 20037508.34, 10018754.17)) return area - def test_load_composite_yaml(self): - """Test loading the yaml for this sensor.""" - from satpy.composites.config_loader import load_compositor_configs_for_sensors - load_compositor_configs_for_sensors(['viirs']) - - def test_histogram_dnb(self, area): - """Test the 'histogram_dnb' compositor.""" - from satpy.composites.viirs import HistogramDNB - - comp = HistogramDNB('histogram_dnb', prerequisites=('dnb',), - standard_name='toa_outgoing_radiance_per_' - 'unit_wavelength') + @pytest.fixture + def c01(self, area): + """Return fake channel 1 data for DNB tests.""" dnb = np.zeros(area.shape) + 0.25 dnb[3, :] += 0.25 dnb[4:, :] += 0.5 @@ -59,6 +50,11 @@ def test_histogram_dnb(self, area): c01 = xr.DataArray(dnb, dims=('y', 'x'), attrs={'name': 'DNB', 'area': area}) + return c01 + + @pytest.fixture + def c02(self, area): + """Return fake sza dataset for DNB tests.""" # data changes by row, sza changes by col for testing sza = np.zeros(area.shape) + 70.0 sza[:, 3] += 20.0 @@ -67,6 +63,32 @@ def test_histogram_dnb(self, area): c02 = xr.DataArray(sza, dims=('y', 'x'), attrs={'name': 'solar_zenith_angle', 'area': area}) + return c02 + + @pytest.fixture + def c03(self, area): + """Return fake lunal zenith angle dataset for DNB tests.""" + lza = np.zeros(area.shape) + 70.0 + lza[:, 3] += 20.0 + lza[:, 4:] += 45.0 + lza = da.from_array(lza, chunks=25) + c03 = xr.DataArray(lza, + dims=('y', 'x'), + attrs={'name': 'lunar_zenith_angle', 'area': area}) + return c03 + + def test_load_composite_yaml(self): + """Test loading the yaml for this sensor.""" + from satpy.composites.config_loader import load_compositor_configs_for_sensors + load_compositor_configs_for_sensors(['viirs']) + + def test_histogram_dnb(self, c01, c02): + """Test the 'histogram_dnb' compositor.""" + from satpy.composites.viirs import HistogramDNB + + comp = HistogramDNB('histogram_dnb', prerequisites=('dnb',), + standard_name='toa_outgoing_radiance_per_' + 'unit_wavelength') res = comp((c01, c02)) assert isinstance(res, xr.DataArray) assert isinstance(res.data, da.Array) @@ -76,27 +98,13 @@ def test_histogram_dnb(self, area): unique_values = np.unique(data) np.testing.assert_allclose(unique_values, [0.5994, 0.7992, 0.999], rtol=1e-3) - def test_adaptive_dnb(self, area): + def test_adaptive_dnb(self, c01, c02): """Test the 'adaptive_dnb' compositor.""" from satpy.composites.viirs import AdaptiveDNB comp = AdaptiveDNB('adaptive_dnb', prerequisites=('dnb',), standard_name='toa_outgoing_radiance_per_' 'unit_wavelength') - dnb = np.zeros(area.shape) + 0.25 - dnb[3, :] += 0.25 - dnb[4:, :] += 0.5 - dnb = da.from_array(dnb, chunks=25) - c01 = xr.DataArray(dnb, - dims=('y', 'x'), - attrs={'name': 'DNB', 'area': area}) - sza = np.zeros(area.shape) + 70.0 - sza[:, 3] += 20.0 - sza[:, 4:] += 45.0 - sza = da.from_array(sza, chunks=25) - c02 = xr.DataArray(sza, - dims=('y', 'x'), - attrs={'name': 'solar_zenith_angle', 'area': area}) res = comp((c01, c02)) assert isinstance(res, xr.DataArray) assert isinstance(res.data, da.Array) @@ -105,34 +113,13 @@ def test_adaptive_dnb(self, area): data = res.compute() np.testing.assert_allclose(data.data, 0.999, rtol=1e-4) - def test_hncc_dnb(self, area): + def test_hncc_dnb(self, area, c01, c02, c03): """Test the 'hncc_dnb' compositor.""" from satpy.composites.viirs import NCCZinke comp = NCCZinke('hncc_dnb', prerequisites=('dnb',), standard_name='toa_outgoing_radiance_per_' 'unit_wavelength') - dnb = np.zeros(area.shape) + 0.25 - dnb[3, :] += 0.25 - dnb[4:, :] += 0.5 - dnb = da.from_array(dnb, chunks=25) - c01 = xr.DataArray(dnb, - dims=('y', 'x'), - attrs={'name': 'DNB', 'area': area}) - sza = np.zeros(area.shape) + 70.0 - sza[:, 3] += 20.0 - sza[:, 4:] += 45.0 - sza = da.from_array(sza, chunks=25) - c02 = xr.DataArray(sza, - dims=('y', 'x'), - attrs={'name': 'solar_zenith_angle', 'area': area}) - lza = np.zeros(area.shape) + 70.0 - lza[:, 3] += 20.0 - lza[:, 4:] += 45.0 - lza = da.from_array(lza, chunks=25) - c03 = xr.DataArray(lza, - dims=('y', 'x'), - attrs={'name': 'lunar_zenith_angle', 'area': area}) mif = xr.DataArray(da.zeros((5,), chunks=5) + 0.1, dims=('y',), attrs={'name': 'moon_illumination_fraction', 'area': area}) @@ -150,7 +137,7 @@ def test_hncc_dnb(self, area): @pytest.mark.parametrize("dnb_units", ["W m-2 sr-1", "W cm-2 sr-1"]) @pytest.mark.parametrize("saturation_correction", [False, True]) - def test_erf_dnb(self, dnb_units, saturation_correction, area): + def test_erf_dnb(self, dnb_units, saturation_correction, area, c02, c03): """Test the 'dynamic_dnb' or ERF DNB compositor.""" from satpy.composites.viirs import ERFDNB @@ -158,6 +145,8 @@ def test_erf_dnb(self, dnb_units, saturation_correction, area): saturation_correction=saturation_correction, standard_name='toa_outgoing_radiance_per_' 'unit_wavelength') + # c01 is different from in the other tests, so don't use the fixture + # here dnb = np.zeros(area.shape) + 0.25 cols = area.shape[1] dnb[2, :cols // 2] = np.nan @@ -169,20 +158,6 @@ def test_erf_dnb(self, dnb_units, saturation_correction, area): c01 = xr.DataArray(dnb, dims=('y', 'x'), attrs={'name': 'DNB', 'area': area, 'units': dnb_units}) - sza = np.zeros(area.shape) + 70.0 - sza[:, 3] += 20.0 - sza[:, 4:] += 45.0 - sza = da.from_array(sza, chunks=25) - c02 = xr.DataArray(sza, - dims=('y', 'x'), - attrs={'name': 'solar_zenith_angle', 'area': area}) - lza = np.zeros(area.shape) + 70.0 - lza[:, 3] += 20.0 - lza[:, 4:] += 45.0 - lza = da.from_array(lza, chunks=25) - c03 = xr.DataArray(lza, - dims=('y', 'x'), - attrs={'name': 'lunar_zenith_angle', 'area': area}) mif = xr.DataArray(da.zeros((5,), chunks=5) + 0.1, dims=('y',), attrs={'name': 'moon_illumination_fraction', 'area': area}) From b45a8d48b989092bd785f4ed3751f413db3df05c Mon Sep 17 00:00:00 2001 From: Gerrit Holl Date: Tue, 22 Mar 2022 11:17:42 +0100 Subject: [PATCH 03/34] Add unit test for snow age RGB Add a unit test for the snow age RGB. This unit test currently fails, because it confirms, among other things, that there there is no units attribute present. Also add a reference to the snow age publication. --- satpy/composites/viirs.py | 25 +++++++++++----------- satpy/tests/compositor_tests/test_viirs.py | 22 +++++++++++++++++++ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/satpy/composites/viirs.py b/satpy/composites/viirs.py index 249a9a0cd5..f0c906d49a 100644 --- a/satpy/composites/viirs.py +++ b/satpy/composites/viirs.py @@ -953,18 +953,19 @@ class SnowAge(GenericCompositor): Product is based on method presented at the second CSPP/IMAPP users' meeting at Eumetsat in Darmstadt on 14-16 April 2015 - # Bernard Bellec snow Look-Up Tables V 1.0 (c) Meteo-France - # These Look-up Tables allow you to create the RGB snow product - # for SUOMI-NPP VIIRS Imager according to the algorithm - # presented at the second CSPP/IMAPP users' meeting at Eumetsat - # in Darmstadt on 14-16 April 2015 - # The algorithm and the product are described in this - # presentation : - # http://www.ssec.wisc.edu/meetings/cspp/2015/Agenda%20PDF/Wednesday/Roquet_snow_product_cspp2015.pdf - # For further information you may contact - # Bernard Bellec at Bernard.Bellec@meteo.fr - # or - # Pascale Roquet at Pascale.Roquet@meteo.fr + Bernard Bellec snow Look-Up Tables V 1.0 (c) Meteo-France + These Look-up Tables allow you to create the RGB snow product + for SUOMI-NPP VIIRS Imager according to the algorithm + presented at the second CSPP/IMAPP users' meeting at Eumetsat + in Darmstadt on 14-16 April 2015 + The algorithm and the product are described in this + presentation : + http://www.ssec.wisc.edu/meetings/cspp/2015/Agenda%20PDF/Wednesday/Roquet_snow_product_cspp2015.pdf + as well as in the paper http://dx.doi.org/10.1016/j.rse.2017.04.028 + For further information you may contact + Bernard Bellec at Bernard.Bellec@meteo.fr + or + Pascale Roquet at Pascale.Roquet@meteo.fr """ def __call__(self, projectables, nonprojectables=None, **info): diff --git a/satpy/tests/compositor_tests/test_viirs.py b/satpy/tests/compositor_tests/test_viirs.py index 74550332aa..54000ccb70 100644 --- a/satpy/tests/compositor_tests/test_viirs.py +++ b/satpy/tests/compositor_tests/test_viirs.py @@ -179,3 +179,25 @@ def test_erf_dnb(self, dnb_units, saturation_correction, area, c02, c03): 2.09233451e-01, 1.43916324e+02, 2.03528498e+02, 2.49270516e+02] np.testing.assert_allclose(nonnan_unique, exp_unique) + + def test_snow_age(self, area): + """Test the 'snow_age' compositor.""" + from satpy.composites.viirs import SnowAge + + projectables = tuple( + xr.DataArray( + da.from_array(np.full(area.shape, 5.*i), chunks=5), + dims=("y", "x"), + attrs={"name": f"M0{i:d}", + "calibration": "reflectance", + "units": "%"}) + for i in range(7, 12)) + comp = SnowAge( + "snow_age", + prerequisites=("M07", "M08", "M09", "M10", "M11",), + standard_name="snow_age") + res = comp(projectables) + assert isinstance(res, xr.DataArray) + assert isinstance(res.data, da.Array) + assert res.attrs["name"] == "snow_age" + assert "units" not in res.attrs From 8a9cd23373b229178559f2e3b8cbaa435ce5e981 Mon Sep 17 00:00:00 2001 From: Gerrit Holl Date: Tue, 22 Mar 2022 11:25:50 +0100 Subject: [PATCH 04/34] Add "no units" test for RatioSharpenedRGB Add a test to confirm that RGBs produced by the RatioSharpenedRGB have no units attribute. --- satpy/tests/test_composites.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/satpy/tests/test_composites.py b/satpy/tests/test_composites.py index 77463872b2..d4358599ca 100644 --- a/satpy/tests/test_composites.py +++ b/satpy/tests/test_composites.py @@ -135,6 +135,8 @@ def setUp(self): 'start_time': datetime(2018, 1, 1, 18), 'modifiers': tuple(), 'resolution': 1000, + 'calibration': 'reflectance', + 'units': '%', 'name': 'test_vis'} ds1 = xr.DataArray(da.ones((2, 2), chunks=2, dtype=np.float64), attrs=attrs, dims=('y', 'x'), @@ -229,6 +231,13 @@ def test_self_sharpened_basic(self): np.testing.assert_allclose(res[1], np.array([[3, 3], [3, 3]], dtype=np.float64)) np.testing.assert_allclose(res[2], np.array([[4, 4], [4, 4]], dtype=np.float64)) + def test_no_units(self): + """Test that the computed RGB has no units attribute.""" + from satpy.composites import RatioSharpenedRGB + comp = RatioSharpenedRGB(name='true_color') + res = comp((self.ds1, self.ds2, self.ds3)) + assert "units" not in res.attrs + class TestDifferenceCompositor(unittest.TestCase): """Test case for the difference compositor.""" From cbbe842d91ca91116e97b03a0b1438fab4701e64 Mon Sep 17 00:00:00 2001 From: Gerrit Holl Date: Tue, 22 Mar 2022 11:29:24 +0100 Subject: [PATCH 05/34] Clear units attribute for SnowAge and RatioSharpened compositors In the SnowAge and RatioSharpened compositors, clear the units attribute after the RGB has been produced. --- satpy/composites/__init__.py | 4 +++- satpy/composites/viirs.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/satpy/composites/__init__.py b/satpy/composites/__init__.py index 90f4e67aac..a0fe70b87a 100644 --- a/satpy/composites/__init__.py +++ b/satpy/composites/__init__.py @@ -979,7 +979,9 @@ def __call__(self, datasets, optional_datasets=None, **info): info.update(self.attrs) # Force certain pieces of metadata that we *know* to be true info.setdefault("standard_name", "true_color") - return super(RatioSharpenedRGB, self).__call__((r, g, b), **info) + res = super(RatioSharpenedRGB, self).__call__((r, g, b), **info) + res.attrs.pop("units", None) + return res def _mean4(data, offset=(0, 0), block_id=None): diff --git a/satpy/composites/viirs.py b/satpy/composites/viirs.py index f0c906d49a..57da11dfca 100644 --- a/satpy/composites/viirs.py +++ b/satpy/composites/viirs.py @@ -1007,4 +1007,6 @@ def __call__(self, projectables, nonprojectables=None, **info): ch2.attrs = info ch3.attrs = info - return super(SnowAge, self).__call__([ch1, ch2, ch3], **info) + res = super(SnowAge, self).__call__([ch1, ch2, ch3], **info) + res.attrs.pop("units", None) + return res From 6972258a2241404076aef42160b32a09496801fc Mon Sep 17 00:00:00 2001 From: Gerrit Holl Date: Tue, 22 Mar 2022 11:33:08 +0100 Subject: [PATCH 06/34] Document removal of units from RGBs --- satpy/composites/__init__.py | 5 ++++- satpy/composites/viirs.py | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/satpy/composites/__init__.py b/satpy/composites/__init__.py index a0fe70b87a..023fc91f72 100644 --- a/satpy/composites/__init__.py +++ b/satpy/composites/__init__.py @@ -920,7 +920,10 @@ def _get_band(self, high_res, low_res, color, ratio): return ret def __call__(self, datasets, optional_datasets=None, **info): - """Sharpen low resolution datasets by multiplying by the ratio of ``high_res / low_res``.""" + """Sharpen low resolution datasets by multiplying by the ratio of ``high_res / low_res``. + + The resulting RGB has the units attribute removed. + """ if len(datasets) != 3: raise ValueError("Expected 3 datasets, got %d" % (len(datasets), )) if not all(x.shape == datasets[0].shape for x in datasets[1:]) or \ diff --git a/satpy/composites/viirs.py b/satpy/composites/viirs.py index 57da11dfca..2c6587570c 100644 --- a/satpy/composites/viirs.py +++ b/satpy/composites/viirs.py @@ -974,11 +974,13 @@ def __call__(self, projectables, nonprojectables=None, **info): The algorithm and the product are described in this presentation : http://www.ssec.wisc.edu/meetings/cspp/2015/Agenda%20PDF/Wednesday/Roquet_snow_product_cspp2015.pdf + as well as in the paper http://dx.doi.org/10.1016/j.rse.2017.04.028 For further information you may contact Bernard Bellec at Bernard.Bellec@meteo.fr or Pascale Roquet at Pascale.Roquet@meteo.fr + The resulting RGB has the units attribute removed. """ if len(projectables) != 5: raise ValueError("Expected 5 datasets, got %d" % From 728927f474270bc90cd1aabd880b61f7cbefa42e Mon Sep 17 00:00:00 2001 From: "Adam.Dybbroe" Date: Thu, 24 Mar 2022 16:33:28 +0100 Subject: [PATCH 07/34] Add example using multiple readers Signed-off-by: Adam.Dybbroe --- satpy/scene.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/satpy/scene.py b/satpy/scene.py index 701aaf6a65..940e8f6f1f 100644 --- a/satpy/scene.py +++ b/satpy/scene.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2010-2017 Satpy developers +# Copyright (c) 2010-2017, 2022 Satpy developers # # This file is part of satpy. # @@ -71,6 +71,13 @@ def __init__(self, filenames=None, reader=None, filter_parameters=None, reader_kwargs=None): """Initialize Scene with Reader and Compositor objects. + Notice (see parameters list below) )that it is possible to load a + combinations of files or sets of files each requiring their specific + reader. For that the `filenames` needs to be a ``dict``, like e.g.:: + + scn = Scene(filenames={'nwcsaf-pps_nc': glob('/path/to/nwc/saf/pps/files/*'), + 'modis_l1b': glob('/path/to/modis/lvl1/files/*')}) + To load data `filenames` and preferably `reader` must be specified. If `filenames` is provided without `reader` then the available readers will be searched for a Reader that can support the provided files. This can take a considerable amount of time so it is recommended that `reader` always be provided. Note without `filenames` From a3777510cdc4579ab3f987c09d9af87184b4fc23 Mon Sep 17 00:00:00 2001 From: Adam Dybbroe Date: Fri, 25 Mar 2022 14:53:02 +0100 Subject: [PATCH 08/34] Update satpy/scene.py Fix typographic error Co-authored-by: Gerrit Holl --- satpy/scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satpy/scene.py b/satpy/scene.py index 940e8f6f1f..bf84fc47fa 100644 --- a/satpy/scene.py +++ b/satpy/scene.py @@ -71,7 +71,7 @@ def __init__(self, filenames=None, reader=None, filter_parameters=None, reader_kwargs=None): """Initialize Scene with Reader and Compositor objects. - Notice (see parameters list below) )that it is possible to load a + Notice (see parameters list below) that it is possible to load a combinations of files or sets of files each requiring their specific reader. For that the `filenames` needs to be a ``dict``, like e.g.:: From 6bae054b2f8c29949976b5211a80e9aa4fb61927 Mon Sep 17 00:00:00 2001 From: Adam Dybbroe Date: Fri, 25 Mar 2022 14:53:16 +0100 Subject: [PATCH 09/34] Update satpy/scene.py Fix typo Co-authored-by: Gerrit Holl --- satpy/scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satpy/scene.py b/satpy/scene.py index bf84fc47fa..bd9371092c 100644 --- a/satpy/scene.py +++ b/satpy/scene.py @@ -72,7 +72,7 @@ def __init__(self, filenames=None, reader=None, filter_parameters=None, """Initialize Scene with Reader and Compositor objects. Notice (see parameters list below) that it is possible to load a - combinations of files or sets of files each requiring their specific + combination of files or sets of files each requiring their specific reader. For that the `filenames` needs to be a ``dict``, like e.g.:: scn = Scene(filenames={'nwcsaf-pps_nc': glob('/path/to/nwc/saf/pps/files/*'), From fbf7a4541649fd27c7cd72a016fb228f96295d9f Mon Sep 17 00:00:00 2001 From: Adam Dybbroe Date: Fri, 25 Mar 2022 14:53:35 +0100 Subject: [PATCH 10/34] Update satpy/scene.py Fix typo Co-authored-by: Gerrit Holl --- satpy/scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satpy/scene.py b/satpy/scene.py index bd9371092c..9ec4819d5f 100644 --- a/satpy/scene.py +++ b/satpy/scene.py @@ -73,7 +73,7 @@ def __init__(self, filenames=None, reader=None, filter_parameters=None, Notice (see parameters list below) that it is possible to load a combination of files or sets of files each requiring their specific - reader. For that the `filenames` needs to be a ``dict``, like e.g.:: + reader. For that ``filenames`` needs to be a `dict`, like e.g.:: scn = Scene(filenames={'nwcsaf-pps_nc': glob('/path/to/nwc/saf/pps/files/*'), 'modis_l1b': glob('/path/to/modis/lvl1/files/*')}) From 464aa7762b866e067d1a3108a14e58d41c1a3bbd Mon Sep 17 00:00:00 2001 From: Gerrit Holl Date: Wed, 30 Mar 2022 12:24:04 +0200 Subject: [PATCH 11/34] Added test to confirm string IDs are accepted. On the side of NinJo there exists an interest to move from numerical IDs to string IDs for the satellite and channel IDs in the ninjogeotiff headers. The ninjogeotiff writer has no objection to this. Confirm this no-objection with a unit test. --- satpy/tests/writer_tests/test_ninjogeotiff.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/satpy/tests/writer_tests/test_ninjogeotiff.py b/satpy/tests/writer_tests/test_ninjogeotiff.py index a559f68728..a339edaa8b 100644 --- a/satpy/tests/writer_tests/test_ninjogeotiff.py +++ b/satpy/tests/writer_tests/test_ninjogeotiff.py @@ -904,3 +904,17 @@ def test_create_unknown_tags(test_image_small_arctic_P): PhysicValue="N/A", SatelliteNameID=6500014, Locatie="Hozomeen") + + +def test_str_ids(test_image_small_arctic_P): + """Test that channel and satellit IDs can be str.""" + from satpy.writers.ninjogeotiff import NinJoTagGenerator + NinJoTagGenerator( + test_image_small_arctic_P, + 42, + "quorn.tif", + ChannelID="la manche", + DataType="GPRN", + PhysicUnit="N/A", + PhysicValue="N/A", + SatelliteNameID="trollsat") From 4f525fe9d28cd21e9d9443546605b3fbd676134b Mon Sep 17 00:00:00 2001 From: "Adam.Dybbroe" Date: Wed, 30 Mar 2022 16:14:06 +0200 Subject: [PATCH 12/34] Add documentation on how the colorize enhancement can be used Signed-off-by: Adam.Dybbroe --- doc/source/enhancements.rst | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/doc/source/enhancements.rst b/doc/source/enhancements.rst index 142be49531..cff3fa4f70 100644 --- a/doc/source/enhancements.rst +++ b/doc/source/enhancements.rst @@ -93,6 +93,62 @@ lookup colorize -------- +The colorize enhancement can be used to map scaled/calibrated physical values +to colors. One or several `standard Trollimage color maps`_ may be used as in +the example here:: + + - name: colorize + method: !!python/name:satpy.enhancements.colorize + kwargs: + palettes: + - {colors: spectral, min_value: 193.15, max_value: 253.149999} + - {colors: greys, min_value: 253.15, max_value: 303.15} + +It is also possible to provide your own custom defined color mapping by +specifying a list of RGB values and the corresponding min and max values +between which to apply the colors. This is a common use case for SST imagery, +as in this example with the EUMETSAT Ocean and Sea Ice SAF (OSISAF) GHRSST +product:: + + - name: osisaf_sst + method: !!python/name:satpy.enhancements.colorize + kwargs: + palettes: + - colors: [ + [255, 0, 255], + [195, 0, 129], + [129, 0, 47], + [195, 0, 0], + [255, 0, 0], + [236, 43, 0], + [217, 86, 0], + [200, 128, 0], + [211, 154, 13], + [222, 180, 26], + [233, 206, 39], + [244, 232, 52], + [255.99609375, 255.99609375, 63.22265625], + [203.125, 255.99609375, 52.734375], + [136.71875, 255.99609375, 27.34375], + [0, 255.99609375, 0], + [0, 207.47265625, 0], + [0, 158.94921875, 0], + [0, 110.42578125, 0], + [0, 82.8203125, 63.99609375], + [0, 55.21484375, 127.9921875], + [0, 27.609375, 191.98828125], + [0, 0, 255.99609375], + [100.390625, 100.390625, 255.99609375], + [150.5859375, 150.5859375, 255.99609375]] + min_value: 296.55 + max_value: 273.55 + +The RGB color values will be interpolated to give a smooth result. This is +contrary to using the palettize enahncement. + +.. _`standard Trollimage color maps`: https://trollimage.readthedocs.io/en/latest/colormap.html#default-colormaps + + palettize --------- From c89a65666a255a3cf62d47aa2b313967feac8d0b Mon Sep 17 00:00:00 2001 From: Adam Dybbroe Date: Thu, 31 Mar 2022 16:33:06 +0200 Subject: [PATCH 13/34] Update doc/source/enhancements.rst Fix typo Co-authored-by: Gerrit Holl --- doc/source/enhancements.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/enhancements.rst b/doc/source/enhancements.rst index cff3fa4f70..4f6c188de8 100644 --- a/doc/source/enhancements.rst +++ b/doc/source/enhancements.rst @@ -144,7 +144,7 @@ product:: max_value: 273.55 The RGB color values will be interpolated to give a smooth result. This is -contrary to using the palettize enahncement. +contrary to using the palettize enhancement. .. _`standard Trollimage color maps`: https://trollimage.readthedocs.io/en/latest/colormap.html#default-colormaps From 0b435ad8655c4daf598fbff11e9cd0a2b4d0b2e9 Mon Sep 17 00:00:00 2001 From: "Adam.Dybbroe" Date: Fri, 1 Apr 2022 10:37:26 +0200 Subject: [PATCH 14/34] Improve documentation pointing to the create_colormap function Signed-off-by: Adam.Dybbroe --- doc/source/enhancements.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/source/enhancements.rst b/doc/source/enhancements.rst index 4f6c188de8..1ab6a00e5e 100644 --- a/doc/source/enhancements.rst +++ b/doc/source/enhancements.rst @@ -93,6 +93,7 @@ lookup colorize -------- + The colorize enhancement can be used to map scaled/calibrated physical values to colors. One or several `standard Trollimage color maps`_ may be used as in the example here:: @@ -106,9 +107,9 @@ the example here:: It is also possible to provide your own custom defined color mapping by specifying a list of RGB values and the corresponding min and max values -between which to apply the colors. This is a common use case for SST imagery, -as in this example with the EUMETSAT Ocean and Sea Ice SAF (OSISAF) GHRSST -product:: +between which to apply the colors. This is for instance a common use case for +Sea Surface Temperature (SST) imagery, as in this example with the EUMETSAT +Ocean and Sea Ice SAF (OSISAF) GHRSST product:: - name: osisaf_sst method: !!python/name:satpy.enhancements.colorize @@ -146,6 +147,10 @@ product:: The RGB color values will be interpolated to give a smooth result. This is contrary to using the palettize enhancement. +The above examples are just two different ways to apply colors to images with +Satoy. There is a wealth of other options for how to declare a colormap, please +see :func:`~satpy.enhancements.create_colormap` for more inspiration. + .. _`standard Trollimage color maps`: https://trollimage.readthedocs.io/en/latest/colormap.html#default-colormaps From 047b8c6a6b6d4ec7e129b9235be1307216e21427 Mon Sep 17 00:00:00 2001 From: "Adam.Dybbroe" Date: Fri, 1 Apr 2022 10:57:08 +0200 Subject: [PATCH 15/34] Rearrange examples so the most common comes first, and the special case comes last Signed-off-by: Adam.Dybbroe --- satpy/scene.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/satpy/scene.py b/satpy/scene.py index 9ec4819d5f..cbea8919c0 100644 --- a/satpy/scene.py +++ b/satpy/scene.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2010-2017, 2022 Satpy developers +# Copyright (c) 2010-2022 Satpy developers # # This file is part of satpy. # @@ -71,21 +71,30 @@ def __init__(self, filenames=None, reader=None, filter_parameters=None, reader_kwargs=None): """Initialize Scene with Reader and Compositor objects. - Notice (see parameters list below) that it is possible to load a - combination of files or sets of files each requiring their specific - reader. For that ``filenames`` needs to be a `dict`, like e.g.:: + To load data `filenames` and preferably `reader` must be specified:: - scn = Scene(filenames={'nwcsaf-pps_nc': glob('/path/to/nwc/saf/pps/files/*'), - 'modis_l1b': glob('/path/to/modis/lvl1/files/*')}) + scn = Scene(filenames=glob('/path/to/viirs/sdr/files/*'), reader='viirs_sdr') - To load data `filenames` and preferably `reader` must be specified. If `filenames` is provided without `reader` - then the available readers will be searched for a Reader that can support the provided files. This can take - a considerable amount of time so it is recommended that `reader` always be provided. Note without `filenames` - the Scene is created with no Readers available requiring Datasets to be added manually:: + + If ``filenames`` is provided without ``reader`` then the available readers + will be searched for a Reader that can support the provided files. This + can take a considerable amount of time so it is recommended that + ``reader`` always be provided. Note without ``filenames`` the Scene is + created with no Readers available requiring Datasets to be added + manually:: scn = Scene() scn['my_dataset'] = Dataset(my_data_array, **my_info) + Further, notice that it is also possible to load a combination of files + or sets of files each requiring their specific reader. For that + ``filenames`` needs to be a `dict` (see parameters list below), like + e.g.:: + + scn = Scene(filenames={'nwcsaf-pps_nc': glob('/path/to/nwc/saf/pps/files/*'), + 'modis_l1b': glob('/path/to/modis/lvl1/files/*')}) + + Args: filenames (iterable or dict): A sequence of files that will be used to load data from. A ``dict`` object should map reader names to a list of filenames for that reader. From d4284c6a503578b219e0e0b8cf921352eb6175ca Mon Sep 17 00:00:00 2001 From: Johan Strandgren Date: Fri, 1 Apr 2022 10:50:37 +0000 Subject: [PATCH 16/34] Add possibility to define the dataset rectification longitude when initializing the Scene object for the seviri_l2_bufr reader. --- satpy/readers/seviri_l2_bufr.py | 20 +++++++++++-- .../tests/reader_tests/test_seviri_l2_bufr.py | 29 +++++++++++++++++-- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/satpy/readers/seviri_l2_bufr.py b/satpy/readers/seviri_l2_bufr.py index d061a22f02..871ae682ee 100644 --- a/satpy/readers/seviri_l2_bufr.py +++ b/satpy/readers/seviri_l2_bufr.py @@ -45,7 +45,7 @@ logger = logging.getLogger('SeviriL2Bufr') -data_center_dict = {55: {'ssp': 'E0415', 'name': '08'}, 56: {'ssp': 'E0000', 'name': '09'}, +data_center_dict = {55: {'ssp': 'E0415', 'name': '08'}, 56: {'ssp': 'E0455', 'name': '09'}, 57: {'ssp': 'E0095', 'name': '10'}, 70: {'ssp': 'E0000', 'name': '11'}} seg_size_dict = {'seviri_l2_bufr_asr': 16, 'seviri_l2_bufr_cla': 16, @@ -66,9 +66,22 @@ class SeviriL2BufrFileHandler(BaseFileHandler): reader='seviri_l2_bufr', reader_kwargs={'with_area_definition': False}) + **Defining dataset recticifation longitude** + + The BUFR data were originally extracted from a rectified two-dimensinal grid with a given central longitude + (typically the sub-satellite point). This information is not available in the file itself nor the filename (for + files from the EUMETSAT archive). Also, it cannot be realiably derived from all datasets themselves. Hence, the + rectification longitude can be defined by the user by providing `rectification_longitude` in the `reader_kwargs`: + + scene = satpy.Scene(filenames, + reader='seviri_l2_bufr', + reader_kwargs={'rectification_longitude': 0.0}) + + If not done, default values applicable to the operational grids of the respective SEVIRI instruments will be used. """ - def __init__(self, filename, filename_info, filetype_info, with_area_definition=False, **kwargs): + def __init__(self, filename, filename_info, filetype_info, with_area_definition=False, + rectification_longitude='default', **kwargs): """Initialise the file handler for SEVIRI L2 BUFR data.""" super(SeviriL2BufrFileHandler, self).__init__(filename, filename_info, @@ -87,6 +100,9 @@ def __init__(self, filename, filename_info, filetype_info, with_area_definition= self.mpef_header['SpacecraftName'] = data_center_dict[sc_id]['name'] self.mpef_header['RectificationLongitude'] = data_center_dict[sc_id]['ssp'] + if rectification_longitude != 'default': + self.mpef_header['RectificationLongitude'] = f'E{int(rectification_longitude * 10):04d}' + self.with_adef = with_area_definition self.seg_size = seg_size_dict[filetype_info['file_type']] diff --git a/satpy/tests/reader_tests/test_seviri_l2_bufr.py b/satpy/tests/reader_tests/test_seviri_l2_bufr.py index 83679fe9a8..20fab5fa6b 100644 --- a/satpy/tests/reader_tests/test_seviri_l2_bufr.py +++ b/satpy/tests/reader_tests/test_seviri_l2_bufr.py @@ -79,6 +79,17 @@ (-5570248.6866, -5567248.2834, 5567248.2834, 5570248.6866) ) +AREA_DEF_FES = geometry.AreaDefinition( + 'msg_seviri_res_48km', + 'MSG SEVIRI Full Earth Scanning service area definition with 48 km resolution', + "", + {'a': 6378169., 'b': 6356583.8, 'lon_0': 0.0, + 'h': 35785831., 'proj': 'geos', 'units': 'm'}, + 232, + 232, + (-5570248.6866, -5567248.2834, 5567248.2834, 5570248.6866) +) + AREA_DEF_EXT = geometry.AreaDefinition( 'msg_seviri_iodc_9km_ext', 'MSG SEVIRI Indian Ocean Data Coverage service area definition with 9 km resolution ' @@ -107,7 +118,7 @@ class SeviriL2BufrData: """Mock SEVIRI L2 BUFR data.""" @unittest.skipIf(sys.platform.startswith('win'), "'eccodes' not supported on Windows") - def __init__(self, filename, with_adef=False): + def __init__(self, filename, with_adef=False, rect_lon='default'): """Initialize by mocking test data for testing the SEVIRI L2 BUFR reader.""" import eccodes as ec @@ -132,7 +143,7 @@ def __init__(self, filename, with_adef=False): with mock.patch('satpy.readers.seviri_l2_bufr.recarray2dict') as recarray2dict: recarray2dict.side_effect = (lambda x: x) self.fh = SeviriL2BufrFileHandler(filename, FILENAME_INFO2, FILETYPE_INFO, - with_area_definition=with_adef) + with_area_definition=with_adef, rectification_longitude=rect_lon) self.fh.mpef_header = MPEF_PRODUCT_HEADER else: @@ -146,7 +157,8 @@ def __init__(self, filename, with_adef=False): with mock.patch('eccodes.codes_release') as ec5: ec5.return_value = 1 self.fh = SeviriL2BufrFileHandler(filename, FILENAME_INFO, FILETYPE_INFO, - with_area_definition=with_adef) + with_area_definition=with_adef, + rectification_longitude=rect_lon) def get_data(self, dataset_info): """Read data from mock file.""" @@ -232,3 +244,14 @@ def test_data_with_area_definition(self, input_file): bufr_obj.fh.seg_size = 3 ad_ext = bufr_obj.fh._construct_area_def(make_dataid(name='dummmy', resolution=9000)) assert ad_ext == AREA_DEF_EXT + + def test_data_with_rect_lon(self, input_file): + """Test data loaded with AreaDefinition and user defined rectification longitude.""" + bufr_obj = SeviriL2BufrData(input_file, with_adef=True, rect_lon=0.0) + np.testing.assert_equal(bufr_obj.fh.ssp_lon, 0.0) + _ = bufr_obj.get_data(DATASET_INFO_LAT) # We need to load the lat/lon data in order to + _ = bufr_obj.get_data(DATASET_INFO_LON) # populate the file handler with these data + _ = bufr_obj.get_data(DATASET_INFO) # We need to lead the data in order to create the AreaDefinition + + ad = bufr_obj.fh.get_area_def(None) + assert ad == AREA_DEF_FES From 0688f08e306e91d0cdab3a4326b44a6cd848ef7e Mon Sep 17 00:00:00 2001 From: Johan Strandgren Date: Fri, 1 Apr 2022 10:53:52 +0000 Subject: [PATCH 17/34] Fix typo. --- satpy/readers/seviri_l2_bufr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satpy/readers/seviri_l2_bufr.py b/satpy/readers/seviri_l2_bufr.py index 871ae682ee..2bf3b1f07a 100644 --- a/satpy/readers/seviri_l2_bufr.py +++ b/satpy/readers/seviri_l2_bufr.py @@ -68,7 +68,7 @@ class SeviriL2BufrFileHandler(BaseFileHandler): **Defining dataset recticifation longitude** - The BUFR data were originally extracted from a rectified two-dimensinal grid with a given central longitude + The BUFR data were originally extracted from a rectified two-dimensional grid with a given central longitude (typically the sub-satellite point). This information is not available in the file itself nor the filename (for files from the EUMETSAT archive). Also, it cannot be realiably derived from all datasets themselves. Hence, the rectification longitude can be defined by the user by providing `rectification_longitude` in the `reader_kwargs`: From dfdfc72b4bca7a281c7f85b9fd8e57a3c369ab5f Mon Sep 17 00:00:00 2001 From: Johan Strandgren Date: Fri, 1 Apr 2022 10:55:44 +0000 Subject: [PATCH 18/34] Fix typo. --- satpy/readers/seviri_l2_bufr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satpy/readers/seviri_l2_bufr.py b/satpy/readers/seviri_l2_bufr.py index 2bf3b1f07a..8e283e1d23 100644 --- a/satpy/readers/seviri_l2_bufr.py +++ b/satpy/readers/seviri_l2_bufr.py @@ -70,7 +70,7 @@ class SeviriL2BufrFileHandler(BaseFileHandler): The BUFR data were originally extracted from a rectified two-dimensional grid with a given central longitude (typically the sub-satellite point). This information is not available in the file itself nor the filename (for - files from the EUMETSAT archive). Also, it cannot be realiably derived from all datasets themselves. Hence, the + files from the EUMETSAT archive). Also, it cannot be reliably derived from all datasets themselves. Hence, the rectification longitude can be defined by the user by providing `rectification_longitude` in the `reader_kwargs`: scene = satpy.Scene(filenames, From b1e4240d4464c443f916fcc19a6d32a8c67fa314 Mon Sep 17 00:00:00 2001 From: "Adam.Dybbroe" Date: Sat, 2 Apr 2022 12:48:18 +0200 Subject: [PATCH 19/34] Fix formulation Signed-off-by: Adam.Dybbroe --- satpy/scene.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/satpy/scene.py b/satpy/scene.py index cbea8919c0..6b1f735f0c 100644 --- a/satpy/scene.py +++ b/satpy/scene.py @@ -88,8 +88,7 @@ def __init__(self, filenames=None, reader=None, filter_parameters=None, Further, notice that it is also possible to load a combination of files or sets of files each requiring their specific reader. For that - ``filenames`` needs to be a `dict` (see parameters list below), like - e.g.:: + ``filenames`` needs to be a `dict` (see parameters list below), e.g.:: scn = Scene(filenames={'nwcsaf-pps_nc': glob('/path/to/nwc/saf/pps/files/*'), 'modis_l1b': glob('/path/to/modis/lvl1/files/*')}) From e3eb5be59369f8f64edb724f3dfd2804d5039ac8 Mon Sep 17 00:00:00 2001 From: Adam Dybbroe Date: Sat, 2 Apr 2022 12:57:51 +0200 Subject: [PATCH 20/34] Update doc/source/enhancements.rst Fix typo Co-authored-by: David Hoese --- doc/source/enhancements.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/enhancements.rst b/doc/source/enhancements.rst index 1ab6a00e5e..635f3919b6 100644 --- a/doc/source/enhancements.rst +++ b/doc/source/enhancements.rst @@ -148,7 +148,7 @@ The RGB color values will be interpolated to give a smooth result. This is contrary to using the palettize enhancement. The above examples are just two different ways to apply colors to images with -Satoy. There is a wealth of other options for how to declare a colormap, please +Satpy. There is a wealth of other options for how to declare a colormap, please see :func:`~satpy.enhancements.create_colormap` for more inspiration. .. _`standard Trollimage color maps`: https://trollimage.readthedocs.io/en/latest/colormap.html#default-colormaps From a1f796e04af592e411550eeec653f0c4660f3d95 Mon Sep 17 00:00:00 2001 From: David Hoese Date: Sat, 2 Apr 2022 21:19:37 -0500 Subject: [PATCH 21/34] Remove marine_clean_aerosol from default AHI rayleigh_corrected modifier --- satpy/etc/composites/ahi.yaml | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/satpy/etc/composites/ahi.yaml b/satpy/etc/composites/ahi.yaml index ef75c4c972..7ab2e2820c 100644 --- a/satpy/etc/composites/ahi.yaml +++ b/satpy/etc/composites/ahi.yaml @@ -2,24 +2,11 @@ sensor_name: visir/ahi modifiers: rayleigh_corrected: - modifier: !!python/name:satpy.modifiers.PSPRayleighReflectance - atmosphere: us-standard - aerosol_type: marine_clean_aerosol - prerequisites: - - wavelength: 0.64 - modifiers: [sunz_corrected] - optional_prerequisites: - - satellite_azimuth_angle - - satellite_zenith_angle - - solar_azimuth_angle - - solar_zenith_angle - - no_aerosol_rayleigh_corrected: modifier: !!python/name:satpy.modifiers.PSPRayleighReflectance atmosphere: us-standard aerosol_type: rayleigh_only prerequisites: - - name: B03 + - wavelength: B03 modifiers: [sunz_corrected] optional_prerequisites: - satellite_azimuth_angle @@ -48,9 +35,9 @@ composites: fractions: [0.6321, 0.2928, 0.0751] prerequisites: - name: B02 - modifiers: [sunz_corrected, no_aerosol_rayleigh_corrected] + modifiers: [sunz_corrected, rayleigh_corrected] - name: B03 - modifiers: [sunz_corrected, no_aerosol_rayleigh_corrected] + modifiers: [sunz_corrected, rayleigh_corrected] - name: B04 modifiers: [sunz_corrected] standard_name: none @@ -247,10 +234,10 @@ composites: compositor: !!python/name:satpy.composites.SelfSharpenedRGB prerequisites: - name: B03 - modifiers: [sunz_corrected, no_aerosol_rayleigh_corrected] + modifiers: [sunz_corrected, rayleigh_corrected] - name: green_true_color_reproduction - name: B01 - modifiers: [sunz_corrected, no_aerosol_rayleigh_corrected] + modifiers: [sunz_corrected, rayleigh_corrected] standard_name: true_color_reproduction # true_color_reducedsize_land: From afd52240b9f406e23bc895b6244afd7c14b5f3e8 Mon Sep 17 00:00:00 2001 From: mherbertson <57386258+mherbertson@users.noreply.github.com> Date: Mon, 4 Apr 2022 05:45:27 +0000 Subject: [PATCH 22/34] Ignore alpha when adding luminance --- satpy/composites/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/satpy/composites/__init__.py b/satpy/composites/__init__.py index 90f4e67aac..f0b4b1922a 100644 --- a/satpy/composites/__init__.py +++ b/satpy/composites/__init__.py @@ -1101,7 +1101,8 @@ def __call__(self, projectables, *args, **kwargs): # Get the enhanced version of the RGB composite to be sharpened rgb_img = enhance2dataset(projectables[1]) - rgb_img *= luminance + # Ignore alpha band when applying luminance + rgb_img = rgb_img.where(rgb_img.bands == 'A', rgb_img * luminance) return super(SandwichCompositor, self).__call__(rgb_img, *args, **kwargs) From 8e34bcb026d9788b2dcbc31e8316a57aa42a3732 Mon Sep 17 00:00:00 2001 From: Martin Raspaud Date: Mon, 4 Apr 2022 15:38:49 +0200 Subject: [PATCH 23/34] Refactor olci nc reader to remove duplicate code --- satpy/readers/olci_nc.py | 132 ++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 79 deletions(-) diff --git a/satpy/readers/olci_nc.py b/satpy/readers/olci_nc.py index aa6eb395a2..789f3580ec 100644 --- a/satpy/readers/olci_nc.py +++ b/satpy/readers/olci_nc.py @@ -97,6 +97,9 @@ def __getitem__(self, item): class NCOLCIBase(BaseFileHandler): """The OLCI reader base.""" + rows_name = "rows" + cols_name = "columns" + def __init__(self, filename, filename_info, filetype_info, engine=None): """Init the olci reader base.""" @@ -118,9 +121,9 @@ def nc(self): decode_cf=True, mask_and_scale=True, engine=self._engine, - chunks={'columns': CHUNK_SIZE, - 'rows': CHUNK_SIZE}) - return dataset.rename({'columns': 'x', 'rows': 'y'}) + chunks={self.cols_name: CHUNK_SIZE, + self.rows_name: CHUNK_SIZE}) + return dataset.rename({self.cols_name: 'x', self.rows_name: 'y'}) @property def start_time(self): @@ -141,7 +144,7 @@ def get_dataset(self, key, info): def __del__(self): """Close the NetCDF file that may still be open.""" - with suppress(IOError, OSError, AttributeError): + with suppress(IOError, OSError, AttributeError, TypeError): self.nc.close() @@ -156,12 +159,9 @@ class NCOLCIGeo(NCOLCIBase): class NCOLCIChannelBase(NCOLCIBase): """Base class for channel reading.""" - def __init__(self, filename, filename_info, filetype_info, - engine=None): + def __init__(self, filename, filename_info, filetype_info, engine=None): """Init the file handler.""" - super(NCOLCIChannelBase, self).__init__(filename, filename_info, - filetype_info) - + super().__init__(filename, filename_info, filetype_info) self.channel = filename_info.get('dataset_name') @@ -171,8 +171,7 @@ class NCOLCI1B(NCOLCIChannelBase): def __init__(self, filename, filename_info, filetype_info, cal, engine=None): """Init the file handler.""" - super(NCOLCI1B, self).__init__(filename, filename_info, - filetype_info) + super().__init__(filename, filename_info, filetype_info) self.cal = cal.nc @staticmethod @@ -241,33 +240,18 @@ def getbitmask(self, wqsf, items=None): return reduce(np.logical_or, [bflags[item] for item in items]) -class NCOLCILowResData(BaseFileHandler): +class NCOLCILowResData(NCOLCIBase): """Handler for low resolution data.""" + rows_name = "tie_rows" + cols_name = "tie_columns" + def __init__(self, filename, filename_info, filetype_info, engine=None): """Init the file handler.""" - super(NCOLCILowResData, self).__init__(filename, filename_info, filetype_info) - self.nc = None - # TODO: get metadata from the manifest file (xfdumanifest.xml) - self.platform_name = PLATFORM_NAMES[filename_info['mission_id']] - self.sensor = 'olci' - self.cache = {} - self.engine = engine - - def _open_dataset(self): - if self.nc is None: - self.nc = xr.open_dataset(self.filename, - decode_cf=True, - mask_and_scale=True, - engine=self.engine, - chunks={'tie_columns': CHUNK_SIZE, - 'tie_rows': CHUNK_SIZE}) - - self.nc = self.nc.rename({'tie_columns': 'x', 'tie_rows': 'y'}) - - self.l_step = self.nc.attrs['al_subsampling_factor'] - self.c_step = self.nc.attrs['ac_subsampling_factor'] + super().__init__(filename, filename_info, filetype_info, engine) + self.l_step = self.nc.attrs['al_subsampling_factor'] + self.c_step = self.nc.attrs['ac_subsampling_factor'] def _do_interpolate(self, data): @@ -293,16 +277,10 @@ def _do_interpolate(self, data): return [xr.DataArray(da.from_array(x, chunks=(CHUNK_SIZE, CHUNK_SIZE)), dims=['y', 'x']) for x in int_data] + @cached_property def _need_interpolation(self): return (self.c_step != 1 or self.l_step != 1) - def __del__(self): - """Close the NetCDF file that may still be open.""" - try: - self.nc.close() - except (OSError, AttributeError): - pass - class NCOLCIAngles(NCOLCILowResData): """File handler for the OLCI angles.""" @@ -317,49 +295,22 @@ def get_dataset(self, key, info): if key['name'] not in self.datasets: return - self._open_dataset() - logger.debug('Reading %s.', key['name']) - if self._need_interpolation() and self.cache.get(key['name']) is None: - + if self._need_interpolation: if key['name'].startswith('satellite'): - zen = self.nc[self.datasets['satellite_zenith_angle']] - zattrs = zen.attrs - azi = self.nc[self.datasets['satellite_azimuth_angle']] - aattrs = azi.attrs + azi, zen = self.satellite_angles elif key['name'].startswith('solar'): - zen = self.nc[self.datasets['solar_zenith_angle']] - zattrs = zen.attrs - azi = self.nc[self.datasets['solar_azimuth_angle']] - aattrs = azi.attrs + azi, zen = self.sun_angles else: raise NotImplementedError("Don't know how to read " + key['name']) - x, y, z = angle2xyz(azi, zen) - - x, y, z = self._do_interpolate((x, y, z)) - - azi, zen = xyz2angle(x, y, z) - azi.attrs = aattrs - zen.attrs = zattrs - if 'zenith' in key['name']: values = zen elif 'azimuth' in key['name']: values = azi else: raise NotImplementedError("Don't know how to read " + key['name']) - - if key['name'].startswith('satellite'): - self.cache['satellite_zenith_angle'] = zen - self.cache['satellite_azimuth_angle'] = azi - elif key['name'].startswith('solar'): - self.cache['solar_zenith_angle'] = zen - self.cache['solar_azimuth_angle'] = azi - - elif key['name'] in self.cache: - values = self.cache[key['name']] else: values = self.nc[self.datasets[key['name']]] @@ -369,12 +320,31 @@ def get_dataset(self, key, info): values.attrs.update(key.to_dict()) return values - def __del__(self): - """Close the NetCDF file that may still be open.""" - try: - self.nc.close() - except (OSError, AttributeError): - pass + @cached_property + def sun_angles(self): + """Return the sun angles.""" + zen = self.nc[self.datasets['solar_zenith_angle']] + azi = self.nc[self.datasets['solar_azimuth_angle']] + azi, zen = self._interpolate_angles(azi, zen) + return azi, zen + + @cached_property + def satellite_angles(self): + """Return the satellite angles.""" + zen = self.nc[self.datasets['satellite_zenith_angle']] + azi = self.nc[self.datasets['satellite_azimuth_angle']] + azi, zen = self._interpolate_angles(azi, zen) + return azi, zen + + def _interpolate_angles(self, azi, zen): + aattrs = azi.attrs + zattrs = zen.attrs + x, y, z = angle2xyz(azi, zen) + x, y, z = self._do_interpolate((x, y, z)) + azi, zen = xyz2angle(x, y, z) + azi.attrs = aattrs + zen.attrs = zattrs + return azi, zen class NCOLCIMeteo(NCOLCILowResData): @@ -382,6 +352,12 @@ class NCOLCIMeteo(NCOLCILowResData): datasets = ['humidity', 'sea_level_pressure', 'total_columnar_water_vapour', 'total_ozone'] + def __init__(self, filename, filename_info, filetype_info, + engine=None): + """Init the file handler.""" + super().__init__(filename, filename_info, filetype_info, engine) + self.cache = {} + # TODO: the following depends on more than columns, rows # float atmospheric_temperature_profile(tie_rows, tie_columns, tie_pressure_levels) ; # float horizontal_wind(tie_rows, tie_columns, wind_vectors) ; @@ -392,11 +368,9 @@ def get_dataset(self, key, info): if key['name'] not in self.datasets: return - self._open_dataset() - logger.debug('Reading %s.', key['name']) - if self._need_interpolation() and self.cache.get(key['name']) is None: + if self._need_interpolation and self.cache.get(key['name']) is None: data = self.nc[key['name']] From ae73b9d422d7af98beffdffc2402bce9efb437e0 Mon Sep 17 00:00:00 2001 From: Martin Raspaud Date: Mon, 4 Apr 2022 15:44:04 +0200 Subject: [PATCH 24/34] Further cleaning --- satpy/readers/olci_nc.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/satpy/readers/olci_nc.py b/satpy/readers/olci_nc.py index 789f3580ec..6f67890bec 100644 --- a/satpy/readers/olci_nc.py +++ b/satpy/readers/olci_nc.py @@ -103,8 +103,7 @@ class NCOLCIBase(BaseFileHandler): def __init__(self, filename, filename_info, filetype_info, engine=None): """Init the olci reader base.""" - super(NCOLCIBase, self).__init__(filename, filename_info, - filetype_info) + super().__init__(filename, filename_info, filetype_info) self._engine = engine self._start_time = filename_info['start_time'] self._end_time = filename_info['end_time'] @@ -161,7 +160,7 @@ class NCOLCIChannelBase(NCOLCIBase): def __init__(self, filename, filename_info, filetype_info, engine=None): """Init the file handler.""" - super().__init__(filename, filename_info, filetype_info) + super().__init__(filename, filename_info, filetype_info, engine) self.channel = filename_info.get('dataset_name') @@ -171,7 +170,7 @@ class NCOLCI1B(NCOLCIChannelBase): def __init__(self, filename, filename_info, filetype_info, cal, engine=None): """Init the file handler.""" - super().__init__(filename, filename_info, filetype_info) + super().__init__(filename, filename_info, filetype_info, engine) self.cal = cal.nc @staticmethod From cd1d8638cfa90df821340e765a21e3f61991b6f1 Mon Sep 17 00:00:00 2001 From: Martin Raspaud Date: Mon, 4 Apr 2022 16:21:03 +0200 Subject: [PATCH 25/34] Replace cached property with regular property Co-authored-by: David Hoese --- satpy/readers/olci_nc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satpy/readers/olci_nc.py b/satpy/readers/olci_nc.py index 6f67890bec..982e774f77 100644 --- a/satpy/readers/olci_nc.py +++ b/satpy/readers/olci_nc.py @@ -276,7 +276,7 @@ def _do_interpolate(self, data): return [xr.DataArray(da.from_array(x, chunks=(CHUNK_SIZE, CHUNK_SIZE)), dims=['y', 'x']) for x in int_data] - @cached_property + @property def _need_interpolation(self): return (self.c_step != 1 or self.l_step != 1) From 99d2e31c8e702116398c2aecb7104011d242fe73 Mon Sep 17 00:00:00 2001 From: mherbertson <57386258+mherbertson@users.noreply.github.com> Date: Tue, 5 Apr 2022 04:51:31 +0000 Subject: [PATCH 26/34] Update test to compare RGBA and A remains intact --- satpy/tests/test_composites.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/satpy/tests/test_composites.py b/satpy/tests/test_composites.py index 77463872b2..d95b37b449 100644 --- a/satpy/tests/test_composites.py +++ b/satpy/tests/test_composites.py @@ -456,20 +456,26 @@ def test_compositor(self, e2d): """Test luminance sharpening compositor.""" from satpy.composites import SandwichCompositor - rgb_arr = da.from_array(np.random.random((3, 2, 2)), chunks=2) - rgb = xr.DataArray(rgb_arr, dims=['bands', 'y', 'x']) + # Test RGBA + bands = ['R', 'G', 'B', 'A'] + rgba_arr = da.from_array(np.random.random((4, 2, 2)), chunks=2) + rgba = xr.DataArray(rgba_arr, dims=['bands', 'y', 'x'], coords={'bands': bands}) lum_arr = da.from_array(100 * np.random.random((2, 2)), chunks=2) lum = xr.DataArray(lum_arr, dims=['y', 'x']) # Make enhance2dataset return unmodified dataset - e2d.return_value = rgb + e2d.return_value = rgba comp = SandwichCompositor(name='test') - res = comp([lum, rgb]) + res = comp([lum, rgba]) - for i in range(3): - np.testing.assert_allclose(res.data[i, :, :], - rgb_arr[i, :, :] * lum_arr / 100.) + for band in rgba: + if band.bands != 'A': + # Check compositor has modified this band + np.testing.assert_allclose(res.loc[band.bands].to_numpy(), band.to_numpy() * lum_arr / 100.) + else: + # Check Alpha band remains intact + np.testing.assert_allclose(res.loc[band.bands].to_numpy(), band.to_numpy()) # make sure the compositor doesn't modify the input data np.testing.assert_allclose(lum.values, lum_arr.compute()) From fe08bec093cdabcfbbaeb320e4ec0938ddcdbfa4 Mon Sep 17 00:00:00 2001 From: mherbertson <57386258+mherbertson@users.noreply.github.com> Date: Tue, 5 Apr 2022 05:03:20 +0000 Subject: [PATCH 27/34] Updated readability --- satpy/tests/test_composites.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/satpy/tests/test_composites.py b/satpy/tests/test_composites.py index d95b37b449..a84282e53b 100644 --- a/satpy/tests/test_composites.py +++ b/satpy/tests/test_composites.py @@ -459,7 +459,8 @@ def test_compositor(self, e2d): # Test RGBA bands = ['R', 'G', 'B', 'A'] rgba_arr = da.from_array(np.random.random((4, 2, 2)), chunks=2) - rgba = xr.DataArray(rgba_arr, dims=['bands', 'y', 'x'], coords={'bands': bands}) + rgba = xr.DataArray(rgba_arr, dims=['bands', 'y', 'x'], + coords={'bands': bands}) lum_arr = da.from_array(100 * np.random.random((2, 2)), chunks=2) lum = xr.DataArray(lum_arr, dims=['y', 'x']) @@ -472,10 +473,12 @@ def test_compositor(self, e2d): for band in rgba: if band.bands != 'A': # Check compositor has modified this band - np.testing.assert_allclose(res.loc[band.bands].to_numpy(), band.to_numpy() * lum_arr / 100.) + np.testing.assert_allclose(res.loc[band.bands].to_numpy(), + band.to_numpy() * lum_arr / 100.) else: # Check Alpha band remains intact - np.testing.assert_allclose(res.loc[band.bands].to_numpy(), band.to_numpy()) + np.testing.assert_allclose(res.loc[band.bands].to_numpy(), + band.to_numpy()) # make sure the compositor doesn't modify the input data np.testing.assert_allclose(lum.values, lum_arr.compute()) From 2f410170148bf277818ae2c1bcc5fe529a8deb9c Mon Sep 17 00:00:00 2001 From: mherbertson <57386258+mherbertson@users.noreply.github.com> Date: Tue, 5 Apr 2022 05:07:40 +0000 Subject: [PATCH 28/34] Remove trailing whitespace --- satpy/tests/test_composites.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/satpy/tests/test_composites.py b/satpy/tests/test_composites.py index a84282e53b..095b5db0a4 100644 --- a/satpy/tests/test_composites.py +++ b/satpy/tests/test_composites.py @@ -459,7 +459,7 @@ def test_compositor(self, e2d): # Test RGBA bands = ['R', 'G', 'B', 'A'] rgba_arr = da.from_array(np.random.random((4, 2, 2)), chunks=2) - rgba = xr.DataArray(rgba_arr, dims=['bands', 'y', 'x'], + rgba = xr.DataArray(rgba_arr, dims=['bands', 'y', 'x'], coords={'bands': bands}) lum_arr = da.from_array(100 * np.random.random((2, 2)), chunks=2) lum = xr.DataArray(lum_arr, dims=['y', 'x']) @@ -473,11 +473,11 @@ def test_compositor(self, e2d): for band in rgba: if band.bands != 'A': # Check compositor has modified this band - np.testing.assert_allclose(res.loc[band.bands].to_numpy(), + np.testing.assert_allclose(res.loc[band.bands].to_numpy(), band.to_numpy() * lum_arr / 100.) else: # Check Alpha band remains intact - np.testing.assert_allclose(res.loc[band.bands].to_numpy(), + np.testing.assert_allclose(res.loc[band.bands].to_numpy(), band.to_numpy()) # make sure the compositor doesn't modify the input data np.testing.assert_allclose(lum.values, lum_arr.compute()) From d88ff92a19bdbeca7d00043569e9205da1d7b226 Mon Sep 17 00:00:00 2001 From: mherbertson <57386258+mherbertson@users.noreply.github.com> Date: Tue, 5 Apr 2022 05:14:53 +0000 Subject: [PATCH 29/34] Remove over indentation --- satpy/tests/test_composites.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/satpy/tests/test_composites.py b/satpy/tests/test_composites.py index 095b5db0a4..eb1690bb46 100644 --- a/satpy/tests/test_composites.py +++ b/satpy/tests/test_composites.py @@ -474,11 +474,11 @@ def test_compositor(self, e2d): if band.bands != 'A': # Check compositor has modified this band np.testing.assert_allclose(res.loc[band.bands].to_numpy(), - band.to_numpy() * lum_arr / 100.) + band.to_numpy() * lum_arr / 100.) else: # Check Alpha band remains intact np.testing.assert_allclose(res.loc[band.bands].to_numpy(), - band.to_numpy()) + band.to_numpy()) # make sure the compositor doesn't modify the input data np.testing.assert_allclose(lum.values, lum_arr.compute()) From eaeb19cd1b3e53d30465e45540c50f146bbf8f40 Mon Sep 17 00:00:00 2001 From: David Hoese Date: Tue, 5 Apr 2022 06:51:01 -0500 Subject: [PATCH 30/34] Fix typo in satpy/etc/composites/ahi.yaml Co-authored-by: Simon Proud --- satpy/etc/composites/ahi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satpy/etc/composites/ahi.yaml b/satpy/etc/composites/ahi.yaml index 7ab2e2820c..f29a36357f 100644 --- a/satpy/etc/composites/ahi.yaml +++ b/satpy/etc/composites/ahi.yaml @@ -6,7 +6,7 @@ modifiers: atmosphere: us-standard aerosol_type: rayleigh_only prerequisites: - - wavelength: B03 + - name: B03 modifiers: [sunz_corrected] optional_prerequisites: - satellite_azimuth_angle From 66b960fd61f8660dd1af8b115dcd5443ad1562a2 Mon Sep 17 00:00:00 2001 From: David Hoese Date: Tue, 5 Apr 2022 14:42:37 -0500 Subject: [PATCH 31/34] Force panel version in CI --- continuous_integration/environment.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/continuous_integration/environment.yaml b/continuous_integration/environment.yaml index 905cfa2f37..d131bad1e0 100644 --- a/continuous_integration/environment.yaml +++ b/continuous_integration/environment.yaml @@ -11,6 +11,7 @@ dependencies: - Cython - sphinx - cartopy + - panel>=0.12.7 - pillow - matplotlib - scipy From 452b2c5a352e37c6a2a3830e9a2504db8055decb Mon Sep 17 00:00:00 2001 From: mherbertson <57386258+mherbertson@users.noreply.github.com> Date: Wed, 6 Apr 2022 00:59:15 +0000 Subject: [PATCH 32/34] Refactor test to include both RGB and RGBA --- satpy/tests/test_composites.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/satpy/tests/test_composites.py b/satpy/tests/test_composites.py index eb1690bb46..01697114f1 100644 --- a/satpy/tests/test_composites.py +++ b/satpy/tests/test_composites.py @@ -448,29 +448,31 @@ def test_compositor(self): np.testing.assert_allclose(res.data, 0.0, atol=1e-9) -class TestSandwichCompositor(unittest.TestCase): +class TestSandwichCompositor: """Test sandwich compositor.""" + # Test RGB and RGBA + @pytest.mark.parametrize("input_shape,bands", + [((3, 2, 2), ['R', 'G', 'B']), + ((4, 2, 2), ['R', 'G', 'B', 'A'])]) @mock.patch('satpy.composites.enhance2dataset') - def test_compositor(self, e2d): + def test_compositor(self, e2d, input_shape, bands): """Test luminance sharpening compositor.""" from satpy.composites import SandwichCompositor - # Test RGBA - bands = ['R', 'G', 'B', 'A'] - rgba_arr = da.from_array(np.random.random((4, 2, 2)), chunks=2) - rgba = xr.DataArray(rgba_arr, dims=['bands', 'y', 'x'], + rgb_arr = da.from_array(np.random.random(input_shape), chunks=2) + rgb = xr.DataArray(rgb_arr, dims=['bands', 'y', 'x'], coords={'bands': bands}) lum_arr = da.from_array(100 * np.random.random((2, 2)), chunks=2) lum = xr.DataArray(lum_arr, dims=['y', 'x']) # Make enhance2dataset return unmodified dataset - e2d.return_value = rgba + e2d.return_value = rgb comp = SandwichCompositor(name='test') - res = comp([lum, rgba]) + res = comp([lum, rgb]) - for band in rgba: + for band in rgb: if band.bands != 'A': # Check compositor has modified this band np.testing.assert_allclose(res.loc[band.bands].to_numpy(), From 8d554e313407ddf921b1db60bc9c4535523c4ceb Mon Sep 17 00:00:00 2001 From: mherbertson <57386258+mherbertson@users.noreply.github.com> Date: Wed, 6 Apr 2022 01:27:46 +0000 Subject: [PATCH 33/34] Remove whitespace and indents --- satpy/tests/test_composites.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/satpy/tests/test_composites.py b/satpy/tests/test_composites.py index 01697114f1..70f13a44b7 100644 --- a/satpy/tests/test_composites.py +++ b/satpy/tests/test_composites.py @@ -452,9 +452,9 @@ class TestSandwichCompositor: """Test sandwich compositor.""" # Test RGB and RGBA - @pytest.mark.parametrize("input_shape,bands", - [((3, 2, 2), ['R', 'G', 'B']), - ((4, 2, 2), ['R', 'G', 'B', 'A'])]) + @pytest.mark.parametrize("input_shape,bands", + [((3, 2, 2), ['R', 'G', 'B']), + ((4, 2, 2), ['R', 'G', 'B', 'A'])]) @mock.patch('satpy.composites.enhance2dataset') def test_compositor(self, e2d, input_shape, bands): """Test luminance sharpening compositor.""" @@ -462,7 +462,7 @@ def test_compositor(self, e2d, input_shape, bands): rgb_arr = da.from_array(np.random.random(input_shape), chunks=2) rgb = xr.DataArray(rgb_arr, dims=['bands', 'y', 'x'], - coords={'bands': bands}) + coords={'bands': bands}) lum_arr = da.from_array(100 * np.random.random((2, 2)), chunks=2) lum = xr.DataArray(lum_arr, dims=['y', 'x']) From b77c4ad87f203863339003abcb3c1cf6aa945e01 Mon Sep 17 00:00:00 2001 From: mherbertson <57386258+mherbertson@users.noreply.github.com> Date: Wed, 6 Apr 2022 02:13:35 +0000 Subject: [PATCH 34/34] Re-indent pytest.mark.parametrize --- satpy/tests/test_composites.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/satpy/tests/test_composites.py b/satpy/tests/test_composites.py index 70f13a44b7..20fbbfdd51 100644 --- a/satpy/tests/test_composites.py +++ b/satpy/tests/test_composites.py @@ -452,9 +452,13 @@ class TestSandwichCompositor: """Test sandwich compositor.""" # Test RGB and RGBA - @pytest.mark.parametrize("input_shape,bands", - [((3, 2, 2), ['R', 'G', 'B']), - ((4, 2, 2), ['R', 'G', 'B', 'A'])]) + @pytest.mark.parametrize( + "input_shape,bands", + [ + ((3, 2, 2), ['R', 'G', 'B']), + ((4, 2, 2), ['R', 'G', 'B', 'A']) + ] + ) @mock.patch('satpy.composites.enhance2dataset') def test_compositor(self, e2d, input_shape, bands): """Test luminance sharpening compositor."""