generating KlustaKwik graphs from the geometries

Justin Kiggins, May 2015 ([email protected] / @neuromusic)

Very large arrays make it unrealistic to manually define the probe architectures that SpikeDetekt2 expects. This example builds a channel_groups dictionary for a large array. It was inspired by the dense silicon arrays designed by Ed Boyden's group:

Close-Packed Silicon Microelectrodes for Scalable Spatially Oversampled Neural Recording

5 shanks with 200 sites each and just 11 microns pitch between sites

first, we need to be able to convert a geometry into a graph

we'll define a function which takes the geometry dict, extracts the coordinates, and uses the Delaunary transformation to generate a triangular tesselation. It then returns this graph in the form that SpikeDetekt2 expects

In [1]:
from scipy import spatial
def get_graph_from_geometry(geometry):
    
    # let's transform the geometry into lists of channel names and coordinates
    chans,coords = zip(*[(ch,xy) for ch,xy in geometry.iteritems()])
    
    # we'll perform the triangulation and extract the 
    tri = spatial.Delaunay(coords)
    
    # then build the list of edges from the triangulation
    indices, indptr = tri.vertex_neighbor_vertices
    edges = []
    for k in range(indices.shape[0]-1):
        for j in indptr[indices[k]:indices[k+1]]:
            edges.append((chans[k],chans[j]))
    return edges

Now, we can build the probe definition from a few basic parameters

This is suitable to write directly to a *.prb file for SpikeDetekt2

In [2]:
w = 2 # the width of the grid of sites
l = 100 # the length of the grid of sites
n_shanks = 5 # the number of shanks
pitch = 11 # site spacing in microns

channel_groups = {}
for sh in range(n_shanks):
    # define the channels on this shank
    channels = [sh*w*l+ch for ch in range(w*l)]
    
    # get the physical coordinates by including the spacing
    row_col = [(ch/w,ch%w) for ch in [c%(w*l) for c in channels]]
    coords = [(pitch*c, pitch*(l-1)-pitch*r) for r,c in row_col]
    
    # then  assign these to the geometry dictionary
    geometry = {ch:xy for ch,xy in zip(channels,coords)}

    # finally, use our function from above to define the graph
    graph = get_graph_from_geometry(geometry)

    channel_groups[sh] = {
        'channels': channels,
        'graph': graph,
        'geometry': geometry,
    }

Did it work?

Let's plot it.

In [3]:
%pylab inline
import seaborn as sns
sns.set_context(rc={'lines.markeredgewidth': 0.1})
sns.set_style('white')

f,ax = plt.subplots(1,n_shanks)
for sh in range(n_shanks):
    coords = [xy for ch,xy in channel_groups[sh]['geometry'].iteritems()]
    
    for pr in channel_groups[sh]['graph']:
        points = [channel_groups[sh]['geometry'][p] for p in pr]
        ax[sh].plot(*zip(*points),color='k',alpha=0.2)
        
    ax[sh].set_xlim(-5,pitch*(w-1)+5)
    ax[sh].set_ylim(-5,pitch*(w-1)*n_shanks+5)
    ax[sh].set_xticks([])
    ax[sh].set_yticks([])
    ax[sh].set_title('shank %i'%sh)
    ax[sh].scatter(*zip(*coords),color='0.2')
    sns.despine()
Populating the interactive namespace from numpy and matplotlib