import zmq import time def main(): """ This example performs these steps: 1. Setup network connection (always required) 2. Setup "local" clock function (always required) 3. Measure clock offset once 4. Measure clock offset more reliably to account for network latency variance (multiple measurements) 5. Infer remote pupil clock time from local clock measurement """ # 1. Setup network connection (always required) socket = setup_pupil_remote_connection(ip_adress="127.0.0.1") # 2. Setup local clock function (always required) # local_clock = time.time # Unix time, less accurate # local_clock = psychopy.clock.MonotonicClock().getTime # new psychopy clock # local_clock = existing_psychopy_clock.getTime # existing psychopy clock local_clock = time.perf_counter # 3. Measure clock offset once offset = measure_clock_offset(socket, clock_function=local_clock) print(f"Clock offset (1 measurement): {offset} seconds") # 4. Measure clock offset more reliably to account for network # latency variance (multiple measurements) number_of_measurements = 10 stable_offset_mean = measure_clock_offset_stable( socket, clock_function=local_clock, nsamples=number_of_measurements ) print( f"Mean clock offset ({number_of_measurements} measurements): " f"{stable_offset_mean} seconds" ) # 5. Infer pupil clock time from "local" clock measurement local_time = local_clock() pupil_time_calculated_locally = local_time + stable_offset_mean print(f"Local time: {local_time}") print(f"Pupil time (calculated locally): {pupil_time_calculated_locally}") def setup_pupil_remote_connection( ip_adress: str = "127.0.0.1", port: int = 50020 ) -> zmq.Socket: """Creates a zmq-REQ socket and connects it to Pupil Capture or Service See https://docs.pupil-labs.com/developer/core/network-api/ for details. """ ctx = zmq.Context.instance() socket = ctx.socket(zmq.REQ) socket.connect(f"tcp://{ip_adress}:{port}") return socket def request_pupil_time(socket): """Uses an existing Pupil Core software connection to request the remote time. Returns the current "pupil time" at the timepoint of reception. See https://docs.pupil-labs.com/core/terminology/#pupil-time for more information about "pupil time". """ socket.send_string("t") pupil_time = socket.recv() return float(pupil_time) def measure_clock_offset(socket, clock_function): """Calculates the offset between the Pupil Core software clock and a local clock. Requesting the remote pupil time takes time. This delay needs to be considered when calculating the clock offset. We measure the local time before (A) and after (B) the request and assume that the remote pupil time was measured at (A+B)/2, i.e. the midpoint between A and B. As a result, we have two measurements from two different clocks that were taken assumingly at the same point in time. The difference between them ("clock offset") allows us, given a new local clock measurement, to infer the corresponding time on the remote clock. """ local_time_before = clock_function() pupil_time = request_pupil_time(socket) local_time_after = clock_function() local_time = (local_time_before + local_time_after) / 2.0 clock_offset = pupil_time - local_time return clock_offset def measure_clock_offset_stable(socket, clock_function, nsamples=10): """Returns the mean clock offset after multiple measurements to reduce the effect of varying network delay. Since the network connection to Pupil Capture/Service is not necessarily stable, one has to assume that the delays to send and receive commands are not symmetrical and might vary. To reduce the possible clock-offset estimation error, this function repeats the measurement multiple times and returns the mean clock offset. The variance of these measurements is expected to be higher for remote connections (two different computers) than for local connections (script and Core software running on the same computer). You can easily extend this function to perform further statistical analysis on your clock-offset measurements to examine the accuracy of the time sync. """ assert nsamples > 0, "Requires at least one sample" offsets = [measure_clock_offset(socket, clock_function) for x in range(nsamples)] return sum(offsets) / len(offsets) # mean offset if __name__ == "__main__": main() ctx = zmq.Context() pupil_remote = zmq.Socket(ctx, zmq.REQ) pupil_remote.connect('tcp://127.0.0.1:50020') #Start Recording pupil_remote.send_string('R') print(pupil_remote.recv_string()) #Start Calibration pupil_remote.send_string('C') print(pupil_remote.recv_string()) #Wait till calibration completion wait_time = 40 print(f"Waiting for {40} seconds before continuing...") time.sleep(40) print("Wait time complete. Continuing...") #End Calibration pupil_remote.send_string('c') print(pupil_remote.recv_string()) #Python Interface Run Experiment import PresPy pc = PresPy.Presentation_control() pc.open_experiment(r"C:\Users\ploizo01\OneDrive - University of Cyprus\Desktop\LAB FILES\Stimuli directory (2) final\SCENARIOS FINAL\Food_Low-Hightest") scen = pc.run_experiment(0) del pc #Stop Recording pupil_remote.send_string('r') print(pupil_remote.recv_string())