1313import pathlib
1414import json
1515import math
16- import re
17-
18-
16+ import pprint
17+ import airspeed
1918import yaml
2019
2120try :
3029from neuron import h
3130
3231
32+ pp = pprint .PrettyPrinter (depth = 4 )
3333logger = logging .getLogger (__name__ )
3434logger .setLevel (logging .INFO )
3535
@@ -469,7 +469,9 @@ def getinfo(seclist: list, doprint: str = "", listall: bool = False):
469469 not replace_brackets (str (sec ))
470470 in paramsectiondict [rm_NML_str (pname [0 ])].keys ()
471471 ):
472- paramsectiondict [rm_NML_str (pname [0 ])][replace_brackets (str (sec ))] = {
472+ paramsectiondict [rm_NML_str (pname [0 ])][
473+ replace_brackets (str (sec ))
474+ ] = {
473475 "id" : str (sec ),
474476 }
475477 if rm_NML_str (pname [0 ]) in seginfo [seg ]:
@@ -488,13 +490,13 @@ def getinfo(seclist: list, doprint: str = "", listall: bool = False):
488490 unique_values = list (set (values ))
489491 # if all values are the same, only print them once as '*'
490492 if len (unique_values ) == 1 :
491- paramsectiondict [rm_NML_str (pname [0 ])][replace_brackets ( str ( sec ))]. update (
492- { "nseg" : sec . nseg , "values" : { "*" : unique_values [ 0 ]}}
493- )
493+ paramsectiondict [rm_NML_str (pname [0 ])][
494+ replace_brackets ( str ( sec ))
495+ ]. update ({ "nseg" : sec . nseg , "values" : { "*" : unique_values [ 0 ]}} )
494496 else :
495- paramsectiondict [rm_NML_str (pname [0 ])][replace_brackets ( str ( sec ))]. update (
496- { "nseg" : sec . nseg , "values" : newseginfo }
497- )
497+ paramsectiondict [rm_NML_str (pname [0 ])][
498+ replace_brackets ( str ( sec ))
499+ ]. update ({ "nseg" : sec . nseg , "values" : newseginfo } )
498500
499501 if listall or numSecPresent > 0 :
500502 mt_dict = {
@@ -724,3 +726,131 @@ def rm_NML_str(astring: str) -> str:
724726 if "_NML2" in astring :
725727 logger .info (f"Removing '_NML2' string from { astring } to ease comparison" )
726728 return astring .replace ("_NML2" , "" )
729+
730+
731+ def export_mod_to_neuroml2 (mod_file : str ):
732+ """Helper function to export a mod file describing an ion channel to
733+ NeuroML2 format.
734+
735+ Note that these exports usually require more manual work before they are
736+ the converstion is complete. This method tries to take as much information
737+ as it can from the mod file to convert it into a NeuroML2 channel file.
738+
739+ Please use `pynml-channelanalysis` and `pynml-modchannelanalysis` commands
740+ to generate steady state etc. plots for the two implementations to compare
741+ them.
742+
743+ See also: https://docs.neuroml.org/Userdocs/CreatingNeuroMLModels.html#b-convert-channels-to-neuroml
744+
745+ :param mod_file: full path to mod file
746+ :type mod_file: str
747+ """
748+ logger .info ("Generating NeuroML2 representation for mod file: " + mod_file )
749+
750+ blocks = {}
751+ info = {}
752+ lines = [(str (ll .strip ())).replace ("\t " , " " ) for ll in open (mod_file )]
753+ line_num = 0
754+ while line_num < len (lines ):
755+ l = lines [line_num ]
756+ if len (l ) > 0 :
757+ logger .info (">>> %i > %s" % (line_num , l ))
758+ # @type l str
759+ if l .startswith ("TITLE" ):
760+ blocks ["TITLE" ] = l [6 :].strip ()
761+ if "{" in l :
762+ block_name = l [: l .index ("{" )].strip ()
763+ blocks [block_name ] = []
764+
765+ li = l [l .index ("{" ) + 1 :]
766+ bracket_depth = __check_brackets (li , 1 )
767+ while bracket_depth > 0 :
768+ if len (li ) > 0 :
769+ blocks [block_name ].append (li )
770+ logger .info (" > %s > %s" % (block_name , li ))
771+ line_num += 1
772+ li = lines [line_num ]
773+
774+ bracket_depth = __check_brackets (li , bracket_depth )
775+
776+ rem = li [:- 1 ].strip ()
777+ if len (rem ) > 0 :
778+ blocks [block_name ].append (rem )
779+
780+ line_num += 1
781+
782+ for line in blocks ["STATE" ]:
783+ if " " in line or "\t " in line :
784+ blocks ["STATE" ].remove (line )
785+ for s in line .split ():
786+ blocks ["STATE" ].append (s )
787+
788+ for line in blocks ["NEURON" ]:
789+ if line .startswith ("SUFFIX" ):
790+ info ["id" ] = line [7 :].strip ()
791+ if line .startswith ("USEION" ) and "WRITE" in line :
792+ info ["species" ] = line .split ()[1 ]
793+
794+ gates = []
795+ for s in blocks ["STATE" ]:
796+ gate = {}
797+ gate ["id" ] = s
798+ gate ["type" ] = "???"
799+ gate ["instances" ] = "???"
800+ gates .append (gate )
801+
802+ info ["type" ] = "ionChannelHH"
803+ info ["gates" ] = gates
804+
805+ info ["notes" ] = (
806+ "NeuroML2 file automatically generated from NMODL file: %s" % mod_file
807+ )
808+
809+ pp .pprint (blocks )
810+
811+ chan_file_name = "%s.channel.nml" % info ["id" ]
812+ chan_file = open (chan_file_name , "w" )
813+ chan_file .write (__merge_with_template (info ))
814+ chan_file .close ()
815+
816+
817+ def __check_brackets (line , bracket_depth ):
818+ """Check matching brackets
819+
820+ :param line: line to check
821+ :type line: str
822+ :param bracket_depth: current depth/level of brackets
823+ :type bracket_depth: int
824+ :returns: new bracket depth
825+ :rtype: int
826+ """
827+ if len (line ) > 0 :
828+ bracket_depth0 = bracket_depth
829+ for c in line :
830+ if c == "{" :
831+ bracket_depth += 1
832+ elif c == "}" :
833+ bracket_depth -= 1
834+ if bracket_depth0 != bracket_depth :
835+ logger .info (
836+ " <%s> moved bracket %i -> %i"
837+ % (line , bracket_depth0 , bracket_depth )
838+ )
839+ return bracket_depth
840+
841+
842+ def __merge_with_template (info ):
843+ """Merge information with the airspeed template file
844+
845+ :param info: information to fill in the template
846+ :type info:
847+ :returns: filled template
848+ :rtype: str
849+ """
850+ templfile = "TEMPLATE.channel.nml"
851+ if not os .path .isfile (templfile ):
852+ templfile = os .path .join (os .path .dirname (__file__ ), templfile )
853+ logger .info ("Merging with template %s" % templfile )
854+ with open (templfile ) as f :
855+ templ = airspeed .Template (f .read ())
856+ return templ .merge (info )
0 commit comments