Example #1
0
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)
Example #2
0
    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)
Example #3
0
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)