"""
NRV-:class:`.nerve` handling.
"""
import numpy as np
from ..backend._log_interface import pass_info, rise_warning
from ..backend._NRV_Class import load_any
from ..backend._NRV_Simulable import NRV_simulable
from ..utils.geom._misc import cshape_overlap_checker, create_cshape
from ..utils._stimulus import stimulus
from ._fascicles import *
from .results._nerve_results import nerve_results
#################
## Nerve class ##
#################
[docs]
class nerve(NRV_simulable):
r"""
A nerve in NRV is defined as:
- a list of :class:`.fascicle`
- an `analytical` :class:`~nrv.fmod.extracellular.stimulation` or a :class:`~nrv.fmod.extracellular.FEM_stimulation` context
A nerve can be instrumented by adding couples of electrodes+stimulus
The extracellular context of the nerve is automatically generated from the context of its
fascicles and an optional context added directly to the nerve.
See Also
--------
:doc:`Simulables users guide</usersguide/simulables>`, :class:`Fascicle-class<._fascicles.fascicle>`, :class:`Axon-class<._axons.axon>`
.. rubric:: Customizable Attributes:
.. list-table::
:widths: 10 10 10 70
:header-rows: 1
* - Attributes
- Type
- Default
- Description
* - ``ID``
- ``int``
- 0
- Identification number of the nerve.
*
- ``L``
- ``float``
- None
- Length of the nerve.
*
- ``D``
- ``float``
- None
- Diameter of the nerve.
*
- ``y_grav_center``
- ``float``
- 0
- y-position of the nerve center.
*
- ``z_grav_center``
- ``float``
- 0
- z-position of the nerve center.
*
- ``postproc_label``
- ``str``
- None
- Label of the axon postprocessing funtion, used for the buildin postproc functions.
*
- ``postproc_function``
- ``function``
- None
- Axon postprocessing funtion, used for the custom postproc functions.
*
- ``postproc_script``
- ``str`` | ``function``
- None
- Either postprocessing funtion or postprocessing funtion label, automatically set depending on the type
*
- ``postproc_kwargs``
- ``dict``
- None
- key arguments of the postporcessing function
*
- ``save_results``
- ``bool``
- False
- If ``True``, nerve configuration and all axon simulations results are saved in ``save_path`` directory.
*
- ``save_path``
- ``str``
- ""
- Path of the directory where simulation results should be saved.
*
- ``return_parameters_only``
- ``bool``
- False
- If ``True`` (and ``save_results`` also ``True``), only the parameters should be returned from the simulation.
*
- ``loaded_footprints``
- ``bool``
- False
- If ``False``, the footprints already computed are favored over new footprint computation.
*
- ``verbose``
- ``bool``
- False
- Plot or not.
Note
----
Customizable attributes can either be set using :meth:`nerve.set_parameters` or simply by reafecting the value of the attribute.
Tip
---
Additional simulation parameters can be changed using (:meth:`.nerve.set_axons_parameters`, :meth:`.nerve.change_stimulus_from_electrode`, ...).
Parameters
----------
length : float
Nerve length, in um
diameter : float
Nerve diameter, in um
Outer_D : float
Outer saline box diameter, in mm
ID : int
Nerve unique identifier
"""
[docs]
def __init__(self, length=10_000, diameter=100, Outer_D=5, ID=0, **kwargs):
"""
Instanciates an empty nerve.
"""
super().__init__(**kwargs)
# to add to a fascicle/nerve common mother class
self.verbose = True
self.loaded_footprints = True
self.return_parameters_only = False
self.save_results = False
self.save_path = ""
self.postproc_label = "default"
self.postproc_function = None
self.postproc_script = None
self.postproc_kwargs = {}
self.n_proc = None
self.type = "nerve"
self.L = length
self.D = None
self.Outer_D = Outer_D
self.ID = ID
# geometric properties
self.y_grav_center: float = 0
self.z_grav_center: float = 0
# Update parameters with kwargs
self.set_parameters(**kwargs)
# Fascicular content
self.fascicles_IDs: list[str] = []
self.fascicles: dict[str, fascicle] = {}
# Axons objects default parameters
self.unmyelinated_param = {
"model": "Rattay_Aberham",
"dt": 0.001,
"Nrec": 0,
"Nsec": 1,
"Nseg_per_sec": length // 25,
"freq": 100,
"freq_min": 0,
"mesh_shape": "plateau_sigmoid",
"alpha_max": 0.3,
"d_lambda": 0.1,
"v_init": None,
"T": None,
"threshold": -40,
}
self.myelinated_param = {
"model": "MRG",
"dt": 0.001,
"node_shift": 0,
"Nseg_per_sec": 3,
"freq": 100,
"freq_min": 0,
"mesh_shape": "plateau_sigmoid",
"alpha_max": 0.3,
"d_lambda": 0.1,
"rec": "nodes",
"T": None,
"threshold": -40,
}
self.set_axons_parameters(**kwargs)
# extra-cellular stimulation
self.extra_stim = None
self.is_extra_stim = False
self.added_extra_stims = []
self.footprints = {}
self.is_footprinted = False
self.myelinated_nseg_per_sec = 3
self.unmyelinated_nseg = length // 25
# intra-cellular stimulation
self.N_intra = 0
self.intra_stim_position = []
self.intra_stim_t_start = []
self.intra_stim_duration = []
self.intra_stim_amplitude = []
self.intra_stim_ON = []
## recording mechanism
self.record = False
self.recorder = None
self.is_simulated = False
# Simulation status
self.define_circular_contour(diameter)
## save/load methods
[docs]
def save(
self,
fname="nerve.json",
extracel_context=False,
intracel_context=False,
rec_context=False,
fascicles_context=True,
save=True,
_fasc_save=False,
blacklist=[],
):
"""
Save a fascicle in a json file
Parameters
----------
fname : str
name of the file to save the fascicle
extracel_context: bool
if True, add the extracellular context to the saving
intracel_context: bool
if True, add the intracellular context to the saving
rec_context: bool
if True, add the recording context to the saving
fascicles_context: bool
if True, add the fascicles are fully saved
blacklist: list[str]
key to exclude from saving
save: bool
if false only return the dictionary
Returns
-------
key_dict: dict
Pyhton dictionary containing all the fascicle information
"""
bl = [i for i in blacklist]
bl += ["postproc_function", "postproc_script"]
if self.postproc_label not in builtin_postproc_functions:
rise_warning(
"custom axon postprocessing cannot be save. Be carefull to set the postproc_function again when reloading fascicle"
)
if not intracel_context:
bl += [
"N_intra",
"intra_stim_position",
"intra_stim_t_start",
"intra_stim_duration",
"intra_stim_amplitude",
"intra_stim_ON",
]
if not extracel_context:
bl += ["extra_stim", "footprints", "is_footprinted"]
if not rec_context:
bl += ["record", "recorder"]
if not fascicles_context:
bl += ["fascicles"]
to_save = save
return super().save(
fname=fname,
save=to_save,
blacklist=bl,
intracel_context=intracel_context,
extracel_context=extracel_context,
rec_context=rec_context,
_fasc_save=_fasc_save,
)
[docs]
def load(
self,
data,
extracel_context=False,
intracel_context=False,
rec_context=False,
blacklist=[],
):
"""
Load a fascicle configuration from a json file
Parameters
----------
fname : str
path to the json file describing a fascicle
extracel_context: bool
if True, load the extracellular context as well
intracel_context: bool
if True, load the intracellular context as well
rec_context: bool
if True, load the recording context as well
blacklist : list[str]
key to exclude from loading
"""
if type(data) == str:
key_dic = json_load(data)
else:
key_dic = data
bl = [i for i in blacklist]
if not intracel_context:
bl += [
"N_intra",
"intra_stim_position",
"intra_stim_t_start",
"intra_stim_duration",
"intra_stim_amplitude",
"intra_stim_ON",
]
if not extracel_context:
bl += [
"extra_stim",
"footprints",
"myelinated_nseg_per_sec",
"unmyelinated_nseg",
"is_footprinted",
]
if not rec_context:
bl += ["record", "recorder"]
super().load(
data=key_dic,
blacklist=bl,
extracel_context=extracel_context,
intracel_context=intracel_context,
rec_context=rec_context,
)
## Nerve property method
@property
def n_fasc(self):
"""
Number of fascicles in the nerves
Returns
-------
int
Number of fascicles.
"""
return len(self.fascicles)
@property
def n_ax(self):
"""
Number of axons in the nerves
Returns
-------
int
Number of axons.
"""
return self.get_n_ax()
[docs]
def get_n_ax(self, id_fasc: int | None = None) -> int:
"""
Returns the number of axons in a given fascicle or in all the nerve
Parameters
----------
id_fasc : _type_, optional
ID of the fascicle from which the number of axons is returned; if None, number of all axons in the nerve is returned, by default None
Returns
-------
int
Number of axons.
"""
n = 0
if id_fasc is None:
id_fasc = self.fascicles.keys()
elif not np.iterable(id_fasc):
id_fasc = [id_fasc]
for i_fasc in id_fasc:
if i_fasc in self.fascicles:
fasc = self.fascicles[i_fasc]
n += fasc.n_ax
return n
[docs]
def set_ID(self, ID):
"""
set the ID of the nerve
Parameters
----------
ID : int
ID number of the nerve
"""
self.ID = ID
[docs]
def define_length(self, L):
"""
Set the length over the x axis of the nerve
Parameters
----------
L : float
length of the nerve in um.
"""
if self.extra_stim is not None or self.N_intra > 0:
rise_warning(
"Modifying length of a fascicle with extra or intra cellular context can lead to error"
)
self.L = L
self.set_axons_parameters(unmyelinated_nseg=self.L // 25)
for fasc in self.fascicles.values():
fasc.define_length(L)
if self.is_extra_stim:
self.extra_stim.reshape_nerve(
Nerve_D=self.D,
Length=self.L,
y_c=self.y_grav_center,
z_c=self.z_grav_center,
)
## generate stereotypic Nerve
[docs]
def define_circular_contour(self, D, y_c=None, z_c=None):
"""
Define a circular countour to the nerve
Parameters
----------
D : float
diameter of the circular nerve contour, in um
y_c : float
y coordinate of the circular contour center, in um
z_c : float
z coordinate of the circular contour center, in um
"""
self.type = "Circular"
self.D = D
if y_c is not None:
self.y_grav_center = y_c
if z_c is not None:
self.z_grav_center = z_c
self.A = np.pi * (D / 2) ** 2
if self.is_extra_stim:
pass_info("FEM nerve resized!")
self.extra_stim.reshape_nerve(
Nerve_D=self.D,
Length=self.L,
y_c=self.y_grav_center,
z_c=self.z_grav_center,
)
[docs]
def get_circular_contour(self):
"""
Returns the properties of the nerve contour considered as a circle (y and z center and diameter)
Returns
-------
D : float
diameter of the contour, in um. Set to 0 if not applicable
y : float
y position of the contour center, in um
z : float
z position of the contour center, in um
"""
y = self.y_grav_center
z = self.z_grav_center
D = self.D
return D, y, z
[docs]
def fit_circular_contour(self, y_c=None, z_c=None, delta=20, Delta=None):
"""
Define a circular countour to the nerve
Parameters
----------
y_c : float
y coordinate of the circular contour center, in um
z_c : float
z coordinate of the circular contour center, in um
delta : float
distance between farest axon and contour, in um
"""
if Delta is not None:
rise_warning(DeprecationWarning, "please use delta instead of Delta")
delta = Delta
N_fasc = len(self.fascicles)
D = 2 * delta
if y_c is not None:
self.y_grav_center = y_c
if z_c is not None:
self.z_grav_center = z_c
if N_fasc == 0:
pass_info("No fascicles to fit - Nerve diameter set to " + str(D) + "um")
else:
_r = 0
for fasc in self.fascicles.values():
y_tr, z_tr = fasc.geom.get_trace(n_theta=1000)
_r_fasc = np.hypot(
(y_tr - self.y_grav_center), (z_tr - self.z_grav_center)
).max()
_r = np.max((_r, _r_fasc))
self.D = 2 * (_r + delta)
print(self.D)
[docs]
def define_ellipsoid_contour(self, a, b, y_c=0, z_c=0, rotate=0):
"""
Define ellipsoidal contour
"""
pass
## generate Fascicle from histology
[docs]
def import_contour(self, smthing_else):
"""
Define contour from a file
"""
pass
## Fascicles handling methods
[docs]
def get_fascicles(self, ID_only=False):
"""
Return a :class:`._fascicle.fascicle` object from it ID in the nerve
Parameters
----------
ID_only : bool, optional
ID of the fascicle, by default False
Returns
-------
_type_
_description_
"""
if ID_only:
return self.fascicles_IDs
else:
return self.fascicles
[docs]
def add_fascicle(
self,
fascicle,
ID: None | int = None,
y: None | float = None,
z: None | float = None,
rot: None | float = None,
degree: bool = False,
**kwargs,
):
"""
Add a fascicle to the list of fascicles
Parameters
----------
fascicle : object
object that can be load to a fascicle to add to the nerve struture
ID : None | int, optional
ID of the fascicle, if None keep the value of from `fascicle`, by default None
y : None | float, optional
y-position of the fascicle, if None keep the value of from `fascicle`, by default None
z : None | float, optional
y-position of the fascicle, if None keep the value of from `fascicle`, by default None
rot : None | float, optional
Rotation angle of the fascicle, if None keep the value of from `fascicle`, by default None
degree : bool, optional
if true angle is considered in degree, by default False
"""
fasc = load_any(fascicle, **kwargs)
if not is_fascicle(fasc):
rise_warning(
"Only fascile (nrv object, dict or file) can be added with add fascicle method: nothing added"
)
else:
if ID is not None:
fasc.set_ID(ID)
if y is None:
y = fasc.y
if z is None:
z = fasc.z
fasc.translate(y - fasc.y, z - fasc.z)
if rot is not None:
fasc.rotate(rot, degree=degree)
if self.__check_fascicle_overlap(fasc):
rise_warning(
"fascicles overlap: fasicle " + str(fasc.ID) + " cannot be added"
)
else:
if fasc.ID in self.fascicles_IDs:
pass_info(
"Fascicle "
+ str(fasc.ID)
+ " already in the nerve: will be replace"
)
self.fascicles[fasc.ID]
else:
self.fascicles_IDs += [fasc.ID]
self.fascicles[fasc.ID] = fasc
self.__merge_fascicular_context(self.fascicles[fasc.ID])
self.fascicles[fasc.ID].define_length(self.L)
def __check_fascicle_overlap(self, fasc: fascicle):
"""
To handle in fascicle group
"""
if len(self.fascicles) == 0:
return False
fasc_geom = [fasc_i.geom for fasc_i in self.fascicles.values()]
return any(cshape_overlap_checker(s=fasc.geom, s_comp=fasc_geom))
def __merge_fascicular_context(self, fasc: fascicle):
"""
Merge a fascicle-local stimulation/recording context into the nerve context.
Parameters
----------
fasc : fascicle
Fascicle whose local context must be incorporated.
"""
if self.is_extra_stim:
self.extra_stim.reshape_fascicle(fasc.geom, fasc.ID)
if fasc.extra_stim is not None:
self.attach_extracellular_stimulation(fasc.extra_stim)
if fasc.recorder is not None:
self.attach_extracellular_recorder(fasc.recorder)
fasc.clear_context(intracel_context=False)
## Axons handling method
[docs]
def set_axons_parameters(
self, unmyelinated_only=False, myelinated_only=False, **kwargs
):
"""
set parameters of axons in the nerve
Parameters
----------
unmyelinated_only: bool
if true modification only done for unmyelinated axons parameters, by default False
myelinated_only: bool
if true modification only done for myelinated axons parameters, by default False
kwargs:
parameters to modify (see myelinated and unmyelinated)
"""
## Standard keys
for key in kwargs:
if "model" in key:
if not myelinated_only and kwargs[key] in unmyelinated_models:
self.unmyelinated_param["model"] = kwargs[key]
elif not unmyelinated_only and kwargs[key] in myelinated_models:
self.myelinated_param["model"] = kwargs[key]
else:
rise_warning(kwargs[key], " is not an implemented axon model")
else:
if not myelinated_only and key in self.unmyelinated_param:
self.unmyelinated_param[key] = kwargs[key]
if not unmyelinated_only and key in self.myelinated_param:
self.myelinated_param[key] = kwargs[key]
## Specific keys
if "unmyelinated_nseg" in kwargs:
self.unmyelinated_param["Nseg_per_sec"] = kwargs["unmyelinated_nseg"]
if "myelinated_nseg_per_sec" in kwargs:
self.myelinated_param["Nseg_per_sec"] = kwargs["myelinated_nseg_per_sec"]
if "Adelta_limit" in kwargs:
self.Adelta_limit = kwargs["self.Adelta_limit"]
for fasc in self.fascicles.values():
fasc.set_axons_parameters(unmyelinated_only=True, **self.unmyelinated_param)
fasc.set_axons_parameters(myelinated_only=True, **self.myelinated_param)
[docs]
def get_axons_parameters(self, unmyelinated_only=False, myelinated_only=False):
"""
get parameters of axons in the nerve
Parameters
----------
unmyelinated_only: bool
modification will only
myelinated_only: bool
modification will only
"""
if unmyelinated_only:
return self.unmyelinated_param
if myelinated_only:
return self.myelinated_param
return self.unmyelinated_param, self.myelinated_param
## Representation methods
[docs]
def plot(
self,
axes: plt.axes,
contour_color: str = "k",
myel_color: str = "b",
unmyel_color: str = "r",
elec_color: str = "gold",
num: bool = False,
**kwgs,
):
"""
plot the nerve in the Y-Z plane (transverse section)
Parameters
----------
axes : matplotlib.axes
axes of the figure to display the fascicle
contour_color : str
matplotlib color string applied to the contour. Black by default
myel_color : str
matplotlib color string applied to the myelinated axons. Red by default
unmyel_color : str
matplotlib color string applied to the myelinated axons. Blue by default
elec_color : str
matplotlib color string applied to the electrodes axons. Blue by default
num : bool
if True, the index of each axon is displayed on top of the circle
"""
## plot contour
axes.add_patch(
plt.Circle(
(self.y_grav_center, self.z_grav_center),
self.D / 2,
color=contour_color,
fill=False,
linewidth=4,
)
)
for i in self.fascicles:
fasc = self.fascicles[i]
fasc.plot(
axes=axes,
contour_color="grey",
myel_color=myel_color,
unmyel_color=unmyel_color,
num=num,
)
if self.extra_stim is not None:
self.extra_stim.plot(axes=axes, color=elec_color, nerve_d=self.D)
axes.set_xlim((-1.1 * self.D / 2, 1.1 * self.D / 2))
axes.set_ylim((-1.1 * self.D / 2, 1.1 * self.D / 2))
## Context handling methods
[docs]
def clear_context(
self, extracel_context=True, intracel_context=True, rec_context=True
):
"""
Clear all stimulation and recording mecanism
"""
if extracel_context:
self.L = None
# extra-cellular stimulation
self.extra_stim = None
self.added_extra_stims = None
self.footprints = {}
self.is_footprinted = False
if intracel_context:
# intra-cellular stimulation
self.N_intra = 0
self.intra_stim_position = []
self.intra_stim_t_start = []
self.intra_stim_duration = []
self.intra_stim_amplitude = []
self.intra_stim_ON = []
if rec_context:
## recording mechanism
self.record = False
self.recorder = None
self.is_simulated = False
# Intra cellular
[docs]
def insert_I_Clamp(
self,
position: float,
t_start: float,
duration: float,
amplitude: float,
expr: str | None = None,
mask_labels: None | Iterable[str] | str = [],
ax_list: None | list = None,
fasc_list: None | list = None,
):
"""
Insert a IC clamp stimulation
Parameters
----------
position : float
relative position over the fascicle. Note that all thin myelinated and myelinated
will be stimulated in the nearest node of Ranvier around the clamp specified position
t_start : float
starting time, in ms
duration : float
duration of the pulse, in ms
amplitude : float
amplitude of the pulse (nA)
expr : str | None, optional
To select a subpopulation of axon for the clamp, If not None mask is generated using :meth:`pandas.DataFrame.eval` of this expression, by default None
mask_labels : None | Iterable[str] | str, optional
To select a subpopulation of axon for the clamp, Label or list of labels already added to the axon populations population, by default []
ax_list : None | list
To select a subpopulation of axon for the clamp, list of axons to insert the clamp on, if None, all axons are stimulated, by default None
fasc_list : None | list
To select a subpopulation of axon for the clamp, list of fascicle to insert the clamp on, if None, all fascicle are stimulated, by default None
"""
if fasc_list is None:
fasc_list = list(self.fascicles.keys())
for _fid, fasc in self.fascicles.items():
if _fid in fasc_list:
fasc.insert_I_Clamp(
position=position,
t_start=t_start,
duration=duration,
amplitude=amplitude,
mask_labels=mask_labels,
expr=expr,
ax_list=ax_list,
)
self.N_intra += 1
[docs]
def clear_I_clamp(self):
"""
Clear any I-clamp attached to the nerve
"""
for fasc in self.fascicles.values():
fasc.clear_I_clamp()
self.N_intra = 0
# Extracellular
[docs]
def change_stimulus_from_electrode(self, ID_elec, stimulus):
"""
Change the stimulus of the ID_elec electrods
Parameters
----------
ID_elec : int
ID of the electrode which should be changed
stimulus : stimulus
New stimulus to set
"""
if self.extra_stim is not None:
self.extra_stim.change_stimulus_from_electrode(ID_elec, stimulus)
else:
rise_warning("Cannot be changed: no extrastim in the nerve")
def __check_perineurium_electrode_overlap(self, electrode: electrode) -> int:
"""
Check if an electrode overlap with one fascicle perineurium of the nerve.
Paramters
---------
electrode : electrode
electrode to check
Returns
-------
int
ID of the fascicle overlapping, -1 if no overlap
"""
y, z, d = 0, 0, 0.001
# CUFF electrodes do not affect intrafascicular state
if not is_CUFF_electrode(electrode):
y = electrode.y
z = electrode.z
if is_LIFE_electrode(electrode):
d = electrode.D
e_geom = create_cshape(center=(y, z), diameter=d)
for fasc in self.fascicles.values():
if cshape_overlap_checker(
s=fasc.geom,
s_comp=e_geom,
on_trace=True,
):
return fasc.ID
return -1
# RECORDING MECHANIMS
[docs]
def shut_recorder_down(self):
"""
Shuts down the recorder locally
"""
self.record = False
for fasc in self.fascicles.values():
fasc.shut_recorder_down()
def __set_fascicles_context(self):
""" """
for fasc in self.fascicles.values():
if self.extra_stim is not None:
fasc.attach_extracellular_stimulation(self.extra_stim)
if self.recorder is not None:
fasc.attach_extracellular_recorder(self.recorder)
def __set_fascicles_simulation_parameters(self):
"""
Propagate global nerve simulation settings to each fascicle.
"""
self.__set_fascicles_context()
for fasc in self.fascicles.values():
fasc.t_sim = self.t_sim
fasc.record_V_mem = self.record_V_mem
fasc.record_I_mem = self.record_I_mem
fasc.record_I_ions = self.record_I_ions
fasc.record_g_mem = self.record_g_mem
fasc.record_g_ions = self.record_g_ions
fasc.record_particles = self.record_particles
fasc.postproc_script = self.postproc_script
fasc.postproc_label = self.postproc_label
fasc.postproc_function = self.postproc_function
fasc.save_results = self.save_results
fasc.return_parameters_only = self.return_parameters_only
fasc.verbose = self.verbose
# Footprint and mesh
@property
def __footprint_to_compute(self):
"""
returns True only if loaded footprint are selected AND footprint exist
"""
return not (self.is_footprinted and self.loaded_footprints)
## SIMULATION
[docs]
def simulate(
self,
**kwargs,
) -> nerve_results:
"""
Simulate the nerve with the proposed extracellular context. Top level method for large
scale neural simulation.
Parameters
----------
Warning
-------
calling this method can result in long processing time, even with large computational ressources.
Keep aware of what is really implemented, ensure configuration and simulation protocol is correctly designed.
"""
if self.verbose:
pass_info("Starting nerve simulation")
nerve_sim = super().simulate(**kwargs)
self.__set_fascicles_simulation_parameters()
if (
self.save_path
): # LR: Force folder creation if any save_path is specified --> usefull for some PP functions (ex: scatter_plot)
folder_name = self.save_path + "Nerve_" + str(self.ID) + "/"
create_folder(folder_name)
if self.save_results:
folder_name = self.save_path + "Nerve_" + str(self.ID) + "/"
config_filename = folder_name + "/00_Nerve_config.json"
self.save(config_filename, fascicles_context=False)
# run FEM model
if self.__footprint_to_compute and self.has_FEM_extracel:
self.compute_electrodes_footprints()
self.loaded_footprints = True
# Simulate all fascicles
fasc_kwargs = kwargs
if self.save_path:
fasc_kwargs["save_path"] = folder_name
if self.verbose:
i_pbar = 1
for fasc in self.fascicles.values():
if self.verbose:
fasc_kwargs["pbar_label"] = f"fascicle {i_pbar}/{self.n_fasc}"
i_pbar += 1
nerve_sim["fascicle" + str(fasc.ID)] = fasc.simulate(
in_nerve=True,
**fasc_kwargs,
)
if self.verbose:
pass_info("...Done!")
# dirty hack to force NRV_class type when saved
if "extra_stim" in nerve_sim:
nerve_sim["extra_stim"] = load_any(nerve_sim["extra_stim"])
if self.record: # recorder not saved in result !!BUG
nerve_sim["recorder"] = self.recorder
self.is_simulated = True
return nerve_sim