コード例 #1
0
class InvertDataControl(ProcessingControlBase):
    def __init__(self, controller, root, accordion):
        super().__init__("Inversion Analysis", controller, accordion)
        self.m_parsedData = None
        self.m_prefactors = []
        # self.m_inputFilePath = None

        self.m_plots["Input Data"] = MPLContainer(self.m_chord.m_notebookRef,
                                                  "Input Data",
                                                  "Desorption Rate (arb. U.)",
                                                  "Temperature (K)", root)
        self.m_plots["Coverage vs. Temp."] = MPLContainer(
            self.m_chord.m_notebookRef, "Coverage vs. Temp.", "Coverage",
            "Temperature (K)", root)
        self.m_plots["Energy vs. Coverage"] = MPLContainer(
            self.m_chord.m_notebookRef, "Energy vs. Coverage", "Energy (eV)",
            "Coverage", root)
        self.m_plots["Sim. Coverage vs Temp."] = MPLContainer(
            self.m_chord.m_notebookRef, "Sim. Coverage vs Temp.",
            "Coverge (ML)", "Temperature (K)", root)
        self.m_plots["Sim. Desorption Rate vs Coverage"] = MPLContainer(
            self.m_chord.m_notebookRef, "Sim. Desorption Rate vs Coverage",
            "Desorption Rate (ML/K)", "Coverage", root)
        self.m_plots["Chi Squared vs Prefactor"] = MPLContainer(
            self.m_chord.m_notebookRef, "Chi Squared vs Prefactor",
            "Chi Squared Value", "Prefactor", root)

    def checkInput(self):
        # if(self.m_inputFilePath == None): #check for file selection
        if (self.m_fileSelectionControl.m_inputFilePath == None):
            tk.messagebox.showerror(
                "Input File",
                "Please select a preprocessed file on which to perform the inversion analysis."
            )
            return False

        if (self.m_RBVariable.get() == 0):  #single prefactor
            if not self.m_tPrefactorEntry.InputIsValid(): return False

        else:  #prefactor range
            if not self.m_tPrefactorStartEntry.InputIsValid(): return False
            if not self.m_tPrefactorEndEntry.InputIsValid(): return False

            currentEntry = float(self.m_tPrefactorStartEntry.get())
            lastEntry = float(self.m_tPrefactorEndEntry.get())

            if (self.m_RBVariable.get() == 1):  #linear range
                if not self.m_tPrefactorIncrementEntry.InputIsValid():
                    return False

                incrementEntry = float(self.m_tPrefactorIncrementEntry.get())
                if ((lastEntry - currentEntry) / incrementEntry > 20):
                    tk.messagebox.showerror(
                        "Number of Data Points",
                        "Too many simulated data points required. Adapt range so that less than 20 simulations are necessary per spectrum."
                    )
                    # raise ValueError #ridiculous amount of data points

            else:  #multiplicative range
                if (math.log10(lastEntry) - math.log10(currentEntry) > 20):
                    tk.messagebox.showerror(
                        "Number of Data Points",
                        "Too many simulated data points required. Adapt range so that less than 20 simulations are necessary per spectrum."
                    )
                    # raise ValueError #ridiculous amount of data points

        return True

    def processInput(self):
        if (not self.checkInput()): return
        self.m_invertedData = None

        self.m_parsedData = ProcessedDataWrapper(
            self.m_fileSelectionControl.m_inputFilePath)
        self.m_parsedData.parseProcessedDataFile()
        if (not self.m_parsedData.m_normalized):
            tk.messagebox.showerror(
                "Input File",
                "Please use an input file with normalized coverages!")
            return
        if (not self.m_parsedData.m_normalized):
            return  #need a normalized monolayer coverage for inversion + simulation to make sense
        # self.m_parsedData.clearInvertedData() #incase we are reusing the wrapper
        # for c in self.m_plots:
        #     c.clearPlots()
        if (self.m_RBVariable.get() == 0):  #single prefactor
            self.m_prefactors = [
                "{:e}".format(float(self.m_tPrefactorEntry.get()))
            ]
        elif (self.m_RBVariable.get() == 1):  #linear range
            currentEntry = float(self.m_tPrefactorStartEntry.get())
            lastEntry = float(self.m_tPrefactorEndEntry.get())
            incrementEntry = float(self.m_tPrefactorIncrementEntry.get())
            self.m_prefactors = []
            while (currentEntry <= lastEntry):
                self.m_prefactors.append("{:e}".format(currentEntry))
                currentEntry += incrementEntry  #increase by order of magnitude
        else:  #multiplicative range
            currentEntry = float(self.m_tPrefactorStartEntry.get())
            lastEntry = float(self.m_tPrefactorEndEntry.get())
            self.m_prefactors = []
            while (currentEntry <= lastEntry):
                self.m_prefactors.append("{:e}".format(currentEntry))
                currentEntry *= 10.0  #increase by order of magnitude

        for p in self.m_prefactors:
            self.m_parsedData.invertProcessedData(
                float(p))  #do the calculations

        self.m_parsedData.simulateCoveragesFromInvertedData()
        self.m_parsedData.evaluateData()

        #plot chi-squared value vs prefactor for all input coverages
        self.m_plots["Chi Squared vs Prefactor"].clearPlots()
        self.m_plots["Chi Squared vs Prefactor"].addPrimaryLinePlots(
            self.m_parsedData.getChiSquaredVSPrefactor(),
            self.m_parsedData.getCoverageLabels(),
            logXAxis=True)  #, logYAxis = True)
        self.m_plots["Chi Squared vs Prefactor"].addPrimaryLinePlots(
            self.m_parsedData.getChiSquaredSumVSPrefactor(), ["Sum"],
            logXAxis=True)  #, logYAxis = True)

        self.m_prefactorCB["values"] = self.m_prefactors
        self.plotDataForSelectedPrefactor()

    def plotDataForSelectedPrefactor(self, *args, **kwargs):
        if (len(self.m_prefactors) == 0):
            return
        else:
            selectedPrefactor = self.m_prefactorCB.get()
            if (selectedPrefactor == ''):
                self.m_prefactorCB.current(0)  #set to first entry
                selectedPrefactor = self.m_prefactorCB.get()

            #plot input data
            self.m_plots["Input Data"].clearPlots()
            self.m_plots["Input Data"].addPrimaryLinePlots(
                self.m_parsedData.getInputData(),
                self.m_parsedData.getCoverageLabels())
            #plot coverage vs temperature from experimental data
            self.m_plots["Coverage vs. Temp."].clearPlots()
            self.m_plots["Coverage vs. Temp."].addPrimaryLinePlots(
                self.m_parsedData.getExpCoverageVSTemp(
                    float(selectedPrefactor)),
                self.m_parsedData.getCoverageLabels())
            #plot desorption energy vs coverage from experimental data
            self.m_plots["Energy vs. Coverage"].clearPlots()
            for e, lbl in zip(
                    self.m_parsedData.getDesEnergyVSCoverageList(
                        float(selectedPrefactor)),
                    self.m_parsedData.getCoverageLabels()):
                self.m_plots["Energy vs. Coverage"].addPrimaryLinePlots(e, lbl)
            #plot simulated coverage vs temperature
            self.m_plots["Sim. Coverage vs Temp."].clearPlots()
            # self.m_plots[3].addLinePlots(self.m_parsedData.getExpDesorptionRateVSTemp())
            self.m_plots["Sim. Coverage vs Temp."].addPrimaryLinePlots(
                self.m_parsedData.getSimCoverageVSTemp(
                    float(selectedPrefactor)),
                self.m_parsedData.getCoverageLabels())
            #plot simulated desorption rate vs temperature
            self.m_plots["Sim. Desorption Rate vs Coverage"].clearPlots()
            self.m_plots[
                "Sim. Desorption Rate vs Coverage"].addPrimaryLinePlots(
                    self.m_parsedData.getSimDesRateVSTemp(
                        float(selectedPrefactor)),
                    self.m_parsedData.getCoverageLabels())

    def changeRB(self):
        self.m_tPrefactorEntry.configure(state='disabled')
        self.m_tPrefactorIncrementEntry.configure(state='disabled')
        self.m_tPrefactorStartEntry.configure(state='disabled')
        self.m_tPrefactorEndEntry.configure(state='disabled')
        if (self.m_RBVariable.get() == 0):  #single prefactor
            self.m_tPrefactorEntry.configure(state='normal')
        elif (self.m_RBVariable.get() == 1):  #linear range
            self.m_tPrefactorIncrementEntry.configure(state='normal')
            self.m_tPrefactorStartEntry.configure(state='normal')
            self.m_tPrefactorEndEntry.configure(state='normal')
        else:  #multiplicative range
            self.m_tPrefactorIncrementEntry.configure(state='disabled')
            self.m_tPrefactorStartEntry.configure(state='normal')
            self.m_tPrefactorEndEntry.configure(state='normal')

    def toggleMarkers(self):
        for c in self.m_plots.values():
            c.toggleMarkers()

    def saveData(self):
        if (self.m_parsedData == None):
            return
        outputFilePath = asksaveasfilename()
        substrings = outputFilePath.split('/')
        #consider using s = '/' result = s.join(substrings[x:y])
        outputFilePath = substrings[0]
        for s in substrings[1:-1]:
            outputFilePath = outputFilePath + '/' + s
        substrings = substrings[-1].split('.')
        fileName = substrings[0]
        if (len(substrings) > 1):
            for s in substrings[1:-1]:
                fileName = fileName + '.' + s
        # dateTimeString = str(datetime.now()).replace('-','').replace(' ', '_').replace(':','')
        # fileName = fileName + '.' + dateTimeString
        outputFilePath = outputFilePath + '/' + fileName
        self.m_parsedData.saveInvertedDataToFile(outputFilePath)

    def initChordUI(self):
        self.m_chordFrame = self.m_chord.m_scrollable_frame

        # File selection
        self.m_fileSelectionControl = SingleInputFileSelectionControl(
            self.m_chordFrame)
        self.m_fileSelectionControl.grid(row=0,
                                         column=0,
                                         columnspan=4,
                                         sticky="nsew")

        #Options

        self.m_optionsLabel = ttk.Label(
            self.m_chordFrame,
            text="Inversion Options:")  #, compound = tk.CENTER)
        self.m_optionsLabel.grid(row=3, column=0, columnspan=2, sticky="nsw")

        #Radiobutton var
        self.m_RBVariable = tk.IntVar(self.m_chordFrame)
        #Single Prefactor

        self.m_singleRB = ttk.Radiobutton(self.m_chordFrame,
                                          text="Single Prefactor",
                                          variable=self.m_RBVariable,
                                          value=0,
                                          command=self.changeRB)
        self.m_singleRB.grid(row=4, column=1, sticky="nsw")

        self.m_tPrefactorLabel = ttk.Label(self.m_chordFrame,
                                           text="Prefactor Value:")
        self.m_tPrefactorLabel.grid(row=5, column=1, sticky="nse")

        self.m_tPrefactorEntry = EnhancedEntry(
            self.m_chordFrame,
            inputValueType=float,
            errorTitle="Prefactor Value",
            errorMessage=
            "Please enter a float for the prefactor. For example 1e17.")
        self.m_tPrefactorEntry.grid(row=5, column=2, sticky="nsw")

        #Or Prefactor Range

        self.m_linearRB = ttk.Radiobutton(self.m_chordFrame,
                                          text="Prefactor Range - Linear",
                                          variable=self.m_RBVariable,
                                          value=1,
                                          command=self.changeRB)
        self.m_linearRB.grid(row=6, column=1, sticky="nsw")

        self.m_tPrefactorIncrementLabel = ttk.Label(self.m_chordFrame,
                                                    text="Increment:")
        self.m_tPrefactorIncrementLabel.grid(row=7, column=1, sticky="nse")

        self.m_tPrefactorIncrementEntry = EnhancedEntry(
            self.m_chordFrame,
            inputValueType=float,
            errorTitle="Increment",
            errorMessage="Please enter a float for the prefactor increment.")
        self.m_tPrefactorIncrementEntry.grid(row=7, column=2, sticky="nsw")

        self.m_multiplicativeRB = ttk.Radiobutton(
            self.m_chordFrame,
            text="Prefactor Range - Multiplicative (10x)",
            variable=self.m_RBVariable,
            value=2,
            command=self.changeRB)
        self.m_multiplicativeRB.grid(row=8, column=1, sticky="nsw")

        self.m_tPrefactorStartLabel = ttk.Label(self.m_chordFrame,
                                                text="Lowest Prefactor:")
        self.m_tPrefactorStartLabel.grid(row=9, column=1, sticky="nse")

        self.m_tPrefactorStartEntry = EnhancedEntry(
            self.m_chordFrame,
            inputValueType=float,
            errorTitle="Lowest Prefactor",
            errorMessage=
            "Please enter a float for the lowest prefactor. For example 1e13.")
        self.m_tPrefactorStartEntry.grid(row=9, column=2, sticky="nsw")

        self.m_tPrefactorEndLabel = ttk.Label(self.m_chordFrame,
                                              text="Highest Prefactor:")
        self.m_tPrefactorEndLabel.grid(row=10, column=1, sticky="nse")

        self.m_tPrefactorEndEntry = EnhancedEntry(
            self.m_chordFrame,
            inputValueType=float,
            errorTitle="Highest Prefactor",
            errorMessage=
            "Please enter a float for the highest prefactor. For example 1e21."
        )
        self.m_tPrefactorEndEntry.grid(row=10, column=2, sticky="nsw")

        #default values

        self.m_tPrefactorStartEntry.setBackingVar("1e15")
        self.m_tPrefactorEndEntry.setBackingVar("1e17")

        self.m_RBVariable.set(2)
        self.changeRB()

        #Process Button

        self.m_processButton = ttk.Button(self.m_chordFrame,
                                          text="Process Input",
                                          command=self.processInput)
        self.m_processButton.grid(row=11,
                                  column=1,
                                  columnspan=2,
                                  sticky="nsew")

        # self.m_prbar = ttk.Progressbar(self.m_chordFrame, orient ="horizontal", mode ="determinate", length = 50) #int(self.m_chordFrame.winfo_width() / 2))
        # self.m_prbar.grid(row = 12, column = 1, columnspan = 2, sticky="nsw")
        # self.m_prbar["value"] = 10

        #Display options

        self.m_displayOptionsLabel = ttk.Label(self.m_chordFrame,
                                               text='Display Options:')
        self.m_displayOptionsLabel.grid(row=12,
                                        column=0,
                                        columnspan=2,
                                        sticky="nsw")

        self.m_toggleMarkersButton = ttk.Button(self.m_chordFrame,
                                                text="Toggle Markers",
                                                command=self.toggleMarkers)
        self.m_toggleMarkersButton.grid(row=13,
                                        column=1,
                                        columnspan=2,
                                        sticky="nsew")

        self.m_prefactorCBLabel = ttk.Label(
            self.m_chordFrame, text='Select prefactor to display data for:')
        self.m_prefactorCBLabel.grid(row=14,
                                     column=1,
                                     columnspan=2,
                                     sticky="nsw")

        self.m_prefactorCB = ttk.Combobox(self.m_chordFrame)
        self.m_prefactorCB.bind(
            "<<ComboboxSelected>>", self.plotDataForSelectedPrefactor
        )  #binding to event because CB does not have 'command' param
        self.m_prefactorCB.grid(row=15, column=1, columnspan=2, sticky="nsew")

        #Save Button

        self.m_saveDataButton = ttk.Button(self.m_chordFrame,
                                           text="Save Inverted Data",
                                           command=self.saveData)
        self.m_saveDataButton.grid(row=16,
                                   column=1,
                                   columnspan=2,
                                   sticky="nsew")

        for child in self.m_chordFrame.winfo_children():
            child.grid_configure(padx=3, pady=3)

        self.m_chordFrame.grid_columnconfigure(index=0, weight=1)
        self.m_chordFrame.grid_columnconfigure(index=1, weight=1)
        self.m_chordFrame.grid_columnconfigure(index=2, weight=1)
コード例 #2
0
class RawDataControl(ProcessingControlBase):
    def __init__(self, controller, root, accordion):
        super().__init__("Process TPD Data", controller, accordion)
        self.m_filePaths = []
        self.m_parsedData = []
        # self.m_notebook.bind("<<NotebookTabChanged>>", self.onNotebookTabChanged)
        # self.m_plots.append(MPLContainer(self.m_notebook, "Raw Data", "Desorption Rate", "Temperature (K)"))
        self.m_plots["Raw Data vs. Temp."] = MPLContainer(
            self.m_chord.m_notebookRef, "Raw Data vs. Temp.",
            "Desorption Rate", "Temperature (K)", root)
        self.m_plots["Raw Data vs. Time"] = MPLContainer(
            self.m_chord.m_notebookRef,
            "Raw Data vs. Time.",
            "Desorption Rate",
            "Time (ms)",
            root,
            secondaryYAxis=True,
            secondaryYAxisName="Temperature (K)",
            legendLoc='center right')
        self.m_plots["Processed Data"] = MPLContainer(
            self.m_chord.m_notebookRef, "Processed Data", "Desorption Rate",
            "Temperature (K)", root)
        self.m_plots["Log Plot (Processed)"] = MPLContainer(
            self.m_chord.m_notebookRef, "Log Plot (Processed)",
            "ln(Desorption Rate)", "Temperature (K)", root)
        self.m_plots["Arrhenius Plot (Processed)"] = MPLContainer(
            self.m_chord.m_notebookRef,
            "Arrhenius Plot (Processed)",
            "ln(Desorption Rate)",
            "Reciprocal Temperature (1/K)",
            root,
            invertXAxis=True)
        # self.m_plots["Temperature Ramp"] = MPLContainer(self.m_chord.m_notebookRef, "Temperature Ramp", "Temperature (K)", "Time (ms)", root)

    def onUpdateFileList(self, fileList):
        self.m_subtractSelection["values"] = fileList
        self.m_normSelection["values"] = fileList

    def toggleSubtractCB(self):
        if (self.m_subtractCB.instate(['!selected'])):
            self.m_subtractSelection.configure(state=tk.DISABLED)
        else:
            self.m_subtractSelection.configure(state='readonly')

    def toggleNormalizeCB(self):
        if (self.m_normalizeCB.instate(['!selected'])):
            self.m_removeBackgroundCB.configure(state=tk.NORMAL)
            self.m_normSelection.configure(state=tk.DISABLED)
        else:
            self.m_removeBackgroundCB.set(
                1
            )  #if we want to normalize, we also have to remove the background, otherwise it does not make sense
            self.m_removeBackgroundCB.configure(state=tk.DISABLED)
            self.m_normSelection.configure(state='readonly')

    def toggleMarkers(self):
        for c in self.m_plots.values():
            c.toggleMarkers()

    def plotSelectedMasses(self):
        for c in self.m_plots.values():
            c.clearPlots()

        tempMasses = self.m_massDisplayOptions.getMassesToDisplay()
        if (len(tempMasses) == 0):
            return

        for d in self.m_parsedData:
            self.m_plots["Raw Data vs. Temp."].addPrimaryLinePlots(
                d.getRawDataVSRawTemp(tempMasses),
                d.getLangmuirLabels(tempMasses))
            self.m_plots["Raw Data vs. Time"].addPrimaryLinePlots(
                d.getRawDataVSRawTime(tempMasses),
                d.getLangmuirLabels(tempMasses))
            self.m_plots["Raw Data vs. Time"].addSecondaryLinePlots(
                d.getRawTempVSRawTime())
            self.m_plots["Processed Data"].addPrimaryLinePlots(
                d.getProcessedData(tempMasses),
                d.getCoverageLabels(tempMasses))
            self.m_plots["Log Plot (Processed)"].addPrimaryLinePlots(
                d.getProcessedLNData(tempMasses),
                d.getCoverageLabels(tempMasses))
            self.m_plots["Arrhenius Plot (Processed)"].addPrimaryLinePlots(
                d.getProcessedArrheniusData(tempMasses),
                d.getCoverageLabels(tempMasses))
            # self.m_plots[3].addPrimaryLinePlots(d.getRawTempVSRawTime(), d.getCoverageLabels(tempMasses))
        # self.m_plots[0].setLegendCenterRight()
        # self.m_plots["Arrhenius Plot (Processed)"].autoScaleTopY()

        #if(showTempBounds)
        # self.m_plots["Raw Data vs. Temp."].addVerticalLine(float(self.m_tCutStartEntry.get()))
        # self.m_plots["Raw Data vs. Temp."].addVerticalLine(float(self.m_tCutEndEntry.get()))

    def checkInput(self):
        if (len(self.m_fileSelectionControl.m_filePaths) == 0
            ):  #check for file selection
            tk.messagebox.showerror(
                "Input Files", "Please select at least one file to process.")
            return False
        if not self.m_calibOffsetEntry.InputIsValid(): return False
        if not self.m_calibScaleEntry.InputIsValid(): return False
        return True

    def prepareStartStopCutValues(self):
        t_Minima = [d.getRawTempMin() for d in self.m_parsedData]
        minStartCut = int(np.amin(t_Minima))
        if (minStartCut == 0):
            minStartCut = 1  #otherwise arrhenius plot will have 1/T -> 1/0 -> Inf value which causes bounds error
        t_Maxima = [d.getRawTempMax() for d in self.m_parsedData]
        maxStopCut = int(np.amax(t_Maxima))
        if (self.m_lowerBoundEntry.InputIsValid()):
            if (minStartCut > int(self.m_lowerBoundEntry.get())):
                self.m_lowerBoundEntry.set(str(minStartCut))
        else:
            self.m_lowerBoundEntry.set(str(minStartCut))
        if (self.m_upperBoundEntry.InputIsValid()):
            if (maxStopCut < int(self.m_upperBoundEntry.get())):
                self.m_upperBoundEntry.set(str(maxStopCut))
        else:
            self.m_upperBoundEntry.set(str(maxStopCut))

    def processInput(self):
        if (not self.checkInput()): return False
        self.m_parsedData = []
        #TODO: check input, maybe highlight missing entries!
        for f in self.m_fileSelectionControl.m_filePaths:
            wrapper = RawDataWrapper(f)
            wrapper.parseRawDataFile()
            self.m_parsedData.append(wrapper)
            self.prepareStartStopCutValues()
            wrapper.processParsedData(
                0,
                0,
                int(self.m_lowerBoundEntry.get()),  #lower temperature boundary
                int(self.m_upperBoundEntry.get()),  #upper temperature boundary
                self.m_removeBackgroundCB.instate(['selected']),
                self.m_smoothCountsCB.instate(['selected']),
                float(self.m_calibScaleEntry.get()
                      ),  #calibration slope 'm' in y=mx+b
                float(self.m_calibOffsetEntry.get())
            )  #calibration offset 'b' in y=mx+b

        if (self.m_normalizeCB.instate([
                'selected'
        ])):  #if we want to normalize data to a specific coverage
            monolayerData = None
            #find the coverage by fileName
            for w in self.m_parsedData:
                if (w.m_fileName == self.m_normSelection.get()):
                    monolayerData = w
                    break
            if (monolayerData == None):
                print("No reference coverage file selected")
                raise ValueError
            #normalize everything except the reference
            for w in [d for d in self.m_parsedData if not d == monolayerData]:
                w.normalizeDataTo(monolayerData)
            #normalize reference data last
            monolayerData.normalizeDataTo(monolayerData)

        #sort input data by coverage
        indexMapBuffer = [
        ]  #index i will contain tuple of (oldIndex,coverage) sorted by coverage
        for i in range(len(self.m_parsedData)):
            indexMapBuffer.append(
                (i, self.m_parsedData[i].getParsedCoverageAsFloat()
                 ))  #sorting input files by coverage
        indexMapBuffer.sort(reverse=False, key=lambda a: a[
            1])  #sort, such that old index and coverage are preserved

        #buffers for different ordering
        sortedParsedDataBuffer = []
        sortedFilePathsBuffer = []
        for i in range(len(indexMapBuffer)):
            sortedParsedDataBuffer.append(
                self.m_parsedData[indexMapBuffer[i][0]])
            sortedFilePathsBuffer.append(
                self.m_fileSelectionControl.m_filePaths[indexMapBuffer[i][0]])
        self.m_fileSelectionControl.m_filePaths = sortedFilePathsBuffer  #reference-copy
        self.m_parsedData = sortedParsedDataBuffer  #reference-copy
        self.m_fileSelectionControl.prepareFileSelections()
        # self.m_fileSelectionControl.onUpdateSelection(self.m_fileSelectionControl.m_fileList)

        self.m_massDisplayOptions.resetMasses(self.m_parsedData)
        self.plotSelectedMasses()

    def saveData(self):
        if (len(self.m_parsedData) == 0):
            tk.messagebox.showerror(
                "Save Error",
                "Please process some data before attempting to save it.")
            return
        outputFilePath = asksaveasfilename()
        substrings = outputFilePath.split('/')
        #consider using s = '/' result = s.join(substrings[x:y])
        outputFilePath = substrings[0]
        for s in substrings[1:-1]:
            outputFilePath = outputFilePath + '/' + s
        substrings = substrings[-1].split('.')
        fileName = substrings[0]
        if (len(substrings) > 1):
            for s in substrings[1:-1]:
                fileName = fileName + '.' + s
        # dateTimeString = str(datetime.now()).replace('-','').replace(' ', '_').replace(':','')
        # fileName = fileName + '.' + dateTimeString
        outputFilePath = outputFilePath + '/' + fileName

        self.SaveProcessedDataToFile(outputFilePath,
                                     self.m_massDisplayOptions.getAllMasses(),
                                     self.m_parsedData)

    def SaveProcessedDataToFile(self, outputFilePath, massList,
                                rawDataWrappers):
        if (massList == None or rawDataWrappers == None):
            raise ValueError
        for m in massList:  #generate one .pdat file per mass (cleanest way to seperate data for masses, and keep things in an easily readable format)
            headerString = "Processed TPD data for mass " + m + \
                "\nHeader length is " + str(len(rawDataWrappers) + 4) + \
                "\nThe following files are included in this data set:\n"
            #outputData starts out column-major
            outputData = rawDataWrappers[0].m_interpolatedTemp.copy(
            )  # start with temperature column
            labels = ["Temperature"]
            coverages = [str(0.0)]
            for w in rawDataWrappers:  #for each raw data file, do....
                headerString = headerString + w.m_fileName + "\n"  #write filename to header for quick overview
                outputData = np.vstack(
                    (outputData, w.m_interpolatedData[m]
                     ))  #append data column for mass m in outputdata
                if (w.m_parsedCoverageAvailable):
                    labels.append(
                        w.m_parsedCoverage
                    )  #will append dosed coverage in Langmuir with "L" at the end
                else:
                    labels.append(w.m_fileName.split(" ")
                                  [0])  # this should append file number
                coverages.append(str(w.m_coverages[m]))

            headerString = headerString + "Calibration params: Temperature offset = " + str(
                self.m_calibOffsetEntry.get()) + " Temperature scale = " + str(
                    self.m_calibScaleEntry.get())

            if (outputFilePath[-5:] == ".pdat"):
                outputFilePath = outputFilePath[:
                                                -5]  #removing .pdat extension from user-written output file path, if it is there

            #making one file per mass, so making name based on mass number
            namedOutputFilePath = outputFilePath + ".M" + str(
                m) + ".pdat"  #pdat for processed data
            if (path.exists(namedOutputFilePath)):
                tk.messagebox.showerror(
                    "File exists!",
                    "Please choose another name, or explicitly delete the " +
                    outputFilePath + "to overwrite in the file explorer.")
            stringData = np.vstack(
                (np.array(labels, dtype=str), np.array(coverages, dtype=str)))

            with open(namedOutputFilePath,
                      mode='a') as fileHandle:  #actually write file
                #write header and stringData first
                np.savetxt(fileHandle,
                           stringData,
                           fmt="%s",
                           delimiter=' ',
                           header=headerString)
                #then write float data (after transposing it)
                np.savetxt(fileHandle, outputData.transpose(), delimiter=' ')

    def initChordUI(self):
        self.m_chordFrame = self.m_chord.m_scrollable_frame

        # File selection

        self.m_fileSelectionControl = InputFileListBoxControl(
            self.m_chordFrame, self.onUpdateFileList)
        self.m_fileSelectionControl.grid(row=0,
                                         column=0,
                                         columnspan=4,
                                         sticky="nsew")

        # Processing options:

        self.m_optionsLabel = ttk.Label(
            self.m_chordFrame,
            text="Processing Options:")  #, compound = tk.CENTER)
        self.m_optionsLabel.grid(row=3, column=0, columnspan=2, sticky="nsw")

        self.m_tCutStartLabel = ttk.Label(self.m_chordFrame,
                                          text="Lower Boundary (Temp.):")
        self.m_tCutStartLabel.grid(row=4, column=1, sticky="nse")

        self.m_lowerBoundEntry = EnhancedEntry(
            self.m_chordFrame,
            inputValueType=int,
            errorTitle="Lower Boundary (Temp.)",
            errorMessage=
            "Please enter an integer for the lower temperature boundary")
        self.m_lowerBoundEntry.grid(row=4, column=2, sticky="nsw")

        self.m_tCutEndLabel = ttk.Label(self.m_chordFrame,
                                        text="Upper Boundary (Temp.):")
        self.m_tCutEndLabel.grid(row=5, column=1, sticky="nse")

        self.m_upperBoundEntry = EnhancedEntry(
            self.m_chordFrame,
            inputValueType=int,
            errorTitle="Upper Boundary (Temp.)",
            errorMessage=
            "Please enter an integer for the upper temperature boundary.")
        self.m_upperBoundEntry.grid(row=5, column=2, sticky="nsw")

        #Temperature (thermocouple) calibration options:

        self.m_calibLabel = ttk.Label(
            self.m_chordFrame,
            text="Temperature Calibration:")  #, compound = tk.CENTER)
        self.m_calibLabel.grid(row=6, column=0, columnspan=2, sticky="nsw")

        self.m_calibFormulaLabel = ttk.Label(
            self.m_chordFrame,
            text="( T_Calibrated = Scale * T_RawData + Offset )")
        self.m_calibFormulaLabel.grid(row=7, column=0,
                                      columnspan=3)  #, sticky = "nse")

        self.m_calibOffsetLabel = ttk.Label(self.m_chordFrame, text="Offset:")
        self.m_calibOffsetLabel.grid(row=8, column=1, sticky="nse")

        self.m_calibOffsetEntry = EnhancedEntry(
            self.m_chordFrame,
            inputValueType=float,
            errorTitle="Calibration Offset",
            errorMessage=
            "Please enter a decimal for the temperature calibration offset.")
        self.m_calibOffsetEntry.grid(row=8, column=2, sticky="nsw")
        self.m_calibOffsetEntry.setBackingVar("0.836")  #default

        self.m_calibScaleLabel = ttk.Label(self.m_chordFrame, text="Scale:")
        self.m_calibScaleLabel.grid(row=9, column=1, sticky="nse")

        self.m_calibScaleEntry = EnhancedEntry(
            self.m_chordFrame,
            inputValueType=float,
            errorTitle="Calibration Scale",
            errorMessage=
            "Please enter a decimal for the temperature calibration scale.")
        self.m_calibScaleEntry.grid(row=9, column=2, sticky="nsw")
        self.m_calibScaleEntry.setBackingVar("0.985")  #default

        # Checkbuttons + Comboboxes for options:

        self.m_smoothTempCB = EnhancedCheckButton(self.m_chordFrame,
                                                  text="Smooth Temperature")
        self.m_smoothTempCB.grid(row=10, column=1, sticky="nsw")
        self.m_smoothTempCB.set(1)
        self.m_smoothTempCB.configure(state=tk.DISABLED)

        self.m_smoothCountsCB = EnhancedCheckButton(self.m_chordFrame,
                                                    text="Smooth Counts/s")
        self.m_smoothCountsCB.grid(row=10, column=2, sticky="nsw")

        self.m_normalizeCB = EnhancedCheckButton(
            self.m_chordFrame,
            text="Normalize to coverage of (select file):",
            command=self.toggleNormalizeCB)
        self.m_normalizeCB.grid(row=11, column=1, sticky="nsw")

        self.m_removeBackgroundCB = EnhancedCheckButton(
            self.m_chordFrame, text="Remove Background")
        self.m_removeBackgroundCB.grid(row=11, column=2, sticky="nsw")

        self.m_normSelection = ttk.Combobox(self.m_chordFrame,
                                            state=tk.DISABLED)
        self.m_normSelection.grid(row=12,
                                  column=1,
                                  columnspan=2,
                                  sticky="nsew")

        self.m_subtractCB = EnhancedCheckButton(
            self.m_chordFrame,
            text="Subtract Spectrum (select file):",
            command=self.toggleSubtractCB,
            state=tk.DISABLED)
        self.m_subtractCB.grid(row=13, column=1, sticky="nsw")

        self.m_subtractSelection = ttk.Combobox(self.m_chordFrame,
                                                state=tk.DISABLED)
        self.m_subtractSelection.grid(row=14,
                                      column=1,
                                      columnspan=2,
                                      sticky="nsew")

        #Process Button

        self.m_processButton = ttk.Button(self.m_chordFrame,
                                          text="Process Input",
                                          command=self.processInput)
        self.m_processButton.grid(row=15,
                                  column=1,
                                  columnspan=2,
                                  sticky="nsew")

        #Display options

        self.m_displayOptionsLabel = ttk.Label(self.m_chordFrame,
                                               text='Display Options:')
        self.m_displayOptionsLabel.grid(row=16,
                                        column=0,
                                        columnspan=2,
                                        sticky="nsw")

        self.m_toggleMarkersButton = ttk.Button(self.m_chordFrame,
                                                text="Toggle Markers",
                                                command=self.toggleMarkers)
        self.m_toggleMarkersButton.grid(row=17,
                                        column=1,
                                        columnspan=2,
                                        sticky="nsew")

        self.m_massDisplayOptions = DisplayOptionsFrame(
            self.m_chordFrame, self.plotSelectedMasses)
        self.m_massDisplayOptions.grid(row=18,
                                       column=0,
                                       columnspan=4,
                                       sticky="nsew")

        # self.m_massDisplayOptions.m_availableMassesListBox

        self.m_saveDataButton = ttk.Button(self.m_chordFrame,
                                           text="Save Processed Data",
                                           command=self.saveData)
        self.m_saveDataButton.grid(row=19,
                                   column=1,
                                   columnspan=3,
                                   sticky="nsew")

        # for child in self.m_chordFrame.winfo_children():
        #     child.grid_configure(padx=3, pady=3)

        # for child in self.m_fileButtonFrame.winfo_children():
        #     child.pack_configure(padx=3, pady=3)

        # for child in self.m_massDisplayOptions.winfo_children():
        #     child.grid_configure(padx=3, pady=3)

        self.m_chordFrame.grid_columnconfigure(index=0, weight=1)
        self.m_chordFrame.grid_columnconfigure(index=1, weight=1)
        self.m_chordFrame.grid_columnconfigure(index=2, weight=1)
        self.m_chordFrame.grid_columnconfigure(index=3, weight=1)