Menu

Commit [r6321]  Maximize  Restore  History

added action potential example

jdh2358 2008-10-24

added /trunk/py4science/examples/data/membrane.dat
added /trunk/py4science/examples/detect_action_potentials.py
/trunk/py4science/examples/detect_action_potentials.py Diff Switch to side-by-side view
--- a
+++ b/trunk/py4science/examples/detect_action_potentials.py
@@ -0,0 +1,183 @@
+"""
+In this example we will load binary membrane potential data from a
+spiking neuron sampled at 2kHz, plot the voltage response vs time, and
+write and algorithm to detect action potentials
+"""
+import numpy as np
+import matplotlib.pyplot as plt
+import matplotlib.mlab as mlab
+
+# Load the data from the file 'data/membrane.dat'.  The data are
+# stored as a single array of 4-byte floats.  To scale the data into
+# millivolts, multiple thebinary data by 100.
+#@ HINT: You can use numpy.fromstring to load binary data, and the numpy
+#@ datatype for 4-byte floats is np.float32
+v = 100*np.fromfile('data/membrane.dat', np.float32) #@
+
+# create an evenly sampled time array the same length as v samed at
+# 2kHz.  The array should be in seconds
+#@ HINT: use np.arange to create an array of len(v) integers and then
+#@ multiple by the stepsize 1/sampling frequency
+t = np.arange(len(v))/2000. #@
+
+# now plot the data, millivolts versus seconds, make an xlabel,
+# ylabel, title and grid
+fig = plt.figure()                     #@
+ax = fig.add_subplot(111)              #@
+ax.plot(t, v)                          #@
+ax.set_xlabel('time (s)')              #@
+ax.set_ylabel('mV')                    #@
+ax.set_title('Spiking aplysia neuron') #@
+ax.grid(True)                          #@
+
+# now let's write an alogirthm to detect the times that an action
+# potential occurs at.  Plot a vertical line at each action potential
+# time from the maximum voltage in the train (approx 5 mV but measure
+# this from v) to the mx + 20mV.  Use a simple algorithm which detects
+# an action potential any time the voltage crosses -10mV from below.
+# You can use a loop when practicing, but this should be done in numpy
+# w/o a loop for full credit (and it will be much faster)
+
+#@ HINT: create a boolean array equal to length(v) which is True if
+#@ v>-10.  Do the same where v<=-10.  An action potential wjere the
+#@ above condition is True and the below condition was True one index
+#@ before.  In the indexing below, we add 1 to correct for the [1:]
+#@ slicing in the logical test.  np.where turns the logical mask into
+#@ an index array For example, the following finds the indicies where
+#@ the uniform random numbers are above .95::
+#@    x = np.random.rand(100)
+#@    ind = np.where(x>0.95)[0]
+above = v>-10                                 #@
+below = v<=-10                                #@
+ind = np.where(above[1:] & below[:-1])[0] + 1 #@
+vmax = v.max()                                #@
+ax.vlines(t[ind], vmax, vmax+20)              #@
+
+# That was the easy way, but if we want a routine that works in batch,
+# where we may not have the leisure to visually inspect the graph to
+# find the threshold for action potentials (-10 in the example above)
+# we have to bee a bit cleverer.  A standard approach in the
+# neurobiology community is to use the 2nd derivative of the voltage
+# series, and when this is sufficiently positive, trigger an action
+# potential.  Write a function that detects action potentials and test
+# it for several scalings of v which add an arbitrary constant and
+# multiply it by an arbitrary scaling
+def detect(t, vstar, refractory=0.05, percent_voltage=90., percent_d2=95.,
+           makefig=None):
+    """
+    detect action potentials in vstar and return the indices.  Remove
+    any event whose distance from the previous event is less than the
+    refractory period -- neurons are known to have a minimum time
+    between sikes and this will help remove duplicate events for each
+    spike.  Return value is a numpy array of indices where the
+    conditions are met
+
+    Input parameters
+    ----------------
+
+    *t*
+        the time array in seconds
+
+    *vstar*
+        the voltage array, equal to len(t)
+
+    *refractory*
+        the refractory period of the neuron, the minimum time in
+        seconds between action potentials
+
+    *percent_voltage*
+        an action potential must be in at least the percent voltage
+        percentile.  Eg, if *percent_voltage*=95, the the voltage at the
+        spike must be at or above the 95th percentile of vstar
+
+    *percent_d2*
+        The second derivative of voltage must be in at least the
+        *percent_d2*-th percentile of second derivatives.
+
+    *makefig*
+        if *makefig* is not None, it is a matplotlib figure instance in
+        which to plot diagnostic information like the voltage trace,
+        the detected action potentials, the 2nd derivative of voltage,
+        and the *percent_voltage* and *percent_d2* tresholds
+    """
+    # make sure *t* and *vstar* are numpy arrays
+    #@ HINT: use np.asarray
+    t = np.asarray(t)
+    vstar = np.asarray(vstar)
+
+    # Compute the 2nd derivative *d2* of *vstar* and measure the
+    # *percent_d2* cutoff percentile of all 2nd derivatives.  Create a
+    # boolean mask array of len(v2) called *mask2* which is True
+    # everwhere that *d2* is >= cutoff.
+
+    #@ HINT: use np.diff(vstar, 2) to compute the 2nd derivative, and
+    #@ remember that the returned array will be two shorter than vstar,
+    #@ so you will have to index into your mask array starting in the
+    #@ 2nd position.  You can use matplotlib.mlab.prctile to compute the
+    #@ percentiles
+    d2 = np.diff(vstar, 2)                       #@
+    threshold, = mlab.prctile(d2, (percent_d2,)) #@
+    mask2 = np.zeros(len(v2), np.bool)           #@
+    mask2[2:] = d2>=threshold                    #@
+
+
+    # Now for the voltage check: the voltage at the action potentials
+    # must greater than the *percent_voltage* percentile, sometimes
+    # membrane noise has a large *d2*, so the 2nd derivative check
+    # alone is not sufficient to be sure we have an action potential.
+    # Create a boolean mask of len(v2) called *mask* that is True
+    # where *vstar* >= cutoff
+    cutoff, = mlab.prctile(vstar, (percent_voltage, )) #@
+    mask = vstar>=cutoff                               #@
+
+    # Now check the refractory condition -- the minimum time between
+    # spikes must be >= *refractory*.  The default mask is True
+    # because the first spike will always be > refractory period First
+    # create the indices where both conditions in *mask* and *mask2*
+    # are True, find the times corresponding to these indicies, and
+    # only kee the times where the interspike-interval (the diff
+    # between times) >= refractory period.
+    ind = np.nonzero(mask & mask2) [0]    #@
+    times = t[ind]                        #@
+    mask = np.ones(len(times), np.bool)   #@
+    mask[1:] = np.diff(times)>=refractory #@
+    ind = ind[mask]                       #@
+
+
+    if makefig is None: return ind
+
+    fig = makefig
+    # plot *t* vs *vstar*, as before, but in an upper subplot plot the
+    # vlines as before, but you will have to figure out how big to
+    # make the vlines based on the min/max range of vstar
+    ax1 = fig.add_subplot(211)                #@
+    ax1.plot(t, vstar)                        #@
+    ax1.axhline(cutoff)                       #@
+    vmax = vstar.max()                        #@
+    vrange = vmax-vstar.min()                 #@
+    ax1.vlines(t[ind], vmax, vmax+0.1*vrange) #@
+
+    # make a lower subplot to show d2 and the threshold; use the
+    # *sharex* keyword arg to link the x axis in the two subplots so as
+    # you pan and zoom in one, the other will be updated
+    ax2 = fig.add_subplot(212, sharex=ax1) #@
+    ax2.plot(t[2:], d2)                    #@
+    ax2.set_ylabel('2nd deriv')            #@
+    ax2.axhline(threshold)                 #@
+
+    # now just return the indices
+    return ind
+
+
+v1 = 20 + 20*v
+v2 = -100 + 0.1*v
+
+fig1 = plt.figure()
+fig1.suptitle('Test auto-detect 1')
+ind1 = detect(t, v1, makefig=fig1)
+
+fig2 = plt.figure()
+fig2.suptitle('Test auto-detect 2')
+ind2 = detect(t, v2, makefig=fig2)
+
+plt.show()
Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.