diff --git a/docs/source/pyneuroml.plot.rst b/docs/source/pyneuroml.plot.rst index 0892cbf5..3d31b666 100644 --- a/docs/source/pyneuroml.plot.rst +++ b/docs/source/pyneuroml.plot.rst @@ -14,6 +14,14 @@ pyneuroml.plot.Plot module :undoc-members: :show-inheritance: +pyneuroml.plot.PlotTimeSeries module +-------------------------------- + +.. automodule:: pyneuroml.plot.PlotTimeSeries + :members: + :undoc-members: + :show-inheritance: + pyneuroml.plot.PlotSpikes module -------------------------------- diff --git a/docs/source/pyneuroml.utils.rst b/docs/source/pyneuroml.utils.rst index 412fde76..8647c8c0 100644 --- a/docs/source/pyneuroml.utils.rst +++ b/docs/source/pyneuroml.utils.rst @@ -22,6 +22,14 @@ pyneuroml.utils.plot module :undoc-members: :show-inheritance: +pyneuroml.utils.misc module +--------------------------- + +.. automodule:: pyneuroml.utils.misc + :members: + :undoc-members: + :show-inheritance: + pyneuroml.utils.cli module --------------------------- diff --git a/man/man1/common.h2m b/man/man1/common.h2m index 72b6d0fa..6357fd94 100644 --- a/man/man1/common.h2m +++ b/man/man1/common.h2m @@ -1,14 +1,16 @@ -[see also] +[see-also] .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (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/generate-man-pages.sh b/man/man1/generate-man-pages.sh index 2ef3123f..63d73b8d 100755 --- a/man/man1/generate-man-pages.sh +++ b/man/man1/generate-man-pages.sh @@ -1,7 +1,7 @@ #!/bin/bash # Copyright 2023 Ankur Sinha -# Author: Ankur Sinha +# Author: Ankur Sinha # File : generate-man-pages.sh # # Generate man pages for all pyNeuroML command lines using help2man @@ -27,6 +27,17 @@ else echo ".PP" >> version.h2m echo "${fullversioninfo}" >> version.h2m + echo "Generating common file: common.h2m" + echo "[see-also]" > common.h2m + + for f in ${bin_location}/pynml* + do + current_file=$(basename $f) + echo ".BR ${current_file} (1)," >> common.h2m + done + echo ".PP" >> common.h2m + echo "Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem." >> common.h2m + cat common.h2m version.h2m >> common-temp.h2m for f in ${bin_location}/pynml* @@ -39,4 +50,3 @@ else rm common-temp.h2m fi - diff --git a/man/man1/pynml-archive.1 b/man/man1/pynml-archive.1 index 2d4e248b..0a3a36c4 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" "February 2024" "pynml-archive v1.2.0" "User Commands" +.TH PYNML-ARCHIVE "1" "February 2024" "pynml-archive v1.2.2" "User Commands" .SH NAME -pynml-archive \- manual page for pynml-archive v1.2.0 +pynml-archive \- manual page for pynml-archive v1.2.2 .SH DESCRIPTION usage: pynml\-archive [\-h] [\-zipfileName ] .TP @@ -27,20 +27,22 @@ Extension to use for archive. .TP \fB\-filelist\fR [ ...] Explicit list of files to create archive of. -.SH ENVIRONMENT -.PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) -.SH "SEE ALSO" +.SH "SEE-ALSO" .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (1). +.BR pynml-tune (1), .PP Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/pynml-channelanalysis.1 b/man/man1/pynml-channelanalysis.1 index b8f72573..c7048c2d 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" "February 2024" "pynml-channelanalysis v1.2.0" "User Commands" +.TH PYNML-CHANNELANALYSIS "1" "February 2024" "pynml-channelanalysis v1.2.2" "User Commands" .SH NAME -pynml-channelanalysis \- manual page for pynml-channelanalysis v1.2.0 +pynml-channelanalysis \- manual page for pynml-channelanalysis v1.2.2 .SH DESCRIPTION usage: pynml\-channelanalysis [\-h] [\-v] [\-minV ] [\-maxV ] .TP @@ -92,20 +92,22 @@ the plots for the channel \fB\-ivCurve\fR Save currents through voltage clamp at each level & plot current vs voltage for ion channel -.SH ENVIRONMENT -.PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) -.SH "SEE ALSO" +.SH "SEE-ALSO" .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (1). +.BR pynml-tune (1), .PP Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/pynml-channelml2nml.1 b/man/man1/pynml-channelml2nml.1 index 4cef3a13..f6d2cc2b 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" "February 2024" "pynml-channelml2nml v1.2.0" "User Commands" +.TH PYNML-CHANNELML2NML "1" "February 2024" "pynml-channelml2nml v1.2.2" "User Commands" .SH NAME -pynml-channelml2nml \- manual page for pynml-channelml2nml v1.2.0 +pynml-channelml2nml \- manual page for pynml-channelml2nml v1.2.2 .SH DESCRIPTION usage: pynml\-channelml2nml [\-h] [\-xsltfile ] .TP @@ -23,20 +23,22 @@ Path to the XSLT file .TP \fB\-saveToFile\fR Name of the outputfile file -.SH ENVIRONMENT -.PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) -.SH "SEE ALSO" +.SH "SEE-ALSO" .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (1). +.BR pynml-tune (1), .PP Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/pynml-modchananalysis.1 b/man/man1/pynml-modchananalysis.1 index 98af2061..12886975 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" "February 2024" "pynml-modchananalysis v1.2.0" "User Commands" +.TH PYNML-MODCHANANALYSIS "1" "February 2024" "pynml-modchananalysis v1.2.2" "User Commands" .SH NAME -pynml-modchananalysis \- manual page for pynml-modchananalysis v1.2.0 +pynml-modchananalysis \- manual page for pynml-modchananalysis v1.2.2 .SH DESCRIPTION usage: pynml\-modchananalysis [\-h] [\-v] [\-nogui] [\-minV ] .TP @@ -54,20 +54,22 @@ in mM) .TP \fB\-modFile\fR Name of the mod file containing the channel -.SH ENVIRONMENT -.PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) -.SH "SEE ALSO" +.SH "SEE-ALSO" .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (1). +.BR pynml-tune (1), .PP Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/pynml-plotchan.1 b/man/man1/pynml-plotchan.1 index 9c4270e0..92aab732 100644 --- a/man/man1/pynml-plotchan.1 +++ b/man/man1/pynml-plotchan.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH PYNML-PLOTCHAN "1" "February 2024" "pynml-plotchan v1.2.0" "User Commands" +.TH PYNML-PLOTCHAN "1" "February 2024" "pynml-plotchan v1.2.2" "User Commands" .SH NAME -pynml-plotchan \- manual page for pynml-plotchan v1.2.0 +pynml-plotchan \- manual page for pynml-plotchan v1.2.2 .SH DESCRIPTION usage: pynml\-plotchan [\-h] [\-noDistancePlots] [\-nogui] .IP @@ -23,20 +23,22 @@ Do not generate distance plots .TP \fB\-nogui\fR Do not show plots as they are generated -.SH ENVIRONMENT -.PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) -.SH "SEE ALSO" +.SH "SEE-ALSO" .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (1). +.BR pynml-tune (1), .PP Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/pynml-plotmorph.1 b/man/man1/pynml-plotmorph.1 index 5ebe97ca..dbc5f836 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" "February 2024" "pynml-plotmorph v1.2.0" "User Commands" +.TH PYNML-PLOTMORPH "1" "February 2024" "pynml-plotmorph v1.2.2" "User Commands" .SH NAME -pynml-plotmorph \- manual page for pynml-plotmorph v1.2.0 +pynml-plotmorph \- manual page for pynml-plotmorph v1.2.2 .SH DESCRIPTION usage: pynml\-plotmorph [\-h] [\-v] [\-nogui] [\-plane2d ] .TP @@ -53,20 +53,22 @@ Name of the image file, for 2D plot \fB\-square\fR Scale axes so that image is approximately square, for 2D plot -.SH ENVIRONMENT -.PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) -.SH "SEE ALSO" +.SH "SEE-ALSO" .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (1). +.BR pynml-tune (1), .PP Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/pynml-plotspikes.1 b/man/man1/pynml-plotspikes.1 index e0246dcb..a04d0d43 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" "February 2024" "pynml-plotspikes v1.2.0" "User Commands" +.TH PYNML-PLOTSPIKES "1" "February 2024" "pynml-plotspikes v1.2.2" "User Commands" .SH NAME -pynml-plotspikes \- manual page for pynml-plotspikes v1.2.0 +pynml-plotspikes \- manual page for pynml-plotspikes v1.2.2 .SH DESCRIPTION usage: pynml\-plotspikes [\-h] [\-format ] [\-rates] [\-showPlotsAlready] .TP @@ -42,20 +42,22 @@ Window for rate calculation in ms .TP \fB\-rateBins\fR Number of bins for rate histogram -.SH ENVIRONMENT -.PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) -.SH "SEE ALSO" +.SH "SEE-ALSO" .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (1). +.BR pynml-tune (1), .PP Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/pynml-plottimeseries.1 b/man/man1/pynml-plottimeseries.1 new file mode 100644 index 00000000..67ec36ea --- /dev/null +++ b/man/man1/pynml-plottimeseries.1 @@ -0,0 +1,39 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. +.TH PYNML-PLOTTIMESERIES "1" "February 2024" "pynml-plottimeseries v1.2.2" "User Commands" +.SH NAME +pynml-plottimeseries \- manual page for pynml-plottimeseries v1.2.2 +.SH DESCRIPTION +usage: pynml\-plottimeseries [\-h] [\-offset] [ ...] +.PP +A script to plot time series data from data files or LEMS files +.SS "positional arguments:" +.TP + +a LEMS file (LEMS_..) or data files to plot time +series from +.SS "options:" +.TP +\fB\-h\fR, \fB\-\-help\fR +show this help message and exit +.TP +\fB\-offset\fR +Toggle whether plots are overlaid or offset +.SH "SEE-ALSO" +.BR pynml (1), +.BR pynml-archive (1), +.BR pynml-channelanalysis (1), +.BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), +.BR pynml-plotchan (1), +.BR pynml-plotmorph (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (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. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/pynml-povray.1 b/man/man1/pynml-povray.1 index 7021ccf0..7d8a71b6 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" "February 2024" "pynml-povray v1.2.0" "User Commands" +.TH PYNML-POVRAY "1" "February 2024" "pynml-povray v1.2.2" "User Commands" .SH NAME -pynml-povray \- manual page for pynml-povray v1.2.0 +pynml-povray \- manual page for pynml-povray v1.2.2 .SH DESCRIPTION usage: pynml\-povray [\-h] [\-split] [\-background ] [\-movie] .TP @@ -94,20 +94,22 @@ cell/network .TP \fB\-segids\fR Show segment ids -.SH ENVIRONMENT -.PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) -.SH "SEE ALSO" +.SH "SEE-ALSO" .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (1). +.BR pynml-tune (1), .PP Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/pynml-sonata.1 b/man/man1/pynml-sonata.1 index 14dbd2fb..f5304e4a 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" "February 2024" "pynml-sonata v1.2.0" "User Commands" +.TH PYNML-SONATA "1" "February 2024" "pynml-sonata v1.2.2" "User Commands" .SH NAME -pynml-sonata \- manual page for pynml-sonata v1.2.0 +pynml-sonata \- manual page for pynml-sonata v1.2.2 .SH DESCRIPTION usage: pynml\-sonata [\-h] [\-h5] [\-jnml] [\-neuron] .IP @@ -32,20 +32,22 @@ jNeuroML \fB\-neuron\fR Execute the generated LEMS/NeuroML2 model with jNeuroML_NEURON -.SH ENVIRONMENT -.PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) -.SH "SEE ALSO" +.SH "SEE-ALSO" .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (1). +.BR pynml-tune (1), .PP Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/pynml-summary.1 b/man/man1/pynml-summary.1 index d3a80014..4f8f07ff 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" "February 2024" "pynml-summary v1.2.0" "User Commands" +.TH PYNML-SUMMARY "1" "February 2024" "pynml-summary v1.2.2" "User Commands" .SH NAME -pynml-summary \- manual page for pynml-summary v1.2.0 +pynml-summary \- manual page for pynml-summary v1.2.2 .SH DESCRIPTION Usage: .PP @@ -17,20 +17,22 @@ enable verbose mode .TP \fB\-h\fR/\-\-help: print this help text and exit -.SH ENVIRONMENT -.PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) -.SH "SEE ALSO" +.SH "SEE-ALSO" .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (1). +.BR pynml-tune (1), .PP Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/pynml-tune.1 b/man/man1/pynml-tune.1 index 73840272..9e5a78f3 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" "February 2024" "pynml-tune v1.2.0" "User Commands" +.TH PYNML-TUNE "1" "February 2024" "pynml-tune v1.2.2" "User Commands" .SH NAME -pynml-tune \- manual page for pynml-tune v1.2.0 +pynml-tune \- manual page for pynml-tune v1.2.2 .SH DESCRIPTION usage: pynml\-tune [\-h] [\-simTime ] [\-dt
] .IP @@ -130,20 +130,22 @@ Extra tag/value pairs can be put into the report.json: \fB\-cleanup\fR Should (some) generated files, e.g. *.dat, be deleted as optimisation progresses? -.SH ENVIRONMENT -.PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) -.SH "SEE ALSO" +.SH "SEE-ALSO" .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (1). +.BR pynml-tune (1), .PP Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/pynml.1 b/man/man1/pynml.1 index 4e52c51e..201b25f4 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" "February 2024" "pynml v1.2.0" "User Commands" +.TH PYNML "1" "February 2024" "pynml v1.2.2" "User Commands" .SH NAME -pynml \- manual page for pynml v1.2.0 +pynml \- manual page for pynml v1.2.2 .SH DESCRIPTION usage: pynml [\-h|\-\-help] [] .PP @@ -28,8 +28,8 @@ Java memory for jNeuroML, e.g. 400M, 2G (used in Suppress GUI, i.e. show no plots, just save results .TP - -LEMS/NeuroML 2/SBML file(s) to process + +LEMS/NeuroML 2/SBML/SEDML file(s) to process .SS "Mutually-exclusive options:" .IP Only one of these options can be selected @@ -46,7 +46,7 @@ The full format of the '\-neuron' option is: \fB\-neuron\fR [\-nogui] [\-run] [\-outputdir dir] .TP \fB\-nogui\fR -do not generate gtaphical elements in NEURON, +do not generate graphical elements in NEURON, just run, save data, and quit .TP \fB\-run\fR @@ -59,6 +59,18 @@ generate NEURON files in directory the LEMS file to use .TP +\fB\-run\-tellurium\fR ... +Load a SEDML file, and run it using tellurium: + \fB\-run\-tellurium\fR [\-outputdir dir] +.TP + +the SEDML file to use +.TP +\fB\-outputdir\fR +save any output reports in directory +default is current directory ie '.' +use 'none' to disable output altogether +.TP \fB\-netpyne\fR ... (Via jNeuroML) Load a LEMS file, and convert it to NetPyNE format. @@ -147,7 +159,7 @@ to SpineML format .TP \fB\-sbml\-import\fR duration dt (Via jNeuroML) Load a SBML file, and convert it -toLEMS format using values for duration & dt +to LEMS format using values for duration & dt in ms (ignoring SBML units) .TP \fB\-sbml\-import\-units\fR duration dt @@ -191,20 +203,22 @@ Validate SBML file(s), unit consistency failure generates an error .TP \fB\-validate\-sedml\fR Validate SEDML file(s) -.SH ENVIRONMENT -.PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) -.SH "SEE ALSO" +.SH "SEE-ALSO" .BR pynml (1), +.BR pynml-archive (1), .BR pynml-channelanalysis (1), .BR pynml-channelml2nml (1), +.BR pynml-modchananalysis (1), .BR pynml-plotchan (1), -.BR pynml-plotspikes (1), .BR pynml-plotmorph (1), -.BR pynml-modchannelanalysis (1), +.BR pynml-plotspikes (1), +.BR pynml-plottimeseries (1), .BR pynml-povray (1), .BR pynml-sonata (1), .BR pynml-summary (1), -.BR pynml-tune (1). +.BR pynml-tune (1), .PP Please see https://docs.neuroml.org for complete documentation on the NeuroML standard and the software ecosystem. +.SH ENVIRONMENT +.PP +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/man/man1/version.h2m b/man/man1/version.h2m index 5d684b71..f1e196ce 100644 --- a/man/man1/version.h2m +++ b/man/man1/version.h2m @@ -1,3 +1,3 @@ [environment] .PP -pyNeuroML v1.2.0 (libNeuroML v0.5.8, jNeuroML v0.13.0) +pyNeuroML v1.2.2 (libNeuroML v0.5.8, jNeuroML v0.13.0) diff --git a/pyneuroml/io.py b/pyneuroml/io.py index 82e8a3be..8bddc244 100644 --- a/pyneuroml/io.py +++ b/pyneuroml/io.py @@ -13,6 +13,7 @@ import sys import textwrap import typing +from typing import Optional import neuroml.loaders as loaders import neuroml.writers as writers @@ -21,7 +22,6 @@ import lems.model.model as lems_model from pyneuroml.errors import FILE_NOT_FOUND_ERR from pyneuroml.validators import validate_neuroml2 -from typing import Optional logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -134,7 +134,7 @@ def write_neuroml2_file( validate: bool = True, verbose_validate: bool = False, hdf5: bool = False, -) -> None: +) -> typing.Optional[typing.Union[bool, typing.Tuple[bool, str]]]: """Write a NeuroMLDocument object to a file using libNeuroML. :param nml2_doc: NeuroMLDocument object to write to file @@ -155,6 +155,7 @@ def write_neuroml2_file( if validate: return validate_neuroml2(nml2_file_name, verbose_validate) + return None def read_lems_file( diff --git a/pyneuroml/lems/__init__.py b/pyneuroml/lems/__init__.py index 23971677..b9d61f15 100644 --- a/pyneuroml/lems/__init__.py +++ b/pyneuroml/lems/__init__.py @@ -1,16 +1,18 @@ +import logging +import os import os.path -from pyneuroml.lems.LEMSSimulation import LEMSSimulation - +import random import shutil -import os -import logging +import typing + +import neuroml +from lxml import etree +from pyneuroml.lems.LEMSSimulation import LEMSSimulation from pyneuroml.pynml import read_neuroml2_file from pyneuroml.utils.plot import get_next_hex_color -import random -import neuroml - logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) def generate_lems_file_for_neuroml( @@ -387,3 +389,173 @@ def get_pop_index(quantity): pop = s[0] index = int(s[1]) return pop, index + + +def load_sim_data_from_lems_file( + lems_file_name: str, + base_dir: str = ".", + get_events: bool = True, + get_traces: bool = True, +) -> typing.Union[typing.Tuple[typing.Dict, typing.Dict], typing.Dict]: + """Load simulation outputs using the LEMS simulation file + + .. versionadded:: 1.2.2 + + :param lems_file_name: name of LEMS file that was used to generate the data + :type lems_file_name: str + :param base_dir: directory to run in + :type base_dir: str + :returns: if both `get_events` and `get_traces` are selected, a tuple with + two dictionaries, one for traces, one for events, is returned. + + Otherwise one dictionary for whichever was selected. + + The events dictionary has the following format: + + .. code-block:: python + + { + '': [] + } + + The traces dictionary has the following format: + + .. code-block:: python + + { + 't': [], + 'col 1': [] + } + + :raises ValueError: if neither traces nor events are selected for loading + :raises ValueError: if no traces are found + :raises ValueError: if no events are found + + """ + if not os.path.isfile(lems_file_name): + real_lems_file = os.path.realpath(os.path.join(base_dir, lems_file_name)) + else: + real_lems_file = os.path.realpath(lems_file_name) + + if not get_events and not get_traces: + raise ValueError("One of events or traces must be True") + + logger.debug( + "Reloading data specified in LEMS file: %s (%s), base_dir: %s, cwd: %s;" + % (lems_file_name, real_lems_file, base_dir, os.getcwd()) + ) + + # Could use pylems to parse all this... + traces = {} # type: dict + events = {} # type: dict + + base_lems_file_path = os.path.dirname(os.path.realpath(lems_file_name)) + tree = etree.parse(real_lems_file) + + sim = tree.getroot().find("Simulation") + ns_prefix = "" + + possible_prefixes = ["{http://www.neuroml.org/lems/0.7.2}"] + if sim is None: + for pre in possible_prefixes: + for comp in tree.getroot().findall(pre + "Component"): + if comp.attrib["type"] == "Simulation": + ns_prefix = pre + sim = comp + + if get_events: + event_output_files = sim.findall(ns_prefix + "EventOutputFile") + for i, of in enumerate(event_output_files): + name = of.attrib["fileName"] + file_name = os.path.join(base_dir, name) + if not os.path.isfile(file_name): # If not relative to the LEMS file... + file_name = os.path.join(base_lems_file_path, name) + + # if not os.path.isfile(file_name): # If not relative to the LEMS file... + # file_name = os.path.join(os.getcwd(),name) + # ... try relative to cwd. + # if not os.path.isfile(file_name): # If not relative to the LEMS file... + # file_name = os.path.join(os.getcwd(),'NeuroML2','results',name) + # ... try relative to cwd in NeuroML2/results subdir. + if not os.path.isfile(file_name): # If not relative to the base dir... + raise OSError( + ("Could not find simulation output " "file %s" % file_name) + ) + format = of.attrib["format"] + logger.info( + "Loading saved events from %s (format: %s)" % (file_name, format) + ) + selections = {} + for col in of.findall(ns_prefix + "EventSelection"): + id = int(col.attrib["id"]) + select = col.attrib["select"] + events[select] = [] + selections[id] = select + + with open(file_name) as f: + for line in f: + values = line.split() + if format == "TIME_ID": + t = float(values[0]) + id = int(values[1]) + elif format == "ID_TIME": + id = int(values[0]) + t = float(values[1]) + logger.debug( + "Found a event in cell %s (%s) at t = %s" + % (id, selections[id], t) + ) + events[selections[id]].append(t) + + if get_traces: + output_files = sim.findall(ns_prefix + "OutputFile") + + for i, of in enumerate(output_files): + traces["t"] = [] + name = of.attrib["fileName"] + file_name = os.path.join(base_dir, name) + + if not os.path.isfile(file_name): # If not relative to the LEMS file... + file_name = os.path.join(base_lems_file_path, name) + + if not os.path.isfile(file_name): # If not relative to the LEMS file... + file_name = os.path.join(os.getcwd(), name) + + # ... try relative to cwd. + if not os.path.isfile(file_name): # If not relative to the LEMS file... + file_name = os.path.join(os.getcwd(), "NeuroML2", "results", name) + # ... try relative to cwd in NeuroML2/results subdir. + if not os.path.isfile(file_name): # If not relative to the LEMS file... + raise OSError( + ("Could not find simulation output " "file %s" % file_name) + ) + + logger.info("Loading traces from %s" % (file_name)) + cols = [] + cols.append("t") + for col in of.findall(ns_prefix + "OutputColumn"): + quantity = col.attrib["quantity"] + traces[quantity] = [] + cols.append(quantity) + + # TODO: could be quicker using numpy etc? + with open(file_name) as f: + for line in f: + values = line.split() + for vi in range(len(values)): + traces[cols[vi]].append(float(values[vi])) + + if get_traces and get_events: + if len(traces) == 0: + raise ValueError("No traces found") + if len(events) == 0: + raise ValueError("No events found") + return traces, events + elif get_events: + if len(events) == 0: + raise ValueError("No events found") + return events + else: + if len(traces) == 0: + raise ValueError("No traces found") + return traces diff --git a/pyneuroml/plot/Plot.py b/pyneuroml/plot/Plot.py index 31cf7d26..a937fad2 100644 --- a/pyneuroml/plot/Plot.py +++ b/pyneuroml/plot/Plot.py @@ -119,6 +119,8 @@ def generate_plot( - "bottom center" places the legend on the bottom, below the figure + Note that if labels is None, a legend is not shown + :type legend_position: str :param show_plot_already: if plot should be shown when created (default: True) :type show_plot_already: boolean diff --git a/pyneuroml/plot/PlotTimeSeries.py b/pyneuroml/plot/PlotTimeSeries.py new file mode 100644 index 00000000..8919857c --- /dev/null +++ b/pyneuroml/plot/PlotTimeSeries.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +""" +Functions related to plotting of time series + +File: pyneuroml/plot/PlotTimeSeries.py + +Copyright 2024 NeuroML contributors +""" + + +import argparse +import logging +import math +import typing + +import numpy +import pyneuroml.lems as pynmll +import pyneuroml.plot.Plot as pynmlplt +from matplotlib import pyplot as plt +from matplotlib_scalebar.scalebar import ScaleBar +from pyneuroml.utils.cli import build_namespace + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +TIME_SERIES_PLOTTER_DEFAULTS = {"offset": False} + + +def plot_time_series( + trace_data: typing.Union[ + typing.Dict[typing.Any, typing.Any], + typing.List[typing.Dict[typing.Any, typing.Any]], + ], + title: str = "", + offset: bool = True, + show_plot_already: bool = True, + scalebar_location: typing.Optional[str] = None, + scalebar_length: typing.Optional[float] = None, + labels: bool = False, + **kwargs: typing.Any, +) -> None: + """Plot time series data from a dictionary of data + + .. versionadded:: 1.2.2 + + .. seealso:: + + :py:func:`pyneuroml.plot.Plot.generate_plot` + general plotting function + + :param trace_data: a dictionary of trace data, or list of such dictionaries + The keys are used as labels in the legend. + At least one 't' key must exist in each dict, which holds the time axis data. + :type trace_data: dict or list(dict) + :param title: plot title, use "" to leave it blank + :type title: str + :param offset: whether the time series should be offset from each other + along the y axis for clarity. + This is often useful when plotting voltage traces for a population of + cells where overlapping traces would make it hard to see individual + values + :type offset: bool + :param scalebar_location: where a scalebar should be placed + If None, no scalebar is shown. + See the documentation of matplotlib-scalebar for more information: + https://github.com/ppinard/matplotlib-scalebar + :type scalebar_location: str or None + :param scalebar_length: length of scalebar in units of data (only valid if + scalebar_location is not None) + :type scalebar_length: float + :param labels: toggle using labels for legends + If labels is None, no legend is shown + :type labels: bool + :param kwargs: other key word arguments that are passed to the + `pyneuroml.plot.Plot.generate_plot` function + :returns: None + :raises ValueError: if a 't' (time) key is not found in the traces data + + """ + if type(trace_data) is dict: + trace_data = [trace_data] + + num_traces = 0 + for td in trace_data: + num_traces += len(td) + logger.debug(f"Plotting {num_traces} time series") + + xs = [] + ys = [] + if labels is True: + labelvals = [] + else: + labelvals = None + + # calculate trace width + miny = float(math.inf) + maxy = float(-math.inf) + + for td in trace_data: + if "t" not in td.keys(): + raise ValueError("A 't' (time) key must be provided in the trace data") + + if offset is True or scalebar_location is not None: + for key, trace in td.items(): + if key == "t": + continue + + max_trace = max(trace) + min_trace = min(trace) + if max_trace > maxy: + maxy = max_trace + if min_trace < miny: + miny = min_trace + + if offset is True or scalebar_location is not None: + trace_width = abs(miny) + abs(maxy) + logger.debug(f"trace max, min, width are: {maxy}, {miny}, {trace_width}") + + ctr = 0 + for td in trace_data: + if offset is False: + for key, trace in td.items(): + if key == "t": + continue + xs.append(td["t"]) + ys.append(trace) + if labels is True: + labelvals.append(key) + else: + for key, trace in td.items(): + if key == "t": + continue + offset_trace = numpy.array(trace) + (ctr * trace_width) + xs.append(td["t"]) + ys.append(offset_trace) + if labels is True: + labelvals.append(key) + ctr += 1 + + ax = pynmlplt.generate_plot( + xvalues=xs, + yvalues=ys, + title=title, + labels=labelvals, + show_plot_already=False, + **kwargs, + ) + + # clear ytics + if offset is True: + ax.set_yticks([]) + + if scalebar_location is not None: + if scalebar_length is None: + scalebar_length = trace_width / 2 + + scalebar_ = ScaleBar( + 1, + rotation="vertical", + scale_loc="none", + location=scalebar_location, + fixed_value=scalebar_length, + label=str(scalebar_length), + label_loc="right", + ) + + print(f"Note: length of the scalebar is {scalebar_length} units") + ax.add_artist(scalebar_) + + if show_plot_already is True: + plt.show() + + +def plot_time_series_from_lems_file( + lems_file_name: str, base_dir: str = ".", title: str = "", **kwargs +) -> None: + """Plot time series from a LEMS file. + + Wrapper around the `plot_time_series` function. This reads a LEMS file, + loads the data and then passes it to it. + + .. versionadded:: 1.2.2 + + .. seealso:: + + :py:func:`pyneuroml.plot.PlotTimeSeries.plot_time_series` + main plotter function + + :param lems_file_name: path to LEMS simulation file + :type lems_file_name: str + :param base_dir: directory where LEMS file resides + :type base_dir: str + :param **kwargs: other arguments passed to `plot_time_series` + :returns: None + + """ + traces = pynmll.load_sim_data_from_lems_file( + lems_file_name, get_events=False, get_traces=True + ) + + plot_time_series(traces, **kwargs) + + +def plot_time_series_from_data_files( + data_file_names: typing.Union[str, typing.List[str]], **kwargs +): + """Plot time series from a data file. + + The first column must be the time, the other columns are data. + + .. versionadded:: 1.2.2 + + :param data_file_names: name/path to data file(s) + :type data_file_names: str or list of strings + :param kwargs: other key word arguments that are passed to the + `plot_time_series` function + + """ + all_traces = [] + if type(data_file_names) is str: + data_file_names = [data_file_names] + + for f in data_file_names: + print(f"Processing: {f}") + traces = {} + data_array = numpy.loadtxt(f) + traces["t"] = data_array[:, 0] + num_cols = numpy.shape(data_array)[1] + for i in range(1, num_cols, 1): + traces[str(i)] = data_array[:, i] + all_traces.append(traces) + + plot_time_series(all_traces, labels=False, **kwargs) + + +def _process_time_series_plotter_args(): + """ + Parse command-line arguments. + """ + parser = argparse.ArgumentParser( + description=("A script to plot time series data from data files or LEMS files") + ) + + parser.add_argument( + "input_files", + type=str, + metavar="", + nargs="*", + help="a LEMS file (LEMS_..) or data files to plot time series from", + ) + + parser.add_argument( + "-offset", + action="store_true", + default=TIME_SERIES_PLOTTER_DEFAULTS["offset"], + help=("Toggle whether plots are overlaid or offset"), + ) + + return parser.parse_args() + + +def _time_series_plotter_main(args=None): + if args is None: + args = _process_time_series_plotter_args() + + a = build_namespace(TIME_SERIES_PLOTTER_DEFAULTS, a=args) + + logger.debug(a) + if len(a.input_files) == 1 and a.input_files[0].startswith("LEMS_"): + plot_time_series_from_lems_file( + a.input_files[0], offset=a.offset, bottom_left_spines_only=True + ) + else: + plot_time_series_from_data_files( + a.input_files, offset=a.offset, bottom_left_spines_only=True + ) diff --git a/pyneuroml/pynml.py b/pyneuroml/pynml.py index 3f81e83e..a683df9e 100644 --- a/pyneuroml/pynml.py +++ b/pyneuroml/pynml.py @@ -27,6 +27,8 @@ from pyneuroml import DEFAULTS, JNEUROML_VERSION, __version__ from pyneuroml.errors import ARGUMENT_ERR, UNKNOWN_ERR from pyneuroml.utils import extract_lems_definition_files + +# these imports are included for backwards compatibility from pyneuroml.utils.units import * from pyneuroml.modelgraphs import * from pyneuroml.runners import * diff --git a/pyneuroml/runners.py b/pyneuroml/runners.py index 65aa463b..23fe4f29 100644 --- a/pyneuroml/runners.py +++ b/pyneuroml/runners.py @@ -27,12 +27,8 @@ from pyneuroml import DEFAULTS, __version__ from pyneuroml.errors import UNKNOWN_ERR -from pyneuroml.utils import ( - get_files_generated_after, - get_model_file_list, - get_path_to_jnml_jar, - get_pyneuroml_tempdir, -) +import pyneuroml.utils +import pyneuroml.utils.misc from typing import Optional logger = logging.getLogger(__name__) @@ -646,7 +642,7 @@ def run_jneuroml( else: pre_jar = "" - jar_path = get_path_to_jnml_jar() + jar_path = pyneuroml.utils.misc.get_path_to_jnml_jar() output = "" retcode = -1 @@ -726,7 +722,7 @@ def run_jneuroml_with_realtime_output( pre_jar = " -Djava.awt.headless=true" else: pre_jar = "" - jar_path = get_path_to_jnml_jar() + jar_path = pyneuroml.utils.misc.get_path_to_jnml_jar() command = "" command_success = False @@ -1202,7 +1198,7 @@ def generate_sim_scripts_in_folder( if root_dir is None: root_dir = "." - tdir = get_pyneuroml_tempdir(rootdir=run_dir, prefix="pyneuroml") + tdir = pyneuroml.utils.get_pyneuroml_tempdir(rootdir=run_dir, prefix="pyneuroml") os.mkdir(tdir) if len(Path(lems_file_name).parts) > 1: @@ -1213,7 +1209,7 @@ def generate_sim_scripts_in_folder( logger.debug("Getting list of model files") model_file_list = [] # type: list lems_def_dir = None - lems_def_dir = get_model_file_list( + lems_def_dir = pyneuroml.utils.get_model_file_list( lems_file_name, model_file_list, root_dir, lems_def_dir ) @@ -1271,7 +1267,7 @@ def generate_sim_scripts_in_folder( **engine_kwargs, ) - generated_files = get_files_generated_after( + generated_files = pyneuroml.utils.get_files_generated_after( start_time, ignore_suffixes=["xml", "nml"] ) diff --git a/pyneuroml/utils/__init__.py b/pyneuroml/utils/__init__.py index c274fae0..e1f09a22 100644 --- a/pyneuroml/utils/__init__.py +++ b/pyneuroml/utils/__init__.py @@ -8,7 +8,7 @@ """ import copy -import datetime +from datetime import datetime import logging import math import os @@ -27,8 +27,8 @@ import numpy from lems.model.model import Model from neuroml.loaders import read_neuroml2_file -from pyneuroml import JNEUROML_VERSION from pyneuroml.errors import UNKNOWN_ERR +import pyneuroml.utils.misc logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -469,7 +469,7 @@ def get_pyneuroml_tempdir(rootdir: str = ".", prefix: str = "pyneuroml"): :rtype: str """ - timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + timestamp = datetime.now().strftime("%Y%m%d%H%M%S") random_suffix = "".join(random.choices(string.ascii_uppercase + string.digits, k=6)) tdir = rootdir + "/" + f"{prefix}_{timestamp}_{random_suffix}/" @@ -584,7 +584,7 @@ def extract_lems_definition_files( :type path: str or None :returns: directory path """ - jar_path = get_path_to_jnml_jar() + jar_path = pyneuroml.utils.misc.get_path_to_jnml_jar() logger.debug( "Loading standard NeuroML2 dimension/unit definitions from %s" % jar_path ) @@ -614,17 +614,3 @@ def extract_lems_definition_files( path = path + "/NeuroML2CoreTypes/" logger.info("NeuroML LEMS definition files extracted to: {}".format(path)) return path - - -def get_path_to_jnml_jar() -> str: - """Get the path to the jNeuroML jar included with PyNeuroML. - - :returns: path of jar file - """ - script_dir = os.path.dirname(os.path.realpath(__file__)) - jar_path = os.path.join( - script_dir, - "./../lib", - "jNeuroML-%s-jar-with-dependencies.jar" % JNEUROML_VERSION, - ) - return jar_path diff --git a/pyneuroml/utils/misc.py b/pyneuroml/utils/misc.py new file mode 100644 index 00000000..98d22bb3 --- /dev/null +++ b/pyneuroml/utils/misc.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +""" +Miscellaneous utils + +File: pyneuroml/utils/misc.py + +Copyright 2024 NeuroML contributors +""" + + +import os + +from pyneuroml import JNEUROML_VERSION + + +def get_path_to_jnml_jar() -> str: + """Get the path to the jNeuroML jar included with PyNeuroML. + + :returns: path of jar file + """ + script_dir = os.path.dirname(os.path.realpath(__file__)) + jar_path = os.path.join( + script_dir, + "./../lib", + "jNeuroML-%s-jar-with-dependencies.jar" % JNEUROML_VERSION, + ) + return jar_path diff --git a/pyneuroml/utils/units.py b/pyneuroml/utils/units.py index 6610d41f..e894efc8 100644 --- a/pyneuroml/utils/units.py +++ b/pyneuroml/utils/units.py @@ -14,7 +14,7 @@ import lems.model.model as lems_model from lems.parser.LEMS import LEMSFileParser -from pyneuroml.utils import get_path_to_jnml_jar +import pyneuroml.utils.misc as pymisc logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -109,7 +109,7 @@ def get_lems_model_with_units() -> lems_model.Model: global lems_model_with_units if lems_model_with_units is None: - jar_path = get_path_to_jnml_jar() + jar_path = pymisc.get_path_to_jnml_jar() logger.debug( "Loading standard NeuroML2 dimension/unit definitions from %s" % jar_path ) diff --git a/setup.cfg b/setup.cfg index 7cf12919..684a70d3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,6 +51,7 @@ console_scripts = pynml-plotmorph = pyneuroml.plot.PlotMorphology:main pynml-channelml2nml = pyneuroml.channelml:main pynml-plotchan = pyneuroml.analysis.ChannelDensityPlot:channel_density_plotter_cli + pynml-plottimeseries = pyneuroml.plot.PlotTimeSeries:_time_series_plotter_main pynml-sonata = neuromllite.SonataReader:main [options.package_data] diff --git a/test-ghactions.sh b/test-ghactions.sh index f3cb6f3e..f09cb427 100755 --- a/test-ghactions.sh +++ b/test-ghactions.sh @@ -1,7 +1,26 @@ set -e +# CI already installs package and all optional dependencies, so this is redundant. +# But we keep it to allow easy local testing. pip install . +echo +echo "################################################" +echo "## Testing all CLI tools" + +full_path=$(command -v pynml) +bin_location=$(dirname $full_path) + +for f in ${bin_location}/pynml* +do + current_exec=$(basename $f) + echo "-> Testing $current_exec runs" + echo + command $current_exec -h + echo + echo +done + echo echo "################################################" echo "## Running unit tests" diff --git a/tests/lems/test_lems.py b/tests/lems/test_lems.py new file mode 100644 index 00000000..8606ab51 --- /dev/null +++ b/tests/lems/test_lems.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +""" +Tests related to pyneuroml.lems module + +File: tests/lems/tests_lems.py + +Copyright 2024 NeuroML contributors +""" + +import logging +import os +import tempfile +import unittest + +import pyneuroml.lems as pyl + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +class TestLEMSModule(unittest.TestCase): + """Test the LEMS module""" + + def test_load_sim_data_from_lems_file(self): + """Test the load_sim_data_from_lems_file function""" + spike_data = """\ +0 0.04350000000009967 +0 0.10960000000009591 +0 0.1793000000001065 +0 0.2487000000001223 +0 0.31810000000013805 +0 0.3876000000001538 +0 0.45710000000016965 +0 0.5265000000001689 +0 0.5961000000001057 +0 0.6655000000000425 +0 0.7348999999999793 +0 0.8042999999999163 +0 0.873899999999853 +0 0.9433999999997897""" + + event_data_file = tempfile.NamedTemporaryFile(mode="w", delete=False, dir=".") + print(spike_data, file=event_data_file) + event_data_file.flush() + event_data_file.close() + + trace_data = """\ +0.0 -0.06 +1.0E-4 -0.05993 +2.0E-4 -0.05986098 +3.0E-4 -0.05979291 +4.0E-4 -0.059725776 +5.0E-4 -0.05965956 +6.0E-4 -0.05959424 +7.0E-4 -0.0595298 +8.0E-4 -0.05946622 +9.0E-4 -0.059403483 +0.001 -0.05934157 +0.0011 -0.059280466 +0.0012 -0.059220158 +0.0013 -0.05916062 +0.0014 -0.05910185 +0.0015 -0.05904382 +0.0016 -0.05898653 +0.0017 -0.05892995 +0.0018 -0.058874078 +0.0019 -0.058818895""" + + trace_file = tempfile.NamedTemporaryFile(mode="w", delete=False, dir=".") + print(trace_data, file=trace_file) + trace_file.flush() + trace_file.close() + + lems_contents = f""" + + + + + + + + + + + + + + + + + + + + + + + + + + + """ + + f = tempfile.NamedTemporaryFile( + mode="w", + delete=False, + dir=".", + ) + print(lems_contents, file=f) + f.flush() + f.close() + + events = pyl.load_sim_data_from_lems_file( + f.name, base_dir=".", get_events=True, get_traces=False + ) + + traces = pyl.load_sim_data_from_lems_file( + f.name, base_dir=".", get_events=False, get_traces=True + ) + + self.assertIsNotNone(events) + self.assertEqual(events["IzPop0[0]"][0], 0.04350000000009967) + self.assertEqual(events["IzPop0[0]"][-1], 0.9433999999997897) + print(events) + + self.assertIsNotNone(traces) + self.assertEqual(traces["t"][0], 0.0) + self.assertEqual(traces["t"][-1], 0.0019) + self.assertEqual(traces["IzhPop0[0]/v"][0], -0.06) + self.assertEqual(traces["IzhPop0[0]/v"][-1], -0.058818895) + print(traces) + + os.unlink(f.name) + os.unlink(event_data_file.name) + os.unlink(trace_file.name) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/plot/test_plot_time_series.py b/tests/plot/test_plot_time_series.py new file mode 100644 index 00000000..a5e29e28 --- /dev/null +++ b/tests/plot/test_plot_time_series.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +""" +Tests for pyneuroml.plot.PlotTimeSeries + +File: test/plot/test_plot_time_series.py + +Copyright 2024 NeuroML contributors +""" + + +import os +import unittest + +import numpy +import logging +import pyneuroml.plot.PlotTimeSeries as pyplts +import tempfile + +from .. import BaseTestCase + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +class TestPlotTimeSeries(BaseTestCase): + """Test PlotTimeSeries module""" + + def test_plot_time_series(self): + """Test plot_time_series function.""" + + npoints = 1000 + traces_dict = {} + traces_dict["t"] = list(numpy.arange(0, 1000, 1000 / npoints)) + + labels = list(range(0, 3)) + + for label in labels: + traces_dict[str(label)] = list( + numpy.random.default_rng().uniform(-30, 80, npoints) + ) + + pyplts.plot_time_series( + traces_dict, + title="", + offset=False, + show_plot_already=False, + save_figure_to="time-series-test.png", + ) + pyplts.plot_time_series( + traces_dict, + title="", + offset=True, + show_plot_already=False, + save_figure_to="time-series-test-2.png", + ) + self.assertIsFile("time-series-test.png") + self.assertIsFile("time-series-test-2.png") + + os.unlink("time-series-test.png") + os.unlink("time-series-test-2.png") + + def test_plot_time_series_from_data_file(self): + """Test plot_time_series_from_data_file function""" + trace_file = tempfile.NamedTemporaryFile(mode="w", delete=False, dir=".") + for i in range(0, 1000): + print( + f"{i/1000}\t{numpy.random.default_rng().random()}\t{numpy.random.default_rng().random()}\t{numpy.random.default_rng().random()}", + file=trace_file, + ) + trace_file.flush() + trace_file.close() + + pyplts.plot_time_series_from_data_files( + trace_file.name, + title="", + offset=False, + show_plot_already=False, + save_figure_to="time-series-test-from-file.png", + ) + self.assertIsFile("time-series-test-from-file.png") + + pyplts.plot_time_series_from_data_files( + trace_file.name, + title="", + offset=True, + show_plot_already=False, + save_figure_to="time-series-test-from-file-2.png", + ) + self.assertIsFile("time-series-test-from-file.png") + + os.unlink("time-series-test-from-file.png") + os.unlink("time-series-test-from-file-2.png") + os.unlink(trace_file.name) + + def test_plot_time_series_from_lems_file(self): + """Test plot_time_series_from_lems_file function""" + trace_file = tempfile.NamedTemporaryFile(mode="w", delete=False, dir=".") + for i in range(0, 1000): + print( + f"{i/1000}\t{numpy.random.default_rng().random()}\t{numpy.random.default_rng().random()}\t{numpy.random.default_rng().random()}", + file=trace_file, + ) + trace_file.flush() + trace_file.close() + + lems_contents = f""" + + + + + + + + + + + + + + + + + + + + + + + + + + """ + + lems_file = tempfile.NamedTemporaryFile( + mode="w", + delete=False, + dir=".", + ) + print(lems_contents, file=lems_file) + lems_file.flush() + lems_file.close() + + pyplts.plot_time_series_from_data_files( + trace_file.name, + title="", + offset=False, + show_plot_already=False, + save_figure_to="time-series-test-from-lems-file.png", + ) + self.assertIsFile("time-series-test-from-lems-file.png") + + pyplts.plot_time_series_from_data_files( + trace_file.name, + title="", + offset=True, + show_plot_already=False, + save_figure_to="time-series-test-from-lems-file-2.png", + ) + self.assertIsFile("time-series-test-from-lems-file.png") + + os.unlink("time-series-test-from-lems-file.png") + os.unlink("time-series-test-from-lems-file-2.png") + os.unlink(trace_file.name) + os.unlink(lems_file.name) + + +if __name__ == "__main__": + unittest.main()