navis-org / navis

Python library for analysis of neuroanatomical data.
https://navis-org.github.io/navis/
GNU General Public License v3.0
82 stars 33 forks source link

New feature: split connectivity #92

Open schlegelp opened 2 years ago

schlegelp commented 2 years ago

Occasionally I find myself splitting neurons into axon & dendrites and then do the same with connectivity.

While this is perfectly possible using Navis, it requires a fair bit of boiler plate. Would be nice to have a dedicated function. Perhaps something more general like a function that takes pre->post synapses and neuron splits (axon/dendrite, by compartment, etc) and returns a compartmentalized connectivity table?

clbarnes commented 2 years ago

How about something like:

def split_connectivity(
    pre: list[TreeNeuron],
    post: list[TreeNeuron] | None,
    split_fn: Callable[[TreeNeuron], dict[Any, TreeNeuron]]
) -> pd.DataFrame:
    pre_compartments = dict()
    for n in pre:
        compartment_to_nrn = split_fn(n)
        for compartment, nrn in split_fn(n).items():
            pre_compartments[(n.id, compartment)] = nrn

    if post is None:
        post_compartments = pre_compartments
    else:
        ...
        # same as above

    data = pd.DataFrame(
        np.array([[None] * len(post_compartments)] * pre_compartments),
        index=pre_compartments.keys(),
        columns=post_compartments.keys()
    )

    for (pre_key, pre_nrn), (post_key, post_nrn) in itertools.product(pre_compartments.items(), post_compartments.items()):
        adj = make_connectivity_matrix(post_name, post_nrn)  # whatever the current function is called...
        data[pre_key, post_key] = adj
    return adj

I think the split_fn is the thing which saves the most boilerplate while providing a lot of flexibility in how people may want to split their neurons. We could always accept some magic strings to use e.g. the default split_axon_dendrite arguments.

schlegelp commented 2 years ago

Yes something along those lines is what I had in mind. It boils down to a three-step process:

  1. Associate synaptic connections with a node/vertex on the pre- and postsynaptic neuron, respectively. This step can be skipped if the connectivity data already has that info (e.g. with CATMAID data).
  2. Have a function that assigns compartments (e.g. 'axon' or 'dendrite').
  3. Divvy up the connectivity on the different compartments.

I would have probably gone for a class design to be even more flexible.

clbarnes commented 2 years ago

Usually in favour of using classes, so sure! It might need to be a little more complex than the above to handle "compartments" which aren't necessarily contiguous. You could build a TreeNeuron on top of a graph which is a forest but that would probably break some validation steps elsewhere. Probably better, as you say, to just label nodes and then treat labels as a compartment (if people want to handle e.g. multiple dendrites then they can just do "dendrite-1", "dendrite-2" etc. in their split function).