diff --git a/satpy/composites/__init__.py b/satpy/composites/__init__.py
index 9100ba7d35..f8a76c812f 100644
--- a/satpy/composites/__init__.py
+++ b/satpy/composites/__init__.py
@@ -283,6 +283,30 @@ def apply_modifier_info(self, origin, destination):
elif o.get(k) is not None:
d[k] = o[k]
+ def check_areas(self, data_arrays):
+ if len(data_arrays) == 1:
+ return data_arrays
+
+ if 'x' in data_arrays[0].dims and \
+ not all(x.sizes['x'] == data_arrays[0].sizes['x']
+ for x in data_arrays[1:]):
+ raise IncompatibleAreas("X dimension has different sizes")
+ if 'y' in data_arrays[0].dims and \
+ not all(x.sizes['y'] == data_arrays[0].sizes['y']
+ for x in data_arrays[1:]):
+ raise IncompatibleAreas("Y dimension has different sizes")
+
+ areas = [ds.attrs.get('area') for ds in data_arrays]
+ if not areas or any(a is None for a in areas):
+ raise ValueError("Missing 'area' attribute")
+
+ if not all(areas[0] == x for x in areas[1:]):
+ LOG.debug("Not all areas are the same in "
+ "'{}'".format(self.attrs['name']))
+ raise IncompatibleAreas("Areas are different")
+
+ return data_arrays
+
class SunZenithCorrectorBase(CompositeBase):
@@ -382,6 +406,7 @@ def __call__(self, projectables, optional_datasets=None, **info):
sunalt, suna = get_alt_az(vis.attrs['start_time'], lons, lats)
suna = xu.rad2deg(suna)
sunz = sun_zenith_angle(vis.attrs['start_time'], lons, lats)
+ # FIXME: Make it daskified
sata, satel = get_observer_look(vis.attrs['satellite_longitude'],
vis.attrs['satellite_latitude'],
vis.attrs['satellite_altitude'],
@@ -577,16 +602,8 @@ class GenericCompositor(CompositeBase):
modes = {1: 'L', 2: 'LA', 3: 'RGB', 4: 'RGBA'}
- def check_area_compatibility(self, projectables):
- areas = [projectable.attrs.get('area', None)
- for projectable in projectables]
- areas = [area for area in areas if area is not None]
- if areas and areas.count(areas[0]) != len(areas):
- LOG.debug("Not all areas are the same in '{}'".format(self.attrs['name']))
- raise IncompatibleAreas
-
def _concat_datasets(self, projectables, mode):
- self.check_area_compatibility(projectables)
+ projectables = self.check_areas(projectables)
try:
data = xr.concat(projectables, 'bands', coords='minimal')
@@ -1012,16 +1029,10 @@ def __call__(self, datasets, optional_datasets=None, **info):
'the same size. Must resample first.')
new_attrs = {}
- p1, p2, p3 = datasets
if optional_datasets:
- high_res = optional_datasets[0]
- low_res = datasets[["red", "green", "blue"].index(
- self.high_resolution_band)]
- if high_res.attrs["area"] != low_res.attrs["area"]:
- raise IncompatibleAreas("High resolution band is not "
- "mapped to the same area as the "
- "low resolution bands. Must "
- "resample first.")
+ datasets = self.check_areas(datasets + optional_datasets)
+ high_res = datasets[-1]
+ p1, p2, p3 = datasets[:3]
if 'rows_per_scan' in high_res.attrs:
new_attrs.setdefault('rows_per_scan',
high_res.attrs['rows_per_scan'])
@@ -1035,6 +1046,8 @@ def __call__(self, datasets, optional_datasets=None, **info):
r = high_res
g = p2 * ratio
b = p3 * ratio
+ g.attrs = p2.attrs.copy()
+ b.attrs = p3.attrs.copy()
elif self.high_resolution_band == "green":
LOG.debug("Sharpening image with high resolution green band")
ratio = high_res / p2
@@ -1042,6 +1055,8 @@ def __call__(self, datasets, optional_datasets=None, **info):
r = p1 * ratio
g = high_res
b = p3 * ratio
+ r.attrs = p1.attrs.copy()
+ b.attrs = p3.attrs.copy()
elif self.high_resolution_band == "blue":
LOG.debug("Sharpening image with high resolution blue band")
ratio = high_res / p3
@@ -1049,13 +1064,16 @@ def __call__(self, datasets, optional_datasets=None, **info):
r = p1 * ratio
g = p2 * ratio
b = high_res
+ r.attrs = p1.attrs.copy()
+ g.attrs = p2.attrs.copy()
else:
# no sharpening
r = p1
g = p2
b = p3
else:
- r, g, b = p1, p2, p3
+ datasets = self.check_areas(datasets)
+ r, g, b = datasets[:3]
# combine the masks
mask = ~(da.isnull(r.data) | da.isnull(g.data) | da.isnull(b.data))
r = r.where(mask)
diff --git a/satpy/composites/abi.py b/satpy/composites/abi.py
index dda6ac2589..151108274e 100644
--- a/satpy/composites/abi.py
+++ b/satpy/composites/abi.py
@@ -33,12 +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 = projectables
- if not all(c.shape == projectables[0].shape
- for c in projectables[1:]):
- raise IncompatibleAreas("Simulated green can only be made from "
- "bands of the same size. Resample "
- "first.")
+ c01, c02, c03 = self.check_areas(projectables)
# Kaba:
# res = (c01 + c02) * 0.45 + 0.1 * c03
diff --git a/satpy/tests/__init__.py b/satpy/tests/__init__.py
index 2aebcb935a..1197d4971b 100644
--- a/satpy/tests/__init__.py
+++ b/satpy/tests/__init__.py
@@ -29,7 +29,7 @@
test_readers, test_resample,
test_scene, test_utils, test_writers,
test_yaml_reader, writer_tests,
- test_enhancements)
+ test_enhancements, compositor_tests)
if sys.version_info < (2, 7):
@@ -55,6 +55,7 @@ def suite():
mysuite.addTests(test_file_handlers.suite())
mysuite.addTests(test_utils.suite())
mysuite.addTests(test_enhancements.suite())
+ mysuite.addTests(compositor_tests.suite())
return mysuite
diff --git a/satpy/tests/compositor_tests/__init__.py b/satpy/tests/compositor_tests/__init__.py
new file mode 100644
index 0000000000..b3a169a5c4
--- /dev/null
+++ b/satpy/tests/compositor_tests/__init__.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2018 PyTroll developers
+#
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+"""Tests for compositors.
+"""
+
+
+import sys
+
+from satpy.tests.compositor_tests import test_abi
+
+if sys.version_info < (2, 7):
+ import unittest2 as unittest
+else:
+ import unittest
+
+
+class TestCheckArea(unittest.TestCase):
+
+ """Test the utility method 'check_areas'."""
+
+ def _get_test_ds(self, shape=(50, 100), dims=('y', 'x')):
+ """Helper method to get a fake DataArray."""
+ import xarray as xr
+ import dask.array as da
+ from pyresample.geometry import AreaDefinition
+ data = da.random.random(shape, chunks=25)
+ area = AreaDefinition(
+ 'test', 'test', 'test',
+ {'proj': 'eqc', 'lon_0': 0.0,
+ 'lat_0': 0.0},
+ shape[dims.index('x')], shape[dims.index('y')],
+ (-20037508.34, -10018754.17, 20037508.34, 10018754.17))
+ attrs = {'area': area}
+ return xr.DataArray(data, dims=dims, attrs=attrs)
+
+ def test_single_ds(self):
+ """Test a single dataset is returned unharmed."""
+ from satpy.composites import CompositeBase
+ ds1 = self._get_test_ds()
+ comp = CompositeBase('test_comp')
+ ret_datasets = comp.check_areas((ds1,))
+ self.assertIs(ret_datasets[0], ds1)
+
+ def test_mult_ds_area(self):
+ """Test multiple datasets successfully pass."""
+ from satpy.composites import CompositeBase
+ ds1 = self._get_test_ds()
+ ds2 = self._get_test_ds()
+ comp = CompositeBase('test_comp')
+ ret_datasets = comp.check_areas((ds1, ds2))
+ self.assertIs(ret_datasets[0], ds1)
+ self.assertIs(ret_datasets[1], ds2)
+
+ def test_mult_ds_no_area(self):
+ """Test that all datasets must have an area attribute."""
+ from satpy.composites import CompositeBase
+ ds1 = self._get_test_ds()
+ ds2 = self._get_test_ds()
+ del ds2.attrs['area']
+ comp = CompositeBase('test_comp')
+ self.assertRaises(ValueError, comp.check_areas, (ds1, ds2))
+
+ def test_mult_ds_diff_area(self):
+ """Test that datasets with different areas fail."""
+ from satpy.composites import CompositeBase, IncompatibleAreas
+ from pyresample.geometry import AreaDefinition
+ ds1 = self._get_test_ds()
+ ds2 = self._get_test_ds()
+ ds2.attrs['area'] = AreaDefinition(
+ 'test', 'test', 'test',
+ {'proj': 'eqc', 'lon_0': 0.0,
+ 'lat_0': 0.0},
+ 100, 50,
+ (-30037508.34, -20018754.17, 10037508.34, 18754.17))
+ comp = CompositeBase('test_comp')
+ self.assertRaises(IncompatibleAreas, comp.check_areas, (ds1, ds2))
+
+ def test_mult_ds_diff_dims(self):
+ """Test that datasets with different dimensions still pass."""
+ from satpy.composites import CompositeBase
+ # x is still 50, y is still 100, even though they are in
+ # different order
+ 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))
+ self.assertIs(ret_datasets[0], ds1)
+ self.assertIs(ret_datasets[1], ds2)
+
+ def test_mult_ds_diff_size(self):
+ """Test that datasets with different sizes fail."""
+ from satpy.composites import CompositeBase, IncompatibleAreas
+ # x is 50 in this one, 100 in ds2
+ # y is 100 in this one, 50 in ds2
+ 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))
+
+
+def suite():
+ """Test suite for all reader tests"""
+ loader = unittest.TestLoader()
+ mysuite = unittest.TestSuite()
+ mysuite.addTests(test_abi.suite())
+ mysuite.addTest(loader.loadTestsFromTestCase(TestCheckArea))
+
+ return mysuite
diff --git a/satpy/tests/compositor_tests/test_abi.py b/satpy/tests/compositor_tests/test_abi.py
new file mode 100644
index 0000000000..38ce051a8a
--- /dev/null
+++ b/satpy/tests/compositor_tests/test_abi.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2018 PyTroll developers
+#
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+"""Tests for ABI compositors.
+"""
+
+import sys
+
+if sys.version_info < (2, 7):
+ import unittest2 as unittest
+else:
+ import unittest
+
+
+class TestABIComposites(unittest.TestCase):
+ def test_simulated_green(self):
+ import xarray as xr
+ import dask.array as da
+ import numpy as np
+ from satpy.composites.abi import SimulatedGreen
+ from pyresample.geometry import AreaDefinition
+ 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 = SimulatedGreen('green', prerequisites=('C01', 'C02', 'C03'),
+ standard_name='toa_bidirectional_reflectance')
+ c01 = xr.DataArray(da.zeros((rows, cols), chunks=25) + 0.25,
+ dims=('y', 'x'),
+ attrs={'name': 'C01', 'area': area})
+ c02 = xr.DataArray(da.zeros((rows, cols), chunks=25) + 0.30,
+ dims=('y', 'x'),
+ attrs={'name': 'C02', 'area': area})
+ c03 = xr.DataArray(da.zeros((rows, cols), chunks=25) + 0.35,
+ dims=('y', 'x'),
+ attrs={'name': 'C03', 'area': area})
+ res = comp((c01, c02, c03))
+ self.assertIsInstance(res, xr.DataArray)
+ self.assertIsInstance(res.data, da.Array)
+ self.assertEqual(res.attrs['name'], 'green')
+ self.assertEqual(res.attrs['standard_name'],
+ 'toa_bidirectional_reflectance')
+ data = res.compute()
+ np.testing.assert_allclose(data, 0.28025)
+
+
+def suite():
+ """The test suite for test_scene.
+ """
+ loader = unittest.TestLoader()
+ mysuite = unittest.TestSuite()
+ mysuite.addTest(loader.loadTestsFromTestCase(TestABIComposites))
+ return mysuite