OSCAR-code/history/cpap.py
2024-01-31 19:14:19 -05:00

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()