Source code for dendrotweaks.biophys.io.loader
# SPDX-FileCopyrightText: 2025 Poirazi Lab <dendrotweaks@dendrites.gr>
# SPDX-License-Identifier: MPL-2.0
import os
import sys
import shutil
import subprocess
import neuron
from neuron import h
[docs]
class MODFileLoader():
def __init__(self):
self._loaded_mechanisms = set()
self.verbose = False
def _log(self, message):
"""Print a message if verbose mode is enabled."""
if self.verbose:
print(message)
# LOADING METHODS
def _get_mechanism_dir(self, path_to_mod_file: str) -> str:
"""
Get the subdirectory for the given mod file.
Parameters
----------
path_to_mod_file : str
Path to the .mod file.
Returns
-------
str
Path to the subdirectory for the mechanism.
"""
mechanism_name = os.path.basename(path_to_mod_file).replace('.mod', '')
parent_dir = os.path.dirname(path_to_mod_file)
if sys.platform.startswith('win'):
return os.path.join(parent_dir, mechanism_name, mechanism_name)
else:
return os.path.join(parent_dir, mechanism_name)
def _clean_mechanism_dir(self, mechanism_dir: str) -> None:
if sys.platform.startswith('win'):
parent_dir = os.path.dirname(mechanism_dir)
shutil.rmtree(parent_dir)
else:
shutil.rmtree(mechanism_dir)
[docs]
def load_mechanism(self, path_to_mod_file: str,
recompile: bool = False) -> None:
"""
Load a mechanism from the specified mod file.
Uses the NEURON neuron.load_mechanisms method to make
the mechanism available in the hoc interpreter.
Creates a temporary directory for the mechanism files
to be able to dynamically load mechanisms.
Parameters
----------
path_to_mod_file : str
Path to the .mod file.
recompile : bool
Force recompilation even if already compiled.
"""
mechanism_name = os.path.basename(path_to_mod_file).replace('.mod', '')
mechanism_dir = self._get_mechanism_dir(path_to_mod_file)
if self.verbose: print(f"{'=' * 60}\nLoading mechanism {mechanism_name} to NEURON...")
# Check if the mechanism is already loaded
if mechanism_name in self._loaded_mechanisms:
self._log(f'Mechanism "{mechanism_name}" already loaded')
return
if recompile and os.path.exists(mechanism_dir):
self._clean_mechanism_dir(mechanism_dir)
self._separate_and_compile(mechanism_name, mechanism_dir, path_to_mod_file)
# Load the mechanism
self._load_mechanism(mechanism_name, mechanism_dir)
# HELPER METHODS
def _separate_and_compile(self, mechanism_name, mechanism_dir, path_to_mod_file):
"""
Separate the mechanism files into their own directory and compile them.
Separation is done to enable dynamic loading of mechanisms.
Compilation is done using the appropriate command based on the platform.
Parameters
----------
mechanism_name : str
Name of the mechanism.
mechanism_dir : str
Directory to store the mechanism files.
path_to_mod_file : str
Path to the .mod file.
"""
if sys.platform.startswith('win'):
dll_file = os.path.join(os.path.dirname(mechanism_dir), 'nrnmech.dll')
if not os.path.exists(dll_file):
self._log(f'Compiling mechanism "{mechanism_name}"...')
os.makedirs(mechanism_dir, exist_ok=True)
shutil.copy(path_to_mod_file, mechanism_dir)
self._compile_files(mechanism_dir, ["mknrndll"], shell=True)
else:
x86_64_dir = os.path.join(mechanism_dir, 'x86_64')
if not os.path.exists(x86_64_dir):
self._log(f'Compiling mechanism "{mechanism_name}"...')
os.makedirs(mechanism_dir, exist_ok=True)
shutil.copy(path_to_mod_file, mechanism_dir)
self._compile_files(mechanism_dir, ["nrnivmodl"])
def _load_mechanism(self, mechanism_name: str, mechanism_dir: str) -> None:
"""
Load the mechanism into NEURON using neuron.load_mechanisms.
This method checks if the mechanism is already loaded
and only loads it if not.
Parameters
----------
mechanism_name : str
Name of the mechanism.
mechanism_dir : str
Directory containing the compiled mechanism files.
"""
if hasattr(h, mechanism_name):
self._log(f'Mechanism "{mechanism_name}" already exists in hoc')
else:
try:
neuron.load_mechanisms(mechanism_dir)
except Exception as e:
print(f"Failed to load mechanism {mechanism_name}: {e}")
return
self._loaded_mechanisms.add(mechanism_name)
self._log(f'Loaded mechanism "{mechanism_name}"')
def _compile_files(self, path, command, shell=False):
"""
Compile the MOD files in the specified directory.
Parameters
----------
path : str or Path
Directory containing MOD files to compile.
command : list
Compilation command to execute. Either "mknrndll" or "nrnivmodl".
shell : bool
Whether to use shell=True for subprocess.run
(Windows compatibility).
Returns
-------
bool
True if compilation succeeded, False otherwise.
"""
path_str = str(path)
try:
result = subprocess.run(
command,
cwd=path_str,
check=True,
capture_output=True,
text=True,
shell=shell
)
self._log("Compilation successful.")
if self.verbose and result.stdout:
print(result.stdout)
return
except subprocess.CalledProcessError as e:
print(f"Compilation failed with return code {e.returncode}")
if self.verbose:
if e.stdout:
print("Compiler output:\n", e.stdout)
if e.stderr:
print("Compiler errors:\n", e.stderr)
return