Esempio n. 1
0
class Contours(Path):
    """
    The Contours element is a subtype of a Path which is characterized
    by the fact that each path geometry may only be associated with
    scalar values. It supports all the same data formats as a `Path`
    but does not allow continuously varying values along the path
    geometry's coordinates. Conceptually Contours therefore represent
    iso-contours or isoclines, i.e. a function of two variables which
    describes a curve along which the function has a constant value.

    The canonical representation is a list of dictionaries storing the
    x- and y-coordinates along with any other (scalar) values:

        [{'x': 1d-array, 'y': 1d-array, 'value': scalar}, ...]

    Alternatively Contours also supports a single columnar
    data-structure to specify an individual contour:

        {'x': 1d-array, 'y': 1d-array, 'value': scalar, 'continuous': 1d-array}

    Since not all formats allow storing scalar values as actual
    scalars arrays which are the same length as the coordinates but
    have only one unique value are also considered scalar. This is
    strictly enforced, ensuring that each path geometry represents
    a valid iso-contour.

    The easiest way of accessing the individual geometries is using
    the `Contours.split` method, which returns each path geometry as a
    separate entity, while the other methods assume a flattened
    representation where all paths are separated by NaN values.
    """

    level = param.Number(default=None, doc="""
        Optional level associated with the set of Contours.""")

    vdims = param.List(default=[], constant=True, doc="""
        Contours optionally accept a value dimension, corresponding
        to the supplied values.""")

    group = param.String(default='Contours', constant=True)

    _level_vdim = Dimension('Level') # For backward compatibility

    def __init__(self, data, kdims=None, vdims=None, **params):
        data = [] if data is None else data
        if params.get('level') is not None:
            self.param.warning(
                "The level parameter on %s elements is deprecated, "
                "supply the value dimension(s) as columns in the data.",
                type(self).__name__)
            vdims = vdims or [self._level_vdim]
            params['vdims'] = []
        else:
            params['vdims'] = vdims
        super(Contours, self).__init__(data, kdims=kdims, **params)
        if params.get('level') is not None:
            with disable_constant(self):
                self.vdims = [asdim(d) for d in vdims]
        else:
            all_scalar = all(self.interface.isscalar(self, vdim) for vdim in self.vdims)
            if not all_scalar:
                raise ValueError("All value dimensions on a Contours element must be scalar")

    def dimension_values(self, dim, expanded=True, flat=True):
        dimension = self.get_dimension(dim, strict=True)
        if dimension in self.vdims and self.level is not None:
            if expanded:
                return np.full(len(self), self.level)
            return np.array([self.level])
        return super(Contours, self).dimension_values(dim, expanded, flat)
Esempio n. 2
0
 class Test(param.Parameterized):
     a = param.Number(bounds=(0, 10))
Esempio n. 3
0
    class Test(param.Parameterized):

        a = param.Number()
Esempio n. 4
0
class AnalyzerViewer(param.Parameterized):

    frame_id = param.Selector(label='Frame id', precedence=0.1)

    # metrics_table = param.DataFrame()
    iou_th = param.Number(0.5, bounds=(0, 1), precedence=0.2)
    score_th = param.Number(0.3, bounds=(0, 1), precedence=0.2)
    bbox_match_method = param.Selector(objects=['iou', 'pred_bbox_center'],
                                       precedence=0.2)

    def __init__(self, analyzer, resize_factor=0.5):

        super(AnalyzerViewer, self).__init__()

        analyzer.evaluate_performance()

        self.analyzer = analyzer
        self.resize_factor = resize_factor

        self.frame_ids = list(analyzer.keys())
        self.frame_ids.sort()
        self.frame_id = self.frame_ids[0]
        self.set_frame_ids()

        self.metrics_tables = None
        self.metrics_tables_total = None

        # axes handles
        self.ax_cm = None
        self.ax_cm_total = None
        self.ax_image_with_boxes = None

        # total performance
        self.fig_cm_total = self.plot_total_confusion_matrix2()

        # set thresholds
        self.iou_th = self.analyzer.iou_th
        self.score_th = self.analyzer.score_th
        self.bbox_match_method = self.analyzer.bbox_match_method

    @param.depends('frame_id')
    def set_frame_ids(self):
        self.param.frame_id.objects = self.frame_ids
        self.param.frame_id.default = self.frame_ids[0]
        self.param.frame_id.check_on_set = True

    @param.depends('frame_id', 'score_th', watch=False)
    def plot_image_with_boxes(self):

        self.analyzer.score_th = self.score_th

        image_with_boxes = self.analyzer.visualize_example(
            key=self.frame_id,
            show_predictions=True,
            show_ground_truth=True,
            class_names=self.analyzer.class_names,
            bgr2rgb=True,
            resize_factor=self.resize_factor,
            filter_pred_by_score=True,
        )

        # TODO: try to improve speed by updating figure content, instead of creating new figure in every function call
        # create holoviews figure
        fig = hv.RGB(image_with_boxes)
        fig_width = image_with_boxes.shape[1]  #// 20
        fig_height = image_with_boxes.shape[0]  #// 20
        fig.options(width=fig_width, height=fig_height)
        fig = pn.pane.HoloViews(fig,
                                width=fig_width,
                                height=fig_height,
                                sizing_mode='scale_both')

        # create matplotlib figure
        # ax = self.ax_image_with_boxes
        # if ax is None:
        #     plt.ioff()
        #     fig, ax = plt.subplots()
        # ax.clear()
        # ax.imshow(image_with_boxes)
        # title_str = '{}'.format(self.frame_id)
        # plt.title(title_str)
        # fig = ax.figure
        # fig.tight_layout()
        # self.ax_image_with_boxes = ax

        return fig

    @param.depends('frame_id',
                   'iou_th',
                   'score_th',
                   'bbox_match_method',
                   watch=False)
    def plot_confusion_matrix(self):

        prediction, ground_truth, _, _, cm = self.analyzer.get_item_unpacked(
            self.frame_id)

        self.analyzer.bbox_match_method = self.bbox_match_method
        self.analyzer.iou_th = self.iou_th
        self.analyzer.score_th = self.score_th

        cm = ConfusionMatrix(
            prediction,
            ground_truth,
            self.analyzer.class_names,
            bbox_match_method=self.analyzer.bbox_match_method,
            iou_th=self.analyzer.iou_th,
            score_th=self.analyzer.score_th,
            add_miss_detection_col=self.analyzer.add_miss_detection_col,
            add_false_detection_row=self.analyzer.add_false_detection_row,
        )

        if self.analyzer.bbox_match_method == 'iou':
            title_str = 'Match Pred Method = IOU\nScore_th = {} | IOU_th = {}'.format(
                self.analyzer.score_th, self.analyzer.iou_th)
        elif self.analyzer.bbox_match_method == 'pred_bbox_center':
            title_str = 'Match Pred Method = Centers\nScore_th = {}'.format(
                self.analyzer.score_th, self.analyzer.bbox_match_method)
        ax, fig = ConfusionMatrix.plot_confusion_matrix(
            cm,
            display_labels=self.analyzer.class_names,
            add_miss_detection_col=self.analyzer.add_miss_detection_col,
            add_false_detection_row=self.analyzer.add_false_detection_row,
            title_str=title_str,
            ax=self.ax_cm,
            display=False,
        )
        self.ax_cm = ax

        # convert to holoviews image
        # img = convert_pyplot_figure_to_ndarray(fig, dpi=180)
        # fig = hv.RGB(img)
        # fig.options(width=img.shape[1], height=img.shape[0])
        # fig = pn.pane.HoloViews(fig, width=250, height=200, sizing_mode='scale_both')

        try:
            self.metrics_tables = cm.metrics_tables
        except:
            self.metrics_tables = ConfusionMatrix.summarize_performance_metrics(
                cm.metrics, cm.class_names)

        return fig

    # @param.depends('frame_id', watch=False)
    # def get_metrics_table(self, metrics_type='global'):
    #
    #     self.metrics_table = self.metrics_tables[metrics_type]
    #
    #     return self.metrics_table

    @param.depends('frame_id',
                   'iou_th',
                   'score_th',
                   'bbox_match_method',
                   watch=False)
    def get_global_metrics_table(self, metrics_type='global'):

        metrics_table = self.metrics_tables[metrics_type]

        matrics_table_widget = pn.widgets.DataFrame(
            metrics_table, name='Gloabl Performance Metrics',
            width=300)  #min_width=200, width_max=350, width_policy='fit')

        return matrics_table_widget

    @param.depends('frame_id',
                   'iou_th',
                   'score_th',
                   'bbox_match_method',
                   watch=False)
    def get_class_metrics_table(self, metrics_type='class'):

        metrics_table = self.metrics_tables[metrics_type]

        matrics_table_widget = pn.widgets.DataFrame(
            metrics_table,
            name='Per Class Performance Metrics',
            min_width=300,
            width_policy='fit')

        return matrics_table_widget

    # @param.depends('iou_th', 'score_th', 'bbox_match_method', watch=False)
    # def plot_total_confusion_matrix(self):
    #     if self.analyzer.bbox_match_method == 'iou':
    #         title_str = 'Match Pred Method = IOU\nScore_th = {} | IOU_th = {}'.format(self.analyzer.score_th, self.analyzer.iou_th)
    #     elif self.analyzer.bbox_match_method == 'pred_bbox_center':
    #         title_str = 'Match Pred Method = Centers\nScore_th = {}'.format(self.analyzer.score_th, self.analyzer.bbox_match_method)
    #
    #     ax, fig = ConfusionMatrix.plot_confusion_matrix(self.analyzer.cm,
    #                                                     display_labels=self.analyzer.class_names,
    #                                                     add_miss_detection_col=self.analyzer.add_miss_detection_col,
    #                                                     add_false_detection_row=self.analyzer.add_false_detection_row,
    #                                                     title_str=title_str,
    #                                                     display=False,
    #                                                     )
    #
    #     return fig

    @param.depends('iou_th', 'score_th', 'bbox_match_method', watch=False)
    def plot_total_confusion_matrix2(self):

        # set analyzer thresholds
        self.analyzer.bbox_match_method = self.bbox_match_method
        self.analyzer.iou_th = self.iou_th
        self.analyzer.score_th = self.score_th

        # recalculate total confusion matrix
        self.analyzer.evaluate_performance(generate_report=False)

        cm = self.analyzer.cm

        if self.analyzer.bbox_match_method == 'iou':
            title_str = 'Match Pred Method = IOU\nScore_th = {} | IOU_th = {}'.format(
                self.analyzer.score_th, self.analyzer.iou_th)
        elif self.analyzer.bbox_match_method == 'pred_bbox_center':
            title_str = 'Match Pred Method = Centers\nScore_th = {}'.format(
                self.analyzer.score_th, self.analyzer.bbox_match_method)
        ax, fig = ConfusionMatrix.plot_confusion_matrix(
            cm,
            display_labels=self.analyzer.class_names,
            add_miss_detection_col=self.analyzer.add_miss_detection_col,
            add_false_detection_row=self.analyzer.add_false_detection_row,
            title_str=title_str,
            ax=self.ax_cm_total,
            display=False,
        )
        self.ax_cm_total = ax

        try:
            self.metrics_tables_total = cm.metrics_tables
        except:
            self.metrics_tables_total = ConfusionMatrix.summarize_performance_metrics(
                self.analyzer.metrics, self.analyzer.class_names)

        return fig

    @param.depends('iou_th', 'score_th', 'bbox_match_method', watch=False)
    def get_global_metrics_table_total(self, metrics_type='global'):

        metrics_table = self.metrics_tables_total[metrics_type]

        matrics_table_widget = pn.widgets.DataFrame(
            metrics_table, name='Gloabl Performance Metrics',
            width=300)  #min_width=200, width_max=350, width_policy='fit')

        return matrics_table_widget

    @param.depends('iou_th', 'score_th', 'bbox_match_method', watch=False)
    def get_class_metrics_table_total(self, metrics_type='class'):

        metrics_table = self.metrics_tables_total[metrics_type]

        matrics_table_widget = pn.widgets.DataFrame(
            metrics_table,
            name='Per Class Performance Metrics',
            min_width=300,
            width_policy='fit')

        return matrics_table_widget

    def view(self, port=8081, show=True):
        # ---------------
        # define widgets
        # ---------------

        # define image with boxes
        # image_with_boxes_pn = pn.pane.Matplotlib(viewer.plot_image_with_boxes, dpi=288)

        tabs = pn.Tabs()

        # ------------------------------------
        # Total confusion matrix summary tab
        # ------------------------------------

        widgets1 = pn.Param(self.param,
                            widgets={
                                'bbox_match_method': {
                                    'type': pn.widgets.RadioButtonGroup,
                                    'button_type': 'success'
                                },
                                'iou_th': {
                                    'type': pn.widgets.FloatSlider,
                                    'step': 0.01
                                },
                                'score_th': {
                                    'type': pn.widgets.FloatSlider,
                                    'step': 0.01
                                },
                            })

        # cm_total = pn.pane.Matplotlib(self.fig_cm_total, sizing_mode='scale_both', max_width=1000, dpi=300)

        # metrics_global_total = pn.widgets.DataFrame(self.analyzer.metrics_tables['global'],
        #                                             name='Gloabl Performance Metrics'
        #                                             , width=300)  #min_width=200, width_max=350, width_policy='fit')
        # metrics_class_total = pn.widgets.DataFrame(self.analyzer.metrics_tables['class'],
        #                                            name='Per Class Performance Metrics',
        #                                            min_width=300, width_policy='fit')
        # metrics_global_total = pn.widgets.DataFrame(self.get_global_metrics_table_total,
        #                                             name='Gloabl Performance Metrics'
        #                                             , width=300)  #min_width=200, width_max=350, width_policy='fit')
        # metrics_class_total = pn.widgets.DataFrame(self.get_class_metrics_table_total,
        #                                            name='Per Class Performance Metrics',
        #                                            min_width=300, width_policy='fit')

        c1 = pn.Column(  #cm_total,
            self.plot_total_confusion_matrix2,
            pn.Spacer(height=5),
            widgets1,
        )
        # c2 = pn.Column(metrics_global_total, pn.Spacer(height=10), metrics_class_total)
        c2 = pn.Column(self.get_global_metrics_table_total,
                       pn.Spacer(height=10),
                       self.get_class_metrics_table_total)
        r1 = pn.Row(c1, pn.Spacer(width=10), c2)
        summary = r1

        tabs.extend([('Summary', summary)])

        # -------------------------------
        # Frames viewer tab
        # -------------------------------

        # define members widgets
        widgets = pn.Param(self.param,
                           widgets={
                               'frame_id': pn.widgets.DiscretePlayer,
                               'bbox_match_method': {
                                   'type': pn.widgets.RadioButtonGroup,
                                   'button_type': 'success'
                               },
                               'iou_th': {
                                   'type': pn.widgets.FloatSlider,
                                   'step': 0.01
                               },
                               'score_th': {
                                   'type': pn.widgets.FloatSlider,
                                   'step': 0.01
                               },
                           })

        c1 = pn.Column(self.plot_image_with_boxes,
                       height_policy='fit',
                       width_policy='max',
                       max_width=1024)

        c2 = pn.Column(
            self.plot_confusion_matrix,
            pn.Spacer(height=5),
            self.param.frame_id,
            pn.Spacer(height=5),
            widgets,
        )
        c3 = pn.Column(self.get_global_metrics_table, pn.Spacer(height=10),
                       self.get_class_metrics_table)

        r1 = pn.Row(c1, c2, pn.Spacer(width=50), c3)

        frames_viewer = r1

        tabs.extend([('Frames Viewer', frames_viewer)])

        tabs.servable()

        self.tabs = tabs

        # -----------
        # deploy app
        # -----------

        if show:  # if running outside of jupyter notebook
            # FIXME: should use better mechanism for selecting ports
            not_done = True
            while not_done:
                try:
                    self.tabs.show(port=port)
                except Exception as e:
                    if str(e) != '[Errno 98] Address already in use':
                        print(e)
                        not_done = False
                        raise
                    else:  # try next port
                        port += 1
Esempio n. 5
0
 class Test(param.Parameterized):
     a = param.Number(default=1.2, bounds=(0, 5), precedence=-1)
     b = param.Boolean(default=True, precedence=1)
Esempio n. 6
0
class GraphPlot(CompositeElementPlot, ColorbarPlot, LegendPlot):

    arrowhead_length = param.Number(default=0.025,
                                    doc="""
      If directed option is enabled this determines the length of the
      arrows as fraction of the overall extent of the graph.""")

    directed = param.Boolean(default=False,
                             doc="""
      Whether to draw arrows on the graph edges to indicate the
      directionality of each edge.""")

    selection_policy = param.ObjectSelector(default='nodes',
                                            objects=['edges', 'nodes', None],
                                            doc="""
        Determines policy for inspection of graph components, i.e. whether to highlight
        nodes or edges when selecting connected edges and nodes respectively."""
                                            )

    inspection_policy = param.ObjectSelector(default='nodes',
                                             objects=['edges', 'nodes', None],
                                             doc="""
        Determines policy for inspection of graph components, i.e. whether to highlight
        nodes or edges when hovering over connected edges and nodes respectively."""
                                             )

    tools = param.List(default=['hover', 'tap'],
                       doc="""
        A list of plugin tools to use on the plot.""")

    # Deprecated options

    color_index = param.ClassSelector(default=None,
                                      class_=(basestring, int),
                                      allow_None=True,
                                      doc="""
        Deprecated in favor of color style mapping, e.g. `node_color=dim('color')`"""
                                      )

    edge_color_index = param.ClassSelector(default=None,
                                           class_=(basestring, int),
                                           allow_None=True,
                                           doc="""
        Deprecated in favor of color style mapping, e.g. `edge_color=dim('color')`"""
                                           )

    # Map each glyph to a style group
    _style_groups = {
        'scatter': 'node',
        'multi_line': 'edge',
        'patches': 'edge',
        'bezier': 'edge'
    }

    style_opts = (['edge_' + p for p in fill_properties + line_properties] +
                  ['node_' + p for p in fill_properties + line_properties] +
                  ['node_size', 'cmap', 'edge_cmap', 'node_cmap'])

    _nonvectorized_styles = ['cmap', 'edge_cmap', 'node_cmap']

    # Filled is only supported for subclasses
    filled = False

    # Bezier paths
    bezier = False

    # Declares which columns in the data refer to node indices
    _node_columns = [0, 1]

    @property
    def edge_glyph(self):
        if self.filled:
            return 'patches_1'
        elif self.bezier:
            return 'bezier_1'
        else:
            return 'multi_line_1'

    def _hover_opts(self, element):
        if self.inspection_policy == 'nodes':
            dims = element.nodes.dimensions()
            dims = [(dims[2].pprint_label, '@{index_hover}')] + dims[3:]
        elif self.inspection_policy == 'edges':
            kdims = [(kd.pprint_label,
                      '@{%s_values}' % kd) if kd in ('start', 'end') else kd
                     for kd in element.kdims]
            dims = kdims + element.vdims
        else:
            dims = []
        return dims, {}

    def get_extents(self, element, ranges, range_type='combined'):
        return super(GraphPlot, self).get_extents(element.nodes, ranges,
                                                  range_type)

    def _get_axis_dims(self, element):
        return element.nodes.dimensions()[:2]

    def _get_edge_colors(self, element, ranges, edge_data, edge_mapping,
                         style):
        cdim = element.get_dimension(self.edge_color_index)
        if not cdim:
            return
        elstyle = self.lookup_options(element, 'style')
        cycle = elstyle.kwargs.get('edge_color')

        idx = element.get_dimension_index(cdim)
        field = dimension_sanitizer(cdim.name)
        cvals = element.dimension_values(cdim)
        if idx in self._node_columns:
            factors = element.nodes.dimension_values(2, expanded=False)
        elif idx == 2 and cvals.dtype.kind in 'uif':
            factors = None
        else:
            factors = unique_array(cvals)

        default_cmap = 'viridis' if factors is None else 'tab20'
        cmap = style.get('edge_cmap', style.get('cmap', default_cmap))
        nan_colors = {
            k: rgba_tuple(v)
            for k, v in self.clipping_colors.items()
        }
        if factors is None or (factors.dtype.kind in 'uif'
                               and idx not in self._node_columns):
            colors, factors = None, None
        else:
            if factors.dtype.kind == 'f':
                cvals = cvals.astype(np.int32)
                factors = factors.astype(np.int32)
            if factors.dtype.kind not in 'SU':
                field += '_str__'
                cvals = [str(f) for f in cvals]
                factors = (str(f) for f in factors)
            factors = list(factors)
            if isinstance(cmap, dict):
                colors = [
                    cmap.get(f, nan_colors.get('NaN', self._default_nan))
                    for f in factors
                ]
            else:
                colors = process_cmap(cycle or cmap, len(factors))
        if field not in edge_data:
            edge_data[field] = cvals
        edge_style = dict(style, cmap=cmap)
        mapper = self._get_colormapper(cdim, element, ranges, edge_style,
                                       factors, colors, 'edge',
                                       'edge_colormapper')
        transform = {'field': field, 'transform': mapper}
        color_type = 'fill_color' if self.filled else 'line_color'
        edge_mapping['edge_' + color_type] = transform
        edge_mapping['edge_nonselection_' + color_type] = transform
        edge_mapping['edge_selection_' + color_type] = transform

    def _get_edge_paths(self, element, ranges):
        path_data, mapping = {}, {}
        xidx, yidx = (1, 0) if self.invert_axes else (0, 1)
        if element._edgepaths is not None:
            edges = element._split_edgepaths.split(
                datatype='array', dimensions=element.edgepaths.kdims)
            if len(edges) == len(element):
                path_data['xs'] = [path[:, xidx] for path in edges]
                path_data['ys'] = [path[:, yidx] for path in edges]
                mapping = {'xs': 'xs', 'ys': 'ys'}
            else:
                raise ValueError(
                    "Edge paths do not match the number of supplied edges."
                    "Expected %d, found %d paths." %
                    (len(element), len(edges)))
        elif self.directed:
            xdim, ydim = element.nodes.kdims[:2]
            x_range = ranges[xdim.name]['combined']
            y_range = ranges[ydim.name]['combined']
            arrow_len = np.hypot(y_range[1] - y_range[0], x_range[1] -
                                 x_range[0]) * self.arrowhead_length
            arrows = get_directed_graph_paths(element, arrow_len)
            path_data['xs'] = [arr[:, 0] for arr in arrows]
            path_data['ys'] = [arr[:, 1] for arr in arrows]
        return path_data, mapping

    def get_data(self, element, ranges, style):
        # Force static source to False
        static = self.static_source
        self.handles['static_source'] = static
        self.static_source = False

        # Get node data
        nodes = element.nodes.dimension_values(2)
        node_positions = element.nodes.array([0, 1])
        # Map node indices to integers
        if nodes.dtype.kind not in 'uif':
            node_indices = {v: i for i, v in enumerate(nodes)}
            index = np.array([node_indices[n] for n in nodes], dtype=np.int32)
            layout = {
                str(node_indices[k]): (y, x) if self.invert_axes else (x, y)
                for k, (x, y) in zip(nodes, node_positions)
            }
        else:
            index = nodes.astype(np.int32)
            layout = {
                str(k): (y, x) if self.invert_axes else (x, y)
                for k, (x, y) in zip(index, node_positions)
            }
        point_data = {'index': index}
        cycle = self.lookup_options(element, 'style').kwargs.get('node_color')
        if isinstance(cycle, Cycle):
            style.pop('node_color', None)
            colors = cycle
        else:
            colors = None
        cdata, cmapping = self._get_color_data(element.nodes,
                                               ranges,
                                               style,
                                               name='node_fill_color',
                                               colors=colors,
                                               int_categories=True)
        point_data.update(cdata)
        point_mapping = cmapping
        if 'node_fill_color' in point_mapping:
            style = {
                k: v
                for k, v in style.items() if k not in
                ['node_fill_color', 'node_nonselection_fill_color']
            }
            point_mapping['node_nonselection_fill_color'] = point_mapping[
                'node_fill_color']

        edge_mapping = {}
        nan_node = index.max() + 1 if len(index) else 0
        start, end = (element.dimension_values(i) for i in range(2))
        if nodes.dtype.kind == 'f':
            start, end = start.astype(np.int32), end.astype(np.int32)
        elif nodes.dtype.kind != 'i':
            start = np.array([node_indices.get(x, nan_node) for x in start],
                             dtype=np.int32)
            end = np.array([node_indices.get(y, nan_node) for y in end],
                           dtype=np.int32)
        path_data = dict(start=start, end=end)
        self._get_edge_colors(element, ranges, path_data, edge_mapping, style)
        if not static:
            pdata, pmapping = self._get_edge_paths(element, ranges)
            path_data.update(pdata)
            edge_mapping.update(pmapping)

        # Get hover data
        if 'hover' in self.handles:
            if self.inspection_policy == 'nodes':
                index_dim = element.nodes.get_dimension(2)
                point_data['index_hover'] = [
                    index_dim.pprint_value(v)
                    for v in element.nodes.dimension_values(2)
                ]
                for d in element.nodes.dimensions()[3:]:
                    point_data[dimension_sanitizer(
                        d.name)] = element.nodes.dimension_values(d)
            elif self.inspection_policy == 'edges':
                for d in element.dimensions():
                    dim_name = dimension_sanitizer(d.name)
                    if dim_name in ('start', 'end'):
                        dim_name += '_values'
                    path_data[dim_name] = element.dimension_values(d)
        data = {
            'scatter_1': point_data,
            self.edge_glyph: path_data,
            'layout': layout
        }
        mapping = {'scatter_1': point_mapping, self.edge_glyph: edge_mapping}
        return data, mapping, style

    def _update_datasource(self, source, data):
        """
        Update datasource with data for a new frame.
        """
        if isinstance(source, ColumnDataSource):
            if self.handles['static_source']:
                source.trigger('data', source.data, data)
            else:
                source.data.update(data)
        else:
            source.graph_layout = data

    def _init_filled_edges(self, renderer, properties, edge_mapping):
        "Replace edge renderer with filled renderer"
        glyph_model = Patches if self.filled else Bezier
        allowed_properties = glyph_model.properties()
        for glyph_type in ('', 'selection_', 'nonselection_', 'hover_',
                           'muted_'):
            glyph = getattr(renderer.edge_renderer, glyph_type + 'glyph', None)
            if glyph is None:
                continue
            group_properties = dict(properties)
            props = self._process_properties(self.edge_glyph, group_properties,
                                             {})
            filtered = self._filter_properties(props, glyph_type,
                                               allowed_properties)
            new_glyph = glyph_model(**dict(filtered, **edge_mapping))
            setattr(renderer.edge_renderer, glyph_type + 'glyph', new_glyph)

    def _get_graph_properties(self, plot, element, data, mapping, ranges,
                              style):
        "Computes the args and kwargs for the GraphRenderer"
        sources = []
        properties, mappings = {}, {}
        for key in ('scatter_1', self.edge_glyph):
            gdata = data.pop(key, {})
            group_style = dict(style)
            style_group = self._style_groups.get('_'.join(key.split('_')[:-1]))

            with abbreviated_exception():
                group_style = self._apply_transforms(element, gdata, ranges,
                                                     group_style, style_group)

            # Get source
            source = self._init_datasource(gdata)
            self.handles[key + '_source'] = source
            sources.append(source)

            # Get style
            others = [
                sg for sg in self._style_groups.values() if sg != style_group
            ]
            glyph_props = self._glyph_properties(plot, element, source, ranges,
                                                 group_style, style_group)
            for k, p in glyph_props.items():
                if any(k.startswith(o) for o in others):
                    continue
                properties[k] = p
            mappings.update(mapping.pop(key, {}))
        properties = {
            p: v
            for p, v in properties.items() if p not in ('legend', 'source')
        }
        properties.update(mappings)

        # Initialize graph layout
        layout = data.pop('layout', {})
        layout = StaticLayoutProvider(graph_layout=layout)
        self.handles['layout_source'] = layout

        return tuple(sources + [layout]), properties

    def _reorder_renderers(self, plot, renderer, mapping):
        "Reorders renderers based on the defined draw order"
        renderers = dict(
            {r: self.handles[r + '_glyph_renderer']
             for r in mapping},
            graph=renderer)
        other = [r for r in plot.renderers if r not in renderers.values()]
        graph_renderers = [
            renderers[k] for k in self._draw_order if k in renderers
        ]
        plot.renderers = other + graph_renderers

    def _set_interaction_policies(self, renderer):
        if self.selection_policy == 'nodes':
            renderer.selection_policy = NodesAndLinkedEdges()
        elif self.selection_policy == 'edges':
            renderer.selection_policy = EdgesAndLinkedNodes()
        else:
            renderer.selection_policy = None

        if self.inspection_policy == 'nodes':
            renderer.inspection_policy = NodesAndLinkedEdges()
        elif self.inspection_policy == 'edges':
            renderer.inspection_policy = EdgesAndLinkedNodes()
        else:
            renderer.inspection_policy = None

    def _init_glyphs(self, plot, element, ranges, source):
        # Get data and initialize data source
        style = self.style[self.cyclic_index]
        data, mapping, style = self.get_data(element, ranges, style)
        self.handles['previous_id'] = element._plot_id

        # Initialize GraphRenderer
        edge_mapping = {
            k: v
            for k, v in mapping[self.edge_glyph].items() if 'color' not in k
        }
        graph_args, properties = self._get_graph_properties(
            plot, element, data, mapping, ranges, style)
        renderer = plot.graph(*graph_args, **properties)
        if self.filled or self.bezier:
            self._init_filled_edges(renderer, properties, edge_mapping)
        self._set_interaction_policies(renderer)

        # Initialize other renderers
        if data and mapping:
            CompositeElementPlot._init_glyphs(self, plot, element, ranges,
                                              source, data, mapping, style)

        # Reorder renderers
        if self._draw_order:
            self._reorder_renderers(plot, renderer, mapping)

        self.handles['glyph_renderer'] = renderer
        self.handles['scatter_1_glyph_renderer'] = renderer.node_renderer
        self.handles[self.edge_glyph +
                     '_glyph_renderer'] = renderer.edge_renderer
        self.handles['scatter_1_glyph'] = renderer.node_renderer.glyph
        self.handles[self.edge_glyph + '_glyph'] = renderer.edge_renderer.glyph
        if 'hover' in self.handles:
            if self.handles['hover'].renderers == 'auto':
                self.handles['hover'].renderers = []
            self.handles['hover'].renderers.append(renderer)
class OperationParameters(param.Parameterized):
    physics = param.ObjectSelector(
        default='SW2',
        objects=['SW2', 'SW3'],
        doc='OP SW2 or OP SW3: Simulation physics. REQUIRED.',
        precedence=1,
    )
    incremental_memory = param.Integer(
        default=40,
        bounds=(1, None),
        softbounds=(20, 200),
        doc='OP INC: Incremental memory block size. REQUIRED.',
        precedence=2,
    )
    blocks_per_processor = param.Integer(
        default=1,
        bounds=(1, None),
        softbounds=(1, 10),
        doc='OP BLK: Number of preconditioning blocks per processor. REQUIRED.',
        precedence=4,
    )
    preconditioner_type = param.ObjectSelector(
        default=1,
        objects={
            '0 - none': 0,
            '1 - one level Additive Schwarz': 1,
            '2 - two level Additive Schwarz': 2,
            '3 - two level Hybrid': 3
        },
        doc='OP PRE: Solver pre-conditioner type. REQUIRED.',
        precedence=5,
    )
    transport = param.Integer(default=0,
                              bounds=(0, None),
                              doc='OP TRN: Number of transport constituents.',
                              precedence=-1)
    vessel = param.Boolean(
        default=True,
        doc='OP BT: Vessel movement active.',
        precedence=6,
    )
    vessel_entrainment = param.Boolean(
        default=False,
        doc='OP BTS: Vessel entrainment active.',
        precedence=-1,
    )
    second_order_temporal_coefficient_active = param.Boolean(
        default=True,
        doc='OP TEM: The second order temporal coefficient will be specified.',
        precedence=8,
    )
    second_order_temporal_coefficient = param.Number(
        default=0,
        bounds=(0, 1),
        doc='OP TEM: Second order temporal coefficient.',
        precedence=8.1,
    )
    petrov_galerkin_coefficient_active = param.Boolean(
        default=True,
        doc="OP TPG: Petrov-Galerkin coefficient active.",
        precedence=9,
    )
    petrov_galerkin_coefficient = param.Number(
        default=0.0,
        bounds=(0.0, 0.5),
        doc="OP TPG: Petrov-Galerkin coefficient.",
        precedence=9.1,
    )
    velocity_gradient = param.Boolean(
        default=False,
        doc="OP NF2: Velocity gradient active.",
        precedence=10,
    )
    wind = param.Boolean(
        default=False,
        doc="OP WND: Wind stress active.",
        precedence=11,
    )
    wave = param.Boolean(
        default=False,
        doc="OP WAV: Short wave stress active.",
        precedence=12,
    )
    dam = param.Boolean(
        default=False,
        doc="OP DAM: Dam break stabilization active.",
        precedence=13,
    )
    diffusive_wave = param.Boolean(
        default=False,
        doc="OP DIF: Diffusive wave solver active.",
        precedence=14,
    )

    def __init__(self):
        super(OperationParameters, self).__init__()
        self._update_vessel_entrainment()
        self._update_second_order_temporal_coefficient_active()
        self._update_petrov_galerkin_coefficient_active()

    # vessel_entrainment requires vessel
    @param.depends('vessel', watch=True)
    def _update_vessel_entrainment(self):
        self.param.vessel_entrainment.precedence = -1
        if self.vessel:
            self.param.vessel_entrainment.precedence = 7

    @param.depends('second_order_temporal_coefficient_active', watch=True)
    def _update_second_order_temporal_coefficient_active(self):
        self.param.second_order_temporal_coefficient.precedence = -1
        if self.second_order_temporal_coefficient_active:
            self.param.second_order_temporal_coefficient.precedence = 8.1

    @param.depends('petrov_galerkin_coefficient_active', watch=True)
    def _update_petrov_galerkin_coefficient_active(self):
        self.param.petrov_galerkin_coefficient.precedence = -1
        if self.petrov_galerkin_coefficient_active:
            self.param.petrov_galerkin_coefficient.precedence = 9.1

    def set_not_required(self, value=False):
        self.vessel = value
        self.vessel_entrainment = value
        self.second_order_temporal_coefficient_active = value
        self.petrov_galerkin_coefficient_active = value
        self.velocity_gradient = value
        self.wind = value
        self.wave = value
        self.dam = value
        self.diffusive_wave = value

    def panel(self):
        return pn.Pane(self.param, show_name=False)
Esempio n. 8
0
class LabelsPlot(ColorbarPlot, AnnotationPlot):

    show_legend = param.Boolean(default=False,
                                doc="""
        Whether to show legend for the plot.""")

    xoffset = param.Number(default=None,
                           doc="""
      Amount of offset to apply to labels along x-axis.""")

    yoffset = param.Number(default=None,
                           doc="""
      Amount of offset to apply to labels along x-axis.""")

    # Deprecated options

    color_index = param.ClassSelector(default=None,
                                      class_=(basestring, int),
                                      allow_None=True,
                                      doc="""
        Deprecated in favor of color style mapping, e.g. `color=dim('color')`"""
                                      )

    style_opts = text_properties + ['cmap', 'angle']

    _nonvectorized_styles = ['cmap']

    _plot_methods = dict(single='text', batched='text')
    _batched_style_opts = text_properties

    def get_data(self, element, ranges, style):
        style = self.style[self.cyclic_index]
        if 'angle' in style and isinstance(style['angle'], (int, float)):
            style['angle'] = np.deg2rad(style.get('angle', 0))

        dims = element.dimensions()
        coords = (1, 0) if self.invert_axes else (0, 1)
        xdim, ydim, tdim = (dimension_sanitizer(dims[i].name)
                            for i in coords + (2, ))
        mapping = dict(x=xdim, y=ydim, text=tdim)
        data = {d: element.dimension_values(d) for d in (xdim, ydim)}
        if self.xoffset is not None:
            mapping['x'] = dodge(xdim, self.xoffset)
        if self.yoffset is not None:
            mapping['y'] = dodge(ydim, self.yoffset)
        data[tdim] = [
            dims[2].pprint_value(v) for v in element.dimension_values(2)
        ]
        self._categorize_data(data, (xdim, ydim), element.dimensions())

        cdim = element.get_dimension(self.color_index)
        if cdim is None:
            return data, mapping, style

        cdata, cmapping = self._get_color_data(element,
                                               ranges,
                                               style,
                                               name='text_color')
        if dims[2] is cdim and cdata:
            # If color dim is same as text dim, rename color column
            data['text_color'] = cdata[tdim]
            mapping['text_color'] = dict(cmapping['text_color'],
                                         field='text_color')
        else:
            data.update(cdata)
            mapping.update(cmapping)
        return data, mapping, style
Esempio n. 9
0
class Image(Dataset, Raster, SheetCoordinateSystem):
    """
    Image represents a regularly sampled 2D grid of an underlying
    continuous space of intensity values, which will be colormapped on
    plotting. The grid of intensity values may be specified as a NxM
    sized array of values along with a bounds, but it may also be
    defined through explicit and regularly spaced x/y-coordinate
    arrays of shape M and N respectively. The two most basic supported
    constructors of an Image therefore include:

        Image((X, Y, Z))

    where X is a 1D array of shape M, Y is a 1D array of shape N and
    Z is a 2D array of shape NxM, or equivalently:

        Image(Z, bounds=(x0, y0, x1, y1))

    where Z is a 2D array of shape NxM defining the intensity values
    and the bounds define the (left, bottom, top, right) edges of four
    corners of the grid. Other gridded formats which support declaring
    of explicit x/y-coordinate arrays such as xarray are also
    supported.

    Note that the interpretation of the orientation of the array
    changes depending on whether bounds or explicit coordinates are
    used.
    """

    bounds = param.ClassSelector(class_=BoundingRegion,
                                 default=BoundingBox(),
                                 doc="""
        The bounding region in sheet coordinates containing the data.""")

    datatype = param.List(
        default=['grid', 'xarray', 'image', 'cube', 'dataframe', 'dictionary'])

    group = param.String(default='Image', constant=True)

    kdims = param.List(default=[Dimension('x'), Dimension('y')],
                       bounds=(2, 2),
                       constant=True,
                       doc="""
        The label of the x- and y-dimension of the Raster in the form
        of a string or dimension object.""")

    vdims = param.List(default=[Dimension('z')],
                       bounds=(1, 1),
                       doc="""
        The dimension description of the data held in the matrix.""")

    rtol = param.Number(default=None,
                        doc="""
        The tolerance used to enforce regular sampling for regular, gridded
        data where regular sampling is expected. Expressed as the maximal
        allowable sampling difference between sample locations.""")

    _ndim = 2

    def __init__(self,
                 data,
                 kdims=None,
                 vdims=None,
                 bounds=None,
                 extents=None,
                 xdensity=None,
                 ydensity=None,
                 rtol=None,
                 **params):
        supplied_bounds = bounds
        if isinstance(data, Image):
            bounds = bounds or data.bounds
            xdensity = xdensity or data.xdensity
            ydensity = ydensity or data.ydensity
            if rtol is None: rtol = data.rtol

        extents = extents if extents else (None, None, None, None)
        if (data is None or (isinstance(data, (list, tuple)) and not data)
                or (isinstance(data, np.ndarray) and data.size == 0)):
            data = data if isinstance(
                data, np.ndarray) and data.ndim == 2 else np.zeros((0, 0))
            bounds = 0
            if not xdensity: xdensity = 1
            if not ydensity: ydensity = 1
        elif isinstance(data, np.ndarray) and data.ndim != self._ndim:
            raise ValueError('%s type expects %d-D array received %d-D'
                             'array.' % (self._ndim, data.ndim))

        if rtol is not None:
            params['rtol'] = rtol
        else:
            params['rtol'] = config.image_rtol

        Dataset.__init__(self,
                         data,
                         kdims=kdims,
                         vdims=vdims,
                         extents=extents,
                         **params)
        if not self.interface.gridded:
            raise DataError(
                "%s type expects gridded data, %s is columnar."
                "To display columnar data as gridded use the HeatMap "
                "element or aggregate the data." %
                (type(self).__name__, self.interface.__name__))

        dim2, dim1 = self.interface.shape(self, gridded=True)[:2]
        if bounds is None:
            xvals = self.dimension_values(0, False)
            l, r, xdensity, _ = util.bound_range(xvals, xdensity,
                                                 self._time_unit)
            yvals = self.dimension_values(1, False)
            b, t, ydensity, _ = util.bound_range(yvals, ydensity,
                                                 self._time_unit)
            bounds = BoundingBox(points=((l, b), (r, t)))
        elif np.isscalar(bounds):
            bounds = BoundingBox(radius=bounds)
        elif isinstance(bounds, (tuple, list, np.ndarray)):
            l, b, r, t = bounds
            bounds = BoundingBox(points=((l, b), (r, t)))

        data_bounds = None
        if self.interface is ImageInterface and not isinstance(
                data, np.ndarray):
            data_bounds = self.bounds.lbrt()
        l, b, r, t = bounds.lbrt()
        xdensity = xdensity if xdensity else util.compute_density(
            l, r, dim1, self._time_unit)
        ydensity = ydensity if ydensity else util.compute_density(
            b, t, dim2, self._time_unit)
        if not util.isfinite(xdensity) or not util.isfinite(ydensity):
            raise ValueError(
                'Density along Image axes could not be determined. '
                'If the data contains only one coordinate along the '
                'x- or y-axis ensure you declare the bounds and/or '
                'density.')
        SheetCoordinateSystem.__init__(self, bounds, xdensity, ydensity)
        self._validate(data_bounds, supplied_bounds)

    def _validate(self, data_bounds, supplied_bounds):
        if len(self.shape) == 3:
            if self.shape[2] != len(self.vdims):
                raise ValueError(
                    "Input array has shape %r but %d value dimensions defined"
                    % (self.shape, len(self.vdims)))

        # Ensure coordinates are regularly sampled
        xdim, ydim = self.kdims
        xvals, yvals = (self.dimension_values(d, expanded=False)
                        for d in self.kdims)
        xvalid = util.validate_regular_sampling(xvals, self.rtol)
        yvalid = util.validate_regular_sampling(yvals, self.rtol)
        msg = ("{clsname} dimension{dims} not evenly sampled to relative "
               "tolerance of {rtol}. Please use the QuadMesh element for "
               "irregularly sampled data or set a higher tolerance on "
               "hv.config.image_rtol or the rtol parameter in the "
               "{clsname} constructor.")
        dims = None
        if not xvalid:
            dims = ' %s is ' % xdim if yvalid else '(s) %s and %s are' % (xdim,
                                                                          ydim)
        elif not yvalid:
            dims = ' %s is' % ydim
        if dims:
            self.warning(
                msg.format(clsname=type(self).__name__,
                           dims=dims,
                           rtol=self.rtol))

        if not supplied_bounds:
            return

        if data_bounds is None:
            (x0, x1), (y0, y1) = (self.interface.range(self, kd.name)
                                  for kd in self.kdims)
            xstep = (1. / self.xdensity) / 2.
            ystep = (1. / self.ydensity) / 2.
            if not isinstance(x0, util.datetime_types):
                x0, x1 = (x0 - xstep, x1 + xstep)
            if not isinstance(y0, util.datetime_types):
                y0, y1 = (y0 - ystep, y1 + ystep)
            bounds = (x0, y0, x1, y1)
        else:
            bounds = data_bounds

        not_close = False
        for r, c in zip(bounds, self.bounds.lbrt()):
            if isinstance(r, util.datetime_types):
                r = util.dt_to_int(r)
            if isinstance(c, util.datetime_types):
                c = util.dt_to_int(c)
            if util.isfinite(r) and not np.isclose(r, c, rtol=self.rtol):
                not_close = True
        if not_close:
            raise ValueError(
                'Supplied Image bounds do not match the coordinates defined '
                'in the data. Bounds only have to be declared if no coordinates '
                'are supplied, otherwise they must match the data. To change '
                'the displayed extents set the range on the x- and y-dimensions.'
            )

    def __setstate__(self, state):
        """
        Ensures old-style unpickled Image types without an interface
        use the ImageInterface.

        Note: Deprecate as part of 2.0
        """
        self.__dict__ = state
        if isinstance(self.data, np.ndarray):
            self.interface = ImageInterface
        super(Dataset, self).__setstate__(state)

    def clone(self,
              data=None,
              shared_data=True,
              new_type=None,
              *args,
              **overrides):
        """
        Returns a clone of the object with matching parameter values
        containing the specified args and kwargs.

        If shared_data is set to True and no data explicitly supplied,
        the clone will share data with the original. May also supply
        a new_type, which will inherit all shared parameters.
        """
        if data is None and (new_type is None or issubclass(new_type, Image)):
            sheet_params = dict(bounds=self.bounds,
                                xdensity=self.xdensity,
                                ydensity=self.ydensity)
            overrides = dict(sheet_params, **overrides)
        return super(Image, self).clone(data, shared_data, new_type, *args,
                                        **overrides)

    def aggregate(self,
                  dimensions=None,
                  function=None,
                  spreadfn=None,
                  **kwargs):
        agg = super(Image, self).aggregate(dimensions, function, spreadfn,
                                           **kwargs)
        return Curve(agg) if isinstance(agg, Dataset) and len(
            self.vdims) == 1 else agg

    def select(self, selection_specs=None, **selection):
        """
        Allows selecting data by the slices, sets and scalar values
        along a particular dimension. The indices should be supplied as
        keywords mapping between the selected dimension and
        value. Additionally selection_specs (taking the form of a list
        of type.group.label strings, types or functions) may be
        supplied, which will ensure the selection is only applied if the
        specs match the selected object.
        """
        if selection_specs and not any(
                self.matches(sp) for sp in selection_specs):
            return self

        selection = {
            self.get_dimension(k).name:
            slice(*sel) if isinstance(sel, tuple) else sel
            for k, sel in selection.items() if k in self.kdims
        }
        coords = tuple(
            selection[kd.name] if kd.name in selection else slice(None)
            for kd in self.kdims)

        shape = self.interface.shape(self, gridded=True)
        if any([isinstance(el, slice) for el in coords]):
            bounds = compute_slice_bounds(coords, self, shape[:2])

            xdim, ydim = self.kdims
            l, b, r, t = bounds.lbrt()

            # Situate resampled region into overall slice
            y0, y1, x0, x1 = Slice(bounds, self)
            y0, y1 = shape[0] - y1, shape[0] - y0
            selection = (slice(y0, y1), slice(x0, x1))
            sliced = True
        else:
            y, x = self.sheet2matrixidx(coords[0], coords[1])
            y = shape[0] - y - 1
            selection = (y, x)
            sliced = False

        datatype = list(
            util.unique_iterator([self.interface.datatype] + self.datatype))
        data = self.interface.ndloc(self, selection)
        if not sliced:
            if np.isscalar(data):
                return data
            elif isinstance(data, tuple):
                data = data[self.ndims:]
            return self.clone(data,
                              kdims=[],
                              new_type=Dataset,
                              datatype=datatype)
        else:
            return self.clone(data,
                              xdensity=self.xdensity,
                              datatype=datatype,
                              ydensity=self.ydensity,
                              bounds=bounds)

    def sample(self, samples=[], **kwargs):
        """
        Allows sampling of an Image as an iterator of coordinates
        matching the key dimensions, returning a new object containing
        just the selected samples. Alternatively may supply kwargs to
        sample a coordinate on an object. On an Image the coordinates
        are continuously indexed and will always snap to the nearest
        coordinate.
        """
        kwargs = {k: v for k, v in kwargs.items() if k != 'closest'}
        if kwargs and samples:
            raise Exception(
                'Supply explicit list of samples or kwargs, not both.')
        elif kwargs:
            sample = [slice(None) for _ in range(self.ndims)]
            for dim, val in kwargs.items():
                sample[self.get_dimension_index(dim)] = val
            samples = [tuple(sample)]

        # If a 1D cross-section of 2D space return Curve
        shape = self.interface.shape(self, gridded=True)
        if len(samples) == 1:
            dims = [
                kd for kd, v in zip(self.kdims, samples[0])
                if not (np.isscalar(v) or isinstance(v, util.datetime_types))
            ]
            if len(dims) == 1:
                kdims = [self.get_dimension(kd) for kd in dims]
                sample = tuple(
                    np.datetime64(s) if isinstance(s, util.datetime_types
                                                   ) else s
                    for s in samples[0])
                sel = {kd.name: s for kd, s in zip(self.kdims, sample)}
                dims = [kd for kd, v in sel.items() if not np.isscalar(v)]
                selection = self.select(**sel)
                selection = tuple(
                    selection.columns(kdims + self.vdims).values())
                datatype = list(
                    util.unique_iterator(self.datatype +
                                         ['dataframe', 'dict']))
                return self.clone(selection,
                                  kdims=kdims,
                                  new_type=Curve,
                                  datatype=datatype)
            else:
                kdims = self.kdims
        else:
            kdims = self.kdims

        xs, ys = zip(*samples)
        if isinstance(xs[0], util.datetime_types):
            xs = np.array(xs).astype(np.datetime64)
        if isinstance(ys[0], util.datetime_types):
            ys = np.array(ys).astype(np.datetime64)
        yidx, xidx = self.sheet2matrixidx(np.array(xs), np.array(ys))
        yidx = shape[0] - yidx - 1

        # Detect out-of-bounds indices
        out_of_bounds = (yidx < 0) | (xidx < 0) | (yidx >= shape[0]) | (
            xidx >= shape[1])
        if out_of_bounds.any():
            coords = [samples[idx] for idx in np.where(out_of_bounds)[0]]
            raise IndexError(
                'Coordinate(s) %s out of bounds for %s with bounds %s' %
                (coords, type(self).__name__, self.bounds.lbrt()))

        data = self.interface.ndloc(self, (yidx, xidx))
        return self.clone(data, new_type=Table, datatype=['dataframe', 'dict'])

    def closest(self, coords=[], **kwargs):
        """
        Given a single coordinate or multiple coordinates as
        a tuple or list of tuples or keyword arguments matching
        the dimension closest will find the closest actual x/y
        coordinates.
        """
        if kwargs and coords:
            raise ValueError("Specify coordinate using as either a list "
                             "keyword arguments not both")
        if kwargs:
            coords = []
            getter = []
            for k, v in kwargs.items():
                idx = self.get_dimension_index(k)
                if np.isscalar(v):
                    coords.append((0, v) if idx else (v, 0))
                else:
                    if isinstance(v, list):
                        coords = [(0, c) if idx else (c, 0) for c in v]
                    if len(coords) not in [0, len(v)]:
                        raise ValueError("Length of samples must match")
                    elif len(coords):
                        coords = [(t[abs(idx - 1)], c) if idx else
                                  (c, t[abs(idx - 1)])
                                  for c, t in zip(v, coords)]
                getter.append(idx)
        else:
            getter = [0, 1]
        getter = itemgetter(*sorted(getter))
        if len(coords) == 1:
            coords = coords[0]
        if isinstance(coords, tuple):
            return getter(self.closest_cell_center(*coords))
        else:
            return [getter(self.closest_cell_center(*el)) for el in coords]

    def range(self, dim, data_range=True, dimension_range=True):
        idx = self.get_dimension_index(dim)
        dimension = self.get_dimension(dim)
        if idx in [0, 1] and data_range and dimension.range == (None, None):
            if self.interface.datatype == 'image':
                l, b, r, t = self.bounds.lbrt()
                return (b, t) if idx else (l, r)
            low, high = super(Image, self).range(dim, data_range)
            density = self.ydensity if idx else self.xdensity
            halfd = (1. / density) / 2.
            if isinstance(low, util.datetime_types):
                halfd = np.timedelta64(int(round(halfd)), self._time_unit)
            return (low - halfd, high + halfd)
        else:
            return super(Image, self).range(dim, data_range, dimension_range)

    def table(self, datatype=None):
        """
        Converts the data Element to a Table, optionally may
        specify a supported data type. The default data types
        are 'numpy' (for homogeneous data), 'dataframe', and
        'dictionary'.
        """
        if datatype and not isinstance(datatype, list):
            datatype = [datatype]
        from ..element import Table
        return self.clone(self.columns(),
                          new_type=Table,
                          **(dict(datatype=datatype) if datatype else {}))

    def _coord2matrix(self, coord):
        return self.sheet2matrixidx(*coord)
Esempio n. 10
0
class Widgets(param.ParameterizedFunction):

    callback = param.Callable(default=None,
                              doc="""
        Custom callable to execute on button press
        (if `button`) else whenever a widget is changed,
        Should accept a Parameterized object argument.""")

    view_position = param.ObjectSelector(
        default='below',
        objects=['below', 'right', 'left', 'above'],
        doc="""
        Layout position of any View parameter widgets.""")

    next_n = param.Parameter(default=0,
                             doc="""
        When executing cells, integer number to execute (or 'all').
        A value of zero means not to control cell execution.""")

    on_init = param.Boolean(default=False,
                            doc="""
        Whether to do the action normally taken (executing cells
        and/or calling a callable) when first instantiating this
        object.""")

    button = param.Boolean(default=False,
                           doc="""
        Whether to show a button to control cell execution.
        If false, will execute `next` cells on any widget
        value change.""")

    button_text = param.String(default="Run",
                               doc="""
        Text to show on the 'next_n'/run button.""")

    show_labels = param.Boolean(default=True)

    display_threshold = param.Number(default=0,
                                     precedence=-10,
                                     doc="""
        Parameters with precedence below this value are not displayed.""")

    default_precedence = param.Number(default=1e-8,
                                      precedence=-10,
                                      doc="""
        Precedence value to use for parameters with no declared precedence.
        By default, zero predecence is available for forcing some parameters
        to the top of the list, and other values above the default_precedence
        values can be used to sort or group parameters arbitrarily.""")

    initializer = param.Callable(default=None,
                                 doc="""
        User-supplied function that will be called on initialization,
        usually to update the default Parameter values of the
        underlying parameterized object.""")

    layout = param.ObjectSelector(default='column',
                                  objects=['row', 'column'],
                                  doc="""
        Whether to lay out the buttons as a row or a column.""")

    continuous_update = param.Boolean(default=False,
                                      doc="""
        If true, will continuously update the next_n and/or callback,
        if any, as a slider widget is dragged.""")

    mode = param.ObjectSelector(default='notebook',
                                objects=['server', 'raw', 'notebook'],
                                doc="""
        Whether to use the widgets in server or notebook mode. In raw mode
        the widgets container will simply be returned.""")

    push = param.Boolean(default=True,
                         doc="""
        Whether to push data in notebook mode. Allows disabling pushing
        of data if the callback handles this itself.""")

    width = param.Integer(default=300,
                          bounds=(0, None),
                          doc="""
        Width of widgetbox the parameter widgets are displayed in.""")

    # Timeout if a notebook comm message is swallowed
    timeout = 20000

    # Timeout before the first event is processed
    debounce = 20

    def __call__(self, parameterized, doc=None, plots=[], **params):
        self.p = param.ParamOverrides(self, params)
        if self.p.initializer:
            self.p.initializer(parameterized)

        self._widgets = {}
        self.parameterized = parameterized
        self.document = None
        self.comm_target = None
        if self.p.mode == 'notebook':
            if not IPYTHON_AVAILABLE:
                raise ImportError('IPython is not available, cannot use '
                                  'Widgets in notebook mode.')
            self.comm = JupyterCommJS(on_msg=self.on_msg)
            # HACK: Detects HoloViews plots and lets them handle the comms
            hv_plots = [plot for plot in plots if hasattr(plot, 'comm')]
            if hv_plots:
                self.comm_target = [p.comm.id for p in hv_plots][0]
                self.document = [p.document for p in hv_plots][0]
                plots = [p.state for p in plots]
                self.p.push = False
            else:
                self.comm_target = uuid.uuid4().hex
                self.document = doc or Document()
        else:
            self.document = doc or curdoc()

        self._queue = []
        self._active = False

        self._widget_options = {}
        self.shown = False

        widgets, views = self.widgets()
        plots = views + plots
        container = widgetbox(widgets, width=self.p.width)
        if plots:
            view_box = column(plots)
            layout = self.p.view_position
            if layout in ['below', 'right']:
                children = [container, view_box]
            else:
                children = [view_box, container]
            container_type = column if layout in ['below', 'above'] else row
            container = container_type(children=children)
        for view in views:
            p_obj = self.parameterized.params(view.name)
            value = getattr(self.parameterized, view.name)
            if value is not None:
                rendered = p_obj.renderer(value, p_obj)
                self._update_trait(view.name, rendered)

        # Keeps track of changes between button presses
        self._changed = {}

        if self.p.on_init:
            self.execute()

        if self.p.mode == 'raw':
            return container

        self.document.add_root(container)
        if self.p.mode == 'notebook':
            self.notebook_handle = notebook_show(container, self.document,
                                                 self.comm_target)
            if self.document._hold is None:
                self.document.hold()
            self.shown = True
            return
        return self.document

    def on_msg(self, msg):
        p_name = msg['p_name']
        p_obj = self.parameterized.params(p_name)
        w = self._widgets[p_name]
        self._queue.append((w, p_obj, p_name, None, None, msg['value']))
        self.change_event()

    def on_change(self, w, p_obj, p_name, attr, old, new):
        self._queue.append((w, p_obj, p_name, attr, old, new))
        if not self._active:
            self.document.add_timeout_callback(self.change_event, 50)
            self._active = True

    def change_event(self):
        if not self._queue:
            self._active = False
            return
        w, p_obj, p_name, attr, old, new_values = self._queue[-1]
        self._queue = []

        error = False
        # Apply literal evaluation to values
        if (isinstance(w, TextInput) and isinstance(p_obj, literal_params)):
            try:
                new_values = ast.literal_eval(new_values)
            except:
                error = 'eval'

        if p_name in self._widget_options:
            mapping = self._widget_options[p_name]
            if isinstance(new_values, list):
                new_values = [mapping[el] for el in new_values]
            else:
                new_values = mapping.get(new_values, new_values)

        if isinstance(p_obj, param.Range):
            new_values = tuple(new_values)

        # If no error during evaluation try to set parameter
        if not error:
            try:
                setattr(self.parameterized, p_name, new_values)
            except ValueError:
                error = 'validation'

        # Style widget to denote error state
        # apply_error_style(w, error)

        if not error and not self.p.button:
            self.execute({p_name: new_values})
        else:
            self._changed[p_name] = new_values

        # document.hold() must have been done already? because this seems to work
        if self.p.mode == 'notebook' and self.p.push and self.document._held_events:
            push_notebook(handle=self.notebook_handle, document=self.document)
        self._active = False

    def _update_trait(self, p_name, p_value, widget=None):
        widget = self._widgets[p_name] if widget is None else widget
        if isinstance(p_value, tuple):
            p_value, size = p_value
        if isinstance(widget, Div):
            widget.text = p_value
        elif self.p.mode == 'notebook' and self.shown:
            return
        else:
            if widget.children:
                widget.children.remove(widget.children[0])
            widget.children.append(p_value)

    def _make_widget(self, p_name):
        p_obj = self.parameterized.params(p_name)

        if isinstance(p_obj, _View):
            p_obj._comm_target = self.comm_target
            p_obj._document = self.document
            p_obj._notebook = self.p.mode == 'notebook'

        widget_class = wtype(p_obj)
        value = getattr(self.parameterized, p_name)

        kw = dict(value=value)
        if isinstance(p_obj, param.Action):

            def action_cb(button):
                getattr(self.parameterized, p_name)(self.parameterized)

            kw['value'] = action_cb

        kw['title'] = p_name

        if hasattr(p_obj, 'get_range') and not isinstance(kw['value'], dict):
            options = named_objs(p_obj.get_range().items())
            value = kw['value']
            lookup = {v: k for k, v in options}
            if isinstance(value, list):
                kw['value'] = [lookup[v] for v in value]
            else:
                kw['value'] = lookup[value]
            opt_lookup = {k: v for k, v in options}
            self._widget_options[p_name] = opt_lookup
            options = [(k, k) for k, v in options]
            kw['options'] = options

        if hasattr(p_obj, 'get_soft_bounds'):
            kw['start'], kw['end'] = p_obj.get_soft_bounds()

        w = widget_class(**kw)

        if hasattr(p_obj, 'callbacks') and value is not None:
            rendered = p_obj.renderer(value, p_obj)
            self._update_trait(p_name, rendered, w)

        if hasattr(p_obj, 'callbacks'):
            p_obj.callbacks[id(self.parameterized)] = functools.partial(
                self._update_trait, p_name)
        elif isinstance(w, (Button, Toggle)):
            if self.p.mode in ['server', 'raw']:
                w.on_change(
                    'active',
                    functools.partial(self.on_change, w, p_obj, p_name))
            else:
                js_callback = self._get_customjs('active', p_name)
                w.js_on_change('active', js_callback)
        elif not p_obj.constant:
            if self.p.mode in ['server', 'raw']:
                cb = functools.partial(self.on_change, w, p_obj, p_name)
                if 'value' in w.properties():
                    w.on_change('value', cb)
                elif 'range' in w.properties():
                    w.on_change('range', cb)
            else:
                if 'value' in w.properties():
                    change = 'value'
                elif 'range' in w.properties():
                    change = 'range'
                customjs = self._get_customjs(change, p_name)
                w.js_on_change(change, customjs)

        return w

    def _get_customjs(self, change, p_name):
        """
        Returns a CustomJS callback that can be attached to send the
        widget state across the notebook comms.
        """
        data_template = "data = {{p_name: '{p_name}', value: cb_obj['{change}']}};"
        fetch_data = data_template.format(change=change, p_name=p_name)
        self_callback = JS_CALLBACK.format(comm_id=self.comm.id,
                                           timeout=self.timeout,
                                           debounce=self.debounce)
        js_callback = CustomJS(code=fetch_data + self_callback)
        return js_callback

    def widget(self, param_name):
        """Get widget for param_name"""
        if param_name not in self._widgets:
            self._widgets[param_name] = self._make_widget(param_name)
        return self._widgets[param_name]

    def execute(self, changed={}):
        if self.p.callback is not None:
            if get_method_owner(self.p.callback) is self.parameterized:
                self.p.callback(**changed)
            else:
                self.p.callback(self.parameterized, **changed)

    def widgets(self):
        """Return name,widget boxes for all parameters (i.e., a property sheet)"""

        params = self.parameterized.params().items()
        key_fn = lambda x: x[1].precedence if x[
            1].precedence is not None else self.p.default_precedence
        sorted_precedence = sorted(params, key=key_fn)
        outputs = [k for k, p in sorted_precedence if isinstance(p, _View)]
        filtered = [
            (k, p) for (k, p) in sorted_precedence
            if ((p.precedence is None) or (
                p.precedence >= self.p.display_threshold)) and k not in outputs
        ]
        groups = itertools.groupby(filtered, key=key_fn)
        sorted_groups = [sorted(grp) for (k, grp) in groups]
        ordered_params = [el[0] for group in sorted_groups for el in group]

        # Format name specially
        ordered_params.pop(ordered_params.index('name'))
        widgets = [Div(text='<b>{0}</b>'.format(self.parameterized.name))]

        def format_name(pname):
            p = self.parameterized.params(pname)
            # omit name for buttons, which already show the name on the button
            name = "" if issubclass(type(p), param.Action) else pname
            return Div(text=name)

        if self.p.show_labels:
            widgets += [self.widget(pname) for pname in ordered_params]
        else:
            widgets += [self.widget(pname) for pname in ordered_params]

        if self.p.button and not (self.p.callback is None
                                  and self.p.next_n == 0):
            display_button = Button(label=self.p.button_text)

            def click_cb():
                # Execute and clear changes since last button press
                try:
                    self.execute(self._changed)
                except Exception as e:
                    self._changed.clear()
                    raise e
                self._changed.clear()

            display_button.on_click(click_cb)
            widgets.append(display_button)

        outputs = [self.widget(pname) for pname in outputs]
        return widgets, outputs
Esempio n. 11
0
class GeneratorSheet(Sheet):
    """
    Sheet for generating a series of 2D patterns.

    Typically generates the patterns by choosing parameters from a
    random distribution, but can use any mechanism.
    """

    src_ports = ['Activity']

    period = param.Number(
        default=1,
        bounds=(0, None),
        inclusive_bounds=(False, True),
        constant=True,
        doc="Delay (in Simulation time) between generating new input patterns."
    )

    phase = param.Number(default=0.05,
                         doc="""
        Delay after the start of the Simulation (at time zero) before
        generating an input pattern.  For a clocked, feedforward simulation, 
        one would typically want to use a small nonzero phase and use delays less
        than the user-visible step size (typically 1.0), so that inputs are
        generated and processed before this step is complete.
        """)

    input_generator = param.ClassSelector(
        PatternGenerator,
        default=Constant(),
        doc=
        """Specifies a particular PatternGenerator type to use when creating patterns."""
    )

    def __init__(self, **params):
        super(GeneratorSheet, self).__init__(**params)
        self.input_generator_stack = []
        self.set_input_generator(self.input_generator)

    def set_input_generator(self, new_ig, push_existing=False):
        """
        Set the input_generator, overwriting the existing one by default.

        If push_existing is false, the existing input_generator is
        discarded permanently.  Otherwise, the existing one is put
        onto a stack, and can later be restored by calling
        pop_input_generator.
        """

        if push_existing:
            self.push_input_generator()

        # CEBALERT: replaces any bounds specified for the
        # PatternGenerator with this sheet's own bounds. When
        # PatternGenerators can draw patterns into supplied
        # boundingboxes, should remove this.
        new_ig.set_matrix_dimensions(self.bounds, self.xdensity, self.ydensity)
        self.input_generator = new_ig

    def push_input_generator(self):
        """Push the current input_generator onto a stack for future retrieval."""
        self.input_generator_stack.append(self.input_generator)

        # CEBALERT: would be better to reorganize code so that
        # push_input_generator must be supplied with a new generator.
        # CEBALERT: presumably we can remove this import.
        from topo.base.patterngenerator import Constant
        self.set_input_generator(Constant())

    def pop_input_generator(self):
        """
        Discard the current input_generator, and retrieve the previous one from the stack.

        Warns if no input_generator is available on the stack.
        """
        if len(self.input_generator_stack) >= 1:
            self.set_input_generator(self.input_generator_stack.pop())
        else:
            self.warning('There is no previous input generator to restore.')

    def generate(self):
        """
        Generate the output and send it out the Activity port.
        """
        self.verbose("Generating a new pattern")

        # JABALERT: What does the [:] achieve here?  Copying the
        # values, instead of the pointer to the array?  Is that
        # guaranteed?
        self.activity[:] = self.input_generator()

        if self.apply_output_fns:
            for of in self.output_fns:
                of(self.activity)
        self.send_output(src_port='Activity', data=self.activity)

    def start(self):
        assert self.simulation

        if self.period > 0:
            # if it has a positive period, then schedule a repeating event to trigger it
            e = FunctionEvent(0, self.generate)
            now = self.simulation.time()
            self.simulation.enqueue_event(
                PeriodicEventSequence(
                    now + self.simulation.convert_to_time_type(self.phase),
                    self.simulation.convert_to_time_type(self.period), [e]))

    def input_event(self, conn, data):
        raise NotImplementedError
Esempio n. 12
0
class Model(param.Parameterized):
    """Data defining this stage"""

    # Values set by previous stage
    report_from_composition = param.Dict()

    # Parameters for the current stage
    ready = param.Boolean(default=False)
    net_gross = param.Number(label="Net/Gross", softbounds=(0, 100))
    scenario_name = param.String(label="Scenario Name")
    porosity_modifier = param.Number(
        default=0, label="Porosity Modifier", bounds=(0, 100)
    )
    net_gross_modified = param.Number(label="Modified N/G", bounds=(0, 100))

    def __init__(self, report_from_composition, net_gross):
        """Set initial values based on previous stage"""
        super().__init__()
        self._state = state.get_user_state().setdefault(APP, {})
        self._state.setdefault("scenarios", {})
        self._current_scenario_name = None
        self.net_gross = net_gross
        self.report_from_composition = report_from_composition
        self.scenario_name = f"Scenario {len(self._state['scenarios']) + 1}"
        self.data = charts.data_as_dataframe(report_from_composition, CFG.columns)

        try:
            session_id = pn.state.curdoc.session_context.id
            logger.insights(f"New result: {self.net_gross}, SessionID: {session_id}")
            logger.insights(
                f"SessionID: {session_id}, Choices: {report_from_composition}"
            )
        except AttributeError as e:
            logger.error(f"SessionID not available: {e}")
            logger.insights(f"New result: {self.net_gross}")
            logger.insights(f"Choices: {report_from_composition}")

    @param.depends("net_gross", watch=True)
    def update_porosity_bounds(self):
        net_gross = dict(self.param.get_param_values())["net_gross"]
        if self.porosity_modifier > net_gross:
            self.porosity_modifier = self.net_gross
        self.param.porosity_modifier.bounds = (0, round(net_gross))

    @param.depends("net_gross", "porosity_modifier", watch=True)
    def update_ng_from_porosity(self):
        """Calculate modified net gross based on porosity"""
        if self.porosity_modifier > self.net_gross:
            self.porosity_modifier = self.net_gross

        self.net_gross_modified = self.net_gross - self.porosity_modifier

    def _store_scenario(self):
        """Store results for the current scenario"""
        scenarios = self._state["scenarios"]

        # Make sure name does not overwrite previous scenarios
        if (
            self.scenario_name != self._current_scenario_name
            and self.scenario_name in scenarios
        ):
            for suffix in itertools.count(start=2):
                name = f"{self.scenario_name} ({suffix})"
                if name not in scenarios:
                    break
            self.scenario_name = name

        # Get information about scenario
        if self._current_scenario_name in scenarios:
            scenario_info = scenarios.pop(self._current_scenario_name)
        else:
            scenario_info = {
                "net_gross": self.net_gross,
                **self.report_from_composition,
            }

        # Update scenario information
        scenario_info["net_gross_modified"] = self.net_gross_modified
        scenario_info["porosity_modifier"] = self.porosity_modifier

        # Store scenario information
        scenarios[self.scenario_name] = scenario_info
        self._current_scenario_name = self.scenario_name

    @pn.depends("scenario_name", "porosity_modifier")
    def scenario_table(self):
        """Table showing current scenarios"""
        self._store_scenario()

        scenarios = self._state["scenarios"]
        table_data = pd.DataFrame(
            {
                "Scenario Name": [s for s in scenarios.keys()],
                "Net/Gross": [round(s["net_gross"]) for s in scenarios.values()],
                "Modified N/G": [
                    round(s["net_gross_modified"]) for s in scenarios.values()
                ],
            }
        ).set_index("Scenario Name")
        return pn.pane.DataFrame(table_data, sizing_mode="stretch_width")

    def new_scenario_button(self):
        """Button for starting new scenario"""
        reset_button = self._state["update_view"]["workflow"]

        def restart_scenario(event):
            """Start a new scenario by restarting the workflow"""
            reset_button.clicks += 1

        button = pn.widgets.Button(
            name="New Scenario", button_type="success", width=125
        )
        button.on_click(restart_scenario)
        return button

    @param.output(param.List)
    def scenario_names(self):
        """Pass on names of scenarios to next stage"""
        return [sn for sn in self._state["scenarios"].keys()]
Esempio n. 13
0
class QuickLookComponent(Component):

    data_repository = param.String(default=sample_data_directory,
                                   label=None,
                                   allow_None=True)

    query_filter = param.String(label="Query Expression")

    new_column_expr = param.String(label="Data Column Expression")

    tract_count = param.Number(default=0)

    status_message_queue = param.List(default=[])

    patch_count = param.Number(default=0)

    visit_count = param.Number(default=0)

    filter_count = param.Number(default=0)

    unique_object_count = param.Number(default=0)

    comparison = param.String()

    selected = param.Tuple(default=(None, None, None, None), length=4)

    selected_metrics_by_filter = param.Dict(
        default={f: []
                 for f in store.active_dataset.filters})

    selected_flag_filters = param.Dict(default={})

    view_mode = ['Skyplot View', 'Detail View']
    data_stack = ['Forced Coadd', 'Unforced Coadd']

    plot_top = None
    plots_list = []
    skyplot_list = []

    label = param.String(default='Quick Look')

    def __init__(self, store, **param):

        super().__init__(**param)

        self.store = store

        self._clear_metrics_button = pn.widgets.Button(name='Clear',
                                                       width=30,
                                                       align='end')
        self._clear_metrics_button.on_click(self._on_clear_metrics)

        self._submit_repository = pn.widgets.Button(name='Load Data',
                                                    width=50,
                                                    align='end')
        self._submit_repository.on_click(self._on_load_data_repository)

        self._submit_comparison = pn.widgets.Button(name='Submit',
                                                    width=50,
                                                    align='end')
        self._submit_comparison.on_click(self._update)

        self.flag_filter_select = pn.widgets.Select(
            name='Add Flag Filter',
            width=160,
            options=self.store.active_dataset.flags)

        self.flag_state_select = pn.widgets.Select(name='Flag State',
                                                   width=75,
                                                   options=['True', 'False'])

        self.flag_submit = pn.widgets.Button(name='Add Flag Filter',
                                             width=10,
                                             height=30,
                                             align='end')
        self.flag_submit.on_click(self.on_flag_submit_click)

        self.flag_filter_selected = pn.widgets.Select(
            name='Active Flag Filters', width=250)

        self.flag_remove = pn.widgets.Button(name='Remove Flag Filter',
                                             width=50,
                                             height=30,
                                             align='end')
        self.flag_remove.on_click(self.on_flag_remove_click)

        self.query_filter_submit = pn.widgets.Button(name='Run Query Filter',
                                                     width=100,
                                                     align='end')
        self.query_filter_submit.on_click(self.on_run_query_filter_click)

        self.query_filter_clear = pn.widgets.Button(name='Clear',
                                                    width=50,
                                                    align='end')
        self.query_filter_clear.on_click(self.on_query_filter_clear)

        self.new_column_submit = pn.widgets.Button(name='Define New Column',
                                                   width=100,
                                                   align='end')
        self.new_column_submit.on_click(self.on_define_new_column_click)

        self.status_message = pn.pane.HTML(sizing_mode='stretch_width',
                                           max_height=10)
        self.adhoc_js = pn.pane.HTML(sizing_mode='stretch_width',
                                     max_height=10)
        self._info = pn.pane.HTML(sizing_mode='stretch_width', max_height=10)
        self._flags = pn.pane.HTML(sizing_mode='stretch_width', max_height=10)
        self._metric_panels = []
        self._metric_layout = pn.Column()
        self._switch_view = self._create_switch_view_buttons()
        self._switch_stack = self._create_switch_datastack_buttons()
        self._plot_top = pn.Row(sizing_mode='stretch_width',
                                margin=(10, 10, 10, 10))

        self._plot_layout = pn.Column(sizing_mode='stretch_width',
                                      margin=(10, 10, 10, 10))

        self.skyplot_layout = pn.Column(sizing_mode='stretch_width',
                                        margin=(10, 10, 10, 10))

        self.list_layout = pn.Column(sizing_mode='stretch_width')

        self._update(None)

    def _on_load_data_repository(self, event):

        global datasets
        global datavisits
        global filtered_datavisits

        self.store.active_dataset = Dataset('')
        self.skyplot_list = []
        self.plots_list = []
        self.plot_top = None

        datasets = {}
        filtered_datasets = {}
        datavisits = {}
        filtered_datavisits = {}

        self._load_metrics()
        self._switch_view_mode()
        self.update_display()

        data_repo_path = self.data_repository
        self.add_status_message('Load Data Start...',
                                data_repo_path,
                                level='info')

        dstack_switch_val = self._switch_stack.value.lower()
        datastack = 'unforced' if 'unforced' in dstack_switch_val else 'forced'
        try:
            self.store.active_dataset = load_data(data_repo_path, datastack)

        except Exception as e:
            self.update_display()
            self.add_message_from_error('Data Loading Error', data_repo_path,
                                        e)
            raise

        self.add_status_message('Data Ready',
                                data_repo_path,
                                level='success',
                                duration=3)
        # update ui
        self.flag_filter_select.options = self.store.active_dataset.flags

        for f in self.store.active_dataset.filters:
            self.selected_metrics_by_filter[f] = []

        self._load_metrics()
        self._switch_view_mode()
        self.update_display()

    def update_display(self):
        self.set_checkbox_style()

    def set_checkbox_style(self):
        code = '''$("input[type='checkbox']").addClass("metric-checkbox");'''
        self.execute_js_script(code)

        global store
        for filter_type, fails in store.active_dataset.failures.items():
            error_metrics = json.dumps(fails)
            code = '$(".' + filter_type + '-checkboxes .metric-checkbox").siblings().filter(function () { return ' + error_metrics + '.indexOf($(this).text()) > -1;}).css("color", "orange");'
            self.execute_js_script(code)

    def add_status_message(self, title, body, level='info', duration=5):
        msg = {'title': title, 'body': body}
        msg_args = dict(msg=msg, level=level, duration=duration)
        self.status_message_queue.append(msg_args)
        self.param.trigger('status_message_queue')  # to work with panel 0.7
        # Drop message in terminal/logger too
        try:
            # temporary try/except until 'level' values are all checked
            getattr(logger, level)(msg)
        except:
            pass

    def on_flag_submit_click(self, event):
        flag_name = self.flag_filter_select.value
        flag_state = self.flag_state_select.value == 'True'
        self.selected_flag_filters.update({flag_name: flag_state})
        self.param.trigger('selected_flag_filters')
        self.add_status_message('Added Flag Filter',
                                '{} : {}'.format(flag_name, flag_state),
                                level='info')

    def on_flag_remove_click(self, event):
        flag_name = self.flag_filter_selected.value.split()[0]
        del self.selected_flag_filters[flag_name]
        self.param.trigger('selected_flag_filters')
        self.add_status_message('Removed Flag Filter', flag_name, level='info')

    def _on_clear_metrics(self, event):

        for k in self.selected_metrics_by_filter.keys():
            self.selected_metrics_by_filter[k] = []

        self.param.trigger('selected_metrics_by_filter')

        code = '''$("input[type='checkbox']").prop("checked", false);'''
        self.execute_js_script(code)

    def on_run_query_filter_click(self, event):
        pass

    def on_query_filter_clear(self, event):
        self.query_filter = ''
        pass

    def on_define_new_column_click(self, event):
        new_column_expr = self.new_column_expr
        logger.info("NEW COLUMN EXPRESSION: '{!s}'".format(new_column_expr))

    def _create_switch_view_buttons(self):
        radio_group = pn.widgets.RadioBoxGroup(name='SwitchView',
                                               options=self.view_mode,
                                               align='center',
                                               value=self.view_mode[0],
                                               inline=True)
        radio_group.param.watch(self._switch_view_mode, ['value'])
        return radio_group

    def _create_switch_datastack_buttons(self):
        radio_group = pn.widgets.RadioBoxGroup(name='SwitchDataStack',
                                               options=self.data_stack,
                                               align='center',
                                               value=self.data_stack[0],
                                               inline=True)
        radio_group.param.watch(self._switch_data_stack, ['value'])
        return radio_group

    def update_selected_by_filter(self, filter_type, selected_values):
        self.selected_metrics_by_filter.update({filter_type: selected_values})
        self.param.trigger('selected_metrics_by_filter')

    def _update(self, event):
        self._update_info()
        self._load_metrics()

    def create_info_element(self, name, value):
        box_css = """
        background-color: #EEEEEE;
        border: 1px solid #777777;
        display: inline-block;
        padding-left: 5px;
        padding-right: 5px;
        margin-left:7px;
        """

        fval = format(value, ',')
        outel = '<li><span style="{}"><b>{}</b> {}</span></li>'
        return outel.format(box_css, fval, name)

    @param.depends('tract_count',
                   'patch_count',
                   'visit_count',
                   'filter_count',
                   'unique_object_count',
                   watch=True)
    def _update_info(self):
        """
        Updates the _info HTML pane with info loaded
        from the current repository.
        """
        html = ''
        html += self.create_info_element('Tracts', self.tract_count)
        html += self.create_info_element('Patches', self.patch_count)
        html += self.create_info_element('Visits', self.visit_count)
        # html += self.create_info_element('Unique Objects',
        #                                  self.unique_object_count)
        self._info.object = '<ul class="list-group list-group-horizontal" style="list-style: none;">{}</ul>'.format(
            html)

    def create_status_message(self, msg, level='info', duration=5):

        import uuid
        msg_id = str(uuid.uuid1())
        color_levels = dict(info='rgba(0,191,255, .8)',
                            error='rgba(249, 180, 45, .8)',
                            warning='rgba(240, 255, 0, .8)',
                            success='rgba(3, 201, 169, .8)')

        box_css = """
        width: 15rem;
        background-color: {};
        border: 1px solid #CCCCCC;
        display: inline-block;
        color: white;
        padding: 5px;
        margin-top: 1rem;
        """.format(color_levels.get(level, 'rgba(0,0,0,0)'))

        remove_msg_func = ('<script>(function() { '
                           'setTimeout(function(){ document.getElementById("' +
                           msg_id + '").outerHTML = ""; }, ' +
                           str(duration * 1000) + ')})()'
                           '</script>')

        text = '<span style="{}"><h5>{}</h5><hr/><p>{}</p></span></li>'.format(
            box_css, msg.get('title'), msg.get('body'))

        return ('<li id="{}" class="status-message nav-item">'
                '{}'
                '{}'
                '</lil>').format(msg_id, remove_msg_func, text)

    def gen_clear_func(self, msg):
        async def clear_message():

            try:
                if msg in self.status_message_queue:
                    self.status_message_queue.remove(msg)
            except ValueError:
                pass

        return clear_message

    @param.depends('status_message_queue', watch=True)
    def _update_status_message(self):

        queue_css = """
        list-style-type: none;
        position: fixed;
        bottom: 2rem;
        right: 2rem;
        background-color: rgba(0,0,0,0);
        border: none;
        display: inline-block;
        margin-left: 7px;
        """

        html = ''

        for msg in self.status_message_queue:
            html += self.create_status_message(**msg)
            set_timeout(msg.get('duration', 5), self.gen_clear_func(msg))

        self.status_message.object = '<ul style="{}">{}</ul>'.format(
            queue_css, html)

    def execute_js_script(self, js_body):
        script = '<script>(function() { ' + js_body + '})()</script>'  # to work with panel 0.7
        self.adhoc_js.object = script

    def get_patch_count(self):
        return 1
        patchs = set()
        for filt, _ in self.selected_metrics_by_filter.items():
            dset = self.get_dataset_by_filter(filt)
            patchs = patchs.union(set(dset.df['patch'].unique()))
        return len(patchs)

    def get_tract_count(self):
        return 1
        tracts = set()
        for filt, _ in self.selected_metrics_by_filter.items():
            dset = self.get_dataset_by_filter(filt)
            tracts = tracts.union(set(dset.df['tract'].unique()))
        return len(tracts)

    def get_visit_count(self):
        return 1
        dvisits = self.get_datavisits()
        visits = set()
        for filt, metrics in self.selected_metrics_by_filter.items():
            for metric in metrics:
                df = dvisits[filt][metric].compute()
                visits = visits.union(set(df['visit'].unique()))
        return len(visits)

    def update_info_counts(self):
        self.tract_count = self.get_tract_count()
        self.patch_count = self.get_patch_count()
        self.visit_count = self.get_visit_count()
        self.unique_object_count = get_unique_object_count()

    def _load_metrics(self):
        """
        Populates the _metrics Row with metrics loaded from the repository
        """
        panels = [
            MetricPanel(metric='LSST',
                        filters=self.store.active_dataset.filters,
                        parent=self)
        ]
        self._metric_panels = panels

        self._metric_layout.objects = [p.panel() for p in panels]
        self.update_display()

    @param.depends('query_filter', watch=True)
    def _update_query_filter(self):
        self.filter_main_dataframe()

    @param.depends('selected_flag_filters', watch=True)
    def _update_selected_flags(self):
        selected_flags = [
            '{} : {}'.format(f, v)
            for f, v in self.selected_flag_filters.items()
        ]
        self.flag_filter_selected.options = selected_flags
        self.filter_main_dataframe()

    def filter_main_dataframe(self):
        global filtered_datasets
        global datasets

        for filt, qa_dataset in datasets.items():
            try:
                query_expr = self._assemble_query_expression()

                if query_expr:
                    filtered_datasets[filt] = QADataset(
                        datasets[filt].df.query(query_expr))

            except Exception as e:
                self.add_message_from_error('Filtering Error', '', e)
                raise
                return

        #self.filter_visits_dataframe()

        self._update_selected_metrics_by_filter()

    def filter_visits_dataframe(self):
        global filtered_datavisits
        global datavisits

        for filt, metrics in datavisits.items():
            for metric, df in metrics.items():

                try:
                    query_expr = self._assemble_query_expression(
                        ignore_query_expr=True)
                    if query_expr and datavisits[filt][metric] is not None:
                        filtered_datavisits[filt][metric] = datavisits[filt][
                            metric].query(query_expr)

                except Exception as e:
                    self.add_message_from_error('Filtering Visits Error', '',
                                                e)
                    return

    def _assemble_query_expression(self, ignore_query_expr=False):
        query_expr = ''

        flags_query = []
        for flag, state in self.selected_flag_filters.items():
            flags_query.append('{}=={}'.format(flag, state))
        if flags_query:
            query_expr += ' & '.join(flags_query)

        if ignore_query_expr:
            return query_expr

        query_filter = self.query_filter.strip()
        if query_filter:
            if query_expr:
                query_expr += ' & {!s}'.format(query_filter)
            else:
                query_expr = '{!s}'.format(query_filter)

        return query_expr

    def get_dataset_by_filter(self, filter_type):
        global datasets
        global filtered_datasets
        if self.query_filter == '' and len(self.selected_flag_filters) == 0:
            return datasets[filter_type]
        else:
            return filtered_datasets[filter_type]

    def get_datavisits(self):
        global datavisits
        global filtered_datavisits
        # if self.query_filter == '' and len(self.selected_flag_filters) == 0:
        if len(self.selected_flag_filters) == 0:
            return datavisits
        else:
            return filtered_datavisits

    def add_message_from_error(self,
                               title,
                               info,
                               exception_obj,
                               level='error'):

        tb = traceback.format_exception_only(type(exception_obj),
                                             exception_obj)[0]
        msg_body = '<b>Info:</b> ' + info + '<br />'
        msg_body += '<b>Cause:</b> ' + tb
        logger.error(title)
        logger.error(msg_body)
        self.add_status_message(title, msg_body, level=level, duration=10)

    @param.depends('selected_metrics_by_filter', watch=True)
    def _update_selected_metrics_by_filter(self):

        plots_list = []
        skyplot_list = []

        top_plot = None

        dvisits = self.get_datavisits()
        try:
            top_plot = visits_plot(dvisits, self.selected_metrics_by_filter)
        except Exception as e:
            self.add_message_from_error('Visits Plot Error', '', e)

        self.plot_top = top_plot

        filter_stream_scatter = FilterStream()
        for filt, plots in self.selected_metrics_by_filter.items():
            filter_stream = FilterStream()
            dset = self.get_dataset_by_filter(filt)
            for i, p in enumerate(plots):
                # skyplots
                plot_sky = skyplot(dset.ds,
                                   filter_stream=filter_stream,
                                   vdim=p)

                skyplot_list.append((filt + ' - ' + p, plot_sky))

                plots_ss = scattersky(dset.ds,
                                      xdim='psfMag',
                                      ydim=p,
                                      filter_stream=filter_stream_scatter)
                plot = plots_ss
                plots_list.append((p, plot))

        self.skyplot_list = skyplot_list
        self.plots_list = plots_list

        self.update_display()
        self._switch_view_mode()

    def linked_tab_plots(self):
        tabs = [(name, pn.panel(plot)) for name, plot in self.skyplot_list]
        return pn.Tabs(*tabs, sizing_mode='stretch_both')

    def attempt_to_clear(self, obj):
        try:
            obj.clear()
        except:
            pass

    def _switch_data_stack(self, *events):
        # clear existing plot layouts
        self.attempt_to_clear(self._plot_top)
        self.attempt_to_clear(self._plot_layout)
        self.attempt_to_clear(self.skyplot_layout)
        self.attempt_to_clear(self.list_layout)

        self._on_clear_metrics(event=None)
        self._on_load_data_repository(None)

    def _switch_view_mode(self, *events):
        # clear existing plot layouts
        self.attempt_to_clear(self._plot_top)
        self.attempt_to_clear(self._plot_layout)
        self.attempt_to_clear(self.skyplot_layout)
        self.attempt_to_clear(self.list_layout)

        if self._switch_view.value == 'Skyplot View':
            self.execute_js_script(
                '''$( ".skyplot-plot-area" ).show(); $( ".metrics-plot-area" ).hide();'''
            )
            self.skyplot_layout.append(self.linked_tab_plots())

        else:
            self.execute_js_script(
                '''$( ".skyplot-plot-area" ).hide(); $( ".metrics-plot-area" ).show();'''
            )
            logger.info(self.plot_top)
            self._plot_top.append(self.plot_top)
            for i, p in self.plots_list:
                self.list_layout.append(p)
            self._plot_layout.append(self.list_layout)

    def jinja(self):
        from ._jinja2_templates import quicklook
        import holoviews as hv
        tmpl = pn.Template(dashboard_html_template)

        data_repo_widget = pn.panel(self.param.data_repository,
                                    show_labels=False)
        data_repo_widget.width = 300
        #        data_repo_row = pn.Row(pn.panel('Data Repository', align='end'),
        #                               data_repo_widget, self._submit_repository)
        data_repo_row = pn.Row(data_repo_widget, self._submit_repository)
        data_repo_row.css_classes = ['data-repo-input']

        query_filter_widget = pn.panel(self.param.query_filter)
        query_filter_widget.width = 260

        new_column_widget = pn.panel(self.param.new_column_expr)
        new_column_widget.width = 260

        datastack_switcher = pn.Row(self._switch_stack)
        datastack_switcher.css_classes = ['stack-switcher']

        view_switcher = pn.Row(self._switch_view)
        view_switcher.css_classes = ['view-switcher']

        clear_button_row = pn.Row(self._clear_metrics_button)

        components = [
            ('metrics_clear_button', clear_button_row),
            ('data_repo_path', data_repo_row),
            ('status_message_queue', self.status_message),
            ('adhoc_js', self.adhoc_js),
            ('infobar', self._info),
            #            ('view_switcher', switcher_row),
            ('stack_switcher', datastack_switcher),
            ('view_switcher', view_switcher),
            ('metrics_selectors', self._metric_layout),
            ('metrics_plots', self._plot_layout),
            ('skyplot_metrics_plots', self.skyplot_layout),
            ('plot_top', self._plot_top),
            ('flags',
             pn.Column(pn.Row(self.flag_filter_select, self.flag_state_select),
                       pn.Row(self.flag_submit),
                       pn.Row(self.flag_filter_selected),
                       pn.Row(self.flag_remove))),
            (
                'query_filter',
                pn.Column(
                    query_filter_widget,
                    pn.Row(self.query_filter_submit, self.query_filter_clear)),
            ),
            (
                'new_column',
                pn.Column(new_column_widget, pn.Row(self.new_column_submit)),
            ),
        ]

        for l, c in components:
            tmpl.add_panel(l, c)

        return tmpl
Esempio n. 14
0
class Ellipse(BaseShape):
    """
    Draw an axis-aligned ellipse at the specified x,y position with
    the given orientation.

    The simplest (default) Ellipse is a circle, specified using:

    Ellipse(x,y, diameter)

    A circle is a degenerate ellipse where the width and height are
    equal. To specify these explicitly, you can use:

    Ellipse(x,y, (width, height))

    There is also an aspect parameter allowing you to generate an ellipse
    by specifying a multiplicating factor that will be applied to the
    height only.

    Note that as a subclass of Path, internally an Ellipse is a
    sequence of (x,y) sample positions. Ellipse could also be
    implemented as an annotation that uses a dedicated ellipse artist.
    """
    x = param.Number(default=0, doc="The x-position of the ellipse center.")

    y = param.Number(default=0, doc="The y-position of the ellipse center.")

    width = param.Number(default=1, doc="The width of the ellipse.")

    height = param.Number(default=1, doc="The height of the ellipse.")

    orientation = param.Number(default=0, doc="""
       Orientation in the Cartesian coordinate system, the
       counterclockwise angle in radians between the first axis and the
       horizontal.""")

    aspect= param.Number(default=1.0, doc="""
       Optional multiplier applied to the diameter to compute the width
       in cases where only the diameter value is set.""")

    samples = param.Number(default=100, doc="The sample count used to draw the ellipse.")

    group = param.String(default='Ellipse', constant=True, doc="The assigned group name.")

    __pos_params = ['x','y', 'height']

    def __init__(self, x, y, spec, **params):

        if isinstance(spec, tuple):
            if 'aspect' in params:
                raise ValueError('Aspect parameter not supported when supplying '
                                 '(width, height) specification.')
            (width, height) = spec
        else:
            width, height = params.get('width', spec), spec

        params['width']=params.get('width',width)
        super(Ellipse, self).__init__(x=x, y=y, height=height, **params)
        angles = np.linspace(0, 2*np.pi, self.samples)
        half_width = (self.width * self.aspect)/ 2.0
        half_height = self.height / 2.0
        #create points
        ellipse = np.array(
            list(zip(half_width*np.sin(angles),
                     half_height*np.cos(angles))))
        #rotate ellipse and add offset
        rot = np.array([[np.cos(self.orientation), -np.sin(self.orientation)],
               [np.sin(self.orientation), np.cos(self.orientation)]])
        self.data = [np.tensordot(rot, ellipse.T, axes=[1,0]).T+np.array([x,y])]
Esempio n. 15
0
class image_overlay(ElementOperation):
    """
    Operation to build a overlay of images to a specification from a
    subset of the required elements.

    This is useful for reordering the elements of an overlay,
    duplicating layers of an overlay or creating blank image elements
    in the appropriate positions.

    For instance, image_overlay may build a three layered input
    suitable for the RGB factory operation even if supplied with one
    or two of the required channels (creating blank channels for the
    missing elements).

    Note that if there is any ambiguity regarding the match, the
    strongest match will be used. In the case of a tie in match
    strength, the first layer in the input is used. One successful
    match is always required.
    """

    output_type = Overlay

    spec = param.String(doc="""
       Specification of the output Overlay structure. For instance:

       Image.R * Image.G * Image.B

       Will ensure an overlay of this structure is created even if
       (for instance) only (Image.R * Image.B) is supplied.

       Elements in the input overlay that match are placed in the
       appropriate positions and unavailable specification elements
       are created with the specified fill group.""")

    fill = param.Number(default=0)

    default_range = param.Tuple(default=(0, 1),
                                doc="""
        The default range that will be set on the value_dimension of
        any automatically created blank image elements.""")

    group = param.String(default='Transform',
                         doc="""
        The group assigned to the resulting overlay.""")

    @classmethod
    def _match(cls, el, spec):
        "Return the strength of the match (None if no match)"
        spec_dict = dict(zip(['type', 'group', 'label'], spec.split('.')))
        if not isinstance(el, Image) or spec_dict['type'] != 'Image':
            raise NotImplementedError("Only Image currently supported")

        sanitizers = {'group': group_sanitizer, 'label': label_sanitizer}
        strength = 1
        for key in ['group', 'label']:
            attr_value = sanitizers[key](getattr(el, key))
            if key in spec_dict:
                if spec_dict[key] != attr_value: return None
                strength += 1
        return strength

    def _match_overlay(self, raster, overlay_spec):
        """
        Given a raster or input overlay, generate a list of matched
        elements (None if no match) and corresponding tuple of match
        strength values.
        """
        ordering = [None] * len(overlay_spec)  # Elements to overlay
        strengths = [0] * len(overlay_spec)  # Match strengths

        elements = raster.values() if isinstance(raster, Overlay) else [raster]

        for el in elements:
            for pos in range(len(overlay_spec)):
                strength = self._match(el, overlay_spec[pos])
                if strength is None: continue  # No match
                elif (strength <= strengths[pos]): continue  # Weaker match
                else:  # Stronger match
                    ordering[pos] = el
                    strengths[pos] = strength
        return ordering, strengths

    def _process(self, raster, key=None):
        specs = tuple(el.strip() for el in self.p.spec.split('*'))
        ordering, strengths = self._match_overlay(raster, specs)
        if all(el is None for el in ordering):
            raise Exception(
                "The image_overlay operation requires at least one match")

        completed = []
        strongest = ordering[np.argmax(strengths)]
        for el, spec in zip(ordering, specs):
            if el is None:
                spec_dict = dict(
                    zip(['type', 'group', 'label'], spec.split('.')))
                el = Image(np.ones(strongest.data.shape) * self.p.fill,
                           group=spec_dict.get('group', 'Image'),
                           label=spec_dict.get('label', ''))
                el.vdims[0].range = self.p.default_range
            completed.append(el)
        return np.prod(completed)
Esempio n. 16
0
class ArrayInput(LiteralInput):
    """
    The `ArrayInput` allows rendering and editing NumPy arrays in a text
    input widget.
    
    Arrays larger than the `max_array_size` will be summarized and editing
    will be disabled.

    Reference: https://panel.holoviz.org/reference/widgets/ArrayInput.html

    :Example:

    >>> To be determined ...
    """

    max_array_size = param.Number(default=1000,
                                  doc="""
        Arrays larger than this limit will be allowed in Python but
        will not be serialized into JavaScript. Although such large
        arrays will thus not be editable in the widget, such a
        restriction helps avoid overwhelming the browser and lets
        other widgets remain usable.""")

    _rename = dict(LiteralInput._rename, max_array_size=None)

    _source_transforms = {'value': None}

    def __init__(self, **params):
        super().__init__(**params)
        self._auto_disabled = False

    def _process_property_change(self, msg):
        msg = super()._process_property_change(msg)
        if 'value' in msg and isinstance(msg['value'], list):
            msg['value'] = np.asarray(msg['value'])
        return msg

    def _process_param_change(self, msg):
        if msg.get('disabled', False):
            self._auto_disabled = False
        value = msg.get('value')
        if value is None:
            return super()._process_param_change(msg)
        if value.size <= self.max_array_size:
            msg['value'] = value.tolist()
            # If array is no longer larger than max_array_size
            # unset disabled
            if self.disabled and self._auto_disabled:
                self.disabled = False
                msg['disabled'] = False
                self._auto_disabled = False
        else:
            msg['value'] = np.array2string(msg['value'],
                                           separator=',',
                                           threshold=self.max_array_size)
            if not self.disabled:
                self.param.warning(
                    f"Number of array elements ({value.size}) exceeds "
                    f"`max_array_size` ({self.max_array_size}), editing "
                    "will be disabled.")
                self.disabled = True
                msg['disabled'] = True
                self._auto_disabled = True
        return super()._process_param_change(msg)
Esempio n. 17
0
class ColorbarPlot(ElementPlot):

    colorbar = param.Boolean(default=False,
                             doc="""
        Whether to draw a colorbar.""")

    clipping_colors = param.Dict(default={},
                                 doc="""
        Dictionary to specify colors for clipped values, allows
        setting color for NaN values and for values above and below
        the min and max value. The min, max or NaN color may specify
        an RGB(A) color as a color hex string of the form #FFFFFF or
        #FFFFFFFF or a length 3 or length 4 tuple specifying values in
        the range 0-1 or a named HTML color.""")

    cbar_padding = param.Number(default=0.01,
                                doc="""
        Padding between colorbar and other plots.""")

    cbar_ticks = param.Parameter(default=None,
                                 doc="""
        Ticks along colorbar-axis specified as an integer, explicit
        list of tick locations, list of tuples containing the
        locations and labels or a matplotlib tick locator object. If
        set to None default matplotlib ticking behavior is
        applied.""")

    cbar_width = param.Number(default=0.05,
                              doc="""
        Width of the colorbar as a fraction of the main plot""")

    symmetric = param.Boolean(default=False,
                              doc="""
        Whether to make the colormap symmetric around zero.""")

    _colorbars = {}

    def __init__(self, *args, **kwargs):
        super(ColorbarPlot, self).__init__(*args, **kwargs)
        self._cbar_extend = 'neither'

    def _adjust_cbar(self, cbar, label, dim):
        noalpha = math.floor(self.style[self.cyclic_index].get('alpha',
                                                               1)) == 1
        if (cbar.solids and noalpha):
            cbar.solids.set_edgecolor("face")
        cbar.set_label(label)
        if isinstance(self.cbar_ticks, ticker.Locator):
            cbar.ax.yaxis.set_major_locator(self.cbar_ticks)
        elif self.cbar_ticks == 0:
            cbar.set_ticks([])
        elif isinstance(self.cbar_ticks, int):
            locator = ticker.MaxNLocator(self.cbar_ticks)
            cbar.ax.yaxis.set_major_locator(locator)
        elif isinstance(self.cbar_ticks, list):
            if all(isinstance(t, tuple) for t in self.cbar_ticks):
                ticks, labels = zip(*self.cbar_ticks)
            else:
                ticks, labels = zip(*[(t, dim.pprint_value(t))
                                      for t in self.cbar_ticks])
            cbar.set_ticks(ticks)
            cbar.set_ticklabels(labels)

    def _finalize_artist(self, element):
        artist = self.handles.get('artist', None)
        if artist and self.colorbar:
            self._draw_colorbar()

    def _draw_colorbar(self, dim=None, redraw=True):
        element = self.hmap.last
        artist = self.handles.get('artist', None)
        fig = self.handles['fig']
        axis = self.handles['axis']
        ax_colorbars, position = ColorbarPlot._colorbars.get(
            id(axis), ([], None))
        specs = [spec[:2] for _, _, spec, _ in ax_colorbars]
        spec = util.get_spec(element)

        if position is None or not redraw:
            if redraw:
                fig.canvas.draw()
            bbox = axis.get_position()
            l, b, w, h = bbox.x0, bbox.y0, bbox.width, bbox.height
        else:
            l, b, w, h = position

        # Get colorbar label
        dim = element.get_dimension(dim)
        if dim:
            label = dim.pprint_label
        elif element.vdims:
            label = element.vdims[0].pprint_label
        elif dim is None:
            label = ''

        padding = self.cbar_padding
        width = self.cbar_width
        if spec[:2] not in specs:
            offset = len(ax_colorbars)
            scaled_w = w * width
            cax = fig.add_axes([
                l + w + padding + (scaled_w + padding + w * 0.15) * offset, b,
                scaled_w, h
            ])
            cbar = fig.colorbar(artist,
                                cax=cax,
                                ax=axis,
                                extend=self._cbar_extend)
            self._adjust_cbar(cbar, label, dim)
            self.handles['cax'] = cax
            self.handles['cbar'] = cbar
            ylabel = cax.yaxis.get_label()
            self.handles['bbox_extra_artists'] += [cax, ylabel]
            ax_colorbars.append((artist, cax, spec, label))

        for i, (artist, cax, spec, label) in enumerate(ax_colorbars):
            scaled_w = w * width
            cax.set_position([
                l + w + padding + (scaled_w + padding + w * 0.15) * i, b,
                scaled_w, h
            ])

        ColorbarPlot._colorbars[id(axis)] = (ax_colorbars, (l, b, w, h))

    def _norm_kwargs(self, element, ranges, opts, vdim, prefix=''):
        """
        Returns valid color normalization kwargs
        to be passed to matplotlib plot function.
        """
        clim = opts.pop(prefix + 'clims', None)
        if clim is None:
            cs = element.dimension_values(vdim)
            if not isinstance(cs, np.ndarray):
                cs = np.array(cs)
            if len(cs) and cs.dtype.kind in 'if':
                clim = ranges[
                    vdim.name] if vdim.name in ranges else element.range(vdim)
                if self.logz:
                    # Lower clim must be >0 when logz=True
                    # Choose the maximum between the lowest non-zero value
                    # and the overall range
                    if clim[0] == 0:
                        vals = element.dimension_values(vdim)
                        clim = (vals[vals != 0].min(), clim[1])
                if self.symmetric:
                    clim = -np.abs(clim).max(), np.abs(clim).max()
            else:
                clim = (0, len(np.unique(cs)))
        if self.logz:
            if self.symmetric:
                norm = mpl_colors.SymLogNorm(vmin=clim[0],
                                             vmax=clim[1],
                                             linthresh=clim[1] / np.e)
            else:
                norm = mpl_colors.LogNorm(vmin=clim[0], vmax=clim[1])
            opts[prefix + 'norm'] = norm
        opts[prefix + 'vmin'] = clim[0]
        opts[prefix + 'vmax'] = clim[1]

        # Check whether the colorbar should indicate clipping
        values = np.asarray(element.dimension_values(vdim))
        if values.dtype.kind not in 'OSUM':
            try:
                el_min, el_max = np.nanmin(values), np.nanmax(values)
            except ValueError:
                el_min, el_max = -np.inf, np.inf
        else:
            el_min, el_max = -np.inf, np.inf
        vmin = -np.inf if opts[prefix + 'vmin'] is None else opts[prefix +
                                                                  'vmin']
        vmax = np.inf if opts[prefix + 'vmax'] is None else opts[prefix +
                                                                 'vmax']
        if el_min < vmin and el_max > vmax:
            self._cbar_extend = 'both'
        elif el_min < vmin:
            self._cbar_extend = 'min'
        elif el_max > vmax:
            self._cbar_extend = 'max'

        # Define special out-of-range colors on colormap
        cmap = opts.get(prefix + 'cmap')
        if isinstance(cmap, list):
            cmap = mpl_colors.ListedColormap(cmap)
        elif isinstance(cmap, util.basestring):
            cmap = copy.copy(plt.cm.get_cmap(cmap))
        else:
            cmap = copy.copy(cmap)
        colors = {}
        for k, val in self.clipping_colors.items():
            if isinstance(val, tuple):
                colors[k] = {
                    'color': val[:3],
                    'alpha': val[3] if len(val) > 3 else 1
                }
            elif isinstance(val, util.basestring):
                color = val
                alpha = 1
                if color.startswith('#') and len(color) == 9:
                    alpha = int(color[-2:], 16) / 255.
                    color = color[:-2]
                colors[k] = {'color': color, 'alpha': alpha}
        if 'max' in colors: cmap.set_over(**colors['max'])
        if 'min' in colors: cmap.set_under(**colors['min'])
        if 'NaN' in colors: cmap.set_bad(**colors['NaN'])
        opts[prefix + 'cmap'] = cmap
Esempio n. 18
0
class PointPlot(LegendPlot, ColorbarPlot):

    color_index = param.ClassSelector(default=None,
                                      class_=(basestring, int),
                                      allow_None=True,
                                      doc="""
      Index of the dimension from which the color will the drawn""")

    size_index = param.ClassSelector(default=None,
                                     class_=(basestring, int),
                                     allow_None=True,
                                     doc="""
      Index of the dimension from which the sizes will the drawn.""")

    scaling_method = param.ObjectSelector(default="area",
                                          objects=["width", "area"],
                                          doc="""
      Determines whether the `scaling_factor` should be applied to
      the width or area of each point (default: "area").""")

    jitter = param.Number(default=None,
                          bounds=(0, None),
                          doc="""
      The amount of jitter to apply to offset the points along the x-axis.""")

    scaling_factor = param.Number(default=1,
                                  bounds=(0, None),
                                  doc="""
      Scaling factor which is applied to either the width or area
      of each point, depending on the value of `scaling_method`.""")

    size_fn = param.Callable(default=np.abs,
                             doc="""
      Function applied to size values before applying scaling,
      to remove values lower than zero.""")

    style_opts = (['cmap', 'palette', 'marker', 'size'] + line_properties +
                  fill_properties)

    _plot_methods = dict(single='scatter', batched='scatter')
    _batched_style_opts = line_properties + fill_properties + ['size']

    def _get_size_data(self, element, ranges, style):
        data, mapping = {}, {}
        sdim = element.get_dimension(self.size_index)
        if not sdim or self.static_source:
            return data, mapping

        map_key = 'size_' + sdim.name
        ms = style.get('size', np.sqrt(6))**2
        sizes = element.dimension_values(self.size_index)
        sizes = compute_sizes(sizes, self.size_fn, self.scaling_factor,
                              self.scaling_method, ms)
        if sizes is None:
            eltype = type(element).__name__
            self.warning('%s dimension is not numeric, cannot '
                         'use to scale %s size.' % (sdim.pprint_label, eltype))
        else:
            data[map_key] = np.sqrt(sizes)
            mapping['size'] = map_key
        return data, mapping

    def get_data(self, element, ranges, style):
        dims = element.dimensions(label=True)

        xidx, yidx = (1, 0) if self.invert_axes else (0, 1)
        mapping = dict(x=dims[xidx], y=dims[yidx])
        data = {}

        if not self.static_source or self.batched:
            xdim, ydim = dims[xidx], dims[yidx]
            data[xdim] = element.dimension_values(xidx)
            data[ydim] = element.dimension_values(yidx)
            self._categorize_data(data, (xdim, ydim), element.dimensions())

        cdata, cmapping = self._get_color_data(element, ranges, style)
        data.update(cdata)
        mapping.update(cmapping)

        sdata, smapping = self._get_size_data(element, ranges, style)
        data.update(sdata)
        mapping.update(smapping)

        if self.jitter:
            axrange = 'y_range' if self.invert_axes else 'x_range'
            mapping['x'] = jitter(dims[xidx],
                                  self.jitter,
                                  range=self.handles[axrange])

        self._get_hover_data(data, element)
        return data, mapping, style

    def get_batched_data(self, element, ranges):
        data = defaultdict(list)
        zorders = self._updated_zorders(element)
        for (key, el), zorder in zip(element.data.items(), zorders):
            self.set_param(**self.lookup_options(el, 'plot').options)
            style = self.lookup_options(element.last, 'style')
            style = style.max_cycles(len(self.ordering))[zorder]
            eldata, elmapping, style = self.get_data(el, ranges, style)
            for k, eld in eldata.items():
                data[k].append(eld)

            # Skip if data is empty
            if not eldata:
                continue

            # Apply static styles
            nvals = len(list(eldata.values())[0])
            sdata, smapping = expand_batched_style(style,
                                                   self._batched_style_opts,
                                                   elmapping, nvals)
            elmapping.update(smapping)
            for k, v in sdata.items():
                data[k].append(v)

            if any(isinstance(t, HoverTool) for t in self.state.tools):
                for dim, k in zip(element.dimensions(), key):
                    sanitized = dimension_sanitizer(dim.name)
                    data[sanitized].append([k] * nvals)

        data = {k: np.concatenate(v) for k, v in data.items()}
        return data, elmapping, style
Esempio n. 19
0
class Projection(EPConnection):
    """
    A projection from a Sheet into a ProjectionSheet.

    Projections are required to support the activate() method, which
    will construct a matrix the same size as the target
    ProjectionSheet, from an input matrix of activity from the source
    Sheet.  Other than that, a Projection may be of any type.
    """
    __abstract=True

    strength = param.Number(default=1.0)

    src_port = param.Parameter(default='Activity')

    dest_port = param.Parameter(default='Activity')

    output_fns = param.HookList(default=[],class_=TransferFn,doc="""
        Function(s) applied to the Projection activity after it is computed.""")

    plastic = param.Boolean(default=True, doc="""
        Whether or not to update the internal state on each call.
        Allows plasticity to be turned off during analysis, and then re-enabled.""")

    activity_group = param.Parameter(default=(0.5,numpy.add), doc="""
       Grouping and precedence specifier for computing activity from
       Projections.  In a ProjectionSheet, all Projections in the
       same activity_group will be summed, and then the results from
       each group will be combined in the order of the activity_group
       using the operator specified by the activity_operator.  For
       instance, if there are two Projections with
       activity_group==(0.2,numpy.add) and two with
       activity_group==(0.6,numpy.divide), activity
       from the first two will be added together, and the result
       divided by the sum of the second two.""")

    # CEBALERT: precedence should probably be defined at some higher level
    # (and see other classes where it's defined, e.g. Sheet)
    precedence = param.Number(default=0.5)


    def __init__(self,**params):
        super(Projection,self).__init__(**params)
        self.activity = array(self.dest.activity)
        self.__saved_activity = []
        self._plasticity_setting_stack = []


    def activate(self,input_activity):
        """
        Compute an activity matrix for output, based on the specified input_activity.

        Subclasses must override this method to whatever it means to
        calculate activity in that subclass.
        """
        raise NotImplementedError


    def learn(self):
        """
        This function has to be re-implemented by sub-classes, if they wish
        to support learning.
        """
        pass


    def apply_learn_output_fns(self,active_units_mask=True):
        """
        Sub-classes can implement this function if they wish to
        perform an operation after learning has completed, such as
        normalizing weight values across different projections.

        The active_units_mask argument determines whether or not to
        apply the output_fn to non-responding units.
        """
        pass



    def override_plasticity_state(self, new_plasticity_state):
        """
        Temporarily override plasticity of medium and long term internal
        state.

        This function should be implemented by all subclasses so that
        it preserves the ability of the Projection to compute
        activity, i.e. to operate over a short time scale, while
        preventing any lasting changes to the state.

        For instance, if new_plasticity_state is False, in a
        Projection with modifiable connection weights, the values of
        those weights should temporarily be made fixed and unchanging
        after this call.  For a Projection with automatic
        normalization, homeostatic plasticity, or other features that
        depend on a history of events (rather than just the current
        item being processed), changes in those properties would be
        disabled temporarily.  Setting the plasticity state to False
        is useful during analysis operations (e.g. map measurement)
        that would otherwise change the state of the underlying
        network.

        Any process that does not have any lasting state, such as
        those affecting only the current activity level, should not
        be affected by this call.

        By default, this call simply calls override_plasticity_state()
        on the Projection's output_fn, and sets the 'plastic'
        parameter to False.
        """
        self._plasticity_setting_stack.append(self.plastic)
        self.plastic=new_plasticity_state

        for of in self.output_fns:
            if hasattr(of,'override_plasticity_state'):
                of.override_plasticity_state(new_plasticity_state)


    def restore_plasticity_state(self):
        """
        Restore previous plasticity state of medium and long term
        internal state after a override_plasticity_state call.

        This function should be implemented by all subclasses to
        remove the effect of the most recent override_plasticity_state call,
        e.g. to reenable plasticity of any type that was disabled.
        """
        self.plastic = self._plasticity_setting_stack.pop()

        for of in self.output_fns:
            if hasattr(of,'restore_plasticity_state'):
                of.restore_plasticity_state()


    def projection_view(self, timestamp=None):
        """Returns the activity in a single projection"""
        if timestamp is None:
            timestamp = self.src.simulation.time()
        sv = SheetView(self.activity.copy(), self.dest.bounds,
                         label='Activity', title='%s {label}' % self.name)
        sv.metadata=AttrDict(proj_src_name=self.src.name,
                             precedence=self.src.precedence,
                             proj_name=self.name,
                             row_precedence=self.src.row_precedence,
                             src_name=self.dest.name,
                             timestamp=timestamp)
        return sv


    def state_pop(self):
        """
        Pop the most recently pushed activity state of the stack.
        """
        self.activity = self.__saved_activity.pop()


    def state_push(self):
        """
        Push the current activity state onto the stack.
        """

        self.__saved_activity.append(array(self.activity))


    def get_projection_view(self, timestamp):
        self.warning("Deprecated, call 'projection_view' method instead.")
        return self.projection_view(timestamp)


    def n_bytes(self):
        """
        Estimate the memory bytes taken by this Projection.

        By default, counts only the activity array, but subclasses
        should implement this method to include also the bytes taken
        by weight arrays and any similar arrays, as a rough lower
        bound from which memory requirements and memory usage patterns
        can be estimated.
        """
        (rows,cols) = self.activity.shape
        return rows*cols


    def n_conns(self):
        """
        Return the size of this projection, in number of connections.

        Must be implemented by subclasses, if only to declare that no
        connections are stored.
        """
        raise NotImplementedError
Esempio n. 20
0
class SpikesPlot(ColorbarPlot):

    color_index = param.ClassSelector(default=None,
                                      allow_None=True,
                                      class_=(basestring, int),
                                      doc="""
      Index of the dimension from which the color will the drawn""")

    spike_length = param.Number(default=0.5,
                                doc="""
      The length of each spike if Spikes object is one dimensional.""")

    position = param.Number(default=0.,
                            doc="""
      The position of the lower end of each spike.""")

    show_legend = param.Boolean(default=True,
                                doc="""
        Whether to show legend for the plot.""")

    style_opts = (['color', 'cmap', 'palette'] + line_properties)

    _plot_methods = dict(single='segment')

    def get_extents(self, element, ranges):
        l, b, r, t = super(SpikesPlot, self).get_extents(element, ranges)
        if len(element.dimensions()) == 1:
            if self.batched:
                bs, ts = [], []
                # Iterate over current NdOverlay and compute extents
                # from position and length plot options
                frame = self.current_frame or self.hmap.last
                for el in frame.values():
                    opts = self.lookup_options(el, 'plot').options
                    pos = opts.get('position', self.position)
                    length = opts.get('spike_length', self.spike_length)
                    bs.append(pos)
                    ts.append(pos + length)
                b = np.nanmin(bs)
                t = np.nanmax(ts)
            else:
                b, t = self.position, self.position + self.spike_length
        else:
            b = np.nanmin([0, b])
            t = np.nanmax([0, t])
        return l, b, r, t

    def get_data(self, element, ranges, style):
        dims = element.dimensions(label=True)

        data = {}
        pos = self.position
        if len(element) == 0 or self.static_source:
            data = {'x': [], 'y0': [], 'y1': []}
        else:
            data['x'] = element.dimension_values(0)
            data['y0'] = np.full(len(element), pos)
            if len(dims) > 1:
                data['y1'] = element.dimension_values(1) + pos
            else:
                data['y1'] = data['y0'] + self.spike_length

        if self.invert_axes:
            mapping = {'x0': 'y0', 'x1': 'y1', 'y0': 'x', 'y1': 'x'}
        else:
            mapping = {'x0': 'x', 'x1': 'x', 'y0': 'y0', 'y1': 'y1'}
        cdim = element.get_dimension(self.color_index)
        if cdim:
            cmapper = self._get_colormapper(cdim, element, ranges, style)
            data[
                cdim.
                name] = [] if self.static_source else element.dimension_values(
                    cdim)
            mapping['color'] = {'field': cdim.name, 'transform': cmapper}

        if any(isinstance(t, HoverTool)
               for t in self.state.tools) and not self.static_source:
            for d in dims:
                data[dimension_sanitizer(d)] = element.dimension_values(d)

        return data, mapping, style
Esempio n. 21
0
class MyParamNumber(param.Parameterized):
    age = param.Number(49, bounds=(0, 100), doc="Any Number between 0 to 100")
Esempio n. 22
0
class ColorbarPlot(ElementPlot):

    colorbar = param.Boolean(default=False,
                             doc="""
        Whether to draw a colorbar.""")

    cbar_width = param.Number(default=0.05,
                              doc="""
        Width of the colorbar as a fraction of the main plot""")

    cbar_padding = param.Number(default=0.01,
                                doc="""
        Padding between colorbar and other plots.""")

    cbar_ticks = param.Parameter(default=None,
                                 doc="""
        Ticks along colorbar-axis specified as an integer, explicit
        list of tick locations, list of tuples containing the
        locations and labels or a matplotlib tick locator object. If
        set to None default matplotlib ticking behavior is
        applied.""")

    symmetric = param.Boolean(default=False,
                              doc="""
        Whether to make the colormap symmetric around zero.""")

    _colorbars = {}

    def _adjust_cbar(self, cbar, label, dim):
        if math.floor(self.style[self.cyclic_index].get('alpha', 1)) == 1:
            cbar.solids.set_edgecolor("face")
        cbar.set_label(label)
        if isinstance(self.cbar_ticks, ticker.Locator):
            cbar.set_major_locator(self.cbar_ticks)
        elif self.cbar_ticks == 0:
            cbar.set_ticks([])
        elif isinstance(self.cbar_ticks, int):
            locator = ticker.MaxNLocator(self.cbar_ticks)
            cbar.set_major_locator(locator)
        elif isinstance(self.cbar_ticks, list):
            if all(isinstance(t, tuple) for t in self.cbar_ticks):
                ticks, labels = zip(*self.cbar_ticks)
            else:
                ticks, labels = zip(*[(t, dim.pprint_value(t))
                                      for t in self.cbar_ticks])
            cbar.set_ticks(ticks)
            cbar.set_ticklabels(labels)

    def _finalize_artist(self, key):
        element = self.hmap.last
        artist = self.handles.get('artist', None)
        if artist and self.colorbar:
            self._draw_colorbar(artist, element)

    def _draw_colorbar(self, artist, element, dim=None):
        fig = self.handles['fig']
        axis = self.handles['axis']
        ax_colorbars, position = ColorbarPlot._colorbars.get(
            id(axis), ([], None))
        specs = [spec[:2] for _, _, spec, _ in ax_colorbars]
        spec = util.get_spec(element)

        if position is None:
            fig.canvas.draw()
            bbox = axis.get_position()
            l, b, w, h = bbox.x0, bbox.y0, bbox.width, bbox.height
        else:
            l, b, w, h = position

        # Get colorbar label
        dim = element.get_dimension(dim)
        if dim is None:
            dim = element.vdims[0]
        label = str(dim)

        padding = self.cbar_padding
        width = self.cbar_width
        if spec[:2] not in specs:
            offset = len(ax_colorbars)
            scaled_w = w * width
            cax = fig.add_axes([
                l + w + padding + (scaled_w + padding + w * 0.15) * offset, b,
                scaled_w, h
            ])
            cbar = plt.colorbar(artist, cax=cax)
            self._adjust_cbar(cbar, label, dim)
            self.handles['cax'] = cax
            self.handles['cbar'] = cbar
            ylabel = cax.yaxis.get_label()
            self.handles['bbox_extra_artists'] += [cax, ylabel]
            ax_colorbars.append((artist, cax, spec, label))

        for i, (artist, cax, spec, label) in enumerate(ax_colorbars[:-1]):
            scaled_w = w * width
            cax.set_position([
                l + w + padding + (scaled_w + padding + w * 0.15) * i, b,
                scaled_w, h
            ])

        ColorbarPlot._colorbars[id(axis)] = (ax_colorbars, (l, b, w, h))

    def _norm_kwargs(self, element, ranges, opts, vdim):
        """
        Returns valid color normalization kwargs
        to be passed to matplotlib plot function.
        """
        clim = opts.pop('clims', None)
        if clim is None:
            clim = ranges[vdim.name] if vdim.name in ranges else element.range(
                vdim)
            if self.symmetric:
                clim = -np.abs(clim).max(), np.abs(clim).max()
        if self.logz:
            if self.symmetric:
                norm = colors.SymLogNorm(vmin=clim[0],
                                         vmax=clim[1],
                                         linthresh=clim[1] / np.e)
            else:
                norm = colors.LogNorm(vmin=clim[0], vmax=clim[1])
            opts['norm'] = norm
        opts['vmin'] = clim[0]
        opts['vmax'] = clim[1]
Esempio n. 23
0
 class Test(param.Parameterized):
     a = param.Number(default=1.2, bounds=(0, 5), label='A')
     b = param.Action(label='B')
Esempio n. 24
0
class PatternGenerator(param.Parameterized):
    """
    A class hierarchy for callable objects that can generate 2D patterns.

    Once initialized, PatternGenerators can be called to generate a
    value or a matrix of values from a 2D function, typically
    accepting at least x and y.

    A PatternGenerator's Parameters can make use of Parameter's
    precedence attribute to specify the order in which they should
    appear, e.g. in a GUI. The precedence attribute has a nominal
    range of 0.0 to 1.0, with ordering going from 0.0 (first) to 1.0
    (last), but any value is allowed.

    The orientation and layout of the pattern matrices is defined by
    the SheetCoordinateSystem class, which see.

    Note that not every parameter defined for a PatternGenerator will
    be used by every subclass.  For instance, a Constant pattern will
    ignore the x, y, orientation, and size parameters, because the
    pattern does not vary with any of those parameters.  However,
    those parameters are still defined for all PatternGenerators, even
    Constant patterns, to allow PatternGenerators to be scaled, rotated,
    translated, etc. uniformly.
    """
    __abstract = True

    bounds = BoundingRegionParameter(
        default=BoundingBox(points=((-0.5, -0.5), (0.5, 0.5))),
        precedence=-1,
        doc="BoundingBox of the area in which the pattern is generated.")

    xdensity = param.Number(default=10,
                            bounds=(0, None),
                            precedence=-1,
                            doc="""
        Density (number of samples per 1.0 length) in the x direction.""")

    ydensity = param.Number(default=10,
                            bounds=(0, None),
                            precedence=-1,
                            doc="""
        Density (number of samples per 1.0 length) in the y direction.
        Typically the same as the xdensity.""")

    x = param.Number(default=0.0,
                     softbounds=(-1.0, 1.0),
                     precedence=0.20,
                     doc="""
        X-coordinate location of pattern center.""")

    y = param.Number(default=0.0,
                     softbounds=(-1.0, 1.0),
                     precedence=0.21,
                     doc="""
        Y-coordinate location of pattern center.""")

    position = param.Composite(attribs=['x', 'y'],
                               precedence=-1,
                               doc="""
        Coordinates of location of pattern center.
        Provides a convenient way to set the x and y parameters together
        as a tuple (x,y), but shares the same actual storage as x and y
        (and thus only position OR x and y need to be specified).""")

    orientation = param.Number(default=0.0,
                               softbounds=(0.0, 2 * pi),
                               precedence=0.40,
                               doc="""
        Polar angle of pattern, i.e., the orientation in the Cartesian coordinate
        system, with zero at 3 o'clock and increasing counterclockwise.""")

    size = param.Number(default=1.0,
                        bounds=(0.0, None),
                        softbounds=(0.0, 6.0),
                        precedence=0.30,
                        doc="""Determines the overall size of the pattern.""")

    scale = param.Number(default=1.0,
                         softbounds=(0.0, 2.0),
                         precedence=0.10,
                         doc="""
        Multiplicative strength of input pattern, defaulting to 1.0""")

    offset = param.Number(default=0.0,
                          softbounds=(-1.0, 1.0),
                          precedence=0.11,
                          doc="""
        Additive offset to input pattern, defaulting to 0.0""")

    mask = param.Parameter(default=None,
                           precedence=-1,
                           doc="""
        Optional object (expected to be an array) with which to multiply the
        pattern array after it has been created, before any output_fns are
        applied. This can be used to shape the pattern.""")

    # Note that the class type is overridden to PatternGenerator below
    mask_shape = param.ClassSelector(param.Parameterized,
                                     default=None,
                                     precedence=0.06,
                                     doc="""
        Optional PatternGenerator used to construct a mask to be applied to
        the pattern.""")

    output_fns = param.HookList(default=[],
                                class_=TransferFn,
                                precedence=0.08,
                                doc="""
        Optional function(s) to apply to the pattern array after it has been created.
        Can be used for normalization, thresholding, etc.""")

    def __init__(self, **params):
        super(PatternGenerator, self).__init__(**params)
        self.set_matrix_dimensions(self.bounds, self.xdensity, self.ydensity)

    def __call__(self, **params_to_override):
        """
        Call the subclass's 'function' method on a rotated and scaled coordinate system.

        Creates and fills an array with the requested pattern.  If
        called without any params, uses the values for the Parameters
        as currently set on the object. Otherwise, any params
        specified override those currently set on the object.
        """
        p = ParamOverrides(self, params_to_override)

        # CEBERRORALERT: position parameter is not currently
        # supported. We should delete the position parameter or fix
        # this.
        #
        # position=params_to_override.get('position',None) if position
        # is not None: x,y = position

        self._setup_xy(p.bounds, p.xdensity, p.ydensity, p.x, p.y,
                       p.orientation)
        fn_result = self.function(p)
        self._apply_mask(p, fn_result)
        result = p.scale * fn_result + p.offset

        for of in p.output_fns:
            of(result)

        return result

    def _setup_xy(self, bounds, xdensity, ydensity, x, y, orientation):
        """
        Produce pattern coordinate matrices from the bounds and
        density (or rows and cols), and transforms them according to
        x, y, and orientation.
        """
        self.debug(
            lambda:
            "bounds=%s, xdensity=%s, ydensity=%s, x=%s, y=%s, orientation=%s" %
            (bounds, xdensity, ydensity, x, y, orientation))
        # Generate vectors representing coordinates at which the pattern
        # will be sampled.

        # CB: note to myself - use slice_._scs if supplied?
        x_points, y_points = SheetCoordinateSystem(
            bounds, xdensity, ydensity).sheetcoordinates_of_matrixidx()

        # Generate matrices of x and y sheet coordinates at which to
        # sample pattern, at the correct orientation
        self.pattern_x, self.pattern_y = self._create_and_rotate_coordinate_arrays(
            x_points - x, y_points - y, orientation)

    def function(self, p):
        """
        Function to draw a pattern that will then be scaled and rotated.

        Instead of implementing __call__ directly, PatternGenerator
        subclasses will typically implement this helper function used
        by __call__, because that way they can let __call__ handle the
        scaling and rotation for them.  Alternatively, __call__ itself
        can be reimplemented entirely by a subclass (e.g. if it does
        not need to do any scaling or rotation), in which case this
        function will be ignored.
        """
        raise NotImplementedError

    def _create_and_rotate_coordinate_arrays(self, x, y, orientation):
        """
        Create pattern matrices from x and y vectors, and rotate
        them to the specified orientation.
        """
        # Using this two-liner requires that x increase from left to
        # right and y decrease from left to right; I don't think it
        # can be rewritten in so little code otherwise - but please
        # prove me wrong.
        pattern_y = subtract.outer(cos(orientation) * y, sin(orientation) * x)
        pattern_x = add.outer(sin(orientation) * y, cos(orientation) * x)
        return pattern_x, pattern_y

    def _apply_mask(self, p, mat):
        """Create (if necessary) and apply the mask to the given matrix mat."""
        mask = p.mask
        ms = p.mask_shape
        if ms is not None:
            mask = ms(x=p.x + p.size *
                      (ms.x * cos(p.orientation) - ms.y * sin(p.orientation)),
                      y=p.y + p.size *
                      (ms.x * sin(p.orientation) + ms.y * cos(p.orientation)),
                      orientation=ms.orientation + p.orientation,
                      size=ms.size * p.size,
                      bounds=p.bounds,
                      ydensity=p.ydensity,
                      xdensity=p.xdensity)
        if mask is not None:
            mat *= mask

    def set_matrix_dimensions(self, bounds, xdensity, ydensity):
        """
        Change the dimensions of the matrix into which the pattern will be drawn.
        Users of this class should call this method rather than changing
        the bounds, xdensity, and ydensity parameters directly.  Subclasses
        can override this method to update any internal data structures that
        may depend on the matrix dimensions.
        """
        self.bounds = bounds
        self.xdensity = xdensity
        self.ydensity = ydensity
Esempio n. 25
0
 class Test(param.Parameterized):
     a = param.Number(default=1.2, bounds=(0, 5), step=0.1)
Esempio n. 26
0
class VTKVolume(AbstractVTK):

    ambient = param.Number(default=0.2,
                           step=1e-2,
                           doc="""
        Value to control the ambient lighting. It is the light an
        object gives even in the absence of strong light. It is
        constant in all directions.""")

    controller_expanded = param.Boolean(default=True,
                                        doc="""
        If True the volume controller panel options is expanded in the view""")

    colormap = param.Selector(default='erdc_rainbow_bright',
                              objects=PRESET_CMAPS,
                              doc="""
        Name of the colormap used to transform pixel value in color.""")

    diffuse = param.Number(default=0.7,
                           step=1e-2,
                           doc="""
        Value to control the diffuse Lighting. It relies on both the
        light direction and the object surface normal.""")

    display_volume = param.Boolean(default=True,
                                   doc="""
        If set to True, the 3D respresentation of the volume is
        displayed using ray casting.""")

    display_slices = param.Boolean(default=False,
                                   doc="""
        If set to true, the orthgonal slices in the three (X, Y, Z)
        directions are displayed. Position of each slice can be
        controlled using slice_(i,j,k) parameters.""")

    edge_gradient = param.Number(default=0.4,
                                 bounds=(0, 1),
                                 step=1e-2,
                                 doc="""
        Parameter to adjust the opacity of the volume based on the
        gradient between voxels.""")

    interpolation = param.Selector(
        default='fast_linear',
        objects=['fast_linear', 'linear', 'nearest'],
        doc="""
        interpolation type for sampling a volume. `nearest`
        interpolation will snap to the closest voxel, `linear` will
        perform trilinear interpolation to compute a scalar value from
        surrounding voxels.  `fast_linear` under WebGL 1 will perform
        bilinear interpolation on X and Y but use nearest for Z. This
        is slightly faster than full linear at the cost of no Z axis
        linear interpolation.""")

    mapper = param.Dict(doc="Lookup Table in format {low, high, palette}")

    max_data_size = param.Number(default=(256**3) * 2 / 1e6,
                                 doc="""
        Maximum data size transfert allowed without subsampling""")

    origin = param.Tuple(default=None, length=3, allow_None=True)

    render_background = param.Color(default='#52576e',
                                    doc="""
        Allows to specify the background color of the 3D rendering.
        The value must be specified as an hexadecimal color string.""")

    rescale = param.Boolean(default=False,
                            doc="""
        If set to True the colormap is rescaled beween min and max
        value of the non-transparent pixel, otherwise  the full range
        of the pixel values are used.""")

    shadow = param.Boolean(default=True,
                           doc="""
        If set to False, then the mapper for the volume will not
        perform shading computations, it is the same as setting
        ambient=1, diffuse=0, specular=0.""")

    sampling = param.Number(default=0.4,
                            bounds=(0, 1),
                            step=1e-2,
                            doc="""
        Parameter to adjust the distance between samples used for
        rendering. The lower the value is the more precise is the
        representation but it is more computationally intensive.""")

    spacing = param.Tuple(default=(1, 1, 1),
                          length=3,
                          doc="""
        Distance between voxel in each direction""")

    specular = param.Number(default=0.3,
                            step=1e-2,
                            doc="""
        Value to control specular lighting. It is the light reflects
        back toward the camera when hitting the object.""")

    specular_power = param.Number(default=8.,
                                  doc="""
        Specular power refers to how much light is reflected in a
        mirror like fashion, rather than scattered randomly in a
        diffuse manner.""")

    slice_i = param.Integer(per_instance=True,
                            doc="""
        Integer parameter to control the position of the slice normal
        to the X direction.""")

    slice_j = param.Integer(per_instance=True,
                            doc="""
        Integer parameter to control the position of the slice normal
        to the Y direction.""")

    slice_k = param.Integer(per_instance=True,
                            doc="""
        Integer parameter to control the position of the slice normal
        to the Z direction.""")

    _serializers = {}

    _rename = {'max_data_size': None, 'spacing': None, 'origin': None}

    _updates = True

    def __init__(self, object=None, **params):
        super(VTKVolume, self).__init__(object, **params)
        self._sub_spacing = self.spacing
        self._update()

    @classmethod
    def applies(cls, obj):
        if ((isinstance(obj, np.ndarray) and obj.ndim == 3)
                or any([isinstance(obj, k) for k in cls._serializers.keys()])):
            return True
        elif 'vtk' not in sys.modules:
            return False
        else:
            import vtk
            return isinstance(obj, vtk.vtkImageData)

    def _get_model(self, doc, root=None, parent=None, comm=None):
        """
        Should return the bokeh model to be rendered.
        """
        if 'panel.models.vtk' not in sys.modules:
            if isinstance(comm, JupyterComm):
                self.param.warning(
                    'VTKVolumePlot was not imported on instantiation '
                    'and may not render in a notebook. Restart '
                    'the notebook kernel and ensure you load '
                    'it as part of the extension using:'
                    '\n\npn.extension(\'vtk\')\n')
            from ...models.vtk import VTKVolumePlot
        else:
            VTKVolumePlot = getattr(sys.modules['panel.models.vtk'],
                                    'VTKVolumePlot')

        props = self._process_param_change(self._init_properties())
        volume_data = self._volume_data

        model = VTKVolumePlot(data=volume_data, **props)
        if root is None:
            root = model
        self._link_props(model, [
            'colormap', 'orientation_widget', 'camera', 'mapper',
            'controller_expanded'
        ], doc, root, comm)
        self._models[root.ref['id']] = (model, parent)
        return model

    def _update_object(self, ref, doc, root, parent, comm):
        self._legend = None
        super(VTKVolume, self)._update_object(ref, doc, root, parent, comm)

    def _init_properties(self):
        return {
            k: v
            for k, v in self.param.get_param_values()
            if v is not None and k not in
            ['default_layout', 'object', 'max_data_size', 'spacing', 'origin']
        }

    def _get_object_dimensions(self):
        if isinstance(self.object, np.ndarray):
            return self.object.shape
        else:
            return self.object.GetDimensions()

    def _process_param_change(self, msg):
        msg = super(VTKVolume, self)._process_param_change(msg)
        if self.object is not None:
            slice_params = {'slice_i': 0, 'slice_j': 1, 'slice_k': 2}
            for k, v in msg.items():
                sub_dim = self._subsample_dimensions
                ori_dim = self._orginal_dimensions
                if k in slice_params:
                    index = slice_params[k]
                    msg[k] = int(np.round(v * sub_dim[index] / ori_dim[index]))
        return msg

    def _process_property_change(self, msg):
        msg = super(VTKVolume, self)._process_property_change(msg)
        if self.object is not None:
            slice_params = {'slice_i': 0, 'slice_j': 1, 'slice_k': 2}
            for k, v in msg.items():
                sub_dim = self._subsample_dimensions
                ori_dim = self._orginal_dimensions
                if k in slice_params:
                    index = slice_params[k]
                    msg[k] = int(np.round(v * ori_dim[index] / sub_dim[index]))
        return msg

    def _update(self, ref=None, model=None):
        self._volume_data = self._get_volume_data()
        if self._volume_data is not None:
            self._orginal_dimensions = self._get_object_dimensions()
            self._subsample_dimensions = self._volume_data['dims']
            self.param.slice_i.bounds = (0, self._orginal_dimensions[0] - 1)
            self.slice_i = (self._orginal_dimensions[0] - 1) // 2
            self.param.slice_j.bounds = (0, self._orginal_dimensions[1] - 1)
            self.slice_j = (self._orginal_dimensions[1] - 1) // 2
            self.param.slice_k.bounds = (0, self._orginal_dimensions[2] - 1)
            self.slice_k = (self._orginal_dimensions[2] - 1) // 2
        if model is not None:
            model.data = self._volume_data

    @classmethod
    def register_serializer(cls, class_type, serializer):
        """
        Register a seriliazer for a given type of class.
        A serializer is a function which take an instance of `class_type`
        (like a vtk.vtkImageData) as input and return a numpy array of the data
        """
        cls._serializers.update({class_type: serializer})

    def _volume_from_array(self, sub_array):
        return dict(
            buffer=base64encode(
                sub_array.ravel(
                    order='F' if sub_array.flags['F_CONTIGUOUS'] else 'C')),
            dims=sub_array.shape
            if sub_array.flags['F_CONTIGUOUS'] else sub_array.shape[::-1],
            spacing=self._sub_spacing
            if sub_array.flags['F_CONTIGUOUS'] else self._sub_spacing[::-1],
            origin=self.origin,
            data_range=(sub_array.min(), sub_array.max()),
            dtype=sub_array.dtype.name)

    def _get_volume_data(self):
        if self.object is None:
            return None
        elif isinstance(self.object, np.ndarray):
            return self._volume_from_array(self._subsample_array(self.object))
        else:
            available_serializer = [
                v for k, v in VTKVolume._serializers.items()
                if isinstance(self.object, k)
            ]
            if not available_serializer:
                import vtk
                from vtk.util import numpy_support

                def volume_serializer(inst):
                    imageData = inst.object
                    array = numpy_support.vtk_to_numpy(
                        imageData.GetPointData().GetScalars())
                    dims = imageData.GetDimensions()[::-1]
                    inst.spacing = imageData.GetSpacing()[::-1]
                    inst.origin = imageData.GetOrigin()
                    return inst._volume_from_array(
                        inst._subsample_array(array.reshape(dims, order='C')))

                VTKVolume.register_serializer(vtk.vtkImageData,
                                              volume_serializer)
                serializer = volume_serializer
            else:
                serializer = available_serializer[0]
            return serializer(self)

    def _subsample_array(self, array):
        original_shape = array.shape
        spacing = self.spacing
        extent = tuple(
            (o_s - 1) * s for o_s, s in zip(original_shape, spacing))
        dim_ratio = np.cbrt((array.nbytes / 1e6) / self.max_data_size)
        max_shape = tuple(int(o_s / dim_ratio) for o_s in original_shape)
        dowsnscale_factor = [
            max(o_s, m_s) / m_s for m_s, o_s in zip(max_shape, original_shape)
        ]

        if any([d_f > 1 for d_f in dowsnscale_factor]):
            try:
                import scipy.ndimage as nd
                sub_array = nd.interpolation.zoom(
                    array,
                    zoom=[1 / d_f for d_f in dowsnscale_factor],
                    order=0)
            except ImportError:
                sub_array = array[::int(np.ceil(dowsnscale_factor[0])), ::int(
                    np.ceil(dowsnscale_factor[1])
                ), ::int(np.ceil(dowsnscale_factor[2]))]
            self._sub_spacing = tuple(e / (s - 1)
                                      for e, s in zip(extent, sub_array.shape))
        else:
            sub_array = array
            self._sub_spacing = self.spacing
        return sub_array
Esempio n. 27
0
 class Test(param.Parameterized):
     a = param.Number(bounds=(0, 10))
     b = param.String(default='A')
Esempio n. 28
0
class RasterGridPlot(GridPlot, OverlayPlot):
    """
    RasterGridPlot evenly spaces out plots of individual projections on
    a grid, even when they differ in size. Since this class uses a single
    axis to generate all the individual plots it is much faster than the
    equivalent using subplots.
    """

    padding = param.Number(default=0.1,
                           doc="""
        The amount of padding as a fraction of the total Grid size""")

    # Parameters inherited from OverlayPlot that are not part of the
    # GridPlot interface. Some of these may be enabled in future in
    # conjunction with GridPlot.

    apply_extents = param.Parameter(precedence=-1)
    apply_ranges = param.Parameter(precedence=-1)
    apply_ticks = param.Parameter(precedence=-1)
    batched = param.Parameter(precedence=-1)
    bgcolor = param.Parameter(precedence=-1)
    default_span = param.Parameter(precedence=-1)
    invert_axes = param.Parameter(precedence=-1)
    invert_xaxis = param.Parameter(precedence=-1)
    invert_yaxis = param.Parameter(precedence=-1)
    invert_zaxis = param.Parameter(precedence=-1)
    labelled = param.Parameter(precedence=-1)
    legend_cols = param.Parameter(precedence=-1)
    legend_position = param.Parameter(precedence=-1)
    legend_limit = param.Parameter(precedence=-1)
    logx = param.Parameter(precedence=-1)
    logy = param.Parameter(precedence=-1)
    logz = param.Parameter(precedence=-1)
    show_grid = param.Parameter(precedence=-1)
    style_grouping = param.Parameter(precedence=-1)
    xlim = param.Parameter(precedence=-1)
    ylim = param.Parameter(precedence=-1)
    zlim = param.Parameter(precedence=-1)
    xticks = param.Parameter(precedence=-1)
    xformatter = param.Parameter(precedence=-1)
    yticks = param.Parameter(precedence=-1)
    yformatter = param.Parameter(precedence=-1)
    zticks = param.Parameter(precedence=-1)
    zaxis = param.Parameter(precedence=-1)
    zrotation = param.Parameter(precedence=-1)
    zformatter = param.Parameter(precedence=-1)

    def __init__(self,
                 layout,
                 keys=None,
                 dimensions=None,
                 create_axes=False,
                 ranges=None,
                 layout_num=1,
                 **params):
        top_level = keys is None
        if top_level:
            dimensions, keys = traversal.unique_dimkeys(layout)
        MPLPlot.__init__(self, dimensions=dimensions, keys=keys, **params)
        if top_level:
            self.comm = self.init_comm()

        self.layout = layout
        self.cyclic_index = 0
        self.zorder = 0
        self.layout_num = layout_num
        self.overlaid = False
        self.hmap = layout
        if layout.ndims > 1:
            xkeys, ykeys = zip(*layout.data.keys())
        else:
            xkeys = layout.keys()
            ykeys = [None]
        self._xkeys = sorted(set(xkeys))
        self._ykeys = sorted(set(ykeys))
        self._xticks, self._yticks = [], []
        self.rows, self.cols = layout.shape
        self.fig_inches = self._get_size()
        _, _, self.layout = self._create_subplots(layout,
                                                  None,
                                                  ranges,
                                                  create_axes=False)
        self.border_extents = self._compute_borders()
        width, height, _, _, _, _ = self.border_extents
        if self.aspect == 'equal':
            self.aspect = float(width / height)
        # Note that streams are not supported on RasterGridPlot
        # until that is implemented this stub is needed
        self.streams = []

    def _finalize_artist(self, key):
        pass

    def get_extents(self, view, ranges, range_type='combined'):
        if range_type == 'hard':
            return (np.nan, ) * 4
        width, height, _, _, _, _ = self.border_extents
        return (0, 0, width, height)

    def _get_frame(self, key):
        return GridPlot._get_frame(self, key)

    @mpl_rc_context
    def initialize_plot(self, ranges=None):
        _, _, b_w, b_h, widths, heights = self.border_extents

        key = self.keys[-1]
        ranges = self.compute_ranges(self.layout, key, ranges)
        self.handles['projs'] = {}
        x, y = b_w, b_h
        for xidx, xkey in enumerate(self._xkeys):
            w = widths[xidx]
            for yidx, ykey in enumerate(self._ykeys):
                h = heights[yidx]
                if self.layout.ndims > 1:
                    vmap = self.layout.get((xkey, ykey), None)
                else:
                    vmap = self.layout.get(xkey, None)
                pane = vmap.select(
                    **{
                        d.name: val
                        for d, val in zip(self.dimensions, key)
                        if d in vmap.kdims
                    })
                pane = vmap.last.values()[-1] if issubclass(
                    vmap.type, CompositeOverlay) else vmap.last
                data = get_raster_array(pane) if pane else None
                ranges = self.compute_ranges(vmap, key, ranges)
                opts = self.lookup_options(pane, 'style')[self.cyclic_index]
                plot = self.handles['axis'].imshow(data,
                                                   extent=(x, x + w, y, y + h),
                                                   **opts)
                cdim = pane.vdims[0].name
                valrange = match_spec(pane,
                                      ranges).get(cdim,
                                                  pane.range(cdim))['combined']
                plot.set_clim(valrange)
                if data is None:
                    plot.set_visible(False)
                self.handles['projs'][(xkey, ykey)] = plot
                y += h + b_h
                if xidx == 0:
                    self._yticks.append(y - b_h - h / 2.)
            y = b_h
            x += w + b_w
            self._xticks.append(x - b_w - w / 2.)

        kwargs = self._get_axis_kwargs()
        return self._finalize_axis(key, ranges=ranges, **kwargs)

    @mpl_rc_context
    def update_frame(self, key, ranges=None):
        grid = self._get_frame(key)
        ranges = self.compute_ranges(self.layout, key, ranges)
        for xkey in self._xkeys:
            for ykey in self._ykeys:
                plot = self.handles['projs'][(xkey, ykey)]
                grid_key = (xkey, ykey) if self.layout.ndims > 1 else (xkey, )
                element = grid.data.get(grid_key, None)
                if element:
                    plot.set_visible(True)
                    img = element.values()[0] if isinstance(
                        element, CompositeOverlay) else element
                    data = get_raster_array(img)
                    plot.set_data(data)
                else:
                    plot.set_visible(False)

        kwargs = self._get_axis_kwargs()
        return self._finalize_axis(key, ranges=ranges, **kwargs)

    def _get_axis_kwargs(self):
        xdim = self.layout.kdims[0]
        ydim = self.layout.kdims[1] if self.layout.ndims > 1 else None
        xticks = (self._xticks, [xdim.pprint_value(l) for l in self._xkeys])
        yticks = (self._yticks,
                  [ydim.pprint_value(l) if ydim else '' for l in self._ykeys])
        return dict(xlabel=xdim.pprint_label,
                    ylabel=ydim.pprint_label if ydim else '',
                    xticks=xticks,
                    yticks=yticks)

    def _compute_borders(self):
        ndims = self.layout.ndims
        width_fn = lambda x: x.range(0)
        height_fn = lambda x: x.range(1)
        width_extents = [
            max_range(self.layout[x, :].traverse(width_fn, [Element]))
            for x in unique_iterator(self.layout.dimension_values(0))
        ]
        if ndims > 1:
            height_extents = [
                max_range(self.layout[:, y].traverse(height_fn, [Element]))
                for y in unique_iterator(self.layout.dimension_values(1))
            ]
        else:
            height_extents = [
                max_range(self.layout.traverse(height_fn, [Element]))
            ]
        widths = [extent[0] - extent[1] for extent in width_extents]
        heights = [extent[0] - extent[1] for extent in height_extents]
        width, height = np.sum(widths), np.sum(heights)
        border_width = (width * self.padding) / (len(widths) + 1)
        border_height = (height * self.padding) / (len(heights) + 1)
        width += width * self.padding
        height += height * self.padding

        return width, height, border_width, border_height, widths, heights

    def __len__(self):
        return max([len(self.keys), 1])
Esempio n. 29
0
 class Test(param.Parameterized):
     a = param.Number(default=1, bounds=(0, 10), precedence=1)
     b = param.String(default='A', precedence=2)
Esempio n. 30
0
class resample_geometry(Operation):
    """
    This operation dynamically culls and resamples Path or Polygons
    elements based on the current zoom level. On first execution a
    RTree is created using the Sort-Tile-Recursive algorithm, which is
    used to query for geometries within the current viewport (defined
    by the x_range and y_range).

    Any geometries returned by the RTree query are tested to ensure
    their area is over the display_threshold, expressed as a fraction
    of the current viewport area. Any remaining polygons are
    simplified using the Douglas-Peucker algorithm, which eliminates
    vertices while ensuring that the curve does not diverge from the
    original curve by more than the tolerance. The tolerance is
    expressed as a fraction of the square root of the area of the
    current viewport.

    Once computed a simplified geometry is cached depending on the
    current zoom level. The number of valid zoom levels can be
    declared and are used to recursively subdivide the domain into
    smaller subregions.

    If requested the geometries can also be clipped to the current
    viewport which avoids having to render vertices that are not
    visible.
    """

    cache = param.Boolean(default=True,
                          doc="""
        Whether to cache simplified geometries depending on the zoom
        level.""")

    clip = param.Boolean(default=False,
                         doc="""
        Whether to disable the cache and clip polygons
        to current bounds.""")

    display_threshold = param.Number(default=0.0001,
                                     doc="""
        The fraction of the current viewport covered by a geometry
        before it is shown.""")

    dynamic = param.Boolean(default=True,
                            doc="""
       Enables dynamic processing by default.""")

    preserve_topology = param.Boolean(default=False,
                                      doc="""
        Whether to preserve topology between geometries. If disabled
        simplification can produce self-intersecting or otherwise
        invalid geometries but will be much faster.""")

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

    tolerance_factor = param.Number(default=0.002,
                                    doc="""
        The tolerance distance for path simplification as a fraction
        of the square root of the area of the current viewport.""")

    x_range = param.NumericTuple(default=None,
                                 length=2,
                                 doc="""
       The x_range as a tuple of min and max x-value. Auto-ranges
       if set to None.""")

    y_range = param.NumericTuple(default=None,
                                 length=2,
                                 doc="""
       The x_range as a tuple of min and max y-value. Auto-ranges
       if set to None.""")

    zoom_levels = param.Integer(default=20,
                                doc="""
        The number of zoom levels to cache.""")

    @param.parameterized.bothmethod
    def instance(self_or_cls, **params):
        inst = super(resample_geometry, self_or_cls).instance(**params)
        inst._cache = {}
        return inst

    def _process(self, element, key=None):
        # Compute view port
        x0, x1 = self.p.x_range or element.range(0)
        y0, y1 = self.p.y_range or element.range(1)
        bounds = bounds_to_poly((x0, y0, x1, y1))

        # Initialize or lookup cache with STRTree
        if element._plot_id in self._cache:
            cache = self._cache[element._plot_id]
            domain, tree, geom_dicts, geom_cache, area_cache = cache
        else:
            if isinstance(element, Polygons):
                geom_dicts = polygons_to_geom_dicts(element)
            elif isinstance(element, Path):
                geom_dicts = path_to_geom_dicts(element)
            geoms = [g['geometry'] for g in geom_dicts]
            tree = STRtree(geoms)
            domain = bounds
            geom_cache, area_cache = {}, {}
            self._cache.clear()
            cache = (domain, tree, geom_dicts, geom_cache, area_cache)
            self._cache[element._plot_id] = cache

        area = bounds.area
        current_zoom = compute_zoom_level(bounds, domain, self.p.zoom_levels)
        tol = np.sqrt(bounds.area) * self.p.tolerance_factor

        # Query RTree, then cull and simplify polygons
        new_geoms, gdict = [], {}
        for g in tree.query(bounds):
            garea = area_cache.get(id(g))
            if garea is None:
                is_poly = 'Polygon' in g.geom_type
                garea = g.area if is_poly else bounds_to_poly(g.bounds).area
                area_cache[id(g)] = garea

            # Skip if geometry area is below display threshold or
            # does not intersect with viewport
            if ((self.p.display_threshold is not None and
                 (garea / area) < self.p.display_threshold)
                    or not g.intersects(bounds)):
                continue

            # Try to look up geometry in cache by zoom level
            cache_id = (id(g), current_zoom)
            if cache_id in geom_cache and not self.p.clip:
                geom_dict = geom_cache[cache_id]
            else:
                if element.vdims:
                    gidx = find_geom(tree._geoms, g)
                    gdict = geom_dicts[gidx]

                g = g.simplify(tol, self.p.preserve_topology)
                if not g:
                    continue  # Skip if geometry empty

                geom_dict = dict(gdict, geometry=g)
                if self.p.cache:
                    geom_cache[cache_id] = geom_dict
                if self.p.clip:
                    geom_dict = dict(geom_dict,
                                     geometry=g.intersection(bounds))
            new_geoms.append(geom_dict)
        return element.clone(new_geoms)