mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-04 02:00:43 +00:00
1782 lines
52 KiB
Python
1782 lines
52 KiB
Python
#!/usr/bin/env python
|
|
'''
|
|
This python script is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This python script 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
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this script; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
'''
|
|
|
|
# Author: Mark Watkins
|
|
# Date: 09/03/2011
|
|
# Purpose: CPAP Support
|
|
# License: GPL
|
|
|
|
#Attempt at faster CPAP Loader
|
|
|
|
import sys
|
|
import os
|
|
from struct import *
|
|
from datetime import datetime as DT
|
|
from datetime import timedelta,date,time #,datetime,date,time
|
|
import time
|
|
from matplotlib.dates import drange
|
|
from matplotlib.figure import Figure
|
|
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
|
|
from pylab import *
|
|
|
|
#from pytz import timezone
|
|
import pytz
|
|
import gobject
|
|
import gtk
|
|
|
|
MYTIMEZONE="Australia/Queensland";
|
|
|
|
|
|
localtz=pytz.timezone(MYTIMEZONE)
|
|
utc = pytz.utc
|
|
utcoff=time.timezone / -(60*60)
|
|
|
|
Device_Types={
|
|
"Unknown":0,
|
|
"PAP":1,
|
|
"Oximeter":2,
|
|
"ZEO":3
|
|
}
|
|
|
|
def LookupDeviceType(type):
|
|
for k,v in Device_Types.iteritems():
|
|
if type.lower()==k.lower():
|
|
return v
|
|
return 0
|
|
|
|
|
|
class Event:
|
|
code=0
|
|
time=None
|
|
data=[]
|
|
def __init__(self,time,code,data):
|
|
self.time=time
|
|
self.code=code
|
|
self.data=data
|
|
|
|
class Waveform:
|
|
time=None
|
|
def __init__(self,time,waveform,size,duration,format,rate):
|
|
self.time=time
|
|
self.waveform=waveform
|
|
self.size=size
|
|
self.duration=duration
|
|
self.format=format
|
|
self.rate=rate
|
|
|
|
|
|
class Machine:
|
|
def __init__(self,brand,model,type):
|
|
self.brand=brand
|
|
self.model=model
|
|
self.type=LookupDeviceType(type)
|
|
|
|
def Open(self):
|
|
print "in Machine.Open()";
|
|
|
|
class OxiMeter(Machine):
|
|
CodeTypes=['Error','Pulse','SpO2']
|
|
|
|
def __init__(self,brand,model):
|
|
Machine.__init__(self,brand,model,"Oximeter")
|
|
|
|
import serial
|
|
class CMS50X(OxiMeter):
|
|
Baudrate=19200
|
|
Timeout=5
|
|
Home=os.path.expanduser('~')
|
|
|
|
#LogDirectory=Home+os.sep+"CMS50"
|
|
#os.system("mkdir "+LogDirectory)
|
|
|
|
def __init__(self):
|
|
OxiMeter.__init__(self,"Contec","CMS50X")
|
|
self.Device=None
|
|
self.devopen=False
|
|
# Borrowed from PySerial
|
|
if (os.name=="nt") or (sys.platform=="win32"):
|
|
self.ports = []
|
|
for i in range(256):
|
|
try:
|
|
s = serial.Serial(i)
|
|
self.ports.append( (i, s.portstr))
|
|
s.close() # explicit close 'cause of delayed GC in java
|
|
except serial.SerialException:
|
|
pass
|
|
self.Device=self.ports[5]
|
|
elif os.name=='posix':
|
|
import glob
|
|
|
|
self.ports=glob.glob('/dev/ttyUSB*')
|
|
if (len(self.ports)>0):
|
|
self.Device=self.ports[0]
|
|
|
|
def Open(self):
|
|
if not self.Device:
|
|
print "No serial device detected"
|
|
return False
|
|
|
|
if self.devopen:
|
|
print "Device is already open"
|
|
return True
|
|
|
|
try:
|
|
self.ser=serial.Serial(self.Device,self.Baudrate,timeout=self.Timeout)
|
|
except:
|
|
print "Couldn't open",self.Device
|
|
return False
|
|
|
|
self.ser.flushInput()
|
|
|
|
self.lastpulse=0
|
|
self.lastspo2=0
|
|
self.devopen=True
|
|
return True
|
|
|
|
def Close(self):
|
|
if (self.devopen):
|
|
ser.close()
|
|
self.devopen=False
|
|
|
|
def Read(self):
|
|
while self.devopen:
|
|
while self.devopen:
|
|
h=self.ser.read(1)
|
|
if len(h)>0:
|
|
if (ord(h[0]) & 0x80): # Sync Bit
|
|
break
|
|
else:
|
|
#print "Timeout!";
|
|
self.devopen=False
|
|
return None
|
|
|
|
c=self.ser.read(4)
|
|
if len(c)==4:
|
|
break
|
|
else:
|
|
#print "Sync error";
|
|
self.devopen=False
|
|
return None
|
|
|
|
|
|
hdr=ord(h)
|
|
if (hdr & 0x10):
|
|
alarm=True
|
|
else: alarm=False
|
|
|
|
if (hdr & 0x8):# or (hdr & 0x20): # (hdr & 0x10)==alarm
|
|
signal=True
|
|
else: signal=False
|
|
|
|
wave1=ord(c[0])
|
|
wave2=ord(c[1])
|
|
pulse=ord(c[2])
|
|
spo2=ord(c[3])
|
|
|
|
return [signal,alarm,wave1,wave2,pulse,spo2]
|
|
|
|
def Save(self,time,code,value):
|
|
if (not self.start): return
|
|
|
|
delta=time-self.lasttime
|
|
s=int(((delta.seconds*1000)+delta.microseconds/1000))
|
|
|
|
self.events[self.start].append(s>>8)
|
|
self.events[self.start].append(s&255)
|
|
self.events[self.start].append(code)
|
|
self.events[self.start].append(value)
|
|
self.evcnt+=1
|
|
|
|
self.lasttime=time
|
|
|
|
def Record(self,path):
|
|
if self.devopen: return (None,None)
|
|
|
|
self.starttime=DT.utcnow()
|
|
self.lasttime=self.starttime
|
|
lt=self.lasttime
|
|
|
|
self.Open()
|
|
|
|
lastpulse=0
|
|
lastspo2=0
|
|
lastalarm=True
|
|
lastsignal=True
|
|
self.evcnt=0
|
|
|
|
wave=[dict(),dict()]
|
|
wavestart=None
|
|
self.start=None
|
|
self.events=dict()
|
|
|
|
while self.devopen:
|
|
D=self.Read()
|
|
if not D: continue
|
|
|
|
t=DT.utcnow();
|
|
d=t-lt
|
|
|
|
if D[0]!=lastsignal or ((t-self.lasttime)>timedelta(seconds=64)):
|
|
self.Save(t,0,D[0])
|
|
lastsignal=D[0]
|
|
|
|
if D[1]!=lastalarm or ((t-self.lasttime)>timedelta(seconds=64)):
|
|
self.Save(t,1,D[1])
|
|
lastalarm=D[1]
|
|
|
|
if (not self.start or (d>timedelta(microseconds=30000))): #Lost serial sync for wavefom
|
|
self.start=t
|
|
print "Starting new event chunk",self.start
|
|
self.events[self.start]=bytearray()
|
|
|
|
lt=t
|
|
|
|
if D[1]: continue
|
|
|
|
if (not wavestart or (d>timedelta(microseconds=30000))): #Lost serial sync for wavefom
|
|
wavestart=t
|
|
wave[0][wavestart]=bytearray()
|
|
wave[1][wavestart]=bytearray()
|
|
print "Starting new wave chunk",wavestart
|
|
|
|
wave[0][wavestart].append(D[2])
|
|
wave[1][wavestart].append(D[3])
|
|
|
|
if (D[4]!=lastpulse) or ((t-self.lasttime)>timedelta(seconds=64)):
|
|
self.Save(t,2,D[4])
|
|
lastpulse=D[4]
|
|
|
|
if (D[5]!=lastspo2) or ((t-self.lasttime)>timedelta(seconds=64)):
|
|
self.Save(t,3,D[5])
|
|
lastspo2=D[5]
|
|
|
|
|
|
self.Close()
|
|
|
|
if (path[-1]!=os.sep): path+=os.sep
|
|
|
|
ed=sorted(self.events.keys())
|
|
basename=path+ed[0].strftime("CMS50-%Y%m%d-%H%M%S")
|
|
efname=basename+".001"
|
|
|
|
magic=0x35534d43 #CMS5
|
|
f=open(efname,"wb");
|
|
j=0
|
|
for k,v in self.events.iteritems():
|
|
header=bytearray(16)
|
|
timestamp=time.mktime(k.timetupple())
|
|
l=len(v)
|
|
struct.pack_into('<IHIIH',header,4,magic,1,timestamp,l,j)
|
|
f.write(header)
|
|
f.write(v)
|
|
j+=1
|
|
|
|
fclose(f)
|
|
|
|
wfname=basename+".002"
|
|
f=open(wfname,"wb");
|
|
j=0
|
|
for k,v in wave[0].iteritems():
|
|
header=bytearray(16)
|
|
timestamp=time.mktime(k.timetupple())
|
|
l=len(v)
|
|
struct.pack_into('<IHIIH',header,4,magic,2,timestamp,l,j)
|
|
f.write(header)
|
|
f.write(v)
|
|
j+=1
|
|
|
|
fclose(f)
|
|
|
|
wfname=basename+".003"
|
|
f=open(wffname,"wb");
|
|
j=0
|
|
for k,v in wave[0].iteritems():
|
|
header=bytearray(16)
|
|
timestamp=time.mktime(k.timetupple())
|
|
l=len(v)
|
|
struct.pack_into('<IHIIH',header,4,magic,3,timestamp,l,j)
|
|
f.write(header)
|
|
f.write(v)
|
|
j+=1
|
|
|
|
fclose(f)
|
|
|
|
|
|
return (self.events,wave)
|
|
|
|
|
|
class CPAP(Machine):
|
|
CodeTypes=['UN','PR','BP','PP','RE','OA','CA','H','FL','CSR','VS','LR']
|
|
|
|
def __init__(self,brand,model):
|
|
Machine.__init__(self,brand,model,"PAP")
|
|
self.session=dict()
|
|
self.sessiontimes=dict()
|
|
self.flowrate=dict()
|
|
self.flowtimes=dict();
|
|
self.machine=dict()
|
|
|
|
def GetBedtime(self,dt,hr=13):
|
|
if (type(dt).__name__=='date'):
|
|
|
|
start=DT(dt.year,dt.month,dt.day,hr,0,0)
|
|
|
|
elif (type(dt).__name__=='datetime'):
|
|
start=dt.replace(hour=hr,minute=0,second=0,microsecond=0)
|
|
else:
|
|
return (None,None)
|
|
|
|
end=localtz.localize(start,is_dst=False)
|
|
#start=utc.localize(start)
|
|
start=end-timedelta(hours=24)
|
|
|
|
sess=self.GetEvSessions(start,end)
|
|
|
|
|
|
if len(sess):
|
|
start=self.sessiontimes[sess[0]][0];
|
|
end=self.sessiontimes[sess[-1]][1];
|
|
return (start,end)
|
|
else:
|
|
return (None,None)
|
|
|
|
def GetEvSessions(self,start=None,end=None):
|
|
A=[]
|
|
for sess,time in self.sessiontimes.iteritems():
|
|
if not start or not end:
|
|
A.append(sess)
|
|
else:
|
|
if (start>time[1]): continue
|
|
if (end<time[0]): continue
|
|
A.append(sess)
|
|
|
|
return sorted(A)
|
|
|
|
def GetFlowSessions(self,start=None,end=None):
|
|
A=[]
|
|
for sess,time in self.flowtimes.iteritems():
|
|
if not start or not end:
|
|
A.append(sess)
|
|
else:
|
|
if (start>time[1]): continue
|
|
if (end<time[0]): continue
|
|
A.append(sess)
|
|
|
|
return sorted(A)
|
|
|
|
def CountEvents(self,type,start,end):
|
|
if type not in self.CodeTypes:
|
|
print "Unrecognized cpap code",field
|
|
return None
|
|
|
|
#print "Searching",type,"between",start.astimezone(localtz),"and",end.astimezone(localtz)
|
|
sesslist=self.GetEvSessions(start,end)
|
|
val=0
|
|
#print sesslist
|
|
for s in sesslist:
|
|
if type not in self.session[s].keys():
|
|
break;
|
|
if (start<self.sessiontimes[s][0]) and (end>self.sessiontimes[s][1]):
|
|
val+=len(self.session[s][type]);
|
|
|
|
else:
|
|
for e in self.session[s][type]:
|
|
if (e.time>=start) and (e.time<=end):
|
|
val+=1
|
|
return val
|
|
|
|
def FirstLastEventTime(self,field,start,end):
|
|
sess=self.GetEvSessions(start,end)
|
|
|
|
a1=self.sessiontimes[sess[0]][0]
|
|
a2=self.sessiontimes[sess[-1]][1]
|
|
#if (a1>=start):
|
|
# st=a1
|
|
#else:
|
|
st=a1
|
|
for e in self.session[sess[0]][field]:
|
|
if (e.time>=start):
|
|
st=e.time
|
|
break;
|
|
|
|
#if (a2<=end):
|
|
#et=a2
|
|
#else:
|
|
et=a2
|
|
for e in self.session[sess[-1]][field]:
|
|
if (e.time>=end):
|
|
et=e.time
|
|
break;
|
|
|
|
return (st,et)
|
|
|
|
|
|
|
|
def GetTotalTime(self,start,end):
|
|
t=timedelta(seconds=0)
|
|
sess=self.GetEvSessions(start,end)
|
|
for s in sess:
|
|
#print self.sessiontimes[s]
|
|
a1=self.sessiontimes[s][0]
|
|
a2=self.sessiontimes[s][1]
|
|
|
|
d=a2-a1
|
|
|
|
if a1<start:
|
|
d-=start-a1
|
|
|
|
if a2>end:
|
|
d-=a2-end
|
|
|
|
#print s,a2-a1
|
|
t+=d
|
|
|
|
return t
|
|
|
|
def GetEvents(self,type,start,end):
|
|
if type not in self.CodeTypes:
|
|
print "Unrecognized cpap code",field
|
|
return None
|
|
|
|
sess=self.GetEvSessions(start,end)
|
|
E=[]
|
|
for s in sess:
|
|
for e in self.session[s][type]:
|
|
if e.time>=start and e.time<=end:
|
|
E.append(e)
|
|
return E
|
|
|
|
def GetEventsPlot(self,type,start,end,dc=None,di=0,padsession=False):
|
|
if type not in self.CodeTypes:
|
|
print "Unrecognized cpap code",field
|
|
return None
|
|
|
|
sess=self.GetEvSessions(start,end)
|
|
T=[]
|
|
D=[]
|
|
laste=None
|
|
firste=None
|
|
for s in sess:
|
|
for e in self.session[s][type]:
|
|
if e.time>=start and e.time<=end:
|
|
if not firste and padsession:
|
|
firste=e
|
|
D.append(0)
|
|
T.append(e.time)
|
|
T.append(e.time)
|
|
if dc:
|
|
D.append(dc)
|
|
else:
|
|
D.append(e.data[di])
|
|
laste=e
|
|
|
|
if padsession:
|
|
if laste:
|
|
D.append(0)
|
|
T.append(laste.time)
|
|
|
|
return (T,D)
|
|
|
|
def GetFlowPlots(self,start,end):
|
|
sess=self.GetFlowSessions(start,end)
|
|
T=[]
|
|
D=[]
|
|
for s in sess:
|
|
X=[]
|
|
Y=[]
|
|
for w in self.flowrate[s]:
|
|
d=timedelta(microseconds=w.rate*1000000.0)
|
|
t=w.time
|
|
for i in w.waveform:
|
|
if t>=start and t<=end:
|
|
Y.append(i)
|
|
X.append(t)
|
|
t+=d
|
|
T.append(X)
|
|
D.append(Y)
|
|
return (T,D)
|
|
|
|
def ScanMachines(self,path):
|
|
print "Pure virtual function"
|
|
exit(1)
|
|
|
|
def OpenSD(self):
|
|
self.machine=dict()
|
|
self.session=dict()
|
|
self.sessiontimes=dict()
|
|
self.flowrate=dict()
|
|
self.flowtimes=dict();
|
|
|
|
if os.name=="posix":
|
|
posix_mountpoints=["/media","/mnt"]
|
|
d=[]
|
|
for i in posix_mountpoints:
|
|
try:
|
|
a=os.listdir(i)
|
|
for j in range(0,len(a)):
|
|
a[j]=i+os.sep+a[j]
|
|
#print j
|
|
d.extend(a)
|
|
except:
|
|
1
|
|
|
|
elif (os.name=="nt") or (sys.platform=="win32"):
|
|
#Meh.. i'll figure this out later.
|
|
d=['D:','E:','F:','G:','H:','I:','J:']
|
|
#elif sys.platform=="darwin": #Darwin is posix aswell, but where?
|
|
# d=[]
|
|
|
|
r=0
|
|
if not len(d):
|
|
print "I've have no idea where for an SDCard on",os.name,sys.platform
|
|
return 0
|
|
|
|
print "Looking for CPAP data in",d
|
|
for i in d:
|
|
if self.ScanMachines(i):
|
|
r+=1
|
|
|
|
return r
|
|
|
|
def GetDays(self,numdays=7,date=None):
|
|
DAYS=[]
|
|
if (not date):
|
|
dt=DT.now()#localtz.localize(DT.utcnow())-timedelta(hours=24);
|
|
else:
|
|
dt=date
|
|
|
|
for i in range(0,numdays):
|
|
d=dt.date();
|
|
|
|
(sleep,wake)=cpap.GetBedtime(dt)
|
|
if sleep!=None:
|
|
ln=wake-sleep
|
|
b=cpap.GetTotalTime(sleep,wake)
|
|
DAYS.append([d,sleep,wake,ln,b])
|
|
dt-=timedelta(hours=24)
|
|
return DAYS
|
|
|
|
|
|
class PRS1(CPAP):
|
|
codes=dict()
|
|
codes[0]=['UN1',[2,1]]
|
|
codes[1]=['UN2',[2,1]]
|
|
codes[2]=['PR',[2,1]]
|
|
codes[3]=['BP',[2,1,1]]
|
|
codes[4]=['PP',[2,1]]
|
|
codes[5]=['RE',[2,1]]
|
|
codes[6]=['OA',[2,1]]
|
|
codes[7]=['CA',[2,1]]
|
|
codes[0xa]=['H',[2,1]]
|
|
codes[0xb]=['UNB',[2,2]]
|
|
codes[0xc]=['FL',[2,1]]
|
|
codes[0xd]=['VS',[2]]
|
|
codes[0xe]=['UNE',[2,1,1,1]]
|
|
codes[0xf]=['CSR',[2,2,1]]
|
|
codes[0x10]=['UN10',[2,2,1]]
|
|
codes[0x11]=['LR',[2,1,1]]
|
|
codes[0x12]=['SUM',[1,1,2]]
|
|
|
|
def __init__(self):
|
|
CPAP.__init__(self,"Philips Respironics","System One")
|
|
|
|
def ScanMachines(self,path):
|
|
try:
|
|
d=os.listdir(path);
|
|
r=d.index("P-Series");
|
|
except:
|
|
return False
|
|
|
|
path+=os.sep+d[r];
|
|
try:
|
|
d=os.listdir(path);
|
|
except:
|
|
print "Path",path,"unreadable"
|
|
return False
|
|
|
|
prs1unit=[]
|
|
l=0
|
|
for f in d:
|
|
if (f[0]!='P'): continue
|
|
if (f[1].isdigit()):
|
|
if f not in self.machine.keys():
|
|
self.machine[f]=[]
|
|
self.machine[f].append(path+os.sep+f)
|
|
l+=1
|
|
|
|
if not l:
|
|
print "No",self.model,"machine data stored under",path
|
|
return False
|
|
|
|
return True
|
|
|
|
def OpenMachine(self,serial):
|
|
if serial not in self.machine.keys():
|
|
print "Couldn't open device!"
|
|
return False
|
|
|
|
self.session=dict()
|
|
self.sessiontimes=dict()
|
|
self.flowrate=dict()
|
|
self.flowtimes=dict();
|
|
|
|
for path in self.machine[serial]:
|
|
self.ReadMachineData(path,serial)
|
|
|
|
|
|
def ReadMachineData(self,path,serial):
|
|
try:
|
|
d=os.listdir(path);
|
|
r=d.index("p0");
|
|
except:
|
|
print "Expected PRS1's p0 directory, and couldn't find it",path
|
|
return False
|
|
|
|
path+=os.sep+"p0"
|
|
try:
|
|
df=os.listdir(path);
|
|
except:
|
|
print "Couldn't read directory"
|
|
return False
|
|
|
|
|
|
r=0
|
|
for f in df:
|
|
filename=f
|
|
e2=f.rfind('.')
|
|
if (e2<0): continue
|
|
ext=int(f[e2+1:])
|
|
seq=int(f[0:e2])
|
|
|
|
if (ext==2) and (not seq in self.session.keys()):
|
|
if self.Read002(path,filename):
|
|
r+=1
|
|
elif (ext==5) and (not seq in self.flowrate.keys()):
|
|
if self.Read005(path,filename):
|
|
r+=1
|
|
if (r>0):
|
|
print "Loaded",r,"files for",serial
|
|
return True
|
|
|
|
def Read002(self,path,filename):
|
|
fn=path+os.sep+filename
|
|
try:
|
|
f=open(fn,'rb');
|
|
except:
|
|
print "Couldn't Open File",fn
|
|
return False
|
|
|
|
header=f.read(16)
|
|
if (len(header)<16):
|
|
print "Not enough header data in",filename
|
|
f.close()
|
|
return False
|
|
|
|
sm=0
|
|
for i in range(0,15): sm+=ord(header[i])
|
|
sm&=0xff
|
|
|
|
h1=ord(header[0]);
|
|
filesize,=unpack_from('<I',header,1)
|
|
h2=ord(header[5]);
|
|
filetype=ord(header[6]);
|
|
sequence,=unpack_from('<I',header,7)
|
|
timestamp,=unpack_from('<I',header,11)
|
|
checksum=ord(header[15])
|
|
|
|
if (checksum!=sm):
|
|
print "Header checksum error"
|
|
f.close();
|
|
return False
|
|
|
|
if (filetype!=2):
|
|
print "Dodgy File",filename
|
|
f.close()
|
|
return False
|
|
|
|
if (sequence in self.session.keys()):
|
|
f.close()
|
|
return False;
|
|
|
|
starttime=utc.localize(DT.utcfromtimestamp(timestamp))
|
|
|
|
bytes=16;
|
|
done=0
|
|
|
|
datasize=filesize-16-2
|
|
data=f.read(datasize);
|
|
if len(data)<datasize:
|
|
print "Short file",filename
|
|
f.close();
|
|
return False
|
|
chksum=f.read(2)
|
|
f.close()
|
|
|
|
offsets=[5,6,7,0x0a,0x0c]
|
|
td=starttime
|
|
i=0
|
|
#print "Opening",filename
|
|
|
|
self.session[sequence]=dict()
|
|
self.sessiontimes[sequence]=dict()
|
|
|
|
for j in self.CodeTypes:
|
|
self.session[sequence][j]=[]
|
|
|
|
while (i<datasize):
|
|
c=ord(data[i])
|
|
i+=1
|
|
if c not in self.codes.keys():
|
|
print "Unknown PRS1 code",hex(c),"in file",filename
|
|
exit(1)
|
|
gc=0
|
|
if self.codes[c][0] in self.CodeTypes:
|
|
gc=self.CodeTypes.index(self.codes[c][0]);
|
|
fields=[]
|
|
|
|
for j in self.codes[c][1]:
|
|
if (j==1):
|
|
val,=unpack_from('!B',data,i)
|
|
elif (j==2):
|
|
val,=unpack_from('<h',data,i)
|
|
else:
|
|
val=0
|
|
i+=j
|
|
fields.append(val)
|
|
|
|
if c!=0x12: #0x12 is a pressure summary and doesn't have a time field
|
|
td+=timedelta(seconds=fields.pop(0))
|
|
|
|
d=td
|
|
if c in offsets: #Don't adjust the actual time with these offsets or things will break
|
|
d-=timedelta(seconds=fields[0])
|
|
|
|
if (c==0x11 and fields[1]>0): #These events are also classed as vibratory snore
|
|
E=Event(td,c,fields)
|
|
self.session[sequence]['VS'].append(E);
|
|
|
|
if (c==2) or (c==3): #CPAP Pressure
|
|
fields[0]/=10.0
|
|
if c==3: fields[1]/=10.0 #Bipap
|
|
|
|
E=Event(d,c,fields)
|
|
#print E.time.astimezone(localtz),self.CodeTypes[gc],fields
|
|
self.session[sequence][self.CodeTypes[gc]].append(E);
|
|
|
|
self.sessiontimes[sequence]=[starttime,td]
|
|
return True
|
|
|
|
def Read005(self,path,filename):
|
|
#print "Importing file",filename
|
|
fn=path+os.sep+filename
|
|
try:
|
|
f=open(fn,'rb');
|
|
except:
|
|
print "Couldn't Open File",fn
|
|
return False
|
|
|
|
done=0
|
|
blocks=0;
|
|
|
|
starts=None
|
|
while not done:
|
|
header=f.read(24)
|
|
if (len(header)<24):
|
|
if (blocks==0):
|
|
print "Not enough header data in",filename
|
|
f.close()
|
|
return False
|
|
done=1
|
|
break;
|
|
|
|
sm=0
|
|
for i in range(0,23): sm+=ord(header[i])
|
|
sm&=0xff
|
|
|
|
h1=ord(header[0]);
|
|
blocksize,=unpack_from('<h',header,1)
|
|
h2=ord(header[5]);
|
|
filetype=ord(header[6]);
|
|
sequence,=unpack_from('<I',header,7)
|
|
timestamp,=unpack_from('<I',header,11)
|
|
seconds,=unpack_from('<h',header,15)
|
|
checksum=ord(header[23])
|
|
|
|
if (checksum!=sm):
|
|
print filename,"Header checksum error",hex(checksum),hex(sm)
|
|
f.close();
|
|
return False
|
|
|
|
if (filetype!=5):
|
|
print "Dodgy File",filename
|
|
f.close()
|
|
return False
|
|
|
|
if (blocks==0):
|
|
if sequence in self.flowrate.keys():
|
|
f.close()
|
|
print "early close"
|
|
return False
|
|
|
|
starttime=utc.localize(DT.utcfromtimestamp(timestamp))
|
|
|
|
if not starts: starts=starttime
|
|
|
|
readsize=(blocksize-24)-2; #blocksize-header-checksum
|
|
wfdata=f.read(readsize);
|
|
chksum=f.read(2)
|
|
|
|
l=len(wfdata)
|
|
if (l<readsize):
|
|
print "Short data in file",filename
|
|
done=1
|
|
# not sure whether to quit or let it add a dodge record.??
|
|
|
|
SampleRate=(5.0*60.0)/1500.0 #or seconds/readsize
|
|
if (blocks==0):
|
|
self.flowrate[sequence]=[]
|
|
|
|
# def __init__(self,time,waveform,size,duration,format,rate):
|
|
|
|
wave=[]
|
|
for i in wfdata:
|
|
wave.append((ord(i)^128)-128)
|
|
|
|
w=Waveform(starttime,wave,l,timedelta(seconds=seconds),1,SampleRate)
|
|
self.flowrate[sequence].append(w)
|
|
|
|
blocks+=1
|
|
|
|
ends=starttime+timedelta(seconds=seconds)
|
|
self.flowtimes[sequence]=[starts,ends]
|
|
return True
|
|
|
|
|
|
|
|
class Graph:
|
|
xlimits=[];
|
|
ylimits=[];
|
|
name=""
|
|
HL=dict();
|
|
|
|
def __init__(self,name):
|
|
self.name=name
|
|
|
|
def Create(self,w=15,h=1.5,width=600,height=150):
|
|
self.fig=Figure(figsize=(w,h),facecolor='w',dpi=100)
|
|
self.canvas=FigureCanvas(self.fig)
|
|
self.canvas.set_size_request(width,height)
|
|
self.ax=self.fig.add_axes([0.05, 0.15, 0.93, 0.7])
|
|
|
|
def Redraw(self):
|
|
self.canvas.draw()
|
|
def SetAxis(self,ax):
|
|
self.ax=ax
|
|
|
|
def SetTitle(self,name=None,size=12,weight='bold',ha='left',va='bottom'):
|
|
if name:
|
|
self.name=name
|
|
self.ax.set_title(name,ha=ha,va=va,position=[0,1],size=size,weight=weight)
|
|
|
|
def Highlight(self,start,end,color,index=0):
|
|
try:
|
|
self.ax.patches.remove(self.HL[index])
|
|
#self.ax.draw_patches(self.HL[index])
|
|
except: 1
|
|
|
|
if (start<self.xlimits[0]) or (end>self.xlimits[1]): #check start and end are within xlimits
|
|
print "Creating Highlights out of xlimit area is a sucky idea in matplotlib";
|
|
|
|
self.HL[index]=self.ax.axvspan(start,end,facecolor=color,alpha=0.5)
|
|
#self.ax.draw_patches(self.HL[index])
|
|
self.ResetLimits()
|
|
|
|
def SetXLim(self,start,end):
|
|
self.xlimits=[start,end]
|
|
self.ax.set_xlim(self.xlimits)
|
|
|
|
def SetYLim(self,bottom,top):
|
|
self.ylimits=[bottom,top]
|
|
self.ax.set_ylim(self.ylimits)
|
|
|
|
def ResetLimits(self):
|
|
self.ax.set_xlim(self.xlimits)
|
|
self.ax.set_ylim(self.ylimits)
|
|
|
|
def SetDateTicks(self):
|
|
e=self.xlimits[1]-self.xlimits[0]
|
|
self.ax.xaxis.set_major_formatter(DateFormatter("%H:%M",tz=localtz))
|
|
if e>=timedelta(hours=10):
|
|
self.ax.xaxis.set_major_locator(HourLocator(range(0,100,2),tz=localtz))
|
|
self.ax.xaxis.set_minor_locator(MinuteLocator(range( 0,100,10),tz=localtz))
|
|
elif e>=timedelta(hours=4):
|
|
self.ax.xaxis.set_major_locator(HourLocator(range(0,100,1),tz=localtz))
|
|
self.ax.xaxis.set_minor_locator(MinuteLocator(range( 0,100,5),tz=localtz))
|
|
elif e>=timedelta(seconds=3600):
|
|
self.ax.xaxis.set_major_locator(MinuteLocator(range(0,100,30),tz=localtz))
|
|
self.ax.xaxis.set_minor_locator(MinuteLocator(range( 0,100,1),tz=localtz))
|
|
elif e>=timedelta(seconds=1200):
|
|
self.ax.xaxis.set_major_locator(MinuteLocator(range(0,100,5),tz=localtz))
|
|
self.ax.xaxis.set_minor_locator(SecondLocator(range( 0,100,15),tz=localtz))
|
|
elif e>=timedelta(seconds=300):
|
|
self.ax.xaxis.set_major_locator(MinuteLocator(range(0,100,1),tz=localtz))
|
|
self.ax.xaxis.set_minor_locator(SecondLocator(range(0,100,5),tz=localtz))
|
|
else:
|
|
self.ax.xaxis.set_major_locator(SecondLocator(range(0,100,30),tz=localtz))
|
|
self.ax.xaxis.set_minor_locator(SecondLocator(range(0,100,1),tz=localtz))
|
|
self.ax.xaxis.set_major_formatter(DateFormatter("%H:%M:%S",tz=localtz))
|
|
|
|
|
|
class LeaksGraph(Graph):
|
|
def __init__(self,cpap,xlim=[0,0],ylim=[0,129],grid=True):
|
|
Graph.__init__(self,"Leak Rate")
|
|
#self.ax=ax
|
|
self.machine=cpap
|
|
self.Create()
|
|
self.ylimits=ylim
|
|
self.grid=grid
|
|
self.xlimits=xlim
|
|
self.T=[]
|
|
self.D=[]
|
|
|
|
def Update(self,start,end):
|
|
if self.xlimits:
|
|
if (start==self.xlimits[0]) and (end==self.xlimits[1]):
|
|
return
|
|
|
|
#(start,end)=self.machine.FirstLastEventTime('LR',start,end)
|
|
|
|
(self.T,self.D)=self.machine.GetEventsPlot('LR',start=start,end=end,padsession=True)
|
|
self.xlimits=[self.T[0],self.T[-1]]
|
|
|
|
avg=sum(self.D)/len(self.D)
|
|
for i in range(0,len(self.D)):
|
|
self.D[i]-=avg;
|
|
|
|
print "Average Leaks:",avg
|
|
|
|
def Plot(self):
|
|
self.ax.cla()
|
|
self.SetTitle(self.name)
|
|
if (self.grid): self.ax.grid(True);
|
|
|
|
|
|
if len(self.T)>0:
|
|
self.ax.plot_date(self.T,self.D,'black',aa=True,tz=localtz)
|
|
self.ax.fill_between(self.T,self.D,0,color='gray')
|
|
|
|
|
|
self.SetDateTicks()
|
|
self.ax.yaxis.set_major_locator(MultipleLocator(20))
|
|
self.ax.yaxis.set_minor_locator(MultipleLocator(5))
|
|
|
|
self.ResetLimits()
|
|
|
|
|
|
class PressureGraph(Graph):
|
|
def __init__(self,cpap,xlim=[0,0],ylim=[1,20],grid=True):
|
|
self.name="Pressure"
|
|
#self.ax=ax
|
|
self.machine=cpap
|
|
self.Create()
|
|
self.ylimits=ylim
|
|
self.grid=grid
|
|
self.xlimits=xlim
|
|
self.T=[]
|
|
self.D=[]
|
|
|
|
def Update(self,start,end):
|
|
if self.xlimits:
|
|
if (start==self.xlimits[0]) and (end==self.xlimits[1]):
|
|
return
|
|
|
|
#(start,end)=self.machine.FirstLastEventTime('LR',start,end)
|
|
self.xlimits=[start,end]
|
|
|
|
(self.T,self.D)=self.machine.GetEventsPlot('PR',start=start,end=end)
|
|
(self.T1,self.D1)=self.machine.GetEventsPlot('BP',start=start,end=end,di=0)
|
|
(self.T2,self.D2)=self.machine.GetEventsPlot('BP',start=start,end=end,di=1)
|
|
|
|
#for i in range(0,len(self.D)):
|
|
# self.D[i]/=10.0;
|
|
#avg=sum(self.D)/len(self.D)
|
|
|
|
#print "Average Pressure:",avg
|
|
|
|
def Plot(self):
|
|
self.ax.cla()
|
|
self.SetTitle(self.name)
|
|
if (self.grid): self.ax.grid(True);
|
|
|
|
if len(self.T)>0:
|
|
self.ax.plot_date(self.T,self.D,'green',aa=True,tz=localtz)
|
|
if (len(self.T1)>0):
|
|
self.ax.plot_date(self.T1,self.D2,'orange',aa=True,tz=localtz)
|
|
if (len(self.T2)>0):
|
|
self.ax.plot_date(self.T2,self.D2,'purple',aa=True,tz=localtz)
|
|
|
|
self.SetDateTicks()
|
|
self.ax.yaxis.set_major_locator(MultipleLocator(5))
|
|
self.ax.yaxis.set_minor_locator(MultipleLocator(1))
|
|
|
|
self.ResetLimits()
|
|
|
|
|
|
class SleepFlagsGraph(Graph):
|
|
colours=['','y','r','k','b','c','m','g']
|
|
flags=['','RE','VS','FL','H','OA','CA','CSR']
|
|
barcolors=['w','#ffffd0','#ffdfdf','#efefef','#d0d0ff','#cfefff','#ebcdef','#dfffdf','w'];
|
|
|
|
marker='.'
|
|
|
|
def __init__(self,cpap,waveform,xlim=[0,0],ylim=[0,10],grid=True):
|
|
self.name="Sleep Flags"
|
|
self.waveform=waveform
|
|
#self.ax=ax
|
|
self.machine=cpap
|
|
self.Create(height=175)
|
|
self.ylimits=[0,len(self.flags)]
|
|
self.grid=grid
|
|
self.xlimits=xlim
|
|
self.T=dict()
|
|
self.D=dict()
|
|
self.canvas.mpl_connect('pick_event', self.onpick)
|
|
self.canvas.mpl_connect('button_press_event', self.on_press)
|
|
self.canvas.mpl_connect('button_release_event', self.on_release)
|
|
#self.canvas.mpl_connect('scroll_event', self.on_scroll)
|
|
self.lastscroll=DT.now()
|
|
self.scrollsteps=0
|
|
|
|
#self.canvas.mpl_connect('motion_notify_event', self.on_motion)
|
|
|
|
def onpick(self,event):
|
|
N = len(event.ind)
|
|
if not N: return True
|
|
return True
|
|
thisline = event.artist
|
|
xdata, ydata = thisline.get_data()
|
|
ind = event.ind
|
|
wavedelta=timedelta(seconds=300) #self.waveform.xlimits[1]-self.waveform.xlimits[0]
|
|
|
|
d=timedelta(seconds=wavedelta.seconds/2)
|
|
self.waveform.xlimits[0]=xdata[ind][0]-d
|
|
if (self.waveform.xlimits[0]<self.xlimits[0]): self.waveform.xlimits[0]=self.xlimits[0]
|
|
if (self.waveform.xlimits[0]>(self.xlimits[1]-wavedelta)): self.waveform.xlimits[0]=self.xlimits[1]-wavedelta
|
|
self.waveform.xlimits[1]=self.waveform.xlimits[0]+wavedelta;
|
|
|
|
self.Highlight(self.waveform.xlimits[0],self.waveform.xlimits[1],'orange')
|
|
self.Redraw()
|
|
self.waveform.ResetLimits()
|
|
self.waveform.SetDateTicks()
|
|
self.waveform.Redraw()
|
|
|
|
def do_scroll(self,steps,event):
|
|
ct=DT.now()
|
|
if (ct<self.scrolltime):
|
|
return
|
|
|
|
if steps!=self.scrollsteps:
|
|
return False
|
|
|
|
#print "Do Scroll...",steps,event
|
|
centre=self.WaveStart+timedelta(seconds=self.WaveDelta.seconds/2,microseconds=self.WaveDelta.microseconds/2)
|
|
|
|
ZoomValue=60;
|
|
|
|
s=self.WaveDelta.seconds
|
|
steps=-steps
|
|
self.WaveDelta+=timedelta(seconds=ZoomValue*steps)
|
|
if (self.WaveDelta<timedelta(seconds=60)):
|
|
self.WaveDelta=timedelta(seconds=60)
|
|
self.WaveStart=centre-timedelta(seconds=self.WaveDelta.seconds/2)
|
|
self.WaveEnd=self.WaveStart+self.WaveDelta
|
|
|
|
self.Highlight(self.WaveStart,self.WaveEnd,'orange')
|
|
self.Redraw()
|
|
self.waveform.SetXLim(self.WaveStart,self.WaveEnd)
|
|
self.waveform.SetDateTicks()
|
|
self.waveform.Redraw()
|
|
|
|
|
|
return False
|
|
|
|
def on_scroll(self,event):
|
|
if event.inaxes != self.ax: return False
|
|
|
|
ct=DT.now()
|
|
lt=ct-self.lastscroll;
|
|
self.lastscroll=ct
|
|
if (self.scrollsteps==0 or lt<timedelta(microseconds=200000)):
|
|
self.scrollsteps+=event.step
|
|
self.scrolltime=ct+timedelta(microseconds=400000);
|
|
self.scrolltimer=gobject.timeout_add(500, self.do_scroll,self.scrollsteps,event)
|
|
#callback in 500ms
|
|
return
|
|
|
|
|
|
self.scrollsteps=0
|
|
return True
|
|
|
|
def on_press(self,event):
|
|
if event.inaxes != self.ax: return
|
|
|
|
contains, attrd = self.ax.patch.contains(event)
|
|
if not contains: return
|
|
#print 'event contains', self.ax.patch.xy
|
|
x0, y0 = self.ax.patch.xy
|
|
self.press = event.xdata, event.ydata
|
|
|
|
def on_release(self,event):
|
|
if event.inaxes != self.ax: return
|
|
#if event.inaxes != self.ax: return
|
|
minx=min(event.xdata,self.press[0]);
|
|
maxx=max(event.xdata,self.press[0]);
|
|
d1=num2date(minx,tz=localtz)
|
|
d2=num2date(maxx,tz=localtz)
|
|
|
|
wd=self.waveform.xlimits[1]-self.waveform.xlimits[0]
|
|
|
|
if (d2-d1)<timedelta(seconds=120):
|
|
if (wd>timedelta(seconds=3600)): wd=timedelta(seconds=300)
|
|
self.waveform.xlimits[0]=d1-timedelta(seconds=wd.seconds/2);
|
|
self.waveform.xlimits[1]=self.waveform.xlimits[0]+wd
|
|
else:
|
|
self.waveform.xlimits[0]=d1
|
|
self.waveform.xlimits[1]=d2
|
|
|
|
if (self.waveform.xlimits[0]<self.xlimits[0]):
|
|
self.waveform.xlimits[0]=self.xlimits[0]
|
|
self.waveform.xlimits[1]=self.xlimits[0]+wd
|
|
|
|
if (self.waveform.xlimits[1]>self.xlimits[1]):
|
|
self.waveform.xlimits[1]=self.xlimits[1]
|
|
self.waveform.xlimits[0]=self.xlimits[1]-wd
|
|
|
|
self.Highlight(self.waveform.xlimits[0],self.waveform.xlimits[1],'orange')
|
|
self.Redraw()
|
|
|
|
self.waveform.ResetLimits();
|
|
self.waveform.SetDateTicks()
|
|
self.waveform.Redraw()
|
|
|
|
def Update(self,start,end):
|
|
if self.xlimits:
|
|
if (start==self.xlimits[0]) and (end==self.xlimits[1]):
|
|
return
|
|
|
|
#(start,end)=self.machine.FirstLastEventTime('LR',start,end)
|
|
self.xlimits=[start,end]
|
|
|
|
#if self.waveform:
|
|
#self.waveform.xlimits[0]=start
|
|
#self.WaveDelta=timedelta(seconds=300)
|
|
#self.waveform.xlimits[1]=start+self.WaveDelta
|
|
|
|
j=0
|
|
for i in self.flags:
|
|
if (i=="CSR"):
|
|
self.T[i]=[]
|
|
E=self.machine.GetEvents(i,start=start,end=end)
|
|
for e in E:
|
|
r=e.time-timedelta(seconds=e.data[1])-timedelta(seconds=e.data[0]/2)
|
|
self.T[i].append(r)
|
|
self.D[i]=[j]*len(self.T[i])
|
|
elif (i!=""):
|
|
(self.T[i],self.D[i])=self.machine.GetEventsPlot(i,start=start,end=end,dc=j)
|
|
|
|
j+=1
|
|
|
|
def Plot(self):
|
|
self.ax.cla()
|
|
self.SetTitle(self.name)
|
|
if (self.grid): self.ax.grid(True);
|
|
|
|
j=0;
|
|
for i in self.flags:
|
|
if (i!=""):
|
|
if (len(self.T[i])>0):
|
|
self.ax.plot_date(self.T[i],self.D[i],self.colours[j]+self.marker,picker=5,aa=False,tz=localtz,alpha=1)
|
|
j+=1
|
|
|
|
self.SetDateTicks()
|
|
self.ax.yaxis.set_major_locator(MultipleLocator(1))
|
|
self.ax.set_yticklabels(self.flags)
|
|
|
|
yTicks=[0]
|
|
yTicks.extend(self.ax.get_yticks())
|
|
h=(yTicks[1]-yTicks[0])
|
|
for i in range(1,len(yTicks)):
|
|
yTicks[i]-=h/2
|
|
a1=date2num(self.xlimits[0])
|
|
a2=date2num(self.xlimits[1])
|
|
self.ax.barh(yTicks, [a2-a1]*len(yTicks), height=h, left=a1, color=self.barcolors,alpha=0.5)
|
|
|
|
self.ResetLimits()
|
|
|
|
|
|
class WaveformGraph(Graph):
|
|
colours=['y','r','k','b','c','m','g']
|
|
flags=['RE','VS','FL','H','OA','CA']
|
|
|
|
def __init__(self,cpap,xlim=[0,0],ylim=[-69,69],grid=True):
|
|
self.name="Flow Rate Waveform"
|
|
#self.ax=ax
|
|
self.machine=cpap
|
|
self.Create()
|
|
self.ylimits=ylim
|
|
self.grid=grid
|
|
self.xlimits=xlim
|
|
self.T=[]
|
|
self.D=[]
|
|
self.FT=dict()
|
|
self.FD=dict()
|
|
self.canvas.mpl_connect('button_press_event', self.on_press)
|
|
self.canvas.mpl_connect('button_release_event', self.on_release)
|
|
self.canvas.mpl_connect('scroll_event', self.on_scroll)
|
|
self.lastscroll=DT.now()
|
|
self.scrollsteps=0
|
|
self.sg=None
|
|
|
|
def set_sleepgraph(self,sg):
|
|
self.sg=sg
|
|
|
|
def on_press(self,event):
|
|
if event.inaxes != self.ax: return
|
|
|
|
contains, attrd = self.ax.patch.contains(event)
|
|
if not contains: return
|
|
#print 'event contains', self.ax.patch.xy
|
|
x0, y0 = self.ax.patch.xy
|
|
self.press = event.xdata, event.ydata
|
|
|
|
def on_release(self,event):
|
|
if event.inaxes != self.ax: return
|
|
#if event.inaxes != self.ax: return
|
|
#minx=min(event.xdata,self.press[0]);
|
|
#maxx=max(event.xdata,self.press[0]);
|
|
d1=num2date(self.press[0],tz=localtz)
|
|
d2=num2date(event.xdata,tz=localtz)
|
|
d=d2-d1
|
|
self.xlimits[0]-=d
|
|
self.xlimits[1]-=d
|
|
if self.xlimits[0]<self.sg.xlimits[0]:
|
|
self.xlimits[0]=self.sg.xlimits[0]
|
|
if self.xlimits[1]>self.sg.xlimits[1]:
|
|
self.xlimits[1]=self.sg.xlimits[1]
|
|
self.xlimits[0]=self.xlimits[1]-d
|
|
|
|
#update SleepGraph
|
|
if (self.sg):
|
|
self.sg.Highlight(self.xlimits[0],self.xlimits[1],'orange')
|
|
self.sg.Redraw()
|
|
|
|
self.ResetLimits()
|
|
self.SetDateTicks()
|
|
self.Redraw()
|
|
|
|
def do_scroll(self,steps,event):
|
|
ct=DT.now()
|
|
if (ct<self.scrolltime):
|
|
return
|
|
|
|
if steps!=self.scrollsteps:
|
|
return False
|
|
|
|
#print "Do Scroll...",steps,event
|
|
|
|
wavedelta=self.xlimits[1]-self.xlimits[0];
|
|
centre=self.xlimits[0]+timedelta(seconds=wavedelta.seconds/2,microseconds=wavedelta.microseconds/2)
|
|
#centre=num2date(event.xdata,tz=localtz)
|
|
ZoomValue=60;
|
|
|
|
steps=-steps
|
|
wavedelta+=timedelta(seconds=ZoomValue*steps)
|
|
if (wavedelta<timedelta(seconds=60)):
|
|
wavedelta=timedelta(seconds=60)
|
|
self.xlimits[0]=centre-timedelta(seconds=wavedelta.seconds/2)
|
|
self.xlimits[1]=self.xlimits[0]+wavedelta
|
|
|
|
if (self.sg):
|
|
self.sg.Highlight(self.xlimits[0],self.xlimits[1],'orange')
|
|
self.sg.Redraw()
|
|
self.SetDateTicks()
|
|
self.ResetLimits();
|
|
self.Redraw()
|
|
|
|
|
|
return False
|
|
|
|
def on_scroll(self,event):
|
|
if event.inaxes != self.ax: return False
|
|
|
|
ct=DT.now()
|
|
lt=ct-self.lastscroll;
|
|
self.lastscroll=ct
|
|
if (self.scrollsteps==0 or lt<timedelta(microseconds=200000)):
|
|
self.scrollsteps+=event.step
|
|
self.scrolltime=ct+timedelta(microseconds=400000);
|
|
self.scrolltimer=gobject.timeout_add(500, self.do_scroll,self.scrollsteps,event)
|
|
#callback in 500ms
|
|
return
|
|
|
|
|
|
self.scrollsteps=0
|
|
return True
|
|
|
|
|
|
def Update(self,start,end):
|
|
if self.xlimits:
|
|
if (start==self.xlimits[0]) and (end==self.xlimits[1]):
|
|
return
|
|
|
|
self.xlimits=[start,end]
|
|
(self.T,self.D)=self.machine.GetFlowPlots(start=start,end=end)
|
|
|
|
for i in self.flags:
|
|
(self.FT[i],self.FD[i])=self.machine.GetEventsPlot(i,start=start,end=end,dc=50)
|
|
|
|
self.FT['CSR']=self.machine.GetEvents('CSR',start=start,end=end)
|
|
|
|
(self.FT['PP'],self.FD['PP'])=self.machine.GetEventsPlot('PP',start=start,end=end,dc=20)
|
|
|
|
(self.FT['PR'],self.FD['PR'])=self.machine.GetEventsPlot('PR',start=start,end=end)
|
|
|
|
self.FD['PUP']=[]
|
|
self.FT['PUP']=[]
|
|
self.FD['PDN']=[]
|
|
self.FT['PDN']=[]
|
|
|
|
lastpressure=0
|
|
for i in range(0,len(self.FD['PR'])):
|
|
cod=None
|
|
p=self.FD['PR'][i]
|
|
if p>lastpressure: cod="PUP"
|
|
elif p<lastpressure: cod="PDN"
|
|
|
|
if (cod):
|
|
self.FT[cod].append(self.FT['PR'][i])
|
|
self.FD[cod].append(-65)
|
|
|
|
lastpressure=p
|
|
|
|
def Plot(self):
|
|
self.ax.cla()
|
|
self.SetTitle(self.name)
|
|
if (self.grid): self.ax.grid(True);
|
|
|
|
j=0
|
|
for i in self.flags:
|
|
if (len(self.FT[i])>0):
|
|
self.ax.plot_date(self.FT[i],self.FD[i],self.colours[j]+'d',aa=True,alpha=.8,tz=localtz)
|
|
self.ax.vlines(self.FT[i],50,-50,self.colours[j],lw=1,alpha=0.4)
|
|
j+=1
|
|
|
|
j=0
|
|
|
|
for i in range(0,len(self.T)):
|
|
self.ax.plot_date(self.T[i],self.D[i],'green',aa=True,tz=localtz,alpha=0.7)
|
|
|
|
self.SetDateTicks()
|
|
self.ax.yaxis.set_major_locator(MultipleLocator(20))
|
|
self.ax.yaxis.set_minor_locator(MultipleLocator(5))
|
|
|
|
if (len(self.FT['PUP'])):
|
|
self.ax.plot_date(self.FT['PUP'],self.FD['PUP'],'k^',aa=True,alpha=.8,tz=localtz)
|
|
if (len(self.FT['PDN'])):
|
|
self.ax.plot_date(self.FT['PDN'],self.FD['PDN'],'kv',aa=True,alpha=.8,tz=localtz)
|
|
|
|
if (len(self.FT['PP'])):
|
|
self.ax.plot_date(self.FT['PP'],self.FD['PP'],'r.',aa=True,alpha=.8,tz=localtz)
|
|
|
|
for E in self.FT['CSR']:
|
|
e=E.time-timedelta(seconds=E.data[1]);
|
|
s=e-timedelta(seconds=E.data[0])
|
|
self.ax.axvspan(s,e,facecolor='#d0ffd0');
|
|
#self.Highlight(s,e,color="#d0ffd0",index=j)
|
|
|
|
self.ResetLimits()
|
|
|
|
def AboutBox(a):
|
|
txt='''<big>SleepyHead v0.02</big>
|
|
|
|
<b>Details:</b>
|
|
Author: Mark Watkins (jedimark)
|
|
|
|
<b>License:</b>
|
|
This software is released under the <i>GNU Public Licence</i>.
|
|
|
|
<b>Disclaimer:</b>
|
|
This is <b>not</b> medical software. Any output this program
|
|
produces should <b>not</b> be used to make medical decisions.
|
|
|
|
<b>Special Thanks:</b>
|
|
Mike Hoolehan - Check out his awesome Onkor Project
|
|
Troy Schultz - For great technical advice
|
|
Mark Bruscke - For encouragement and advice
|
|
|
|
and to the very awesome <a href='http://www.cpaptalk.com'>CPAPTalk Forum</a>
|
|
'''
|
|
msg=gtk.MessageDialog(flags=gtk.DIALOG_MODAL,type=gtk.MESSAGE_INFO,buttons=gtk.BUTTONS_CLOSE)
|
|
msg.set_markup(txt)
|
|
|
|
msg.run()
|
|
msg.destroy()
|
|
|
|
def CreateMenu():
|
|
file_menu = gtk.Menu()
|
|
open_item = gtk.MenuItem("_Backup SD Card")
|
|
save_item = gtk.MenuItem("_Print")
|
|
quit_item = gtk.MenuItem("E_xit")
|
|
file_menu.append(open_item)
|
|
file_menu.append(save_item)
|
|
file_menu.append(quit_item)
|
|
quit_item.connect_object ("activate", lambda x: gtk.main_quit(), "file.quit")
|
|
open_item.show()
|
|
save_item.show()
|
|
quit_item.show()
|
|
|
|
help_menu = gtk.Menu()
|
|
about_item = gtk.MenuItem("_About")
|
|
about_item.connect_object("activate",AboutBox,"help.about")
|
|
help_menu.append(about_item)
|
|
about_item.show()
|
|
|
|
file_item = gtk.MenuItem("_File")
|
|
file_item.show()
|
|
help_item = gtk.MenuItem("_Help")
|
|
help_item.show()
|
|
|
|
menu_bar = gtk.MenuBar()
|
|
menu_bar.show()
|
|
file_item.set_submenu(file_menu)
|
|
menu_bar.append(file_item)
|
|
|
|
help_item.set_submenu(help_menu)
|
|
menu_bar.append(help_item)
|
|
|
|
return menu_bar
|
|
|
|
|
|
class DailyGraphs:
|
|
def __init__(self,cpap):
|
|
self.cpap=cpap
|
|
self.layout=gtk.ScrolledWindow()
|
|
self.layout.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
|
|
self.vbox = gtk.VBox()
|
|
self.layout.add_with_viewport(self.vbox)
|
|
|
|
self.graph=dict();
|
|
self.graph['Waveform']=WaveformGraph(cpap)
|
|
self.graph['Leaks']=LeaksGraph(cpap)
|
|
self.graph['Pressure']=PressureGraph(cpap)
|
|
self.graph['SleepFlags']=SleepFlagsGraph(cpap,self.graph['Waveform'])
|
|
self.graph['Waveform'].set_sleepgraph(self.graph['SleepFlags'])
|
|
|
|
self.vbox.pack_start(self.graph['SleepFlags'].canvas,expand=False)
|
|
self.vbox.pack_start(self.graph['Waveform'].canvas,expand=False)
|
|
self.vbox.pack_start(self.graph['Leaks'].canvas,expand=False)
|
|
self.vbox.pack_start(self.graph['Pressure'].canvas,expand=False)
|
|
|
|
self.machines=gtk.combo_box_new_text()
|
|
|
|
for mach in cpap.machine.keys():
|
|
self.machines.append_text(mach)
|
|
|
|
self.datesel=gtk.Calendar()
|
|
|
|
self.textbox=gtk.TextView(buffer=None)
|
|
self.textbox.set_editable(False)
|
|
|
|
self.datesel.connect('month_changed',self.cal_month_selected,cpap)
|
|
self.datesel.connect('day_selected',self.cal_day_selected)
|
|
|
|
self.machines.connect("changed",self.select_machine,cpap)
|
|
|
|
self.databox = gtk.VBox(homogeneous=False)
|
|
self.rescanbutton=gtk.Button("_Rescan Media")
|
|
self.rescanbutton.connect('pressed',self.pushed_rescan)
|
|
#self.zeobutton=gtk.Button("Load _ZEO Data")
|
|
#self.oxibutton=gtk.Button("Load _Oximeter Data")
|
|
self.databox.pack_start(self.machines,expand=False,padding=2)
|
|
self.databox.pack_start(self.datesel,expand=False,padding=2)
|
|
self.databox.pack_start(self.rescanbutton,expand=False,padding=0)
|
|
#self.databox.pack_start(self.zeobutton,expand=False,padding=0)
|
|
#self.databox.pack_start(self.oxibutton,expand=False,padding=0)
|
|
self.databox.pack_start(self.textbox,expand=True,padding=2)
|
|
|
|
self.machines.set_active(0)
|
|
#self.cal_month_selected(self.datesel,cpap)
|
|
#self.cal_day_selected(self.datesel)
|
|
|
|
def select_machine(self,combo,cpap):
|
|
msg=gtk.MessageDialog(type=gtk.MESSAGE_INFO,buttons=gtk.BUTTONS_NONE,message_format="Please wait, Loading CPAP Data")
|
|
msg.show_all()
|
|
gtk.gdk.window_process_all_updates()
|
|
|
|
mach=combo.get_active_text();
|
|
cpap.OpenMachine(mach)
|
|
msg.destroy()
|
|
self.cal_month_selected(self.datesel,self.cpap)
|
|
self.cal_day_selected(self.datesel)
|
|
|
|
def pushed_rescan(self,event):
|
|
|
|
self.cpap.OpenSD()
|
|
|
|
mach=self.machines.get_active_text();
|
|
self.machines.get_model().clear()
|
|
j=0
|
|
cmi=-1
|
|
for m in cpap.machine.keys():
|
|
i=self.machines.insert_text(j,m)
|
|
if (m==mach): cmi=j
|
|
j+=1
|
|
|
|
if (cmi>=0):
|
|
self.machines.set_active(cmi)
|
|
else:
|
|
self.machines.set_active(0)
|
|
|
|
|
|
#cpap.OpenMachine(mach)
|
|
|
|
self.cal_month_selected(self.datesel,self.cpap)
|
|
#self.cal_day_selected(self.datesel)
|
|
|
|
def Draw(self):
|
|
for k,v in self.graph.iteritems():
|
|
v.Redraw()
|
|
|
|
def ShowGraphs(self,show):
|
|
if show:
|
|
vis=True
|
|
else:
|
|
vis=False
|
|
|
|
for i in self.graph.keys():
|
|
self.graph[i].canvas.set_visible(vis)
|
|
|
|
def Update(self,start,end):
|
|
for k,v in self.graph.iteritems():
|
|
v.Update(start,end)
|
|
sess=cpap.GetFlowSessions(start,end)
|
|
if (len(sess)>0):
|
|
wvis=True;
|
|
else: wvis=False;
|
|
self.graph['Waveform'].canvas.set_visible(wvis)
|
|
text="Date: "+start.astimezone(localtz).strftime("%Y-%m-%d")+"\n\n"
|
|
text+="Bedtime: "+start.astimezone(localtz).strftime("%H:%M:%S")+"\n"
|
|
text+="Waketime: "+end.astimezone(localtz).strftime("%H:%M:%S")+"\n\n"
|
|
|
|
tt=cpap.GetTotalTime(start,end)
|
|
text+="Total Time: "+str(tt)+"\n\n"
|
|
|
|
if not wvis:
|
|
text+="No Waveform Data Available\n\n"
|
|
oa=cpap.CountEvents('OA',start,end)
|
|
h=cpap.CountEvents('H',start,end)
|
|
ah=oa+h
|
|
ca=cpap.CountEvents('CA',start,end)
|
|
fl=cpap.CountEvents('FL',start,end)
|
|
vs=cpap.CountEvents('VS',start,end)
|
|
re=cpap.CountEvents('RE',start,end)
|
|
|
|
PR=cpap.GetEvents('PR',start,end)
|
|
|
|
if (len(PR)>0):
|
|
avgp=0
|
|
laste=PR[0]
|
|
lastp=int(PR[0].data[0]*10)
|
|
lastt=PR[0].time
|
|
TPR=[timedelta(seconds=0)]*256
|
|
don=False
|
|
totalptime=timedelta(0)
|
|
for e in PR[1:]:
|
|
p=int(e.data[0]*10)
|
|
TPR[lastp]+=(e.time-lastt)
|
|
totalptime+=(e.time-lastt)
|
|
lastt=e.time
|
|
lastp=p
|
|
|
|
|
|
#if (not don):
|
|
# TPR[lastp]+=lastt-
|
|
|
|
np=timedelta(seconds=totalptime.seconds*.9)
|
|
npc=timedelta(seconds=0)
|
|
npp=0
|
|
lastp=0
|
|
for i in range(0,256):
|
|
lpc=npc
|
|
npc+=TPR[i]
|
|
if (npc>=np):
|
|
s2=1-(float(lpc.seconds)/float(npc.seconds))
|
|
d=(i-lastp)/10.0
|
|
npp=(lastp/10.0)+(s2*d)
|
|
break
|
|
|
|
if TPR[i]>timedelta(seconds=0):
|
|
lastp=i
|
|
|
|
avgp=0
|
|
|
|
sm1=0
|
|
sm2=0
|
|
sm3=0
|
|
for i in range(0,256):
|
|
if TPR[i]>timedelta(seconds=0):
|
|
s=float(TPR[i].seconds)/float(totalptime.seconds)
|
|
sm1+=s*float(i)
|
|
sm2+=s
|
|
sm3=(float(i)/10.0)*TPR[i].seconds
|
|
|
|
#avgp=sm3/totalptime.seconds
|
|
avgp=sm1/sm2/10.0 #Weighted Average
|
|
else:
|
|
avgp=0
|
|
npp=0
|
|
|
|
LK=cpap.GetEvents('LR',start,end);
|
|
avgl=0
|
|
for e in LK: avgl+=e.data[0]-19
|
|
avgl/=len(LK)
|
|
|
|
|
|
|
|
CSR=cpap.GetEvents('CSR',start,end);
|
|
dur=0
|
|
for e in CSR: dur+=e.data[0];
|
|
csr=(100.0/tt.seconds)*dur
|
|
|
|
text+="Average Pressure=%(#)0.2f\n"%{'#':avgp}
|
|
text+="90%% Pressure=%(#)0.2f\n\n"%{'#':npp}
|
|
|
|
text+="CSR %% of night=%(#)0.2f\n" % {"#":csr}
|
|
|
|
s=tt.seconds/3600.0
|
|
text+="OA=%(#)0.2f\n"%{'#':oa/s}
|
|
text+="H=%(#)0.2f\n"%{'#':h/s}
|
|
text+="CA=%(#)0.2f\n"%{'#':ca/s}
|
|
text+="FL=%(#)0.2f\n"%{'#':fl/s}
|
|
text+="VS=%(#)0.2f\n"%{'#':vs/s}
|
|
text+="RE=%(#)0.2f\n"%{'#':re/s}
|
|
text+="AHI=%(#)0.2f\n\n"%{'#':ah/s}
|
|
|
|
text+="Leak=%(#)0.2f\n"%{'#':avgl}
|
|
|
|
|
|
buf=self.textbox.get_buffer()
|
|
buf.set_text(text)
|
|
#self.date.set_text())
|
|
# self.bedtime.set_text("Bedtime: "+start.astimezone(localtz).strftime("%H:%M:%S"))
|
|
# self.waketime.set_text("Waketime: "+end.astimezone(localtz).strftime("%H:%M:%S"))
|
|
|
|
def Plot(self):
|
|
for k,v in self.graph.iteritems():
|
|
v.Plot()
|
|
self.graph['Waveform'].ResetLimits()
|
|
|
|
def cal_month_selected(self,cal,cpap):
|
|
(y,m,d)=cal.get_date();
|
|
|
|
d=1
|
|
m+=2
|
|
|
|
if (m>11):
|
|
y+=1
|
|
m%=12
|
|
|
|
ldom=DT(y,m,d,0,0,0)-timedelta(hours=1)
|
|
#print "Getting",ldom.day,"days back from",ldom
|
|
D=cpap.GetDays(ldom.day,date=ldom)
|
|
cal.freeze()
|
|
for i in range(0,ldom.day-1):
|
|
cal.unmark_day(i)
|
|
|
|
for i in D:
|
|
cal.mark_day(i[0].day)
|
|
|
|
cal.thaw()
|
|
|
|
def cal_day_selected(self,cal):
|
|
(y,m,d)=cal.get_date()
|
|
dat=DT(y,m+1,d,0,0,0)
|
|
(st,et)=cpap.GetBedtime(dat)
|
|
|
|
if st:
|
|
msg=gtk.MessageDialog(type=gtk.MESSAGE_INFO,buttons=gtk.BUTTONS_NONE,message_format="Updating Plots - Please wait")
|
|
msg.show_all()
|
|
gtk.gdk.window_process_all_updates()
|
|
|
|
self.ShowGraphs(True)
|
|
#print "Bedtime",st.astimezone(localtz),"Wakeup",et.astimezone(localtz)
|
|
self.Update(st,et)
|
|
self.Plot()
|
|
self.Draw()
|
|
msg.destroy()
|
|
else:
|
|
self.ShowGraphs(False)
|
|
text="No data available for selected date"
|
|
buf=self.textbox.get_buffer()
|
|
buf.set_text(text)
|
|
|
|
|
|
path="/home/mark/.sleepyhead/CMS50"
|
|
#cms50=CMS50X()
|
|
#(event,wave)=cms50.Record(path)
|
|
|
|
#exit(1)
|
|
|
|
cpap=PRS1()
|
|
cpap.OpenSD()
|
|
|
|
win=gtk.Window()
|
|
win.connect("destroy", lambda x: gtk.main_quit())
|
|
win.set_default_size(1200,680)
|
|
win.set_title("SleepyHead v0.02")
|
|
|
|
mainbox=gtk.VBox()
|
|
mainbox.pack_start(CreateMenu(),expand=False)
|
|
|
|
notebook=gtk.Notebook()
|
|
notebook.unset_flags(gtk.CAN_FOCUS)
|
|
|
|
dailybox=gtk.HBox()
|
|
|
|
spo2box=gtk.HBox()
|
|
|
|
|
|
mainbox.pack_start(notebook,expand=True)
|
|
|
|
DG=DailyGraphs(cpap)
|
|
dailybox.pack_start(DG.databox,expand=False)
|
|
dailybox.pack_start(DG.layout,expand=True)
|
|
|
|
page1=notebook.insert_page(dailybox,gtk.Label("Daily"))
|
|
#page2=notebook.insert_page(dailybox,gtk.Label("Overview"))
|
|
#page3=notebook.insert_page(spo2box,gtk.Label("SpO2"))
|
|
notebook.set_current_page(page1)
|
|
|
|
win.add(mainbox)
|
|
|
|
|
|
win.show_all()
|
|
gtk.main()
|
|
|
|
|
|
|