def test_add_stimulus_presentations(nwbfile, stimulus_presentations_behavior,
                                    stimulus_timestamps, roundtrip,
                                    roundtripper,
                                    stimulus_templates: StimulusTemplate):
    nwb.add_stimulus_timestamps(nwbfile, stimulus_timestamps)
    nwb.add_stimulus_presentations(nwbfile, stimulus_presentations_behavior)
    nwb.add_stimulus_template(nwbfile=nwbfile,
                              stimulus_template=stimulus_templates)

    # Add index for this template to NWB in-memory object:
    nwb_template = nwbfile.stimulus_template[stimulus_templates.image_set_name]
    compare = (stimulus_presentations_behavior['image_set'] ==
               nwb_template.name)
    curr_stimulus_index = stimulus_presentations_behavior[compare]
    nwb.add_stimulus_index(nwbfile, curr_stimulus_index, nwb_template)

    if roundtrip:
        obt = roundtripper(nwbfile, BehaviorNwbApi)
    else:
        obt = BehaviorNwbApi.from_nwbfile(nwbfile)

    expected = stimulus_presentations_behavior.copy()
    expected['is_change'] = [False, True, True, True, True]

    obtained = obt.get_stimulus_presentations()

    pd.testing.assert_frame_equal(expected[sorted(expected.columns)],
                                  obtained[sorted(obtained.columns)],
                                  check_dtype=False)
示例#2
0
def test_add_stimulus_presentations(nwbfile, stimulus_presentations_behavior, stimulus_timestamps, roundtrip, roundtripper, stimulus_templates):
    nwb.add_stimulus_timestamps(nwbfile, stimulus_timestamps)
    nwb.add_stimulus_presentations(nwbfile, stimulus_presentations_behavior)
    for key, val in stimulus_templates.items():
        nwb.add_stimulus_template(nwbfile, val, key)

        # Add index for this template to NWB in-memory object:
        nwb_template = nwbfile.stimulus_template[key]
        curr_stimulus_index = stimulus_presentations_behavior[stimulus_presentations_behavior['image_set'] == nwb_template.name]
        nwb.add_stimulus_index(nwbfile, curr_stimulus_index, nwb_template)

    if roundtrip:
        obt = roundtripper(nwbfile, BehaviorOphysNwbApi)
    else:
        obt = BehaviorOphysNwbApi.from_nwbfile(nwbfile)

    pd.testing.assert_frame_equal(stimulus_presentations_behavior, obt.get_stimulus_presentations(), check_dtype=False)
示例#3
0
    def save(self, session_object):

        nwbfile = NWBFile(
            session_description=str(session_object.metadata['session_type']),
            identifier=str(session_object.ophys_experiment_id),
            session_start_time=session_object.metadata['experiment_datetime'],
            file_create_date=pytz.utc.localize(datetime.datetime.now()))

        # Add stimulus_timestamps to NWB in-memory object:
        nwb.add_stimulus_timestamps(nwbfile,
                                    session_object.stimulus_timestamps)

        # Add running data to NWB in-memory object:
        unit_dict = {
            'v_sig': 'V',
            'v_in': 'V',
            'speed': 'cm/s',
            'timestamps': 's',
            'dx': 'cm'
        }
        nwb.add_running_data_df_to_nwbfile(nwbfile,
                                           session_object.running_data_df,
                                           unit_dict)

        # Add stimulus template data to NWB in-memory object:
        for name, image_data in session_object.stimulus_templates.items():
            nwb.add_stimulus_template(nwbfile, image_data, name)

            # Add index for this template to NWB in-memory object:
            nwb_template = nwbfile.stimulus_template[name]
            stimulus_index = session_object.stimulus_presentations[
                session_object.stimulus_presentations['image_set'] ==
                nwb_template.name]
            nwb.add_stimulus_index(nwbfile, stimulus_index, nwb_template)

        # Add stimulus presentations data to NWB in-memory object:
        nwb.add_stimulus_presentations(nwbfile,
                                       session_object.stimulus_presentations)

        # Add trials data to NWB in-memory object:
        nwb.add_trials(nwbfile, session_object.trials,
                       TRIAL_COLUMN_DESCRIPTION_DICT)

        # Add licks data to NWB in-memory object:
        if len(session_object.licks) > 0:
            nwb.add_licks(nwbfile, session_object.licks)

        # Add rewards data to NWB in-memory object:
        if len(session_object.rewards) > 0:
            nwb.add_rewards(nwbfile, session_object.rewards)

        # Add max_projection image data to NWB in-memory object:
        nwb.add_max_projection(nwbfile, session_object.max_projection)

        # Add average_image image data to NWB in-memory object:
        nwb.add_average_image(nwbfile, session_object.average_projection)

        # Add segmentation_mask_image image data to NWB in-memory object:
        nwb.add_segmentation_mask_image(nwbfile,
                                        session_object.segmentation_mask_image)

        # Add metadata to NWB in-memory object:
        nwb.add_metadata(nwbfile, session_object.metadata)

        # Add task parameters to NWB in-memory object:
        nwb.add_task_parameters(nwbfile, session_object.task_parameters)

        # Add roi metrics to NWB in-memory object:
        nwb.add_cell_specimen_table(nwbfile,
                                    session_object.cell_specimen_table)

        # Add dff to NWB in-memory object:
        nwb.add_dff_traces(nwbfile, session_object.dff_traces,
                           session_object.ophys_timestamps)

        # Add corrected_fluorescence to NWB in-memory object:
        nwb.add_corrected_fluorescence_traces(
            nwbfile, session_object.corrected_fluorescence_traces)

        # Add motion correction to NWB in-memory object:
        nwb.add_motion_correction(nwbfile, session_object.motion_correction)

        # Write the file:
        with NWBHDF5IO(self.path, 'w') as nwb_file_writer:
            nwb_file_writer.write(nwbfile)

        return nwbfile
示例#4
0
    def save(self, session_object):
        # Cannot type session_object due to a circular dependency
        # TODO fix circular dependency and add type

        session_metadata: BehaviorOphysMetadata = \
            session_object.api.get_metadata()

        session_type = session_metadata.session_type

        nwbfile = NWBFile(
            session_description=session_type,
            identifier=str(session_object.ophys_experiment_id),
            session_start_time=session_metadata.date_of_acquisition,
            file_create_date=pytz.utc.localize(datetime.datetime.now()),
            institution="Allen Institute for Brain Science",
            keywords=[
                "2-photon", "calcium imaging", "visual cortex", "behavior",
                "task"
            ],
            experiment_description=get_expt_description(session_type))

        # Add stimulus_timestamps to NWB in-memory object:
        nwb.add_stimulus_timestamps(nwbfile,
                                    session_object.stimulus_timestamps)

        # Add running acquisition ('dx', 'v_sig', 'v_in') data to NWB
        # This data should be saved to NWB but not accessible directly from
        # Sessions
        nwb.add_running_acquisition_to_nwbfile(
            nwbfile, session_object.api.get_running_acquisition_df())

        # Add running data to NWB in-memory object:
        nwb.add_running_speed_to_nwbfile(nwbfile,
                                         session_object.running_speed,
                                         name="speed",
                                         from_dataframe=True)
        nwb.add_running_speed_to_nwbfile(nwbfile,
                                         session_object.raw_running_speed,
                                         name="speed_unfiltered",
                                         from_dataframe=True)

        # Add stimulus template data to NWB in-memory object:
        # Use the semi-private _stimulus_templates attribute because it is
        # a StimulusTemplate object. The public stimulus_templates property
        # of the session_object returns a DataFrame.
        session_stimulus_templates = session_object._stimulus_templates
        self._add_stimulus_templates(
            nwbfile=nwbfile,
            stimulus_templates=session_stimulus_templates,
            stimulus_presentations=session_object.stimulus_presentations)

        # search for omitted rows and add stop_time before writing to NWB file
        set_omitted_stop_time(
            stimulus_table=session_object.stimulus_presentations)

        # Add stimulus presentations data to NWB in-memory object:
        nwb.add_stimulus_presentations(nwbfile,
                                       session_object.stimulus_presentations)

        # Add trials data to NWB in-memory object:
        nwb.add_trials(nwbfile, session_object.trials,
                       TRIAL_COLUMN_DESCRIPTION_DICT)

        # Add licks data to NWB in-memory object:
        if len(session_object.licks) > 0:
            nwb.add_licks(nwbfile, session_object.licks)

        # Add rewards data to NWB in-memory object:
        if len(session_object.rewards) > 0:
            nwb.add_rewards(nwbfile, session_object.rewards)

        # Add max_projection image data to NWB in-memory object:
        nwb.add_max_projection(nwbfile, session_object.max_projection)

        # Add average_image image data to NWB in-memory object:
        nwb.add_average_image(nwbfile, session_object.average_projection)

        # Add segmentation_mask_image image data to NWB in-memory object:
        nwb.add_segmentation_mask_image(nwbfile,
                                        session_object.segmentation_mask_image)

        # Add metadata to NWB in-memory object:
        nwb.add_metadata(nwbfile, session_object.metadata, behavior_only=False)

        # Add task parameters to NWB in-memory object:
        nwb.add_task_parameters(nwbfile, session_object.task_parameters)

        # Add roi metrics to NWB in-memory object:
        nwb.add_cell_specimen_table(nwbfile,
                                    session_object.cell_specimen_table,
                                    session_object.metadata)

        # Add dff to NWB in-memory object:
        nwb.add_dff_traces(nwbfile, session_object.dff_traces,
                           session_object.ophys_timestamps)

        # Add corrected_fluorescence to NWB in-memory object:
        nwb.add_corrected_fluorescence_traces(
            nwbfile, session_object.corrected_fluorescence_traces)

        # Add motion correction to NWB in-memory object:
        nwb.add_motion_correction(nwbfile, session_object.motion_correction)

        # Add eye tracking and rig geometry to NWB in-memory object
        # if eye_tracking data exists.
        if session_object.eye_tracking is not None:
            self.add_eye_tracking_data_to_nwb(
                nwbfile, session_object.eye_tracking,
                session_object.eye_tracking_rig_geometry)

        # Add events
        self.add_events(nwbfile=nwbfile, events=session_object.events)

        # Write the file:
        with NWBHDF5IO(self.path, 'w') as nwb_file_writer:
            nwb_file_writer.write(nwbfile)

        return nwbfile
示例#5
0
    def save(self, session_object):

        session_metadata: BehaviorMetadata = \
            session_object.api.get_metadata()

        session_type = str(session_metadata.session_type)

        nwbfile = NWBFile(
            session_description=session_type,
            identifier=str(session_object.behavior_session_id),
            session_start_time=session_metadata.date_of_acquisition,
            file_create_date=pytz.utc.localize(datetime.datetime.now()),
            institution="Allen Institute for Brain Science",
            keywords=["visual", "behavior", "task"],
            experiment_description=get_expt_description(session_type))

        # Add stimulus_timestamps to NWB in-memory object:
        nwb.add_stimulus_timestamps(nwbfile,
                                    session_object.stimulus_timestamps)

        # Add running acquisition ('dx', 'v_sig', 'v_in') data to NWB
        # This data should be saved to NWB but not accessible directly from
        # Sessions
        nwb.add_running_acquisition_to_nwbfile(
            nwbfile, session_object.api.get_running_acquisition_df())

        # Add running data to NWB in-memory object:
        nwb.add_running_speed_to_nwbfile(nwbfile,
                                         session_object.running_speed,
                                         name="speed",
                                         from_dataframe=True)
        nwb.add_running_speed_to_nwbfile(nwbfile,
                                         session_object.raw_running_speed,
                                         name="speed_unfiltered",
                                         from_dataframe=True)

        # Add stimulus template data to NWB in-memory object:
        # Use the semi-private _stimulus_templates attribute because it is
        # a StimulusTemplate object. The public stimulus_templates property
        # of the session_object returns a DataFrame.
        session_stimulus_templates = session_object._stimulus_templates
        self._add_stimulus_templates(
            nwbfile=nwbfile,
            stimulus_templates=session_stimulus_templates,
            stimulus_presentations=session_object.stimulus_presentations)

        # search for omitted rows and add stop_time before writing to NWB file
        set_omitted_stop_time(
            stimulus_table=session_object.stimulus_presentations)

        # Add stimulus presentations data to NWB in-memory object:
        nwb.add_stimulus_presentations(nwbfile,
                                       session_object.stimulus_presentations)

        # Add trials data to NWB in-memory object:
        nwb.add_trials(nwbfile, session_object.trials,
                       TRIAL_COLUMN_DESCRIPTION_DICT)

        # Add licks data to NWB in-memory object:
        if len(session_object.licks) > 0:
            nwb.add_licks(nwbfile, session_object.licks)

        # Add rewards data to NWB in-memory object:
        if len(session_object.rewards) > 0:
            nwb.add_rewards(nwbfile, session_object.rewards)

        # Add metadata to NWB in-memory object:
        nwb.add_metadata(nwbfile, session_object.metadata, behavior_only=True)

        # Add task parameters to NWB in-memory object:
        nwb.add_task_parameters(nwbfile, session_object.task_parameters)

        # Write the file:
        with NWBHDF5IO(self.path, 'w') as nwb_file_writer:
            nwb_file_writer.write(nwbfile)

        return nwbfile
示例#6
0
def create_nwb_file(session, nwb_file_path):
    logging.info('Staring session {}'.format(session.session_id))
    nwbfile = pynwb.NWBFile(
        session_description=session.description,
        session_id=str(session.session_id),
        identifier=str(session.experiment_metadata['experiment_id']),
        session_start_time=session.start_time,
        # experiment_description=str(session.experiment_metadata['experiment_id'])
    )

    ### Build the stimulus table ###
    stim_table_df = session.stimulus_table
    nwbfile = add_stimulus_timestamps(nwbfile,
                                      stim_table_df['start_time'].values)
    nwbfile = add_stimulus_presentations(nwbfile, stim_table_df)

    ### Image Segmentation ###
    optical_channel = pynwb.ophys.OpticalChannel(
        name='optical_channel',
        description='description',
        emission_lambda=session.emission_wavelength)

    # device = pynwb.device.Device(name='Allen Institute two-photon pipeline: {}'.format(session.session_metadata['rig']))
    device = pynwb.device.Device(
        name='{}'.format(session.session_metadata['rig']))
    nwbfile.add_device(device)

    imaging_plane = nwbfile.create_imaging_plane(
        name=session.imaging_plane_name,
        optical_channel=optical_channel,
        description=session.imaging_plane_name,
        device=device,
        excitation_lambda=session.excitation_lambda,
        imaging_rate=session.imaging_rate,
        indicator=session.calcium_indicator,
        location='area: {},depth: {}'.format(
            session.experiment_metadata["area"],
            str(session.experiment_metadata["depth"])),
        unit="Fluorescence (au)",
        reference_frame="Intrinsic imaging home",
    )

    ophys_module = nwbfile.create_processing_module(
        session.ophys_module_name,
        'contains optical physiology processed data')

    max_projection = pynwb.ophys.TwoPhotonSeries(
        name='max_project',
        data=[session.max_projection],
        imaging_plane=imaging_plane,
        dimension=[session.image_width, session.image_height],
        rate=30.0)
    # nwbfile.add_acquisition(max_projection)
    ophys_module.add_data_interface(max_projection)

    img_seg = pynwb.ophys.ImageSegmentation(
        name=session.image_segmentation_name)
    ophys_module.add_data_interface(img_seg)
    ps = img_seg.create_plane_segmentation(
        name=session.plane_segmentation_name,
        description=
        "Segmentation for imaging plane (de Vries et al., 2019, Nat Neurosci)",
        imaging_plane=imaging_plane,
    )

    for cell_id, roi_mask in session.roi_masks.items():
        ps.add_roi(id=cell_id, image_mask=roi_mask)

    rt_region = ps.create_roi_table_region(
        description="segmented cells with cell_specimen_ids")

    ### Fluorescence traces ###
    fluorescence = pynwb.ophys.Fluorescence()
    ophys_module.add_data_interface(fluorescence)

    timestamps = session.twop_timestamps
    raw_traces = session.raw_traces
    assert (raw_traces.shape[1] <= timestamps.shape[0])
    fluorescence.create_roi_response_series(
        name='raw_traces',
        data=raw_traces,
        rois=rt_region,
        unit='lumens',
        timestamps=timestamps[:raw_traces.shape[1]])

    neuropil_traces = session.neuropil_traces
    assert (neuropil_traces.shape[1] <= timestamps.shape[0])
    fluorescence.create_roi_response_series(
        name='neuropil_traces',
        data=neuropil_traces,
        rois=rt_region,
        unit='lumens',
        timestamps=timestamps[:neuropil_traces.shape[1]])

    demixed_traces = session.demixed_traces
    assert (demixed_traces.shape[1] <= timestamps.shape[0])
    fluorescence.create_roi_response_series(
        name='demixed_traces',
        data=demixed_traces,
        rois=rt_region,
        unit='lumens',
        timestamps=timestamps[:demixed_traces.shape[1]])

    dff_traces = session.dff_traces
    assert (dff_traces.shape[1] <= timestamps.shape[0])
    fluorescence.create_roi_response_series(
        name='DfOverF',
        data=session.dff_traces,
        rois=rt_region,
        unit='lumens',
        timestamps=timestamps[:dff_traces.shape[1]])

    ### Running Speed ###
    running_velocity = pynwb.base.TimeSeries(
        name="running_speed",
        data=session.running_velocity[:len(session.stimulus_timestamps)],
        timestamps=session.stimulus_timestamps,
        unit="cm/s",
        description="Speed of the mouse on a rotating wheel",
    )

    behavior_module = pynwb.behavior.BehavioralTimeSeries()
    behavior_module.add_timeseries(running_velocity)
    ophys_module.add_data_interface(behavior_module)

    ### Motion Correction ###
    # NOTE: Older versions of pynwb had a schema bug in ophys.CorrectedImageStack class that prevents it from being
    #       acccessed. Unfortunately currently allensdk has frozen pynwb at version 1.0.2. To fix we need to add the
    #       x/y corrections as a time series rather than using CorrectImageStack.
    # add_motion_correction_cis(session, ophys_module, nwbfile)
    add_motion_correction_pm(session, ophys_module, nwbfile)

    ### Subject and lab metadata ###
    sex_lu = {'F': 'female', 'M': 'male'}
    subject_metadata = pynwb.file.Subject(
        age=session.session_metadata['age'][1:],
        genotype=session.session_metadata['full_genotype'],
        sex=sex_lu.get(session.session_metadata['sex'], 'unknown'),
        species='Mus musculus',
        subject_id=str(session.session_metadata['specimen_id']),
        description=session.session_metadata['name'])
    nwbfile.subject = subject_metadata

    ### Events ###
    # add_events_discrete(session=session, ophys_module=ophys_module, nwbfile=nwbfile)
    add_events_contiguous(session=session,
                          ophys_module=ophys_module,
                          nwbfile=nwbfile,
                          roi_table=rt_region)

    # pd.set_option('display.max_columns', None)
    # nwbfile.units = pynwb.misc.Units.from_dataframe(session.units, name='units')
    # # events_indices = session.event_indices
    # nwbfile.units.add_column(
    #     name='event_times',
    #     data=session.event_times,
    #     index=session.event_indices,
    #     description='times (s) of detected L0 events'
    # )
    #
    # nwbfile.units.add_column(
    #     name='event_amplitudes',
    #     data=session.event_amps,
    #     index=session.event_indices,
    #     description='amplitudes (s) of detected L0 events'
    # )

    ### Eye Tracking ###
    eye_dlc_path = session.eye_dlc_screen_mapping
    if eye_dlc_path is not None:
        eye_gazing_data = read_eye_gaze_mappings(eye_dlc_path)
        add_eye_gaze_mapping_data_to_nwbfile(nwbfile, eye_gazing_data)

    with pynwb.NWBHDF5IO(str(nwb_file_path), mode="w") as io:
        io.write(nwbfile)
示例#7
0
def write_ecephys_nwb(output_path,
                      session_id,
                      session_start_time,
                      stimulus_table_path,
                      invalid_epochs,
                      probes,
                      running_speed_path,
                      session_sync_path,
                      eye_tracking_rig_geometry,
                      eye_dlc_ellipses_path,
                      eye_gaze_mapping_path,
                      pool_size,
                      optotagging_table_path=None,
                      session_metadata=None,
                      **kwargs):

    nwbfile = pynwb.NWBFile(
        session_description='Data and metadata for an Ecephys session',
        identifier=f"{session_id}",
        session_id=f"{session_id}",
        session_start_time=session_start_time,
        institution="Allen Institute for Brain Science")

    if session_metadata is not None:
        nwbfile = add_metadata_to_nwbfile(nwbfile, session_metadata)

    stimulus_columns_to_drop = [
        "colorSpace", "depth", "interpolate", "pos", "rgbPedestal", "tex",
        "texRes", "flipHoriz", "flipVert", "rgb", "signalDots"
    ]
    stimulus_table = read_stimulus_table(
        stimulus_table_path, columns_to_drop=stimulus_columns_to_drop)
    nwbfile = add_stimulus_timestamps(
        nwbfile, stimulus_table['start_time'].values
    )  # TODO: patch until full timestamps are output by stim table module
    nwbfile = add_stimulus_presentations(nwbfile, stimulus_table)
    nwbfile = add_invalid_times(nwbfile, invalid_epochs)

    if optotagging_table_path is not None:
        optotagging_table = pd.read_csv(optotagging_table_path)
        nwbfile = add_optotagging_table_to_nwbfile(nwbfile, optotagging_table)

    nwbfile = add_probewise_data_to_nwbfile(nwbfile, probes)

    running_speed, raw_running_data = read_running_speed(running_speed_path)
    add_running_speed_to_nwbfile(nwbfile, running_speed)
    add_raw_running_data_to_nwbfile(nwbfile, raw_running_data)

    add_eye_tracking_rig_geometry_data_to_nwbfile(nwbfile,
                                                  eye_tracking_rig_geometry)

    # Collect eye tracking/gaze mapping data from files
    eye_tracking_frame_times = su.get_synchronized_frame_times(
        session_sync_file=session_sync_path,
        sync_line_label_keys=Dataset.EYE_TRACKING_KEYS)
    eye_dlc_tracking_data = read_eye_dlc_tracking_ellipses(
        Path(eye_dlc_ellipses_path))
    if eye_gaze_mapping_path:
        eye_gaze_data = read_eye_gaze_mappings(Path(eye_gaze_mapping_path))
    else:
        eye_gaze_data = None

    add_eye_tracking_data_to_nwbfile(nwbfile, eye_tracking_frame_times,
                                     eye_dlc_tracking_data, eye_gaze_data)

    Manifest.safe_make_parent_dirs(output_path)
    with pynwb.NWBHDF5IO(output_path, mode='w') as io:
        logging.info(f"writing session nwb file to {output_path}")
        io.write(nwbfile, cache_spec=True)

    probes_with_lfp = [p for p in probes if p["lfp"] is not None]
    probe_outputs = write_probewise_lfp_files(probes_with_lfp,
                                              session_id,
                                              session_metadata,
                                              session_start_time,
                                              pool_size=pool_size)

    return {'nwb_path': output_path, "probe_outputs": probe_outputs}
示例#8
0
def write_ecephys_nwb(output_path,
                      session_id,
                      session_start_time,
                      stimulus_table_path,
                      invalid_epochs,
                      probes,
                      running_speed_path,
                      session_sync_path,
                      eye_tracking_rig_geometry,
                      eye_dlc_ellipses_path,
                      eye_gaze_mapping_path,
                      pool_size,
                      optotagging_table_path=None,
                      session_metadata=None,
                      **kwargs):

    nwbfile = pynwb.NWBFile(session_description='EcephysSession',
                            identifier='{}'.format(session_id),
                            session_start_time=session_start_time)

    if session_metadata is not None:
        nwbfile = add_metadata_to_nwbfile(nwbfile, session_metadata)

    stimulus_table = read_stimulus_table(stimulus_table_path)
    nwbfile = add_stimulus_timestamps(
        nwbfile, stimulus_table['start_time'].values
    )  # TODO: patch until full timestamps are output by stim table module
    nwbfile = add_stimulus_presentations(nwbfile, stimulus_table)
    nwbfile = add_invalid_times(nwbfile, invalid_epochs)

    if optotagging_table_path is not None:
        optotagging_table = pd.read_csv(optotagging_table_path)
        nwbfile = add_optotagging_table_to_nwbfile(nwbfile, optotagging_table)

    nwbfile = add_probewise_data_to_nwbfile(nwbfile, probes)

    running_speed, raw_running_data = read_running_speed(running_speed_path)
    add_running_speed_to_nwbfile(nwbfile, running_speed)
    add_raw_running_data_to_nwbfile(nwbfile, raw_running_data)

    # --- Add eye tracking ellipse fits to nwb file ---
    eye_tracking_frame_times = su.get_synchronized_frame_times(
        session_sync_file=session_sync_path,
        sync_line_label_keys=Dataset.EYE_TRACKING_KEYS)
    eye_dlc_tracking_data = read_eye_dlc_tracking_ellipses(
        Path(eye_dlc_ellipses_path))

    if eye_tracking_data_is_valid(eye_dlc_tracking_data=eye_dlc_tracking_data,
                                  synced_timestamps=eye_tracking_frame_times):
        add_eye_tracking_ellipse_fit_data_to_nwbfile(
            nwbfile,
            eye_dlc_tracking_data=eye_dlc_tracking_data,
            synced_timestamps=eye_tracking_frame_times)

        # --- Append eye tracking rig geometry info to nwb file (with eye tracking) ---
        append_eye_tracking_rig_geometry_data_to_nwbfile(
            nwbfile, eye_tracking_rig_geometry=eye_tracking_rig_geometry)

        # --- Add gaze mapped positions to nwb file ---
        if eye_gaze_mapping_path:
            eye_gaze_data = read_eye_gaze_mappings(Path(eye_gaze_mapping_path))
            add_eye_gaze_mapping_data_to_nwbfile(nwbfile,
                                                 eye_gaze_data=eye_gaze_data)

    Manifest.safe_make_parent_dirs(output_path)
    io = pynwb.NWBHDF5IO(output_path, mode='w')
    logging.info(f"writing session nwb file to {output_path}")
    io.write(nwbfile)
    io.close()

    probes_with_lfp = [p for p in probes if p["lfp"] is not None]
    probe_outputs = write_probewise_lfp_files(probes_with_lfp,
                                              session_start_time,
                                              pool_size=pool_size)

    return {'nwb_path': output_path, "probe_outputs": probe_outputs}