jupyterpidaq.DAQinstance
1# Tools for using a Jupyter notebook as a lab notebook that collects and 2# displays data from an analog to digital converter in real time. The interface 3# also allows for annotation, analysis and display of the data using common 4# python tools. Common activities can be done using menus and buttons rather 5# than typing python commands. 6# J. Gutow <gutow@uwosh.edu> July 2021 7# license GPL V3 or greater. 8 9###### 10# Environment setup 11###### 12# Use os tools for file path and such 13import os 14import time 15import logging 16 17# Start Logging 18import JPSLUtils 19 20logname = 'DAQinstance_' + time.strftime('%y-%m-%d_%H%M%S', 21 time.localtime()) + '.log' 22logging.basicConfig(filename=logname, level=logging.WARN) 23# logging.basicConfig(filename=logname, level=logging.DEBUG) 24 25# below is equivalent to %matplotlib notebook in a Jupyter cell 26from IPython import get_ipython 27 28ipython = get_ipython() 29print('Importing drivers and searching for available data acquisition ' 30 'hardware.',end='') 31 32# imports below must work. Allow normal python error response. 33import ipywidgets as widgets 34from pandas_GUI import * 35print ('.',end='') 36 37from plotly import io as pio 38pio.templates.default = "simple_white" #default plot format 39from plotly import graph_objects as go 40print ('.',end='') 41 42import numpy as np 43print ('.',end='') 44 45import pandas as pd 46print ('.',end='') 47 48from IPython.display import display, HTML 49from IPython.display import Javascript as JS 50 51print ('.',end='') 52 53# Below allows asynchronous calls to get and plot the data in real time. 54# Actually read the DAQ board on a different process. 55import threading 56from multiprocessing import Process, Pipe 57 58print('.',end='') 59 60# The process that monitors the board 61from jupyterpidaq.DAQProc import DAQProc 62 63print('.',end='') 64 65# Board definitions 66from jupyterpidaq import Boards as boards 67 68print('.',end='') 69 70global availboards 71availboards = boards.load_boards() 72 73print('.',end='') 74 75# GUI for settings 76from jupyterpidaq.ChannelSettings import ChannelSettings 77 78print('.',end='') 79 80# Sensor definitions 81from jupyterpidaq.Sensors import sensors 82 83print('.',end='') 84 85# JPSL Utilities 86from JPSLMenus import * 87from JPSLUtils import * 88 89print('.',end='') 90 91# globals to put stuff in from threads. 92data = [] # all data from DAQ tools avg_values 93stdev = [] # all standard deviations 94timestamp = [] # all timestamps 95 96# global list to keep track of runs 97runs = [] 98 99###### 100# Interactive elements definitions 101###### 102 103# Locate JupyterPiDAQ package directory 104mydir = os.path.dirname( 105 __file__) # absolute path to directory containing this file. 106 107# Add a "DAQ Menu" to the notebook. 108tempJSfile = open(os.path.join(mydir, 'javascript', 'JupyterPiDAQmnu.js')) 109tempscript = '<script type="text/javascript">' 110tempscript += tempJSfile.read() + '</script>' 111tempJSfile.close() 112display(HTML(tempscript)) 113JPSLUtils.OTJS('createCmdMenu()') 114 115print('Done with setup.') 116 117# cleanup log file if it is empty 118logging.shutdown() 119try: 120 if os.stat(logname).st_size == 0: 121 os.remove(logname) 122except FileNotFoundError: 123 pass 124 125# Data Aquistion Instance (a run). 126class DAQinstance(): 127 def __init__(self, idno, title='None', ntraces=4, **kwargs): 128 """ 129 Data Aquistion Instance (a run). 130 131 :param idno : id number you wish to use to keep track 132 :param title: optional name 133 :param ntraces: number of traces (default = 4) more than 4 easily 134 overwhelms a pi4. 135 :param kwargs: 136 :ignore_skew: bool (default: True) if True only a single average 137 collection time will be recorded for each time in a multichannel 138 data collection. If False a separate set of time will be 139 recorded for each channel. 140 """ 141 from plotly import graph_objects as go 142 self.ignore_skew = kwargs.pop('ignore_skew',True) 143 self.idno = idno 144 self.livefig = go.FigureWidget(layout_template='simple_white') 145 self.PLTconn, self.DAQconn = Pipe() 146 self.DAQCTL, self.PLTCTL = Pipe() 147 self.pltthread = threading.Thread(target=self.updatingplot, args=( 148 self.PLTconn, self.PLTCTL)) 149 self.title = str(title) 150 self.svname = title + '.jpidaq.html' 151 self.averaging_time = 0.1 # seconds adjusted based on collection rate 152 self.gain = [1] * ntraces 153 self.data = [] 154 self.timestamp = [] 155 self.stdev = [] 156 self.pandadf = None 157 self.ntraces = ntraces 158 self.separate_plots = True 159 self.traces = [] 160 # index map from returned data to trace, 161 self.tracemap = [] 162 self.tracefrdatachn = [] 163 self.tracelbls = [] 164 self.units = [] 165 for i in range(self.ntraces): 166 self.traces.append(ChannelSettings(i, availboards)) 167 self.ratemax = 20.0 # Hz 168 self.rate = 1.0 # Hz 169 self.deltamin = 1 / self.ratemax 170 self.delta = 1.0 / self.rate 171 self.setupbtn = widgets.Button( 172 description='Set Parameters', 173 disabled=False, 174 button_style='info', 175 # 'success', 'info', 'warning', 'danger' or '' 176 tooltip='Click to set collection parameters to displayed values.', 177 icon='') 178 self.collectbtn = widgets.Button( 179 description='Start Collecting', 180 disabled=False, 181 button_style='success', 182 # 'success', 'info', 'warning', 'danger' or '' 183 tooltip='Start collecting data and plotting it. ' 184 'Will make new graph.', 185 icon='') 186 self.separate_traces_checkbox = widgets.Checkbox( 187 value = self.separate_plots, 188 description = 'Display multiple traces as stacked graphs. ' \ 189 'Uncheck to display all on the same graph.', 190 layout = widgets.Layout(width='80%'), 191 disabled = False) 192 self.rateinp = widgets.BoundedFloatText( 193 value=self.rate, 194 min=0, 195 max=self.ratemax, 196 step=self.ratemax / 1000.0, 197 description='Rate (Hz):', 198 disabled=False) 199 self.timelbl = widgets.Text( 200 value='Time(s)', 201 placeholder='Type something', 202 description='X-axis label (time):', 203 disabled=False) 204 self.runtitle = widgets.Text( 205 value=self.title, 206 placeholder='Type title/description', 207 description='Run title', 208 disabled=False) 209 self.defaultparamtxt = '' 210 self.defaultcollecttxt = '<span style="color:blue;"> To accurately ' 211 self.defaultcollecttxt += 'read point location on graph you can zoom ' 212 self.defaultcollecttxt += 'in. Drag to zoom. Additional controls show ' 213 self.defaultcollecttxt += 'above plot when you hover over it.</span>' 214 self.collecttxt = widgets.HTML( 215 value=self.defaultcollecttxt, 216 placeholder='', 217 description='') 218 self.setup_layout_bottom = widgets.HBox( 219 [self.rateinp, self.timelbl, self.setupbtn]) 220 self.setup_layout = widgets.VBox([self.separate_traces_checkbox, 221 self.setup_layout_bottom]) 222 self.collect_layout = widgets.HBox([self.collectbtn, self.collecttxt]) 223 self.output = widgets.Output() 224 def _make_defaultparamtxt(self): 225 """ 226 Uses AdvancedHTMLParser (mimics javascript) to generate valid HTML for 227 the default parameter text. 228 :return: valid html string for the default parameter text. 229 """ 230 from AdvancedHTMLParser import AdvancedTag as domel 231 run_info=domel('div') 232 run_info.setAttribute('id','DAQRun_' + str(self.idno) + '_info') 233 run_info.setAttribute('class','run_info') 234 run_id = domel('table') 235 run_id.setAttribute('id','run_id') 236 run_id.setAttribute('border', '1') 237 tr = domel('tr') 238 tr.appendInnerHTML('<th>Title</th><th>Id #</th>') 239 run_id.appendChild(tr) 240 tr = domel('tr') 241 tr.appendInnerHTML('<td>' + str(self.title) + '</td>' \ 242 '<td>' + str(self.idno) + '</td>') 243 run_id.appendChild(tr) 244 run_info.appendChild(run_id) 245 # table of run parameters 246 run_param = domel('table') 247 run_param.setAttribute('border','1') 248 run_param.setAttribute('id','run_param') 249 tr = domel('tr') 250 tr.setAttribute('style','text-align:center;') 251 tr.appendInnerHTML('<th>Approx. Rate (Hz)</th>' \ 252 '<th>Approx. Delta (s)</th>' \ 253 '<th>X-label </th>' \ 254 '<th>X-cols</th>' \ 255 '<th>Y-cols</th>' \ 256 '<th>err-cols<sup ' \ 257 'style="color:blue;">a</sup></th>' \ 258 '<th>One Plot</th>' ) 259 run_param.appendChild(tr) 260 run_info.appendChild(run_param) 261 tr = domel('tr') 262 tr.setAttribute('style','text-align:center;') 263 tr.appendInnerHTML('<td>' + str(self.rate) + '</td>' \ 264 '<td>' + str(self.delta) + '</td>' \ 265 '<td>' + self.timelbl.value + '</td>') 266 xlist = '[' 267 ylist = '[' 268 errlist = '[' 269 if self.ignore_skew: 270 xlist += '0]' 271 tempcount = 0 272 for k in self.traces: 273 if k.isactive: 274 ylist += str(2 * tempcount + 1) + ',' 275 errlist += str(2 * tempcount + 2) + ',' 276 tempcount += 1 277 ylist = ylist[:-1] + ']' 278 errlist = errlist[:-1] + ']' 279 else: 280 tempcount = 0 281 for k in self.traces: 282 if k.isactive: 283 xlist += str(3 * tempcount) + ',' 284 ylist += str(3 * tempcount + 1) + ',' 285 errlist += str(3 * tempcount + 2) + ',' 286 tempcount += 1 287 xlist = xlist[:-1] + ']' 288 ylist = ylist[:-1] + ']' 289 errlist = errlist[:-1] + ']' 290 tr.appendInnerHTML('<td>' + xlist + '</td><td>' + ylist + '</td>') 291 td = domel('td') 292 td.appendText(errlist) 293 tr.appendChild(td) 294 td = domel('td') 295 td.appendText(str(not (self.separate_plots))) 296 tr.appendChild(td) 297 run_param.appendChild(tr) 298 footer = domel('tfoot') 299 tr = domel('tr') 300 td = domel('td') 301 td.setAttribute('colspan','7') 302 td.appendInnerHTML('<sup style="color:blue;">a</sup>The ' \ 303 'standard deviation of the number in the ' \ 304 'column immediately to the left based on the ' \ 305 'variation in signal during the averaging time ' \ 306 'for the data point.') 307 tr.appendChild(td) 308 footer.appendChild(tr) 309 run_param.appendChild(footer) 310 # table of trace information 311 traceinfo = domel('table') 312 traceinfo.setAttribute('class','traceinfo') 313 traceinfo.setAttribute('id','traceinfo') 314 traceinfo.setAttribute('border','1') 315 tr = domel('tr') 316 tr.setAttribute('style','text-align:center;') 317 tr.appendInnerHTML('<th>Trace #</th><th>Title</th><th>Units</th>' \ 318 '<th>Board</th><th>Channel</th><th>Sensor</th>' \ 319 '<th>Gain</th>') 320 traceinfo.appendChild(tr) 321 for i in range(self.ntraces): 322 if (self.traces[i].isactive): 323 self.tracemap.append(i) 324 tr = domel('tr') 325 tr.setAttribute('style', 'text-align:center;') 326 tr.appendInnerHTML('<td>' + str(i) + '</td>' \ 327 '<td>' + self.traces[i].tracelbl.value + '</td>' \ 328 '<td>' + self.traces[i].units.value + '</td>' \ 329 '<td>' + str(self.traces[i].boardchoice.value) + \ 330 ' ' + self.traces[i].board.name + '</td>' \ 331 '<td>' + str(self.traces[i].channel) + '</td>' \ 332 '<td >' + self.traces[i].sensorchoice.value + '</td>' \ 333 '<td>' + str(self.traces[i].gains.value) + '</td>') 334 traceinfo.appendChild(tr) 335 run_info.appendChild(traceinfo) 336 return run_info.asHTML() 337 338 def _load_from_html(self, file): 339 """ 340 Loads data and parameters for a completed run from a saved html file. 341 :param file: filename or path. 342 :return: 343 """ 344 import pandas as pd 345 from JPSLUtils import find_pandas_dataframe_names 346 from AdvancedHTMLParser import AdvancedHTMLParser as parser 347 from plotly import graph_objects as go 348 whichrun = pd.read_html(file, attrs={'id': 'run_id'})[0] 349 run_title = whichrun['Title'][0] 350 run_id = whichrun['Id #'][0] 351 svname = pd.read_html(file, attrs={'id': 'file_info'})[0]['Saved ' \ 352 'as'][0] 353 self.pandadf = pd.read_html(file, attrs={'class': 'dataframe'}, 354 index_col=0)[0] 355 self.title = run_title 356 self.svname = svname 357 run_param = \ 358 pd.read_html(file, attrs={'id': 'run_param'}, skiprows=[2])[0] 359 self.rate = run_param['Approx. Rate (Hz)'][0] 360 self.delta = run_param['Approx. Delta (s)'][0] 361 # reassiging timelbl to a value from a widget 362 self.timelbl = run_param['X-label'][0] 363 self.separate_plots = not (run_param['One Plot'][0]) 364 xcols = list(map(int,run_param['X-cols'][0].replace('[', 365 '').replace(']','').split(','))) 366 ycols = list(map(int,run_param['Y-cols'][0].replace('[', 367 '').replace(']','').split(','))) 368 errcols = list(map(int,run_param['err-colsa'][0].replace('[', 369 '').replace(']', '').split(','))) 370 htmldatafile = parser(file) 371 self.defaultparamtxt = htmldatafile.getElementsByClassName( 372 'run_info')[0].asHTML() 373 traceinfo = pd.read_html(file, attrs={'id': 'traceinfo'})[0] 374 for k in traceinfo.index: 375 # Do not refill the widgets. This truncates and changes the 376 # definitions of some things from widgets to values. 377 self.traces[k].isactive = True 378 self.traces[k].tracelbl= traceinfo['Title'][k] 379 self.traces[k].units = traceinfo['Units'][k] 380 boardchoice, boardname = (traceinfo['Board'][k]).split(' ',1) 381 self.traces[k].boardchoice = boardchoice 382 self.traces[k].board = boardname 383 self.traces[k].channel = traceinfo['Channel'][k] 384 self.traces[k].gains= traceinfo['Gain'][k] 385 self.traces[k].sensor = traceinfo['Sensor'][k] 386 # Plot 387 if self.separate_plots: 388 self.livefig.set_subplots(rows=len(ycols), cols=1, 389 shared_xaxes=True) 390 self.livefig.update_xaxes( 391 title=self.timelbl, row=len(ycols), col=1) 392 else: 393 self.livefig.update_xaxes(title=self.timelbl) 394 self.livefig.update_yaxes(title="Values") 395 for i in range(len(ycols)): 396 namestr = self.pandadf.columns[ycols[i]] 397 xcol = None 398 if len(xcols) == 1: 399 xcol = xcols[0] 400 else: 401 xcol = xcols[i] 402 scat = go.Scatter( 403 y=self.pandadf.iloc[0:, ycols[i]], 404 x=self.pandadf.iloc[0:,xcol], name=namestr) 405 if self.separate_plots: 406 self.livefig.update_yaxes(title=self.traces[i].units, 407 row=i+1, col=1) 408 self.livefig.add_trace(scat, row=i+1, col=1) 409 else: 410 self.livefig.add_trace(scat) 411 pass 412 413 def setupclick(self, btn): 414 # Could just use the values in widgets, but this forces intentional 415 # selection and locks them for the run. 416 from copy import copy 417 self.title = copy(self.runtitle.value) 418 self.rate = copy(self.rateinp.value) 419 self.delta = 1 / self.rate 420 self.separate_plots = copy(self.separate_traces_checkbox.value) 421 self.defaultparamtxt = self._make_defaultparamtxt() 422 self.runtitle.close() 423 del self.runtitle 424 self.setup_layout.close() 425 del self.setup_layout 426 self.output.clear_output() 427 doRun(runs[self.idno-1]) 428 with self.output: 429 display(runs[self.idno - 1].livefig) 430 pass 431 432 def setup(self): 433 with self.output: 434 display(HTML("<h3 id ='RunSetUp' " 435 "style='text-align:center;'>Set Run Parameters</h3>")) 436 self.setupbtn.on_click(self.setupclick) 437 display(self.runtitle) 438 for i in range(self.ntraces): 439 self.traces[i].setup() 440 display(self.setup_layout) 441 display(self.output) 442 pass 443 444 def collectclick(self, btn): 445 if (btn.description == 'Start Collecting'): 446 btn.description = 'Stop Collecting' 447 btn.button_style = 'danger' 448 btn.tooltip = 'Stop the data collection' 449 # do not allow parameters to be reset after starting run. 450 self.setupbtn.disabled = True 451 self.setupbtn.tooltip = 'Parameters locked. The run has started.' 452 self.rateinp.disabled = True 453 self.timelbl.disabled = True 454 nactive = 0 455 for k in self.traces: 456 if k.isactive: 457 nactive += 1 458 whichchn = [] 459 gains =[] 460 for i in range(self.ntraces): 461 if (self.traces[i].isactive): 462 brd = self.traces[i].board 463 chn = self.traces[i].channel 464 newchn = True 465 if len(whichchn) > 0: 466 for k in range(len(whichchn)): 467 if whichchn[k]['board'] == brd \ 468 and whichchn[k]['chnl'] == chn: 469 self.tracefrdatachn.append(k) 470 newchn = False 471 if newchn: 472 whichchn.append({'board': brd, 473 'chnl': chn}) 474 gains.append(self.traces[i].toselectedgain) 475 self.tracefrdatachn.append(len(whichchn)-1) 476 # Use up to 30% of the time for averaging if channels were spaced 477 # evenly between data collection times (with DACQ2 they appear 478 # more synchronous than that). 479 self.averaging_time = self.delta / nactive / 3 480 DAQ = Process(target=DAQProc, 481 args=( 482 whichchn, gains, self.averaging_time, self.delta, 483 self.DAQconn, self.DAQCTL)) 484 DAQ.start() 485 self.pltthread.start() 486 # self.updatingplot() hangs up user interface 487 else: 488 btn.description = 'Done' 489 btn.button_style = '' 490 btn.tooltip = '' 491 # wait a plotting thread to terminate 492 self.pltthread.join() 493 self.data = data 494 self.timestamp = timestamp 495 self.stdev = stdev 496 self.fillpandadf() 497 # save data to html file so it is human readable and can be loaded 498 # elsewhere. 499 #self.svname = self.title + '_' + time.strftime('%y-%m-%d_%H%M%S', 500 # time.localtime()) + '.html' 501 svhtml = '<!DOCTYPE html>' \ 502 '<html><body>'+ self.defaultparamtxt + \ 503 '<table id="file_info" border="1"><tr><th>Saved as ' \ 504 '</th></tr><tr><td>' + \ 505 self.svname+'</td></tr></table>' \ 506 '<h2>DATA</h2>'+ \ 507 self.pandadf.to_html() + '</body></html>' 508 f = open(self.svname,'w') 509 f.write(svhtml) 510 f.close() 511 self.collectbtn.close() 512 del self.collectbtn 513 with self.output: 514 display(HTML( 515 '<span style="color:blue;font-weight:bold;">DATA SAVED TO:' + 516 self.svname + '</span>')) 517 return 518 519 def fillpandadf(self): 520 datacolumns = [] 521 temptimes = np.transpose(self.timestamp) 522 tempdata = np.transpose(self.data) 523 tempstdev = np.transpose(self.stdev) 524 chncnt = 0 525 for i in range(self.ntraces): 526 if (self.traces[i].isactive): 527 chncnt += 1 528 for i in range(chncnt): 529 if self.ignore_skew and i > 0: 530 pass 531 else: 532 datacolumns.append(temptimes[i]) 533 datacolumns.append(tempdata[i]) 534 datacolumns.append(tempstdev[i]) 535 titles = [] 536 # Column labels. 537 chncnt = 0 538 for i in range(self.ntraces): 539 if (self.traces[i].isactive): 540 chncnt += 1 541 if self.ignore_skew: 542 if chncnt == 1: 543 titles.append(self.timelbl.value) 544 pass 545 else: 546 titles.append(self.traces[ 547 i].tracelbl.value + '_' + self.timelbl.value) 548 titles.append( 549 self.traces[i].tracelbl.value + '(' + self.traces[ 550 i].units.value + ')') 551 titles.append( 552 self.traces[i].tracelbl.value + '_' + 'stdev') 553 #print(str(titles)) 554 #print(str(datacolumns)) 555 self.pandadf = pd.DataFrame(np.transpose(datacolumns), columns=titles) 556 557 def updatingplot(self, PLTconn, PLTCTL): 558 """ 559 Runs until a check of self.collectbtn.description does not return 560 'Stop Collecting'. This would probably be more efficient if set a 561 boolean. 562 Parameters 563 ---------- 564 PLTconn: Pipe 565 connection plotter end 566 DAQconn: Pipe 567 connection DAQ end 568 PLTCTL: Pipe 569 control pipe plotter end 570 DAQCTL: Pipe 571 control pipe DAQ end 572 """ 573 starttime = time.time() 574 global data 575 data = [] 576 global timestamp 577 timestamp = [] 578 global stdev 579 stdev = [] 580 datalegend = [] 581 timelegend = [] 582 stdevlegend = [] 583 whichchn = [] 584 gains = [] 585 toplotx = [] 586 toploty = [] 587 nactive = 0 588 def convert_pkg(): 589 plttime = 0 590 if self.ignore_skew: 591 plttime = sum(pkg[0]) / len(pkg[0]) 592 traceidx = 0 593 tmptime = [] 594 tmpavg = [] 595 tmpstd = [] 596 tmpavg_std = [] 597 for i, k in zip(self.tracemap, self.tracefrdatachn): 598 599 avg = pkg[1][k] 600 std = pkg[2][k] 601 avg_std = pkg[3][k] 602 avg_vdd = pkg[4][k] 603 avg, std, avg_std = self.traces[i].toselectedunits(avg, 604 std, avg_std, 605 avg_vdd) 606 avg, std, avg_std = sensors. \ 607 to_reasonable_significant_figures_fast(avg, std, avg_std) 608 tmptime.append(pkg[0][k]) 609 tmpavg.append(avg) 610 tmpstd.append(std) 611 tmpavg_std.append(avg_std) 612 if self.ignore_skew: 613 toplotx[traceidx].append(plttime) 614 else: 615 toplotx[traceidx].append(pkg[0][k]) 616 toploty[traceidx].append(avg) 617 traceidx += 1 618 timestamp.append(tmptime) 619 data.append(tmpavg) 620 stdev.append(tmpavg_std) 621 return 622 623 for k in self.traces: 624 if k.isactive: 625 nactive += 1 626 if self.separate_plots: 627 self.livefig.set_subplots(rows = nactive, cols = 1, 628 shared_xaxes= True) 629 self.livefig.update_xaxes(title = self.timelbl.value, 630 row = nactive, col = 1) 631 else: 632 self.livefig.update_yaxes(title = "Values") 633 self.livefig.update_xaxes(title = self.timelbl.value) 634 active_count = 0 635 for i in range(self.ntraces): 636 if (self.traces[i].isactive): 637 active_count += 1 638 tempstr = self.traces[i].tracelbl.value + '(' + \ 639 self.traces[i].units.value + ')' 640 timelegend.append('time_' + tempstr) 641 datalegend.append(tempstr) 642 stdevlegend.append('stdev_' + tempstr) 643 if self.separate_plots: 644 scat = go.Scatter(y=[],x=[], name=tempstr) 645 self.livefig.add_trace(scat, row = active_count, 646 col = 1) 647 self.livefig.update_yaxes(title = self.traces[ 648 i].units.value, row = active_count, col = 1) 649 else: 650 self.livefig.add_scatter(y=[],x=[], name=tempstr) 651 toplotx.append([]) 652 toploty.append([]) 653 lastupdatetime = time.time() 654 655 pts = 0 656 oldpts = 0 657 #print('about to enter while loop',end='') 658 while (self.collectbtn.description == 'Stop Collecting'): 659 #print('.',end='') 660 while PLTconn.poll(): 661 pkg = PLTconn.recv() 662 self.lastpkgstr = str(pkg) 663 #print(self.lastpkgstr) 664 # convert voltage to requested units. 665 convert_pkg() 666 currenttime = time.time() 667 mindelay = 1.0 668 if self.separate_traces_checkbox.value: 669 mindelay = nactive*1.0 670 else: 671 mindelay = nactive*0.5 672 if (currenttime - lastupdatetime)>(mindelay+len(toplotx[0])*len( 673 toplotx)/1000): 674 lastupdatetime = currenttime 675 for k in range(len(self.livefig.data)): 676 self.livefig.data[k].x=toplotx[k] 677 self.livefig.data[k].y=toploty[k] 678 #time.sleep(1) 679 PLTCTL.send('send') 680 time.sleep(self.delta) 681 # print ('btn.description='+str(btn.description)) 682 endtime = time.time() 683 PLTCTL.send('stop') 684 time.sleep(0.5) # wait 0.5 second to collect remaining data 685 PLTCTL.send('send') 686 time.sleep(0.5) 687 msg = '' 688 while (msg != 'done'): 689 while PLTconn.poll(): 690 pkg = PLTconn.recv() 691 # print(str(pkg)) 692 # convert voltage to requested units. 693 convert_pkg() 694 PLTCTL.send('send') 695 time.sleep(0.2) 696 if PLTCTL.poll(): 697 msg = PLTCTL.recv() 698 # print (str(msg)) 699 if (msg != 'done'): 700 print('Received unexpected message: ' + str(msg)) 701 for k in range(len(self.livefig.data)): 702 self.livefig.data[k].x = toplotx[k] 703 self.livefig.data[k].y = toploty[k] 704 return 705 706# TODO delete newRun once sure not needed. 707# def newRun(livefig): 708# """ 709# Set up a new data collection run and add it to the list of runs. 710# """ 711# nrun = len(runs) + 1 712# runs.append(DAQinstance(nrun, livefig, title='Run-' + str(nrun))) 713# runs[nrun - 1].setup() 714# pass 715 716def Run(name): 717 """Load a run from stored data or start a new run if the local file for 718 the run does not exist. 719 Parameters 720 ---------- 721 name: str 722 String name for the run. The data will be stored in a file of this 723 name with the extension of `.jpidaq.html`. 724 """ 725 from pathlib import Path 726 from IPython import get_ipython 727 from IPython.display import display 728 global_dict = get_ipython().user_ns 729 runs = None 730 if 'runs' in global_dict and 'DAQinstance' in global_dict: 731 runs = global_dict['runs'] 732 else: 733 return ('Initialization of JupyterPiDAQ required') 734 # Check if run completed, if so reload data, display and exit 735 datafilepath = Path.cwd() / Path(str(name) + '.jpidaq.html') 736 if datafilepath.exists(): 737 # display the data as a live plotly plot. 738 svname = name + '.jpidaq.html' 739 runs.append(DAQinstance(len(runs)+1, title = name)) 740 runs[-1]._load_from_html(svname) 741 display(HTML(runs[-1].defaultparamtxt)) 742 display(HTML('<h3>Saved as: '+runs[-1].svname+'</h3>')) 743 display(runs[-1].livefig) 744 display(HTML(runs[-1].defaultcollecttxt)) 745 return 746 nrun = len(runs) + 1 747 runs.append(DAQinstance(nrun, title=name)) 748 runs[-1].setup() 749 return 750 751def doRun(whichrun): 752 with whichrun.output: 753 display(HTML('<span id="LiveRun_'+str(whichrun.idno)+'"></span>')) 754 display(HTML(whichrun.defaultparamtxt)) 755 if hasattr(whichrun, "collectbtn"): 756 # only show if hasn't already collected data 757 whichrun.collectbtn.on_click(whichrun.collectclick) 758 display(whichrun.collectbtn) 759 display(HTML(whichrun.defaultcollecttxt)) 760 pass 761 762# TODO delete displayRun once not needed. 763# def displayRun(runidx,file): 764# """ 765# Displays a run. It can fall back to loading from a file if the outputarea 766# is accidentally cleared. 767# :param runidx: index+1 for the run in the runs array. Thus, the run id #. 768# :param file: name of the file the run is saved to 769# :return: A string warning if things are not initialized properly. 770# """ 771# from IPython import get_ipython 772# from JPSLUtils import find_pandas_dataframe_names, find_figure_names 773# idxnum = runidx - 1 774# run_id_table = pd.read_html(file, attrs={'id': 'run_id'})[0] 775# run_title = run_id_table['Title'][0] 776# run_id = run_id_table['Id #'][0] 777# svname = pd.read_html(file, attrs={'id': 'file_info'})[0]['Saved as'][0] 778# global_dict = get_ipython().user_ns 779# runs = None 780# if 'runs' in global_dict and 'DAQinstance' in global_dict: 781# runs = global_dict['runs'] 782# else: 783# return ('Initialization of JupyterPiDAQ required') 784# exists = None 785# if len(runs)>=runidx: 786# if isinstance(runs[idxnum].livefig,go.FigureWidget) and runs[ 787# idxnum].idno == run_id and runs[idxnum].svname ==svname: 788# exists = True 789# else: 790# exists = False 791# if exists: 792# display(HTML(runs[idxnum].defaultparamtxt)) 793# display(HTML('<h3>Saved as: '+runs[idxnum].svname+'</h3>')) 794# runs[idxnum].livefig.show() 795# display(HTML(runs[idxnum].defaultcollecttxt)) 796# JPSLUtils.select_containing_cell("LiveRun_"+str(runidx)) 797# JPSLUtils.delete_selected_cell() 798# else: 799# # Fall back on loading the data from the default save file. 800# # Note: the file must be available. 801# nrunfigs = 0 802# for k in find_figure_names(): 803# if k.startswith('run_fig'): 804# nrunfigs+=1 805# runfigname = 'run_fig'+str(nrunfigs+1) 806# global_dict[runfigname] = go.FigureWidget() 807# fig = global_dict[runfigname] 808# runs.append(DAQinstance(run_id, fig, title=run_title)) 809# idxnum = len(runs)-1 810# runs[idxnum]._load_from_html(file) 811# display(HTML(runs[idxnum].defaultparamtxt)) 812# display(HTML('<h3>Saved as: ' + runs[idxnum].svname + '</h3>')) 813# runs[idxnum].livefig.show() 814# display(HTML(runs[idxnum].defaultcollecttxt)) 815# # protect the cell 816# JPSLUtils.OTJS('protect_selected_cells();') 817# pass 818 819def update_runsdrp(): 820 # get list of runs 821 runlst = [('Choose Run', -1)] 822 for i in range(len(runs)): 823 runlst.append((str(i + 1) + ': ' + runs[i].title, i)) 824 # buid selection menu 825 global runsdrp 826 runsdrp = widgets.Dropdown( 827 options=runlst, 828 value=-1, 829 description='Select Run #:', 830 disabled=False, 831 ) 832 pass 833 834def showSelectedRunTable(change): 835 global runsdrp 836 global last_run_table_out 837 whichrun = runsdrp.value 838 runsdrp.close() 839 last_run_table_out.clear_output() 840 tbldiv = '<div style="height:10em;">' + str(runs[whichrun].title) 841 tbldiv += str(runs[whichrun].pandadf.to_html()) + '</div>' 842 with last_run_table_out: 843 display(HTML(tbldiv)) 844 return 845 846def showDataTable(): 847 """ 848 Provides a menu to select which run. Then displays the run in a 849 10 em high scrolling table. Selection menu is removed after choice 850 is made. 851 """ 852 from ipywidgets import Output 853 global last_run_table_out 854 last_run_table_out = Output() 855 update_runsdrp() 856 global runsdrp 857 runsdrp.observe(showSelectedRunTable, names='value') 858 with last_run_table_out: 859 display(runsdrp) 860 display(last_run_table_out) 861 # will display selected run and delete menu upon selection. 862 return 863def newCalculatedColumn(): 864 """ 865 Uses jupyter-pandas-GUI.new_pandas_column_GUI to provide a GUI expression 866 composer. This method finds the datasets and launches the GUI. 867 """ 868 df_info = [] 869 for i in range(len(runs)): 870 df_info.append([runs[i].pandadf, 'runs['+str(i)+'].pandadf', 871 str(runs[i].title)]) 872 new_pandas_column_GUI(df_info) 873 pass 874 875def newPlot(): 876 """ 877 Uses jupyter-pandas-GUI.plot_pandas_GUI to provide a GUI expression 878 composer. This method finds the datasets and launches the GUI. 879 """ 880 df_info = [] 881 for i in range(len(runs)): 882 if isinstance(runs[i].pandadf,pd.DataFrame): 883 df_info.append([runs[i].pandadf, 'runs['+str(i)+'].pandadf', 884 str(runs[i].title)]) 885 plot_pandas_GUI(df_info) 886 pass 887 888def newFit(): 889 """ 890 Uses jupyter-pandas-GUI.fit_pandas_GUI to provide a GUI expression 891 composer. This method finds the datasets and launches the GUI. 892 """ 893 df_info = [] 894 for i in range(len(runs)): 895 if isinstance(runs[i].pandadf,pd.DataFrame): 896 df_info.append([runs[i].pandadf, 'runs['+str(i)+'].pandadf', 897 str(runs[i].title)]) 898 fit_pandas_GUI(df_info) 899 pass
127class DAQinstance(): 128 def __init__(self, idno, title='None', ntraces=4, **kwargs): 129 """ 130 Data Aquistion Instance (a run). 131 132 :param idno : id number you wish to use to keep track 133 :param title: optional name 134 :param ntraces: number of traces (default = 4) more than 4 easily 135 overwhelms a pi4. 136 :param kwargs: 137 :ignore_skew: bool (default: True) if True only a single average 138 collection time will be recorded for each time in a multichannel 139 data collection. If False a separate set of time will be 140 recorded for each channel. 141 """ 142 from plotly import graph_objects as go 143 self.ignore_skew = kwargs.pop('ignore_skew',True) 144 self.idno = idno 145 self.livefig = go.FigureWidget(layout_template='simple_white') 146 self.PLTconn, self.DAQconn = Pipe() 147 self.DAQCTL, self.PLTCTL = Pipe() 148 self.pltthread = threading.Thread(target=self.updatingplot, args=( 149 self.PLTconn, self.PLTCTL)) 150 self.title = str(title) 151 self.svname = title + '.jpidaq.html' 152 self.averaging_time = 0.1 # seconds adjusted based on collection rate 153 self.gain = [1] * ntraces 154 self.data = [] 155 self.timestamp = [] 156 self.stdev = [] 157 self.pandadf = None 158 self.ntraces = ntraces 159 self.separate_plots = True 160 self.traces = [] 161 # index map from returned data to trace, 162 self.tracemap = [] 163 self.tracefrdatachn = [] 164 self.tracelbls = [] 165 self.units = [] 166 for i in range(self.ntraces): 167 self.traces.append(ChannelSettings(i, availboards)) 168 self.ratemax = 20.0 # Hz 169 self.rate = 1.0 # Hz 170 self.deltamin = 1 / self.ratemax 171 self.delta = 1.0 / self.rate 172 self.setupbtn = widgets.Button( 173 description='Set Parameters', 174 disabled=False, 175 button_style='info', 176 # 'success', 'info', 'warning', 'danger' or '' 177 tooltip='Click to set collection parameters to displayed values.', 178 icon='') 179 self.collectbtn = widgets.Button( 180 description='Start Collecting', 181 disabled=False, 182 button_style='success', 183 # 'success', 'info', 'warning', 'danger' or '' 184 tooltip='Start collecting data and plotting it. ' 185 'Will make new graph.', 186 icon='') 187 self.separate_traces_checkbox = widgets.Checkbox( 188 value = self.separate_plots, 189 description = 'Display multiple traces as stacked graphs. ' \ 190 'Uncheck to display all on the same graph.', 191 layout = widgets.Layout(width='80%'), 192 disabled = False) 193 self.rateinp = widgets.BoundedFloatText( 194 value=self.rate, 195 min=0, 196 max=self.ratemax, 197 step=self.ratemax / 1000.0, 198 description='Rate (Hz):', 199 disabled=False) 200 self.timelbl = widgets.Text( 201 value='Time(s)', 202 placeholder='Type something', 203 description='X-axis label (time):', 204 disabled=False) 205 self.runtitle = widgets.Text( 206 value=self.title, 207 placeholder='Type title/description', 208 description='Run title', 209 disabled=False) 210 self.defaultparamtxt = '' 211 self.defaultcollecttxt = '<span style="color:blue;"> To accurately ' 212 self.defaultcollecttxt += 'read point location on graph you can zoom ' 213 self.defaultcollecttxt += 'in. Drag to zoom. Additional controls show ' 214 self.defaultcollecttxt += 'above plot when you hover over it.</span>' 215 self.collecttxt = widgets.HTML( 216 value=self.defaultcollecttxt, 217 placeholder='', 218 description='') 219 self.setup_layout_bottom = widgets.HBox( 220 [self.rateinp, self.timelbl, self.setupbtn]) 221 self.setup_layout = widgets.VBox([self.separate_traces_checkbox, 222 self.setup_layout_bottom]) 223 self.collect_layout = widgets.HBox([self.collectbtn, self.collecttxt]) 224 self.output = widgets.Output() 225 def _make_defaultparamtxt(self): 226 """ 227 Uses AdvancedHTMLParser (mimics javascript) to generate valid HTML for 228 the default parameter text. 229 :return: valid html string for the default parameter text. 230 """ 231 from AdvancedHTMLParser import AdvancedTag as domel 232 run_info=domel('div') 233 run_info.setAttribute('id','DAQRun_' + str(self.idno) + '_info') 234 run_info.setAttribute('class','run_info') 235 run_id = domel('table') 236 run_id.setAttribute('id','run_id') 237 run_id.setAttribute('border', '1') 238 tr = domel('tr') 239 tr.appendInnerHTML('<th>Title</th><th>Id #</th>') 240 run_id.appendChild(tr) 241 tr = domel('tr') 242 tr.appendInnerHTML('<td>' + str(self.title) + '</td>' \ 243 '<td>' + str(self.idno) + '</td>') 244 run_id.appendChild(tr) 245 run_info.appendChild(run_id) 246 # table of run parameters 247 run_param = domel('table') 248 run_param.setAttribute('border','1') 249 run_param.setAttribute('id','run_param') 250 tr = domel('tr') 251 tr.setAttribute('style','text-align:center;') 252 tr.appendInnerHTML('<th>Approx. Rate (Hz)</th>' \ 253 '<th>Approx. Delta (s)</th>' \ 254 '<th>X-label </th>' \ 255 '<th>X-cols</th>' \ 256 '<th>Y-cols</th>' \ 257 '<th>err-cols<sup ' \ 258 'style="color:blue;">a</sup></th>' \ 259 '<th>One Plot</th>' ) 260 run_param.appendChild(tr) 261 run_info.appendChild(run_param) 262 tr = domel('tr') 263 tr.setAttribute('style','text-align:center;') 264 tr.appendInnerHTML('<td>' + str(self.rate) + '</td>' \ 265 '<td>' + str(self.delta) + '</td>' \ 266 '<td>' + self.timelbl.value + '</td>') 267 xlist = '[' 268 ylist = '[' 269 errlist = '[' 270 if self.ignore_skew: 271 xlist += '0]' 272 tempcount = 0 273 for k in self.traces: 274 if k.isactive: 275 ylist += str(2 * tempcount + 1) + ',' 276 errlist += str(2 * tempcount + 2) + ',' 277 tempcount += 1 278 ylist = ylist[:-1] + ']' 279 errlist = errlist[:-1] + ']' 280 else: 281 tempcount = 0 282 for k in self.traces: 283 if k.isactive: 284 xlist += str(3 * tempcount) + ',' 285 ylist += str(3 * tempcount + 1) + ',' 286 errlist += str(3 * tempcount + 2) + ',' 287 tempcount += 1 288 xlist = xlist[:-1] + ']' 289 ylist = ylist[:-1] + ']' 290 errlist = errlist[:-1] + ']' 291 tr.appendInnerHTML('<td>' + xlist + '</td><td>' + ylist + '</td>') 292 td = domel('td') 293 td.appendText(errlist) 294 tr.appendChild(td) 295 td = domel('td') 296 td.appendText(str(not (self.separate_plots))) 297 tr.appendChild(td) 298 run_param.appendChild(tr) 299 footer = domel('tfoot') 300 tr = domel('tr') 301 td = domel('td') 302 td.setAttribute('colspan','7') 303 td.appendInnerHTML('<sup style="color:blue;">a</sup>The ' \ 304 'standard deviation of the number in the ' \ 305 'column immediately to the left based on the ' \ 306 'variation in signal during the averaging time ' \ 307 'for the data point.') 308 tr.appendChild(td) 309 footer.appendChild(tr) 310 run_param.appendChild(footer) 311 # table of trace information 312 traceinfo = domel('table') 313 traceinfo.setAttribute('class','traceinfo') 314 traceinfo.setAttribute('id','traceinfo') 315 traceinfo.setAttribute('border','1') 316 tr = domel('tr') 317 tr.setAttribute('style','text-align:center;') 318 tr.appendInnerHTML('<th>Trace #</th><th>Title</th><th>Units</th>' \ 319 '<th>Board</th><th>Channel</th><th>Sensor</th>' \ 320 '<th>Gain</th>') 321 traceinfo.appendChild(tr) 322 for i in range(self.ntraces): 323 if (self.traces[i].isactive): 324 self.tracemap.append(i) 325 tr = domel('tr') 326 tr.setAttribute('style', 'text-align:center;') 327 tr.appendInnerHTML('<td>' + str(i) + '</td>' \ 328 '<td>' + self.traces[i].tracelbl.value + '</td>' \ 329 '<td>' + self.traces[i].units.value + '</td>' \ 330 '<td>' + str(self.traces[i].boardchoice.value) + \ 331 ' ' + self.traces[i].board.name + '</td>' \ 332 '<td>' + str(self.traces[i].channel) + '</td>' \ 333 '<td >' + self.traces[i].sensorchoice.value + '</td>' \ 334 '<td>' + str(self.traces[i].gains.value) + '</td>') 335 traceinfo.appendChild(tr) 336 run_info.appendChild(traceinfo) 337 return run_info.asHTML() 338 339 def _load_from_html(self, file): 340 """ 341 Loads data and parameters for a completed run from a saved html file. 342 :param file: filename or path. 343 :return: 344 """ 345 import pandas as pd 346 from JPSLUtils import find_pandas_dataframe_names 347 from AdvancedHTMLParser import AdvancedHTMLParser as parser 348 from plotly import graph_objects as go 349 whichrun = pd.read_html(file, attrs={'id': 'run_id'})[0] 350 run_title = whichrun['Title'][0] 351 run_id = whichrun['Id #'][0] 352 svname = pd.read_html(file, attrs={'id': 'file_info'})[0]['Saved ' \ 353 'as'][0] 354 self.pandadf = pd.read_html(file, attrs={'class': 'dataframe'}, 355 index_col=0)[0] 356 self.title = run_title 357 self.svname = svname 358 run_param = \ 359 pd.read_html(file, attrs={'id': 'run_param'}, skiprows=[2])[0] 360 self.rate = run_param['Approx. Rate (Hz)'][0] 361 self.delta = run_param['Approx. Delta (s)'][0] 362 # reassiging timelbl to a value from a widget 363 self.timelbl = run_param['X-label'][0] 364 self.separate_plots = not (run_param['One Plot'][0]) 365 xcols = list(map(int,run_param['X-cols'][0].replace('[', 366 '').replace(']','').split(','))) 367 ycols = list(map(int,run_param['Y-cols'][0].replace('[', 368 '').replace(']','').split(','))) 369 errcols = list(map(int,run_param['err-colsa'][0].replace('[', 370 '').replace(']', '').split(','))) 371 htmldatafile = parser(file) 372 self.defaultparamtxt = htmldatafile.getElementsByClassName( 373 'run_info')[0].asHTML() 374 traceinfo = pd.read_html(file, attrs={'id': 'traceinfo'})[0] 375 for k in traceinfo.index: 376 # Do not refill the widgets. This truncates and changes the 377 # definitions of some things from widgets to values. 378 self.traces[k].isactive = True 379 self.traces[k].tracelbl= traceinfo['Title'][k] 380 self.traces[k].units = traceinfo['Units'][k] 381 boardchoice, boardname = (traceinfo['Board'][k]).split(' ',1) 382 self.traces[k].boardchoice = boardchoice 383 self.traces[k].board = boardname 384 self.traces[k].channel = traceinfo['Channel'][k] 385 self.traces[k].gains= traceinfo['Gain'][k] 386 self.traces[k].sensor = traceinfo['Sensor'][k] 387 # Plot 388 if self.separate_plots: 389 self.livefig.set_subplots(rows=len(ycols), cols=1, 390 shared_xaxes=True) 391 self.livefig.update_xaxes( 392 title=self.timelbl, row=len(ycols), col=1) 393 else: 394 self.livefig.update_xaxes(title=self.timelbl) 395 self.livefig.update_yaxes(title="Values") 396 for i in range(len(ycols)): 397 namestr = self.pandadf.columns[ycols[i]] 398 xcol = None 399 if len(xcols) == 1: 400 xcol = xcols[0] 401 else: 402 xcol = xcols[i] 403 scat = go.Scatter( 404 y=self.pandadf.iloc[0:, ycols[i]], 405 x=self.pandadf.iloc[0:,xcol], name=namestr) 406 if self.separate_plots: 407 self.livefig.update_yaxes(title=self.traces[i].units, 408 row=i+1, col=1) 409 self.livefig.add_trace(scat, row=i+1, col=1) 410 else: 411 self.livefig.add_trace(scat) 412 pass 413 414 def setupclick(self, btn): 415 # Could just use the values in widgets, but this forces intentional 416 # selection and locks them for the run. 417 from copy import copy 418 self.title = copy(self.runtitle.value) 419 self.rate = copy(self.rateinp.value) 420 self.delta = 1 / self.rate 421 self.separate_plots = copy(self.separate_traces_checkbox.value) 422 self.defaultparamtxt = self._make_defaultparamtxt() 423 self.runtitle.close() 424 del self.runtitle 425 self.setup_layout.close() 426 del self.setup_layout 427 self.output.clear_output() 428 doRun(runs[self.idno-1]) 429 with self.output: 430 display(runs[self.idno - 1].livefig) 431 pass 432 433 def setup(self): 434 with self.output: 435 display(HTML("<h3 id ='RunSetUp' " 436 "style='text-align:center;'>Set Run Parameters</h3>")) 437 self.setupbtn.on_click(self.setupclick) 438 display(self.runtitle) 439 for i in range(self.ntraces): 440 self.traces[i].setup() 441 display(self.setup_layout) 442 display(self.output) 443 pass 444 445 def collectclick(self, btn): 446 if (btn.description == 'Start Collecting'): 447 btn.description = 'Stop Collecting' 448 btn.button_style = 'danger' 449 btn.tooltip = 'Stop the data collection' 450 # do not allow parameters to be reset after starting run. 451 self.setupbtn.disabled = True 452 self.setupbtn.tooltip = 'Parameters locked. The run has started.' 453 self.rateinp.disabled = True 454 self.timelbl.disabled = True 455 nactive = 0 456 for k in self.traces: 457 if k.isactive: 458 nactive += 1 459 whichchn = [] 460 gains =[] 461 for i in range(self.ntraces): 462 if (self.traces[i].isactive): 463 brd = self.traces[i].board 464 chn = self.traces[i].channel 465 newchn = True 466 if len(whichchn) > 0: 467 for k in range(len(whichchn)): 468 if whichchn[k]['board'] == brd \ 469 and whichchn[k]['chnl'] == chn: 470 self.tracefrdatachn.append(k) 471 newchn = False 472 if newchn: 473 whichchn.append({'board': brd, 474 'chnl': chn}) 475 gains.append(self.traces[i].toselectedgain) 476 self.tracefrdatachn.append(len(whichchn)-1) 477 # Use up to 30% of the time for averaging if channels were spaced 478 # evenly between data collection times (with DACQ2 they appear 479 # more synchronous than that). 480 self.averaging_time = self.delta / nactive / 3 481 DAQ = Process(target=DAQProc, 482 args=( 483 whichchn, gains, self.averaging_time, self.delta, 484 self.DAQconn, self.DAQCTL)) 485 DAQ.start() 486 self.pltthread.start() 487 # self.updatingplot() hangs up user interface 488 else: 489 btn.description = 'Done' 490 btn.button_style = '' 491 btn.tooltip = '' 492 # wait a plotting thread to terminate 493 self.pltthread.join() 494 self.data = data 495 self.timestamp = timestamp 496 self.stdev = stdev 497 self.fillpandadf() 498 # save data to html file so it is human readable and can be loaded 499 # elsewhere. 500 #self.svname = self.title + '_' + time.strftime('%y-%m-%d_%H%M%S', 501 # time.localtime()) + '.html' 502 svhtml = '<!DOCTYPE html>' \ 503 '<html><body>'+ self.defaultparamtxt + \ 504 '<table id="file_info" border="1"><tr><th>Saved as ' \ 505 '</th></tr><tr><td>' + \ 506 self.svname+'</td></tr></table>' \ 507 '<h2>DATA</h2>'+ \ 508 self.pandadf.to_html() + '</body></html>' 509 f = open(self.svname,'w') 510 f.write(svhtml) 511 f.close() 512 self.collectbtn.close() 513 del self.collectbtn 514 with self.output: 515 display(HTML( 516 '<span style="color:blue;font-weight:bold;">DATA SAVED TO:' + 517 self.svname + '</span>')) 518 return 519 520 def fillpandadf(self): 521 datacolumns = [] 522 temptimes = np.transpose(self.timestamp) 523 tempdata = np.transpose(self.data) 524 tempstdev = np.transpose(self.stdev) 525 chncnt = 0 526 for i in range(self.ntraces): 527 if (self.traces[i].isactive): 528 chncnt += 1 529 for i in range(chncnt): 530 if self.ignore_skew and i > 0: 531 pass 532 else: 533 datacolumns.append(temptimes[i]) 534 datacolumns.append(tempdata[i]) 535 datacolumns.append(tempstdev[i]) 536 titles = [] 537 # Column labels. 538 chncnt = 0 539 for i in range(self.ntraces): 540 if (self.traces[i].isactive): 541 chncnt += 1 542 if self.ignore_skew: 543 if chncnt == 1: 544 titles.append(self.timelbl.value) 545 pass 546 else: 547 titles.append(self.traces[ 548 i].tracelbl.value + '_' + self.timelbl.value) 549 titles.append( 550 self.traces[i].tracelbl.value + '(' + self.traces[ 551 i].units.value + ')') 552 titles.append( 553 self.traces[i].tracelbl.value + '_' + 'stdev') 554 #print(str(titles)) 555 #print(str(datacolumns)) 556 self.pandadf = pd.DataFrame(np.transpose(datacolumns), columns=titles) 557 558 def updatingplot(self, PLTconn, PLTCTL): 559 """ 560 Runs until a check of self.collectbtn.description does not return 561 'Stop Collecting'. This would probably be more efficient if set a 562 boolean. 563 Parameters 564 ---------- 565 PLTconn: Pipe 566 connection plotter end 567 DAQconn: Pipe 568 connection DAQ end 569 PLTCTL: Pipe 570 control pipe plotter end 571 DAQCTL: Pipe 572 control pipe DAQ end 573 """ 574 starttime = time.time() 575 global data 576 data = [] 577 global timestamp 578 timestamp = [] 579 global stdev 580 stdev = [] 581 datalegend = [] 582 timelegend = [] 583 stdevlegend = [] 584 whichchn = [] 585 gains = [] 586 toplotx = [] 587 toploty = [] 588 nactive = 0 589 def convert_pkg(): 590 plttime = 0 591 if self.ignore_skew: 592 plttime = sum(pkg[0]) / len(pkg[0]) 593 traceidx = 0 594 tmptime = [] 595 tmpavg = [] 596 tmpstd = [] 597 tmpavg_std = [] 598 for i, k in zip(self.tracemap, self.tracefrdatachn): 599 600 avg = pkg[1][k] 601 std = pkg[2][k] 602 avg_std = pkg[3][k] 603 avg_vdd = pkg[4][k] 604 avg, std, avg_std = self.traces[i].toselectedunits(avg, 605 std, avg_std, 606 avg_vdd) 607 avg, std, avg_std = sensors. \ 608 to_reasonable_significant_figures_fast(avg, std, avg_std) 609 tmptime.append(pkg[0][k]) 610 tmpavg.append(avg) 611 tmpstd.append(std) 612 tmpavg_std.append(avg_std) 613 if self.ignore_skew: 614 toplotx[traceidx].append(plttime) 615 else: 616 toplotx[traceidx].append(pkg[0][k]) 617 toploty[traceidx].append(avg) 618 traceidx += 1 619 timestamp.append(tmptime) 620 data.append(tmpavg) 621 stdev.append(tmpavg_std) 622 return 623 624 for k in self.traces: 625 if k.isactive: 626 nactive += 1 627 if self.separate_plots: 628 self.livefig.set_subplots(rows = nactive, cols = 1, 629 shared_xaxes= True) 630 self.livefig.update_xaxes(title = self.timelbl.value, 631 row = nactive, col = 1) 632 else: 633 self.livefig.update_yaxes(title = "Values") 634 self.livefig.update_xaxes(title = self.timelbl.value) 635 active_count = 0 636 for i in range(self.ntraces): 637 if (self.traces[i].isactive): 638 active_count += 1 639 tempstr = self.traces[i].tracelbl.value + '(' + \ 640 self.traces[i].units.value + ')' 641 timelegend.append('time_' + tempstr) 642 datalegend.append(tempstr) 643 stdevlegend.append('stdev_' + tempstr) 644 if self.separate_plots: 645 scat = go.Scatter(y=[],x=[], name=tempstr) 646 self.livefig.add_trace(scat, row = active_count, 647 col = 1) 648 self.livefig.update_yaxes(title = self.traces[ 649 i].units.value, row = active_count, col = 1) 650 else: 651 self.livefig.add_scatter(y=[],x=[], name=tempstr) 652 toplotx.append([]) 653 toploty.append([]) 654 lastupdatetime = time.time() 655 656 pts = 0 657 oldpts = 0 658 #print('about to enter while loop',end='') 659 while (self.collectbtn.description == 'Stop Collecting'): 660 #print('.',end='') 661 while PLTconn.poll(): 662 pkg = PLTconn.recv() 663 self.lastpkgstr = str(pkg) 664 #print(self.lastpkgstr) 665 # convert voltage to requested units. 666 convert_pkg() 667 currenttime = time.time() 668 mindelay = 1.0 669 if self.separate_traces_checkbox.value: 670 mindelay = nactive*1.0 671 else: 672 mindelay = nactive*0.5 673 if (currenttime - lastupdatetime)>(mindelay+len(toplotx[0])*len( 674 toplotx)/1000): 675 lastupdatetime = currenttime 676 for k in range(len(self.livefig.data)): 677 self.livefig.data[k].x=toplotx[k] 678 self.livefig.data[k].y=toploty[k] 679 #time.sleep(1) 680 PLTCTL.send('send') 681 time.sleep(self.delta) 682 # print ('btn.description='+str(btn.description)) 683 endtime = time.time() 684 PLTCTL.send('stop') 685 time.sleep(0.5) # wait 0.5 second to collect remaining data 686 PLTCTL.send('send') 687 time.sleep(0.5) 688 msg = '' 689 while (msg != 'done'): 690 while PLTconn.poll(): 691 pkg = PLTconn.recv() 692 # print(str(pkg)) 693 # convert voltage to requested units. 694 convert_pkg() 695 PLTCTL.send('send') 696 time.sleep(0.2) 697 if PLTCTL.poll(): 698 msg = PLTCTL.recv() 699 # print (str(msg)) 700 if (msg != 'done'): 701 print('Received unexpected message: ' + str(msg)) 702 for k in range(len(self.livefig.data)): 703 self.livefig.data[k].x = toplotx[k] 704 self.livefig.data[k].y = toploty[k] 705 return
128 def __init__(self, idno, title='None', ntraces=4, **kwargs): 129 """ 130 Data Aquistion Instance (a run). 131 132 :param idno : id number you wish to use to keep track 133 :param title: optional name 134 :param ntraces: number of traces (default = 4) more than 4 easily 135 overwhelms a pi4. 136 :param kwargs: 137 :ignore_skew: bool (default: True) if True only a single average 138 collection time will be recorded for each time in a multichannel 139 data collection. If False a separate set of time will be 140 recorded for each channel. 141 """ 142 from plotly import graph_objects as go 143 self.ignore_skew = kwargs.pop('ignore_skew',True) 144 self.idno = idno 145 self.livefig = go.FigureWidget(layout_template='simple_white') 146 self.PLTconn, self.DAQconn = Pipe() 147 self.DAQCTL, self.PLTCTL = Pipe() 148 self.pltthread = threading.Thread(target=self.updatingplot, args=( 149 self.PLTconn, self.PLTCTL)) 150 self.title = str(title) 151 self.svname = title + '.jpidaq.html' 152 self.averaging_time = 0.1 # seconds adjusted based on collection rate 153 self.gain = [1] * ntraces 154 self.data = [] 155 self.timestamp = [] 156 self.stdev = [] 157 self.pandadf = None 158 self.ntraces = ntraces 159 self.separate_plots = True 160 self.traces = [] 161 # index map from returned data to trace, 162 self.tracemap = [] 163 self.tracefrdatachn = [] 164 self.tracelbls = [] 165 self.units = [] 166 for i in range(self.ntraces): 167 self.traces.append(ChannelSettings(i, availboards)) 168 self.ratemax = 20.0 # Hz 169 self.rate = 1.0 # Hz 170 self.deltamin = 1 / self.ratemax 171 self.delta = 1.0 / self.rate 172 self.setupbtn = widgets.Button( 173 description='Set Parameters', 174 disabled=False, 175 button_style='info', 176 # 'success', 'info', 'warning', 'danger' or '' 177 tooltip='Click to set collection parameters to displayed values.', 178 icon='') 179 self.collectbtn = widgets.Button( 180 description='Start Collecting', 181 disabled=False, 182 button_style='success', 183 # 'success', 'info', 'warning', 'danger' or '' 184 tooltip='Start collecting data and plotting it. ' 185 'Will make new graph.', 186 icon='') 187 self.separate_traces_checkbox = widgets.Checkbox( 188 value = self.separate_plots, 189 description = 'Display multiple traces as stacked graphs. ' \ 190 'Uncheck to display all on the same graph.', 191 layout = widgets.Layout(width='80%'), 192 disabled = False) 193 self.rateinp = widgets.BoundedFloatText( 194 value=self.rate, 195 min=0, 196 max=self.ratemax, 197 step=self.ratemax / 1000.0, 198 description='Rate (Hz):', 199 disabled=False) 200 self.timelbl = widgets.Text( 201 value='Time(s)', 202 placeholder='Type something', 203 description='X-axis label (time):', 204 disabled=False) 205 self.runtitle = widgets.Text( 206 value=self.title, 207 placeholder='Type title/description', 208 description='Run title', 209 disabled=False) 210 self.defaultparamtxt = '' 211 self.defaultcollecttxt = '<span style="color:blue;"> To accurately ' 212 self.defaultcollecttxt += 'read point location on graph you can zoom ' 213 self.defaultcollecttxt += 'in. Drag to zoom. Additional controls show ' 214 self.defaultcollecttxt += 'above plot when you hover over it.</span>' 215 self.collecttxt = widgets.HTML( 216 value=self.defaultcollecttxt, 217 placeholder='', 218 description='') 219 self.setup_layout_bottom = widgets.HBox( 220 [self.rateinp, self.timelbl, self.setupbtn]) 221 self.setup_layout = widgets.VBox([self.separate_traces_checkbox, 222 self.setup_layout_bottom]) 223 self.collect_layout = widgets.HBox([self.collectbtn, self.collecttxt]) 224 self.output = widgets.Output()
Data Aquistion Instance (a run).
Parameters
- idno: id number you wish to use to keep track
- title: optional name
- ntraces: number of traces (default = 4) more than 4 easily overwhelms a pi4.
- kwargs: :ignore_skew: bool (default: True) if True only a single average collection time will be recorded for each time in a multichannel data collection. If False a separate set of time will be recorded for each channel.
414 def setupclick(self, btn): 415 # Could just use the values in widgets, but this forces intentional 416 # selection and locks them for the run. 417 from copy import copy 418 self.title = copy(self.runtitle.value) 419 self.rate = copy(self.rateinp.value) 420 self.delta = 1 / self.rate 421 self.separate_plots = copy(self.separate_traces_checkbox.value) 422 self.defaultparamtxt = self._make_defaultparamtxt() 423 self.runtitle.close() 424 del self.runtitle 425 self.setup_layout.close() 426 del self.setup_layout 427 self.output.clear_output() 428 doRun(runs[self.idno-1]) 429 with self.output: 430 display(runs[self.idno - 1].livefig) 431 pass
433 def setup(self): 434 with self.output: 435 display(HTML("<h3 id ='RunSetUp' " 436 "style='text-align:center;'>Set Run Parameters</h3>")) 437 self.setupbtn.on_click(self.setupclick) 438 display(self.runtitle) 439 for i in range(self.ntraces): 440 self.traces[i].setup() 441 display(self.setup_layout) 442 display(self.output) 443 pass
445 def collectclick(self, btn): 446 if (btn.description == 'Start Collecting'): 447 btn.description = 'Stop Collecting' 448 btn.button_style = 'danger' 449 btn.tooltip = 'Stop the data collection' 450 # do not allow parameters to be reset after starting run. 451 self.setupbtn.disabled = True 452 self.setupbtn.tooltip = 'Parameters locked. The run has started.' 453 self.rateinp.disabled = True 454 self.timelbl.disabled = True 455 nactive = 0 456 for k in self.traces: 457 if k.isactive: 458 nactive += 1 459 whichchn = [] 460 gains =[] 461 for i in range(self.ntraces): 462 if (self.traces[i].isactive): 463 brd = self.traces[i].board 464 chn = self.traces[i].channel 465 newchn = True 466 if len(whichchn) > 0: 467 for k in range(len(whichchn)): 468 if whichchn[k]['board'] == brd \ 469 and whichchn[k]['chnl'] == chn: 470 self.tracefrdatachn.append(k) 471 newchn = False 472 if newchn: 473 whichchn.append({'board': brd, 474 'chnl': chn}) 475 gains.append(self.traces[i].toselectedgain) 476 self.tracefrdatachn.append(len(whichchn)-1) 477 # Use up to 30% of the time for averaging if channels were spaced 478 # evenly between data collection times (with DACQ2 they appear 479 # more synchronous than that). 480 self.averaging_time = self.delta / nactive / 3 481 DAQ = Process(target=DAQProc, 482 args=( 483 whichchn, gains, self.averaging_time, self.delta, 484 self.DAQconn, self.DAQCTL)) 485 DAQ.start() 486 self.pltthread.start() 487 # self.updatingplot() hangs up user interface 488 else: 489 btn.description = 'Done' 490 btn.button_style = '' 491 btn.tooltip = '' 492 # wait a plotting thread to terminate 493 self.pltthread.join() 494 self.data = data 495 self.timestamp = timestamp 496 self.stdev = stdev 497 self.fillpandadf() 498 # save data to html file so it is human readable and can be loaded 499 # elsewhere. 500 #self.svname = self.title + '_' + time.strftime('%y-%m-%d_%H%M%S', 501 # time.localtime()) + '.html' 502 svhtml = '<!DOCTYPE html>' \ 503 '<html><body>'+ self.defaultparamtxt + \ 504 '<table id="file_info" border="1"><tr><th>Saved as ' \ 505 '</th></tr><tr><td>' + \ 506 self.svname+'</td></tr></table>' \ 507 '<h2>DATA</h2>'+ \ 508 self.pandadf.to_html() + '</body></html>' 509 f = open(self.svname,'w') 510 f.write(svhtml) 511 f.close() 512 self.collectbtn.close() 513 del self.collectbtn 514 with self.output: 515 display(HTML( 516 '<span style="color:blue;font-weight:bold;">DATA SAVED TO:' + 517 self.svname + '</span>')) 518 return
520 def fillpandadf(self): 521 datacolumns = [] 522 temptimes = np.transpose(self.timestamp) 523 tempdata = np.transpose(self.data) 524 tempstdev = np.transpose(self.stdev) 525 chncnt = 0 526 for i in range(self.ntraces): 527 if (self.traces[i].isactive): 528 chncnt += 1 529 for i in range(chncnt): 530 if self.ignore_skew and i > 0: 531 pass 532 else: 533 datacolumns.append(temptimes[i]) 534 datacolumns.append(tempdata[i]) 535 datacolumns.append(tempstdev[i]) 536 titles = [] 537 # Column labels. 538 chncnt = 0 539 for i in range(self.ntraces): 540 if (self.traces[i].isactive): 541 chncnt += 1 542 if self.ignore_skew: 543 if chncnt == 1: 544 titles.append(self.timelbl.value) 545 pass 546 else: 547 titles.append(self.traces[ 548 i].tracelbl.value + '_' + self.timelbl.value) 549 titles.append( 550 self.traces[i].tracelbl.value + '(' + self.traces[ 551 i].units.value + ')') 552 titles.append( 553 self.traces[i].tracelbl.value + '_' + 'stdev') 554 #print(str(titles)) 555 #print(str(datacolumns)) 556 self.pandadf = pd.DataFrame(np.transpose(datacolumns), columns=titles)
558 def updatingplot(self, PLTconn, PLTCTL): 559 """ 560 Runs until a check of self.collectbtn.description does not return 561 'Stop Collecting'. This would probably be more efficient if set a 562 boolean. 563 Parameters 564 ---------- 565 PLTconn: Pipe 566 connection plotter end 567 DAQconn: Pipe 568 connection DAQ end 569 PLTCTL: Pipe 570 control pipe plotter end 571 DAQCTL: Pipe 572 control pipe DAQ end 573 """ 574 starttime = time.time() 575 global data 576 data = [] 577 global timestamp 578 timestamp = [] 579 global stdev 580 stdev = [] 581 datalegend = [] 582 timelegend = [] 583 stdevlegend = [] 584 whichchn = [] 585 gains = [] 586 toplotx = [] 587 toploty = [] 588 nactive = 0 589 def convert_pkg(): 590 plttime = 0 591 if self.ignore_skew: 592 plttime = sum(pkg[0]) / len(pkg[0]) 593 traceidx = 0 594 tmptime = [] 595 tmpavg = [] 596 tmpstd = [] 597 tmpavg_std = [] 598 for i, k in zip(self.tracemap, self.tracefrdatachn): 599 600 avg = pkg[1][k] 601 std = pkg[2][k] 602 avg_std = pkg[3][k] 603 avg_vdd = pkg[4][k] 604 avg, std, avg_std = self.traces[i].toselectedunits(avg, 605 std, avg_std, 606 avg_vdd) 607 avg, std, avg_std = sensors. \ 608 to_reasonable_significant_figures_fast(avg, std, avg_std) 609 tmptime.append(pkg[0][k]) 610 tmpavg.append(avg) 611 tmpstd.append(std) 612 tmpavg_std.append(avg_std) 613 if self.ignore_skew: 614 toplotx[traceidx].append(plttime) 615 else: 616 toplotx[traceidx].append(pkg[0][k]) 617 toploty[traceidx].append(avg) 618 traceidx += 1 619 timestamp.append(tmptime) 620 data.append(tmpavg) 621 stdev.append(tmpavg_std) 622 return 623 624 for k in self.traces: 625 if k.isactive: 626 nactive += 1 627 if self.separate_plots: 628 self.livefig.set_subplots(rows = nactive, cols = 1, 629 shared_xaxes= True) 630 self.livefig.update_xaxes(title = self.timelbl.value, 631 row = nactive, col = 1) 632 else: 633 self.livefig.update_yaxes(title = "Values") 634 self.livefig.update_xaxes(title = self.timelbl.value) 635 active_count = 0 636 for i in range(self.ntraces): 637 if (self.traces[i].isactive): 638 active_count += 1 639 tempstr = self.traces[i].tracelbl.value + '(' + \ 640 self.traces[i].units.value + ')' 641 timelegend.append('time_' + tempstr) 642 datalegend.append(tempstr) 643 stdevlegend.append('stdev_' + tempstr) 644 if self.separate_plots: 645 scat = go.Scatter(y=[],x=[], name=tempstr) 646 self.livefig.add_trace(scat, row = active_count, 647 col = 1) 648 self.livefig.update_yaxes(title = self.traces[ 649 i].units.value, row = active_count, col = 1) 650 else: 651 self.livefig.add_scatter(y=[],x=[], name=tempstr) 652 toplotx.append([]) 653 toploty.append([]) 654 lastupdatetime = time.time() 655 656 pts = 0 657 oldpts = 0 658 #print('about to enter while loop',end='') 659 while (self.collectbtn.description == 'Stop Collecting'): 660 #print('.',end='') 661 while PLTconn.poll(): 662 pkg = PLTconn.recv() 663 self.lastpkgstr = str(pkg) 664 #print(self.lastpkgstr) 665 # convert voltage to requested units. 666 convert_pkg() 667 currenttime = time.time() 668 mindelay = 1.0 669 if self.separate_traces_checkbox.value: 670 mindelay = nactive*1.0 671 else: 672 mindelay = nactive*0.5 673 if (currenttime - lastupdatetime)>(mindelay+len(toplotx[0])*len( 674 toplotx)/1000): 675 lastupdatetime = currenttime 676 for k in range(len(self.livefig.data)): 677 self.livefig.data[k].x=toplotx[k] 678 self.livefig.data[k].y=toploty[k] 679 #time.sleep(1) 680 PLTCTL.send('send') 681 time.sleep(self.delta) 682 # print ('btn.description='+str(btn.description)) 683 endtime = time.time() 684 PLTCTL.send('stop') 685 time.sleep(0.5) # wait 0.5 second to collect remaining data 686 PLTCTL.send('send') 687 time.sleep(0.5) 688 msg = '' 689 while (msg != 'done'): 690 while PLTconn.poll(): 691 pkg = PLTconn.recv() 692 # print(str(pkg)) 693 # convert voltage to requested units. 694 convert_pkg() 695 PLTCTL.send('send') 696 time.sleep(0.2) 697 if PLTCTL.poll(): 698 msg = PLTCTL.recv() 699 # print (str(msg)) 700 if (msg != 'done'): 701 print('Received unexpected message: ' + str(msg)) 702 for k in range(len(self.livefig.data)): 703 self.livefig.data[k].x = toplotx[k] 704 self.livefig.data[k].y = toploty[k] 705 return
Runs until a check of self.collectbtn.description does not return 'Stop Collecting'. This would probably be more efficient if set a boolean.
Parameters
PLTconn: Pipe connection plotter end DAQconn: Pipe connection DAQ end PLTCTL: Pipe control pipe plotter end DAQCTL: Pipe control pipe DAQ end
717def Run(name): 718 """Load a run from stored data or start a new run if the local file for 719 the run does not exist. 720 Parameters 721 ---------- 722 name: str 723 String name for the run. The data will be stored in a file of this 724 name with the extension of `.jpidaq.html`. 725 """ 726 from pathlib import Path 727 from IPython import get_ipython 728 from IPython.display import display 729 global_dict = get_ipython().user_ns 730 runs = None 731 if 'runs' in global_dict and 'DAQinstance' in global_dict: 732 runs = global_dict['runs'] 733 else: 734 return ('Initialization of JupyterPiDAQ required') 735 # Check if run completed, if so reload data, display and exit 736 datafilepath = Path.cwd() / Path(str(name) + '.jpidaq.html') 737 if datafilepath.exists(): 738 # display the data as a live plotly plot. 739 svname = name + '.jpidaq.html' 740 runs.append(DAQinstance(len(runs)+1, title = name)) 741 runs[-1]._load_from_html(svname) 742 display(HTML(runs[-1].defaultparamtxt)) 743 display(HTML('<h3>Saved as: '+runs[-1].svname+'</h3>')) 744 display(runs[-1].livefig) 745 display(HTML(runs[-1].defaultcollecttxt)) 746 return 747 nrun = len(runs) + 1 748 runs.append(DAQinstance(nrun, title=name)) 749 runs[-1].setup() 750 return
Load a run from stored data or start a new run if the local file for the run does not exist.
Parameters
name: str
String name for the run. The data will be stored in a file of this
name with the extension of .jpidaq.html
.
752def doRun(whichrun): 753 with whichrun.output: 754 display(HTML('<span id="LiveRun_'+str(whichrun.idno)+'"></span>')) 755 display(HTML(whichrun.defaultparamtxt)) 756 if hasattr(whichrun, "collectbtn"): 757 # only show if hasn't already collected data 758 whichrun.collectbtn.on_click(whichrun.collectclick) 759 display(whichrun.collectbtn) 760 display(HTML(whichrun.defaultcollecttxt)) 761 pass
820def update_runsdrp(): 821 # get list of runs 822 runlst = [('Choose Run', -1)] 823 for i in range(len(runs)): 824 runlst.append((str(i + 1) + ': ' + runs[i].title, i)) 825 # buid selection menu 826 global runsdrp 827 runsdrp = widgets.Dropdown( 828 options=runlst, 829 value=-1, 830 description='Select Run #:', 831 disabled=False, 832 ) 833 pass
835def showSelectedRunTable(change): 836 global runsdrp 837 global last_run_table_out 838 whichrun = runsdrp.value 839 runsdrp.close() 840 last_run_table_out.clear_output() 841 tbldiv = '<div style="height:10em;">' + str(runs[whichrun].title) 842 tbldiv += str(runs[whichrun].pandadf.to_html()) + '</div>' 843 with last_run_table_out: 844 display(HTML(tbldiv)) 845 return
847def showDataTable(): 848 """ 849 Provides a menu to select which run. Then displays the run in a 850 10 em high scrolling table. Selection menu is removed after choice 851 is made. 852 """ 853 from ipywidgets import Output 854 global last_run_table_out 855 last_run_table_out = Output() 856 update_runsdrp() 857 global runsdrp 858 runsdrp.observe(showSelectedRunTable, names='value') 859 with last_run_table_out: 860 display(runsdrp) 861 display(last_run_table_out) 862 # will display selected run and delete menu upon selection. 863 return
Provides a menu to select which run. Then displays the run in a 10 em high scrolling table. Selection menu is removed after choice is made.
864def newCalculatedColumn(): 865 """ 866 Uses jupyter-pandas-GUI.new_pandas_column_GUI to provide a GUI expression 867 composer. This method finds the datasets and launches the GUI. 868 """ 869 df_info = [] 870 for i in range(len(runs)): 871 df_info.append([runs[i].pandadf, 'runs['+str(i)+'].pandadf', 872 str(runs[i].title)]) 873 new_pandas_column_GUI(df_info) 874 pass
Uses jupyter-pandas-GUI.new_pandas_column_GUI to provide a GUI expression composer. This method finds the datasets and launches the GUI.
876def newPlot(): 877 """ 878 Uses jupyter-pandas-GUI.plot_pandas_GUI to provide a GUI expression 879 composer. This method finds the datasets and launches the GUI. 880 """ 881 df_info = [] 882 for i in range(len(runs)): 883 if isinstance(runs[i].pandadf,pd.DataFrame): 884 df_info.append([runs[i].pandadf, 'runs['+str(i)+'].pandadf', 885 str(runs[i].title)]) 886 plot_pandas_GUI(df_info) 887 pass
Uses jupyter-pandas-GUI.plot_pandas_GUI to provide a GUI expression composer. This method finds the datasets and launches the GUI.
889def newFit(): 890 """ 891 Uses jupyter-pandas-GUI.fit_pandas_GUI to provide a GUI expression 892 composer. This method finds the datasets and launches the GUI. 893 """ 894 df_info = [] 895 for i in range(len(runs)): 896 if isinstance(runs[i].pandadf,pd.DataFrame): 897 df_info.append([runs[i].pandadf, 'runs['+str(i)+'].pandadf', 898 str(runs[i].title)]) 899 fit_pandas_GUI(df_info) 900 pass
Uses jupyter-pandas-GUI.fit_pandas_GUI to provide a GUI expression composer. This method finds the datasets and launches the GUI.