This notebook illustrates the configuration of the lamina LPU described in Neurokernel RFC #2: The Cartridge: A Canonical Neural Circuit Abstraction of the Lamina Neuropil - Construction and Composition Rules.
Please start the notebook using
$ ipython notebook
i.e., without --pylab=inline
Import some plotting utilities
In [1]:
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as p
Assuming that the Neurokernel source has been cloned to ~/neurokernel
, we first consider the two csv files that specify neurons and synapses in the lamina model.
In [2]:
%cd -q ~/neurokernel/examples/lamina/data
The neuron model types and their parameter values are specified in the file neuron_type_lamina.csv
. The entire list of neurons is shown below. Note that for convenience, the LPU contains neurons from both the retina and lamina. Descriptions of each of the columns follow:
name
- Name of neuron morphological type.model
- Specifies the name of the class implementing the neuron model. columnar
- A value of 1 in column indicates that the neuron/element is a columnar neuron/element; a number greater than 1 indicates the total number of non-columnar neurons of the specified name
in the lamina. dummy
- Used to indicate alpha-profiles.input
- Indicates whether the neuron is an input axon from another LPU.output
- Indicates whether the neuron is an output neuron.extern
- Specifies if the neuron receives external input, e.g., a video signal. public
- Specifies if the neuron may communicate with other LPUs via Neurokernel's API. The remaining columns in the table are neuron model parameters.
In [3]:
import pandas as pd
pd.set_option('display.max_columns', 20)
mpl.rcParams['figure.figsize'] = (17, 7)
neuron_data = pd.read_csv("./neuron_types_lamina.csv")
neuron_data = neuron_data.dropna(axis=1)
neuron_data.head(n=100)
Out[3]:
The synaptic connection attributes inside a cartridge and those specified by Composition Rule II are loaded from the csv file synapse_lamina.csv
. A few examples are listed below. Descriptions of each of the columns follow:
prename
, postname
- Indicate the neurons connected by the synapse.model
- Indicates the synapse model used in the LPU. cart
- Specifies the relative position from the cartridge of the presynaptic neuron to that of the postsynaptic neuron (see In [6] below). mode
- indicates if the synapse affects the neurotransmitter release at the axon terminals of the postsynaptic neuron. scale
is the number of synapses between the pre- and post-synaptic neurons. The remaining columns are synapse model parameters.
In [4]:
synapse_data = pd.read_csv("./synapse_lamina.csv")
synapse_data = synapse_data.dropna(axis=1)
synapse_data.head(n=7)
Out[4]:
To change the lamina circuitry, rows may be added, deleted, or modified in both csv files.
These files are processed by the generate_lamina_gexf.py
script to generate a GEXF file containing the full lamina configuration comprising 768 cartridges; this file may be used to instantiate the lamina LPU using Neurokernel.
In [5]:
%run generate_lamina_gexf.py
The next section describes the steps in the generation of an LPU configuration using the data in the csv files described above.
We first generate a lamina configuration model using the above two csv files, for a total of 24$\times$32=768 cartridges, where 24 is the number of rows and 32 is the number of columns for a hexagonal array. The hexagonal array is shown below.
In [6]:
%cd -q ~/neurokernel/examples/lamina/data
import vision_configuration as vc
lamina = vc.Lamina(24, 32, 'neuron_types_lamina.csv', 'synapse_lamina.csv', None)
print lamina.num_cartridges
In [7]:
p.figure(figsize=(15,7))
X = lamina.hexarray.X
Y = lamina.hexarray.Y
p.plot(X.reshape(-1), Y.reshape(-1), 'o', markerfacecolor = 'w',
markeredgecolor = 'b', markersize = 12)
p.axis('equal')
p.axis([X.min()-1, X.max()+1, Y.min()-1, Y.max()+1])
p.xlabel('x (arbitrary unit)')
p.ylabel('y (arbitrary unit)')
p.gca().invert_yaxis()
# label one cartridge position
center_row = 12
center_col = 16
p.plot(X[center_row, center_col], Y[center_row, center_col],
marker = '$0$', markeredgecolor = 'r', hold = True)
# find and label all its neighbors, the numbers are used for
# column 'cart' in the synapse_lamina.csv
neighbors = lamina.hexarray.find_neighbor(center_row, center_col)
for neighbor, i in zip(neighbors[1:], range(1,7)):
if neighbor is not None:
neighbor_row = lamina.hexarray.row[neighbor]
neighbor_col = lamina.hexarray.col[neighbor]
p.plot(X[neighbor_row, neighbor_col], Y [neighbor_row, neighbor_col],
marker = '$'+str(i)+'$', markeredgecolor = 'r', hold = True)
tx1 = p.text(55, 10, 'Anterior', fontsize=12)
ar1 = p.arrow(65, 9.5, -3, 0, head_width = 1, color = 'k')
tx2 = p.text(63, 4, 'Dorsal', fontsize = 12)
ar2 = p.arrow(65, 9.5, 0, -3, head_width = 1, color = 'k')
We now create all the cartridges. Each cartridge contains one copy of all specified columnar neurons and elements as well as all the intra-cartridge connections. Individual neurons and synapses in each cartridge can be accessed as follows:
In [8]:
lamina.create_cartridges()
lamina.cartridges[100]
Out[8]:
In [9]:
lamina.cartridges[100].neurons['L2']
Out[9]:
In [10]:
lamina.cartridges[100].synapses[8]
Out[10]:
We assign each cartridge to a position on the hexagonal grid and link it to its 6 immediate neighbor cartridges; the first element of the neighbors
attribute is the cartridge itself, while the remaining 6 elements are its neighbors:
In [11]:
lamina.connect_cartridges()
lamina.cartridges[100].neighbors
Out[11]:
The non-columnar neurons are created as follows:
In [12]:
lamina.create_non_columnar_neurons()
After all the cartridges and non-columnar neurons are created, we can specify interconnects between cartridges based on the composition rules. We first configure inter-cartridge synapses based on Composition Rule II:
In [13]:
lamina.connect_composition_II()
In the example below, the L4 neuron in cartridge 100 (shown as a red dot), receives inputs (green lines) from neurons in some neighboring cartridges (green dots), and provides outputs (blue lines) to neurons in other neighboring cartridges (blue dots):
In [14]:
p.figure(figsize=(15,7))
p.plot(X.reshape(-1), Y.reshape(-1), 'o', markerfacecolor = 'w',
markeredgecolor = 'b', markersize = 10)
p.axis('equal')
p.axis([X.min()-1, X.max()+1, Y.min()-1, Y.max()+1])
p.gca().invert_yaxis()
# plot the position of L4 neuron in cartridge 236
neuron = lamina.cartridges[236].neurons['L4']
x, y = neuron.position()
p.plot(x, y, 'o', markerfacecolor = 'r', markersize = 10, hold = True)
# plot the positions of the neuron the L4 neuron is presynaptic to
for synapse in neuron.outgoing_synapses:
post_x, post_y = synapse.post_neuron.position()
p.plot(post_x+0.1, post_y+0.1, 'o', markerfacecolor = 'b', markersize = 3, hold = True)
p.plot([x, post_x+0.1], [y, post_y+0.1], 'b', hold = True)
# plot the positions of the neuron the L4 neuron is postsynaptic to
for synapse in neuron.incoming_synapses:
pre_x, pre_y = synapse.pre_neuron.position()
p.plot(pre_x-0.1, pre_y-0.1, 'o', markerfacecolor = 'g', markersize = 3, hold = True)
p.plot([x, pre_x-0.1], [y, pre_y-0.1], 'g', hold = True)
p.xlabel('x (arbitrary unit)')
p.ylabel('y (arbitrary unit)')
tx1 = p.text(55, 10, 'Anterior', fontsize=12)
ar1 = p.arrow(65, 9.5, -3, 0, head_width = 1, color = 'k')
tx2 = p.text(63, 4, 'Dorsal', fontsize = 12)
ar2 = p.arrow(65, 9.5, 0, -3, head_width = 1, color = 'k')
We then configure inter-cartridge synapses based on Composition Rule I:
In [15]:
lamina.connect_composition_I()
In the example below, amacrine cell 0 (red dot) receives inputs (green lines) from neurons in several neighboring cartridges (green dots), and provides outputs (blue lines) to neurons in other neighboring cartridges (blue dots):
In [16]:
p.figure(figsize = (15,7))
p.plot(X.reshape(-1), Y.reshape(-1), 'o', markerfacecolor = 'w',
markeredgecolor = 'b', markersize = 10)
p.axis('equal')
p.axis([X.min()-1, X.max()+1, Y.min()-1, Y.max()+1])
p.gca().invert_yaxis()
# plot the position of Amacrine cell 240
neuron = lamina.non_columnar_neurons['Am'][240]
x, y = neuron.position()
p.plot(x, y, 'o', markerfacecolor = 'r', markersize = 10, hold = True)
# plot the positions of the neuron the Am 240 is presynaptic to
for synapse in neuron.outgoing_synapses:
post_x, post_y = synapse.post_neuron.position()
p.plot(post_x+0.1, post_y+0.1, 'o', markerfacecolor = 'b', markersize = 3, hold = True)
p.plot([x, post_x+0.1], [y, post_y+0.1], 'b', hold = True)
# plot the positions of the neuron the Am 0 is postsynaptic to
for synapse in neuron.incoming_synapses:
pre_x, pre_y = synapse.pre_neuron.position()
p.plot(pre_x-0.1, pre_y-0.1, 'o', markerfacecolor = 'g', markersize = 3, hold = True)
p.plot([x, pre_x-0.1], [y, pre_y-0.1], 'g', hold = True)
p.xlabel('x (arbitrary unit)')
p.ylabel('y (arbitrary unit)')
tx1 = p.text(55, 10, 'Anterior', fontsize=12)
ar1 = p.arrow(65, 9.5, -3, 0, head_width = 1, color = 'k')
tx2 = p.text(63, 4, 'Dorsal', fontsize = 12)
ar2 = p.arrow(65, 9.5, 0, -3, head_width = 1, color = 'k')
We now specify selectors to each public neuron to enable possible connections to other LPUs:
In [17]:
lamina.add_selectors()
The selector of cartridge neurons, e.g., L1 neurons, are of the form
In [18]:
lamina.cartridges[0].neurons['L1'].params['selector']
Out[18]:
Finally, we output the full configuration to GEXF file format that can be used to instantiate the lamina LPU:
In [19]:
lamina.export_to_gexf('lamina.gexf.gz')
We first generate an input of duration 1.0 second and then execute the model:
In [20]:
%cd -q ~/neurokernel/examples/lamina/data
%run gen_vis_input.py
%cd -q ~/neurokernel/examples/lamina
%run lamina_demo.py
Next, we generate a video of the membrane potentials of specific neurons:
In [21]:
%run visualize_output.py
The visualization script produces a video that depicts an input video signal provided to the retina and the responses of R2, L1, L2, L3 and L4 neurons in all the cartridges. Each pixel in the visualization corresponds to the neuron associated with one cartridge.
In [22]:
import IPython.display
IPython.display.YouTubeVideo('QDVoro2GhAU')
Out[22]: