48
1 SI Appendix Real-Time Measurement of Small Molecules Directly in Awake, Ambulatory Animals Netzahualcóyotl Arroyo-Currás, Jacob Somerson, Philip A. Vieira, Kyle L. Ploense, Tod E. Kippin and Kevin W. Plaxco Figure S1. Continuous measurements in flowing whole blood in vitro......................................... 2 Figure S2. Kinetic Differential Measurements exploit the difference in current decay rates between the bound and unbound state of the aptamer .................................................................... 3 Figure S3. Continuous measurement of the antibiotic tobramycin in the bloodstream of an anesthetized rat................................................................................................................................ 4 Figure S4. Pharmacokinetics of doxorubicin in vivo ..................................................................... 5 Figure S5. Cross-reactivity studies of E-AB sensors deployed in living rats ................................ 6 Figure S6. Titration curves for aminoglycoside- and doxorubicin-detecting E-AB sensors in flowing whole blood in vitro........................................................................................................... 7 Materials and Methods ................................................................................................................. 8 Chemicals and Materials ................................................................................................................ 8 Sensor Fabrication.......................................................................................................................... 9 Electrochemical Methods and Data Processing ............................................................................. 9 In vitro Experiments........................................................................................................................ 9 In vivo Experiments....................................................................................................................... 10 Supplementary Text.................................................................................................................... 12 Igor Script Used for Data Analysis............................................................................................... 12 RStudio Script Used for Real-Time Data Plotting ........................................................................ 14 RTMonitor.R ................................................................................................................................. 14 config.R ......................................................................................................................................... 47 References .................................................................................................................................... 48

SI Appendix 2ndRevision - PNAS · SI Appendix Real-Time ... Membrane-protected, KDM-corrected sensors respond identically to two identical injections of 1 mM kanamycin without loss

Embed Size (px)

Citation preview

1

SI Appendix Real-Time Measurement of Small Molecules Directly in Awake, Ambulatory

Animals

Netzahualcóyotl Arroyo-Currás, Jacob Somerson, Philip A. Vieira, Kyle L. Ploense, Tod E. Kippin and Kevin W. Plaxco

Figure S1. Continuous measurements in flowing whole blood in vitro ......................................... 2

Figure S2. Kinetic Differential Measurements exploit the difference in current decay rates between the bound and unbound state of the aptamer .................................................................... 3

Figure S3. Continuous measurement of the antibiotic tobramycin in the bloodstream of an anesthetized rat ................................................................................................................................ 4

Figure S4. Pharmacokinetics of doxorubicin in vivo ..................................................................... 5 Figure S5. Cross-reactivity studies of E-AB sensors deployed in living rats ................................ 6

Figure S6. Titration curves for aminoglycoside- and doxorubicin-detecting E-AB sensors in flowing whole blood in vitro ........................................................................................................... 7

Materials and Methods ................................................................................................................. 8 Chemicals and Materials ................................................................................................................ 8

Sensor Fabrication .......................................................................................................................... 9 Electrochemical Methods and Data Processing ............................................................................. 9

In vitro Experiments ........................................................................................................................ 9 In vivo Experiments ....................................................................................................................... 10

Supplementary Text.................................................................................................................... 12 Igor Script Used for Data Analysis ............................................................................................... 12 RStudio Script Used for Real-Time Data Plotting ........................................................................ 14

RTMonitor.R ................................................................................................................................. 14 config.R ......................................................................................................................................... 47

References .................................................................................................................................... 48

2

Figure S1. Continuous measurements in flowing whole blood in vitro. (A) This plot shows a comparison of baseline drift between the membrane-modified platform described in this work (no current change in 6 h) and conventional aminoglycoside E-AB sensors (40% current loss in 6 h). Normalized currents correspond to peak currents from square-wave voltammograms divided by the peak current of the first voltammogram. (B) Membrane-protected, KDM-corrected sensors respond identically to two identical injections of 1 mM kanamycin without loss in signal after even hours in flowing whole blood, illustrating the extent to which KDM-corrected sensors remain accurate even under these challenging conditions. Note that the sensor response time is slower in these in vitro experiments than in our in vivo experiments, likely due to the much slower flow velocities found in this artificial circulatory system than in the actual circulatory system of the rat. (C) Membrane-free sensors, in contrast exhibit significant signal decay due to cellular fouling. Errors correspond to the standard deviation observed across 3 independently fabricated sensors.

3

Figure S2. Kinetic Differential Measurements exploit the difference in current decay rates between the bound and unbound state of the aptamer. (A) Shown is a cyclic voltammogram recorded at 100 mV s-1 (negative current for reduction) from an aminoglycoside-detecting E-AB sensor. This voltammogram presents the voltage limits where no current is passing through the working electrode (0.0 V) and where the reduction of methylene blue can be carried out at constant potential (for example, -0.31 V). (B) Stepping the potential of this same sensor from 0.0 V to -0.31 V produces chronoamperometric measurements that can be used to illustrate the KDM method. Here, the current-time curves were recorded in the absence and presence of target (1 mM tobramycin), with a sampling rate of one point per millisecond. Because the rates of current decay are different for the two states (i.e., the target-bound aptamer transfers electrons more rapidly than the target-free state), sampling the current 33 ms after the jump (corresponding to a square-wave frequency of 30 Hz) produces a decrease in signaling current upon target binding. In contrast, sampling 4.2 ms after the pulse (corresponding to a square wave frequency of 240 Hz) produces a current increase.

A B

Binding increases

current

Binding decreases

current

4

Figure S3. Continuous measurement of the antibiotic tobramycin in the bloodstream of an anesthetized rat. Shown are data collected on a living rat given two sequential 20 mg/kg intravenous injections of the drug.

5

Figure S4. Pharmacokinetics of doxorubicin in vivo. Shown is an average of five 2 mg/m2 injections of doxorubicin measured in the jugular vein of a single rat (from Fig. 2A). The indicated fit is to a two-compartment pharmacokinetic model.

6

Figure S5. Cross-reactivity studies of E-AB sensors deployed in living rats. The specificity of E-AB sensors cannot be greater than that of the aptamers from which they are constructed, and the specificity of aptamers targeting small molecules is sometimes limited. Here we show the response of (A) an aminoglycoside-sensing E-AB sensor and (B) a DOX-sensing E-AB sensor emplaced in the right jugular veins of rats to serial intravenous injections of 8 mg/m2 DOX and of 25 mg/kg tobramycin, respectively.

B A

7

Figure S6. Titration curves for aminoglycoside- and doxorubicin-detecting E-AB sensors in flowing whole blood in vitro. (A) To illustrate the use of KDM with aminoglycoside-detecting sensors we show titration curves for kanamycin (Kd = 800 µM) recorded at 60 Hz and 500 Hz and the KDM-corrected curve that is the difference between the two. (B, C) KDM-corrected curves are shown here for gentamicin (Kd = 700 µM) and tobramycin (Kd = 790 µM). (D) To illustrate the use of KDM with doxorubicin-specific sensors (Kd = 5 µM) we present titration curves for doxorubicin recorded at 10 Hz and 120 Hz and the KDM-corrected curve that is the difference between the two. In the case of doxorubicin, data was normalized versus a point recorded in the absence of target, hence the small offset observed in the y-axis. Errors correspond to the standard deviations observed across three independently fabricated and tested sensors.

A B

C D

8

Materials and Methods Chemicals and Materials

Sodium hydroxide, sulfuric acid, tris(hydroxymethyl)aminomethane (Tris), ethylenediaminetetraacetic acid (EDTA), sodium hydrogen phosphate, sodium chloride, potassium chloride, and potassium dihydrogen phosphate were ordered from Fisher Scientific (Waltham, MA). 6-Mercapto-1-hexanol and tris(2-carboxyethyl)phosphine were ordered from Sigma Aldrich (St. Louis, MO). Tobramycin sulfate, gentamycin sulfate, and kanamycin monosulfate were ordered in USP grade from Gold BioTechnology, Inc (St. Louis, MO). Doxorubicin hydrochloride was ordered from LC Laboratories (Woburn, MA). A 1X stock solution of phosphate buffered saline (PBS) was prepared by mixing 8 g of sodium chloride, 0.2 g of potassium chloride, 1.44 g of sodium hydrogen phosphate, and 0.24 g of potassium hydrogen phosphate in 800 mL of distilled water. The pH of this solution was adjusted to 7.4 using hydrochloric acid and the volume was adjusted to 1 L. A 1X stock solution of Tris-EDTA buffer was prepared by mixing 1 ml of 1 M Tris-HCl (pH 8.0) with 0.2 ml EDTA (0.5 M), adjusting the final volume to 100 mL. All chemicals were used as received. Heparinized human and bovine blood for flowing in vitro measurements were purchased from Hemostat Laboratories (Dixon, CA).

The E-AB sensors employed here were adapted from previous work.1-3 To fabricate them we ordered the relevant methylene-blue-and-thiol-modified DNA constructs from Biosearch Technologies. The 5’ end of each is modified with a thiol on a 6-carbon linker and the 3’ end is modified with carboxy-modified methylene blue attached to the DNA via the formation of an amide bond to a primary amine on a 7-carbon linker (Table 1). The length of the surface tethering carbon linker represents a compromise between the two main criteria for electrochemical biosensor applications: stability and electron-transfer efficiency. We selected a 6-carbon linker because it exhibits good stability and improved signaling relative to that seen, for example, when using 11-carbon linkers.4 The modified DNAs were purified through dual HPLC by the supplier and used as received. Upon receipt each construct was dissolved to 200 µM in 1X Tris-EDTA buffer and frozen at -20 °C in individual aliquots until use.

Catheters (18G) and 1 mL syringes were purchased from Becton Dickinson (Franklin Lakes, NJ). Silver wire (200 µm diameter) was purchased from Alfa Aesar (Ward Hill, MA); to employ this as a reference electrode it was immersed in bleach overnight to form a silver chloride film. Gold-plated tungsten wire (100 µm diameter) was purchased from GoodFellow (Huntingdon, England). Insulated pure gold and silver wires (100 µm diameter) were purchased from A-M systems. Polyethersulfone membranes (P/N: C02-E20U-05-N) were purchased as MicroKros Fliter Modules from Spectrum Laboratories (Rancho Dominguez, CA). The filter

Table 1. DNA aptamer sequences used in this work.

Aptamer Sequence

Doxorubicin 5’ – HS- (CH2)6 – ACCATCTGTGTAAGGGGTAAGGGGTGGT – (CH2)7 –NH– Methylene Blue – 3’

Aminoglycoside 5’ – HS- (CH2)6 – GGGACTTGGTTTAGGTAATGAGTCCC – (CH2)7 –NH– Methylene Blue – 3’

9

modules were cut open and the hollow membranes were extracted from them. Heat-shrink polytetrafluoroethylene insulation (PTFE, HS Sub-Lite-Wall, 0.02, 0.005, 0.003±0.001 in, black-opaque, Lot # 17747112-3) to use on gold-plated tungsten wires was purchased from ZEUS (Branchburg Township, CA).

Sensor Fabrication

Segments of either gold-plated tungsten wire (anesthetized animals) or more malleable pure gold wire (awake animals) 7 cm in length were cut to make sensors. These wires were then insulated by applying heat to shrinkable tubing around the body of the wires, as depicted in Fig. 1B of the paper. The sensor window (i.e., the region without insulation) was approximately 5-8 mm in length. To increase surface area of these working electrodes (to obtain larger peak currents) the sensor surface was roughened electrochemically via immersion in 0.5 M sulfuric acid followed by jumping between Einitial = 0.0 V to Ehigh = 2.0 V vs Ag/AgCl, back and forth, for 100,000 pulses. Each pulse was of 2 ms duration with no “quiet time.”

To fabricate sensors an aliquot of the appropriate DNA construct was thawed and then reduced for 1 h at room temperature with a 1000-fold molar excess of tris(2-carboxyethyl)phosphine. A freshly roughened gold electrode was then rinsed in di-ionized water before being immersed in a solution of the appropriate reduced DNA construct at 200-500 nM in PBS for 1 h at room temperature. Following this the sensor was inserted into hollow polysulfone fibers 1.5 cm in length and 200 µm in diameter. The membranes were mechanically attached to the sensors by wrapping the edges with parafilm™. After attaching the membranes, the sensors were immersed overnight at 4°C for 12 h in 20 mM 6-mercapto-1-hexanol in PBS to coat the remaining gold surface and remove nonspecifically adsorbed DNA. After this the sensors were rinsed with di-ionized water and stored in PBS. Electrochemical Methods and Data Processing

For all sensing experiments, the sensors were interrogated using square wave voltammetry from 0.0 V to -0.5 V vs. Ag/AgCl, using an amplitude of 50 mV, potential step sizes of 1-5 mV, and varying frequencies from 10 Hz to 500 Hz. The files corresponding to each voltammogram were recorded in serial order using macros in CH Instruments software. The post-experiment analysis of results was carried out using a script coded in Igor Pro 7 (SI Appendix, p.13).

All in vitro measurements were performed using a three-electrode setup and with a CH Instruments electrochemical workstation (Austin, TX, Model 660D) using commercial Ag/AgCl reference electrodes filled with saturated KCl solution and platinum counter electrodes.

All in vivo measurements were performed using a two-electrode setup in which the reference and counter electrodes were a silver wire coated with a silver chloride film as described above. The measurements carried out in vivo were recorded using a handheld potentiostat from CH Instruments (Model 1242 B).

In vitro Experiments

To measure aptamer affinity and correlate signal gain to target concentration, sensors were interrogated by square-wave voltammetry first in flowing PBS and next in flowing heparinized human or bovine blood with increasing concentrations of the corresponding target. These experiments were carried out in a closed flow system intended to mimic the type of blood

10

transport found in veins. Blood flow was achieved using a magnetic gear pump (Benchtop Analog Drive, 0.261 mL/rev) from Cole Parmer (Vernon Hills, IL), setting flow rates to 1-4 mL min-1 as measured by a flow meter. To construct the binding curves (titrations of aptamer with target), stock solutions of each target molecule were prepared fresh prior to measurements in PBS buffer or blood, respectively. Examples of typical binding curves are provided (SI Appendix, Fig. S6).

In vivo Experiments

Animals. Adult male Sprague-Dawley rats (300-500 g; Charles River Laboratories, Wilmington, MA, USA) were pair housed in a temperature and humidity controlled vivarium on a 12-h light-dark cycle and provided ad libitum access to food and water. All animal procedures were consistent with the guidelines of the NIH Guide for Care and Use of Laboratory Animals and approved by the Institutional Animal Care and Use Committee of the University of California Santa Barbara.

Surgery. For the anesthetized preparation, rats were anesthetized using isoflurane gas inhalation (2.5%) and monitored throughout the experiment using a pulse oximeter (Nonin Medical, Plymouth, MN) to measure heart rate and %SpO2 to insure depth of anesthesia. After exposing both ventral jugular veins, a simple catheter made from a SILASTIC tube (Dow Corning, Midland, MI, USA) fitted with a steel cannula (Plastics One, Roanoke, VA, USA) was implanted into the left jugular vein. 0.1-0.3 mL of heparin (1000U/mL, SAGENT Pharmaceuticals, Schaumburg, IL, USA) were immediately infused through the catheter to prevent blood clotting. The sensor was inserted into the right jugular vein and secured in place with surgical suture. Following drug infusions (see below), animals were euthanized by overdose on isoflurane.

For the awake preparation, rats were anesthetized (as above) and then mounted on a stereotaxic apparatus (David Kopf Instruments, Tujunga, CA, USA) with a gas anesthesia head holder to maintain anesthesia. After a subcutaneous injection of an analgesic (1mg/kg Flunixiject, Henry Schein Animal Health, Dublin, OH, USA), a midline incision was made along the dorsal surface of the scalp and a second incision was made on the ventral portion of the neck above the jugular vein. Using a similar catheter construction described above, we implanted the catheter tube into the right jugular vein and sutured it in place before sealing the wound with skin glue. The surface of the skull was then exposed and 4 screws were drilled into the bone to provide a platform for the cannula to be cemented to the head. Dental cement was applied to the skull surface while the cannula was held in place using the stereotaxic arm. After the cement had set, the catheter was flushed with antibiotics (1 mg/kg gentamicin Henry Schein Inc., Dublin, OH, USA and 1 mg/kg cefazolin, WG CriticalCare, Paramus, NJ, USA) and the animal was monitored for postoperative recovery before being returned to the vivarium colony. Daily monitoring of weight and condition of recovery followed for 4 days in which the animal was treated with analgesic (as above) and observed for signs of distress/wound inflammation. No further procedures were carried out on these animals for a minimum of one week.

Measurements. A 30 min sensor baseline was established before the first drug infusion. For anesthetized animals a 3 mL syringe filled with the target drug was connected to the sensor-free catheter (placed in the jugular opposite that in which the sensor is emplaced) and placed in a motorized syringe pump (KDS 200, KD Scientific Inc., Holliston, MA, USA). After establishing a stable baseline, the target drug was infused through this catheter at a rate of 0.2 mL/min. Target

11

drugs included kanamycin (0.1 M solution; homemade), gentamicin (10 mg/mL, Henry Schein Inc., Dublin, OH, USA), tobramycin (0.1 M solution; homemade), and doxorubicin (1.0 mM, homemade). After drug infusion, recordings were taken for up to 2 h before the next infusion. The real-time plotting and analysis of voltammetric data were carried out with the help of a script written in R Studio (SI Appendix, p.15).

For the awake preparation, a pre-catheterized animal was first briefly anesthetized (as above). The sensor was threaded down the catheter and tightly attached to it via a homemade plastic joint. The joint protects the sensor from being accidentally pulled out by the animal while exploring his surroundings. Once implanted, the E-AB sensor was affixed to a leash in an operant chamber (Med Associates Inc., Fairfield, VT, USA). The animal was then allowed to recover from anesthesia and explore the chamber while recordings proceeded as described above. Following the baseline recording, the target drug was introduced via either an intramuscular injection (thigh) or via an intravenous injection given through the same catheter used to emplace the sensor.

12

Supplementary Text

Igor Script Used for Data Analysis The function is “LoadAllVoltammograms” and called in by using the command LAV("").

The first line of text and the columns to be imported must be specified using the /L modifier in the LoadWave function. The complete script is included below:

#pragma rtGlobals=3 //Global access method and strict wave access //_This function makes a menu tab to access the function_ Menu "Macros" "LoadAllVoltammograms",LAV("") End //_This function loads the x axis for all voltammograms_ Function LoadXScale(fileName, pathName) String fileName // Name of file to load or "" to get dialog String pathName // Name of path or "" to get dialog // Load the waves and set the local variables. LoadWave/A/J/D/O/L={0,0,0,0,0}/P=$pathName fileName if (V_flag==0) // No waves loaded. Perhaps user canceled. return -1 endif // Put the names of the three waves into string variables String s0 s0 = StringFromList(0, S_waveNames) Wave w0 = $s0 // Create wave references. Edit/N=Voltammograms w0 return 0 // Signifies success. End //_This loop function loads the differential current of all voltammograms and makes a table_ Function LAV(pathName) String pathName String fileName String tmpr="tmpr",tmpn="tmpn" String xaxis Make/D/O/N=1 $tmpr Wave rtmp=$tmpr Make/D/O/N=1 $tmpn Wave ntmp=$tmpn LoadXScale("","") Variable index=0

13

if (strlen(pathName)==0) NewPath/O temporaryPath if (V_flag != 0) return -1 endif pathName = "temporaryPath" endif Variable result do fileName = IndexedFile($pathName, index, ".txt") if (strlen(fileName) == 0) break endif if (index>0) InsertPoints index,1,rtmp InsertPoints index,1,ntmp endif LoadWave/A/J/D/O/L={0,0,0,1,0}/P=$pathName fileName if (V_flag==0) // No waves loaded. Perhaps user canceled. return -1 endif // Put the names of the three waves into string variables String s0 s0 = StringFromList(0, S_waveNames) Wave w0 = $s0 Smooth/B=1 10, w0 AppendToTable w0 waveStats/Q/R=(30,300) w0 rtmp[index]=V_max-V_min ntmp[index]=index index += 1 while (1) Duplicate/O ntmp $"FileNumber" Duplicate/O rtmp $"Differential" DIsplay $"Differential" vs $"FileNumber" Killwaves rtmp,ntmp if (Exists("temporaryPath")) KillPath temporaryPath endif return 0 // Signifies success. End //_This function produces a prompt to open a file_

14

RStudio Script Used for Real-Time Data Plotting

The R compiler and the R Studio software are needed for these scripts to work. Two files were coded: RTMonitor.R and config.R.

RTMonitor.R -----------------------------------------------------------------------------------------------------------------

# VER 3.3 rm(list = ls()) ### Functions Definitions { Stack <- function(data = NULL) { # ver 3.0 nc = list( data <- list(), top <- function() { return(data[length(data)]) }, pop <- function() { d <- nc$top() data[length(data)] <- NULL return(d) }, push <- function(e) { data[length(data) + 1] <- e }, empty <- function() { data <- list() }, isempty <- function() { return(is.null(nc$data)) }, main = function(data) { nc$data <- data } ) nc$main(data) nc <- list2env(nc) class(nc) <- "Config" return(nc) } Config <- function(presetpath = './config.R') { # ver1.3.3

15

nc = list( var.list = list(), val.list = list(), presetpath = NULL, get = function(x) nc[[x]], set = function(x, value) nc[[x]] <<- value, loadpreset = function(presetpath) { # Config setting # Read saved config.R file, if can't find, then use default. cat('Reading CONFIG.R as preset config...') if (file.exists(presetpath)) { config.file <- file(presetpath) config.lines <- readLines(config.file) close(config.file) # Seperate variables with values and store in var.list and val.list var.lines <- config.lines[1:(length(config.lines))] var.lines.list <- strsplit0(var.lines, split = '=', simplify = FALSE) var.list <- list() val.list <- list() for (i in 1:length(var.lines.list)) { var.list[[length(var.list) + 1]] <- var.lines.list[[i]][1] val.list[[length(val.list) + 1]] <- strsplit0(var.lines.list[[i]][2], split = ' ') } } nc$set('var.list', var.list) nc$set('val.list', val.list) }, changevarval = function(var.list, val.list) { # Source var.list = nc$var.list val.list = nc$val.list # making config from user input printcursetup(var.list, val.list, '=') while (T) switch ( as.character(menu.config()), '1' = { printcursetup(var.list, val.list, '=') choice <- menu.edit.modify(length(var.list)) if (choice == 0)

16

next i <- choice cat(sprintf('Input your new configs\n')) cat(sprintf( 'Previously, %d. %s = %s\n', i, paste0(var.list[[i]]), paste0(collapse = ' ', val.list[[i]]) )) input <- readline(prompt = sprintf('Now, %d. %s = ', i, paste0(var.list[[i]]))) val.list[[i]] <- strsplit0(input, split = ' ') printcursetup(var.list, val.list, '=') nc$set('var.list', var.list) nc$set('val.list', val.list) }, # Modify '2' = { printcursetup(var.list, val.list, '=') while (T) switch ( as.character(menu.save()), '1' = { cat("-------Save_and_Overwrite-------\n") zz <- file('./config.R', 'w') for (i in 1:length(val.list)) { writeLines( sprintf( '%s=%s', paste0(collapse = ' ', var.list[[i]]), paste0(collapse = ' ', val.list[[i]]) ), con = zz,sep = "\n" ) } close(zz) printcursetup(var.list, val.list, '=') break }, # Save_and_Overwrite '2' = { cat("----------------Save_as---------------\n") cat("Input file name. Leave empty as default\n") config.filename <- readline('Save as: ') if (config.filename == '') { zz = file(paste0(

17

'config-', format(Sys.time(), "%Y%m%d%H%M%S"), '.R' ), 'w') } else { zz = file(paste0(config.filename, '.R'), 'w') } for (i in 1:length(val.list)) { writeLines( sprintf( '%s=%s', paste0(collapse = ' ', var.list[[i]]), paste0(collapse = ' ', val.list[[i]]) ), con = zz,sep = "\n" ) } l <- length(config.lines) writeLines(as.character(config.lines[(l - 1):l]), con = zz, sep = '\n') close(zz) printcursetup(var.list, val.list, '=') break }, # Save as '0' = break ) }, # Save '3' = { cat('Choose your config file to load') waitasec() config.to.load <- file(choose.files()) config.lines <- readLines(config.to.load) # Seperate variables with values and store in var.list and val.list var.lines <- config.lines[1:(length(config.lines))] var.lines.list <- strsplit0(var.lines, split = '=', simplify = FALSE) var.list <- list() val.list <- list() for (i in 1:length(var.lines.list)) { var.list[[length(var.list) + 1]] <- var.lines.list[[i]][1] val.list[[length(val.list) + 1]] <- strsplit0(var.lines.list[[i]][2], split = ' ')

18

} printcursetup(var.list, val.list, '=') nc$set('var.list', var.list) nc$set('val.list', val.list) next }, # Load '9' = break, # Continue '0' = quit() # Back ) }, recovernumeric = function(var.list = nc$var.list, val.list = nc$val.list) { if (is.null(var.list)) return(null) for (index in 1:length(val.list)) { if (!is.na(as.numeric(val.list[[index]]))) { val.list[[index]] <- as.numeric(val.list[[index]]) } } # recovering numeric nc$set('var.list', var.list) nc$set('val.list', val.list) }, assignconfig = function(envir = sys.frame(which = -1L)) { nc$recovernumeric() val.list <- nc$val.list var.list <- nc$var.list for (index in 1:length(var.list)) assign(var.list[[index]], val.list[[index]], envir = envir) }, setup = function(presetpath = nc$presetpath) { nc$loadpreset(presetpath) nc$changevarval(nc$var.list, nc$val.list) nc$recovernumeric() # get var.list to namespace of Config for (i in 1:length(nc$var.list)) nc$set(nc$var.list[[i]], nc$val.list[[i]]) }, main = function(presetpath) { nc$set('presetpath', presetpath) } ) nc$main(presetpath) nc <- list2env(nc) class(nc) <- "Config" return(nc) } readme.copyright <- function() {

19

cat(' CHI Graphic Realtime Watcher \n') cat(' Yanxian Lin Plaxco Lab \n') cat(' UC Santa Barbara \n') cat('--------------------------------------------------\n') cat(' Ver 1.3.3 01/29/2016 \n') readline() } readme.instruction <- function() { cat(' \n') cat('Welcome to CHI Graphic Realtime Watcher. \n') cat(' \n') cat('This program monitor CHI txt data files in a \n') cat('defined path and present realtime graphic. \n') cat(' \n') cat('Before continue, make sure you have read the \n') cat('readme.txt and know the option of config. \n') readline() cat(' \n') cat(' \n') cat(' \n') cat('During program running, you can press ESC any time\n') cat('you want to shut down the process \n') readline('Enter to start >>> ') } # menu menu.config <- function(default = 9) { # print main menu cat("---------Main-----------\n") cat(' 1 . Modify \n') cat(' 2 . Save \n') cat(' 3 . Load \n') cat(' 9 . Continue \n') cat(' 0 . Back \n') while (T) { choice <- input.number('>> [9] ', default = default) if (choice %in% c(1,2,3,9,0)) break tryagain() } return(choice) } menu.edit <- function(default = 1) { # Editing Menu cat("--------Main/Edit/------\n") cat(' 1 . Modify \n')

20

cat(' 2 . Add \n') cat(' 3 . Delete \n') cat(' 0 . Back \n') while (T) { choice <- input.number('>> [1] ', default = default) if (choice %in% c(1,2,3,0)) break tryagain() } return(choice) } menu.edit.modify <- function(length.of.object, default = 0) { # Modifying Menu cat("--------Main/1-Edit/1-Modify--------------------\n") cat("Enter config number above to change. \n") cat("When finished, Enter 0 \n") while (T) { choice <- input.number('>> [0] ', default = default) if (choice %in% 0:length.of.object) break tryagain() } return(choice) } menu.edit.delete <- function(length.of.object, default = 0) { # Deleting Menu cat("--------Main/1-Edit/2-Delete/-----------\n") cat("Enter config number to delete. \n") cat("When finished, Enter 0 \n") while (T) { choice <- input.number('>> [0] ', default = default) if (choice %in% 0:length.of.object) break tryagain() } return(choice) } menu.edit.add <- function(default = '0') { # Adding Menu cat("-----------------Main/Edit/Add/---------------\n") cat("Enter parameter name to add. \n") cat('Enter 0 to cancel and go back \n')

21

choice <- input.string('>> ') } menu.save <- function(default = 0) { cat("-------Main/Save/-------\n") cat(' 1 . Save as default \n') cat(' 2 . Save as ... \n') cat(' 0 . Cancel \n') while (T) { choice <- input.number('>> [0] ', default = default) if (choice %in% c(1,2,0)) break tryagain() } return(choice) } # string strsplit0 <- function(strVec, split, simplify = TRUE) { # Split a string vector with multiple split # # Args: # strVec: One vector whose elements are to be split. # split: character vector containing regular expression(s) to use for splitting. # simplify: If TRUE, returns character vector; if not, list; Default is TRUE. # # Returns: # vector or list of split characters. if (simplify == TRUE) { strsplit0 <- c() for (sp in split) { strVec <- strVecSplit(strVec, sp) } strsplit0 <- strVec return(strsplit0) } else { strsplit0 <- list() for (str in strVec) { for (sp in split) { str <- strVecSplit(str, sp) } strsplit0[[length(strsplit0) + 1]] <- str } return(strsplit0) } } strVecSplit <- function(strVec, split) {

22

strVecSplit <- c() for (str in strVec) { unlist <- unlist(strsplit(str, split = split)) strVecSplit <- c(strVecSplit, unlist) } return(strVecSplit) } # Used by strsplit0() # input and output readline0 <- function(fmt = '', ...) { return(readline(prompt = sprintf(fmt, ...))) } # readline modified @ver1.2 input.number <- function(fmt = 'Please Enter a Number >> ', default = NA, ...) { while (T) { input.anything <- readline(prompt = sprintf(fmt, ...)) if (input.anything == '') return(default) if (!is.na(as.numeric(input.anything))) return (as.numeric(input.anything)) tryagain() } } input.string <- function(fmt = 'Please Enter a String >> ', default = '', no.input = T, ...) { while (T) { input.anything <- readline0(fmt, ...) if (no.input & input.anything == '') return(default) if (!no.input & input.anything == '') tryagain() return(input.anything) } } tryagain <- function() { cat('Please try again...\n') } waitasec <- function() { readline("Press Enter to continue >>") } printcursetup <- function(var.list, val.list, connection = '=') { # print current setup # Ver 1.1 cat(' \n') cat(' Current Setup \n') cat('------------------------\n') for (i in 1:length(var.list)) {

23

cat(sprintf( '%2d. %-20s %s %s\n', i, paste0(collapse = ' ', var.list[[i]]), connection, paste0(collapse = ' ', val.list[[i]]) )) } } # print current setup @ver1.1 choosefrom <- function(from, prompt = '', default.index = 1, return.index = F) { while (T) { if (prompt == '') cat(sprintf("Choose item from list below\n")) cat(prompt) for (i in 1:length(from)) { cat(sprintf("%s. %s\n", i, from[[i]])) } index <- as.integer(as.numeric(input.number('>> ', default = default.index))) if (!(index %in% 1:length(from))) { tryagain() next } if (return.index) return(index) return(from[[index]]) } } read.csv0 <- function(path, skip = 0) { df <- NULL try({ df <- read.csv(path[length(path)], skip = skip) }, silent = T) if (is.null(df)) { cat(sprintf('File access failed @ \n %s', basename(path))) } return(df) } ifelse0 <- function(logic, yes, no) { if (logic) { return(yes) } else { return(no)

24

} } assign0 <- function(var, val, default = NA) { if (length(var) > length(val)) { assign0(var[1:length(val)], val) assign0(var[(length(val) + 1):length(var)], rep(default, length(var) - length(val))) }else{ for (i in 1:length(val)) { assign(var[i], val[i], envir = sys.frame()) } } } # math finiteIntegrate <- function(x, y, r = seq_along(y)) { # VER 3.1 # return integral(0->x) y(r) A <- 0 t <- 0 B <- 0 result <- 0 for (i in r[-length(r)]) { A[i] <- (y[i] + y[i + 1]) / 2 t[i] <- (r[i] + r[i + 1]) / 2 B[i + 1] <- B[i] + A[i] * t[i] } B <- B[-1] for (i in seq_along(x)) { ind <- which(x[i] <= t)[1] if (is.na(ind)) { ind <- length(t) x[i] <- t[ind - 1] } if (ind == 1) { result[i] <- 0 next } t2 <- t[ind] B2 <- B[ind] t1 <- t[ind - 1] B1 <- B[ind - 1] result[i] <- B1 + (B2 - B1) * (x[i] - t1) / (t2 - t1) } return(result) }

25

fit.2norm <- function(y, x = seq_along(y), para, area.output = F) { # VER 3.1 # Normal distribution + Normal distribution + linear baseline # Verify para if (class(para) != "numeric" | length(para) <= 7) warning('Function fit.2norm has no enough initial parameters') return(NULL) # First present the data in a data-frame r <- y tab <- data.frame(x = x, r = r) #Apply function nls # Load para mu1 <- para[1] sigma1 <- para[2] k1 <- para[3] mu2 <- para[4] sigma2 <- para[5] k2 <- para[6] a <- para[7] b <- para[8] res <- NULL try({ res <- (nls( r ~ k1 * exp(-1 / 2 * (x - mu1) ^ 2 / sigma1 ^ 2) + k2 * exp(-1 / 2 * (x - mu2) ^ 2 / sigma2 ^ 2) + a * x + b, start = c( k1 = k1, mu1 = mu1, sigma1 = sigma1, k2 = k2, mu2 = mu2, sigma2 = sigma2, a = a, b = x[1] ) , # a = 1e-10, data = tab )) }, silent = T) if (is.null(res)) { cat(sprintf('nls curve fit failed @ \n')) return(NULL) } v <- summary(res)$parameters[,"Estimate"] fit <- function(x) v[1] * exp(-1 / 2 * (x - v[2]) ^ 2 / v[3] ^ 2) +

26

v[4] * exp(-1 / 2 * (x - v[5]) ^ 2 / v[6] ^ 2) + v[7] * x + v[8] y <- fit(seq_along(x)) if (!area.output) return(y) fit.baseline <- function(x) v[4] * x + v[5] baseline <- fit.baseline(x) area <- finiteIntegrate(x = x, y = y - baseline) return(area) } fit.norm <- function(y, x = seq_along(y), para, area.output = F) { # VER 3.1 # Normal distribution + linear baseline # Verify para if (class(para) != "numeric" | length(para) <= 4) { warning('Function fit.2norm has no enough initial parameters') return(NULL) } # First present the data in a data-frame r <- y tab <- data.frame(x = x, r = r) #Apply function nls # Load para mu <- para[1] sigma <- para[2] k <- para[3] a <- para[4] b <- para[5] res <- NULL try({ res <- (nls( r ~ k * exp(-1 / 2 * (x - mu) ^ 2 / sigma ^ 2) + a * x + b, # +a*x start = c( mu = mu, sigma = sigma, k = k, a = a, b = x[1] ) , # a = 1e-10, data = tab )) }, silent = T) if (is.null(res)) { cat(sprintf('nls curve fit failed @ \n'))

27

return(NULL) } v <- summary(res)$parameters[,"Estimate"] fit <- function(x) v[3] * exp(-1 / 2 * (x - v[1]) ^ 2 / v[2] ^ 2) + v[4] * x + v[5] # v[4]*x + y <- fit(x) if (!area.output) return(y) fit.baseline <- function(x) v[4] * x + v[5] baseline <- fit.baseline(x) area <- finiteIntegrate(x = x, y = y - baseline) return(area) } fit.smooth <- function(y, x = seq_along(y), r = x, para = NULL) { # VER 1.0 smooth.y <- NULL try({ smooth.y <- smooth.spline(x, y)$y }, silent = T) if (is.null(smooth.y)) { cat(sprintf('Fit.smooth Failed')) return(NULL) } # if (!area.output) return(smooth.y) # area <- finiteIntegrate(x = x, y = smooth.y, r = x) # return(area) } fit.curve <- function(x, mode = 'fit.smooth', config) { # Ver 3.1 curve <- NULL if (mode == 'fit.norm') { para <- c(config$mu1, config$sigma1, config$k1, config$a, config$b) curve <- fit.norm(x, para = para) } else if (mode == 'fit.smooth') { curve <- fit.smooth(x) } else if (mode == 'fit.2norm') { para <- c( config$mu1, config$sigma1, config$k1, config$mu2, config$sigma2, config$k2, config$a, config$b

28

) curve <- fit.2norm(x, para = para) } else { cat('curve fit model does not exist\n') return(NULL) } # return NULL if no peaks or no velleys if (is.null(curve)) { warning('Curve not fit\n') return(NULL) } else { return(curve) } } get.valley <- function(curve) { #Ver 3.3 curve.min <- min(curve) len = length(curve) if (len == 1) return (NULL) if (curve.min == curve[len]) return(get.valley(curve[1:len - 1])) if (curve.min == curve[1]) return(get.valley(curve[2:len])) return(curve.min) } get.peak <- function(curve) { #Ver 3.3 curve.max <- max(curve) len = length(curve) if (len == 1) return (NULL) if (curve.max == curve[len]) return(get.peak(curve[1:len - 1])) if (curve.max == curve[1]) return(get.peak(curve[2:len])) return(curve.max) } # get.2valleys <- function(curve) { # # VER 3.4 # valley[1] <- get.valley(curve) # if(is.null(valley[1])) return(NULL) # valley.x[1] <- which(curve == valley[1]) # peak[1] <- get.peak(curve) # if(is.null(peak[1])) return(NULL) # peak.x[1] <- which(curve == peak[1]) # if(peak.x[1] < valley.x[1]) { # valley[2] <- get.valley(curve[1:valley.x[1]])

29

# } else { # valley[2] <- get.valley(curve[valley.x[1]:length(curve)]) # } # if (is.null(valley[2])) return(NULL) # valley.x[2] <- which(curve == valley[2]) # # baseline # return(cbind(valley.x, valley)) # } find.peak <- function(x, mode = 'fit.smooth', config) { # Ver 3.3 curve <- fit.curve(x, mode = mode, config) if (is.null(curve)) { cat(sprintf('Curve fit failed, no peak found!')) return(NULL) } # warning if multiple peaks or multiple velleys # Then return the highest peaks and lowest velleys # Ver 3.3 curve.max <- get.peak(curve) curve.max.p <- which(curve == curve.max)[1] curve.min <- get.valley(curve) curve.min.p <- which(curve == curve.min)[1] # Ver 3.1 # curve.max <- max(curve) # curve.max.p <- which(curve == curve.max) # curve.min <- min(curve) # curve.min.p <- which(curve == curve.min) poi <- floor((curve.max.p + curve.min.p) / 2) voi <- curve[poi] if(is.na(poi)|is.na(voi)) return(NULL) # VER 3.3.2 if (curve[1] < voi) { # Positive Peak return(c(curve.max - curve.min, curve.max.p)) } else if (voi < curve[1]) { # Negative Peak return(c(curve.max - curve.min, curve.min.p)) } else { warning('find.peak() UNKNOWN ERROR \n') return(NULL) } } fit.area <- function(y, x = seq_along(y), mode = 'fit.smooth', config) {

30

# Ver 3.4 area <- NULL if (mode == 'fit.smooth') { area <- fit.smooth(y, x = x) # smooth.y <- fit.smooth(y, x = x) # valleys <- get.2valleys(smooth.y) } else { cat('area fit model does not exist\n') return(NULL) } # return NULL if no peaks or no velleys if (is.null(area)) { warning('area not fit\n') return(NULL) } else { return(area) } } find.area <- function(y, x = seq_along(y), mode = 'fit.smooth', config) { # Ver 3.2 area <- fit.area( y = y - y[1], x = x, mode = mode, config = config ) if (is.null(area)) { cat(sprintf('area fit failed, no area found!')) return(NULL) } return(area[length(area)]) } odd <- function(x) x[(1:length(x)) %% 2 != 0] even <- function(x) x[(1:length(x)) %% 2 == 0] evenb <- function(x) x[!odd(x)] # monitoring plot.raw.n.fit <- function(df, config, mark, screen.n) { # VER 1.3.0 screen(screen.n) erase.screen() flush.console() par(mar = config$margin) mode <- config$mode # df <- NULL

31

# try({ # df <- read.csv(path, skip = skip) # }, silent = T) # if (is.null(df)) { # cat(sprintf( # 'plot.raw.n.fit() > File access failed @ \n %s', basename(path) # )) # return(NULL) # } # fc <- NULL try({ fc <- fit.curve(df[,2], mode, config) }, silent = T) if (is.null(fc)) { cat( sprintf( 'plot.raw.n.fit() > fit.curve() > Curve fit failed @ \n %s', basename(path) ) ) return(NULL) } plot( df[,1], df[,2], type = 'l', col = 'blue', main = mark, xlab = 'Voltage', ylab = 'Current' ) par(new = T) plot( df[,1], fc, type = 'l', col = 2, axes = F, main = '', xlab = '', ylab = '' ) par(new = F) } get.mark <- function(filename, marks) { # VER 1.0 # mark this.file filename.parts <- strVecSplit(filename, '_')

32

mark.index <- which(marks %in% filename.parts) if (length(mark.index) == 0) return(NA) mark <- marks[[mark.index]] return(mark) } plot.calculate <- function(data4plot, config, screen.n) { if (nrow(data4plot) == 0) return with(data4plot, { now <- timepassed[length(timepassed)] window.t <- config$window.t if (!is.numeric(window.t)) { timelim <- NULL } else { x.lo <- now - window.t x.hi <- now timelim <- c(x.lo, x.hi) } screen(screen.n) par(mar = config$margin) plot( timepassed, peak.y, type = 'l', xlim = timelim, main = paste0(marks[2], ' - ', marks[1]), xlab = 'time', ylab = 'Peak Current' ) points(now, peak.y[length(peak.y)], col = 'red', pch = 16) }) } } ### Initialization { ### Working Directory workingDirectory <- dirname(sys.frame(1)$ofile) # Test # workingDirectory <- 'D:\\Desktop\\Github\\R\\RTMonitor\\RTMonitor'

33

setwd(workingDirectory) ## Copyright and Instruction readme.copyright() readme.instruction() ## Setting up configs preset.path <- './config.R' config = Config(preset.path) config$setup() ### Monitoring Ver1.3.3 # Initialize marks <- config$marks Data.unsorted <- data.frame() Data.sorted <- data.frame() data4plot <- data.frame() time.start <- Sys.time() cur.file.list <- list() psv.null.files <- list() # Test Only options(stringsAsFactors = FALSE) scan.num <- as.integer(config$watching.time / config$watching.interval) # split screen # screen.split <- config$screen.split try(dev.off()) split.screen(screen.split) if (config$plotMode == 1) { split.screen(screen = 1, c(1, length(marks))) split.screen(screen = 2, c(1, length(marks))) } if (config$plotMode == 2) { split.screen(screen = 1, c(1, length(marks))) } if (config$plotMode == 3) { split.screen(screen = 1, c(1, length(marks))) split.screen(screen = 2, c(1, length(marks))) }

34

} ### scan cur.file.list <- list() if.first.sort <- TRUE Data.normed <- data.frame() df.stack <- data.frame() first.peak <- NULL timepassed <- c() peak.y <- c() for (scani in 1:scan.num) { cat('>') Sys.sleep(config$watching.interval) monitor.path <- paste0(config$monitor.path, collapse = ' ') # building Data.unsorted from file.list = setdiff(...) new.file.list <- list.files(monitor.path, pattern = '.txt', full.names = T) file.list <- setdiff(new.file.list, cur.file.list) cur.file.list <- new.file.list if (length(file.list) == 0) next # file.list --> Data.unsorted VER 3.1 # file.list --> peak.y.us, peak.x.us VER 4.0 data <- data.frame() for (this.file in file.list) { # Load data VER 3.1 # df <- NULL if (is.null(df)) for (i in 1:3) { try({ df <- read.csv(this.file, skip = config$skipRow) }, silent = T) } if (is.null(df)) { next

35

} # find peak and build Data.unsorted VER 1.3 # peak <- find.peak(df[,2], mode = config$mode, config = config) area <- find.area(df[,2], mode = config$mode, config = config) # VER 3.2.2 if (is.null(peak)) { psv.null.files <- append(psv.null.files, this.file) cat(this.file) cat('\n peak is null \n') next } mtime <- file.info(this.file)$mtime this.y <- peak[1] this.x <- peak[2] filename <- basename(this.file) this.mark <- get.mark(filename, marks) this.resi <- sub(this.mark, '', filename) this.timepassed <- difftime(mtime, time.start, units = config$unit.time) data <- rbind( data, list( time = mtime, peak.x = this.x, peak.y = this.y, mark = this.mark, filename = this.resi, timepassed = this.timepassed, path = this.file, area = area # VER 3.2.2 ) ) } # file.list --> Data.unsorted if (nrow(data) == 0) { next } # Data.unsorted <- rbind(Data.unsorted, data) Data.unsorted <- data print(basename(tail(data$path))) # Test if (is.null(Data.unsorted$timepassed)) { cat('NON DATA TXT FILES EXISTS') next

36

} # Sorting Data.unsorted --> Data.sorted # Data.sorted <- Data.unsorted[order(Data.unsorted$timepassed),] # Data.sorted <- rbind(Data.sorted, Data.unsorted[order(Data.unsorted$timepassed),]) if (nrow(Data.sorted) == 0) next # Normalize Data.sorted VER 3.1 # if (if.first.sort) { print('FIRST SORT') # TEST for (i in 1:length(marks)) { this.mark <- marks[i] this.first.peak <- Data.sorted[which(Data.sorted$mark == this.mark),]$peak.y[1] if (!any(is.na(this.first.peak)) & !any(is.null(this.first.peak))) if (any(is.na(first.peak[i])) | any(is.null(first.peak[i]))) first.peak[i] <- this.first.peak } if (length(marks) == sum(!is.na(first.peak))) if.first.sort <- F } # VER 3.1 if (config$normalized == 1) { some.df <- data.frame() for (i in 1:length(marks)) { mark <- marks[i] subset.index <- !is.na(Data.sorted$mark) & (Data.sorted$mark == mark) this.df <- Data.sorted[subset.index,] this.df$peak.y <- this.df$peak.y / first.peak[i] some.df <- rbind(some.df, this.df) } Data.sorted <- some.df } # Ver 3.3 if (config$plotMode == 1) {

37

Data.normed <- rbind(Data.normed, Data.sorted) print(nrow(Data.normed)) # TEST # plot uncalculated data VER 1.2.6 # for (this.mark in marks) { this.df.sub <- subset(x = Data.normed, mark == this.mark) if (nrow(this.df.sub) == 0) next # timepassed <- rbind(timepassed, this.df.sub$timepassed) # path <- rbind(path, this.df.sub$path) # peak.y <- rbind(peak.y, this.df.sub$peak.y) timepassed <- this.df.sub$timepassed path <- this.df.sub$path peak.y <- this.df.sub$peak.y now <- timepassed[length(timepassed)] window.t <- config$window.t if (!is.numeric(window.t)) { timelim <- NULL } else { x.lo <- now - window.t x.hi <- now timelim <- c(x.lo, x.hi) } df <- read.csv0(path[length(path)], skip = config$skipRow) if (is.null(df)) next # plot raw data and fitting curve screen.n <- which(this.mark == marks) + screen.split[1] * screen.split[2] suc <- F try({ plot.raw.n.fit( df = df, config = config, mark = this.mark, screen.n = screen.n ) suc <- T }) if (!suc) next # plot history peak current

38

screen.n <- which(this.mark == marks) + length(marks) + screen.split[1] * screen.split[2] this.sc.n <- NULL try({ this.sc.n <- screen(screen.n) }, silent = T) if (is.null(this.sc.n)) { next } else { erase.screen() flush.console() par(mar = config$margin) plot( timepassed, peak.y, type = 'l', xlim = timelim, ylim = ifelse0(config$normalized == 1, config$ylim, NULL), # VER 1.2.6 # main = this.mark, xlab = 'time', ylab = 'Peak Current', axes = F ) grid() at.time <- pretty(timepassed) labels.time <- pretty(timepassed / config$scale.time) axis(1, at = at.time, labels = labels.time) at.y <- pretty(peak.y) labels.y <- pretty(peak.y / config$scale.y) axis(2, at = at.y, labels = labels.y) points(now, peak.y[length(peak.y)], col = 'red', pch = 16) } } next } # Ver 3.4 TODO: if (config$plotMode == 0) { print(paste('Data.sorted', nrow(Data.sorted))) # TEST Data.normed <- rbind(Data.normed, Data.sorted) print(nrow(Data.normed)) # TEST # plot uncalculated data VER 1.2.6 # for (this.mark in marks) { this.df.sub <- subset(x = Data.normed, mark == this.mark)

39

if (nrow(this.df.sub) == 0) next # timepassed <- rbind(timepassed, this.df.sub$timepassed) # path <- rbind(path, this.df.sub$path) # peak.y <- rbind(peak.y, this.df.sub$peak.y) timepassed <- this.df.sub$timepassed path <- this.df.sub$path peak.y <- this.df.sub$peak.y now <- timepassed[length(timepassed)] window.t <- config$window.t if (!is.numeric(window.t)) { timelim <- NULL } else { x.lo <- now - window.t x.hi <- now timelim <- c(x.lo, x.hi) } df <- read.csv0(path[length(path)], skip = config$skipRow) if (is.null(df)) next # plot raw data and fitting curve screen.n <- which(this.mark == marks) + screen.split[1] * screen.split[2] suc <- F try({ plot.raw.n.fit( df = df, config = config, mark = this.mark, screen.n = screen.n ) suc <- T }) if (!suc) next # plot history peak current screen.n <- which(this.mark == marks) + length(marks) + screen.split[1] * screen.split[2] this.sc.n <- NULL try({ this.sc.n <- screen(screen.n) }, silent = T) if (is.null(this.sc.n)) { next

40

} else { erase.screen() flush.console() par(mar = config$margin) plot( timepassed, peak.y, type = 'l', xlim = timelim, ylim = ifelse0(config$normalized == 1, config$ylim, NULL), # VER 1.2.6 # main = this.mark, xlab = 'time', ylab = 'Peak Current', axes = F ) grid() at.time <- pretty(timepassed) labels.time <- pretty(timepassed / config$scale.time) axis(1, at = at.time, labels = labels.time) at.y <- pretty(peak.y) labels.y <- pretty(peak.y / config$scale.y) axis(2, at = at.y, labels = labels.y) points(now, peak.y[length(peak.y)], col = 'red', pch = 16) } } next } if (config$plotMode == 2) { print(paste('Data.sorted', nrow(Data.sorted))) # TEST while (nrow(Data.sorted) != 0) { # Data.sorted as a queue for current scan this.item <- Data.sorted[1,] Data.sorted <- Data.sorted[-1,] Data.normed <- rbind(Data.normed, this.item) # Data.normed as a stack for future processing # In Data.normed get rows labeled with current filename -> thisFilename.df thisFilenameIndex <- which(Data.normed$filename == this.item$filename) thisFilename.df <- Data.normed[thisFilenameIndex,] thisdfmark1 <- with(thisFilename.df, thisFilename.df[which(mark == marks[1]),]) thisdfmark2 <- with(thisFilename.df, thisFilename.df[which(mark == marks[2]),])

41

# In thisFilename.df process and remove rows with marks[1] and marks[2] if (nrow(thisdfmark1) != 0 && nrow(thisdfmark2) != 0) { data4plot <- rbind( data4plot, list( peak.y = thisdfmark2$peak.y - thisdfmark1$peak.y, peak.x = mean(thisdfmark2$peak.x, thisdfmark1$peak.x), timepassed = mean( thisdfmark2$timepassed, thisdfmark1$timepassed ), mark = 'mark2 - mark1', filename = this.item$filename ) ) Data.normed <- Data.normed[-thisFilenameIndex,] } # # In Data.normed clean rows that are not labeled within the marks # Data.normed.noMarkIndex <- # with(Data.normed, which(mark != marks[1]) & which(mark !=

marks[2])) # Data.normed <- Data.normed[-Data.normed.noMarkIndex, ] print(paste('Data.normed', nrow(Data.normed))) # TEST ## VER 3.2 # Data.normed <- rbind(Data.normed, Data.sorted) # print(nrow(Data.normed)) # TEST # # # calculate difference # # Data.normed --> data4plot # # # data4plot <- data.frame() # filename.factor <- as.factor(Data.normed$filename) # # for (thisfnf in levels(filename.factor)) { # thisFilename.df <- # Data.normed[which(Data.normed$filename == thisfnf),] # thisdfmark1 <- # with(thisFilename.df, thisFilename.df[which(mark == marks[1]),]) # thisdfmark2 <- # with(thisFilename.df, thisFilename.df[which(mark == marks[2]),]) # if (nrow(thisdfmark1) == 0 | nrow(thisdfmark2) == 0)

42

# next # # data4plot <- # rbind( # data4plot, list( # peak.y = thisdfmark2$peak.y - thisdfmark1$peak.y, # peak.x = mean(thisdfmark2$peak.x, thisdfmark1$peak.x), # timepassed = mean(thisdfmark2$timepassed,

thisdfmark1$timepassed), # mark = 'mark2 - mark1', # filename = thisfnf # ) # ) # } # if (nrow(data4plot) == 0) # next # plot raw data and fitting curve 1 # if (nrow(thisdfmark1) == 0 | nrow(thisdfmark2) == 0) next screen.n <- 1 + screen.split[1] * screen.split[2] df <- read.csv0(thisdfmark1$path, skip = config$skipRow) if (is.null(df)) next try({ plot.raw.n.fit( df = df, config = config, mark = thisdfmark1$mark, screen.n = screen.n ) }, silent = F) # plot raw data and fitting curve 2 # screen.n <- 2 + screen.split[1] * screen.split[2] df <- read.csv0(thisdfmark2$path, skip = config$skipRow) if (is.null(df)) next try({ plot.raw.n.fit( df = df, config = config, mark = thisdfmark2$mark, screen.n = screen.n ) }, silent = F) # Plot data4plot #

43

with(data4plot, { now <- timepassed[length(timepassed)] window.t <- config$window.t if (!is.numeric(window.t)) { timelim <- NULL } else { x.lo <- now - window.t x.hi <- now timelim <- c(x.lo, x.hi) } screen.n <- 2 screen(screen.n) par(mar = config$margin) plot( timepassed, peak.y, type = 'l', xlim = timelim, main = paste0(marks[2], ' - ', marks[1]), xlab = 'time', ylab = 'Peak Current' ) points(now, peak.y[length(peak.y)], col = 'red', pch = 16) }) } } if (config$plotMode == 3) { print(paste('Data.sorted', nrow(Data.sorted))) # TEST print(paste('Data.normed', nrow(Data.normed))) # TEST while (nrow(Data.sorted) != 0) { # Data.sorted as a queue for current scan this.item <- Data.sorted[1,] Data.sorted <- Data.sorted[-1,]

44

this.mark <- this.item$mark if (!this.mark %in% marks) next # Fetch data with this.mark in Data.normed # timepassed <- c(timepassed, this.item$timepassed) path <- this.item$path peak.y <- c(peak.y, this.item$area) now <- this.item$timepassed window.t <- config$window.t if (!is.numeric(window.t)) { timelim <- NULL } else { x.lo <- now - window.t x.hi <- now timelim <- c(x.lo, x.hi) } # Read current data.file # df <- read.csv0(path, skip = config$skipRow) if (is.null(df)) next # plot raw data and fitting curve screen.n <- which(this.mark == marks) + screen.split[1] * screen.split[2] suc <- F try({ plot.raw.n.fit( df = df, config = config, mark = this.mark, screen.n = screen.n ) suc <- T }) if (!suc) next # plot history peak current # screen.n <- which(this.mark == marks) + length(marks) + screen.split[1] * screen.split[2] this.sc.n <- NULL try({ this.sc.n <- screen(screen.n)

45

}, silent = T) if (is.null(this.sc.n)) { next } else { erase.screen() flush.console() par(mar = config$margin) plot( timepassed, peak.y, type = 'l', xlim = timelim, ylim = ifelse0(config$normalized == 1, config$ylim, NULL), # VER 1.2.6 # main = this.mark, xlab = 'time', ylab = 'Peak Current', axes = F ) grid() at.time <- pretty(timepassed) labels.time <- pretty(timepassed / config$scale.time) axis(1, at = at.time, labels = labels.time) at.y <- pretty(peak.y) labels.y <- pretty(peak.y / config$scale.y) axis(2, at = at.y, labels = labels.y) points(now, peak.y[length(peak.y)], col = 'red', pch = 16) } } # Data.normed <- rbind(Data.normed, Data.sorted) # print(nrow(Data.normed)) # TEST # # # plot uncalculated data VER 1.2.6 # # # for (this.mark in marks) { # this.df.sub <- # subset(x = Data.normed, mark == this.mark) # if (nrow(this.df.sub) == 0) # next # # # timepassed <- rbind(timepassed, this.df.sub$timepassed) # # path <- rbind(path, this.df.sub$path) # # peak.y <- rbind(peak.y, this.df.sub$peak.y) # timepassed <- this.df.sub$timepassed # path <- this.df.sub$path # peak.y <- this.df.sub$area # now <- timepassed[length(timepassed)] # window.t <- config$window.t # if (!is.numeric(window.t)) {

46

# timelim <- NULL # } else { # x.lo <- now - window.t # x.hi <- now # timelim <- c(x.lo, x.hi) # } # # df <- # read.csv0(path[length(path)], skip = config$skipRow) # if (is.null(df)) # next # # # plot raw data and fitting curve # screen.n <- # which(this.mark == marks) + screen.split[1] * screen.split[2] # suc <- F # try({ # plot.raw.n.fit( # df = df, config = config, mark = this.mark, screen.n = screen.n # ) # suc <- T # }) # if (!suc) # next # # # plot history peak current # screen.n <- # which(this.mark == marks) + length(marks) + screen.split[1] *

screen.split[2] # this.sc.n <- NULL # try({ # this.sc.n <- screen(screen.n) # }, silent = T) # if (is.null(this.sc.n)) { # next # } else { # erase.screen() # flush.console() # par(mar = config$margin) # plot( # timepassed, peak.y, type = 'l', xlim = timelim, # ylim = ifelse0(config$normalized == 1, config$ylim, NULL), # VER

1.2.6 # # main = this.mark, # xlab = 'time', ylab = 'Peak Current', axes = F # )

47

# grid() # at.time <- pretty(timepassed) # labels.time <- # pretty(timepassed / config$scale.time) # axis(1, at = at.time, labels = labels.time) # at.y <- pretty(peak.y) # labels.y <- pretty(peak.y / config$scale.y) # axis(2, at = at.y, labels = labels.y) # points(now, peak.y[length(peak.y)], col = 'red', pch = 16) # } # } # # next } }

config.R -----------------------------------------------------------------------------------------------------------------

k1=1 mu1=160 sigma1=100 a=1e-10 b=5e-7 initial.read=20 skipRow=15 screen.split=2 1 margin=2 2 2 2 type=l scale.y=1 scale.time=1 ylim=0 3 tlim=0 120 window.t=auto unit.current=A unit.voltage=V unit.time=s mode=fit.smooth watching.time=6000 watching.interval=0.1 marks=30Hz 60Hz 120Hz 240Hz 500Hz normalized=1 plotMode=1

monitor.path=C:\Write Folder Path here

48

References 1. Wang Y, Rando RR (1995) Specific binding of aminoglycoside antibiotics to RNA.

Chem Biol 2(5):281–290. 2. Rowe AA, Miller EA, Plaxco KW (2010) Reagentless measurement of aminoglycoside

antibiotics in blood serum via an electrochemical, ribonucleic acid aptamer-based biosensor. Anal Chem 82(17):7090–7095.

3. Wochner A, et al. (2008) A DNA aptamer with high affinity and specificity for therapeutic anthracyclines. Anal Biochem 373(1):34–42.

4. Lai RY, Seferos DS, Heeger AJ, Bazan GC, Plaxco KW (2006) Comparison of the Signaling and Stability of Electrochemical DNA Sensors Fabricated from 6- or 11-Carbon Self-Assembled Monolayers. Langmuir. 22(25): 10796–10800.