Skip to content

Commit

Permalink
Merge pull request #8 from catalystneuro/epoch
Browse files Browse the repository at this point in the history
Epoch
  • Loading branch information
pauladkisson authored Dec 11, 2024
2 parents 5539005 + f63de52 commit cff3d34
Show file tree
Hide file tree
Showing 12 changed files with 434 additions and 48 deletions.
5 changes: 4 additions & 1 deletion src/jadhav_lab_to_nwb/olson_2024/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from .olson_2024_behavior_interface import Olson2024BehaviorInterface
from .olson_2024_video_interface import Olson2024VideoInterface
from .olson_2024_dlc_interface import Olson2024DeepLabCutInterface
from .olson_2024_spike_gadgets_recording_interface import Olson2024SpikeGadgetsRecordingInterface
from .olson_2024_sorting_interface import Olson2024SortingInterface
from .olson_2024_spike_gadgets_lfp_interface import Olson2024SpikeGadgetsLFPInterface
from .olson_2024_nwbconverter import Olson2024NWBConverter
from .olson_2024_epoch_interface import Olson2024EpochInterface
from .olson_2024_nwbconverter import Olson2024NWBConverter, get_start_datetime
70 changes: 40 additions & 30 deletions src/jadhav_lab_to_nwb/olson_2024/olson_2024_convert_session.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Primary script to run to convert an entire session for of data using the NWBConverter."""
from pathlib import Path
import datetime
from datetime import datetime
from zoneinfo import ZoneInfo
import shutil

Expand All @@ -9,37 +9,36 @@
) # TODO: remove after this issue gets fixed: https://github.com/catalystneuro/neuroconv/issues/1143
from neuroconv.utils import load_dict_from_file, dict_deep_update

from jadhav_lab_to_nwb.olson_2024 import Olson2024NWBConverter
from jadhav_lab_to_nwb.olson_2024 import Olson2024NWBConverter, get_start_datetime


def session_to_nwb(data_dir_path: str | Path, output_dir_path: str | Path, stub_test: bool = False):
def session_to_nwb(
data_dir_path: str | Path, subject_id: str, session_id: str, output_dir_path: str | Path, stub_test: bool = False
):

data_dir_path = Path(data_dir_path)
output_dir_path = Path(output_dir_path)
if stub_test:
output_dir_path = output_dir_path / "nwb_stub"
output_dir_path.mkdir(parents=True, exist_ok=True)
session_folder_path = data_dir_path / f"{subject_id}_{session_id}"
nwbfile_path = output_dir_path / f"sub-{subject_id}_ses-{session_id}.nwb"

session_id = "sample_session"
subject_id = "SL18"
nwbfile_path = output_dir_path / f"{session_id}.nwb"
# Get epoch info
epoch_folder_paths = list(session_folder_path.glob(rf"{session_folder_path.name}_S[0-9][0-9]_F[0-9][0-9]_*"))
epoch_folder_paths = sorted(epoch_folder_paths)

source_data = dict()
conversion_options = dict()

# Add Ephys
file_path = (
data_dir_path
/ "SL18_D19"
/ "SL18_D19_S01_F01_BOX_SLP_20230503_112642"
/ "SL18_D19_S01_F01_BOX_SLP_20230503_112642.rec"
)
source_data.update(dict(Recording=dict(file_path=file_path)))
file_paths = [epoch_folder_path / f"{epoch_folder_path.name}.rec" for epoch_folder_path in epoch_folder_paths]
source_data.update(dict(Recording=dict(file_paths=file_paths)))
conversion_options.update(dict(Recording=dict(stub_test=stub_test)))

# Add Sorting
spike_times_folder_path = data_dir_path / "SL18_D19" / "SL18_D19.SpikesFinal"
unit_stats_folder_path = data_dir_path / "SL18_D19" / "SL18_D19.ExportedUnitStats"
spike_times_folder_path = session_folder_path / f"{session_folder_path.name}.SpikesFinal"
unit_stats_folder_path = session_folder_path / f"{session_folder_path.name}.ExportedUnitStats"
source_data.update(
dict(
Sorting=dict(spike_times_folder_path=spike_times_folder_path, unit_stats_folder_path=unit_stats_folder_path)
Expand All @@ -48,35 +47,41 @@ def session_to_nwb(data_dir_path: str | Path, output_dir_path: str | Path, stub_
conversion_options.update(dict(Sorting=dict()))

# Add LFP
folder_path = data_dir_path / "SL18_D19" / "SL18_D19.LFP"
folder_path = session_folder_path / f"{session_folder_path.name}.LFP"
source_data.update(dict(LFP=dict(folder_path=folder_path)))
conversion_options.update(dict(LFP=dict(stub_test=stub_test)))

# Add DLC
dlc_folder_path = session_folder_path / f"{session_folder_path.name}.DLC"
file_paths = [file_path for file_path in dlc_folder_path.glob(r"*.csv") if not (file_path.name.startswith("._"))]
source_data.update(dict(DeepLabCut=dict(file_paths=file_paths, subject_name=subject_id)))
conversion_options.update(dict(DeepLabCut=dict()))

# Add Video
file_paths = [
data_dir_path
/ "SL18_D19"
/ "SL18_D19_S01_F01_BOX_SLP_20230503_112642"
/ "SL18_D19_S01_F01_BOX_SLP_20230503_112642.1.h264"
]
file_paths = []
for epoch_folder_path in epoch_folder_paths:
file_path = epoch_folder_path / f"{epoch_folder_path.name}.1.h264"
file_paths.append(file_path)
source_data.update(dict(Video=dict(file_paths=file_paths)))
conversion_options.update(dict(Video=dict()))

# Add DLC
file_path = "/Volumes/T7/CatalystNeuro/Jadhav/SubLearnProject/SL18_D19/SL18_D19.DLC/SL18_D19_S01_F01_BOX_SLP_20230503_112642.1DLC_resnet50_SubLearnSleepBoxRedLightJun26shuffle1_100000.csv"
source_data.update(dict(DeepLabCut=dict(file_path=file_path, subject_name=subject_id)))
conversion_options.update(dict(DeepLabCut=dict()))

# Add Behavior
folder_path = "/Volumes/T7/CatalystNeuro/Jadhav/SubLearnProject/SL18_D19/SL18_D19.DIO"
folder_path = session_folder_path / f"{session_folder_path.name}.DIO"
source_data.update(dict(Behavior=dict(folder_path=folder_path)))
conversion_options.update(dict(Behavior=dict()))

# Add Epoch
source_data.update(dict(Epoch=dict(epoch_folder_paths=epoch_folder_paths)))
conversion_options.update(dict(Epoch=dict()))

converter = Olson2024NWBConverter(source_data=source_data)
metadata = converter.get_metadata()

# Add datetime to conversion
metadata = converter.get_metadata()
metadata["NWBFile"]["session_start_time"] = datetime.datetime(2023, 5, 3, 11, 26, 42, tzinfo=ZoneInfo("US/Eastern"))
session_start_time = get_start_datetime(epoch_folder_paths[0].name)
est = ZoneInfo("US/Eastern")
session_start_time = session_start_time.replace(tzinfo=est)
metadata["NWBFile"]["session_start_time"] = session_start_time

# Update default metadata with the editable in the corresponding yaml file
editable_metadata_path = Path(__file__).parent / "olson_2024_metadata.yaml"
Expand All @@ -97,8 +102,13 @@ def session_to_nwb(data_dir_path: str | Path, output_dir_path: str | Path, stub_
if output_dir_path.exists():
shutil.rmtree(output_dir_path, ignore_errors=True)

# Example Session
subject_id = "SL18"
session_id = "D19"
session_to_nwb(
data_dir_path=data_dir_path,
subject_id=subject_id,
session_id=session_id,
output_dir_path=output_dir_path,
stub_test=stub_test,
)
61 changes: 61 additions & 0 deletions src/jadhav_lab_to_nwb/olson_2024/olson_2024_dlc_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Primary class for converting experiment-specific behavioral video."""
from pynwb.file import NWBFile
from pydantic import FilePath
from typing import Optional

from neuroconv.utils import DeepDict, dict_deep_update
from neuroconv.basedatainterface import BaseDataInterface
from neuroconv.datainterfaces import DeepLabCutInterface

from .utils.utils import get_epoch_name


class Olson2024DeepLabCutInterface(BaseDataInterface):
"""DeepLabCut interface for olson_2024 conversion"""

keywords = ("DLC",)

def __init__(
self,
file_paths: list[FilePath],
config_file_paths: Optional[list[FilePath]] = None,
subject_name: str = "ind1",
verbose: bool = True,
):
# file_paths must be sorted in the order that the videos were recorded
assert len(file_paths) > 0, "At least one file path must be provided."
if config_file_paths is None:
config_file_paths = [None] * len(file_paths)
assert len(file_paths) == len(
config_file_paths
), "The number of file paths must match the number of config file paths."
dlc_interfaces = []
for file_path, config_file_path in zip(file_paths, config_file_paths):
dlc_interface = DeepLabCutInterface(
file_path=file_path,
config_file_path=config_file_path,
subject_name=subject_name,
verbose=verbose,
)
dlc_interfaces.append(dlc_interface)
self.dlc_interfaces = dlc_interfaces

def get_metadata(self) -> DeepDict:
metadata = super().get_metadata()
for dlc_interface in self.dlc_interfaces:
metadata = dict_deep_update(metadata, dlc_interface.get_metadata())
return metadata

def get_metadata_schema(self) -> DeepDict:
metadata_schema = super().get_metadata_schema()
for dlc_interface in self.dlc_interfaces:
metadata_schema = dict_deep_update(metadata_schema, dlc_interface.get_metadata_schema())
return metadata_schema

def add_to_nwbfile(self, nwbfile: NWBFile, metadata: dict):
for dlc_interface in self.dlc_interfaces:
file_path = dlc_interface.source_data["file_path"]
epoch_name = get_epoch_name(name=file_path.name)
dlc_interface.add_to_nwbfile(
nwbfile=nwbfile, metadata=metadata, container_name=f"PoseEstimation_{epoch_name}"
)
93 changes: 93 additions & 0 deletions src/jadhav_lab_to_nwb/olson_2024/olson_2024_epoch_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Primary class for converting experiment-specific behavior."""
from pynwb.file import NWBFile
from pydantic import DirectoryPath
import numpy as np

from neuroconv.basedatainterface import BaseDataInterface
from .tools.spikegadgets import readCameraModuleTimeStamps
from .utils.utils import get_epoch_name


class Olson2024EpochInterface(BaseDataInterface):
"""Epoch interface for olson_2024 conversion"""

keywords = ("behavior",)

def __init__(self, epoch_folder_paths: list[DirectoryPath]):
super().__init__(epoch_folder_paths=epoch_folder_paths)

def get_metadata_schema(self):
metadata_schema = super().get_metadata_schema()
metadata_schema["properties"]["Epochs"] = {
"description": "Metadata for each epoch",
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"task_name": {"type": "string"},
"led_configuration": {"type": "string"},
"led_list": {"type": "array", "items": {"type": "string"}},
"led_positions": {"type": "array", "items": {"type": "string"}},
},
"required": ["name", "task_name", "led_configuration", "led_list", "led_positions"],
},
}
metadata_schema["properties"]["Tasks"] = {
"description": "Metadata for each task",
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"description": {"type": "string"},
"camera_id": {"type": "string"},
},
"required": ["name", "description", "camera_id"],
},
}
return metadata_schema

def add_to_nwbfile(self, nwbfile: NWBFile, metadata: dict):
epoch_folder_paths = self.source_data["epoch_folder_paths"]
nwbfile.add_epoch_column(name="epoch_name", description="Full name of the epoch")
nwbfile.add_epoch_column(name="epoch_id", description="Epoch ID")
nwbfile.add_epoch_column(name="frag_id", description="Frag ID") # TODO: What is a frag ID?
nwbfile.add_epoch_column(name="env_id", description="Environment ID")
nwbfile.add_epoch_column(name="task_id", description="Task ID")
nwbfile.add_epoch_column(name="task_name", description="Full name of the task")
nwbfile.add_epoch_column(name="task_description", description="Description of the task")
nwbfile.add_epoch_column(name="camera_id", description="Camera ID")
nwbfile.add_epoch_column(name="led_configuration", description="LED configuration")
nwbfile.add_epoch_column(name="led_list", description="Comma-separated list of LEDs")
nwbfile.add_epoch_column(name="led_positions", description="Comma-separated list of LED positions")
for epoch_folder_path in epoch_folder_paths:
epoch_name = get_epoch_name(epoch_folder_path.name)
epoch_id, frag_id, env_id, task_id = epoch_name.split("_")
epoch_metadata = next(meta for meta in metadata["Epochs"] if meta["name"] == epoch_id)
task_name = epoch_metadata["task_name"]
task_metadata = next(meta for meta in metadata["Tasks"] if meta["name"] == task_name)
task_description = task_metadata["description"]
camera_id = task_metadata["camera_id"]
led_configuration = epoch_metadata["led_configuration"]
led_list = ",".join(epoch_metadata["led_list"])
led_positions = ",".join(epoch_metadata["led_positions"])
video_timestamps_file_path = epoch_folder_path / f"{epoch_folder_path.name}.1.videoTimeStamps"
timestamps, _ = readCameraModuleTimeStamps(video_timestamps_file_path)
start_time = timestamps[0]
stop_time = timestamps[-1]
nwbfile.add_epoch(
start_time=start_time,
stop_time=stop_time,
epoch_name=epoch_name,
epoch_id=epoch_id,
frag_id=frag_id,
env_id=env_id,
task_id=task_id,
task_name=task_name,
task_description=task_description,
camera_id=camera_id,
led_configuration=led_configuration,
led_list=led_list,
led_positions=led_positions,
)
73 changes: 67 additions & 6 deletions src/jadhav_lab_to_nwb/olson_2024/olson_2024_metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ Ecephys:
location: Right Subiculum
device: ProbeNameTBD
nTrodes: [44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59]
ElectricalSeries:
- name: ElectricalSeries
description: Raw acquisition of extracellular electrophysiology data recorded by SpikeGadgets.
ElectricalSeries_description: Raw acquisition of extracellular electrophysiology data recorded by SpikeGadgets.
LFP:
ElectricalSeries:
- name: ElectricalSeriesLFP
Expand All @@ -49,9 +47,7 @@ Behavior:
Module:
name: behavior
description: Behavioral data recorded during foraging shuttle task, in which the rat must shuttle between home and distal reward locations (reward well).
Videos:
- name: Video SL18_D19_S01_F01_BOX_SLP_20230503_112642.1
description: Video of the rat in the box.
Video_description: Video of the rat in the box.
Events:
- id: ECU_Din1
name: reward_well_1
Expand Down Expand Up @@ -125,3 +121,68 @@ Behavior:
- id: ECU_Dout24
name: barrier_8
description: Whenever Barrier 8 is placed in the box

Epochs:
- name: S01
task_name: Sleep
led_configuration: single
led_list:
- redled
led_positions:
- center
- name: S02
task_name: HomeAltVisitAll
led_configuration: left/right
led_list:
- redled
- greenled
led_positions:
- right
- left
- name: S03
task_name: Sleep
led_configuration: single
led_list:
- redled
led_positions:
- center
- name: S04
task_name: HomeAltVisitAll
led_configuration: left/right
led_list:
- redled
- greenled
led_positions:
- right
- left
- name: S05
task_name: Sleep
led_configuration: single
led_list:
- redled
led_positions:
- center
- name: S06
task_name: HomeAltVisitAll
led_configuration: left/right
led_list:
- redled
- greenled
led_positions:
- right
- left
- name: S07
task_name: Sleep
led_configuration: single
led_list:
- redled
led_positions:
- center

Tasks:
- name: Sleep
description: The animal sleeps in a small empty box.
camera_id: "0"
- name: HomeAltVisitAll
description: Shuttle task between home and 4 destinations.
camera_id: "1"
Loading

0 comments on commit cff3d34

Please sign in to comment.