Tkinter was selected to build the graphical user interface (GUI) of the software. Through this GUI, users can input multiple parameters tailored to their specific analysis needs. Using check buttons, users can choose from a variety of plotting options to analyze their data most effectively. This flexible design ensures a more tailored and customizable experience for data visualization.
# Create main window using Tkinter
root = tk.Tk()
root.title("Plotter Control Window")
root.geometry('640x320')
# Example Checkbutton
basisVar = tk.IntVar(value=1)
basis_Check=tk.Checkbutton(root,text = "Basis Spectra", variable = basisVar, onvalue = 1, offvalue = 0).place(x = 10, y = 110)
Each filename is read from the desired directory or input source. Each file name is of the format time_hh.mm.ss.asc. Thus, from each file name, the timestamp is extracted, and the time elapsed since the earliest time is then calculated. This time difference is then stored in an array called Time, which is used whenever plotting temporal information.
# Calculate Time from filename
for file in fileList:
filename =
os.path.join(dataFolder_entVar.get(), file)
Time[fileList.index(file) =
(int(filename[-12:-10]) * 60 +
int(filename[-9:-7]) +
int(filename[-6:-4])/60) - t_0
If desired, a background correction is applied to the data. It is assumed that the data within a certain range of wavelengths, those far from the peak of emission, are a background signal. For each spectrum, the average intensity is calculated over that background range. This background intensity is then subtracted from the entire spectrum. These corrected data better represent the emission being observed. As such, they often better allow for the observation of meaningful features for further analysis.
# Calculate Background Intensity
back_df = pd.read_csv(filename, sep='\t',
header = None, names = ['Wavelength', 'Intensity'])
backCol = (back_df['Intensity'])[background]
backCorr = np.sum(backCol)/(1 + maxBackPix - minBackPix)
# Insert into indexed position in the array
backCorrArr[fileList.index(file)] = backCorr
The real and imaginary components of the spectral phasor are computed separately. The real part corresponds to the cosine projection of the signal, while the imaginary part corresponds to the sine projection. After both components are derived, they are inserted into their own arrays, then compiled into a single dataframe. By plotting the imaginary values on the vertical axis and the real values on the horizontal axis, the phasor plot is generated. This visualization maps each spectrum to a single point in the complex plane.
The layout of these phasors gives composition information about the data. Phasors from a two-component system lie on a line between the pure-component phasors. The position along this line conveys how much of each component makes up the signal in question. In a three-component system, phasors lie within the triangle bound by the pure-component phasors.
# Calculate roots of unity for phasor
reRoot = np.zeros(1 + maxPix - minPix)
for i in pixels:
reRoot[i] = np.cos((2 * np.pi * i) / maxPix)
imRoot = np.zeros(1 + maxPix - minPix)
for i in pixels:
imRoot[i] = np.sin((2 * np.pi * i) / maxPix)
# Create empty Arrays to be filled
rePhasorArr=np.zeros(len(fileList))
imPhasorArr=np.zeros(len(fileList))
# Loop through files to fill df
for file in fileList:
filename =
os.path.join(dataFolder_entVar.get(), file)
# Calculate Normalized Spectra
intensity_df = pd.read_csv(filename, sep =
'\t', header = None, names
= ['Wavelength', 'Intensity'])
# Append data to list
rePhasorArr[fileList.index(file)] =
np.sum(reRoot * normSpec)
imPhasorArr[fileList.index(file)] =
np.sum(imRoot * normSpec)
After normalization, the centroid is calculated as the sum of the data, having been weighted by wavelength. This value represents the mean of the spectrum and indicates where most of the signal is concentrated along the wavelength axis. The variance is determined by taking the sum of the data weighted by the square of the wavelengths, minus the square of the centroid, all square-rooted. This provides a measure of how spread out or concentrated the spectral data is around the centroid. Together, the centroid and variance offer further insight into the overall shape and distribution of each spectrum.
# Normalize
normSpec = intensitySpec/intensitySpec.sum()
# Centroid Calculations
weightedSpec = wavelengths * normSpec
centroid = weightedSpec.sum()
# RMS Calculations (rms=sqrt(E[X^2]-E[X]^2))
rms_value = np.sqrt(((wavelengths ** 2) * normSpec).sum() - centroid ** 2)
centroidArr[fileList.index(file)] = centroid
rmsArr[fileList.index(file)] = rms_value
It can be assumed that each spectrum is some combination of the form Af1+(1-A)f2, where f1 and f2 are some user-selected basis spectra. We can then minimize the least squares error of this composition with respect to A, thus finding a best fit spectral decomposition of the desired form.
# Calculate A (Frac1)
A = -((normBasis1 - normBasis2) * (normBasis2 - normSpec)).sum() / ((normBasis1 - normBasis2) ** 2).sum()
# Insert into indexed position in the array
frac1Arr[fileList.index(file)] = A
frac2Arr[fileList.index(file)] = 1 - A
The data, as they are being calculated, are compiled from arrays into pandas dataframes for easier manipulation and plotting. When the plot button is clicked, plots whose check buttons are marked are generated. Each plot is then created with plotly, opening in a new tab of the user's default browser. These plots are interactive, with data points labeled as the user hovers over them, and easy zoom and pan abilities. This, along with the modular nature of browser windows and tabs, allows for easy exploration of the data and the analyses done to them.
# Phasor Plot
if phasorVar.get() == 1:
# Plot data using Plotly
fig = go.Figure()
fig.add_trace(go.Scatter(x =
phasor_df['Re[Phasor]'],
y = phasor_df['Im[Phasor]'], mode =
'markers', name = 'Phasor'))
# Customize layout
fig.update_layout(title = "Phasor",
xaxis_title =
"Re[Phasor]", yaxis_title = "Im[Phasor]",
template = 'plotly_dark')
# Show plot
fig.show()