-
-
Notifications
You must be signed in to change notification settings - Fork 404
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added support for dynamic operations and overlaying #588
Changes from all commits
15b3474
2a41e03
c449d3d
ed86730
b53a296
0fd852c
0c15545
f3a74c5
0aedb52
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
from numbers import Number | ||
import itertools | ||
from itertools import groupby | ||
import numpy as np | ||
|
||
import param | ||
|
@@ -97,6 +98,56 @@ def _dimension_keys(self): | |
for k in self.keys()] | ||
|
||
|
||
def _dynamic_mul(self, dimensions, other, keys): | ||
""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Glad to see this functionality but sadly it makes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do agree, a separate utility makes sense to me, so please do open an issue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. See issue #600. |
||
Implements dynamic version of overlaying operation overlaying | ||
DynamicMaps and HoloMaps where the key dimensions of one is | ||
a strict superset of the other. | ||
""" | ||
# If either is a HoloMap compute Dimension values | ||
if not isinstance(self, DynamicMap) or not isinstance(other, DynamicMap): | ||
keys = sorted((d, v) for k in keys for d, v in k) | ||
grouped = dict([(g, [v for _, v in group]) | ||
for g, group in groupby(keys, lambda x: x[0])]) | ||
dimensions = [d(values=grouped[d.name]) for d in dimensions] | ||
mode = 'bounded' | ||
map_obj = None | ||
elif (isinstance(self, DynamicMap) and (other, DynamicMap) and | ||
self.mode != other.mode): | ||
raise ValueEror("Cannot overlay DynamicMaps with mismatching mode.") | ||
else: | ||
map_obj = self if isinstance(self, DynamicMap) else other | ||
mode = map_obj.mode | ||
|
||
def dynamic_mul(*key): | ||
key = key[0] if mode == 'open' else key | ||
layers = [] | ||
try: | ||
if isinstance(self, DynamicMap): | ||
_, self_el = util.get_dynamic_item(self, dimensions, key) | ||
if self_el is not None: | ||
layers.append(self_el) | ||
else: | ||
layers.append(self[key]) | ||
except KeyError: | ||
pass | ||
try: | ||
if isinstance(other, DynamicMap): | ||
_, other_el = util.get_dynamic_item(other, dimensions, key) | ||
if other_el is not None: | ||
layers.append(other_el) | ||
else: | ||
layers.append(other[key]) | ||
except KeyError: | ||
pass | ||
return Overlay(layers) | ||
if map_obj: | ||
return map_obj.clone(callback=dynamic_mul, shared_data=False, | ||
kdims=dimensions) | ||
else: | ||
return DynamicMap(callback=dynamic_mul, kdims=dimensions) | ||
|
||
|
||
def __mul__(self, other): | ||
""" | ||
The mul (*) operator implements overlaying of different Views. | ||
|
@@ -108,7 +159,7 @@ def __mul__(self, other): | |
will try to match up the dimensions, making sure that items | ||
with completely different dimensions aren't overlaid. | ||
""" | ||
if isinstance(other, self.__class__): | ||
if isinstance(other, HoloMap): | ||
self_set = {d.name for d in self.kdims} | ||
other_set = {d.name for d in other.kdims} | ||
|
||
|
@@ -117,8 +168,10 @@ def __mul__(self, other): | |
self_in_other = self_set.issubset(other_set) | ||
other_in_self = other_set.issubset(self_set) | ||
dimensions = self.kdims | ||
|
||
if self_in_other and other_in_self: # superset of each other | ||
super_keys = sorted(set(self._dimension_keys() + other._dimension_keys())) | ||
keys = self._dimension_keys() + other._dimension_keys() | ||
super_keys = util.unique_iterator(keys) | ||
elif self_in_other: # self is superset | ||
dimensions = other.kdims | ||
super_keys = other._dimension_keys() | ||
|
@@ -127,6 +180,9 @@ def __mul__(self, other): | |
else: # neither is superset | ||
raise Exception('One set of keys needs to be a strict subset of the other.') | ||
|
||
if isinstance(self, DynamicMap) or isinstance(other, DynamicMap): | ||
return self._dynamic_mul(dimensions, other, super_keys) | ||
|
||
items = [] | ||
for dim_keys in super_keys: | ||
# Generate keys for both subset and superset and sort them by the dimension index. | ||
|
@@ -146,6 +202,11 @@ def __mul__(self, other): | |
items.append((new_key, Overlay([other[other_key]]))) | ||
return self.clone(items, kdims=dimensions, label=self._label, group=self._group) | ||
elif isinstance(other, self.data_type): | ||
if isinstance(self, DynamicMap): | ||
from .operation import DynamicFunction | ||
def dynamic_mul(element): | ||
return element * other | ||
return DynamicFunction(self, function=dynamic_mul) | ||
items = [(k, v * other) for (k, v) in self.data.items()] | ||
return self.clone(items, label=self._label, group=self._group) | ||
else: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -886,3 +886,27 @@ def arglexsort(arrays): | |
recarray['f%s' % i] = array | ||
return recarray.argsort() | ||
|
||
|
||
def get_dynamic_item(map_obj, dimensions, key): | ||
""" | ||
Looks up an item in a DynamicMap given a list of dimensions | ||
and a corresponding key. The dimensions must be a subset | ||
of the map_obj key dimensions. | ||
""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An example would be nice in the docstring here. Doesn't have to be an actual doctest if that is too tricky to get working... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do, can add a unit test too. |
||
if isinstance(key, tuple): | ||
dims = {d.name: k for d, k in zip(dimensions, key) | ||
if d in map_obj.kdims} | ||
key = tuple(dims.get(d.name) for d in map_obj.kdims) | ||
el = map_obj.select([lambda x: type(x).__name__ == 'DynamicMap'], | ||
**dims) | ||
elif key < map_obj.counter: | ||
key_offset = max([key-map_obj.cache_size, 0]) | ||
key = map_obj.keys()[min([key-key_offset, | ||
len(map_obj)-1])] | ||
el = map_obj[key] | ||
elif key >= map_obj.counter: | ||
el = next(map_obj) | ||
key = list(map_obj.keys())[-1] | ||
else: | ||
el = None | ||
return key, el |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -515,19 +515,7 @@ def _get_frame(self, key): | |
self.current_key = key | ||
return self.current_frame | ||
elif self.dynamic: | ||
if isinstance(key, tuple): | ||
dims = {d.name: k for d, k in zip(self.dimensions, key) | ||
if d in self.hmap.kdims} | ||
frame = self.hmap.select([DynamicMap], **dims) | ||
elif key < self.hmap.counter: | ||
key_offset = max([key-self.hmap.cache_size, 0]) | ||
key = self.hmap.keys()[min([key-key_offset, len(self.hmap)-1])] | ||
frame = self.hmap[key] | ||
elif key >= self.hmap.counter: | ||
frame = next(self.hmap) | ||
key = self.hmap.keys()[-1] | ||
else: | ||
frame = None | ||
key, frame = util.get_dynamic_item(self.hmap, self.dimensions, key) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice to see this as a utility! |
||
if not isinstance(key, tuple): key = (key,) | ||
if not key in self.keys: | ||
self.keys.append(key) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import numpy as np | ||
from holoviews import Dimension, DynamicMap, Image | ||
from holoviews import Dimension, DynamicMap, Image, HoloMap | ||
from holoviews.core.operation import DynamicFunction | ||
from holoviews.element.comparison import ComparisonTestCase | ||
|
||
frequencies = np.linspace(0.5,2.0,5) | ||
|
@@ -78,3 +79,55 @@ def test_sampled_bounded_resample(self): | |
dmap=DynamicMap(fn, sampled=True) | ||
self.assertEqual(dmap[{0, 1, 2}].keys(), [0, 1, 2]) | ||
|
||
|
||
class DynamicTestOperation(ComparisonTestCase): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great! Thanks for adding these tests! |
||
|
||
def test_dynamic_function(self): | ||
fn = lambda i: Image(sine_array(0,i)) | ||
dmap=DynamicMap(fn, sampled=True) | ||
dmap_with_fn = DynamicFunction(dmap, function=lambda x: x.clone(x.data*2)) | ||
self.assertEqual(dmap_with_fn[5], Image(sine_array(0,5)*2)) | ||
|
||
|
||
def test_dynamic_function_with_kwargs(self): | ||
fn = lambda i: Image(sine_array(0,i)) | ||
dmap=DynamicMap(fn, sampled=True) | ||
def fn(x, multiplier=2): | ||
return x.clone(x.data*multiplier) | ||
dmap_with_fn = DynamicFunction(dmap, function=fn, kwargs=dict(multiplier=3)) | ||
self.assertEqual(dmap_with_fn[5], Image(sine_array(0,5)*3)) | ||
|
||
|
||
|
||
class DynamicTestOverlay(ComparisonTestCase): | ||
|
||
def test_dynamic_element_overlay(self): | ||
fn = lambda i: Image(sine_array(0,i)) | ||
dmap=DynamicMap(fn, sampled=True) | ||
dynamic_overlay = dmap * Image(sine_array(0,10)) | ||
overlaid = Image(sine_array(0,5)) * Image(sine_array(0,10)) | ||
self.assertEqual(dynamic_overlay[5], overlaid) | ||
|
||
def test_dynamic_element_underlay(self): | ||
fn = lambda i: Image(sine_array(0,i)) | ||
dmap=DynamicMap(fn, sampled=True) | ||
dynamic_overlay = Image(sine_array(0,10)) * dmap | ||
overlaid = Image(sine_array(0,10)) * Image(sine_array(0,5)) | ||
self.assertEqual(dynamic_overlay[5], overlaid) | ||
|
||
def test_dynamic_dynamicmap_overlay(self): | ||
fn = lambda i: Image(sine_array(0,i)) | ||
dmap=DynamicMap(fn, sampled=True) | ||
fn2 = lambda i: Image(sine_array(0,i*2)) | ||
dmap2=DynamicMap(fn2, sampled=True) | ||
dynamic_overlay = dmap * dmap2 | ||
overlaid = Image(sine_array(0,5)) * Image(sine_array(0,10)) | ||
self.assertEqual(dynamic_overlay[5], overlaid) | ||
|
||
def test_dynamic_holomap_overlay(self): | ||
fn = lambda i: Image(sine_array(0,i)) | ||
dmap = DynamicMap(fn, sampled=True) | ||
hmap = HoloMap({i: Image(sine_array(0,i*2)) for i in range(10)}) | ||
dynamic_overlay = dmap * hmap | ||
overlaid = Image(sine_array(0,5)) * Image(sine_array(0,10)) | ||
self.assertEqual(dynamic_overlay[5], overlaid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy with this approach.