banners

Bioinformatics is defined as the application of tools of computation and analysis to the capture and interpretation of biological data. In the same way as we mentioned in the hardware section, we created a homemade spectrophotometer. In the software section we created a controller for our spectrophotometer called “UAMonitor”.

The following software was used:

  • Windows 11 Pro x64
  • Proteus 8.9 SP2 (Build 28501) with Simulino library
  • Anaconda Spyder 5.1.5 with Python 3.10.7 64 bits on Windows
  • ARDUINO Genuino 1.8.19 Arduino code compiler
  • V.S.P.D. 6 v6.9 (Build 6.9.1.134) (x86,x64) Serial port simulator.
  • The following computer was used to simulation:

  • PC Intel core i5-1035G1 CPU @ 1.00GHz, 8 GB RAM, unity HDD 1TB

Development of simulated circuit in Proteus

We developed a Simulation in Proteus to prove our program controller. We used the library Simulino to do it and we configured a virtual serial port with V.S.P.D. The following elements are used:

  • SIMULINO UNO
  • 1 blue LED
  • 1 red LED
  • 2 OPT 101P sensors
  • 2 resistor 1M Ohm
  • 2 resistor 300 Ohm
  • RS232 connector (COMPIM)

Figure 1: Circuit connection in Proteus.

Development of Arduino Code

The most important part of the Arduino program is reading the inputs that UAMonitor sends. As well as collect the information that the fluorescence and optical density sensors collect. Next, we will explain each part of the code:

#define Flu A2

#define OD A1

#define ledFlu 9

#define ledOD 10

In the first section we define the outputs and inputs of our system, the pin A2 is used to fluorescence sensor, and the pin A1 is used to Optical density sensor. In the other hand the pin 9 is used to the fluorescence LED (Blue) and the pin 10 is used to optical density LED (Red).

void setup() {

Serial.begin(9600);

delay(30);

pinMode(ledFlu, OUTPUT);

pinMode(ledOD, OUTPUT);

digitalWrite(ledFlu,0);

digitalWrite(ledOD,0);

}

In the next section we established the led blue and red like an Outputs and we turned off them. Also, we open the serial port to communicate with UAMonitor.

int vFlu=0,vOD=0,pos, i=0,res;
String dat,dat1,dat2;
void loop() {
int vFlu1 = analogRead(Flu);
int vOD1 = analogRead(OD);
vFlu1 = map(vFlu1, 0, 1024, 0, 200);
vOD1 = map(vOD1, 0, 1024, 0, 200);
Serial.println(String(vFlu1) + "," + String(vOD1));

In the third section we declared some variables and we save the lectures of the fluorescence and optical density sensor. After that, we mapped the values to adjusted it to a 1 to 200 scale and sent these lectures by the serial port.

if(Serial.available()){
dat = Serial.readString();
pos = dat.indexOf(',');

dat1= dat.substring(0,pos);
dat2= dat.substring(pos+1);
if(vFlu != dat1.toInt()){
vFlu = dat1.toInt();
digitalWrite(ledFlu,vFlu);
}
if(vOD != dat2.toInt()){
vOD = dat2.toInt();
digitalWrite(ledOD,vOD);
}

In this section we save the instruction that we received in the serial port. We tried to read if UAMonitor sent any communication to turn off or turn on the leds.

if(dat2.toInt()==1 && dat1.toInt() == 1){
i = 0;
}
if(vOD == 1 && vOD == 1){
res = 200%i;
if(res < 100){
digitalWrite(ledFlu,1);
digitalWrite(ledOD,0);
}
else{
digitalWrite(ledFlu,0);
digitalWrite(ledOD,1);
}
}
delay(10);
}

Finally, we created a function to turn on the leds alternately to measure fluorescence and optical density at the same time, but this feature could not be perfected in time and was deprecated.

After the program is finished, it is saved and a file is generated. hex that is loaded into Proteus for the simulation.

Figure 2: Simulation

Subsequently, the .hex file is uploaded to Arduino uno to the Proteus simulation.

Figure 3: Load .hex file, in Proteus.

We need to open the virtual serial port using V.S.P.D. We use port "COM 1" to "COM 2".

Figure 4: Creating port COM1 to COM2 on VSPE.

COM2 port is assigned using COMPIM in Proteus and set the baud rate to 9600 baud.

Figure 5: Port mapping in Proteus.

Development of Python Code

Before making the connection via serial communication, the "serial" module must be installed from the CMD Console. In order to be able to import the respective libraries for the serial connection.

Figure 6: Installation of serial module in CMD Console

With the module installed, a base code is generated, which sends and receives values for Arduino to compare and generate an action in the Proteus simulation:

#Abilitate if you use jupiterlab
#%matplotlib widget
#Paketing used
#Add libraries to import a plot in a Tkinterface
import tkinter
import serial
import time
import os
import re
import threading
import multiprocessing
import numpy as np
import pandas as pd
from datetime import date
from datetime import datetime
from tkinter import filedialog

# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
from matplotlib.pyplot import *
import matplotlib.animation as animation
from matplotlib import backend_bases
#Inicial conditions
ledFlu=0
ledOD=0

In the first section, we import all the libraries that we used. Also created some initial values. If you use jupiter lab you need to erase “#” to the line 2.

#Create a Tkinterface with our icon and title
root = tkinter.Tk()
root.wm_title("UAMonitor")

if "nt" == os.name:
root.wm_iconbitmap(bitmap = "iGEM.ico")
else:
root.wm_iconbitmap(bitmap = "iGEM.xbm")

#root['background']='yellow'

#imgicon = PhotoImage(file=os.path.join(Documents/Mauricio/iGEM/Arduino/Espectro_Foto/sketch_sep28a/iGEM.ico,'iGEM.ico'))
#root.tk.call('wm', 'iconphoto', root._w, imgicon)


"""
Frame = tkinter.Frame() #Frame creation
Frame.config(cursor="heart")
Frame.config(width="150", height="150")
Frame.pack(fill="both")
Frame.config(bg="blue")
Frame.pack(side="bottom")
"""

In the second place, we created a tkinter windows and assign an icon image. In dependence of kernel, we use a different file to put as icon.

# initialize the data arrays
gDATA = []
gDATA.append([0])
gDATA.append([0])
gDATA.append([0])

# create a figure with two subplots
fig, (ax1, ax2) = subplots(1,2)

#Add the figure to the Tkinterface
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)

#Configure Fluorescence plot
ax1.set_title('Fluorescence')
ax1.grid()
ax1.set_xlim((0,100))
ax1.set_ylim((-1,100))

#Configure Optical Density plot
ax2.set_title('Optical Density')
ax2.grid()
ax2.set_xlim((0,100))
ax2.set_ylim((-1,100))

# intialize two line objects (one in each axes)
line1, = ax1.plot(gDATA[0], gDATA[1], lw=2, color='green')
line2, = ax2.plot(gDATA[0], gDATA[2], lw=2, color='orange')
line = [line1, line2]


def update_line(num,line,data):
# axis limits checking. Same as before, just for both axes
for ax in [ax1, ax2]:
xmin, xmax = ax.get_xlim()
if max(data[0])>= xmax:
ax.set_xlim(xmin, 1.5*xmax)
ax.figure.canvas.draw()
# update the data of both line objects
line[0].set_data(data[0], data[1])
line[1].set_data(data[0], data[2])
return line
ani = animation.FuncAnimation(fig, update_line, blit=True, fargs=(line, gDATA),interval=100, repeat=False)

In the next section we created an array to save the values of we received from Arduino. We created a figure which we attached to the Tkinter window. Also, we configurated the figure as two graphs. We define the titles, limits and colors of the graphs. We define the data of our array as the points of the graph and configurate a function which animate (and update) the graphs in the Tkinter windows.

#In the next part we remove the button configure subplot because cause a warning message.
# mpl.rcParams['toolbar'] = 'None'
backend_bases.NavigationToolbar2.toolitems = (
('Home', 'Reset original view', 'home', 'home'),
('Back', 'Back to previous view', 'back', 'back'),
('Forward', 'Forward to next view', 'forward', 'forward'),
(None, None, None, None),
('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'),
('Zoom', 'Zoom to rectangle', 'zoom_to_rect', 'zoom'),
(None, None, None, None),
('Save', 'Save the figure', 'filesave', 'save_figure'),
)

#Add a Toolbar to control the figure
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)

In this section, we created a toolbar to modify the graphs in real time, we can move it, zoom in, zoom out, and save it. Also, we removed the button configure subplot in the tool bar.

def timer(condition):
global tim
tim=0
while True:
time.sleep(0.1)
tim = tim + 0.1
if Mode != condition:
break
return

In this section we create a timer that is reset every time the measurement mode is changed and always runs as a secondary thread.

def GetData(gDATA,condition,state):

global format
#Time
if condition != 'M' or state != 'O':
format = datetime.now().strftime('%d-%m-%Y, %H;%M;%S')

global vFlu
vFlu=0
global vOD
vOD=0

gDATA[0]=[0]
gDATA[1]=[0]
gDATA[2]=[0]


#datAR =serialArduino.readline().decode('ascii')
datAR =serialArduino.readline().decode('ascii').strip()
time.sleep(0.5)

if condition == 'M' and state == 'O':
time.sleep(1.0)

format_before = '0'

while True:

if datAR:
pos=datAR.index(",")
if state == 'F':
vFlu=int(datAR[:pos])
if state == 'O':
vOD=int(datAR[pos+1:])
gDATA[0].append(tim)
gDATA[1].append(vFlu)
gDATA[2].append(vOD)

saves = pd.DataFrame(gDATA,index=['Time', 'Fluorecense','Optical Density']).transpose()
saves.to_csv('data '+str(format)+'('+str(condition)+').csv', index=False)
if len(gDATA[0]) > 200:
if condition != 'M' or state != 'O':
if format_before != '0':
data = pd.read_csv('data '+str(format)+'('+str(condition)+').csv', index=False)
updated_data = pd.read_csv('data '+str(format)+'.csv')
final_dataframe = pd.concat([data, updated_data]).drop_duplicates(subset='Time', keep='last').reset_index(drop=True)
final_dataframe.to_csv('data '+str(format)+'.csv', index=False)
os.remove('data '+str(format_before)+'.csv')
i = 0
gDATA[0]=[]
gDATA[1]=[]
gDATA[2]=[]
format_before = format
format=datetime.now().strftime('%d-%m-%Y, %H;%M;%S')
datAR =serialArduino.readline().decode('ascii').strip()
time.sleep(1)
if Mode != condition:
break
return

This section contains the most complicated function. This communicates the Arduino to collect data from the fluorescence and optical density light sensors. This information is saved in the previously created array. Likewise, it assigns the time in which the function to create a CVS file that is saved automatically is started. Finally, when the array reaches a length of 200, it restarts and creates a new CVS file that is concatenated with the already created.

def Monitor_loop(condition):
while True:
Assing_LED(1,0)
time.sleep(1)
Assing_LED(0,1)
time.sleep(1)
print("Sí")
if condition != 'M':
break
return

#This funtion control the state of the LEDs
def Assing_LED(ledFlu,ledOD):
dat = str(ledFlu) + ","+ str(ledOD)
serialArduino.write(dat.encode('ascii'))
return

def _stop():
if serialArduino != None:
Assing_LED(0,0)
global Mode
Mode = 'X'
return

def _connect():
global serialArduino
serialArduino = serial.Serial("COM2",9600,timeout=1.0)
if serialArduino != None:
tkinter.messagebox.showinfo("Information Window", "Successful connection with Arduino")
return

def _quit():
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to
_stop()
serialArduino.close()
return # Fatal Python Error: PyEval_RestoreThread: NULL tstate

def _save():
dataFile=pd.read_csv('data '+str(format)+'('+str(Mode)+').csv')
SAVING_PATH = filedialog.asksaveasfile(mode='w', defaultextension=".csv")
dataFile.to_csv(SAVING_PATH)

In this section, we define multiple functions that have different purposes. First, the Monitor_loop function controls a loop in which the fluorescence and optical density LEDs alternately turn on and off. Afterwards, the Assing_LED function sends the necessary information to turn on or off the leds in the Arduino. The third function stops all measurements. The fourth function closes the serial port and closes the window created by Tkinter. And the fifth function saves the files created by the GetData function in the desired location.

'''**********************
* MODES MENU *
************************
* *
* F >> Fluorence mode *
* O >> OD mode *
* M >> Monitor mode *
* X >> Salir *
* *
************************'''
#Definition of Fluorescence Mode def _FLU():
global Mode
Mode='O'
Assing_LED(1,0)
Cronometer = threading.Thread(target = timer, args=('F',))
dataCollectorFLU = threading.Thread(target = GetData, args=(gDATA,'F','F',))
dataCollectorFLU.start()
Cronometer.start()
return
#Definition of Optical Density Mode
def _OD():
global Mode
Mode='O'
Assing_LED(0,1)
Cronometer = threading.Thread(target = timer, args=('O',))
dataCollectorOD = threading.Thread(target = GetData, args=(gDATA,'O','O',))
dataCollectorOD.start()
Cronometer.start()
return

#Definition of Monitor Mode
def _MON():
global Mode
Mode='M'
Assing_LED(1,1)
#loop= threading.Thread(target = Monitor_loop, args=('M',))
#loop.start()
Cronometer = threading.Thread(target = timer, args=('M',))
dataCollectorMON_FLU = threading.Thread(target = GetData, args=(gDATA,'M','F',))
dataCollectorMON_OD = threading.Thread(target = GetData, args=(gDATA,'M','O',))
dataCollectorMON_FLU.start()
dataCollectorMON_OD.start()
Cronometer.start()
return

In this section we enable the measurement modes of fluorescence, optical density and a monitor mode that could not be improved and was disabled in the final version. These measurement modes open multiple threads that control the data collection, the timer and turn on the LEDs appropriately.

#Enable a button and option to quit the window
qui = tkinter.Button(master=root, text="Quit", command=_quit, fg="#E0218A")
qui.pack(side=tkinter.RIGHT)

#Enable a button and option to stop mesurate
sto = tkinter.Button(master=root, text="Stop", command=_stop, fg="#E0218A")
sto.pack(side=tkinter.RIGHT)

#Enable a button of the function to connect the program to Arduino
con = tkinter.Button(master=root, text="Connect Arduino", command=_connect, fg="#E0218A")
con.pack(side=tkinter.TOP)

#Label to indicate the version
label = tkinter.Label(root, text="UAMonitor beta v0.1")
label.pack(side=tkinter.BOTTOM, anchor=tkinter.CENTER)

#Enable a button of the Monitor Mode
Mon = tkinter.Button(master=root, text="Save", command=_save, fg="#E0218A")
Mon.pack(side=tkinter.BOTTOM)

#Enable a button of the OD Mode
OD = tkinter.Button(master=root, text="OD Mode", command=_OD, fg="#E0218A")
OD.pack(side=tkinter.LEFT)

#Enable a button of the Fluorescence Mode
Flu = tkinter.Button(master=root, text="Fluorecense Mode", command=_FLU, fg="#E0218A")
Flu.pack(side=tkinter.LEFT)

#Enable a button of the Monitor Mode
#Mon = tkinter.Button(master=root, text="Monitor Mode", command=_MON, fg="#E0218A")
#Mon.pack(side=tkinter.LEFT)

root.mainloop()
# If you put root.destroy() here, it will cause an error if the window is closed with the window manager.

Finally, we enable all the buttons that we used, every button calls a function witch we mentioned before.

This is the appearance of the final version of our software with all the functionalities enables:

Figure 7:: Visual interface of UAMonitor

Recommendations:

  • It must be verified that the Proteus has all the Arduino libraries, otherwise there will be errors in the circuit simulation.

  • Care must be taken with the assignment of COM ports, if there is a port already in use, the port must be changed in all the codes that allow it, this new COM must be the same for all.

  • When using the Tkinter library in versions greater than Python 2.7, the library must be imported with lowercase “from tkinter import * ”.

Achievements

As a proof of concept for our optical density and fluorescence measuring sensor, we performed a calibration curve using E. coli according to the dry weight protocol described in the Experiments section. This calibration curve will help us to calculate the concentration (in grams of biomass per liter of medium) of our microorganism that is being analyzed using the absorbance data recorded and monitored by the built software UAMonitor VER 0.1.

Table 18 Data taken from the OD/Fluorescence sensor coupled to the UAMonitor 0.1 for the calculation of the optical density of E. coli.

CONCENTRATION

(gBiomass/L)

ABSORBANCE 1

ABSORBANCE 2

ABSORBANCE 3

MEAN

DEVIATION

1.96

4.600

3.700

4.850

4.380

0.604

1

2.550

2.880

3.120

2.850

0.286

0.5

1.150

1.070

1.240

1.150

0.085

0.4

0.780

0.910

0.840

0.840

0.065

0.2

0.430

0.520

0.510

0.480

0.049

0.1

0.280

0.250

0.320

0.280

0.035

0.05

0.070

0.050

0.150

0.090

0.052

Figure 8: Calibration curve using different concentrations of E. coli in LB medium measuring absorbance with the OD/Fluorescence sensor.

We can observe that the best data obtained for the construction of the calibration curve are found at concentrations between 0 - 1 g Biomass/L, because this region linearly relates the absorbance and the turbidity of the medium. Fitting the average of the absorbances to a straight line, we can find an R value of 0.9942, which represents a highly fitted value.