Skip to content

Commit df881c9

Browse files
authored
Merge pull request #87 from pp-mo/dask_masked_fix
Dask masked with iris
2 parents 3c0c286 + c8d3636 commit df881c9

9 files changed

Lines changed: 43 additions & 69 deletions

File tree

iris_grib/__init__.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import numpy.ma as ma
3737

3838
import iris
39-
from iris._lazy_data import as_lazy_data, convert_nans_array
39+
from iris._lazy_data import as_lazy_data
4040
import iris.coord_systems as coord_systems
4141
from iris.exceptions import TranslationError, NotYetImplementedError
4242

@@ -145,7 +145,6 @@ class GribWrapper(object):
145145
def __init__(self, grib_message, grib_fh=None):
146146
"""Store the grib message and compute our extra keys."""
147147
self.grib_message = grib_message
148-
self.realised_dtype = np.array([0.]).dtype
149148

150149
if self.edition != 1:
151150
emsg = 'GRIB edition {} is not supported by {!r}.'
@@ -184,14 +183,11 @@ def __init__(self, grib_message, grib_fh=None):
184183
# The byte offset requires to be reset back to the first byte
185184
# of this message. The file pointer offset is always at the end
186185
# of the current message due to the grib-api reading the message.
187-
proxy = GribDataProxy(shape, self.realised_dtype, grib_fh.name,
186+
proxy = GribDataProxy(shape, np.array([0.]).dtype, grib_fh.name,
188187
offset - message_length)
189188
self._data = as_lazy_data(proxy)
190189
else:
191-
values_array = _message_values(grib_message, shape)
192-
# mask where the values are nan
193-
self.data = convert_nans_array(values_array,
194-
nans_replacement=ma.masked)
190+
self.data = _message_values(grib_message, shape)
195191

196192
def _confirm_in_scope(self):
197193
"""Ensure we have a grib flavour that we choose to support."""
@@ -698,6 +694,10 @@ def _message_values(grib_message, shape):
698694
data = gribapi.grib_get_double_array(grib_message, 'values')
699695
data = data.reshape(shape)
700696

697+
# Handle missing values in a sensible way.
698+
mask = np.isnan(data)
699+
if mask.any():
700+
data = ma.array(data, mask=mask, fill_value=np.nan)
701701
return data
702702

703703

iris_grib/_save_rules.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,17 +1157,20 @@ def product_definition_section(cube, grib):
11571157
def data_section(cube, grib):
11581158
# Masked data?
11591159
if ma.isMaskedArray(cube.data):
1160-
fill_value = cube.fill_value
1161-
if fill_value is None or np.isnan(cube.fill_value):
1160+
if not np.isnan(cube.data.fill_value):
1161+
# Use the data's fill value.
1162+
fill_value = float(cube.data.fill_value)
1163+
else:
11621164
# We can't use the cube's fill value if it's NaN,
11631165
# the GRIB API doesn't like it.
11641166
# Calculate an MDI outside the data range.
11651167
min, max = cube.data.min(), cube.data.max()
11661168
fill_value = min - (max - min) * 0.1
1169+
# Prepare the unmasked data array, using fill_value as the MDI.
1170+
data = cube.data.filled(fill_value)
11671171
else:
11681172
fill_value = None
1169-
1170-
data = cube.data
1173+
data = cube.data
11711174

11721175
# units scaling
11731176
grib2_info = gptx.cf_phenom_to_grib2_info(cube.standard_name,

iris_grib/message.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import numpy as np
3131
import numpy.ma as ma
3232

33-
from iris._lazy_data import array_masked_to_nans, as_lazy_data
33+
from iris._lazy_data import as_lazy_data
3434
from iris.exceptions import TranslationError
3535

3636

@@ -120,10 +120,6 @@ def bmdi(self):
120120
# Default for fill value is None.
121121
return None
122122

123-
@property
124-
def realised_dtype(self):
125-
return np.dtype('f8')
126-
127123
def core_data(self):
128124
return self.data
129125

@@ -164,8 +160,7 @@ def data(self):
164160
shape = (grid_section['numberOfDataPoints'],)
165161
else:
166162
shape = (grid_section['Nj'], grid_section['Ni'])
167-
proxy = _DataProxy(shape, self.realised_dtype, np.nan,
168-
self._recreate_raw)
163+
proxy = _DataProxy(shape, np.dtype('f8'), self._recreate_raw)
169164
data = as_lazy_data(proxy)
170165
else:
171166
fmt = 'Grid definition template {} is not supported'
@@ -196,9 +191,7 @@ class _DataProxy(object):
196191

197192
__slots__ = ('shape', 'dtype', 'recreate_raw')
198193

199-
def __init__(self, shape, dtype, fill_value, recreate_raw):
200-
# TODO: I (@pelson) have no idea why fill_value remains an argument as
201-
# it isn't used. It was copied verbatim from iris' dask branch.
194+
def __init__(self, shape, dtype, recreate_raw):
202195
self.shape = shape
203196
self.dtype = dtype
204197
self.recreate_raw = recreate_raw
@@ -260,10 +253,10 @@ def __getitem__(self, keys):
260253
# Only the non-masked values are included in codedValues.
261254
_data = np.empty(shape=bitmap.shape)
262255
_data[bitmap.astype(bool)] = data
263-
# Use nan where input = 1, the opposite of the behaviour
264-
# specified by the GRIB spec.
265-
_data[np.logical_not(bitmap.astype(bool))] = np.nan
266-
data = _data
256+
# `ma.masked_array` masks where input = 1, the opposite of
257+
# the behaviour specified by the GRIB spec.
258+
data = ma.masked_array(_data, mask=np.logical_not(bitmap),
259+
fill_value=np.nan)
267260
else:
268261
msg = 'Shapes of data and bitmap do not match.'
269262
raise TranslationError(msg)

iris_grib/tests/results/unit/load_cubes/load_cubes/reduced_raw.cml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" ?>
22
<cubes xmlns="urn:x-iris:cubeml-0.2">
3-
<cube core-dtype="float64" dtype="float64" standard_name="geopotential" units="m2 s-2">
3+
<cube dtype="float64" standard_name="geopotential" units="m2 s-2">
44
<attributes>
55
<attribute name="centre" value="European Centre for Medium Range Weather Forecasts"/>
66
</attributes>

iris_grib/tests/unit/load_convert/test_reference_time_coord.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# (C) British Crown Copyright 2014 - 2016, Met Office
1+
# (C) British Crown Copyright 2014 - 2017, Met Office
22
#
33
# This file is part of iris-grib.
44
#
@@ -60,12 +60,12 @@ def _check(self, section, standard_name=None):
6060
coord = reference_time_coord(section)
6161
self.assertEqual(coord, expected)
6262

63-
def test_start_of_forecast(self):
63+
def test_start_of_forecast__0(self):
6464
section = deepcopy(self.section)
6565
section['significanceOfReferenceTime'] = 0
6666
self._check(section, 'forecast_reference_time')
6767

68-
def test_start_of_forecast(self):
68+
def test_start_of_forecast__1(self):
6969
section = deepcopy(self.section)
7070
section['significanceOfReferenceTime'] = 1
7171
self._check(section, 'forecast_reference_time')

iris_grib/tests/unit/message/test_GribMessage.py

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -92,38 +92,16 @@ def test_no_bitmap(self):
9292
self.assertEqual(result.shape, self.shape)
9393
self.assertArrayEqual(result, expected)
9494

95-
def test_bitmap_present_int_data(self):
95+
def test_bitmap_present(self):
9696
# Test the behaviour where bitmap and codedValues shapes
97-
# are not equal, and codedValues is integer data.
98-
data_type = np.int64
97+
# are not equal.
9998
input_values = np.arange(5)
100-
output_values = np.array([-1, -1, 0, 1, -1, -1, -1, 2, -1, 3, -1, 4],
101-
dtype=data_type)
99+
output_values = np.array([-1, -1, 0, 1, -1, -1, -1, 2, -1, 3, -1, 4])
102100
message = _make_test_message({3: self._section_3,
103101
6: {'bitMapIndicator': 0,
104102
'bitmap': self.bitmap},
105103
7: {'codedValues': input_values}})
106-
result = as_concrete_data(message.data, nans_replacement=ma.masked,
107-
result_dtype=data_type)
108-
expected = ma.masked_array(output_values,
109-
np.logical_not(self.bitmap))
110-
expected = expected.reshape(self.shape)
111-
self.assertMaskedArrayEqual(result, expected)
112-
self.assertEqual(result.dtype, data_type)
113-
114-
def test_bitmap_present_float_data(self):
115-
# Test the behaviour where bitmap and codedValues shapes
116-
# are not equal, and codedValues is float data.
117-
data_type = np.float32
118-
input_values = np.arange(5, dtype=np.float32) + 5
119-
output_values = np.array([-1, -1, 5, 6, -1, -1, -1, 7, -1, 8, -1, 9],
120-
dtype=data_type)
121-
message = _make_test_message({3: self._section_3,
122-
6: {'bitMapIndicator': 0,
123-
'bitmap': self.bitmap},
124-
7: {'codedValues': input_values}})
125-
result = as_concrete_data(message.data, nans_replacement=ma.masked,
126-
result_dtype=data_type)
104+
result = as_concrete_data(message.data)
127105
expected = ma.masked_array(output_values,
128106
np.logical_not(self.bitmap))
129107
expected = expected.reshape(self.shape)

iris_grib/tests/unit/message/test__DataProxy.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# (C) British Crown Copyright 2014 - 2016, Met Office
1+
# (C) British Crown Copyright 2014 - 2017, Met Office
22
#
33
# This file is part of iris-grib.
44
#
@@ -37,20 +37,20 @@
3737
class Test__bitmap(tests.IrisGribTest):
3838
def test_no_bitmap(self):
3939
section_6 = {'bitMapIndicator': 255, 'bitmap': None}
40-
data_proxy = _DataProxy(0, 0, 0, 0)
40+
data_proxy = _DataProxy(0, 0, 0)
4141
result = data_proxy._bitmap(section_6)
4242
self.assertIsNone(result)
4343

4444
def test_bitmap_present(self):
4545
bitmap = randint(2, size=(12))
4646
section_6 = {'bitMapIndicator': 0, 'bitmap': bitmap}
47-
data_proxy = _DataProxy(0, 0, 0, 0)
47+
data_proxy = _DataProxy(0, 0, 0)
4848
result = data_proxy._bitmap(section_6)
4949
self.assertArrayEqual(bitmap, result)
5050

5151
def test_bitmap__invalid_indicator(self):
5252
section_6 = {'bitMapIndicator': 100, 'bitmap': None}
53-
data_proxy = _DataProxy(0, 0, 0, 0)
53+
data_proxy = _DataProxy(0, 0, 0)
5454
with self.assertRaisesRegexp(TranslationError, 'unsupported bitmap'):
5555
data_proxy._bitmap(section_6)
5656

iris_grib/tests/unit/save_rules/test_data_section.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ def test_simple(self):
9494

9595
def test_masked_with_finite_fill_value(self):
9696
cube = iris.cube.Cube(np.ma.MaskedArray([1.0, 2.0, 3.0, 1.0, 2.0, 3.0],
97-
mask=[0, 0, 0, 1, 1, 1]),
98-
fill_value=2000)
97+
mask=[0, 0, 0, 1, 1, 1],
98+
fill_value=2000))
9999
grib_message = mock.sentinel.GRIB_MESSAGE
100100
with mock.patch(GRIB_API) as grib_api:
101101
data_section(cube, grib_message)
@@ -107,8 +107,8 @@ def test_masked_with_finite_fill_value(self):
107107

108108
def test_masked_with_nan_fill_value(self):
109109
cube = iris.cube.Cube(np.ma.MaskedArray([1.0, 2.0, 3.0, 1.0, 2.0, 3.0],
110-
mask=[0, 0, 0, 1, 1, 1]),
111-
fill_value=np.nan)
110+
mask=[0, 0, 0, 1, 1, 1],
111+
fill_value=np.nan))
112112
grib_message = mock.sentinel.GRIB_MESSAGE
113113
with mock.patch(GRIB_API) as grib_api:
114114
data_section(cube, grib_message)
@@ -135,8 +135,8 @@ def test_scaled_with_finite_fill_value(self):
135135
# When re-scaling masked data with a finite fill value, ensure
136136
# the fill value and any filled values are also re-scaled.
137137
cube = iris.cube.Cube(np.ma.MaskedArray([1.0, 2.0, 3.0, 1.0, 2.0, 3.0],
138-
mask=[0, 0, 0, 1, 1, 1]),
139-
fill_value=2000,
138+
mask=[0, 0, 0, 1, 1, 1],
139+
fill_value=2000),
140140
standard_name='geopotential_height', units='km')
141141
grib_message = mock.sentinel.GRIB_MESSAGE
142142
with mock.patch(GRIB_API) as grib_api:
@@ -152,8 +152,8 @@ def test_scaled_with_nan_fill_value(self):
152152
# a fill value is chosen which allows for the scaling, and any
153153
# filled values match the chosen fill value.
154154
cube = iris.cube.Cube(np.ma.MaskedArray([-1.0, 2.0, -1.0, 2.0],
155-
mask=[0, 0, 1, 1]),
156-
fill_value=np.nan,
155+
mask=[0, 0, 1, 1],
156+
fill_value=np.nan),
157157
standard_name='geopotential_height', units='km')
158158
grib_message = mock.sentinel.GRIB_MESSAGE
159159
with mock.patch(GRIB_API) as grib_api:

iris_grib/tests/unit/test_load_cubes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# (C) British Crown Copyright 2014 - 2016, Met Office
1+
# (C) British Crown Copyright 2014 - 2017, Met Office
22
#
33
# This file is part of iris-grib.
44
#
@@ -41,7 +41,7 @@ def test(self):
4141
rules_load.return_value = expected_result
4242
result = load_cubes(files, callback)
4343
kwargs = {}
44-
loader = Loader(generator, kwargs, converter, None)
44+
loader = Loader(generator, kwargs, converter)
4545
rules_load.assert_called_once_with(files, callback, loader)
4646
self.assertIs(result, expected_result)
4747

0 commit comments

Comments
 (0)