diff --git a/man/man1/common.h2m b/man/man1/common.h2m index edfe4dc6c..72b6d0fa2 100644 --- a/man/man1/common.h2m +++ b/man/man1/common.h2m @@ -2,6 +2,7 @@ .BR pynml (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), .BR pynml-plotspikes (1), .BR pynml-plotmorph (1), .BR pynml-modchannelanalysis (1), diff --git a/man/man1/pynml-archive.1 b/man/man1/pynml-archive.1 index daeca0924..a4925e73f 100644 --- a/man/man1/pynml-archive.1 +++ b/man/man1/pynml-archive.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH PYNML-ARCHIVE "1" "May 2023" "pynml-archive v1.0.1" "User Commands" +.TH PYNML-ARCHIVE "1" "August 2023" "pynml-archive v1.0.10" "User Commands" .SH NAME -pynml-archive \- manual page for pynml-archive v1.0.1 +pynml-archive \- manual page for pynml-archive v1.0.10 .SH DESCRIPTION usage: pynml\-archive [\-h] [\-zipfileName ] .TP @@ -29,11 +29,12 @@ Extension to use for archive. Explicit list of files to create archive of. .SH ENVIRONMENT .PP -pyNeuroML v1.0.1 (libNeuroML v0.5.0, jNeuroML v0.12.2) +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) .SH "SEE ALSO" .BR pynml (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), .BR pynml-plotspikes (1), .BR pynml-plotmorph (1), .BR pynml-modchannelanalysis (1), diff --git a/man/man1/pynml-channelanalysis.1 b/man/man1/pynml-channelanalysis.1 index 18c15c983..ad2ecb2e5 100644 --- a/man/man1/pynml-channelanalysis.1 +++ b/man/man1/pynml-channelanalysis.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH PYNML-CHANNELANALYSIS "1" "May 2023" "pynml-channelanalysis v1.0.1" "User Commands" +.TH PYNML-CHANNELANALYSIS "1" "August 2023" "pynml-channelanalysis v1.0.10" "User Commands" .SH NAME -pynml-channelanalysis \- manual page for pynml-channelanalysis v1.0.1 +pynml-channelanalysis \- manual page for pynml-channelanalysis v1.0.10 .SH DESCRIPTION usage: pynml\-channelanalysis [\-h] [\-v] [\-minV ] [\-maxV ] .TP @@ -94,11 +94,12 @@ Save currents through voltage clamp at each level & plot current vs voltage for ion channel .SH ENVIRONMENT .PP -pyNeuroML v1.0.1 (libNeuroML v0.5.0, jNeuroML v0.12.2) +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) .SH "SEE ALSO" .BR pynml (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), .BR pynml-plotspikes (1), .BR pynml-plotmorph (1), .BR pynml-modchannelanalysis (1), diff --git a/man/man1/pynml-channelml2nml.1 b/man/man1/pynml-channelml2nml.1 index 14c84df91..0fe81c655 100644 --- a/man/man1/pynml-channelml2nml.1 +++ b/man/man1/pynml-channelml2nml.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH PYNML-CHANNELML2NML "1" "May 2023" "pynml-channelml2nml v1.0.1" "User Commands" +.TH PYNML-CHANNELML2NML "1" "August 2023" "pynml-channelml2nml v1.0.10" "User Commands" .SH NAME -pynml-channelml2nml \- manual page for pynml-channelml2nml v1.0.1 +pynml-channelml2nml \- manual page for pynml-channelml2nml v1.0.10 .SH DESCRIPTION usage: pynml\-channelml2nml [\-h] [\-xsltfile ] .TP @@ -25,11 +25,12 @@ Path to the XSLT file Name of the outputfile file .SH ENVIRONMENT .PP -pyNeuroML v1.0.1 (libNeuroML v0.5.0, jNeuroML v0.12.2) +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) .SH "SEE ALSO" .BR pynml (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), .BR pynml-plotspikes (1), .BR pynml-plotmorph (1), .BR pynml-modchannelanalysis (1), diff --git a/man/man1/pynml-modchananalysis.1 b/man/man1/pynml-modchananalysis.1 index 3960db818..51974e119 100644 --- a/man/man1/pynml-modchananalysis.1 +++ b/man/man1/pynml-modchananalysis.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH PYNML-MODCHANANALYSIS "1" "May 2023" "pynml-modchananalysis v1.0.1" "User Commands" +.TH PYNML-MODCHANANALYSIS "1" "August 2023" "pynml-modchananalysis v1.0.10" "User Commands" .SH NAME -pynml-modchananalysis \- manual page for pynml-modchananalysis v1.0.1 +pynml-modchananalysis \- manual page for pynml-modchananalysis v1.0.10 .SH DESCRIPTION usage: pynml\-modchananalysis [\-h] [\-v] [\-nogui] [\-minV ] .TP @@ -56,11 +56,12 @@ in mM) Name of the mod file containing the channel .SH ENVIRONMENT .PP -pyNeuroML v1.0.1 (libNeuroML v0.5.0, jNeuroML v0.12.2) +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) .SH "SEE ALSO" .BR pynml (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), .BR pynml-plotspikes (1), .BR pynml-plotmorph (1), .BR pynml-modchannelanalysis (1), diff --git a/man/man1/pynml-plotchan.1 b/man/man1/pynml-plotchan.1 new file mode 100644 index 000000000..0a3f2bae6 --- /dev/null +++ b/man/man1/pynml-plotchan.1 @@ -0,0 +1,42 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. +.TH PYNML-PLOTCHAN "1" "August 2023" "pynml-plotchan v1.0.10" "User Commands" +.SH NAME +pynml-plotchan \- manual page for pynml-plotchan v1.0.10 +.SH DESCRIPTION +usage: pynml\-plotchan [\-h] [\-noDistancePlots] [\-nogui] +.IP + [ ...] +.PP +A script to generate channel density plotsfor different ion channels on a +NeuroML2cell +.SS "positional arguments:" +.TP + +Name of the NeuroML 2 file(s) +.SS "options:" +.TP +\fB\-h\fR, \fB\-\-help\fR +show this help message and exit +.TP +\fB\-noDistancePlots\fR +Do not generate distance plots +.TP +\fB\-nogui\fR +Do not show plots as they are generated +.SH ENVIRONMENT +.PP +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) +.SH "SEE ALSO" +.BR pynml (1), +.BR pynml-channelanalysis (1), +.BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), +.BR pynml-plotspikes (1), +.BR pynml-plotmorph (1), +.BR pynml-modchannelanalysis (1), +.BR pynml-povray (1), +.BR pynml-sonata (1), +.BR pynml-summary (1), +.BR pynml-tune (1). +.PP +Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. diff --git a/man/man1/pynml-plotmorph.1 b/man/man1/pynml-plotmorph.1 index 40736895d..f5d826389 100644 --- a/man/man1/pynml-plotmorph.1 +++ b/man/man1/pynml-plotmorph.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH PYNML-PLOTMORPH "1" "May 2023" "pynml-plotmorph v1.0.1" "User Commands" +.TH PYNML-PLOTMORPH "1" "August 2023" "pynml-plotmorph v1.0.10" "User Commands" .SH NAME -pynml-plotmorph \- manual page for pynml-plotmorph v1.0.1 +pynml-plotmorph \- manual page for pynml-plotmorph v1.0.10 .SH DESCRIPTION usage: pynml\-plotmorph [\-h] [\-v] [\-nogui] [\-plane2d ] .TP @@ -51,11 +51,12 @@ Scale axes so that image is approximately square, for 2D plot .SH ENVIRONMENT .PP -pyNeuroML v1.0.1 (libNeuroML v0.5.0, jNeuroML v0.12.2) +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) .SH "SEE ALSO" .BR pynml (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), .BR pynml-plotspikes (1), .BR pynml-plotmorph (1), .BR pynml-modchannelanalysis (1), diff --git a/man/man1/pynml-plotspikes.1 b/man/man1/pynml-plotspikes.1 index f3e2533f8..535222e99 100644 --- a/man/man1/pynml-plotspikes.1 +++ b/man/man1/pynml-plotspikes.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH PYNML-PLOTSPIKES "1" "May 2023" "pynml-plotspikes v1.0.1" "User Commands" +.TH PYNML-PLOTSPIKES "1" "August 2023" "pynml-plotspikes v1.0.10" "User Commands" .SH NAME -pynml-plotspikes \- manual page for pynml-plotspikes v1.0.1 +pynml-plotspikes \- manual page for pynml-plotspikes v1.0.10 .SH DESCRIPTION usage: pynml\-plotspikes [\-h] [\-format ] [\-rates] [\-showPlotsAlready] .TP @@ -44,11 +44,12 @@ Window for rate calculation in ms Number of bins for rate histogram .SH ENVIRONMENT .PP -pyNeuroML v1.0.1 (libNeuroML v0.5.0, jNeuroML v0.12.2) +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) .SH "SEE ALSO" .BR pynml (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), .BR pynml-plotspikes (1), .BR pynml-plotmorph (1), .BR pynml-modchannelanalysis (1), diff --git a/man/man1/pynml-povray.1 b/man/man1/pynml-povray.1 index 346890b60..9e64faa47 100644 --- a/man/man1/pynml-povray.1 +++ b/man/man1/pynml-povray.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH PYNML-POVRAY "1" "May 2023" "pynml-povray v1.0.1" "User Commands" +.TH PYNML-POVRAY "1" "August 2023" "pynml-povray v1.0.10" "User Commands" .SH NAME -pynml-povray \- manual page for pynml-povray v1.0.1 +pynml-povray \- manual page for pynml-povray v1.0.10 .SH DESCRIPTION usage: pynml\-povray [\-h] [\-split] [\-background ] [\-movie] .TP @@ -96,11 +96,12 @@ cell/network Show segment ids .SH ENVIRONMENT .PP -pyNeuroML v1.0.1 (libNeuroML v0.5.0, jNeuroML v0.12.2) +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) .SH "SEE ALSO" .BR pynml (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), .BR pynml-plotspikes (1), .BR pynml-plotmorph (1), .BR pynml-modchannelanalysis (1), diff --git a/man/man1/pynml-sonata.1 b/man/man1/pynml-sonata.1 index e1e7fdad3..27c76552b 100644 --- a/man/man1/pynml-sonata.1 +++ b/man/man1/pynml-sonata.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH PYNML-SONATA "1" "May 2023" "pynml-sonata v1.0.1" "User Commands" +.TH PYNML-SONATA "1" "August 2023" "pynml-sonata v1.0.10" "User Commands" .SH NAME -pynml-sonata \- manual page for pynml-sonata v1.0.1 +pynml-sonata \- manual page for pynml-sonata v1.0.10 .SH DESCRIPTION usage: pynml\-sonata [\-h] [\-h5] [\-jnml] [\-neuron] .IP @@ -34,11 +34,12 @@ Execute the generated LEMS/NeuroML2 model with jNeuroML_NEURON .SH ENVIRONMENT .PP -pyNeuroML v1.0.1 (libNeuroML v0.5.0, jNeuroML v0.12.2) +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) .SH "SEE ALSO" .BR pynml (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), .BR pynml-plotspikes (1), .BR pynml-plotmorph (1), .BR pynml-modchannelanalysis (1), diff --git a/man/man1/pynml-summary.1 b/man/man1/pynml-summary.1 index 63e94f857..ebdce7cd2 100644 --- a/man/man1/pynml-summary.1 +++ b/man/man1/pynml-summary.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH PYNML-SUMMARY "1" "May 2023" "pynml-summary v1.0.1" "User Commands" +.TH PYNML-SUMMARY "1" "August 2023" "pynml-summary v1.0.10" "User Commands" .SH NAME -pynml-summary \- manual page for pynml-summary v1.0.1 +pynml-summary \- manual page for pynml-summary v1.0.10 .SH DESCRIPTION Usage: .PP @@ -19,11 +19,12 @@ enable verbose mode print this help text and exit .SH ENVIRONMENT .PP -pyNeuroML v1.0.1 (libNeuroML v0.5.0, jNeuroML v0.12.2) +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) .SH "SEE ALSO" .BR pynml (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), .BR pynml-plotspikes (1), .BR pynml-plotmorph (1), .BR pynml-modchannelanalysis (1), diff --git a/man/man1/pynml-tune.1 b/man/man1/pynml-tune.1 index f8703d0c3..fd49d41a5 100644 --- a/man/man1/pynml-tune.1 +++ b/man/man1/pynml-tune.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH PYNML-TUNE "1" "May 2023" "pynml-tune v1.0.1" "User Commands" +.TH PYNML-TUNE "1" "August 2023" "pynml-tune v1.0.10" "User Commands" .SH NAME -pynml-tune \- manual page for pynml-tune v1.0.1 +pynml-tune \- manual page for pynml-tune v1.0.10 .SH DESCRIPTION usage: pynml\-tune [\-h] [\-simTime ] [\-dt
] .IP @@ -132,11 +132,12 @@ Should (some) generated files, e.g. *.dat, be deleted as optimisation progresses? .SH ENVIRONMENT .PP -pyNeuroML v1.0.1 (libNeuroML v0.5.0, jNeuroML v0.12.2) +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) .SH "SEE ALSO" .BR pynml (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), .BR pynml-plotspikes (1), .BR pynml-plotmorph (1), .BR pynml-modchannelanalysis (1), diff --git a/man/man1/pynml.1 b/man/man1/pynml.1 index b3717671d..47cbca4bb 100644 --- a/man/man1/pynml.1 +++ b/man/man1/pynml.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH PYNML "1" "May 2023" "pynml v1.0.1" "User Commands" +.TH PYNML "1" "August 2023" "pynml v1.0.10" "User Commands" .SH NAME -pynml \- manual page for pynml v1.0.1 +pynml \- manual page for pynml v1.0.10 .SH DESCRIPTION usage: pynml [\-h|\-\-help] [] .PP @@ -180,11 +180,12 @@ latest Schema v1.8.1 Schema .SH ENVIRONMENT .PP -pyNeuroML v1.0.1 (libNeuroML v0.5.0, jNeuroML v0.12.2) +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) .SH "SEE ALSO" .BR pynml (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-plotchan (1), .BR pynml-plotspikes (1), .BR pynml-plotmorph (1), .BR pynml-modchannelanalysis (1), diff --git a/man/man1/version.h2m b/man/man1/version.h2m index 6b890d914..8fbcc8210 100644 --- a/man/man1/version.h2m +++ b/man/man1/version.h2m @@ -1,3 +1,3 @@ [environment] .PP -pyNeuroML v1.0.1 (libNeuroML v0.5.0, jNeuroML v0.12.2) +pyNeuroML v1.0.10 (libNeuroML v0.5.3, jNeuroML v0.12.2) diff --git a/pyneuroml/analysis/ChannelDensityPlot.py b/pyneuroml/analysis/ChannelDensityPlot.py index afe7602f1..e4f1d121d 100644 --- a/pyneuroml/analysis/ChannelDensityPlot.py +++ b/pyneuroml/analysis/ChannelDensityPlot.py @@ -4,18 +4,41 @@ # ion channel densities in NeuroML2 cells # -import os -import math import logging - +import math +import os import pprint - +import typing +from collections import OrderedDict + +import matplotlib +from matplotlib.colors import LinearSegmentedColormap +import matplotlib.pyplot as plt +import numpy +from neuroml import ( + Cell, + Cell2CaPools, + ChannelDensity, + ChannelDensityGHK, + ChannelDensityGHK2, + ChannelDensityNernst, + ChannelDensityNernstCa2, + ChannelDensityNonUniform, + ChannelDensityNonUniformGHK, + ChannelDensityNonUniformNernst, + ChannelDensityVShift, + VariableParameter, +) +from pyneuroml.plot.Plot import generate_plot +from pyneuroml.plot.PlotMorphology import plot_2D_cell_morphology from pyneuroml.pynml import get_value_in_si, read_neuroml2_file -from pyneuroml.analysis.NML2ChannelAnalysis import get_ion_color -from neuroml import Cell, Cell2CaPools - +from pyneuroml.utils import get_ion_color +from pyneuroml.utils.cli import build_namespace +from sympy import sympify +import argparse logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) pp = pprint.PrettyPrinter(depth=6) @@ -30,22 +53,47 @@ substitute_ion_channel_names = {"LeakConductance": "Pas"} +CHANNEL_DENSITY_PLOTTER_CLI_DEFAULTS = { + "nogui": False, + "noDistancePlots": False, +} -# redefined: TODO Check -def get_ion_color(ion): - col = [] - if ion.lower() == "na": - col = [30, 144, 255] - elif ion.lower() == "k": - col = [205, 92, 92] - elif ion.lower() == "ca": - col = [143, 188, 143] - elif ion.lower() == "h": - col = [255, 217, 179] - else: - col = [169, 169, 169] - return col +def channel_density_plotter_process_args(): + """Parse command-line arguments. + + :returns: None + """ + parser = argparse.ArgumentParser( + description=( + "A script to generate channel density plots" + "for different ion channels on a NeuroML2" + "cell" + ) + ) + + parser.add_argument( + "cellFiles", + type=str, + nargs="+", + metavar="", + help="Name of the NeuroML 2 file(s)", + ) + + parser.add_argument( + "-noDistancePlots", + action="store_true", + default=CHANNEL_DENSITY_PLOTTER_CLI_DEFAULTS["noDistancePlots"], + help=("Do not generate distance plots"), + ) + parser.add_argument( + "-nogui", + action="store_true", + default=CHANNEL_DENSITY_PLOTTER_CLI_DEFAULTS["nogui"], + help=("Do not show plots as they are generated"), + ) + + return parser.parse_args() def _get_rect(ion_channel, row, max_, min_, r, g, b, extras=False): @@ -73,7 +121,7 @@ def _get_rect(ion_channel, row, max_, min_, r, g, b, extras=False): + str(offset) + '" width="' + str(width) - + '" + height="' + + '" height="' + str(height) + '" style="fill:rgb(' + str(r) @@ -274,7 +322,6 @@ def generate_channel_density_plots( row += 1 if passives_erevs: - if ena: sb += add_text(row, "E Na = %s " % ena) row += 1 @@ -335,10 +382,472 @@ def add_text(row, text): ) +def get_channel_densities( + nml_cell: Cell, +) -> typing.Dict[ + str, + typing.List[ + typing.Union[ + ChannelDensity, + ChannelDensityGHK, + ChannelDensityGHK2, + ChannelDensityVShift, + ChannelDensityNernst, + ChannelDensityNernstCa2, + ChannelDensityNonUniform, + ChannelDensityNonUniformGHK, + ChannelDensityNonUniformNernst, + ] + ], +]: + """Get channel densities from a NeuroML Cell. + + :param nml_cell: a NeuroML cell object + :type nml_cell: neuroml.Cell + :returns: ordered dictionary of channel densities on cell with the ion + channel id as the key, and list of channel density objects as the value + """ + # order matters because if two channel densities apply conductances to same + # segments, only the latest value is applied + channel_densities = OrderedDict() # type: typing.Dict[str, typing.List[typing.Any]] + dens = nml_cell.biophysical_properties.membrane_properties.info( + show_contents=True, return_format="dict" + ) + for name, obj in dens.items(): + logger.debug(f"Name: {name}") + # channel_densities; channel_density_nernsts, etc + if name.startswith("channel_densit"): + for m in obj["members"]: + try: + channel_densities[m.ion_channel].append(m) + except KeyError: + channel_densities[m.ion_channel] = [] + channel_densities[m.ion_channel].append(m) + + logger.debug(f"Found channel densities: {channel_densities}") + return channel_densities + + +def get_conductance_density_for_segments( + cell: Cell, + channel_density: typing.Union[ + ChannelDensity, + ChannelDensityGHK, + ChannelDensityGHK2, + ChannelDensityVShift, + ChannelDensityNernst, + ChannelDensityNernstCa2, + ChannelDensityNonUniform, + ChannelDensityNonUniformGHK, + ChannelDensityNonUniformNernst, + ], + seg_ids: typing.Optional[typing.Union[str, typing.List[str]]] = None, +) -> typing.Dict[int, float]: + """Get conductance density for provided segments to be able to generate a + morphology plot. + + If no segment ids are provided, provide values for all segments that the + channel density is present on. + + For uniform channel densities, the value is reported in SI units, but for + non-uniform channel densities, for example ChannelDensityNonUniform, where + the conductance density can be a function of an arbitrary variable, like + distance from soma, the conductance density can be provided by an arbitrary + function. In this case, the units of the conductance are not reported since + the arbitrary function only provides a magnitude. + + For non-uniform channel densities, we evaluate the provided expression + using sympy.sympify. + + :param cell: a NeuroML Cell + :type cell: Cell + :param seg_ids: segment id or list of segment ids to report, if None, + report on all segments that channel density is present + :type seg_ids: None or str or list(str) + :param channel_density: a channel density object + :type channel_density: ChannelDensityGHK or ChannelDensityGHK2 or ChannelDensityVShift or ChannelDensityNernst or ChannelDensityNernstCa2 or ChannelDensityNonUniform or ChannelDensityNonUniformGHK or ChannelDensityNonUniformNernst, + :returns: dictionary with keys as segment ids and the conductance density + for that segment as the value + + .. versionadded:: 1.0.10 + + """ + data = {} + segments = [] + + # for uniform + try: + seg_group_name = channel_density.segment_groups + # for NonUniform + except AttributeError: + seg_group_name = channel_density.variable_parameters[0].segment_groups + seg_group = cell.get_segment_group(seg_group_name) + segments = cell.get_all_segments_in_group(seg_group) + + # add any segments explicitly listed + try: + segments.extend(channel_density.segments) + except TypeError: + pass + # non uniform channel densities do not have a segments child element + except AttributeError: + pass + + # filter to seg_ids + if seg_ids is not None: + if type(seg_ids) == str: + segments = [seg_ids] + else: + segments = list(set(seg_ids) & set(segments)) + + if "NonUniform" not in channel_density.__class__.__name__: + logger.debug(f"Got a uniform channel density: {channel_density.id}") + + for seg in cell.morphology.segments: + if seg.id in segments: + value = get_value_in_si(channel_density.cond_density) + if value is not None: + data[seg.id] = value + else: + # get the inhomogeneous param/value from the channel density + param = channel_density.variable_parameters[0] # type: VariableParameter + inhom_val = param.inhomogeneous_value.value + # H(x) -> Heaviside(x, 0) + if "H" in inhom_val: + newstr = inhom_val.replace("H", "Heaviside") + """ + # not needed, we use the same H as in sympy + for arg in preorder_traversal(inhom_expr): + if isinstance(arg, Function) and str(arg.func) == "H": + newstr = newstr.replace(str(arg.args[0]), f"{arg.args[0]}, 0") + newstr = newstr.replace("H(", "Heaviside(") + """ + inhom_expr = sympify(newstr) + else: + inhom_expr = sympify(inhom_val) + inhom_param_id = param.inhomogeneous_value.inhomogeneous_parameters + logger.debug(f"Inhom value: {inhom_val}, Inhom param id: {inhom_param_id}") + + inhom_params = seg_group.inhomogeneous_parameters + req_inhom_param = None + for p in inhom_params: + if p.id == inhom_param_id: + req_inhom_param = p + break + if req_inhom_param is None: + raise ValueError( + f"Could not find InhomogeneousValue definition for id: {inhom_param_id}" + ) + logger.debug(f"InhomogeneousParameter found: {req_inhom_param.id}") + expr_variable = req_inhom_param.variable + + # TODO: can probably speed this up using lambdify: + # https://docs.sympy.org/latest/tutorials/intro-tutorial/basic_operations.html#lambdify + # code currently not slow, so leaving this for the future + for seg in cell.morphology.segments: + if seg.id in segments: + distance_to_seg = cell.get_distance(seg.id) + data[seg.id] = float(inhom_expr.subs(expr_variable, distance_to_seg)) + + return data + + +def plot_channel_densities( + cell: Cell, + channel_density_ids: typing.Optional[typing.Union[str, typing.List[str]]] = None, + ion_channels: typing.Optional[typing.Union[str, typing.List[str]]] = None, + ymin: typing.Optional[float] = None, + ymax: typing.Optional[float] = None, + colormap_name: str = "autumn_r", + plane2d: str = "xy", + distance_plots: bool = False, + show_plots_already: bool = True, + morph_plot_type: str = "constant", + morph_min_width: float = 2.0, +): + """Plot channel densities on a Cell on morphology plots. + + You can either provide a list of channel densities where it'll generate one + plot per channel density. Or, you can provide a list of ions, and it'll + generate one plot per ion---adding up the conductance densities of the + different channel densities for that ion. If neither are provided, plots + for all ion channels on the cell are generated. + + .. versionadded:: 1.0.10 + + :param cell: a NeuroML cell object + :type cell: neuroml.Cell + :param channel_density_ids: a channel density id or list of ids + :type channel_density_ids: str or list(str) + :param ion_channels: an ion channel or list of ions channels + :type ion_channels: str or list(str) + :param ymin: min y value for plots, if None, automatically calculated + :type ymin: float or None + :param ymax: max y value for plots, if None, automatically calculated + :type ymax: float or None + :param plane2d: plane to plot morphology plot in, passed on to the :py:method:`plot_2D_cell_morphology` function + :type plane2d: str "xy" or "yz" or "zx" + :param morph_plot_type: plot type for morphology plot passed on to + plot_2D_cell_morphology + :type morph_plot_type: str + :param morph_min_width: min width for morphology plot passed on to + plot_2D_cell_morphology + :type morph_min_width: float + :param distance_plots: also generate plots showing conductance densities at + distances from the soma + :type distance_plots: bool + :param colormap_name: name of matplotlib colormap to use for morph plot. + Note that if there's only one overlay value, the colormap is modified + to only show the max value of the colormap to indicate this. + :type colormap_name: str + :returns: None + """ + if channel_density_ids is not None and ion_channels is not None: + raise ValueError( + "Only one of channel_density_ids or ions channels may be provided" + ) + + channel_densities = get_channel_densities(cell) + logger.debug(f"Got channel densities {channel_densities}") + # if neither are provided, generate plots for all ion channels on the cell + if channel_density_ids is None and ion_channels is None: + ion_channels = list(channel_densities.keys()) + + # calculate distances of segments from soma for later use + if distance_plots: + distances = {} + for seg in cell.morphology.segments: + distances[seg.id] = cell.get_distance(seg.id) + + # sorted by distances + sorted_distances = { + k: v for k, v in sorted(distances.items(), key=lambda item: item[1]) + } + + if channel_density_ids is not None: + if type(channel_density_ids) == str: + channel_density_ids_list = [] + channel_density_ids_list.append(channel_density_ids) + channel_density_ids = channel_density_ids_list + + logger.info(f"Plotting channel density plots for {channel_density_ids}") + + for ion_channel, cds in channel_densities.items(): + logger.debug(f"Looking at {ion_channel}: {cds}") + for cd in cds: + if cd.id in channel_density_ids: + print(f"Generating plots for {cd.id}") + data = get_conductance_density_for_segments(cell, cd) + logger.debug(f"Got data for {cd.id}") + + # default colormap: what user passed + colormap_name_to_pass = colormap_name + + # define a new colormap with a single color if there's + # only one value + this_max = numpy.max(list(data.values())) + this_min = numpy.min(list(data.values())) + if this_max == this_min: + logger.debug( + "Only one data value found, creating custom colormap" + ) + selected_colormap = matplotlib.colormaps[colormap_name] + maxcolor = selected_colormap(1.0) + cdict = { + "red": ( + (0.0, maxcolor[0], maxcolor[0]), + (1.0, maxcolor[0], maxcolor[0]), + ), + "green": ( + (0.0, maxcolor[1], maxcolor[1]), + (1.0, maxcolor[1], maxcolor[1]), + ), + "blue": ( + (0.0, maxcolor[2], maxcolor[2]), + (1.0, maxcolor[2], maxcolor[2]), + ), + "alpha": ( + (0.0, maxcolor[3], maxcolor[3]), + (1.0, maxcolor[3], maxcolor[3]), + ), + } + colormap_name_to_pass = "new_pyneuroml_morph_color_map" + newcolormap = LinearSegmentedColormap( + colormap_name_to_pass, cdict + ) + matplotlib.colormaps.register(newcolormap, force=True) + + plot_2D_cell_morphology( + cell=cell, + title=f"{cd.id}", + plot_type=morph_plot_type, + min_width=morph_min_width, + overlay_data=data, + overlay_data_label="(S/m2)", + save_to_file=f"{cell.id}_{cd.id}.cd.png", + datamin=ymin, + plane2d=plane2d, + nogui=not show_plots_already, + colormap_name=colormap_name_to_pass, + ) + if distance_plots: + xvals = [] + yvals = [] + for segid, distance in sorted_distances.items(): + # if segid is not in data, it'll just skip and + # continue + try: + yvals.append(data[segid]) + xvals.append(distance) + except KeyError: + pass + + generate_plot( + xvalues=[xvals], + yvalues=[yvals], + title=f"{cd.id}", + title_above_plot=True, + xaxis="Distance from soma (um)", + yaxis="g density (S/m2)", + save_figure_to=f"{cell.id}_{cd.id}_cd_vs_dist.png", + show_plot_already=show_plots_already, + linestyles=[" "], + linewidths=["0"], + markers=["."], + ) + if show_plots_already is False: + plt.close() + + elif ion_channels is not None: + if type(ion_channels) == str: + ion_channel_list = [] + ion_channel_list.append(ion_channels) + ion_channels = ion_channel_list + logger.info(f"Plotting channel density plots for ion channels: {ion_channels}") + + for ion_channel, cds in channel_densities.items(): + if ion_channel in ion_channels: + logger.debug(f"Looking at {ion_channel}: {cds}") + print(f"Generating plots for {ion_channel}") + data = {} + for cd in cds: + data_for_cd = get_conductance_density_for_segments(cell, cd) + logger.debug(f"Got data for {cd.id}") + for seg, val in data_for_cd.items(): + try: + data[seg] += val + except KeyError: + data[seg] = val + + # plot per ion channel + # note: plotting code is almost identical to code above with + # changes to use ion channel instead of cd + # so, when updating, remember to update both + # can probably be split out into a private helper method + + # default colormap: what user passed + colormap_name_to_pass = colormap_name + + # define a new colormap with a single color if there's + # only one value + this_max = numpy.max(list(data.values())) + this_min = numpy.min(list(data.values())) + if this_max == this_min: + logger.debug("Only one data value found, creating custom colormap") + selected_colormap = matplotlib.colormaps[colormap_name] + maxcolor = selected_colormap(1.0) + cdict = { + "red": ( + (0.0, maxcolor[0], maxcolor[0]), + (1.0, maxcolor[0], maxcolor[0]), + ), + "green": ( + (0.0, maxcolor[1], maxcolor[1]), + (1.0, maxcolor[1], maxcolor[1]), + ), + "blue": ( + (0.0, maxcolor[2], maxcolor[2]), + (1.0, maxcolor[2], maxcolor[2]), + ), + "alpha": ( + (0.0, maxcolor[3], maxcolor[3]), + (1.0, maxcolor[3], maxcolor[3]), + ), + } + colormap_name_to_pass = "new_pyneuroml_morph_color_map" + newcolormap = LinearSegmentedColormap(colormap_name_to_pass, cdict) + matplotlib.colormaps.register(newcolormap, force=True) + + plot_2D_cell_morphology( + cell=cell, + title=f"{ion_channel}", + plot_type=morph_plot_type, + min_width=morph_min_width, + overlay_data=data, + overlay_data_label="(S/m2)", + save_to_file=f"{cell.id}_{ion_channel}.ion.png", + datamin=ymin, + plane2d=plane2d, + nogui=not show_plots_already, + colormap_name=colormap_name_to_pass, + ) + if distance_plots: + xvals = [] + yvals = [] + for segid, distance in sorted_distances.items(): + # if segid is not in data, it'll just skip and + # continue + try: + yvals.append(data[segid]) + xvals.append(distance) + except KeyError: + pass + + generate_plot( + xvalues=[xvals], + yvalues=[yvals], + title=f"{ion_channel}", + title_above_plot=True, + xaxis="Distance from soma (um)", + yaxis="g density (S/m2)", + save_figure_to=f"{cell.id}_{ion_channel}_ion_vs_dist.png", + show_plot_already=show_plots_already, + linestyles=[" "], + linewidths=["0"], + markers=["."], + ) + if show_plots_already is False: + plt.close() + # will never reach here + + +def channel_density_plotter_cli(args=None): + if args is None: + args = channel_density_plotter_process_args() + channel_density_plotter_runner(a=args) + + +def channel_density_plotter_runner(a=None, **kwargs): + a = build_namespace(CHANNEL_DENSITY_PLOTTER_CLI_DEFAULTS, a, **kwargs) + + if len(a.cell_files) > 0: + for cell_file in a.cell_files: + nml_doc = read_neuroml2_file( + cell_file, include_includes=True, verbose=False, optimized=True + ) + # show all plots at end + plot_channel_densities( + nml_doc.cells[0], + show_plots_already=not a.nogui, + distance_plots=not a.no_distance_plots, + ) + + if __name__ == "__main__": generate_channel_density_plots( "../../examples/test_data/HHCellNetwork.net.nml", True, True ) + generate_channel_density_plots( "../../../neuroConstruct/osb/showcase/BlueBrainProjectShowcase/NMC/NeuroML2/cADpyr229_L23_PC_5ecbf9b163_0_0.cell.nml", True, diff --git a/pyneuroml/analysis/NML2ChannelAnalysis.py b/pyneuroml/analysis/NML2ChannelAnalysis.py index 0ae649e6f..7bb3c03cd 100644 --- a/pyneuroml/analysis/NML2ChannelAnalysis.py +++ b/pyneuroml/analysis/NML2ChannelAnalysis.py @@ -17,7 +17,8 @@ import neuroml from pyneuroml.pynml import run_lems_with_jneuroml, read_neuroml2_file -from pyneuroml.utils import convert_case +from pyneuroml.utils import get_ion_color, get_colour_hex, get_state_color +from pyneuroml.utils.cli import build_namespace logger = logging.getLogger(__name__) @@ -31,9 +32,6 @@ V = "rampCellPop0[0]/v" # Key for voltage trace in results dictionary. -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, "minV": -100, @@ -232,99 +230,6 @@ def process_args(): return parser.parse_args() -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: 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": - col = "#CD5C5C" - elif ion.lower() == "ca": - col = "#8FBC8F" - elif ion.lower() == "h": - col = "#ffd9b3" - else: - col = "#A9A9A9" - - return col - - -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" - if s.startswith("k"): - col = "#FF0000" - if s.startswith("r"): - col = "#FF0000" - if s.startswith("h"): - col = "#00FF00" - if s.startswith("l"): - col = "#00FF00" - if s.startswith("n"): - col = "#0000FF" - if s.startswith("a"): - col = "#FF0000" - if s.startswith("b"): - col = "#00FF00" - if s.startswith("c"): - col = "#0000FF" - if s.startswith("q"): - col = "#FF00FF" - if s.startswith("e"): - col = "#00FFFF" - if s.startswith("f"): - col = "#DDDD00" - if s.startswith("p"): - col = "#880000" - if s.startswith("s"): - col = "#888800" - if s.startswith("u"): - col = "#880088" - - return col - - def merge_with_template( model: typing.Dict[typing.Any, typing.Any], templfile: str ) -> str: @@ -767,7 +672,6 @@ def compute_iv_curve(channel, a, results, grid=True): times[voltage].append(t) currents[voltage].append(i) if t >= t_start and t <= t_steady_end: - if i > i_max: i_max = i if i < i_min: @@ -881,38 +785,6 @@ def make_md_file(info): logger.info("Written Markdown info to: %s" % new_md_file) -def build_namespace(a=None, **kwargs): - if a is None: - a = argparse.Namespace() - - # Add arguments passed in by keyword. - for key, value in kwargs.items(): - setattr(a, key, value) - - # Add defaults for arguments not provided. - for key, value in DEFAULTS.items(): - if not hasattr(a, key): - setattr(a, key, value) - - # Change all values from camelCase to under_score. - # This should have always worked in one pass, but for some reason - # it is failing (stochastically) on some keys, so it needs to keep - # trying until all keys are under_score. - flag = True - while flag: - flag = False - keys = list(a.__dict__.keys()) - for key in keys: - value = a.__dict__[key] - new_key = convert_case(key) - if new_key != key: - setattr(a, new_key, value) - delattr(a, key) - flag = True - - return a - - def main(args=None): if args is None: args = process_args() @@ -920,7 +792,7 @@ def main(args=None): def run(a=None, **kwargs): - a = build_namespace(a, **kwargs) + a = build_namespace(DEFAULTS, a, **kwargs) # if (not a.nogui) or a.html: # print('mpl') @@ -942,7 +814,6 @@ def run(a=None, **kwargs): other_chan_files = [] if len(a.channel_files) > 0: - for channel_file in a.channel_files: channels = get_channels_from_channel_file(channel_file) # TODO look past 1st channel... diff --git a/pyneuroml/plot/PlotMorphology.py b/pyneuroml/plot/PlotMorphology.py index abe4d8d25..c9bb7c9ae 100644 --- a/pyneuroml/plot/PlotMorphology.py +++ b/pyneuroml/plot/PlotMorphology.py @@ -474,11 +474,19 @@ def plot_2D_cell_morphology( acolormap = matplotlib.colormaps[colormap_name] norm = matplotlib.colors.Normalize(vmin=data_min, vmax=data_max) - fig.colorbar( - matplotlib.cm.ScalarMappable(norm=norm, cmap=acolormap), - ax=ax, - label=overlay_data_label, - ) + if data_min == data_max: + fig.colorbar( + matplotlib.cm.ScalarMappable(norm=norm, cmap=acolormap), + label=overlay_data_label, + ax=ax, + ticks=[data_min], + ) + else: + fig.colorbar( + matplotlib.cm.ScalarMappable(norm=norm, cmap=acolormap), + label=overlay_data_label, + ax=ax, + ) # random default color for seg in cell.morphology.segments: diff --git a/pyneuroml/plot/PlotSpikes.py b/pyneuroml/plot/PlotSpikes.py index 647fa9c7c..c6289c604 100644 --- a/pyneuroml/plot/PlotSpikes.py +++ b/pyneuroml/plot/PlotSpikes.py @@ -1,14 +1,13 @@ import argparse +import logging +import os +import sys +from collections import OrderedDict import matplotlib.pyplot as plt -from collections import OrderedDict import numpy as np -import re -import sys -import os -import logging from pyneuroml.plot import generate_plot - +from pyneuroml.utils.cli import build_namespace logger = logging.getLogger(__name__) @@ -29,33 +28,6 @@ POP_NAME_SPIKEFILE_WITH_GIDS = "Spiketimes for GIDs" -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 build_namespace(a=None, **kwargs): - if a is None: - a = argparse.Namespace() - - # Add arguments passed in by keyword. - for key, value in kwargs.items(): - setattr(a, key, value) - - # Add defaults for arguments not provided. - for key, value in DEFAULTS.items(): - if not hasattr(a, key): - setattr(a, key, value) - # Change all keys to camel case - for key, value in a.__dict__.copy().items(): - new_key = convert_case(key) - if new_key != key: - setattr(a, new_key, value) - delattr(a, key) - return a - - def process_args(): """ Parse command-line arguments. @@ -78,9 +50,10 @@ def process_args(): metavar="", default=DEFAULTS["format"], help="How the spiketimes are represented on each line of file: \n" - + "%s: id of cell, space(s) / tab(s), time of spike (default);\n"%FORMAT_ID_T - + "%s: id of cell, space(s) / tab(s), time of spike, allowing NEST dat file comments/metadata;\n"%FORMAT_ID_TIME_NEST_DAT - + "%s: time of spike, space(s) / tab(s), id of cell;\n"%FORMAT_T_ID + + "%s: id of cell, space(s) / tab(s), time of spike (default);\n" % FORMAT_ID_T + + "%s: id of cell, space(s) / tab(s), time of spike, allowing NEST dat file comments/metadata;\n" + % FORMAT_ID_TIME_NEST_DAT + + "%s: time of spike, space(s) / tab(s), id of cell;\n" % FORMAT_T_ID + "sonata: SONATA format HDF5 file containing spike times", ) @@ -148,7 +121,6 @@ def read_sonata_spikes_hdf5_file(file_name): ids_times_pops = {} if hasattr(h5file.root.spikes, "gids"): - gids = h5file.root.spikes.gids timestamps = h5file.root.spikes.timestamps ids_times = {} @@ -203,7 +175,7 @@ def read_sonata_spikes_hdf5_file(file_name): def run(a=None, **kwargs): - a = build_namespace(a, **kwargs) + a = build_namespace(DEFAULTS, a, **kwargs) logger.info( "Generating spiketime plot for %s; format: %s; plotting: %s; save to: %s" % (a.spiketime_files, a.format, a.show_plots_already, a.save_spike_plot_to) @@ -276,7 +248,6 @@ def run(a=None, **kwargs): else: markersizes.append(4) else: - for file_name in a.spiketime_files: logger.info("Loading spike times from: %s" % file_name) spikes_file = open(file_name) @@ -293,7 +264,9 @@ def run(a=None, **kwargs): ids_in_file[name] = [] for line in spikes_file: - if not line.startswith("#") and not (line.startswith("sender") and a.format == FORMAT_ID_TIME_NEST_DAT): + if not line.startswith("#") and not ( + line.startswith("sender") and a.format == FORMAT_ID_TIME_NEST_DAT + ): if a.format == FORMAT_ID_T or a.format == FORMAT_ID_TIME_NEST_DAT: [id, t] = line.split() elif a.format == FORMAT_T_ID: @@ -349,7 +322,6 @@ def run(a=None, **kwargs): ) if a.rates: - plt.figure() bins = a.rate_bins for name in times: diff --git a/pyneuroml/tune/NeuroMLTuner.py b/pyneuroml/tune/NeuroMLTuner.py index 0d07c24ea..c938d72f7 100644 --- a/pyneuroml/tune/NeuroMLTuner.py +++ b/pyneuroml/tune/NeuroMLTuner.py @@ -33,6 +33,7 @@ from neurotune import evaluators from neurotune import utils from pyneuroml.tune.NeuroMLController import NeuroMLController +from pyneuroml.utils.cli import build_namespace from pyneuroml import print_v pp = pprint.PrettyPrinter(indent=4) @@ -370,7 +371,7 @@ def run_optimisation(**kwargs: typing.Any) -> typing.Optional[dict]: :returns: a report of the optimisation in a dictionary. """ - a = build_namespace(**kwargs) + a = build_namespace(DEFAULTS, **kwargs) return _run_optimisation(a) @@ -953,38 +954,5 @@ def parse_list_arg(str_list_arg: str) -> typing.Optional[list[typing.Any]]: return ret -def build_namespace( - a: typing.Optional[argparse.Namespace] = None, **kwargs: typing.Any -) -> argparse.Namespace: - """Build an argparse namespace.""" - if a is None: - a = argparse.Namespace() - - # Add arguments passed in by keyword. - for key, value in kwargs.items(): - setattr(a, key, value) - - # Add defaults for arguments not provided. - for key, value in DEFAULTS.items(): - new_key = convert_case(key) - if not hasattr(a, key) and not hasattr(a, new_key): - setattr(a, key, value) - - # Change all values to under_score from camelCase. - # Cannot change dictionary while iterating over it, so iterate over a copy - for key, value in a.__dict__.copy().items(): - new_key = convert_case(key) - if new_key != key: - setattr(a, new_key, value) - delattr(a, key) - return a - - -def convert_case(name: str) -> str: - """Converts from camelCase to snake_case""" - s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) - return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() - - if __name__ == "__main__": main() diff --git a/pyneuroml/utils/__init__.py b/pyneuroml/utils/__init__.py index 78790342d..3dffc2948 100644 --- a/pyneuroml/utils/__init__.py +++ b/pyneuroml/utils/__init__.py @@ -5,19 +5,25 @@ Copyright 2023 NeuroML Contributors """ -import math + import copy import logging +import math import re -import numpy +import typing import neuroml +import numpy from neuroml.loaders import read_neuroml2_file logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) +MAX_COLOUR = (255, 0, 0) # type: typing.Tuple[int, int, int] +MIN_COLOUR = (255, 255, 0) # type: typing.Tuple[int, int, int] + + def extract_position_info( nml_model: neuroml.NeuroMLDocument, verbose: bool = False ) -> tuple: @@ -146,13 +152,105 @@ def convert_case(name): return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() +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": + col = "#CD5C5C" + elif ion.lower() == "ca": + col = "#8FBC8F" + elif ion.lower() == "h": + col = "#ffd9b3" + else: + col = "#A9A9A9" + + return col + + +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 + + +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" + if s.startswith("k"): + col = "#FF0000" + if s.startswith("r"): + col = "#FF0000" + if s.startswith("h"): + col = "#00FF00" + if s.startswith("l"): + col = "#00FF00" + if s.startswith("n"): + col = "#0000FF" + if s.startswith("a"): + col = "#FF0000" + if s.startswith("b"): + col = "#00FF00" + if s.startswith("c"): + col = "#0000FF" + if s.startswith("q"): + col = "#FF00FF" + if s.startswith("e"): + col = "#00FFFF" + if s.startswith("f"): + col = "#DDDD00" + if s.startswith("p"): + col = "#880000" + if s.startswith("s"): + col = "#888800" + if s.startswith("u"): + col = "#880088" + + return col + + def rotate_cell( cell: neuroml.Cell, x: float = 0, y: float = 0, z: float = 0, order: str = "xyz", - relative_to_soma: bool = False + relative_to_soma: bool = False, ) -> neuroml.Cell: """Return a new cell object rotated in the provided order along the provided angles (in radians) relative to the soma position. @@ -176,41 +274,50 @@ def rotate_cell( https://github.com/LFPy/LFPy/blob/master/LFPy/cell.py#L1600 """ - valid_orders = [ - "xyz", "yzx", "zxy", "xzy", "yxz", "zyx" - ] + valid_orders = ["xyz", "yzx", "zxy", "xzy", "yxz", "zyx"] if order not in valid_orders: raise ValueError(f"order must be one of {valid_orders}") soma_seg_id = cell.get_morphology_root() soma_seg = cell.get_segment(soma_seg_id) - cell_origin = numpy.array([soma_seg.proximal.x, soma_seg.proximal.y, soma_seg.proximal.z]) + cell_origin = numpy.array( + [soma_seg.proximal.x, soma_seg.proximal.y, soma_seg.proximal.z] + ) newcell = copy.deepcopy(cell) print(f"Rotating {newcell.id} by {x}, {y}, {z}") # calculate rotations if x != 0: anglex = x - rotation_x = numpy.array([[1, 0, 0], - [0, math.cos(anglex), -math.sin(anglex)], - [0, math.sin(anglex), math.cos(anglex)] - ]) + rotation_x = numpy.array( + [ + [1, 0, 0], + [0, math.cos(anglex), -math.sin(anglex)], + [0, math.sin(anglex), math.cos(anglex)], + ] + ) logger.debug(f"x matrix is: {rotation_x}") if y != 0: angley = y - rotation_y = numpy.array([[math.cos(angley), 0, math.sin(angley)], - [0, 1, 0], - [-math.sin(angley), 0, math.cos(angley)] - ]) + rotation_y = numpy.array( + [ + [math.cos(angley), 0, math.sin(angley)], + [0, 1, 0], + [-math.sin(angley), 0, math.cos(angley)], + ] + ) logger.debug(f"y matrix is: {rotation_y}") if z != 0: anglez = z - rotation_z = numpy.array([[math.cos(anglez), -math.sin(anglez), 0], - [math.sin(anglez), math.cos(anglez), 0], - [0, 0, 1] - ]) + rotation_z = numpy.array( + [ + [math.cos(anglez), -math.sin(anglez), 0], + [math.sin(anglez), math.cos(anglez), 0], + [0, 0, 1], + ] + ) logger.debug(f"z matrix is: {rotation_z}") # rotate each segment @@ -232,17 +339,17 @@ def rotate_cell( # rotate for axis in order: - if axis == 'x' and x != 0: + if axis == "x" and x != 0: if prox.any(): prox = numpy.dot(prox, rotation_x) dist = numpy.dot(dist, rotation_x) - if axis == 'y' and y != 0: + if axis == "y" and y != 0: if prox.any(): prox = numpy.dot(prox, rotation_y) dist = numpy.dot(dist, rotation_y) - if axis == 'z' and z != 0: + if axis == "z" and z != 0: if prox.any(): prox = numpy.dot(prox, rotation_z) dist = numpy.dot(dist, rotation_z) diff --git a/setup.cfg b/setup.cfg index 29ed4beb7..e752196e7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,6 +51,7 @@ console_scripts = pynml-plotspikes = pyneuroml.plot.PlotSpikes:main pynml-plotmorph = pyneuroml.plot.PlotMorphology:main pynml-channelml2nml = pyneuroml.channelml:main + pynml-plotchan = pyneuroml.analysis.ChannelDensityPlot:channel_density_plotter_cli pynml-sonata = neuromllite.SonataReader:main [options.package_data] @@ -81,6 +82,7 @@ hdf5 = analysis = pyelectro + sympy tune = neurotune>=0.2.6 diff --git a/tests/analysis/test_channel_density_plots.py b/tests/analysis/test_channel_density_plots.py new file mode 100644 index 000000000..5001d2f3e --- /dev/null +++ b/tests/analysis/test_channel_density_plots.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +""" +Tests for channel density plot related methods + +File: tests/analysis/test_channel_density_plots.py + +Copyright 2023 NeuroML contributors +""" + +import logging +import unittest + +import neuroml +from pyneuroml.analysis.ChannelDensityPlot import ( + get_channel_densities, + get_conductance_density_for_segments, + plot_channel_densities, +) +from pyneuroml.pynml import read_neuroml2_file + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +class TestChannelDensityPlots(unittest.TestCase): + + """Tests for ChannelDensityPlot""" + + def test_get_channel_densities(self): + """test get_channel_densities method.""" + cell_file = "tests/plot/L23-example/HL23PYR.cell.nml" + cell = read_neuroml2_file(cell_file).cells[0] + print(cell.id) + channel_densities = get_channel_densities(cell) + total_num = 0 + for channel, densities in channel_densities.items(): + total_num += len(densities) + self.assertEqual(total_num, 22) + + def test_get_conductance_density_for_segments(self): + """Test get_conductance_density_for_segments.""" + cell_file = "tests/plot/L23-example/HL23PYR.cell.nml" + cell = read_neuroml2_file(cell_file).cells[0] # type: neuroml.Cell + print(cell.id) + channel_densities = get_channel_densities(cell) + + channel_densities_Im = channel_densities["Im"] + data_Im = get_conductance_density_for_segments(cell, channel_densities_Im[0]) + soma_group = cell.get_all_segments_in_group("soma_group") + self.assertEqual(data_Im[soma_group[0]], 3.06) + self.assertEqual(data_Im[soma_group[-1]], 3.06) + + data_Im = get_conductance_density_for_segments(cell, channel_densities_Im[1]) + axon_group = cell.get_all_segments_in_group("axon_group") + self.assertEqual(data_Im[axon_group[0]], 0.000000) + + channel_densities_Ih = channel_densities["Ih"] + for cd in channel_densities_Ih: + if "NonUniform" in cd.__class__.__name__: + data_Ih = get_conductance_density_for_segments(cell, cd) + self.assertNotIn(soma_group[0], data_Ih.keys()) + self.assertEqual( + len(data_Ih), + len(cell.get_all_segments_in_group("apical_dendrite_group")), + ) + + def test_plot_channel_densities(self): + """Test the plot_channel_densities function.""" + cell_file = "tests/plot/L23-example/HL23PYR.cell.nml" + cell = read_neuroml2_file(cell_file).cells[0] # type: neuroml.Cell + + # check with channel densities + plot_channel_densities( + cell, + channel_density_ids=["Ih_apical", "Ih_somatic", "Ih_basal", "Ih"], + distance_plots=True, + show_plots_already=False, + ) + plot_channel_densities( + cell, ion_channels=["Ih"], distance_plots=True, show_plots_already=False + ) + # no channel densities or ion channels, so generate for all ion + # channels + plot_channel_densities(cell, distance_plots=True, show_plots_already=False) diff --git a/tests/plot/L23-example/HL23PV.cell.nml b/tests/plot/L23-example/HL23PV.cell.nml index d29db4b4d..73247b64d 100644 --- a/tests/plot/L23-example/HL23PV.cell.nml +++ b/tests/plot/L23-example/HL23PV.cell.nml @@ -1,7 +1,7 @@ - NeuroML 2 file generated from ModelView by: NEURON -- VERSION 8.2.1 HEAD (c1590692) 2022-08-09 + NeuroML 2 file generated from ModelView by: NEURON -- VERSION 8.2.2+ HEAD (93d41fafd+) 2022-12-15 Authors: Michael Hines, Sushil Kambampati and Padraig Gleeson, - Yale University and UCL + Yale University and UCL @@ -18184,8 +18184,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -18412,8 +18412,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -18676,8 +18676,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -18981,8 +18981,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -19142,8 +19142,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -19228,8 +19228,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -19367,8 +19367,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -19605,8 +19605,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -19820,8 +19820,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -19923,8 +19923,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -20082,8 +20082,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -20370,8 +20370,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -20550,8 +20550,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -20632,8 +20632,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -20723,8 +20723,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -20853,8 +20853,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -21068,8 +21068,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -21271,8 +21271,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -21495,8 +21495,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -21717,8 +21717,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -21813,8 +21813,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -22221,8 +22221,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -22363,8 +22363,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -22463,8 +22463,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -22628,8 +22628,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -22689,8 +22689,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -22741,8 +22741,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -22900,114 +22900,114 @@ Default axon segment group for the cell - + Default dendrite segment group for the cell - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + + - + + - + + - - - - - - - - - - - - + + + - - - - - - - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + - - + + @@ -23036,8 +23036,8 @@ - - + + diff --git a/tests/plot/L23-example/HL23PYR.cell.nml b/tests/plot/L23-example/HL23PYR.cell.nml index d3bd61546..f86f9674b 100644 --- a/tests/plot/L23-example/HL23PYR.cell.nml +++ b/tests/plot/L23-example/HL23PYR.cell.nml @@ -1,7 +1,7 @@ NeuroML 2 file generated from ModelView by: NEURON -- VERSION 8.2.1 HEAD (c1590692) 2022-08-09 Authors: Michael Hines, Sushil Kambampati and Padraig Gleeson, - Yale University and UCL + Yale University and UCL @@ -23482,8 +23482,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -23580,8 +23580,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -23686,8 +23686,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -23839,8 +23839,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -23886,8 +23886,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -24027,8 +24027,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -24136,8 +24136,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -24273,8 +24273,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -24383,8 +24383,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -24513,8 +24513,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -24645,8 +24645,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -24770,8 +24770,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -24889,8 +24889,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -25013,8 +25013,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -25092,8 +25092,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -25138,8 +25138,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -25195,8 +25195,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -25285,8 +25285,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -25393,8 +25393,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -25484,8 +25484,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -25596,8 +25596,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -25762,8 +25762,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -25907,8 +25907,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -26052,8 +26052,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -26174,8 +26174,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -26218,8 +26218,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -26406,8 +26406,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -26527,8 +26527,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -26637,8 +26637,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -26781,8 +26781,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -26889,8 +26889,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -26976,8 +26976,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -27200,8 +27200,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -27308,8 +27308,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -27490,8 +27490,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -27601,8 +27601,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -27712,8 +27712,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -27813,8 +27813,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -27922,8 +27922,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -28063,8 +28063,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -28165,8 +28165,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -28351,8 +28351,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -28480,8 +28480,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -28645,8 +28645,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -28783,8 +28783,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -28953,8 +28953,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -29084,8 +29084,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -29273,8 +29273,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -29409,8 +29409,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -29490,8 +29490,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -29635,8 +29635,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -29779,8 +29779,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -29898,8 +29898,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -30036,19 +30036,6 @@ Basal dendrites - - - - - - - - - - - - - @@ -30060,73 +30047,86 @@ + + + + + + + + + + + + + Apical dendrite_group - - - - - - - - - - - - - - - - - + + + + - - + - + - + + - - - - + + + - - - + + + + + + + + + + + + + + + + + @@ -30135,30 +30135,115 @@ Myelin group + + All without myelin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default soma segment group for the cell Default axon segment group for the cell - + Default dendrite segment group for the cell - + - - - - - - - - - @@ -30242,12 +30327,21 @@ + + + + + + + + + - - + + @@ -30255,7 +30349,7 @@ - + @@ -30272,16 +30366,16 @@ - + - - - + + + diff --git a/tests/plot/L23-example/HL23SST.cell.nml b/tests/plot/L23-example/HL23SST.cell.nml index 6d1cbacb8..b16099896 100644 --- a/tests/plot/L23-example/HL23SST.cell.nml +++ b/tests/plot/L23-example/HL23SST.cell.nml @@ -1,17 +1,17 @@ - NeuroML 2 file generated from ModelView by: NEURON -- VERSION 7.8.2 HEAD (09b151ec) 2020-12-16 + NeuroML 2 file generated from ModelView by: NEURON -- VERSION 8.2.2+ HEAD (93d41fafd+) 2022-12-15 Authors: Michael Hines, Sushil Kambampati and Padraig Gleeson, - Yale University and UCL + Yale University and UCL - - + + - + @@ -13433,8 +13433,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13478,8 +13478,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13525,8 +13525,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13663,8 +13663,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13800,8 +13800,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13925,8 +13925,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13963,8 +13963,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14020,8 +14020,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14218,8 +14218,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14490,8 +14490,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14609,8 +14609,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14688,8 +14688,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14893,8 +14893,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -15058,8 +15058,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -15265,8 +15265,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -15320,8 +15320,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -15530,8 +15530,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -15715,8 +15715,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -15776,8 +15776,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -15922,8 +15922,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -16000,8 +16000,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -16052,8 +16052,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -16106,8 +16106,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -16288,8 +16288,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -16435,8 +16435,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -16567,8 +16567,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -16630,8 +16630,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -16698,8 +16698,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -16816,8 +16816,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -16971,8 +16971,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -17100,8 +17100,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -17208,71 +17208,126 @@ Basal dendrites - - - - - - - - - - - - - + + - - - - - + + - + + + - - - - - + - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + Myelin group + + + + All without myelin + - + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + - - - Apical dendrite_group - - - - - - Myelin group - + Default soma segment group for the cell @@ -17280,114 +17335,107 @@ Default axon segment group for the cell - + Default dendrite segment group for the cell - - + + + + - - - - + - - + + - - + + - - - - - - - + + - - - + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - + + + diff --git a/tests/plot/L23-example/HL23VIP.cell.nml b/tests/plot/L23-example/HL23VIP.cell.nml index d3ecfdea8..d5bc5cf21 100644 --- a/tests/plot/L23-example/HL23VIP.cell.nml +++ b/tests/plot/L23-example/HL23VIP.cell.nml @@ -1,7 +1,7 @@ - NeuroML 2 file generated from ModelView by: NEURON -- VERSION 7.8.2 HEAD (09b151ec) 2020-12-16 + NeuroML 2 file generated from ModelView by: NEURON -- VERSION 8.2.2+ HEAD (93d41fafd+) 2022-12-15 Authors: Michael Hines, Sushil Kambampati and Padraig Gleeson, - Yale University and UCL + Yale University and UCL @@ -11976,8 +11976,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -12046,8 +12046,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -12172,8 +12172,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -12248,8 +12248,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -12304,8 +12304,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -12390,8 +12390,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -12672,8 +12672,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -12865,8 +12865,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13027,8 +13027,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13137,8 +13137,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13308,8 +13308,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13418,8 +13418,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13549,8 +13549,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13659,8 +13659,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -13810,8 +13810,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14007,8 +14007,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14126,8 +14126,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14225,8 +14225,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14392,8 +14392,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14584,8 +14584,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14673,8 +14673,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14787,8 +14787,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14898,8 +14898,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -14986,8 +14986,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -15117,8 +15117,8 @@ These segmentGroups correspond to the 'cables' of NeuroML v1.8.1, and map to/from NEURON sections. - - + + @@ -15279,147 +15279,147 @@ Default dendrite segment group for the cell - - + - - - + + + + + + + + + - - + - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - + + + - - - + + + + + - - + + - - + - + + + + + + + + + + - + + + - - + + + - - - - - - + + + - - - + + + + + + + + + + - - - - + + + - + + + + + + + + - + + + + + - - - - - - - - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + +