Neurons (using particle system)

This tutorial demonstrates how to generate huge numbers of neurons, up to millions, with relatively little impact on the renderer’s performance. Here, we’ll put 300,000 neurons in the scene

Open In Colab

Install Urchin

Urchin is a Python package stored on PyPI, the following code needs to be run the first time you use Urchin in a Python environment.

Urchin’s full documentation can be found on our website.

[ ]:
#Installing urchin
!pip install oursin -U
[ ]:
# If necessary, install pandas as well
!pip install pandas

Setup Urchin and open the renderer webpage

By default Urchin opens the 3D renderer in a webpage. Make sure pop-ups are enabled, or the page won’t open properly. You can also open the renderer site yourself by replacing [ID here] with the ID that is output by the call to .setup() at https://data.virtualbrainlab.org/Urchin/?ID=[ID here]

Note that Urchin communicates to the renderer webpage through an internet connection, we don’t currently support offline use (we hope to add support in the future).

[1]:
#Importing necessary libraries:
import oursin as urchin
urchin.setup()

import requests
import pandas as pd
import io
(URN) connected to server
Login sent with ID: 7c8d93d6, copy this ID into the renderer to connect.

Load Allen Institute dataset

For this tutorial, we’ll be using the Allen Institute’s Visual Behavior Neuropixels dataset. This data was recorded with six probes at a time from cortical targets in visual cortex as well as a few subcortical regions. Let’s start by just loading the neuron positions, as well as their raw firing rates.

We’ve preprocessed the data for you and saved it into a convenient CSV file.

[2]:
file_id = '14omMPBKYip6pQqaYxWbT_KybRyTbn870'

download_link = f"https://drive.google.com/uc?id={file_id}"
response = requests.get(download_link)

df = pd.read_csv(io.StringIO(response.text))

# some rows have a zero value for the coordinates, remove those rows
coord_cols = ['left_right_ccf_coordinate', 'anterior_posterior_ccf_coordinate', 'dorsal_ventral_ccf_coordinate']
df = df[(df[coord_cols] != 0).all(axis=1)]
[3]:
df.head()
[3]:
Unnamed: 0 unit_id left_right_ccf_coordinate anterior_posterior_ccf_coordinate dorsal_ventral_ccf_coordinate firing_rate percentile_rank color size_scale
0 0 1157005856 6719.0 8453.0 3353.0 0.931674 0.262039 #cbbadc 0.046550
1 1 1157005853 6719.0 8453.0 3353.0 8.171978 0.775100 #6a399a 0.079213
2 2 1157005720 6590.0 8575.0 3842.0 13.353274 0.885895 #551d8c 0.088849
3 3 1157006074 6992.0 8212.0 2477.0 12.006044 0.864987 #59228f 0.086945
4 4 1157006072 6992.0 8212.0 2477.0 6.058306 0.692708 #7a4ea5 0.072731

Before doing anything with the neurons, let’s just the load the root area for the CCF.

[4]:
urchin.ccf25.load()
[5]:
urchin.ccf25.root.set_visibility(True)
urchin.ccf25.root.set_material('transparent-lit')
urchin.ccf25.root.set_alpha(0.15)
urchin.ccf25.root.set_color("#000000")

Cool. Let’s create a group of neuron objects in Urchin. To create a group of neurons, call the urchin.neurons.create(n) function, passing the number of neurons n as a parameter. The create function returns a list of neuron objects, which can then be passed to plural functions to set the position, color, size (etc) of all the neurons at once.

[6]:
neurons = urchin.particles.create(len(df))

Now let’s go through the dataframe, and set the positions of the neurons. When you’re setting the positions of a large number of neurons like this, it’s best practice to the use the “plural” functions. If you were to call Neuron.set_position() for each neuron in the list you would generate an unnecessary amount of communication overhead!

To use the plural urchin.neurons.set_positions() function, pass in the neurons list, followed by the new position for each neuron.

Note that we’re about to create 300,000 neurons! This will take a second!

[8]:
positions_list = []

positions_list = df[['anterior_posterior_ccf_coordinate', 'left_right_ccf_coordinate', 'dorsal_ventral_ccf_coordinate']].values.tolist()

urchin.particles.set_positions(neurons, positions_list)
[31]:
urchin.camera.main.set_zoom(8)
await urchin.camera.main.screenshot(size=[600,400])
(Camera receive) Camera CameraMain received an image
(Camera receive) CameraMain complete
[31]:
../../../../_images/urchin_tutorials_urchin-examples_basics_neurons_particles_16_1.png

Cool! That looks promising. Maybe we should add all the brain regions that the probes are going through, just to make things look good? Check out the Areas tutorial for more details on this code.

[10]:
brain_areas = ["VISp", "VISl", "VISal", "VISpm", "VISam", "VISrl", "LGd", "LP", "CA1", "CA3", "DG"]

area_list = urchin.ccf25.get_areas(brain_areas)

urchin.ccf25.set_visibilities(area_list, True, urchin.utils.Side.RIGHT)
urchin.ccf25.set_materials(area_list, 'transparent-unlit', "right")
urchin.ccf25.set_alphas(area_list, 0.2, "right")
[33]:

await urchin.camera.main.screenshot(size=[600,400])
(Camera receive) Camera CameraMain received an image
(Camera receive) CameraMain complete
[33]:
../../../../_images/urchin_tutorials_urchin-examples_basics_neurons_particles_19_1.png

Urchin can take in color input in different formats, including hex codes, RGB floats, and RGB ints. Urchin uses hex colors represented as strings by default, but you can also pass colors as lists or tuples. The followig examples are all equivalent:

neurons[i].set_color((0,255,0))

neurons[i].set_color([0,255,0])

neurons[i].set_color((0,1.0,0))

neurons[i].set_color("00FF00")

Let’s set the color of all the neurons according to their firing rates. We’ve pre-computed the colors in the CSV file for convenience.

[11]:
urchin.particles.set_colors(neurons, list(df['color'].values))
[35]:

await urchin.camera.main.screenshot(size=[600,400])
(Camera receive) Camera CameraMain received an image
(Camera receive) CameraMain complete
[35]:
../../../../_images/urchin_tutorials_urchin-examples_basics_neurons_particles_22_1.png

We can also set the scale of neurons in a similar way

[12]:
urchin.particles.set_sizes(neurons, list(df['size_scale'].values))
[37]:
await urchin.camera.main.screenshot(size=[600,400])
(Camera receive) Camera CameraMain received an image
(Camera receive) CameraMain complete
[37]:
../../../../_images/urchin_tutorials_urchin-examples_basics_neurons_particles_25_1.png

Now the neurons are scaled and colored by their average firing rate in the dataset, which is a pretty intuitive way of representing the data!

Note that the “plural” functions you used each have a “singular” version as well, that can be called directly from the Neuron objects, for example by doing: neurons[0].set_size(3.0)

Materials

There are only three options right now for neuron materials, we’ll add more soon! The current options are “gaussian”, “circle”, and “circle-lit”. The lit particles are affected by the scene lighting, while the unlit ones will have the exact color you set them to. If you are coloring neurons by a colormap you shouldn’t use the -lit variants!

[13]:
urchin.particles.set_material('circle')
[41]:
await urchin.camera.main.screenshot(size=[600,400])
(Camera receive) Camera CameraMain received an image
(Camera receive) CameraMain complete
[41]:
../../../../_images/urchin_tutorials_urchin-examples_basics_neurons_particles_30_1.png

[TODO]: Camera area targeting (not working in 0.5.0, sorry!)

[60]:
urchin.camera.main.set_target_area('VISp-rh')
[ ]: