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)
class Test(param.Parameterized): a = param.Number(bounds=(0, 10))
class Test(param.Parameterized): a = param.Number()
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
class Test(param.Parameterized): a = param.Number(default=1.2, bounds=(0, 5), precedence=-1) b = param.Boolean(default=True, precedence=1)
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)
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
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)
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
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
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()]
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
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])]
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)
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)
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
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
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
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
class MyParamNumber(param.Parameterized): age = param.Number(49, bounds=(0, 100), doc="Any Number between 0 to 100")
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]
class Test(param.Parameterized): a = param.Number(default=1.2, bounds=(0, 5), label='A') b = param.Action(label='B')
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
class Test(param.Parameterized): a = param.Number(default=1.2, bounds=(0, 5), step=0.1)
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
class Test(param.Parameterized): a = param.Number(bounds=(0, 10)) b = param.String(default='A')
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])
class Test(param.Parameterized): a = param.Number(default=1, bounds=(0, 10), precedence=1) b = param.String(default='A', precedence=2)
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)