diff --git a/ekgplotter/ekgplotter/analyze_test.py b/ekgplotter/ekgplotter/analyze_test.py new file mode 100644 index 0000000..1fe3255 --- /dev/null +++ b/ekgplotter/ekgplotter/analyze_test.py @@ -0,0 +1,106 @@ +data = [20, 14, 9, 9, 37, 42, 33, 31, 20, 42, 255, 47, 29, 34, 8, 16, 4, 5, 10, 18, 7, 10, 19, 4, 12, 11, 29, 46, 48, + 44, 25, 27, 255, 46, 48, 28, 13, 13, 13, 10, 17, 16, 20, 12, 8, 13, 0, 2, 40, 45, 37, 37, 39, 29, 255, 21, 25, 23, + 18, 8, 14, 3, 16, 19, 2, 6, 20, 13, 5, 5, 37, 36, 38, 34, 21, 27, 255, 35, 31, 48, 11, 17, 1, 17, 14, 12, 20, 0, 6, + 17, 1, 9, 27, 46, 21, 50, 44, 26, 255, 50, 38, 36, 35, 17, 20, 4, 18, 20, 6, 16, 20, 12, 10, 20, 8, 20, 20, 45, 40, + 21, 37, 38, 255, 35, 44, 29, 17, 7, 6, 14, 6, 13, 16, 9, 8, 10, 7, 13, 49, 43, 41, 49, 40, 50, 49, 255, 29, 42, 36, + 7, 5, 10, 20, 0, 19, 6, 18, 18, 13, 16, 3, 50, 40, 46, 26, 45, 49, 36, 255, 24, 39, 36, 2, 7, 3, 1, 1, 15, 12, 6, + 6, 5, 0, 7, 32, 43, 30, 50, 39, 50, 47, 255, 23, 41, 24, 6, 6, 1, 11, 9, 16, 5, 6, 3, 11, 7, 17, 12, 28, 47, 31, + 23, 31, 37, 32, 255, 50, 21, 22, 50, 1, 2, 1, 6, 12, 15, 9, 2, 4, 10, 14, 13, 46, 35, 39, 44, 39, 42, 29, 255, 20, + 45, 38, 29, 13, 20, 7, 13, 12, 12, 3, 12, 10, 14, 15, 20, 26, 30, 44, 27, 48, 46, 21, 255, 39, 40, 38, 22, 18, 3, 20, + 20, 4, 14, 11, 14, 10, 0, 12, 0, 25, 25, 28, 24, 44, 22, 31, 255, 29, 25, 39, 28, 7, 1, 6, 7, 7, 11, 15, 7, 10, 17, + 0, 0, 27, 23, 41, 21, 21, 41, 45, 45, 255, 27, 36, 46, 10, 13, 14, 8, 3, 15, 14, 4, 15, 12, 11, 2, 14, 35, 42, 50, 26, + 37, 30, 35, 38, 255, 34, 47, 33, 20, 3, 2, 2, 14, 11, 11, 7, 3, 15, 18, 9, 13, 27, 30, 27, 47, 34, 22, 38, 20, 255, + 22, 46, 50, 3, 9, 18, 19, 11, 19, 6, 2, 4, 0, 14, 13, 5, 39, 40, 48, 43, 33, 25, 43, 20, 255, 45, 42, 47, 11, 4, 0, + 1, 6, 8, 15, 16, 18, 1, 11, 0, 6, 5, 38, 47, 32, 23, 40, 37, 39, 255, 26, 30, 36, 37, 7, 5, 7, 11, 8, 13, 12, 12, 17, + 12, 1, 11, 8, 14, 44, 25, 31, 41, 50, 39, 46, 255, 49, 39, 44, 23, 4, 5, 14, 15, 1, 13, 7, 9, 7, 5, 4, 7, 16, 2, 41, + 30, 37, 36, 34, 23, 32, 255, 49, 32, 33, 41, 2, 13, 17, 8, 19, 18, 15, 4, 12, 18, 6, 19, 10, 8, 38, 31, 36, 34, 44, + 50, 20, 255, 39, 39, 24, 30, 11, 5, 12, 16, 20, 18, 2, 8, 17, 14, 24, 7, 10, 13, 6, 27, 41, 31, 46, 29, 46, 39, 28, + 255, 43, 28, 38, 49, 11, 11, 19, 1, 12, 14, 0, 18, 4, 21, 4, 16, 11, 16, 42, 44, 49, 23, 38, 31, 35, 47, 255, 30, 23, + 23, 45, 1, 10, 3, 8, 16, 16, 6, 17, 10, 24, 3, 19, 11, 3, 24, 27, 42, 45, 27, 46, 26, 41, 255, 31, 44, 39, 49, 10, + 4, 20, 3, 9, 15, 7, 15, 5, 19, 4, 10, 9, 13, 30, 29, 47, 21, 24, 36, 45, 20, 255, 33, 45, 25, 24, 2, 9, 19, 2, 20, + 17, 19, 10, 20, 12, 10, 19, 20, 9, 20, 30, 38, 34, 22, 46, 26, 24, 26, 255, 23, 26, 29, 37, 0, 10, 9, 20, 14, 19, + 2, 0, 1, 4, 12, 19, 6, 17, 17, 46, 47, 42, 34, 32, 29, 40, 47, 255, 27, 30, 25, 36, 14, 3, 11, 20, 4, 0, 1, 9, 19, + 15, 13, 2, 19, 1, 7, 42, 20, 25, 46, 30, 49, 39, 28, 255, 34, 43, 24, 34, 6, 1, 18, 9, 8, 11, 11, 17, 11, 6, 26, + 7, 3, 1, 14, 49, 41, 37, 27, 34, 22, 47, 20, 24, 255, 36, 34, 20, 26, 13, 7, 18, 10, 6, 9, 4, 0, 14, 14, 27, 5, + 13, 0, 7, 39, 21, 42, 34, 36, 45, 24, 28, 43, 255, 20, 26, 36, 31, 8, 8, 0, 0, 14, 0, 1, 8, 15, 1, 19, 13, 15, + 17, 9, 44, 38, 33, 21, 37, 48, 32, 30, 34, 255, 32, 32, 33, 40, 19, 6, 5, 2, 7, 3, 10, 19, 10, 12, 14, 16, 15, + 14, 7, 29, 29, 50, 31, 26, 36, 31, 22, 32, 255, 48, 45, 42, 31, 50, 13, 9, 5, 16, 7, 12, 12, 4, 6, 1, 24, 14, 12, + 6, 3, 25, 27, 50, 40, 25, 35, 37, 25, 47, 255, 30, 29, 45, 36, 34, 0, 13, 20, 13, 18, 11, 16, 16, 0, 9, 23, 8, + 7, 4, 18, 41, 50, 20, 45, 48, 43, 31, 36, 30, 255, 37, 30, 22, 38, 39, 10, 16, 9, 7, 13, 18, 1, 5, 14, 20, 21, + 13, 19, 15, 13, 34, 26, 21, 45, 35, 44, 39, 23, 21, 255, 27, 47, 45, 41, 39, 9, 11, 12, 10, 13, 4, 8, 15, 4, + 19, 3, 1, 15, 9, 5, 2, 8, 33, 38, 35, 26, 48, 42, 25, 50, 23, 255, 24, 26, 29, 20, 32, 12, 1, 9, 10, 12, 6, + 14, 6, 14, 1, 19, 14, 20, 2, 8, 4, 31, 43, 27, 24, 33, 33, 27, 23, 21, 255, 41, 37, 23, 28, 33, 2, 17, 8, 9, + 16, 10, 17, 11, 4, 4, 4, 17, 19, 13, 3, 0, 20, 36, 31, 50, 48, 31, 43, 36, 34, 255, 31, 41, 29, 47, 21, 18, + 15, 11, 20, 14, 14, 11, 4, 12, 2, 12] + + +from collections import deque + +data_points = 20 +item_data1 = deque([0] * data_points) + +pos1 = 0 + +lengths = [0] + +item_point1 = 0 + +def set_point(plotPoint, pos, value, ix, max_items): + scale = 254 / max_items * ix + return 6 * ix + value / max_items + scale + + +def find_max_value(item_data): + max_value = 0 + max_index = 0 + for ix, i in enumerate(item_data): + if i > max_value: + max_value = i + max_index = ix + return max_index, max_value + + +def rearrange(item_data, actual_pos, max_items): + max_value_index, max_value = find_max_value(item_data) + mean = int(max_items / 2.) + start = mean - max_value_index + if start != 0: + item_data.rotate(start) + pos = (actual_pos + start) % max_items + else: + pos = actual_pos + print "rearrange", mean, start, actual_pos, pos, item_data + return pos + + +def set_value(item_data, pos, max_pos, value): + print "setValue before", pos, None, max_pos, value, item_data + item_data[pos] = value + new_pos = (pos + 1) % max_pos + print "setValue after ", pos, new_pos, max_pos, value, item_data + return new_pos + +def resize(item_data, max_length, new_max_length): + if new_max_length > max_length: + pad = (new_max_length - max_length) + print "pad", pad + item_data.extend([0] * pad) + return new_max_length + else: + return max_length + +for value in data: + if value > 250: + data_points = resize(item_data1, data_points, lengths[-1]) + lengths.append(0) + else: + lengths[-1] += 1 + + ix = 0 + set_point(None, pos1, value, ix, 3) + pos1 = set_value(item_data1, pos1, data_points, value) + pos1 = rearrange(item_data1, pos1, data_points) + + print + +print "lengths", lengths diff --git a/ekgplotter/ekgplotter/ekg_plotter_qt_only.py b/ekgplotter/ekgplotter/ekg_plotter_qt_only.py new file mode 100644 index 0000000..a370410 --- /dev/null +++ b/ekgplotter/ekgplotter/ekg_plotter_qt_only.py @@ -0,0 +1,266 @@ +# -*- coding: utf-8 -*- +""" +Demonstrates use of PlotWidget class. This is little more than a +GraphicsView with a PlotItem placed in its center. +""" + + + +from collections import deque + +from PyQt4 import QtNetwork +from pyqtgraph.Qt import QtGui, QtCore +import numpy as np +import pyqtgraph as pg + +import numpy as np + + +try: + from chaosc.c_osc_lib import decode_osc +except ImportError as e: + print(e) + from chaosc.osc_lib import decode_osc + + + +class OSCPlotter(QtGui.QWidget): + def __init__(self, parent=None): + super(OSCPlotter, self).__init__(parent) + self.plot_data1 = deque([0] * 100) + self.plot_data2 = deque([254 * 0.3] * 100) + self.plot_data3 = deque([254 * 0.6] * 100) + self.is_item1 = True + self.is_item2 = True + self.is_item3 = True + self.osc_sock = QtNetwork.QUdpSocket(self) + self.osc_sock.bind(10000) + self.osc_sock.readyRead.connect(self.receive_osc) + self.tcpServer = QtNetwork.QTcpServer(self) + self.tcpServer.listen(QtNetwork.QHostAddress("0.0.0.0"), 9000) + self.tcpServer.newConnection.connect(self.addConnection) + self.connections = [] + self.streams = [] + self.streaming_timer = QtCore.QTimer(self) + self.streaming_timer.timeout.connect(self.sendMJpeg) + self.streaming_timer.start(20) + + + def addConnection(self): + clientConnection = self.tcpServer.nextPendingConnection() + #clientConnection.nextBlockSize = 0 + self.connections.append(clientConnection) + + clientConnection.readyRead.connect(self.receiveMessage) + clientConnection.disconnected.connect(self.removeConnection) + clientConnection.error.connect(self.socketError) + + def removeConnection(self): + pass + + def socketError(self): + pass + + def receiveMessage(self): + for ix, s in enumerate(self.connections): + if s.bytesAvailable() > 0: + stream = QtCore.QDataStream(s) + stream.setVersion(QtCore.QDataStream.Qt_4_8) + + #if s.nextBlockSize == 0: + #if s.bytesAvailable() < 4: + #return + #s.nextBlockSize = stream.readUInt32() + #if s.bytesAvailable() < s.nextBlockSize: + #return + + textFromClient = stream.readRawData(s.bytesAvailable()) + print "data", repr(textFromClient) + socketId = s.socketDescriptor() + if textFromClient.startswith("GET /index.html"): + self.sendMessage("HTTP/1.1 200 Ok\r\nContent-Language: en\r\nContent-type: text/html; charset=\"utf-8\"\r\n\r\n" + open("index.html").read() + "\r\n\r\n", + s.socketDescriptor()) + elif textFromClient.startswith("GET /camera.jpg"): + self.sendImage(socketId) + elif textFromClient.startswith("GET /camera.mjpeg"): + self.prepareMJpeg(socketId) + #self.sendImage(s.socketDescriptor()) + else: + self.sendMessage("HTTP/1.1 404 Not Found\r\n\r\n", s.socketDescriptor()) + + + def sendMessage(self, text, socketId): + for s in self.connections: + if s.socketDescriptor() == socketId: + print "sendMessage" + + reply = QtCore.QByteArray() + stream = QtCore.QDataStream(reply, QtCore.QIODevice.WriteOnly) + #stream.setVersion(QtCore.QDataStream.Qt_4_8) + stream.writeString(text) + + s.write(reply) + s.close() + + + def prepareMJpeg(self, socketId): + for s in self.connections: + if s.socketDescriptor() == socketId: + self.streams.append(socketId) + reply = QtCore.QByteArray() + stream = QtCore.QDataStream(reply, QtCore.QIODevice.WriteOnly) + stream.setVersion(QtCore.QDataStream.Qt_4_8) + stream.writeRawData("HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=--aaboundary\r\n\r\n") + s.write(reply) + #s.close() + + + def sendMJpeg(self): + reply = QtCore.QByteArray() + stream = QtCore.QDataStream(reply, QtCore.QIODevice.WriteOnly) + stream.setVersion(QtCore.QDataStream.Qt_4_8) + stream.writeRawData("--aaboundary\r\nContent-Type: image/jpeg\r\nContent-length: %d\r\n\r\n%s\r\n\r\n\r\n" % (len(self.jpeg_data), self.jpeg_data)) + for socketId in self.streams: + for connection in self.connections: + if connection.socketDescriptor() == socketId: + connection.write(reply) + #connection.close() + + + def sendImage(self, socketId): + for connection in self.connections: + if connection.socketDescriptor() == socketId: + reply = QtCore.QByteArray() + stream = QtCore.QDataStream(reply, QtCore.QIODevice.WriteOnly) + stream.setVersion(QtCore.QDataStream.Qt_4_8) + + #tmp = open("camera.jpg", "rb").read() + header = QtCore.QString("HTTP/1.1 200 OK\r\nContent-Type: image/jpeg\r\nContent-length: %d\r\n\r\n" % len(self.jpeg_data)) + stream.writeRawData(header) + stream.writeRawData(self.jpeg_data) + stream.writeRawData("\r\n\r\n\r\n") + connection.write(reply) + connection.close() + + + def init(self): + self.plt = pg.PlotWidget(name='EKG') ## giving the plots names allows us to link their axes together + #self.legend = self.plt.addLegend() + + l = QtGui.QVBoxLayout() + self.statusLabel = QtGui.QLabel("Listening for broadcasted messages") + self.setLayout(l) + l.addWidget(self.plt) + l.addWidget(self.statusLabel) + + self.plotItem1 = pg.PlotCurveItem(pen=pg.mkPen('r', width=4), name="bjoern") + self.plotItem2 = pg.PlotCurveItem(pen=pg.mkPen('g', width=4), name="merle") + self.plotItem3 = pg.PlotCurveItem(pen=pg.mkPen('b', width=4), name="uwe") + #self.plotItem1.setPos(0, 0*6) + #self.plotItem2.setPos(0, 1*6) + #self.plotItem3.setPos(0, 2*6) + self.plt.addItem(self.plotItem1) + self.plt.addItem(self.plotItem2) + self.plt.addItem(self.plotItem3) + self.plt.setLabel('left', "EKG") + self.plt.setLabel('bottom', "Time") + self.plt.showGrid(True, True) + ba = self.plt.getAxis("bottom") + bl = self.plt.getAxis("left") + ba.setTicks([]) + bl.setTicks([]) + self.plt.setYRange(0, 254) + self.plotItem1.setData(y=np.array(self.plot_data1), clear=True) + self.plotItem2.setData(y=np.array(self.plot_data2), clear=True) + self.plotItem3.setData(y=np.array(self.plot_data3), clear=True) + #self.plotItem1.setShadowPen(pg.mkPen((200, 200, 200), width=6, cosmetic=True)) + #self.plotItem2.setShadowPen(pg.mkPen((200, 200, 200), width=6, cosmetic=True)) + #self.plotItem3.setShadowPen(pg.mkPen((200, 200, 200), width=6, cosmetic=True)) + + + + def receive_osc(self): + #print "receive_osc" + while self.osc_sock.hasPendingDatagrams(): + packet, address, port = self.osc_sock.readDatagram(self.osc_sock.pendingDatagramSize()) + osc_address, typetags, args = decode_osc(packet, 0, len(packet)) + + #print "osc", osc_address, args[0] + self.statusLabel.setText(osc_address) + update = False + if osc_address == "/bjoern/ekg": + self.plot_data1.appendleft(args[0] / 3) + self.plot_data1.pop() + update = True + elif osc_address == "/merle/ekg": + self.plot_data2.appendleft(args[0] / 3 + 254/3) + self.plot_data2.pop() + update = True + elif osc_address == "/uwe/ekg": + self.plot_data3.appendleft(args[0] /3 + 254/3*2) + self.plot_data3.pop() + update = True + #elif osc_address == "/plot/uwe": + #if args[0] == 1 and self.is_item3 == False: + #self.plt.addItem(self.plotItem3) + #self.is_item3 = True + ##self.legend.addItem(self.plotItem3, "uwe") + #elif args[0] == 0 and self.is_item3 == True: + #self.plt.removeItem(self.plotItem3) + #self.is_item3 = False + ##self.legend.removeItem("uwe") + #elif osc_address == "/plot/merle": + #if args[0] == 1 and self.is_item2 == False: + #self.plt.addItem(self.plotItem2) + #self.is_item2 = True + ##self.legend.addItem(self.plotItem2, "merle") + #elif args[0] == 0 and self.is_item2 == True: + #self.plt.removeItem(self.plotItem2) + #self.is_item2 = True + ##self.legend.removeItem("merle") + #elif osc_address == "/plot/bjoern": + #if args[0] == 1 and self.is_item1 == False: + #self.plt.addItem(self.plotItem1) + #self.is_item1 = True + ##self.legend.addItem(self.plotItem1, name="bjoern") + #elif args[0] == 0 and self.is_item1 == True: + #self.plt.removeItem(self.plotItem1) + #self.is_item1 = False + ##self.legend.removeItem("bjoern") + + if update: + self.plotItem1.setData(y=np.array(self.plot_data1), clear=True) + self.plotItem2.setData(y=np.array(self.plot_data2), clear=True) + self.plotItem3.setData(y=np.array(self.plot_data3), clear=True) + #item = plt.plot(plot_data1, pen=(0, 3*1.3), clear=True) + + exporter = pg.exporters.ImageExporter.ImageExporter(self.plt.plotItem) + exporter.parameters()['width'] = 1280 + #exporter.parameters()['height'] = 720 + name = 'tmpfile' + img = exporter.export(name, True) + buffer = QtCore.QBuffer() + buffer.open(QtCore.QIODevice.ReadWrite) + img.save(buffer, "JPG", 100) + self.jpeg_data = buffer.data() + + + +#QtGui.QApplication.setGraphicsSystem('raster') +app = QtGui.QApplication([]) +mw = QtGui.QMainWindow() +mw.setWindowTitle('pyqtgraph example: PlotWidget') +mw.resize(1280, 720) +cw = OSCPlotter() +cw.init() +mw.setCentralWidget(cw) + +mw.show() + + + +## Start Qt event loop unless running in interactive mode or using pyside. +if __name__ == '__main__': + import sys + if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): + QtGui.QApplication.instance().exec_() diff --git a/ekgplotter/ekgplotter/main.py b/ekgplotter/ekgplotter/main.py index a6e949b..757ee6f 100644 --- a/ekgplotter/ekgplotter/main.py +++ b/ekgplotter/ekgplotter/main.py @@ -55,10 +55,10 @@ from pyqtgraph.widgets.PlotWidget import PlotWidget from chaosc.argparser_groups import * from chaosc.lib import resolve_host -#try: - #from chaosc.c_osc_lib import * -#except ImportError: -from chaosc.osc_lib import * +try: + from chaosc.c_osc_lib import * +except ImportError: + from chaosc.osc_lib import * try: @@ -69,16 +69,18 @@ except ImportError as e: + class OSCThread(threading.Thread): def __init__(self, args): super(OSCThread, self).__init__() self.args = args self.running = True - self.own_address = socket.getaddrinfo(args.own_host, args.own_port, socket.AF_INET6, socket.SOCK_DGRAM, 0, socket.AI_V4MAPPED | socket.AI_ALL | socket.AI_CANONNAME)[-1][4][:2] - self.chaosc_address = chaosc_host, chaosc_port = socket.getaddrinfo(args.chaosc_host, args.chaosc_port, socket.AF_INET6, socket.SOCK_DGRAM, 0, socket.AI_V4MAPPED | socket.AI_ALL | socket.AI_CANONNAME)[-1][4][:2] + self.own_address = resolve_host(args.own_host, args.own_port, args.address_family) - self.osc_sock = socket.socket(10, 2, 17) + self.chaosc_address = chaosc_host, chaosc_port = resolve_host(args.chaosc_host, args.chaosc_port, args.address_family) + + self.osc_sock = socket.socket(args.address_family, 2, 17) self.osc_sock.bind(self.own_address) self.osc_sock.setblocking(0) @@ -125,10 +127,11 @@ class OSCThread(threading.Thread): while self.running: reads, writes, errs = select.select([self.osc_sock], [], [], 0.05) if reads: - osc_input = reads[0].recv(4096) + osc_input = reads[0].recv(128) osc_address, typetags, messages = decode_osc(osc_input, 0, len(osc_input)) #print("thread osc_address", osc_address) if osc_address.find(b"ekg") > -1 or osc_address.find(b"plot") != -1: + #print("send", osc_address) msg_queue.put_nowait((osc_address, messages)) else: msg_queue.put_nowait((b"/bjoern/ekg", [0])) @@ -220,7 +223,6 @@ class Actor(object): class EkgPlot(object): def __init__(self, actor_names, num_data, colors): - self.qtapp = QtGui.QApplication([]) self.plot = pg.PlotWidget(title="

EKG

") self.plot.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.plot.hide() @@ -269,34 +271,50 @@ class EkgPlot(object): def update(self, osc_address, value): + print("update"), osc_address res = self.ekg_regex.match(osc_address) if res: + #print("matched data") actor_name = res.group(1) actor_obj = self.actors[actor_name] max_actors = len(self.active_actors) - ix = self.active_actors.index(actor_obj) actor_data = actor_obj.data data_pointer = actor_obj.data_pointer actor_data[data_pointer] = value - actor_obj.set_point(value, ix, max_actors) + try: + ix = self.active_actors.index(actor_obj) + actor_obj.set_point(value, ix, max_actors) + actor_obj.plotItem.setData(y=np.array(actor_obj.scale_data(ix, max_actors)), clear=True) + except ValueError as e: + #print("data", e) + pass actor_obj.data_pointer = (data_pointer + 1) % self.num_data - actor_obj.plotItem.setData(y=np.array(actor_obj.scale_data(ix, max_actors)), clear=True) return res = self.ctl_regex.match(osc_address) if res: actor_name = res.group(1) actor_obj = self.actors[actor_name] + #print("matched ctl", value, actor_name, actor_obj.active) if value == 1 and not actor_obj.active: - print("actor on", actor_name) - self.plot.addItem(actor_obj) + #print("actor on", actor_name, actor_obj, self.active_actors) actor_obj.active = True - self.active_actors.append(actor_obj) - elif value == 0 and not actor_obj.active: - print("actor off", actor_name) - self.plot.removeItem(actor_obj) - actor_obj.active = True - self.active_actors.remove(actor_obj) + if actor_obj not in self.active_actors: + self.plot.addItem(actor_obj.plotItem) + self.plot.addItem(actor_obj.plotPoint) + self.active_actors.append(actor_obj) + assert actor_obj in self.active_actors + elif value == 0 and actor_obj.active: + #print("actor off", actor_name, actor_obj, self.active_actors) + actor_obj.active = False + self.plot.removeItem(actor_obj.plotItem) + self.plot.removeItem(actor_obj.plotPoint) + try: + self.active_actors.remove(actor_obj) + except ValueError as e: + #print("ctl", e) + pass + assert actor_obj not in self.active_actors self.set_positions() @@ -362,9 +380,7 @@ class MyHandler(BaseHTTPRequestHandler): print("children", plotter.plot.children()) plotter.plot.deleteLater() plotter.plot.close() - self.plotter.qtapp.processEvents() del self.plotter.plot - del self.plotter.qtapp del self.plotter del plotter thread.running = False @@ -394,61 +410,49 @@ class MyHandler(BaseHTTPRequestHandler): self.wfile.write(data) return except (KeyboardInterrupt, SystemError): - print("queue size", queue.qsize()) + print("queue size", msg_queue.qsize()) thread.running = False thread.join() if hasattr(self, "plotter"): plotter.plot.deleteLater() - except IOError: + except IOError as e: + print("ioerror", e) + print('-'*40) + print('Exception happened during processing of request from') + import traceback + traceback.print_exc() # XXX But this goes to stderr! + print( '-'*40) self.send_error(404,'File Not Found: %s' % self.path) class JustAHTTPServer(HTTPServer): - address_family = socket.AF_INET6 pass def main(): - a = create_arg_parser("ekgplotter") - own_group = add_main_group(a) + arg_parser = create_arg_parser("ekgplotter") + own_group = add_main_group(arg_parser) own_group.add_argument('-x', "--http_host", default="::", help='my host, defaults to "socket.gethostname()"') own_group.add_argument('-X', "--http_port", default=9000, type=int, help='my port, defaults to 9000') - add_chaosc_group(a) - add_subscriber_group(a, "ekgplotter") - args = finalize_arg_parser(a) + add_chaosc_group(arg_parser) + add_subscriber_group(arg_parser, "ekgplotter") + args = finalize_arg_parser(arg_parser) + qtapp = QtGui.QApplication([]) - http_host, http_port = resolve_host(args.http_host, args.http_port) - print(http_host, http_port) + http_host, http_port = resolve_host(args.http_host, args.http_port, args.address_family) server = JustAHTTPServer((http_host, http_port), MyHandler) + server.address_family = args.address_family server.args = args print("%s: starting up http server on '%s:%d'" % ( datetime.now().strftime("%x %X"), http_host, http_port)) - print("before start:") - #objgraph.show_growth() try: server.serve_forever() except KeyboardInterrupt: - print('^C received, shutting down server') - print() - #print(gc.garbage) - #print() - #print "queue size", queue.qsize() - #print "show growth", objgraph.show_growth() - #import random - #objgraph.show_chain( - #objgraph.find_backref_chain( - #random.choice(objgraph.by_type('function')), - #objgraph.is_proper_module), - #filename='chain.png') - #roots = objgraph.get_leaking_objects() - #print "root", len(roots) - #objgraph.show_most_common_types(objects=roots) - #objgraph.show_refs(roots[:3], refcounts=True, filename='roots.png') server.socket.close() diff --git a/ekgplotter/ekgplotter/main_old.py b/ekgplotter/ekgplotter/main_old.py new file mode 100644 index 0000000..feeae76 --- /dev/null +++ b/ekgplotter/ekgplotter/main_old.py @@ -0,0 +1,217 @@ +import numpy as np +import string,cgi,time, random, socket +from os import curdir, sep +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from SocketServer import ThreadingMixIn, ForkingMixIn +import select +import re + +from collections import deque + +from PyQt4.QtCore import QBuffer, QByteArray, QIODevice, QThread, QObject +from PyQt4 import QtGui +from PyQt4 import QtCore +import pyqtgraph as pg + +from pyqtgraph.widgets.PlotWidget import PlotWidget + + +try: + from chaosc.c_osc_lib import decode_osc +except ImportError as e: + print(e) + from chaosc.osc_lib import decode_osc + +QAPP = None + +def mkQApp(): + if QtGui.QApplication.instance() is None: + global QAPP + QAPP = QtGui.QApplication([]) + + +class PlotWindow(PlotWidget): + def __init__(self, title=None, **kargs): + mkQApp() + self.win = QtGui.QMainWindow() + PlotWidget.__init__(self, **kargs) + self.win.setCentralWidget(self) + for m in ['resize']: + setattr(self, m, getattr(self.win, m)) + if title is not None: + self.win.setWindowTitle(title) + + +class WorkThread(QThread): + osc_received = QtCore.pyqtSignal(str, int, name='osc_received') + + def __init__(self): + QThread.__init__(self) + self.osc_sock = socket.socket(2, 2, 17) + self.osc_sock.setblocking(0) + self.osc_sock.setsockopt(socket.SOL_SOCKET, + socket.SO_RCVBUF, 4096 * 8) + self.osc_sock.bind(("", 10000)) + + + def __del__(self): + self.wait() + + def run(self): + while 1: + reads, writes, errs = select.select([self.osc_sock], [], [], 0.01) + if reads: + osc_input = reads[0].recv(4096) + osc_address, typetags, args = decode_osc(osc_input, 0, len(osc_input)) + print "signal", osc_address, args[0] + self.osc_received.emit(osc_address, args[0]) + + +class MyHandler(QtCore.QObject, BaseHTTPRequestHandler): + + def __init__(self, request, client_address, parent): + self.plot_data1 = deque([0] * 100) + self.plot_data2 = deque([254/3] * 100) + self.plot_data3 = deque([254/3*2] * 100) + self.is_item1 = True + self.is_item2 = True + self.is_item3 = True + QtCore.QObject.__init__(self) + BaseHTTPRequestHandler.__init__(self, request, client_address, parent) + + @QtCore.pyqtSlot('QString', int, name="receive_osc") + def receive_osc(self, osc_address, value): + print "change", osc_address, value + if osc_address == "/bjoern/ekg": + self.plot_data1.appendleft(args[0] / 3) + self.plot_data1.pop() + elif osc_address == "/merle/ekg": + self.plot_data2.appendleft(args[0] / 3 + 254/3) + self.plot_data2.pop() + elif osc_address == "/uwe/ekg": + self.plot_data3.appendleft(args[0] / 3 + 254/3*2) + self.plot_data3.pop() + elif osc_address == "/plot/uwe": + if value == 1 and self.is_item3 == False: + self.plt.addItem(self.plotItem3) + elif value == 0 and self.is_item3 == True: + self.plt.removeItem(self.plotItem3) + elif osc_address == "/plot/merle": + if value == 1 and self.is_item2 == False: + self.plt.addItem(self.plotItem2) + elif value == 0 and self.is_item2 == True: + self.plt.removeItem(self.plotItem2) + elif osc_address == "/plot/bjoern": + if value == 1 and self.is_item1 == False: + self.plt.addItem(self.plotItem1) + elif value == 0 and self.is_item1 == True: + self.plt.removeItem(self.plotItem1) + + def do_GET(self): + print "get" + global plotValues + 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() + return + if self.path.endswith(".mjpeg"): + self.thread = WorkThread() + #self.thread.osc_received.connect(self.change) + #self.connect(self.thread, thread.osc_received, self.change) + self.thread.osc_received.connect(self.receive_osc) + self.thread.start() + + self.send_response(200) + + self.plt = plt = PlotWindow(title="EKG", name="Merle") + plt.addLegend() + #plt = pg.plot(pen=(0, 3*1.3)) + plt.resize(1280, 720) + self.plotItem1 = plotItem1 = pg.PlotCurveItem(pen=pg.mkPen('r', width=4), name="bjoern") + self.plotItem2 = plotItem2 = pg.PlotCurveItem(pen=pg.mkPen('g', width=4), name="merle") + self.plotItem3 = plotItem3 = pg.PlotCurveItem(pen=pg.mkPen('b', width=4), name="uwe") + 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', "EKG") + plt.setLabel('bottom', "Time") + plt.showGrid(True, True) + ba = plt.getAxis("bottom") + bl = plt.getAxis("left") + ba.setTicks([]) + bl.setTicks([]) + plt.setYRange(0, 254) + #print type(plt) + self.wfile.write("Content-Type: multipart/x-mixed-replace; boundary=--aaboundary") + self.wfile.write("\r\n\r\n") + + last = time.time() + now = last + + while 1: + plotItem1.setData(y=np.array(self.plot_data1), clear=True) + plotItem2.setData(y=np.array(self.plot_data2), clear=True) + plotItem3.setData(y=np.array(self.plot_data3), clear=True) + #item = plt.plot(plot_data1, pen=(0, 3*1.3), clear=True) + + exporter = pg.exporters.ImageExporter.ImageExporter(plt.plotItem) + exporter.parameters()['width'] = 1280 + #exporter.parameters()['height'] = 720 + name = 'tmpfile' + img = exporter.export(name, True) + buffer = QBuffer() + buffer.open(QIODevice.ReadWrite) + img.save(buffer, "JPG", 100) + JpegData = buffer.data() + self.wfile.write("--aaboundary\r\n") + self.wfile.write("Content-Type: image/jpeg\r\n") + self.wfile.write("Content-length: %d\r\n\r\n" % len(JpegData)) + self.wfile.write(JpegData) + self.wfile.write("\r\n\r\n\r\n") + now = time.time() + dur = now - last + #print dur + wait = 0.04 - dur + if wait > 0: + time.sleep(wait) + last = now + return + if 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 + return + except IOError: + self.send_error(404,'File Not Found: %s' % self.path) + + +class ThreadedHTTPServer(HTTPServer, ForkingMixIn): + """Handle requests in a separate process.""" + +def main(): + try: + server = ThreadedHTTPServer(('0.0.0.0', 9000), MyHandler) + print 'started httpserver...' + server.serve_forever() + except KeyboardInterrupt: + print '^C received, shutting down server' + server.socket.close() + +if __name__ == '__main__': + main() diff --git a/ekgplotter/ekgplotter/main_workstations.py b/ekgplotter/ekgplotter/main_workstations.py new file mode 100644 index 0000000..a6c145f --- /dev/null +++ b/ekgplotter/ekgplotter/main_workstations.py @@ -0,0 +1,461 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# This file is part of sensors2osc package +# +# sensors2osc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# sensors2osc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with sensors2osc. If not, see . +# +# found the mjpeg part here, thanks for the nice code :) +# http://hardsoftlucid.wordpress.com/2013/04/11/mjpeg-server-for-webcam-in-python-with-opencv/ +# the osc integration stuff is implemented by me +# +# Copyright (C) 2014 Stefan Kögl + +from __future__ import absolute_import + +from datetime import datetime +import threading +import Queue +import numpy as np +import string,cgi,time, random, socket +from os import curdir, sep +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from SocketServer import ThreadingMixIn, ForkingMixIn +import select +import re + +from collections import deque + +from PyQt4.QtCore import QBuffer, QByteArray, QIODevice +from PyQt4 import QtGui +import pyqtgraph as pg + +from pyqtgraph.widgets.PlotWidget import PlotWidget + +from chaosc.argparser_groups import * + +try: + from chaosc.c_osc_lib import * +except ImportError: + from chaosc.osc_lib import * + +QtGui.QApplication.setGraphicsSystem('opengl') + + +try: + from chaosc.c_osc_lib import decode_osc +except ImportError as e: + print(e) + from chaosc.osc_lib import decode_osc + +QAPP = QtGui.QApplication([]) + + +class PlotWindow(PlotWidget): + def __init__(self, title=None, **kargs): + self.win = QtGui.QMainWindow() + PlotWidget.__init__(self, **kargs) + self.win.setCentralWidget(self) + for m in ['resize']: + setattr(self, m, getattr(self.win, m)) + if title is not None: + self.win.setWindowTitle(title) + + +class OSCThread(threading.Thread): + def __init__(self, args): + super(OSCThread, self).__init__() + self.args = args + self.running = True + self.own_address = socket.getaddrinfo(args.own_host, args.own_port, socket.AF_INET6, socket.SOCK_DGRAM, 0, socket.AI_V4MAPPED | socket.AI_ALL | socket.AI_CANONNAME)[-1][4][:2] + + self.chaosc_address = chaosc_host, chaosc_port = socket.getaddrinfo(args.chaosc_host, args.chaosc_port, socket.AF_INET6, socket.SOCK_DGRAM, 0, socket.AI_V4MAPPED | socket.AI_ALL | socket.AI_CANONNAME)[-1][4][:2] + + self.osc_sock = socket.socket(2, 2, 17) + self.osc_sock.bind(self.own_address) + self.osc_sock.setblocking(0) + + print "%s: starting up osc receiver on '%s:%d'" % ( + datetime.now().strftime("%x %X"), self.own_address[0], self.own_address[1]) + + self.subscribe_me() + + def subscribe_me(self): + """Use this procedure for a quick'n dirty subscription to your chaosc instance. + + :param chaosc_address: (chaosc_host, chaosc_port) + :type chaosc_address: tuple + + :param receiver_address: (host, port) + :type receiver_address: tuple + + :param token: token to get authorized for subscription + :type token: str + """ + print "%s: subscribing to '%s:%d' with label %r" % (datetime.now().strftime("%x %X"), self.chaosc_address[0], self.chaosc_address[1], self.args.subscriber_label) + msg = OSCMessage("/subscribe") + msg.appendTypedArg(self.own_address[0], "s") + msg.appendTypedArg(self.own_address[1], "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.sendto(msg.encode_osc(), self.chaosc_address) + + + def unsubscribe_me(self): + if self.args.keep_subscribed: + return + + print "%s: unsubscribing from '%s:%d'" % (datetime.now().strftime("%x %X"), self.chaosc_address[0], self.chaosc_address[1]) + msg = OSCMessage("/unsubscribe") + msg.appendTypedArg(self.own_address[0], "s") + msg.appendTypedArg(self.own_address[1], "i") + msg.appendTypedArg(self.args.authenticate, "s") + self.osc_sock.sendto(msg.encode_osc(), self.chaosc_address) + + def run(self): + + while self.running: + reads, writes, errs = select.select([self.osc_sock], [], [], 0.05) + if reads: + osc_input = reads[0].recv(4096) + osc_address, typetags, messages = decode_osc(osc_input, 0, len(osc_input)) + if osc_address.find("ekg") > -1 or osc_address.find("plot") != -1: + queue.put_nowait((osc_address, messages)) + else: + queue.put_nowait(("/bjoern/ekg", [0])) + queue.put_nowait(("/merle/ekg", [0])) + queue.put_nowait(("/uwe/ekg", [0])) + self.unsubscribe_me() + print "OSCThread is going down" + + +queue = Queue.Queue() + +class MyHandler(BaseHTTPRequestHandler): + + def __del__(self): + self.thread.running = False + self.thread.join() + + def do_GET(self): + print "get" + + self.thread = thread = OSCThread(self.server.args) + 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] + + + def set_point(plotPoint, pos, value, ix, max_items): + scale = 254 / max_items * ix + y = 6 * ix + value / max_items + scale + plotPoint.setData(x = [pos], y = [y]) + + + def find_max_value(item_data): + max_index = -1 + for ix, i in enumerate(item_data): + if i > 250: + return ix, i + return None, None + + + def rearrange(item_data, actual_pos, max_items): + max_value_index, max_value = find_max_value(item_data) + if max_value_index is None: + return actual_pos + mean = int(max_items / 2.) + start = mean - max_value_index + if start != 0: + item_data.rotate(start) + pos = (actual_pos + start) % max_items + else: + pos = actual_pos + #print "rearrange", mean, start, actual_pos, pos, item_data + return pos + + + def set_value(item_data, pos, max_pos, value): + #print "setValue before", pos, None, max_pos, value, item_data, len(item_data) + item_data[pos] = value + new_pos = (pos + 1) % max_pos + #print "setValue after ", pos, new_pos, max_pos, value, item_data, len(item_data) + return new_pos + + def resize(item_data, max_length, new_max_length, pos): + #print "resize", max_length, new_max_length + if new_max_length < 15: + return max_length, pos + + if new_max_length > max_length: + pad = (new_max_length - max_length) + #print "pad", pad + for i in range(pad): + if i % 2 == 0: + item_data.append(0) + else: + item_data.appendleft(0) + pos += 1 + return new_max_length, pos + elif new_max_length < max_length: + pad = (max_length - new_max_length) + for i in range(pad): + if i % 2 == 0: + item_data.pop() + if pos >= new_max_length: + pos = 0 + else: + item_data.popleft() + if pos > 0: + pos -= 1 + return new_max_length, pos + return max_length, pos + + 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 = 43 + + self.send_response(200) + pos1 = 0 + pos2 = 0 + pos3 = 0 + + lengths1 = [0] + + data1_max_value = 0 + data2_max_value = 0 + data3_max_value = 0 + + data1_distance = data_points + data2_distance = data_points + data3_distance = data_points + + plot_data1 = deque([0] * data_points) + plot_data2 = deque([0] * data_points) + plot_data3 = deque([0] * data_points) + plt = PlotWidget(title="

EKG

") + plt.hide() + plotItem1 = pg.PlotCurveItem(pen=pg.mkPen('r', width=1), width=1, name="bjoern") + plotItem2 = pg.PlotCurveItem(pen=pg.mkPen('g', width=1), width=1, name="merle") + plotItem3 = pg.PlotCurveItem(pen=pg.mkPen('b', width=1), width=1, name="uwe") + shadowPen = pg.mkPen("w", width=10) + plotItem1.setShadowPen(pen=shadowPen, width=3, cosmetic=True) + plotItem2.setShadowPen(pen=shadowPen, width=3, cosmetic=True) + plotItem3.setShadowPen(pen=shadowPen, width=3, cosmetic=True) + pen = pg.mkPen("w", size=1) + brush = pg.mkBrush("w") + plotPoint1 = pg.ScatterPlotItem(pen=pen, brush=brush, size=15) + plotPoint2 = pg.ScatterPlotItem(pen=pen, brush=brush, size=10) + plotPoint3 = pg.ScatterPlotItem(pen=pen, brush=brush, size=10) + 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.addItem(plotPoint1) + plt.addItem(plotPoint2) + plt.addItem(plotPoint3) + + plt.setLabel('left', "

Amplitude

") + plt.setLabel('bottom', "

Time

") + plt.showGrid(True, True) + ba = plt.getAxis("bottom") + bl = plt.getAxis("left") + ba.setTicks([]) + bl.setTicks([]) + ba.setWidth(0) + ba.setHeight(0) + bl.setWidth(0) + bl.setHeight(0) + plt.setYRange(0, 255) + + 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 + max_items = len(actors) + value = args[0] + + if osc_address == "/bjoern/ekg": + #if value > 250: + #data_points, pos1 = resize(plot_data1, len(plot_data1), lengths1[-1], pos1) + #foo, pos2 = resize(plot_data2, len(plot_data2), lengths1[-1], pos2) + #foo, pos3 = resize(plot_data3, len(plot_data3), lengths1[-1], pos3) + #print "length1", lengths1 + #lengths1.append(0) + #else: + #lengths1[-1] += 1 + + ix = actors.index(plotItem1) + + #pos1 = rearrange(plot_data1, pos1, data_points) + set_point(plotPoint1, pos1, value, ix, max_items) + pos1 = set_value(plot_data1, pos1, data_points, value) + try: + plotItem1.setData(y=np.array(scale_data(plot_data1, ix, max_items)), clear=True) + except ValueError: + pass + + elif osc_address == "/merle/ekg": + ix = actors.index(plotItem2) + + #pos2 = rearrange(plot_data2, pos2, data_points) + set_point(plotPoint2, pos2, value, ix, max_items) + pos2 = set_value(plot_data2, pos2, data_points, value) + try: + plotItem2.setData(y=np.array(scale_data(plot_data2, ix, max_items)), clear=True) + except ValueError: + pass + elif osc_address == "/uwe/ekg": + ix = actors.index(plotItem3) + #pos3 = rearrange(plot_data3, pos3, data_points) + set_point(plotPoint3, pos3, value, ix, max_items) + pos3 = set_value(plot_data3, pos3, data_points, value) + try: + plotItem3.setData(y=np.array(scale_data(plot_data3, ix, max_items)), 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 JustAHTTPServer(HTTPServer): + + pass + + +def main(): + a = create_arg_parser("ekgplotter") + own_group = add_main_group(a) + own_group.add_argument('-x', "--http_host", default="0.0.0.0", + help='my host, defaults to "socket.gethostname()"') + own_group.add_argument('-X', "--http_port", default=9000, + type=int, help='my port, defaults to 9000') + add_chaosc_group(a) + add_subscriber_group(a, "ekgplotter") + args = finalize_arg_parser(a) + + try: + host, port = socket.getaddrinfo(args.http_host, args.http_port, socket.AF_INET6, socket.SOCK_DGRAM, 0, socket.AI_V4MAPPED | socket.AI_ALL | socket.AI_CANONNAME)[-1][4][:2] + + server = JustAHTTPServer(("0.0.0.0", 9000), MyHandler) + server.args = args + print "%s: starting up http server on '%s:%d'" % ( + datetime.now().strftime("%x %X"), host, port) + server.serve_forever() + except KeyboardInterrupt: + print '^C received, shutting down server' + server.socket.close() + + +if __name__ == '__main__': + main() + diff --git a/ekgplotter/ekgplotter/ok_main.py b/ekgplotter/ekgplotter/ok_main.py new file mode 100644 index 0000000..e3b89a2 --- /dev/null +++ b/ekgplotter/ekgplotter/ok_main.py @@ -0,0 +1,408 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# This file is part of sensors2osc package +# +# sensors2osc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# sensors2osc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with sensors2osc. If not, see . +# +# found the mjpeg part here, thanks for the nice code :) +# http://hardsoftlucid.wordpress.com/2013/04/11/mjpeg-server-for-webcam-in-python-with-opencv/ +# the osc integration stuff is implemented by me +# +# Copyright (C) 2014 Stefan Kögl + +from __future__ import absolute_import + +from datetime import datetime +import threading +import Queue +import numpy as np +import string,cgi,time, random, socket +from os import curdir, sep +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from SocketServer import ThreadingMixIn, ForkingMixIn +import select +import re + +from collections import deque + +from PyQt4.QtCore import QBuffer, QByteArray, QIODevice +from PyQt4 import QtGui +import pyqtgraph as pg + +from pyqtgraph.widgets.PlotWidget import PlotWidget + +from chaosc.argparser_groups import * + +try: + from chaosc.c_osc_lib import * +except ImportError: + from chaosc.osc_lib import * + +QtGui.QApplication.setGraphicsSystem('opengl') + + +try: + from chaosc.c_osc_lib import decode_osc +except ImportError as e: + print(e) + from chaosc.osc_lib import decode_osc + +QAPP = QtGui.QApplication([]) + + +class PlotWindow(PlotWidget): + def __init__(self, title=None, **kargs): + self.win = QtGui.QMainWindow() + PlotWidget.__init__(self, **kargs) + self.win.setCentralWidget(self) + for m in ['resize']: + setattr(self, m, getattr(self.win, m)) + if title is not None: + self.win.setWindowTitle(title) + + +class OSCThread(threading.Thread): + def __init__(self, args): + super(OSCThread, self).__init__() + self.args = args + self.running = True + self.own_address = socket.getaddrinfo(args.own_host, args.own_port, socket.AF_INET6, socket.SOCK_DGRAM, 0, socket.AI_V4MAPPED | socket.AI_ALL | socket.AI_CANONNAME)[-1][4][:2] + + self.chaosc_address = chaosc_host, chaosc_port = socket.getaddrinfo(args.chaosc_host, args.chaosc_port, socket.AF_INET6, socket.SOCK_DGRAM, 0, socket.AI_V4MAPPED | socket.AI_ALL | socket.AI_CANONNAME)[-1][4][:2] + + self.osc_sock = socket.socket(2, 2, 17) + self.osc_sock.bind(self.own_address) + self.osc_sock.setblocking(0) + + print "%s: starting up osc receiver on '%s:%d'" % ( + datetime.now().strftime("%x %X"), self.own_address[0], self.own_address[1]) + + self.subscribe_me() + + def subscribe_me(self): + """Use this procedure for a quick'n dirty subscription to your chaosc instance. + + :param chaosc_address: (chaosc_host, chaosc_port) + :type chaosc_address: tuple + + :param receiver_address: (host, port) + :type receiver_address: tuple + + :param token: token to get authorized for subscription + :type token: str + """ + print "%s: subscribing to '%s:%d' with label %r" % (datetime.now().strftime("%x %X"), self.chaosc_address[0], self.chaosc_address[1], self.args.subscriber_label) + msg = OSCMessage("/subscribe") + msg.appendTypedArg(self.own_address[0], "s") + msg.appendTypedArg(self.own_address[1], "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.sendto(msg.encode_osc(), self.chaosc_address) + + + def unsubscribe_me(self): + if self.args.keep_subscribed: + return + + print "%s: unsubscribing from '%s:%d'" % (datetime.now().strftime("%x %X"), self.chaosc_address[0], self.chaosc_address[1]) + msg = OSCMessage("/unsubscribe") + msg.appendTypedArg(self.own_address[0], "s") + msg.appendTypedArg(self.own_address[1], "i") + msg.appendTypedArg(self.args.authenticate, "s") + self.osc_sock.sendto(msg.encode_osc(), self.chaosc_address) + + def run(self): + + while self.running: + reads, writes, errs = select.select([self.osc_sock], [], [], 0.05) + if reads: + osc_input = reads[0].recv(4096) + osc_address, typetags, messages = decode_osc(osc_input, 0, len(osc_input)) + if osc_address.find("ekg") > -1 or osc_address.find("plot") != -1: + queue.put_nowait((osc_address, messages)) + else: + queue.put_nowait(("/bjoern/ekg", [0])) + queue.put_nowait(("/merle/ekg", [0])) + queue.put_nowait(("/uwe/ekg", [0])) + self.unsubscribe_me() + print "OSCThread is going down" + + +queue = Queue.Queue() + +class MyHandler(BaseHTTPRequestHandler): + + def do_GET(self): + print "get" + + self.thread = thread = OSCThread(self.server.args) + 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] + + def set_point(plotPoint, pos, value, ix, max_items): + scale = 254 / max_items * ix + plotPoint.setData(x = [pos], y = [6*ix + value / max_items + scale]) + + + def setValue(dataItem, pos, maxPos, value): + dataItem[pos] = value + return (pos + 1) % maxPos + + def find_max_index(dataItem): + max_value = 0 + max_index = 0 + for ix, i in enumerate(dataItem): + if i > max_value: + max_value = i + max_index = ix + return max_index, max_value + + def rearrange(data, index, max_items): + max_value_index, max_value = findMax(data) + mean = int(max_items / 2.) + start = mean - max_value_index + data.rotate(start) + pos = (index + start) % max_items + print "rearrange", index, max_items, pos + return pos + + def checkDataPoints(value, data_max_value): + if value > max_value and value > 200: + return True, value + return False, data_max_value + + 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 = 21 + + self.send_response(200) + pos1 = 0 + pos2 = 0 + pos3 = 0 + + data1_max_value = 0 + data2_max_value = 0 + data3_max_value = 0 + + data1_distance = data_points + data2_distance = data_points + data3_distance = data_points + + plot_data1 = deque([0] * data_points) + plot_data2 = deque([0] * data_points) + plot_data3 = deque([0] * data_points) + plt = PlotWidget(title="

EKG

", name="Merle") + plt.hide() + plotItem1 = pg.PlotCurveItem(pen=pg.mkPen('r', width=2), width=2, name="bjoern") + plotItem2 = pg.PlotCurveItem(pen=pg.mkPen('g', width=2), width=2, name="merle") + plotItem3 = pg.PlotCurveItem(pen=pg.mkPen('b', width=2), width=2, name="uwe") + shadowPen = pg.mkPen("w", width=10) + plotItem1.setShadowPen(pen=shadowPen, width=6, cosmetic=True) + plotItem2.setShadowPen(pen=shadowPen, width=6, cosmetic=True) + plotItem3.setShadowPen(pen=shadowPen, width=6, cosmetic=True) + pen = pg.mkPen("w", size=1) + brush = pg.mkBrush("w") + plotPoint1 = pg.ScatterPlotItem(pen=pen, brush=brush, size=10) + plotPoint2 = pg.ScatterPlotItem(pen=pen, brush=brush, size=10) + plotPoint3 = pg.ScatterPlotItem(pen=pen, brush=brush, size=10) + 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.addItem(plotPoint1) + plt.addItem(plotPoint2) + plt.addItem(plotPoint3) + + plt.setLabel('left', "

Amplitude

") + plt.setLabel('bottom', "

Time

") + 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 + max_items = len(actors) + value = args[0] + + if osc_address == "/bjoern/ekg": + ix = actors.index(plotItem1) + res, tmp = checkDataPoints(value, data1_max_value) + if res and res > 20: + data_points = tmp + data1_maxdata1_max_value = 0 + set_point(plotPoint1, pos1, value, ix, max_items) + pos1 = setValue(plot_data1, pos1, data_points, value) + pos1 = rearrange(plot_data1, pos1, data_points) + try: + plotItem1.setData(y=np.array(scale_data(plot_data1, ix, max_items)), clear=True) + except ValueError: + pass + elif osc_address == "/merle/ekg": + ix = actors.index(plotItem2) + set_point(plotPoint2, pos2, value, ix, max_items) + pos2 = setValue(plot_data2, pos2, data_points, value) + pos2 = rearrange(plot_data2, pos2, data_points) + try: + plotItem2.setData(y=np.array(scale_data(plot_data2, ix, max_items)), clear=True) + except ValueError: + pass + elif osc_address == "/uwe/ekg": + ix = actors.index(plotItem3) + set_point(plotPoint3, pos3, value, ix, max_items) + pos3 = setValue(plot_data3, pos3, data_points, value) + pos3 = rearrange(plot_data3, pos3, data_points) + try: + plotItem3.setData(y=np.array(scale_data(plot_data3, ix, max_items)), 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 JustAHTTPServer(HTTPServer): + pass + + +def main(): + a = create_arg_parser("ekgplotter") + own_group = add_main_group(a) + own_group.add_argument('-x', "--http_host", default="0.0.0.0", + help='my host, defaults to "socket.gethostname()"') + own_group.add_argument('-X', "--http_port", default=9000, + type=int, help='my port, defaults to 9000') + add_chaosc_group(a) + add_subscriber_group(a, "ekgplotter") + args = finalize_arg_parser(a) + + try: + host, port = socket.getaddrinfo(args.http_host, args.http_port, socket.AF_INET6, socket.SOCK_DGRAM, 0, socket.AI_V4MAPPED | socket.AI_ALL | socket.AI_CANONNAME)[-1][4][:2] + + server = JustAHTTPServer(("0.0.0.0", 9000), MyHandler) + server.args = args + print "%s: starting up http server on '%s:%d'" % ( + datetime.now().strftime("%x %X"), host, port) + server.serve_forever() + except KeyboardInterrupt: + print '^C received, shutting down server' + server.socket.close() + + +if __name__ == '__main__': + main() +