Skip to content

Commit e23adb6

Browse files
committed
Rejiggered how neurons handle synapses
Neurons now know much more about their synapses, removing the need for a separate synapse object. Might be a bit slow.
1 parent 3e27290 commit e23adb6

File tree

2 files changed

+140
-155
lines changed

2 files changed

+140
-155
lines changed

catmaid_interface.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,15 @@ def post_synaptic_count( connector_list, proj_opts ):
179179

180180
return nps
181181

182+
def get_connector_data( connector_list, proj_opts ):
183+
if len( connector_list ) > 0:
184+
url = proj_opts['baseurl'] + '/{}/connector/skeletons'.format(proj_opts['project_id'])
185+
opts = {}
186+
for ind, id in enumerate(connector_list):
187+
opts[ 'connector_ids[{}]'.format(ind) ] = id
188+
d = requests.post( url, data = opts, auth = catmaid_auth_token( proj_opts['token'], proj_opts['authname'], proj_opts['authpass'] )).json()
189+
return d
190+
182191
# write_skeletons_from_list: pull JSON files (plus key details) for
183192
def write_skeletons_from_list( id_list, proj_opts ):
184193
for id in id_list:

neuron_analytics.py

Lines changed: 131 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -76,42 +76,44 @@ def write_node_info(g, filename, delimiter=','):
7676
f_nodeinfo.write('\n')
7777
f_nodeinfo.close()
7878

79-
8079
class SynapseListObj:
81-
82-
def __init__(self, skdata, category, proj_opts):
83-
if category == 'pre':
84-
cat_ind = 0
85-
elif category == 'post':
86-
cat_ind = 1
87-
else:
88-
raise NameError('category should be pre or post only')
89-
90-
self.conn_ids = [dat[1] for dat in skdata[1] if dat[2] == cat_ind]
91-
self.conn_id2node_id = {dat[1]: dat[0]
92-
for dat in skdata[1] if dat[2] == cat_ind}
93-
self.conn_id2loc = {dat[1]: dat[3:6]
94-
for dat in skdata[1] if dat[2] == cat_ind}
95-
80+
def __init__(self, conndata ):
81+
self.conn_ids = [ dat[0] for dat in conndata ]
9682

9783
class InputSynapseListObj(SynapseListObj):
98-
99-
def __init__(self, skdata, proj_opts):
100-
SynapseListObj.__init__(self, skdata, 'post', proj_opts)
101-
84+
def __init__(self, conndata, self_id):
85+
SynapseListObj.__init__(self, conndata )
86+
self.target_node_ids = {}
87+
for dat in conndata:
88+
for skid, nid in zip( dat[1]['postsynaptic_to'], dat[1]['postsynaptic_to_node'] ):
89+
if skid == self_id:
90+
if dat[0] in self.target_node_ids:
91+
self.target_node_ids[ dat[0] ].append(nid)
92+
else:
93+
self.target_node_ids[ dat[0] ] = [nid]
94+
self.from_ids = { dat[0] : dat[1]['presynaptic_to'] for dat in conndata }
95+
self.from_node_ids = { dat[0] : dat[1]['presynaptic_to_node'] for dat in conndata }
10296
def num(self):
103-
return len(self.conn_ids)
104-
97+
return sum( map( lambda x: len(x), self.target_node_ids.values() ) )
10598

10699
class OutputSynapseListObj(SynapseListObj):
107-
108-
def __init__(self, skdata, proj_opts):
109-
SynapseListObj.__init__(self, skdata, 'pre', proj_opts)
110-
self.num_targets = {val[0]: val[1] for val in skdata[5]}
100+
def __init__(self, conndata, self_id):
101+
SynapseListObj.__init__(self, conndata )
102+
self.from_node_ids = {dat[0] : dat[1]['presynaptic_to_node'] for dat in conndata }
103+
self.target_ids = { dat[0] : dat[1]['postsynaptic_to'] for dat in conndata }
104+
self.target_node_ids = {dat[0] : dat[1]['postsynaptic_to_node'] for dat in conndata }
105+
106+
def num_targets( self ):
107+
return {id : len(self.target_ids[id]) for id in self.target_ids}
108+
109+
def num_targets_connector( self, conn_id):
110+
if conn_id in self.target_ids:
111+
return len( self.target_ids[conn_id])
112+
else:
113+
print( 'No such presynaptic connector id in neuron' )
111114

112115
def num(self):
113-
return sum( self.num_targets.values() )
114-
116+
return sum( self.num_targets().values() )
115117

116118
class NeuronObj:
117119

@@ -141,8 +143,10 @@ def __init__(self, skid, proj_opts):
141143
self.Ab[self.node2ind[key], self.node2ind[
142144
self.nodeparent[key]]] = 1
143145

144-
self.inputs = InputSynapseListObj(skdata, proj_opts)
145-
self.outputs = OutputSynapseListObj(skdata, proj_opts)
146+
pre_conn_ids = [dat[1] for dat in skdata[1] if dat[2] == 0]
147+
post_conn_ids = [dat[1] for dat in skdata[1] if dat[2] == 1]
148+
self.inputs = InputSynapseListObj( ci.get_connector_data( post_conn_ids, proj_opts ), self.id )
149+
self.outputs = OutputSynapseListObj( ci.get_connector_data( pre_conn_ids, proj_opts ), self.id )
146150

147151
def __str__( self ):
148152
return self.name
@@ -163,118 +167,122 @@ def get_url_to_node( self, nodeid, proj_opts, printflag=True, zoomlevel = 0 ):
163167
else:
164168
return ur
165169

170+
def find_end_nodes(self):
171+
# Returns a list of node ids that are end nodes (have no children)
172+
y = np.where(self.Ab.sum(0) == 0)[1]
173+
return [self.nodeids[ind] for ind in y]
174+
175+
def find_branch_points(self):
176+
# Returns a list of node ids that are branch points (have multiple children)
177+
y = np.where(self.Ab.sum(0) > 1)[1]
178+
return [self.nodeids[ind] for ind in y]
179+
180+
def minimal_paths(self):
181+
# Returns list of lists, the minimally overlapping paths from each end
182+
# point toward root
183+
D = dist_to_root(self)
184+
ids_end = self.find_end_nodes()
185+
186+
ends_sorted = [ids_end[ind] for ind in np.argsort(
187+
D[[self.node2ind[id] for id in ids_end]])[::-1]]
188+
not_visited = [True] * len(self.nodeids)
189+
min_paths = []
190+
191+
for start_nd in ends_sorted:
192+
nd = start_nd
193+
min_paths.append([nd]) # Start a new list with this end as a seed
194+
while not_visited[self.node2ind[nd]] and (self.nodeparent[nd] is not None):
195+
not_visited[self.node2ind[nd]] = False
196+
nd = self.nodeparent[nd]
197+
min_paths[-1].append(nd)
198+
199+
return min_paths
200+
201+
def strahler_number(self):
202+
# Computes strahler number for a neuron
203+
paths = self.minimal_paths()
204+
sn = {}
205+
for nid in self.nodeids:
206+
sn[nid] = 0
207+
208+
for path in paths[::-1]:
209+
sn[path[0]] = 1
210+
for ii, nid in enumerate(path[1:]):
211+
if sn[nid] == sn[path[ii]]:
212+
sn[nid] = sn[path[ii]] + 1
213+
else:
214+
sn[nid] = sn[path[ii]]
215+
return sn
216+
217+
def split_into_components(self, nids, from_parent=True):
218+
# Return n-component list, each element is a list of node ids in the component.
219+
# nids is a list of child nodes that will be split from their parent node.
220+
# if from_parent is toggled false, parents divorce childen and not the
221+
# default.
222+
Ab_sp = copy.deepcopy(self.Ab)
223+
224+
if from_parent:
225+
for id in nids:
226+
nind = self.node2ind[id]
227+
Ab_sp[:, nind] = 0
228+
else:
229+
for id in nids:
230+
nind = self.node2ind[id]
231+
Ab_sp[nind, :] = 0
166232

233+
ncmp, cmp_label = csgraph.connected_components(Ab_sp, directed=False)
167234

168-
class SynapseDict:
169-
170-
def __init__(self, neuron_list):
171-
self.pre_skid = dict()
172-
self.post_skid = defaultdict(list)
173-
for neuron in neuron_list:
174-
rel_skid = neuron.id
175-
for connid in neuron.inputs.conn_ids:
176-
self.post_skid[connid].append(rel_skid)
177-
for connid in neuron.outputs.conn_ids:
178-
self.pre_skid[connid] = rel_skid
179-
180-
def update_synapse_dict(self, neuron_list):
181-
for neuron in neuron_list:
182-
rel_skid = neuron.id
183-
for connid in neuron.inputs.conn_ids:
184-
self.post_skid[connid].append(rel_skid)
185-
for connid in neuron.outputs.conn_ids:
186-
self.pre_skid[connid] = rel_skid
187-
188-
189-
def dist_to_root(nrn):
190-
# Returns distance to root for each node in nrn as an array
191-
D = csgraph.shortest_path(
192-
nrn.A.transpose(), directed=True, unweighted=False, method='D')
193-
return D[nrn.node2ind[nrn.root]]
194-
195-
196-
def find_end_nodes(nrn):
197-
# Returns a list of node ids that are end nodes (have no children)
198-
y = np.where(nrn.Ab.sum(0) == 0)[1]
199-
return [nrn.nodeids[ind] for ind in y]
200-
201-
def find_branch_points(nrn):
202-
# Returns a list of node ids that are branch points (have multiple children)
203-
y = np.where(nrn.Ab.sum(0) > 1)[1]
204-
return [nrn.nodeids[ind] for ind in y]
235+
cmps = list()
236+
for cmp_val in range(ncmp):
237+
comp_inds = np.where(cmp_label == cmp_val)
238+
cmps.append([self.nodeids[ind] for ind in comp_inds[0]])
205239

240+
cmp_label_dict = {self.nodeids[ind]:cmp for ind,cmp in enumerate(cmp_label) }
206241

207-
def minimal_paths(nrn):
208-
# Returns list of lists, the minimally overlapping paths from each end
209-
# point toward root
210-
D = dist_to_root(nrn)
211-
ids_end = find_end_nodes(nrn)
242+
return cmps, cmp_label_dict
212243

213-
ends_sorted = [ids_end[ind] for ind in np.argsort(
214-
D[[nrn.node2ind[id] for id in ids_end]])[::-1]]
215-
not_visited = [True] * len(nrn.nodeids)
216-
min_paths = []
244+
def dist_to_root(self):
245+
# Returns distance to root for each node in nrn as an array
246+
D = csgraph.shortest_path(
247+
self.A.transpose(), directed=True, unweighted=False, method='D')
248+
return D[self.node2ind[self.root]]
217249

218-
for start_nd in ends_sorted:
219-
nd = start_nd
220-
min_paths.append([nd]) # Start a new list with this end as a seed
221-
while not_visited[nrn.node2ind[nd]] and (nrn.nodeparent[nd] is not None):
222-
not_visited[nrn.node2ind[nd]] = False
223-
nd = nrn.nodeparent[nd]
224-
min_paths[-1].append(nd)
250+
def split_by_tag( self, tag_str ):
251+
nids = self.tags[ tag_str ]
252+
cmps, cmp_label = self.split_into_components( nids )
253+
return cmps, cmp_label
225254

226-
return min_paths
255+
class SynapseObject:
256+
def __init__(self, conn_ids, proj_opts ):
257+
conndata = ci.get_connector_data( post_conn_ids, proj_opts )
258+
self.connectors = { dat[0] : dat[1] for dat in conndata }
227259

228-
def neurons_from_annotations( annotation_list, proj_opts, syns=None):
260+
def neurons_from_annotations( annotation_list, proj_opts ):
229261
anno_dict = ci.get_annotation_dict( proj_opts )
230262
id_list = ci.get_ids_from_annotation( [anno_dict[anno] for anno in annotation_list], proj_opts )
231-
(neurons, syns) = neurons_from_id_list( id_list, proj_opts, syns=syns)
232-
return (neurons, syns)
263+
neurons = neurons_from_id_list( id_list, proj_opts )
264+
return neurons
233265

234-
def neurons_from_id_list(id_list, proj_opts, syns=None):
235-
# Given a list of ids, build a list of NeuronObj and a SynapseDict
266+
def neurons_from_id_list(id_list, proj_opts ):
267+
# Given a list of ids, build a list of NeuronObjs
236268
# associated with them, if one is already pre-existing
237269
neurons = [NeuronObj(id, proj_opts) for id in id_list]
270+
return neurons
238271

239-
if syns is None:
240-
syns = SynapseDict(neurons)
241-
else:
242-
syns.update_synapse_dict(neurons)
243-
244-
return (neurons, syns)
245-
246-
247-
def strahler_number(neuron):
248-
# Computes strahler number for a neuron
249-
paths = minimal_paths(neuron)
250-
sn = {}
251-
for nid in neuron.nodeids:
252-
sn[nid] = 0
253-
254-
for path in paths[::-1]:
255-
sn[path[0]] = 1
256-
for ii, nid in enumerate(path[1:]):
257-
if sn[nid] == sn[path[ii]]:
258-
sn[nid] = sn[path[ii]] + 1
259-
else:
260-
sn[nid] = sn[path[ii]]
261-
262-
return sn
263-
264-
def get_adjacency_matrix( neurons, syns, input_normalized = False ):
272+
def get_adjacency_matrix( neurons, input_normalized = False ):
265273
# Build a weighted adjacency matrix from neurons
266274
A = np.zeros( (len(neurons), len(neurons)) )
267275
skid_to_ind = { skid:ii for ii, skid in enumerate([nrn.id for nrn in neurons])}
268276
ind_to_skid = { ii:skid for ii, skid in enumerate([nrn.id for nrn in neurons])}
269-
277+
ids = [nrn.id for nrn in neurons]
270278
for nrn in neurons:
271-
for conn_id in nrn.outputs.conn_ids:
272-
for targ in syns.post_skid[ conn_id ]:
273-
if input_normalized is True:
274-
A[ skid_to_ind[ targ ], skid_to_ind[ nrn.id ]] += 1.0 / neurons[ skid_to_ind[ targ ] ].inputs.num()
275-
else:
276-
A[ skid_to_ind[ targ ], skid_to_ind[ nrn.id ]] += 1
277-
279+
for conn_id in nrn.outputs.target_ids:
280+
for targ in nrn.outputs.target_ids[conn_id]:
281+
if targ in ids:
282+
if input_normalized is True:
283+
A[ skid_to_ind[ targ ], skid_to_ind[ nrn.id ]] += 1.0 / neurons[ skid_to_ind[ targ ] ].inputs.num()
284+
else:
285+
A[ skid_to_ind[ targ ], skid_to_ind[ nrn.id ]] += 1
278286
return A, skid_to_ind, ind_to_skid
279287

280288
def group_adjacency_matrix( neurons, syns, groups, func=np.sum ):
@@ -288,11 +296,6 @@ def group_adjacency_matrix( neurons, syns, groups, func=np.sum ):
288296
Agr[ ii, jj ] = func( Ared )
289297
return Agr
290298

291-
def split_neuron_by_tag( neuron, tag_str ):
292-
nids = neuron.tags[ tag_str ]
293-
cmps, cmp_label = split_neuron_into_components( neuron, nids )
294-
return cmps, cmp_label
295-
296299
def sort_neurons_by( neurons, sort_vector ):
297300
if len( sort_vector ) != len( neurons ):
298301
print( 'Vector must be same length as neurons' )
@@ -310,30 +313,3 @@ def number_inputs( neurons ):
310313

311314
def number_outputs( neurons ):
312315
return [nrn.outputs.num() for nrn in neurons]
313-
314-
def split_neuron_into_components(neuron, nids, from_parent=True):
315-
# Return n-component list, each element is a list of node ids in the component.
316-
# nids is a list of child nodes that will be split from their parent node.
317-
# if from_parent is toggled false, parents divorce childen and not the
318-
# default.
319-
Ab_sp = copy.deepcopy(neuron.Ab)
320-
321-
if from_parent:
322-
for id in nids:
323-
nind = neuron.node2ind[id]
324-
Ab_sp[:, nind] = 0
325-
else:
326-
for id in nids:
327-
nind = neuron.node2ind[id]
328-
Ab_sp[nind, :] = 0
329-
330-
ncmp, cmp_label = csgraph.connected_components(Ab_sp, directed=False)
331-
332-
cmps = list()
333-
for cmp_val in range(ncmp):
334-
comp_inds = np.where(cmp_label == cmp_val)
335-
cmps.append([neuron.nodeids[ind] for ind in comp_inds[0]])
336-
337-
cmp_label_dict = {neuron.nodeids[ind]:cmp for ind,cmp in enumerate(cmp_label) }
338-
339-
return cmps, cmp_label_dict

0 commit comments

Comments
 (0)