def test_landsea_mask_in_weights(self):
        """Test that the final result from collapsing the nbhood retains the
           mask from weights input."""
        self.nbhooded_cube.data[0, 0:2, 0] = np.nan
        self.nbhooded_cube.data[2, 3:, 4] = np.nan
        mask = np.array([[[1, 1, 1, 0, 0], [1, 1, 0, 0, 0], [1, 0, 0, 0, 0],
                          [1, 0, 0, 0, 0], [1, 1, 1, 1, 1]],
                         [[1, 1, 1, 0, 0], [1, 1, 0, 0, 0], [1, 0, 0, 0, 0],
                          [1, 0, 0, 0, 0], [1, 1, 1, 1, 1]],
                         [[1, 1, 1, 0, 0], [1, 1, 0, 0, 0], [1, 0, 0, 0, 0],
                          [1, 0, 0, 0, 0], [1, 1, 1, 1, 1]]])
        self.weights_cube.data = np.ma.masked_array(self.weights_cube.data,
                                                    mask=mask)
        plugin = CollapseMaskedNeighbourhoodCoordinate(
            "topographic_zone", weights=self.weights_cube)
        result = plugin.process(self.nbhooded_cube)
        expected_result = np.array([[0.0, 0.0, 0.0, 0.2, 0.2],
                                    [0.0, 0.0, 0.2, 0.2, 0.2],
                                    [0.0, 0.19, 0.2, 0.2, 0.2],
                                    [0.0, 0.2, 0.2, 0.19, 0.2],
                                    [0.0, 0.0, 0.0, 0.0, 0.0]])
        expected_mask = np.array([[True, True, True, False, False],
                                  [True, True, False, False, False],
                                  [True, False, False, False, False],
                                  [True, False, False, False, False],
                                  [True, True, True, True, True]])

        self.assertArrayAlmostEqual(expected_result, result.data.data)
        self.assertArrayAlmostEqual(expected_mask, result.data.mask)
        self.assertEqual(expected_mask.dtype, bool)
        self.assertIsInstance(expected_result, np.ndarray)
class Test_process(IrisTest):
    """Test the process method of CollapseMaskedNeighbourhoodCoordinate"""
    def setUp(self):
        """Set up a weights cube and default plugin to use in unittests."""
        weights_data = np.array([[[0.8, 0.7, 0.0, 0.0, 0.0],
                                  [0.7, 0.3, 0.0, 0.0, 0.0],
                                  [0.3, 0.1, 0.0, 0.0, 0.0],
                                  [0.0, 0.0, 0.0, 0.0, 0.0],
                                  [0.0, 0.0, 0.0, 0.0, 0.0]],
                                 [[0.2, 0.3, 1.0, 1.0, 1.0],
                                  [0.3, 0.7, 1.0, 1.0, 1.0],
                                  [0.7, 0.9, 1.0, 1.0, 1.0],
                                  [1.0, 1.0, 1.0, 0.9, 0.5],
                                  [1.0, 1.0, 1.0, 0.6, 0.2]],
                                 [[0.0, 0.0, 0.0, 0.0, 0.0],
                                  [0.0, 0.0, 0.0, 0.0, 0.0],
                                  [0.0, 0.0, 0.0, 0.0, 0.0],
                                  [0.0, 0.0, 0.0, 0.1, 0.5],
                                  [0.0, 0.0, 0.0, 0.4, 0.8]]])
        topographic_zone_points = [50, 150, 250]
        topographic_zone_bounds = [[0, 100], [100, 200], [200, 300]]

        weights_cubes = iris.cube.CubeList([])
        for data, point, bounds in zip(weights_data, topographic_zone_points,
                                       topographic_zone_bounds):
            weights_cubes.append(
                set_up_topographic_zone_cube(data,
                                             point,
                                             bounds,
                                             num_grid_points=5))
        self.weights_cube = weights_cubes.merge_cube()
        nbhood_data = np.ones((3, 5, 5))
        nbhood_data[0] = nbhood_data[0] * 0.1
        nbhood_data[1] = nbhood_data[1] * 0.2
        nbhood_data[2] = nbhood_data[2] * 0.1
        self.nbhooded_cube = self.weights_cube.copy(nbhood_data)
        self.plugin = CollapseMaskedNeighbourhoodCoordinate(
            "topographic_zone", self.weights_cube)

    def test_basic(self):
        """Test that a cube is returned from the process method"""
        result = self.plugin.process(self.nbhooded_cube)
        self.assertIsInstance(result, iris.cube.Cube)

    def test_no_NaNs_in_nbhooded_cube(self):
        """No NaNs in the neighbourhood cube, so no renormalization is needed.
           The neighbourhood data has all values of 0.1 for the top and bottom
           bands and 0.2 for points in the middle band. The weights are used
           to calculate the weighted average amongst the bands."""

        expected_result = np.array([[0.12, 0.13, 0.2, 0.2, 0.2],
                                    [0.13, 0.17, 0.2, 0.2, 0.2],
                                    [0.17, 0.19, 0.2, 0.2, 0.2],
                                    [0.2, 0.2, 0.2, 0.19, 0.15],
                                    [0.2, 0.2, 0.2, 0.16, 0.12]])

        result = self.plugin.process(self.nbhooded_cube)
        self.assertArrayAlmostEqual(expected_result, result.data)

    def test_some_NaNs_in_nbhooded_cube(self):
        """Some NaNs in the neighbourhood cube, so renormalizing is needed.
           The neighbourhood data has all values of 0.1 for the top and bottom
           bands and 0.2 for points in the middle band. The weights are used
           to calculate the weighted average amongst the bands. This is the
           same test as above, but a few of the weights are modified where
           there are NaNs in the neighbourhood data, giving slightly different
           results."""
        self.nbhooded_cube.data[0, 0:2, 0] = np.nan
        self.nbhooded_cube.data[2, 3:, 4] = np.nan
        expected_result = np.array([[0.2, 0.13, 0.2, 0.2, 0.2],
                                    [0.2, 0.17, 0.2, 0.2, 0.2],
                                    [0.17, 0.19, 0.2, 0.2, 0.2],
                                    [0.2, 0.2, 0.2, 0.19, 0.2],
                                    [0.2, 0.2, 0.2, 0.16, 0.2]])
        result = self.plugin.process(self.nbhooded_cube)
        self.assertArrayAlmostEqual(expected_result, result.data)

    def test_landsea_mask_in_weights(self):
        """Test that the final result from collapsing the nbhood retains the
           mask from weights input."""
        self.nbhooded_cube.data[0, 0:2, 0] = np.nan
        self.nbhooded_cube.data[2, 3:, 4] = np.nan
        mask = np.array([[[1, 1, 1, 0, 0], [1, 1, 0, 0, 0], [1, 0, 0, 0, 0],
                          [1, 0, 0, 0, 0], [1, 1, 1, 1, 1]],
                         [[1, 1, 1, 0, 0], [1, 1, 0, 0, 0], [1, 0, 0, 0, 0],
                          [1, 0, 0, 0, 0], [1, 1, 1, 1, 1]],
                         [[1, 1, 1, 0, 0], [1, 1, 0, 0, 0], [1, 0, 0, 0, 0],
                          [1, 0, 0, 0, 0], [1, 1, 1, 1, 1]]])
        self.weights_cube.data = np.ma.masked_array(self.weights_cube.data,
                                                    mask=mask)
        plugin = CollapseMaskedNeighbourhoodCoordinate(
            "topographic_zone", weights=self.weights_cube)
        result = plugin.process(self.nbhooded_cube)
        expected_result = np.array([[0.0, 0.0, 0.0, 0.2, 0.2],
                                    [0.0, 0.0, 0.2, 0.2, 0.2],
                                    [0.0, 0.19, 0.2, 0.2, 0.2],
                                    [0.0, 0.2, 0.2, 0.19, 0.2],
                                    [0.0, 0.0, 0.0, 0.0, 0.0]])
        expected_mask = np.array([[True, True, True, False, False],
                                  [True, True, False, False, False],
                                  [True, False, False, False, False],
                                  [True, False, False, False, False],
                                  [True, True, True, True, True]])

        self.assertArrayAlmostEqual(expected_result, result.data.data)
        self.assertArrayAlmostEqual(expected_mask, result.data.mask)
        self.assertEqual(expected_mask.dtype, bool)
        self.assertIsInstance(expected_result, np.ndarray)

    def test_multidimensional_neighbourhood_input(self):
        """Test that we can collapse the right dimension when there
           are additional leading dimensions like threshold."""
        nbhooded_cube = add_dimensions_to_cube(self.nbhooded_cube,
                                               {"threshold": 3})
        expected_result = np.array([[[0.12, 0.13, 0.2, 0.2, 0.2],
                                     [0.13, 0.17, 0.2, 0.2, 0.2],
                                     [0.17, 0.19, 0.2, 0.2, 0.2],
                                     [0.2, 0.2, 0.2, 0.19, 0.15],
                                     [0.2, 0.2, 0.2, 0.16, 0.12]],
                                    [[0.12, 0.13, 0.2, 0.2, 0.2],
                                     [0.13, 0.17, 0.2, 0.2, 0.2],
                                     [0.17, 0.19, 0.2, 0.2, 0.2],
                                     [0.2, 0.2, 0.2, 0.19, 0.15],
                                     [0.2, 0.2, 0.2, 0.16, 0.12]],
                                    [[0.12, 0.13, 0.2, 0.2, 0.2],
                                     [0.13, 0.17, 0.2, 0.2, 0.2],
                                     [0.17, 0.19, 0.2, 0.2, 0.2],
                                     [0.2, 0.2, 0.2, 0.19, 0.15],
                                     [0.2, 0.2, 0.2, 0.16, 0.12]]])
        result = self.plugin.process(nbhooded_cube)
        self.assertArrayAlmostEqual(expected_result, result.data)

    def test_preserve_dimensions_input(self):
        """Test that the dimensions on the output cube are the same as the
           input cube, apart from the collapsed dimension.
           Add threshold and realization coordinates and check they are in the
           right place after collapsing the topographic_zone coordinate."""
        nbhood_cube = self.weights_cube.copy()
        nbhood_cube.remove_coord("realization")
        nbhood_cube = add_dimensions_to_cube(nbhood_cube, {
            "threshold": 3,
            "realization": 4
        })
        result = self.plugin.process(nbhood_cube)
        expected_dims = [
            coord for coord in nbhood_cube.dim_coords
            if coord.long_name is not "topographic_zone"
        ]
        self.assertEqual(result.dim_coords, tuple(expected_dims))
        self.assertEqual(result.coord_dims("realization"), (0, ))
        self.assertEqual(result.coord_dims("threshold"), (1, ))
        self.assertEqual(result.coord_dims("projection_y_coordinate"), (2, ))
        self.assertEqual(result.coord_dims("projection_x_coordinate"), (3, ))

    def test_preserve_dimensions_with_single_point(self):
        """Test that the dimensions on the output cube are the same as the
           input cube, appart from the collapsed dimension.
           Add threshold and realization coordinates and check they are in the
           right place after collapsing the topographic_zone coordinate.
           Check that a dimension coordinate with a single point is preserved
           and not demoted to a scalar coordinate."""
        nbhood_cube = self.weights_cube.copy()
        nbhood_cube.remove_coord("realization")
        nbhood_cube = add_dimensions_to_cube(nbhood_cube, {
            "threshold": 3,
            "realization": 1
        })
        result = self.plugin.process(nbhood_cube)
        expected_dims = [
            coord for coord in nbhood_cube.dim_coords
            if coord.long_name is not "topographic_zone"
        ]
        self.assertEqual(result.dim_coords, tuple(expected_dims))
        self.assertEqual(result.coord_dims("realization"), (0, ))
        self.assertEqual(result.coord_dims("threshold"), (1, ))
        self.assertEqual(result.coord_dims("projection_y_coordinate"), (2, ))
        self.assertEqual(result.coord_dims("projection_x_coordinate"), (3, ))