Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Drop non-dimensional coordinates in Compositor #796

Merged
merged 8 commits into from
Jun 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 41 additions & 16 deletions satpy/composites/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@

LOG = logging.getLogger(__name__)

NEGLIBLE_COORDS = ['time']
"""Keywords identifying non-dimensional coordinates to be ignored during composite generation."""


class IncompatibleAreas(Exception):
"""Error raised upon compositing things of different shapes."""
Expand Down Expand Up @@ -309,10 +312,28 @@ def apply_modifier_info(self, origin, destination):
elif o.get(k) is not None:
d[k] = o[k]

def check_areas(self, data_arrays):
"""Check that the areas of the *data_arrays* are compatible."""
def match_data_arrays(self, data_arrays):
"""Match data arrays so that they can be used together in a composite."""
self.check_geolocation(data_arrays)
return self.drop_coordinates(data_arrays)

def drop_coordinates(self, data_arrays):
"""Drop neglible non-dimensional coordinates."""
new_arrays = []
for ds in data_arrays:
drop = [coord for coord in ds.coords
if coord not in ds.dims and any([neglible in coord for neglible in NEGLIBLE_COORDS])]
if drop:
new_arrays.append(ds.drop(drop))
else:
new_arrays.append(ds)

return new_arrays

def check_geolocation(self, data_arrays):
"""Check that the geolocations of the *data_arrays* are compatible."""
if len(data_arrays) == 1:
return data_arrays
return
sfinkens marked this conversation as resolved.
Show resolved Hide resolved

if 'x' in data_arrays[0].dims and \
not all(x.sizes['x'] == data_arrays[0].sizes['x']
Expand All @@ -325,7 +346,7 @@ def check_areas(self, data_arrays):

areas = [ds.attrs.get('area') for ds in data_arrays]
if all(a is None for a in areas):
return data_arrays
return
elif any(a is None for a in areas):
raise ValueError("Missing 'area' attribute")

Expand All @@ -334,7 +355,11 @@ def check_areas(self, data_arrays):
"'{}'".format(self.attrs['name']))
raise IncompatibleAreas("Areas are different")

return data_arrays
def check_areas(self, data_arrays):
"""Check that the areas of the *data_arrays* are compatible."""
warnings.warn('satpy.composites.CompositeBase.check_areas is deprecated, use '
'satpy.composites.CompositeBase.match_data_arrays instead')
return self.match_data_arrays(data_arrays)


class SunZenithCorrectorBase(CompositeBase):
Expand All @@ -356,7 +381,7 @@ def __init__(self, max_sza=95.0, **kwargs):

def __call__(self, projectables, **info):
"""Generate the composite."""
projectables = self.check_areas(projectables)
projectables = self.match_data_arrays(projectables)
vis = projectables[0]
if vis.attrs.get("sunz_corrected"):
LOG.debug("Sun zen correction already applied")
Expand Down Expand Up @@ -514,11 +539,11 @@ def __call__(self, projectables, optional_datasets=None, **info):
"""
from pyspectral.rayleigh import Rayleigh
if not optional_datasets or len(optional_datasets) != 4:
vis, red = self.check_areas(projectables)
vis, red = self.match_data_arrays(projectables)
sata, satz, suna, sunz = self.get_angles(vis)
red.data = da.rechunk(red.data, vis.data.chunks)
else:
vis, red, sata, satz, suna, sunz = self.check_areas(
vis, red, sata, satz, suna, sunz = self.match_data_arrays(
projectables + optional_datasets)
sata, satz, suna, sunz = optional_datasets
# get the dask array underneath
Expand Down Expand Up @@ -721,7 +746,7 @@ def __call__(self, projectables, nonprojectables=None, **info):
"""Generate the composite."""
if len(projectables) != 2:
raise ValueError("Expected 2 datasets, got %d" % (len(projectables),))
projectables = self.check_areas(projectables)
projectables = self.match_data_arrays(projectables)
info = combine_metadata(*projectables)
info['name'] = self.attrs['name']

Expand Down Expand Up @@ -778,7 +803,7 @@ def __call__(self, projectables, nonprojectables=None, **attrs):
# num may not be in `self.modes` so only check if we need to
mode = self.modes[num]
if len(projectables) > 1:
projectables = self.check_areas(projectables)
projectables = self.match_data_arrays(projectables)
data = self._concat_datasets(projectables, mode)
# Skip masking if user wants it or a specific alpha channel is given.
if self.common_channel_mask and mode[-1] != 'A':
Expand Down Expand Up @@ -956,7 +981,7 @@ def __init__(self, name, lim_low=85., lim_high=95., **kwargs):

def __call__(self, projectables, **kwargs):
"""Generate the composite."""
projectables = self.check_areas(projectables)
projectables = self.match_data_arrays(projectables)

day_data = projectables[0]
night_data = projectables[1]
Expand Down Expand Up @@ -1058,7 +1083,7 @@ class RealisticColors(GenericCompositor):

def __call__(self, projectables, *args, **kwargs):
"""Generate the composite."""
projectables = self.check_areas(projectables)
projectables = self.match_data_arrays(projectables)
vis06 = projectables[0]
vis08 = projectables[1]
hrv = projectables[2]
Expand Down Expand Up @@ -1181,7 +1206,7 @@ def __call__(self, datasets, optional_datasets=None, **info):

new_attrs = {}
if optional_datasets:
datasets = self.check_areas(datasets + optional_datasets)
datasets = self.match_data_arrays(datasets + optional_datasets)
high_res = datasets[-1]
p1, p2, p3 = datasets[:3]
if 'rows_per_scan' in high_res.attrs:
Expand All @@ -1207,7 +1232,7 @@ def __call__(self, datasets, optional_datasets=None, **info):
g = self._get_band(high_res, p2, 'green', ratio)
b = self._get_band(high_res, p3, 'blue', ratio)
else:
datasets = self.check_areas(datasets)
datasets = self.match_data_arrays(datasets)
r, g, b = datasets[:3]

# combine the masks
Expand Down Expand Up @@ -1305,7 +1330,7 @@ class LuminanceSharpeningCompositor(GenericCompositor):
def __call__(self, projectables, *args, **kwargs):
"""Generate the composite."""
from trollimage.image import rgb2ycbcr, ycbcr2rgb
projectables = self.check_areas(projectables)
projectables = self.match_data_arrays(projectables)
luminance = projectables[0].copy()
luminance /= 100.
# Limit between min(luminance) ... 1.0
Expand Down Expand Up @@ -1339,7 +1364,7 @@ class SandwichCompositor(GenericCompositor):

def __call__(self, projectables, *args, **kwargs):
"""Generate the composite."""
projectables = self.check_areas(projectables)
projectables = self.match_data_arrays(projectables)
luminance = projectables[0]
luminance /= 100.
# Limit between min(luminance) ... 1.0
Expand Down
2 changes: 1 addition & 1 deletion satpy/composites/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class SimulatedGreen(GenericCompositor):
"""A single-band dataset resembles a Green (0.55 µm)."""

def __call__(self, projectables, optional_datasets=None, **attrs):
c01, c02, c03 = self.check_areas(projectables)
c01, c02, c03 = self.match_data_arrays(projectables)

# Kaba:
# res = (c01 + c02) * 0.45 + 0.1 * c03
Expand Down
2 changes: 1 addition & 1 deletion satpy/composites/ahi.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class GreenCorrector(GenericCompositor):
def __call__(self, projectables, optional_datasets=None, **attrs):
"""Boost vegetation effect thanks to NIR (0.8µm) band."""

green, nir = self.check_areas(projectables)
green, nir = self.match_data_arrays(projectables)
LOG.info('Boosting vegetation on green band')

new_green = green * 0.85 + nir * 0.15
Expand Down
2 changes: 1 addition & 1 deletion satpy/composites/cloud_products.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class PrecipCloudsRGB(GenericCompositor):
def __call__(self, projectables, *args, **kwargs):
"""Make an RGB image out of the three probability categories of the NWCSAF precip product."""

projectables = self.check_areas(projectables)
projectables = self.match_data_arrays(projectables)
light = projectables[0]
moderate = projectables[1]
intense = projectables[2]
Expand Down
4 changes: 2 additions & 2 deletions satpy/composites/viirs.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ def __init__(self, *args, **kwargs):

def __call__(self, datasets, optional_datasets, **info):
if not optional_datasets or len(optional_datasets) != 4:
vis = self.check_areas([datasets[0]])[0]
vis = self.match_data_arrays([datasets[0]])[0]
sensor_aa, sensor_za, solar_aa, solar_za = self.get_angles(vis)
else:
vis, sensor_aa, sensor_za, solar_aa, solar_za = self.check_areas(
vis, sensor_aa, sensor_za, solar_aa, solar_za = self.match_data_arrays(
datasets + optional_datasets)
# get the dask array underneath
sensor_aa = sensor_aa.data
Expand Down
50 changes: 29 additions & 21 deletions satpy/tests/compositor_tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
import unittest


class TestCheckArea(unittest.TestCase):
"""Test the utility method 'check_areas'."""
class TestMatchDataArrays(unittest.TestCase):
"""Test the utility method 'match_data_arrays'."""

def _get_test_ds(self, shape=(50, 100), dims=('y', 'x')):
"""Helper method to get a fake DataArray."""
Expand All @@ -54,7 +54,7 @@ def test_single_ds(self):
from satpy.composites import CompositeBase
ds1 = self._get_test_ds()
comp = CompositeBase('test_comp')
ret_datasets = comp.check_areas((ds1,))
ret_datasets = comp.match_data_arrays((ds1,))
self.assertIs(ret_datasets[0], ds1)

def test_mult_ds_area(self):
Expand All @@ -63,7 +63,7 @@ def test_mult_ds_area(self):
ds1 = self._get_test_ds()
ds2 = self._get_test_ds()
comp = CompositeBase('test_comp')
ret_datasets = comp.check_areas((ds1, ds2))
ret_datasets = comp.match_data_arrays((ds1, ds2))
self.assertIs(ret_datasets[0], ds1)
self.assertIs(ret_datasets[1], ds2)

Expand All @@ -74,7 +74,7 @@ def test_mult_ds_no_area(self):
ds2 = self._get_test_ds()
del ds2.attrs['area']
comp = CompositeBase('test_comp')
self.assertRaises(ValueError, comp.check_areas, (ds1, ds2))
self.assertRaises(ValueError, comp.match_data_arrays, (ds1, ds2))

def test_mult_ds_diff_area(self):
"""Test that datasets with different areas fail."""
Expand All @@ -89,7 +89,7 @@ def test_mult_ds_diff_area(self):
100, 50,
(-30037508.34, -20018754.17, 10037508.34, 18754.17))
comp = CompositeBase('test_comp')
self.assertRaises(IncompatibleAreas, comp.check_areas, (ds1, ds2))
self.assertRaises(IncompatibleAreas, comp.match_data_arrays, (ds1, ds2))

def test_mult_ds_diff_dims(self):
"""Test that datasets with different dimensions still pass."""
Expand All @@ -99,7 +99,7 @@ def test_mult_ds_diff_dims(self):
ds1 = self._get_test_ds(shape=(50, 100), dims=('y', 'x'))
ds2 = self._get_test_ds(shape=(3, 100, 50), dims=('bands', 'x', 'y'))
comp = CompositeBase('test_comp')
ret_datasets = comp.check_areas((ds1, ds2))
ret_datasets = comp.match_data_arrays((ds1, ds2))
self.assertIs(ret_datasets[0], ds1)
self.assertIs(ret_datasets[1], ds2)

Expand All @@ -111,7 +111,15 @@ def test_mult_ds_diff_size(self):
ds1 = self._get_test_ds(shape=(50, 100), dims=('x', 'y'))
ds2 = self._get_test_ds(shape=(3, 50, 100), dims=('bands', 'y', 'x'))
comp = CompositeBase('test_comp')
self.assertRaises(IncompatibleAreas, comp.check_areas, (ds1, ds2))
self.assertRaises(IncompatibleAreas, comp.match_data_arrays, (ds1, ds2))

def test_nondimensional_coords(self):
from satpy.composites import CompositeBase
ds = self._get_test_ds(shape=(2, 2))
ds['acq_time'] = ('y', [0, 1])
comp = CompositeBase('test_comp')
ret_datasets = comp.match_data_arrays([ds, ds])
self.assertNotIn('acq_time', ret_datasets[0].coords)


class TestRatioSharpenedCompositors(unittest.TestCase):
Expand Down Expand Up @@ -166,7 +174,7 @@ def test_bad_color(self):
from satpy.composites import RatioSharpenedRGB
self.assertRaises(ValueError, RatioSharpenedRGB, name='true_color', high_resolution_band='bad')

def test_check_areas(self):
def test_match_data_arrays(self):
"""Test that all of the areas have to be the same resolution."""
from satpy.composites import RatioSharpenedRGB, IncompatibleAreas
comp = RatioSharpenedRGB(name='true_color')
Expand Down Expand Up @@ -739,8 +747,8 @@ def test_get_sensors(self):
@mock.patch('satpy.composites.GenericCompositor._get_sensors')
@mock.patch('satpy.composites.combine_metadata')
@mock.patch('satpy.composites.check_times')
@mock.patch('satpy.composites.GenericCompositor.check_areas')
def test_call_with_mock(self, check_areas, check_times, combine_metadata, get_sensors):
@mock.patch('satpy.composites.GenericCompositor.match_data_arrays')
def test_call_with_mock(self, match_data_arrays, check_times, combine_metadata, get_sensors):
"""Test calling generic compositor"""
from satpy.composites import IncompatibleAreas
combine_metadata.return_value = dict()
Expand All @@ -749,25 +757,25 @@ def test_call_with_mock(self, check_areas, check_times, combine_metadata, get_se
res = self.comp([self.all_valid])
self.assertEqual(res.shape[0], 1)
self.assertEqual(res.attrs['mode'], 'L')
check_areas.assert_not_called()
match_data_arrays.assert_not_called()
# This compositor has been initialized without common masking, so the
# masking shouldn't have been called
projectables = [self.all_valid, self.first_invalid, self.second_invalid]
check_areas.return_value = projectables
match_data_arrays.return_value = projectables
res = self.comp2(projectables)
check_areas.assert_called_once()
check_areas.reset_mock()
match_data_arrays.assert_called_once()
match_data_arrays.reset_mock()
# Dataset for alpha given, so shouldn't be masked
projectables = [self.all_valid, self.all_valid]
check_areas.return_value = projectables
match_data_arrays.return_value = projectables
res = self.comp(projectables)
check_areas.assert_called_once()
check_areas.reset_mock()
match_data_arrays.assert_called_once()
match_data_arrays.reset_mock()
# When areas are incompatible, masking shouldn't happen
check_areas.side_effect = IncompatibleAreas()
match_data_arrays.side_effect = IncompatibleAreas()
self.assertRaises(IncompatibleAreas,
self.comp, [self.all_valid, self.wrong_shape])
check_areas.assert_called_once()
match_data_arrays.assert_called_once()

def test_call(self):
"""Test calling generic compositor"""
Expand All @@ -794,7 +802,7 @@ def suite():
mysuite.addTests(test_abi.suite())
mysuite.addTests(test_ahi.suite())
mysuite.addTests(test_viirs.suite())
mysuite.addTest(loader.loadTestsFromTestCase(TestCheckArea))
mysuite.addTest(loader.loadTestsFromTestCase(TestMatchDataArrays))
mysuite.addTest(loader.loadTestsFromTestCase(TestRatioSharpenedCompositors))
mysuite.addTest(loader.loadTestsFromTestCase(TestSunZenithCorrector))
mysuite.addTest(loader.loadTestsFromTestCase(TestDifferenceCompositor))
Expand Down