4 matplotlib.use('GTKAgg')
11 from mpl_toolkits.axes_grid.parasite_axes import SubplotHost
12 from driver.contec import *
19 driver = CMS50(port=port, verbose=verbose)
22 print "Not plugged in?"
25 # apt-get install python-matplotlib
27 def fmt_time_tick(tick):
29 return "%02d:%02d"%((tick/60)%24, tick % 60)
36 self.last_packet = { 'hr': 0, 'spo2': 0 }
37 self.background = None
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)
50 self.canvas = self.fig.canvas
52 self.hr_host = SubplotHost(self.fig, 111)
53 self.fig.add_subplot(self.hr_host)
55 self.fast_x = arange(self.fast_size)
56 self.slow_x = arange(self.slow_size)
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([])
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())
77 #self.fig.legend((self.hr_line, spo2_line), ('HR', 'SpO2'), 'upper left')
78 self.hr_host.legend(loc="lower center")
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)
85 self.manager = get_current_fig_manager()
87 toolbar = self.manager.toolbar
88 self.label = gtk.Label('No Reading')
91 self.user_field = gtk.Entry(max=30)
92 self.user_field.set_text('self')
93 self.user_field.show()
95 vbox = self.manager.vbox
97 l = gtk.Label('Status: ')
99 hbox.pack_start(l, False, False)
100 hbox.pack_start(self.label, False, False)
101 l = gtk.Label(' User: ')
103 hbox.pack_start(l, False, False)
104 hbox.pack_start(self.user_field, False, False)
106 vbox.pack_start(hbox, False, False)
107 vbox.reorder_child(toolbar, -1)
111 self.b_pause = Button(axes([0.5, button_y, 0.1, button_h]), "Pause")
112 self.b_pause.on_clicked(self.pause)
114 self.b_test = Button(axes([0.6, button_y, 0.1, button_h]), "Test")
115 self.b_test.on_clicked(self.test)
117 self.b_review = Button(axes([0.8, button_y, 0.1, button_h]), "Review")
118 self.b_review.on_clicked(self.review)
120 self.b_quit = Button(axes([0.9, button_y, 0.09, button_h]), "Quit")
121 self.b_quit.on_clicked(self.quit)
128 def quit(self, event):
131 def pause(self, event):
132 self.is_pause = not self.is_pause
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 ""))
138 def update_background(self):
139 if self.background is None:
141 #self.background = self.canvas.copy_from_bbox(pulse_ax.bbox)
142 self.background = self.canvas.copy_from_bbox(self.hr_host.bbox)
144 def test(self, event):
146 'date': datetime.datetime.today(),
152 { 'hr': 55, 'spo2': 99, 'valid': True },
153 { 'hr': 57, 'spo2': 98, 'valid': True },
154 { 'hr': 58, 'spo2': 97, 'valid': True },
156 self.update_upload(head, packets)
158 def handle_upload(self, head_packet):
159 self.label.set_markup("Uploading, please wait...")
162 packet = driver.read_packet()
165 if packet['type'] == 'upload':
167 if packet['type'] != 'upload_data':
169 packets.append(packet)
170 length = len(packets)
171 print "got %d packets"%(length)
173 start_time = datetime.time(head_packet['hours'], head_packet['minutes'])
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)
183 def review(self, event):
184 user = self.user_field.get_text()
185 files = os.listdir(self.user_dir(user))
188 print "no files in user dir"
190 head_packet, packets = self.load_upload(files[-1])
191 self.update_upload(head_packet, packets)
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")
198 line = file.readline()
201 line = file.readline()
204 line = line.strip("\n")
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)
214 'hours': packets[0]['date'].hour,
215 'minutes': packets[0]['date'].minute,
216 'date': packets[0]['date']
218 return head_packet, packets
220 def save_upload(self, head_packet, packets):
221 length = len(packets)
222 start = head_packet['date']
224 user = self.user_field.get_text()
225 target_dir = self.user_dir(user)
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"
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'])
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)
249 self.upload_host.set_ylabel("Rate")
250 self.upload_spo2_host.set_ylabel("SpO2")
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 ]
257 for ind in range(1, len(packets)):
258 if not packets[ind]['valid']:
261 quiet_count = quiet_count - 1
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
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()
288 def update_ui(self, *args):
289 self.update_background()
291 self.label.set_markup("Not plugged in")
295 packet = driver.read_packet()
297 self.label.set_markup("No Connection")
299 if packet['type'] == 'upload':
300 self.handle_upload(packet)
305 self.count = self.count + 1
306 packet = driver.read_packet()
308 self.label.set_markup("No Connection")
310 if packet['type'] == 'upload':
311 self.handle_upload(packet)
315 self.pulse1_y = append(self.pulse1_y[1:], packet['pulse1'])
317 self.pulse1_y = append(self.pulse1_y[1:], None)
318 self.pulse1_line.set_data(self.fast_x, self.pulse1_y)
320 if not packet['valid']:
321 self.label.set_markup("No Reading")
323 if packet['beat'] and packet['valid']:
325 self.last_packet = packet
327 if self.count % (60 * App.SEC_PER_SLOW) == 0:
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'])
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)
338 # Optimize drawing the pulse
339 # Instead of calling self.canvas.draw(), just draw the pulse itself and
340 # blit it to the window.
342 # This doesn't clear properly for some reason
343 #self.canvas.restore_region(self.background, bbox=self.pulse_ax.bbox)
345 self.canvas.restore_region(self.background)
346 self.pulse_ax.draw_artist(self.pulse1_line)
347 self.canvas.blit(self.pulse_ax.bbox)
353 gobject.idle_add(app.update_ui)