Tools for generating cell placements

Although users are always welcomed to place their neuronal somata according to their own custom generated positions, BMTK provides several tools to facilitate this placement, including simple geometries and placement based on geometries or densities from *.nrrd files. Units can be placed with a minimum distance between them (please see second part of this tutorial).

Generate a cylindrical column of cell positions

This function places random locations uniformly within a cylinder or cylindrical ring. Note that ‘y’ is the vertical axis.

[1]:
from bmtk.builder.auxi.node_params import positions_columnar

positions = positions_columnar(400, center=[0.0, 50.0, 0.0], height=300.0, min_radius=0.0, max_radius=200,
                        plot=True)
_images/tutorial_cell_placement_2_0.png

We can also use this function to create a cylindrical ring of locations. For instance, in the Allen V1 model, this method was used to introduce a surround of less computationally intensive unit types around the core of biophysically detailed units.

[2]:
positions = positions_columnar(400, center=[0.0, 50.0, 0.0], height=300.0, min_radius=100.0, max_radius=200,
                        plot=True)
_images/tutorial_cell_placement_4_0.png

Generate a rectangular prism volume of cell positions

Alternatively, if a sheet of cells (with some thickness) or other such geometry is desired, the following function can be used.

[3]:
from bmtk.builder.auxi.node_params import positions_rect_prism

positions = positions_rect_prism(400, center=[0.0, 50.0, 0.0], height=20.0, x_length=100.0, z_length=100.0, plot=True)
_images/tutorial_cell_placement_6_0.png

Generate an ovoid volume of cell positions

Lastly, if an ellipsoid is desired, e.g., to simulate a nucleus, one can use the following function:

[4]:
from bmtk.builder.auxi.node_params import positions_ellipsoid

positions = positions_ellipsoid (400, center=[0.0, 50.0, 0.0], height=50.0, x_length=100.0, z_length=200.0, plot=True)
_images/tutorial_cell_placement_8_0.png

Generate random locations according to an Nrrd file

We may want to place the units according to a file containing 3D location information, such as the Allen Brain Atlas Common Coordinate Framework. You can obtain a structural mask that describes the extent of a structure of interest. Cells will be placed randomly within that structure at a density of max_dens. If the *.nrrd file contains varying cell densities, this function can also be used to place cells randomly according to that density raster.

You will need to install the pynrrd library to use this function.

Note that the units of cell density are in cells/mm3 while positions are in units of microns.

If the structure is bilateral and only one half is desired for modeling, you can specify the axis along which to split it.

[5]:
from bmtk.builder.auxi.node_params import positions_nrrd
filename = './sources/nrrd/structure_721.nrrd'
max_dens = 100000       # Cells per mm^3

positions = positions_nrrd(filename, max_dens, plot=True)
positions: (102437, 3)
_images/tutorial_cell_placement_10_1.png
[6]:
positions = positions_nrrd(filename, max_dens, split_bilateral='z', plot=True)
positions: (51218, 3)
_images/tutorial_cell_placement_11_1.png

You can also directly use a 3-dimensional density matrix with the function positions_density_matrix to generate cell placements.

Cell Placements with minimum distance between units

The same built-in placement options above are available now with a minimum distance enforced between cells. With the use of the cell placement object, multiple populations can be placed at the same time, but must use the same minimum distance, which is enforced between and within populations.

For a given minimum distance, there is a maximum density above which it will be physically impossible to pack the units. The functions will inform users of the packing limit for random close packing or hexagonal close packing. The default method is progressive sampling (‘prog’), which iteratively replaces units that violate the minimum distance until the final density is achieved. As this can be slow to run when the requested density is near the packing limit, you may set verbose to True to monitor the progress. You may find that in such cases just slightly decreasing the dmin value significantly speeds up the run.

The alternative jittered lattice method (‘lattice’) may be faster at very high density and may allow you to slightly exceed the random close packing limit, but produces an unrealistic geometric regularity in order to achieve those densities.

[7]:
from bmtk.builder.auxi.node_params import CellLocations

filename = './sources/nrrd/structure_721.nrrd'

max_dens = 100000

ctx = CellLocations('cortex')
ctx.dmin = 18.0
ctx.CCF_orientation=True   # Use Allen Common Coordinate Framework orientation with y-axis as vertical

ctx.add_positions_nrrd(filename, max_dens, pop_names=['Pyr'], split_bilateral='z', method='prog', verbose=True)

ctx.plot_locs()
Density limit:206311.963
Max density requested:100000.000
5873/51218 cells placed
12009/51218 cells placed
17996/51218 cells placed
23656/51218 cells placed
28617/51218 cells placed
32939/51218 cells placed
36509/51218 cells placed
39492/51218 cells placed
41881/51218 cells placed
43671/51218 cells placed
45262/51218 cells placed
46550/51218 cells placed
47585/51218 cells placed
48535/51218 cells placed
49333/51218 cells placed
50050/51218 cells placed
50679/51218 cells placed
51226/51218 cells placed
51218 cells
positions: (51218, 3)
_images/tutorial_cell_placement_14_1.png

It is preferable to use partitioning (with the pop_names and partitions keywords) to place units by the same general rule into the same spatial structure. In the example below, the first call to add_positions_nrrd() assigns 85% of returned cells to Pyr and 15% to PV. However, you can also add placements iteratively (second call to add_positions_nrrd()).

[8]:
ctx = CellLocations('cortex')
ctx.dmin = 18.0
ctx.CCF_orientation=True   # Use Allen Common Coordinate Framework orientation with y-axis as vertical

ctx.add_positions_nrrd(filename, max_dens/2, pop_names=['Pyr','PV'], partitions=[0.85, 0.15], split_bilateral='z',
                      method='prog', verbose=False)

ctx.add_positions_nrrd(filename, max_dens/2, pop_names=['Pyr2'], split_bilateral='z', method='prog', verbose=False)

ctx.plot_locs()
Density limit:206311.963
Max density requested:50000.000
positions: (25609, 3)
Density limit:206311.963
Max density requested:50000.000
positions: (25609, 3)
_images/tutorial_cell_placement_16_1.png

Again, you can slightly exceed the above packing limit or speed up placement at densities close to the packing limit by using a jittered lattice method. However, be aware that the resulting placements will have an artifactual regularity.

[9]:
ctx = CellLocations('cortex')
ctx.dmin = 20.0
ctx.CCF_orientation=True   # Use Allen Common Coordinate Framework orientation with y-axis as vertical

ctx.add_positions_nrrd(filename, max_dens/2, pop_names=['Pyr','PV'], partitions=[0.85, 0.15], split_bilateral='z',
                      method='lattice', verbose=False)

ctx.plot_locs()
Density limit:176661.987
Max density requested:49999.268
positions: (25609, 3)
_images/tutorial_cell_placement_18_1.png

Here are some more examples with simple geometries, not necessarily all realistic!

[10]:
N=4000

ctx = CellLocations('ctx')
ctx.dmin = 18.0

ctx.add_positions_columnar(['Pyr','PV'], partitions=[0.85, 0.15], N=N, center=[0.0, 150.0, 0.0], height = 300.0,
                               min_radius = 50.0, max_radius = 200.0, method='prog', verbose=False)

ctx.plot_locs()
Density limit:206311.963
Max density requested:113176.848
_images/tutorial_cell_placement_20_1.png
[11]:
N=2500

ctx = CellLocations('cortex')
ctx.dmin = 18.0

ctx.add_positions_rect_prism(['Pyr','PV'], partitions=[0.85, 0.15], N=N, center=[0.0, 150.0, 0.0], height = 300.0,
                               x_length = 300.0, z_length = 400.0, method='prog', verbose=False)

ctx.add_positions_columnar('Pyr2', partitions=[1.0], N=N, center=[0.0, 50.0, 0.0], height = 300.0,
                               min_radius = 50.0, max_radius = 200.0, method='prog', verbose=False)

ctx.plot_locs()
Density limit:206311.963
Max density requested:69444.444
Density limit:206311.963
Max density requested:70735.530
_images/tutorial_cell_placement_21_1.png
[12]:
N=3000

ctx = CellLocations('cortex')
ctx.dmin = 18.0

ctx.add_positions_ellipsoid(['Pyr','PV'], partitions=[0.85, 0.15], N=N, center=[0.0, 150.0, 0.0], height = 300.0,
                               x_length = 300.0, z_length = 500.0, method='prog', verbose=False)
ctx.plot_locs()
Density limit:206311.963
Max density requested:127323.954
_images/tutorial_cell_placement_22_1.png

Once we are satisfied with the placement, we can pass them to add_nodes() as follows (based on the ellipsoid example directly above):

[13]:
from bmtk.builder.networks import NetworkBuilder

net = NetworkBuilder('cortex')
net.add_nodes(N=ctx.Pyr.N,
              positions=ctx.Pyr.positions,
              pop_name='Pyr', location='VisL4', ei='e',  # optional parameters
              model_type='point_process',  # Tells the simulator to use point-based neurons
              model_template='nest:iaf_psc_alpha',  # tells the simulator to use NEST iaf_psc_alpha models
              dynamics_params='472363762_point.json'  # File containing iaf_psc_alpha model parameters
             )

net.add_nodes(N=ctx.PV.N,
              positions=ctx.PV.positions,
              pop_name='PV', location='VisL4', ei='i',  # optional parameters
              model_type='point_process',  # Tells the simulator to use point-based neurons
              model_template='nest:iaf_psc_alpha',  # tells the simulator to use NEST iaf_psc_alpha models
              dynamics_params='472912177_point.json'  # File containing iaf_psc_alpha model parameters
             )