Source code for nicetoolbox.detectors.feature_detectors.gaze_interaction.utils

"""
Utility functions for visualizing gaze interaction data.
"""

import os

import matplotlib.pyplot as plt
import numpy as np


[docs]def visualize_gaze_interaction(data, categories_list, output_folder, people_names=None, camera_names=None) -> None: """ Visualizes the gaze interaction results across frames for multiple people and cameras. Args: data (ndarray): The input data array of shape (distances, look_at, mutual). categories_list (list): The list of category names (i.e., ["distance_gaze_face", "gaze_look_at", "gaze_mutual"]). output_folder (str): The path to the output folder where the plots will be saved. people_names (list, optional): The list of names for each person. Defaults to None. camera_names (list, optional): The list of names for each camera. Defaults to None. Returns: None """ num_people = len(data) for camera_idx in range(data.shape[1]): camera_name = camera_names[camera_idx] if camera_names is not None else "3d" unit = "(in pixels)" if camera_name != "3d" else "(real-world units: m/cm/mm)" _, axs = plt.subplots(num_people, 1, figsize=(10, 15)) # Ensure axs is a list in case num_people is 1 if num_people == 1: axs = [axs] gaze_distance_max = 0 for i, (ax, dat) in enumerate(zip(axs, data)): # create secondary axis for boolean values ax2 = ax.twinx() gaze_distance_max = np.nanmax(dat[camera_idx, :, 0]) for j, category in enumerate(categories_list): # first category is gaze distance and it is continuous if j == 0: ax.plot( dat[camera_idx, :, j], label=f"{category} (left axis)", color="tab:blue", ) # other categories are boolean elif j == 1 or j == 2: x = (np.arange(dat.shape[1]),) y = (dat[camera_idx, :, j],) ax2.scatter( x, y, label=f"{category} (right axis)", marker="_" if j == 1 else ".", color="tab:orange" if j == 1 else "tab:green", alpha=0.5, zorder=2 if j == 1 else 1, s=1 if gaze_distance_max > 100 else 5, ) if people_names is None: people_names = ["PersonL", "PersonR"] ax.set_title(f"Gaze Interaction Across Frames - {people_names[i]}") ax.set_xlabel("Frame Index") ax.set_ylabel(f"Gaze Distance to the Other Person’s Face \n {unit}") ax2.set_ylabel("True/False (1/0)") if gaze_distance_max < 5: increment = 0.2 elif gaze_distance_max < 100: increment = 2 else: increment = 20 # Set specific ticks for axis ax.set_ylim(-0.1, gaze_distance_max + increment) yticks = np.arange(0, gaze_distance_max + increment, increment) ax.set_yticks(yticks) ax2.set_yticks([0, 1]) ax2.set_ylim(-0.1, 1.1) # Add legends from both axes lines, labels = ax.get_legend_handles_labels() lines2, labels2 = ax2.get_legend_handles_labels() ax2.legend( lines + lines2, labels + labels2, loc="upper left", bbox_to_anchor=(1.03, 1), ) camera_name = camera_names[camera_idx] if camera_names is not None else "camera_3d" # Save the plot plt.subplots_adjust(right=0.85) plt.savefig( os.path.join(output_folder, f"gaze_interaction_{camera_name}.png"), bbox_inches="tight", dpi=500, )