Skip to content

Commit a84aad2

Browse files
Merge pull request #206 from NeuroML/feat/document-channelanalysis
Feat/document channelanalysis
2 parents b38f1e5 + 08e9e86 commit a84aad2

File tree

5 files changed

+182
-53
lines changed

5 files changed

+182
-53
lines changed

pyneuroml/analysis/NML2ChannelAnalysis.py

Lines changed: 167 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99
import os.path
1010
import argparse
1111
import pprint
12-
import re
1312
import logging
13+
import typing
1414

1515
import airspeed
1616
import matplotlib.pyplot as plt
1717

18+
import neuroml
1819
from pyneuroml.pynml import run_lems_with_jneuroml, read_neuroml2_file
20+
from pyneuroml.utils import convert_case
1921

2022

2123
logger = logging.getLogger(__name__)
@@ -29,8 +31,8 @@
2931

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

32-
MAX_COLOUR = (255, 0, 0)
33-
MIN_COLOUR = (255, 255, 0)
34+
MAX_COLOUR = (255, 0, 0) # type: typing.Tuple[int, int, int]
35+
MIN_COLOUR = (255, 255, 0) # type: typing.Tuple[int, int, int]
3436

3537
DEFAULTS = {
3638
"v": False,
@@ -55,8 +57,9 @@
5557

5658

5759
def process_args():
58-
"""
59-
Parse command-line arguments.
60+
"""Parse command-line arguments.
61+
62+
:returns: None
6063
"""
6164
parser = argparse.ArgumentParser(
6265
description=(
@@ -229,16 +232,40 @@ def process_args():
229232
return parser.parse_args()
230233

231234

232-
def get_colour_hex(fract, min_colour=MIN_COLOUR, max_color=MAX_COLOUR):
233-
rgb = [hex(int(x + (y - x) * fract)) for x, y in zip(min_colour, max_color)]
235+
def get_colour_hex(
236+
fract: float,
237+
min_colour: typing.Tuple[int, int, int] = MIN_COLOUR,
238+
max_colour: typing.Tuple[int, int, int] = MAX_COLOUR,
239+
) -> str:
240+
"""Get colour hex at fraction between `min_colour` and `max_colour`.
241+
242+
:param fract: fraction between `min_colour` and `max_colour`
243+
:type fract: float between (0, 1)
244+
:param min_colour: lower colour tuple (R, G, B)
245+
:type min_colour: tuple
246+
:param max_colour upper colour tuple (R, G, B)
247+
:type max_colour: tuple
248+
:returns: colour in hex representation
249+
:rtype: string
250+
"""
251+
rgb = [hex(int(x + (y - x) * fract)) for x, y in zip(min_colour, max_colour)]
234252
col = "#"
235253
for c in rgb:
236254
col += c[2:4] if len(c) == 4 else "00"
237255
return col
238256

239257

240258
# Better off elsewhere..?
241-
def get_ion_color(ion):
259+
def get_ion_color(ion: str) -> str:
260+
"""Get colours for ions in hex format.
261+
262+
Hard codes for na, k, ca, h. All others get a grey.
263+
264+
:param ion: name of ion
265+
:type ion: str
266+
:returns: colour in hex
267+
:rtype: str
268+
"""
242269
if ion.lower() == "na":
243270
col = "#1E90FF"
244271
elif ion.lower() == "k":
@@ -253,7 +280,16 @@ def get_ion_color(ion):
253280
return col
254281

255282

256-
def get_state_color(s):
283+
def get_state_color(s: str) -> str:
284+
"""Get colours for state variables.
285+
286+
Hard codes for m, k, r, h, l, n, a, b, c, q, e, f, p, s, u.
287+
288+
:param state: name of state
289+
:type state: str
290+
:returns: colour in hex format
291+
:rtype: str
292+
"""
257293
col = "#000000"
258294
if s.startswith("m"):
259295
col = "#FF0000"
@@ -289,7 +325,18 @@ def get_state_color(s):
289325
return col
290326

291327

292-
def merge_with_template(model, templfile):
328+
def merge_with_template(
329+
model: typing.Dict[typing.Any, typing.Any], templfile: str
330+
) -> str:
331+
"""Merge model information with airspeed template.
332+
333+
:param model: model information
334+
:type model: dict
335+
:param templfile: name of airspeed template file
336+
:type templfile: string
337+
:returns: merged template string
338+
:rtype: str
339+
"""
293340
if not os.path.isfile(templfile):
294341
templfile = os.path.join(os.path.dirname(sys.argv[0]), templfile)
295342
with open(templfile) as f:
@@ -298,24 +345,59 @@ def merge_with_template(model, templfile):
298345

299346

300347
def generate_lems_channel_analyser(
301-
channel_file,
302-
channel,
303-
min_target_voltage,
304-
step_target_voltage,
305-
max_target_voltage,
306-
clamp_delay,
307-
clamp_duration,
308-
clamp_base_voltage,
309-
duration,
310-
erev,
311-
gates,
312-
temperature,
313-
ca_conc,
314-
iv_curve,
315-
scale_dt=1,
316-
dat_suffix="",
317-
verbose=True,
348+
channel_file: str,
349+
channel: str,
350+
min_target_voltage: float,
351+
step_target_voltage: float,
352+
max_target_voltage: float,
353+
clamp_delay: float,
354+
clamp_duration: float,
355+
clamp_base_voltage: float,
356+
duration: float,
357+
erev: float,
358+
gates: typing.List[str],
359+
temperature: float,
360+
ca_conc: float,
361+
iv_curve: bool,
362+
scale_dt: float = 1,
363+
dat_suffix: str = "",
364+
verbose: bool = True,
318365
):
366+
"""Generate analysis simulation LEMS file.
367+
368+
:param channel_file: path to channel file
369+
:type channel_file: str
370+
:param channel: name of channel
371+
:type channel: string
372+
:param min_target_voltage: magnitude of minimum target voltage in mV
373+
:type min_target_voltage: float
374+
:param max_target_voltage: magnitude of maximum target voltage in mV
375+
:type max_target_voltage: float
376+
:param clamp_delay: magnitude of delay for clamp in ms
377+
:type clamp_delay: float
378+
:param clamp_duration: magnitude of duration of clamp in ms
379+
:type clamp_delay: float
380+
:param clamp_base_voltage: magnitude of base voltage of clamp in mV
381+
:type clamp_base_voltage: float
382+
:param duration: magnitude of duration of simulation in ms
383+
:type duration: float
384+
:param erev: magnitude of reversal potential in mV
385+
:type erev: float
386+
:param gates: list of gates
387+
:type gates: list of str
388+
:param temperature: magnitude of temperature in degC
389+
:type temperature: float
390+
:param ca_conc: magnitude of calcium concentration in mM
391+
:type ca_conc: float
392+
:param iv_curve: toggle whether to plot iv curve
393+
:type iv_curve: bool
394+
:param scale_dt: magnitude to scale dt by in ms
395+
:type scale_dt: float
396+
:param dat_suffix: suffix of data file
397+
:type dat_suffix: str
398+
:param verbose: toggle verbosity
399+
:type verbose: bool
400+
"""
319401

320402
logger.info(
321403
("Generating LEMS file to investigate %s in %s, %smV->%smV, " "%sdegC")
@@ -331,7 +413,7 @@ def generate_lems_channel_analyser(
331413
target_voltages_map = []
332414
for t in target_voltages:
333415
fract = float(target_voltages.index(t)) / (len(target_voltages) - 1)
334-
info = {}
416+
info = {} # type: typing.Dict[typing.Any, typing.Any]
335417
info["v"] = t
336418
info["v_str"] = str(t).replace("-", "min")
337419
info["col"] = get_colour_hex(fract)
@@ -370,13 +452,16 @@ def generate_lems_channel_analyser(
370452
return merged
371453

372454

373-
def convert_case(name):
374-
"""Converts from camelCase to under_score"""
375-
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
376-
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
377-
455+
def get_channels_from_channel_file(
456+
channel_file: str,
457+
) -> typing.List[typing.Union[neuroml.IonChannelHH, neuroml.IonChannel]]:
458+
"""Get IonChannelHH and IonChannel instances from a NeuroML channel file.
378459
379-
def get_channels_from_channel_file(channel_file):
460+
:param channel_file: complete path to a channel file
461+
:type channel_file: str
462+
:returns: list of channels
463+
:rtype: list
464+
"""
380465
doc = read_neuroml2_file(
381466
channel_file, include_includes=True, verbose=False, already_included=[]
382467
)
@@ -388,15 +473,34 @@ def get_channels_from_channel_file(channel_file):
388473
return channels
389474

390475

391-
def get_includes_from_channel_file(channel_file):
476+
def get_includes_from_channel_file(
477+
channel_file: str,
478+
) -> typing.List[neuroml.IncludeType]:
479+
"""Get includes from a NeuroML channel file
480+
481+
:param channel_file: complete path to a channel file
482+
:type channel_file: str
483+
:returns: list of includes
484+
:rtype: list
485+
"""
392486
doc = read_neuroml2_file(channel_file)
393487
includes = []
394488
for incl in doc.includes:
395489
includes.append(incl.href)
396490
return includes
397491

398492

399-
def process_channel_file(channel_file, a):
493+
def process_channel_file(channel_file: str, a) -> typing.List[typing.Any]:
494+
"""Process the channel file.
495+
496+
:param channel_file: complete path to channel file
497+
:type channel_file: str
498+
:param a: argparse.Namespace instance holding all arguments
499+
:type a: argparse.Namsepace
500+
:returns: list of channel information
501+
:rtype: list
502+
:raises IOError: if channel file could not be found
503+
"""
400504
# Get name of channel mechanism to test
401505
if a.v:
402506
logger.info("Going to test channel from file: " + channel_file)
@@ -437,7 +541,23 @@ def process_channel_file(channel_file, a):
437541
return channels_info
438542

439543

440-
def get_channel_gates(channel):
544+
def get_channel_gates(
545+
channel: typing.Union[neuroml.IonChannel, neuroml.IonChannelHH]
546+
) -> typing.List[
547+
typing.Union[
548+
neuroml.GateHHUndetermined,
549+
neuroml.GateHHRates,
550+
neuroml.GateHHTauInf,
551+
neuroml.GateHHInstantaneous,
552+
]
553+
]:
554+
"""Get gates in a channel
555+
556+
:param channel: channel object
557+
:type channel: neuroml.IonChannel, neuroml.IonChannelHH
558+
:returns: list of gates
559+
:rtype: list
560+
"""
441561
channel_gates = []
442562
for gates in [
443563
"gates",
@@ -450,7 +570,16 @@ def get_channel_gates(channel):
450570
return channel_gates
451571

452572

453-
def get_conductance_expression(channel):
573+
def get_conductance_expression(
574+
channel: typing.Union[neuroml.IonChannel, neuroml.IonChannelHH]
575+
) -> str:
576+
"""Get expression of conductance in channel.
577+
578+
:param channel: channel object
579+
:type channel: neuroml.IonChannel, neuroml.IonChannelHH
580+
:returns: expression for conductance of channel.
581+
:rtype: str
582+
"""
454583
expr = "g = gmax "
455584
for gates in [
456585
"gates",

pyneuroml/analysis/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ def generate_current_vs_frequency_curve(
170170
stims = []
171171
if len(custom_amps_nA) > 0:
172172
stims = [float(a) for a in custom_amps_nA]
173-
stim_info = ["%snA" % float(a) for a in custom_amps_nA] # type: typing.Union[str, typing.List[str]]
173+
stim_info = [
174+
"%snA" % float(a) for a in custom_amps_nA
175+
] # type: typing.Union[str, typing.List[str]]
174176
else:
175177
# else generate a list using the provided arguments
176178
amp = start_amp_nA

pyneuroml/pynml.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2522,14 +2522,12 @@ def execute_command_in_dir(
25222522

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

2525-
logger.info(
2526-
"Command completed successfully!"
2527-
)
2525+
logger.info("Command completed successfully!")
25282526
if verbose:
25292527
logger.info(
2530-
"Output: \n %s%s"
2531-
% (prefix, return_string.replace("\n", "\n " + prefix))
2532-
)
2528+
"Output: \n %s%s"
2529+
% (prefix, return_string.replace("\n", "\n " + prefix))
2530+
)
25332531
return (0, return_string)
25342532

25352533
except AttributeError:

pyneuroml/utils/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
Copyright 2021 NeuroML Contributors
77
"""
88

9-
import typing
109
import neuroml
1110
import logging
11+
import re
1212

1313

1414
logger = logging.getLogger(__name__)
@@ -126,3 +126,9 @@ def extract_position_info(
126126
positions[name] = pop_positions
127127

128128
return cell_id_vs_cell, pop_id_vs_cell, positions, pop_id_vs_color, pop_id_vs_radii
129+
130+
131+
def convert_case(name):
132+
"""Converts from camelCase to under_score"""
133+
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
134+
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()

pyneuroml/utils/cli.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,7 @@
99

1010

1111
import argparse
12-
import re
13-
14-
15-
def convert_case(name):
16-
"""Converts from camelCase to under_score"""
17-
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
18-
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
12+
from . import convert_case
1913

2014

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

0 commit comments

Comments
 (0)