diff --git a/AnalysisUI.py b/AnalysisUI.py new file mode 100644 index 0000000..fe6a4ea --- /dev/null +++ b/AnalysisUI.py @@ -0,0 +1,173 @@ +import sys +from PyQt5.QtWidgets import * + +import os +import numpy as np +import shelve + +import matplotlib +matplotlib.use('QT5Agg') +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from Plotting import EventPlots +import NanoporeClasses as NC + +class AnalysisUI(QWidget): + + def __init__(self): + super().__init__() + # self.AllVariable() + + + #def AllVariable(self): + # Let's dump all the variable we will need here... + self.importedFiles = [] + self.selectedFiles = [] + self.analyzedFiles = [] + + # Defining the plots + self.TurnToolbarsOn = False + self.pltFig = plt.figure(1, figsize=(12, 10)) + self.pltFig.__attached = True + self.figurePlotting = FigureCanvas(figure=self.pltFig) + + self.initUI() + + def initUI(self): + # Main Window + self.setGeometry(300, 200, 1200, 850) + self.setWindowTitle('Translocation event analysis') + + # Assemble the different layout pieces + self.MakeFileImportLayout() + self.DisplayAnalysis() + windowLayout = QHBoxLayout() + windowLayout.addWidget(self.FileImportLayout) + windowLayout.addWidget(self.AnalysisResults) + self.setLayout(windowLayout) + + # All Actions go here: + self.button_fileimport.clicked.connect(self.ImportButtonPushed) + self.button_clearfilelist.clicked.connect(self.ClearListButtonPushed) + self.list_filelist.clicked.connect(self.SelectionInFileListChanged) + # self.button_startAnalysis.clicked.connect(self.AnalysisButtonPressed) + + self.plotCurrent.clicked.connect(self.PlotSelected) + self.plotVoltage.clicked.connect(self.PlotSelected) + self.showLog.clicked.connect(self.PlotSelected) + self.show() + + def MakeFileImportLayout(self): + self.FileImportLayout = QGroupBox("File Import") + self.FileImportLayout.setFixedWidth(400) + layout = QGridLayout() + self.button_fileimport = QPushButton('Open Files') + self.button_fileimport.setToolTip('Select the files you want to import into the list. You can do multiple selections.') + self.button_clearfilelist = QPushButton('Clear List') + self.button_clearfilelist.setToolTip('This deletes your current list of files.') + self.list_filelist = QListWidget() + self.list_filelist.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.list_filelist.setToolTip('This is the list of current files. selected files will be plotted.') + layout.addWidget(self.button_fileimport, 0, 0) + layout.addWidget(self.list_filelist, 1, 0) + layout.addWidget(self.button_clearfilelist, 2, 0) + self.FileImportLayout.setLayout(layout) + + def DisplayAnalysis(self): + self.AnalysisResults = QGroupBox("Analysis results") + self.Plotsettings = QGroupBox("Plot Settings") + self.Plotsettings.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum)) + self.Plotting = QGroupBox("Plotting") + + # layout.setColumnStretch(1, 4) # stretch factors + # layout.setColumnStretch(2, 4) + + self.plotCurrent = QCheckBox('Show Current') + self.plotVoltage = QCheckBox('Seperate by voltage') + self.showLog = QCheckBox('Show log') + + layoutsettings = QGridLayout() + layoutsettings.addWidget(self.plotCurrent, 0, 0) + layoutsettings.addWidget(self.plotVoltage, 0, 1) + layoutsettings.addWidget(self.showLog, 0, 2) + self.Plotsettings.setLayout(layoutsettings) + + layoutPlotting = QGridLayout() + layoutPlotting.addWidget(self.figurePlotting) + self.Plotting.setLayout(layoutPlotting) + + # Final Assembly + layout = QGridLayout() + layout.addWidget(self.Plotsettings, 0, 0) + layout.addWidget(self.Plotting, 1, 0) + self.AnalysisResults.setLayout(layout) + + # Button Actions + def ImportButtonPushed(self, event): + files = QFileDialog.getOpenFileNames(self, 'Select Files to Add to list', filter="data files (*.dat, *.db)") + # Add to file list if unique: + for file in files[0]: + if file not in self.importedFiles: + self.importedFiles.append(file) + self.UpdateFileList() + + def ClearListButtonPushed(self, event): + self.importedFiles = [] + self.UpdateFileList() + + def UpdateFileList(self): + self.list_filelist.clear() + i = 0 + for file in self.importedFiles: + item = QListWidgetItem(os.path.split(file)[1], type=i) + self.list_filelist.addItem(item) + i += 1 + + def SelectionInFileListChanged(self, event): + items = self.list_filelist.selectedItems() + selectedFiles = [] + for item in items: + idx = item.type() + selectedFiles.append(str(self.importedFiles[idx])) + self.selectedFiles = selectedFiles + print('The following files are selected in the list:') + print(selectedFiles) + self.PlotSelected() + + def ClearFigure(self): + self.pltFig.clf() + + def PlotSelected(self): + self.ClearFigure() + plotCurrent = self.plotCurrent.isChecked() + plotVoltage = self.plotVoltage.isChecked() + showLog = self.showLog.isChecked() + + if len(self.selectedFiles)>0: + translocationEvents = NC.AllEvents() + for filename in self.selectedFiles: + shelfFile = shelve.open(os.path.splitext(filename)[0]) + translocationEventstemp = shelfFile['TranslocationEvents'] + shelfFile.close() + translocationEvents.AddEvent(translocationEventstemp) + + EventPlots.PlotG_tau(translocationEvents, fig=self.pltFig, showCurrent=plotCurrent, showLog=showLog) + + self.figurePlotting.draw() + + + def closeEvent(self, event): + reply = QMessageBox.question(self, 'Message', + "Are you sure to quit?", QMessageBox.Yes | + QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.Yes: + event.accept() + else: + event.ignore() + + +# Execution +if __name__ == '__main__': + app = QApplication(sys.argv) + ex = AnalysisUI() + sys.exit(app.exec_()) diff --git a/EventDetection.py b/EventDetection.py index cb3107f..5a0e276 100644 --- a/EventDetection.py +++ b/EventDetection.py @@ -1,425 +1,484 @@ import NanoporeClasses as NC import shelve import os import glob from time import sleep import Functions from pprint import pprint import matplotlib.pyplot as plt from matplotlib.ticker import EngFormatter import numpy as np import sys import argparse import datetime from tkinter.filedialog import askopenfilenames,askdirectory import timeit import LoadData timeInSec = EngFormatter(unit='s', places=2) +Amp = EngFormatter(unit='A', places=2) #Default parameters extension = '*.log' coefficients = {'a': 0.999, 'E': 0, 'S': 5, 'maxEventLength': 200e-3, # maximal event length to be considered an event 'minEventLength': 600e-6, # maximal event length to be considered an impulse 'fitLength': 3e-3, # minimal event length to be fitted for impulses 'dt': 25, # go back dt points for fitting of impulses 'hbook': 1, 'delta': 0.2e-9, + 'deltaRel': None, # If set overrides delta, calculates delta relative to baseline 'ChimeraLowPass': 10e3} def GetParameters(): print("Usage:") - print("run(inputData, newExtension=None, newCoefficients={}, outputFile=None, force=False, cut=False, verboseLevel=0)") + print("run(inputData, newExtension=None, newCoefficients={}, outputFile=None, " + "force=False, cut=False, verboseLevel=0)") print() print("Default extension:") print(extension) print() print("Default coefficients:") pprint(coefficients) +def eventdetectionwithfilegeneration(file, coefficients, verboseLevel=1, forceRun=False, CutTraces=False): + # Create new class that contains all events + AllEvents = NC.AllEvents() + # Loop over all files in folder + # Extract filename and generate filename to save located events + if verboseLevel >= 1: + print('analysing ' + file) + directory = os.path.dirname(file) + os.sep + 'analysisfiles' + if not os.path.exists(directory): + os.makedirs(directory, exist_ok=True) + filename, file_extension = os.path.splitext(os.path.basename(file)) + savefilename = directory + os.sep + filename + 'data' + shelfFile = shelve.open(savefilename) + if ~forceRun: + try: #Check if file can be loaded + coefficientsloaded=shelfFile['coefficients'] + tEL = shelfFile['TranslocationEvents'] + # If coefficients before are not identical, analysis needs to run again + if (coefficientsloaded == coefficients): + if verboseLevel >= 1: + print('loaded from file') + else: + forceRun = True + except: + # Except if cannot be loaded, analysis needs to run + forceRun = True + pass + + if forceRun: + # Extract list of events for this file + tEL = eventdetection(file, coefficients, verboseLevel, CutTraces) + if verboseLevel >= 1: + print('Saved {} events'.format(len(tEL.events))) + + # Open savefile and save events for this file + shelfFile['TranslocationEvents'] = tEL + shelfFile['coefficients'] = coefficients + if verboseLevel >= 1: + print('saved to file') + shelfFile.close() + # Add events to the initially created class that contains all events + AllEvents.AddEvent(tEL) + return AllEvents def batcheventdetection(folder, extension, coefficients, verboseLevel=1, forceRun=False, CutTraces=False): # Create new class that contains all events AllEvents = NC.AllEvents() - print('found ' + str(len(glob.glob(os.path.join(folder,extension)))) + ' files.') + print('found ' + str(len(glob.glob(os.path.join(folder, extension)))) + ' files.') # Loop over all files in folder - for fullfilename in glob.glob(os.path.join(folder,extension)): + for fullfilename in glob.glob(os.path.join(folder, extension)): # Extract filename and generate filename to save located events if verboseLevel >= 1: print('analysing '+fullfilename) filename, file_extension = os.path.splitext(os.path.basename(fullfilename)) directory = os.path.dirname(fullfilename) + os.sep + 'analysisfiles' if not os.path.exists(directory): os.makedirs(directory,exist_ok=True) savefilename = directory + os.sep + filename + 'data' shelfFile = shelve.open(savefilename) if ~forceRun: try: #Check if file can be loaded coefficientsloaded=shelfFile['coefficients'] tEL = shelfFile['TranslocationEvents'] # If coefficients before are not identical, analysis needs to run again if (coefficientsloaded == coefficients): if verboseLevel >= 1: print('loaded from file') else: forceRun = True except: # Except if cannot be loaded, analysis needs to run forceRun = True pass if forceRun: # Extract list of events for this file tEL = eventdetection(fullfilename, coefficients, verboseLevel, CutTraces) if verboseLevel >= 1: print('Saved {} events'.format(len(tEL.events))) # Open savefile and save events for this file shelfFile['TranslocationEvents'] = tEL shelfFile['coefficients'] = coefficients if verboseLevel >= 1: print('saved to file') shelfFile.close() # Add events to the initially created class that contains all events AllEvents.AddEvent(tEL) return AllEvents - def eventdetection(fullfilename, coefficients, verboseLevel=1, CutTraces=False, showFigures=False): """ Function used to find the events of TranslocationEvents class in the raw data in file 'filename'. It calls the function RecursiveLowPassFast to approximatively locate rough events in the data. If a short TranslocationEvent object is detected its type attribute will be changed to 'Impulse' and the meanTrace attribute will take the value of the minimal current value within the event. Then the CUSUM function will be called to build the CUSUM-fit and assign values to the different attributes of the TranslocationEvent objects. Depending on how the CUSUM was able to fit the trace inside and around the event, the type attribute of the TransocationEvent will be set to 'Real' (if the CUSUM fit went well) or 'Rough' (if the CUSUM was not able to fit the trace). Parameters ---------- fullfilename : str Full path to data file. coefficients : dict Contains the parameters for the analysis. verboseLevel : bool, optional 1 by default. It will print strings indicating the progress of the function in the console with different levels of depth. CutTraces : bool, optional False by default. If True, will cut the signal traces around the events to avoid having appended chunks detected as events. showFigures : bool , optional False by default. If True, it will display a simple figure with the shape of the signal. Returns ------- list of TranslocationEvent All the events in the signal. """ + if os.path.getsize(fullfilename) <= 8: + print('File incorrect size') + return -1 + if 'ChimeraLowPass' in coefficients: ChimeraLowPass = coefficients['ChimeraLowPass'] else: ChimeraLowPass = None loadedData = LoadData.OpenFile(fullfilename, ChimeraLowPass, True, CutTraces) maxTime = coefficients['maxEventLength'] minTime = coefficients['minEventLength'] fitTime = coefficients['fitLength'] IncludedBaseline = int(1e-2 * loadedData['samplerate']) delta = coefficients['delta'] hbook = coefficients['hbook'] dt = coefficients['dt'] + deltaRel = coefficients['deltaRel'] samplerate = loadedData['samplerate'] + if verboseLevel >= 1: print('\n Recursive lowpass...', end='') # Call RecursiveLowPassFast to detect events in current trace start_time = timeit.default_timer() if 'i1Cut' in loadedData: events = [] for cutTrace in loadedData['i1Cut']: events.extend(Functions.RecursiveLowPassFast(cutTrace, coefficients, loadedData['samplerate'])) else: events = Functions.RecursiveLowPassFast(loadedData['i1'], coefficients, loadedData['samplerate']) if verboseLevel >= 1: print('done. Calculation took {}'.format(timeInSec.format_data(timeit.default_timer() - start_time))) print('Roughly detected events: {}'.format(len(events))) # Make a new class translocationEventList that contains all the found events in this file translocationEventList = NC.AllEvents() # Plot current file if showFigures: plt.figure(1) plt.cla() timeVals=np.linspace(0, len(loadedData['i1'])/loadedData['samplerate'], num=len(loadedData['i1'])) plt.plot(timeVals,loadedData['i1']) plt.draw() plt.pause(0.001) if verboseLevel >= 1: print('Fine tuning...', end='') # Call RecursiveLowPassFast to detect events in current trace start_time = timeit.default_timer() cusumEvents = 0 # Loop over all roughly detected events for event in events: beginEvent = event[0] endEvent = event[1] localBaseline = event[2] stdEvent = event[3] if 'blockLength' in loadedData: voltI = int(beginEvent / loadedData['blockLength']) else: voltI = int(0) # Check if the start of the event is later than the first few ms in IncludedBaseline if beginEvent > IncludedBaseline: # Extract trace and extract baseline traceforfitting = loadedData['i1'][int(beginEvent - dt):int(endEvent + dt)] # If event is shorter than fitTime if 3 < (endEvent-beginEvent) <= (fitTime * samplerate): # Try to fit and estimate real output = Functions.ImpulseFitting(traceforfitting, localBaseline, samplerate, verbose=(verboseLevel >= 3)) elif (endEvent-beginEvent) < (maxTime * samplerate): output = 2 else: if verboseLevel >= 2: - print('Too long event {t:4.0f} ms'.format(t=1000*(endEvent-beginEvent)/samplerate)) + print('Too long event {t:4.0f} ms\n'.format(t=1000*(endEvent-beginEvent)/samplerate)) output = -1 # Don't include in rest of analysis # if output -1 fit failed, if output -2 differential failed if not isinstance(output, int) and len(output) > 1: (ps1, pe1, pe2, rsquared_event, Idrop) = output if verboseLevel >= 3: print('ps1: {}, pe1: {}, pe2: {}, rsquared: {}'.format(ps1, pe1, pe2, rsquared_event)) # length small enough and r squared big enough if (pe2-ps1)/samplerate < minTime and rsquared_event > 0.7: newEvent = NC.TranslocationEvent(fullfilename, 'Impulse') trace = traceforfitting[ps1:pe2] ps1c = beginEvent - 25 + ps1 pe2c = beginEvent - 25 + pe2 traceBefore = loadedData['i1'][int(ps1c) - IncludedBaseline:int(ps1c)] traceAfter = loadedData['i1'][int(pe2c):int(pe2c) + IncludedBaseline] newEvent.SetEvent(trace, ps1c, localBaseline, samplerate, currentDrop=Idrop) newEvent.SetCoefficients(coefficients, loadedData['v1'][voltI]) newEvent.SetBaselineTrace(traceBefore, traceAfter) newEvent.eventLength = (pe1-ps1)/samplerate if verboseLevel >= 2: print('Fitted impulse of {t:1.3f} ms and {i:2.2f} nA and {r:1.2f} R-squared\n'.format( t=newEvent.eventLength*1e3, i=newEvent.currentDrop*1e9, r=rsquared_event)) elif (pe2-ps1)/samplerate < 3 * minTime and rsquared_event < 0.5: if verboseLevel >= 2: - print('Bad fit {r:1.2f} R-squared\n'.format(r=rsquared_event)) + print('Bad fit {r:1.2f} R-squared, event ignored.\n'.format(r=rsquared_event)) output = -1 # Don't include in rest of analysis else: if verboseLevel >= 3: print('Too long event for impulse') output = 2 # Good enough for CUSUM fitting if isinstance(output, int) and abs(output) == 2: # CUSUM fit sigma = np.sqrt(stdEvent) + + # If deltaRel is set calculate delta based on relative value to baseline + if deltaRel is not None: + delta = deltaRel * localBaseline + if verboseLevel >= 2: + print('Using relative delta for CUSUM fitting: {}'.format(Amp.format_data(delta))) + h = hbook * delta / sigma (mc, kd, krmv) = Functions.CUSUM(traceforfitting, delta, h, verbose=(verboseLevel >= 3)) krmv = [krmvVal + int(beginEvent) - dt + 1 for krmvVal in krmv] if len(krmv) > 1: trace = loadedData['i1'][int(krmv[0]):int(krmv[-1])] traceBefore = loadedData['i1'][int(krmv[0]) - IncludedBaseline:int(krmv[0])] traceAfter = loadedData['i1'][int(krmv[-1]):int(krmv[-1]) + IncludedBaseline] beginEvent = krmv[0] # Adding CUSUM fitted event newEvent = NC.TranslocationEvent(fullfilename, 'CUSUM') newEvent.SetEvent(trace, beginEvent, localBaseline, samplerate) newEvent.SetBaselineTrace(traceBefore, traceAfter) newEvent.SetCoefficients(coefficients, loadedData['v1'][voltI]) newEvent.SetCUSUMVariables(mc, kd, krmv) cusumEvents += 1 if verboseLevel >= 2: print('Fitted CUSUM of {t:1.3f} ms and {i:2.2f} nA\n'.format( t=newEvent.eventLengthCUSUM * 1e3, i=newEvent.currentDropCUSUM * 1e9)) else: trace = loadedData['i1'][int(beginEvent):int(endEvent)] traceBefore = loadedData['i1'][int(beginEvent) - IncludedBaseline:int(beginEvent)] traceAfter = loadedData['i1'][int(endEvent):int(endEvent) + IncludedBaseline] # Adding roughly detected event newEvent = NC.TranslocationEvent(fullfilename, 'Rough') newEvent.SetEvent(trace, beginEvent, localBaseline, samplerate) newEvent.SetBaselineTrace(traceBefore, traceAfter) newEvent.SetCoefficients(coefficients, loadedData['v1'][voltI]) if verboseLevel >= 2: print('CUSUM failed. Adding only roughly located event of {t:1.3f} ms and {i:2.2f} nA\n'.format( t=len(trace) * 1e3/samplerate, i=(np.mean(np.append(traceBefore,traceAfter)) - np.mean(trace) )* 1e9)) if output != -1: # Add event to TranslocationList translocationEventList.AddEvent(newEvent) if verboseLevel >= 1: print('done. Total fitting took {}'.format(timeInSec.format_data(timeit.default_timer() - start_time))) print('{} events fitted with CUSUM\n'.format(cusumEvents)) #Plot events if True if showFigures: minVal = np.max(loadedData['i1'])#-1.05e-9 #np.min(loadedData['i1']) maxVal = np.max(loadedData['i1'])+0.05e-9#-1.05e-9 #np.min(loadedData['i1']) for i in range(len(events)): beginEvent=events[i][0] endEvent = events[i][1] if beginEvent>100 and (endEvent - beginEvent) >= (minTime * loadedData['samplerate']) and (endEvent - beginEvent) < ( coefficients['maxEventLength'] * loadedData['samplerate']): plt.plot([beginEvent/loadedData['samplerate'], beginEvent/loadedData['samplerate']], [minVal, maxVal], 'y-', lw=1) plt.show() return translocationEventList - - - def LoadEvents(loadname): - #Check if loadname is a directory or not + # Check if loadname is a directory or not if os.path.isdir(loadname): - #create standard filename for loading + # create standard filename for loading loadFile = os.path.join(loadname,os.path.basename(loadname)+'_Events') else: loadFile, file_extension = os.path.splitext(loadname) - #Open file and extract TranslocationEvents - shelfFile=shelve.open(loadFile) - TranslocationEvents=shelfFile['TranslocationEvents'] + # Open file and extract TranslocationEvents + shelfFile = shelve.open(loadFile) + TranslocationEvents = shelfFile['TranslocationEvents'] shelfFile.close() - AllEvents=NC.AllEvents() + AllEvents = NC.AllEvents() AllEvents.AddEvent(TranslocationEvents) AllEvents.SetFolder(loadname) return AllEvents def run(inputData, newExtension=None, newCoefficients={}, outputFile=None, force=False, cut=False, verboseLevel=0): """ Function used to call all the other functions in the module needed to find the events in raw nanopore experiment data. Parameters ----------- inputData : str Full path to data file. newExtension : str, optional None by default. NewExtension for input directory. newCoefficients : dict Contains the default parameters for the analysis. outputFile : str, optional None by default. Full path to output file. force : bool, optional False by default. cut : bool, optional False by default. False by default. If True, will cut the signal traces around the events to avoid having appended chunks detected as events. verboseLevel : int, optional 0 by default. 0 by default. If higher, it will print various outputs during running Returns ------- AllEvents object All the events. """ if newExtension is None: newExtension = extension for key in newCoefficients: coefficients[key]=newCoefficients[key] if os.path.isdir(inputData): TranslocationEvents = batcheventdetection(inputData, newExtension, coefficients, verboseLevel, force, cut) else: TranslocationEvents = eventdetection(inputData,coefficients, verboseLevel, cut) #Check if list is empty if outputFile is not None and TranslocationEvents.events: if os.path.isdir(inputData): outputData = inputData + os.sep + 'Data' + os.sep + 'Data' + datetime.date.today().strftime("%Y%m%d") LoadData.SaveVariables(outputFile, TranslocationEvents=TranslocationEvents) return True else: return TranslocationEvents -if __name__=='__main__': + +if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-i', '--input', help='Input directory or file') parser.add_argument('-e', '--ext', help='Extension for input directory') parser.add_argument('-o', '--output', help='Output file for saving') - parser.add_argument('-c', '--coeff', help='Coefficients for selecting events [-C filter E standarddev maxlength minlength', nargs = '+') - parser.add_argument('-u', '--cut', help='Cut Traces before detecting event, prevent detecting appended chunks as event', action='store_true') + parser.add_argument('-c', '--coeff', + help='Coefficients for selecting events [-C filter E standarddev maxlength minlength', + nargs='+') + parser.add_argument('-u', '--cut', + help='Cut Traces before detecting event, prevent detecting appended chunks as event', + action='store_true') parser.add_argument('-f', '--force', help='Force analysis to run (don''t load from file', action='store_true') args = parser.parse_args() - inputData=args.input - if inputData==None: - inputData=askdirectory() + inputData = args.input + if inputData is None: + inputData = askdirectory() - outputData=args.output - if outputData==None: + outputData = args.output + if outputData is None: if os.path.isdir(inputData): outputData = inputData + os.sep + 'Data' + os.sep + 'Data' + datetime.date.today().strftime("%Y%m%d") else: outputData = os.path.dirname(inputData) + os.sep + 'Data' + os.sep + 'Data' + datetime.date.today().strftime("%Y%m%d") - if args.coeff is not None: if len(args.coeff) % 2 == 0: for i in range(0, len(args.coeff), 2): if i <= len(args.coeff): coefficients[str(args.coeff[i])]=float(args.coeff[i+1]) if args.ext is not None: extension = args.ext print('Loading from: ' + inputData) print('Saving to: ' + outputData) print('Coefficients are: ', end='') pprint(coefficients) if os.path.isdir(inputData): print('extension is: ' + extension +'\nStarting.... \n') TranslocationEvents = batcheventdetection(inputData, extension, newcoefficients=coefficients, force=args.force, cut=args.cut) else: print('Starting.... \n') TranslocationEvents = eventdetection(inputData,coefficients, args.cut) #Check if list is empty if TranslocationEvents.events: LoadData.SaveVariables(outputData, TranslocationEvents=TranslocationEvents) diff --git a/EventDetectorUI.py b/EventDetectorUI.py new file mode 100644 index 0000000..72c8b1c --- /dev/null +++ b/EventDetectorUI.py @@ -0,0 +1,390 @@ +import sys +import os +from PyQt5.QtWidgets import * +from PyQt5 import QtCore +from PyQt5.QtGui import QColor, QIntValidator +import matplotlib +matplotlib.use('QT5Agg') +import numpy as np +import matplotlib.pylab as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar + +font = {'weight' : 'regular', + 'size' : 4} + +matplotlib.rc('font', **font) # pass in the font dict as kwargs +matplotlib.rc('lines', linewidth = 0.5) +from EventDetection import * +from Plotting.EventPlots import * +from LoadData import * + +class AnalysisUI(QWidget): + + def __init__(self): + super().__init__() + self.initUI() + + def AllVariable(self): + # Let's dump all the variable we will need here... + self.importedFiles = [] + self.selectedFiles = [] + self.analyzedFiles = [] + self.rawtrace = [] + self.rawtracesamplerate = 0 + + self.AllEvents = NC.AllEvents() + self.wholetracedrawnforthefirsttime = True + self.dsrawtrace = [] + self.dssamplerate = 0 + + # Defining the plots + self.TurnToolbarsOn = True + self.fig_singleEvent = plt.figure(1, figsize=(10, 10)) + self.ax_singleEvent = self.fig_singleEvent.add_subplot(111) + self.ax_singleEvent.plot(np.arange(1, 10), np.arange(1, 10)) + + self.fig_wholeTrace = plt.figure(2, figsize=(16, 9)) + self.ax_wholeTrace = self.fig_wholeTrace.add_subplot(111) + self.line_wholeTrace, = self.ax_wholeTrace.plot(np.arange(1, 10), -np.arange(1, 10)) + + self.figure_singleEvent = FigureCanvas(figure=self.fig_singleEvent) + self.figure_wholeTrace = FigureCanvas(figure=self.fig_wholeTrace) + + if self.TurnToolbarsOn: + self.toolbarWholeTrace = NavigationToolbar(self.figure_wholeTrace, self) + self.toolbarSingleEvent = NavigationToolbar(self.figure_singleEvent, self) + + def initUI(self): + # Main Window + self.AllVariable() + self.setGeometry(0, 0, 1200, 600) + self.setWindowTitle('A user interface is like a joke...') + + # Assemble the different layout pieces + self.MakeFileImportLayout() + self.MakeAnalysisParametersAndRunLayout() + self.MakeEventNavigatorLayout() + windowLayout = QHBoxLayout() + windowLayout.addWidget(self.FileImportLayout) + windowLayout.addWidget(self.AnalysisParameters) + windowLayout.addWidget(self.EventNavigatorLayout) + self.setLayout(windowLayout) + + ## All Actions go here: + self.button_fileimport.clicked.connect(self.ImportButtonPushed) + self.button_clearfilelist.clicked.connect(self.ClearListButtonPushed) + self.list_filelist.clicked.connect(self.SelectionInFileListChanged) + self.list_filelist.doubleClicked.connect(self.FileDoubleClicked) + self.button_startAnalysis.clicked.connect(self.AnalysisButtonPressed) + self.text_eventNumber.returnPressed.connect(self.EventNumberChanged) + self.button_eventForward.clicked.connect(self.ForwardButtonPushed) + self.button_eventBackward.clicked.connect(self.BackwardButtonPushed) + self.setting_LPForWholeTracePlot.editingFinished.connect(self.LPForWholeTracePlotChanged) + self.show() + + def MakeFileImportLayout(self): + self.FileImportLayout = QGroupBox("File Import") + self.FileImportLayout.setFixedWidth(200) + layout = QGridLayout() + self.button_fileimport = QPushButton('Open Files') + self.button_fileimport.setToolTip('Select the files you want to import into the list. You can do multiple selections.') + self.button_clearfilelist = QPushButton('Clear List') + self.button_clearfilelist.setToolTip('This deletes your current list of files.') + self.list_filelist = QListWidget() + self.list_filelist.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.list_filelist.setToolTip('This is the list of current files. Entries in green are already analyzed. Your selection defines which files are used in the analysis.') + layout.addWidget(self.button_fileimport, 0, 0) + layout.addWidget(self.list_filelist, 1, 0) + layout.addWidget(self.button_clearfilelist, 2, 0) + self.FileImportLayout.setLayout(layout) + + def MakeAnalysisParametersAndRunLayout(self): + self.AnalysisParameters = QGroupBox("Analysis") + self.AnalysisParameters.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) + self.LowPassSettings = QGroupBox("Low Pass Settings") + self.LowPassSettings.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) + self.CusumSettings = QGroupBox("Cusum Settings") + self.CusumSettings.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) #This allows this part to remain small in vertical position when the UI resizes. + self.OtherSettings = QGroupBox("Other Settings") + self.OtherSettings.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) #This allows this part to remain small in vertical position when the UI resizes. + self.button_startAnalysis = QPushButton('Start Analysis') + self.button_startAnalysis.setToolTip('This button will provoke your computer to sel-destruct. Use at your own risk!!!!!') + + ## Low Pass Settings + self.settings_LP_a = QComboBox() + availableoptions = [0.99, 0.999, 0.9999] + for i in availableoptions: + self.settings_LP_a.addItem(str(i)) + self.settings_LP_a.setToolTip('How strong should the filter be? Higher number is stronger filtering, which means less found events...') + + + ## EndValueE + self.settings_LP_E = QDoubleSpinBox() + self.settings_LP_E.setValue(0) + self.settings_LP_E.setSingleStep(0.1) + self.settings_LP_E.setToolTip('The current needs to go above mean + E*std to terminate the event') + + ## StartValueS + self.settings_LP_S = QDoubleSpinBox() + self.settings_LP_S.setValue(5) + self.settings_LP_S.setSingleStep(0.1) + self.settings_LP_S.setToolTip('The current needs to go below mean + S*std to initiate an event') + + layout1 = QGridLayout() + layout1.addWidget(QLabel('Filter Coefficient (a)'), 0, 0) + layout1.addWidget(self.settings_LP_a, 0, 1) + layout1.addWidget(QLabel('Start Coefficient (S)'), 1, 0) + layout1.addWidget(self.settings_LP_S, 1, 1) + layout1.addWidget(QLabel('End Coefficient (E)'), 3, 0) + layout1.addWidget(self.settings_LP_E, 3, 1) + self.LowPassSettings.setLayout(layout1) + + + ##Cusum Settings + self.settings_cusum_hBook = QDoubleSpinBox() + self.settings_cusum_hBook.setValue(1) + self.settings_cusum_hBook.setSingleStep(0.1) + self.settings_cusum_hBook.setToolTip('Some random stupid parameter') + + + self.settings_cusum_dI = QDoubleSpinBox() + self.settings_cusum_dI.setValue(1) + self.settings_cusum_dI.setSuffix(' nA') + self.settings_cusum_dI.setSingleStep(0.1) + self.settings_cusum_dI.setToolTip('The most probable current drop') + + + layoutCusum = QGridLayout() + layoutCusum.addWidget(QLabel('h-Book'), 0, 0) + layoutCusum.addWidget(self.settings_cusum_hBook, 0, 1) + layoutCusum.addWidget(QLabel('Delta I'), 1, 0) + layoutCusum.addWidget(self.settings_cusum_dI, 1, 1) + self.CusumSettings.setLayout(layoutCusum) + + + ## Other Settings + self.settings_other_ChimeraLP = QDoubleSpinBox() + self.settings_other_ChimeraLP.setValue(10) + self.settings_other_ChimeraLP.setSingleStep(10) + self.settings_other_ChimeraLP.setSuffix(' kHz') + self.settings_other_ChimeraLP.setToolTip('Low pass setting for high-bandwidth Chimera Data') + + + self.settings_other_MaxEventLength = QDoubleSpinBox() + self.settings_other_MaxEventLength.setValue(0.2) + self.settings_other_MaxEventLength.setSingleStep(0.05) + self.settings_other_MaxEventLength.setSuffix(' s') + self.settings_other_MaxEventLength.setToolTip('maximal event length to be considered an event') + + + self.settings_other_MinEventLength = QDoubleSpinBox() + self.settings_other_MinEventLength.setValue(600) + self.settings_other_MinEventLength.setSingleStep(100) + self.settings_other_MinEventLength.setSuffix(' us') + self.settings_other_MinEventLength.setToolTip('maximal event length to be considered an impulse') + + + self.settings_other_fitLength = QDoubleSpinBox() + self.settings_other_fitLength.setValue(3) + self.settings_other_fitLength.setSingleStep(0.5) + self.settings_other_fitLength.setSuffix(' ms') + self.settings_other_fitLength.setToolTip('minimal event length to be fitted for impulses') + + layoutOther = QGridLayout() + layoutOther.addWidget(QLabel('Chimera LP'), 0, 0) + layoutOther.addWidget(self.settings_other_ChimeraLP, 0, 1) + layoutOther.addWidget(QLabel('Max event length'), 1, 0) + layoutOther.addWidget(self.settings_other_MaxEventLength, 1, 1) + layoutOther.addWidget(QLabel('Min event length'), 2, 0) + layoutOther.addWidget(self.settings_other_MinEventLength, 2, 1) + layoutOther.addWidget(QLabel('Fit length'), 3, 0) + layoutOther.addWidget(self.settings_other_fitLength, 3, 1) + self.OtherSettings.setLayout(layoutOther) + + ##Final Assembly + layout = QGridLayout() + layout.addWidget(self.LowPassSettings, 0, 0) + layout.addWidget(self.CusumSettings, 1, 0) + layout.addWidget(self.OtherSettings, 2, 0) + layout.addWidget(self.button_startAnalysis, 3, 0) + self.AnalysisParameters.setLayout(layout) + + def MakeEventNavigatorLayout(self): + self.EventNavigatorLayout = QGroupBox("Event Navigator") + self.navigation_buttons =QButtonGroup() + self.button_eventForward = QPushButton('Forward') + self.button_eventForward.setToolTip('Advance one event forward') + self.button_eventBackward = QPushButton('Backward') + self.button_eventBackward.setToolTip('Go one event back') + self.setting_LPForWholeTracePlot = QDoubleSpinBox() + self.setting_LPForWholeTracePlot.setValue(10) + self.setting_LPForWholeTracePlot.setSingleStep(0.5) + self.setting_LPForWholeTracePlot.setSuffix(' kHz') + self.text_eventNumber = QLineEdit('0') + self.text_eventNumber.setValidator(QIntValidator(0, 100000)) + self.text_eventNumber.setToolTip('This is the number of the current event. You can edit this field to jump directly to an event.') + self.text_eventNumber.setAlignment(QtCore.Qt.AlignCenter) + layout = QGridLayout() + layout.addWidget(self.figure_singleEvent, 0, 0, 1, 5) + self.figure_singleEvent.setFixedHeight(250) + self.figure_singleEvent.setFixedWidth(600) + layout.addWidget(self.button_eventForward, 2, 4) + layout.addWidget(self.text_eventNumber, 2, 3) + layout.addWidget(self.button_eventBackward, 2, 2) + layout.addWidget(self.setting_LPForWholeTracePlot, 2, 1) + layout.addWidget(QLabel('Downsample plot below: '), 2, 0) + layout.addWidget(self.figure_wholeTrace, 3, 0, 1, 5) + self.figure_wholeTrace.setFixedHeight(250) + self.figure_wholeTrace.setFixedWidth(600) + + if self.TurnToolbarsOn: + layout.addWidget(self.toolbarSingleEvent, 1, 0, 1, 5) + layout.addWidget(self.toolbarWholeTrace, 4, 0, 1, 5) + self.EventNavigatorLayout.setLayout(layout) + + +## Button Actions + + def ImportButtonPushed(self, event): + files = QFileDialog.getOpenFileNames(self, 'Select Files to Add to list', filter = "Images (*.dat *.log)") + ## Add to file list if unique: + for i in files[0]: + if i not in self.importedFiles: + self.importedFiles.append(i) + self.UpdateFileList() + + def ClearListButtonPushed(self, event): + self.importedFiles = [] + self.UpdateFileList() + + def UpdateFileList(self): + self.list_filelist.clear() + for ind, i in enumerate(self.importedFiles): + self.list_filelist.addItem(os.path.split(i)[1]) + folder = os.path.dirname(i) + os.sep + 'analysisfiles' + filename, file_extension = os.path.splitext(os.path.basename(i)) + potentialanalysisfile = folder + os.sep + filename + 'data' + print(potentialanalysisfile) + print(os.path.exists(potentialanalysisfile)) + if os.path.isfile(potentialanalysisfile + '.dat') or os.path.isfile(potentialanalysisfile + '.db'): + ## If file is present, make the row green. This means analysis was done on it. + self.list_filelist.item(ind).setBackground(QColor('green')) + + def SelectionInFileListChanged(self, event): + items = self.list_filelist.selectedItems() + x = [] + for i in range(len(items)): + x.append(str(self.list_filelist.selectedItems()[i].text())) + self.selectedFiles = x + print('The following files are selected in the list:') + print(x) + + def GetFullFilePath(self, listoffiles): + fullfilepaths = [] + for i in listoffiles: + for j in self.importedFiles: + if i in j: + fullfilepaths.append(j) + return fullfilepaths + + def AnalysisButtonPressed(self): + # Get Full File Paths: + fullfilepaths = self.GetFullFilePath(self.selectedFiles) + print(fullfilepaths) + # Fill coefficient dictionary from the user inputs + coefficients = {} + coefficients['maxEventLength'] = self.settings_other_MaxEventLength.value() + coefficients['minEventLength'] = self.settings_other_MinEventLength.value() + coefficients['fitLength'] = self.settings_other_fitLength.value() + coefficients['delta'] = self.settings_cusum_dI.value()*1e-9 + coefficients['hbook'] = self.settings_cusum_hBook.value() + coefficients['a'] = np.float(self.settings_LP_a.currentText()) + coefficients['S'] = self.settings_LP_S.value() + coefficients['E'] = self.settings_LP_E.value() + coefficients['ChimeraLowPass'] = self.settings_other_ChimeraLP.value() + coefficients['dt'] = 25 + coefficients['deltaRel'] = None + for i in fullfilepaths: + eventdetectionwithfilegeneration(i, coefficients, forceRun=True, verboseLevel=1) + self.UpdateFileList() + + def BackwardButtonPushed(self): + if (int(self.text_eventNumber.text())-1) < 0: + self.text_eventNumber.setText('0') + else: + self.text_eventNumber.setText(str(int(self.text_eventNumber.text())-1)) + self.EventNumberChanged() + + def ForwardButtonPushed(self): + if (int(self.text_eventNumber.text())+1) >= len(self.AllEvents.events): + self.text_eventNumber.setText(str(len(self.AllEvents.events)-1)) + else: + self.text_eventNumber.setText(str(int(self.text_eventNumber.text())+1)) + self.EventNumberChanged() + + def EventNumberChanged(self): + print(int(self.text_eventNumber.text())) + if len(self.AllEvents.events): + self.DrawEvent(int(self.text_eventNumber.text())) + self.DrawWholeTrace(int(self.text_eventNumber.text())) + + def DrawEvent(self, number): + self.ax_singleEvent.clear() + PlotEvent(self.AllEvents.events[number], ax=self.ax_singleEvent, savefile=os.getcwd(), showCUSUM=True, + showCurrent=False, showButtons=False, axisFormatter=False, plotTitleBool=False) + self.figure_singleEvent.draw() + + def DrawWholeTrace(self, number): + ShowEventInTrace_SignalPreloaded(self.dsrawtrace, self.AllEvents, number, self.ax_wholeTrace, line=self.line_wholeTrace, firstCall=self.wholetracedrawnforthefirsttime, dscorrection=self.dssamplerate/self.rawtracesamplerate) + self.wholetracedrawnforthefirsttime = False + #self.ax_wholeTrace.autoscale(enable=True) + self.figure_wholeTrace.draw() + #self.figure_wholeTrace.flush_events() + + + def FileDoubleClicked(self, event): + DoubleclickedFile = self.list_filelist.selectedItems()[0].text() + # Get Analysis file + fullfilepath = self.GetFullFilePath([DoubleclickedFile])[0] + filename, file_extension = os.path.splitext(os.path.basename(fullfilepath)) + folder = os.path.dirname(fullfilepath) + os.sep + 'analysisfiles' + analysisfilepath = folder + os.sep + filename + 'data' + print('Analysis File Path: {}'.format(analysisfilepath)) + if os.path.isfile(analysisfilepath) or os.path.isfile(analysisfilepath + '.dat') or os.path.isfile(analysisfilepath + '.db'): + ## Display the analysis + print('Lets show the analysis') + ## Load the analysis file + self.AllEvents = LoadEvents(analysisfilepath) + self.text_eventNumber.setValidator(QIntValidator(0, len(self.AllEvents.events)-1)) + inputfile = LoadData.OpenFile(self.AllEvents.events[0].filename, ChimeraLowPass = 10e3, approxImpulseResponse = True, Split = True, verbose = False) + self.rawtrace = inputfile['i1'] + self.rawtracesamplerate = inputfile['samplerate'] + self.LPForWholeTracePlotChanged() + self.DrawEvent(int(self.text_eventNumber.text())) + self.DrawWholeTrace(int(self.text_eventNumber.text())) + print(self.AllEvents) + else: + error = QMessageBox.critical(self, 'Important Message', 'Please select a file that has been analyzed (turned green).', QMessageBox.Retry) + self.wholetracedrawnforthefirsttime = True + + def LPForWholeTracePlotChanged(self): + print(self.setting_LPForWholeTracePlot.value()) + self.dsrawtrace, self.dssamplerate = LowPassAndResample(self.rawtrace, self.rawtracesamplerate, self.setting_LPForWholeTracePlot.value() * 1e3, LPtoSR=2) + + def closeEvent(self, event): + ''' + reply = QMessageBox.question(self, 'Message', + "Are you sure to quit? Don't make me sad... :( :( :(", QMessageBox.Yes | + QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.Yes: + event.accept() + else: + event.ignore() + ''' + +## Execution +if __name__ == '__main__': + app = QApplication(sys.argv) + ex = AnalysisUI() + sys.exit(app.exec_()) diff --git a/Functions.py b/Functions.py index 00753c1..e072889 100644 --- a/Functions.py +++ b/Functions.py @@ -1,1272 +1,1272 @@ # -*- coding: utf-8 -*- import math import os import pickle as pkl from scipy import signal from scipy.optimize import curve_fit, OptimizeWarning from scipy.signal import find_peaks import matplotlib.pyplot as plt import numpy as np import pandas as pd import scipy import scipy.signal as sig from matplotlib.ticker import EngFormatter from numpy import linalg as lin from scipy import constants as cst from scipy.optimize import curve_fit import LoadData #for MakeIv import pywt #for WaveletDenoising def GetSmallMoS2PoreSize(Conductance, Sigma_Bulk=11.18): """ Function used to calculate pore size of small MoS2 pore: 1. Pérez M.D.B., Nicolaï A., Delarue P., Meunier V., Drndić M., Senet P. (2019). Improved model of ionic transport in 2-D MoS 2 membranes with sub-5 nm pores. Appl. Phys. Lett. 114, 023107. doi: 10.1063/1.5061825 Parameters ---------- Conductance : float Measured conductance. Sigma_Bulk : float conductivity of the buffer (bulk). Returns ------- scalar. Pore size in nm """ def SmallMoS2PoreConductance(d, sigma_bulk): # All in nanometers l = 0.96 phi_Cl = 0.793 phi_K = 0.832 delta_Cl = 0.41 delta_K = 0.38 epsilon_K = 1.03 epsilon_Cl = 0.97 A = sigma_bulk / 2 * 1 / ((4 * l + np.pi * d) / (np.pi * d ** 2)) B_Cl = np.exp((-4 * phi_Cl) / (np.pi * d ** 2)) * (d) / (delta_Cl + epsilon_Cl * d) B_K = np.exp((-4 * phi_K) / (np.pi * d ** 2)) * (d) / (delta_K + epsilon_K * d) return A * (B_Cl + B_K) def DrndicModelRootEquation(d, G, sigma_bulk): return SmallMoS2PoreConductance(d, sigma_bulk) - G return scipy.optimize.fsolve(DrndicModelRootEquation, 10, (Conductance, Sigma_Bulk)) def LowPass(data, samplerate, lowPass): """ Function used to filter data with a digital Bessel filter of the 4th order. Specially useful for high bandwidth recordings. Parameters ---------- data : Data to be filtered. samplerate : float Sampling frequency of the data aquisition. lowPass : float Cutoff frequency of the filter. Returns ------- dict Dictionary output of the filtered data with in keys: 'i1' : a list of the currents filtered 'samplerate' : float of new sampling frequency corresponding to 2*Cutoff frequency (Nyquist-Shannon sampling theorem). """ Wn = round(2 * lowPass / samplerate, 4) # [0,1] nyquist frequency b, a = signal.bessel(4, Wn, btype='low', analog=False) # 4-th order digital filter z, p, k = signal.tf2zpk(b, a) eps = 1e-9 r = np.max(np.abs(p)) approx_impulse_len = int(np.ceil(np.log(eps) / np.log(r))) Filt_sig = (signal.filtfilt(b, a, data, method='gust', irlen=approx_impulse_len)) ds_factor = np.ceil(samplerate / (2 * lowPass)) output = {} output['data'] = scipy.signal.resample(Filt_sig, int(len(data) / ds_factor)) output['samplerate'] = samplerate / ds_factor return output def WaveletDenoising(data, decompositionLevel = 5): """ Function used to remove noise from a signal using wavelet denoising with a bio-orthogonal wavelet implemented in the PyWavelets package.PyWavelets is open source wavelet transform software for Python. It uses Stationary Wavelet Transforms and a hard detail coefficients thresholding. See Shekar, Siddharth, et al. "Wavelet denoising of high-bandwidth nanopore and Ion channel signals". Nano letters (2019). DOI: 10.1021/acs.nanolett.8b04388 Parameters ---------- data : numpy array Data to be filtered. decompositionLevel : int Maximum decomposition level for the wavelet transform. Returns ------- dict Dictionary output of the filtered data with in keys: 'data' : a list of the current signal filtered """ #choose a wavelet basis - Here bio-orthogonal wavelet = pywt.Wavelet('bior1.5') N = len(data) #choose a maximum decomposition level -> function parameter #compute the wavelet transform for the signal x[n] to obtain detail coefficients wj,k (cD) and approximation coefficients aJ,k (cA) where j=1, ..., J and k = 1, ..., N coeffs = pywt.swt(data, wavelet, level = decompositionLevel) #multilevel reconstruction in the paper they used 5, 7 and 8 levels #calculate the noise threshold lambdaj for wj,k newcoeffs=[] j=0 while j < decompositionLevel: sigma=np.median(abs(coeffs[j][1]-np.mean(coeffs[j][1])))/0.6745 l = sigma*np.sqrt(2*np.log(N)) #choose thresholding scheme T and threshold wj,k to obtain w'j,k = T(wj,k) newcoeffs.append(tuple((np.array(coeffs[j][0]) , np.array(pywt.threshold(coeffs[j][1], value = l, mode='hard'))))) j=j+1 #perform the inerse wavelet transform using aj,k and w'j,k output = {} output['data'] = pywt.iswt(newcoeffs, wavelet) #multilevel reconstruction return output def GetKClConductivity(Conc, Temp): p = pkl.load(open('KCl_ConductivityValues.p', 'rb')) if Conc == 1.0: Conc = np.uint(1) return np.polyval(p[str(Conc)], Temp) def GetTempFromKClConductivity(Conc, Cond): p = pkl.load(open('KCl_ConductivityValues.p', 'rb')) if Conc == 1.0: Conc = np.uint(1) return (Cond - p[str(Conc)][1]) / p[str(Conc)][0] def RecursiveLowPassFast(signal, coeff, samplerate): """ Function used to find where roughly where event are in a noisy signal using a first order recursive low pass filter defined as : u[k] = au[k-1]+(1-a)i[k] with u the mean value at sample k, i the input signal and a < 1, a parameter. Parameters ---------- signal : numpy array of float Input signal. coeff: dict Coefficients used in the recursive low pass detection of rough events. samplerate : float Sampling frequency of the data aquisition. Returns ------- numpy array of float Array with the rough event locations in the input signal. """ padlen = np.uint64(samplerate) prepadded = np.ones(padlen) * np.mean(signal[0:1000]) signaltofilter = np.concatenate((prepadded, signal)) mltemp = scipy.signal.lfilter([1 - coeff['a'], 0], [1, -coeff['a']], signaltofilter) vltemp = scipy.signal.lfilter([1 - coeff['a'], 0], [1, -coeff['a']], np.square(signaltofilter - mltemp)) ml = np.delete(mltemp, np.arange(padlen)) vl = np.delete(vltemp, np.arange(padlen)) sl = ml - coeff['S'] * np.sqrt(vl) Ni = len(signal) points = np.array(np.where(signal <= sl)[0]) to_pop = np.array([]) for i in range(1, len(points)): if points[i] - points[i - 1] == 1: to_pop = np.append(to_pop, i) points = np.unique(np.delete(points, to_pop)) RoughEventLocations = [] NumberOfEvents = 0 for i in points: if NumberOfEvents is not 0: if i >= RoughEventLocations[NumberOfEvents - 1][0] and i <= RoughEventLocations[NumberOfEvents - 1][1]: continue NumberOfEvents += 1 start = i El = ml[i] + coeff['E'] * np.sqrt(vl[i]) Mm = ml[i] Vv = vl[i] duration = 0 endp = start if (endp + 1) < len(signal): while signal[endp + 1] < El and endp < (Ni - 2): # and duration < coeff['maxEventLength']*samplerate: duration += 1 endp += 1 if duration >= coeff['maxEventLength'] * samplerate or endp > ( Ni - 10): # or duration <= coeff['minEventLength'] * samplerate: NumberOfEvents -= 1 continue else: k = start while signal[k] < Mm and k > 1: k -= 1 start = k - 1 k2 = i + 1 # while signal[k2] > Mm: # k2 -= 1 # endp = k2 if start < 0: start = 0 RoughEventLocations.append((start, endp, ml[start], vl[start])) return np.array(RoughEventLocations) # , ml, vl, sl def RecursiveLowPassFastUp(signal, coeff, samplerate): """ RecursiveLowPassFast function specially written for the detection of events characterized not by a current drop, but by a current increase. Parameters ---------- signal : numpy array of float Input signal. coeff: dict Coefficients used in the recursive low pass detection of rough events. samplerate : float Sampling frequency of the data aquisition. Returns ------- numpy array of float Array with the rough event locations in the input signal. """ ml = scipy.signal.lfilter([1 - coeff['a'], 0], [1, -coeff['a']], signal) vl = scipy.signal.lfilter([1 - coeff['a'], 0], [1, -coeff['a']], np.square(signal - ml)) sl = ml + coeff['S'] * np.sqrt(vl) Ni = len(signal) points = np.array(np.where(signal >= sl)[0]) to_pop = np.array([]) for i in range(1, len(points)): if points[i] - points[i - 1] == 1: to_pop = np.append(to_pop, i) points = np.delete(points, to_pop) points = np.delete(points, np.array(np.where(points == 0)[0])) RoughEventLocations = [] NumberOfEvents = 0 for i in points: if NumberOfEvents is not 0: if i >= RoughEventLocations[NumberOfEvents - 1][0] and i <= RoughEventLocations[NumberOfEvents - 1][1]: continue NumberOfEvents += 1 start = i El = ml[i] + coeff['E'] * np.sqrt(vl[i]) Mm = ml[i] duration = 0 while signal[i + 1] > El and i < (Ni - 2) and duration < coeff['maxEventLength'] * samplerate: duration += 1 i += 1 if duration >= coeff['maxEventLength'] * samplerate or i > (Ni - 10): NumberOfEvents -= 1 else: k = start while signal[k] > Mm and k > 2: k -= 1 start = k - 1 k2 = i + 1 while signal[k2] > Mm: k2 -= 1 endp = k2 RoughEventLocations.append((start, endp, ml[start], vl[start])) return np.array(RoughEventLocations) def Reshape1DTo2D(inputarray, buffersize): npieces = int(len(inputarray) / buffersize) voltages = np.array([], dtype=np.float64) currents = np.array([], dtype=np.float64) for i in range(1, npieces + 1): if i % 2 == 1: currents = np.append(currents, inputarray[(i - 1) * buffersize:i * buffersize - 1], axis=0) # print('Length Currents: {}'.format(len(currents))) else: voltages = np.append(voltages, inputarray[(i - 1) * buffersize:i * buffersize - 1], axis=0) # print('Length Voltages: {}'.format(len(voltages))) v1 = np.ones((len(voltages)), dtype=np.float64) i1 = np.ones((len(currents)), dtype=np.float64) v1[:] = voltages i1[:] = currents out = {'v1': v1, 'i1': i1} #print('Currents:' + str(v1.shape)) #print('Voltages:' + str(i1.shape)) return out #def PoreFromIVFile(file) def MakeIVData(output, approach='mean', delay=0.7, UseVoltageRange=0, verbose=False): """ Function used to build and analyse a dataset containing the applied voltages and measured currents during a voltage sweep experiment. Used to assess the linearity and drift of the resulting signal through a Nanopore. MakeIVData can use 2 types of approaches to find the right current-voltage points: approach = 'mean': the default argument, will use the Mean method. Its better to use it for flat signals (low capacitance nanopore substrates for example). approach = 'exponential': is to be used if there is a decay in the signal usually due to a capacitive effet of the membrane. Parameters ---------- output : dict Output format of the OpenFile function of the LoadData module with the signal to use for the I-V characteristic curve points calculations. approach : str, optional 'mean' by default. Specifies the method to extimate current-voltage data points for the I-V curve 'mean' or 'exponential'. delay : float, optional 0.7 by default. Time delay in seconds before the first change of applied voltage. UseVoltageRange : float, optional 0 by default. verbose : bool, optional False by default. If True, it will allow to print strings indicating the progress of the function in the console. Returns ------- dict all the values and parameters necessary to draw an I-V characteristic plot. """ (ChangePoints, sweepedChannel) = CutDataIntoVoltageSegments(output, verbose) if ChangePoints is 0: return 0 if output['graphene']: currents = ['i1', 'i2'] else: currents = ['i1'] Values = output[sweepedChannel][ChangePoints] Values = np.append(Values, output[sweepedChannel][::-1][0]) delayinpoints = np.int64(delay * output['samplerate']) if delayinpoints > ChangePoints[0]: raise ValueError("Delay is longer than Changepoint") # Store All Data All = {} for current in currents: Item = {} ItemExp = {} l = len(Values) Item['Voltage'] = np.zeros(l) Item['StartPoint'] = np.zeros(l, dtype=np.uint64) Item['EndPoint'] = np.zeros(l, dtype=np.uint64) Item['Mean'] = np.zeros(l) Item['STD'] = np.zeros(l) Item['SE'] = np.zeros(l) Item['SweepedChannel'] = sweepedChannel ItemExp['SweepedChannel'] = sweepedChannel Item['YorkFitValues'] = {} ItemExp['YorkFitValues'] = {} ### MEAN METHOD### # First item Item['Voltage'][0] = Values[0] trace = output[current][0 + delayinpoints:ChangePoints[0]] Item['StartPoint'][0] = 0 + delayinpoints Item['EndPoint'][0] = ChangePoints[0] Item['Mean'][0] = np.mean(trace) isProblematic = math.isclose(Values[0], 0, rel_tol=1e-3) or math.isclose(Values[0], np.min(Values), rel_tol=1e-3) or math.isclose( Values[0], np.max(Values), rel_tol=1e-3) if sweepedChannel is 'v2' and isProblematic: print('In Problematic If for Values: {}, index {}'.format(Values[0], 0)) Item['STD'][0] = np.std(trace[-np.int64(output['samplerate']):]) else: Item['STD'][0] = np.std(trace) Item['SE'][0] = Item['STD'][0] / np.sqrt(len(trace)) for i in range(1, len(Values) - 1): trace = output[current][ChangePoints[i - 1] + delayinpoints: ChangePoints[i]] Item['Voltage'][i] = Values[i] Item['StartPoint'][i] = ChangePoints[i - 1] + delayinpoints Item['EndPoint'][i] = ChangePoints[i] if len(trace) > 0: Item['Mean'][i] = np.mean(trace) isProblematic = math.isclose(Values[i], 0, rel_tol=1e-3) or math.isclose(Values[i], np.min(Values), rel_tol=1e-3) or math.isclose( Values[i], np.max(Values), rel_tol=1e-3) if sweepedChannel is 'v2' and isProblematic: print('In Problematic If for Values: {}, index {}'.format(Values[i], i)) Item['STD'][i] = np.std(trace[-np.int64(output['samplerate']):]) else: Item['STD'][i] = np.std(trace) Item['SE'][i] = Item['STD'][i] / np.sqrt(len(trace)) if verbose: print('{}, {},{}'.format(i, ChangePoints[i - 1] + delayinpoints, ChangePoints[i])) else: Item['Mean'][i] = np.NaN Item['STD'][i] = np.NaN # Last if 1: trace = output[current][ChangePoints[len(ChangePoints) - 1] + delayinpoints: len(output[current]) - 1] Item['Voltage'][-1:] = Values[len(Values) - 1] Item['StartPoint'][-1:] = ChangePoints[len(ChangePoints) - 1] + delayinpoints Item['EndPoint'][-1:] = len(output[current]) - 1 if len(trace) > 0: Item['Mean'][-1:] = np.mean(trace) isProblematic = math.isclose(Values[-1:], 0, rel_tol=1e-3) or math.isclose(Values[-1:], np.min(Values), rel_tol=1e-3) or math.isclose( Values[-1:], np.max(Values), rel_tol=1e-3) if sweepedChannel is 'v2' and isProblematic: print('In Problematic If for Values: {}, index {}'.format(Values[-1:], i)) Item['STD'][-1:] = np.std(trace[-np.int64(output['samplerate']):]) else: Item['STD'][-1:] = np.std(trace) else: Item['Mean'][-1:] = np.NaN Item['STD'][-1:] = np.NaN Item['SE'][-1:] = Item['STD'][-1:] / np.sqrt(len(trace)) ## GET RECTIFICATION FACTOR parts = {'pos': Item['Voltage'] > 0, 'neg': Item['Voltage'] < 0} a = {} b = {} sigma_a = {} sigma_b = {} b_save = {} x_values = {} for part in parts: (a[part], b[part], sigma_a[part], sigma_b[part], b_save[part]) = YorkFit( Item['Voltage'][parts[part]].flatten(), Item['Mean'][parts[part]].flatten(), 1e-12 * np.ones(len(Item['Voltage'][parts[part]].flatten())), Item['STD'][parts[part]].flatten()) factor = b['neg'] / b['pos'] Item['Rectification'] = factor ItemExp['Rectification'] = factor ###EXPONENTIAL METHOD### if approach is not 'mean': l = 1000 ItemExp['StartPoint'] = np.zeros(l, dtype=np.uint64) ItemExp['EndPoint'] = np.zeros(l, dtype=np.uint64) baselinestd = np.std(output[current][0:np.int64(1 * output['samplerate'])]) dataset = pd.Series(output[current]) movingstd = dataset.rolling(10).std() # Extract The Desired Parts ind = (np.abs(movingstd - baselinestd) < 1e-9) & (np.abs(output[current]) < np.max(output[current]) / 2) # How Many Parts? restart = True counter = 0 for i, value in enumerate(ind): if value and restart: ItemExp['StartPoint'][counter] = i print('Start: {}\t'.format(i)) restart = False elif not value and not restart: if ((i - 1) - ItemExp['StartPoint'][counter]) > 1 * output['samplerate']: ItemExp['EndPoint'][counter] = i - 1 print('End: {}'.format(i - 1)) restart = True counter += 1 else: restart = True if not restart: counter += 1 ItemExp['EndPoint'][counter] = len(ind) ItemExp['StartPoint'] = ItemExp['StartPoint'][np.where(ItemExp['StartPoint'])] ItemExp['EndPoint'] = ItemExp['EndPoint'][np.where(ItemExp['EndPoint'])] NumberOfPieces = len(ItemExp['StartPoint']) ItemExp['Voltage'] = np.zeros(NumberOfPieces) ItemExp['ExpValues'] = np.zeros((3, NumberOfPieces)) ItemExp['STD'] = np.zeros(NumberOfPieces) ItemExp['Mean'] = np.zeros(NumberOfPieces) # Make It Piecewise for i in np.arange(0, NumberOfPieces): trace = output[current][ItemExp['StartPoint'][i]:ItemExp['EndPoint'][i]] popt, pcov = MakeExponentialFit(np.arange(len(trace)) / output['samplerate'], trace) ItemExp['Voltage'][i] = output[sweepedChannel][ItemExp['StartPoint'][i]] if popt[0]: ItemExp['ExpValues'][:, i] = popt ItemExp['Mean'][i] = popt[2] try: ItemExp['STD'][i] = np.sqrt(np.diag(pcov))[2] except RuntimeWarning: ItemExp['STD'][i] = np.std(trace) / np.sqrt(len(trace)) print('ExpFit: STD of voltage {} failed calculating...'.format(ItemExp['Voltage'][i])) else: print('Exponential Fit on for ' + current + ' failed at V=' + str(ItemExp['Voltage'][0])) ItemExp['Mean'][i] = np.mean(trace) ItemExp['STD'][i] = np.std(trace) ## FIT THE EXP CALCULATED VALUES ### (a, b, sigma_a, sigma_b, b_save) = YorkFit(ItemExp['Voltage'], ItemExp['Mean'], 1e-12 * np.ones(len(ItemExp['Voltage'])), ItemExp['STD']) x_fit = np.linspace(min(Item['Voltage']), max(Item['Voltage']), 1000) y_fit = scipy.polyval([b, a], x_fit) ItemExp['YorkFitValues'] = {'x_fit': x_fit, 'y_fit': y_fit, 'Yintercept': a, 'Slope': b, 'Sigma_Yintercept': sigma_a, 'Sigma_Slope': sigma_b, 'Parameter': b_save} All[current + '_Exp'] = ItemExp ##Remove NaNs nans = np.isnan(Item['Mean'][:]) nans = np.logical_not(nans) Item['Voltage'] = Item['Voltage'][nans] Item['StartPoint'] = Item['StartPoint'][nans] Item['EndPoint'] = Item['EndPoint'][nans] Item['Mean'] = Item['Mean'][nans] Item['STD'] = Item['STD'][nans] Item['SE'] = Item['SE'][nans] ## Restrict to set voltage Range if UseVoltageRange is not 0: print('Voltages Cut') ind = np.argwhere((Item['Voltage'] >= UseVoltageRange[0]) & (Item['Voltage'] <= UseVoltageRange[1])) Item['Voltage'] = Item['Voltage'][ind].flatten() Item['StartPoint'] = Item['StartPoint'][ind].flatten() Item['EndPoint'] = Item['EndPoint'][ind].flatten() Item['Mean'] = Item['Mean'][ind].flatten() Item['STD'] = Item['STD'][ind].flatten() Item['SE'] = Item['SE'][ind].flatten() ## FIT THE MEAN CALCULATED VALUES ### # Yorkfit (a, b, sigma_a, sigma_b, b_save) = YorkFit(Item['Voltage'].flatten(), Item['Mean'].flatten(), 1e-9 * np.ones(len(Item['Voltage'])), Item['STD'].flatten()) x_fit = np.linspace(min(Item['Voltage']), max(Item['Voltage']), 1000) y_fit = scipy.polyval([b, a], x_fit) Item['YorkFit'] = {'x_fit': x_fit, 'y_fit': y_fit, 'Yintercept': a, 'Slope': b, 'Sigma_Yintercept': sigma_a, 'Sigma_Slope': sigma_b, 'Parameter': b_save} # Polyfit p = np.polyfit(Item['Voltage'].flatten(), Item['Mean'].flatten(), 1) x_fit2 = np.linspace(min(Item['Voltage']), max(Item['Voltage']), 1000) y_fit2 = scipy.polyval(p, x_fit) Item['PolyFit'] = {'x_fit': x_fit2, 'y_fit': y_fit2, 'Yintercept': p[1], 'Slope': p[0]} All[current] = Item All['Currents'] = currents return All def ExpFunc(x, a, b, c): """ Function used to create an exponential function used MakeIVData to fit the signal around the capacitive drift and therefore predict the stabilised current signal at each voltage jump in the MakeIV function. Parameters ---------- x : numpy array Input values. a : float b : float c : flaot Returns ------- numpy array a*exp(-b*x)+c function. """ return a * np.exp(-b * x) + c def fourier(x, a0, w, *a): n = int(len(a) / 2) ret = a0 for i in range(0, n): ret += a[i] * np.cos((i + 1) * w * x) ret += a[i + n] * np.sin((i + 1) * w * x) return ret def gaussian(x,b,a,mu,sigma): ret = b - a * np.exp(-0.5 * ((x - mu) / sigma) **2) return ret def ImpulseFitting(trace, baseline, samplerate,verbose=False): baseline = baseline * 1e9 trace = trace * 1e9 time = np.linspace(0, len(trace) / samplerate, num=len(trace)) # Gaussian fitting b = baseline a = baseline - np.min(trace) # shortEvent.currentDrop*1e9 mu = np.argmin(trace) / samplerate # len(time)/(2*samplerate) sigma_g = (len(time) - 50) / (8 * samplerate) p0 = [b, a, mu, sigma_g] try: popt, pcov = curve_fit(gaussian, time, trace, p0, method='trf') perr = np.sqrt(np.diag(pcov)) if verbose: print('error in fitting parameters:') print(perr) except RuntimeError or OptimizeWarning: return -1 fitTrace = gaussian(time, *popt) if verbose: ss_res = sum((trace - fitTrace) ** 2) ss_tot = sum((np.mean(trace) - trace) ** 2) r2 = 1 - ss_res / ss_tot print('RSquared of fit: {r:1.3f}'.format(r=r2)) # Calculate differential diffTrace = np.diff(np.diff(fitTrace)) peaks, _ = find_peaks(diffTrace) peaks = np.array([i for i in peaks if trace[i]]) maxdata = peaks[np.argsort(diffTrace[peaks])] if len(peaks) > 0 else [-3] peaks2, _ = find_peaks(-1 * diffTrace) # find starting and end points pe1 = maxdata[-1] + 2 peaks2_ = [i for i in peaks2 if i < pe1] ps1 = max(peaks2_) if len(peaks2_) > 0 else -1 peaks2_ = [i for i in peaks2 if i > pe1] pe2 = min(peaks2_) + 2 if len(peaks2_) > 0 else -1 if ps1 > -1 and pe2 > -1 and pe1 > -1: ss_res = sum((trace[ps1:pe2] - fitTrace[ps1:pe2])**2) # ss_reg = sum((np.mean(trace[ps1:pe2]) - fitTrace[ps1:pe2])**2) ss_tot = sum((np.mean(trace[ps1:pe2]) - trace[ps1:pe2])**2) r2drop = 1 - ss_res / ss_tot # area = baseline * (pe2-ps1) - sum(trace[ps1:pe2]) area = baseline * (pe2 - ps1) - sum(fitTrace[ps1:pe2]) Idrop = (area / (pe1 - ps1)) * 1e-9 else: return -2 return ps1, pe1, pe2, r2drop, Idrop def YorkFit(X, Y, sigma_X, sigma_Y, r=0): """ Function used to do the linear regression taking the error into account as the standard deviation. It is used in MakeIV, for the estimation of the linear regression curve of voltage versus current, estimated by a voltage sweep experiment. Parameters ---------- X : numpy array Y : numpy array sigma_X : numpy array signma_Y : numpy array r : float, optional Returns ------- tuple coefficients a and b of the linear regression y=ax+b and their variances. """ N_itermax = 10 # maximum number of interations tol = 1e-15 # relative tolerance to stop at N = len(X) temp = np.matrix([X, np.ones(N)]) # make initial guess at b using linear squares tmp = np.matrix(Y) * lin.pinv(temp) b_lse = np.array(tmp)[0][0] # a_lse=tmp(2); b = b_lse # initial guess omega_X = np.true_divide(1, np.power(sigma_X, 2)) omega_Y = np.true_divide(1, np.power(sigma_Y, 2)) alpha = np.sqrt(omega_X * omega_Y) b_save = np.zeros(N_itermax + 1) # vector to save b iterations in b_save[0] = b for i in np.arange(N_itermax): W = omega_X * omega_Y / (omega_X + b * b * omega_Y - 2 * b * r * alpha) X_bar = np.sum(W * X) / np.sum(W) Y_bar = np.sum(W * Y) / np.sum(W) U = X - X_bar V = Y - Y_bar beta = W * (U / omega_Y + b * V / omega_X - (b * U + V) * r / alpha) b = sum(W * beta * V) / sum(W * beta * U) b_save[i + 1] = b if np.abs((b_save[i + 1] - b_save[i]) / b_save[i + 1]) < tol: break a = Y_bar - b * X_bar x = X_bar + beta y = Y_bar + b * beta x_bar = sum(W * x) / sum(W) y_bar = sum(W * y) / sum(W) u = x - x_bar # %v=y-y_bar sigma_b = np.sqrt(1 / sum(W * u * u)) sigma_a = np.sqrt(1. / sum(W) + x_bar * x_bar * sigma_b * sigma_b) return (a, b, sigma_a, sigma_b, b_save) def DoubleExpFunc(x, Y0, percentFast, plateau, Kfast, Kslow): SpanFast = (Y0 - plateau) * percentFast * 0.01 SpanSlow = (Y0 - plateau) * (100 - percentFast) * 0.01 return plateau + ((Y0 - plateau) * percentFast * 0.01) * np.exp(-Kfast * x) + ( (Y0 - plateau) * (100 - percentFast) * 0.01) * np.exp(-Kslow * x) def MakeExponentialFit(xdata, ydata): try: popt, pcov = curve_fit(ExpFunc, xdata, ydata, method='lm', xtol=1e-20, ftol=1e-12, gtol=1e-20) # popt, pcov = curve_fit(DoubleExpFunc, xdata, ydata, p0 = (ydata[0], 50, ydata[-1], 1 / 15, 1 / 60)) return (popt, pcov) except RuntimeError: popt = (0, 0, 0) pcov = 0 return (popt, pcov) def CutDataIntoVoltageSegments(output, verbose=False): """ Function used to make the I-V curve (in MakeIVData). It cuts the data in segments where a constant voltage was applied. Parameters ---------- output : dict Output format of the OpenFile function of the LoadData module with the signal to use for the I-V characteristic curve points calculations. verbose : bool, optional False by default. If True, it will allow to print strings indicating the progress of the function in the console. Returns ------- tuple ChangePoints: ndarray with data points where the voltage applied changed sweepedChannel: string with the key giving acess to the stored the voltage values applied. """ sweepedChannel = '' if output['type'] == 'ChimeraNotRaw' or (output['type'] == 'Axopatch' and not output['graphene']): ChangePoints = np.where(np.diff(output['v1']))[0] sweepedChannel = 'v1' if len(ChangePoints) is 0: print('No voltage sweeps in this file:\n{}'.format(output['filename'])) return (0, 0) elif (output['type'] == 'Axopatch' and output['graphene']): ChangePoints = np.where(np.diff(output['v1']))[0] sweepedChannel = 'v1' if len(ChangePoints) is 0: ChangePoints = np.where(np.diff(output['v2']))[0] if len(ChangePoints) is 0: print('No voltage sweeps in this file') return (0, 0) else: sweepedChannel = 'v2' if verbose: print('Cutting into Segments...\n{} change points detected in channel {}...'.format(len(ChangePoints), sweepedChannel)) return (ChangePoints, sweepedChannel) def CalculatePoreSize(G, L, s): # https://doi.org/10.1088/0957-4484/22/31/315101 return (G + np.sqrt(G * (G + 16 * L * s / np.pi))) / (2 * s) def CalculateCapillarySize(G, D=0.3e-3, t=3.3e-3, s=10.5): # DOI: 10.1021/nn405029j # s=conductance (10.5 S/m at 1M KCl) # t=taper length (3.3 mm) # D=diameter nanocapillary at the shaft (0.3 mm) return G * (4 * t / np.pi + 0.5 * D) / (s * D - 0.5 * G) def CalculateShrunkenCapillarySize(G, D=0.3e-3, t=3.3e-3, s=10.5, ts=500e-9, Ds=500e-9): # DOI: 10.1021/nn405029j # s=conductance (10.5 S/m at 1M KCl) # t=taper length (3.3 mm) # D=diameter nanocapillary at the shaft (0.3 mm) # ts=taper length of the shrunken part (543nm fit) # Ds=diameter nanocapillary at the shrunken part (514nm fit) return G * D * (8 * ts / np.pi + Ds) / ((2 * s * D * Ds) - (G * (8 * t / np.pi + Ds))) def CalculateConductance(L, s, d): return s * 1 / (4 * L / (np.pi * d ** 2) + 1 / d) def GetSurfaceChargeFromLeeEquation(s, c, D, G, L, A, B, version=1): lDu = 1 / (8 * np.pi * B * D * s) * ( version * np.sqrt( (4 * np.pi * A * D ** 2 * s + np.pi * B * D ** 2 * s - 4 * B * G * L - 8 * np.pi * D * G) ** 2 - 16 * np.pi * B * D * s * (np.pi * A * D ** 3 * s - 4 * A * D * G * L - 2 * np.pi * D ** 2 * G)) - 4 * np.pi * A * D ** 2 * s - np.pi * B * D ** 2 * s + 4 * B * G * L + 8 * np.pi * D * G ) e = cst.elementary_charge Na = cst.Avogadro return lDu * (2 * c * Na * 1e3 * e) def ConductivityFromConductanceModel(L, d, G): return G * (4 * L / (np.pi * d ** 2) + 1 / d) def RefinedEventDetection(out, AnalysisResults, signals, limit): for sig in signals: # Choose correct reference if sig is 'i1_Up': sig1 = 'i1' elif sig is 'i2_Up': sig1 = 'i2' else: sig1 = sig if sig is 'i1' or 'i1_Up': volt = 'v1' elif sig is 'i2' or 'i2_Up': volt = 'v2' if len(AnalysisResults[sig]['RoughEventLocations']) > 1: startpoints = np.uint64(AnalysisResults[sig]['RoughEventLocations'][:, 0]) endpoints = np.uint64(AnalysisResults[sig]['RoughEventLocations'][:, 1]) localBaseline = AnalysisResults[sig]['RoughEventLocations'][:, 2] localVariance = AnalysisResults[sig]['RoughEventLocations'][:, 3] CusumBaseline = 500 numberofevents = len(startpoints) AnalysisResults[sig]['StartPoints'] = startpoints AnalysisResults[sig]['EndPoints'] = endpoints AnalysisResults[sig]['LocalBaseline'] = localBaseline AnalysisResults[sig]['LocalVariance'] = localVariance AnalysisResults[sig]['NumberOfEvents'] = len(startpoints) deli = np.zeros(numberofevents) dwell = np.zeros(numberofevents) fitvalue = np.zeros(numberofevents) AllEvents = [] #####FIX THE UP AND DOWN STORY!! ALL THE PROBLEMS ARE IN HERE... for i in range(numberofevents): length = endpoints[i] - startpoints[i] if out[sig1][startpoints[i]] < 0 and (sig == 'i1_Up' or sig == 'i2_Up'): if length <= limit and length > 3: # Impulsion Fit to minimal value fitvalue[i] = np.max(out[sig1][startpoints[i] + np.uint(1):endpoints[i] - np.uint(1)]) elif length > limit: fitvalue[i] = np.mean(out[sig1][startpoints[i] + np.uint(5):endpoints[i] - np.uint(5)]) else: fitvalue[i] = np.max(out[sig1][startpoints[i]:endpoints[i]]) deli[i] = -(localBaseline[i] - fitvalue[i]) elif out[sig1][startpoints[i]] < 0 and (sig == 'i1' or sig == 'i2'): if length <= limit and length > 3: # Impulsion Fit to minimal value fitvalue[i] = np.min(out[sig1][startpoints[i] + np.uint(1):endpoints[i] - np.uint(1)]) elif length > limit: fitvalue[i] = np.mean(out[sig1][startpoints[i] + np.uint(5):endpoints[i] - np.uint(5)]) else: fitvalue[i] = np.min(out[sig1][startpoints[i]:endpoints[i]]) deli[i] = -(localBaseline[i] - fitvalue[i]) elif out[sig1][startpoints[i]] > 0 and (sig == 'i1_Up' or sig == 'i2_Up'): if length <= limit and length > 3: # Impulsion Fit to minimal value fitvalue[i] = np.max(out[sig1][startpoints[i] + np.uint(1):endpoints[i] - np.uint(1)]) elif length > limit: fitvalue[i] = np.mean(out[sig1][startpoints[i] + np.uint(5):endpoints[i] - np.uint(5)]) else: fitvalue[i] = np.max(out[sig1][startpoints[i]:endpoints[i]]) deli[i] = (localBaseline[i] - fitvalue[i]) elif out[sig1][startpoints[i]] > 0 and (sig == 'i1' or sig == 'i2'): if length <= limit and length > 3: # Impulsion Fit to minimal value fitvalue[i] = np.min(out[sig1][startpoints[i] + np.uint(1):endpoints[i] - np.uint(1)]) elif length > limit: fitvalue[i] = np.mean(out[sig1][startpoints[i] + np.uint(5):endpoints[i] - np.uint(5)]) else: fitvalue[i] = np.min(out[sig1][startpoints[i]:endpoints[i]]) deli[i] = (localBaseline[i] - fitvalue[i]) else: print( 'Strange event that has to come from a neighboring universe...Computer will self-destruct in 3s!') dwell[i] = (endpoints[i] - startpoints[i]) / out['samplerate'] AllEvents.append(out[sig1][startpoints[i]:endpoints[i]]) frac = deli / localBaseline dt = np.array(0) dt = np.append(dt, np.diff(startpoints) / out['samplerate']) numberofevents = len(dt) # AnalysisResults[sig]['CusumFits'] = AllFits AnalysisResults[sig]['FractionalCurrentDrop'] = frac AnalysisResults[sig]['DeltaI'] = deli AnalysisResults[sig]['DwellTime'] = dwell AnalysisResults[sig]['Frequency'] = dt AnalysisResults[sig]['LocalVoltage'] = out[volt][startpoints] AnalysisResults[sig]['AllEvents'] = AllEvents AnalysisResults[sig]['FitLevel'] = fitvalue else: AnalysisResults[sig]['NumberOfEvents'] = 0 return AnalysisResults def MakeIV(filenames, directory, conductance=10, title=''): """ Function used to make IV plots for a voltage sweep. To do so, it calls the MakeIVData function seen earlier and plots its output. Parameters ---------- filenames : list of strings Full paths to data files. directory : string Full path to directory used to save I-V curve images. conductance : list of float, optional title : str, optional Plot title. """ if len(conductance) is 1: conductance = np.ones(len(filenames)) * conductance for idx, filename in enumerate(filenames): print(filename) # Make Dir to save images output = LoadData.OpenFile(filename) if not os.path.exists(directory): os.makedirs(directory) AllData = MakeIVData(output, delay=2) if AllData == 0: print('!!!! No Sweep in: ' + filename) continue # Plot Considered Part # figExtracteParts = plt.figure(1) # ax1 = figExtracteParts.add_subplot(211) # ax2 = figExtracteParts.add_subplot(212, sharex=ax1) # (ax1, ax2) = uf.PlotExtractedPart(output, AllData, current = 'i1', unit=1e9, axis = ax1, axis2=ax2) # plt.show() # figExtracteParts.savefig(directory + os.sep + 'PlotExtracted_' + str(os.path.split(filename)[1])[:-4] + '.eps') # figExtracteParts.savefig(directory + os.sep + 'PlotExtracted_' + str(os.path.split(filename)[1])[:-4] + '.png', dpi=150) # Plot IV if output['graphene']: figIV2 = plt.figure(3) ax2IV = figIV2.add_subplot(111) ax2IV = PlotIV(output, AllData, current='i2', unit=1e9, axis=ax2IV, WithFit=1, title=title[idx]) figIV2.tight_layout() figIV2.savefig(directory + os.sep + str(os.path.split(filename)[1]) + '_IV_i2.png', dpi=300) figIV2.savefig(directory + os.sep + str(os.path.split(filename)[1]) + '_IV_i2.eps') figIV2.clear() figIV = plt.figure(2) ax1IV = figIV.add_subplot(111) ax1IV = PlotIV(output, AllData, current='i1', unit=1e9, axis=ax1IV, WithFit=1, PoreSize=[conductance[idx], 20e-9], title=title[idx]) figIV.tight_layout() # Save Figures figIV.savefig(directory + os.sep + str(os.path.split(filename)[1]) + 'IV_i1.png', dpi=300) figIV.savefig(directory + os.sep + str(os.path.split(filename)[1]) + 'IV_i1.eps') figIV.clear() def TwoChannelAnalysis(filenames, outdir, UpwardsOn=0): """ Function used to analyse two channel recordings with in one channel the current and voltage values across the nanopore and in the other of the transverse current and voltage values just before the nanopore. Calls the RecursiveLowPassFast function to detect the events (and if needed the RecursiveLowPassFastUp for the argument UpwardsOn = 1) to detect the events. Then calls RefinedEventDetection to get more precise informations on the events. It correlates the 2 channels to seek the common and uncommon events. Finally the TranslocationEvents detection will be plotted. Parameters ---------- filenames : list of strings Full paths to data files. outdir : Full path to output directory. UpwardsOn : bool, optional False by default. If True, we will run a RecursiveLowPassFastUp to detect events with current increases instead of current drops. """ for filename in filenames: print(filename) inp = LoadData.OpenFile(filename) file = os.sep + str(os.path.split(filename)[1][:-4]) if not os.path.exists(outdir): os.makedirs(outdir) # Low Pass Event Detection AnalysisResults = {} if inp['graphene']: chan = ['i1', 'i2'] # For looping over the channels else: chan = ['i1'] for sig in chan: AnalysisResults[sig] = {} AnalysisResults[sig]['RoughEventLocations'] = RecursiveLowPassFast(inp[sig], pm.coefficients[sig], inp['samplerate']) if UpwardsOn: # Upwards detection can be turned on or off AnalysisResults[sig + '_Up'] = {} AnalysisResults[sig + '_Up']['RoughEventLocations'] = RecursiveLowPassFastUp(inp[sig], pm.coefficients[sig], inp['samplerate']) # f.PlotRecursiveLPResults(AnalysisResults['i2'], inp, directory, 1e-3, channel='i2') # f.PlotRecursiveLPResults(AnalysisResults['i2_Up'], inp, directory+'UP_', 1e-3, channel='i2') # Refine the Rough Event Detection done by the LP filter and Add event infos AnalysisResults = RefinedEventDetection(inp, AnalysisResults, signals=chan, limit=pm.MinimalFittingLimit * inp['samplerate']) # Correlate the two channels if inp['graphene']: (CommonIndexes, OnlyIndexes) = f.CorrelateTheTwoChannels(AnalysisResults, 10e-3 * inp['samplerate']) print('\n\nAnalysis Done...\nThere are {} common events\n{} Events on i1 only\n{} Events on i2 only'.format( len(CommonIndexes['i1']), len(OnlyIndexes['i1']), len(OnlyIndexes['i2']))) # Plot The Events f.SaveAllPlots(CommonIndexes, OnlyIndexes, AnalysisResults, directory, inp, pm.PlotBuffer) else: # Plot The Events f.SaveAllAxopatchEvents(AnalysisResults, directory, inp, pm.PlotBuffer) def PlotExtractedPart(output, AllData, current='i1', unit=1e9, axis='', axis2=''): time = np.arange(0, len(output[current])) / output['samplerate'] axis.plot(time, output[current] * unit, 'b', label=str(os.path.split(output['filename'])[1])[:-4]) axis.set_ylabel('Current ' + current + ' [nA]') axis.set_title('Time Trace') for i in range(0, len(AllData[current]['StartPoint'])): axis.plot(time[AllData[current]['StartPoint'][i]:AllData[current]['EndPoint'][i]], output[current][AllData[current]['StartPoint'][i]:AllData[current]['EndPoint'][i]] * unit, 'r') axis2.plot(time, output[AllData[current]['SweepedChannel']], 'b', label=str(os.path.split(output['filename'])[1])[:-4]) axis2.set_ylabel('Voltage ' + AllData[current]['SweepedChannel'] + ' [V]') axis2.set_xlabel('Time') return (axis, axis2) def xcorr(x, y, k, normalize=True): n = x.shape[0] # initialize the output array out = np.empty((2 * k) + 1, dtype=np.double) lags = np.arange(-k, k + 1) # pre-compute E(x), E(y) mu_x = x.mean() mu_y = y.mean() # loop over lags for ii, lag in enumerate(lags): # use slice indexing to get 'shifted' views of the two input signals if lag < 0: xi = x[:lag] yi = y[-lag:] elif lag > 0: xi = x[:-lag] yi = y[lag:] else: xi = x yi = y # x - mu_x; y - mu_y xdiff = xi - mu_x ydiff = yi - mu_y # E[(x - mu_x) * (y - mu_y)] out[ii] = xdiff.dot(ydiff) / n # NB: xdiff.dot(ydiff) == (xdiff * ydiff).sum() if normalize: # E[(x - mu_x) * (y - mu_y)] / (sigma_x * sigma_y) out /= np.std(x) * np.std(y) return lags, out def CUSUM(input, delta, h,verbose=False): """ Function used in the detection of abrupt changes in mean current; optimal for Gaussian signals. CUSUM is based on the cummulative sum algorithm. This function will define new start and end points more precisely than just the RecursiveLowPassFast and will fit levels inside the TransolocationEvent objects. Parameters ---------- input : numpy array Input signal. delat : float Most likely jump to be detected in the signal. h : float Threshold for the detection test. Returns ------- mc : the piecewise constant segmented signal kd : a list of float detection times (in samples) krmv : a list of float estimated change times (in samples). """ # initialization Nd = k0 = 0 kd = [] krmv = [] k = 1 l = len(input) m = np.zeros(l) m[k0] = input[k0] v = np.zeros(l) sp = np.zeros(l) Sp = np.zeros(l) gp = np.zeros(l) sn = np.zeros(l) Sn = np.zeros(l) gn = np.zeros(l) while k < l: m[k] = np.mean(input[k0:k + 1]) v[k] = np.var(input[k0:k + 1]) sp[k] = delta / v[k] * (input[k] - m[k] - delta / 2) sn[k] = -delta / v[k] * (input[k] - m[k] + delta / 2) Sp[k] = Sp[k - 1] + sp[k] Sn[k] = Sn[k - 1] + sn[k] gp[k] = np.max([gp[k - 1] + sp[k], 0]) gn[k] = np.max([gn[k - 1] + sn[k], 0]) if gp[k] > h or gn[k] > h: kd.append(k) if gp[k] > h: kmin = np.argmin(Sp[k0:k + 1]) krmv.append(kmin + k0) else: kmin = np.argmin(Sn[k0:k + 1]) krmv.append(kmin + k0) # Re-initialize k0 = k m[k0] = input[k0] v[k0] = sp[k0] = Sp[k0] = gp[k0] = sn[k0] = Sn[k0] = gn[k0] = 0 Nd = Nd + 1 k += 1 if verbose: print('delta:' + str(delta)) print('h:' + str(h)) print('Nd: '+ str(Nd)) print('krmv: ' + str(krmv)) if Nd == 0: mc = np.mean(input) * np.ones(k) elif Nd == 1: mc = np.append(m[krmv[0]] * np.ones(krmv[0]), m[k - 1] * np.ones(k - krmv[0])) else: mc = m[krmv[0]] * np.ones(krmv[0]) for ii in range(1, Nd): mc = np.append(mc, m[krmv[ii]] * np.ones(krmv[ii] - krmv[ii - 1])) mc = np.append(mc, m[k - 1] * np.ones(k - krmv[Nd - 1])) return (mc, kd, krmv) def GetPSD(input): Fulltrace = input['i1'] samplerate = input['samplerate'] - f, Pxx_den = sig.welch(Fulltrace, samplerate,nperseg=2**18) + f, Pxx_den = sig.welch(Fulltrace, samplerate, nperseg=2**18) return f, Pxx_den def CondPoreSizeTick(x, pos): formCond = EngFormatter(unit='S') formPoreSize = EngFormatter(unit='m') outstring = '{}, {}'.format(formCond.format_eng(x), formPoreSize.format_eng(CalculatePoreSize(x, 1e-9, 10))) return outstring def DebyeHuckelParam(c, T=20): ## Valid Only for same valency and c1 = c2 epsilon_r = 87.740 - 0.40008 * T + 9.398e-4 * T ** 2 - 1.410e-6 * T ** 3 print('Dielectric of Water is {}'.format(epsilon_r)) n = c * 1e3 * cst.Avogadro formCond = EngFormatter(unit='m') k = np.sqrt((cst.e ** 2 * 2 * n) / (cst.k * (T + 273.15) * cst.epsilon_0 * epsilon_r)) return [k, 1 / k] # , '{}'.format(formCond.format_eng(1/k))] def ElectrostaticPotential(T, sigma, Tau=5e18, pK=7.9, pH=11): return cst.k * T / cst.e * (np.log(-sigma / (cst.e * Tau + sigma)) + (pK - pH) * np.log(10)) def GetTempRedoxPotential(Cdiff=1e-3 / 1e-1, T=293.15): return cst.R * T / cst.physical_constants['Faraday constant'][0] * np.log(Cdiff) def GetRedoxError(cmax, cmin, ErrorPercent=0.1, T=293.15): cmaxErr = cmax * ErrorPercent cminErr = cmin * ErrorPercent return cst.R * T / cst.physical_constants['Faraday constant'][0] * np.sqrt( (1 / cmax ** 2 * cmaxErr ** 2 + 1 / cmin ** 2 * cminErr ** 2)) ##Result: Negative ion divided by positive ion # Goldman–Hodgkin–Katz voltage equation def GetIonSelectivityWithoutPotential(c_trans, c_cis, Erev, T): n_trans = c_trans # *1e3#*cst.Avogadro n_cis = c_cis # *1e3#*cst.Avogadro A = Erev * cst.physical_constants['Faraday constant'][0] / (cst.R * T) return -(n_trans - n_cis * np.exp(-A)) * (1 - np.exp(A)) / ((n_trans - n_cis * np.exp(A)) * (1 - np.exp(-A))) def GetIonSelectivityWithPotential(c_trans, c_cis, Erev, T, phi): sel1 = GetIonSelectivityWithoutPotential(c_trans, c_cis, Erev, T) return sel1 * np.exp((-2 * phi * cst.physical_constants['Faraday constant'][0]) / (cst.R * T)) def Selectivity(ConcGradient, V): return V * cst.physical_constants['Faraday constant'][0] / ((cst.R * 293.15) * np.log(ConcGradient)) def GetRedox(cmax, cmin, T=295.15): return cst.R * T / cst.physical_constants['Faraday constant'][0] * np.log(cmax / cmin) diff --git a/MainPlotGui.py b/MainPlotGui.py index a1349ef..b4fd2fa 100644 --- a/MainPlotGui.py +++ b/MainPlotGui.py @@ -1,96 +1,100 @@ from PyQt5.QtWidgets import QMainWindow, QApplication import pyqtgraph.exporters from PyQt5 import QtGui from plotgui import Ui_Plotting import pyqtgraph as pg import Functions as f import sys import numpy as np import os import glob import LoadData # Draw UI app = QApplication(sys.argv) window = QMainWindow() ui = Ui_Plotting() ui.setupUi(window) folder = '' currentFile = '' +ui.graphicsView.setBackground('w') +ui.graphicsView_2.setBackground('w') +ui.graphicsView_3.setBackground('w') + def ChangedFolder(): global folder ui.listWidget.clear() folder = QtGui.QFileDialog.getExistingDirectory(window, 'Select in which Folder to save your data', os.getcwd()) if folder: print('Opening ' + folder) os.chdir(folder) file_list = glob.glob(folder + os.sep + '*.dat') file_list.extend(glob.glob(folder + os.sep + '*.log')) file_list.extend(glob.glob(folder + os.sep + '*.abf')) - file_list.sort(key=os.path.getmtime) + file_list.sort(key = os.path.getmtime) for i in file_list: ui.listWidget.addItem(os.path.split(i)[1]) def ItemChanged(it): print(it.text()) global currentFile currentFile = it.text() # Update Plots UpdatePlots(currentFile) ui.label.setText(currentFile) def UpdatePlots(currentFile): - inp = LoadData.OpenFile(folder + os.sep + currentFile,None,True) + inp = LoadData.OpenFile(folder + os.sep + currentFile, None, True) ui.graphicsView.plotItem.clear() ui.graphicsView_2.plotItem.clear() ui.graphicsView_3.plotItem.clear() if 'i1' in inp: - ui.graphicsView.plot(y=inp['i1'], x=np.arange(0, len(inp['i1']))/ inp['samplerate'], pen = 'r') + ui.graphicsView.plot(y=inp['i1'], x=np.arange(0, len(inp['i1']))/ inp['samplerate'], pen = 'k') if 'i2' in inp: - ui.graphicsView_3.plot(y=inp['i2'], x=np.arange(0, len(inp['i2']))/inp['samplerate'], pen = 'r') + ui.graphicsView_3.plot(y=inp['i2'], x=np.arange(0, len(inp['i2']))/inp['samplerate'], pen = 'b') if 'v2' in inp: - ui.graphicsView_2.plot(y=inp['v2'], x=np.arange(0, len(inp['i2']))/inp['samplerate'], pen = 'r') + ui.graphicsView_2.plot(y=inp['v2'], x=np.arange(0, len(inp['i2']))/inp['samplerate'], pen = 'b') if 'v1' in inp: ui.graphicsView_2.plot(y=inp['v1'], x=np.arange(0, len(inp['i1']))/inp['samplerate'], pen = 'k') def SaveWindow(): win = pg.GraphicsWindow() #win.addItem(ui.graphicsView.plotItem) win.show() exporter = pg.exporters.ImageExporter(ui.graphicsView.plotItem) exporter.parameters()['width'] = 10000 # (note this also affects height parameter) exporter.export('fileName.png') exporter2 = pg.exporters.ImageExporter(ui.graphicsView_3.plotItem) exporter2.parameters()['width'] = 10000 # (note this also affects height parameter) exporter2.export('fileName_1.png') ui.listWidget.itemDoubleClicked.connect(ItemChanged) ui.pushButton.pressed.connect(ChangedFolder) ui.pushSave.pressed.connect(SaveWindow) ## Plotting Settings ui.graphicsView.plotItem.setDownsampling(ds=10, auto=True, mode='subsample') ui.graphicsView.plotItem.setClipToView(True) pg.setConfigOptions(antialias=True) pg.setConfigOptions(background=None) ui.graphicsView.plotItem.setLabel('left', "Current", units='A') ui.graphicsView.plotItem.setLabel('bottom', "Time", units='s') ui.graphicsView_2.plotItem.setDownsampling(ds=10, auto=True, mode='subsample') ui.graphicsView_2.plotItem.setClipToView(True) ui.graphicsView_2.plotItem.setXLink(ui.graphicsView.plotItem) ui.graphicsView_2.plotItem.setLabel('left', "Voltage", units='V') ui.graphicsView_2.plotItem.setLabel('bottom', "Time", units='s') ui.graphicsView_3.plotItem.setDownsampling(ds=100, auto=True, mode='subsample') ui.graphicsView_3.plotItem.setClipToView(True) ui.graphicsView_3.plotItem.setXLink(ui.graphicsView.plotItem) ui.graphicsView_3.plotItem.setLabel('left', "Current", units='A') ui.graphicsView_3.plotItem.setLabel('bottom', "Time", units='s') window.show() sys.exit(app.exec_()) \ No newline at end of file diff --git a/MakeIndividualIVs.py b/MakeIndividualIVs.py index 60097f9..bd894e5 100644 --- a/MakeIndividualIVs.py +++ b/MakeIndividualIVs.py @@ -1,225 +1,228 @@ # -*- coding: utf-8 -*- import numpy as np import Functions as uf import os import matplotlib.pyplot as plt import matplotlib from tkinter import Tk from tkinter.filedialog import askopenfilenames from matplotlib.font_manager import FontProperties import platform import csv import argparse from pprint import pprint import LoadData #Set font properties fontP = FontProperties() fontP.set_size('small') props = dict(boxstyle='round', facecolor='wheat', alpha=0.5) matplotlib.rcParams['pdf.fonttype'] = 42 matplotlib.rcParams['ps.fonttype'] = 42 #Set units formatting from matplotlib.ticker import EngFormatter Res = EngFormatter(unit='Ω', places=2) Cond = EngFormatter(unit='S', places=2) SpesCond = EngFormatter(unit='S/m', places=2) size = EngFormatter(unit='m', places=2) if (platform.system()=='Darwin'): Tk().withdraw() os.system('''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "python" to true' ''') # Define default parameters for size fitting expname = 'All' Parameters = { 'Type': 'Nanopore', # Nanopore, Nanocapillary, NanocapillaryShrunken 'reversePolarity': 0, 'specificConductance': 10.5, # 10.5 S/m for 1M KCl 'delay': 3, # seconds for reading current # Nanopore 'poreLength': 1e-9, # Nanocapillary 'taperLength': 3.3e-3, 'innerDiameter': 0.2e-3, 'taperLengthShaft': 543e-9, 'innerDiameterShaft': 514e-9, # Fit option 'CurveFit': 'PolyFit', # PolyFit YorkFit 'fittingMethod': 'expFit' # expFit, mean } def GetParameters(): print("Usage:") - print("run(filenames,newParameters={},verbose=False)") + print("run(filenames,newParameters={},verbose=False, noPlot=False") + print('Returns poresize in m') print() print("Default Parameters:") pprint(Parameters) def run(filenames, newParameters={}, verbose=False, noPlot=False): """ Function used to call all the necessary other functions to make I-V curves. It takes a list of filenames as necessary argument. For each data file, a corresponding I-V curve will be generated. It iterates on the filenames in the list and calls the corresponding MakeIVData from Functions module. Parameters ---------- filenames : list of strings Full paths to data files. newParameters : dict Contains the default parameters for the analysis. verbose : bool, optional False by default. If True, it will display a simple figure with the shape of the signal. + noPlot : bool, optional + False by default. If True it will disable the plotting """ for key in newParameters: Parameters[key]=newParameters[key] Type=Parameters['Type'] CurveFit=Parameters['CurveFit'] specificConductance=Parameters['specificConductance'] for filename in filenames: os.chdir(os.path.dirname(filename)) print(filename) # Make Dir to save images output = LoadData.OpenFile(filename, verbose=verbose) if Parameters['reversePolarity']: print('Polarity Reversed!!!!') output['i1']= -output['i1'] output['v1']= -output['v1'] directory = (str(os.path.split(filename)[0]) + os.sep + expname + '_SavedImages') if not os.path.exists(directory): os.makedirs(directory) AllData = uf.MakeIVData(output, approach=Parameters['fittingMethod'], delay = Parameters['delay'])#, UseVoltageRange = [-0.4, 0.4]) if AllData == 0: print('!!!! No Sweep in: ' + filename) continue current = 'i1' Slope = AllData[current][CurveFit]['Slope'] Yintercept = AllData[current][CurveFit]['Yintercept'] # Plot IV if output['graphene'] and not noPlot: figIV2 = plt.figure(3, figsize=(10, 10)) figIV2.clear() ax2IV = figIV2.add_subplot(111) ax2IV = uf.PlotIV(output, AllData, current='i2', unit=1e9, axis=ax2IV, WithFit=1) figIV2.tight_layout() figIV2.savefig(directory + os.sep + str(os.path.split(filename)[1]) + '_IV_i2.png', dpi=300) figIV2.savefig(directory + os.sep + str(os.path.split(filename)[1]) + '_IV_i2.eps') if Type == 'Nanopore': poreLength = Parameters['poreLength'] poresize = uf.CalculatePoreSize(np.abs(Slope), poreLength, specificConductance) textstr = 'Nanopore Size\n\nSpecific Conductance: {}\nLength: {}\n\nConductance: {}\nDiameter: {}' \ .format(SpesCond.format_data(specificConductance), size.format_data(poreLength), Cond.format_data(Slope), size.format_data(poresize)) elif Type == 'Nanocapillary': taperLength = Parameters['taperLength'] innerDiameter = Parameters['innerDiameter'] poresize = uf.CalculateCapillarySize(np.abs(Slope), innerDiameter, taperLength, specificConductance) textstr = 'Nanocapillary Size\n\nSpecific Conductance: {}\nTaper lenghth {}:\nInner diameter: {}:\n\nConductance: {}\nDiameter: {}'. \ format(SpesCond.format_data(specificConductance), size.format_data(taperLength), size.format_data(innerDiameter), Cond.format_data(Slope), size.format_data(poresize)) elif Type == 'NanocapillaryShrunken': taperLength = Parameters['taperLength'] innerDiameter = Parameters['innerDiameter'] taperLengthShaft = Parameters['taperLengthShaft'] innerDiameterShaft = Parameters['innerDiameterShaft'] poresize = uf.CalculateShrunkenCapillarySize(np.abs(Slope), innerDiameter, taperLength, specificConductance, taperLengthShaft, innerDiameterShaft) textstr = 'Shrunken Nanocapillary Size\n\nSpecific Conductance: {}\nTaper length: {}\nInner diameter: {}\nTaper length at shaft: {}' \ '\nInner Diameter at shaft: {}:\n\nConductance: {}\nDiameter: {}'. \ format(SpesCond.format_data(specificConductance), size.format_data(taperLength), size.format_data(innerDiameter), size.format_data(taperLengthShaft), size.format_data(innerDiameterShaft), Cond.format_data(Slope), size.format_data(poresize)) if not noPlot: figIV = plt.figure(2, figsize=(10, 7)) ax1IV = figIV.add_subplot(111) ax1IV.text(0.05, 0.95, textstr, transform=ax1IV.transAxes, fontsize=12, verticalalignment='top', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)) ind = np.argsort(AllData[current]['Voltage']) ax1IV.errorbar(AllData[current]['Voltage'][ind], AllData[current]['Mean'][ind], yerr=AllData[current]['SE'][ind], fmt='o', color='b') ax1IV.plot(AllData[current]['Voltage'][ind], np.polyval([Slope,Yintercept], AllData[current]['Voltage'][ind]), color='r') ax1IV.set_title(str(os.path.split(filename)[1])+ '\nR=' + Res.format_data(1/Slope) + ', G=' + Cond.format_data(Slope)) ax1IV.set_ylabel('Current') ax1IV.set_xlabel('Voltage') ax1IV.xaxis.set_major_formatter(EngFormatter(unit='V')) ax1IV.yaxis.set_major_formatter(EngFormatter(unit='A')) figIV.savefig(directory + os.sep + str(os.path.split(filename)[1]) + Type+'IV_i1.pdf', transparent=True) x=AllData[current]['Voltage'][ind] y=AllData[current]['Mean'][ind] csvfile=directory + os.sep + str(os.path.split(filename)[1]) + Type+'IV_i1.csv' with open(csvfile, 'w') as output: writer=csv.writer(output, lineterminator='\n') for i in range(len(x)): writer.writerow([x[i] , y[i]]) plt.show() figIV.clear() return poresize else: return poresize if __name__=='__main__': parser = argparse.ArgumentParser() parser.add_argument('-i', '--input', help='input file') parser.add_argument('-c', '--coeff', help='Coefficients for calculating pore size', nargs='+') parser args = parser.parse_args() inputData=args.input if inputData==None: inputData=askopenfilenames() else: inputData={inputData} newParameters={} if args.coeff is not None: if len(args.coeff) % 2 == 0: for i in range(0, len(args.coeff), 2): if i <= len(args.coeff): newParameters[str(args.coeff[i])]=args.coeff[i+1] run(inputData,newParameters) if (platform.system()=='Darwin'): Tk().withdraw() root=Tk() root.update() \ No newline at end of file diff --git a/NanoporeClasses.py b/NanoporeClasses.py index ffc332c..81da148 100644 --- a/NanoporeClasses.py +++ b/NanoporeClasses.py @@ -1,207 +1,208 @@ import numpy as np from pprint import pprint import shelve import os import math import matplotlib.pyplot as plt from matplotlib.ticker import EngFormatter Amp = EngFormatter(unit='A', places=2) Time = EngFormatter(unit='s', places=2) Volt = EngFormatter(unit='V', places=2) Cond = EngFormatter(unit='S', places=2) class TranslocationEvent: """ Class used to represent an event. Attributes ---------- filename : str directory of the source file from which the event was extracted type: str classification of the event 'CUSUM' (CUSUM-fitted), 'Rough' (non-CUSUM fitted), 'Impulse' (short events) evenTrace: lst(int) list of data points within the event baseline:float mean current value in the basline around the event samplerate:float sampling frequency beginEvent: int start coordinate of the event in the signal endEvent: int end coordinate of the event in the signal meanTrace: float mean current in the event minTrace: float minimum current in the event eventLength: float lenght of the event in seconds currentDrop: float current drop defined as the difference of the baseline and meanTrace or minTrace dependeng on the event type before: lst(float) list of data points before the event, used for plotting after: lst(float) list of data points after the event, used for plotting changeTimes: lst(int) list of coordinates where the current level changes in the event, only for multilevel events kd: **** segmentedSignal: lst(float) CUSUM fit beginEventCUSUM: more precise start coordinate of the event detected with the CUSUM algorithm currentDropCUSUM: more precise start coordinate of the event detected with the CUSUM algorithm coefficients: coefficients used in the CUSUM algorithm voltage: voltage applied across the nanopore during the data recording """ def __init__(self, filename, type='roughEvent'): self.filename = filename self.type = type def SetEvent(self, eventTrace, beginEvent, baseline, samplerate, currentDrop=None): self.eventTrace = eventTrace self.baseline = baseline self.samplerate = samplerate self.beginEvent = beginEvent self.endEvent = beginEvent+len(eventTrace) self.meanTrace = np.mean(eventTrace) self.minTrace = np.min(eventTrace) self.eventLength = len(eventTrace)/samplerate if currentDrop is None: self.currentDrop = baseline - self.meanTrace else: self.currentDrop = currentDrop def SetCoefficients(self,coefficients,voltage): self.coefficients = coefficients self.voltage = voltage def SetBaselineTrace(self, before,after): self.before = before self.after = after self.baseline=np.mean(np.append(before,after)) def SetCUSUMVariables(self, segmentedSignal, kd, changeTimes): self.changeTimes = changeTimes self.kd = kd self.segmentedSignal = segmentedSignal self.changeTimes = changeTimes if len(changeTimes) >= 1: self.beginEventCUSUM = changeTimes[0] self.currentDropCUSUM = max(segmentedSignal)-min(segmentedSignal) if len(changeTimes) >= 2: self.endEventCUSUM = changeTimes[-1] self.eventLengthCUSUM = (changeTimes[-1]-changeTimes[0])/self.samplerate if hasattr(self,'before') and hasattr(self,'after') and hasattr(self,'eventTrace'): self.mcbefore=np.mean(self.before)*np.ones(len(self.before)) self.mcafter = np.mean(self.after) * np.ones(len(self.after)) self.mctrace=np.array([]) for ii in range(1,len(changeTimes)): self.mctrace=np.append(self.mctrace,np.mean(self.eventTrace[changeTimes[ii-1]-changeTimes[0]:changeTimes[ii]-changeTimes[0]])*np.ones(changeTimes[ii]-changeTimes[ii-1])) class AllEvents: """" Class used to represent all the events in a nanopore experiment output as a list of events. Attributes ---------- events : lst(events) list of events containing all the events detected by the eventdetection function """ def __init__(self): self.events=[] def AddEvent(self, translocationEvent): if isinstance(translocationEvent,AllEvents): if len(self.events) == 0: self.events = translocationEvent.events else: self.events.extend(translocationEvent.events) elif isinstance(translocationEvent,list): self.events.extend(translocationEvent) else: self.events.append(translocationEvent) def GetAllLengths(self): Lengths=[event.lengthEvents for event in self.events] return Lengths def GetAllIdrops(self): currentDrops=[event.currentDrop for event in self.events] return currentDrops def GetAllIdropsNorm(self): currentDrops = [event.currentDrop/ event.baseline for event in self.events] return currentDrops def GetEventTypes(self,eventType): events = [] - events = [event for event in self.events if event.type == eventType] + events = [event for event in self.events if str.upper(event.type) == str.upper(eventType)] return events def GetEventsforVoltages(self,voltage): events = [event for event in self.events if event.voltage == voltage] return events def GetAllVoltages(self): voltages = [event.voltage for event in self.events] + voltages.sort() voltages = set(voltages) return voltages def SetFolder(self,loadname): self.savefile=loadname def GetEventsMinCondition(self,minCurrent=-math.inf,maxCurrent=math.inf,minLength=0,maxLength=math.inf): minCurrent = -math.inf if not minCurrent else minCurrent maxCurrent = math.inf if not maxCurrent else maxCurrent minLength = 0 if not minLength else minLength maxLength = math.inf if not maxLength else maxLength newEvents=AllEvents() for event in self.events: if minCurrent 0] + tau = [event.eventLength for event in filteredevents if event.voltage > 0] + return tau, yVals + +def PlotGTau(eventClass, xLim = None, yLim = None, showCurrent = False, normalized = False, showLog=False): + """ + Function used to produce scatter plots and histograms of the events. + The figure produced has 3 subplots: + Up: Histogram with the number of events per event length + Right: Histogram with the number of events per conductance drop [nS] + Center: Scatter-plot of all the events wiht in on x-coordinates the event length [s] + and in y-coordinates the conductance drop [nS]. + + In the plots, the events were distributed into the 3 types of events: + CUSUM-fitted in red ('Real' type) + Impulse in blue ('Impulse' type) + Non-fitted in green ('Rough' type) + + Parameters + ---------- + eventClass : AllEvents object + All the events to be plotted. + xLim : 2D list, optional + limits on x axis + yLim : 2D list, optional + limits on y axis + showCurrent : bool, optional + False by default. If True, it will change the SI unit in the y-axis from siemens [S] to ampers [A]. + So instead of units in conductance drop, it will have current drop. + normalized : bool, optional + False by default. If True, it will change in the y-axis the unit from siemens [S] to normalized current drop without unit. + showLog : bool, optional + False by default. If True, it will change the axis to logarithmic + + """ + # categorize events in three types + # CUSUM fitted events + CUSUMEvents = eventClass.GetEventTypes('CUSUM') + + if len(CUSUMEvents) == 0: + CUSUMEvents = eventClass.GetEventTypes('Real') + + # Non-fitted events + nonFittedEvents = eventClass.GetEventTypes('Rough') + + # Impulse events + impulseEvents = eventClass.GetEventTypes('Impulse') + + # select min max + events = [event for event in eventClass.events if showCurrent or event.voltage is not 0] + allTau, allYVals = extractytau(events, showCurrent, normalized) + + maxy = 1 if normalized else 1.1 * np.percentile(allYVals, 99) + minx = 0.9 * np.percentile(allTau, 1) if showLog else 0 + + brx = (minx, 1.1 * np.percentile(allTau, 99)) if xLim is None else tuple(xLim) + bry = (0, maxy) if yLim is None else tuple(yLim) + + br = dict() + br['x'] = brx + br['y'] = bry + + xlabel = 'Dwell time (s)' + ylabel = 'Current drop (A)' + + tau1, yVals1 = extractytau(impulseEvents, showCurrent, normalized) + points = hv.Points((tau1, yVals1), label='impulse') + + tau2, yVals2 = extractytau(CUSUMEvents, showCurrent, normalized) + points2 = hv.Points((tau2, yVals2), label='CUSUM') + + tau3, yVals3 = extractytau(nonFittedEvents, showCurrent, normalized) + points3 = hv.Points((tau3, yVals3), label='non-fitted') + + xhist, yhist = (histogram(points3, bin_range=br[dim], dimension=dim) * + histogram(points2, bin_range=br[dim], dimension=dim) * + histogram(points, bin_range=br[dim], dimension=dim) + for dim in 'xy') + + # xhist, yhist = (histogram(points3*points2*points, bin_range=br[dim], dimension=dim) + # for dim in 'xy') + + return ((points3 * points2 * points).opts(logx=showLog, xlabel=xlabel, ylabel=ylabel, width=500, height=500, xlim=brx, + ylim=bry) << yhist.opts( + width=200) << xhist.opts(height=150, logx=showLog)).opts( + opts.Histogram(xlabel='', ylabel='', alpha=0.3, show_legend=False)) + + +def PlotG_tau(translocationEvents, fig=None, savefile=None, showCurrent=False, normalized=False, showCUSUM=True, showLog=False): """ Function used to produce scatter plots and histograms of the events. The figure produced has 3 subplots: Up: Histogram with the number of events per event length Right: Histogram with the number of events per conductance drop [nS] Center: Scatter-plot of all the events wiht in on x-coordinates the event length [s] and in y-coordinates the conductance drop [nS]. In the plots, the events were distributed into the 3 types of events: CUSUM-fitted in red ('Real' type) Non-fitted in blue ('Rough' type) Impulse in green ('Impulse' type) Parameters ---------- events : AllEvents object All the events to be plotted. savefile : str, optional Full path to file where the plots will be saved. - showCurrentInstead : bool, optional + showCurrent : bool, optional False by default. If True, it will change the SI unit in the y-axis from siemens [S] to ampers [A]. So instead of units in conductance drop, it will have current drop. normalized : bool, optional False by default. If True, it will change in the y-axis the unit from siemens [S] to normalized current drop without unit. showCUSUM : bool, optional True by default. """ - #Clean up events - events = [event for event in events if event.voltage is not 0] + # categorize events in three types + # CUSUM fitted events + CUSUMEvents = translocationEvents.GetEventTypes('CUSUM') - #categorize events in three types - #CUSUM fitted events - if any(hasattr(event, 'changeTimes') and len(event.changeTimes)>2 for event in events): - CUSUMIndices, CUSUMEdevents = zip(*[(ind, event) for ind, event in enumerate(events) if - hasattr(event, 'changeTimes') and len(event.changeTimes) > 2]) - else: - CUSUMIndices = CUSUMEdevents = [] + if len(CUSUMEvents) == 0: + CUSUMEvents = translocationEvents.GetEventTypes('Real') - #Non-fitted events - if any(event.type =='Rough' for event in events): - nonFittedIndices, nonFittedEvents = zip(*[(ind, event) for ind, event in enumerate(events) if - event.type == 'Rough']) - else: - nonFittedIndices = nonFittedEvents = [] + # Non-fitted events + nonFittedEvents = translocationEvents.GetEventTypes('Rough') - #Impulse events - if any(event.type == 'Impulse' for event in events): - impulseIndices, impulseEvents = zip(*[(ind, event) for ind, event in enumerate(events) if - event.type == 'Impulse']) - else: - impulseIndices = impulseEvents = [] + # Impulse events + impulseEvents = translocationEvents.GetEventTypes('Impulse') - catEvents=(CUSUMEdevents, nonFittedEvents, impulseEvents) - catIndices=(CUSUMIndices,nonFittedIndices,impulseIndices) + catEvents = (CUSUMEvents, nonFittedEvents, impulseEvents) - #Extract y and tau out of events - def extractytau(filteredevents): - if showCurrentInstead: - yVals = [event.currentDrop for event in filteredevents] - tau = [event.eventLength for event in filteredevents] - else: - yVals = [event.currentDrop / event.voltage for event in filteredevents if event.voltage > 0] - tau = [event.eventLength for event in filteredevents if event.voltage > 0] - return tau,yVals - - #Save figure - def SavePlot(event): + # Save figure + def SaveG(event): # Check if directory exists directory = os.path.dirname(savefile) - if showCurrentInstead: + if showCurrent: fig.savefig(directory + os.sep + 'PlotITau.pdf', transparent=True) else: fig.savefig(directory + os.sep + 'PlotGTau.pdf', transparent=True) + def ShowLog(event): + name = axScatter.xaxis._scale.name + bshowlog.label.set_text('Show ' + name) + axScatter.set_xscale('linear') if name == 'log' else axScatter.set_xscale('log') + # definitions for the axes left, width = 0.15, 0.55 bottom, height = 0.1, 0.6 left_h = left + width + 0.015 bottom_h = bottom + height + 0.015 rect_scatter = [left, bottom, width, height] rect_histx = [left, bottom_h, width, 0.2] rect_histy = [left_h, bottom, 0.2, height] # start with a rectangular Figure - fig = plt.figure(1, figsize=(10, 8)) + if fig is None: + fig = plt.figure(1, figsize=(10, 8)) - #define axes and link histogram to scatterplot - axScatter = plt.axes(rect_scatter) + # define axes and link histogram to scatterplot + axScatter = fig.add_axes(rect_scatter) - axHistx = plt.axes(rect_histx, sharex=axScatter) - axHisty = plt.axes(rect_histy, sharey=axScatter) + axHistx = fig.add_axes(rect_histx, sharex=axScatter) + axHisty = fig.add_axes(rect_histy, sharey=axScatter) - #Checkboxes to turn on or off events + # Checkboxes to turn on or off events rax = plt.axes([0.75, 0.73, 0.14, 0.15]) - visBool=[True,True,True] - labelsCheckBox=('CUSUM-fitted', 'Not fitted', 'impulse') - check = CheckButtons(rax,labelsCheckBox , visBool) + visBool = [True, False, True] + labelsCheckBox = ('CUSUM-fitted', 'Not fitted', 'impulse') + check = CheckButtons(rax, labelsCheckBox, visBool) # Link button to axes to preserve function rax._check = check - bax = plt.axes([0.77, 0.9, 0.1, 0.03]) bnext = Button(bax, 'Save figure') bnext.on_clicked(SavePlot) # Link button to axes to preserve function bax._bnext = bnext + baxl = plt.axes([0.77, 0.95, 0.1, 0.03]) + txt = 'log' if showLog else 'linear' + bshowlog = Button(baxl, 'Show ' + txt) + bshowlog.on_clicked(ShowLog) + # Link button to axes to preserve function + bax._bshowlog = bshowlog - #Show labels + # Show labels def setlabels(): plt.setp(axHistx.get_xticklabels(), visible=False) plt.setp(axHisty.get_yticklabels(), visible=False) axScatter.set_xlabel('Event length (s)') axScatter.xaxis.set_major_formatter(Time) if normalized: axScatter.set_ylabel('current drop (normalized)') else: - if showCurrentInstead: + if showCurrent: axScatter.set_ylabel('current drop (A)') axScatter.yaxis.set_major_formatter(Amp) else: axScatter.set_ylabel('Conductance drop (G)') axScatter.yaxis.set_major_formatter(Cond) + # Set limits + allEvents = [event for event in translocationEvents.events if showCurrent or event.voltage is not 0] + bins = 100 + extra = 0.1 # 0.1 = 10% + allTau, allYVals = extractytau(allEvents, showCurrent) + yClean = [y for y in allYVals if str(y) != 'nan'] + taurangeHist = np.linspace(min(allTau), max(allTau), num=bins) + yValsrangeHist = np.linspace(min(yClean), max(yClean), num=bins) - # Determine nice limits by hand: - def limits(tau, yVals, axScatter): - extra = 0.1 # 0.1 = 10% - tauRange = np.max(tau) - np.min(tau) - yClean = [y for y in yVals if str(y) != 'nan'] - yRange = np.max(yClean) - np.min(yClean) - - axScatter.set_xlim((np.min(tau) - extra * tauRange, np.max(tau) + extra * tauRange)) - axScatter.set_ylim((np.min(yClean) - extra * yRange, np.max(yClean) + extra * yRange)) - - - #define colors of the 3 classes - colors=['tomato', 'lightgreen','skyblue'] - linecolors=['red','green', 'blue'] + # define colors of the 3 classes + colors = ['tomato', 'lightgreen', 'skyblue'] + linecolors = ['red', 'green', 'blue'] # the scatter plot: - scatters=[None]*3 + scatters = [None]*3 + def PlotEvents(visBool): - #clear axes + # clear axes axScatter.clear() axHistx.clear() axHisty.clear() + nrEvents = 0 - #lists for setting the limits - alltau=[] - allyVals=[] for i in range(len(catEvents)): - #If checkbox is True, plot events + # If checkbox is True, plot events if visBool[i]: - #Extract Tau and Y - events=catEvents[i] - tau,yVals = extractytau(events) + # Extract Tau and Y + events = catEvents[i] + tau, yVals = extractytau(events, showCurrent) scatters[i] = axScatter.scatter(tau, yVals, color=colors[i], marker='o', s=30, - linewidths=0.1,edgecolors=linecolors[i], picker=5,visible=visBool[i]) # added some stuff here to improve aesthetics - axScatter.set_xscale('log') - axHistx.hist(tau, bins=100, color=colors[i], visible=visBool[i]) - axHisty.hist(yVals, bins=50, orientation='horizontal', color=colors[i], visible=visBool[i]) + linewidths=0.1, edgecolors=linecolors[i], picker=5, visible=visBool[i]) - alltau.extend(tau) - allyVals.extend(yVals) + axHistx.hist(tau, bins=taurangeHist, color=colors[i], visible=visBool[i]) + axHisty.hist(yVals, bins=yValsrangeHist, orientation='horizontal', color=colors[i], visible=visBool[i]) + nrEvents += len(events) + + if showLog: + axScatter.set_xscale('log') + + # set limits + if len(allTau) > 0: + tauRange = np.max(allTau) - np.min(allTau) + yRange = np.max(yClean) - np.min(yClean) + axScatter.set_xlim((np.max([np.min(allTau) - extra * tauRange, 1e-6]), np.max(allTau) + extra * tauRange)) + axScatter.set_ylim((np.min(yClean) - extra * yRange, np.max(yClean) + extra * yRange)) - #set limits - if len(alltau)>0: - limits(alltau,allyVals,axScatter) setlabels() - plt.title('{} number of events'.format(len(alltau))) + fig.suptitle('{} number of events'.format(nrEvents)) PlotEvents(visBool) # If click on checkbox, switch Boolean and replot events def func(label): for i in range(len(labelsCheckBox)): if label == labelsCheckBox[i]: visBool[i] = not visBool[i] PlotEvents(visBool) plt.draw() - - # WHen clicking on event + # When clicking on event def onpick(event): for i in range(len(catEvents)): if event.artist == scatters[i]: N = len(event.ind) if not N: return True figi = plt.figure(figsize=(10, 6)) for subplotnum, dataind in enumerate(event.ind): ax = figi.add_subplot(N, 1, subplotnum + 1) PlotEvent(catEvents[i][dataind], ax, savefile, showCUSUM) figi.show() return True fig.canvas.mpl_connect('pick_event', onpick) check.on_clicked(func) - plt.show() + if not hasattr(fig, '_AnalysisUI__attached'): + plt.show() -def PlotGTau(eventClass, xLim = None, yLim = None, showCurrentInstead = False): - #categorize events in three types - #CUSUM fitted events - CUSUMEvents = eventClass.GetEventTypes('CUSUM') - - #Non-fitted events - nonFittedEvents = eventClass.GetEventTypes('Rough') +def PlotGTauVoltage (eventClass, xLim=None, yLim=None, showCurrent=False): + bokeh.io.output_notebook(INLINE) - #Impulse events - impulseEvents = eventClass.GetEventTypes('Impulse') - - #Extract y and tau out of events - def extractytau(filteredevents): - if showCurrentInstead: - yVals = [event.currentDrop for event in filteredevents] - tau = [event.eventLength for event in filteredevents] - else: - yVals = [event.currentDrop / event.voltage for event in filteredevents if event.voltage > 0] - tau = [event.eventLength for event in filteredevents if event.voltage > 0] - return tau,yVals + #sort the voltages + # categorize events in three types + # CUSUM fitted events + voltageLimits = [0.01, 0.91] + voltagesList = eventClass.GetAllVoltages() #define of variables - TOOLS="box_zoom,pan,wheel_zoom,reset" - colors=['tomato', 'lightgreen','skyblue'] - linecolors=['red','green', 'blue'] + TOOLS = "box_zoom,pan,wheel_zoom,reset" + colors = ['tomato', 'lightgreen','skyblue', 'magenta','black'] + linecolors = ['red', 'green', 'blue', 'magenta', 'black'] + + assert(len(voltagesList)<=len(colors)) backgroundColor = "#fafafa" bins = 50 output_notebook() p = figure(plot_width=500, plot_height=500, min_border=10, min_border_left=50, tools=TOOLS, x_axis_location=None, y_axis_location=None,title="Linked Histograms") p.background_fill_color = "#fafafa" #select min max - events = [event for event in eventClass.events if showCurrentInstead or event.voltage is not 0] + events = [event for event in eventClass.events if showCurrent or event.voltage is not 0] - allTau, allYVals = extractytau(events) + allTau, allYVals = extractytau(events, showCurrent) #Set limits if xLim is None: taurange = np.linspace(min(allTau), max(allTau), num=bins) else: assert(len(xLim) is 2) p.x_range=Range1d(xLim[0], xLim[1]) taurange = np.linspace(xLim[0], xLim[1], num=bins) if yLim is None: yValsrange = np.linspace(min(allYVals), max(allYVals), num=bins) else: assert(len(yLim) is 2) p.y_range=Range1d(yLim[0], yLim[1]) yValsrange = np.linspace(yLim[0], yLim[1], num=bins) #initialize values zerostau = np.zeros(len(taurange) - 1) previoustau = np.zeros(len(taurange) - 1) zerosy = np.zeros(len(yValsrange)-1) previousy = np.zeros(len(yValsrange) - 1) #Define histogram bottom ph = figure(plot_width=p.plot_width, plot_height=100, x_range=p.x_range, y_range=(0, 1), min_border=10, min_border_left=50, y_axis_location="right") ph.background_fill_color = backgroundColor ph.xgrid.grid_line_color = None ph.yaxis.major_label_orientation = np.pi / 4 #Define the second histogram pv = figure(plot_width=300, plot_height=p.plot_height, x_range=(0, 1), y_range=p.y_range, min_border=10, y_axis_location="right") pv.ygrid.grid_line_color = None pv.xaxis.major_label_orientation = np.pi / 4 pv.background_fill_color = backgroundColor legend_it = [] - for selectEvents, name, color, linecolor in zip([CUSUMEvents, nonFittedEvents, impulseEvents], ["CUSUM", "Non-fitted", "Impulse"], colors, linecolors): - tau, yVals = extractytau(selectEvents) + i = 0 + #for i in range(len(voltagesList)): + for voltage in voltagesList: + color = colors[i] + linecolor = linecolors[i] + i+=1 + #for voltage, color, linecolor in zip(voltagesList, colors, linecolors): + selectEvents = eventClass.GetEventsforVoltages(voltage) + tau, yVals = extractytau(selectEvents, showCurrent) c = p.scatter(tau, yVals, size=3, line_width=2, color=color, alpha=0.8) hhist, hedges = np.histogram(tau, bins=taurange) hh = ph.quad(bottom=zerostau +previoustau, left=hedges[:-1], right=hedges[1:], top=hhist+previoustau, fill_color = color,line_color = linecolor) previoustau +=hhist vhist, vedges = np.histogram(yVals, bins=yValsrange) vh = pv.quad(left=zerosy + previousy, bottom=vedges[:-1], top=vedges[1:], right=vhist+previousy, color=color, line_color= linecolor) previousy +=vhist #Sharing legend is broke #legend_it.append((name, [c, hh, vh])) + name = str(Volt.format_data(voltage)) legend_it.append(LegendItem(label=name, renderers=[vh])) #p.legend.location = "top_right" #legend = Legend(items=legend_it, location=(0, -30)) legend = Legend(items=legend_it) pv.add_layout(legend, 'right') #pv.legend.click_policy = "hide" ph.y_range.end = max(previoustau)*1.1 pv.x_range.end = max(previousy) * 1.1 fig = gridplot([[p, pv],[ph, None]], toolbar_location="left") # show the results show(fig) - - # for selectEvents, name, color in zip([CUSUMEvents, nonFittedEvents, impulseEvents], ["CUSUM", "Non-fitted", "Impulse"], colors): - # tau, yVals = extractytau(selectEvents) - # - # c = p.scatter(tau, yVals, line_width=2, color=color, alpha=0.8) - # - # # create the horizontal histogram - # hhist, hedges = np.histogram(tau, bins=20) - # hzeros = np.zeros(len(hedges) - 1) - # hmax = max(hhist) * 1.1 - # - # ph.quad(bottom=0, left=hedges[:-1], right=hedges[1:], top=hhist, color="white", line_color="#3A5785") - # hh1 = ph.quad(bottom=0, left=hedges[:-1], right=hedges[1:], top=hzeros, alpha=0.5,color=color, line_color=None) - # hh2 = ph.quad(bottom=0, left=hedges[:-1], right=hedges[1:], top=hzeros, alpha=0.1, color=color, line_color=None) - # - # legend_it.append((name, [c])) - - #p.legend.location = "top_right" - # legend = Legend(items=legend_it, location=(0, -30)) - # p.add_layout(legend, 'right') - # - # p.legend.click_policy = "hide" - # - # def update(attr, old, new): - # inds = new - # if len(inds) == 0 or len(inds) == len(x): - # hhist1, hhist2 = hzeros, hzeros - # #vhist1, vhist2 = vzeros, vzeros - # else: - # neg_inds = np.ones_like(x, dtype=np.bool) - # neg_inds[inds] = False - # hhist1, _ = np.histogram(x[inds], bins=hedges) - # #vhist1, _ = np.histogram(y[inds], bins=vedges) - # hhist2, _ = np.histogram(x[neg_inds], bins=hedges) - # #vhist2, _ = np.histogram(y[neg_inds], bins=vedges) - # - # hh1.data_source.data["top"] = hhist1 - # hh2.data_source.data["top"] = -hhist2 - # #vh1.data_source.data["right"] = vhist1 - # #vh2.data_source.data["right"] = -vhist2 - # - # c.data_source.selected.on_change('indices', update) - # show(p) - - -def PlotEvent(event,ax=None, savefile=os.getcwd(), showCUSUM=True, showCurrent=False): +def PlotEvent(event, ax=None, savefile=os.getcwd(), showCUSUM=True, showCurrent=False, showButtons = True, axisFormatter = True, plotTitleBool = True): """ Function used to plot a single event passed in argument. The event will be represented in a blue trace and the baseline in a red trace. Parameters ---------- event : TranslocationEvent object Event to be plotted. ax :axes.Axes object or array of Axes object Axes of the plot. savefile : str, optional By default, the current working directory. Full path to the directory to save the plot. showCUSUM : bool, optional False by default. If True, it will plot the CUSUM-fit overlayed in yellow. """ #Link event to axes to keep it around if ax is None: #plt.figure(figsize=(10, 6)) fig, ax = plt.subplots(figsize=(10, 6)) - ax._event=event + ax._event = event def SavePlot(eventMouse): # Check if directory exists directory = os.path.dirname(savefile) savename=directory + os.sep + 'event.pdf' - i=1 + i = 1 while os.path.exists(directory + os.sep + 'event_{}.pdf'.format(i)): i += 1 savename = directory + os.sep + 'event_{}.pdf'.format(i) eventMouse.inaxes.figure.savefig(savename, transparent=True) def ShowFullTrace(eventMouse): event=eventMouse.inaxes.figure.axes[0]._event ShowEventInTrace(event) - if showCUSUM and hasattr(event, 'changeTimes') and len(event.changeTimes)>2: + if showCUSUM and hasattr(event, 'changeTimes') and len(event.changeTimes)>=2: eventLength = event.eventLengthCUSUM currentDrop = event.currentDropCUSUM else: showCUSUM=False eventLength = event.eventLength currentDrop = event.currentDrop fn=filename_w_ext = os.path.basename(event.filename) if showCurrent: plotTitle = fn + '\n' + 'Event length: {}\nCurrent drop: {} with voltage {}'.format( Time.format_data(eventLength), Cond.format_data(currentDrop), Volt.format_data(event.voltage)) else: plotTitle = fn + '\n' + 'Event length: {}\nConductance drop: {} with voltage {}'.\ format(Time.format_data(eventLength), Cond.format_data(currentDrop/event.voltage),Volt.format_data(event.voltage)) ax.set_xlabel('time (s)') ax.set_ylabel('current (A)') - ax.xaxis.set_major_formatter(Time) - ax.yaxis.set_major_formatter(Amp) + if axisFormatter: + ax.xaxis.set_major_formatter(Time) + ax.yaxis.set_major_formatter(Amp) - if plotTitle: + if plotTitleBool: plt.title(plotTitle) + if showButtons: + # Add buttons - #Add buttons - - #Save button - bax = plt.axes([0.77, 0.95, 0.15, 0.03]) - bsave = Button(bax, 'Save figure') - bsave.on_clicked(SavePlot) - #Link button to axes to preserve function - ax._bsave = bsave + # Save button + bax = plt.axes([0.77, 0.95, 0.15, 0.03]) + bsave = Button(bax, 'Save figure') + bsave.on_clicked(SavePlot) + # Link button to axes to preserve function + ax._bsave = bsave - #Show original trace button - bax2 = plt.axes([0.77, 0.9, 0.15, 0.03]) - bfull = Button(bax2, 'Show original Trace') - # Link button to axes to preserve function - ax._bfull = bfull - bfull.on_clicked(ShowFullTrace) + # Show original trace button + bax2 = plt.axes([0.77, 0.9, 0.15, 0.03]) + bfull = Button(bax2, 'Show original Trace') + # Link button to axes to preserve function + ax._bfull = bfull + bfull.on_clicked(ShowFullTrace) #Plotting timeVals1 = np.linspace(0, len(event.before) / event.samplerate, num=len(event.before)) timeVals2 = np.linspace(0 + max(timeVals1), len(event.eventTrace) / event.samplerate + max(timeVals1), num=len(event.eventTrace)) timeVals3 = np.linspace(0 + max(timeVals2), len(event.after) / event.samplerate + max(timeVals2), num=len(event.after)) ax.plot(np.append(timeVals1,timeVals2[0]), np.append(event.before,event.eventTrace[0]), color='tomato') ax.plot(timeVals2, event.eventTrace, color='mediumslateblue') ax.plot(np.append(timeVals2[-1],timeVals3), np.append(event.eventTrace[-1],event.after), color='tomato') if showCUSUM: timeVals = np.linspace(0, len(event.segmentedSignal) / event.samplerate, num=len(event.segmentedSignal)) if hasattr(event,'mcbefore') and hasattr(event,'mcafter') and hasattr(event,'mctrace'): ax.plot(timeVals1, event.mcbefore,'--', color='tomato') x=np.append(np.append(timeVals1[-1],timeVals2),timeVals3[0]) y=np.append(np.append(event.mcbefore[-1],event.mctrace),event.mcafter[0]) ax.plot(x, y, color='yellow') ax.plot(timeVals3, event.mcafter,'--', color='tomato') else: ax.plot(timeVals,event.segmentedSignal, color='yellow') #,timeVals3[0],event.mcafter[0] else: beforeBaseline=np.full(len(event.before), event.baseline) ax.plot(timeVals1,beforeBaseline, '--', color='tomato') afterBaseline = np.full(len(event.after), event.baseline) ax.plot(timeVals3,afterBaseline, '--', color='tomato') meanTrace = np.full(len(event.eventTrace), event.baseline-event.currentDrop) ax.plot(timeVals2,meanTrace, '--', color='mediumslateblue') if 'fig' in locals(): plt.show() +def ShowEventInTrace_SignalPreloaded(FullTrace, AllData, eventnumber, ax, line = None, firstCall = True, dscorrection = None): + times = np.linspace(0, len(FullTrace) / AllData.events[eventnumber].samplerate, num=len(FullTrace)) + if line: + line.set_ydata(FullTrace) + line.set_xdata(times) + if firstCall: + ax.set_xlim(np.min(times), np.max(times)) + ax.set_ylim(np.min(FullTrace), np.max(FullTrace)) + print('updated lines') + else: + ax.plot(times, FullTrace, zorder=1) + print('Updated plot') + + ax.set_xlabel('time (s)') + ax.set_ylabel('current (A)') + + # Create a Rectangle patch + if dscorrection: + if hasattr(AllData.events[eventnumber], 'changeTimes') and len(AllData.events[eventnumber].changeTimes) > 2: + start_i = (AllData.events[eventnumber].beginEventCUSUM - len(AllData.events[eventnumber].before)) / AllData.events[eventnumber].samplerate * dscorrection + end_i = (AllData.events[eventnumber].endEventCUSUM + len(AllData.events[eventnumber].after)) / AllData.events[eventnumber].samplerate * dscorrection + else: + start_i = (AllData.events[eventnumber].beginEvent - len(AllData.events[eventnumber].before)) / AllData.events[eventnumber].samplerate * dscorrection + end_i = (AllData.events[eventnumber].endEvent + len(AllData.events[eventnumber].after)) / AllData.events[eventnumber].samplerate * dscorrection + else: + if hasattr(AllData.events[eventnumber], 'changeTimes') and len(AllData.events[eventnumber].changeTimes) > 2: + start_i = (AllData.events[eventnumber].beginEventCUSUM - len(AllData.events[eventnumber].before)) / \ + AllData.events[eventnumber].samplerate + end_i = (AllData.events[eventnumber].endEventCUSUM + len(AllData.events[eventnumber].after)) / \ + AllData.events[eventnumber].samplerate + else: + start_i = (AllData.events[eventnumber].beginEvent - len(AllData.events[eventnumber].before)) / \ + AllData.events[eventnumber].samplerate + end_i = (AllData.events[eventnumber].endEvent + len(AllData.events[eventnumber].after)) / AllData.events[ + eventnumber].samplerate + + minE = np.min(np.append(np.append(AllData.events[eventnumber].eventTrace, AllData.events[eventnumber].before), AllData.events[eventnumber].after)) + maxE = np.max(np.append(np.append(AllData.events[eventnumber].eventTrace, AllData.events[eventnumber].before), AllData.events[eventnumber].after)) + rect = patches.Rectangle((start_i, minE - 0.1 * (maxE - minE)), end_i - start_i, maxE + 0.2 * (maxE - minE) - minE, + linestyle='--', linewidth=1, edgecolor='r', facecolor='none', zorder=10) + # Add the patch to the Axes + print(ax.patches.clear()) + ax.add_patch(rect) + def ShowEventInTrace(event): """ Function used to show the event with it's location framed in red in the original full signal trace in blue. Parameters ---------- event : TranslocationEvent object Event to be plotted. """ - filename=event.filename - loadedData = LoadData.OpenFile(filename,1e3,True) #, ChimeraLowPass, True, CutTraces) + filename = event.filename + loadedData = LoadData.OpenFile(filename, 1e3, True) #, ChimeraLowPass, True, CutTraces) fig, ax = plt.subplots(figsize=(10, 6)) FullTrace = loadedData['i1'] - times=np.linspace(0, len(FullTrace) / event.samplerate, num=len(FullTrace)) - ax.plot(times,FullTrace,zorder=1) + times = np.linspace(0, len(FullTrace) / event.samplerate, num=len(FullTrace)) + ax.plot(times, FullTrace, zorder=1) ax.set_xlabel('time (s)') ax.set_ylabel('current (A)') ax.xaxis.set_major_formatter(Time) ax.yaxis.set_major_formatter(Amp) # Create a Rectangle patch if hasattr(event,'changeTimes') and len(event.changeTimes)>2: start_i = (event.beginEventCUSUM - len(event.before))/event.samplerate end_i = (event.endEventCUSUM + len(event.after))/event.samplerate else: start_i = (event.beginEvent - len(event.before))/event.samplerate end_i = (event.endEvent + len(event.after))/event.samplerate minE=np.min(np.append(np.append(event.eventTrace,event.before),event.after)) maxE=np.max(np.append(np.append(event.eventTrace,event.before),event.after)) rect = patches.Rectangle((start_i, minE-0.1*(maxE-minE)), end_i-start_i, maxE+0.2*(maxE-minE)-minE, linestyle='--', linewidth=1, edgecolor='r', facecolor='none',zorder=10) # Add the patch to the Axes ax.add_patch(rect) plt.title(os.path.basename(filename)) plt.show() def PlotCurrentTrace(currentTrace, samplerate): """ Function used in TranslocationEvent class methods in the NanoporeClasses module to plot events. It will plot the TranslocationEvent's currrentTrace passed in argument. Parameters ---------- currentTrace : list of float Data points in current to be plotted. samplerate : float Sampling frequency of the data acquisition. """ timeVals = np.linspace(0, len(currentTrace) / samplerate, num=len(currentTrace)) fig,ax=plt.subplots(figsize=(10, 6)) ax.plot(timeVals, currentTrace) ax.set_xlabel('time (s)') ax.set_ylabel('current (A)') ax.xaxis.set_major_formatter(Time) ax.yaxis.set_major_formatter(Amp) plt.show() def PlotCurrentTraceBaseline(before, currentTrace, after, samplerate, plotTitle=''): """ Function used in TranslocationEvent class methods in the NanoporeClasses module to plot events. It will plot TranslocationEvent's currentTrace and the surrounding baseline (after and before) passed in argument. Parameters ---------- before : list of float Data points in current of the baseline trace before the event. currentTrace : list of float Data points in current of the event trace. after : list of float Data points in current of the baseline trace after the event. samplerate : float Sampling frequency of the data aquisition. plotTitle : str, optional Plot title. """ timeVals1 = np.linspace(0, len(before) / samplerate, num=len(before)) timeVals2 = np.linspace(0 + max(timeVals1), len(currentTrace) / samplerate + max(timeVals1), num=len(currentTrace)) timeVals3 = np.linspace(0 + max(timeVals2), len(after) / samplerate + max(timeVals2), num=len(after)) #plt.figure(figsize=(10, 6)) fig,ax=plt.subplots(figsize=(10, 6)) ax.plot(timeVals1, before, color='red') ax.plot(timeVals2, currentTrace) ax.plot(timeVals3, after, color='red') ax.set_xlabel('time (s)') ax.set_ylabel('current (A)') ax.xaxis.set_major_formatter(Time) ax.yaxis.set_major_formatter(Amp) if plotTitle: plt.title(plotTitle) plt.show() if __name__=='__main__': + from tkinter import Tk + from tkinter.filedialog import askopenfilenames, askdirectory + + matplotlib.use('TkAgg') + + if (platform.system() == 'Darwin'): + root = Tk() + root.withdraw() + + if (platform.system() == 'Darwin'): + os.system('''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "python" to true' ''') + parser = argparse.ArgumentParser() parser.add_argument('-i', '--input', help='Input file') args = parser.parse_args() inputData = args.input if inputData == None: inputData = askopenfilenames(filetypes=[('data files', '*.dat')]) #for Mac systems, replace 'Data*.dat' with >> '*.dat' #if inputData: # inputData=os.path.splitext(inputData[0])[0] if (platform.system() == 'Darwin'): root.update() translocationEvents = NC.AllEvents() if inputData: for filename in inputData: shelfFile = shelve.open(os.path.splitext(filename)[0]) translocationEventstemp = shelfFile['TranslocationEvents'] shelfFile.close() translocationEvents.AddEvent(translocationEventstemp) - PlotG_tau(translocationEvents.events, inputData) - - -def PlotGTauVoltage (eventClass, xLim = None, yLim = None, showCurrentInstead = False): - - - #sort the voltages - # categorize events in three types - # CUSUM fitted events - voltageLimits = [0.01, 0.91] - voltagesList = eventClass.GetAllVoltages() - - #Extract y and tau out of events - def extractytau(filteredevents): - if showCurrentInstead: - yVals = [event.currentDrop for event in filteredevents] - tau = [event.eventLength for event in filteredevents] - else: - yVals = [event.currentDrop / event.voltage for event in filteredevents if event.voltage > 0] - tau = [event.eventLength for event in filteredevents if event.voltage > 0] - return tau,yVals - - #define of variables - TOOLS = "box_zoom,pan,wheel_zoom,reset" - colors = ['tomato', 'lightgreen','skyblue', 'magenta','black'] - linecolors = ['red', 'green', 'blue', 'magenta', 'black'] - - assert(len(voltagesList)<=len(colors)) - backgroundColor = "#fafafa" - bins = 50 - - output_notebook() - p = figure(plot_width=500, plot_height=500, min_border=10, min_border_left=50, tools=TOOLS, - x_axis_location=None, y_axis_location=None,title="Linked Histograms") - - p.background_fill_color = "#fafafa" - - #select min max - events = [event for event in eventClass.events if showCurrentInstead or event.voltage is not 0] + PlotG_tau(translocationEvents, savefile=inputData) - allTau, allYVals = extractytau(events) - #Set limits - if xLim is None: - taurange = np.linspace(min(allTau), max(allTau), num=bins) - else: - assert(len(xLim) is 2) - p.x_range=Range1d(xLim[0], xLim[1]) - taurange = np.linspace(xLim[0], xLim[1], num=bins) - - if yLim is None: - yValsrange = np.linspace(min(allYVals), max(allYVals), num=bins) - else: - assert(len(yLim) is 2) - p.y_range=Range1d(yLim[0], yLim[1]) - yValsrange = np.linspace(yLim[0], yLim[1], num=bins) - - #initialize values - zerostau = np.zeros(len(taurange) - 1) - previoustau = np.zeros(len(taurange) - 1) - zerosy = np.zeros(len(yValsrange)-1) - previousy = np.zeros(len(yValsrange) - 1) - - - #Define histogram bottom - ph = figure(plot_width=p.plot_width, plot_height=100, x_range=p.x_range, - y_range=(0, 1), min_border=10, min_border_left=50, y_axis_location="right") - - ph.background_fill_color = backgroundColor - ph.xgrid.grid_line_color = None - ph.yaxis.major_label_orientation = np.pi / 4 - - #Define the second histogram - pv = figure(plot_width=300, plot_height=p.plot_height, x_range=(0, 1), - y_range=p.y_range, min_border=10, y_axis_location="right") - - pv.ygrid.grid_line_color = None - pv.xaxis.major_label_orientation = np.pi / 4 - pv.background_fill_color = backgroundColor - - legend_it = [] - - i = 0 - #for i in range(len(voltagesList)): - for voltage in voltagesList: - color = colors[i] - linecolor = linecolors[i] - i+=1 - #for voltage, color, linecolor in zip(voltagesList, colors, linecolors): - selectEvents = eventClass.GetEventsforVoltages(voltage) - tau, yVals = extractytau(selectEvents) - - c = p.scatter(tau, yVals, size=3, line_width=2, color=color, alpha=0.8) - - hhist, hedges = np.histogram(tau, bins=taurange) - - hh = ph.quad(bottom=zerostau +previoustau, left=hedges[:-1], right=hedges[1:], top=hhist+previoustau, - fill_color = color,line_color = linecolor) - - previoustau +=hhist - - vhist, vedges = np.histogram(yVals, bins=yValsrange) - vh = pv.quad(left=zerosy + previousy, bottom=vedges[:-1], top=vedges[1:], right=vhist+previousy, - color=color, line_color= linecolor) - previousy +=vhist - - #Sharing legend is broke - #legend_it.append((name, [c, hh, vh])) - name = str(Volt.format_data(voltage)) - legend_it.append(LegendItem(label=name, renderers=[vh])) - - #p.legend.location = "top_right" - #legend = Legend(items=legend_it, location=(0, -30)) - legend = Legend(items=legend_it) - pv.add_layout(legend, 'right') - - #pv.legend.click_policy = "hide" - - ph.y_range.end = max(previoustau)*1.1 - pv.x_range.end = max(previousy) * 1.1 - - fig = gridplot([[p, pv],[ph, None]], toolbar_location="left") - - # show the results - show(fig) \ No newline at end of file diff --git a/Plotting/PlotData.py b/Plotting/PlotData.py index 8726926..6b68b5e 100644 --- a/Plotting/PlotData.py +++ b/Plotting/PlotData.py @@ -1,92 +1,102 @@ import Functions import os import LoadData import matplotlib.pyplot as plt import matplotlib import numpy as np from scipy import signal from matplotlib.ticker import EngFormatter from ipywidgets import interact import numpy as np from bokeh.io import push_notebook from bokeh.plotting import figure, show, output_notebook from bokeh.models import FuncTickFormatter, NumeralTickFormatter +# Needed for plotting in jupyter +from bokeh.resources import INLINE +import bokeh.io +bokeh.io.output_notebook(INLINE) + import math Amp = EngFormatter(unit='A', places=2) Time = EngFormatter(unit='s', places=2) Volt = EngFormatter(unit='V', places=2) Cond = EngFormatter(unit='S', places=2) def custom_formatterA(): units = [('m', 1e-3), ('µ', 1e-6), ('n', 1e-9), ('p', 1e-12)] if tick == 0: return '0' for u in units: if Math.abs(tick) >= u[1]: return '{0:.1f} {}A'.format(tick / u[1], u[0]) #Note, Javascript Math return '{:.2E}'.format(tick) def custom_formattersec(): units = [ ('µs', 1e6), ('ms', 1e3), ('s', 1.0), ('min', 1/60), ('hour', 1/3600), ] for u in units: if tick <= 100/u[1]: return '{0:.1f}{}'.format(tick * u[1], u[0]) def SimpleTracePlot(filename, lowPass = 10e3): loadedData = LoadData.OpenFile(filename,lowPass,True) #, ChimeraLowPass, True, CutTraces) if loadedData['samplerate'] > lowPass: output = Functions.LowPass(loadedData['i1'], loadedData['samplerate'], lowPass) FullTrace = output['data'] samplerate = output['samplerate'] else: FullTrace = loadedData['i1'] samplerate = loadedData['samplerate'] output_notebook() p = figure(plot_height=300, plot_width=900,tools='pan,box_zoom,xwheel_zoom,reset,save', active_scroll='xwheel_zoom') times=np.linspace(0, len(FullTrace) / samplerate, num=len(FullTrace)) p.line(times,FullTrace) p.xaxis.axis_label = 'time (s)' p.yaxis.axis_label = 'current (A)' p.yaxis[0].formatter = FuncTickFormatter.from_py_func(custom_formatterA) p.title.text = os.path.basename(filename) show(p) -def PlotPSD(filename): - loadedData = LoadData.OpenFile(filename,approxImpulseResponse = True) #, ChimeraLowPass, True, CutTraces) - frequencies,P_den = Functions.GetPSD(loadedData) +def PlotPSD(inputdata): + if isinstance(inputdata, str): + filename = inputdata + loadedData = LoadData.OpenFile(filename, approxImpulseResponse=True) #, ChimeraLowPass, True, CutTraces) + else: + filename = '' + loadedData = inputdata + frequencies, P_den = Functions.GetPSD(loadedData) output_notebook() - p = figure(plot_height=300, plot_width=900,y_axis_type="log",tools='pan,box_zoom,xwheel_zoom,reset,save') + p = figure(plot_height=300, plot_width=900, x_axis_type="log", y_axis_type="log", tools='pan,box_zoom,xwheel_zoom,reset,save') - p.line(frequencies,P_den) + p.line(frequencies,P_den*1e24) p.xaxis.axis_label = 'Frequencies (Hz)' - p.yaxis.axis_label = 'Power' + p.yaxis.axis_label = 'PSD pA^2/Hz)' p.title.text = 'PSD :' + os.path.basename(filename) show(p) diff --git a/plotgui.py b/plotgui.py index 54707b0..daf2f3c 100644 --- a/plotgui.py +++ b/plotgui.py @@ -1,498 +1,498 @@ # -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'PlotGui.ui' # # Created by: PyQt5 UI code generator 5.8.2 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Plotting(object): def setupUi(self, Plotting): Plotting.setObjectName("Plotting") - Plotting.resize(1400, 800) + Plotting.resize(1600, 800) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) + sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(Plotting.sizePolicy().hasHeightForWidth()) Plotting.setSizePolicy(sizePolicy) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Light, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Midlight, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Dark, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Mid, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.BrightText, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Shadow, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.AlternateBase, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipBase, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipText, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Light, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Midlight, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Dark, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Mid, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.BrightText, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Shadow, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.AlternateBase, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipBase, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Light, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Midlight, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Dark, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Mid, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.BrightText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Shadow, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.AlternateBase, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipBase, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, brush) Plotting.setPalette(palette) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap("../Pyth-Ion-Fork/Downloads/PythIon/pythionlogo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) Plotting.setWindowIcon(icon) Plotting.setAutoFillBackground(False) Plotting.setDockNestingEnabled(True) self.centralwidget = QtWidgets.QWidget(Plotting) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth()) self.centralwidget.setSizePolicy(sizePolicy) self.centralwidget.setObjectName("centralwidget") self.gridlayout = QtWidgets.QGridLayout(self.centralwidget) self.gridlayout.setObjectName("gridlayout") self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) self.verticalLayout.setObjectName("verticalLayout") self.graphicsView = PlotWidget(self.centralwidget) self.graphicsView.setObjectName("graphicsView") self.verticalLayout.addWidget(self.graphicsView) self.graphicsView_3 = PlotWidget(self.centralwidget) self.graphicsView_3.setObjectName("graphicsView_3") self.verticalLayout.addWidget(self.graphicsView_3) self.graphicsView_2 = PlotWidget(self.centralwidget) self.graphicsView_2.setObjectName("graphicsView_2") self.verticalLayout.addWidget(self.graphicsView_2) self.gridlayout.addLayout(self.verticalLayout, 2, 2, 1, 1) self.listWidget = QtWidgets.QListWidget(self.centralwidget) self.listWidget.setMaximumSize(QtCore.QSize(400, 16777215)) self.listWidget.setObjectName("listWidget") self.gridlayout.addWidget(self.listWidget, 2, 0, 1, 1) self.pushButton = QtWidgets.QPushButton(self.centralwidget) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) brush = QtGui.QBrush(QtGui.QColor(255, 124, 127)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Light, brush) brush = QtGui.QBrush(QtGui.QColor(253, 62, 67)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Midlight, brush) brush = QtGui.QBrush(QtGui.QColor(126, 0, 3)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Dark, brush) brush = QtGui.QBrush(QtGui.QColor(168, 1, 4)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Mid, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.BrightText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Shadow, brush) brush = QtGui.QBrush(QtGui.QColor(253, 128, 131)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.AlternateBase, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipBase, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) brush = QtGui.QBrush(QtGui.QColor(255, 124, 127)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Light, brush) brush = QtGui.QBrush(QtGui.QColor(253, 62, 67)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Midlight, brush) brush = QtGui.QBrush(QtGui.QColor(126, 0, 3)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Dark, brush) brush = QtGui.QBrush(QtGui.QColor(168, 1, 4)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Mid, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.BrightText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Shadow, brush) brush = QtGui.QBrush(QtGui.QColor(253, 128, 131)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.AlternateBase, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipBase, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipText, brush) brush = QtGui.QBrush(QtGui.QColor(126, 0, 3)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) brush = QtGui.QBrush(QtGui.QColor(255, 124, 127)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Light, brush) brush = QtGui.QBrush(QtGui.QColor(253, 62, 67)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Midlight, brush) brush = QtGui.QBrush(QtGui.QColor(126, 0, 3)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Dark, brush) brush = QtGui.QBrush(QtGui.QColor(168, 1, 4)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Mid, brush) brush = QtGui.QBrush(QtGui.QColor(126, 0, 3)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.BrightText, brush) brush = QtGui.QBrush(QtGui.QColor(126, 0, 3)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Shadow, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.AlternateBase, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipBase, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, brush) self.pushButton.setPalette(palette) self.pushButton.setObjectName("pushButton") self.gridlayout.addWidget(self.pushButton, 0, 0, 1, 1) self.label = QtWidgets.QLabel(self.centralwidget) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) brush = QtGui.QBrush(QtGui.QColor(255, 124, 127)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Light, brush) brush = QtGui.QBrush(QtGui.QColor(253, 62, 67)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Midlight, brush) brush = QtGui.QBrush(QtGui.QColor(126, 0, 3)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Dark, brush) brush = QtGui.QBrush(QtGui.QColor(168, 1, 4)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Mid, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.BrightText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Shadow, brush) brush = QtGui.QBrush(QtGui.QColor(253, 128, 131)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.AlternateBase, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipBase, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) brush = QtGui.QBrush(QtGui.QColor(255, 124, 127)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Light, brush) brush = QtGui.QBrush(QtGui.QColor(253, 62, 67)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Midlight, brush) brush = QtGui.QBrush(QtGui.QColor(126, 0, 3)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Dark, brush) brush = QtGui.QBrush(QtGui.QColor(168, 1, 4)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Mid, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.BrightText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Shadow, brush) brush = QtGui.QBrush(QtGui.QColor(253, 128, 131)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.AlternateBase, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipBase, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipText, brush) brush = QtGui.QBrush(QtGui.QColor(126, 0, 3)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) brush = QtGui.QBrush(QtGui.QColor(255, 124, 127)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Light, brush) brush = QtGui.QBrush(QtGui.QColor(253, 62, 67)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Midlight, brush) brush = QtGui.QBrush(QtGui.QColor(126, 0, 3)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Dark, brush) brush = QtGui.QBrush(QtGui.QColor(168, 1, 4)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Mid, brush) brush = QtGui.QBrush(QtGui.QColor(126, 0, 3)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.BrightText, brush) brush = QtGui.QBrush(QtGui.QColor(126, 0, 3)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Shadow, brush) brush = QtGui.QBrush(QtGui.QColor(252, 1, 7)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.AlternateBase, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipBase, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, brush) self.label.setPalette(palette) self.label.setObjectName("label") self.gridlayout.addWidget(self.label, 0, 2, 1, 1) self.pushSave = QtWidgets.QPushButton(self.centralwidget) self.pushSave.setObjectName("pushSave") self.gridlayout.addWidget(self.pushSave, 1, 0, 1, 1) Plotting.setCentralWidget(self.centralwidget) self.actionInvert = QtWidgets.QAction(Plotting) self.actionInvert.setObjectName("actionInvert") self.actionsetbuffer = QtWidgets.QAction(Plotting) self.actionsetbuffer.setObjectName("actionsetbuffer") self.Poresizeraction = QtWidgets.QAction(Plotting) self.Poresizeraction.setObjectName("Poresizeraction") self.actionBatch_Process = QtWidgets.QAction(Plotting) self.actionBatch_Process.setObjectName("actionBatch_Process") self.actionSave_All = QtWidgets.QAction(Plotting) self.actionSave_All.setObjectName("actionSave_All") self.retranslateUi(Plotting) QtCore.QMetaObject.connectSlotsByName(Plotting) def retranslateUi(self, Plotting): _translate = QtCore.QCoreApplication.translate Plotting.setWindowTitle(_translate("Plotting", "PythIon")) self.pushButton.setText(_translate("Plotting", "Load Folder")) self.label.setText(_translate("Plotting", "File Infos")) self.pushSave.setText(_translate("Plotting", "Save Graphs")) self.actionInvert.setText(_translate("Plotting", "Invert data")) self.actionsetbuffer.setText(_translate("Plotting", "Set Event Buffer Size")) self.Poresizeraction.setText(_translate("Plotting", "PoreSizer")) self.actionBatch_Process.setText(_translate("Plotting", "Batch Process")) self.actionSave_All.setText(_translate("Plotting", "Save All")) from pyqtgraph import PlotWidget