Skip to content

Commit

Permalink
Using libsonata to cover more of the use cases (#154)
Browse files Browse the repository at this point in the history
* Using libsonata to parse the configs
* removed support for point_neurons (at least for now)
* Removed EdgeStorage and NodeStorage
* fix lint
* update changelog

Co-authored-by: Mike Gevaert <michael.gevaert@epfl.ch>
  • Loading branch information
joni-herttuainen and mgeplf committed Aug 11, 2022
1 parent 5152257 commit 2640307
Show file tree
Hide file tree
Showing 28 changed files with 1,168 additions and 1,445 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
Changelog
=========

Version v0.13.2
Version v1.0.0
---------------

Improvements
~~~~~~~~~~~~
- Add black & isort to handle formatting
- Use libsonata to provide more of the functionality
- parsing config files
- accessing data

Removed
~~~~~~~
- NodeStorage & EdgeStorage classes
- point_neuron support


Version v0.13.1
---------------
Expand Down
2 changes: 1 addition & 1 deletion bluepysnap/bbp.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from bluepysnap.sonata_constants import DYNAMICS_PREFIX, Edge, Node

NODE_TYPES = {"biophysical", "virtual", "astrocyte", "single_compartment", "point_neuron"}
NODE_TYPES = {"biophysical", "virtual", "astrocyte", "single_compartment"}
EDGE_TYPES = {"chemical", "electrical", "synapse_astrocyte", "endfoot"}


Expand Down
17 changes: 11 additions & 6 deletions bluepysnap/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from cached_property import cached_property

from bluepysnap.config import Config
from bluepysnap.config import CircuitConfig
from bluepysnap.edges import Edges
from bluepysnap.node_sets import NodeSets
from bluepysnap.nodes import Nodes
Expand All @@ -32,23 +32,28 @@ def __init__(self, config):
"""Initializes a circuit object from a SONATA config file.
Args:
config (str/dict): Path to a SONATA config file or sonata config dict.
config (str): Path to a SONATA config file.
Returns:
Circuit: A Circuit object.
"""
self._config = Config(config).resolve()
self._config = CircuitConfig.from_config(config)

@property
def to_libsonata(self):
"""Libsonata instance of the circuit."""
return self._config.to_libsonata

@property
def config(self):
"""Network config dictionary."""
return self._config
return self._config.to_dict()

@cached_property
def node_sets(self):
"""Returns the NodeSets object bound to the circuit."""
if "node_sets_file" in self._config:
return NodeSets(self._config["node_sets_file"])
if "node_sets_file" in self.config:
return NodeSets(self.config["node_sets_file"])
return {}

@cached_property
Expand Down
7 changes: 5 additions & 2 deletions bluepysnap/circuit_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@

from bluepysnap import BluepySnapError
from bluepysnap.bbp import EDGE_TYPES, NODE_TYPES
from bluepysnap.config import Config
from bluepysnap.config import Parser
from bluepysnap.morph import EXTENSIONS_MAPPING
from bluepysnap.utils import load_json

L = logging.getLogger("brainbuilder")
MAX_MISSING_FILES_DISPLAY = 10
Expand All @@ -41,6 +42,8 @@ def __str__(self):
"""Returns only message by default."""
return str(self.message)

__repr__ = __str__

def __eq__(self, other):
"""Two errors are equal if inherit from Error and their level, message are equal."""
if not isinstance(other, Error):
Expand Down Expand Up @@ -857,7 +860,7 @@ def validate(config_file, bbp_check=False, print_errors=True):
Returns:
list: List of errors, empty if no errors
"""
config = Config(config_file).resolve()
config = Parser.parse(load_json(config_file), str(Path(config_file).parent))
errors = _check_required_datasets(config)

if not errors:
Expand Down
83 changes: 62 additions & 21 deletions bluepysnap/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@

"""SONATA network config parsing."""

import json
from collections.abc import Iterable, Mapping
from pathlib import Path

from bluepysnap import utils
import libsonata

from bluepysnap.exceptions import BluepySnapError

# List of keys which are expected to have paths
Expand All @@ -29,40 +31,41 @@
"biophysical_neuron_models_dir",
"vasculature_file",
"vasculature_mesh",
"end_feet_area",
"endfeet_meshes_file",
"microdomains_file",
"neurolucida-asc",
"h5v1",
"edges_file",
"nodes_file",
"edges_type_file",
"nodes_type_file",
"node_sets_file",
"output_dir",
"network",
"mechanisms_dir",
}


class Config:
class Parser:
"""SONATA network config parser.
See Also:
https://github.com/AllenInstitute/sonata/blob/master/docs/SONATA_DEVELOPER_GUIDE.md#network_config
"""

def __init__(self, config):
"""Initializes a Config object from a path to the actual config.
def __init__(self, config, config_dir):
"""Initializes a Resolver object.
Args:
config (str/dict): Path to the SONATA configuration file or dict containing the config.
config (dict): Dict containing the config.
config_dir(str): Path to the directory containing the config file.
Returns:
Config: A Config object.
Parser: A Parser object.
"""
if isinstance(config, dict):
content = config.copy()
configdir = None
else:
configdir = str(Path(config).parent.resolve())
content = utils.load_json(str(config))
self.manifest = Config._resolve_manifest(content.pop("manifest", {}), configdir)
content = config.copy()

self.manifest = Parser._resolve_manifest(content.pop("manifest", {}), config_dir)
self.content = content

@staticmethod
Expand All @@ -73,8 +76,6 @@ def _resolve_manifest(manifest, configdir):
if not isinstance(v, str):
raise BluepySnapError(f"{v} should be a string value.")
if not Path(v).is_absolute() and not v.startswith("$"):
if configdir is None:
raise BluepySnapError("Dictionary config with relative paths is not allowed.")
result[k] = str(Path(configdir, v).resolve())

while True:
Expand Down Expand Up @@ -110,9 +111,7 @@ def _resolve_string(self, value, key):
return str(Path(*vs))
# only way to know if value is a relative path or a normal string
elif value.startswith(".") or key in EXPECTED_PATH_KEYS:
if self.manifest["${configdir}"] is not None:
return str(Path(self.manifest["${configdir}"], value).resolve())
raise BluepySnapError("Dictionary config with relative paths is not allowed.")
return str(Path(self.manifest["${configdir}"], value).resolve())
else:
# we cannot know if a string is a path or not if it does not contain anchor or .
return value
Expand All @@ -132,6 +131,48 @@ def resolve(self):
return self._resolve(self.content)

@staticmethod
def parse(filepath):
def parse(config, configdir):
"""Parse SONATA network config."""
return Config(filepath).resolve()
return Parser(config, configdir).resolve()


class Config:
"""Common config class."""

def __init__(self, config, config_class):
"""Initializes the Config class.
Args:
config (str): Path to the configuration file
config_class (class): libsonata class corresponding to the configuration file, either
libsonata.CircuitConfig or libsonata.SimulationConfig
"""
self._config_dir = str(Path(config).parent.absolute())
self._libsonata = config_class.from_file(config)

@property
def to_libsonata(self):
"""Return the libsonata instance of the config."""
return self._libsonata

def to_dict(self):
"""Return the configuration as a dict with absolute paths."""
return Parser.parse(json.loads(self._libsonata.expanded_json), self._config_dir)


class CircuitConfig(Config):
"""Handle CircuitConfig."""

@classmethod
def from_config(cls, config_path):
"""Instantiate the config class from circuit configuration."""
return cls(config_path, libsonata.CircuitConfig)


class SimulationConfig(Config):
"""Handle SimulationConfig."""

@classmethod
def from_config(cls, config_path):
"""Instantiate the config class from simulation configuration."""
return cls(config_path, libsonata.SimulationConfig)

0 comments on commit 2640307

Please sign in to comment.