diff --git a/bmtk/simulator/bionet/modules/ecp.py b/bmtk/simulator/bionet/modules/ecp.py index 3fb8118b..c03a1d04 100644 --- a/bmtk/simulator/bionet/modules/ecp.py +++ b/bmtk/simulator/bionet/modules/ecp.py @@ -30,9 +30,10 @@ from dateutil.tz import tzlocal from uuid import uuid4 + from bmtk.simulator.bionet.modules.sim_module import SimulatorMod from bmtk.utils.sonata.utils import add_hdf5_magic, add_hdf5_version - +from bmtk.simulator.bionet.io_tools import io pc = h.ParallelContext() MPI_RANK = int(pc.id()) @@ -83,8 +84,12 @@ def __init__(self, tmp_dir, file_name, electrode_positions, file_name_nwb=None, self._nwb_path = None if file_name_nwb: - self._nwb_path = file_name_nwb if os.path.isabs(file_name_nwb) else os.path.join(tmp_dir, file_name_nwb) - + try: + import pynwb + self._nwb_path = file_name_nwb if os.path.isabs(file_name_nwb) else os.path.join(tmp_dir, file_name_nwb) + + except ImportError as ie: + io.log_warning('pynwb library is not installed, simulation cannot be saved to {file_name_nwb}.') self._local_gids = [] @@ -246,18 +251,19 @@ def finalize(self, sim): self._delete_tmp_files() pc.barrier() - if self._nwb_path: + if self._nwb_path and MPI_RANK == 0: convert2nwb(self._nwb_path, self._ecp_output, self._positions_file) + pc.barrier() def convert2nwb(nwb_path, orig_hdf5_lfp, electrodes_file): import pynwb + # io.log_debug('Writing to NWB format in {nwb_path}') if os.path.exists(nwb_path): io = pynwb.NWBHDF5IO(nwb_path, 'a') nwbfile = io.read() - print('appending lfp') else: io = pynwb.NWBHDF5IO(nwb_path, "w") nwbfile = pynwb.NWBFile( @@ -265,7 +271,6 @@ def convert2nwb(nwb_path, orig_hdf5_lfp, electrodes_file): session_description="test time", identifier=str(uuid4()), ) - print('new nwb file') electrodes_metdata_df = pd.read_csv(electrodes_file, sep=' ').set_index('channel') @@ -281,14 +286,11 @@ def convert2nwb(nwb_path, orig_hdf5_lfp, electrodes_file): ) # electrodes_file - nwbfile.add_electrode_column(name="channel_id", description="label of electrode") with h5py.File(orig_hdf5_lfp, 'r') as orig_h5: channels = orig_h5['ecp/channel_id'][()] for chan in channels: chan_data = electrodes_metdata_df.loc[chan] - # print(chan_data.get('location', 'None')) - # print(chan_data.get('x_pos', 'Not Avail')) nwbfile.add_electrode( group=electrode_group, channel_id=chan, @@ -327,8 +329,6 @@ def convert2nwb(nwb_path, orig_hdf5_lfp, electrodes_file): io.write(nwbfile) - # print(nwb_path) - class RecXElectrode(object): """Extracellular electrode diff --git a/bmtk/utils/reports/spike_trains/spikes_file_writers.py b/bmtk/utils/reports/spike_trains/spikes_file_writers.py index 76dcf0cb..47e0974d 100644 --- a/bmtk/utils/reports/spike_trains/spikes_file_writers.py +++ b/bmtk/utils/reports/spike_trains/spikes_file_writers.py @@ -159,12 +159,11 @@ def write_nwb(path, spiketrain_reader, mode='a', include_population=True, units= if MPI_rank == 0: # Last checked pynwb doesn't support writing on multiple cores, must let first core do all the # writing to NWB. - if os.path.exists(path) and mode != 'w': io = pynwb.NWBHDF5IO(path, 'a') nwbfile = io.read() else: - io = pynwb.NWBHDF5IO(path, mode) + io = pynwb.NWBHDF5IO(path, 'w') nwbfile = pynwb.NWBFile( session_description='BMTK {} generated NWB spikes file'.format(bmtk.__version__), identifier='Generated in-silico, no session id', # TODO: No idea what to put here? @@ -180,20 +179,22 @@ def write_nwb(path, spiketrain_reader, mode='a', include_population=True, units= nwbfile.add_unit_column(name="node_id", description="id of each node within a population") - for population in spiketrain_reader.populations: - for node_id in spiketrain_reader.node_ids(population=population): - spikes_times = spiketrain_reader.get_times(node_id=node_id, population=population) - if spikes_times is None or len(spikes_times) == 0: - # No spikes for given node, don't try to write to nwb - continue - - # sometimes bmtk/sonata may default to use different 32 or unsigned data-types which will cause - # nwb to throw a fit. Need to explicity convert data-types just in case. - spikes_times = spikes_times.astype('float64') - node_id = int(node_id) + for population in spiketrain_reader.populations: + for node_id in spiketrain_reader.node_ids(population=population): + spikes_times = spiketrain_reader.get_times(node_id=node_id, population=population) + if spikes_times is None or len(spikes_times) == 0: + # No spikes for given node, don't try to write to nwb + continue + + # sometimes bmtk/sonata may default to use different 32 or unsigned data-types which will cause + # nwb to throw a fit. Need to explicity convert data-types just in case. + spikes_times = np.array(spikes_times).astype('float64') + node_id = int(node_id) + if MPI_rank == 0: add_unit(node_id, population, spikes_times) - # with pynwb.NWBHDF5IO(path, mode) as io: + # with pynwb.NWBHDF5IO(path, mode) as io: + if MPI_rank == 0: io.write(nwbfile) comm_barrier() diff --git a/docs/autodocs/source/bionet.rst b/docs/autodocs/source/bionet.rst index 56d414d0..482cefc3 100644 --- a/docs/autodocs/source/bionet.rst +++ b/docs/autodocs/source/bionet.rst @@ -237,6 +237,29 @@ And in the config cell’s ECP. +**Saving ECP to NWB (Neurodata without Borders) format** + +You can save the recorded extraceullar field to NWB format by specifying the "file_name_nwb" option. + +.. code:: json + + { + "ecp": { + "cells": "all", + "variable_name": "v", + "module": "extracellular", + "electrode_positions": "components/xelectrode/linear_probe.csv", + "file_name": "ecp.h5", + "file_name_nwb": "session_data.nwb" + } + } + +In the above case will create an ElectricalSeries acquistion in the session_data.nwb file which can be analyzed +using pynwb, Matlab, pynapple, or other tools that support the reading of the NWB ecephys data. If the same session +nwb is also being used to write spikes or membrane properties, BMTK will attempt to append all data of the same +simulation "session" into a single file. + + Synaptic Variables ++++++++++++++++++ Similar to recording from membrane potential, by setting the ``module`` parameter to ``netcon_report``, you can record the