import glob
import json
import os
from collections import OrderedDict
from snudda.neurons import NeuronMorphology
from snudda.utils.snudda_path import snudda_parse_path
[docs]class NeuronPrototype:
""" Helper class, returns a neuron prototype based on parameter_id, morph_id and modulation_id """
def __init__(self,
neuron_path,
neuron_name,
morphology_path=None,
parameter_path=None,
mechanism_path=None,
modulation_path=None,
snudda_data=None,
meta_path=None,
virtual_neuron=False,
load_morphology=True,
axon_stump_id_flag=False,
verbose=False):
self.verbose = verbose
self.snudda_data = snudda_data
if neuron_path:
self.neuron_path = snudda_parse_path(neuron_path, self.snudda_data)
elif parameter_path:
self.neuron_path = os.path.dirname(snudda_parse_path(parameter_path, self.snudda_data))
elif morphology_path:
# morphology path usually points to "morphology" directory, but it can also point to a specific morphology
# which should be in neuron_path then (old format), but it might be in morphology folder...
if os.path.isdir(snudda_parse_path(morphology_path, self.snudda_data)):
self.neuron_path = os.path.dirname(snudda_parse_path(morphology_path, self.snudda_data))
elif os.path.isfile(snudda_parse_path(morphology_path, self.snudda_data)):
morph_path = os.path.dirname(snudda_parse_path(morphology_path, self.snudda_data))
if "morphology" in morph_path:
self.neuron_path = os.path.dirname(morph_path)
else:
self.neuron_path = morph_path
else:
self.neuron_path = None
if morphology_path:
self.morphology_path = snudda_parse_path(morphology_path, self.snudda_data)
elif self.neuron_path:
self.morphology_path = snudda_parse_path(os.path.join(self.neuron_path, "morphology"), self.snudda_data)
else:
self.morphology_path = None
if mechanism_path:
self.mechanism_path = snudda_parse_path(mechanism_path, self.snudda_data)
elif self.neuron_path:
self.mechanism_path = snudda_parse_path(os.path.join(self.neuron_path, "mechanisms.json"), self.snudda_data)
else:
self.mechanism_path = None
if parameter_path:
self.parameter_path = snudda_parse_path(parameter_path, self.snudda_data)
elif self.neuron_path:
self.parameter_path = snudda_parse_path(os.path.join(self.neuron_path, "parameters.json"), self.snudda_data)
else:
self.parameter_path = None
if meta_path:
self.meta_path = snudda_parse_path(meta_path, self.snudda_data)
elif self.neuron_path:
self.meta_path = snudda_parse_path(os.path.join(self.neuron_path, "meta.json"), self.snudda_data)
else:
self.meta_path = None
if modulation_path:
self.modulation_path = snudda_parse_path(modulation_path, self.snudda_data)
elif self.neuron_path:
self.modulation_path = snudda_parse_path(os.path.join(self.neuron_path, "modulation.json"), self.snudda_data)
if not os.path.exists(self.modulation_path):
self.modulation_path = None
else:
self.modulation_path = None
self.neuron_name = neuron_name
self.parameter_info = None
self.meta_info = None
self.modulation_info = None
self.virtual_neuron = virtual_neuron
self.axon_stump_id_flag = axon_stump_id_flag
self.load_morphology = load_morphology
self.morphology_cache = dict()
self.morphology_lookup = dict()
self.load_info()
[docs] def load_info(self):
""" Reads information about the neuron prototype. """
self.morphology_cache = dict()
self.morphology_lookup = dict()
par_path = self.parameter_path
if par_path is None or not os.path.exists(par_path):
if self.verbose:
print(f"Missing parameters.json : {par_path}")
self.parameter_info = None
return
if self.meta_path and os.path.exists(self.meta_path):
with open(self.meta_path, "r") as fm:
self.meta_info = json.load(fm, object_pairs_hook=OrderedDict)
with open(par_path, "r") as f:
self.parameter_info = json.load(f, object_pairs_hook=OrderedDict)
# We now expect a dictionary of parameter sets. If it is a list, we convert it to a dictionary
if type(self.parameter_info) == list:
self.parameter_info = {"default": self.parameter_info}
mod_path = self.modulation_path
if mod_path is not None and os.path.exists(mod_path):
with open(mod_path, "r") as f:
self.modulation_info = json.load(f, object_pairs_hook=OrderedDict)
else:
self.modulation_info = None
def get_num_morphologies(self, parameter_id=None, parameter_key=None):
if parameter_key is None:
parameter_key = self.get_parameter_key(parameter_id=parameter_id)
elif parameter_id is not None:
assert parameter_key == self.get_parameter_key(parameter_id=parameter_id), \
f"Internal error, parameter_id {parameter_id} does not match parameter_key {parameter_key} specified."
if self.meta_info and parameter_key:
morph_key_list = self.meta_info[parameter_key].keys()
return len(morph_key_list)
else:
return 1
def get_parameter_key(self, parameter_id):
assert parameter_id is not None
if self.parameter_info:
par_key_list = list(self.parameter_info.keys())
par_key = par_key_list[parameter_id % len(par_key_list)]
else:
par_key = None
return par_key
def get_morph_key(self, parameter_id, morphology_id, parameter_key=None):
if parameter_id is None:
par_key = parameter_key
else:
par_key = self.get_parameter_key(parameter_id=parameter_id)
assert parameter_key is None or par_key == parameter_key, \
(f"parameter_id = {parameter_id} gives parameter_key {par_key}, "
f"different from parameter_key {parameter_key} provided")
if self.meta_info:
assert par_key in self.meta_info, f"Parameter key {par_key} missing in {self.meta_path}"
morph_key_list = list(self.meta_info[par_key].keys())
assert len(morph_key_list) > 0, f"No morphologies available for parameter key {par_key} in {self.meta_path}"
morph_key = morph_key_list[morphology_id % len(morph_key_list)]
else:
morph_key = None
return morph_key
def get_modulation_key(self, parameter_id, morphology_id, modulation_id):
if self.modulation_info:
if type(self.modulation_info) == list:
# Old format without keys
return None
param_key = self.get_parameter_key(parameter_id)
morph_key = self.get_morph_key(parameter_id=None, parameter_key=param_key, morphology_id=morphology_id)
modulation_key_list = self.meta_info[param_key][morph_key]["neuromodulation"]
# modulation_key_list = list(self.modulation_info.keys())
modulation_key = modulation_key_list[modulation_id % len(modulation_key_list)]
else:
modulation_key = None
return modulation_key
def get_input_parameters(self, parameter_id=None, morphology_id=None, parameter_key=None, morphology_key=None):
if parameter_key is not None and morphology_key is not None:
if self.meta_info and "input" in self.meta_info[parameter_key][morphology_key]:
input_info = self.meta_info[parameter_key][morphology_key]["input"]
else:
input_info = dict()
else:
# Fallback if the user gave ID instead of Keys (will remove this at some point)
par_key = self.get_parameter_key(parameter_id=parameter_id)
morph_key = self.get_morph_key(parameter_id=parameter_id, morphology_id=morphology_id)
if self.meta_info and "input" in self.meta_info[par_key][morph_key]:
input_info = self.meta_info[par_key][morph_key]["input"]
else:
input_info = dict()
return input_info
[docs] def get_morphology(self, parameter_key=None, morphology_key=None, parameter_id=None, morphology_id=None):
"""
Returns morphology for a given parameter_id, morphology_id combination.
(Each parameter set has a set of morphologies that it is valid for)
"""
# TODO: In the future remove parameter_id and morphology_id and only use keys
if parameter_id is not None:
par_key = self.get_parameter_key(parameter_id=parameter_id)
if parameter_key:
assert par_key == parameter_key, \
f"Mismatch. Parameter ID {parameter_id} has parameter_key {par_key}, not {parameter_key}"
elif parameter_key:
par_key = parameter_key
else:
par_key = None
if self.meta_info and par_key is not None:
if morphology_id is not None:
morph_key = self.get_morph_key(parameter_id=parameter_id, morphology_id=morphology_id,
parameter_key=par_key)
if morphology_key:
assert morph_key == morphology_key, \
f"Mismatch: Expected morphology_key {morph_key}, got {morphology_key}"
elif morphology_key is not None:
morph_key = morphology_key
assert morphology_key in self.meta_info[par_key], f"Missing morphology_key {morphology_key}"
else:
assert False, f"morphology_id or morphology_key must be specified if meta_info is used"
morph_path = os.path.join(self.morphology_path, self.meta_info[par_key][morph_key]["morphology"])
assert os.path.isfile(morph_path), f"Morphology file {morph_path} is missing (listed in {self.meta_path})"
elif self.morphology_path and os.path.isfile(self.morphology_path):
# Fallback if morphology file is specified in the path
morph_path = self.morphology_path
else:
# No morphology
file_list = glob.glob(os.path.join(self.neuron_path, "*swc"))
if len(file_list) > 0:
morph_path = file_list[0]
assert len(file_list) == 1, f"More than one morphology available in {self.neuron_path}"
else:
print(f"No morphology in {self.neuron_path}")
morph_path = None
if morph_path is None:
print(f"morph_path is None for {self.neuron_name} path: {self.neuron_path}. "
f"Is SNUDDA_DATA set correctly? ({os.environ['SNUDDA_DATA'] if 'SNUDDA_DATA' in os.environ else None})")
import pdb
pdb.set_trace()
assert morph_path is not None, \
(f"morph_path is None for {self.neuron_name} path: {self.neuron_path}. "
f"Is SNUDDA_DATA set correctly? ({os.environ['SNUDDA_DATA']})")
return morph_path
[docs] def get_parameters(self, parameter_key=None, parameter_id=None):
"""
Returns neuron parameter set
Args:
parameter_key (str) : parameter key for the parameter set
parameter_id (int) : parameter ID, which of the parameter sets to use
Returns:
One parameter set as a list of dictionaries
"""
if self.parameter_info:
if parameter_key is not None:
assert parameter_key in self.parameter_info, \
f"Parameter key {parameter_key} not in parameter_info {self.parameter_info}. ERROR, no key: {parameter_key}"
par_set = self.parameter_info[parameter_key]
else:
assert parameter_id is not None, "If parameter_key is not set, then parameter_id must be set"
par_key_list = list(self.parameter_info.keys())
par_key = par_key_list[parameter_id % len(par_key_list)]
par_set = self.parameter_info[par_key]
else:
par_set = []
return par_set
[docs] def get_modulation_parameters(self, modulation_key=None, modulation_id=None):
"""
Returns modulation parameter set, give either ID or key
Args:
modulation_key (str) : modulation key
modulation_id (int) : modulation ID, which of the modulation parameter sets to use
Returns:
One parameter set as a list of dictionaries
"""
if self.modulation_info:
if modulation_key:
modulation_set = self.modulation_info[modulation_key]
elif modulation_id is not None:
possible_keys = [x for x in self.modulation_info.keys()]
modulation_set = self.modulation_info[possible_keys[modulation_id % len(self.modulation_info)]]
else:
assert False, f"You need to specify modulation_key or modulation_id to look up a modulation set"
else:
modulation_set = []
return modulation_set
[docs] def instantiate(self):
"""
Instantiates all morphologies at once, instead of on demand.
"""
n_par = len(self.parameter_info) if self.parameter_info is not None else 1
for par_id in range(0, n_par):
if self.verbose:
print(f"Instantiates par_id = {par_id}")
par_data = self.get_parameters(parameter_id=par_id)
n_morph = self.get_num_morphologies(parameter_id=par_id)
for morph_id in range(0, n_morph):
morph_path = self.get_morphology(parameter_id=par_id, morphology_id=morph_id)
morph_tag = os.path.basename(morph_path)
if morph_tag not in self.morphology_cache:
if self.verbose:
print(f"morph_tag = {morph_tag}")
self.morphology_cache[morph_tag] = NeuronMorphology(swc_filename=morph_path,
param_data=self.parameter_path,
mech_filename=self.mechanism_path,
neuron_path=self.neuron_path,
snudda_data=self.snudda_data,
name=self.neuron_name,
hoc=None,
load_morphology=self.load_morphology,
virtual_neuron=self.virtual_neuron,
axon_stump_id_flag=self.axon_stump_id_flag)
[docs] def apply(self, function_name, arguments):
"""
Applies function to all morphology prototypes
"""
res = []
for m in self.morphology_cache.values():
function = getattr(m, function_name)
res.append(function(*arguments))
return res
def all_have_axon(self):
return all([len(m.axon) > 0 for m in self.morphology_cache.values()])
def all_have_dend(self):
return all([len(m.dend) > 0 for m in self.morphology_cache.values()])
[docs] def clone(self, parameter_id=None, morphology_id=None, modulation_id=None,
parameter_key=None, morphology_key=None, modulation_key=None,
position=None, rotation=None, get_cache_original=False):
"""
Creates a clone of the neuron prototype, with given position and rotation.
Use either keys or id (keys have precedence if both given)
Args:
parameter_id (int) : parameter set to use
morphology_id (int) : morphology set to use, a parameter set can have multiple morphology variations
modulation_id (int) : neuro-modulation parameter set to use
parameter_key (str) : parameter key for parameter set in parameters.json
morphology_key (str) : morphology key for morphology in meta.json
modulation_key (str) : modulation key for neuromodulation in modulation.json
position (float,float,float) : position of neuron clone
rotation : rotation (3x3 numpy matrix)
get_cache_original (bool) : return the original rather than a clone
"""
morph_path = self.get_morphology(parameter_key=parameter_key, morphology_key=morphology_key,
parameter_id=parameter_id, morphology_id=morphology_id)
morph_tag = os.path.basename(morph_path)
if morph_tag not in self.morphology_cache:
# TODO: hoc file will depend on both morphology_id and parameter_id, we ignore it for now
self.morphology_cache[morph_tag] = NeuronMorphology(swc_filename=morph_path,
param_data=self.parameter_path,
mech_filename=self.mechanism_path,
neuron_path=self.neuron_path,
snudda_data=self.snudda_data,
name=self.neuron_name,
hoc=None,
load_morphology=self.load_morphology,
virtual_neuron=self.virtual_neuron,
axon_stump_id_flag=self.axon_stump_id_flag)
if get_cache_original:
assert position is None and rotation is None and modulation_id is None, \
"If get_cache_original is passed position, rotation and modulation_id must be None"
morph = self.morphology_cache[morph_tag]
else:
# Add the keys also for parameter, morphology and modulation
if parameter_key is None:
parameter_key = self.get_parameter_key(parameter_id=parameter_id)
if morphology_key is None:
morphology_key = self.get_morph_key(parameter_id=parameter_id, morphology_id=morphology_id)
if modulation_key is None:
modulation_key = self.get_modulation_key(parameter_id=parameter_id,
morphology_id=morphology_id,
modulation_id=modulation_id)
morph = self.morphology_cache[morph_tag].clone(position=position,
rotation=rotation,
parameter_id=parameter_id,
morphology_id=morphology_id,
modulation_id=modulation_id,
parameter_key=parameter_key,
morphology_key=morphology_key,
modulation_key=modulation_key)
return morph