diff --git a/.travis.yml b/.travis.yml index a563ed67c0..b7502bca30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -105,10 +105,10 @@ install: script: - if [[ $TEST_TARGET == 'default' ]]; then - python -m iris.tests.runner --default-tests --system-tests --print-failed-images; + python -m iris.tests.runner --default-tests --system-tests; fi - if [[ $TEST_TARGET == 'example' ]]; then - python -m iris.tests.runner --example-tests --print-failed-images; + python -m iris.tests.runner --example-tests; fi # "make html" produces an error when run on Travis that does not # affect any downstream functionality but causes the build to fail diff --git a/lib/iris/tests/__init__.py b/lib/iris/tests/__init__.py index 8dcc0e136c..5529990dd0 100644 --- a/lib/iris/tests/__init__.py +++ b/lib/iris/tests/__init__.py @@ -139,7 +139,7 @@ plt.switch_backend('tkagg') _DISPLAY_FIGURES = True -_DEFAULT_IMAGE_TOLERANCE = 10.0 +_DEFAULT_IMAGE_TOLERANCE = 0.1 def main(): diff --git a/lib/iris/tests/integration/plot/__init__.py b/lib/iris/tests/integration/plot/__init__.py deleted file mode 100644 index fb405492cf..0000000000 --- a/lib/iris/tests/integration/plot/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# (C) British Crown Copyright 2014 - 2015, Met Office -# -# This file is part of Iris. -# -# Iris is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the -# Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Iris 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Iris. If not, see . -"""Integration tests for the :mod:`iris.plot` package.""" - -from __future__ import (absolute_import, division, print_function) -from six.moves import (filter, input, map, range, zip) # noqa diff --git a/lib/iris/tests/integration/plotting/__init__.py b/lib/iris/tests/integration/plotting/__init__.py new file mode 100644 index 0000000000..91d6b76be0 --- /dev/null +++ b/lib/iris/tests/integration/plotting/__init__.py @@ -0,0 +1,295 @@ +# (C) British Crown Copyright 2014 - 2016, Met Office +# +# This file is part of Iris. +# +# Iris is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Iris 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Iris. If not, see . +""" +Integration tests for the packages :mod:`iris.plot` and :mod:`iris.quickplot`. + +""" + +from __future__ import (absolute_import, division, print_function) +from six.moves import (filter, input, map, range, zip) # noqa +import iris.tests as tests + +from functools import wraps +import types +import warnings + +import cf_units +import matplotlib.pyplot as plt +import numpy as np + +import iris.coords as coords +from iris.exceptions import CoordinateNotFoundError +import iris.tests.stock + + +# Helper functions and classes. + +_LOAD_CUBE_ONCE_CACHE = {} + + +def load_cube_once(filename, constraint): + """Same syntax as load_cube, but will only load a file once, + + then cache the answer in a dictionary. + + """ + global _LOAD_CUBE_ONCE_CACHE + key = (filename, str(constraint)) + cube = _LOAD_CUBE_ONCE_CACHE.get(key, None) + + if cube is None: + cube = iris.load_cube(filename, constraint) + _LOAD_CUBE_ONCE_CACHE[key] = cube + + return cube + + +class LambdaStr(object): + """Provides a callable function which has a sensible __repr__.""" + def __init__(self, repr, lambda_fn): + self.repr = repr + self.lambda_fn = lambda_fn + + def __call__(self, *args, **kwargs): + return self.lambda_fn(*args, **kwargs) + + def __repr__(self): + return self.repr + + +def simple_cube(): + cube = iris.tests.stock.realistic_4d() + cube = cube[:, 0, 0, :] + cube.coord('time').guess_bounds() + return cube + + +def load_theta(): + path = tests.get_data_path(('PP', 'COLPEX', 'theta_and_orog_subset.pp')) + theta = load_cube_once(path, 'air_potential_temperature') + + # Improve the unit + theta.units = 'K' + + return theta + + +# Permutations classes. + + +@tests.skip_data +@tests.skip_plot +class Test1dScatter(tests.GraphicsTest): + + def test_coord_coord(self): + x = self.cube.coord('longitude') + y = self.cube.coord('altitude') + c = self.cube.data + self.draw_method(x, y, c=c, edgecolor='none') + self.check_graphic() + + def test_coord_coord_map(self): + x = self.cube.coord('longitude') + y = self.cube.coord('latitude') + c = self.cube.data + self.draw_method(x, y, c=c, edgecolor='none') + plt.gca().coastlines() + self.check_graphic() + + def test_coord_cube(self): + x = self.cube.coord('latitude') + y = self.cube + c = self.cube.coord('Travel Time').points + self.draw_method(x, y, c=c, edgecolor='none') + self.check_graphic() + + def test_cube_coord(self): + x = self.cube + y = self.cube.coord('altitude') + c = self.cube.coord('Travel Time').points + self.draw_method(x, y, c=c, edgecolor='none') + self.check_graphic() + + def test_cube_cube(self): + x = iris.load_cube( + tests.get_data_path(('NAME', 'NAMEIII_trajectory.txt')), + 'Rel Humidity') + y = self.cube + c = self.cube.coord('Travel Time').points + self.draw_method(x, y, c=c, edgecolor='none') + self.check_graphic() + + def test_incompatible_objects(self): + # cubes/coordinates of different sizes cannot be plotted + x = self.cube + y = self.cube.coord('altitude')[:-1] + with self.assertRaises(ValueError): + self.draw_method(x, y) + + def test_not_cube_or_coord(self): + # inputs must be cubes or coordinates + x = np.arange(self.cube.shape[0]) + y = self.cube + with self.assertRaises(TypeError): + self.draw_method(x, y) + + +@tests.skip_data +@tests.skip_plot +class TestNDCoordinatesGiven(tests.GraphicsTest): + def setUp(self): + self.results = {'yx': ([self.contourf, ['grid_latitude', + 'grid_longitude']], + [self.contourf, ['grid_longitude', + 'grid_latitude']], + [self.contour, ['grid_latitude', + 'grid_longitude']], + [self.contour, ['grid_longitude', + 'grid_latitude']], + [self.pcolor, ['grid_latitude', + 'grid_longitude']], + [self.pcolor, ['grid_longitude', + 'grid_latitude']], + [self.pcolormesh, ['grid_latitude', + 'grid_longitude']], + [self.pcolormesh, ['grid_longitude', + 'grid_latitude']], + [self.points, ['grid_latitude', + 'grid_longitude']], + [self.points, ['grid_longitude', + 'grid_latitude']],), + 'zx': ([self.contourf, ['model_level_number', + 'grid_longitude']], + [self.contourf, ['grid_longitude', + 'model_level_number']], + [self.contour, ['model_level_number', + 'grid_longitude']], + [self.contour, ['grid_longitude', + 'model_level_number']], + [self.pcolor, ['model_level_number', + 'grid_longitude']], + [self.pcolor, ['grid_longitude', + 'model_level_number']], + [self.pcolormesh, ['model_level_number', + 'grid_longitude']], + [self.pcolormesh, ['grid_longitude', + 'model_level_number']], + [self.points, ['model_level_number', + 'grid_longitude']], + [self.points, ['grid_longitude', + 'model_level_number']],), + 'tx': ([self.contourf, ['time', 'grid_longitude']], + [self.contourf, ['grid_longitude', 'time']], + [self.contour, ['time', 'grid_longitude']], + [self.contour, ['grid_longitude', 'time']], + [self.pcolor, ['time', 'grid_longitude']], + [self.pcolor, ['grid_longitude', 'time']], + [self.pcolormesh, ['time', 'grid_longitude']], + [self.pcolormesh, ['grid_longitude', 'time']], + [self.points, ['time', 'grid_longitude']], + [self.points, ['grid_longitude', 'time']],), + 'x': ([self.plot, ['grid_longitude']],), + 'y': ([self.plot, ['grid_latitude']],), + } + + def draw(self, draw_method, *args, **kwargs): + draw_fn = getattr(self, draw_method) + draw_fn(*args, **kwargs) + self.check_graphic() + + def run_tests(self, cube, results): + for draw_method, coords in results: + draw_method(cube, coords=coords) + try: + self.check_graphic() + except AssertionError as err: + self.fail('Draw method %r failed with coords: %r. ' + 'Assertion message: %s' % (draw_method, coords, err)) + + def run_tests_1d(self, cube, results): + # there is a different calling convention for 1d plots + for draw_method, coords in results: + draw_method(cube.coord(coords[0]), cube) + try: + self.check_graphic() + except AssertionError as err: + msg = 'Draw method {!r} failed with coords: {!r}. ' \ + 'Assertion message: {!s}' + self.fail(msg.format(draw_method, coords, err)) + + def test_yx(self): + test_cube = self.cube[0, 0, :, :] + self.run_tests(test_cube, self.results['yx']) + + def test_zx(self): + test_cube = self.cube[0, :15, 0, :] + self.run_tests(test_cube, self.results['zx']) + + def test_tx(self): + test_cube = self.cube[:, 0, 0, :] + self.run_tests(test_cube, self.results['tx']) + + def test_x(self): + test_cube = self.cube[0, 0, 0, :] + self.run_tests_1d(test_cube, self.results['x']) + + def test_y(self): + test_cube = self.cube[0, 0, :, 0] + self.run_tests_1d(test_cube, self.results['y']) + + def test_bad__duplicate_coord(self): + cube = self.cube[0, 0, :, :] + with self.assertRaises(ValueError): + self.draw('contourf', cube, coords=['grid_longitude', + 'grid_longitude']) + + def test_bad__too_many_coords(self): + cube = self.cube[0, 0, :, :] + with self.assertRaises(ValueError): + self.draw('contourf', cube, coords=['grid_longitude', + 'grid_longitude', + 'grid_latitude']) + + def test_bad__coord_name(self): + cube = self.cube[0, 0, :, :] + with self.assertRaises(CoordinateNotFoundError): + self.draw('contourf', cube, coords=['grid_longitude', 'wibble']) + + def test_bad__no_coords_given(self): + cube = self.cube[0, 0, :, :] + with self.assertRaises(ValueError): + self.draw('contourf', cube, coords=[]) + + def test_bad__duplicate_coord_ref(self): + cube = self.cube[0, 0, :, :] + with self.assertRaises(ValueError): + self.draw('contourf', cube, coords=[cube.coord('grid_longitude'), + cube.coord('grid_longitude')]) + + def test_bad__too_many_coord_refs(self): + cube = self.cube[0, 0, :, :] + with self.assertRaises(ValueError): + self.draw('contourf', cube, coords=[cube.coord('grid_longitude'), + cube.coord('grid_longitude'), + cube.coord('grid_longitude')]) + + def test_non_cube_coordinate(self): + cube = self.cube[0, :, :, 0] + pts = -100 + np.arange(cube.shape[1]) * 13 + x = coords.DimCoord(pts, standard_name='model_level_number', + attributes={'positive': 'up'}) + self.draw('contourf', cube, coords=['grid_latitude', x]) diff --git a/lib/iris/tests/integration/plot/test_colorbar.py b/lib/iris/tests/integration/plotting/test_colorbar.py similarity index 100% rename from lib/iris/tests/integration/plot/test_colorbar.py rename to lib/iris/tests/integration/plotting/test_colorbar.py diff --git a/lib/iris/tests/integration/plot/test_netcdftime.py b/lib/iris/tests/integration/plotting/test_netcdftime.py similarity index 100% rename from lib/iris/tests/integration/plot/test_netcdftime.py rename to lib/iris/tests/integration/plotting/test_netcdftime.py diff --git a/lib/iris/tests/integration/plotting/test_plot.py b/lib/iris/tests/integration/plotting/test_plot.py new file mode 100644 index 0000000000..18d8e88433 --- /dev/null +++ b/lib/iris/tests/integration/plotting/test_plot.py @@ -0,0 +1,155 @@ +# (C) British Crown Copyright 2016, Met Office +# +# This file is part of Iris. +# +# Iris is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Iris 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Iris. If not, see . +"""Integration tests for the package :mod:`iris.plot`.""" + +from __future__ import (absolute_import, division, print_function) +from six.moves import (filter, input, map, range, zip) # noqa +import six + +# import iris tests first so that some things can be initialised before +# importing anything else +import iris.tests as tests +import iris + +import iris.tests.integration.plotting as plotting + +# Run tests in no graphics mode if matplotlib is not available. +if tests.MPL_AVAILABLE: + import matplotlib.pyplot as plt + from iris.plot import (plot, contour, contourf, pcolor, pcolormesh, + scatter, points, orography_at_points) + + +@tests.skip_plot +class TestSimple(tests.GraphicsTest): + def test_points(self): + cube = plotting.simple_cube() + contourf(cube) + self.check_graphic() + + def test_bounds(self): + cube = plotting.simple_cube() + pcolor(cube) + self.check_graphic() + + +@tests.skip_plot +class TestMissingCoord(tests.GraphicsTest): + def _check(self, cube): + contourf(cube) + self.check_graphic() + pcolor(cube) + self.check_graphic() + + def test_no_u(self): + cube = plotting.simple_cube() + cube.remove_coord('grid_longitude') + self._check(cube) + + def test_no_v(self): + cube = plotting.simple_cube() + cube.remove_coord('time') + self._check(cube) + + def test_none(self): + cube = plotting.simple_cube() + cube.remove_coord('grid_longitude') + cube.remove_coord('time') + self._check(cube) + + +@tests.skip_plot +class TestHybridHeight(tests.GraphicsTest): + def setUp(self): + self.cube = iris.tests.stock.realistic_4d()[0, :15, 0, :] + + def test_points(self): + contourf(self.cube, coords=['level_height', 'grid_longitude']) + self.check_graphic() + contourf(self.cube, coords=['altitude', 'grid_longitude']) + self.check_graphic() + + def test_orography(self): + contourf(self.cube) + orography_at_points(self.cube) + self.check_graphic() + + def test_orography_specific_coords(self): + coords = ['altitude', 'grid_longitude'] + contourf(self.cube, coords=coords) + orography_at_points(self.cube, coords=coords) + self.check_graphic() + + +@tests.skip_data +@tests.skip_plot +class TestMissingCoordSystem(tests.GraphicsTest): + def test(self): + cube = tests.stock.simple_pp() + cube.coord("latitude").coord_system = None + cube.coord("longitude").coord_system = None + contourf(cube) + self.check_graphic() + + +@tests.skip_data +@tests.skip_plot +class Test1dPlotScatter(plotting.Test1dScatter): + + def setUp(self): + self.cube = iris.load_cube( + tests.get_data_path(('NAME', 'NAMEIII_trajectory.txt')), + 'Temperature') + self.draw_method = scatter + self.check_graphic() + + +@tests.skip_data +@tests.skip_plot +class TestPlotCoordinatesGiven(plotting.TestNDCoordinatesGiven): + def setUp(self): + filename = tests.get_data_path(('PP', 'COLPEX', + 'theta_and_orog_subset.pp')) + self.cube = plotting.load_cube_once(filename, + 'air_potential_temperature') + + self.draw_module = iris.plot + self.contourf = plotting.LambdaStr('iris.plot.contourf', + lambda cube, *args, **kwargs: + contourf(cube, *args, **kwargs)) + self.contour = plotting.LambdaStr('iris.plot.contour', + lambda cube, *args, **kwargs: + contour(cube, *args, **kwargs)) + self.pcolor = plotting.LambdaStr('iris.quickplot.pcolor', + lambda cube, *args, **kwargs: + pcolor(cube, *args, **kwargs)) + self.pcolormesh = plotting.LambdaStr('iris.quickplot.pcolormesh', + lambda cube, *args, **kwargs: + pcolormesh(cube, *args, **kwargs)) + self.points = plotting.LambdaStr('iris.plot.points', + lambda cube, *args, **kwargs: + points(cube, c=cube.data, + *args, **kwargs)) + self.plot = plotting.LambdaStr('iris.plot.plot', + lambda cube, *args, **kwargs: + plot(cube, *args, **kwargs)) + + super(TestPlotCoordinatesGiven, self).setUp() + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/integration/plotting/test_quickplot.py b/lib/iris/tests/integration/plotting/test_quickplot.py new file mode 100644 index 0000000000..357aac8162 --- /dev/null +++ b/lib/iris/tests/integration/plotting/test_quickplot.py @@ -0,0 +1,189 @@ +# (C) British Crown Copyright 2016, Met Office +# +# This file is part of Iris. +# +# Iris is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Iris 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Iris. If not, see . +"""Integration tests for the package :mod:`iris.quickplot`.""" + +from __future__ import (absolute_import, division, print_function) +from six.moves import (filter, input, map, range, zip) # noqa +import six + +# import iris tests first so that some things can be initialised before +# importing anything else +import iris.tests as tests +import iris +import iris.tests.integration.plotting as plotting + +# Run tests in no graphics mode if matplotlib is not available. +if tests.MPL_AVAILABLE: + import matplotlib.pyplot as plt + from iris.plot import (plot, contour, contourf, pcolor, pcolormesh, points) + + +@tests.skip_data +@tests.skip_plot +class TestAttributePositive(tests.GraphicsTest): + def test_1d_positive_up(self): + path = tests.get_data_path(('NetCDF', 'ORCA2', 'votemper.nc')) + cube = iris.load_cube(path) + plot(cube.coord('depth'), cube[0, :, 60, 80]) + self.check_graphic() + + def test_1d_positive_down(self): + path = tests.get_data_path(('NetCDF', 'ORCA2', 'votemper.nc')) + cube = iris.load_cube(path) + plot(cube[0, :, 60, 80], cube.coord('depth')) + self.check_graphic() + + def test_2d_positive_up(self): + path = tests.get_data_path(('NetCDF', 'testing', + 'small_theta_colpex.nc')) + cube = iris.load_cube(path)[0, :, 42, :] + pcolormesh(cube) + self.check_graphic() + + def test_2d_positive_down(self): + path = tests.get_data_path(('NetCDF', 'ORCA2', 'votemper.nc')) + cube = iris.load_cube(path)[0, :, 42, :] + pcolormesh(cube) + self.check_graphic() + + +@tests.skip_data +@tests.skip_plot +class TestQuickplotCoordinatesGiven(plotting.TestNDCoordinatesGiven): + def setUp(self): + filename = tests.get_data_path( + ('PP', 'COLPEX', 'theta_and_orog_subset.pp')) + self.cube = plotting.load_cube_once(filename, + 'air_potential_temperature') + + self.contourf = plotting.LambdaStr('iris.quickplot.contourf', + lambda cube, *args, **kwargs: + contourf(cube, *args, **kwargs)) + self.contour = plotting.LambdaStr('iris.quickplot.contour', + lambda cube, *args, **kwargs: + contour(cube, *args, **kwargs)) + self.pcolor = plotting.LambdaStr('iris.quickplot.pcolor', + lambda cube, *args, **kwargs: + pcolor(cube, *args, **kwargs)) + self.pcolormesh = plotting.LambdaStr('iris.quickplot.pcolormesh', + lambda cube, *args, **kwargs: + pcolormesh(cube, *args, **kwargs)) + self.points = plotting.LambdaStr('iris.quickplot.points', + lambda cube, *args, **kwargs: + points(cube, c=cube.data, + *args, **kwargs)) + self.plot = plotting.LambdaStr('iris.quickplot.plot', + lambda cube, *args, **kwargs: + plot(cube, *args, **kwargs)) + + super(TestQuickplotCoordinatesGiven, self).setUp() + + +@tests.skip_data +@tests.skip_plot +class TestLabels(tests.GraphicsTest): + def setUp(self): + self.theta = plotting.load_theta() + + def _slice(self, coords): + """Returns the first cube containing the requested coordinates.""" + for cube in self.theta.slices(coords): + break + return cube + + def _small(self): + # Use a restricted size so we can make out the detail. + cube = self._slice(['model_level_number', 'grid_longitude']) + return cube[:5, :5] + + def test_contour(self): + contour(self._small()) + self.check_graphic() + + contour(self._small(), + coords=['model_level_number', 'grid_longitude']) + self.check_graphic() + + def test_contourf(self): + contourf(self._small()) + self.check_graphic() + + contourf(self._small(), + coords=['model_level_number', 'grid_longitude']) + self.check_graphic() + + contourf(self._small(), + coords=['grid_longitude', 'model_level_number']) + self.check_graphic() + + def test_contourf_nameless(self): + cube = self._small() + cube.standard_name = None + contourf(cube, + coords=['grid_longitude', 'model_level_number']) + self.check_graphic() + + def test_pcolor(self): + pcolor(self._small()) + self.check_graphic() + + def test_pcolormesh(self): + pcolormesh(self._small()) + self.check_graphic() + + def test_map(self): + cube = self._slice(['grid_latitude', 'grid_longitude']) + contour(cube) + self.check_graphic() + + def test_add_roll(self): + # Check that the result of adding 360 to the data is almost identical. + cube = self._slice(['grid_latitude', 'grid_longitude']) + lon = cube.coord('grid_longitude') + lon.points = lon.points + 360 + contour(cube) + self.check_graphic() + + def test_alignment(self): + cube = self._small() + contourf(cube) + points(cube) + self.check_graphic() + + +@tests.skip_data +@tests.skip_plot +class TestTimeReferenceUnitsLabels(tests.GraphicsTest): + + def setUp(self): + path = tests.get_data_path(('PP', 'aPProt1', 'rotatedMHtimecube.pp')) + self.cube = iris.load_cube(path)[:, 0, 0] + + def test_reference_time_units(self): + # units should not be displayed for a reference time + plot(self.cube.coord('time'), self.cube) + plt.gcf().autofmt_xdate() + self.check_graphic() + + def test_not_reference_time_units(self): + # units should be displayed for other time coordinates + plot(self.cube.coord('forecast_period'), self.cube) + self.check_graphic() + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/integration/plotting/test_symbols.py b/lib/iris/tests/integration/plotting/test_symbols.py new file mode 100644 index 0000000000..9eb1bc09dc --- /dev/null +++ b/lib/iris/tests/integration/plotting/test_symbols.py @@ -0,0 +1,46 @@ +# (C) British Crown Copyright 2016, Met Office +# +# This file is part of Iris. +# +# Iris is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Iris 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Iris. If not, see . +"""Integration tests for the package :mod:`iris.symbols`.""" + +from __future__ import (absolute_import, division, print_function) +from six.moves import (filter, input, map, range, zip) # noqa +import six + +# import iris tests first so that some things can be initialised before +# importing anything else +import iris.tests as tests + +# Run tests in no graphics mode if matplotlib is not available. +if tests.MPL_AVAILABLE: + import matplotlib.pyplot as plt + from iris.plot import symbols as iplt_symbols + import iris.symbols as symbols + + +@tests.skip_plot +class TestSymbols(tests.GraphicsTest): + def test_cloud_cover(self): + iplt_symbols(list(range(10)), + [0] * 10, + [symbols.CLOUD_COVER[i] for i in range(10)], + 0.375) + plt.axis('off') + self.check_graphic() + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/results/visual_tests/test_plot.TestHybridHeight.test_orography.0.png b/lib/iris/tests/results/visual_tests/test_plot.TestHybridHeight.test_orography.0.png index 077d80f4e4..2932aaeae4 100644 Binary files a/lib/iris/tests/results/visual_tests/test_plot.TestHybridHeight.test_orography.0.png and b/lib/iris/tests/results/visual_tests/test_plot.TestHybridHeight.test_orography.0.png differ diff --git a/lib/iris/tests/test_coding_standards.py b/lib/iris/tests/test_coding_standards.py index ec0c19aba5..2ba957c3ea 100644 --- a/lib/iris/tests/test_coding_standards.py +++ b/lib/iris/tests/test_coding_standards.py @@ -118,7 +118,6 @@ class StandardReportWithExclusions(pep8.StandardReport): '*/iris/tests/test_pp_module.py', '*/iris/tests/test_pp_stash.py', '*/iris/tests/test_pp_to_cube.py', - '*/iris/tests/test_quickplot.py', '*/iris/tests/test_regrid.py', '*/iris/tests/test_std_names.py', '*/iris/tests/test_trajectory.py', diff --git a/lib/iris/tests/test_plot.py b/lib/iris/tests/test_plot.py deleted file mode 100644 index 026fc59acc..0000000000 --- a/lib/iris/tests/test_plot.py +++ /dev/null @@ -1,924 +0,0 @@ -# (C) British Crown Copyright 2010 - 2015, Met Office -# -# This file is part of Iris. -# -# Iris is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the -# Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Iris 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Iris. If not, see . - -from __future__ import (absolute_import, division, print_function) -from six.moves import (filter, input, map, range, zip) # noqa -import six - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests - -from functools import wraps -import types -import warnings - -import cf_units -import numpy as np - -import iris -import iris.coords as coords -import iris.tests.stock - -# Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: - import matplotlib.pyplot as plt - import iris.plot as iplt - import iris.quickplot as qplt - import iris.symbols - - -def simple_cube(): - cube = iris.tests.stock.realistic_4d() - cube = cube[:, 0, 0, :] - cube.coord('time').guess_bounds() - return cube - - -@tests.skip_plot -class TestSimple(tests.GraphicsTest): - def test_points(self): - cube = simple_cube() - qplt.contourf(cube) - self.check_graphic() - - def test_bounds(self): - cube = simple_cube() - qplt.pcolor(cube) - self.check_graphic() - - -@tests.skip_plot -class TestMissingCoord(tests.GraphicsTest): - def _check(self, cube): - qplt.contourf(cube) - self.check_graphic() - - qplt.pcolor(cube) - self.check_graphic() - - def test_no_u(self): - cube = simple_cube() - cube.remove_coord('grid_longitude') - self._check(cube) - - def test_no_v(self): - cube = simple_cube() - cube.remove_coord('time') - self._check(cube) - - def test_none(self): - cube = simple_cube() - cube.remove_coord('grid_longitude') - cube.remove_coord('time') - self._check(cube) - - -@tests.skip_data -@tests.skip_plot -class TestMissingCS(tests.GraphicsTest): - @tests.skip_data - def test_missing_cs(self): - cube = tests.stock.simple_pp() - cube.coord("latitude").coord_system = None - cube.coord("longitude").coord_system = None - qplt.contourf(cube) - qplt.plt.gca().coastlines() - self.check_graphic() - - -@tests.skip_plot -class TestHybridHeight(tests.GraphicsTest): - def setUp(self): - self.cube = iris.tests.stock.realistic_4d()[0, :15, 0, :] - - def _check(self, plt_method, test_altitude=True): - plt_method(self.cube) - self.check_graphic() - - plt_method(self.cube, coords=['level_height', 'grid_longitude']) - self.check_graphic() - - plt_method(self.cube, coords=['grid_longitude', 'level_height']) - self.check_graphic() - - if test_altitude: - plt_method(self.cube, coords=['grid_longitude', 'altitude']) - self.check_graphic() - - plt_method(self.cube, coords=['altitude', 'grid_longitude']) - self.check_graphic() - - def test_points(self): - self._check(qplt.contourf) - - def test_bounds(self): - self._check(qplt.pcolor, test_altitude=False) - - def test_orography(self): - qplt.contourf(self.cube) - iplt.orography_at_points(self.cube) - iplt.points(self.cube) - self.check_graphic() - - coords = ['altitude', 'grid_longitude'] - qplt.contourf(self.cube, coords=coords) - iplt.orography_at_points(self.cube, coords=coords) - iplt.points(self.cube, coords=coords) - self.check_graphic() - - # TODO: Test bounds once they are supported. - with self.assertRaises(NotImplementedError): - qplt.pcolor(self.cube) - iplt.orography_at_bounds(self.cube) - iplt.outline(self.cube) - self.check_graphic() - - -@tests.skip_plot -class Test1dPlotMultiArgs(tests.GraphicsTest): - # tests for iris.plot using multi-argument calling convention - - def setUp(self): - self.cube1d = _load_4d_testcube()[0, :, 0, 0] - self.draw_method = iplt.plot - - def test_cube(self): - # just plot a cube against its dim coord - self.draw_method(self.cube1d) # altitude vs temp - self.check_graphic() - - def test_coord(self): - # plot the altitude coordinate - self.draw_method(self.cube1d.coord('altitude')) - self.check_graphic() - - def test_coord_cube(self): - # plot temperature against sigma - self.draw_method(self.cube1d.coord('sigma'), self.cube1d) - self.check_graphic() - - def test_cube_coord(self): - # plot a vertical profile of temperature - self.draw_method(self.cube1d, self.cube1d.coord('altitude')) - self.check_graphic() - - def test_coord_coord(self): - # plot two coordinates that are not mappable - self.draw_method(self.cube1d.coord('sigma'), - self.cube1d.coord('altitude')) - self.check_graphic() - - def test_coord_coord_map(self): - # plot lat-lon aux coordinates of a trajectory, which draws a map - lon = iris.coords.AuxCoord([0, 5, 10, 15, 20, 25, 30, 35, 40, 45], - standard_name='longitude', - units='degrees_north') - lat = iris.coords.AuxCoord([45, 55, 50, 60, 55, 65, 60, 70, 65, 75], - standard_name='latitude', - units='degrees_north') - self.draw_method(lon, lat) - plt.gca().coastlines() - self.check_graphic() - - def test_cube_cube(self): - # plot two phenomena against eachother, in this case just dummy data - cube1 = self.cube1d.copy() - cube2 = self.cube1d.copy() - cube1.rename('some phenomenon') - cube2.rename('some other phenomenon') - cube1.units = cf_units.Unit('no_unit') - cube2.units = cf_units.Unit('no_unit') - cube1.data[:] = np.linspace(0, 1, 7) - cube2.data[:] = np.exp(cube1.data) - self.draw_method(cube1, cube2) - self.check_graphic() - - def test_incompatible_objects(self): - # incompatible objects (not the same length) should raise an error - with self.assertRaises(ValueError): - self.draw_method(self.cube1d.coord('time'), (self.cube1d)) - - def test_multimidmensional(self): - # multidimensional cubes are not allowed - cube = _load_4d_testcube()[0, :, :, 0] - with self.assertRaises(ValueError): - self.draw_method(cube) - - def test_not_cube_or_coord(self): - # inputs must be cubes or coordinates, otherwise an error should be - # raised - xdim = np.arange(self.cube1d.shape[0]) - with self.assertRaises(TypeError): - self.draw_method(xdim, self.cube1d) - - def test_plot_old_coords_kwarg(self): - # Coords used to be a valid kwarg to plot, but it was deprecated and - # we are maintaining a reasonable exception, check that it is raised - # here. - with self.assertRaises(TypeError): - self.draw_method(self.cube1d, coords=None) - - -@tests.skip_plot -class Test1dQuickplotPlotMultiArgs(Test1dPlotMultiArgs): - # tests for iris.plot using multi-argument calling convention - - def setUp(self): - self.cube1d = _load_4d_testcube()[0, :, 0, 0] - self.draw_method = qplt.plot - - -@tests.skip_data -@tests.skip_plot -class Test1dScatter(tests.GraphicsTest): - - def setUp(self): - self.cube = iris.load_cube( - tests.get_data_path(('NAME', 'NAMEIII_trajectory.txt')), - 'Temperature') - self.draw_method = iplt.scatter - - def test_coord_coord(self): - x = self.cube.coord('longitude') - y = self.cube.coord('altitude') - c = self.cube.data - self.draw_method(x, y, c=c, edgecolor='none') - self.check_graphic() - - def test_coord_coord_map(self): - x = self.cube.coord('longitude') - y = self.cube.coord('latitude') - c = self.cube.data - self.draw_method(x, y, c=c, edgecolor='none') - plt.gca().coastlines() - self.check_graphic() - - def test_coord_cube(self): - x = self.cube.coord('latitude') - y = self.cube - c = self.cube.coord('Travel Time').points - self.draw_method(x, y, c=c, edgecolor='none') - self.check_graphic() - - def test_cube_coord(self): - x = self.cube - y = self.cube.coord('altitude') - c = self.cube.coord('Travel Time').points - self.draw_method(x, y, c=c, edgecolor='none') - self.check_graphic() - - def test_cube_cube(self): - x = iris.load_cube( - tests.get_data_path(('NAME', 'NAMEIII_trajectory.txt')), - 'Rel Humidity') - y = self.cube - c = self.cube.coord('Travel Time').points - self.draw_method(x, y, c=c, edgecolor='none') - self.check_graphic() - - def test_incompatible_objects(self): - # cubes/coordinates of different sizes cannot be plotted - x = self.cube - y = self.cube.coord('altitude')[:-1] - with self.assertRaises(ValueError): - self.draw_method(x, y) - - def test_multidimensional(self): - # multidimensional cubes/coordinates are not allowed - x = _load_4d_testcube()[0, :, :, 0] - y = x.coord('model_level_number') - with self.assertRaises(ValueError): - self.draw_method(x, y) - - def test_not_cube_or_coord(self): - # inputs must be cubes or coordinates - x = np.arange(self.cube.shape[0]) - y = self.cube - with self.assertRaises(TypeError): - self.draw_method(x, y) - - -@tests.skip_data -@tests.skip_plot -class Test1dQuickplotScatter(Test1dScatter): - - def setUp(self): - self.cube = iris.load_cube( - tests.get_data_path(('NAME', 'NAMEIII_trajectory.txt')), - 'Temperature') - self.draw_method = qplt.scatter - - -@tests.skip_data -@tests.skip_plot -class TestAttributePositive(tests.GraphicsTest): - def test_1d_positive_up(self): - path = tests.get_data_path(('NetCDF', 'ORCA2', 'votemper.nc')) - cube = iris.load_cube(path) - qplt.plot(cube.coord('depth'), cube[0, :, 60, 80]) - self.check_graphic() - - def test_1d_positive_down(self): - path = tests.get_data_path(('NetCDF', 'ORCA2', 'votemper.nc')) - cube = iris.load_cube(path) - qplt.plot(cube[0, :, 60, 80], cube.coord('depth')) - self.check_graphic() - - def test_2d_positive_up(self): - path = tests.get_data_path(('NetCDF', 'testing', - 'small_theta_colpex.nc')) - cube = iris.load_cube(path)[0, :, 42, :] - qplt.pcolormesh(cube) - self.check_graphic() - - def test_2d_positive_down(self): - path = tests.get_data_path(('NetCDF', 'ORCA2', 'votemper.nc')) - cube = iris.load_cube(path)[0, :, 42, :] - qplt.pcolormesh(cube) - self.check_graphic() - - -# Caches _load_4d_testcube so subsequent calls are faster -def cache(fn, cache={}): - def inner(*args, **kwargs): - key = fn.__name__ - if key not in cache: - cache[key] = fn(*args, **kwargs) - return cache[key] - return inner - - -@cache -def _load_4d_testcube(): - # Load example 4d data (TZYX). - test_cube = iris.tests.stock.realistic_4d() - # Replace forecast_period coord with a multi-valued version. - time_coord = test_cube.coord('time') - n_times = len(time_coord.points) - forecast_dims = test_cube.coord_dims(time_coord) - test_cube.remove_coord('forecast_period') - # Make up values (including bounds), to roughly match older testdata. - point_values = np.linspace((1 + 1.0 / 6), 2.0, n_times) - point_uppers = point_values + (point_values[1] - point_values[0]) - bound_values = np.column_stack([point_values, point_uppers]) - # NOTE: this must be a DimCoord - # - an equivalent AuxCoord produces different plots. - new_forecast_coord = iris.coords.DimCoord( - points=point_values, - bounds=bound_values, - standard_name='forecast_period', - units=cf_units.Unit('hours') - ) - test_cube.add_aux_coord(new_forecast_coord, forecast_dims) - # Heavily reduce dimensions for faster testing. - # NOTE: this makes ZYX non-contiguous. Doesn't seem to matter for now. - test_cube = test_cube[:, ::10, ::10, ::10] - return test_cube - - -@cache -def _load_wind_no_bounds(): - # Load the COLPEX data => TZYX - path = tests.get_data_path(('PP', 'COLPEX', 'small_eastward_wind.pp')) - wind = iris.load_cube(path, 'x_wind') - - # Remove bounds from all coords that have them. - wind.coord('grid_latitude').bounds = None - wind.coord('grid_longitude').bounds = None - wind.coord('level_height').bounds = None - wind.coord('sigma').bounds = None - - return wind[:, :, :50, :50] - - -def _time_series(src_cube): - # Until we have plotting support for multiple axes on the same dimension, - # remove the time coordinate and its axis. - cube = src_cube.copy() - cube.remove_coord('time') - return cube - - -def _date_series(src_cube): - # Until we have plotting support for multiple axes on the same dimension, - # remove the forecast_period coordinate and its axis. - cube = src_cube.copy() - cube.remove_coord('forecast_period') - return cube - - -@tests.skip_plot -class SliceMixin(object): - """Mixin class providing tests for each 2-dimensional permutation of axes. - - Requires self.draw_method to be the relevant plotting function, - and self.results to be a dictionary containing the desired test results.""" - - def test_yx(self): - cube = self.wind[0, 0, :, :] - self.draw_method(cube) - self.check_graphic() - - def test_zx(self): - cube = self.wind[0, :, 0, :] - self.draw_method(cube) - self.check_graphic() - - def test_tx(self): - cube = _time_series(self.wind[:, 0, 0, :]) - self.draw_method(cube) - self.check_graphic() - - def test_zy(self): - cube = self.wind[0, :, :, 0] - self.draw_method(cube) - self.check_graphic() - - def test_ty(self): - cube = _time_series(self.wind[:, 0, :, 0]) - self.draw_method(cube) - self.check_graphic() - - def test_tz(self): - cube = _time_series(self.wind[:, :, 0, 0]) - self.draw_method(cube) - self.check_graphic() - - -@tests.skip_data -class TestContour(tests.GraphicsTest, SliceMixin): - """Test the iris.plot.contour routine.""" - def setUp(self): - self.wind = _load_4d_testcube() - self.draw_method = iplt.contour - - -@tests.skip_data -class TestContourf(tests.GraphicsTest, SliceMixin): - """Test the iris.plot.contourf routine.""" - def setUp(self): - self.wind = _load_4d_testcube() - self.draw_method = iplt.contourf - - -@tests.skip_data -class TestPcolor(tests.GraphicsTest, SliceMixin): - """Test the iris.plot.pcolor routine.""" - def setUp(self): - self.wind = _load_4d_testcube() - self.draw_method = iplt.pcolor - - -@tests.skip_data -class TestPcolormesh(tests.GraphicsTest, SliceMixin): - """Test the iris.plot.pcolormesh routine.""" - def setUp(self): - self.wind = _load_4d_testcube() - self.draw_method = iplt.pcolormesh - - -def check_warnings(method): - """ - Decorator that adds a catch_warnings and filter to assert - the method being decorated issues a UserWarning. - - """ - @wraps(method) - def decorated_method(self, *args, **kwargs): - # Force reset of iris.coords warnings registry to avoid suppression of - # repeated warnings. warnings.resetwarnings() does not do this. - if hasattr(coords, '__warningregistry__'): - coords.__warningregistry__.clear() - - # Check that method raises warning. - with warnings.catch_warnings(): - warnings.simplefilter("error") - with self.assertRaises(UserWarning): - return method(self, *args, **kwargs) - return decorated_method - - -def ignore_warnings(method): - """ - Decorator that adds a catch_warnings and filter to suppress - any warnings issues by the method being decorated. - - """ - @wraps(method) - def decorated_method(self, *args, **kwargs): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - return method(self, *args, **kwargs) - return decorated_method - - -class CheckForWarningsMetaclass(type): - """ - Metaclass that adds a further test for each base class test - that checks that each test raises a UserWarning. Each base - class test is then overriden to ignore warnings in order to - check the underlying functionality. - - """ - def __new__(cls, name, bases, local): - def add_decorated_methods(attr_dict, target_dict, decorator): - for key, value in attr_dict.items(): - if (isinstance(value, types.FunctionType) and - key.startswith('test')): - new_key = '_'.join((key, decorator.__name__)) - if new_key not in target_dict: - wrapped = decorator(value) - wrapped.__name__ = new_key - target_dict[new_key] = wrapped - else: - raise RuntimeError('A attribute called {!r} ' - 'already exists.'.format(new_key)) - - def override_with_decorated_methods(attr_dict, target_dict, - decorator): - for key, value in attr_dict.items(): - if (isinstance(value, types.FunctionType) and - key.startswith('test')): - target_dict[key] = decorator(value) - - # Add decorated versions of base methods - # to check for warnings. - for base in bases: - add_decorated_methods(base.__dict__, local, check_warnings) - - # Override base methods to ignore warnings. - for base in bases: - override_with_decorated_methods(base.__dict__, local, - ignore_warnings) - - return type.__new__(cls, name, bases, local) - - -@tests.skip_data -class TestPcolorNoBounds(six.with_metaclass(CheckForWarningsMetaclass, - tests.GraphicsTest, SliceMixin)): - """ - Test the iris.plot.pcolor routine on a cube with coordinates - that have no bounds. - - """ - - def setUp(self): - self.wind = _load_wind_no_bounds() - self.draw_method = iplt.pcolor - - -@tests.skip_data -class TestPcolormeshNoBounds(six.with_metaclass(CheckForWarningsMetaclass, - tests.GraphicsTest, - SliceMixin)): - """ - Test the iris.plot.pcolormesh routine on a cube with coordinates - that have no bounds. - - """ - - def setUp(self): - self.wind = _load_wind_no_bounds() - self.draw_method = iplt.pcolormesh - - -@tests.skip_plot -class Slice1dMixin(object): - """Mixin class providing tests for each 1-dimensional permutation of axes. - - Requires self.draw_method to be the relevant plotting function, - and self.results to be a dictionary containing the desired test results.""" - - def test_x(self): - cube = self.wind[0, 0, 0, :] - self.draw_method(cube) - self.check_graphic() - - def test_y(self): - cube = self.wind[0, 0, :, 0] - self.draw_method(cube) - self.check_graphic() - - def test_z(self): - cube = self.wind[0, :, 0, 0] - self.draw_method(cube) - self.check_graphic() - - def test_t(self): - cube = _time_series(self.wind[:, 0, 0, 0]) - self.draw_method(cube) - self.check_graphic() - - def test_t_dates(self): - cube = _date_series(self.wind[:, 0, 0, 0]) - self.draw_method(cube) - plt.gcf().autofmt_xdate() - plt.xlabel('Phenomenon time') - - self.check_graphic() - - -@tests.skip_data -class TestPlot(tests.GraphicsTest, Slice1dMixin): - """Test the iris.plot.plot routine.""" - def setUp(self): - self.wind = _load_4d_testcube() - self.draw_method = iplt.plot - - -@tests.skip_data -class TestQuickplotPlot(tests.GraphicsTest, Slice1dMixin): - """Test the iris.quickplot.plot routine.""" - def setUp(self): - self.wind = _load_4d_testcube() - self.draw_method = qplt.plot - - -_load_cube_once_cache = {} - - -def load_cube_once(filename, constraint): - """Same syntax as load_cube, but will only load a file once, - - then cache the answer in a dictionary. - - """ - global _load_cube_once_cache - key = (filename, str(constraint)) - cube = _load_cube_once_cache.get(key, None) - - if cube is None: - cube = iris.load_cube(filename, constraint) - _load_cube_once_cache[key] = cube - - return cube - - -class LambdaStr(object): - """Provides a callable function which has a sensible __repr__.""" - def __init__(self, repr, lambda_fn): - self.repr = repr - self.lambda_fn = lambda_fn - - def __call__(self, *args, **kwargs): - return self.lambda_fn(*args, **kwargs) - - def __repr__(self): - return self.repr - - -@tests.skip_data -@tests.skip_plot -class TestPlotCoordinatesGiven(tests.GraphicsTest): - def setUp(self): - filename = tests.get_data_path(('PP', 'COLPEX', - 'theta_and_orog_subset.pp')) - self.cube = load_cube_once(filename, 'air_potential_temperature') - - self.draw_module = iris.plot - self.contourf = LambdaStr('iris.plot.contourf', - lambda cube, *args, **kwargs: - iris.plot.contourf(cube, *args, **kwargs)) - self.contour = LambdaStr('iris.plot.contour', - lambda cube, *args, **kwargs: - iris.plot.contour(cube, *args, **kwargs)) - self.points = LambdaStr('iris.plot.points', - lambda cube, *args, **kwargs: - iris.plot.points(cube, c=cube.data, - *args, **kwargs)) - self.plot = LambdaStr('iris.plot.plot', - lambda cube, *args, **kwargs: - iris.plot.plot(cube, *args, **kwargs)) - - self.results = {'yx': ([self.contourf, ['grid_latitude', - 'grid_longitude']], - [self.contourf, ['grid_longitude', - 'grid_latitude']], - [self.contour, ['grid_latitude', - 'grid_longitude']], - [self.contour, ['grid_longitude', - 'grid_latitude']], - [self.points, ['grid_latitude', - 'grid_longitude']], - [self.points, ['grid_longitude', - 'grid_latitude']],), - 'zx': ([self.contourf, ['model_level_number', - 'grid_longitude']], - [self.contourf, ['grid_longitude', - 'model_level_number']], - [self.contour, ['model_level_number', - 'grid_longitude']], - [self.contour, ['grid_longitude', - 'model_level_number']], - [self.points, ['model_level_number', - 'grid_longitude']], - [self.points, ['grid_longitude', - 'model_level_number']],), - 'tx': ([self.contourf, ['time', 'grid_longitude']], - [self.contourf, ['grid_longitude', 'time']], - [self.contour, ['time', 'grid_longitude']], - [self.contour, ['grid_longitude', 'time']], - [self.points, ['time', 'grid_longitude']], - [self.points, ['grid_longitude', 'time']],), - 'x': ([self.plot, ['grid_longitude']],), - 'y': ([self.plot, ['grid_latitude']],) - } - - def draw(self, draw_method, *args, **kwargs): - draw_fn = getattr(self.draw_module, draw_method) - draw_fn(*args, **kwargs) - self.check_graphic() - - def run_tests(self, cube, results): - for draw_method, coords in results: - draw_method(cube, coords=coords) - try: - self.check_graphic() - except AssertionError as err: - self.fail('Draw method %r failed with coords: %r. ' - 'Assertion message: %s' % (draw_method, coords, err)) - - def run_tests_1d(self, cube, results): - # there is a different calling convention for 1d plots - for draw_method, coords in results: - draw_method(cube.coord(coords[0]), cube) - try: - self.check_graphic() - except AssertionError as err: - msg = 'Draw method {!r} failed with coords: {!r}. ' \ - 'Assertion message: {!s}' - self.fail(msg.format(draw_method, coords, err)) - - def test_yx(self): - test_cube = self.cube[0, 0, :, :] - self.run_tests(test_cube, self.results['yx']) - - def test_zx(self): - test_cube = self.cube[0, :15, 0, :] - self.run_tests(test_cube, self.results['zx']) - - def test_tx(self): - test_cube = self.cube[:, 0, 0, :] - self.run_tests(test_cube, self.results['tx']) - - def test_x(self): - test_cube = self.cube[0, 0, 0, :] - self.run_tests_1d(test_cube, self.results['x']) - - def test_y(self): - test_cube = self.cube[0, 0, :, 0] - self.run_tests_1d(test_cube, self.results['y']) - - def test_badcoords(self): - cube = self.cube[0, 0, :, :] - draw_fn = getattr(self.draw_module, 'contourf') - self.assertRaises(ValueError, draw_fn, cube, - coords=['grid_longitude', 'grid_longitude']) - self.assertRaises(ValueError, draw_fn, cube, - coords=['grid_longitude', 'grid_longitude', - 'grid_latitude']) - self.assertRaises(iris.exceptions.CoordinateNotFoundError, draw_fn, - cube, coords=['grid_longitude', 'wibble']) - self.assertRaises(ValueError, draw_fn, cube, coords=[]) - self.assertRaises(ValueError, draw_fn, cube, - coords=[cube.coord('grid_longitude'), - cube.coord('grid_longitude')]) - self.assertRaises(ValueError, draw_fn, cube, - coords=[cube.coord('grid_longitude'), - cube.coord('grid_longitude'), - cube.coord('grid_longitude')]) - - def test_non_cube_coordinate(self): - cube = self.cube[0, :, :, 0] - pts = -100 + np.arange(cube.shape[1]) * 13 - x = coords.DimCoord(pts, standard_name='model_level_number', - attributes={'positive': 'up'}) - self.draw('contourf', cube, coords=['grid_latitude', x]) - - -@tests.skip_data -@tests.skip_plot -class TestPlotDimAndAuxCoordsKwarg(tests.GraphicsTest): - def setUp(self): - filename = tests.get_data_path(('NetCDF', 'rotated', 'xy', - 'rotPole_landAreaFraction.nc')) - self.cube = iris.load_cube(filename) - - def test_default(self): - iplt.contourf(self.cube) - plt.gca().coastlines() - self.check_graphic() - - def test_coords(self): - # Pass in dimension coords. - rlat = self.cube.coord('grid_latitude') - rlon = self.cube.coord('grid_longitude') - iplt.contourf(self.cube, coords=[rlon, rlat]) - plt.gca().coastlines() - self.check_graphic() - # Pass in auxiliary coords. - lat = self.cube.coord('latitude') - lon = self.cube.coord('longitude') - iplt.contourf(self.cube, coords=[lon, lat]) - plt.gca().coastlines() - self.check_graphic() - - def test_coord_names(self): - # Pass in names of dimension coords. - iplt.contourf(self.cube, coords=['grid_longitude', 'grid_latitude']) - plt.gca().coastlines() - self.check_graphic() - # Pass in names of auxiliary coords. - iplt.contourf(self.cube, coords=['longitude', 'latitude']) - plt.gca().coastlines() - self.check_graphic() - - def test_yx_order(self): - # Do not attempt to draw coastlines as it is not a map. - iplt.contourf(self.cube, coords=['grid_latitude', 'grid_longitude']) - self.check_graphic() - iplt.contourf(self.cube, coords=['latitude', 'longitude']) - self.check_graphic() - - -@tests.skip_plot -class TestSymbols(tests.GraphicsTest): - def test_cloud_cover(self): - iplt.symbols(list(range(10)), - [0] * 10, - [iris.symbols.CLOUD_COVER[i] for i in range(10)], - 0.375) - iplt.plt.axis('off') - self.check_graphic() - - -@tests.skip_plot -class TestPlottingExceptions(tests.IrisTest): - def setUp(self): - self.bounded_cube = tests.stock.lat_lon_cube() - self.bounded_cube.coord("latitude").guess_bounds() - self.bounded_cube.coord("longitude").guess_bounds() - - def test_boundmode_multidim(self): - # Test exception translation. - # We can't get contiguous bounded grids from multi-d coords. - cube = self.bounded_cube - cube.remove_coord("latitude") - cube.add_aux_coord(coords.AuxCoord(points=cube.data, - standard_name='latitude', - units='degrees'), [0, 1]) - with self.assertRaises(ValueError): - iplt.pcolormesh(cube, coords=['longitude', 'latitude']) - - def test_boundmode_4bounds(self): - # Test exception translation. - # We can only get contiguous bounded grids with 2 bounds per point. - cube = self.bounded_cube - lat = coords.AuxCoord.from_coord(cube.coord("latitude")) - lat.bounds = np.array([lat.points, lat.points + 1, - lat.points + 2, lat.points + 3]).transpose() - cube.remove_coord("latitude") - cube.add_aux_coord(lat, 0) - with self.assertRaises(ValueError): - iplt.pcolormesh(cube, coords=['longitude', 'latitude']) - - def test_different_coord_systems(self): - cube = self.bounded_cube - lat = cube.coord('latitude') - lon = cube.coord('longitude') - lat.coord_system = iris.coord_systems.GeogCS(7000000) - lon.coord_system = iris.coord_systems.GeogCS(7000001) - with self.assertRaises(ValueError): - iplt.pcolormesh(cube, coords=['longitude', 'latitude']) - - -@tests.skip_data -@tests.skip_plot -class TestPlotOtherCoordSystems(tests.GraphicsTest): - def test_plot_tmerc(self): - filename = tests.get_data_path(('NetCDF', 'transverse_mercator', - 'tmean_1910_1910.nc')) - self.cube = iris.load_cube(filename) - iplt.pcolormesh(self.cube[0]) - plt.gca().coastlines() - self.check_graphic() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/test_quickplot.py b/lib/iris/tests/test_quickplot.py deleted file mode 100644 index ef26e8b609..0000000000 --- a/lib/iris/tests/test_quickplot.py +++ /dev/null @@ -1,205 +0,0 @@ -# (C) British Crown Copyright 2010 - 2015, Met Office -# -# This file is part of Iris. -# -# Iris is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the -# Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Iris 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Iris. If not, see . -""" -Tests the high-level plotting interface. - -""" - -from __future__ import (absolute_import, division, print_function) -from six.moves import (filter, input, map, range, zip) # noqa - -# import iris tests first so that some things can be initialised before importing anything else -import iris.tests as tests -import iris.tests.test_plot as test_plot - -import iris - -# Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: - import matplotlib.pyplot as plt - import iris.plot as iplt - import iris.quickplot as qplt - - -# Caches _load_theta so subsequent calls are faster -def cache(fn, cache={}): - def inner(*args, **kwargs): - key = "result" - if not cache: - cache[key] = fn(*args, **kwargs) - return cache[key] - return inner - - -@cache -def _load_theta(): - path = tests.get_data_path(('PP', 'COLPEX', 'theta_and_orog_subset.pp')) - theta = iris.load_cube(path, 'air_potential_temperature') - - # Improve the unit - theta.units = 'K' - - return theta - - -@tests.skip_data -@tests.skip_plot -class TestQuickplotCoordinatesGiven(test_plot.TestPlotCoordinatesGiven): - def setUp(self): - filename = tests.get_data_path(('PP', 'COLPEX', 'theta_and_orog_subset.pp')) - self.cube = test_plot.load_cube_once(filename, 'air_potential_temperature') - - self.draw_module = iris.quickplot - self.contourf = test_plot.LambdaStr('iris.quickplot.contourf', lambda cube, *args, **kwargs: - iris.quickplot.contourf(cube, *args, **kwargs)) - self.contour = test_plot.LambdaStr('iris.quickplot.contour', lambda cube, *args, **kwargs: - iris.quickplot.contour(cube, *args, **kwargs)) - self.points = test_plot.LambdaStr('iris.quickplot.points', lambda cube, *args, **kwargs: - iris.quickplot.points(cube, c=cube.data, *args, **kwargs)) - self.plot = test_plot.LambdaStr('iris.quickplot.plot', lambda cube, *args, **kwargs: - iris.quickplot.plot(cube, *args, **kwargs)) - - self.results = {'yx': ( - [self.contourf, ['grid_latitude', 'grid_longitude']], - [self.contourf, ['grid_longitude', 'grid_latitude']], - [self.contour, ['grid_latitude', 'grid_longitude']], - [self.contour, ['grid_longitude', 'grid_latitude']], - [self.points, ['grid_latitude', 'grid_longitude']], - [self.points, ['grid_longitude', 'grid_latitude']], - ), - 'zx': ( - [self.contourf, ['model_level_number', 'grid_longitude']], - [self.contourf, ['grid_longitude', 'model_level_number']], - [self.contour, ['model_level_number', 'grid_longitude']], - [self.contour, ['grid_longitude', 'model_level_number']], - [self.points, ['model_level_number', 'grid_longitude']], - [self.points, ['grid_longitude', 'model_level_number']], - ), - 'tx': ( - [self.contourf, ['time', 'grid_longitude']], - [self.contourf, ['grid_longitude', 'time']], - [self.contour, ['time', 'grid_longitude']], - [self.contour, ['grid_longitude', 'time']], - [self.points, ['time', 'grid_longitude']], - [self.points, ['grid_longitude', 'time']], - ), - 'x': ( - [self.plot, ['grid_longitude']], - ), - 'y': ( - [self.plot, ['grid_latitude']], - ), - } - - -@tests.skip_data -@tests.skip_plot -class TestLabels(tests.GraphicsTest): - def setUp(self): - self.theta = _load_theta() - - def _slice(self, coords): - """Returns the first cube containing the requested coordinates.""" - for cube in self.theta.slices(coords): - break - return cube - - def _small(self): - # Use a restricted size so we can make out the detail - cube = self._slice(['model_level_number', 'grid_longitude']) - return cube[:5, :5] - - def test_contour(self): - qplt.contour(self._small()) - self.check_graphic() - - qplt.contourf(self._small(), coords=['model_level_number', 'grid_longitude']) - self.check_graphic() - - def test_contourf(self): - qplt.contourf(self._small()) - - cube = self._small() - iplt.orography_at_points(cube) - - self.check_graphic() - - qplt.contourf(self._small(), coords=['model_level_number', 'grid_longitude']) - self.check_graphic() - - qplt.contourf(self._small(), coords=['grid_longitude', 'model_level_number']) - self.check_graphic() - - def test_contourf_nameless(self): - cube = self._small() - cube.standard_name = None - qplt.contourf(cube, coords=['grid_longitude', 'model_level_number']) - self.check_graphic() - - def test_pcolor(self): - qplt.pcolor(self._small()) - self.check_graphic() - - def test_pcolormesh(self): - qplt.pcolormesh(self._small()) - - #cube = self._small() - #iplt.orography_at_bounds(cube) - - self.check_graphic() - - def test_map(self): - cube = self._slice(['grid_latitude', 'grid_longitude']) - qplt.contour(cube) - self.check_graphic() - - # check that the result of adding 360 to the data is *almost* identically the same result - lon = cube.coord('grid_longitude') - lon.points = lon.points + 360 - qplt.contour(cube) - self.check_graphic() - - def test_alignment(self): - cube = self._small() - qplt.contourf(cube) - #qplt.outline(cube) - qplt.points(cube) - self.check_graphic() - - -@tests.skip_data -@tests.skip_plot -class TestTimeReferenceUnitsLabels(tests.GraphicsTest): - - def setUp(self): - path = tests.get_data_path(('PP', 'aPProt1', 'rotatedMHtimecube.pp')) - self.cube = iris.load_cube(path)[:, 0, 0] - - def test_reference_time_units(self): - # units should not be displayed for a reference time - qplt.plot(self.cube.coord('time'), self.cube) - plt.gcf().autofmt_xdate() - self.check_graphic() - - def test_not_reference_time_units(self): - # units should be displayed for other time coordinates - qplt.plot(self.cube.coord('forecast_period'), self.cube) - self.check_graphic() - - -if __name__ == "__main__": - tests.main()