Source code for pyheadtracker.hmd

"""
Accessing position and orientation data from head mounted displays (HMDs).

Due to pyopenxr not being available on macOS, this module is only available on Windows and Linux.

Classes:
- `openXR`: A class for accessing position and orientation data from OpenXR-compatible HMDs, relying on the pyopenxr library
"""

import xr
from xr.utils.gl import ContextObject
from typing import Optional
from .dtypes import Quaternion, YPR, Position, HTBase
from .utils import quat2ypr


[docs] class openXR(HTBase): """ A class for accessing position and orientation data from OpenXR-compatible HMDs. This class uses the pyopenxr library to read the head pose data from the HMD. Upon request, the position and orientation is returned. Attributes ---------- context : ContextObject The OpenXR context object. reference_position : Position A reference position, where the tracking data can be normalized to. reference_orientation_inv : Quaternion The inverse of the reference orientation, used to normalize the orientation data. reset_position : bool A flag to indicate if the position should be reset to the reference position. reset_orientation : bool A flag to indicate if the orientation should be reset to the reference orientation. orient_format : str The format of the orientation data. Internally, Quaternions are used. Possible values are "ypr" (Yaw, Pitch, Roll) or "q" (Quaternion). Default is "q". Methods ------- read_raw_pose(frame_state: xr.FrameState) -> xr.Posef or None Returns if available the raw pose data from the HMD without any adjustments in the openXR coordinate system definition. read_pose(frame_state: xr.FrameState) -> dict or None Returns if available the adjusted position and orientation data from the HMD. The dictionary contains a Position and Quaternion object. read_orientation(frame_state: xr.FrameState) -> Quaternion or None Returns if available the current head orientation as a Quaternion object. read_position(frame_state: xr.FrameState) -> Position or None Returns if available the current head position as a Position object. zero() Resets the position and orientation to the reference values. zero_orientation() Resets the orientation to the reference orientation. zero_position() Resets the position to the reference position. """
[docs] def __init__( self, context: ContextObject, initial_pose: Optional[xr.Posef] = None, reset_position: bool = False, reset_orientation: bool = False, orient_format: str = "q", ): """ Parameters ---------- context : ContextObject The OpenXR context object. initial_pose : xr.Posef, optional The initial pose of the HMD. If not provided, the position will be set to (0, 0, 0) and the orientation to the identity quaternion. reset_position : bool, optional If True, the position will be reset to the initial pose when read_pose is called. Default is False. reset_orientation : bool, optional If True, the orientation will be reset to the initial pose when read_pose is called. Default is False. orient_format : str, optional The format of the orientation data. Possible values are "ypr" (Yaw, Pitch, Roll) or "q" (Quaternion). Default is "q". """ self.context = context if initial_pose is None: self.reference_position = Position(0.0, 0.0, 0.0) self.reference_orientation_inv = Quaternion(1.0, 0.0, 0.0, 0.0).inverse() else: self.reference_position = Position( -initial_pose.position.z, -initial_pose.position.x, initial_pose.position.y, ) self.reference_orientation_inv = Quaternion( initial_pose.orientation.w, -initial_pose.orientation.z, -initial_pose.orientation.x, initial_pose.orientation.y, ).inverse() self.reset_position = reset_position self.reset_orientation = reset_orientation assert orient_format in [ "ypr", "q", ], "Orientation format must be 'ypr' or 'q'" self.orient_format = orient_format
[docs] def read_raw_pose(self, frame_state: xr.FrameState): """ Get the raw pose data from the HMD without any adjustments in the OpenXR coordinate system definition. Parameters ---------- frame_state : xr.FrameState The current frame state from OpenXR. Returns ------- xr.Posef or None The raw pose data from the HMD, or None if the pose is not available. """ view_state, views = xr.locate_views( session=self.context.session, view_locate_info=xr.ViewLocateInfo( view_configuration_type=self.context.view_configuration_type, display_time=frame_state.predicted_display_time, space=self.context.space, ), ) flags = xr.ViewStateFlags(view_state.view_state_flags) if flags & xr.ViewStateFlags.POSITION_VALID_BIT: return views[xr.utils.Eye.LEFT.value].pose else: return None
[docs] def read_pose(self, frame_state: xr.FrameState): """ Read and post-process the current head pose. Get the current head position and orientation as a dictionary containing Position and Quaternion objects. The position and orientation are adjusted relative to the initial pose. Parameters ---------- frame_state : xr.FrameState The current frame state from OpenXR. Returns ------- dict or None A dictionary containing the adjusted position and orientation, or None if the pose is not available. """ raw_pose = self.read_raw_pose(frame_state) if raw_pose is None: return None # Convert the raw pose to a Position and Quaternion # Note: OpenXR uses a right-handed coordinate system as often used in 3D graphics, so we need to adjust the position and orientation accordingly for audio applications. raw_position = Position( -raw_pose.position.z, -raw_pose.position.x, raw_pose.position.y, ) raw_orientation = Quaternion( raw_pose.orientation.w, -raw_pose.orientation.z, -raw_pose.orientation.x, raw_pose.orientation.y, ) if self.reset_position: # Reset the position to zero self.reference_position = raw_position self.reset_position = False if self.reset_orientation: # Reset the orientation self.reference_orientation_inv = raw_orientation.inverse() self.reset_orientation = False # Get position and orientation relative to the initial pose new_position = self.__adjust_position(raw_position) new_orientation = self.__adjust_orientation(raw_orientation) if self.orient_format == "ypr": new_orientation = quat2ypr(new_orientation) return {"position": new_position, "orientation": new_orientation}
[docs] def read_orientation(self, frame_state: xr.FrameState): """ Get the current head orientation as a Quaternion. Parameters ---------- frame_state : xr.FrameState The current frame state from OpenXR. Returns ------- Quaternion or None The current head orientation as a Quaternion object, or None if the pose is not available. """ pose = self.read_pose(frame_state) if pose is not None: return pose["orientation"] else: return None
[docs] def read_position(self, frame_state: xr.FrameState): """ Get the current head position as a Position object. Parameters ---------- frame_state : xr.FrameState The current frame state from OpenXR. Returns ------- Position or None The current head position as a Position object, or None if the pose is not available. """ pose = self.read_pose(frame_state) if pose is not None: return pose["position"] else: return None
[docs] def zero(self): """ Reset reference position and orientation to the current one. """ self.reset_position = True self.reset_orientation = True
[docs] def zero_orientation(self): """ Reset reference orientation to the current one. """ self.reset_orientation = True
[docs] def zero_position(self): """ Reset reference position to the current one. """ self.reset_position = True
def __adjust_orientation(self, current_orientation: Quaternion): return current_orientation * self.reference_orientation_inv def __adjust_position(self, current_position: Position): return current_position - self.reference_position