Skip to content

common_session.py

Session

Bases: SpyglassMixin, Imported

Source code in src/spyglass/common/common_session.py
@schema
class Session(SpyglassMixin, dj.Imported):
    definition = """
    # Table for holding experimental sessions.
    # Note that each session can have multiple experimenters and data acquisition
    # devices. See DataAcquisitionDevice and Experimenter part tables below.
    -> Nwbfile
    ---
    -> [nullable] Subject
    -> [nullable] Institution
    -> [nullable] Lab
    session_id = NULL: varchar(200)
    session_description: varchar(2000)
    session_start_time: datetime
    timestamps_reference_time: datetime
    experiment_description = NULL: varchar(2000)
    """

    class DataAcquisitionDevice(SpyglassMixin, dj.Part):
        definition = """
        # Part table linking Session to multiple DataAcquisitionDevice entries.
        -> Session
        -> DataAcquisitionDevice
        """

        # NOTE: as a Part table, it is ill advised to delete entries directly
        # (https://docs.datajoint.org/python/computation/03-master-part.html),
        # but you can use `delete(force=True)`.

    class Experimenter(SpyglassMixin, dj.Part):
        definition = """
        # Part table linking Session to multiple LabMember entries.
        -> Session
        -> LabMember
        """

    def make(self, key):
        """Populate the Session table and others from an nwb file.

        Calls the insert_from_nwbfile method for each of the following tables:
            - Institution
            - Lab
            - LabMember
            - Subject
            - DataAcquisitionDevice
            - CameraDevice
            - Probe
            - IntervalList
        """
        # These imports must go here to avoid cyclic dependencies
        # from .common_task import Task, TaskEpoch
        from .common_interval import IntervalList

        # from .common_ephys import Unit

        nwb_file_name = key["nwb_file_name"]
        nwb_file_abspath = Nwbfile.get_abs_path(nwb_file_name)
        nwbf = get_nwb_file(nwb_file_abspath)
        config = get_config(nwb_file_abspath, calling_table=self.camel_name)

        # certain data are not associated with a single NWB file / session
        # because they may apply to multiple sessions. these data go into
        # dj.Manual tables. e.g., a lab member may be associated with multiple
        # experiments, so the lab member table should not be dependent on
        # (contain a primary key for) a session.

        # here, we create new entries in these dj.Manual tables based on the
        # values read from the NWB file then, they are linked to the session
        # via fields of Session (e.g., Subject, Institution, Lab) or part
        # tables (e.g., Experimenter, DataAcquisitionDevice).

        logger.info("Session populates Institution...")
        institution_name = Institution().insert_from_nwbfile(nwbf, config)

        logger.info("Session populates Lab...")
        lab_name = Lab().insert_from_nwbfile(nwbf, config)

        logger.info("Session populates LabMember...")
        LabMember().insert_from_nwbfile(nwbf, config)

        logger.info("Session populates Subject...")
        subject_id = Subject().insert_from_nwbfile(nwbf, config)

        if not debug_mode:  # TODO: remove when demo files agree on device
            logger.info("Session populates Populate DataAcquisitionDevice...")
            DataAcquisitionDevice.insert_from_nwbfile(nwbf, config)

        logger.info("Session populates Populate CameraDevice...")
        CameraDevice.insert_from_nwbfile(nwbf, config)

        logger.info("Session populates Populate Probe...")
        Probe.insert_from_nwbfile(nwbf, config)

        Session().insert1(
            {
                "nwb_file_name": nwb_file_name,
                "subject_id": subject_id,
                "institution_name": institution_name,
                "lab_name": lab_name,
                "session_id": nwbf.session_id,
                "session_description": nwbf.session_description,
                "session_start_time": nwbf.session_start_time,
                "timestamps_reference_time": nwbf.timestamps_reference_time,
                "experiment_description": nwbf.experiment_description,
            },
            skip_duplicates=True,
            allow_direct_insert=True,  # for populate_all_common
        )

        logger.info("Skipping Apparatus for now...")
        # Apparatus().insert_from_nwbfile(nwbf)

        # interval lists depend on Session (as a primary key) but users may
        # want to add these manually so this is a manual table that is also
        # populated from NWB files

        logger.info("Session populates IntervalList...")
        IntervalList().insert_from_nwbfile(nwbf, nwb_file_name=nwb_file_name)

        # logger.info('Unit...')
        # Unit().insert_from_nwbfile(nwbf, nwb_file_name=nwb_file_name)

        self._add_data_acquisition_device_part(nwb_file_name, nwbf, config)
        self._add_experimenter_part(nwb_file_name, nwbf, config)

    def _add_data_acquisition_device_part(self, nwb_file_name, nwbf, config={}):
        # get device names from both the NWB file and the associated config file
        device_names, _, _ = DataAcquisitionDevice.get_all_device_names(
            nwbf, config
        )

        for device_name in device_names:
            # ensure that the foreign key exists and do nothing if not
            query = DataAcquisitionDevice & {
                "data_acquisition_device_name": device_name
            }
            if len(query) == 0:
                logger.warn(
                    "Cannot link Session with DataAcquisitionDevice.\n"
                    + f"DataAcquisitionDevice does not exist: {device_name}"
                )
                continue
            key = dict()
            key["nwb_file_name"] = nwb_file_name
            key["data_acquisition_device_name"] = device_name
            Session.DataAcquisitionDevice.insert1(key)

    def _add_experimenter_part(
        self, nwb_file_name: str, nwbf, config: dict = None
    ):
        # Use config file over nwb file
        config = config or dict()
        if members := config.get("LabMember"):
            experimenter_list = [
                member["lab_member_name"] for member in members
            ]
        elif nwbf.experimenter is not None:
            experimenter_list = nwbf.experimenter
        else:
            return

        for name in experimenter_list:
            # ensure that the foreign key exists and do nothing if not
            query = LabMember & {"lab_member_name": name}
            if len(query) == 0:
                logger.warn(
                    "Cannot link Session with LabMember. "
                    + f"LabMember does not exist: {name}"
                )
                continue

            key = dict()
            key["nwb_file_name"] = nwb_file_name
            key["lab_member_name"] = name
            Session.Experimenter.insert1(key)

make(key)

Populate the Session table and others from an nwb file.

Calls the insert_from_nwbfile method for each of the following tables: - Institution - Lab - LabMember - Subject - DataAcquisitionDevice - CameraDevice - Probe - IntervalList

Source code in src/spyglass/common/common_session.py
def make(self, key):
    """Populate the Session table and others from an nwb file.

    Calls the insert_from_nwbfile method for each of the following tables:
        - Institution
        - Lab
        - LabMember
        - Subject
        - DataAcquisitionDevice
        - CameraDevice
        - Probe
        - IntervalList
    """
    # These imports must go here to avoid cyclic dependencies
    # from .common_task import Task, TaskEpoch
    from .common_interval import IntervalList

    # from .common_ephys import Unit

    nwb_file_name = key["nwb_file_name"]
    nwb_file_abspath = Nwbfile.get_abs_path(nwb_file_name)
    nwbf = get_nwb_file(nwb_file_abspath)
    config = get_config(nwb_file_abspath, calling_table=self.camel_name)

    # certain data are not associated with a single NWB file / session
    # because they may apply to multiple sessions. these data go into
    # dj.Manual tables. e.g., a lab member may be associated with multiple
    # experiments, so the lab member table should not be dependent on
    # (contain a primary key for) a session.

    # here, we create new entries in these dj.Manual tables based on the
    # values read from the NWB file then, they are linked to the session
    # via fields of Session (e.g., Subject, Institution, Lab) or part
    # tables (e.g., Experimenter, DataAcquisitionDevice).

    logger.info("Session populates Institution...")
    institution_name = Institution().insert_from_nwbfile(nwbf, config)

    logger.info("Session populates Lab...")
    lab_name = Lab().insert_from_nwbfile(nwbf, config)

    logger.info("Session populates LabMember...")
    LabMember().insert_from_nwbfile(nwbf, config)

    logger.info("Session populates Subject...")
    subject_id = Subject().insert_from_nwbfile(nwbf, config)

    if not debug_mode:  # TODO: remove when demo files agree on device
        logger.info("Session populates Populate DataAcquisitionDevice...")
        DataAcquisitionDevice.insert_from_nwbfile(nwbf, config)

    logger.info("Session populates Populate CameraDevice...")
    CameraDevice.insert_from_nwbfile(nwbf, config)

    logger.info("Session populates Populate Probe...")
    Probe.insert_from_nwbfile(nwbf, config)

    Session().insert1(
        {
            "nwb_file_name": nwb_file_name,
            "subject_id": subject_id,
            "institution_name": institution_name,
            "lab_name": lab_name,
            "session_id": nwbf.session_id,
            "session_description": nwbf.session_description,
            "session_start_time": nwbf.session_start_time,
            "timestamps_reference_time": nwbf.timestamps_reference_time,
            "experiment_description": nwbf.experiment_description,
        },
        skip_duplicates=True,
        allow_direct_insert=True,  # for populate_all_common
    )

    logger.info("Skipping Apparatus for now...")
    # Apparatus().insert_from_nwbfile(nwbf)

    # interval lists depend on Session (as a primary key) but users may
    # want to add these manually so this is a manual table that is also
    # populated from NWB files

    logger.info("Session populates IntervalList...")
    IntervalList().insert_from_nwbfile(nwbf, nwb_file_name=nwb_file_name)

    # logger.info('Unit...')
    # Unit().insert_from_nwbfile(nwbf, nwb_file_name=nwb_file_name)

    self._add_data_acquisition_device_part(nwb_file_name, nwbf, config)
    self._add_experimenter_part(nwb_file_name, nwbf, config)