Skip to content
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

Operation refactor in core #1421

Merged
merged 3 commits into from
May 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions holoviews/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
from .core.spaces import (HoloMap, Callable, DynamicMap, # noqa (API import)
GridSpace, GridMatrix)

from .interface import * # noqa (API import)
from .operation import ElementOperation, MapOperation, TreeOperation # noqa (API import)
from .element import * # noqa (API import)
from .interface import * # noqa (API import)
from .operation import Operation, TreeOperation # noqa (API import)
from .operation import ElementOperation # noqa (Deprecated API import)
from .element import * # noqa (API import)
from .element import __all__ as elements_list
from . import util # noqa (API import)

Expand Down
2 changes: 1 addition & 1 deletion holoviews/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

def public(obj):
if not isinstance(obj, type): return False
baseclasses = [Dimension, Dimensioned, ElementOperation, BoundingBox,
baseclasses = [Dimension, Dimensioned, Operation, BoundingBox,
SheetCoordinateSystem, AttrTree]
return any([issubclass(obj, bc) for bc in baseclasses])

Expand Down
176 changes: 97 additions & 79 deletions holoviews/core/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,55 @@

class Operation(param.ParameterizedFunction):
"""
Base class for all Operation types.
An Operation process an Element or HoloMap at the level of
individual elements or overlays. If a holomap is passed in as input,
a processed holomap is returned as output where the individual
elements have been transformed accordingly. An Operation may turn
overlays in new elements or vice versa.

An Operation can be set to be dynamic, which will return a
DynamicMap with a callback that will apply the operation
dynamically. An Operation may also supply a list of Stream classes
on a streams parameter, which can allow dynamic control over the
parameters on the operation.
"""

group = param.String(default='Operation', doc="""
The group string used to identify the output of the
Operation. By default this should match the operation name.""")


dynamic = param.ObjectSelector(default='default',
objects=['default', True, False], doc="""
Whether the operation should be applied dynamically when a
specific frame is requested, specified as a Boolean. If set to
'default' the mode will be determined based on the input type,
i.e. if the data is a DynamicMap it will stay dynamic.""")

input_ranges = param.ClassSelector(default={},
class_=(dict, tuple), doc="""
Ranges to be used for input normalization (if applicable) in a
format appropriate for the Normalization.ranges parameter.

By default, no normalization is applied. If key-wise
normalization is required, a 2-tuple may be supplied where the
first component is a Normalization.ranges list and the second
component is Normalization.keys. """)

link_inputs = param.Boolean(default=False, doc="""
If the operation is dynamic, whether or not linked streams
should be transferred from the operation inputs for backends
that support linked streams.

For example if an operation is applied to a DynamicMap with an
RangeXY, this switch determines whether the corresponding
visualization should update this stream with range changes
originating from the newly generated axes.""")

streams = param.List(default=[], doc="""
List of streams that are applied if dynamic=True, allowing
for dynamic interaction with the plot.""")

@classmethod
def search(cls, element, pattern):
"""
Expand Down Expand Up @@ -69,58 +110,11 @@ def get_overlay_bounds(cls, overlay):
raise ValueError("Extents across the overlay are inconsistent")



class ElementOperation(Operation):
"""
An ElementOperation process an Element or HoloMap at the level of
individual elements or overlays. If a holomap is passed in as
input, a processed holomap is returned as output where the
individual elements have been transformed accordingly. An
ElementOperation may turn overlays in new elements or vice versa.

An ElementOperation can be set to be dynamic, which will return a
DynamicMap with a callback that will apply the operation
dynamically. An ElementOperation may also supply a list of Stream
classes on a streams parameter, which can allow dynamic control
over the parameters on the operation.
"""

dynamic = param.ObjectSelector(default='default',
objects=['default', True, False], doc="""
Whether the operation should be applied dynamically when a
specific frame is requested, specified as a Boolean. If set to
'default' the mode will be determined based on the input type,
i.e. if the data is a DynamicMap it will stay dynamic.""")

input_ranges = param.ClassSelector(default={},
class_=(dict, tuple), doc="""
Ranges to be used for input normalization (if applicable) in a
format appropriate for the Normalization.ranges parameter.

By default, no normalization is applied. If key-wise
normalization is required, a 2-tuple may be supplied where the
first component is a Normalization.ranges list and the second
component is Normalization.keys. """)

link_inputs = param.Boolean(default=False, doc="""
If the operation is dynamic, whether or not linked streams
should be transferred from the operation inputs for backends
that support linked streams.

For example if an operation is applied to a DynamicMap with an
RangeXY, this switch determines whether the corresponding
visualization should update this stream with range changes
originating from the newly generated axes.""")

streams = param.List(default=[], doc="""
List of streams that are applied if dynamic=True, allowing
for dynamic interaction with the plot.""")

def _process(self, view, key=None):
"""
Process a single input element and outputs new single element
or overlay. If a HoloMap is passed into a ElementOperation,
the individual components are processed sequentially with the
Process a single input element and outputs new single element or
overlay. If a HoloMap is passed into an Operation, the
individual components are processed sequentially with the
corresponding key passed as the optional key argument.
"""
raise NotImplementedError
Expand Down Expand Up @@ -169,58 +163,82 @@ def __call__(self, element, **params):
return processed


class ElementOperation(Operation):

def __init__(self, *args, **kwargs):
self.warning('ElementOperation has been deprecated and renamed to Operation.')
super(ElementOperation, self).__init__(*args, **kwargs)


class OperationCallable(Callable):
"""
OperationCallable allows wrapping an ElementOperation and the
objects it is processing to allow traversing the operations
applied on a DynamicMap.
OperationCallable allows wrapping an Operation and the objects it is
processing to allow traversing the operations applied on a
DynamicMap.
"""

operation = param.ClassSelector(class_=ElementOperation, doc="""
The ElementOperation being wrapped.""")
operation = param.ClassSelector(class_=Operation, doc="""
The Operation being wrapped into an OperationCallable.""")

def __init__(self, callable, **kwargs):
if 'operation' not in kwargs:
raise ValueError('An OperationCallable must have an operation specified')
super(OperationCallable, self).__init__(callable, **kwargs)


class MapOperation(param.ParameterizedFunction):

class TreeOperation(param.ParameterizedFunction):
"""
A MapOperation takes a HoloMap containing elements or overlays and
processes them at the HoloMap level, returning arbitrary new
HoloMap objects as output. Unlike ElementOperation, MapOperations
can compute over all the keys and dimensions of the input map.
A TreeOperation is the most general Operation type; it accepts any
HoloViews datastructure and outputs a Layout containing one or
more elements.
"""

group = param.String(default='MapOperation', doc="""
The group string to identify the output of the MapOperation.
By default this will match the MapOperation name.""")
group = param.String(default='Operation', doc="""
The group string used to identify the output of the
Operation. By default this should match the operation name.""")

def __call__(self, vmap, **params):
self.p = param.ParamOverrides(self, params)

if not isinstance(vmap, HoloMap):
raise Exception('MapOperation can only process Maps.')
@classmethod
def search(cls, element, pattern):
"""
Helper method that returns a list of elements that match the
given path pattern of form {type}.{group}.{label}.

return self._process(vmap)
The input may be a Layout, an Overlay type or a single
Element.
"""
if isinstance(element, Layout):
return [el for cell in element for el in cls.search(cell, pattern)]
if isinstance(element, (NdOverlay, Overlay)):
return [el for el in element if el.matches(pattern)]
elif isinstance(element, Element):
return [element] if element.matches(pattern) else []


def _process(self, view):
@classmethod
def get_overlay_label(cls, overlay, default_label=''):
"""
Process a single input HoloMap, returning a new HoloMap
instance.
Returns a label if all the elements of an overlay agree on a
consistent label, otherwise returns the default label.
"""
raise NotImplementedError
if all(el.label==overlay.get(0).label for el in overlay):
return overlay.get(0).label
else:
return default_label


@classmethod
def get_overlay_bounds(cls, overlay):
"""
Returns the extents if all the elements of an overlay agree on
a consistent extents, otherwise raises an exception.
"""
if all(el.bounds==overlay.get(0).bounds for el in overlay):
return overlay.get(0).bounds
else:
raise ValueError("Extents across the overlay are inconsistent")

class TreeOperation(Operation):
"""
A TreeOperation is the most general Operation type; it accepts any
HoloViews datastructure and outputs a Layout containing one or
more elements.
"""

def process_element(self, element, key, **params):
"""
Expand Down
6 changes: 3 additions & 3 deletions holoviews/core/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,8 +705,8 @@ class Compositor(param.Parameterized):
A Compositor is a way of specifying an operation to be automatically
applied to Overlays that match a specified pattern upon display.

Any ElementOperation that takes an Overlay as input may be used to
define a compositor.
Any Operation that takes an Overlay as input may be used to define a
compositor.

For instance, a compositor may be defined to automatically display
three overlaid monochrome matrices as an RGB image as long as the
Expand All @@ -719,7 +719,7 @@ class Compositor(param.Parameterized):
'display'.""")

operation = param.Parameter(doc="""
The ElementOperation to apply when collapsing overlays.""")
The Operation to apply when collapsing overlays.""")

pattern = param.String(doc="""
The overlay pattern to be processed. An overlay pattern is a
Expand Down
20 changes: 8 additions & 12 deletions holoviews/core/spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,6 @@ def collapse(self, dimensions=None, function=None, spreadfn=None, **kwargs):
on the HoloMap. Homogenous Elements may be collapsed by
supplying a function, inhomogenous elements are merged.
"""
from .operation import MapOperation
if not dimensions:
dimensions = self.kdims
if not isinstance(dimensions, list): dimensions = [dimensions]
Expand All @@ -273,18 +272,15 @@ def collapse(self, dimensions=None, function=None, spreadfn=None, **kwargs):

collapsed = groups.clone(shared_data=False)
for key, group in groups.items():
if isinstance(function, MapOperation):
collapsed[key] = function(group, **kwargs)
else:
group_data = [el.data for el in group]
args = (group_data, function, group.last.kdims)
if hasattr(group.last, 'interface'):
col_data = group.type(group.table().aggregate(group.last.kdims, function, spreadfn, **kwargs))
group_data = [el.data for el in group]
args = (group_data, function, group.last.kdims)
if hasattr(group.last, 'interface'):
col_data = group.type(group.table().aggregate(group.last.kdims, function, spreadfn, **kwargs))

else:
data = group.type.collapse_data(*args, **kwargs)
col_data = group.last.clone(data)
collapsed[key] = col_data
else:
data = group.type.collapse_data(*args, **kwargs)
col_data = group.last.clone(data)
collapsed[key] = col_data
return collapsed if self.ndims > 1 else collapsed.last


Expand Down
4 changes: 2 additions & 2 deletions holoviews/element/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from ..core import Dataset, OrderedDict
from ..core.boundingregion import BoundingBox
from ..core.operation import ElementOperation
from ..core.operation import Operation
from ..core.sheetcoords import Slice
from ..core.util import (is_nan, sort_topologically, one_to_one,
cartesian_product, is_cyclic)
Expand Down Expand Up @@ -102,7 +102,7 @@ def reduce_fn(x):
return np.NaN


class categorical_aggregate2d(ElementOperation):
class categorical_aggregate2d(Operation):
"""
Generates a gridded Dataset of 2D aggregate arrays indexed by the
first two dimensions of the passed Element, turning all remaining
Expand Down
8 changes: 4 additions & 4 deletions holoviews/interface/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,8 @@ def __str__(self):
class Analyze(Collect):
"""
An Analyze is a type of Collect that updates an Attrtree with
the results of a ElementOperation. Analyze takes a ViewRef object as
input which is resolved to generate input for the ElementOperation.
the results of a Operation. Analyze takes a ViewRef object as
input which is resolved to generate input for the Operation.
"""

def __init__(self, reference, analysis, *args, **kwargs):
Expand Down Expand Up @@ -406,7 +406,7 @@ class Collector(AttrTree):

The analysis method takes a reference to data on the attrtree (a
ViewRef) and passes the resolved output to the given analysisfn
ElementOperation.
Operation.

>>> Collector.for_type(str, lambda x: ViewableElement(x, name=x))
>>> Collector.interval_hook = param.Dynamic.time_fn.advance
Expand Down Expand Up @@ -505,7 +505,7 @@ def collect(self, obj, *args, **kwargs):

def analyze(self, reference, analysisfn, *args, **kwargs):
"""
Given a ViewRef and the ElementOperation analysisfn, process the
Given a ViewRef and the Operation analysisfn, process the
data resolved by the reference with analysisfn at each step.
"""
task = Analyze(reference, analysisfn, *args, **kwargs)
Expand Down
7 changes: 4 additions & 3 deletions holoviews/operation/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
from ..core.operation import ElementOperation, MapOperation, TreeOperation # noqa (API import)
from ..core.operation import Operation, TreeOperation # noqa (API import)
from ..core.operation import ElementOperation # noqa (Deprecated API import)
from ..core.options import Compositor

from .element import * # noqa (API import)
from ..core import Overlay # noqa (API import)

def public(obj):
if not isinstance(obj, type): return False
baseclasses = [ElementOperation, MapOperation, TreeOperation]
baseclasses = [Operation, TreeOperation]
return any([issubclass(obj, bc) for bc in baseclasses])


_public = list(set([_k for _k, _v in locals().items() if public(_v)]))

_current_locals = [el for el in locals().items()]
for _k, _v in _current_locals:
if public(_v) and issubclass(_v, ElementOperation):
if public(_v) and issubclass(_v, Operation):
Compositor.operations.append(_v)

__all__ = _public + ['Compositor']
Loading