Tobii Pro SDK Python API
calibration_during_gaze_recording.py
1 """
2 This example is more comprehensive than most, as it aims to illustrate
3 how you can put together a complete, ready-to-run script, by combining
4 information from different examples. Note that running this script
5 will produce a small CSV data file in the parent directory.
6 """
7 import csv
8 import sys
9 import time
10 
11 import tobii_research as tr
12 
13 # <BeginExample>
14 # Enter the address to the desired eye tracker if you have
15 # more than one (otherwise leave this as an empty string).
16 # (example address: "tobii-prp://TPFC2-100123456789")
17 EYETRACKER_ADDRESS = ""
18 
19 # This list will be filled with dictionaries, with one dictionary per
20 # recording sample.
21 gaze_data_samples = []
22 
23 def get_eyetracker():
24  # Check if a specific eye tracker address has been provided, and if so,
25  # try to locate it and return the corresponding eye tracker object.
26  if EYETRACKER_ADDRESS:
27  eyetracker = tr.EyeTracker(EYETRACKER_ADDRESS)
28  if not eyetracker:
29  sys.exit("Specified eye tracker not found, please check the address.")
30  return eyetracker
31  # If we reach this point, no specific address was provided, so return
32  # the first found eye tracker.
33  all_eyetrackers = tr.find_all_eyetrackers()
34  if not all_eyetrackers:
35  sys.exit(
36  "No connected eye trackers found. Please check the connection "
37  "and/or install any missing drivers with Tobii Pro Eye Tracker Manager."
38  )
39  return all_eyetrackers[0]
40 
41 def gaze_data_callback(gaze_data):
42  # Note that no data processing or saving to file is done here,
43  # as stream callbacks should normally be kept minimal (to ensure
44  # that the system is able to keep up with the eye tracker)
45  # unless you need to e.g. provide real-time feedback.
46  global gaze_data_samples
47  gaze_data_samples.append(gaze_data)
48 
49 def calibrate(eyetracker):
50  calibration = tr.ScreenBasedCalibration(eyetracker)
51 
52  # Enter calibration mode.
53  calibration.enter_calibration_mode()
54  print("Entered calibration mode for eye tracker with serial number {0}.".format(eyetracker.serial_number))
55 
56  # Define the points on screen we should calibrate at.
57  # The coordinates are normalized, i.e. (0.0, 0.0) is the upper left corner and (1.0, 1.0) is the lower right corner.
58  points_to_calibrate = [(0.5, 0.5), (0.1, 0.1), (0.1, 0.9), (0.9, 0.1), (0.9, 0.9)]
59 
60  for point in points_to_calibrate:
61  # Normally, you would use a Python package like pygame, PsychoPy or PIL to present an image
62  # indicating the calibration point here.
63  print("Show a point on screen at {0}.".format(point))
64 
65  # Wait a little for user to focus.
66  time.sleep(0.7)
67 
68  print("Collecting data at {0}.".format(point))
69  if calibration.collect_data(point[0], point[1]) != tr.CALIBRATION_STATUS_SUCCESS:
70  # Try again if it didn't go well the first time.
71  # Not all eye tracker models will fail at this point, but instead fail on ComputeAndApply.
72  calibration.collect_data(point[0], point[1])
73 
74  print("Computing and applying calibration.")
75  calibration_result = calibration.compute_and_apply()
76  print("Compute and apply returned {0} and collected at {1} points.".
77  format(calibration_result.status, len(calibration_result.calibration_points)))
78 
79  # Analyze the data and maybe remove points that weren't good.
80  recalibrate_point = (0.1, 0.1)
81  print("Removing calibration point at {0}.".format(recalibrate_point))
82  calibration.discard_data(recalibrate_point[0], recalibrate_point[1])
83 
84  # Redo collection at the discarded point
85  print("Show a point on screen at {0}.".format(recalibrate_point))
86  calibration.collect_data(recalibrate_point[0], recalibrate_point[1])
87 
88  # Compute and apply again.
89  print("Computing and applying calibration.")
90  calibration_result = calibration.compute_and_apply()
91  print("Compute and apply returned {0} and collected at {1} points.".
92  format(calibration_result.status, len(calibration_result.calibration_points)))
93 
94  # See that you're happy with the result.
95 
96  # The calibration is done. Leave calibration mode.
97  calibration.leave_calibration_mode()
98 
99  print("Left calibration mode.")
100 
101 def save_gaze_data(gaze_samples_list):
102  if not gaze_samples_list:
103  print("No gaze samples were collected. Skipping saving")
104  return
105  # To show what kinds of data are available in each sample's dictionary,
106  # we print the available keys here.
107  print("Sample dictionary keys:", gaze_samples_list[0].keys())
108  # This is meant to serve as a simple example of how you can save
109  # some of the gaze data - check the keys to see what else is available.
110  file_handle = open("my_gaze_data.csv", "w")
111  gaze_writer = csv.writer(file_handle)
112  gaze_writer.writerow(["time_seconds", "left_x", "left_y", "right_x", "right_y"])
113  start_time = gaze_samples_list[0]["system_time_stamp"]
114  for recording_dict in gaze_samples_list:
115  sample_time_from_start = recording_dict["system_time_stamp"] - start_time
116  # convert from microseconds to seconds
117  sample_time_from_start = sample_time_from_start / (10**(6))
118  # x is horizontal coordinate on the screen
119  # y is vertical coordinate on the screen
120  left_x, left_y = recording_dict["left_gaze_point_on_display_area"]
121  right_x, right_y = recording_dict["right_gaze_point_on_display_area"]
122  gaze_writer.writerow(
123  [sample_time_from_start, left_x, left_y, right_x, right_y]
124  )
125  file_handle.close()
126 
127 def main():
128  eyetracker = get_eyetracker()
129  # Start gaze recording.
130  print("Subscribing to gaze data for eye tracker with serial number {0}.".format(eyetracker.serial_number))
131  eyetracker.subscribe_to(tr.EYETRACKER_GAZE_DATA, gaze_data_callback, as_dictionary=True)
132  calibrate(eyetracker)
133  # End gaze recording.
134  print("Unsubscribing from gaze data for eye tracker with serial number {0}.".format(eyetracker.serial_number))
135  eyetracker.unsubscribe_from(tr.EYETRACKER_GAZE_DATA, gaze_data_callback)
136  # Save the data, now that all recording is finished.
137  save_gaze_data(gaze_data_samples)
138 
139 # <EndExample>
140 
141 # This is a Python convention used with scripts that are to be run directly
142 # (rather than only being used by other scripts).
143 if __name__ == "__main__":
144  main()