We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Masking Tools could be a lot more user friendly
`datacube.storage.masking.describe_variable_flags()` should return an object with not only `__repr__()` but also `_repr_html_()` defined, so that the table of information is legible in a jupyter notebook (like how pandas works). 

Current output is about 50 lines illegibly formatted like this:
```
'Flag name          | Description                                      | Bit. No                                        | 
Value | Meaning        \n-------------------+--------------------------------------------------+-------------------------
-----------------------+-------+----------------\nga_good_pixel      | Best Quality Pixel                               |
[13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] | 16383 | True           \nblue_saturated     | Blue band is saturated 
| 0                                              | 0     | True           \n                   |       
| 0                                              | 1     | False          \n-------------------+-------------------------
-------------------------+------------------------------------------------+-------+----------------\ngreen_saturated    |
Green band is saturated                          | 1                                              | 0     | True  
\n                   |                                                  | 1  
```

This would also be an opportunity to simplify the masking api. For example, once you retrieve this pretty-displaying object, actually generating the mask could be as simple as reading an attribute/property from the same object. 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `datacube/storage/masking.py`
Content:
```
1 """
2 Tools for masking data based on a bit-mask variable with attached definition.
3 
4 The main functions are `make_mask(variable)` `describe_flags(variable)`
5 """
6 from __future__ import absolute_import
7 
8 import collections
9 import warnings
10 
11 from datacube.utils import generate_table
12 
13 from xarray import DataArray, Dataset
14 
15 FLAGS_ATTR_NAME = 'flags_definition'
16 
17 
18 def list_flag_names(variable):
19     """
20     Returns the available masking flags for the variable
21 
22     :param variable: Masking xarray.Dataset or xarray.DataArray
23     :return: list
24     """
25     flags_def = get_flags_def(variable)
26     return sorted(list(flags_def.keys()))
27 
28 
29 def describe_variable_flags(variable):
30     """
31     Returns a string describing the available flags for a masking variable
32 
33     Interprets the `flags_definition` attribute on the provided variable and returns
34     a string like::
35 
36         Bits are listed from the MSB (bit 13) to the LSB (bit 0)
37         Bit     Value   Flag Name            Description
38         13      0       cloud_shadow_fmask   Cloud Shadow (Fmask)
39         12      0       cloud_shadow_acca    Cloud Shadow (ACCA)
40         11      0       cloud_fmask          Cloud (Fmask)
41         10      0       cloud_acca           Cloud (ACCA)
42 
43     :param variable: Masking xarray.Dataset or xarray.DataArray
44     :return: str
45     """
46     flags_def = get_flags_def(variable)
47 
48     return describe_flags_def(flags_def)
49 
50 
51 def describe_flags_def(flags_def):
52     return '\n'.join(generate_table(list(_table_contents(flags_def))))
53 
54 
55 def _table_contents(flags_def):
56     yield 'Flag name', 'Description', 'Bit. No', 'Value', 'Meaning'
57     for name, defn in sorted(flags_def.items(), key=_order_bitdefs_by_bits):
58         name, desc = name, defn['description']
59         for value, meaning in defn['values'].items():
60             yield name, desc, str(defn['bits']), str(value), str(meaning)
61             name, desc = '', ''
62 
63 
64 def _order_bitdefs_by_bits(bitdef):
65     name, defn = bitdef
66     try:
67         return min(defn['bits'])
68     except TypeError:
69         return defn['bits']
70 
71 
72 def make_mask(variable, **flags):
73     """
74     Returns a mask array, based on provided flags
75 
76     When multiple flags are provided, they will be combined in a logical AND fashion.
77 
78     For example:
79 
80     >>> make_mask(pqa, cloud_acca=False, cloud_fmask=False, land_obs=True) # doctest: +SKIP
81 
82     OR
83 
84     >>> make_mask(pqa, **GOOD_PIXEL_FLAGS) # doctest: +SKIP
85 
86     where `GOOD_PIXEL_FLAGS` is a dict of flag_name to True/False
87 
88     :param variable:
89     :type variable: xarray.Dataset or xarray.DataArray
90     :param flags: list of boolean flags
91     :return: boolean xarray.DataArray or xarray.Dataset
92     """
93     flags_def = get_flags_def(variable)
94 
95     mask, mask_value = create_mask_value(flags_def, **flags)
96 
97     return variable & mask == mask_value
98 
99 
100 def valid_data_mask(data):
101     """
102     Returns bool arrays where the data is not `nodata`
103 
104     :param Dataset or DataArray data:
105     :return: Dataset or DataArray
106     """
107     if isinstance(data, Dataset):
108         return data.apply(valid_data_mask)
109 
110     if isinstance(data, DataArray):
111         if 'nodata' not in data.attrs:
112             return True
113         return data != data.nodata
114 
115     raise TypeError('valid_data_mask not supported for type {}'.format(type(data)))
116 
117 
118 def mask_valid_data(data, keep_attrs=True):
119     """
120     Deprecated. This function was poorly named. It is now available as `mask_invalid_data`.
121     """
122     warnings.warn("`mask_valid_data` has been renamed to `mask_invalid_data`. Please use that instead.",
123                   DeprecationWarning)
124     return mask_invalid_data(data, keep_attrs=keep_attrs)
125 
126 
127 def mask_invalid_data(data, keep_attrs=True):
128     """
129     Sets all `nodata` values to ``nan``.
130 
131     This will convert converts numeric data to type `float`.
132 
133     :param Dataset or DataArray data:
134     :param bool keep_attrs: If the attributes of the data should be included in the returned .
135     :return: Dataset or DataArray
136     """
137     if isinstance(data, Dataset):
138         # Pass keep_attrs as a positional arg to the DataArray func
139         return data.apply(mask_invalid_data, keep_attrs=keep_attrs, args=(keep_attrs,))
140 
141     if isinstance(data, DataArray):
142         if 'nodata' not in data.attrs:
143             return data
144         out_data_array = data.where(data != data.nodata)
145         if keep_attrs:
146             out_data_array.attrs = data.attrs
147         return out_data_array
148 
149     raise TypeError('mask_invalid_data not supported for type {}'.format(type(data)))
150 
151 
152 def create_mask_value(bits_def, **flags):
153     mask = 0
154     value = 0
155 
156     for flag_name, flag_ref in flags.items():
157         defn = bits_def[flag_name]
158 
159         try:
160             [flag_value] = (bit_val
161                             for bit_val, val_ref in defn['values'].items()
162                             if val_ref == flag_ref)
163             flag_value = int(flag_value)  # Might be string if coming from DB
164         except ValueError:
165             raise ValueError('Unknown value %s specified for flag %s' %
166                              (flag_ref, flag_name))
167 
168         if isinstance(defn['bits'], collections.Iterable):  # Multi-bit flag
169             # Set mask
170             for bit in defn['bits']:
171                 mask = set_value_at_index(mask, bit, True)
172 
173             shift = min(defn['bits'])
174             real_val = flag_value << shift
175 
176             value |= real_val
177 
178         else:
179             bit = defn['bits']
180             mask = set_value_at_index(mask, bit, True)
181             value = set_value_at_index(value, bit, flag_value)
182 
183     return mask, value
184 
185 
186 def mask_to_dict(bits_def, mask_value):
187     """
188     Describes which flags are set for a mask value
189 
190     :param bits_def:
191     :param mask_value:
192     :return: Mapping of flag_name -> set_value
193     :rtype: dict
194     """
195     return_dict = {}
196     for flag_name, flag_defn in bits_def.items():
197 
198         # Make bits a list, even if there is only one
199         flag_bits = flag_defn['bits']
200         if not isinstance(flag_defn['bits'], list):
201             flag_bits = [flag_bits]
202 
203         # The amount to shift flag_value to line up with mask_value
204         flag_shift = min(flag_bits)
205 
206         # Mask our mask_value, we are only interested in the bits for this flag
207         flag_mask = 0
208         for i in flag_bits:
209             flag_mask |= (1 << i)
210         masked_mask_value = mask_value & flag_mask
211 
212         for flag_value, value in flag_defn['values'].items():
213             shifted_value = int(flag_value) << flag_shift
214             if shifted_value == masked_mask_value:
215                 assert flag_name not in return_dict
216                 return_dict[flag_name] = value
217     return return_dict
218 
219 
220 def _get_minimum_bit(bit_or_bits):
221     try:
222         return min(bit_or_bits)
223     except TypeError:
224         return bit_or_bits
225 
226 
227 def get_flags_def(variable):
228     try:
229         return getattr(variable, FLAGS_ATTR_NAME)
230     except AttributeError:
231         # Maybe we have a DataSet, not a DataArray
232         for var in variable.data_vars.values():
233             if _is_data_var(var):
234                 try:
235                     return getattr(var, FLAGS_ATTR_NAME)
236                 except AttributeError:
237                     pass
238 
239         raise ValueError('No masking variable found')
240 
241 
242 def _is_data_var(variable):
243     return variable.name != 'crs' and len(variable.coords) > 1
244 
245 
246 def set_value_at_index(bitmask, index, value):
247     """
248     Set a bit value onto an integer bitmask
249 
250     eg. set bits 2 and 4 to True
251     >>> mask = 0
252     >>> mask = set_value_at_index(mask, 2, True)
253     >>> mask = set_value_at_index(mask, 4, True)
254     >>> print(bin(mask))
255     0b10100
256     >>> mask = set_value_at_index(mask, 2, False)
257     >>> print(bin(mask))
258     0b10000
259 
260     :param bitmask: existing int bitmask to alter
261     :type bitmask: int
262     :type index: int
263     :type value: bool
264     """
265     bit_val = 2 ** index
266     if value:
267         bitmask |= bit_val
268     else:
269         bitmask &= (~bit_val)
270     return bitmask
271
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/datacube/storage/masking.py b/datacube/storage/masking.py
--- a/datacube/storage/masking.py
+++ b/datacube/storage/masking.py
@@ -7,6 +7,7 @@
 
 import collections
 import warnings
+import pandas
 
 from datacube.utils import generate_table
 
@@ -26,12 +27,13 @@
     return sorted(list(flags_def.keys()))
 
 
-def describe_variable_flags(variable):
+def describe_variable_flags(variable, with_pandas=True):
     """
-    Returns a string describing the available flags for a masking variable
+    Returns either a Pandas Dataframe (with_pandas=True - default) or a string
+    (with_pandas=False) describing the available flags for a masking variable
 
-    Interprets the `flags_definition` attribute on the provided variable and returns
-    a string like::
+    Interprets the `flags_definition` attribute on the provided variable and
+    returns a Pandas Dataframe or string like::
 
         Bits are listed from the MSB (bit 13) to the LSB (bit 0)
         Bit     Value   Flag Name            Description
@@ -41,11 +43,14 @@
         10      0       cloud_acca           Cloud (ACCA)
 
     :param variable: Masking xarray.Dataset or xarray.DataArray
-    :return: str
+    :return: Pandas Dataframe or str
     """
     flags_def = get_flags_def(variable)
 
-    return describe_flags_def(flags_def)
+    if not with_pandas:
+        return describe_flags_def(flags_def)
+
+    return pandas.DataFrame.from_dict(flags_def, orient='index')
 
 
 def describe_flags_def(flags_def):
@@ -128,7 +133,7 @@
     """
     Sets all `nodata` values to ``nan``.
 
-    This will convert converts numeric data to type `float`.
+    This will convert numeric data to type `float`.
 
     :param Dataset or DataArray data:
     :param bool keep_attrs: If the attributes of the data should be included in the returned .

{"golden_diff": "diff --git a/datacube/storage/masking.py b/datacube/storage/masking.py\n--- a/datacube/storage/masking.py\n+++ b/datacube/storage/masking.py\n@@ -7,6 +7,7 @@\n \n import collections\n import warnings\n+import pandas\n \n from datacube.utils import generate_table\n \n@@ -26,12 +27,13 @@\n     return sorted(list(flags_def.keys()))\n \n \n-def describe_variable_flags(variable):\n+def describe_variable_flags(variable, with_pandas=True):\n     \"\"\"\n-    Returns a string describing the available flags for a masking variable\n+    Returns either a Pandas Dataframe (with_pandas=True - default) or a string\n+    (with_pandas=False) describing the available flags for a masking variable\n \n-    Interprets the `flags_definition` attribute on the provided variable and returns\n-    a string like::\n+    Interprets the `flags_definition` attribute on the provided variable and\n+    returns a Pandas Dataframe or string like::\n \n         Bits are listed from the MSB (bit 13) to the LSB (bit 0)\n         Bit     Value   Flag Name            Description\n@@ -41,11 +43,14 @@\n         10      0       cloud_acca           Cloud (ACCA)\n \n     :param variable: Masking xarray.Dataset or xarray.DataArray\n-    :return: str\n+    :return: Pandas Dataframe or str\n     \"\"\"\n     flags_def = get_flags_def(variable)\n \n-    return describe_flags_def(flags_def)\n+    if not with_pandas:\n+        return describe_flags_def(flags_def)\n+\n+    return pandas.DataFrame.from_dict(flags_def, orient='index')\n \n \n def describe_flags_def(flags_def):\n@@ -128,7 +133,7 @@\n     \"\"\"\n     Sets all `nodata` values to ``nan``.\n \n-    This will convert converts numeric data to type `float`.\n+    This will convert numeric data to type `float`.\n \n     :param Dataset or DataArray data:\n     :param bool keep_attrs: If the attributes of the data should be included in the returned .\n", "issue": "Masking Tools could be a lot more user friendly\n`datacube.storage.masking.describe_variable_flags()` should return an object with not only `__repr__()` but also `_repr_html_()` defined, so that the table of information is legible in a jupyter notebook (like how pandas works). \r\n\r\nCurrent output is about 50 lines illegibly formatted like this:\r\n```\r\n'Flag name          | Description                                      | Bit. No                                        | \r\nValue | Meaning        \\n-------------------+--------------------------------------------------+-------------------------\r\n-----------------------+-------+----------------\\nga_good_pixel      | Best Quality Pixel                               |\r\n[13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] | 16383 | True           \\nblue_saturated     | Blue band is saturated \r\n| 0                                              | 0     | True           \\n                   |       \r\n| 0                                              | 1     | False          \\n-------------------+-------------------------\r\n-------------------------+------------------------------------------------+-------+----------------\\ngreen_saturated    |\r\nGreen band is saturated                          | 1                                              | 0     | True  \r\n\\n                   |                                                  | 1  \r\n```\r\n\r\nThis would also be an opportunity to simplify the masking api. For example, once you retrieve this pretty-displaying object, actually generating the mask could be as simple as reading an attribute/property from the same object. \n", "before_files": [{"content": "\"\"\"\nTools for masking data based on a bit-mask variable with attached definition.\n\nThe main functions are `make_mask(variable)` `describe_flags(variable)`\n\"\"\"\nfrom __future__ import absolute_import\n\nimport collections\nimport warnings\n\nfrom datacube.utils import generate_table\n\nfrom xarray import DataArray, Dataset\n\nFLAGS_ATTR_NAME = 'flags_definition'\n\n\ndef list_flag_names(variable):\n    \"\"\"\n    Returns the available masking flags for the variable\n\n    :param variable: Masking xarray.Dataset or xarray.DataArray\n    :return: list\n    \"\"\"\n    flags_def = get_flags_def(variable)\n    return sorted(list(flags_def.keys()))\n\n\ndef describe_variable_flags(variable):\n    \"\"\"\n    Returns a string describing the available flags for a masking variable\n\n    Interprets the `flags_definition` attribute on the provided variable and returns\n    a string like::\n\n        Bits are listed from the MSB (bit 13) to the LSB (bit 0)\n        Bit     Value   Flag Name            Description\n        13      0       cloud_shadow_fmask   Cloud Shadow (Fmask)\n        12      0       cloud_shadow_acca    Cloud Shadow (ACCA)\n        11      0       cloud_fmask          Cloud (Fmask)\n        10      0       cloud_acca           Cloud (ACCA)\n\n    :param variable: Masking xarray.Dataset or xarray.DataArray\n    :return: str\n    \"\"\"\n    flags_def = get_flags_def(variable)\n\n    return describe_flags_def(flags_def)\n\n\ndef describe_flags_def(flags_def):\n    return '\\n'.join(generate_table(list(_table_contents(flags_def))))\n\n\ndef _table_contents(flags_def):\n    yield 'Flag name', 'Description', 'Bit. No', 'Value', 'Meaning'\n    for name, defn in sorted(flags_def.items(), key=_order_bitdefs_by_bits):\n        name, desc = name, defn['description']\n        for value, meaning in defn['values'].items():\n            yield name, desc, str(defn['bits']), str(value), str(meaning)\n            name, desc = '', ''\n\n\ndef _order_bitdefs_by_bits(bitdef):\n    name, defn = bitdef\n    try:\n        return min(defn['bits'])\n    except TypeError:\n        return defn['bits']\n\n\ndef make_mask(variable, **flags):\n    \"\"\"\n    Returns a mask array, based on provided flags\n\n    When multiple flags are provided, they will be combined in a logical AND fashion.\n\n    For example:\n\n    >>> make_mask(pqa, cloud_acca=False, cloud_fmask=False, land_obs=True) # doctest: +SKIP\n\n    OR\n\n    >>> make_mask(pqa, **GOOD_PIXEL_FLAGS) # doctest: +SKIP\n\n    where `GOOD_PIXEL_FLAGS` is a dict of flag_name to True/False\n\n    :param variable:\n    :type variable: xarray.Dataset or xarray.DataArray\n    :param flags: list of boolean flags\n    :return: boolean xarray.DataArray or xarray.Dataset\n    \"\"\"\n    flags_def = get_flags_def(variable)\n\n    mask, mask_value = create_mask_value(flags_def, **flags)\n\n    return variable & mask == mask_value\n\n\ndef valid_data_mask(data):\n    \"\"\"\n    Returns bool arrays where the data is not `nodata`\n\n    :param Dataset or DataArray data:\n    :return: Dataset or DataArray\n    \"\"\"\n    if isinstance(data, Dataset):\n        return data.apply(valid_data_mask)\n\n    if isinstance(data, DataArray):\n        if 'nodata' not in data.attrs:\n            return True\n        return data != data.nodata\n\n    raise TypeError('valid_data_mask not supported for type {}'.format(type(data)))\n\n\ndef mask_valid_data(data, keep_attrs=True):\n    \"\"\"\n    Deprecated. This function was poorly named. It is now available as `mask_invalid_data`.\n    \"\"\"\n    warnings.warn(\"`mask_valid_data` has been renamed to `mask_invalid_data`. Please use that instead.\",\n                  DeprecationWarning)\n    return mask_invalid_data(data, keep_attrs=keep_attrs)\n\n\ndef mask_invalid_data(data, keep_attrs=True):\n    \"\"\"\n    Sets all `nodata` values to ``nan``.\n\n    This will convert converts numeric data to type `float`.\n\n    :param Dataset or DataArray data:\n    :param bool keep_attrs: If the attributes of the data should be included in the returned .\n    :return: Dataset or DataArray\n    \"\"\"\n    if isinstance(data, Dataset):\n        # Pass keep_attrs as a positional arg to the DataArray func\n        return data.apply(mask_invalid_data, keep_attrs=keep_attrs, args=(keep_attrs,))\n\n    if isinstance(data, DataArray):\n        if 'nodata' not in data.attrs:\n            return data\n        out_data_array = data.where(data != data.nodata)\n        if keep_attrs:\n            out_data_array.attrs = data.attrs\n        return out_data_array\n\n    raise TypeError('mask_invalid_data not supported for type {}'.format(type(data)))\n\n\ndef create_mask_value(bits_def, **flags):\n    mask = 0\n    value = 0\n\n    for flag_name, flag_ref in flags.items():\n        defn = bits_def[flag_name]\n\n        try:\n            [flag_value] = (bit_val\n                            for bit_val, val_ref in defn['values'].items()\n                            if val_ref == flag_ref)\n            flag_value = int(flag_value)  # Might be string if coming from DB\n        except ValueError:\n            raise ValueError('Unknown value %s specified for flag %s' %\n                             (flag_ref, flag_name))\n\n        if isinstance(defn['bits'], collections.Iterable):  # Multi-bit flag\n            # Set mask\n            for bit in defn['bits']:\n                mask = set_value_at_index(mask, bit, True)\n\n            shift = min(defn['bits'])\n            real_val = flag_value << shift\n\n            value |= real_val\n\n        else:\n            bit = defn['bits']\n            mask = set_value_at_index(mask, bit, True)\n            value = set_value_at_index(value, bit, flag_value)\n\n    return mask, value\n\n\ndef mask_to_dict(bits_def, mask_value):\n    \"\"\"\n    Describes which flags are set for a mask value\n\n    :param bits_def:\n    :param mask_value:\n    :return: Mapping of flag_name -> set_value\n    :rtype: dict\n    \"\"\"\n    return_dict = {}\n    for flag_name, flag_defn in bits_def.items():\n\n        # Make bits a list, even if there is only one\n        flag_bits = flag_defn['bits']\n        if not isinstance(flag_defn['bits'], list):\n            flag_bits = [flag_bits]\n\n        # The amount to shift flag_value to line up with mask_value\n        flag_shift = min(flag_bits)\n\n        # Mask our mask_value, we are only interested in the bits for this flag\n        flag_mask = 0\n        for i in flag_bits:\n            flag_mask |= (1 << i)\n        masked_mask_value = mask_value & flag_mask\n\n        for flag_value, value in flag_defn['values'].items():\n            shifted_value = int(flag_value) << flag_shift\n            if shifted_value == masked_mask_value:\n                assert flag_name not in return_dict\n                return_dict[flag_name] = value\n    return return_dict\n\n\ndef _get_minimum_bit(bit_or_bits):\n    try:\n        return min(bit_or_bits)\n    except TypeError:\n        return bit_or_bits\n\n\ndef get_flags_def(variable):\n    try:\n        return getattr(variable, FLAGS_ATTR_NAME)\n    except AttributeError:\n        # Maybe we have a DataSet, not a DataArray\n        for var in variable.data_vars.values():\n            if _is_data_var(var):\n                try:\n                    return getattr(var, FLAGS_ATTR_NAME)\n                except AttributeError:\n                    pass\n\n        raise ValueError('No masking variable found')\n\n\ndef _is_data_var(variable):\n    return variable.name != 'crs' and len(variable.coords) > 1\n\n\ndef set_value_at_index(bitmask, index, value):\n    \"\"\"\n    Set a bit value onto an integer bitmask\n\n    eg. set bits 2 and 4 to True\n    >>> mask = 0\n    >>> mask = set_value_at_index(mask, 2, True)\n    >>> mask = set_value_at_index(mask, 4, True)\n    >>> print(bin(mask))\n    0b10100\n    >>> mask = set_value_at_index(mask, 2, False)\n    >>> print(bin(mask))\n    0b10000\n\n    :param bitmask: existing int bitmask to alter\n    :type bitmask: int\n    :type index: int\n    :type value: bool\n    \"\"\"\n    bit_val = 2 ** index\n    if value:\n        bitmask |= bit_val\n    else:\n        bitmask &= (~bit_val)\n    return bitmask\n", "path": "datacube/storage/masking.py"}], "after_files": [{"content": "\"\"\"\nTools for masking data based on a bit-mask variable with attached definition.\n\nThe main functions are `make_mask(variable)` `describe_flags(variable)`\n\"\"\"\nfrom __future__ import absolute_import\n\nimport collections\nimport warnings\nimport pandas\n\nfrom datacube.utils import generate_table\n\nfrom xarray import DataArray, Dataset\n\nFLAGS_ATTR_NAME = 'flags_definition'\n\n\ndef list_flag_names(variable):\n    \"\"\"\n    Returns the available masking flags for the variable\n\n    :param variable: Masking xarray.Dataset or xarray.DataArray\n    :return: list\n    \"\"\"\n    flags_def = get_flags_def(variable)\n    return sorted(list(flags_def.keys()))\n\n\ndef describe_variable_flags(variable, with_pandas=True):\n    \"\"\"\n    Returns either a Pandas Dataframe (with_pandas=True - default) or a string\n    (with_pandas=False) describing the available flags for a masking variable\n\n    Interprets the `flags_definition` attribute on the provided variable and\n    returns a Pandas Dataframe or string like::\n\n        Bits are listed from the MSB (bit 13) to the LSB (bit 0)\n        Bit     Value   Flag Name            Description\n        13      0       cloud_shadow_fmask   Cloud Shadow (Fmask)\n        12      0       cloud_shadow_acca    Cloud Shadow (ACCA)\n        11      0       cloud_fmask          Cloud (Fmask)\n        10      0       cloud_acca           Cloud (ACCA)\n\n    :param variable: Masking xarray.Dataset or xarray.DataArray\n    :return: Pandas Dataframe or str\n    \"\"\"\n    flags_def = get_flags_def(variable)\n\n    if not with_pandas:\n        return describe_flags_def(flags_def)\n\n    return pandas.DataFrame.from_dict(flags_def, orient='index')\n\n\ndef describe_flags_def(flags_def):\n    return '\\n'.join(generate_table(list(_table_contents(flags_def))))\n\n\ndef _table_contents(flags_def):\n    yield 'Flag name', 'Description', 'Bit. No', 'Value', 'Meaning'\n    for name, defn in sorted(flags_def.items(), key=_order_bitdefs_by_bits):\n        name, desc = name, defn['description']\n        for value, meaning in defn['values'].items():\n            yield name, desc, str(defn['bits']), str(value), str(meaning)\n            name, desc = '', ''\n\n\ndef _order_bitdefs_by_bits(bitdef):\n    name, defn = bitdef\n    try:\n        return min(defn['bits'])\n    except TypeError:\n        return defn['bits']\n\n\ndef make_mask(variable, **flags):\n    \"\"\"\n    Returns a mask array, based on provided flags\n\n    When multiple flags are provided, they will be combined in a logical AND fashion.\n\n    For example:\n\n    >>> make_mask(pqa, cloud_acca=False, cloud_fmask=False, land_obs=True) # doctest: +SKIP\n\n    OR\n\n    >>> make_mask(pqa, **GOOD_PIXEL_FLAGS) # doctest: +SKIP\n\n    where `GOOD_PIXEL_FLAGS` is a dict of flag_name to True/False\n\n    :param variable:\n    :type variable: xarray.Dataset or xarray.DataArray\n    :param flags: list of boolean flags\n    :return: boolean xarray.DataArray or xarray.Dataset\n    \"\"\"\n    flags_def = get_flags_def(variable)\n\n    mask, mask_value = create_mask_value(flags_def, **flags)\n\n    return variable & mask == mask_value\n\n\ndef valid_data_mask(data):\n    \"\"\"\n    Returns bool arrays where the data is not `nodata`\n\n    :param Dataset or DataArray data:\n    :return: Dataset or DataArray\n    \"\"\"\n    if isinstance(data, Dataset):\n        return data.apply(valid_data_mask)\n\n    if isinstance(data, DataArray):\n        if 'nodata' not in data.attrs:\n            return True\n        return data != data.nodata\n\n    raise TypeError('valid_data_mask not supported for type {}'.format(type(data)))\n\n\ndef mask_valid_data(data, keep_attrs=True):\n    \"\"\"\n    Deprecated. This function was poorly named. It is now available as `mask_invalid_data`.\n    \"\"\"\n    warnings.warn(\"`mask_valid_data` has been renamed to `mask_invalid_data`. Please use that instead.\",\n                  DeprecationWarning)\n    return mask_invalid_data(data, keep_attrs=keep_attrs)\n\n\ndef mask_invalid_data(data, keep_attrs=True):\n    \"\"\"\n    Sets all `nodata` values to ``nan``.\n\n    This will convert numeric data to type `float`.\n\n    :param Dataset or DataArray data:\n    :param bool keep_attrs: If the attributes of the data should be included in the returned .\n    :return: Dataset or DataArray\n    \"\"\"\n    if isinstance(data, Dataset):\n        # Pass keep_attrs as a positional arg to the DataArray func\n        return data.apply(mask_invalid_data, keep_attrs=keep_attrs, args=(keep_attrs,))\n\n    if isinstance(data, DataArray):\n        if 'nodata' not in data.attrs:\n            return data\n        out_data_array = data.where(data != data.nodata)\n        if keep_attrs:\n            out_data_array.attrs = data.attrs\n        return out_data_array\n\n    raise TypeError('mask_invalid_data not supported for type {}'.format(type(data)))\n\n\ndef create_mask_value(bits_def, **flags):\n    mask = 0\n    value = 0\n\n    for flag_name, flag_ref in flags.items():\n        defn = bits_def[flag_name]\n\n        try:\n            [flag_value] = (bit_val\n                            for bit_val, val_ref in defn['values'].items()\n                            if val_ref == flag_ref)\n            flag_value = int(flag_value)  # Might be string if coming from DB\n        except ValueError:\n            raise ValueError('Unknown value %s specified for flag %s' %\n                             (flag_ref, flag_name))\n\n        if isinstance(defn['bits'], collections.Iterable):  # Multi-bit flag\n            # Set mask\n            for bit in defn['bits']:\n                mask = set_value_at_index(mask, bit, True)\n\n            shift = min(defn['bits'])\n            real_val = flag_value << shift\n\n            value |= real_val\n\n        else:\n            bit = defn['bits']\n            mask = set_value_at_index(mask, bit, True)\n            value = set_value_at_index(value, bit, flag_value)\n\n    return mask, value\n\n\ndef mask_to_dict(bits_def, mask_value):\n    \"\"\"\n    Describes which flags are set for a mask value\n\n    :param bits_def:\n    :param mask_value:\n    :return: Mapping of flag_name -> set_value\n    :rtype: dict\n    \"\"\"\n    return_dict = {}\n    for flag_name, flag_defn in bits_def.items():\n\n        # Make bits a list, even if there is only one\n        flag_bits = flag_defn['bits']\n        if not isinstance(flag_defn['bits'], list):\n            flag_bits = [flag_bits]\n\n        # The amount to shift flag_value to line up with mask_value\n        flag_shift = min(flag_bits)\n\n        # Mask our mask_value, we are only interested in the bits for this flag\n        flag_mask = 0\n        for i in flag_bits:\n            flag_mask |= (1 << i)\n        masked_mask_value = mask_value & flag_mask\n\n        for flag_value, value in flag_defn['values'].items():\n            shifted_value = int(flag_value) << flag_shift\n            if shifted_value == masked_mask_value:\n                assert flag_name not in return_dict\n                return_dict[flag_name] = value\n    return return_dict\n\n\ndef _get_minimum_bit(bit_or_bits):\n    try:\n        return min(bit_or_bits)\n    except TypeError:\n        return bit_or_bits\n\n\ndef get_flags_def(variable):\n    try:\n        return getattr(variable, FLAGS_ATTR_NAME)\n    except AttributeError:\n        # Maybe we have a DataSet, not a DataArray\n        for var in variable.data_vars.values():\n            if _is_data_var(var):\n                try:\n                    return getattr(var, FLAGS_ATTR_NAME)\n                except AttributeError:\n                    pass\n\n        raise ValueError('No masking variable found')\n\n\ndef _is_data_var(variable):\n    return variable.name != 'crs' and len(variable.coords) > 1\n\n\ndef set_value_at_index(bitmask, index, value):\n    \"\"\"\n    Set a bit value onto an integer bitmask\n\n    eg. set bits 2 and 4 to True\n    >>> mask = 0\n    >>> mask = set_value_at_index(mask, 2, True)\n    >>> mask = set_value_at_index(mask, 4, True)\n    >>> print(bin(mask))\n    0b10100\n    >>> mask = set_value_at_index(mask, 2, False)\n    >>> print(bin(mask))\n    0b10000\n\n    :param bitmask: existing int bitmask to alter\n    :type bitmask: int\n    :type index: int\n    :type value: bool\n    \"\"\"\n    bit_val = 2 ** index\n    if value:\n        bitmask |= bit_val\n    else:\n        bitmask &= (~bit_val)\n    return bitmask\n", "path": "datacube/storage/masking.py"}]}