Skip to content

Commit 102d0a5

Browse files
committed
Impl: ignore_missing option on get_flag, v0.3.1
Request from @A-Mahon: Add a method to ncflag that combines the functionality of find_flag and get_flag, flattening the arrays as in get_flag, but not raising an exception if any of the flags are not present.
1 parent 94fef3c commit 102d0a5

File tree

5 files changed

+56
-56
lines changed

5 files changed

+56
-56
lines changed

ncflag/flag_wrapper.py

+24-18
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,15 @@ def init_from_netcdf(cls, nc_var, shape=None, fill=None):
8787
nc_var.flag_meanings,
8888
nc_var.flag_values,
8989
getattr(nc_var, "flag_masks", None),
90-
name=nc_var.name
90+
name=nc_var.name,
9191
)
9292
else:
9393
instance = cls(
9494
nc_var[:],
9595
nc_var.flag_meanings,
9696
nc_var.flag_values,
9797
getattr(nc_var, "flag_masks", None),
98-
name=nc_var.name
98+
name=nc_var.name,
9999
)
100100

101101
instance._nc_var = nc_var
@@ -127,26 +127,31 @@ def write_to_netcdf(self, nc_var=None):
127127
# only write masks if they aren't the default all bits 1 or somethign existed before
128128
nc_var.flag_masks = self._flag_masks
129129

130-
def get_flag(self, flag_meaning):
130+
def get_flag(self, flag_meaning, ignore_missing=False):
131131
"""
132132
Get an array of booleans, same length as flags, at each index indicating if flag_meaning was set.
133133
134134
:type flag_meaning: str | list[str] | tuple[str]
135-
:param flag_meaning: flag meaning(s) to be looked up
135+
:param flag_meaning: flag meaning(s) to be looked up, bitwise_or reduced across first axis if multiple
136+
:type ignore_missing: bool
137+
:param ignore_missing: if False, raises exception for missing flags, if True assumes missing
138+
flags are not set.
136139
:rtype: np.array
137140
:return: array of booleans where flag_meaning(s) is(are) set
138141
"""
139142

140143
def get(meaning):
141-
""" Get the meaning of an individual flag_meaning. """
144+
"""Get the meaning of an individual flag_meaning."""
145+
if ignore_missing and meaning not in self.flag_meanings:
146+
return np.full_like(self.flags, False, dtype=bool)
142147
index = self.flag_meanings.index(meaning)
143148

144149
# start by default assuming there are no flags set.
145150
# Do not return a masked array! All value should be either True or False.
146151
# A flag is either set or not set. There is no inbetween.
147-
default = np.full(self.flags.shape, False, dtype=np.bool)
152+
default = np.full(self.flags.shape, False, dtype=bool)
148153
if not np.ma.is_masked(self.flags):
149-
mask = np.full_like(self.flags, True, np.bool)
154+
mask = np.full_like(self.flags, True, dtype=bool)
150155
else:
151156
mask = ~np.ma.getmask(self.flags)
152157

@@ -156,15 +161,15 @@ def get(meaning):
156161
) == self._flag_values[index]
157162
return default
158163

164+
any_set = np.zeros(self.flags.shape, dtype=bool)
159165
if isinstance(flag_meaning, (list, tuple)):
160166
# if receive a sequence, or them together.
161-
any_set = np.zeros(self.flags.shape, dtype=np.bool)
162167
for each in flag_meaning:
163168
any_set |= get(each)
164-
return any_set
165169
else:
166170
# otherwise just get the one.
167-
return get(flag_meaning)
171+
any_set |= get(flag_meaning)
172+
return any_set
168173

169174
def reduce(self, exclude_mask=0, axis=-1):
170175
"""
@@ -183,7 +188,9 @@ def reduce(self, exclude_mask=0, axis=-1):
183188
if exclude_mask != 0:
184189
exclude_mask = self.flags.dtype.type(exclude_mask)
185190
excluded_flags_not_set = (self.flags & exclude_mask) == 0
186-
new_flags = np.ma.bitwise_or.reduce(self.flags * excluded_flags_not_set, axis=axis)
191+
new_flags = np.ma.bitwise_or.reduce(
192+
self.flags * excluded_flags_not_set, axis=axis
193+
)
187194
else:
188195
new_flags = np.ma.bitwise_or.reduce(self.flags, axis=axis)
189196

@@ -197,7 +204,7 @@ def reduce(self, exclude_mask=0, axis=-1):
197204
def get_flag_at_index(self, flag_meaning, i):
198205
"""
199206
Returns True or False if flag_meaning set at index i?
200-
207+
201208
:type flag_meaning: str
202209
:param flag_meaning: flag meaning intended to be set
203210
:type i: int
@@ -215,7 +222,7 @@ def get_flag_at_index(self, flag_meaning, i):
215222
def get_flags_set_at_index(self, i, exit_on_good=False):
216223
"""
217224
Get a list of the flag_meanings set at a particular index.
218-
225+
219226
:type i: int
220227
:param i: the index to examine
221228
:type exit_on_good: bool
@@ -279,7 +286,7 @@ def set_flag(self, flag_meaning, should_be_set, zero_if_unset=False):
279286
:return: None
280287
"""
281288
index = self.flag_meanings.index(flag_meaning)
282-
bool_flags = np.array(should_be_set).astype(np.bool)
289+
bool_flags = np.array(should_be_set).astype(bool)
283290
if np.ma.is_masked(self.flags):
284291
# if it's masked, set initial flag to 0 where going to set flags (from bool_flags)
285292
masked_modify = bool_flags & np.ma.getmaskarray(self.flags) | zero_if_unset
@@ -300,7 +307,7 @@ def set_flag_at_index(self, flag_meaning, i):
300307
AND the original flag with the NOT mask, to 0 out any bits impacting the target location
301308
of the flag_meaning within the bit vec while leaving the rest set or unset as they were.
302309
Then, OR the flag value onto the target, preserves all other independent flags set.
303-
310+
304311
:type flag_meaning: str
305312
:param flag_meaning: flag meaning intended to be set
306313
:type i: int
@@ -325,7 +332,7 @@ def get_value_for_meaning(self, flag_meaning):
325332
eg:
326333
>>> f = FlagWrap([], "good bad", [0, 1])
327334
>>> f.flags = np.full(10, f.get_value_for_meaning("bad"), dtype=np.uint)
328-
335+
329336
Note: Raises ValueError if flag_meaning is not found.
330337
331338
:type flag_meaning: str
@@ -347,7 +354,7 @@ def get_mask_for_meaning(self, flag_meaning):
347354
masks together in order to test if *any* of those flags are set.
348355
349356
Note: Raises ValueError if flag_meaning not found.
350-
357+
351358
:type flag_meaning: str
352359
:param flag_meaning: string flag name to return corrsponding mask of
353360
:rtype: int
@@ -369,4 +376,3 @@ def is_valid_meaning(self, flag_meaning):
369376
:return: if flag_meaning is a valid meaning for the flag.
370377
"""
371378
return flag_meaning in self.flag_meanings
372-

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
setup(
55
name="ncflag",
6-
version="0.3.0",
6+
version="0.3.1",
77
description="Utility and library to interface with CF-Compliant NetCDF flag variables.",
88
author="Stefan Codrescu",
99
author_email="[email protected]",

test/test_misc_api.py

+15-10
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,24 @@
44

55

66
class TestApi(TestCase):
7+
def setUp(self):
8+
self.flags = np.array([0, 0, 1, 2, 3], dtype=np.ubyte)
9+
self.flag_meanings = "good medium bad extra_bad"
10+
self.flag_values = np.array([0, 1, 2, 3])
11+
self.f = FlagWrap(self.flags, self.flag_meanings, self.flag_values)
712

813
def test_valid_meaning(self):
9-
flags = np.array([0, 0, 1, 2, 3], dtype=np.ubyte)
10-
flag_meanings = "good medium bad extra_bad"
11-
flag_values = np.array([0, 1, 2, 3])
12-
f = FlagWrap(flags, flag_meanings, flag_values)
13-
14-
for flag_meaning in flag_meanings.split():
15-
self.assertTrue(f.is_valid_meaning(flag_meaning))
14+
for flag_meaning in self.flag_meanings.split():
15+
self.assertTrue(self.f.is_valid_meaning(flag_meaning))
1616

1717
for not_a_meaning in ["test", "not", "valid", "good1", "extra"]:
18-
self.assertFalse(f.is_valid_meaning(not_a_meaning))
19-
20-
18+
self.assertFalse(self.f.is_valid_meaning(not_a_meaning))
2119

20+
def test_get_flag_on_missing_meaning(self):
21+
for not_a_meaning in ["test", "not", "valid", "good1", "extra"]:
22+
flags = self.f.get_flag(not_a_meaning, ignore_missing=True)
23+
self.assertEqual(np.count_nonzero(flags), 0)
2224

25+
with self.assertRaises(ValueError):
26+
# check backwards compat: missing meaning raises
27+
self.f.get_flag(not_a_meaning, ignore_missing=False)

test/test_reduce.py

+12-23
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,17 @@
66
class TestReduce(TestCase):
77
def test_reduce_axis0(self):
88

9-
flags = np.array([
10-
[0, 1],
11-
[1, 0],
12-
[0, 0],
13-
[1, 1]
14-
], dtype=np.ubyte)
9+
flags = np.array([[0, 1], [1, 0], [0, 0], [1, 1]], dtype=np.ubyte)
1510

1611
f = FlagWrap(flags, "good bad", [0, 1])
1712

1813
f_reduced = f.reduce(axis=0)
1914
np.testing.assert_array_equal(f_reduced.get_flag("good"), [0, 0])
2015
np.testing.assert_array_equal(f_reduced.get_flag("bad"), [1, 1])
2116

22-
2317
def test_reduce_axis1(self):
2418

25-
flags = np.array([
26-
[0, 1],
27-
[1, 0],
28-
[0, 0],
29-
[1, 1]
30-
], dtype=np.ubyte)
19+
flags = np.array([[0, 1], [1, 0], [0, 0], [1, 1]], dtype=np.ubyte)
3120

3221
f = FlagWrap(flags, "good bad", [0, 1])
3322

@@ -37,12 +26,15 @@ def test_reduce_axis1(self):
3726

3827
def test_reduce_mask(self):
3928

40-
flags = np.array([
41-
[3, 1], # red + green, red
42-
[0, 4], # ---, blue
43-
[4, 3], # blue, red + green
44-
[2, 1] # green, red
45-
], dtype=np.ubyte)
29+
flags = np.array(
30+
[
31+
[3, 1], # red + green, red
32+
[0, 4], # ---, blue
33+
[4, 3], # blue, red + green
34+
[2, 1], # green, red
35+
],
36+
dtype=np.ubyte,
37+
)
4638

4739
f = FlagWrap(flags, "red green blue", [1, 2, 4], [1, 2, 4])
4840

@@ -51,11 +43,8 @@ def test_reduce_mask(self):
5143
f_reduced = f.reduce(axis=1, exclude_mask=1)
5244

5345
# there should be no red, since we masked it
54-
np.testing.assert_array_equal(f_reduced.get_flag("red"), [0, 0, 0, 0])
46+
np.testing.assert_array_equal(f_reduced.get_flag("red"), [0, 0, 0, 0])
5547

5648
# make sure green and blue got through, when they weren't mixed with red
5749
np.testing.assert_array_equal(f_reduced.get_flag("green"), [0, 0, 0, 1])
5850
np.testing.assert_array_equal(f_reduced.get_flag("blue"), [0, 1, 1, 0])
59-
60-
61-

test/test_theoretical.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ def test_teject_misformed_flags(self):
1414
FlagWrap(np.array([]), "flag1 flag2", [1, 2], [-1, -1, -1])
1515

1616
def test_exclusive_flag_type(self):
17-
""" A barrage of tests to make sure everything works properly for flag variables defined
18-
so that every flag_meaning is mutually exclusive. """
17+
"""A barrage of tests to make sure everything works properly for flag variables defined
18+
so that every flag_meaning is mutually exclusive."""
1919

2020
original_flags = np.array([0, 0, 1, 2, 3, -1], dtype=np.ubyte)
2121

@@ -122,9 +122,9 @@ def test_exclusive_flag_type(self):
122122
# ok, we'll call it good there.
123123

124124
def test_maskedarray_initial_flags(self):
125-
""" Similar to the previous test, except start with a masked array and fill...
125+
"""Similar to the previous test, except start with a masked array and fill...
126126
This will be what it looks like if someone does init_from_netcdf for an unwritten variable
127-
that actually has shape. """
127+
that actually has shape."""
128128

129129
original_flags = np.ma.zeros(5, dtype=np.ubyte)
130130
original_flags.mask = True

0 commit comments

Comments
 (0)