Beispiel #1
0
	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
Beispiel #2
0
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')