From 88377a2322c4cdc64069b6bd6baa702a3f56ce03 Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 15:59:07 -0800 Subject: [PATCH 01/15] updated readme --- README.md | 135 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 74 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 4304f3a..b062097 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,23 @@ # schneider-lab-to-nwb NWB conversion scripts for Schneider lab data to the [Neurodata Without Borders](https://nwb-overview.readthedocs.io/) data format. - -## Installation -## Basic installation - -You can install the latest release of the package with pip: - -``` -pip install schneider-lab-to-nwb -``` - -We recommend that you install the package inside a [virtual environment](https://docs.python.org/3/tutorial/venv.html). A simple way of doing this is to use a [conda environment](https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/environments.html) from the `conda` package manager ([installation instructions](https://docs.conda.io/en/latest/miniconda.html)). Detailed instructions on how to use conda environments can be found in their [documentation](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html). - -### Running a specific conversion -Once you have installed the package with pip, you can run any of the conversion scripts in a notebook or a python file: - -https://github.com/catalystneuro/schneider-lab-to-nwb//tree/main/src/schneider_2024/schneider_2024_convert_session.py - - - - ## Installation from Github -Another option is to install the package directly from Github. This option has the advantage that the source code can be modifed if you need to amend some of the code we originally provided to adapt to future experimental differences. To install the conversion from GitHub you will need to use `git` ([installation instructions](https://github.com/git-guides/install-git)). We also recommend the installation of `conda` ([installation instructions](https://docs.conda.io/en/latest/miniconda.html)) as it contains all the required machinery in a single and simple instal +We recommend installing the package directly from Github. This option has the advantage that the source code can be modifed if you need to amend some of the code we originally provided to adapt to future experimental differences. To install the conversion from GitHub you will need to use `git` ([installation instructions](https://github.com/git-guides/install-git)). We also recommend the installation of `conda` ([installation instructions](https://docs.conda.io/en/latest/miniconda.html)) as it contains all the required machinery in a single and simple instal From a terminal (note that conda should install one in your system) you can do the following: -``` +```bash git clone https://github.com/catalystneuro/schneider-lab-to-nwb cd schneider-lab-to-nwb conda env create --file make_env.yml -conda activate schneider-lab-to-nwb-env +conda activate schneider_lab_to_nwb_env ``` This creates a [conda environment](https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/environments.html) which isolates the conversion code from your system libraries. We recommend that you run all your conversion related tasks and analysis from the created environment in order to minimize issues related to package dependencies. Alternatively, if you want to avoid conda altogether (for example if you use another virtual environment tool) you can install the repository with the following commands using only pip: -``` +```bash git clone https://github.com/catalystneuro/schneider-lab-to-nwb cd schneider-lab-to-nwb pip install -e . @@ -45,51 +25,84 @@ pip install -e . Note: both of the methods above install the repository in [editable mode](https://pip.pypa.io/en/stable/cli/pip_install/#editable-installs). +The dependencies for this environment are stored in the dependencies section of the `pyproject.toml` file. -### Running a specific conversion -To run a specific conversion, you might need to install first some conversion specific dependencies that are located in each conversion directory: -``` -pip install -r src/schneider_lab_to_nwb/schneider_2024/schneider_2024_requirements.txt -``` +## Helpful Definitions -You can run a specific conversion with the following command: -``` -python src/schneider_lab_to_nwb/schneider_2024/schneider_2024_convert_session.py -``` +This conversion project is comprised primarily by DataInterfaces, NWBConverters, and conversion scripts. + +In neuroconv, a [DataInterface](https://neuroconv.readthedocs.io/en/main/user_guide/datainterfaces.html) is a class that specifies the procedure to convert a single data modality to NWB. +This is usually accomplished with a single read operation from a distinct set of files. +For example, in this conversion, the `Zempolich2024BehaviorInterface` contains the code that converts all of the behavioral data to NWB from a raw .mat file. + +In neuroconv, a [NWBConverter](https://neuroconv.readthedocs.io/en/main/user_guide/nwbconverter.html) is a class that combines many data interfaces and specifies the relationships between them, such as temporal alignment. +This allows users to combine multiple modalites into a single NWB file in an efficient and modular way. + +In this conversion project, the conversion scripts determine which sessions to convert, +instantiate the appropriate NWBConverter object, +and convert all of the specified sessions, saving them to an output directory of .nwb files. ## Repository structure Each conversion is organized in a directory of its own in the `src` directory: schneider-lab-to-nwb/ ├── LICENSE + ├── MANIFEST.in + ├── README.md ├── make_env.yml ├── pyproject.toml - ├── README.md - ├── requirements.txt - ├── setup.py └── src - ├── schneider_lab_to_nwb - │ ├── conversion_directory_1 - │ └── schneider_2024 - │ ├── schneider_2024_behaviorinterface.py - │ ├── schneider_2024_convert_session.py - │ ├── schneider_2024_metadata.yml - │ ├── schneider_2024_nwbconverter.py - │ ├── schneider_2024_requirements.txt - │ ├── schneider_2024_notes.md - - │ └── __init__.py - │ ├── conversion_directory_b - - └── __init__.py - - For example, for the conversion `schneider_2024` you can find a directory located in `src/schneider-lab-to-nwb/schneider_2024`. Inside each conversion directory you can find the following files: - -* `schneider_2024_convert_sesion.py`: this script defines the function to convert one full session of the conversion. -* `schneider_2024_requirements.txt`: dependencies specific to this conversion. -* `schneider_2024_metadata.yml`: metadata in yaml format for this specific conversion. -* `schneider_2024_behaviorinterface.py`: the behavior interface. Usually ad-hoc for each conversion. -* `schneider_2024_nwbconverter.py`: the place where the `NWBConverter` class is defined. -* `schneider_2024_notes.md`: notes and comments concerning this specific conversion. - -The directory might contain other files that are necessary for the conversion but those are the central ones. + └── schneider_lab_to_nwb + ├── __init__.py + ├── another_conversion + └── zempolich_2024 + ├── __init__.py + ├── zempolich_2024_behaviorinterface.py + ├── zempolich_2024_convert_all_sessions.py + ├── zempolich_2024_convert_session.py + ├── zempolich_2024_intrinsic_signal_imaging_interface.py + ├── zempolich_2024_metadata.yaml + ├── zempolich_2024_notes.md + ├── zempolich_2024_nwbconverter.py + ├── zempolich_2024_open_ephys_recording_interface.py + └── zempolich_2024_optogeneticinterface.py + +For the conversion `zempolich_2024` you can find a directory located in `src/schneider-lab-to-nwb/zempolich_2024`. Inside that conversion directory you can find the following files: + +* `__init__.py` : This init file imports all the datainterfaces and NWBConverters so that they can be accessed directly from schneider_lab_to_nwb.zempolich_2024. +* `zempolich_2024_convert_session.py` : This conversion script defines the `session_to_nwb()` function, which converts a single session of data to NWB. + When run as a script, this file converts 4 example sessions to NWB, representing all the various edge cases in the dataset. +* `zempolich_2024_convert_dataset.py` : This conversion script defines the `dataset_to_nwb()` function, which converts the entire Zempolich 2024 dataset to NWB. + When run as a script, this file calls `dataset_to_nwb()` with the appropriate arguments. +* `zempolich_2024_nwbconverter.py` : This module defines the primary conversion class, `Zempolich2024NWBConverter`, which aggregates all of the various datainterfaces relevant for this conversion. +* `zempolich_2024_behaviorinterface.py` : This module defines `Zempolich2024BehaviorInterface`, which is the data interface for behavioral .mat files. +* `zempolich_2024_optogeneticinterface.py` : This module defines `Zempolich2024OptogeneticInterface`, which is the data interface for optogenetic stimulation from .mat files. +* `zempolich_2024_intrinsic_signal_imaging_interface.py` : This module defines `Zempolich2024IntrinsicSignalOpticalImagingInterface`, which is the data interface for intrinsic signal images (.tiff and .jpg). +* `zempolich_2024_open_ephys_recording_interface.py` : This module defines `Zempolich2024OpenEphysRecordingInterface`, which is a lightweight wrapper around neuroconv's `OpenEphysLegacyRecordingInterface` that is responsible for converting the OpenEphys recording data. + This interface adds some extra conversion-specific metadata like relative channel positions, brain area, etc. +* `zempolich_2024metadata.yaml` : This metadata .yaml file provides high-level metadata for the nwb files directly as well as useful dictionaries for some of the data interfaces. + For example, + - Subject/species is "Mus musculus", which is directly included in the NWB file. + - Ecephys/folder_name_to_start_datetime gives a mapping from 2-part folder names (ex. m53/Day1_A1) to session start times, + which is used in cases where the session start time recorded by OpenEphys is ambiguous. + +* `zempolich_2024_notes.md` : This markdown file contains my notes from the conversion for each of the data interfaces. + It specifically highlights various edge cases as well as questions I had for the Schneider Lab (active and resolved). + +Future conversions for this repo should follow the example of zempolich_2024 and create another folder of +conversion scripts and datainterfaces. As a placeholder, here we have `src/schneider-lab-to-nwb/another_conversion`. + +## Running a Conversion + +To convert the 4 example sessions, simply run +```bash +python src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py +``` + +To convert the whole dataset, simply run +```bash +python src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_dataset.py +``` + +Note that the dataset conversion uses multiprocessing, currently set to 4 workers. To use more or fewer workers, simply +change the `max_workers` argument to `dataset_to_nwb()`. From c4e39087b3dea594cddca11161bac3db8d6170e0 Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 16:05:37 -0800 Subject: [PATCH 02/15] added docstring to session_to_nwb --- .../zempolich_2024_convert_session.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py index 32df239..acecf9f 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py @@ -20,6 +20,29 @@ def session_to_nwb( stub_test: bool = False, verbose: bool = True, ): + """Convert a session of data to NWB format. + + Parameters + ---------- + behavior_file_path : str | Path + Path to the behavior .mat file. + video_folder_path : str | Path + Path to the folder containing the video files. + intrinsic_signal_optical_imaging_folder_path : str | Path + Path to the folder containing the intrinsic signal optical imaging files. + output_dir_path : str | Path + Path to the directory where the output NWB file will be saved. + ephys_folder_path : Optional[str | Path], optional + Path to the folder containing electrophysiology data, by default None. + has_opto : bool, optional + Whether the session includes optogenetic data, by default False. + brain_region : Literal["A1", "M2"], optional + Brain region of interest, by default "A1". + stub_test : bool, optional + Whether to run in stub test mode, by default False. + verbose : bool, optional + Whether to print verbose output, by default True. + """ behavior_file_path = Path(behavior_file_path) video_folder_path = Path(video_folder_path) intrinsic_signal_optical_imaging_folder_path = Path(intrinsic_signal_optical_imaging_folder_path) From 5116c3f5f68409c30bf899869b6828fa8041bd42 Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 16:06:45 -0800 Subject: [PATCH 03/15] added docstring to NWBConverter --- .../zempolich_2024/zempolich_2024_nwbconverter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_nwbconverter.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_nwbconverter.py index 7a67805..32a9a62 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_nwbconverter.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_nwbconverter.py @@ -15,7 +15,7 @@ class Zempolich2024NWBConverter(NWBConverter): - """Primary conversion class for my extracellular electrophysiology dataset.""" + """Primary conversion class.""" data_interface_classes = dict( Recording=Zempolich2024OpenEphysRecordingInterface, From 3a7ef1dab354922f6da8e277e8c2c9e3037f2774 Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 16:11:57 -0800 Subject: [PATCH 04/15] added docstrings to convert_all_sessions fns --- .../zempolich_2024_convert_all_sessions.py | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_all_sessions.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_all_sessions.py index 467ca54..38690a1 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_all_sessions.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_all_sessions.py @@ -51,7 +51,19 @@ def dataset_to_nwb( pass -def get_nwbfile_name_from_kwargs(session_to_nwb_kwargs): +def get_nwbfile_name_from_kwargs(session_to_nwb_kwargs: dict) -> str: + """Get the name of the NWB file from the session_to_nwb kwargs. + + Parameters + ---------- + session_to_nwb_kwargs : dict + The arguments for session_to_nwb. + + Returns + ------- + str + The name of the NWB file that would be created by running session_to_nwb(**session_to_nwb_kwargs). + """ behavior_file_path = session_to_nwb_kwargs["behavior_file_path"] subject_id = behavior_file_path.name.split("_")[1] session_id = behavior_file_path.name.split("_")[2] @@ -78,10 +90,7 @@ def safe_session_to_nwb(*, session_to_nwb_kwargs: dict, exception_file_path: str f.write(traceback.format_exc()) -def get_session_to_nwb_kwargs_per_session( - *, - data_dir_path: str | Path, -): +def get_session_to_nwb_kwargs_per_session(*, data_dir_path: str | Path): """Get the kwargs for session_to_nwb for each session in the dataset. Parameters @@ -119,6 +128,24 @@ def get_session_to_nwb_kwargs_per_session( def get_brain_region_kwargs(ephys_path, ephys_behavior_path, opto_path, brain_region): + """Get the session_to_nwb kwargs for each session in the dataset for a given brain region. + + Parameters + ---------- + ephys_path : pathlib.Path + Path to the directory containing electrophysiology data for subjects. + ephys_behavior_path : pathlib.Path + Path to the directory containing electrophysiology behavior data files. + opto_path : pathlib.Path + Path to the directory containing optogenetics behavior data files. + brain_region : str + The brain region associated with the sessions. + + Returns + ------- + list[dict[str, Any]] + A list of dictionaries containing the kwargs for session_to_nwb for each session in the dataset within a specific brain region. + """ session_to_nwb_kwargs_per_session = [] for subject_dir in ephys_path.iterdir(): subject_id = subject_dir.name From 12fa334cb91b03c4e7bdf63794f31cac134f1a28 Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 16:14:49 -0800 Subject: [PATCH 05/15] added docstrings to behaviorinterface methods --- .../zempolich_2024_behaviorinterface.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_behaviorinterface.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_behaviorinterface.py index 2a14fa0..808119f 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_behaviorinterface.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_behaviorinterface.py @@ -19,13 +19,14 @@ class Zempolich2024BehaviorInterface(BaseDataInterface): keywords = ("behavior",) def __init__(self, file_path: FilePath): - super().__init__(file_path=file_path) - - def get_metadata(self) -> DeepDict: - # Automatically retrieve as much metadata as possible from the source files available - metadata = super().get_metadata() + """Initialize the behavior interface. - return metadata + Parameters + ---------- + file_path : FilePath + Path to the behavior .mat file. + """ + super().__init__(file_path=file_path) def get_metadata_schema(self) -> dict: metadata_schema = super().get_metadata_schema() @@ -90,6 +91,17 @@ def get_metadata_schema(self) -> dict: return metadata_schema def add_to_nwbfile(self, nwbfile: NWBFile, metadata: dict, normalize_timestamps: bool = False): + """Add behavior data to the NWBFile. + + Parameters + ---------- + nwbfile : NWBFile + The NWBFile to which to add the behavior data. + metadata : dict + The metadata for the behavior data. + normalize_timestamps : bool, optional + Whether to normalize the timestamps to the start of the first behavioral time series, by default False + """ # Read Data file_path = self.source_data["file_path"] file = read_mat(file_path) From 259b1f55600ad5200c86bc95a149302290e0dec6 Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 17:04:45 -0800 Subject: [PATCH 06/15] added docstrings to isoiinterface methods --- ...lich_2024_intrinsic_signal_imaging_interface.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_intrinsic_signal_imaging_interface.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_intrinsic_signal_imaging_interface.py index 7017f9c..8fe671c 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_intrinsic_signal_imaging_interface.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_intrinsic_signal_imaging_interface.py @@ -8,7 +8,6 @@ from PIL import Image from neuroconv.basedatainterface import BaseDataInterface -from neuroconv.utils import DeepDict from neuroconv.tools import nwb_helpers @@ -18,13 +17,14 @@ class Zempolich2024IntrinsicSignalOpticalImagingInterface(BaseDataInterface): keywords = ("intrinsic signal optical imaging",) def __init__(self, folder_path: DirectoryPath): - super().__init__(folder_path=folder_path) - - def get_metadata(self) -> DeepDict: - # Automatically retrieve as much metadata as possible from the source files available - metadata = super().get_metadata() + """Initialize the intrinsic signal optical imaging interface. - return metadata + Parameters + ---------- + folder_path : DirectoryPath + Path to the folder containing the intrinsic signal optical imaging files. + """ + super().__init__(folder_path=folder_path) def get_metadata_schema(self) -> dict: metadata_schema = super().get_metadata_schema() From 34b92a91cfaad81e782327693cb150b56e2ec03a Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 17:06:33 -0800 Subject: [PATCH 07/15] added docstrings to optogeneticinterface methods --- .../zempolich_2024_optogeneticinterface.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_optogeneticinterface.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_optogeneticinterface.py index 812f24d..6b94236 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_optogeneticinterface.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_optogeneticinterface.py @@ -8,7 +8,6 @@ from pynwb.ogen import OptogeneticSeries, OptogeneticStimulusSite from neuroconv.basedatainterface import BaseDataInterface -from neuroconv.utils import DeepDict class Zempolich2024OptogeneticInterface(BaseDataInterface): @@ -17,18 +16,27 @@ class Zempolich2024OptogeneticInterface(BaseDataInterface): keywords = ["optogenetics"] def __init__(self, file_path: FilePath): - super().__init__(file_path=file_path) - - def get_metadata(self) -> DeepDict: - metadata = super().get_metadata() - - return metadata + """Initialize the OptogeneticInterface. - def get_metadata_schema(self) -> dict: - metadata_schema = super().get_metadata_schema() - return metadata_schema + Parameters + ---------- + file_path : FilePath + Path to the .mat file containing the optogenetic stimulation data. + """ + super().__init__(file_path=file_path) def add_to_nwbfile(self, nwbfile: NWBFile, metadata: dict, brain_region: Literal["A1", "M2"] = "A1"): + """Add optogenetic stimulation data to the NWBFile. + + Parameters + ---------- + nwbfile : NWBFile + NWBFile to which the data will be added. + metadata : dict + Metadata dictionary. + brain_region : Literal["A1", "M2"], optional + Brain region for which the optogenetic stimulation data will be added, by default "A1". + """ # Read Data file_path = self.source_data["file_path"] file = read_mat(file_path) From 34f3be2946b8ce1f362695d723061b82a6550aaa Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 17:07:42 -0800 Subject: [PATCH 08/15] added docstrings to openephysinterface methods --- ...zempolich_2024_open_ephys_recording_interface.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_open_ephys_recording_interface.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_open_ephys_recording_interface.py index b36045f..f91eb04 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_open_ephys_recording_interface.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_open_ephys_recording_interface.py @@ -20,6 +20,19 @@ def get_metadata(self) -> dict: def add_to_nwbfile( self, nwbfile: NWBFile, metadata: dict, brain_region: Literal["A1", "M2"] = "A1", **conversion_options ): + """Add the recording to an NWBFile. + + Parameters + ---------- + nwbfile : NWBFile + The NWBFile to which the recording will be added. + metadata : dict + The metadata for the conversion. + brain_region : Literal["A1", "M2"], optional + The brain region from which the recording was taken, by default "A1". + conversion_options : dict + Additional options for the conversion. + """ folder_path = self.source_data["folder_path"] channel_positions = np.load(folder_path / "channel_positions.npy") if True: # TODO: Replace with `if stub_test:` once all channels are present in the data From ed8127e404f6a6702e69c4f3e023d4f175136639 Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 17:11:09 -0800 Subject: [PATCH 09/15] removed unused imports --- .../zempolich_2024/zempolich_2024_behaviorinterface.py | 3 +-- .../zempolich_2024/zempolich_2024_nwbconverter.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_behaviorinterface.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_behaviorinterface.py index 808119f..8af9cff 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_behaviorinterface.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_behaviorinterface.py @@ -3,13 +3,12 @@ from pydantic import FilePath import numpy as np from pymatreader import read_mat -from hdmf.common.table import DynamicTableRegion from pynwb.behavior import BehavioralTimeSeries, TimeSeries from pynwb.device import Device from ndx_events import Events, AnnotatedEventsTable from neuroconv.basedatainterface import BaseDataInterface -from neuroconv.utils import DeepDict, get_base_schema +from neuroconv.utils import get_base_schema from neuroconv.tools import nwb_helpers diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_nwbconverter.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_nwbconverter.py index 32a9a62..cc67c8f 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_nwbconverter.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_nwbconverter.py @@ -4,7 +4,6 @@ PhySortingInterface, VideoInterface, ) -from neuroconv.basedatainterface import BaseDataInterface from schneider_lab_to_nwb.zempolich_2024 import ( Zempolich2024OpenEphysRecordingInterface, From 24104d7cde7fdd4c6c3f33f59bb7229558e00905 Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 17:15:26 -0800 Subject: [PATCH 10/15] replaced str | Path with pydantic FilePath and DirectoryPath objects --- .../zempolich_2024_convert_all_sessions.py | 1 + .../zempolich_2024_convert_session.py | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_all_sessions.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_all_sessions.py index 38690a1..ab334f9 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_all_sessions.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_all_sessions.py @@ -5,6 +5,7 @@ import traceback from tqdm import tqdm import shutil +from pydantic import FilePath, DirectoryPath from schneider_lab_to_nwb.zempolich_2024.zempolich_2024_convert_session import session_to_nwb diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py index acecf9f..2fffd2a 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py @@ -4,17 +4,18 @@ import shutil from datetime import datetime from typing import Optional, Literal +from pydantic import FilePath, DirectoryPath from neuroconv.utils import load_dict_from_file, dict_deep_update from schneider_lab_to_nwb.zempolich_2024 import Zempolich2024NWBConverter def session_to_nwb( - behavior_file_path: str | Path, - video_folder_path: str | Path, - intrinsic_signal_optical_imaging_folder_path: str | Path, - output_dir_path: str | Path, - ephys_folder_path: Optional[str | Path] = None, + behavior_file_path: FilePath, + video_folder_path: DirectoryPath, + intrinsic_signal_optical_imaging_folder_path: DirectoryPath, + output_dir_path: DirectoryPath, + ephys_folder_path: Optional[DirectoryPath] = None, has_opto: bool = False, brain_region: Literal["A1", "M2"] = "A1", stub_test: bool = False, @@ -24,15 +25,15 @@ def session_to_nwb( Parameters ---------- - behavior_file_path : str | Path + behavior_file_path : FilePath Path to the behavior .mat file. - video_folder_path : str | Path + video_folder_path : DirectoryPath Path to the folder containing the video files. - intrinsic_signal_optical_imaging_folder_path : str | Path + intrinsic_signal_optical_imaging_folder_path : DirectoryPath Path to the folder containing the intrinsic signal optical imaging files. - output_dir_path : str | Path + output_dir_path : DirectoryPath Path to the directory where the output NWB file will be saved. - ephys_folder_path : Optional[str | Path], optional + ephys_folder_path : Optional[DirectoryPath], optional Path to the folder containing electrophysiology data, by default None. has_opto : bool, optional Whether the session includes optogenetic data, by default False. @@ -125,8 +126,8 @@ def session_to_nwb( def add_session_start_time_to_metadata( - behavior_file_path: str | Path, - ephys_folder_path: Optional[str | Path], + behavior_file_path: FilePath, + ephys_folder_path: Optional[DirectoryPath], metadata: dict, ): if ephys_folder_path is not None: From 1b5e0ee4131047ac5cb0c22cbfd37e41f608919b Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 17:15:43 -0800 Subject: [PATCH 11/15] replaced str | Path with pydantic FilePath and DirectoryPath objects --- .../zempolich_2024_convert_all_sessions.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_all_sessions.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_all_sessions.py index ab334f9..53e5594 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_all_sessions.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_all_sessions.py @@ -12,8 +12,8 @@ def dataset_to_nwb( *, - data_dir_path: str | Path, - output_dir_path: str | Path, + data_dir_path: DirectoryPath, + output_dir_path: DirectoryPath, max_workers: int = 1, verbose: bool = True, ): @@ -21,9 +21,9 @@ def dataset_to_nwb( Parameters ---------- - data_dir_path : str | Path + data_dir_path : DirectoryPath The path to the directory containing the raw data. - output_dir_path : str | Path + output_dir_path : DirectoryPath The path to the directory where the NWB files will be saved. max_workers : int, optional The number of workers to use for parallel processing, by default 1 @@ -72,14 +72,14 @@ def get_nwbfile_name_from_kwargs(session_to_nwb_kwargs: dict) -> str: return nwbfile_name -def safe_session_to_nwb(*, session_to_nwb_kwargs: dict, exception_file_path: str | Path): +def safe_session_to_nwb(*, session_to_nwb_kwargs: dict, exception_file_path: FilePath): """Convert a session to NWB while handling any errors by recording error messages to the exception_file_path. Parameters ---------- session_to_nwb_kwargs : dict The arguments for session_to_nwb. - exception_file_path : Path + exception_file_path : FilePath The path to the file where the exception messages will be saved. """ exception_file_path = Path(exception_file_path) @@ -91,12 +91,12 @@ def safe_session_to_nwb(*, session_to_nwb_kwargs: dict, exception_file_path: str f.write(traceback.format_exc()) -def get_session_to_nwb_kwargs_per_session(*, data_dir_path: str | Path): +def get_session_to_nwb_kwargs_per_session(*, data_dir_path: DirectoryPath): """Get the kwargs for session_to_nwb for each session in the dataset. Parameters ---------- - data_dir_path : str | Path + data_dir_path : DirectoryPath The path to the directory containing the raw data. Returns @@ -128,16 +128,18 @@ def get_session_to_nwb_kwargs_per_session(*, data_dir_path: str | Path): return session_to_nwb_kwargs_per_session -def get_brain_region_kwargs(ephys_path, ephys_behavior_path, opto_path, brain_region): +def get_brain_region_kwargs( + ephys_path: DirectoryPath, ephys_behavior_path: DirectoryPath, opto_path: DirectoryPath, brain_region: str +): """Get the session_to_nwb kwargs for each session in the dataset for a given brain region. Parameters ---------- - ephys_path : pathlib.Path + ephys_path : DirectoryPath Path to the directory containing electrophysiology data for subjects. - ephys_behavior_path : pathlib.Path + ephys_behavior_path : DirectoryPath Path to the directory containing electrophysiology behavior data files. - opto_path : pathlib.Path + opto_path : DirectoryPath Path to the directory containing optogenetics behavior data files. brain_region : str The brain region associated with the sessions. From dfc0571f7d29a419c9794616a0fd750f43575a55 Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 17:16:59 -0800 Subject: [PATCH 12/15] added docstring to add_session_start_time_to_metadata --- .../zempolich_2024/zempolich_2024_convert_session.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py index 2fffd2a..3168b93 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py @@ -130,6 +130,17 @@ def add_session_start_time_to_metadata( ephys_folder_path: Optional[DirectoryPath], metadata: dict, ): + """Add the session start time to the metadata, including timezone information. + + Parameters + ---------- + behavior_file_path : FilePath + Path to the behavior .mat file. + ephys_folder_path : Optional[DirectoryPath] + Path to the folder containing electrophysiology data, by default None. + metadata : dict + The metadata for the session. + """ if ephys_folder_path is not None: folder_name = ephys_folder_path.parent.name + "/" + ephys_folder_path.name folder_name_to_start_datetime = metadata["Ecephys"].pop("folder_name_to_start_datetime") From c990799182283d054d44328890d81e43905c1842 Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 17:39:19 -0800 Subject: [PATCH 13/15] updated docstrings to match base data interface --- .../zempolich_2024/zempolich_2024_behaviorinterface.py | 6 +++--- .../zempolich_2024_open_ephys_recording_interface.py | 8 +++----- .../zempolich_2024/zempolich_2024_optogeneticinterface.py | 6 +++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_behaviorinterface.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_behaviorinterface.py index 8af9cff..56d3234 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_behaviorinterface.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_behaviorinterface.py @@ -94,10 +94,10 @@ def add_to_nwbfile(self, nwbfile: NWBFile, metadata: dict, normalize_timestamps: Parameters ---------- - nwbfile : NWBFile - The NWBFile to which to add the behavior data. + nwbfile : pynwb.NWBFile + The in-memory object to add the data to. metadata : dict - The metadata for the behavior data. + Metadata dictionary with information used to create the NWBFile. normalize_timestamps : bool, optional Whether to normalize the timestamps to the start of the first behavioral time series, by default False """ diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_open_ephys_recording_interface.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_open_ephys_recording_interface.py index f91eb04..bb0db3a 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_open_ephys_recording_interface.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_open_ephys_recording_interface.py @@ -24,14 +24,12 @@ def add_to_nwbfile( Parameters ---------- - nwbfile : NWBFile - The NWBFile to which the recording will be added. + nwbfile : pynwb.NWBFile + The in-memory object to add the data to. metadata : dict - The metadata for the conversion. + Metadata dictionary with information used to create the NWBFile. brain_region : Literal["A1", "M2"], optional The brain region from which the recording was taken, by default "A1". - conversion_options : dict - Additional options for the conversion. """ folder_path = self.source_data["folder_path"] channel_positions = np.load(folder_path / "channel_positions.npy") diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_optogeneticinterface.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_optogeneticinterface.py index 6b94236..27c27d9 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_optogeneticinterface.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_optogeneticinterface.py @@ -30,10 +30,10 @@ def add_to_nwbfile(self, nwbfile: NWBFile, metadata: dict, brain_region: Literal Parameters ---------- - nwbfile : NWBFile - NWBFile to which the data will be added. + nwbfile : pynwb.NWBFile + The in-memory object to add the data to. metadata : dict - Metadata dictionary. + Metadata dictionary with information used to create the NWBFile. brain_region : Literal["A1", "M2"], optional Brain region for which the optogenetic stimulation data will be added, by default "A1". """ From b1cd93df718d443df68a81e2163538d3c5c9b0fa Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Wed, 20 Nov 2024 18:09:39 -0800 Subject: [PATCH 14/15] fixed bug with convert_sessions --- .../zempolich_2024/zempolich_2024_convert_session.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py index 3168b93..60b494e 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_convert_session.py @@ -220,6 +220,7 @@ def main(): intrinsic_signal_optical_imaging_folder_path=intrinsic_signal_optical_imaging_folder_path, brain_region="M2", output_dir_path=output_dir_path, + has_opto=True, stub_test=stub_test, verbose=verbose, ) From 1e36537ed962369331861bd52c89ab74cea7abe4 Mon Sep 17 00:00:00 2001 From: pauladkisson Date: Thu, 21 Nov 2024 10:48:28 -0800 Subject: [PATCH 15/15] added institution --- .../zempolich_2024/zempolich_2024_metadata.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_metadata.yaml b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_metadata.yaml index 433fada..a68359f 100644 --- a/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_metadata.yaml +++ b/src/schneider_lab_to_nwb/zempolich_2024/zempolich_2024_metadata.yaml @@ -5,7 +5,7 @@ NWBFile: - optogenetics experiment_description: Identifying mistakes is important for improving performance during acoustic behaviors like speech and musicianship. Although hearing is instrumental for monitoring and adapting these behaviors, the neural circuits that integrate motor, acoustic, and goal-related signals to detect errors and guide ongoing sensorimotor adaptation in mammals remain unidentified. Here, we develop a novel closed-loop, sound-guided behavior that requires mice to use real-time acoustic feedback to guide skilled ongoing forelimb movements. Large scale electrophysiology recordings reveal that the mouse auditory cortex integrates information about sound and movement, as well as encodes error- and learning-related signals during this sound-generating behavior. Distinct groups of auditory cortex neurons signal different error types, and the activity of these neurons predicts both within-trial and across-trial behavioral adaptations. Brief, behavior-triggered optogenetic suppression of auditory cortex during error signaling hinders behavioral corrections on both rapid and long time scales, indicating that cortical error signals are necessary for skilled acoustic behaviors. Together, these experiments identify a cortical role for detecting errors and learning from mistakes and suggest that the auditory cortex plays a critical role in skilled, sound-generating behavior in mammals. - institution: Institution where the lab is located + institution: New York University lab: Schneider experimenter: - Zempolich, Grant W.