Files
librepods/head-tracking/head_orientation.py
2025-03-14 17:32:47 +05:30

143 lines
5.2 KiB
Python

import math
import drawille
import numpy as np
import logging
import os
class Colors:
RESET = "\033[0m"
BOLD = "\033[1m"
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
MAGENTA = "\033[95m"
CYAN = "\033[96m"
WHITE = "\033[97m"
BG_BLACK = "\033[40m"
class ColorFormatter(logging.Formatter):
FORMATS = {
logging.DEBUG: Colors.BLUE + "[%(levelname)s] %(message)s" + Colors.RESET,
logging.INFO: Colors.GREEN + "%(message)s" + Colors.RESET,
logging.WARNING: Colors.YELLOW + "%(message)s" + Colors.RESET,
logging.ERROR: Colors.RED + "[%(levelname)s] %(message)s" + Colors.RESET,
logging.CRITICAL: Colors.RED + Colors.BOLD + "[%(levelname)s] %(message)s" + Colors.RESET
}
def format(self, record):
log_fmt = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt, datefmt="%H:%M:%S")
return formatter.format(record)
handler = logging.StreamHandler()
handler.setFormatter(ColorFormatter())
log = logging.getLogger(__name__)
log.setLevel(logging.INFO)
log.addHandler(handler)
log.propagate = False
class HeadOrientation:
def __init__(self, use_terminal=False):
self.orientation_offset = 5500
self.o1_neutral = 19000
self.o2_neutral = 0
self.o3_neutral = 0
self.calibration_samples = []
self.calibration_complete = False
self.calibration_sample_count = 10
self.fig = None
self.ax = None
self.arrow = None
self.animation = None
self.use_terminal = use_terminal
def reset_calibration(self):
self.calibration_samples = []
self.calibration_complete = False
def add_calibration_sample(self, orientation_values):
if len(self.calibration_samples) < self.calibration_sample_count:
self.calibration_samples.append(orientation_values)
return False
if not self.calibration_complete:
self._calculate_calibration()
return True
return True
def _calculate_calibration(self):
if len(self.calibration_samples) < 3:
log.warning("Not enough calibration samples")
return
samples = np.array(self.calibration_samples)
self.o1_neutral = np.mean(samples[:, 0])
avg_o2 = np.mean(samples[:, 1])
avg_o3 = np.mean(samples[:, 2])
self.o2_neutral = avg_o2
self.o3_neutral = avg_o3
log.info("Calibration complete: o1_neutral=%.2f, o2_neutral=%.2f, o3_neutral=%.2f",
self.o1_neutral, self.o2_neutral, self.o3_neutral)
self.calibration_complete = True
def calculate_orientation(self, o1, o2, o3):
if not self.calibration_complete:
return {'pitch': 0, 'yaw': 0}
o1_norm = o1 - self.o1_neutral
o2_norm = o2 - self.o2_neutral
o3_norm = o3 - self.o3_neutral
pitch = (o2_norm + o3_norm) / 2 / 32000 * 180
yaw = (o2_norm - o3_norm) / 2 / 32000 * 180
return {'pitch': pitch, 'yaw': yaw}
def create_face_art(self, pitch, yaw):
if self.use_terminal:
try:
ts = os.get_terminal_size()
width, height = ts.columns, ts.lines * 2
except Exception:
width, height = 80, 40
else:
width, height = 80, 40
center_x, center_y = width // 2, height // 2
radius = (min(width, height) // 2 - 2) // 2
pitch_rad = math.radians(pitch)
yaw_rad = math.radians(yaw)
canvas = drawille.Canvas()
def rotate_point(x, y, z, pitch_r, yaw_r):
cos_y, sin_y = math.cos(yaw_r), math.sin(yaw_r)
cos_p, sin_p = math.cos(pitch_r), math.sin(pitch_r)
x1 = x * cos_y - z * sin_y
z1 = x * sin_y + z * cos_y
y1 = y * cos_p - z1 * sin_p
z2 = y * sin_p + z1 * cos_p
scale = 1 + (z2 / width)
return int(center_x + x1 * scale), int(center_y + y1 * scale)
for angle in range(0, 360, 2):
rad = math.radians(angle)
x = radius * math.cos(rad)
y = radius * math.sin(rad)
x1, y1 = rotate_point(x, y, 0, pitch_rad, yaw_rad)
canvas.set(x1, y1)
for eye in [(-radius//2, -radius//3, 2), (radius//2, -radius//3, 2)]:
ex, ey, ez = eye
x1, y1 = rotate_point(ex, ey, ez, pitch_rad, yaw_rad)
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
canvas.set(x1 + dx, y1 + dy)
nx, ny = rotate_point(0, 0, 1, pitch_rad, yaw_rad)
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
canvas.set(nx + dx, ny + dy)
smile_depth = radius // 8
mouth_local_y = radius // 4
mouth_length = radius
for x_offset in range(-mouth_length // 2, mouth_length // 2 + 1):
norm = abs(x_offset) / (mouth_length / 2)
y_offset = int((1 - norm ** 2) * smile_depth)
local_x = x_offset
local_y = mouth_local_y + y_offset
mx, my = rotate_point(local_x, local_y, 0, pitch_rad, yaw_rad)
canvas.set(mx, my)
return canvas.frame()