class MatrixDisplayFilter(object): def __init__(self, main_window, data_fields, text_fields): self.output = None self._signalHandler = SignalHandler() self.sigOutputChanged = self._signalHandler.sigOutputChanged self.view_box = main_window.matrix_widget.view_box self.main_window = main_window self.legend = None self.colorMap = ColorMapParameter() self.data_fields = data_fields self.text_fields = text_fields self.field_names = [field[0] for field in self.data_fields] self.colorMap.setFields(self.data_fields) self.params = Parameter.create( name='Matrix Display', type='group', children=[ self.colorMap, { 'name': 'Text format', 'type': 'str' }, { 'name': 'Show Confidence', 'type': 'list', 'values': [field[0] for field in self.data_fields], 'value': 'None' }, # {'name': 'log_scale', 'type': 'bool'}, ]) self.params.sigTreeStateChanged.connect(self.invalidate_output) self.colorMap.sigColorMapChanged.connect(self.set_default_text) def set_default_text(self): map_field = self.get_colormap_field() default_text = self.text_fields.get(map_field, '') self.params.child('Text format').setValue(default_text) def element_display_output(self, result, default_bgcolor): colormap = self.colorMap show_confidence = self.params['Show Confidence'] text_format = self.params['Text format'] self.output = {} # if show_confidence != 'None': # default_bgcolor = np.array([128., 128., 128., 255.]) # else: # default_bgcolor = np.array([220., 220., 220.]) # if result['no_data'] is False: # self.output['bgcolor'] = tuple(default_bgcolor) # self.output['fgcolor'] = 0.6 # self.output['text'] = '' # else: result_vals = result.loc[:, 'metric_summary'] result_vals.replace(np.nan, '') mappable_result = { k: v for k, v in result_vals.iteritems() if np.isscalar(v) } color = colormap.map(mappable_result)[0] # desaturate low confidence cells if show_confidence != 'None': lower, upper = result[show_confidence, 'metric_conf'] confidence = (1.0 - (upper - lower))**2 color = color * confidence + default_bgcolor * (1.0 - confidence) # invert text color for dark background self.output['fgcolor'] = 'w' if sum(color[:3]) < 384 else 'k' text_result = { k: FormattableNumber(v) if isinstance(v, float) else v for k, v in result_vals.iteritems() } self.output['text'] = text_format.format(**text_result) if self.output['text'] == 'nan': self.output['text'] = '' self.output['bgcolor'] = tuple(color) return self.output def colormap_legend(self): if self.legend is not None: self.view_box.removeItem(self.legend) if len(self.colorMap.children()) == 0: pg.QtGui.QMessageBox.information( self.main_window, '', "No Analysis ColorMap is selected, please add one and Update Results", pg.QtGui.QMessageBox.Ok) raise Exception("No color maps are selected.") cmap_item = [ cmap for cmap in self.colorMap.children() if cmap['Enabled'] is True ][0] # log_scale = self.params.child('log_scale').value() colors = cmap_item.value().color x_min = cmap_item['Min'] x_max = cmap_item['Max'] x = np.linspace(x_min, x_max, len(colors)) name = cmap_item.name() if name.endswith('Probability'): log_scale = True else: log_scale = False # units = self.colorMap.fields[name].get('units', None) min_scale, _ = pg.siScale(x_min) max_scale, _ = pg.siScale(x_max) scale = min_scale if (1 / min_scale) < (1 / max_scale) else max_scale # if units is not None: # units = scale + units # else: # units = '' self.legend = pg.GradientLegend([25, 300], [-20, -30]) if log_scale is True: cmap2 = pg.ColorMap(x, colors) self.legend.setGradient(cmap2.getGradient()) self.legend.setLabels({ '%0.02f' % (a * scale): b for a, b in zip(cmap_item.value().pos, x) }) else: self.legend.setGradient(cmap_item.value().getGradient()) self.legend.setLabels({ '%0.02f' % (a * scale): b for a, b in zip(x, cmap_item.value().pos) }) self.view_box.addItem(self.legend) def get_colormap_field(self): color_map_fields = self.colorMap.children() if len(color_map_fields) == 0: field_name = '' elif len(color_map_fields) > 1: field_name = [ field.name() for field in color_map_fields if field['Enabled'] is True ][0] else: field_name = color_map_fields[0].name() return field_name def invalidate_output(self): self.output = None
class ScatterPlotWidget(pg.QtGui.QSplitter): """ This is a high-level widget for exploring relationships in tabular data. Given a multi-column record array, the widget displays a scatter plot of a specific subset of the data. Includes controls for selecting the columns to plot, filtering data, and determining symbol color and shape. The widget consists of four components: 1) A list of column names from which the user may select 1 or 2 columns to plot. If one column is selected, the data for that column will be plotted in a histogram-like manner by using :func:`pseudoScatter() <pyqtgraph.pseudoScatter>`. If two columns are selected, then the scatter plot will be generated with x determined by the first column that was selected and y by the second. 2) A DataFilter that allows the user to select a subset of the data by specifying multiple selection criteria. 3) A ColorMap that allows the user to determine how points are colored by specifying multiple criteria. 4) A PlotWidget for displaying the data. """ sigScatterPlotClicked = pg.QtCore.Signal(object, object, object) def __init__(self, parent=None): pg.QtGui.QSplitter.__init__(self, pg.QtCore.Qt.Horizontal) self.ctrlPanel = pg.QtGui.QSplitter(pg.QtCore.Qt.Vertical) self.addWidget(self.ctrlPanel) self.fieldList = pg.QtGui.QListWidget() self.fieldList.setSelectionMode(self.fieldList.ExtendedSelection) self.ptree = pg.parametertree.ParameterTree(showHeader=False) self.filter = DataFilterParameter() self.colorMap = ColorMapParameter() self.style = StyleMapParameter() self.params = pg.parametertree.Parameter.create(name='params', type='group', children=[self.filter, self.colorMap, self.style]) self.ptree.setParameters(self.params, showTop=False) self.plot = PlotWidget() self.ctrlPanel.addWidget(self.fieldList) self.ctrlPanel.addWidget(self.ptree) self.addWidget(self.plot) fg = pg.mkColor(pg.getConfigOption('foreground')) fg.setAlpha(150) self.filterText = pg.TextItem(border=pg.getConfigOption('foreground'), color=fg) self.filterText.setPos(60,20) self.filterText.setParentItem(self.plot.plotItem) self.data = None self.indices = None self.mouseOverField = None self.scatterPlot = None self.selectionScatter = None self.selectedIndices = [] self._visibleXY = None # currently plotted points self._visibleData = None # currently plotted records self._visibleIndices = None self._indexMap = None self.fieldList.itemSelectionChanged.connect(self.fieldSelectionChanged) self.filter.sigFilterChanged.connect(self.filterChanged) self.colorMap.sigColorMapChanged.connect(self.updatePlot) self.style.sigStyleChanged.connect(self.updatePlot) def setFields(self, fields, mouseOverField=None): """ Set the list of field names/units to be processed. The format of *fields* is the same as used by :func:`ColorMapWidget.setFields <pyqtgraph.widgets.ColorMapWidget.ColorMapParameter.setFields>` """ self.fields = OrderedDict(fields) self.mouseOverField = mouseOverField self.fieldList.clear() for f,opts in fields: item = pg.QtGui.QListWidgetItem(f) item.opts = opts item = self.fieldList.addItem(item) self.filter.setFields(fields) self.colorMap.setFields(fields) self.style.setFields(fields) def setSelectedFields(self, *fields): self.fieldList.itemSelectionChanged.disconnect(self.fieldSelectionChanged) try: self.fieldList.clearSelection() for f in fields: i = list(self.fields.keys()).index(f) item = self.fieldList.item(i) item.setSelected(True) finally: self.fieldList.itemSelectionChanged.connect(self.fieldSelectionChanged) self.fieldSelectionChanged() def setData(self, data): """ Set the data to be processed and displayed. Argument must be a numpy record array. """ self.data = data self.indices = np.arange(len(data)) self.filtered = None self.filteredIndices = None self.updatePlot() def setSelectedIndices(self, inds): """Mark the specified indices as selected. Must be a sequence of integers that index into the array given in setData(). """ self.selectedIndices = inds self.updateSelected() def setSelectedPoints(self, points): """Mark the specified points as selected. Must be a list of points as generated by the sigScatterPlotClicked signal. """ self.setSelectedIndices([pt.originalIndex for pt in points]) def fieldSelectionChanged(self): sel = self.fieldList.selectedItems() if len(sel) > 2: self.fieldList.blockSignals(True) try: for item in sel[1:-1]: item.setSelected(False) finally: self.fieldList.blockSignals(False) self.updatePlot() def filterChanged(self, f): self.filtered = None self.updatePlot() desc = self.filter.describe() if len(desc) == 0: self.filterText.setVisible(False) else: self.filterText.setText('\n'.join(desc)) self.filterText.setVisible(True) def updatePlot(self): self.plot.clear() if self.data is None or len(self.data) == 0: return if self.filtered is None: mask = self.filter.generateMask(self.data) self.filtered = self.data[mask] self.filteredIndices = self.indices[mask] data = self.filtered if len(data) == 0: return colors = np.array([pg.mkBrush(*x) for x in self.colorMap.map(data)]) style = self.style.map(data) ## Look up selected columns and units sel = list([str(item.text()) for item in self.fieldList.selectedItems()]) units = list([item.opts.get('units', '') for item in self.fieldList.selectedItems()]) if len(sel) == 0: self.plot.setTitle('') return if len(sel) == 1: self.plot.setLabels(left=('N', ''), bottom=(sel[0], units[0]), title='') if len(data) == 0: return #x = data[sel[0]] #y = None xy = [data[sel[0]], None] elif len(sel) == 2: self.plot.setLabels(left=(sel[1],units[1]), bottom=(sel[0],units[0])) if len(data) == 0: return xy = [data[sel[0]], data[sel[1]]] #xydata = [] #for ax in [0,1]: #d = data[sel[ax]] ### scatter catecorical values just a bit so they show up better in the scatter plot. ##if sel[ax] in ['MorphologyBSMean', 'MorphologyTDMean', 'FIType']: ##d += np.random.normal(size=len(cells), scale=0.1) #xydata.append(d) #x,y = xydata ## convert enum-type fields to float, set axis labels enum = [False, False] for i in [0,1]: axis = self.plot.getAxis(['bottom', 'left'][i]) if xy[i] is not None and (self.fields[sel[i]].get('mode', None) == 'enum' or xy[i].dtype.kind in ('S', 'O')): vals = self.fields[sel[i]].get('values', list(set(xy[i]))) xy[i] = np.array([vals.index(x) if x in vals else len(vals) for x in xy[i]], dtype=float) axis.setTicks([list(enumerate(vals))]) enum[i] = True else: axis.setTicks(None) # reset to automatic ticking ## mask out any nan values mask = np.ones(len(xy[0]), dtype=bool) if xy[0].dtype.kind == 'f': mask &= np.isfinite(xy[0]) if xy[1] is not None and xy[1].dtype.kind == 'f': mask &= np.isfinite(xy[1]) xy[0] = xy[0][mask] for k in style.keys(): if style[k] is None: continue style[k] = style[k][mask] style['symbolBrush'] = colors[mask] data = data[mask] indices = self.filteredIndices[mask] ## Scatter y-values for a histogram-like appearance if xy[1] is None: ## column scatter plot xy[1] = pg.pseudoScatter(xy[0]) else: xy[1] = xy[1][mask] ## beeswarm plots for ax in [0,1]: if not enum[ax]: continue imax = int(xy[ax].max()) if len(xy[ax]) > 0 else 0 for i in range(imax+1): keymask = xy[ax] == i scatter = pg.pseudoScatter(xy[1-ax][keymask], bidir=True) if len(scatter) == 0: continue smax = np.abs(scatter).max() if smax != 0: scatter *= 0.2 / smax xy[ax][keymask] += scatter if self.scatterPlot is not None: try: self.scatterPlot.sigPointsClicked.disconnect(self.plotClicked) except: pass self._visibleXY = xy self._visibleData = data self._visibleIndices = indices self._indexMap = None self.scatterPlot = self.plot.plot(xy[0], xy[1], data=data, **style) self.scatterPlot.sigPointsClicked.connect(self.plotClicked) self.updateSelected() def updateSelected(self): if self._visibleXY is None: return # map from global index to visible index indMap = self._getIndexMap() inds = [indMap[i] for i in self.selectedIndices if i in indMap] x,y = self._visibleXY[0][inds], self._visibleXY[1][inds] if self.selectionScatter is not None: self.plot.plotItem.removeItem(self.selectionScatter) if len(x) == 0: return self.selectionScatter = self.plot.plot(x, y, pen=None, symbol='s', symbolSize=12, symbolBrush=None, symbolPen='y') def _getIndexMap(self): # mapping from original data index to visible point index if self._indexMap is None: self._indexMap = {j:i for i,j in enumerate(self._visibleIndices)} return self._indexMap def plotClicked(self, plot, points, ev): # Tag each point with its index into the original dataset for pt in points: pt.originalIndex = self._visibleIndices[pt.index()] self.sigScatterPlotClicked.emit(self, points, ev)
class MatrixDisplayFilter(object): def __init__(self, view_box, data_fields): self.output = None self._signalHandler = SignalHandler() self.sigOutputChanged = self._signalHandler.sigOutputChanged self.view_box = view_box self.legend = None self.colorMap = ColorMapParameter() self.data_fields = data_fields self.field_names = [field[0] for field in self.data_fields] self.colorMap.setFields(self.data_fields) self.params = Parameter.create(name='Matrix Display', type='group', children=[ self.colorMap, {'name': 'Text format', 'type': 'str'}, {'name': 'Show Confidence', 'type': 'list', 'values': [field[0] for field in self.data_fields]}, {'name': 'log_scale', 'type': 'bool'}, ]) self.params.sigTreeStateChanged.connect(self.invalidate_output) def element_display_output(self, result, default_bgcolor): colormap = self.colorMap show_confidence = self.params['Show Confidence'] text_format = self.params['Text format'] self.output = {} # if show_confidence != 'None': # default_bgcolor = np.array([128., 128., 128., 255.]) # else: # default_bgcolor = np.array([220., 220., 220.]) # if result['no_data'] is False: # self.output['bgcolor'] = tuple(default_bgcolor) # self.output['fgcolor'] = 0.6 # self.output['text'] = '' # else: result_vals = result.loc[:, 'metric_summary'] mappable_result = {k:v for k,v in result_vals.iteritems() if np.isscalar(v)} color = colormap.map(mappable_result)[0] # desaturate low confidence cells if show_confidence != 'None': lower, upper = result[show_confidence, 'metric_conf'] confidence = (1.0 - (upper - lower)) ** 2 color = color * confidence + default_bgcolor * (1.0 - confidence) # invert text color for dark background self.output['fgcolor'] = 'w' if sum(color[:3]) < 384 else 'k' text_result = {k:FormattableNumber(v) if isinstance(v, float) else v for k, v in result_vals.iteritems()} self.output['text'] = text_format.format(**text_result) self.output['bgcolor'] = tuple(color) return self.output def colormap_legend(self): if self.legend is not None: self.view_box.removeItem(self.legend) cmap_item = [cmap for cmap in self.colorMap.children() if cmap['Enabled'] is True][0] log_scale = self.params.child('log_scale').value() colors = cmap_item.value().color x_min = cmap_item['Min'] x_max = cmap_item['Max'] x = np.linspace(x_min, x_max, len(colors)) name = cmap_item.name() # units = self.colorMap.fields[name].get('units', None) scale, prefix = pg.siScale(x_min) # if units is not None: # units = scale + units # else: # units = '' self.legend = pg.GradientLegend([25, 300], [-20, -30]) if log_scale is True: cmap2 = pg.ColorMap(x, colors) self.legend.setGradient(cmap2.getGradient()) self.legend.setLabels({'%0.02f' % (a*scale):b for a,b in zip(cmap_item.value().pos, x)}) else: self.legend.setGradient(cmap_item.value().getGradient()) self.legend.setLabels({'%0.02f' % (a*scale):b for a,b in zip(x, cmap_item.value().pos)}) self.view_box.addItem(self.legend) def get_colormap_field(self): color_map_fields = self.colorMap.children() if len(color_map_fields) > 1: field_name = [field.name() for field in color_map_fields if field['Enabled'] is True][0] else: field_name = color_map_fields[0].name() return field_name def invalidate_output(self): self.output = None
class MatrixDisplayFilter(object): def __init__(self, view_box, data_fields): self.output = None self._signalHandler = SignalHandler() self.sigOutputChanged = self._signalHandler.sigOutputChanged self.view_box = view_box self.legend = None self.colorMap = ColorMapParameter() self.data_fields = data_fields self.field_names = [field[0] for field in self.data_fields] self.colorMap.setFields(self.data_fields) self.params = Parameter.create( name='Matrix Display', type='group', children=[ self.colorMap, { 'name': 'Text format', 'type': 'str' }, { 'name': 'Show Confidence', 'type': 'list', 'values': [field[0] for field in self.data_fields] }, { 'name': 'log_scale', 'type': 'bool' }, ]) self.params.sigTreeStateChanged.connect(self.invalidate_output) def element_display_output(self, result, default_bgcolor): colormap = self.colorMap show_confidence = self.params['Show Confidence'] text_format = self.params['Text format'] self.output = {} # if show_confidence != 'None': # default_bgcolor = np.array([128., 128., 128., 255.]) # else: # default_bgcolor = np.array([220., 220., 220.]) # if result['no_data'] is False: # self.output['bgcolor'] = tuple(default_bgcolor) # self.output['fgcolor'] = 0.6 # self.output['text'] = '' # else: result_vals = result.loc[:, 'metric_summary'] mappable_result = { k: v for k, v in result_vals.iteritems() if np.isscalar(v) } color = colormap.map(mappable_result)[0] # desaturate low confidence cells if show_confidence != 'None': lower, upper = result[show_confidence, 'metric_conf'] confidence = (1.0 - (upper - lower))**2 color = color * confidence + default_bgcolor * (1.0 - confidence) # invert text color for dark background self.output['fgcolor'] = 'w' if sum(color[:3]) < 384 else 'k' text_result = { k: FormattableNumber(v) if isinstance(v, float) else v for k, v in result_vals.iteritems() } self.output['text'] = text_format.format(**text_result) self.output['bgcolor'] = tuple(color) return self.output def colormap_legend(self): if self.legend is not None: self.view_box.removeItem(self.legend) cmap_item = [ cmap for cmap in self.colorMap.children() if cmap['Enabled'] is True ][0] log_scale = self.params.child('log_scale').value() colors = cmap_item.value().color x_min = cmap_item['Min'] x_max = cmap_item['Max'] x = np.linspace(x_min, x_max, len(colors)) name = cmap_item.name() # units = self.colorMap.fields[name].get('units', None) scale, prefix = pg.siScale(x_min) # if units is not None: # units = scale + units # else: # units = '' self.legend = pg.GradientLegend([25, 300], [-20, -30]) if log_scale is True: cmap2 = pg.ColorMap(x, colors) self.legend.setGradient(cmap2.getGradient()) self.legend.setLabels({ '%0.02f' % (a * scale): b for a, b in zip(cmap_item.value().pos, x) }) else: self.legend.setGradient(cmap_item.value().getGradient()) self.legend.setLabels({ '%0.02f' % (a * scale): b for a, b in zip(x, cmap_item.value().pos) }) self.view_box.addItem(self.legend) def get_colormap_field(self): color_map_fields = self.colorMap.children() if len(color_map_fields) > 1: field_name = [ field.name() for field in color_map_fields if field['Enabled'] is True ][0] else: field_name = color_map_fields[0].name() return field_name def invalidate_output(self): self.output = None