Skip to content

Commit

Permalink
Merge pull request #3 from catalystneuro/behavior
Browse files Browse the repository at this point in the history
Prototype Behavior Interface
  • Loading branch information
pauladkisson authored Sep 6, 2024
2 parents bf34d65 + e01f8ee commit e871a87
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1,25 +1,115 @@
"""Primary class for converting experiment-specific behavior."""
from pynwb.file import NWBFile
from pydantic import FilePath
import numpy as np
from h5py import File
from hdmf.common.table import DynamicTableRegion
from pynwb.behavior import BehavioralTimeSeries, TimeSeries
from ndx_events import EventTypesTable, EventsTable, Task, TimestampVectorData

from neuroconv.basedatainterface import BaseDataInterface
from neuroconv.utils import DeepDict
from neuroconv.tools import nwb_helpers


class Schneider2024BehaviorInterface(BaseDataInterface):
"""Behavior interface for schneider_2024 conversion"""

keywords = ["behavior"]

def __init__(self):
# This should load the data lazily and prepare variables you need
pass

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()
metadata = super().get_metadata()

return metadata

def add_to_nwbfile(self, nwbfile: NWBFile, metadata: dict):
# All the custom code to add the data the nwbfile
# Read Data
file_path = self.source_data["file_path"]
with File(file_path, "r") as file:
behavioral_time_series, name_to_times, name_to_values = [], dict(), dict()
for time_series_dict in metadata["Behavior"]["TimeSeries"]:
name = time_series_dict["name"]
timestamps = np.array(file["continuous"][name]["time"]).squeeze()
data = np.array(file["continuous"][name]["value"]).squeeze()
time_series = TimeSeries(
name=name,
timestamps=timestamps,
data=data,
unit="a.u.",
description=time_series_dict["description"],
)
behavioral_time_series.append(time_series)
for event_dict in metadata["Behavior"]["Events"]:
name = event_dict["name"]
times = np.array(file["events"][name]["time"]).squeeze()
name_to_times[name] = times
for event_dict in metadata["Behavior"]["ValuedEvents"]:
name = event_dict["name"]
times = np.array(file["events"][name]["time"]).squeeze()
values = np.array(file["events"][name]["value"]).squeeze()
name_to_times[name] = times
name_to_values[name] = values

# Add Data to NWBFile
behavior_module = nwb_helpers.get_module(
nwbfile=nwbfile,
name="behavior",
description="Behavioral data from the experiment.",
)

# Add BehavioralTimeSeries
behavioral_time_series = BehavioralTimeSeries(
time_series=behavioral_time_series,
name="behavioral_time_series",
)
behavior_module.add(behavioral_time_series)

# Add Events
event_types_table = EventTypesTable(name="event_types", description="Metadata about event types.")
event_type_name_to_row = dict()
i = 0
for event_dict in metadata["Behavior"]["Events"]:
event_type_name_to_row[event_dict["name"]] = i
event_types_table.add_row(
event_name=event_dict["name"],
event_type_description=event_dict["description"],
)
i += 1
for event_dict in metadata["Behavior"]["ValuedEvents"]:
event_type_name_to_row[event_dict["name"]] = i
event_types_table.add_row(
event_name=event_dict["name"],
event_type_description=event_dict["description"],
)
i += 1
events_table = EventsTable(
name="events_table",
description="Metadata about events.",
target_tables={"event_type": event_types_table},
)
for event_dict in metadata["Behavior"]["Events"]:
event_times = name_to_times[event_dict["name"]]
event_type = event_type_name_to_row[event_dict["name"]]
for event_time in event_times:
events_table.add_row(timestamp=event_time, event_type=event_type)
valued_events_table = EventsTable(
name="valued_events_table",
description="Metadata about valued events.",
target_tables={"event_type": event_types_table},
)
valued_events_table.add_column(name="value", description="Value of the event.")
for event_dict in metadata["Behavior"]["ValuedEvents"]:
event_times = name_to_times[event_dict["name"]]
event_values = name_to_values[event_dict["name"]]
event_type = event_type_name_to_row[event_dict["name"]]
for event_time, event_value in zip(event_times, event_values):
valued_events_table.add_row(timestamp=event_time, event_type=event_type, value=event_value)
behavior_module.add(events_table)
behavior_module.add(valued_events_table)

raise NotImplementedError()
task = Task(event_types=event_types_table)
nwbfile.add_lab_meta_data(task)
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def session_to_nwb(data_dir_path: Union[str, Path], output_dir_path: Union[str,
output_dir_path = Path(output_dir_path)
recording_folder_path = data_dir_path / "Raw Ephys" / "m69_2023-10-31_17-24-15_Day1_A1"
sorting_folder_path = data_dir_path / "Processed Ephys" / "m69_2023-10-31_17-24-15_Day1_A1"
behavior_file_path = data_dir_path / "Behavior" / "m69_231031" / "raw_m69_231031_001.mat"
if stub_test:
output_dir_path = output_dir_path / "nwb_stub"
recording_folder_path = recording_folder_path.with_name(recording_folder_path.name + "_stubbed")
Expand All @@ -38,9 +39,9 @@ def session_to_nwb(data_dir_path: Union[str, Path], output_dir_path: Union[str,
source_data.update(dict(Sorting=dict(folder_path=sorting_folder_path)))
conversion_options.update(dict(Sorting=dict()))

# # Add Behavior
# source_data.update(dict(Behavior=dict()))
# conversion_options.update(dict(Behavior=dict()))
# Add Behavior
source_data.update(dict(Behavior=dict(file_path=behavior_file_path)))
conversion_options.update(dict(Behavior=dict()))

converter = Schneider2024NWBConverter(source_data=source_data)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,27 @@ Ecephys:
ElectricalSeries:
- name: ElectricalSeries
description: Recording of AC neural responses in mice performing this behavioral task will utilize dense 128-channel recording probes (Masmanidis Lab). These recording probes span a depth ~1mm, allowing for sampling of all layers of cortex. Electrophysiology data will be recorded using OpenEphys Acquisition Board v2.4 and associated OpenEphys GUI software.
# UnitProperties:
# - name: n_spikes
# description: Number of spikes recorded from each unit.
# - name: fr
# description: Average firing rate of each unit.
# - name: depth
# description: Estimated depth of each unit in micrometers.
# - name: Amplitude
# description: Per-template amplitudes, computed as the L2 norm of the template.
# - name: ContamPct
# description: Contamination rate for each template, computed as fraction of refractory period violations relative to expectation based on a Poisson process.
# - name: KSLabel
# description: Label indicating whether each template is 'mua' (multi-unit activity) or 'good' (refractory).
# - name: original_cluster_id
# description: Original cluster ID assigned by Kilosort.
# - name: amp
# description: For every template, the maximum amplitude of the template waveforms across all channels.
# - name: ch
# description: The channel label of the best channel, as defined by the user.
# - name: sh
# description: The shank label of the best channel.

Behavior:
TimeSeries:
- name: encoder
description: Sampled values for entire duration of experiment for lever pressing/treadmill behavior read from a quadrature encoder.
- name: lick
description: Samples values for entire duration of experiment for voltage signal readout from an infrared/capacitive) lickometer sensor.
Events:
- name: target
description: Time at which the target zone is entered during a press.
- name: targetOUT
description: Time at which the target zone is overshot during a press.
- name: toneIN
description: Time at which target entry tone is played.
- name: toneOUT
description: Time at which target exit tone is played.
- name: valve
description: Times at which solenoid valve opens to deliver water after a correct trial.
ValuedEvents:
- name: tuningTones
description: Times at which tuning tones are played to an animal after a behavioral experiment during ephys recording sessions.

Sorting:
units_description: Neural spikes will be sorted offline using Kilosort 2.5 and Phy2 software and manually curated to ensure precise spike time acquisition.
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# Notes concerning the schneider_2024 conversion

## Behavior
- opto events is just [0, 1] like empty variables (lick events), and opto_time in 'push' is all NaNs. Where is the actual opto stim times recorded?
- TuningTones has a values field with integers, what does this correspond to?
- 'push' is basically some kind of trials table, but need descriptions for variables ex. ITI_respect?

## Data Requests
- Mice sexes
- Remaining data for Grant's project
- More detailed position info for recording probe
- Subfield of auditory cortex: A1? A2? AAF? etc.
- stereotactic coordinates of the whole probe
-
- Detailed description of the behavioral paradigm
- Description of lickometer and lever/treadmill quadrature encoder.
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ class Schneider2024NWBConverter(NWBConverter):
data_interface_classes = dict(
Recording=OpenEphysRecordingInterface,
Sorting=PhySortingInterface,
Behavior=Schneider2024BehaviorInterface,
)

0 comments on commit e871a87

Please sign in to comment.