View of the ocean from the OIST Seaside house

Last summer, I attended the OIST Computational Neuroscience Course (OCNC) in Okinawa, where we were taught the basics of computational neuroscience with a very hands-on approach. It was both rigorous and fun at the same time. The location of the summer school was spectacular - we were located at the sea-side house of OIST, with a beautiful view of the ocean (header image). The beach was a two minute walk away and visiting it everyday made the summer school even more special.

One of the things I learned about was place cells. Place cells are typically pyramidal neurons in the hippocampus that fire when the mouse/rat is in a particular ‘place’ in the environment. Collectively, place fields are thought to represent spatial locations, thereby encoding a cognitive map of the world. For example, figure 1 shows different place fields firing at different locations of the track, thus forming a cognitive map of the track.


Wikipedia image - Attribution: Stuartlayton at English Wikipedia

While discussing about place cells, we wondered if we could figure out the location the rat was in using only the activity of the place cells. This is probably what the rat does, and therefore we should also be able to do the same. Place cell firing rate generally increases as the rat comes close to the cell’s preferred location, and decreases as the rat moves away from it. The actual spikes, however, are not periodic and are generally noisy between trials. The firing of the neuron, and therefore the noisy spikes, could be due to an underlying Poisson process. Is it be possible for us to determine the location of the rat in the maze if place cells have Poisson firing? To do this, let’s first generate data of a (place cell) neuron with Poisson firing. This is the subject of this blog. The inference of location based on place cell firing will be the subject of a subsequent blog.

Let’s try to simulate data for the place cell. We start with three place cells which fire at different locations on a 1-D line. Their location preferences are normally distributed (Gaussian distribution). There is quite a bit of overlap of place cell preferences, i.e. two or more cells will fire at a particular location, albeit at different firing rates. A normally distributed location preference means that the cell will have a higher firing rate at the center of the distribution when compared to its edges. However, things become a lot more complicated as the actual spikes are due to a Poisson process. This might not quite be true, but for now, let us just assume that place cell firing is indeed Poissonian, as it makes the whole problem a lot more interesting.

Let’s begin by writing a function to generate a Gaussian location preference. Instead of just writing a function for one Gaussian, let’s write one for sum of Gaussians so that we can create bimodal distributions, if necessary.

%pylab inline --no-import-all
import numpy as np
 
# Gaussian function
def sum_of_gaussians(a, positions, c):
    f = lambda x: sum(a * np.exp( -(x-b)**2 / (2 * c**2)) for b in positions)
    return f

Our next step is to simulate the Poisson firing of a neuron. This is the tricky part. We have to assume that neurons fire maximally only once a millisecond (very reasonable assumption, given the refractory period of a neuron). We now want to find the probability of a neuron ‘not firing’ in a one millisecond bin, given a particular firing rate. This turns out to be simply the negative exponential of the rate of firing (substitute k=0 in the probability equation of a Poisson distribution). To mimic firing, we obtain a random value between 0 and 1 from a uniform distribution, and if this turns out to be more than our probability, we say our neuron is fired a spike (again, the key assumption is that there cannot be more than one spike in a millisecond).

The above method is a nice way to obtain Poissonian firing (I think. Are there better ways? Do let me know). To make the neuron fire variably, depending on the location, we simply need to pass a rate function that changes with time (indicating change in activity to due location change). The below function does all the above.

def poisson_firing(ratefn, time):
    """
    ratefn describes the prescribed rate for each point in time t.
    (Try to use a lambda function for the ratefn input)
    Time can be in seconds or milliseconds
 
    Assumption:
    Assuming only one spike can happen in a millisecond (reasonable assumption)
    Effective refractory period for continuous firing is 1 ms
    (i.e max firing = 1000 Hz)
    Good enough assumption as long as a rate function with an sane firing
    rate is used
    """
 
    dt = time[1]-time[0]
    spiketimes = []
 
    for t in time:
        average_events = dt * ratefn(t)
        P = np.exp(-1 * average_events) # Probability of no spikes
        rand = np.random.random()
        if np.random.random() > P:  # Append spike if random number greater than probability
            spiketimes.append(t)
 
    return spiketimes
 

Let’s first create our three place neurons with broad and overlapping location preference.

# 1-D line
vel = 1    # 1 unit per sec
t = np.arange(0, 30, 0.001)
x = lambda t: vel * t
 
# Preference distribution of neurons (tuning curve) - max firing - 50 Hz
neuron1_preference = sum_of_gaussians(50, [10], 5)
neuron2_preference = sum_of_gaussians(50, [15], 5)
neuron3_preference = sum_of_gaussians(50, [20], 5)
 
# Plot preference
pylab.plot(x(t), neuron1_preference(x(t)))
pylab.plot(x(t), neuron2_preference(x(t)))
pylab.plot(x(t), neuron3_preference(x(t)))
 

Now, let’s simulate their firing rate if the animal is moving with a fixed velocity.

# Obtain poisson based firing rates of each (place cell) neuron
trials = 10
 
neuron1_firing = []
neuron2_firing = []
neuron3_firing = []
 
for _ in range(trials):
    neuron1_firing.append(poisson_firing(lambda t: neuron1_preference(vel * t), t))
    neuron2_firing.append(poisson_firing(lambda t: neuron2_preference(vel * t), t))
    neuron3_firing.append(poisson_firing(lambda t: neuron3_preference(vel * t), t))
 
# Raster plot each of the three neurons
for i in range(trials):
    pylab.plot(neuron1_firing[i], (5 * i + 1) * np.ones(len(neuron1_firing[i])), '|b')
    pylab.plot(neuron2_firing[i], (5 * i + 2) * np.ones(len(neuron2_firing[i])), '|g')
    pylab.plot(neuron3_firing[i], (5 * i + 3) * np.ones(len(neuron3_firing[i])), '|r')
 
# beautify
pylab.axes().set_xticks(np.arange(0,30+1,5))
pylab.axes().set_yticks(np.arange(0,trials*2,3))

We can see that our neuron firing rate is different between trials, but they still respond to location with a normally distributed increase in firing rate. We have successfully simulated place cells with Gaussian location preference and Poissonian firing (a blog on decoding this will be written soon).

You can the download the jupyter notebook for the above code directly from here.

[P.S: The idea was stimulated by discussions with Indrajith Nair, who attended OCNC along with me]

[Updated on 2017-05-01: Add comments to the poisson_firing function and added some text to explain it better]