Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed and place cells #96

Closed
hsw28 opened this issue Dec 4, 2023 · 13 comments
Closed

Speed and place cells #96

hsw28 opened this issue Dec 4, 2023 · 13 comments

Comments

@hsw28
Copy link

hsw28 commented Dec 4, 2023

Hi,
I am using ratinabox to model some place cell activity. As we know, place cells are really only active representations of the animals location when the animal is moving/during theta (of course they also activate during SWRs, etc, but those dont represent the current location). I would like to look at the code that "specifies" this property but I cant find it. Would you mind directing me to the right place? Many thanks!!

@TomGeorge1234
Copy link
Collaborator

TomGeorge1234 commented Dec 4, 2023

Hi Hannah!

Yes, you're right. Out of the box PlaceCells (as they stand) don't model down to that level of biological realism. The goal for RiaB is to provide a minimal set of tools from which this kind of stuff can be explored, so you're on the right path.

The lines of code which determine the PlaceCell firing rates are located in the get_state() method of PlaceCells. You'll see that, by default, it's just a basic Gaussian and there's no Agent.velocity dependence or theta dependence.

def get_state(self, evaluate_at="agent", **kwargs):
"""Returns the firing rate of the place cells.
By default position is taken from the Agent and used to calculate firinf rates. This can also by passed directly (evaluate_at=None, pos=pass_array_of_positions) or ou can use all the positions in the environment (evaluate_at="all").
Returns:
firingrates: an array of firing rates
"""
if evaluate_at == "agent":
pos = self.Agent.pos
elif evaluate_at == "all":
pos = self.Agent.Environment.flattened_discrete_coords
else:
pos = kwargs["pos"]
pos = np.array(pos)
# place cell fr's depend only on how far the agent is from cell centres (and their widths)
dist = (
self.Agent.Environment.get_distances_between___accounting_for_environment(
self.place_cell_centres, pos, wall_geometry=self.wall_geometry
)
) # distances to place cell centres
widths = np.expand_dims(self.place_cell_widths, axis=-1)
if self.description == "gaussian":
firingrate = np.exp(-(dist**2) / (2 * (widths**2)))
if self.description == "gaussian_threshold":
firingrate = np.maximum(
np.exp(-(dist**2) / (2 * (widths**2))) - np.exp(-1 / 2),
0,
) / (1 - np.exp(-1 / 2))
if self.description == "diff_of_gaussians":
ratio = 1.5
firingrate = np.exp(-(dist**2) / (2 * (widths**2))) - (
1 / ratio**2
) * np.exp(-(dist**2) / (2 * ((ratio * widths) ** 2)))
firingrate *= ratio**2 / (ratio**2 - 1)
if self.description == "one_hot":
closest_centres = np.argmin(np.abs(dist), axis=0)
firingrate = np.eye(self.n)[closest_centres].T
if self.description == "top_hat":
firingrate = 1 * (dist < self.widths)
firingrate = (
firingrate * (self.max_fr - self.min_fr) + self.min_fr
) # scales from being between [0,1] to [min_fr, max_fr]
return firingrate

So how could you model more these more realistic dependencies?

I see two options:

  • Subclass PlaceCells: You could make a subclass called MyMoreRealisticPlaceCells(PlaceCells) which inherits from PlaceCells where you write your own get_state() method which implements more complex theta or velocity dependence.
  • Mix PlaceCells with other cells: You could create a cells which linearly "mix" PlaceCells with, say, VelocityCells using a class we call FeedForwardLayer to make velocity-dependent-place cells. I did this for GridCells in this demo

It depends what you're interested in. The first is probably more flexibility but the second is probably slightly easier. By the way, I once made a subclass of PlaceCells which phase precess (here so you might be interested in checking that out too. Once you've decided I'm happy to help you out here if you have specific questions.

And, of course, once made you'd be welcome to contribute either of these two models to the repo so others can use them too, if you'd like :))

@hsw28
Copy link
Author

hsw28 commented Dec 4, 2023

Got it, super helpful and thanks for the fast response! I will likely work on creating a class that makes place cell activity contingent on velocity-- I'll likely have some questions so would you prefer I leave this issue open for those or close it and open a new issue/discussion if need be? Thanks again!

@TomGeorge1234
Copy link
Collaborator

Np at all. I'll leave it open for now and wait for questions

@hsw28
Copy link
Author

hsw28 commented Dec 5, 2023

likely dumb question that it's easier just to ask:

in my class i am calling some attributes from Neurons/PlaceCells like so

def plot_rate_timeseries(self):
    # This method acts as a wrapper to the parent class's plot_ratemap method
    super(CombinedNeurons, self).plot_rate_timeseries()

def plot_rate_map(self):
    # This method acts as a wrapper to the parent class's plot_ratemap method
    super(CombinedNeurons, self).plot_rate_map()

But when it comes to plot_place_cell_centre I CANNOT find where that lives! If I try to call it as above:

def plot_place_cell_centre(self):
    # This method acts as a wrapper to the parent class's plot_ratemap method
    super(CombinedNeurons, self).plot_place_cell_centre()

I get the error: AttributeError: 'CombinedNeurons' object has no attribute 'plot_place_cell_centres'. I am assuming it doesnt live in the Neuron parent class but then where is it? Thanks!

@TomGeorge1234
Copy link
Collaborator

not a stupid question!

  • Firstly (and probably the answer) is that the function is called plot_place_cell_locations() (not ...._centre). It's here:

    def plot_place_cell_locations(

  • Secondly is that your CombinedNeurons subclass should be a subclass of PlaceCells (where this function lives) not (or at least not only) Neurons

  • Thirdly, unless you want to add extra things into these function you shouldn't need to write such basic wrappers like this. The subclasses "inherit" all methods from their parents by default so MySubClassInstance.a_method_defined_in_the_parent_class() should work just fine without you duplicating each function like this.

  • Fourthly, the search bar at the top of GitHub is a pretty effective way to find things/functions. If not, go to Neurons.py and poke around there for a while and you should find stuff.

Screenshot 2023-12-06 at 10 35 48

Does this help?

@hsw28
Copy link
Author

hsw28 commented Dec 6, 2023

great, thank you! my subclass is a subclass of PlaceCells, not just Neurons (sorry if I was unclear). I added the wrappers because I am calling the plotting functions (eg plot_rate_map) from a different main script.

I (of course) tried to search but was searching on plot_place_cell_centres-- it is described in your list of plotting functions (here:

) which is where I must have pulled it from :)

Either way, all working now, so thank you!!

@hsw28
Copy link
Author

hsw28 commented Dec 6, 2023

Ok, a few other questions:
Here are some plots of my agents movement (taken from real position data)
image
image

And then here are the modelled place cell
the centers seem to be relatively uniformly distributed despite that the agent is spending significantly more time on the perimeter of the arena. Also, why is there a stripe at the top with no centers, despite that the animal visited that area?
image

thank you again!

@TomGeorge1234
Copy link
Collaborator

Oh sorry for the misnamed plot function. I've fixed that!

Yes, same as before, in RiaB PlaceCells don't model this level of biological realism (this is a conscious choice to keep the toolkit simple). If you wanted you could definitely write your own code that sets PlaceCells.place_cell_centres to be sampled from the motion heat map distribution (or something more complex) but, by default, cell centres are just scattered uniformly across the environment plus a bit of jitter so this aspect is totally expected.

As for the band at the top, I just looked into it and it seems like you have spotted a very subtle bug, thanks! It was essentially a fence-post error do with how positioned are sampled uniformly across the Environment. I push a fix in version 1.11.4 which should mend things can you let me know. if you agree, update riab with

pip install -U ratinabox

@hsw28
Copy link
Author

hsw28 commented Dec 7, 2023

great, thank you! if the place cells are scattered randomly across the environment, and can even be in locations where the animal has not travelled, what function does the agents position then have?

i was basing my modified class off of only creating place cells when the agent is travelling at a certain speed, but if the agents position does not matter for creating place cells, the speed will then also be irrelevant

@TomGeorge1234
Copy link
Collaborator

The agent position determines the firing rate of the place cell...but not (by default) its receptive field.

I'm imagining your simulation looks a bit like this:

Env = Environment(params={'scale':0.4,'aspect':2.5})
Ag = Agent(Env)
#<---you probably imported a trajectory here...I'll use the inbuilt random motion model 
PCs = PlaceCells(Ag, params={'n':80})
PCs.plot_place_cell_locations()

#The main simulation loop
while Ag.t < 60:
    Ag.update()
    PCs.update()

Two plotting functions may help here:

  • PlaceCells.plot_rate_maps() show the receptive fields of the place cells. These are just Gaussian centres at a location which doesn't depend on the statistics of the Agent.
PCs.plot_rate_map(chosen_neurons='10',shape=(5,2))

a509b8b1-31bc-477a-91ee-0dbbc1409353

  • PlaceCells.plot_rate_timeseries() shows the data created under the motion model.

c947f0d8-dd23-43ff-bc12-a598fe615fa4

So you see that PlaceCells are just static Gaussians (there are more complex versions of place cells and other cell types entirely but these are the default I assume you're using), uniformly scattered across the environment. These Gaussians themselves don't depend on the position of the Agent but the position of the Agent maps through these Gaussians to determine the firing rate of each cell at each moment.

Now you totally could make the Gaussians depend on the Agent position. You could extract the positions, create a density map, a sample positions from the density and re-set the place cell locations PlaceCells.place_cell_centres = my_new_centres

Or, alternatively, you could keep the Gaussians static but multiply them by a velocity-dependent term in your new cells get_state() method. It would look something like:

... # the rest of your get_state function 
fr = super().get_state(evaluate_at=evaluate_at, **kwargs)
fr *= self.Agent.velocity #or something more complex 

Does this make sense?

@hsw28
Copy link
Author

hsw28 commented Dec 8, 2023

Ok got it, so place cells where the animal never visits just wont ever be activated. That's good enough for me, I think! Thank you!

@TomGeorge1234
Copy link
Collaborator

How're you getting on? I'm thinking of closing this issue if theres no more questions for now but up to you

@hsw28
Copy link
Author

hsw28 commented Dec 16, 2023

Doing great! I've built 4 models so far haha. Thank you! Feel free to close :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants