Skip to content

Commit

Permalink
Merge pull request #2068 from gerritholl/no-units-for-rgbs
Browse files Browse the repository at this point in the history
Make sure RGBs do not have units attributes.
  • Loading branch information
mraspaud committed Apr 6, 2022
2 parents 47ce205 + 72f3acb commit 9d57970
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 106 deletions.
9 changes: 7 additions & 2 deletions satpy/composites/__init__.py
Expand Up @@ -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 \
Expand Down Expand Up @@ -979,7 +982,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):
Expand Down
31 changes: 18 additions & 13 deletions satpy/composites/viirs.py
Expand Up @@ -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):
Expand All @@ -973,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" %
Expand Down Expand Up @@ -1006,4 +1009,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
161 changes: 70 additions & 91 deletions 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.
#
Expand All @@ -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(
Expand All @@ -43,25 +38,57 @@ def test_histogram_dnb(self):
'lat_0': 0.0},
cols, rows,
(-20037508.34, -10018754.17, 20037508.34, 10018754.17))
return area

comp = HistogramDNB('histogram_dnb', prerequisites=('dnb',),
standard_name='toa_outgoing_radiance_per_'
'unit_wavelength')
dnb = np.zeros((rows, cols)) + 0.25
@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
dnb = da.from_array(dnb, chunks=25)
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((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})
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)
Expand All @@ -71,35 +98,13 @@ 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, c01, c02):
"""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[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[:, 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)
Expand All @@ -108,42 +113,13 @@ 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, c01, c02, c03):
"""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[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[:, 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[:, 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})
Expand All @@ -161,23 +137,18 @@ 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, c02, c03):
"""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
# 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
dnb[3, :] += 0.25
dnb[4:, :] += 0.5
Expand All @@ -187,20 +158,6 @@ 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[:, 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[:, 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})
Expand All @@ -222,3 +179,25 @@ def test_erf_dnb(self, dnb_units, saturation_correction):
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
9 changes: 9 additions & 0 deletions satpy/tests/test_composites.py
Expand Up @@ -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'),
Expand Down Expand Up @@ -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."""
Expand Down

0 comments on commit 9d57970

Please sign in to comment.