From 3cd10dafee9075813a342867cc62112a5581e041 Mon Sep 17 00:00:00 2001 From: danilobenozzo Date: Wed, 27 Nov 2024 15:59:52 +0100 Subject: [PATCH 01/14] device for membrane current recording --- .../devices/membrane_current_recorder.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 bsb_neuron/devices/membrane_current_recorder.py diff --git a/bsb_neuron/devices/membrane_current_recorder.py b/bsb_neuron/devices/membrane_current_recorder.py new file mode 100644 index 0000000..069d006 --- /dev/null +++ b/bsb_neuron/devices/membrane_current_recorder.py @@ -0,0 +1,35 @@ +from bsb import LocationTargetting, config + +from ..device import NeuronDevice + + +@config.node +class MembraneCurrentRecorder(NeuronDevice, classmap_entry="membrane_current_recorder"): + locations = config.attr(type=LocationTargetting, default={"strategy": "everywhere"}) + + def implement(self, adapter, simulation, simdata): + for model, pop in self.targetting.get_targets( + adapter, simulation, simdata + ).items(): + for target in pop: + for location in self.locations.get_locations(target): + self._add_imem_recorder( + simdata.result, + location, + name=self.name, + cell_type=target.cell_model.name, + cell_id=target.id, + loc=location._loc, + ) + + def _get_imem(self, location): + from patch import p + + section = location.section + x = location.arc(0) + return p.record(section(x).__record_imem__()) + + def _add_imem_recorder(self, results, location, **annotations): + section = location.section + x = location.arc(0) + results.record(section(x).__record_imem__(), **annotations) From 301e988274dcc377f0832b32dba14351841b4ad4 Mon Sep 17 00:00:00 2001 From: danilobenozzo Date: Thu, 16 Jan 2025 19:16:40 +0100 Subject: [PATCH 02/14] lfp recorder device - LineSourcePotential only --- bsb_neuron/adapter.py | 4 + bsb_neuron/devices/__init__.py | 2 + bsb_neuron/devices/lfp_recorder.py | 73 +++++++++++++++++++ .../devices/membrane_current_recorder.py | 2 +- 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 bsb_neuron/devices/lfp_recorder.py diff --git a/bsb_neuron/adapter.py b/bsb_neuron/adapter.py index dc9c929..5dc3413 100644 --- a/bsb_neuron/adapter.py +++ b/bsb_neuron/adapter.py @@ -36,6 +36,10 @@ def record(self, obj, **annotations): from quantities import ms v = p.record(obj) + # if "M" in annotations.keys(): + # M = annotations["M"] + # print(M.shape) + # v = M @ np.array(v, dtype=float, ndmin=2) def flush(segment): if "units" not in annotations.keys(): diff --git a/bsb_neuron/devices/__init__.py b/bsb_neuron/devices/__init__.py index ce2852e..cf5503b 100644 --- a/bsb_neuron/devices/__init__.py +++ b/bsb_neuron/devices/__init__.py @@ -4,3 +4,5 @@ from .synapse_recorder import SynapseRecorder from .voltage_clamp import VoltageClamp from .voltage_recorder import VoltageRecorder +from .membrane_current_recorder import MembraneCurrentRecorder +from .lfp_recorder import LFPRecorder diff --git a/bsb_neuron/devices/lfp_recorder.py b/bsb_neuron/devices/lfp_recorder.py new file mode 100644 index 0000000..55669be --- /dev/null +++ b/bsb_neuron/devices/lfp_recorder.py @@ -0,0 +1,73 @@ +from bsb import LocationTargetting, config +from lfpykit import CellGeometry, LineSourcePotential +import numpy as np + +from .membrane_current_recorder import MembraneCurrentRecorder + + +@config.node +class LFPRecorder(MembraneCurrentRecorder, classmap_entry="lfp_recorder"): + locations = config.attr(type=LocationTargetting, default={"strategy": "everywhere"}) + x_s = np.ones(10) * 50 + y_s = np.ones(10) * 50 + z_s = np.arange(10) * 20 + sigma = 0.3 + + def implement(self, adapter, simulation, simdata): + for model, pop in self.targetting.get_targets( + adapter, simulation, simdata + ).items(): + + origins = simdata.placement[model].load_positions() + for target in pop: + # collect all locations from the target cell + locations = self.locations.get_locations(target) + n_locs = len(locations) + x_i = np.zeros([n_locs, 2]) + y_i = np.zeros([n_locs, 2]) + z_i = np.zeros([n_locs, 2]) + d_i = np.zeros([n_locs, 2]) + + for i_loc, location in enumerate(locations): + # get for each location xyz coords and diam + section = location.section + idx_loc = section.locations.index( + location._loc + ) # index in section.location + idx_next_loc = ( + idx_loc + 1 if location.arc(0) < 1 else idx_loc + ) # there is another point after + x_i[i_loc] = [section.x3d(idx_loc), section.x3d(idx_next_loc)] + y_i[i_loc] = [section.y3d(idx_loc), section.y3d(idx_next_loc)] + z_i[i_loc] = [section.z3d(idx_loc), section.z3d(idx_next_loc)] + d_i[i_loc] = [section.diam3d(idx_loc), section.diam3d(idx_next_loc)] + + # note: recording by default done section(loc.arc(0)) + # create CellGeometry of targer by using the selected locations + # matrix M (given the probe geometry/properties) + origin = origins[target.id] + print('origin cell ', target.id, ' ', origin) + cell_i = CellGeometry( + x=x_i + origin[0], y=y_i + origin[1], z=z_i + origin[2], d=d_i + ) + lsp = LineSourcePotential( + cell_i, x=self.x_s, y=self.y_s, z=self.z_s, sigma=self.sigma + ) + M_i = lsp.get_transformation_matrix() + pos_nan = np.isnan( + np.sum(M_i, 0) + ) # check for nan, this happens when points with 0 length + + for i_loc, location in enumerate(locations): + if ~pos_nan[i_loc]: + super()._add_imem_recorder( + simdata.result, + location, + name=self.name, + cell_type=target.cell_model.name, + cell_id=target.id, + loc=location._loc, + M=M_i[:, i_loc], # .reshape([M_i.shape[0],1]), + ) + # pass M through the device + # find where to apply it, nb it can be applied at each time point and del the simulate signal (keep only V_ex) diff --git a/bsb_neuron/devices/membrane_current_recorder.py b/bsb_neuron/devices/membrane_current_recorder.py index 069d006..155fc92 100644 --- a/bsb_neuron/devices/membrane_current_recorder.py +++ b/bsb_neuron/devices/membrane_current_recorder.py @@ -32,4 +32,4 @@ def _get_imem(self, location): def _add_imem_recorder(self, results, location, **annotations): section = location.section x = location.arc(0) - results.record(section(x).__record_imem__(), **annotations) + results.record(section(x).__record_imem__(), **annotations, units="nA") From 828b331d06708076c155e8c81be9df021e712b26 Mon Sep 17 00:00:00 2001 From: filimarc Date: Tue, 11 Mar 2025 08:40:21 +0100 Subject: [PATCH 03/14] fix: add RecMEAElectrode type --- bsb_neuron/devices/lfp_recorder.py | 66 ++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/bsb_neuron/devices/lfp_recorder.py b/bsb_neuron/devices/lfp_recorder.py index 55669be..bf2e03c 100644 --- a/bsb_neuron/devices/lfp_recorder.py +++ b/bsb_neuron/devices/lfp_recorder.py @@ -1,19 +1,56 @@ -from bsb import LocationTargetting, config -from lfpykit import CellGeometry, LineSourcePotential +from bsb import LocationTargetting, config, types +from lfpykit import CellGeometry, LineSourcePotential, RecMEAElectrode import numpy as np +import MEAutility as mu from .membrane_current_recorder import MembraneCurrentRecorder +@config.node +class MeaElectrode: + electrode_name = config.attr(type=str, required=True) + definitions = config.dict( + type=types.or_(types.list(type=int), types.list(type=float), float, str), + default=None, + ) + rotations = config.dict(type=types.or_(types.list(type=int), float), default=None) + + def __boot__(self): + if self.electrode_name in mu.return_mea_list(): + self.custom = False + else: + if self.definitions: + self.custom = True + self.definitions["electrode_name"] = self.electrode_name + + else: + raise ValueError( + f"Do not find {self.electrode_name} probe. Available models for MEA arrays: {mu.return_mea_list()}" + ) + + def return_probe(self): + # Check if we are using a custom probe and create MEA object + if self.custom: + pos = mu.core.get_positions(self.definitions) + if mu.core.check_if_rect(self.definitions): + mea_obj = mu.RectMEA(positions=pos, info=self.definitions) + else: + mea_obj = mu.MEA(positions=pos, info=self.definitions) + else: + mea_obj = mu.MEA.return_mea(self.electrode_name) + # If a rotation is selected rotate the array + if self.rotations: + mea_obj.rotate(self.rotations["axis"], self.rotations["angle"]) + return mea_obj + + @config.node class LFPRecorder(MembraneCurrentRecorder, classmap_entry="lfp_recorder"): locations = config.attr(type=LocationTargetting, default={"strategy": "everywhere"}) - x_s = np.ones(10) * 50 - y_s = np.ones(10) * 50 - z_s = np.arange(10) * 20 - sigma = 0.3 + mea_electrode = config.attr(type=MeaElectrode, required=True) def implement(self, adapter, simulation, simdata): + my_probe = self.mea_electrode.return_probe() for model, pop in self.targetting.get_targets( adapter, simulation, simdata ).items(): @@ -46,13 +83,24 @@ def implement(self, adapter, simulation, simdata): # create CellGeometry of targer by using the selected locations # matrix M (given the probe geometry/properties) origin = origins[target.id] - print('origin cell ', target.id, ' ', origin) + print("origin cell ", target.id, " ", origin) cell_i = CellGeometry( x=x_i + origin[0], y=y_i + origin[1], z=z_i + origin[2], d=d_i ) - lsp = LineSourcePotential( - cell_i, x=self.x_s, y=self.y_s, z=self.z_s, sigma=self.sigma + + lsp = RecMEAElectrode( + cell_i, + sigma_T=0.3, + sigma_S=1.5, + sigma_G=0.0, + h=300.0, + z_shift=0.0, + steps=20, + probe=my_probe, ) + # lsp = LineSourcePotential( + # cell_i, x=self.x_s, y=self.y_s, z=self.z_s, sigma=self.sigma + # ) M_i = lsp.get_transformation_matrix() pos_nan = np.isnan( np.sum(M_i, 0) From e4e8f03c0e65c5cf07dd916b715caccff8254e7e Mon Sep 17 00:00:00 2001 From: filimarc Date: Tue, 11 Mar 2025 15:27:30 +0100 Subject: [PATCH 04/14] fix: Update dependencies --- bsb_neuron/devices/__init__.py | 4 ++-- bsb_neuron/devices/lfp_recorder.py | 6 +++--- pyproject.toml | 4 +++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/bsb_neuron/devices/__init__.py b/bsb_neuron/devices/__init__.py index cf5503b..344bcc5 100644 --- a/bsb_neuron/devices/__init__.py +++ b/bsb_neuron/devices/__init__.py @@ -1,8 +1,8 @@ from .current_clamp import CurrentClamp from .ion_recorder import IonRecorder +from .lfp_recorder import LFPRecorder +from .membrane_current_recorder import MembraneCurrentRecorder from .spike_generator import SpikeGenerator from .synapse_recorder import SynapseRecorder from .voltage_clamp import VoltageClamp from .voltage_recorder import VoltageRecorder -from .membrane_current_recorder import MembraneCurrentRecorder -from .lfp_recorder import LFPRecorder diff --git a/bsb_neuron/devices/lfp_recorder.py b/bsb_neuron/devices/lfp_recorder.py index bf2e03c..e7b182d 100644 --- a/bsb_neuron/devices/lfp_recorder.py +++ b/bsb_neuron/devices/lfp_recorder.py @@ -1,7 +1,7 @@ -from bsb import LocationTargetting, config, types -from lfpykit import CellGeometry, LineSourcePotential, RecMEAElectrode -import numpy as np import MEAutility as mu +import numpy as np +from bsb import LocationTargetting, config, types +from lfpykit import CellGeometry, RecMEAElectrode from .membrane_current_recorder import MembraneCurrentRecorder diff --git a/pyproject.toml b/pyproject.toml index 082b773..4d811f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,9 @@ dynamic = ["version", "description"] dependencies = [ "bsb-core~=4.0", "nrn-patch~=4.0", - "arborize[neuron]~=4.1" + "arborize[neuron]~=4.1", + "MEAutility~=1.5", + "LFPykit~=0.5" ] [tool.flit.module] From 6d7c5bd97ed9a0420962b2d3d1a7bb8deb3ba010 Mon Sep 17 00:00:00 2001 From: filimarc Date: Wed, 12 Mar 2025 14:07:21 +0100 Subject: [PATCH 05/14] refactor: change mea module import --- bsb_neuron/devices/lfp_recorder.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bsb_neuron/devices/lfp_recorder.py b/bsb_neuron/devices/lfp_recorder.py index bf2e03c..ffb8b72 100644 --- a/bsb_neuron/devices/lfp_recorder.py +++ b/bsb_neuron/devices/lfp_recorder.py @@ -1,7 +1,8 @@ from bsb import LocationTargetting, config, types from lfpykit import CellGeometry, LineSourcePotential, RecMEAElectrode +from copy import deepcopy import numpy as np -import MEAutility as mu +import MEAutility.core as mu from .membrane_current_recorder import MembraneCurrentRecorder @@ -27,12 +28,13 @@ def __boot__(self): raise ValueError( f"Do not find {self.electrode_name} probe. Available models for MEA arrays: {mu.return_mea_list()}" ) + self.probe = self.return_probe() def return_probe(self): # Check if we are using a custom probe and create MEA object if self.custom: - pos = mu.core.get_positions(self.definitions) - if mu.core.check_if_rect(self.definitions): + pos = mu.get_positions(self.definitions) + if mu.check_if_rect(self.definitions): mea_obj = mu.RectMEA(positions=pos, info=self.definitions) else: mea_obj = mu.MEA(positions=pos, info=self.definitions) @@ -50,7 +52,7 @@ class LFPRecorder(MembraneCurrentRecorder, classmap_entry="lfp_recorder"): mea_electrode = config.attr(type=MeaElectrode, required=True) def implement(self, adapter, simulation, simdata): - my_probe = self.mea_electrode.return_probe() + my_probe = self.mea_electrode.probe for model, pop in self.targetting.get_targets( adapter, simulation, simdata ).items(): From 72ae3fc9220ff36fbd4d3aa14259f020e1105eee Mon Sep 17 00:00:00 2001 From: filimarc Date: Tue, 18 Mar 2025 17:42:54 +0100 Subject: [PATCH 06/14] fix: clean info for MEA object --- bsb_neuron/devices/lfp_recorder.py | 52 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/bsb_neuron/devices/lfp_recorder.py b/bsb_neuron/devices/lfp_recorder.py index 5eb153f..f364a37 100644 --- a/bsb_neuron/devices/lfp_recorder.py +++ b/bsb_neuron/devices/lfp_recorder.py @@ -1,5 +1,3 @@ -from copy import deepcopy - import MEAutility.core as mu import numpy as np from bsb import LocationTargetting, config, types @@ -29,16 +27,20 @@ def __boot__(self): raise ValueError( f"Do not find {self.electrode_name} probe. Available models for MEA arrays: {mu.return_mea_list()}" ) - self.probe = self.return_probe() def return_probe(self): # Check if we are using a custom probe and create MEA object if self.custom: - pos = mu.get_positions(self.definitions) - if mu.check_if_rect(self.definitions): - mea_obj = mu.RectMEA(positions=pos, info=self.definitions) + # Clean definitions, make sure that scaffold objects are not passed to MEA classes + info_dict = {} + for key, value in self.definitions.items(): + if key not in ["scaffold", "_config_parent"]: + info_dict[key] = value + pos = mu.get_positions(info_dict) + if mu.check_if_rect(info_dict): + mea_obj = mu.RectMEA(positions=pos, info=info_dict) else: - mea_obj = mu.MEA(positions=pos, info=self.definitions) + mea_obj = mu.MEA(positions=pos, info=info_dict) else: mea_obj = mu.MEA.return_mea(self.electrode_name) # If a rotation is selected rotate the array @@ -53,7 +55,7 @@ class LFPRecorder(MembraneCurrentRecorder, classmap_entry="lfp_recorder"): mea_electrode = config.attr(type=MeaElectrode, required=True) def implement(self, adapter, simulation, simdata): - my_probe = self.mea_electrode.probe + my_probe = self.mea_electrode.return_probe() for model, pop in self.targetting.get_targets( adapter, simulation, simdata ).items(): @@ -101,24 +103,22 @@ def implement(self, adapter, simulation, simdata): steps=20, probe=my_probe, ) - # lsp = LineSourcePotential( - # cell_i, x=self.x_s, y=self.y_s, z=self.z_s, sigma=self.sigma - # ) - M_i = lsp.get_transformation_matrix() - pos_nan = np.isnan( - np.sum(M_i, 0) - ) # check for nan, this happens when points with 0 length - for i_loc, location in enumerate(locations): - if ~pos_nan[i_loc]: - super()._add_imem_recorder( - simdata.result, - location, - name=self.name, - cell_type=target.cell_model.name, - cell_id=target.id, - loc=location._loc, - M=M_i[:, i_loc], # .reshape([M_i.shape[0],1]), - ) + # M_i = lsp.get_transformation_matrix() + # pos_nan = np.isnan( + # np.sum(M_i, 0) + # ) # check for nan, this happens when points with 0 length + # + # for i_loc, location in enumerate(locations): + # if ~pos_nan[i_loc]: + # super()._add_imem_recorder( + # simdata.result, + # location, + # name=self.name, + # cell_type=target.cell_model.name, + # cell_id=target.id, + # loc=location._loc, + # M=M_i[:, i_loc], # .reshape([M_i.shape[0],1]), + # ) # pass M through the device # find where to apply it, nb it can be applied at each time point and del the simulate signal (keep only V_ex) From 23f2e56dd9a6fc0d6c0b0ee08266eb87a2936158 Mon Sep 17 00:00:00 2001 From: filimarc Date: Mon, 24 Mar 2025 17:51:10 +0100 Subject: [PATCH 07/14] fix: allow definitions to load any type --- bsb_neuron/devices/lfp_recorder.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bsb_neuron/devices/lfp_recorder.py b/bsb_neuron/devices/lfp_recorder.py index f364a37..b4baffa 100644 --- a/bsb_neuron/devices/lfp_recorder.py +++ b/bsb_neuron/devices/lfp_recorder.py @@ -1,3 +1,5 @@ +import typing + import MEAutility.core as mu import numpy as np from bsb import LocationTargetting, config, types @@ -9,10 +11,7 @@ @config.node class MeaElectrode: electrode_name = config.attr(type=str, required=True) - definitions = config.dict( - type=types.or_(types.list(type=int), types.list(type=float), float, str), - default=None, - ) + definitions: dict[typing.Any] = config.dict(type=types.any_()) rotations = config.dict(type=types.or_(types.list(type=int), float), default=None) def __boot__(self): @@ -104,7 +103,7 @@ def implement(self, adapter, simulation, simdata): probe=my_probe, ) - # M_i = lsp.get_transformation_matrix() + M_i = lsp.get_transformation_matrix() # pos_nan = np.isnan( # np.sum(M_i, 0) # ) # check for nan, this happens when points with 0 length From 19cb90900a159404fb07545a5bbdbe9d68b76b93 Mon Sep 17 00:00:00 2001 From: filimarc Date: Tue, 1 Apr 2025 18:38:41 +0200 Subject: [PATCH 08/14] fix: add shift option | use local id for target --- bsb_neuron/devices/lfp_recorder.py | 49 +++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/bsb_neuron/devices/lfp_recorder.py b/bsb_neuron/devices/lfp_recorder.py index b4baffa..272f79e 100644 --- a/bsb_neuron/devices/lfp_recorder.py +++ b/bsb_neuron/devices/lfp_recorder.py @@ -13,6 +13,7 @@ class MeaElectrode: electrode_name = config.attr(type=str, required=True) definitions: dict[typing.Any] = config.dict(type=types.any_()) rotations = config.dict(type=types.or_(types.list(type=int), float), default=None) + shift = config.list(type=int, default=None) def __boot__(self): if self.electrode_name in mu.return_mea_list(): @@ -45,6 +46,8 @@ def return_probe(self): # If a rotation is selected rotate the array if self.rotations: mea_obj.rotate(self.rotations["axis"], self.rotations["angle"]) + if self.shift: + mea_obj.move(self.shift) return mea_obj @@ -60,7 +63,7 @@ def implement(self, adapter, simulation, simdata): ).items(): origins = simdata.placement[model].load_positions() - for target in pop: + for local_cell_id, target in enumerate(pop): # collect all locations from the target cell locations = self.locations.get_locations(target) n_locs = len(locations) @@ -84,40 +87,44 @@ def implement(self, adapter, simulation, simdata): d_i[i_loc] = [section.diam3d(idx_loc), section.diam3d(idx_next_loc)] # note: recording by default done section(loc.arc(0)) - # create CellGeometry of targer by using the selected locations + # create CellGeometry of target by using the selected locations # matrix M (given the probe geometry/properties) - origin = origins[target.id] + # local_cell_id = simdata.placement[model].convert_to_local(target.id)[0] + origin = origins[local_cell_id] print("origin cell ", target.id, " ", origin) + # print( + # f"Rank: {MPI.get_rank()} - {target.id} l {simdata.placement[model].convert_to_local(target.id)} - pop: {len(origins)}" + # ) cell_i = CellGeometry( x=x_i + origin[0], y=y_i + origin[1], z=z_i + origin[2], d=d_i ) - lsp = RecMEAElectrode( cell_i, sigma_T=0.3, sigma_S=1.5, sigma_G=0.0, - h=300.0, - z_shift=0.0, + h=400.0, + z_shift=-100.0, + method="linesource", steps=20, probe=my_probe, ) M_i = lsp.get_transformation_matrix() - # pos_nan = np.isnan( - # np.sum(M_i, 0) - # ) # check for nan, this happens when points with 0 length - # - # for i_loc, location in enumerate(locations): - # if ~pos_nan[i_loc]: - # super()._add_imem_recorder( - # simdata.result, - # location, - # name=self.name, - # cell_type=target.cell_model.name, - # cell_id=target.id, - # loc=location._loc, - # M=M_i[:, i_loc], # .reshape([M_i.shape[0],1]), - # ) + pos_nan = np.isnan( + np.sum(M_i, 0) + ) # check for nan, this happens when points with 0 length + + for i_loc, location in enumerate(locations): + if ~pos_nan[i_loc]: + super()._add_imem_recorder( + simdata.result, + location, + name=self.name, + cell_type=target.cell_model.name, + cell_id=target.id, + loc=location._loc, + M=M_i[:, i_loc], # .reshape([M_i.shape[0], 1]) + ) # pass M through the device # find where to apply it, nb it can be applied at each time point and del the simulate signal (keep only V_ex) From 4409a649c43951444e785e59a14096daa1999f1e Mon Sep 17 00:00:00 2001 From: filimarc Date: Tue, 15 Apr 2025 14:57:19 +0200 Subject: [PATCH 09/14] fix: change the recorder for collecting currents all together All the membrane currents of a pop are collected together then flushed in a final array --- bsb_neuron/adapter.py | 24 ++++++++++++ bsb_neuron/devices/lfp_recorder.py | 59 +++++++++++++++++++++++------- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/bsb_neuron/adapter.py b/bsb_neuron/adapter.py index 285ed79..7543f80 100644 --- a/bsb_neuron/adapter.py +++ b/bsb_neuron/adapter.py @@ -49,6 +49,30 @@ def flush(segment): self.create_recorder(flush) + def record_lfp(self, obj_list, matrices, **annotations): + from patch import p + from quantities import ms + + v_list = [[p.record(obj) for obj in location_list] for location_list in obj_list] + + def flush(segment): + if "units" not in annotations.keys(): + annotations["units"] = "mV" + + V = sum( + [ + np.array(matrices[cell_id]) @ np.array(v_list[cell_id]) + for cell_id in range(len(obj_list)) + ] + ) + # Need to flatten the array to pass AnalogSignal -> V_flat should be something like: [(mea array at time 0), (mea array at time 1)...(final mea array)] + V_flat = V.flatten(order="F") + segment.analogsignals.append( + AnalogSignal(V_flat, sampling_period=p.dt * ms, **annotations) + ) + + self.create_recorder(flush) + @contextlib.contextmanager def fill_parameter_data(parameters, data): diff --git a/bsb_neuron/devices/lfp_recorder.py b/bsb_neuron/devices/lfp_recorder.py index 272f79e..a54071b 100644 --- a/bsb_neuron/devices/lfp_recorder.py +++ b/bsb_neuron/devices/lfp_recorder.py @@ -63,6 +63,8 @@ def implement(self, adapter, simulation, simdata): ).items(): origins = simdata.placement[model].load_positions() + list_of_sections = [[] for x in range(len(pop))] + trs_matrices = [0 for x in range(len(pop))] for local_cell_id, target in enumerate(pop): # collect all locations from the target cell locations = self.locations.get_locations(target) @@ -73,6 +75,7 @@ def implement(self, adapter, simulation, simdata): d_i = np.zeros([n_locs, 2]) for i_loc, location in enumerate(locations): + # get for each location xyz coords and diam section = location.section idx_loc = section.locations.index( @@ -96,7 +99,7 @@ def implement(self, adapter, simulation, simdata): # f"Rank: {MPI.get_rank()} - {target.id} l {simdata.placement[model].convert_to_local(target.id)} - pop: {len(origins)}" # ) cell_i = CellGeometry( - x=x_i + origin[0], y=y_i + origin[1], z=z_i + origin[2], d=d_i + x=y_i + origin[0], y=x_i + origin[1], z=z_i + origin[2], d=d_i ) lsp = RecMEAElectrode( cell_i, @@ -111,20 +114,48 @@ def implement(self, adapter, simulation, simdata): ) M_i = lsp.get_transformation_matrix() - pos_nan = np.isnan( - np.sum(M_i, 0) + + print(f" Size of M: {M_i.shape} ") + pos_nan = np.logical_not( + np.isnan(np.sum(M_i, 0)) ) # check for nan, this happens when points with 0 length + trs_matrices[local_cell_id] = M_i[:, pos_nan] + for i_loc, location in enumerate(locations): - if ~pos_nan[i_loc]: - super()._add_imem_recorder( - simdata.result, - location, - name=self.name, - cell_type=target.cell_model.name, - cell_id=target.id, - loc=location._loc, - M=M_i[:, i_loc], # .reshape([M_i.shape[0], 1]) + if pos_nan[i_loc]: + section = location.section + x = location.arc(0) + list_of_sections[local_cell_id].append( + section(x).__record_imem__() ) - # pass M through the device - # find where to apply it, nb it can be applied at each time point and del the simulate signal (keep only V_ex) + print(f" Size of obj: {len(list_of_sections[local_cell_id])} ") + print( + f" Size of filtered M: {trs_matrices[local_cell_id].shape} - Nan list {np.sum(pos_nan)} - Len locations: {len(locations)}" + ) + + trs_matrix_size = np.sum([np.shape(mat)[1] for mat in trs_matrices]) + obj_list_size = np.sum([len(obj) for obj in list_of_sections]) + if trs_matrix_size != obj_list_size: + raise ValueError( + f" In LFP recorder {self.name} numbers of computed sections do not match! {trs_matrix_size} != {obj_list_size}" + ) + + simdata.result.record_lfp( + list_of_sections, + trs_matrices, + name=self.name, + cell_type=target.cell_model.name, + ) + + # super()._add_imem_recorder( + # simdata.result, + # location, + # name=self.name, + # cell_type=target.cell_model.name, + # cell_id=target.id, + # loc=location._loc, + # M=M_i[:, i_loc], # .reshape([M_i.shape[0], 1]) + # ) + # pass M through the device + # find where to apply it, nb it can be applied at each time point and del the simulate signal (keep only V_ex) From bff46ccf520294dd9da9131a25601e0270e684e5 Mon Sep 17 00:00:00 2001 From: filimarc Date: Wed, 16 Apr 2025 10:47:12 +0200 Subject: [PATCH 10/14] fix: add new annotations --- bsb_neuron/devices/lfp_recorder.py | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/bsb_neuron/devices/lfp_recorder.py b/bsb_neuron/devices/lfp_recorder.py index a54071b..3732419 100644 --- a/bsb_neuron/devices/lfp_recorder.py +++ b/bsb_neuron/devices/lfp_recorder.py @@ -65,6 +65,7 @@ def implement(self, adapter, simulation, simdata): origins = simdata.placement[model].load_positions() list_of_sections = [[] for x in range(len(pop))] trs_matrices = [0 for x in range(len(pop))] + global_ids = [0 for x in range(len(pop))] for local_cell_id, target in enumerate(pop): # collect all locations from the target cell locations = self.locations.get_locations(target) @@ -92,12 +93,9 @@ def implement(self, adapter, simulation, simdata): # note: recording by default done section(loc.arc(0)) # create CellGeometry of target by using the selected locations # matrix M (given the probe geometry/properties) - # local_cell_id = simdata.placement[model].convert_to_local(target.id)[0] + origin = origins[local_cell_id] - print("origin cell ", target.id, " ", origin) - # print( - # f"Rank: {MPI.get_rank()} - {target.id} l {simdata.placement[model].convert_to_local(target.id)} - pop: {len(origins)}" - # ) + cell_i = CellGeometry( x=y_i + origin[0], y=x_i + origin[1], z=z_i + origin[2], d=d_i ) @@ -115,12 +113,13 @@ def implement(self, adapter, simulation, simdata): M_i = lsp.get_transformation_matrix() - print(f" Size of M: {M_i.shape} ") pos_nan = np.logical_not( np.isnan(np.sum(M_i, 0)) ) # check for nan, this happens when points with 0 length + # Store the transform matrix and the cell id in lists trs_matrices[local_cell_id] = M_i[:, pos_nan] + global_ids[local_cell_id] = target.id for i_loc, location in enumerate(locations): if pos_nan[i_loc]: @@ -129,10 +128,6 @@ def implement(self, adapter, simulation, simdata): list_of_sections[local_cell_id].append( section(x).__record_imem__() ) - print(f" Size of obj: {len(list_of_sections[local_cell_id])} ") - print( - f" Size of filtered M: {trs_matrices[local_cell_id].shape} - Nan list {np.sum(pos_nan)} - Len locations: {len(locations)}" - ) trs_matrix_size = np.sum([np.shape(mat)[1] for mat in trs_matrices]) obj_list_size = np.sum([len(obj) for obj in list_of_sections]) @@ -145,17 +140,6 @@ def implement(self, adapter, simulation, simdata): list_of_sections, trs_matrices, name=self.name, - cell_type=target.cell_model.name, + cell_type=model.name, + id_list=global_ids, ) - - # super()._add_imem_recorder( - # simdata.result, - # location, - # name=self.name, - # cell_type=target.cell_model.name, - # cell_id=target.id, - # loc=location._loc, - # M=M_i[:, i_loc], # .reshape([M_i.shape[0], 1]) - # ) - # pass M through the device - # find where to apply it, nb it can be applied at each time point and del the simulate signal (keep only V_ex) From f1a235099e0542c2f197619efe5255f00cc07af8 Mon Sep 17 00:00:00 2001 From: filimarc Date: Wed, 16 Apr 2025 13:47:03 +0200 Subject: [PATCH 11/14] feat: add Checkpoints while running simulations --- bsb_neuron/adapter.py | 7 +++++++ bsb_neuron/devices/lfp_recorder.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/bsb_neuron/adapter.py b/bsb_neuron/adapter.py index 7543f80..ebdfd59 100644 --- a/bsb_neuron/adapter.py +++ b/bsb_neuron/adapter.py @@ -6,6 +6,7 @@ from bsb import ( AdapterError, AdapterProgress, + AdapterCheckpoint, Chunk, DatasetNotFoundError, SimulationData, @@ -148,11 +149,17 @@ def run(self, *simulations: "Simulation"): self.engine.finitialize(self.initial) duration = max(sim.duration for sim in simulations) progress = AdapterProgress(duration) + checkpoint = AdapterCheckpoint(simulations) for oi, i in progress.steps(step=1): pc.psolve(i) tick = progress.tick(i) for listener in self._progress_listeners: listener(simulations, tick) + if checkpoint.get_status(i): + [ + self.simdata[sim].result.flush() + for sim in checkpoint.checkpoints[i] + ] progress.complete() report("Finished simulation.", level=2) finally: diff --git a/bsb_neuron/devices/lfp_recorder.py b/bsb_neuron/devices/lfp_recorder.py index 3732419..1cd83dc 100644 --- a/bsb_neuron/devices/lfp_recorder.py +++ b/bsb_neuron/devices/lfp_recorder.py @@ -55,6 +55,9 @@ def return_probe(self): class LFPRecorder(MembraneCurrentRecorder, classmap_entry="lfp_recorder"): locations = config.attr(type=LocationTargetting, default={"strategy": "everywhere"}) mea_electrode = config.attr(type=MeaElectrode, required=True) + checkpoints = config.attr( + type=types.or_(float, types.ndarray(dtype=float)), default=[] + ) def implement(self, adapter, simulation, simdata): my_probe = self.mea_electrode.return_probe() From 73b2d95c3a74effde5ceda53094773e1de8045e9 Mon Sep 17 00:00:00 2001 From: filimarc Date: Wed, 16 Apr 2025 16:43:54 +0200 Subject: [PATCH 12/14] fix: add a method to choose the suitable step to use --- bsb_neuron/adapter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bsb_neuron/adapter.py b/bsb_neuron/adapter.py index ebdfd59..d64398b 100644 --- a/bsb_neuron/adapter.py +++ b/bsb_neuron/adapter.py @@ -149,8 +149,10 @@ def run(self, *simulations: "Simulation"): self.engine.finitialize(self.initial) duration = max(sim.duration for sim in simulations) progress = AdapterProgress(duration) + progress_step = 1 checkpoint = AdapterCheckpoint(simulations) - for oi, i in progress.steps(step=1): + minimum_step = checkpoint.suitable_step(progress_step) + for oi, i in progress.steps(step=minimum_step): pc.psolve(i) tick = progress.tick(i) for listener in self._progress_listeners: From 94bd68f1dec5448298b1f8bd95d94765b9498cd5 Mon Sep 17 00:00:00 2001 From: filimarc Date: Thu, 17 Apr 2025 11:33:49 +0200 Subject: [PATCH 13/14] fix: Free the memory when flush is called --- bsb_neuron/adapter.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bsb_neuron/adapter.py b/bsb_neuron/adapter.py index d64398b..cdfbced 100644 --- a/bsb_neuron/adapter.py +++ b/bsb_neuron/adapter.py @@ -36,10 +36,6 @@ def record(self, obj, **annotations): from quantities import ms v = p.record(obj) - # if "M" in annotations.keys(): - # M = annotations["M"] - # print(M.shape) - # v = M @ np.array(v, dtype=float, ndmin=2) def flush(segment): if "units" not in annotations.keys(): @@ -47,6 +43,9 @@ def flush(segment): segment.analogsignals.append( AnalogSignal(list(v), sampling_period=p.dt * ms, **annotations) ) + # Free the memory + print(f"Size V vec: {v.size()}") + v.remove(0, v.size() - 1) self.create_recorder(flush) @@ -71,6 +70,10 @@ def flush(segment): segment.analogsignals.append( AnalogSignal(V_flat, sampling_period=p.dt * ms, **annotations) ) + # Free the memory of the Vectors + for location_list in v_list: + for obj in location_list: + obj.remove(0, obj.size() - 1) self.create_recorder(flush) From 980e31967da04fdc170b041392edcbdfa82adfd2 Mon Sep 17 00:00:00 2001 From: filimarc Date: Thu, 17 Apr 2025 11:53:43 +0200 Subject: [PATCH 14/14] fix: use list for checkpoints type handler --- bsb_neuron/devices/lfp_recorder.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bsb_neuron/devices/lfp_recorder.py b/bsb_neuron/devices/lfp_recorder.py index 1cd83dc..9212503 100644 --- a/bsb_neuron/devices/lfp_recorder.py +++ b/bsb_neuron/devices/lfp_recorder.py @@ -55,9 +55,7 @@ def return_probe(self): class LFPRecorder(MembraneCurrentRecorder, classmap_entry="lfp_recorder"): locations = config.attr(type=LocationTargetting, default={"strategy": "everywhere"}) mea_electrode = config.attr(type=MeaElectrode, required=True) - checkpoints = config.attr( - type=types.or_(float, types.ndarray(dtype=float)), default=[] - ) + checkpoints = config.attr(type=types.or_(float, types.list(type=float)), default=[]) def implement(self, adapter, simulation, simdata): my_probe = self.mea_electrode.return_probe()