Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 167 additions & 38 deletions pyneuroml/analysis/NML2ChannelAnalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
import os.path
import argparse
import pprint
import re
import logging
import typing

import airspeed
import matplotlib.pyplot as plt

import neuroml
from pyneuroml.pynml import run_lems_with_jneuroml, read_neuroml2_file
from pyneuroml.utils import convert_case


logger = logging.getLogger(__name__)
Expand All @@ -29,8 +31,8 @@

V = "rampCellPop0[0]/v" # Key for voltage trace in results dictionary.

MAX_COLOUR = (255, 0, 0)
MIN_COLOUR = (255, 255, 0)
MAX_COLOUR = (255, 0, 0) # type: typing.Tuple[int, int, int]
MIN_COLOUR = (255, 255, 0) # type: typing.Tuple[int, int, int]

DEFAULTS = {
"v": False,
Expand All @@ -55,8 +57,9 @@


def process_args():
"""
Parse command-line arguments.
"""Parse command-line arguments.

:returns: None
"""
parser = argparse.ArgumentParser(
description=(
Expand Down Expand Up @@ -229,16 +232,40 @@ def process_args():
return parser.parse_args()


def get_colour_hex(fract, min_colour=MIN_COLOUR, max_color=MAX_COLOUR):
rgb = [hex(int(x + (y - x) * fract)) for x, y in zip(min_colour, max_color)]
def get_colour_hex(
fract: float,
min_colour: typing.Tuple[int, int, int] = MIN_COLOUR,
max_colour: typing.Tuple[int, int, int] = MAX_COLOUR,
) -> str:
"""Get colour hex at fraction between `min_colour` and `max_colour`.

:param fract: fraction between `min_colour` and `max_colour`
:type fract: float between (0, 1)
:param min_colour: lower colour tuple (R, G, B)
:type min_colour: tuple
:param max_colour upper colour tuple (R, G, B)
:type max_colour: tuple
:returns: colour in hex representation
:rtype: string
"""
rgb = [hex(int(x + (y - x) * fract)) for x, y in zip(min_colour, max_colour)]
col = "#"
for c in rgb:
col += c[2:4] if len(c) == 4 else "00"
return col


# Better off elsewhere..?
def get_ion_color(ion):
def get_ion_color(ion: str) -> str:
"""Get colours for ions in hex format.

Hard codes for na, k, ca, h. All others get a grey.

:param ion: name of ion
:type ion: str
:returns: colour in hex
:rtype: str
"""
if ion.lower() == "na":
col = "#1E90FF"
elif ion.lower() == "k":
Expand All @@ -253,7 +280,16 @@ def get_ion_color(ion):
return col


def get_state_color(s):
def get_state_color(s: str) -> str:
"""Get colours for state variables.

Hard codes for m, k, r, h, l, n, a, b, c, q, e, f, p, s, u.

:param state: name of state
:type state: str
:returns: colour in hex format
:rtype: str
"""
col = "#000000"
if s.startswith("m"):
col = "#FF0000"
Expand Down Expand Up @@ -289,7 +325,18 @@ def get_state_color(s):
return col


def merge_with_template(model, templfile):
def merge_with_template(
model: typing.Dict[typing.Any, typing.Any], templfile: str
) -> str:
"""Merge model information with airspeed template.

:param model: model information
:type model: dict
:param templfile: name of airspeed template file
:type templfile: string
:returns: merged template string
:rtype: str
"""
if not os.path.isfile(templfile):
templfile = os.path.join(os.path.dirname(sys.argv[0]), templfile)
with open(templfile) as f:
Expand All @@ -298,24 +345,59 @@ def merge_with_template(model, templfile):


def generate_lems_channel_analyser(
channel_file,
channel,
min_target_voltage,
step_target_voltage,
max_target_voltage,
clamp_delay,
clamp_duration,
clamp_base_voltage,
duration,
erev,
gates,
temperature,
ca_conc,
iv_curve,
scale_dt=1,
dat_suffix="",
verbose=True,
channel_file: str,
channel: str,
min_target_voltage: float,
step_target_voltage: float,
max_target_voltage: float,
clamp_delay: float,
clamp_duration: float,
clamp_base_voltage: float,
duration: float,
erev: float,
gates: typing.List[str],
temperature: float,
ca_conc: float,
iv_curve: bool,
scale_dt: float = 1,
dat_suffix: str = "",
verbose: bool = True,
):
"""Generate analysis simulation LEMS file.

:param channel_file: path to channel file
:type channel_file: str
:param channel: name of channel
:type channel: string
:param min_target_voltage: magnitude of minimum target voltage in mV
:type min_target_voltage: float
:param max_target_voltage: magnitude of maximum target voltage in mV
:type max_target_voltage: float
:param clamp_delay: magnitude of delay for clamp in ms
:type clamp_delay: float
:param clamp_duration: magnitude of duration of clamp in ms
:type clamp_delay: float
:param clamp_base_voltage: magnitude of base voltage of clamp in mV
:type clamp_base_voltage: float
:param duration: magnitude of duration of simulation in ms
:type duration: float
:param erev: magnitude of reversal potential in mV
:type erev: float
:param gates: list of gates
:type gates: list of str
:param temperature: magnitude of temperature in degC
:type temperature: float
:param ca_conc: magnitude of calcium concentration in mM
:type ca_conc: float
:param iv_curve: toggle whether to plot iv curve
:type iv_curve: bool
:param scale_dt: magnitude to scale dt by in ms
:type scale_dt: float
:param dat_suffix: suffix of data file
:type dat_suffix: str
:param verbose: toggle verbosity
:type verbose: bool
"""

logger.info(
("Generating LEMS file to investigate %s in %s, %smV->%smV, " "%sdegC")
Expand All @@ -331,7 +413,7 @@ def generate_lems_channel_analyser(
target_voltages_map = []
for t in target_voltages:
fract = float(target_voltages.index(t)) / (len(target_voltages) - 1)
info = {}
info = {} # type: typing.Dict[typing.Any, typing.Any]
info["v"] = t
info["v_str"] = str(t).replace("-", "min")
info["col"] = get_colour_hex(fract)
Expand Down Expand Up @@ -370,13 +452,16 @@ def generate_lems_channel_analyser(
return merged


def convert_case(name):
"""Converts from camelCase to under_score"""
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()

def get_channels_from_channel_file(
channel_file: str,
) -> typing.List[typing.Union[neuroml.IonChannelHH, neuroml.IonChannel]]:
"""Get IonChannelHH and IonChannel instances from a NeuroML channel file.

def get_channels_from_channel_file(channel_file):
:param channel_file: complete path to a channel file
:type channel_file: str
:returns: list of channels
:rtype: list
"""
doc = read_neuroml2_file(
channel_file, include_includes=True, verbose=False, already_included=[]
)
Expand All @@ -388,15 +473,34 @@ def get_channels_from_channel_file(channel_file):
return channels


def get_includes_from_channel_file(channel_file):
def get_includes_from_channel_file(
channel_file: str,
) -> typing.List[neuroml.IncludeType]:
"""Get includes from a NeuroML channel file

:param channel_file: complete path to a channel file
:type channel_file: str
:returns: list of includes
:rtype: list
"""
doc = read_neuroml2_file(channel_file)
includes = []
for incl in doc.includes:
includes.append(incl.href)
return includes


def process_channel_file(channel_file, a):
def process_channel_file(channel_file: str, a) -> typing.List[typing.Any]:
"""Process the channel file.

:param channel_file: complete path to channel file
:type channel_file: str
:param a: argparse.Namespace instance holding all arguments
:type a: argparse.Namsepace
:returns: list of channel information
:rtype: list
:raises IOError: if channel file could not be found
"""
# Get name of channel mechanism to test
if a.v:
logger.info("Going to test channel from file: " + channel_file)
Expand Down Expand Up @@ -437,7 +541,23 @@ def process_channel_file(channel_file, a):
return channels_info


def get_channel_gates(channel):
def get_channel_gates(
channel: typing.Union[neuroml.IonChannel, neuroml.IonChannelHH]
) -> typing.List[
typing.Union[
neuroml.GateHHUndetermined,
neuroml.GateHHRates,
neuroml.GateHHTauInf,
neuroml.GateHHInstantaneous,
]
]:
"""Get gates in a channel

:param channel: channel object
:type channel: neuroml.IonChannel, neuroml.IonChannelHH
:returns: list of gates
:rtype: list
"""
channel_gates = []
for gates in [
"gates",
Expand All @@ -450,7 +570,16 @@ def get_channel_gates(channel):
return channel_gates


def get_conductance_expression(channel):
def get_conductance_expression(
channel: typing.Union[neuroml.IonChannel, neuroml.IonChannelHH]
) -> str:
"""Get expression of conductance in channel.

:param channel: channel object
:type channel: neuroml.IonChannel, neuroml.IonChannelHH
:returns: expression for conductance of channel.
:rtype: str
"""
expr = "g = gmax "
for gates in [
"gates",
Expand Down
4 changes: 3 additions & 1 deletion pyneuroml/analysis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,9 @@ def generate_current_vs_frequency_curve(
stims = []
if len(custom_amps_nA) > 0:
stims = [float(a) for a in custom_amps_nA]
stim_info = ["%snA" % float(a) for a in custom_amps_nA] # type: typing.Union[str, typing.List[str]]
stim_info = [
"%snA" % float(a) for a in custom_amps_nA
] # type: typing.Union[str, typing.List[str]]
else:
# else generate a list using the provided arguments
amp = start_amp_nA
Expand Down
10 changes: 4 additions & 6 deletions pyneuroml/pynml.py
Original file line number Diff line number Diff line change
Expand Up @@ -2522,14 +2522,12 @@ def execute_command_in_dir(

return_string = return_string.decode("utf-8") # For Python 3

logger.info(
"Command completed successfully!"
)
logger.info("Command completed successfully!")
if verbose:
logger.info(
"Output: \n %s%s"
% (prefix, return_string.replace("\n", "\n " + prefix))
)
"Output: \n %s%s"
% (prefix, return_string.replace("\n", "\n " + prefix))
)
return (0, return_string)

except AttributeError:
Expand Down
8 changes: 7 additions & 1 deletion pyneuroml/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
Copyright 2021 NeuroML Contributors
"""

import typing
import neuroml
import logging
import re


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -126,3 +126,9 @@ def extract_position_info(
positions[name] = pop_positions

return cell_id_vs_cell, pop_id_vs_cell, positions, pop_id_vs_color, pop_id_vs_radii


def convert_case(name):
"""Converts from camelCase to under_score"""
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
8 changes: 1 addition & 7 deletions pyneuroml/utils/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,7 @@


import argparse
import re


def convert_case(name):
"""Converts from camelCase to under_score"""
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
from . import convert_case


def build_namespace(DEFAULTS={}, a=None, **kwargs):
Expand Down