new ekgplotter tool

This commit is contained in:
Stefan Kögl 2014-03-15 20:57:14 +01:00
parent 1cf7e6a75b
commit 495fa03401
4 changed files with 375 additions and 75 deletions

View File

@ -0,0 +1,169 @@
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
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 MyHandler(BaseHTTPRequestHandler):
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.send_response(200)
plot_data1 = deque([0] * 100)
plot_data2 = deque([254/3] * 100)
plot_data3 = deque([254/3*2] * 100)
plt = PlotWindow(title="EKG", name="Merle")
plt.addLegend()
#plt = pg.plot(pen=(0, 3*1.3))
plt.resize(1280, 720)
plotItem1 = pg.PlotCurveItem(pen=(0, 3*1.3), name="bjoern")
plotItem2 = pg.PlotCurveItem(pen=(1, 3*1.3), name="merle")
plotItem3 = pg.PlotCurveItem(pen=(2, 3*1.3), 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")
osc_sock = socket.socket(2, 2, 17)
osc_sock.bind(("", 10000))
osc_sock.setblocking(0)
last = time.time()
now = last
while 1:
for i in xrange(3):
reads, writes, errs = select.select([osc_sock], [], [], 0.01)
#print reads, writes, errs
if reads:
osc_input = reads[0].recv(4096)
osc_address, typetags, args = decode_osc(osc_input, 0, len(osc_input))
#print "osc", osc_address, typetags, args
if osc_address.startswith("/bjoern"):
plot_data1.appendleft(args[0] / 3)
plot_data1.pop()
elif osc_address.startswith("/merle"):
plot_data2.appendleft(args[0] / 3 + 254/3)
plot_data2.pop()
elif osc_address.startswith("/uwe"):
plot_data3.appendleft(args[0] / 3 + 254/3*2)
plot_data3.pop()
plotItem1.setData(y=np.array(plot_data1), clear=True)
plotItem2.setData(y=np.array(plot_data2), clear=True)
plotItem3.setData(y=np.array(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(ThreadingMixIn, HTTPServer):
class ThreadedHTTPServer(HTTPServer, ForkingMixIn):
"""Handle requests in a separate thread."""
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()

View File

@ -0,0 +1,7 @@
<HTML>
<BODY>
<img src="/camera.mjpeg" alt="Smiley face">
</BODY>
</HTML>

View File

@ -1,3 +1,31 @@
#!/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 <http://www.gnu.org/licenses/>.
#
# 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
import threading
import Queue
import numpy as np import numpy as np
import string,cgi,time, random, socket import string,cgi,time, random, socket
from os import curdir, sep from os import curdir, sep
@ -14,6 +42,10 @@ import pyqtgraph as pg
from pyqtgraph.widgets.PlotWidget import PlotWidget from pyqtgraph.widgets.PlotWidget import PlotWidget
from chaosc.argparser_groups import *
QtGui.QApplication.setGraphicsSystem('raster')
try: try:
from chaosc.c_osc_lib import decode_osc from chaosc.c_osc_lib import decode_osc
@ -21,17 +53,11 @@ except ImportError as e:
print(e) print(e)
from chaosc.osc_lib import decode_osc from chaosc.osc_lib import decode_osc
QAPP = None
def mkQApp():
if QtGui.QApplication.instance() is None:
global QAPP
QAPP = QtGui.QApplication([]) QAPP = QtGui.QApplication([])
class PlotWindow(PlotWidget): class PlotWindow(PlotWidget):
def __init__(self, title=None, **kargs): def __init__(self, title=None, **kargs):
mkQApp()
self.win = QtGui.QMainWindow() self.win = QtGui.QMainWindow()
PlotWidget.__init__(self, **kargs) PlotWidget.__init__(self, **kargs)
self.win.setCentralWidget(self) self.win.setCentralWidget(self)
@ -41,14 +67,61 @@ class PlotWindow(PlotWidget):
self.win.setWindowTitle(title) self.win.setWindowTitle(title)
class OSCThread(threading.Thread):
def __init__(self, args):
super(OSCThread, self).__init__()
self.running = True
self.osc_sock = socket.socket(2, 2, 17)
self.osc_sock.bind((args.own_host, args.own_port))
self.osc_sock.setblocking(0)
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]))
print "OSCThread is going down"
queue = Queue.Queue()
class MyHandler(BaseHTTPRequestHandler): class MyHandler(BaseHTTPRequestHandler):
def do_GET(self): def do_GET(self):
print "get"
global plotValues 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: try:
self.path=re.sub('[^.a-zA-Z0-9]', "",str(self.path)) self.path=re.sub('[^.a-zA-Z0-9]', "",str(self.path))
if self.path=="" or self.path==None or self.path[:1]==".": if self.path=="" or self.path==None or self.path[:1]==".":
return return
if self.path.endswith(".html"): if self.path.endswith(".html"):
f = open(curdir + sep + self.path) f = open(curdir + sep + self.path)
self.send_response(200) self.send_response(200)
@ -56,20 +129,26 @@ class MyHandler(BaseHTTPRequestHandler):
self.end_headers() self.end_headers()
self.wfile.write(f.read()) self.wfile.write(f.read())
f.close() f.close()
return elif self.path.endswith(".mjpeg"):
if self.path.endswith(".mjpeg"): data_points = 1000
self.send_response(200)
plot_data1 = deque([0] * 100) self.send_response(200)
plot_data2 = deque([254/3] * 100) plot_data1 = data = deque([0] * data_points)
plot_data3 = deque([254/3*2] * 100) plot_data2 = data = deque([0] * data_points)
plt = PlotWindow(title="EKG", name="Merle") plot_data3 = data = deque([0] * data_points)
plt.addLegend() plt = PlotWidget(title="<h1>EKG</h1>", name="Merle")
#plt = pg.plot(pen=(0, 3*1.3)) plt.hide()
plt.resize(1280, 720) plotItem1 = pg.PlotCurveItem(pen=pg.mkPen('r', width=2), name="bjoern")
plotItem1 = pg.PlotCurveItem(pen=(0, 3*1.3), name="bjoern") plotItem2 = pg.PlotCurveItem(pen=pg.mkPen('g', width=2), name="merle")
plotItem2 = pg.PlotCurveItem(pen=(1, 3*1.3), name="merle") plotItem3 = pg.PlotCurveItem(pen=pg.mkPen('b', width=2), name="uwe")
plotItem3 = pg.PlotCurveItem(pen=(2, 3*1.3), 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) plotItem1.setPos(0, 0*6)
plotItem2.setPos(0, 1*6) plotItem2.setPos(0, 1*6)
plotItem3.setPos(0, 2*6) plotItem3.setPos(0, 2*6)
@ -77,69 +156,100 @@ class MyHandler(BaseHTTPRequestHandler):
plt.addItem(plotItem2) plt.addItem(plotItem2)
plt.addItem(plotItem3) plt.addItem(plotItem3)
plt.setLabel('left', "EKG") plt.setLabel('left', "<h2>Amplitude</h2>")
plt.setLabel('bottom', "Time") plt.setLabel('bottom', "<h2>Time</h2>")
plt.showGrid(True, True) plt.showGrid(True, True)
ba = plt.getAxis("bottom") ba = plt.getAxis("bottom")
bl = plt.getAxis("left") bl = plt.getAxis("left")
ba.setTicks([]) ba.setTicks([])
bl.setTicks([]) bl.setTicks([])
plt.setYRange(0, 254) plt.setYRange(0, 254)
print type(plt)
self.wfile.write("Content-Type: multipart/x-mixed-replace; boundary=--aaboundary") self.wfile.write("Content-Type: multipart/x-mixed-replace; boundary=--aaboundary")
self.wfile.write("\r\n\r\n") self.wfile.write("\r\n\r\n")
osc_sock = socket.socket(2, 2, 17)
osc_sock.bind(("", 10000))
osc_sock.setblocking(0) plt.resize(1280, 720)
last = time.time()
now = last
while 1: while 1:
for i in xrange(3): while 1:
reads, writes, errs = select.select([osc_sock], [], [], 0.01) try:
#print reads, writes, errs osc_address, args = queue.get_nowait()
if reads: except Queue.Empty:
osc_input = reads[0].recv(4096) break
osc_address, typetags, args = decode_osc(osc_input, 0, len(osc_input))
#print "osc", osc_address, typetags, args
if osc_address.startswith("/bjoern"):
plot_data1.appendleft(args[0] / 3)
plot_data1.pop()
elif osc_address.startswith("/merle"):
plot_data2.appendleft(args[0] / 3 + 254/3)
plot_data2.pop()
elif osc_address.startswith("/uwe"):
plot_data3.appendleft(args[0] / 3 + 254/3*2)
plot_data3.pop()
plotItem1.setData(y=np.array(plot_data1), clear=True) value = args[0]
plotItem2.setData(y=np.array(plot_data2), clear=True) if osc_address == "/bjoern/ekg":
plotItem3.setData(y=np.array(plot_data3), clear=True) plot_data1.append(value)
#item = plt.plot(plot_data1, pen=(0, 3*1.3), clear=True) 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) exporter = pg.exporters.ImageExporter.ImageExporter(plt.plotItem)
exporter.parameters()['width'] = 1280 img = exporter.export("tmpfile", True)
#exporter.parameters()['height'] = 720
name = 'tmpfile'
img = exporter.export(name, True)
buffer = QBuffer() buffer = QBuffer()
buffer.open(QIODevice.ReadWrite) buffer.open(QIODevice.WriteOnly)
img.save(buffer, "JPG", 100) img.save(buffer, "JPG", 100)
JpegData = buffer.data() JpegData = buffer.data()
self.wfile.write("--aaboundary\r\n") del buffer
self.wfile.write("Content-Type: image/jpeg\r\n") 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))
self.wfile.write("Content-length: %d\r\n\r\n" % len(JpegData))
self.wfile.write(JpegData) elif self.path.endswith(".jpeg"):
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) f = open(curdir + sep + self.path)
self.send_response(200) self.send_response(200)
self.send_header('Content-type','image/jpeg') self.send_header('Content-type','image/jpeg')
@ -147,23 +257,37 @@ class MyHandler(BaseHTTPRequestHandler):
self.wfile.write(f.read()) self.wfile.write(f.read())
f.close() f.close()
return return
return except (KeyboardInterrupt, SystemError):
thread.running = False
thread.join()
except IOError: except IOError:
self.send_error(404,'File Not Found: %s' % self.path) self.send_error(404,'File Not Found: %s' % self.path)
def __del__(self):
self.thread.running = False
self.thread.join()
class JustAHTTPServer(HTTPServer):
pass
#class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
class ThreadedHTTPServer(HTTPServer, ForkingMixIn):
"""Handle requests in a separate thread."""
def main(): def main():
a = create_arg_parser("ekgplotter")
add_main_group(a)
add_chaosc_group(a)
add_subscriber_group(a, "ekgplotter")
args = finalize_arg_parser(a)
try: try:
server = ThreadedHTTPServer(('0.0.0.0', 9000), MyHandler) server = JustAHTTPServer(('0.0.0.0', 9000), MyHandler)
print 'started httpserver...' print 'started httpserver...'
server.serve_forever() server.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
print '^C received, shutting down server' print '^C received, shutting down server'
server.socket.close() server.socket.close()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -22,7 +22,7 @@ setup(
exclude_package_data = {'': ['.gitignore']}, exclude_package_data = {'': ['.gitignore']},
install_requires=[], install_requires=["pyqtgraph"],
# installing unzipped # installing unzipped
zip_safe = False, zip_safe = False,