|
import copy |
|
import eyekit as ek |
|
import numpy as np |
|
import pandas as pd |
|
from PIL import Image |
|
|
|
|
|
MEASURES_DICT = { |
|
"number_of_fixations": [], |
|
"initial_fixation_duration": [], |
|
"first_of_many_duration": [], |
|
"total_fixation_duration": [], |
|
"gaze_duration": [], |
|
"go_past_duration": [], |
|
"second_pass_duration": [], |
|
"initial_landing_position": [], |
|
"initial_landing_distance": [], |
|
"landing_distances": [], |
|
"number_of_regressions_in": [], |
|
} |
|
|
|
|
|
def get_fix_seq_and_text_block( |
|
dffix, |
|
trial, |
|
x_txt_start=None, |
|
y_txt_start=None, |
|
font_face="Courier New", |
|
font_size=None, |
|
line_height=None, |
|
use_corrected_fixations=True, |
|
correction_algo="warp", |
|
): |
|
if use_corrected_fixations and correction_algo is not None: |
|
fixations_tuples = [ |
|
( |
|
(x[1]["x"], x[1][f"y_{correction_algo}"], x[1]["corrected_start_time"], x[1]["corrected_end_time"]) |
|
if x[1]["corrected_start_time"] < x[1]["corrected_end_time"] |
|
else (x[1]["x"], x[1]["y"], x[1]["corrected_start_time"], x[1]["corrected_end_time"] + 1) |
|
) |
|
for x in dffix.iterrows() |
|
] |
|
else: |
|
fixations_tuples = [ |
|
( |
|
(x[1]["x"], x[1]["y"], x[1]["corrected_start_time"], x[1]["corrected_end_time"]) |
|
if x[1]["corrected_start_time"] < x[1]["corrected_end_time"] |
|
else (x[1]["x"], x[1]["y"], x[1]["corrected_start_time"], x[1]["corrected_end_time"] + 1) |
|
) |
|
for x in dffix.iterrows() |
|
] |
|
|
|
try: |
|
fixation_sequence = ek.FixationSequence(fixations_tuples) |
|
except Exception as e: |
|
print(e) |
|
print(f"Creating fixation failed for {trial['trial_id']} {trial['filename']}") |
|
return dffix |
|
|
|
if "display_coords" in trial: |
|
display_coords = trial["display_coords"] |
|
else: |
|
display_coords = (0, 0, 1920, 1080) |
|
screen_size = ((display_coords[2] - display_coords[0]), (display_coords[3] - display_coords[1])) |
|
|
|
y_diffs = np.unique(trial["line_heights"]) |
|
if len(y_diffs) == 1: |
|
y_diff = y_diffs[0] |
|
else: |
|
y_diff = np.min(y_diffs) |
|
chars_list = trial["chars_list"] |
|
max_line = int(chars_list[-1]["assigned_line"]) |
|
words_on_lines = {x: [] for x in range(int(max_line) + 1)} |
|
[words_on_lines[x["assigned_line"]].append(x["char"]) for x in chars_list] |
|
sentence_list = ["".join([s for s in v]) for idx, v in words_on_lines.items()] |
|
|
|
if x_txt_start is None: |
|
x_txt_start = float(chars_list[0]["char_xmin"]) |
|
if y_txt_start is None: |
|
y_txt_start = float(chars_list[0]["char_ymax"]) |
|
|
|
if font_face is None and "font" in trial: |
|
font_face = trial["font"] |
|
elif font_face is None: |
|
font_face = "DejaVu Sans Mono" |
|
|
|
if font_size is None and "font_size" in trial: |
|
font_size = trial["font_size"] |
|
elif font_size is None: |
|
font_size = float(y_diff * 0.333) |
|
if line_height is None: |
|
line_height = float(y_diff) |
|
textblock = ek.TextBlock( |
|
sentence_list, |
|
position=(float(x_txt_start), float(y_txt_start)), |
|
font_face=font_face, |
|
line_height=line_height, |
|
font_size=font_size, |
|
anchor="left", |
|
align="left", |
|
) |
|
|
|
|
|
ek.io.save(fixation_sequence, f'results/fixation_sequence_eyekit_{trial["trial_id"]}.json', compress=False) |
|
ek.io.save(textblock, f'results/textblock_eyekit_{trial["trial_id"]}.json', compress=False) |
|
|
|
return fixation_sequence, textblock, screen_size |
|
|
|
|
|
def eyekit_plot(textblock, fixation_sequence, screen_size): |
|
img = ek.vis.Image(*screen_size) |
|
img.draw_text_block(textblock) |
|
for word in textblock.words(): |
|
img.draw_rectangle(word, color="hotpink") |
|
img.draw_fixation_sequence(fixation_sequence) |
|
img.save("temp_eyekit_img.png", crop_margin=200) |
|
img_png = Image.open("temp_eyekit_img.png") |
|
return img_png |
|
|
|
|
|
def plot_with_measure(textblock, fixation_sequence, screen_size, measure, use_characters=False): |
|
|
|
eyekitplot_img = eyekit_plot(textblock, fixation_sequence, screen_size) |
|
eyekitplot_img = ek.vis.Image(*screen_size) |
|
eyekitplot_img.draw_text_block(textblock) |
|
if use_characters: |
|
measure_results = getattr(ek.measure, measure)(textblock.characters(), fixation_sequence) |
|
enum = textblock.characters() |
|
else: |
|
measure_results = getattr(ek.measure, measure)(textblock.words(), fixation_sequence) |
|
enum = textblock.words() |
|
for word in enum: |
|
eyekitplot_img.draw_rectangle(word, color="lightseagreen") |
|
x = word.onset |
|
y = word.y_br - 3 |
|
label = f"{measure_results[word.id]}" |
|
eyekitplot_img.draw_annotation((x, y), label, color="lightseagreen", font_face="Arial bold", font_size=15) |
|
eyekitplot_img.draw_fixation_sequence(fixation_sequence, color="gray") |
|
eyekitplot_img.save("multiline_passage_piccol.png", crop_margin=100) |
|
img_png = Image.open("multiline_passage_piccol.png") |
|
return img_png |
|
|
|
|
|
def get_eyekit_measures(_txt, _seq, get_char_measures=False): |
|
measures = copy.deepcopy(MEASURES_DICT) |
|
words = [] |
|
for w in _txt.words(): |
|
words.append(w.text) |
|
for m in measures.keys(): |
|
measures[m].append(getattr(ek.measure, m)(w, _seq)) |
|
word_measures_df = pd.DataFrame(measures) |
|
word_measures_df["word_number"] = np.arange(0, len(words)) |
|
word_measures_df["word"] = words |
|
|
|
first_column = word_measures_df.pop("word") |
|
word_measures_df.insert(0, "word", first_column) |
|
first_column = word_measures_df.pop("word_number") |
|
word_measures_df.insert(0, "word_number", first_column) |
|
|
|
if get_char_measures: |
|
measures = copy.deepcopy(MEASURES_DICT) |
|
|
|
characters = [] |
|
for c in _txt.characters(): |
|
characters.append(c.text) |
|
for m in measures.keys(): |
|
measures[m].append(getattr(ek.measure, m)(c, _seq)) |
|
character_measures_df = pd.DataFrame(measures) |
|
character_measures_df["char_number"] = np.arange(0, len(characters)) |
|
character_measures_df["character"] = characters |
|
|
|
first_column = character_measures_df.pop("character") |
|
character_measures_df.insert(0, "character", first_column) |
|
first_column = character_measures_df.pop("char_number") |
|
character_measures_df.insert(0, "char_number", first_column) |
|
else: |
|
character_measures_df = None |
|
return word_measures_df, character_measures_df |
|
|