SeedMea.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 '''
00004 SeedMea.py
00005 Copyright 2013 University of Massachusetts Lowell
00006 Author: Abraham Shultz, Jonathan Hasenzahl
00007 '''
00008 
00009 ## @package SeedMea
00010 #
00011 # This is a ROS node that is used by itself to generate network connectivity
00012 # pickle files for the brian_recv.py and brian_to_csv.py nodes. This node creates
00013 # the connectivity maps, and those nodes run the actual simulation. You only need
00014 # to run this node if you do not yet have pickle files or you want to generate
00015 # new ones.
00016 #
00017 # More info:
00018 # 
00019 # Generates a connectivity map by simulating growth of seeded neuron cells. 
00020 # 
00021 # The growth simulation is a three-stage process:
00022 # 1. Determine the cell density for each region of the MEA.
00023 # 2. Assign cells to locations based on density. 
00024 # 3. Connect the cells based on a connectivity function. 
00025 #
00026 # Cell density is assigned based on random midpoint displacement fractals, aka "plasma fractals". 
00027 # This approach was chosen because it provides a stochastic approach that is computationally 
00028 # efficient. If it is not a good model of the actual distribution of neurons, a different algorithm
00029 # can be used. 
00030 #
00031 # Cells are probabilistically assigned to locations based on the cell density (actually cell probability)
00032 # at that point. Cells are modeled as point locations. 
00033 #
00034 # For each pair of cells, the probability of their connection is determined based on the distance 
00035 # between the cells and the cells are connected accordingly. Note that this results in N(N-1) comparisons, 
00036 # so roughly n^2 connections. Put that in your NP pipe and smoke it. 
00037 #
00038 # @copyright Copyright 2013 University of Massachusetts Lowell
00039 # @author Abraham Shultz
00040 # @author Jonathan Hasenzahl
00041 
00042 import roslib; roslib.load_manifest('neuro_recv')
00043 import rospy
00044 import math
00045 import numpy as np
00046 import networkx as nx
00047 import pygame
00048 from pickle import Pickler
00049 import datetime
00050 from brian import *
00051 import random
00052 from pprint import pprint
00053 import Image
00054 
00055 
00056 ## @brief Data structure for a MEA channel pad
00057 #  neurons: list of neurons within range of the pad
00058 #  total_weight: combined weights of all the neurons
00059 class Channel():
00060     def __init__(self):
00061         self.neurons = []
00062         self.total_weight = 0.0
00063         
00064     def __str__(self):
00065         name = 'Weight:' + str(self.total_weight)
00066         if len(self.neurons) == 0:
00067             name += '[No neurons]'
00068         else:
00069             for neuron in self.neurons:
00070                 name += '[' + str(neuron) + ']'
00071         return name
00072         
00073     def add(self, neuron):
00074         self.neurons.append(neuron)
00075         self.total_weight += neuron.weight
00076         
00077     def size(self):
00078         return len(self.neurons)
00079 
00080 ## @brief Data structure for a neuron that is close to a channel pad.
00081 #   data: unique id of the neuron
00082 #   weight: distance from the center of the pad
00083 class CloseNeuron():
00084     def __init__(self, data, dist):
00085         self.data = data
00086         self.weight = 40.0 - dist
00087         
00088     def __str__(self):
00089         return str(self.data) + ':' + str(self.weight)
00090 
00091 
00092 ## Takes a 2-D Numpy array and fill it in with a density map. The map values are in the range 
00093 #  0-1 and describe the probability of there being a cell soma located at that location. 
00094 #  1 is a cell, 0 is no cell. 
00095 def genPCell(density_map):
00096     #Assign a random value to each corner of the array
00097     max_x, max_y = density_map.shape
00098     random.seed()
00099     
00100     #Set corners to random values
00101     density_map[0][0] = random.random()
00102     density_map[max_x -1][0] = random.random()
00103     density_map[0][max_y - 1] = random.random()
00104     density_map[max_x-1][max_y-1] = random.random()
00105     
00106     #perform recursive plasma fractal generation
00107     squarePlasma(density_map)
00108     
00109 def displace(size, originalSize):
00110     max = (float(size)/float(originalSize)) * 3.00
00111     displacement = (random.random() - 0.5) * max
00112     return displacement
00113 
00114 def diSqPlasma(array):
00115     pass
00116 
00117 def squarePlasma(array):
00118     if array.shape == (2,2):
00119         return
00120     
00121     #Calculate midpoint values
00122     height, width = array.shape
00123     array[height/2][width-1] = (array[0][width-1] + array[height-1][width-1])/2
00124     array[height/2][0] = (array[0][0] + array[height-1][0])/2
00125     array[0][width/2] = (array[0][0] + array[0][width-1])/2
00126     array[height-1][width/2] = (array[height-1][0] + array[height-1][width-1])/2
00127     
00128     #Calculate center value
00129     center = 0
00130     center += array[height/2][width-1]
00131     center += array[height/2][0]
00132     center += array[0][width/2]
00133     center += array[height-1][width/2]
00134     center = center/4
00135     #Add displacement
00136     center += displace(height+width, 257*2)
00137     if center > 1:
00138         center = 1
00139     if center < 0:
00140         center = 0;
00141     #Set center value 
00142     array[height/2][width/2] = center
00143     
00144     #perform this step on sub-arrays
00145     squarePlasma(array[0:height/2+1,0:width/2+1])
00146     squarePlasma(array[0:height/2+1,width/2:width])
00147     squarePlasma(array[height/2:height,0:width/2+1])
00148     squarePlasma(array[height/2:height,width/2:width])
00149     
00150 ## Find the next highest power of two, to get a shape that's good for
00151 # plasma fractal generation    
00152 def nextPowTwo(start):
00153     y = math.floor(math.log(start,2))
00154     return int(math.pow(2, y+1))
00155 
00156 ## Calculate the probability of a connection between two neurons, 
00157 #  based on their distance apart.  
00158 #  Generates a value from 0 to 1, centered at 
00159 def gaussConnect(distance, center):
00160     return math.e - ((distance - center)**2)/(2*center/2)**2
00161 
00162 ## Use pygame to render an array. The result is an image of the same dimensionality as the
00163 #  array, with each pixel set to 255*the value of the corresponding array location. 
00164 def renderMap(densityData, title):
00165     if densityData.ndim != 2:
00166         print "Can only render 2-D arrays"
00167     
00168     
00169     #Display the image
00170     win = pygame.display.set_mode(densityData.shape)
00171     win.fill((0,0,0))
00172     pygame.display.set_caption(title)
00173     screen = pygame.display.get_surface()
00174     color = densityData * 255
00175     color = color.astype(int)
00176     pygame.surfarray.blit_array(screen, color)
00177     pygame.display.flip()
00178     
00179     #Write the array to an image
00180     pic = Image.fromarray(color.astype(float))
00181     pic.convert('RGB').save("{0}.png".format(title), "PNG")
00182     
00183     while 1:
00184         for event in pygame.event.get(): 
00185             if event.type == pygame.QUIT:
00186                 return
00187 
00188 def grimReap(density_map, survival_rate):
00189     #For each cell, give it a survival_rate % chance of getting killed
00190     x_max, y_max = density_map.shape
00191     for ii in xrange(0,x_max):
00192         for jj in xrange(0, y_max):
00193             if random.random() > survival_rate/100.00:
00194                 density_map[ii][jj] = 0
00195                 
00196 def densityReap(density_map, expected_cells):
00197     #Count the cells
00198     total_cells = 0
00199     for cell in density_map.flat:
00200         total_cells += cell
00201     #Decrease the surplus population
00202     surplus = total_cells - expected_cells
00203     print "total: ", total_cells
00204     print "expected: ", expected_cells
00205     print "surplus: ", surplus
00206     x_max, y_max = density_map.shape
00207     #Pick a random location in the dish, and if there is a cell there,
00208     #kill it. Repeat until the surplus population is gone
00209     while surplus > 0:
00210         #pick a random location
00211         rand_X = random.randint(0,x_max-1)
00212         rand_Y = random.randint(0,y_max-1)
00213         if density_map[rand_X][rand_Y] == 1:
00214             density_map[rand_X][rand_Y] = 0
00215             surplus -= 1
00216 
00217 def countCells(density_map):
00218     #Count the cells
00219     total_cells = 0
00220     for cell in density_map.flat:
00221         total_cells += cell
00222     return total_cells
00223         
00224 if __name__ == '__main__':
00225     # Make this a ROS node so it can be run like other nodes even though it
00226     # doesn't do any ROS stuff
00227     rospy.init_node("seed_mea")
00228     
00229     #Set up some defaults, measurements in um
00230     soma_dia = 30
00231     dish_width = 2500 
00232     dendrite_max = 180
00233     #Distance of maximum connections
00234     axon_growth = 220
00235     #Axons cannot reach out of dish
00236     axon_max = dish_width
00237     #Cell survival rate, as percentage of plated cells
00238     survival_rate = 65
00239     #Cell connectivity, as percentage of total connections
00240     connectivity_rate = 55
00241     #Cell density in cells/mm^2
00242     cell_density = 900
00243     
00244     #Assume neurons are square, don't pile up
00245     #Create a list of lists representing possible cell locations
00246     cells_per_edge = int(dish_width/soma_dia)
00247     
00248     #Bump edge length up to make plasma fractal calculation easy
00249     edge_len = nextPowTwo(cells_per_edge) + 1
00250     
00251     #Calculate cell distribution probability
00252     density_map = np.zeros((edge_len, edge_len))
00253     genPCell(density_map)
00254     renderMap(density_map, "Plasma")
00255     
00256     #Convert probabilities of cells into presence or absence
00257     #Iterating the data (e.g "for row in density_map") gets 
00258     #copies, iterating the indexes gets references
00259     for row in xrange(edge_len):
00260         for col in xrange(edge_len):
00261             if random.random() < density_map[row][col]:
00262                 density_map[row][col] = 1
00263             else:
00264                 density_map[row][col] = 0
00265     renderMap(density_map, "Cells")
00266     
00267     #Reap cells to configured density
00268     #Convert to mm^2 and multiply by density to get expected value
00269     expected_cells = (dish_width/1000)**2 * cell_density
00270     densityReap(density_map, expected_cells)
00271     renderMap(density_map, "Density Corrected")
00272                 
00273     #Kill off some percentage of the cells to reflect cell death. 
00274     #Can lose 45-55% of the cells by 17 days in vitro, which is about the mature level
00275     grimReap(density_map, survival_rate)
00276     renderMap(density_map, "Cull the Weak")
00277     
00278     #At this point, each element of the density map represents 
00279     #a soma-sized patch of space that either does or does not contain
00280     #the soma of a cell.
00281     print "Cells remaining:", countCells(density_map)
00282     
00283     #Calculate connections
00284     #Connections are stored as a list of tuples, (from neuron, to neuron)
00285     print "Building connectivity...",
00286     neuron_list = []
00287     neuron_id = 0
00288     for row_1 in xrange(edge_len):
00289         for col_1 in xrange(edge_len):
00290             if density_map[row_1][col_1] == 1:
00291                 #If there is a neuron at the current position store its location
00292                 #and an ID, incrementing the ID
00293                 neuron_list.append(((row_1, col_1), neuron_id))
00294                 neuron_id += 1
00295     #Now have a list of neurons, their locations, and their IDs. Run through it 
00296     #and connect the neurons based on pairwise probability of connection
00297     conn_list = []
00298     for from_neuron in neuron_list:
00299         for to_neuron in neuron_list:
00300             #Calculate distance. Grid units are soma diameters, so multiply by that to get real distance
00301             distance = math.sqrt((from_neuron[0][0] - to_neuron[0][0])**2 + (from_neuron[0][1] - to_neuron[0][1])**2)
00302             distance *= soma_dia
00303             if random.random() < gaussConnect(distance, axon_growth):
00304                 #Just note that they are connected
00305                 conn_list.append((from_neuron[1], to_neuron[1]))
00306                 
00307     print "done."
00308     
00309     #Done building terrible huge list with a time consuming process. Pickle the results.
00310     print "Saving connectivity...", 
00311     now = datetime.datetime.now()
00312     file_date = "{0}-{1}-{2}-{3}:{4}:{5}".format(now.year, now.month, now.day, now.hour, now.minute, now.second) 
00313     
00314     #Build a networkx graph
00315     conn_graph = nx.DiGraph()
00316     for connection in conn_list:
00317         conn_graph.add_edge(connection[0], connection[1])
00318     
00319     #Save the networkx graph
00320     with open("nx_graph_{0}.pickle".format(file_date), "w") as outfile:
00321         Pickler(outfile, 0).dump(conn_graph)
00322         outfile.close()
00323 
00324     #Save the connectivity list
00325     with open("connections_{0}.pickle".format(file_date), "w") as outfile:
00326         Pickler(outfile, 0).dump(conn_list)
00327         outfile.close()
00328         
00329     print "done."
00330     
00331     #Generate a Brian environment based on the connectivity 
00332           
00333     #LIF model, different time constants for excitory and inhibitory
00334     eqs = '''
00335     dv/dt = (ge+gi-(v+49*mV))/(20*ms) : volt
00336     dge/dt = -ge/(5*ms) : volt
00337     dgi/dt = -gi/(10*ms) : volt
00338     '''
00339     
00340     #Set up neuron population and initial voltage
00341     neuron_count = int(countCells(density_map))
00342     P = NeuronGroup(neuron_count, eqs, threshold=-50*mV, reset=-60*mV)
00343     P.v = -60*mV + 15 * mV * rand(len(P)) 
00344     
00345     #Partition population based on assumption of 25% inhibitory, 75% excitatory
00346     excitory_count = int(neuron_count * 0.75)
00347     inhib_count = neuron_count - excitory_count
00348     #Pe = P.subgroup(excitory_count)
00349     #Pi = P.subgroup(inhib_count)
00350     
00351     #Set up connections. First, pick a random set of inhibitory neuron ids. 
00352     #Everything else is excitatory. Then create the connection objects and 
00353     #populate them according to the connectivity list
00354     inhib_neurons = random.sample(xrange(neuron_count), inhib_count)
00355     Ce = Connection (P, P, 'ge')
00356     Ci = Connection (P, P, 'gi')
00357     for connection in conn_list:
00358         if connection[0] in inhib_neurons:
00359             #Need to subtract the count because first parameter is its index in Pi, not P
00360             Ci[connection[0], connection[1]] = -9*mV
00361         else:
00362             #print "{0} -> {1}".format(connection[0], connection[1])
00363             Ce[connection[0], connection[1]] = 1.62*mV
00364     
00365     #Determine which neurons have MEA pads under them. 
00366     #These are the ones that will be monitored when the simulation runs. 
00367     #For each pad, calculate the location of the pad in grid coordinates
00368     pad_rows = 8
00369     pad_cols = 8
00370     ignore_corners = True #Corner electrodes are frequently used as reference points, not recorded
00371     pad_dia = 30 #In um
00372     pad_spacing = 200 #again, in um, center to center
00373     pad_threshold = 40 #In um; how far a neuron can be from the center of a pad
00374     
00375     #For each pad, check the neurons to find all that are within a soma diameter
00376     #of the pad. Those neurons are used to record at that pad when the
00377     #simulation runs.
00378     pad_neuron_map = {}
00379     for pad_x in xrange(0,pad_rows):
00380         for pad_y in xrange (0,pad_cols):
00381             if ignore_corners:
00382                 if pad_x == 0 and pad_y == 0:
00383                     continue
00384                 elif pad_x == 0 and pad_y == (pad_cols - 1):
00385                     continue
00386                 elif pad_x == (pad_rows - 1) and pad_y == 0:
00387                     continue
00388                 elif pad_x == (pad_rows - 1) and pad_y == (pad_cols - 1):
00389                     continue 
00390                 
00391             #Calculate the pad location in um
00392             pad_x_loc = pad_x * pad_spacing
00393             pad_y_loc = pad_y * pad_spacing
00394              
00395             #For each neuron, determine how close it is to the pad
00396             close_neurons = Channel()
00397             for neuron in neuron_list:
00398                 #Neuron grid is in soma diameters, convert to um
00399                 neuron_x_loc = neuron[0][0] * soma_dia
00400                 neuron_y_loc = neuron[0][1] * soma_dia
00401                 #Calculate distance from center of pad
00402                 dist = math.sqrt((neuron_x_loc - pad_x_loc)**2 + (neuron_y_loc - pad_y_loc)**2)
00403                 #
00404                 if dist < pad_threshold:
00405                     #Close enough to influence pad
00406                     close_neurons.add(CloseNeuron(neuron[1], dist))
00407                     
00408             if close_neurons.size() == 0:
00409                 pad_neuron_map[(pad_x, pad_y)] = None
00410                 print '(' + str(pad_x) + ',' + str(pad_y) + '): None'
00411             else:
00412                 pad_neuron_map[(pad_x, pad_y)] = close_neurons
00413                 print '(' + str(pad_x) + ',' + str(pad_y) + '):' + str(close_neurons)
00414     #pprint(pad_neuron_map)
00415     
00416     #Save the pad neuron map list
00417     with open("pad_{0}.pickle".format(file_date), "w") as outfile:
00418         Pickler(outfile, 0).dump(pad_neuron_map)
00419         outfile.close()
00420     
00421     print "done."


neuro_recv
Author(s): Jonathan Hasenzahl
autogenerated on Sun Jan 5 2014 11:12:29