# -*- coding: utf-8 -*-
#
# data_swc.py
#
# This file is part of DeNSE.
#
# Copyright (C) 2019 SeNEC Initiative
#
# DeNSE is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# DeNSE is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with DeNSE. If not, see <http://www.gnu.org/licenses/>.
from io import StringIO
import json
import os
from os.path import join, isfile, isdir
import numpy as np
from .. import _pygrowth as _pg
from ..elements import Neuron, Population
from ..units import *
__all__ = [
"save_to_swc",
"load_swc"
]
# SWC ids for soma, axon and dendrites
_SOMA_ID = 1
_AXON_ID = 2
_BASAL_ID = 3
_APICAL_ID = 4
# save and load functions
[docs]def save_to_swc(filename, gid=None, resolution=10, split=True):
'''
Save neurons to SWC file.
SWC files are a common format used to store neuron morphologies,
and are especially used to share digitally reconstructed neurons
using `NeuroMorpho.org <https://neuromorpho.org/>`_.
The format was designed to store trees as connected cylindrical segments
to form the basis of compartmental models.
Parameters
----------
filename : str
Name of the SWC file to write.
If `split` is True, it will be combined with the neurons' gids to form
each final filename.
gid : int or list of ints
Neurons to save.
resolution : int, optional (default: 10)
Coarse-graining factor of the structure: only one point every
`resolution` will be kept.
split : bool, optional (default: True)
Whether the neurons should be stored into a single SWC file or each into
its own SWC file (ignored if `gid` contains only one neuron).
If `split` is True, the gid of the neurons will automatically be
appended to `filename` to make each independent SWC file.
See also
--------
:func:`~dense.io.load_swc` to load SWC data.
:func:`~dense.io.save_to_neuroml` for NeuroML format.
'''
if isinstance(gid, (int, Neuron)):
gid = [gid]
if not filename.lower().endswith(".swc"):
raise ValueError("`filename` should end with '.swc'")
assert not isdir(filename), "`filename` cannot be a folder."
_pg._neuron_to_swc(
filename=filename, gid=gid, resolution=resolution, split=split)
[docs]def load_swc(swc_path, info=None):
"""
Import SWC files as a :class:`~dense.elements.Neuron` or a
:class:`~dense.elements.Population`.
SWC data will be automatically loaded from the file given by `swc_path`
or from all SWC file inside `swc_path` if it is a folder.
Parameters
----------
swc_path: str
Path to a file or folder containing SWC information to load.
info : str, optional (default: None)
Optional JSON file containing additional information about the neurons.
Returns
-------
a :class:`~dense.element.Neuron` if a single neuron is concerned or
a :class:`~dense.Population` object if several neurons are involved.
See also
--------
:func:`~dense.io.save_to_swc` to neurons as SWC data.
"""
data = None
info = {} if info is None else json.loads(info)
if swc_path.lower().endswith(".swc"):
data = _neuron_from_swc(swc_path, info=info)
else:
data = _neurons_from_swc_folder(swc_path, info=info)
pop = Population.from_swc(data, info)
if len(data) == 1:
return next(iter(pop))
return pop
def _neurons_from_swc_folder(path, info):
data = {}
for elt in os.listdir(path):
if elt.lower().endswith(".swc"):
data.update(_neuron_from_swc(join(path, elt), info))
return data
'''
Tool functions
'''
def _neuron_from_swc(swc_file, info):
"""
Parameters
----------
swc_file : str
Address of the file.
info : dict
Dictionary containing additional information which will be updated by
the function.
Returns
-------
data : dict
Morphology dict containing one entry by neuron, with the following
information:
* position
* soma_radius
* axon
* one entry per dendrite
"""
neurons, data = {}, {}
with open(swc_file, "r") as f:
filecontent = f.readlines()
last_gid = None
for i, l in enumerate(filecontent):
if l.startswith("#start_neuron"):
gid = int(l.split(" ")[1])
neurons[gid] = [i]
if last_gid is not None:
neurons[last_gid].append(i)
last_gid = gid
neurons[last_gid].append(i)
for gid, (start, stop) in neurons.items():
str_data = "".join(filecontent[start:stop])
s = StringIO(str_data)
data[gid] = _swc_to_segments(s)
if "gids" in info:
info["gids"] += len(neurons)
else:
info["gids"] = len(neurons)
return data
def _swc_to_segments(path):
"""
Import a single neuron SWC file, and create a new segments for each branching point.
Return:
======
Paths: an array of same length (length_thresh) paths (xy)
"""
data = np.loadtxt(path)
neuron_data = {}
pos_idx = np.where(data[:, 1] == _SOMA_ID)[0][0]
neuron_data["position"] = data[pos_idx, 2:4] * um
neuron_data["soma_radius"] = data[pos_idx, 5] * um
basal = _segment_from_swc(data, _BASAL_ID)
apical = _segment_from_swc(data, _APICAL_ID)
axon = _segment_from_swc(data, _AXON_ID)
neuron_data["basal"] = basal
neuron_data["apical"] = apical
neuron_data["axon"] = axon
return neuron_data
def _segment_from_swc(data, element_type):
"""
From a single neuron swc file select one element type and
cut the tree in a set of segments without branching.
Returns the segments in a list if the element exist else None.
"""
segments = []
parent_sample = -10
FORK_ID = 5
has_forked = False
for line in data:
if line[1] == element_type:
sample_number = int(line[0])
parent_sample = int(line[-1])
if parent_sample == _SOMA_ID:
segments.append([])
if parent_sample == sample_number - 1 and not has_forked:
segments[-1][-1]["xy"].append(line[2:4])
segments[-1][-1]["diameter"].append(line[5])
segments[-1][-1]["last_id"] = sample_number
segments[-1][-1]["length"] += 1
else:
first_sample = sample_number
segments[-1].append({
"length": 0,
"distance_from_soma":None,
"first_id": first_sample,
"parent_id": parent_sample,
"xy": [line[2:4]],
"theta": None,
"last_id": first_sample,
"diameter": [line[5]],
})
has_forked = False
elif int(line[1]) == FORK_ID:
has_forked = True
parent_sample = int(line[-1])
if parent_sample == _SOMA_ID:
segments.append([])
lengths = []
remove = []
for i, seg in enumerate(segments):
size = len(seg)
lengths.append(size)
if size == 0:
remove.append(i)
for entry in seg:
entry["xy"] = np.array(entry["xy"])
entry["diameter"] = 2*np.array(entry["diameter"])
for i in remove[::-1]:
del segments[i]
if np.sum(lengths):
return segments
return None