bluerecording 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ x86_64/
2
+ **/core*
3
+ figures/
4
+ positions_all_new/
5
+ positions_all_new_big/
6
+ **/*coeffs*.h5
7
+ *.SUCCESS
8
+ networks/
9
+ !examples/compare-to-reference-solutions_old/data/configuration/networks/
10
+ !examples/single_cell_l5_tpc/configuration/networks/
11
+ network/
12
+ examples/allen/data/simulation/configuration/components
13
+ bkg_inputs/
14
+ atlas/
15
+ reporting/
16
+ !examples/compare-to-reference-solutions_old/data/reporting/
17
+ reporting_small/
18
+ reporting_big/
19
+ output_sonata/
20
+ **/*.png
21
+ **/*.pyc
22
+ **/*.log
23
+ **/*.out
24
+ mod/
25
+ .ipynb_checkpoints/
26
+ **/*.dat
27
+ **/*.swp
28
+ **/*.swn
29
+ **/*.swo
30
+ **/*.err
31
+ build/
32
+ bluerecording.egg-info
33
+ .exception_node
34
+ *.nrrd
35
+ neurodamus/
36
+ positions/
37
+ *.pkl
38
+ !*_ref.pkl
39
+ venv/
40
+ *_venv/
41
+ env.sh
42
+ pkls/
43
+ pkls_big/
44
+ htmlcov/
45
+ **/EEG.h5
46
+ **/ECoG.h5
47
+ **/LFP.h5
48
+ *removeme*
49
+ *DS_Store
50
+ *Infinite_VeryFar_HighRes.h5
51
+ *Infinite_Close_HighRes_SmallSphere.h5
52
+ libsonatareport/
53
+ neurodamus-models/
54
+ nrn/
55
+ .code
56
+ .kiro
57
+ *output/
58
+ .vscode/
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.4
2
+ Name: bluerecording
3
+ Version: 0.4.0
4
+ Summary: A tool for calculating extracellular recording lead fields
5
+ Requires-Python: <3.14,>=3.10
6
+ Requires-Dist: h5py
7
+ Requires-Dist: libsonata
8
+ Requires-Dist: morphio
9
+ Requires-Dist: mpi4py
10
+ Requires-Dist: neurodamus>=4.2.1
11
+ Requires-Dist: pandas
12
+ Requires-Dist: scikit-learn
13
+ Requires-Dist: voxcell
14
+ Provides-Extra: all
15
+ Requires-Dist: bluepysnap; extra == 'all'
16
+ Requires-Dist: ipympl; extra == 'all'
17
+ Requires-Dist: jupyterlab; extra == 'all'
18
+ Requires-Dist: neuron; extra == 'all'
19
+ Requires-Dist: pytest; extra == 'all'
20
+ Requires-Dist: pytest-forked; extra == 'all'
21
+ Requires-Dist: pytest-mpi; extra == 'all'
22
+ Provides-Extra: neuron
23
+ Requires-Dist: neuron; extra == 'neuron'
24
+ Provides-Extra: notebooks
25
+ Requires-Dist: bluepysnap; extra == 'notebooks'
26
+ Requires-Dist: ipympl; extra == 'notebooks'
27
+ Requires-Dist: jupyterlab; extra == 'notebooks'
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest; extra == 'test'
30
+ Requires-Dist: pytest-forked; extra == 'test'
31
+ Requires-Dist: pytest-mpi; extra == 'test'
@@ -0,0 +1,40 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ from importlib.metadata import version
3
+
4
+ __version__ = version("bluerecording")
5
+
6
+
7
+ def _check_dependencies():
8
+ """Verify runtime dependencies that need special attention.
9
+
10
+ - h5py is declared in pyproject.toml but must be the MPI-enabled build.
11
+ The default pip wheel lacks MPI support.
12
+ - neuron is an optional extra ([neuron]). Required at runtime but kept
13
+ optional because it may be built from source.
14
+ """
15
+ import h5py
16
+
17
+ if not h5py.get_config().mpi:
18
+ raise ImportError(
19
+ "h5py is installed but was built without MPI support.\n"
20
+ "bluerecording requires parallel HDF5 I/O.\n"
21
+ "Fix with:\n"
22
+ " pip uninstall h5py\n"
23
+ " HDF5_MPI=ON pip install --no-cache-dir --no-binary=h5py h5py "
24
+ "--no-build-isolation\n"
25
+ "Or use './dev_setup.sh' followed by 'source env.sh' "
26
+ "which handles this automatically."
27
+ )
28
+
29
+ try:
30
+ import neuron # noqa: F401
31
+ except ImportError:
32
+ raise ImportError(
33
+ "bluerecording requires NEURON.\n"
34
+ "Install with: pip install bluerecording[neuron]\n"
35
+ "Or use './dev_setup.sh' followed by 'source env.sh' "
36
+ "to build from source."
37
+ )
38
+
39
+
40
+ _check_dependencies()
@@ -0,0 +1,68 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Shared circuit initialization via neurodamus.
3
+
4
+ Provides the entry point for loading a circuit model and extracting
5
+ the discretization info (node IDs, compartment structure, morphology access)
6
+ needed by both get_positions and write_weights.
7
+ """
8
+ import libsonata
9
+ import numpy as np
10
+
11
+ from .utils import get_circuit_path
12
+
13
+
14
+ def init_circuit(path_to_simconfig: str):
15
+ """Initialize neurodamus and extract circuit discretization info.
16
+
17
+ Args:
18
+ path_to_simconfig: Path to the SONATA simulation configuration file.
19
+
20
+ Returns:
21
+ node_manager: The neurodamus node manager for the single population.
22
+ ids: GIDs assigned to this MPI rank.
23
+ cols: (N, 2) int64 array of (gid, section) pairs describing every
24
+ compartment on this rank.
25
+ population: libsonata NodePopulation, needed for morphology file
26
+ resolution.
27
+ population_name: Name of the SONATA node population.
28
+ morphologies_dir: Fully resolved path to the morphologies directory,
29
+ as provided by libsonata.
30
+ """
31
+ # Lazy import: neurodamus pulls in NEURON, which is not available
32
+ # in lightweight installs (e.g. CI with --quick).
33
+ import neurodamus
34
+
35
+ nd = neurodamus.Neurodamus(
36
+ path_to_simconfig,
37
+ disable_reports=True,
38
+ direct_mode=True,
39
+ build_model=True,
40
+ enable_coord_mapping=True,
41
+ keep_build=False,
42
+ simulator="NEURON",
43
+ )
44
+ assert len(nd.circuits.node_managers) == 1, (
45
+ "Multiple or no node managers are not allowed for the moment"
46
+ )
47
+ node_manager = next(iter(nd.circuits.node_managers.values()))
48
+
49
+ ids = node_manager.get_final_gids()
50
+ points = node_manager.target_manager.get_target(None).get_point_list(
51
+ node_manager,
52
+ libsonata.SimulationConfig.Report.Sections.all,
53
+ libsonata.SimulationConfig.Report.Compartments.all,
54
+ )
55
+ cols = np.array(
56
+ [(p.gid, s) for p in points for s in sorted(p.sclst_ids)],
57
+ dtype=np.int64,
58
+ ).reshape(-1, 2)
59
+
60
+ population_name = node_manager.population_name
61
+
62
+ circuit_conf = libsonata.CircuitConfig.from_file(
63
+ get_circuit_path(path_to_simconfig)
64
+ )
65
+ population = circuit_conf.node_population(population_name)
66
+ morphologies_dir = circuit_conf.node_population_properties(population_name).morphologies_dir
67
+
68
+ return node_manager, ids, cols, population, population_name, morphologies_dir
@@ -0,0 +1,131 @@
1
+ import argparse
2
+ from pathlib import Path
3
+ from . import positions
4
+ from .circuit import init_circuit
5
+ from .weights import DEFAULT_SIGMA, write_h5_file
6
+ from .weights import initialize_h5_file
7
+ from . import __version__
8
+
9
+ def main():
10
+ parser = argparse.ArgumentParser(
11
+ prog="bluerecording",
12
+ description="Bluerecording CLI"
13
+ )
14
+
15
+ parser.add_argument(
16
+ "--version",
17
+ action="version",
18
+ version=f"%(prog)s {__version__}"
19
+ )
20
+
21
+ subparsers = parser.add_subparsers(dest="command", required=True)
22
+
23
+ # write_positions command
24
+ gp_parser = subparsers.add_parser(
25
+ "write_positions",
26
+ help="Compute and save segment positions to disk"
27
+ )
28
+ gp_parser.add_argument(
29
+ "path_to_simconfig",
30
+ type=str,
31
+ help="Path to the simulation configuration file"
32
+ )
33
+ gp_parser.add_argument(
34
+ "path_to_positions_folder",
35
+ type=str,
36
+ help="Path to the folder where positions will be stored"
37
+ )
38
+ gp_parser.add_argument(
39
+ "--no-replace-axons",
40
+ action="store_false",
41
+ dest="replace_axons",
42
+ help="Do not replace existing axons (default: replace)"
43
+ )
44
+
45
+ # write_weights command
46
+ ww_parser = subparsers.add_parser(
47
+ "write_weights",
48
+ help="Compute electrode weights for all cells in the circuit"
49
+ )
50
+ ww_parser.add_argument(
51
+ "path_to_simconfig",
52
+ type=str,
53
+ help="Path to the simulation configuration file"
54
+ )
55
+ ww_parser.add_argument(
56
+ "electrode_csv",
57
+ type=str,
58
+ help="Path to the electrode CSV file"
59
+ )
60
+ ww_parser.add_argument(
61
+ "output_path",
62
+ type=str,
63
+ help="Path to the output H5 weights file, or a directory (weights.h5 will be created inside)"
64
+ )
65
+ ww_parser.add_argument(
66
+ "--no-replace-axons",
67
+ action="store_false",
68
+ dest="replace_axons",
69
+ help="Do not replace existing axons (default: replace)"
70
+ )
71
+ ww_parser.add_argument(
72
+ "--sigma",
73
+ type=float,
74
+ nargs="+",
75
+ default=None,
76
+ help=f"Extracellular conductivity in S/m (default: {DEFAULT_SIGMA})"
77
+ )
78
+ ww_parser.add_argument(
79
+ "--path-to-fields",
80
+ type=str,
81
+ nargs="+",
82
+ default=None,
83
+ help="Path(s) to H5 potential field files for reciprocity electrodes"
84
+ )
85
+ ww_parser.add_argument(
86
+ "--with-neurite-type",
87
+ action="store_true",
88
+ default=False,
89
+ dest="with_neurite_type",
90
+ help="Append a neurite_types dataset to the weights file",
91
+ )
92
+ ww_parser.add_argument(
93
+ "--write-positions",
94
+ action="store_true",
95
+ default=False,
96
+ dest="write_positions",
97
+ help="Also save segment positions alongside the weights file",
98
+ )
99
+
100
+ args = parser.parse_args()
101
+
102
+ if args.command == "write_positions":
103
+ node_manager, ids, cols, population, _, morphologies_dir = init_circuit(args.path_to_simconfig)
104
+ positions_df, _, _ = positions.get_positions(
105
+ node_manager, ids, cols, population,
106
+ morphologies_dir=morphologies_dir,
107
+ replace_axons=args.replace_axons,
108
+ )
109
+ positions.save_positions(positions_df, args.path_to_positions_folder)
110
+
111
+ elif args.command == "write_weights":
112
+ node_manager, ids, cols, population, population_name, morphologies_dir = init_circuit(args.path_to_simconfig)
113
+ positions_df, cols, neurite_types = positions.get_positions(
114
+ node_manager, ids, cols, population,
115
+ morphologies_dir=morphologies_dir,
116
+ replace_axons=args.replace_axons,
117
+ )
118
+ output_file = Path(args.output_path)
119
+ if output_file.is_dir() or not output_file.suffix:
120
+ output_file.mkdir(parents=True, exist_ok=True)
121
+ output_file = output_file / "weights.h5"
122
+ elif output_file.suffix != ".h5":
123
+ parser.error(f"output_path must be a directory or an .h5 file, got '{output_file}'")
124
+
125
+ initialize_h5_file(cols, population_name, str(output_file), args.electrode_csv,
126
+ with_neurite_type=args.with_neurite_type)
127
+ write_h5_file(positions_df, cols, population_name, str(output_file),
128
+ sigma=args.sigma, path_to_fields=args.path_to_fields,
129
+ neurite_types=neurite_types if args.with_neurite_type else None)
130
+ if args.write_positions:
131
+ positions.save_positions(positions_df, output_file.parent)