aboutsummaryrefslogtreecommitdiff
path: root/src/argaze/TobiiGlassesPro2/TobiiInertialMeasureUnit.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/argaze/TobiiGlassesPro2/TobiiInertialMeasureUnit.py')
-rw-r--r--src/argaze/TobiiGlassesPro2/TobiiInertialMeasureUnit.py253
1 files changed, 0 insertions, 253 deletions
diff --git a/src/argaze/TobiiGlassesPro2/TobiiInertialMeasureUnit.py b/src/argaze/TobiiGlassesPro2/TobiiInertialMeasureUnit.py
deleted file mode 100644
index 35bb035..0000000
--- a/src/argaze/TobiiGlassesPro2/TobiiInertialMeasureUnit.py
+++ /dev/null
@@ -1,253 +0,0 @@
-#!/usr/bin/env python
-
-from typing import Tuple
-import json
-import time
-import math
-
-from argaze.TobiiGlassesPro2 import TobiiData
-
-import numpy
-from scipy.optimize import curve_fit
-import cv2 as cv
-
-
-EARTH_GRAVITY = -9.81
-"""Earth gravity force (m/s2)."""
-
-EARTH_GRAVITY_VECTOR = [0, EARTH_GRAVITY, 0]
-"""Earth gravity force vector."""
-
-CAMERA_TO_IMU_TRANSLATION_VECTOR = [8, -1, -5]
-"""Translation vector from camera referential to imu referential (cm)."""
-
-CAMERA_TO_IMU_ROTATION_VECTOR = [18, 0, 180]
-"""Rotation vector from camera referential to imu referential (euler, degree)."""
-
-class TobiiInertialMeasureUnit():
- """Ease Tobbi [Inertial Measure Unit](https://connect.tobii.com/s/article/How-are-the-MEMS-data-reported-for-Tobii-Pro-Glasses-2?language=en_US) data handling"""
-
- def __init__(self):
- """Define IMU calibration data."""
-
- self.__gyroscope_offset = numpy.zeros(3)
- self.__accelerometer_coefficients = numpy.array([[1., 0.], [1., 0.], [1., 0.]])
-
- self.__plumb = numpy.array(EARTH_GRAVITY_VECTOR)
-
- self.reset_rotation()
- self.reset_translation()
-
- def load_calibration_file(self, calibration_filepath):
- """Load IMU calibration from a .json file."""
-
- with open(calibration_filepath) as calibration_file:
-
- # Deserialize .json
- # TODO find a better way
- calibration_data = json.load(calibration_file)
-
- # Load calibration data
- self.__gyroscope_offset = numpy.array(calibration_data['gyroscope_offset'])
- self.__accelerometer_coefficients = numpy.array(calibration_data['accelerometer_coefficients'])
-
- def save_calibration_file(self, calibration_filepath):
- """Save IMU calibration into .json file."""
-
- calibration_data = {
- 'gyroscope_offset': list(self.__gyroscope_offset),
- 'accelerometer_coefficients': [list(self.__accelerometer_coefficients[0]), list(self.__accelerometer_coefficients[1]), list(self.__accelerometer_coefficients[2])]
- }
-
- with open(calibration_filepath, 'w', encoding='utf-8') as calibration_file:
-
- json.dump(calibration_data, calibration_file, ensure_ascii=False, indent=4)
-
- def calibrate_gyroscope_offset(self, gyroscope_ts_buffer) -> numpy.array:
- """Calibrate gyroscope offset from a timestamped gyroscope buffer.
- **Returns:** numpy.array"""
-
- # Consider gyroscope values without timestamps
- gyroscope_values = []
- for ts, data_object in gyroscope_ts_buffer.items():
- gyroscope_values.append(data_object.value)
-
- # Calculate average value for each axis
- gx_offset = numpy.mean(numpy.array(gyroscope_values)[:, 0])
- gy_offset = numpy.mean(numpy.array(gyroscope_values)[:, 1])
- gz_offset = numpy.mean(numpy.array(gyroscope_values)[:, 2])
-
- # Store result
- self.__gyroscope_offset = numpy.array([gx_offset, gy_offset, gz_offset])
-
- return self.__gyroscope_offset
-
- @property
- def gyroscope_offset(self) -> numpy.array:
- """Get gyroscope offset."""
-
- return self.__gyroscope_offset
-
- def apply_gyroscope_offset(self, gyroscope_data_object: TobiiData.Gyroscope) -> "TobiiData.Gyroscope":
- """Remove gyroscope offset to given gyroscope data."""
-
- return TobiiData.Gyroscope(gyroscope_data_object.value - self.__gyroscope_offset)
-
- def reset_rotation(self):
- """Reset rotation value before to start integration process."""
-
- self.__last_gyroscope_ts = None
-
- self.__rotation = numpy.zeros(3)
-
- def update_rotation(self, gyroscope_data_ts, gyroscope_data_object):
- """Integrate timestamped gyroscope values to update rotation."""
-
- # Convert deg/s into deg/ms
- current_gyroscope = gyroscope_data_object.value * 1e-3
-
- # Init gyroscope integration
- if self.__last_gyroscope_ts == None:
-
- self.__last_gyroscope_ts = gyroscope_data_ts
- self.__last_gyroscope = current_gyroscope
-
- # Calculate elapsed time in ms
- delta_time = (gyroscope_data_ts - self.__last_gyroscope_ts) / 1e3
-
- # Integrate gyroscope
- self.__rotation = self.__rotation + (self.__last_gyroscope * delta_time)
-
- # Store current as last
- self.__last_gyroscope_ts = gyroscope_data_ts
- self.__last_gyroscope = current_gyroscope
-
- @property
- def rotation(self) -> numpy.array:
- """Return current rotation value (euler angles in degree)."""
-
- return self.__rotation
-
- def _accelerometer_linear_fit(self, x, a, b):
- """Linear function for accelerometer axis correction."""
- return a * x + b
-
- def calibrate_accelerometer_axis_coefficients(self, axis, upward_ts_buffer, downward_ts_buffer, perpendicular_ts_buffer):
- """Calibrate one accelerometer axis using three data set (upward/+1g, downward/-1g, perpendicular/0g) for linear fit."""
-
- # Consider accelerometer axis values without timestamps
- accelerometer_values = []
- expected_values = []
-
- for (upward_ts, upward_data_object), (downward_ts, downward_data_object), (perpendicular_ts, perpendicular_data_object) in zip(upward_ts_buffer.items(), downward_ts_buffer.items(), perpendicular_ts_buffer.items()):
-
- accelerometer_values.append(upward_data_object.value[axis])
- expected_values.append(+EARTH_GRAVITY)
-
- accelerometer_values.append(downward_data_object.value[axis])
- expected_values.append(-EARTH_GRAVITY)
-
- accelerometer_values.append(perpendicular_data_object.value[axis])
- expected_values.append(0.0)
-
- # Find optimal coefficients according linear fit between accelerometer values and expected values
- optimal_coefficients, _ = curve_fit(self._accelerometer_linear_fit, accelerometer_values, expected_values, maxfev = 10000)
-
- # Store results for the given axis
- self.__accelerometer_coefficients[axis] = numpy.array(optimal_coefficients)
-
- @property
- def accelerometer_coefficients(self) -> numpy.array:
- """Return accelerometer coefficients."""
-
- return self.__accelerometer_coefficients
-
- def apply_accelerometer_coefficients(self, accelerometer_data_object: TobiiData.Accelerometer) -> "TobiiData.Accelerometer":
- """Add accelerometer offset to given accelerometer data."""
-
- x = self._accelerometer_linear_fit(accelerometer_data_object.value[0], *self.__accelerometer_coefficients[0])
- y = self._accelerometer_linear_fit(accelerometer_data_object.value[1], *self.__accelerometer_coefficients[1])
- z = self._accelerometer_linear_fit(accelerometer_data_object.value[2], *self.__accelerometer_coefficients[2])
-
- return TobiiData.Accelerometer(numpy.array([x, y , z]))
-
- def reset_translation(self, translation_speed = numpy.zeros(3)):
- """Reset translation value before to start integration process."""
-
- self.__last_accelerometer_ts = None
-
- self.__translation_speed = translation_speed
- self.__translation = numpy.zeros(3)
-
- def update_translation(self, accelerometer_data_ts, accelerometer_data_object):
- """Integrate timestamped accelerometer values to update translation."""
-
- print('> update_translation: accelerometer_data_ts=', accelerometer_data_ts)
-
- # Convert m/s2 into cm/ms2
- current_accelerometer = accelerometer_data_object.value * 1e-4
-
- print('\tcurrent_accelerometer(cm/ms2)=', current_accelerometer)
- print('\tcurrent_accelerometer norm=', numpy.linalg.norm(current_accelerometer))
-
- # Init accelerometer integration
- if self.__last_accelerometer_ts == None:
-
- self.__last_accelerometer_ts = accelerometer_data_ts
- self.__last_accelerometer = current_accelerometer
- self.__last_translation_speed = numpy.zeros(3)
-
- # Calculate elapsed time in ms
- delta_time = (accelerometer_data_ts - self.__last_accelerometer_ts) / 1e3
-
- print('\tdelta_time=', delta_time)
-
- # Integrate accelerometer
- self.__translation_speed = self.__translation_speed + (self.__last_accelerometer * delta_time)
- self.__translation = self.__translation + (self.__last_translation_speed * delta_time)
-
- print('\tself.__translation_speed(cm/ms)=', self.__translation_speed)
- print('\tself.__translation(cm)=', self.__translation)
-
- # Store current as last
- self.__last_accelerometer = current_accelerometer
- self.__last_accelerometer_ts = accelerometer_data_ts
- self.__last_translation_speed = self.__translation_speed
-
- print('< update_translation')
-
- #else:
-
- # print('no valid head plumb')
-
- @property
- def translation(self) -> numpy.array:
- """Return current translation vector."""
-
- return self.__translation
-
- @property
- def translation_speed(self) -> numpy.array:
- """Return current translation speed vector."""
-
- return self.__translation_speed
-
- def rotate_plumb(self, rvec):
- """Rotate imu plumb to remove gravity effect in accelerometer data."""
-
- C, _ = cv.Rodrigues(rvec)
- self.__plumb = C.dot(EARTH_GRAVITY_VECTOR)
-
- # Check plumb length
- assert(math.isclose(numpy.linalg.norm(self.__plumb), math.fabs(EARTH_GRAVITY), abs_tol=1e-3))
-
- @property
- def plumb(self) -> numpy.array:
- """Return plumb vector."""
-
- return self.__plumb
-
- def apply_plumb(self, accelerometer_data_object: TobiiData.Accelerometer) -> "TobiiData.Accelerometer":
- """Remove gravity along plumb vector to given accelerometer data."""
-
- return TobiiData.Accelerometer(accelerometer_data_object.value - self.__plumb)