Example #1
0
def PlotPupilometry(csv_file, plot_png):
    '''
    Read pupilometry CSV and plot timeseries
    '''

    if not os.path.isfile(csv_file):
        print('* Pupilometry file not found - returning')
        return False

    # Load pupilometry data from CSV file
    p = engine.ReadPupilometry(csv_file)

    # Extract timeseries
    t = p[:, 0]
    area = p[:, 1]
    px, py = p[:, 2], p[:, 3]
    blink = p[:, 4]
    art = p[:, 5]

    # Downsample if total samples > 2000
    nt = p.shape[0]
    if nt > 2000:
        dt = int(nt / 2000.0)
        inds = np.arange(0, nt, dt)
        p = p[inds, :]

    # Create figure, plot all timeseries in subplots
    fig = plt.figure(figsize=(6, 8))

    ax = fig.add_subplot(411)
    ax.plot(t, area)
    ax.set_title('Corrected Pupil Area', y=1.1, fontsize=8)
    ax.tick_params(axis='both', labelsize=8)

    ax = fig.add_subplot(412)
    ax.plot(t, px, 'g', label='X')
    ax.plot(t, py, 'b', label='Y')
    ax.set_title('Pupil Center', y=1.1, fontsize=8)
    ax.tick_params(axis='both', labelsize=8)
    ax.legend(shadow=False, prop={'size': 6}, labelspacing=0.25)

    ax = fig.add_subplot(413)
    ax.plot(t, blink)
    ax.set_ylim([-0.1, 1.1])
    ax.set_title('Blink', y=1.1, fontsize=8)
    ax.tick_params(axis='both', labelsize=8)

    ax = fig.add_subplot(414)
    ax.plot(t, art)
    ax.set_title('Artifact Power', y=1.1, fontsize=8)
    ax.tick_params(axis='both', labelsize=8)

    # Pack all subplots and labels tightly
    fig.subplots_adjust(hspace=0.6)

    # Save figure to file in same directory as CSV file
    plt.savefig(plot_png, dpi=150, bbox_inches='tight')

    # Close figure without showing it
    plt.close(fig)
Example #2
0
def ArtifactStartTime(csv_file):
    '''
    Estimate the time of the first artifact
    '''

    if not os.path.isfile(csv_file):
        print('* Pupilometry file not found - returning')
        return False

    # Load pupilometry data from CSV file
    p = engine.ReadPupilometry(csv_file)

    # Extract time and artifact power vectors
    t, art   = p[:,0], p[:,5]

    # Threshold at median artifact power distribution
    art_on = art > np.median(art)

    # Time in seconds corresponding to first detected artifact
    art_t0 = t[np.argmax(art_on)]

    return art_t0
Example #3
0
def AutoCalibrate(ss_res_dir, cfg):
    '''
    Automatic calibration transform from pupil center timeseries
    '''

    # Get fixation heatmap percentile limits and Gaussian blur sigma
    pmin = cfg.getfloat('CALIBRATION', 'heatpercmin')
    pmax = cfg.getfloat('CALIBRATION', 'heatpercmax')
    plims = (pmin, pmax)
    sigma = cfg.getfloat('CALIBRATION', 'heatsigma')

    # Get target coordinates
    targetx = json.loads(cfg.get('CALIBRATION', 'targetx'))
    targety = json.loads(cfg.get('CALIBRATION', 'targety'))

    # Gaze space target coordinates (n x 2)
    targets = np.array([targetx, targety]).transpose()

    # Calibration pupilometry file
    cal_pupils_csv = os.path.join(ss_res_dir, 'cal_pupils.csv')

    if not os.path.isfile(cal_pupils_csv):
        print('* Calibration pupilometry not found - returning')
        return False

    # Read raw pupilometry data
    p = engine.ReadPupilometry(cal_pupils_csv)

    # Extract useful timeseries
    t = p[:, 0]  # Video soft timestamp
    px = p[:, 2]  # Video pupil center, x
    py = p[:, 3]  # Video pupil center, y
    blink = p[:, 4]  # Video blink

    # Remove NaNs (blinks, etc) from t, x and y
    ok = np.where(blink == 0)
    t, x, y = t[ok], px[ok], py[ok]

    # Find spatial fixations and sort temporally
    # Returns heatmap with axes
    fixations, hmap, xedges, yedges = FindFixations(x, y, plims, sigma)

    # Temporally sort fixations - required for matching to targets
    fixations = SortFixations(t, x, y, fixations)

    # Plot labeled calibration heatmap to results directory
    PlotCalibration(ss_res_dir, hmap, xedges, yedges, fixations)

    # Check for autocalibration problems
    n_targets = targets.shape[0]
    n_fixations = fixations.shape[0]

    if n_targets == n_fixations:

        # Compute calibration mapping video to gaze space
        C = CalibrationModel(fixations, targets)

        # Determine central fixation coordinate in video space
        central_fix = CentralFixation(fixations, targets)

        # Write calibration results to CSV files in the results subdir
        WriteCalibration(ss_res_dir, fixations, C, central_fix)

    else:

        print(
            '* Number of detected fixations (%d) and targets (%d) differ - exiting'
            % (n_fixations, n_targets))

        # Return empty/dummy values
        C = np.array([])
        central_fix = 0.0, 0.0

    return C, central_fix
Example #4
0
def ApplyCalibration(ss_dir, C, central_fix, cfg):
    '''
    Apply calibration transform to gaze pupil center timeseries

    - apply motion correction if requested (highpass or known fixations)
    - Save calibrated gaze to text file in results directory

    Arguments
    ----

    Returns
    ----
    '''

    print('  Calibrating pupilometry timeseries')

    # Uncalibrated gaze pupilometry file
    gaze_uncal_csv = os.path.join(ss_dir, 'results', 'gaze_pupils.csv')

    # Known central fixations file
    fixations_txt = os.path.join(ss_dir, 'videos', 'fixations.txt')

    if not os.path.isfile(gaze_uncal_csv):
        print('* Uncalibrated gaze pupilometry not found - returning')
        return False

    # Read raw pupilometry data
    p = engine.ReadPupilometry(gaze_uncal_csv)

    # Extract useful timeseries
    t = p[:, 0]  # Video soft timestamp
    x = p[:, 2]  # Pupil x
    y = p[:, 3]  # Pupil y

    # Retrospective motion correction - only use when consistent glint is unavailable
    motioncorr = cfg.get('ARTIFACTS', 'motioncorr')
    mocokernel = cfg.getint('ARTIFACTS', 'mocokernel')

    if motioncorr == 'knownfixations':

        print('  Motion correction using known fixations')
        print('  Central fixation at (%0.3f, %0.3f)' %
              (central_fix[0], central_fix[1]))

        x, y, bx, by = moco.KnownFixations(t, x, y, fixations_txt, central_fix)

    elif motioncorr == 'highpass':

        print('  Motion correction by high pass filtering (%d sample kernel)' %
              mocokernel)
        print('  Central fixation at (%0.3f, %0.3f)' %
              (central_fix[0], central_fix[1]))

        x, y, bx, by = moco.HighPassFilter(t, x, y, mocokernel, central_fix)

    elif motioncorr == 'glint':
        print(
            '  Using glint for motion correction. Skipping here, in calibrate.py (for now)'
        )

        # Return dummy x and y baseline estimates
        bx, by = np.zeros_like(x), np.zeros_like(y)

    else:

        print('* Unknown motion correction requested (%s) - skipping' %
              (motioncorr))

        # Return dummy x and y baseline estimates
        bx, by = np.zeros_like(x), np.zeros_like(y)

    # Additional binomial coordinates
    xx = x * x
    yy = y * y
    xy = x * y

    # Construct R
    R = np.array((xx, xy, yy, x, y, np.ones_like(x)))

    # Apply calibration transform to pupil-glint vector timeseries
    # (2 x n) = (2 x 6) x (6 x n)
    gaze = C.dot(R)

    # Write calibrated gaze to CSV file in results directory
    gaze_csv = os.path.join(ss_dir, 'results', 'gaze_calibrated.csv')
    WriteGaze(gaze_csv, t, gaze[0, :], gaze[1, :], bx, by)

    return True