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)
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
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
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