NeuroM Tutorial Notebook

NeuroM contains helper functions that allow to easily load neuronal morphologies from files into NeuroM data structures. It also provides convenient methods to query various properties of the morphologies, as well as an easy way to visualize morphological objects.


In [1]:
%matplotlib inline

# Import neurom module
import neurom as nm
# Import neurom visualization module
from neurom import viewer

1. Loading a morphology or a population

NeuroM can load morphologies from swc, h5 or NL ascii files. Please note that the Neurolucida ascii reader is experimental! There are no guarantees regarding correctness of loading data from files in this format.


In [2]:
# Load a single morphology  
neuron = nm.load_neuron('../test_data/valid_set/Neuron.swc')

# Load a population of morphologies from a set of files
pop = nm.load_neurons('../test_data/valid_set/')

# Get a single morphology from the population
single_neuron = pop.neurons[0]

2. Morphology visualization


In [3]:
# Visualize a morphology in two dimensions
fig, ax = viewer.draw(neuron)



In [4]:
# Visualize a morphology in three dimensions
fig, ax = viewer.draw(neuron, mode='3d')



In [5]:
# Visualize a single tree in three dimensions
fig, ax = viewer.draw(neuron.neurites[0], mode='3d')



In [6]:
# Visualize the dendrogram of a morphology
fig, ax = viewer.draw(neuron, mode='dendrogram')


3. Morphology analysis

3.1 Morphometrics extraction


In [7]:
# Extract the total number of neurites (basal and apical dendrites, and axons)
number_of_neurites = nm.get('number_of_neurites', neuron)

# Extract the total number of sections
number_of_sections = nm.get('number_of_sections', neuron)

# Extract the soma radius
soma_radius = neuron.soma.radius

# Extract the number of sections per neurite
number_of_sections_per_neurite = nm.get('number_of_sections_per_neurite', neuron)

# Print result
print "Neuron id          : {0} \n\
Number of neurites : {1} \n\
Soma radius        : {2:.2f} \n\
Number of sections : {3}".format(neuron.name, number_of_neurites[0], soma_radius, number_of_sections[0])
print
print "Neurite type \t\t\t| Number of sections"
for i, neurite in enumerate(neuron.neurites):    
    print "{0:31} | {1}".format(str(neurite.type), number_of_sections_per_neurite[i])


Neuron id          : Neuron 
Number of neurites : 4 
Soma radius        : 0.13 
Number of sections : 84

Neurite type 			| Number of sections
NeuriteType.axon                | 21
NeuriteType.basal_dendrite      | 21
NeuriteType.basal_dendrite      | 21
NeuriteType.apical_dendrite     | 21

In [8]:
# Extract the lengths of the sections
section_lengths = nm.get('section_lengths', neuron)

# Extract the lengths of the segments
segment_lengths = nm.get('segment_lengths', neuron)

# Extract the local bifurcation angles
local_bif_angles = nm.get('local_bifurcation_angles', neuron)

# Extract the remote bifurcation angles
remote_bif_angles = nm.get('remote_bifurcation_angles', neuron)

# Extract the radial distances of the sections
section_radial_distances = nm.get('section_radial_distances', neuron)

# Extract the path distances of the sections
section_path_distances = nm.get('section_path_distances', neuron)

# Print result
features = (segment_lengths, section_lengths, local_bif_angles, 
            remote_bif_angles, section_path_distances, section_radial_distances)

def check(feature_list, n): 
    return '{0:.2f}'.format(feature_list[n]) if n < len(feature_list) else ''

print '|sg_len|sc_len|lc_bif_angles|rm_bif_angles|sc_path_dists|sc_rad_dists|'
for n in range(0, 50):
    args = (check(f, n) for f in features)
    print '|{0:^6}|{1:^6}|{2:^13}|{3:^13}|{4:^13}|{5:^12}|'.format(*args)


|sg_len|sc_len|lc_bif_angles|rm_bif_angles|sc_path_dists|sc_rad_dists|
| 0.10 | 9.58 |    2.09     |    0.34     |    9.58     |    8.84    |
| 0.65 | 9.65 |    2.09     |    0.57     |    19.23    |   15.75    |
| 1.01 |10.26 |    2.09     |    0.59     |    19.84    |   16.74    |
| 1.06 | 9.19 |    2.09     |    0.49     |    29.03    |   23.23    |
| 1.15 | 9.28 |    2.09     |    0.16     |    29.12    |   23.07    |
| 0.94 |10.73 |    2.09     |    0.52     |    39.85    |   30.58    |
| 1.30 | 9.59 |    2.09     |    0.76     |    38.71    |   30.18    |
| 1.09 |10.45 |    2.09     |    0.47     |    49.17    |   37.80    |
| 1.18 | 8.93 |    2.09     |    0.72     |    47.64    |   36.63    |
| 1.09 |10.05 |    2.09     |    0.34     |    57.70    |   44.10    |
| 1.41 | 9.97 |    2.09     |    0.58     |    57.61    |   43.97    |
| 0.93 |10.72 |    2.09     |    0.26     |    68.33    |   51.29    |
| 0.80 |10.55 |    2.09     |    0.12     |    68.16    |   51.92    |
| 1.12 | 9.11 |    2.09     |    0.21     |    77.28    |   57.79    |
| 1.38 |10.09 |    2.09     |    0.51     |    78.26    |   59.43    |
| 0.81 |10.33 |    2.09     |    0.36     |    88.59    |   66.61    |
| 0.49 | 9.18 |    2.09     |    0.24     |    87.43    |   66.25    |
| 1.13 | 8.86 |    2.09     |    0.26     |    96.29    |   71.36    |
| 0.72 |10.37 |    2.09     |    0.41     |    97.81    |   74.05    |
| 0.85 | 9.95 |    2.09     |    0.19     |   107.76    |   80.70    |
| 1.41 |11.02 |    2.09     |    0.25     |   108.83    |   82.44    |
| 0.54 | 7.97 |    2.09     |    0.28     |    7.97     |    7.31    |
| 1.33 | 8.73 |    2.09     |    0.20     |    16.70    |   14.54    |
| 1.25 |10.71 |    2.09     |    0.37     |    18.68    |   16.26    |
| 0.82 |10.52 |    2.09     |    0.14     |    29.20    |   25.07    |
| 1.23 | 9.63 |    2.09     |    0.06     |    28.32    |   23.94    |
| 0.32 |10.13 |    2.09     |    0.46     |    38.45    |   32.50    |
| 1.53 |10.10 |    2.09     |    0.15     |    38.42    |   32.21    |
| 1.40 |10.90 |    2.09     |    0.06     |    49.33    |   41.32    |
| 0.44 |11.65 |    2.09     |    0.36     |    50.07    |   42.47    |
| 1.41 |10.20 |    2.09     |    0.16     |    60.28    |   50.59    |
| 0.97 | 9.54 |    2.09     |    0.37     |    59.61    |   50.36    |
| 1.18 | 9.53 |    2.09     |    0.23     |    69.14    |   58.20    |
| 0.53 |10.80 |    2.09     |    0.21     |    70.41    |   59.19    |
| 1.25 |10.25 |    2.09     |    0.08     |    80.66    |   67.45    |
| 0.85 |11.61 |    2.09     |    0.17     |    82.02    |   69.32    |
| 0.74 | 8.93 |    2.09     |    0.25     |    90.95    |   76.34    |
| 0.88 | 8.23 |    2.09     |    0.20     |    90.25    |   76.26    |
| 0.40 | 9.67 |    2.09     |    0.16     |    99.92    |   83.73    |
| 0.97 |10.13 |    2.09     |    0.18     |   100.38    |   84.86    |
| 1.41 |10.97 |             |             |   111.35    |   94.04    |
| 0.94 |10.89 |             |             |   111.28    |   94.43    |
| 0.97 | 8.22 |             |             |    8.22     |    7.46    |
| 0.95 | 9.59 |             |             |    17.82    |   15.43    |
| 0.69 |11.02 |             |             |    19.24    |   15.37    |
| 0.76 |10.26 |             |             |    29.50    |   24.71    |
| 0.83 |10.76 |             |             |    30.00    |   22.84    |
| 0.97 |10.38 |             |             |    40.38    |   31.87    |
| 0.78 |10.62 |             |             |    40.62    |   30.50    |
| 0.96 |11.05 |             |             |    51.67    |   40.23    |

3.2 Analyze different types of trees

The previous examples treated all neurites in the same way. NeuroM allows you to extract morphometrics for a selected type of trees.


In [9]:
# Extract the section lengths of axonal trees
ax_section_lengths = nm.get('section_lengths', neuron, neurite_type=nm.AXON)

# Extract the section lengths of basal dendrite trees
ba_section_lengths = nm.get('section_lengths', neuron, neurite_type=nm.BASAL_DENDRITE)

# Extract the section lengths of apical dendrite trees
ap_section_lengths = nm.get('section_lengths', neuron, neurite_type=nm.APICAL_DENDRITE)

print '\nAxonal section lengths = ', ax_section_lengths
print '\nBasal section lengths =  ', ba_section_lengths
print '\nApical section lengths = ', ap_section_lengths


Axonal section lengths =  [  9.57911737   9.64901212  10.26444194   9.18963499   9.28095558
  10.72637819   9.58862945  10.45414656   8.92750196  10.05466932
   9.96815205  10.72221858  10.55440382   9.11262954  10.09303133
  10.33071556   9.17709438   8.86068767  10.37491982   9.95295124
  11.01846074]

Basal section lengths =   [  7.97232242   8.73002814  10.71154672  10.51683552   9.63361814
  10.1348335   10.1034446   10.90464832  11.65250813  10.20352358
   9.54012263   9.53084499  10.79778536  10.25222844  11.60598013
   8.92943746   8.23366666   9.66996901  10.13395757  10.96762258
  10.89245052   8.22452877   9.59239376  11.0190682   10.25855549
  10.75631381  10.38491293  10.62047288  11.05192629  10.06943611
  10.10998146  10.55534081  10.58562592  10.74722939   8.23176374
   9.8508199    8.93049233  10.73839347   9.48292967   8.58137852
   9.0358861    8.48759244]

Apical section lengths =  [  9.21270799  11.05092479  11.02994892  10.7541096   10.17670693
   9.36444805  10.49054247   9.52925566   9.49194374  10.36496319
   8.42121218  10.92441795  10.34721651   8.99513591  11.75828156
  11.56005879  10.38431278   9.97008743  10.85399109   9.87885774
   9.81392252]

3.3 Perform statistical analysis on extracted measurements

Now we are ready to extract basic statistical measurements, using common Python functions. For this, we will use numpy, which is a package for scientific computing with Python.


In [10]:
import numpy as np

# We can get the mean section length
mean_sl = np.mean(section_lengths)

# We can get the standard deviation of the section lengths
std_sl = np.std(section_lengths)

# We can get the minimum section length
min_sl = np.min(section_lengths)

# ... and the maximum section length
max_sl = np.max(section_lengths)

print 'Section length statistics:'
print '  mean = {0:.2f} +- {1:.2f}'.format(mean_sl, std_sl)
print '  [min, max]: [{0:.2f}, {1:.2f}]'.format(min_sl, max_sl)


Section length statistics:
  mean = 10.01 +- 0.86
  [min, max]: [7.97, 11.76]

3.4 Generate plots from the extracted morphometrics

The distribution of the extracted measurements can be plotted with matplotlib, which is a Python library for plot generation. We will use the matplotlib.pyplot sub module.


In [11]:
import matplotlib.pyplot as plt

# Select the feature of choice
feature = nm.get('segment_lengths', neuron)

# Create empty figure
fig = plt.figure(figsize=(11,3))

# Create histogram
ax = fig.add_subplot('131')
ax.hist(feature, bins=25, edgecolor='black')

# Create cumulative histogram
ax = fig.add_subplot('132')
ax.hist(feature, bins=25, cumulative=True, edgecolor='black')

# Create boxplot; flier points are indicated with green dots
ax = fig.add_subplot('133')
_ = ax.boxplot(feature, sym='g.')


3.5 Fit the extracted data with a statistical distribution

Now we are ready to fit the extracted data using common Python functions. For this, we will use scipy, which is a package for numerical routines for scientific computing with Python.


In [12]:
from neurom import stats

data = nm.get('segment_lengths', neuron)

# Let’s start with a normal distribution. We will fit the data that we extracted above with a normal distribution
p = stats.fit(data, distribution='norm')

# The output of the function is a named tuple of type FitResults
print 'Fit output type : ', type(p)

# The parameters are stored in the variable params, which in the case of the normal distribution stores the mu and sigma
# of the normal distribution
mu, sigma = p.params
ks_dist, pvalue = p.errs

# Print result
print '[mu, sigma] : [{0:.2f}, {1:.2f}]\n'.format(mu, sigma)

# We need to check the statistical error of the performed fit to evaluate the accuracy of the 
# selected model. To do so we use the errors variable of FitResults:
print 'Kolmogorov-Smirnov distance : {0:.2f}'.format(ks_dist)
print 'P-value : {0:.2f}'.format(pvalue)


Fit output type :  <class 'neurom.stats.FitResults'>
[mu, sigma] : [1.00, 0.31]

Kolmogorov-Smirnov distance : 0.05
P-value : 0.02

The result of the fitting can be visualized:


In [13]:
from scipy.stats import norm

# Create a histogram as above
fig = plt.figure()
plt.hist(data, bins=25, normed=True, edgecolor='black')

# Plot range: 5 standard deviations around the mean
norm_range = np.arange(mu - 5.*sigma, mu + 5.*sigma, 0.001)

# Plot the normal pdf with the given range, mu and sigma
_ = plt.plot(norm_range, norm.pdf(norm_range, mu, sigma), linewidth=3., c='r', alpha=0.8)


It is also possible to find the optimal distribution that best fits the data, among a number of distributions that are supported by scipy:


In [14]:
p = stats.optimal_distribution(data, distr_to_check=('lognorm', 'logistic', 'norm'))
print 'Fit results:', p


Fit results: FitResults(params=(1.0008157314553803, 0.30873679377786745), errs=KstestResult(statistic=0.053380962543574939, pvalue=0.016059728228707604), type='norm')

3.6 Apply more advanced manipulation on extracted data

In this example, we extract all section lengths that exceed a selected threshold.


In [15]:
# Threshold value
threshold = 10

# Get the ids of sections which length exceeds the threshold
selected_ids = np.where(section_lengths > threshold)

# Get the values of section lengths that exceed the threshold
section_lengths[selected_ids]


Out[15]:
array([ 10.26444194,  10.72637819,  10.45414656,  10.05466932,
        10.72221858,  10.55440382,  10.09303133,  10.33071556,
        10.37491982,  11.01846074,  10.71154672,  10.51683552,
        10.1348335 ,  10.1034446 ,  10.90464832,  11.65250813,
        10.20352358,  10.79778536,  10.25222844,  11.60598013,
        10.13395757,  10.96762258,  10.89245052,  11.0190682 ,
        10.25855549,  10.75631381,  10.38491293,  10.62047288,
        11.05192629,  10.06943611,  10.10998146,  10.55534081,
        10.58562592,  10.74722939,  10.73839347,  11.05092479,
        11.02994892,  10.7541096 ,  10.17670693,  10.49054247,
        10.36496319,  10.92441795,  10.34721651,  11.75828156,
        11.56005879,  10.38431278,  10.85399109])

3.7 Combine morphometrics

We can study relations between different morphometrics. For example, we can combine section length and path length to soma:


In [16]:
# Get the length of all sections with a radial distance between 0.0 and 60.0
section_indices = np.where((section_radial_distances >= 0.0) & (section_radial_distances < 60.0))
selected_section_lengths = section_lengths[section_indices]
print selected_section_lengths


[  9.57911737   9.64901212  10.26444194   9.18963499   9.28095558
  10.72637819   9.58862945  10.45414656   8.92750196  10.05466932
   9.96815205  10.72221858  10.55440382   9.11262954  10.09303133
   7.97232242   8.73002814  10.71154672  10.51683552   9.63361814
  10.1348335   10.1034446   10.90464832  11.65250813  10.20352358
   9.54012263   9.53084499  10.79778536   8.22452877   9.59239376
  11.0190682   10.25855549  10.75631381  10.38491293  10.62047288
  11.05192629  10.06943611  10.10998146  10.55534081  10.58562592
  10.74722939   8.23176374   9.8508199    9.21270799  11.05092479
  11.02994892  10.7541096   10.17670693   9.36444805  10.49054247
   9.52925566   9.49194374  10.36496319   8.42121218  10.92441795]