Automated pupillometer with STLAB

Automated pupillometers are the standard instruments for measuring the PLR. These handheld devices are aimed at a person’s eye to deliver a light stimulus and use infrared video recording and internal algorithms to provide an instant readout of the PLR and its associated parameters. Our own NeurOptics PLR-3000 is one such device, and it does an absolutely fantastic job. The STLAB-integrating sphere rig is no match for its compactness, portability and ease of use, but we figured at least that we could make it function in a similar way.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import os.path as op
from time import sleep

import numpy as np

from pyplr.stlab import SpectraTuneLab
from pyplr.pupil import PupilCore
from pyplr.utils import unpack_data_pandas
from pyplr.preproc import butterworth_series
from pyplr.plr import PLR
from pyplr.protocol import (input_subject_id_gui,
                            subject_dir,
                            record_dir)
def main(subject_id=None,
         baseline=2.,
         duration=8.,
         sample_rate=120,
         stimulus='./stimuli/PLR-3000-180-mw.dsf',
         record=True,
         control=False,
         config=None):

    # Set up subject and recording
    if subject_id is None:
        subject_id = input_subject_id_gui()
    subj_dir = subject_dir(subject_id)
    rec_dir = record_dir(subj_dir)

    # Set up Pupil Core
    p = PupilCore()

    # Setup STLAB
    d = SpectraTuneLab(password='****************')
    d.load_video_file(stimulus)

    # Prepare annotation
    annotation = p.new_annotation('LIGHT_ON')

    if control:
        input("Press Enter to administer stimulus...")

    if record:
        p.command('R {}'.format(rec_dir))
        sleep(1.)

    # Start light_stamper and pupil_grabber
    lst_future = p.light_stamper(
        annotation, threshold=15, timeout=6.)
    pgr_future = p.pupil_grabber(
        topic='pupil.1.3d', seconds=duration+baseline+2)

    # Baseline period
    sleep(baseline)

    # Present Stimulus
    d.play_video_file()

    # Wait for futures
    while lst_future.running() or pgr_future.running():
        print('Waiting for futures...')
        sleep(1)

    if record:
        p.command('r')

    if not lst_future.result()[0]:
        print('light was not detected. Ending program.')
        sys.exit(0)

    # Retrieve and process pupil data
    data = unpack_data_pandas(
        pgr_future.result(), cols=['timestamp','diameter_3d'])
    data = butterworth_series(
        data,
        filt_order=3,
        cutoff_freq=4/(sample_rate/2),
        fields=['diameter_3d'])

    # Get light_stamper timestamp
    ts = lst_future.result()[1]

    # Find the closest timestamp in the pupil data
    idx = (np.abs(ts - data.index)).argmin()

    # Trim
    start = int(idx-(baseline*sample_rate))
    end = int(idx+(duration*sample_rate))
    data = data.iloc[start:end]
    data.reset_index(inplace=True)

    # Analyse PLR
    plr = PLR(plr=data.diameter_3d.to_numpy(),
              sample_rate=sample_rate,
              onset_idx=idx,
              stim_duration=1)
    plr.parameters().to_csv(op.join(rec_dir, 'plr_parameters.csv'))
    plr.plot().savefig(op.join(rec_dir, 'plr_plot.png'), bbox_inches='tight')
    data.to_csv(op.join(rec_dir, 'raw_data.csv'))

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print('Killed by user')
        sys.exit(0)