axis labels, work when not plugged in
[quantself:qschart.git] / oximeter
1 #!/usr/bin/python
2
3 import matplotlib
4 matplotlib.use('GTKAgg')
5 from pylab import *
6 import gtk
7 import gobject
8 import pdb
9 import os
10 import datetime
11 from mpl_toolkits.axes_grid.parasite_axes import SubplotHost
12 from driver.contec import *
13
14
15 port = '/dev/ttyUSB0'
16 verbose = True
17
18 try:
19     driver = CMS50(port=port, verbose=verbose)
20 except Exception, e:
21     print e
22     print "Not plugged in?"
23     driver = None
24
25 # apt-get install python-matplotlib
26
27 def fmt_time_tick(tick):
28     tick = tick / 60
29     return "%02d:%02d"%((tick/60)%24, tick % 60)
30
31 class App:
32     SEC_PER_SLOW = 5
33
34     def __init__(self):
35         self.is_pause = False
36         self.last_packet = { 'hr': 0, 'spo2': 0 }
37         self.background = None
38         self.slow_size = 360
39         self.fast_size = 100
40         self.setup_ui()
41
42     def user_dir(self, user):
43         target_dir = os.path.join(os.environ['HOME'], ".quantself", "om", user)
44         if not os.access(target_dir, os.F_OK):
45             os.makedirs(target_dir)
46         return target_dir
47
48     def setup_ui(self):
49         self.fig = figure(1)
50         self.canvas = self.fig.canvas
51
52         self.hr_host = SubplotHost(self.fig, 111)
53         self.fig.add_subplot(self.hr_host)
54
55         self.fast_x = arange(self.fast_size)
56         self.slow_x = arange(self.slow_size)
57
58         self.pulse1_y = [ None for x in zeros(self.fast_size)]
59         self.pulse_ax = self.fig.add_axes((0.3,0.7,0.1,0.1), title="Pulse")
60         self.pulse1_line, = self.pulse_ax.plot(self.fast_x, self.pulse1_y, '-')
61         self.pulse_ax.axis([0, self.fast_size, 0, 100])
62         self.pulse_ax.set_xticks([])
63         self.pulse_ax.set_yticks([])
64
65         spo2_host = self.hr_host.twinx()
66         self.hr_y = [ None for x in zeros(self.slow_size)]
67         self.hr_line, = self.hr_host.plot(self.slow_x, self.hr_y, 'r-', linewidth=2, label="Rate")
68         self.spo2_y = [ None for x in zeros(self.slow_size)]
69         self.spo2_line, = spo2_host.plot(self.slow_x, self.spo2_y, 'g-', linewidth=2, label="SpO2")
70         self.hr_host.axis([0, self.slow_size, 40, 150])
71         spo2_host.axis([0, self.slow_size, 70, 100])
72         self.hr_host.set_ylabel("Rate")
73         spo2_host.set_ylabel("SpO2")
74         self.hr_host.axis["left"].label.set_color(self.hr_line.get_color())
75         spo2_host.axis["right"].label.set_color(self.spo2_line.get_color())
76
77         #self.fig.legend((self.hr_line, spo2_line), ('HR', 'SpO2'), 'upper left')
78         self.hr_host.legend(loc="lower center")
79
80         self.hr_host.set_xticks(range(0, self.slow_size, 60))
81         self.hr_host.set_xticks(range(0, self.slow_size, 12), minor=True)
82         self.hr_host.set_xticklabels([fmt_time_tick((self.slow_size - tick)*App.SEC_PER_SLOW) for tick in range(0, self.slow_size, 60)])
83         self.hr_host.axis["bottom"].major_ticklabels.set_rotation(45)
84
85         self.manager = get_current_fig_manager()
86
87         toolbar = self.manager.toolbar
88         self.label = gtk.Label('No Reading')
89         self.label.show()
90
91         self.user_field = gtk.Entry(max=30)
92         self.user_field.set_text('self')
93         self.user_field.show()
94
95         vbox = self.manager.vbox
96         hbox = gtk.HBox()
97         l = gtk.Label('Status: ')
98         l.show()
99         hbox.pack_start(l, False, False)
100         hbox.pack_start(self.label, False, False)
101         l = gtk.Label('  User: ')
102         l.show()
103         hbox.pack_start(l, False, False)
104         hbox.pack_start(self.user_field, False, False)
105         hbox.show()
106         vbox.pack_start(hbox, False, False)
107         vbox.reorder_child(toolbar, -1)
108
109         button_h = 0.05
110         button_y = 0.94
111         self.b_pause = Button(axes([0.5, button_y, 0.1, button_h]), "Pause")
112         self.b_pause.on_clicked(self.pause)
113
114         self.b_test = Button(axes([0.6, button_y, 0.1, button_h]), "Test")
115         self.b_test.on_clicked(self.test)
116
117         self.b_review = Button(axes([0.8, button_y, 0.1, button_h]), "Review")
118         self.b_review.on_clicked(self.review)
119
120         self.b_quit = Button(axes([0.9, button_y, 0.09, button_h]), "Quit")
121         self.b_quit.on_clicked(self.quit)
122
123         self.fig.show()
124
125
126         self.count = 0
127
128     def quit(self, event):
129         exit(0)
130
131     def pause(self, event):
132         self.is_pause = not self.is_pause
133         self.make_label()
134
135     def make_label(self):
136         self.label.set_markup("HR %d / SpO2 %d   %s"%(self.last_packet['hr'], self.last_packet['spo2'], "PAUSED" if self.is_pause else ""))
137
138     def update_background(self):
139         if self.background is None:
140             #pdb.set_trace()
141             #self.background = self.canvas.copy_from_bbox(pulse_ax.bbox)
142             self.background = self.canvas.copy_from_bbox(self.hr_host.bbox)
143
144     def test(self, event):
145         head = {
146             'date': datetime.datetime.today(),
147             'hours': 10,
148             'minutes': 55,
149             'seconds': 0,
150         }
151         packets = [
152             { 'hr': 55, 'spo2': 99, 'valid': True },
153             { 'hr': 57, 'spo2': 98, 'valid': True },
154             { 'hr': 58, 'spo2': 97, 'valid': True },
155         ]
156         self.update_upload(head, packets)
157
158     def handle_upload(self, head_packet):
159         self.label.set_markup("Uploading, please wait...")
160         packets = []
161         while True:
162             packet = driver.read_packet()
163             if packet is None:
164                 return
165             if packet['type'] == 'upload':
166                 continue
167             if packet['type'] != 'upload_data':
168                 break
169             packets.append(packet)
170         length = len(packets)
171         print "got %d packets"%(length)
172
173         start_time = datetime.time(head_packet['hours'], head_packet['minutes'])
174
175         today = datetime.date.today()
176         start = datetime.datetime.combine(today, start_time)
177         if start + datetime.timedelta(0, length) > datetime.datetime.today():
178             start = datetime.datetime.combine(today - datetime.timedelta(1), start_time)
179         head_packet['date'] = start
180         self.save_upload(head_packet, packets)
181         self.update_upload(head_packet, packets)
182
183     def review(self, event):
184         user = self.user_field.get_text()
185         files = os.listdir(self.user_dir(user))
186         files.sort()
187         if len(files) == 0:
188             print "no files in user dir"
189             return
190         head_packet, packets = self.load_upload(files[-1])
191         self.update_upload(head_packet, packets)
192
193     def load_upload(self, file):
194         user = self.user_field.get_text()
195         source_file = os.path.join(self.user_dir(user), file)
196         file = open(source_file, "r")
197         # header
198         line = file.readline()
199         packets = []
200         while True:
201             line = file.readline()
202             if line == '':
203                 break
204             line = line.strip("\n")
205             packet = {}
206             packet['date'], packet['hr'], packet['spo2'], packet['valid'], packet['byte0'] = line.split("\t")
207             packet['date'] = datetime.datetime.strptime(packet['date'], "%Y-%m-%d %H:%M:%S")
208             packet['valid'] = packet['valid'] != "0"
209             packet['hr'] = int(packet['hr'])
210             packet['spo2'] = int(packet['spo2'])
211             packets.append(packet)
212         file.close()
213         head_packet = {
214             'hours': packets[0]['date'].hour,
215             'minutes': packets[0]['date'].minute,
216             'date': packets[0]['date']
217         }
218         return head_packet, packets
219
220     def save_upload(self, head_packet, packets):
221         length = len(packets)
222         start = head_packet['date']
223
224         user = self.user_field.get_text()
225         target_dir = self.user_dir(user)
226
227         target_file = os.path.join(target_dir, start.strftime(user + "-%Y-%m-%d-%H-%M.csv"))
228         if os.access(target_file, os.F_OK):
229             os.remove(target_file)
230         file = open(target_file, "w")
231         print "saving upload to %s"%(target_file)
232         print >> file, "time\theartrate\tspo2\tvalid\tbyte0"
233         count = 0
234         for packet in packets:
235             dt = start + datetime.timedelta(0, count)
236             print >> file, "%s\t%d\t%d\t%d\t%d"%(dt.isoformat(" "), packet['hr'], packet['spo2'], 1 if packet['valid'] else 0, packet['byte0'])
237             count = count + 1
238         file.close()
239
240
241     def update_upload(self, head_packet, packets):
242         self.upload_fig = figure(2)
243         self.upload_host = SubplotHost(self.upload_fig, 111, title=head_packet['date'].date().isoformat())
244         self.upload_fig.add_subplot(self.upload_host)
245         self.upload_host.plot([1,1], [2,4])
246         self.upload_spo2_host = self.upload_host.twinx()
247         self.upload_host.axis["bottom"].major_ticklabels.set_rotation(45)
248
249         self.upload_host.set_ylabel("Rate")
250         self.upload_spo2_host.set_ylabel("SpO2")
251
252         start = (head_packet['hours'] * 60 + head_packet['minutes']) * 60
253         upload_x = range(start, start + len(packets))
254         hr_y = [ packet['hr'] if packet['valid'] else None for packet in packets ]
255         spo2_y = [ packet['spo2'] if packet['valid'] else None for packet in packets ]
256         quiet_count = 0
257         for ind in range(1, len(packets)):
258             if not packets[ind]['valid']:
259                 quiet_count = 60
260             if quiet_count > 0:
261                 quiet_count = quiet_count - 1
262                 hr_y[ind] = None
263                 spo2_y[ind] = None
264
265         hr_line, = self.upload_host.plot(upload_x, hr_y, 'r-', linewidth=2, label="Rate")
266         spo2_line, = self.upload_spo2_host.plot(upload_x, spo2_y, 'g-', linewidth=2, label="SpO2")
267         self.upload_host.axis["left"].label.set_color(self.hr_line.get_color())
268         self.upload_spo2_host.axis["right"].label.set_color(self.spo2_line.get_color())
269         self.upload_host.axis([start, start + len(packets), 40, 150])
270         self.upload_spo2_host.axis([start, start +len(packets), 70, 100])
271         minutes = len(packets) / 60
272         if minutes < 10:
273             spacing = 1
274         elif minutes < 60:
275             spacing = 5
276         elif minutes < 120:
277             spacing = 10
278         else:
279             spacing = 30
280         ticks = range(start, start+len(packets), spacing * 60)
281         self.upload_host.set_xticks(ticks)
282         minor_ticks = range(start, start+len(packets), spacing * 12)
283         self.upload_host.set_xticks(minor_ticks, minor=True)
284         self.upload_host.set_xticklabels([fmt_time_tick(tick) for tick in ticks], rotation=90, fontsize=8)
285         self.upload_fig.show()
286         self.upload_fig.canvas.draw()
287
288     def update_ui(self, *args):
289         self.update_background()
290         if driver is None:
291             self.label.set_markup("Not plugged in")
292             return
293         if self.is_pause:
294             for ind in range(5):
295                 packet = driver.read_packet()
296                 if packet is None:
297                     self.label.set_markup("No Connection")
298                     continue
299                 if packet['type'] == 'upload':
300                     self.handle_upload(packet)
301                     return True
302             return True
303
304         for ind in range(5):
305             self.count = self.count + 1
306             packet = driver.read_packet()
307             if packet is None:
308                 self.label.set_markup("No Connection")
309                 continue
310             if packet['type'] == 'upload':
311                 self.handle_upload(packet)
312                 return True
313
314             if packet['valid']:
315                 self.pulse1_y = append(self.pulse1_y[1:], packet['pulse1'])
316             else:
317                 self.pulse1_y = append(self.pulse1_y[1:], None)
318             self.pulse1_line.set_data(self.fast_x, self.pulse1_y)
319
320             if not packet['valid']:
321                 self.label.set_markup("No Reading")
322
323             if packet['beat'] and packet['valid']:
324                 self.make_label()
325                 self.last_packet = packet
326
327             if self.count % (60 * App.SEC_PER_SLOW) == 0:
328                 if packet['valid']:
329                     self.hr_y = append(self.hr_y[1:], self.last_packet['hr'])
330                     self.spo2_y = append(self.spo2_y[1:], self.last_packet['spo2'])
331                 else:
332                     self.hr_y = append(self.hr_y[1:], None)
333                     self.spo2_y = append(self.spo2_y[1:], None)
334                 self.hr_line.set_data(self.slow_x, self.hr_y)
335                 self.spo2_line.set_data(self.slow_x, self.spo2_y)
336                 self.canvas.draw()
337
338             # Optimize drawing the pulse
339             # Instead of calling self.canvas.draw(), just draw the pulse itself and
340             # blit it to the window.
341
342             # This doesn't clear properly for some reason
343             #self.canvas.restore_region(self.background, bbox=self.pulse_ax.bbox)
344
345             self.canvas.restore_region(self.background)
346             self.pulse_ax.draw_artist(self.pulse1_line)
347             self.canvas.blit(self.pulse_ax.bbox)
348         return True
349
350 app = App()
351
352
353 gobject.idle_add(app.update_ui)
354 show()