Skip to content

Commit 8c074f0

Browse files
committed
add mask/scale roundtrip test for variable cf encoding/decoding
1 parent 51f7515 commit 8c074f0

File tree

1 file changed

+62
-8
lines changed

1 file changed

+62
-8
lines changed

xarray/tests/test_coding.py

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,71 @@ def test_coder_roundtrip() -> None:
9595
assert_identical(original, roundtripped)
9696

9797

98-
@pytest.mark.parametrize("unpacked_dtype", [np.float32, np.float64, np.int32])
98+
@pytest.mark.parametrize("ptype", "u1 u2 u4 i1 i2 i4".split())
99+
@pytest.mark.parametrize("utype", "f4 f8".split())
100+
def test_mask_scale_roundtrip(utype: str, ptype: str) -> None:
101+
# this tests cf conforming packing/unpacking via
102+
# encode_cf_variable/decode_cf_variable
103+
# f4->i4 packing is skipped as non-conforming
104+
if utype[1] == "4" and ptype[1] == "4":
105+
pytest.skip("Can't pack float32 into int32/uint32")
106+
# fillvalues according to netCDF4
107+
filldict = {
108+
"i1": -127,
109+
"u1": 255,
110+
"i2": -32767,
111+
"u2": 65535,
112+
"i4": -2147483647,
113+
"u4": 4294967295,
114+
}
115+
fillvalue = filldict[ptype]
116+
unpacked_dtype = np.dtype(utype).type
117+
packed_dtype = np.dtype(ptype).type
118+
info = np.iinfo(packed_dtype)
119+
120+
# create original "encoded" Variable
121+
packed_data = np.array(
122+
[info.min, fillvalue, info.max - 1, info.max], dtype=packed_dtype
123+
)
124+
attrs = dict(
125+
scale_factor=unpacked_dtype(1),
126+
add_offset=unpacked_dtype(0),
127+
_FillValue=packed_dtype(fillvalue),
128+
)
129+
original = xr.Variable(("x",), packed_data, attrs=attrs)
130+
131+
# create wanted "decoded" Variable
132+
unpacked_data = np.array(
133+
[info.min, fillvalue, info.max - 1, info.max], dtype=unpacked_dtype
134+
)
135+
encoding = dict(
136+
scale_factor=unpacked_dtype(1),
137+
add_offset=unpacked_dtype(0),
138+
_FillValue=packed_dtype(fillvalue),
139+
)
140+
wanted = xr.Variable(("x"), unpacked_data, encoding=encoding)
141+
wanted = wanted.where(wanted != fillvalue)
142+
143+
# decode original and compare with wanted
144+
decoded = decode_cf_variable("x", original)
145+
assert wanted.dtype == decoded.dtype
146+
xr.testing.assert_identical(wanted, decoded)
147+
148+
# encode again and compare with original
149+
encoded = encode_cf_variable(decoded)
150+
assert original.dtype == encoded.dtype
151+
xr.testing.assert_identical(original, encoded)
152+
153+
154+
@pytest.mark.parametrize("unpacked_dtype", "f4 f8 i4".split())
99155
@pytest.mark.parametrize("packed_dtype", "u1 u2 i1 i2 f2 f4".split())
100-
def test_scaling_converts_to_float32(
101-
packed_dtype: str, unpacked_dtype: type[np.number]
102-
) -> None:
156+
def test_scaling_converts_to_float32(packed_dtype: str, unpacked_dtype: str) -> None:
103157
# if scale_factor but no add_offset is given transform to float32 in any case
104158
# this minimizes memory usage, see #1840, #1842
105159
original = xr.Variable(
106160
("x",),
107161
np.arange(10, dtype=packed_dtype),
108-
encoding=dict(scale_factor=unpacked_dtype(10)),
162+
encoding=dict(scale_factor=np.dtype(unpacked_dtype).type(10)),
109163
)
110164
coder = variables.CFScaleOffsetCoder()
111165
encoded = coder.encode(original)
@@ -115,7 +169,7 @@ def test_scaling_converts_to_float32(
115169
assert roundtripped.dtype == np.float32
116170

117171

118-
@pytest.mark.parametrize("unpacked_dtype", [np.float32, np.float64, np.int32])
172+
@pytest.mark.parametrize("unpacked_dtype", "f4 f8 i4".split())
119173
@pytest.mark.parametrize("packed_dtype", "u1 u2 i1 i2 f2 f4".split())
120174
def test_scaling_converts_to_float64(
121175
packed_dtype: str, unpacked_dtype: type[np.number]
@@ -125,7 +179,7 @@ def test_scaling_converts_to_float64(
125179
original = xr.Variable(
126180
("x",),
127181
np.arange(10, dtype=packed_dtype),
128-
encoding=dict(add_offset=unpacked_dtype(10)),
182+
encoding=dict(add_offset=np.dtype(unpacked_dtype).type(10)),
129183
)
130184
coder = variables.CFScaleOffsetCoder()
131185
encoded = coder.encode(original)
@@ -139,7 +193,7 @@ def test_scaling_converts_to_float64(
139193
@pytest.mark.parametrize("add_offset", (0.1, [0.1]))
140194
def test_scaling_offset_as_list(scale_factor, add_offset) -> None:
141195
# test for #4631
142-
# att: scale_factor and add_offset are not conforming to cf specs here
196+
# attention: scale_factor and add_offset are not conforming to cf specs here
143197
encoding = dict(scale_factor=scale_factor, add_offset=add_offset)
144198
original = xr.Variable(("x",), np.arange(10.0), encoding=encoding)
145199
coder = variables.CFScaleOffsetCoder()

0 commit comments

Comments
 (0)