Skip to content

Commit

Permalink
Merge pull request #522 from opendatacube/remove_mask_flag_in_value_map
Browse files Browse the repository at this point in the history
Fix mask flag in value map
  • Loading branch information
pindge authored Mar 3, 2021
2 parents 3852e83 + e7fcf94 commit 21a1e1a
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 57 deletions.
108 changes: 67 additions & 41 deletions datacube_ows/styles/colormap.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,74 @@
from matplotlib import patches as mpatches, pyplot as plt
from xarray import Dataset, DataArray, merge

from datacube_ows.config_utils import OWSConfigEntry
from datacube_ows.styles.base import StyleDefBase

_LOG = logging.getLogger(__name__)


class ValueMapRule(OWSConfigEntry):
def __init__(self, style_def, band, cfg):
super().__init__(self, cfg)
self.style = style_def
self.band = style_def.local_band(band)

self.title = cfg["title"]
self.abstract = cfg.get("abstract")
if self.title and self.abstract:
self.label = f"{self.title} - {self.abstract}"
elif self.title:
self.label = self.title
elif self.abstract:
self.label = self.abstract
else:
self.label = None

flags = cfg["flags"]
self.or_flags = False
if "or" in flags:
self.or_flags = True
flags = flags["or"]
elif "and" in flags:
flags = flags["and"]
self.flags = flags
self.color_str = cfg["color"]
self.rgb = Color(self.color_str)

if cfg.get("mask", False):
self.alpha = 0.0
else:
self.alpha = cfg.get("alpha", 1.0)

def create_mask(self, data):
if self.or_flags:
mask = None
for f in self.flags.items():
f = {f[0]: f[1]}
if mask is None:
mask = make_mask(data, **f)
else:
mask |= make_mask(data, **f)
else:
mask = make_mask(data, **self.flags)
return mask

@classmethod
def value_map_from_config(cls, style, cfg):
vmap = {}
for band_name, rules in cfg.items():
band_rules = [cls(style, band_name, rule) for rule in rules]
vmap[band_name] = band_rules
return vmap


class ColorMapStyleDef(StyleDefBase):
auto_legend = True

def __init__(self, product, style_cfg):
super(ColorMapStyleDef, self).__init__(product, style_cfg)
style_cfg = self._raw_cfg
self.value_map = style_cfg["value_map"]
self.value_map = ValueMapRule.value_map_from_config(self, style_cfg["value_map"])
for band in self.value_map.keys():
self.raw_needed_bands.add(band)

Expand All @@ -42,23 +98,6 @@ def create_colordata(data, rgb, alpha, mask):
masked = target.where(mask).where(numpy.isfinite(data)) # remask
return masked

@staticmethod
def create_mask(data, flags):
if "or" in flags:
fs = flags["or"]
mask = None
for f in fs.items():
f = {f[0]: f[1]}
if mask is None:
mask = make_mask(data, **f)
else:
mask |= make_mask(data, **f)
else:
fs = flags if "and" not in flags else flags["and"]
mask = make_mask(data, **fs)
return mask


def transform_single_date_data(self, data):
# pylint: disable=too-many-locals, too-many-branches
# extent mask data per band to preseve nodata
Expand All @@ -71,30 +110,19 @@ def transform_single_date_data(self, data):
# data[band] = data[band].where(extent_mask)

imgdata = Dataset()
for cfg_band, values in self.value_map.items():
for cfg_band, rules in self.value_map.items():
# Run through each item
band = self.product.band_idx.band(cfg_band)
band_data = Dataset()
bdata = data[band]
if bdata.dtype.kind == 'f':
# Convert back to int for bitmasking
bdata = ColorMapStyleDef.reint(bdata)
for value in values:
flags = value["flags"]
rgb = Color(value["color"])
alpha = value.get("alpha", 1.0)
mask_source_band = value.get("mask", False)

mask = ColorMapStyleDef.create_mask(bdata, flags)

if mask_source_band:
# disable checking on the use of ~mask
# pylint: disable=invalid-unary-operand-type
bdata = bdata.where(~mask)
bdata = ColorMapStyleDef.reint(bdata)
else:
masked = ColorMapStyleDef.create_colordata(bdata, rgb, alpha, mask)
band_data = masked if len(band_data.data_vars) == 0 else band_data.combine_first(masked)
for rule in rules:
mask = rule.create_mask(bdata)

masked = ColorMapStyleDef.create_colordata(bdata, rule.rgb, rule.alpha, mask)
band_data = masked if len(band_data.data_vars) == 0 else band_data.combine_first(masked)

imgdata = band_data if len(imgdata.data_vars) == 0 else merge([imgdata, band_data])

Expand All @@ -104,13 +132,11 @@ def transform_single_date_data(self, data):
def single_date_legend(self, bytesio):
patches = []
for band in self.value_map.keys():
for value in self.value_map[band]:
# only include values that have a title set
if "title" in value and "abstract" in value and "color" in value and value["title"]:
rgb = Color(value["color"])
label = fill(value["title"] + " - " + value["abstract"], 30)
for rule in reversed(self.value_map[band]):
# only include values that are not transparent (and that have a non-blank title or abstract)
if rule.alpha > 0.001 and rule.label:
try:
patch = mpatches.Patch(color=rgb.hex_l, label=label)
patch = mpatches.Patch(color=rule.rgb.hex_l, label=rule.label)
# pylint: disable=broad-except
except Exception as e:
print("Error creating patch?", e)
Expand Down
10 changes: 8 additions & 2 deletions docs/cfg_colourmap_styles.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,14 @@ match the rule. The ``color`` entry is in html RGB hex format.
The ``alpha`` and ``mask`` entries are optional and allow transparency. ``alpha`` should
be a floating point number between 0.0 (fully transparent) and 1.0 (fully opaque)
and defaults to 1.0 (i.e. fully transparent). The ``mask`` entry is boolean (default
False). Setting ``mask`` to true is functionally equivalent to setting ``alpha`` to
0.0, but uses a more efficient implementation.
False). Setting ``mask`` to true is the same equivalent to setting ``alpha`` to
0.0. (A third option would be to use the standard style
`pq_masks <https://datacube-ows.readthedocs.io/en/latest/cfg_styling.html#bit-flag-masks-pq-masks>`_.
Bit-flag Masks (pq_masks)

syntax.)

to achieve the same effect

``color`` is still required when mask is True, but is not used in this case.

Expand Down
29 changes: 15 additions & 14 deletions tests/test_styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,33 +466,34 @@ def style_cfg_map_mask():
"color": "#FFFFFF",
},
{
"title": "Non-Transparent",
"abstract": "A Non-Transparent Value",
"title": "Impossible",
"abstract": "Will already have matched a previous rule",
"flags": {
"bar": 1,
},
"color": "#111111",
"color": "#54d56f",
}
]
}
}
return cfg

def test_RBGAMapped_Masking(product_layer_mask_map, style_cfg_map_mask):
def test_RGBAMapped_Masking(product_layer_mask_map, style_cfg_map_mask):
def fake_make_mask(data, **kwargs):
val = kwargs["bar"]
return data == val


dim = np.array([0, 1, 2, 3, 4, 5])
band = np.array([0, 0, 1, 1, 2, 2])
timarray = [np.datetime64(datetime.date.today())]
times = DataArray(timarray, coords=[timarray], dims=["time"], name="time")
da = DataArray(band, name='foo')
da = DataArray(band, name='foo', coords={"dim": dim}, dims=["dim"])
dst = Dataset(data_vars={'foo': da})
ds = concat([dst], times)

npmap = np.array([True, True, True, True, True, True])
damap = DataArray(npmap)
damap = DataArray(npmap, coords={"dim": dim}, dims=["dim"])

with patch('datacube_ows.styles.colormap.make_mask', new_callable=lambda: fake_make_mask) as fmm:
style_def = datacube_ows.styles.StyleDef(product_layer_mask_map, style_cfg_map_mask)
Expand All @@ -502,14 +503,14 @@ def fake_make_mask(data, **kwargs):
b = data["blue"]
a = data["alpha"]

assert (r[2:3:1] == 0)
assert (g[2:3:1] == 0)
assert (b[2:3:1] == 0)
assert (a[2:3:1] == 0)
assert (r[4:5:1] == 255)
assert (g[4:5:1] == 255)
assert (b[4:5:1] == 255)
assert (a[4:5:1] == 255)
assert (r.values[2] == 17)
assert (g.values[2] == 17)
assert (b.values[2] == 17)
assert (a.values[2] == 0)
assert (r.values[4] == 255)
assert (g.values[4] == 255)
assert (b.values[4] == 255)
assert (a.values[4] == 255)


def test_reint():
Expand Down

0 comments on commit 21a1e1a

Please sign in to comment.