def _eventsegment(self): """ Cut up a trajectory into individual events. This algorithm uses simple thresholding. By working with absolute values of currents, we handle positive and negative potentials without switches. When the current drops below 'thrCurr', mark the start of the event. When the current returns to the baseline (obtained by averaging the open channel current immediately preceeding the start of the event), mark the event end. Pad the event by 'eventPad' points and hand off to the event processing algorithm. """ try: while(1): t=self.currData.popleft() self.globalDataIndex+=1 # store the latest point in a fixed buffer if not self.eventstart: self.preeventdat.append(t) # Mark the start of the event if abs(t) < self.thrCurr: #print "event", self.eventstart=True self.eventdat=[] self.eventdat.append(t) self.dataStart=self.globalDataIndex-len(self.preeventdat)-1 if self.eventstart: mean=abs(util.avg(self.preeventdat)) while(abs(t)<mean): t=self.currData.popleft() self.eventdat.append(t) self.globalDataIndex+=1 # end of event. Reset the flag self.eventstart=False # Check if there are enough data points to pad the event. If not pop more. if len(self.currData) < self.eventPad: self.currData.extend(list(self.trajDataObj.popdata(self.nPoints))) # Cleanup event pad data before adding it to the event. We look for: # 1. the start of a second event # 2. Outliers # The threshold for accepting a point is eventThreshold/2.0 eventpaddat = util.selectS( [ self.currData[i] for i in range(self.eventPad) ], self.eventThreshold/2.0, self.meanOpenCurr, self.sdOpenCurr ) #print self.trajDataObj.FsHz, self.windowOpenCurrentMean, self.sdOpenCurr, self.slopeOpenCurr if len(self.eventdat)>=self.minEventLength: self.eventcount+=1 # print "i=", self.eventcount #sys.stderr.write('event mean curr={0:0.2f}, len(preeventdat)={1}\n'.format(sum(self.eventdat)/len(self.eventdat),len(self.preeventdat))) #print list(self.preeventdat) + self.eventdat + [ self.currData[i] for i in range(self.eventPad) ] #print "ecount=", self.eventcount, self.eventProcHnd # print "eventProcSettings", self.eventProcSettingsDict self._processEvent( self.eventProcHnd( list(self.preeventdat) + self.eventdat + eventpaddat, self.FsHz, eventstart=len(self.preeventdat)+1, # event start point eventend=len(self.preeventdat)+len(self.eventdat)+1, # event end point baselinestats=[ self.meanOpenCurr, self.sdOpenCurr, self.slopeOpenCurr ], algosettingsdict=self.eventProcSettingsDict.copy(), savets=self.writeEventTS, absdatidx=self.dataStart ) ) self.preeventdat.clear() except IndexError: return
class singleStepEvent(metaEventProcessor.metaEventProcessor): """ Analyze a single-step event characteristic of PEG blockades. Two relevant pieces of information are measured for each event and stored as metadata: 1. Blockade depth: the ratio of the open channel current to the blocked current 2. Residence time: the time the molecule spends inside the pore When an event cannot be analyzed, the blockade depth and residence time are set to -1. """ def _init(self, **kwargs): """ Initialize the single step analysis class. """ # initialize the object's metadata (to -1) as class attributes self.mdOpenChCurrent = -1 self.mdBlockedCurrent = -1 self.mdEventStart = -1 self.mdEventEnd = -1 self.mdBlockDepth = -1 self.mdResTime = -1 self.mdTempA12 = -1 # Settings for single step event processing # settings for gaussian fits try: self.histBinSz = float(self.settingsDict.pop("binSize", 1.0)) self.histPad = int(self.settingsDict.pop("histPad", 10)) self.maxFitIters = int(self.settingsDict.pop("maxFitIters", 5000)) # rejection criterion settings self.a12Ratio = float(self.settingsDict.pop( "a12Ratio", 1.e6)) # max ratio of density between open and blocked states self.minEvntTime = float( self.settingsDict.pop("minEvntTime", 20.e-6)) # data conditioning settings self.datPad = int(self.settingsDict.pop("minDataPad", 50)) except ValueError as err: raise commonExceptions.SettingsTypeError(err) ########################################################################### # Interface functions implemented starting here ########################################################################### def _processEvent(self): """ This function implements the core logic to analyze one single step-event. """ try: # Fit a sum of two gaussians to locate the open channel # and blocked channel states self.__blockadeDepthFit() # If the blockade depth was calculated properly, # the processing status should be normal. Proceed # to calculate the residence time. self.__residenceTime() except: raise def mdList(self): """ Return a list of meta-data from the analysis of single step events. We explicitly control the order of the data to keep formatting consistent. """ return [ self.mdProcessingStatus, self.mdOpenChCurrent, self.mdBlockedCurrent, self.mdEventStart, self.mdEventEnd, self.mdBlockDepth, self.mdResTime, self.mdTempA12 ] def mdHeadings(self): """ Explicity set the metadata to print out. """ return [ 'ProcessingStatus', 'OpenChCurrent', 'BlockedCurrent', 'EventStart', 'EventEnd', 'BlockDepth', 'ResTime', 'A12TempOutput' ] def mdHeadingDataType(self): """ Return a list of meta-data tags data types. """ return ['TEXT', 'REAL', 'REAL', 'REAL', 'REAL', 'REAL', 'REAL', 'REAL'] def mdAveragePropertiesList(self): """ Return a list of meta-data properties that will be averaged and displayed at the end of a run. """ return ['mdBlockDepth'] def formatsettings(self): """ Return a formatted string of settings for display """ fmtstr = "" fmtstr += '\tEvent processing settings:\n\t\t' fmtstr += 'Algorithm = {0}\n\n'.format(self.__class__.__name__) fmtstr += '\t\tData conditioning: pad data size = {0} points\n\n'.format( self.datPad) fmtstr += '\t\tHistogram bin size = {0} pA\n'.format(self.histBinSz) fmtstr += '\t\tHistogram padding = {0} points\n\n'.format(self.histPad) fmtstr += '\t\tPore current fit: max. iterations = {0}\n\n'.format( self.maxFitIters) fmtstr += '\t\tPore current fit rejection: peak ratio limit (A12) = {0}\n'.format( self.a12Ratio) fmtstr += '\t\tPore current fit rejection: min. event time = {0} us\n'.format( 1.e6 * self.minEvntTime) return fmtstr ########################################################################### # Local functions ########################################################################### def __currentEstimate(self, dat, blockSz): """ Return a current list by partitioning the data into blocks and concatenating data from blocks with the most common mean value. Args: dat data to process blockSz block size in points """ if len(dat) < 2 * blockSz or blockSz == 0 or len(dat) < 10: self.rejectEvent('eShortEvent') return #print len(dat), blockSz # partition the data into sub-blocks that have a length # of the minimum of 50 elements or len(dat)/10, to guarantee # at least 10 blocks dblock = [datblock(d) for d in util.partition(dat, blockSz)] # Find the sub-block with the most common mean. Assuming # most of the pre and post event data is at the open channel # conductance, this will filter out spurious events c = util.commonest([round(d.mean) for d in dblock]) # Gather data from all the sub-blocks that match the most # common mean, collect them into one list and return them # to the calling function r = [] [r.extend(d.data) for d in dblock if c == round(d.mean)] return r def __blockadeDepthFit(self): """ """ # Get first order estimates of the open channel and blocked state currents # We will use these to guide the final fits. pre = self.__currentEstimate( self.eventData[:self.eStartEstimate - self.datPad], min( 50, int( len(self.eventData[:self.eStartEstimate - self.datPad]) / 10.0))) post = self.__currentEstimate( self.eventData[self.eEndEstimate + self.datPad:], min( 50, int( len(self.eventData[self.eEndEstimate + self.datPad:]) / 10.0))) bcCurrEst = self.__meanBlockedCurrent( self.eventData[self.eStartEstimate - self.datPad:self.eEndEstimate + self.datPad], 2.0) if self.mdProcessingStatus == 'normal': ocCurrEst = util.flat2([pre, post]) self.__fitsumgauss(ocCurrEst + bcCurrEst, util.avg(ocCurrEst), np.std(ocCurrEst), util.avg(bcCurrEst)) def __fitsumgauss(self, cleandat, oc, ocsd, bc): """ """ np.seterr(invalid='raise', over='raise') # keep track of the current polarity sign = np.sign(util.avg(cleandat)) # use the absolute current value to simplify the calc. # We put the sign back in the current when returning the results evntabs = np.abs(cleandat) # Histogram and fit try: # PDF of event data ehist, bins = np.histogram(evntabs, bins=np.arange( np.min(evntabs) - self.histPad, np.max(evntabs) + self.histPad, self.histBinSz), density=True) popt, pcov = scipy.optimize.curve_fit( self.__sumgauss, [b + (0.5 * self.histBinSz) for b in bins[:-1]], ehist, p0=[ np.max(ehist), np.abs(bc), np.abs(ocsd), np.max(ehist), np.abs(oc), np.abs(ocsd) ], maxfev=self.maxFitIters) except BaseException, err: #print oc, ocsd, bc, len(cleandat) # reject event if it can't be fit self.rejectEvent('eBDFit') return try: # If the fit was successful, determine if the parameters are acceptable if np.abs(popt[3] / popt[0]) > self.a12Ratio or popt[1] / popt[ 4] < 0 or popt[3] / popt[0] < 0 or np.abs( popt[4] - popt[1]) < 2.75 * popt[5]: self.rejectEvent('eBDPeak') return elif np.abs(popt[2]) > 2 * np.abs(popt[5]): self.rejectEvent('eEvntNoise') return except: # unspecified fitting error self.rejectEvent('eBDFit') # One last test: make sure we have a flat region at the base of the event # to avoid underestimating the blockade depth. if len( util.selectS( self.eventData[self.eStartEstimate - self.datPad:self.eEndEstimate + self.datPad], 2, sign * popt[1], popt[2])) < int(self.minEvntTime * self.Fs): self.rejectEvent('eShortEvent') return # The event is good, save it. try: self.mdOpenChCurrent = uncertainties.ufloat( (sign * round(popt[4], 4), round(np.abs(popt[5]), 4))) self.mdBlockedCurrent = uncertainties.ufloat( (sign * round(popt[1], 4), round(np.abs(popt[2]), 4))) bd = self.mdBlockedCurrent / self.mdOpenChCurrent self.mdBlockDepth = uncertainties.ufloat( (round(uncertainties.nominal_value(bd), 5), round(uncertainties.std_dev(bd), 6))) self.mdTempA12 = uncertainties.ufloat((popt[0], popt[3])) #print 'normal\t'+str(s1)+'\t'+str(s2)+'\t'+'0.0+/-0.0'+'\t'+'0.0+/-0.0'+'\t'+str(bd1)+'\t'+'0.0+/-0.0'+'\t'+str(a12) except AssertionError: self.rejectEvent('eBDFit')