Documentation

pymaid

pymaid (python-catmaid) lets you interface with a CATMAID server such as those provided by VFB.

27 Dec 2021

Overview

pymaid lets you interface with a CATMAID server. It’s built on top of navis and returns data (neurons, volumes) in a way that you can plug them straight into navis to use features such as plotting.

Official documentation here.

Connecting

The VFB CATMAID servers (see here for what’s available) are public and don’t require an API token for read-only access which makes connecting simple:

import pymaid
import navis

navis.set_pbars(jupyter=False)
pymaid.set_pbars(jupyter=False)

# Connect to the VFB CATMAID server hosting the FAFB data
rm = pymaid.connect_catmaid(server="https://fafb.catmaid.virtualflybrain.org/", api_token=None, max_threads=10)

# Test call to see if connection works 
print(f'Server is running CATMAID version {rm.catmaid_version}')
WARNING: Could not load OpenGL library.
INFO  : Global CATMAID instance set. Caching is ON. (pymaid)
Server is running CATMAID version 2020.02.15-905-g93a969b37

Retrieving neurons

Let’s start with pulling a neuron based on its ID:

# Find a neuron from its ID (16) -> this is an olfactory projection neuron
n = pymaid.get_neurons(16)
n

typeCatmaidNeuron
nameUniglomerular mALT VA6 adPN 017 DB
id16
n_nodes16840
n_connectors2158
n_branches1172
n_leafs1230
cable_length4003103.232861
soma[2941309]
units1 nanometer

This neuron’s type is pymaid.CatmaidNeuron, which is a subclass of navis.TreeNeuron. The list version is pymaid.CatmaidNeuronList, which a subclass of navis.NeuronList. This adds a bit of extra functionality (such as lazy loading of data) and allows CatmaidNeuron and CatmaidNeuronList work as drop in replacements for their parent classes.

# Plot CatmaidNeuron with navis
navis.plot3d(n, width=1000, connectors=True, c='k')

get_neurons() returns neurons including their “connectors” - i.e. pre- (red) and postsynapses (blue). For this particular neuron, the published data comprehensively labels the axonal synapses but not the dendrites. Analogous to the nodes table, you can access the connectors like so:

n.connectors.head()

node_idconnector_idtypexyz
097891978950436882.09375161840.453125212160.0
12591979540437120.00000160998.000000211920.0
22665983000437183.75000162323.515625214880.0
32646983730437041.68750162451.937500214120.0
42654984150436760.90625163689.796875214440.0

Let’s run a bigger example and pull all data published with Bates, Schlegel et al. 2020. For this, we will use “annotations”. These are effectively text labels that group neurons together, in this case by paper. Instead of get_neurons we can use find_neurons to avoid downloading unnecessary data.

bates = pymaid.find_neurons(annotations='Paper: Bates and Schlegel et al 2020')
len(bates)
INFO  : Found 583 neurons matching the search parameters (pymaid)





583

bates is a CatmaidNeuronList containing 583 neurons. Importantly pymaid has not yet loaded any data other than names! Note all the “NAs” in the summary:

bates.head()

typenameskeleton_idn_nodesn_connectorsn_branchesn_leafscable_lengthsomaunits
0CatmaidNeuronUniglomerular mALT DA1 lPN 57316 2863105 ML2863104NANANANANANA1 nanometer
1CatmaidNeuronUniglomerular mALT DA3 adPN 57350 HG57349NANANANANANA1 nanometer
2CatmaidNeuronUniglomerular mALT DA1 lPN 57354 GA57353NANANANANANA1 nanometer
3CatmaidNeuronUniglomerular mALT VA6 adPN 017 DB16NANANANANANA1 nanometer
4CatmaidNeuronUniglomerular mALT VA5 lPN 57362 ML57361NANANANANANA1 nanometer

We could have used pymaid.get_neurons(annotations='Paper: Bates and Schlegel et al 2020') instead to load all data up-front, but this would increase memory usage.

The CatmaidNeuronList we have created will lazy load data from the server when required.

# Access the first neuron's nodes 
# -> this will trigger a data download
_ = bates[0].nodes 

# Run summary again 
bates.head()

typenameskeleton_idn_nodesn_connectorsn_branchesn_leafscable_lengthsomaunits
0CatmaidNeuronUniglomerular mALT DA1 lPN 57316 2863105 ML286310467744702802921522064.513255[3245741]1 nanometer
1CatmaidNeuronUniglomerular mALT DA3 adPN 57350 HG57349NANANANANANA1 nanometer
2CatmaidNeuronUniglomerular mALT DA1 lPN 57354 GA57353NANANANANANA1 nanometer
3CatmaidNeuronUniglomerular mALT VA6 adPN 017 DB16NANANANANANA1 nanometer
4CatmaidNeuronUniglomerular mALT VA5 lPN 57362 ML57361NANANANANANA1 nanometer

We have now loaded data for the first neuron.

Next we willl find and plot all uniglomelar DA1 projection neurons by their name.

# Name will be match pattern "Uniglomerular {tract} DA1 {lineage}"
import re 
prog = re.compile("Uniglomerular(.*?) DA1 ")

# Match all neuron names in `bates` against that pattern
is_da1 = list(map(lambda x: prog.match(x) != None, bates.name))

# Subset list 
da1 = bates[is_da1]
da1.head()

typenameskeleton_idn_nodesn_connectorsn_branchesn_leafscable_lengthsomaunits
0CatmaidNeuronUniglomerular mALT DA1 lPN 57316 2863105 ML286310467744702802921522064.513255[3245741]1 nanometer
1CatmaidNeuronUniglomerular mALT DA1 lPN 57354 GA57353NANANANANANA1 nanometer
2CatmaidNeuronUniglomerular mALT DA1 lPN 57382 ML57381NANANANANANA1 nanometer
3CatmaidNeuronUniglomerular mlALT DA1 vPN mlALTed Milk 23348...2334841NANANANANANA1 nanometer
4CatmaidNeuronUniglomerular mALT DA1 lPN PN021 2345090 DB RJVR2345089NANANANANANA1 nanometer
# Plot neurons by their lineage  
for n in da1:
    # Split name into components and keep the lineage
    n.lineage = n.name.split(' ')[3]    

# Generate a color per lineage
import seaborn as sns
import numpy as np 

lineages = np.unique(da1.lineage) 
lin_cmap = dict(zip(lineages, sns.color_palette('muted', len(lineages))))
neuron_cmap = {n.id: lin_cmap[n.lineage] for n in da1}

navis.plot3d(da1, color=neuron_cmap, hover_name=True)

Let’s add the neuropil meshes. These are called “volumes” on the CATMAID servers. To find out what’s available:

vols = pymaid.get_volume()
vols.head()
INFO  : Retrieving list of available volumes. (pymaid)

idnamecommentuser_ideditor_idproject_idcreation_timeedition_timeannotationsareavolumewatertightmeta_computed
0439v14.neuropilNone5524712017-10-05T21:01:18.683Z2018-08-30T17:21:20.910ZNone6.377313e+111.533375e+16FalseTrue
1440AME_RAccessory medulla right555512017-10-08T13:54:03.279Z2017-10-08T13:54:03.279ZNone1.894095e+094.799292e+12TrueTrue
2441LO_RLobula right555512017-10-08T13:54:03.840Z2017-10-08T13:54:03.840ZNone4.103282e+105.790708e+14TrueTrue
3442NONoduli555512017-10-08T13:54:04.084Z2017-10-08T13:54:04.084ZNone3.955158e+091.796395e+13TrueTrue
4443BU_RBulb right555512017-10-08T13:54:04.263Z2017-10-08T13:54:04.263ZNone1.445868e+094.109262e+12TrueTrue
# Get the neuropil volume 
v14neuropil = pymaid.get_volume('v14.neuropil')

# Make it slightly more transparent
v14neuropil.color = (.8, .8, .8, .3)
INFO  : Cached data used. Use `pymaid.clear_cache()` to clear. (pymaid)
# Plot with neuropil volume
navis.plot3d([da1, v14neuropil], color=neuron_cmap)

Suggested exercises:

  • find all uniglomerular projection neurons (name starts with Uniglomerular)
  • calculate the number of pre-/post-synapses in the right lateral horn (LH) (use pymaid.get_volume and navis.in_volume)
  • group the neurons by glomerulus based on label (nomenclature is Uniglomerular {tract} {glomerulus} {lineage} {metadata})
  • plot LH pre- vs post-synapses in a scatter plot (e.g. using seaborn.scatterplot)

Pulling connectivity

CATMAID lets you fetch connectivity data either as a list of up- and downstream partners or as whole adjacency matrices.

# Pull downstream partners of DA1 PNs
da1_ds = pymaid.get_partners(da1,
                             threshold=3,  # anything with >= 3 synapses
                             directions=['outgoing']  # downstream partners only
                              )

# Result is a pandas DataFrame
da1_ds.head()
INFO  : Fetching connectivity table for 17 neurons (pymaid)
INFO  : Done. Found 0 pre-, 270 postsynaptic and 0 gap junction-connected neurons (pymaid)

neuron_nameskeleton_idnum_nodesrelation286310457353573812334841234508927295...23194574207871755022237951761221323978123817535731157323total
0Uniglomerular mlALT DA1 vPN mlALTed Milk 18114...181144211769downstream30340015...0032026002120151.0
1Uniglomerular mlALT DA1 vPN mlALTed Milk 23348...23348416362downstream0000140...22170280263200139.0
2LHAV4a4#1 1911125 FML PS RJVR19111246969downstream2369005...0019013001915109.0
3LHAV2a3#1 1870231 RJVR AJES PS187023014820downstream523280010...0019070057105.0
4LHAV4c1#1 488056 downstream DA1 GSXEJ48805512137downstream15300016...001501500171192.0

5 rows × 22 columns

Each row is a synaptic downstream partner of our query DA1 neurons. The columns to the left contain the synapses they receive from individual query neurons. For example 1811442 (first row) receives 30 synapses from the DA1 PN with ID 2863104.

# Get an adjacency matrix between all Bates, Schlegel et al. neurons
adj = pymaid.adjacency_matrix(bates)
adj.head()

targets28631045734957353165736115738898573654182038381339911524119...57323462436218534232842610573334624374308018357337462437857341
sources
28631040.00.00.00.00.00.00.00.00.00.0...2.00.012.00.00.00.00.00.00.00.0
573490.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
573530.00.00.00.00.00.00.00.00.00.0...0.00.05.00.00.00.00.00.00.00.0
160.00.00.01.00.00.00.00.00.01.0...0.00.00.00.00.00.00.00.00.00.0
573610.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0

5 rows × 583 columns

# Plot a quick & dirty adjacency matrix
import seaborn as sns 

ax = sns.clustermap(adj, vmax=10, cmap='Greys')
/shared-libs/python3.7/py/lib/python3.7/site-packages/seaborn/matrix.py:649: UserWarning:

Clustering large matrix with scipy. Installing `fastcluster` may give better performance.

png

We can also ask for where in space specific connections are made:

# Axo-axonic connections between two different types of DA1 PNs
cn = pymaid.get_connectors_between(2863104, 1811442)
cn.head()

connector_idconnector_locnode1_idsource_neuronconfidence1creator1node1_locnode2_idtarget_neuronconfidence2creator2node2_loc
06736296[359448.44, 159319.03, 150560.0]316340828631045NaN[359487.3, 159145.66, 150600.0]673629818114425NaN[359611.9, 159541.48, 150560.0]
16795172[356041.88, 149555.53, 147920.0]679519528631045NaN[354724.44, 149284.1, 147920.0]679515318114425NaN[356366.16, 149854.86, 147920.0]
26795291[355189.5, 150232.48, 148240.0]679529328631045NaN[354595.62, 149464.8, 148240.0]679521418114425NaN[355472.28, 150294.75, 148160.0]
36795747[355030.4, 154047.86, 145800.0]679574928631045NaN[355045.38, 154180.1, 145800.0]679574518114425NaN[355024.44, 153945.73, 145760.0]
46797452[353221.4, 148570.9, 147320.0]679745628631045NaN[354213.9, 148397.44, 147320.0]679743718114425NaN[353447.6, 148704.88, 147560.0]
# Visualize
points = np.vstack(cn.connector_loc)

navis.plot3d([da1.idx[[2863104, 1811442]],  # plot the two neurons
              points],  # plot the points of synaptic contacts as scatter 
              scatter_kws=dict(name="synaptic contacts")
              )