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)
def do_GET(self): self.thread = thread = OSCThread() thread.daemon = True thread.start() actors = list() is_item1 = True is_item2 = True is_item3 = True def setPositions(): for ix, item in enumerate(actors): item.setPos(0, ix * 6) def scale_data(data, ix, max_items): scale = 254 / max_items * ix return [value / max_items + scale for value in data] try: self.path = re.sub('[^.a-zA-Z0-9]', "", str(self.path)) if self.path == "" or self.path == None or self.path[:1] == ".": return if self.path.endswith(".html"): f = open(curdir + sep + self.path) self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(f.read()) f.close() elif self.path.endswith(".mjpeg"): data_points = 1000 self.send_response(200) plot_data1 = data = deque([0] * data_points) plot_data2 = data = deque([0] * data_points) plot_data3 = data = deque([0] * data_points) plt = PlotWidget(title="<h1>EKG</h1>", name="Merle") plt.hide() plotItem1 = pg.PlotCurveItem(pen=pg.mkPen('r', width=2), name="bjoern") plotItem2 = pg.PlotCurveItem(pen=pg.mkPen('g', width=2), name="merle") plotItem3 = pg.PlotCurveItem(pen=pg.mkPen('b', width=2), name="uwe") print type(plotItem1) pen = pg.mkPen(254, 254, 254) plotItem1.setShadowPen(pen=pen, width=6, cosmetic=True) plotItem2.setShadowPen(pen=pen, width=6, cosmetic=True) plotItem3.setShadowPen(pen=pen, width=6, cosmetic=True) actors.append(plotItem1) actors.append(plotItem2) actors.append(plotItem3) plotItem1.setPos(0, 0 * 6) plotItem2.setPos(0, 1 * 6) plotItem3.setPos(0, 2 * 6) plt.addItem(plotItem1) plt.addItem(plotItem2) plt.addItem(plotItem3) plt.setLabel('left', "<h2>Amplitude</h2>") plt.setLabel('bottom', "<h2>Time</h2>") plt.showGrid(True, True) ba = plt.getAxis("bottom") bl = plt.getAxis("left") ba.setTicks([]) bl.setTicks([]) plt.setYRange(0, 254) self.wfile.write( "Content-Type: multipart/x-mixed-replace; boundary=--aaboundary" ) self.wfile.write("\r\n\r\n") plt.resize(1280, 720) while 1: while 1: try: osc_address, args = queue.get_nowait() except Queue.Empty: break value = args[0] if osc_address == "/bjoern/ekg": plot_data1.append(value) plot_data1.popleft() try: plotItem1.setData(y=np.array( scale_data(plot_data1, actors.index(plotItem1), len(actors))), clear=True) except ValueError: pass elif osc_address == "/merle/ekg": plot_data2.append(value) plot_data2.popleft() try: plotItem2.setData(y=np.array( scale_data(plot_data2, actors.index(plotItem2), len(actors))), clear=True) except ValueError: pass elif osc_address == "/uwe/ekg": plot_data3.append(value) plot_data3.popleft() try: plotItem3.setData(y=np.array( scale_data(plot_data3, actors.index(plotItem3), len(actors))), clear=True) except ValueError: pass elif osc_address == "/plot/uwe": if value == 1 and is_item3 == False: print "uwe on" plt.addItem(plotItem3) is_item3 = True actors.append(plotItem3) setPositions() elif value == 0 and is_item3 == True: print "uwe off" plt.removeItem(plotItem3) is_item3 = False actors.remove(plotItem3) setPositions() elif osc_address == "/plot/merle": if value == 1 and is_item2 == False: print "merle on" plt.addItem(plotItem2) is_item2 = True actors.append(plotItem2) setPositions() elif value == 0 and is_item2 == True: print "merle off" plt.removeItem(plotItem2) is_item2 = False actors.remove(plotItem2) setPositions() elif osc_address == "/plot/bjoern": if value == 1 and is_item1 == False: print "bjoern on" plt.addItem(plotItem1) is_item1 = True actors.append(plotItem1) setPositions() elif value == 0 and is_item1 == True: print "bjoern off" plt.removeItem(plotItem1) is_item1 = False actors.remove(plotItem1) setPositions() exporter = pg.exporters.ImageExporter.ImageExporter( plt.plotItem) img = exporter.export("tmpfile", True) buffer = QBuffer() buffer.open(QIODevice.WriteOnly) img.save(buffer, "JPG", 100) JpegData = buffer.data() del buffer self.wfile.write( "--aaboundary\r\nContent-Type: image/jpeg\r\nContent-length: %d\r\n\r\n%s\r\n\r\n\r\n" % (len(JpegData), JpegData)) elif self.path.endswith(".jpeg"): f = open(curdir + sep + self.path) self.send_response(200) self.send_header('Content-type', 'image/jpeg') self.end_headers() self.wfile.write(f.read()) f.close() return except (KeyboardInterrupt, SystemError): thread.running = False thread.join() except IOError: self.send_error(404, 'File Not Found: %s' % self.path)
class EkgPlotWidget(QMainWindow): def __init__(self, args, parent=None): self.args = args QMainWindow.__init__(self, parent) self.mcount = 0 self.osc_sock = QUdpSocket(self) logger.info("osc bind localhost %d", self.args.client_port) self.osc_sock.bind(QHostAddress(self.args.client_host), self.args.client_port) self.osc_sock.readyRead.connect(self.got_message) self.osc_sock.error.connect(self.handle_osc_error) self.subscribe() self.plot_widget = PlotWidget() self.setCentralWidget(self.plot_widget) self.resize(args.client_width, args.client_height) colors = ["r", "g", "b"] self.active_actors = list() self.actors = dict() self.max_value = 255 actor_names = ["merle", "uwe", "bjoern"] self.max_actors = len(actor_names) self.actor_height = self.max_value / self.max_actors self.fps = 12.5 self.num_data = 100 self.plot_widget.showGrid(False, False) self.plot_widget.setYRange(0, 255) self.plot_widget.setXRange(0, self.num_data) self.plot_widget.resize(args.client_width, args.client_height) bottom_axis = self.plot_widget.getAxis("bottom") left_axis = self.plot_widget.getAxis("left") bottom_axis.setTicks([]) left_axis.setTicks([]) bottom_axis.hide() left_axis.hide() for ix, (actor_name, color) in enumerate(zip(actor_names, colors)): self.add_actor(actor_name, self.num_data, color, ix, self.max_actors, self.actor_height) self.set_positions() self.ekg_regex = re.compile("^/(.*?)/ekg$") self.heartbeat_regex = re.compile("^/(.*?)/heartbeat$") self.timer = QtCore.QTimer() self.timer.timeout.connect(self.render_image) self.timer.start(50) def subscribe(self): logger.info("subscribe") msg = OSCMessage("/subscribe") msg.appendTypedArg(self.args.client_host, "s") msg.appendTypedArg(self.args.client_port, "i") msg.appendTypedArg(self.args.authenticate, "s") if self.args.subscriber_label is not None: msg.appendTypedArg(self.args.subscriber_label, "s") self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress(self.args.chaosc_host), self.args.chaosc_port) def unsubscribe(self): logger.info("unsubscribe") msg = OSCMessage("/unsubscribe") msg.appendTypedArg(self.args.client_host, "s") msg.appendTypedArg(self.args.client_port, "i") msg.appendTypedArg(self.args.authenticate, "s") self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress(self.args.chaosc_host), self.args.chaosc_port) def handle_osc_error(self, error): logger.info("osc socket error %d", error) def closeEvent(self, event): logger.info("closeEvent %r", event) self.unsubscribe() event.accept() def pubdir(self): return os.path.dirname(os.path.abspath(__file__)) def add_actor(self, actor_name, num_data, color, ix, max_actors, actor_height): actor_obj = Actor(actor_name, num_data, color, ix, max_actors, actor_height) self.actors[actor_name] = actor_obj self.plot_widget.addItem(actor_obj.plotItem) self.plot_widget.addItem(actor_obj.plotPoint) self.active_actors.append(actor_obj) def set_positions(self): for ix, actor_obj in enumerate(self.active_actors): actor_obj.plotItem.setPos(0, ix * 2) actor_obj.plotPoint.setPos(0, ix * 2) def active_actor_count(self): return self.max_actors def update(self, osc_address, args): res = self.ekg_regex.match(osc_address) if res: self.mcount += 1 actor_name = res.group(1) actor_obj = self.actors[actor_name] actor_obj.add_value(args[0]) logger.info("actor: %r, %r", actor_name, args) def render_image(self): for actor_obj in self.active_actors: actor_obj.add_value(actor_obj.osci.next()) actor_obj.render() @QtCore.pyqtSlot() def render_image(self): for actor_obj in self.active_actors: actor_obj.render() print self.mcount def got_message(self): while self.osc_sock.hasPendingDatagrams(): data, address, port = self.osc_sock.readDatagram(self.osc_sock.pendingDatagramSize()) try: osc_address, typetags, args = decode_osc(data, 0, len(data)) self.update(osc_address, args) except ValueError, error: logger.exception(error)