Morphology: Trees (Notebook)

Open In Colab

This tutorial will guide you through creating a neuronal morphology representation step by step. First, we will read data from an SWC file using the SWCReader class. Next, we will use factory functions to create a PointTree i.e. the graph-tree representation of our morphological reconstruction. Then, we will split the point tree into sections to obtain a SectionTree. Finally, we will build a SegmentTree that divides our cell into computational segments for simulation.

Warning
Note that for most use cases, you won't need to follow the manual morphology processing described below. You can simply use the load_morphology method of the Model class. Only follow the steps below if the method fails to read the morphology automatically or if you want to manually intervene in the process.
!pip install dendrotweaks

If you are using Google Colab, you might also need to restart the session as the installation downgraded some packages (numpy). You can do it manually or programmatically as shown below:

# import os
# os.kill(os.getpid(), 9)

Let’s begin by importing the standard libraries and the dendrotweaks library.

import numpy as np
import matplotlib.pyplot as plt
import os
import dendrotweaks as dd
dd.__version__
'0.4.6'
dd.apply_dark_theme() # Set the theme for the plots
os.makedirs('examples', exist_ok=True)
if not os.listdir('examples/'):
    print("Downloading example data...")
    dd.download_example_data('examples')

Reading an SWC file

path_to_swc_file = os.path.join('examples', 'Park_2019', 'morphology', 'original.swc')
print(f'Path to file: {path_to_swc_file}')
Path to file: examples/Park_2019/morphology/Park_2019.swc

The first step in neuronal morphology analysis is reading the SWC file. DendroTweaks provides a straightforward method to load SWC data into a pandas DataFrame:

from dendrotweaks.morphology.io import SWCReader
reader = SWCReader()
df = reader.read_file(path_to_swc_file)

For initial data exploration, you can use standard pandas operations:

df.head()
Index Type Domain Color X Y Z R Parent
0 1 1 soma #E69F00 11.621167 104.434833 -6.694167 10.218962 -1
1 2 1 soma #E69F00 11.621167 94.215871 -6.694167 10.218962 1
2 3 1 soma #E69F00 11.621167 114.653796 -6.694167 10.218962 1
3 4 2 axon #F0E442 5.260000 92.000000 -9.100000 0.295000 1
4 5 2 axon #F0E442 5.290000 91.490000 -9.100000 0.295000 4
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2214 entries, 0 to 2213
Data columns (total 9 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   Index   2214 non-null   int64  
 1   Type    2214 non-null   int64  
 2   Domain  2214 non-null   object 
 3   Color   2214 non-null   object 
 4   X       2214 non-null   float64
 5   Y       2214 non-null   float64
 6   Z       2214 non-null   float64
 7   R       2214 non-null   float64
 8   Parent  2214 non-null   int64  
dtypes: float64(4), int64(3), object(2)
memory usage: 155.8+ KB

Creating a Point Tree

While the DataFrame provides a convenient way to manipulate data, a tree structure is more suitable for morphological analysis. The PointTree class represents a tree structure of neuronal points, with each node containing spatial information. To create a point tree from the DataFrame, use the create_point_tree function:

from dendrotweaks.morphology.io import create_point_tree
point_tree = create_point_tree(df)

Once we have the point tree, we can perform various postprocessing steps to refine and standardize the morphological data. We will start by removing overlapping nodes:

point_tree.remove_overlaps()

Soma notation

Next, we will check and standardize the soma notation. The soma can be represented in three ways in SWC files:

  • Three-point soma: soma represented by three points

  • One-point soma: soma represented by a single point

  • Contour: set of points defining the soma boundary

NeuroMorpho.org considers the three-point soma notation the standard representation. For more details, see the Soma format representation in NeuroMorpho.Org

To check the current soma notation we can use the soma_notation property:

point_tree.soma_notation
'3PS'

We can change the soma notation to the three-point soma using the change_soma_notation method:

point_tree.change_soma_notation('3PS')

Sorting

We now want to make sure that the nodes are properly sorted. For this we will perform depth-first traversal of the tree and update each node’s index as we visit the node.

point_tree.sort(sort_children=True, force=False)
Sorted PointTree(root=Point(idx=0), num_nodes=2214).

Sorting the trees is performed based on ther topological structure only. That is tree graph traversal is performed depth-first and children are selected in a way that the child with a smaller subtree (in terms of the number of bifurcations in the subtree) is traversed first. This ensures deterministic traversal and indexing of a neruonal morhology.

Shifting and aligning the tree

We can also shift the tree to the soma center and align the apical dendrite with the vertical axis.

The align_apical_dendrite method should be used only for cells with an apical dendrite. The round_coordinates method rounds the coordinates to the specified number of decimal places.

point_tree.shift_coordinates_to_soma_center()
point_tree.align_apical_dendrite()
point_tree.round_coordinates(8)

Let’s now validate the tree structure

from dendrotweaks.morphology.io import validate_tree

The method will automatically detect the tree type and run the required validation steps

validate_tree(point_tree)
Checking for unique node ids...
Checking for unique root node...
Checking for duplicate children...
Checking tree connectivity...
Checking for loops...
Checking for bifurcations with more than 2 children...
Checking for NaN values...
Checking for bifurcations in the soma...
Checking if the tree is sorted...
***Validation complete.***

Creating a Section Tree

Now we are ready to create a section tree from the point tree. The create_section_tree function splits the point tree into sections based on the bifurcation points:

from dendrotweaks.morphology.io import create_section_tree
sec_tree = create_section_tree(point_tree)
Extended 44 nodes.
Sorted PointTree(root=Point(idx=0), num_nodes=2258).
sections_to_highlight = [
    sec for sec in sec_tree 
    if sec.domain_name == 'apic' and sec.path_distance() < 50
]

fig, ax = plt.subplots(figsize=(8, 8))
sec_tree.plot(
    ax=ax, 
    show_domains=True, 
    show_points=True, 
    show_lines=True, 
    highlight_sections=sections_to_highlight # in red
)
../_images/24e9d6af5e594fbbce8e1a49cf4ff1173ff3c56b8b61f703ac45c3cad1d25014.png
sec_tree.topology()
parent |   idx
---------------
    -1 |   •0
     0 |   ├─•1
     0 |   ├─•2
     0 |   ├─•3
     0 |   ├─•4
     0 |   ├─•5
     0 |   ├─•6
     6 |   │ ├─•7
     6 |   │ └─•8
     0 |   └─•9
     9 |     ├─•10
    10 |     │ ├─•11
    11 |     │ │ ├─•12
    11 |     │ │ └─•13
    10 |     │ └─•14
    14 |     │   ├─•15
    14 |     │   └─•16
     9 |     └─•17
    17 |       ├─•18
    18 |       │ ├─•19
    19 |       │ │ ├─•20
    19 |       │ │ └─•21
    21 |       │ │   ├─•22
    21 |       │ │   └─•23
    23 |       │ │     ├─•24
    23 |       │ │     └─•25
    18 |       │ └─•26
    26 |       │   ├─•27
    26 |       │   └─•28
    28 |       │     ├─•29
    28 |       │     └─•30
    30 |       │       ├─•31
    30 |       │       └─•32
    17 |       └─•33
    33 |         ├─•34
    34 |         │ ├─•35
    34 |         │ └─•36
    33 |         └─•37
    37 |           ├─•38
    37 |           └─•39
    39 |             ├─•40
    40 |             │ ├─•41
    40 |             │ └─•42
    42 |             │   ├─•43
    42 |             │   └─•44
    39 |             └─•45
    45 |               ├─•46
    46 |               │ ├─•47
    46 |               │ └─•48
    45 |               └─•49
    49 |                 ├─•50
    49 |                 └─•51
validate_tree(point_tree)
Checking for unique node ids...
Checking for unique root node...
Checking for duplicate children...
Checking tree connectivity...
Checking for loops...
Checking for bifurcations with more than 2 children...
Checking for NaN values...
Checking for bifurcations in the soma...
Checking the extended tree for geometric continuity...
Checking if the tree is sorted...
***Validation complete.***

Creating domains

from dendrotweaks.morphology.io import create_domains
domains = create_domains(sec_tree)
domains
{'apic': <Domain(apic, 4, #0072B2, 43 sections)>,
 'axon': <Domain(axon, 2, #F0E442, 1 sections)>,
 'dend': <Domain(dend, 3, #019E73, 7 sections)>,
 'soma': <Domain(soma, 1, #E69F00, 1 sections)>}

Creating a Segment Tree

from dendrotweaks.morphology.io import create_segment_tree
from dendrotweaks.utils import calculate_lambda_f
for sec in sec_tree.sections:
    sec.create_and_reference()
d_lambda = 0.1
f = 100

for sec in sec_tree.sections:
    lambda_f = calculate_lambda_f(sec.distances, sec.diameters, sec.Ra, sec.cm, f)
    nseg = max(1, int((sec.L / (d_lambda * lambda_f) + 0.9) / 2) * 2 + 1)
    sec._nseg = sec._ref.nseg = nseg
seg_tree = create_segment_tree(sec_tree)
validate_tree(seg_tree)
Checking for unique node ids...
Checking for unique root node...
Checking for duplicate children...
Checking tree connectivity...
Checking for loops...
Checking for bifurcations with more than 2 children...
Checking if the tree is sorted...
***Validation complete.***