Skip to content

Commit

Permalink
Draw morphology with synapses (#82)
Browse files Browse the repository at this point in the history
Move all plotting functions under `plot` package
  • Loading branch information
asanin-epfl authored Jul 16, 2021
1 parent 2c2dad9 commit 7f11061
Show file tree
Hide file tree
Showing 18 changed files with 341 additions and 52 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

Version 2.8.0
-------------
- Add functionality to plot morphologies with synapses. Move all plot functionality under
``plot`` package. (#82)

Version 2.7.0
-------------
- Fix compatibility with numpy>=1.21 (#80)
Expand Down
46 changes: 26 additions & 20 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
|docs|

MorphTool
=========

Expand All @@ -13,6 +15,14 @@ MorphIO v2 and NeuroM v1
------------------------
If you want to work with old NeuroM v1 and MorphIO v2, use the version 2.4.7 of morph-tool.

Documentation
-------------

MorphIO documentation is built and hosted on `readthedocs <https://morph-tool.readthedocs.org/>`__.

* `latest snapshot <https://morph-tool.readthedocs.org/en/latest/>`_
* `latest release <https://morph-tool.readthedocs.org/en/stable/>`_

Installation
------------
It is recommended to install in a fresh virtualenv.
Expand Down Expand Up @@ -259,35 +269,27 @@ Splitting this section into 3 compartments would results in the following paths:
[2. , 2. ]]
Dendrogram with synapses
------------------------
Plot morphologies with synapses
-------------------------------

This functionality is available only when the package is installed with **dendrogram** extras:
This functionality is available only when the package is installed with **plot** extras:

.. code:: bash
pip install morph-tool
Draw NeuroM dendrogram with synapses on it. Synapses must be represented as a DataFrame. Required
columns in this dataframe are:

.. code:: python
from morph_tool import dendrogram
required_columns = [dendrogram.SOURCE_NODE_ID, dendrogram.TARGET_NODE_ID,
dendrogram.POST_SECTION_ID, dendrogram.POST_SECTION_POS,
dendrogram.PRE_SECTION_ID, dendrogram.PRE_SECTION_POS]
pip install morph-tool[plot]
or equivalently
Dendrogram
~~~~~~~~~~

.. code:: python
Draw NeuroM dendrogram with synapses on it. Synapses must be represented as a DataFrame. See
`dendrogram <https://morph-tool.readthedocs.io/en/latest/morph_tool.plot.dendrogram.html>`__.

required_columns = ['@source_node', '@target_node',
'afferent_section_id', 'afferent_section_pos',
'efferent_section_id', 'efferent_section_pos']
Morphology
~~~~~~~~~~

Draw NeuroM morphology with synapses on it. Synapses must be represented as a DataFrame. See
`morphology <https://morph-tool.readthedocs.io/en/latest/morph_tool.plot.morphology.html>`__.

For usage examples look at ``examples/dendrogram.py``.

Contributing
------------
Expand All @@ -309,3 +311,7 @@ morph-tool is licensed under the terms of the GNU Lesser General Public License
Refer to COPYING.LESSER and COPYING for details.

Copyright (c) 2018-2021 Blue Brain Project/EPFL

.. |docs| image:: https://readthedocs.org/projects/morph-tool/badge/?version=latest
:target: https://morph-tool.readthedocs.io/
:alt: documentation status
16 changes: 16 additions & 0 deletions doc/source/api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
API Documentation
=================

.. toctree::
:hidden:

Introduction <self>

.. autosummary::
:nosignatures:
:toctree: _morph_tool_build

morph_tool.plot
morph_tool.plot.consts
morph_tool.plot.dendrogram
morph_tool.plot.morphology
8 changes: 8 additions & 0 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.napoleon',
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
]

# Add any paths that contain templates here, relative to this directory.
Expand Down Expand Up @@ -84,6 +87,11 @@
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

autosummary_generate = True
autosummary_imported_members = False
autosummary_mock_imports = ['neuron', 'plotly']
autodoc_default_options = {'members': True}


# -- Options for HTML output ---------------------------------------------------

Expand Down
1 change: 1 addition & 0 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@

Home <self>
morphdb
api
changelog
20 changes: 13 additions & 7 deletions examples/dendrogram.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import numpy as np
import pandas as pd
import neurom as nm
from morph_tool import dendrogram
from morph_tool.plot import dendrogram, consts


def plain_example():
"""Example that shows how to draw a neuron dendrogram with a plain synapses dataframe."""
"""Example that shows how to plot a neuron dendrogram with a plain synapses dataframe."""
# Those properties are required in synapses dataframe for positioning
required_synapse_properties = [
dendrogram.SOURCE_NODE_ID, dendrogram.TARGET_NODE_ID,
dendrogram.POST_SECTION_ID, dendrogram.POST_SECTION_POS,
dendrogram.PRE_SECTION_ID, dendrogram.PRE_SECTION_POS,
consts.SOURCE_NODE_ID, consts.TARGET_NODE_ID,
consts.POST_SECTION_ID, consts.POST_SECTION_POS,
consts.PRE_SECTION_ID, consts.PRE_SECTION_POS,
]
# or use plain strings
# required_columns = ['@source_node', '@target_node',
# 'afferent_section_id', 'afferent_section_pos',
# 'efferent_section_id', 'efferent_section_pos']
data = np.array([
[0, 116, 4, 0.81408846, 3, 0.7344886],
[0, 116, 5, 0.145983203, 4, 0.24454929],
Expand All @@ -20,6 +24,8 @@ def plain_example():
[116, 0, 2, 0.5815143, 1, 0.68261607],
])
synapses = pd.DataFrame(columns=required_synapse_properties, data=data)
synapses = synapses.astype({'@target_node': int, '@source_node': int,
'afferent_section_id': int, 'efferent_section_id': int})
neuron = nm.load_neuron('dendrogram_plain_example.swc')
fig = dendrogram.draw(neuron, synapses, 116)
fig.show()
Expand All @@ -43,15 +49,15 @@ def plain_example():


def circuit_example():
"""Example that shows how to draw a neuron dendrogram with synapses from a bluepysnap circuit.
"""Example that shows how to plot a neuron dendrogram with synapses from a bluepysnap circuit.
To make this example work, you would need a proper SONATA circuit.
"""
from bluepysnap import Circuit
from bluepysnap.sonata_constants import Edge
from bluepysnap.bbp import Synapse
circuit = Circuit('/path/to/sonata_circuit_config.json')
# you can use `bluepysnap.sonata_constants.Edge` instead of `dendrogram` for position constants
# you can use `bluepysnap.sonata_constants.Edge` instead of `morph_tool.draw.consts`
edge_properties = [
Edge.SOURCE_NODE_ID, Edge.TARGET_NODE_ID,
Edge.POST_SECTION_ID, Edge.POST_SECTION_POS,
Expand Down
5 changes: 3 additions & 2 deletions examples/dendrogram_plain_example.swc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
5 3 6 5 0 0. 3
6 2 0 0 0 1. 1
7 2 0 -4 0 1. 6
8 2 6 -4 0 0. 7
9 2 -5 -4 0 0. 7
8 2 6 -4 0 1. 7
9 2 -5 -4 0 .5 7
10 2 -6 -5 0 .5 9
# index, type, x, y, z, radius, parent
80 changes: 80 additions & 0 deletions examples/draw_morphology.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import numpy as np
import pandas as pd
import neurom as nm
from morph_tool.plot import morphology
from morph_tool.plot import consts


def example_afferent():
neuron = nm.load_neuron('dendrogram_plain_example.swc')
data = np.array([
[4, 0, 0.81408846, 'additional value'],
[6, 2, 0.545983203, 'additional value'],
[1, 0, 0.4290702, 'additional value'],
])
columns = [consts.POST_SECTION_ID, consts.POST_SEGMENT_ID, consts.POST_SEGMENT_OFFSET,
'additional_data']
synapses = pd.DataFrame(data, columns=columns)

fig = morphology.draw(neuron, synapses)
fig.show()


def example_efferent():
neuron = nm.load_neuron('dendrogram_plain_example.swc')
data = np.array([
[4, 0, 0.81408846, 'additional value'],
[6, 2, 0.645983203, 'additional value'],
[1, 0, 0.4290702, 'additional value'],
])
columns = [consts.PRE_SECTION_ID, consts.PRE_SEGMENT_ID, consts.PRE_SEGMENT_OFFSET,
'additional_data']
synapses = pd.DataFrame(data, columns=columns)

fig = morphology.draw(neuron, synapses)
fig.show()


def example_afferent_efferent():
neuron = nm.load_neuron('dendrogram_plain_example.swc')
data = np.array([
[4, 0, 0.81408846, np.nan, np.nan, np.nan],
[6, 2, 0.145983203, np.nan, np.nan, np.nan],
[np.nan, np.nan, np.nan, 1, 0, 0.4290702],
[np.nan, np.nan, np.nan, 1, 0, 0.29180855],
[np.nan, np.nan, np.nan, 1, 0, 0.68261607],
])
columns = [consts.POST_SECTION_ID, consts.POST_SEGMENT_ID, consts.POST_SEGMENT_OFFSET,
consts.PRE_SECTION_ID, consts.PRE_SEGMENT_ID, consts.PRE_SEGMENT_OFFSET]
synapses = pd.DataFrame(data, columns=columns)

fig = morphology.draw(neuron, synapses)
fig.show()


def circuit_example():
"""Example that shows how to plot a morphology with synapses from a Sonata circuit.
To make this example work, you would need a proper SONATA circuit.
"""
from bluepysnap import Circuit
from bluepysnap.bbp import Synapse
circuit = Circuit('/path/to/sonata_circuit_config.json')
edge_properties = [
'afferent_section_id', 'afferent_segment_id', 'afferent_segment_offset',
Synapse.U_SYN, Synapse.D_SYN, Synapse.F_SYN, Synapse.G_SYNX,
]
morph_id = 110
synapses = circuit.edges['default'].afferent_edges(morph_id, edge_properties)
morph_filepath = circuit.nodes['All'].morph.get_filepath(morph_id)
neuron = nm.load_neuron(morph_filepath)

fig = morphology.draw(neuron, synapses)
fig.show()


if __name__ == '__main__':
# circuit_example()
# example_afferent()
example_efferent()
# example_afferent_efferent()
8 changes: 4 additions & 4 deletions morph_tool/morphdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,14 @@ def _from_neurondb_dat(cls, neurondb, morph_paths, label):
obj.df['mtype_no_subtype'] = fulltypes[0]
obj.df['msubtype'] = fulltypes[1]
else:
obj.df['mtype_no_subtype'] = obj.df.mtype
obj.df['mtype_no_subtype'] = obj.df['mtype']
obj.df['msubtype'] = ''

obj.df['label'] = label
for missing_col in set(COLUMNS) - set(obj.df.columns):
obj.df[missing_col] = None
obj.df.layer = obj.df.layer.astype('str')
obj.df['path'] = obj.df.name.map(morph_paths)
obj.df['path'] = obj.df['name'].map(morph_paths)
obj.df = obj.df.reindex(columns=COLUMNS)
for key in BOOLEAN_REPAIR_ATTRS:
obj.df[key] = True
Expand Down Expand Up @@ -339,7 +339,7 @@ def features(self, config: Dict, n_workers=1):

df = self.df.copy().reset_index(drop=True)
df.columns = pd.MultiIndex.from_product((["properties"], df.columns.values))
stats = extract_dataframe(df['properties', 'path'], config, n_workers)
stats = extract_dataframe(df.loc[:, ('properties', 'path')], config, n_workers)
return df.join(stats.drop(columns='name', level=1), how='inner')

def check_files_exist(self):
Expand All @@ -349,7 +349,7 @@ def check_files_exist(self):
raise ValueError(
f'DataFrame has morphologies with undefined filepaths: {missing_morphs}')

for path in self.df.path:
for path in self.df['path']:
if not path.exists():
raise ValueError(f'Non existing path: {path}')

Expand Down
4 changes: 2 additions & 2 deletions morph_tool/nrnhines.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ def get_NRN_cell(filename: Path):
try:
# pylint: disable=import-outside-toplevel
from bluepyopt import ephys
except ImportError as e:
except ImportError as e_:
raise ImportError(
'bluepyopt not installed; please use `pip install morph-tool[nrn]`') from e
'bluepyopt not installed; please use `pip install morph-tool[nrn]`') from e_
m = ephys.morphologies.NrnFileMorphology(str(filename))
sim = ephys.simulators.NrnSimulator()
cell = ephys.models.CellModel('test', morph=m, mechs=[])
Expand Down
7 changes: 7 additions & 0 deletions morph_tool/plot/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Module for plotting functions. In order to use it, you must install morph-tool as:
.. code:: bash
pip install morph-tool[plot]
"""
38 changes: 38 additions & 0 deletions morph_tool/plot/consts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Constants for names that are required to be in columns of ``synapses`` arg of
``morph_tool.plot`` package. These constants are the same as edge properties of `Sonata
<https://github.com/AllenInstitute/sonata/blob/master/docs/SONATA_DEVELOPER_GUIDE.md#
edges---optional-reserved-attributes>`__. So you can use these constants, their values as plain
strings or constants from `bluepysnap <https://github.com/BlueBrain/snap/>`__ library:
An example of using constants or plain strings:
.. literalinclude:: ../../../examples/dendrogram.py
:pyobject: plain_example
An example of using with a circuit and bluepysnap constants:
.. literalinclude:: ../../../examples/dendrogram.py
:pyobject: circuit_example
"""

#: Contains string `@target_node` that must be used as column name in `synapses` arg.
TARGET_NODE_ID = '@target_node'
#: Contains string `@source_node` that must be used as column name in `synapses` arg.
SOURCE_NODE_ID = '@source_node'

#: Contains string `afferent_section_id` that must be used as column name in `synapses` arg.
POST_SECTION_ID = 'afferent_section_id'
#: Contains string `afferent_section_pos` that must be used as column name in `synapses` arg.
POST_SECTION_POS = 'afferent_section_pos'
#: Contains string `afferent_segment_id` that must be used as column name in `synapses` arg.
POST_SEGMENT_ID = 'afferent_segment_id'
#: Contains string `afferent_segment_offset` that must be used as column name in `synapses` arg.
POST_SEGMENT_OFFSET = 'afferent_segment_offset'

#: Contains string `efferent_section_id` that must be used as column name in `synapses` arg.
PRE_SECTION_ID = 'efferent_section_id'
#: Contains string `efferent_section_pos` that must be used as column name in `synapses` arg.
PRE_SECTION_POS = 'efferent_section_pos'
#: Contains string `efferent_segment_id` that must be used as column name in `synapses` arg.
PRE_SEGMENT_ID = 'efferent_segment_id'
#: Contains string `efferent_segment_offset` that must be used as column name in `synapses` arg.
PRE_SEGMENT_OFFSET = 'efferent_segment_offset'
Loading

0 comments on commit 7f11061

Please sign in to comment.