Skip to content

Support for structured behavioral data #1343

Description

@weiglszonja

The Pagan Lab is conducting behavioral experiments using BControl, a platform for rodent behavior measurement & real-time stimulus control developed and maintained by the Brody Lab at Princeton University.

We are representing this data using the ndx-structured-behavior NWB extension, which captures states, events, and the actions from the protocols in a structured format.

We would like to ingest this data into Spyglass. Specifically, we want to be able to represent what states, events, and actions occurred within a trial. (this would also require the ingestion of trials from NWB, see #1342 )

Does the Spyglass team have plans to support structured behavioral data, such as that represented by ndx-structured-behavior?

If not, what would be the recommended approach for integrating these data types (states, events, actions) into the Spyglass schema?

Here is an example of a custom table that represents the enter/exit times of all states from a Bcontrol session.

import datajoint as dj
import numpy as np
from spyglass.common.common_nwbfile import Nwbfile
from spyglass.utils import SpyglassMixin
from spyglass.utils.nwb_helper_fn import get_nwb_file

schema = dj.schema("states_table")


@schema
class StatesTable(SpyglassMixin, dj.Imported):
    """Table for storing state information from NWB files.

    This table stores time intervals representing different states during behavioral experiments.
    Each state has a type identifier and a set of start/end times. The states are extracted from
    the NWB file's acquisition data, specifically from the 'task_recording' group's 'states' table.

    The primary key of this table consists of:
    1. A foreign key to the Session table (nwb_file_name)
    2. The state type identifier (state_type)

    Attributes
    ----------
    definition : str
        DataJoint table definition
    """

    definition = """
    # Time intervals used for analysis
    -> Session
    state_type: varchar(170)  # descriptive name of this interval
    ---
    valid_times: longblob  # numpy array with start/end times for each interval
    pipeline = "": varchar(64)  # type of interval list
    """

    def make(self, key):
        """Populate the table with state information from an NWB file.

        This method extracts state information from an NWB file and inserts it into the StatesTable.
        It reads the states from the 'task_recording' acquisition group in the NWB file,
        processes them to extract state types and time intervals, and inserts them into the table.

        The method groups states by their state_name and creates a record for each unique state type,
        with an array of start/end times for all intervals of that state type.

        Parameters
        ----------
        key : dict
            Dictionary containing the 'nwb_file_name' key, which specifies the NWB file to process.

        Notes
        -----
        This method assumes that:
        - The NWB file exists and is accessible
        - The NWB file contains a 'task_recording' acquisition with a 'states' table
        - The 'states' table has 'state_type', 'start_time', and 'stop_time' columns
        - The 'state_type' column contains DynamicTableRegion references to state names

        The method handles the conversion of DynamicTableRegion references to string values
        and groups the states by their names for efficient storage.
        """

        nwb_file_name = key["nwb_file_name"]
        nwb_file_abspath = Nwbfile().get_abs_path(nwb_file_name)
        nwbf = get_nwb_file(nwb_file_abspath)

        states = nwbf.acquisition["task_recording"].states.to_dataframe()
        # convert DynamicTableRegion column to string column
        states["state_name"] = states["state_type"].apply(lambda row: row["state_name"].iloc[0])

        inserts = (
            states.groupby("state_name")
            .apply(
                lambda df: {
                    "nwb_file_name": nwb_file_name,
                    "state_type": df.name,
                    "valid_times": np.array(df[["start_time", "stop_time"]]),
                },
                include_groups=False,
            )
            .tolist()
        )

        self.insert(inserts, allow_direct_insert=True, skip_duplicates=True)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Behaviorissues int the behavior moduleenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions