diff --git a/lib/iris/_lazy_data.py b/lib/iris/_lazy_data.py
index e7ffccce32..0da1c4c785 100644
--- a/lib/iris/_lazy_data.py
+++ b/lib/iris/_lazy_data.py
@@ -131,14 +131,14 @@ def as_concrete_data(data):
return data
-def lazy_masked_fill_value(data):
+def get_fill_value(data):
"""
- Return the fill value of a lazy masked array.
+ Return the fill value of a concrete or lazy masked array.
Args:
* data:
- A dask array, NumPy `ndarray` or masked array
+ A dask array, NumPy `ndarray` or masked array.
Returns:
The fill value of `data` if `data` represents a masked array, or None.
@@ -147,9 +147,14 @@ def lazy_masked_fill_value(data):
# If lazy, get the smallest slice of the data from which we can retrieve
# the fill_value.
if is_lazy_data(data):
- inds = tuple([0] * (data.ndim-1))
- smallest_slice = data[inds][:0]
- data = as_concrete_data(smallest_slice)
+ inds = [0] * data.ndim
+ if inds:
+ inds[-1] = slice(0, 1)
+ data = data[tuple(inds)]
+ data = as_concrete_data(data)
+ else:
+ if isinstance(data, ma.core.MaskedConstant):
+ data = ma.array(data.data, mask=data.mask)
# Now get the fill value.
fill_value = None
diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py
index a58a583c67..a2d87d2eed 100644
--- a/lib/iris/fileformats/netcdf.py
+++ b/lib/iris/fileformats/netcdf.py
@@ -56,7 +56,7 @@
import iris.fileformats._pyke_rules
import iris.io
import iris.util
-from iris._lazy_data import as_lazy_data, lazy_masked_fill_value
+from iris._lazy_data import as_lazy_data, get_fill_value
# Show Pyke inference engine statistics.
DEBUG = False
@@ -1926,10 +1926,7 @@ def set_packing_ncattrs(cfvar):
if packing is None:
# Determine whether there is a cube MDI value.
- if ma.isMaskedArray(cube.data):
- fill_value = cube.data.fill_value
- else:
- fill_value = None
+ fill_value = get_fill_value(cube.core_data())
# Get the values in a form which is valid for the file format.
data = self._ensure_valid_dtype(cube.data, 'cube', cube)
@@ -1946,16 +1943,8 @@ def set_packing_ncattrs(cfvar):
else:
# Create the cube CF-netCDF data variable.
- # Set `fill_value` if the data array is masked. If the data array
- # is lazy masked, we realise the smallest possible slice of the
- # array and retrieve the fill value from that.
if packing is None:
- if not cube.has_lazy_data() and ma.isMaskedArray(cube.data):
- fill_value = cube.data.fill_value
- elif cube.has_lazy_data():
- fill_value = lazy_masked_fill_value(cube.lazy_data())
- else:
- fill_value = None
+ fill_value = get_fill_value(cube.core_data())
dtype = cube.dtype.newbyteorder('=')
cf_var = self._dataset.createVariable(
diff --git a/lib/iris/tests/unit/lazy_data/test_get_fill_value.py b/lib/iris/tests/unit/lazy_data/test_get_fill_value.py
new file mode 100644
index 0000000000..e83739f331
--- /dev/null
+++ b/lib/iris/tests/unit/lazy_data/test_get_fill_value.py
@@ -0,0 +1,75 @@
+# (C) British Crown Copyright 2017, 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 .
+"""Test the function :func:`iris._lazy data.get_fill_value`."""
+
+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 dask.array as da
+import numpy as np
+import numpy.ma as ma
+
+from iris._lazy_data import as_lazy_data, get_fill_value
+
+
+class Test_get_fill_value(tests.IrisTest):
+ def setUp(self):
+ # Array shape and fill-value.
+ spec = [((2, 3, 4, 5), -1), # 4d array
+ ((2, 3, 4), -2), # 3d array
+ ((2, 3), -3), # 2d array
+ ((2,), -4), # 1d array
+ ((), -5)] # 0d array
+ self.arrays = [np.empty(shape) for (shape, _) in spec]
+ self.masked = [ma.empty(shape, fill_value=fv) for (shape, fv) in spec]
+ self.lazy_arrays = [as_lazy_data(array) for array in self.arrays]
+ self.lazy_masked = [as_lazy_data(array) for array in self.masked]
+ # Add the masked constant case.
+ mc = ma.array([0], mask=True)[0]
+ self.masked.append(mc)
+ self.lazy_masked.append(as_lazy_data(mc))
+ # Collect the expected fill-values.
+ self.expected_fill_values = [fv for (_, fv) in spec]
+ mc_fill_value = ma.masked_array(0, dtype=mc.dtype).fill_value
+ self.expected_fill_values.append(mc_fill_value)
+
+ def test_arrays(self):
+ for array in self.arrays:
+ self.assertIsNone(get_fill_value(array))
+
+ def test_masked(self):
+ for array, expected in zip(self.masked, self.expected_fill_values):
+ result = get_fill_value(array)
+ self.assertEqual(result, expected)
+
+ def test_lazy_arrays(self):
+ for array in self.lazy_arrays:
+ self.assertIsNone(get_fill_value(array))
+
+ def test_lazy_masked(self):
+ for array, expected in zip(self.lazy_masked,
+ self.expected_fill_values):
+ result = get_fill_value(array)
+ self.assertEqual(result, expected)
+
+
+if __name__ == '__main__':
+ tests.main()
diff --git a/lib/iris/tests/unit/lazy_data/test_lazy_masked_fill_value.py b/lib/iris/tests/unit/lazy_data/test_lazy_masked_fill_value.py
deleted file mode 100644
index aaaefea657..0000000000
--- a/lib/iris/tests/unit/lazy_data/test_lazy_masked_fill_value.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# (C) British Crown Copyright 2017, 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 .
-"""Test the function :func:`iris._lazy data.lazy_masked_fill_value`."""
-
-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 dask.array as da
-import numpy as np
-import numpy.ma as ma
-
-from iris._lazy_data import lazy_masked_fill_value, _MAX_CHUNK_SIZE
-
-
-class Test_as_lazy_data(tests.IrisTest):
- def setUp(self):
- shape = (2, 3, 4)
- data = np.arange(24).reshape(shape)
- mask = np.zeros(shape)
- mask[data % 5 == 1] = 1
- self.fill_value = 9999
- self.m = ma.masked_array(data, mask=mask, fill_value=self.fill_value)
- self.dm = da.from_array(self.m, asarray=False,
- chunks=_MAX_CHUNK_SIZE)
-
- def test_lazy_masked_ND(self):
- fill_value = lazy_masked_fill_value(self.dm)
- self.assertEqual(fill_value, self.fill_value)
-
- def test_lazy_masked_0D(self):
- data = self.dm[0, 0, :1]
- fill_value = lazy_masked_fill_value(data)
- self.assertEqual(fill_value, self.fill_value)
-
- def test_lazy_masked_1D(self):
- data = self.dm[0, 0, :]
- fill_value = lazy_masked_fill_value(data)
- self.assertEqual(fill_value, self.fill_value)
-
- def test_lazy_masked_2D(self):
- data = self.dm[0, :]
- fill_value = lazy_masked_fill_value(data)
- self.assertEqual(fill_value, self.fill_value)
-
- def test_real_masked(self):
- fill_value = lazy_masked_fill_value(self.m)
- self.assertEqual(fill_value, self.fill_value)
-
- def test_lazy_unmasked(self):
- data = da.from_array(self.m.filled(),
- chunks=_MAX_CHUNK_SIZE)
- fill_value = lazy_masked_fill_value(data)
- self.assertIsNone(fill_value)
-
- def test_real_unmasked(self):
- data = self.m.filled()
- fill_value = lazy_masked_fill_value(data)
- self.assertIsNone(fill_value)
-
- def test_data_not_realised(self):
- # Check that only the zero-element slice is realised.
- data = self.dm[0, :]
- lazy_masked_fill_value(data)
- self.assertIsInstance(data, da.core.Array)
-
-
-if __name__ == '__main__':
- tests.main()