diff --git a/.github/workflows/PR-checks.yml b/.github/workflows/PR-checks.yml index ec237fd3e..115e0788c 100644 --- a/.github/workflows/PR-checks.yml +++ b/.github/workflows/PR-checks.yml @@ -9,7 +9,7 @@ on: jobs: build: - name: 📦 ${{ matrix.component }} build + name: 📦 ${{ matrix.component }} build runs-on: ubuntu-latest strategy: fail-fast: false @@ -21,7 +21,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./${{ matrix.component }} @@ -101,7 +101,7 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 2 - - name: 'Check Grammar' + - name: "Check Grammar" id: cspell uses: streetsidesoftware/cspell-action@v5 with: diff --git a/.github/workflows/build-deploy-production.yml b/.github/workflows/build-deploy-production.yml index 682612ab0..2da959989 100644 --- a/.github/workflows/build-deploy-production.yml +++ b/.github/workflows/build-deploy-production.yml @@ -16,7 +16,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./neon @@ -32,13 +32,13 @@ jobs: - name: Make build working-directory: ./neon run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: name: neon path: neon/.vitepress/dist - + invisible: name: Build Invisible runs-on: ubuntu-latest @@ -49,12 +49,12 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./invisible run: npm install - + # Copy node modules to provide dependencies for shared components in root folder - name: Copy node_modules shell: bash @@ -65,13 +65,13 @@ jobs: - name: Make build working-directory: ./invisible run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: name: invisible path: invisible/.vitepress/dist - + core: name: Build Core runs-on: ubuntu-latest @@ -82,12 +82,12 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./core run: npm install - + # Copy node modules to provide dependencies for shared components in root folder - name: Copy node_modules shell: bash @@ -98,13 +98,13 @@ jobs: - name: Make build working-directory: ./core run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: name: core path: core/.vitepress/dist - + alpha-lab: name: Build Alpha Lab runs-on: ubuntu-latest @@ -115,12 +115,12 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./alpha-lab run: npm install - + # Copy node modules to provide dependencies for shared components in root folder - name: Copy node_modules shell: bash @@ -131,7 +131,7 @@ jobs: - name: Make build working-directory: ./alpha-lab run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: @@ -148,12 +148,12 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./landing-page run: npm install - + # Copy node modules to provide dependencies for shared components in root folder - name: Copy node_modules shell: bash @@ -164,19 +164,19 @@ jobs: - name: Make build working-directory: ./landing-page run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: name: landing-page path: landing-page/.vitepress/dist - + debugging-info: name: Upload debugging info runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - name: Write commit SHA to gitcommit.txt shell: bash run: | @@ -203,13 +203,13 @@ jobs: shell: bash run: | du -ah . - + - name: Download gitcommit uses: actions/download-artifact@v3 with: name: gitcommit path: gitcommit.txt - + - name: Download Neon uses: actions/download-artifact@v3 with: @@ -221,13 +221,13 @@ jobs: with: name: invisible path: invisible/ - + - name: Download Core uses: actions/download-artifact@v3 with: name: core path: core/ - + - name: Download AlphaLab uses: actions/download-artifact@v3 with: @@ -248,7 +248,6 @@ jobs: remote_host: ${{ secrets.REMOTE_HOST}} remote_user: ${{ secrets.REMOTE_USER }} remote_key: ${{ secrets.DEPLOY_KEY }} - deploy-production: name: Deploy to production @@ -272,13 +271,13 @@ jobs: with: name: invisible path: invisible/ - + - name: Download Core uses: actions/download-artifact@v3 with: name: core path: core/ - + - name: Download AlphaLab uses: actions/download-artifact@v3 with: diff --git a/.github/workflows/build-deploy-staging.yml b/.github/workflows/build-deploy-staging.yml index 92b61a2bf..9b6fdf33b 100644 --- a/.github/workflows/build-deploy-staging.yml +++ b/.github/workflows/build-deploy-staging.yml @@ -14,7 +14,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./neon @@ -30,13 +30,13 @@ jobs: - name: Make build working-directory: ./neon run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: name: neon path: neon/.vitepress/dist - + invisible: name: Build Invisible runs-on: ubuntu-latest @@ -47,12 +47,12 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./invisible run: npm install - + # Copy node modules to provide dependencies for shared components in root folder - name: Copy node_modules shell: bash @@ -63,13 +63,13 @@ jobs: - name: Make build working-directory: ./invisible run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: name: invisible path: invisible/.vitepress/dist - + core: name: Build Core runs-on: ubuntu-latest @@ -80,12 +80,12 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./core run: npm install - + # Copy node modules to provide dependencies for shared components in root folder - name: Copy node_modules shell: bash @@ -96,13 +96,13 @@ jobs: - name: Make build working-directory: ./core run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: name: core path: core/.vitepress/dist - + alpha-lab: name: Build Alpha Lab runs-on: ubuntu-latest @@ -113,12 +113,12 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./alpha-lab run: npm install - + # Copy node modules to provide dependencies for shared components in root folder - name: Copy node_modules shell: bash @@ -129,7 +129,7 @@ jobs: - name: Make build working-directory: ./alpha-lab run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: @@ -146,12 +146,12 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./landing-page run: npm install - + # Copy node modules to provide dependencies for shared components in root folder - name: Copy node_modules shell: bash @@ -162,19 +162,19 @@ jobs: - name: Make build working-directory: ./landing-page run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: name: landing-page path: landing-page/.vitepress/dist - + debugging-info: name: Upload debugging info runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - name: Write commit SHA to gitcommit.txt shell: bash run: | @@ -201,13 +201,13 @@ jobs: shell: bash run: | du -ah . - + - name: Download gitcommit uses: actions/download-artifact@v3 with: name: gitcommit path: gitcommit.txt - + - name: Download Neon uses: actions/download-artifact@v3 with: @@ -219,13 +219,13 @@ jobs: with: name: invisible path: invisible/ - + - name: Download Core uses: actions/download-artifact@v3 with: name: core path: core/ - + - name: Download AlphaLab uses: actions/download-artifact@v3 with: diff --git a/.github/workflows/build-only.yml b/.github/workflows/build-only.yml index cab837c5e..c587819f9 100644 --- a/.github/workflows/build-only.yml +++ b/.github/workflows/build-only.yml @@ -1,7 +1,6 @@ name: Build only -on: - pull_request +on: pull_request jobs: neon: @@ -14,7 +13,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./neon @@ -30,13 +29,13 @@ jobs: - name: Make build working-directory: ./neon run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: name: neon path: neon/.vitepress/dist - + invisible: name: Build Invisible runs-on: ubuntu-latest @@ -47,12 +46,12 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./invisible run: npm install - + # Copy node modules to provide dependencies for shared components in root folder - name: Copy node_modules shell: bash @@ -63,13 +62,13 @@ jobs: - name: Make build working-directory: ./invisible run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: name: invisible path: invisible/.vitepress/dist - + core: name: Build Core runs-on: ubuntu-latest @@ -80,12 +79,12 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./core run: npm install - + # Copy node modules to provide dependencies for shared components in root folder - name: Copy node_modules shell: bash @@ -96,13 +95,13 @@ jobs: - name: Make build working-directory: ./core run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: name: core path: core/.vitepress/dist - + alpha-lab: name: Build Alpha Lab runs-on: ubuntu-latest @@ -113,12 +112,12 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./alpha-lab run: npm install - + # Copy node modules to provide dependencies for shared components in root folder - name: Copy node_modules shell: bash @@ -129,7 +128,7 @@ jobs: - name: Make build working-directory: ./alpha-lab run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: @@ -146,12 +145,12 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Install dependencies working-directory: ./landing-page run: npm install - + # Copy node modules to provide dependencies for shared components in root folder - name: Copy node_modules shell: bash @@ -162,19 +161,19 @@ jobs: - name: Make build working-directory: ./landing-page run: npm run docs:build - + - name: Upload uses: actions/upload-artifact@v3 with: name: landing-page path: landing-page/.vitepress/dist - + debugging-info: name: Upload debugging info runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - name: Write commit SHA to gitcommit.txt shell: bash run: | diff --git a/alpha-lab/.vitepress/config.mts b/alpha-lab/.vitepress/config.mts index e98080c8a..7d4e663fc 100644 --- a/alpha-lab/.vitepress/config.mts +++ b/alpha-lab/.vitepress/config.mts @@ -6,6 +6,12 @@ import { config as default_config, theme_config as default_theme_config } from " let theme_config_additions = { sidebar: [ { text: "Welcome", link: "/" }, + { + text: "Coordinate Systems", + items: [ + { text: "IMU Transformations", link: "/imu-transformations/" }, + ], + }, { text: "Gaze Mapping", items: [ diff --git a/alpha-lab/cards.json b/alpha-lab/cards.json index fab5a8e0f..360d3b8e9 100644 --- a/alpha-lab/cards.json +++ b/alpha-lab/cards.json @@ -148,5 +148,15 @@ }, "image": "/alpha-lab/tag-aligner.webp", "category": "Gaze Mapping" + }, + { + "title": "IMU Transformations", + "details": "Transform IMU data into different representations and coordinate systems with these code snippets.", + "link": { + "text": "View", + "href": "/alpha-lab/imu-transformations/" + }, + "image": "/alpha-lab/imu-transformations.webp", + "category": "Coordinate Systems" } ] diff --git a/alpha-lab/imu-transformations/imu-pitch-yaw-roll-black.png b/alpha-lab/imu-transformations/imu-pitch-yaw-roll-black.png new file mode 100644 index 000000000..f35c581a6 Binary files /dev/null and b/alpha-lab/imu-transformations/imu-pitch-yaw-roll-black.png differ diff --git a/alpha-lab/imu-transformations/imu-scene_camera_offset-black.png b/alpha-lab/imu-transformations/imu-scene_camera_offset-black.png new file mode 100644 index 000000000..808f96257 Binary files /dev/null and b/alpha-lab/imu-transformations/imu-scene_camera_offset-black.png differ diff --git a/alpha-lab/imu-transformations/imu_heading_visualization.ipynb b/alpha-lab/imu-transformations/imu_heading_visualization.ipynb new file mode 100644 index 000000000..d8eb6cf9f --- /dev/null +++ b/alpha-lab/imu-transformations/imu_heading_visualization.ipynb @@ -0,0 +1,549 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "import cv2\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from scipy.spatial.transform import Rotation as R\n", + "\n", + "\n", + "def transform_imu_to_world(imu_coordinates, imu_quaternions):\n", + " # This array contains a timeseries of transformation matrices,\n", + " # as calculated from the IMU's timeseries of quaternions values.\n", + " imu_to_world_matrices = R.from_quat(imu_quaternions).as_matrix()\n", + "\n", + " if np.ndim(imu_coordinates) == 1:\n", + " return imu_to_world_matrices @ imu_coordinates\n", + " else:\n", + " return np.array(\n", + " [\n", + " imu_to_world @ imu_coord\n", + " for imu_to_world, imu_coord in zip(\n", + " imu_to_world_matrices, imu_coordinates\n", + " )\n", + " ]\n", + " )\n", + "\n", + "\n", + "def transform_scene_to_imu(\n", + " coords_in_scene, translation_in_imu=np.array([0.0, -1.3, -6.62])\n", + "):\n", + " imu_scene_rotation_diff = np.deg2rad(-90 - 12)\n", + " scene_to_imu = np.array(\n", + " [\n", + " [1.0, 0.0, 0.0],\n", + " [\n", + " 0.0,\n", + " np.cos(imu_scene_rotation_diff),\n", + " -np.sin(imu_scene_rotation_diff),\n", + " ],\n", + " [\n", + " 0.0,\n", + " np.sin(imu_scene_rotation_diff),\n", + " np.cos(imu_scene_rotation_diff),\n", + " ],\n", + " ]\n", + " )\n", + "\n", + " coords_in_imu = scene_to_imu @ coords_in_scene.T\n", + "\n", + " coords_in_imu[0, :] += translation_in_imu[0]\n", + " coords_in_imu[1, :] += translation_in_imu[1]\n", + " coords_in_imu[2, :] += translation_in_imu[2]\n", + "\n", + " return coords_in_imu.T\n", + "\n", + "\n", + "def spherical_to_cartesian_scene(elevations, azimuths):\n", + " \"\"\"\n", + " Convert Neon's spherical representation of 3D gaze to Cartesian coordinates.\n", + " \"\"\"\n", + "\n", + " elevations_rad = np.deg2rad(elevations)\n", + " azimuths_rad = np.deg2rad(azimuths)\n", + "\n", + " # Elevation of 0 in Neon system corresponds to Y = 0, but\n", + " # an elevation of 0 in traditional spherical coordinates would\n", + " # correspond to Y = 1, so we convert elevation to the\n", + " # more traditional format.\n", + " elevations_rad += np.pi / 2\n", + "\n", + " # Azimuth of 0 in Neon system corresponds to X = 0, but\n", + " # an azimuth of 0 in traditional spherical coordinates would\n", + " # correspond to X = 1. Also, azimuth to the right in Neon is\n", + " # more positive, whereas it is more negative in traditional\n", + " # spherical coordiantes. So, we convert azimuth to the more\n", + " # traditional format.\n", + " azimuths_rad *= -1.0\n", + " azimuths_rad += np.pi / 2\n", + "\n", + " return np.array(\n", + " [\n", + " np.sin(elevations_rad) * np.cos(azimuths_rad),\n", + " np.cos(elevations_rad),\n", + " np.sin(elevations_rad) * np.sin(azimuths_rad),\n", + " ]\n", + " ).T\n", + "\n", + "\n", + "def transform_scene_to_world(\n", + " coords_in_scene, imu_quaternions, translation_in_imu=np.array([0.0, -1.3, -6.62])\n", + "):\n", + " coords_in_imu = transform_scene_to_imu(coords_in_scene, translation_in_imu)\n", + " return transform_imu_to_world(coords_in_imu, imu_quaternions)\n", + "\n", + "\n", + "def gaze_3d_to_world(gaze_elevation, gaze_azimuth, imu_quaternions):\n", + " cart_gazes_in_scene = spherical_to_cartesian_scene(gaze_elevation, gaze_azimuth)\n", + " return transform_scene_to_world(\n", + " cart_gazes_in_scene, imu_quaternions, translation_in_imu=np.zeros(3)\n", + " )\n", + "\n", + "\n", + "def imu_heading_in_world(imu_quaternions):\n", + " heading_neutral_in_imu_coords = np.array([0.0, 1.0, 0.0])\n", + " return transform_imu_to_world(heading_neutral_in_imu_coords, imu_quaternions)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "rec_dir = \"./2024-08-19_14-20-57-48fd6838/\"\n", + "\n", + "gaze = pd.read_csv(rec_dir + \"gaze.csv\")\n", + "eye3d = pd.read_csv(rec_dir + \"3d_eye_states.csv\")\n", + "imu = pd.read_csv(rec_dir + \"imu.csv\")\n", + "world = pd.read_csv(rec_dir + \"world_timestamps.csv\")\n", + "\n", + "gaze_ts = gaze[\"timestamp [ns]\"]\n", + "imu_ts = imu[\"timestamp [ns]\"]\n", + "world_ts = world[\"timestamp [ns]\"]\n", + "\n", + "info = []\n", + "with open(rec_dir + \"info.json\", \"r\") as f:\n", + " info = json.load(f)\n", + "\n", + "gaze[\"relative ts [s]\"] = (gaze_ts - info[\"start_time\"]) * 1e-9\n", + "imu[\"relative ts [s]\"] = (imu_ts - info[\"start_time\"]) * 1e-9\n", + "world[\"relative ts [s]\"] = (world_ts - info[\"start_time\"]) * 1e-9\n", + "\n", + "relative_demo_video_ts = np.arange(\n", + " world[\"relative ts [s]\"].iloc[200], world[\"relative ts [s]\"].iloc[-100], 1/30\n", + ")\n", + "\n", + "# We have more gaze datapoints (sampled at 200Hz) than\n", + "# IMU datapoints (sampled at 110Hz). We also need to sample values at the\n", + "# framerate of the visualization video that we will make, so linearly\n", + "# interpolate the IMU and gaze datapoints to be congruent with each other\n", + "# and the video render.\n", + "quaternions_resampled = np.array(\n", + " [\n", + " np.interp(relative_demo_video_ts, imu[\"relative ts [s]\"], imu[\"quaternion x\"]),\n", + " np.interp(relative_demo_video_ts, imu[\"relative ts [s]\"], imu[\"quaternion y\"]),\n", + " np.interp(relative_demo_video_ts, imu[\"relative ts [s]\"], imu[\"quaternion z\"]),\n", + " np.interp(relative_demo_video_ts, imu[\"relative ts [s]\"], imu[\"quaternion w\"]),\n", + " ]\n", + ").T\n", + "\n", + "gaze_elevation_resampled = np.interp(relative_demo_video_ts, gaze[\"relative ts [s]\"], gaze[\"elevation [deg]\"])\n", + "gaze_azimuth_resampled = np.interp(relative_demo_video_ts, gaze[\"relative ts [s]\"], gaze[\"azimuth [deg]\"])\n", + "\n", + "# Now, we can apply the functions.\n", + "\n", + "headings_in_world = imu_heading_in_world(quaternions_resampled)\n", + "\n", + "cart_gazes_in_world = gaze_3d_to_world(\n", + " gaze_elevation_resampled, gaze_azimuth_resampled, quaternions_resampled\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.animation as animation\n", + "\n", + "fig, axs = plt.subplots(1, 2, figsize=(10.5, 5))\n", + "\n", + "# Overhead visualization\n", + "\n", + "axs[0].axis(\"square\")\n", + "axs[0].set_aspect(\"equal\", adjustable=\"box\")\n", + "axs[0].set_xlim(-1.29, 1.29)\n", + "axs[0].set_ylim(-1.29, 1.29)\n", + "\n", + "axs[0].axis(\"off\")\n", + "\n", + "circle = plt.Circle((0, 0), 1.0, color=\"black\", fill=False)\n", + "axs[0].add_patch(circle)\n", + "\n", + "axs[0].text(0, 1.11, \"N\", ha=\"center\", va=\"bottom\", fontsize=\"xx-large\")\n", + "axs[0].text(0, -1.11, \"S\", ha=\"center\", va=\"top\", fontsize=\"xx-large\")\n", + "axs[0].text(1.11, 0, \"E\", ha=\"left\", va=\"center\", fontsize=\"xx-large\")\n", + "axs[0].text(-1.11, 0, \"W\", ha=\"right\", va=\"center\", fontsize=\"xx-large\")\n", + "\n", + "axs[0].plot([0, 0], [1.0, 1.08], color=\"black\")\n", + "axs[0].plot([0, 0], [-1.0, -1.08], color=\"black\")\n", + "axs[0].plot([1.0, 1.08], [0, 0], color=\"black\")\n", + "axs[0].plot([-1.0, -1.08], [0, 0], color=\"black\")\n", + "\n", + "heading_quiver_overhead = axs[0].quiver(\n", + " 0,\n", + " 0,\n", + " headings_in_world[0, 0],\n", + " headings_in_world[0, 1],\n", + " color=\"b\",\n", + " label=\"IMU Heading\",\n", + " scale=1,\n", + " scale_units=\"xy\",\n", + " angles=\"xy\",\n", + " width=0.01,\n", + ")\n", + "\n", + "gaze_quiver_overhead = axs[0].quiver(\n", + " 0,\n", + " 0,\n", + " cart_gazes_in_world[0, 0],\n", + " cart_gazes_in_world[0, 1],\n", + " color=\"r\",\n", + " label=\"Gaze vector\",\n", + " scale=1,\n", + " scale_units=\"xy\",\n", + " angles=\"xy\",\n", + " width=0.01,\n", + ")\n", + "\n", + "axs[0].legend(loc=\"lower right\", prop={\"size\": 11})\n", + "\n", + "axs[0].set_title(\"Heading and Gaze in World - Overhead\", fontsize=\"xx-large\")\n", + "\n", + "\n", + "# Side profile visualization\n", + "\n", + "axs[1].axis(\"square\")\n", + "axs[1].set_aspect(\"equal\", adjustable=\"box\")\n", + "axs[1].set_xlim(-1.29, 1.29)\n", + "axs[1].set_ylim(-1.29, 1.29)\n", + "\n", + "axs[1].axis(\"off\")\n", + "\n", + "circle = plt.Circle((0, 0), 1.0, color=\"black\", fill=False)\n", + "axs[1].add_patch(circle)\n", + "\n", + "axs[1].plot([0, 0], [1.0, 1.08], color=\"black\")\n", + "axs[1].plot([0, 0], [-1.0, -1.08], color=\"black\")\n", + "\n", + "axs[1].text(0, 1.11, \"Sky\", ha=\"center\", va=\"bottom\", fontsize=\"xx-large\")\n", + "axs[1].text(0, -1.11, \"Earth\", ha=\"center\", va=\"top\", fontsize=\"xx-large\")\n", + "\n", + "axs[1].hlines(0, -1.0, 1.0, color=\"black\", linestyle=\"--\")\n", + "\n", + "heading_quiver_sideprofile = axs[1].quiver(\n", + " 0,\n", + " 0,\n", + " headings_in_world[0, 1],\n", + " headings_in_world[0, 2],\n", + " color=\"b\",\n", + " scale=1,\n", + " scale_units=\"xy\",\n", + " angles=\"xy\",\n", + " width=0.01,\n", + ")\n", + "\n", + "gaze_quiver_sideprofile = axs[1].quiver(\n", + " 0,\n", + " 0,\n", + " cart_gazes_in_world[0, 1],\n", + " cart_gazes_in_world[0, 2],\n", + " color=\"r\",\n", + " scale=1,\n", + " scale_units=\"xy\",\n", + " angles=\"xy\",\n", + " width=0.01,\n", + ")\n", + "\n", + "axs[1].set_title(\"Heading and Gaze in World - Side-profile\", fontsize=\"xx-large\")\n", + "\n", + "\n", + "def update(frame):\n", + " global heading_quiver_overhead\n", + " heading_quiver_overhead.remove()\n", + " heading_quiver_overhead = axs[0].quiver(\n", + " 0,\n", + " 0,\n", + " headings_in_world[frame, 0],\n", + " headings_in_world[frame, 1],\n", + " color=\"b\",\n", + " label=\"IMU Heading\",\n", + " scale=1,\n", + " scale_units=\"xy\",\n", + " angles=\"xy\",\n", + " width=0.01,\n", + " )\n", + "\n", + " global gaze_quiver_overhead\n", + " gaze_quiver_overhead.remove()\n", + " gaze_quiver_overhead = axs[0].quiver(\n", + " 0,\n", + " 0,\n", + " cart_gazes_in_world[frame, 0],\n", + " cart_gazes_in_world[frame, 1],\n", + " color=\"r\",\n", + " label=\"Gaze vector\",\n", + " scale=1,\n", + " scale_units=\"xy\",\n", + " angles=\"xy\",\n", + " width=0.01,\n", + " )\n", + " \n", + " global heading_quiver_sideprofile\n", + " heading_quiver_sideprofile.remove()\n", + " heading_quiver_sideprofile = axs[1].quiver(\n", + " 0,\n", + " 0,\n", + " headings_in_world[frame, 1],\n", + " headings_in_world[frame, 2],\n", + " color=\"b\",\n", + " scale=1,\n", + " scale_units=\"xy\",\n", + " angles=\"xy\",\n", + " width=0.01,\n", + " )\n", + "\n", + " global gaze_quiver_sideprofile\n", + " gaze_quiver_sideprofile.remove()\n", + " gaze_quiver_sideprofile = axs[1].quiver(\n", + " 0,\n", + " 0,\n", + " cart_gazes_in_world[frame, 1],\n", + " cart_gazes_in_world[frame, 2],\n", + " color=\"r\",\n", + " scale=1,\n", + " scale_units=\"xy\",\n", + " angles=\"xy\",\n", + " width=0.01,\n", + " )\n", + "\n", + " return\n", + "\n", + "\n", + "fig.tight_layout()\n", + "\n", + "ani = animation.FuncAnimation(\n", + " fig=fig, func=update, frames=len(relative_demo_video_ts), interval=33.3333333333\n", + ")\n", + "ani.save(\"imu_heading.mp4\", writer=\"ffmpeg\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "gaze_left_start = 140\n", + "head_left_start = 315\n", + "gaze_head_left_start = 515\n", + "gaze_left_end = 255\n", + "head_left_end = 460\n", + "gaze_head_left_end = 650\n", + "\n", + "\n", + "gaze_right_start = 1201\n", + "head_right_start = 1364\n", + "gaze_head_right_start = 1614\n", + "gaze_right_end = 1325\n", + "head_right_end = 1560\n", + "gaze_head_right_end = 1762\n", + "\n", + "\n", + "gaze_up_start = 690\n", + "head_up_start = 850\n", + "gaze_head_up_start = 1030\n", + "gaze_up_end = 805\n", + "head_up_end = 1005\n", + "gaze_head_up_end = 1160\n", + "\n", + "\n", + "gaze_down_start = 1828\n", + "head_down_start = 2028\n", + "gaze_head_down_start = 2218\n", + "gaze_down_end = 1931\n", + "head_down_end = 2155\n", + "gaze_head_down_end = 2356\n", + "\n", + "\n", + "free_viewing_start = 2415\n", + "free_viewing_end = len(world_ts[200:-100]) - 1" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# The gaze+eye overlay video was made with the pl-neon-recording library:\n", + "# https://github.com/pupil-labs/pl-neon-recording\n", + "import pupil_labs.neon_recording as nr\n", + "\n", + "native_rec_dir = \"./native_2024-08-19_14-20-57-48fd6838/\"\n", + "recording = nr.load(native_rec_dir)\n", + "\n", + "\n", + "def overlay_image(img, img_overlay, x, y):\n", + " \"\"\"Overlay `img_overlay` onto `img` at (x, y).\"\"\"\n", + "\n", + " # Image ranges\n", + " y1, y2 = max(0, y), min(img.shape[0], y + img_overlay.shape[0])\n", + " x1, x2 = max(0, x), min(img.shape[1], x + img_overlay.shape[1])\n", + "\n", + " # Overlay ranges\n", + " y1o, y2o = max(0, -y), min(img_overlay.shape[0], img.shape[0] - y)\n", + " x1o, x2o = max(0, -x), min(img_overlay.shape[1], img.shape[1] - x)\n", + "\n", + " if y1 >= y2 or x1 >= x2 or y1o >= y2o or x1o >= x2o:\n", + " return\n", + "\n", + " img_crop = img[y1:y2, x1:x2]\n", + " img_overlay_crop = img_overlay[y1o:y2o, x1o:x2o]\n", + " img_crop[:] = img_overlay_crop\n", + "\n", + "\n", + "def overlay_text(img, x, y, text=\"hello!\"):\n", + " text_background = np.zeros((110, 1600, 3), dtype=np.uint8)\n", + " text_background[:] = (255, 255, 255)\n", + "\n", + " overlay_image(img, text_background, 0, y-85)\n", + "\n", + " text_position = (x, y)\n", + " font = cv2.FONT_HERSHEY_SIMPLEX\n", + " font_scale = 3\n", + " color = (0, 0, 0)\n", + " thickness = 4\n", + " img = cv2.putText(img, text, text_position, font, font_scale, color, thickness)\n", + " return img\n", + "\n", + "\n", + "def make_overlaid_video(recording, output_video_path, fps=30):\n", + " video_writer = cv2.VideoWriter(\n", + " str(output_video_path),\n", + " cv2.VideoWriter_fourcc(*\"MJPG\"),\n", + " fps,\n", + " (recording.scene.width, recording.scene.height),\n", + " )\n", + "\n", + " output_timestamps = np.arange(\n", + " world_ts.iloc[200] * 1e-9,\n", + " world_ts.iloc[-100] * 1e-9,\n", + " 1 / 30,\n", + " )\n", + "\n", + " combined_data = zip(\n", + " recording.scene.sample(output_timestamps),\n", + " recording.eye.sample(output_timestamps),\n", + " recording.gaze.sample(output_timestamps),\n", + " )\n", + "\n", + " frame_idx = 0\n", + " for scene_frame, eye_frame, gaze_datum in combined_data:\n", + " frame_idx += 1\n", + " frame_pixels = scene_frame.bgr\n", + " eye_pixels = cv2.cvtColor(eye_frame.gray, cv2.COLOR_GRAY2BGR)\n", + " \n", + " frame_pixels = cv2.circle(\n", + " frame_pixels, (int(gaze_datum.x), int(gaze_datum.y)), 50, (0, 0, 255), 10\n", + " )\n", + "\n", + " overlay_image(frame_pixels, eye_pixels, 50, 50)\n", + " \n", + " if frame_idx >= gaze_left_start and frame_idx <= gaze_left_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Gaze left\")\n", + " elif frame_idx >= head_left_start and frame_idx <= head_left_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Head left\")\n", + " elif frame_idx >= gaze_head_left_start and frame_idx <= gaze_head_left_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Gaze and head left\")\n", + " elif frame_idx >= gaze_right_start and frame_idx <= gaze_right_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Gaze right\")\n", + " elif frame_idx >= head_right_start and frame_idx <= head_right_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Head right\")\n", + " elif frame_idx >= gaze_head_right_start and frame_idx <= gaze_head_right_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Gaze and head right\")\n", + " elif frame_idx >= gaze_up_start and frame_idx <= gaze_up_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Gaze up\")\n", + " elif frame_idx >= head_up_start and frame_idx <= head_up_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Head up\")\n", + " elif frame_idx >= gaze_head_up_start and frame_idx <= gaze_head_up_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Gaze and head up\")\n", + " elif frame_idx >= gaze_down_start and frame_idx <= gaze_down_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Gaze down\")\n", + " elif frame_idx >= head_down_start and frame_idx <= head_down_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Head down\")\n", + " elif frame_idx >= gaze_head_down_start and frame_idx <= gaze_head_down_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Gaze and head down\")\n", + " elif frame_idx >= free_viewing_start and frame_idx <= free_viewing_end:\n", + " frame_pixels = overlay_text(frame_pixels, 100, 1100, \"Free viewing\")\n", + " \n", + " video_writer.write(frame_pixels)\n", + "\n", + " video_writer.release()\n", + "\n", + "\n", + "make_overlaid_video(recording, \"eye-gaze-overlay-output-video.avi\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/alpha-lab/imu-transformations/index.md b/alpha-lab/imu-transformations/index.md new file mode 100644 index 000000000..0d158e347 --- /dev/null +++ b/alpha-lab/imu-transformations/index.md @@ -0,0 +1,263 @@ +--- +title: "Transform IMU Data" +description: "Transform IMU data into different representations and coordinate systems." +permalink: /alpha-lab/imu-transformations/ +meta: + - name: twitter:card + content: summary + - name: twitter:image + content: "https://i.ytimg.com/vi/DMvmCpCL1ZQ/maxresdefault.jpg" + - name: twitter:player + content: "https://www.youtube.com/embed/DMvmCpCL1ZQ" + - name: twitter:width + content: "1280" + - name: twitter:height + content: "720" + - property: og:image + content: "https://i.ytimg.com/vi/DMvmCpCL1ZQ/maxresdefault.jpg" +tags: [Neon, Cloud] +--- + + + +# IMU Transformations + + + +This guide contains various transformation functions that help with relating [Neon's IMU data](https://docs.pupil-labs.com/neon/data-collection/data-streams/#movement-imu-data) with other data streams. + +As you work through this guide, you may want to check out the [Application Example](#application-example) to see the code in action. + +## Rotation between the IMU and the World + +The IMU data includes a description of how the IMU is rotated in relation to the world. Concretely, the IMU data contains quaternions that define a rotation transformation between the [the world coordinate system](http://docs.pupil-labs.com/neon/data-collection/data-streams/#movement-imu-data) and the IMU's local coordinate system at different points in time. + +The `transform_imu_to_world` function below demonstrates how to use these quaternions to transform data from the IMU's local coordinate system to the world coordinate system. + +```python +from scipy.spatial.transform import Rotation as R + +def transform_imu_to_world(imu_coordinates, imu_quaternions): + # This array contains a timeseries of transformation matrices, + # as calculated from the IMU's timeseries of quaternions values. + imu_to_world_matrices = R.from_quat(imu_quaternions).as_matrix() + + if np.ndim(imu_coordinates) == 1: + return imu_to_world_matrices @ imu_coordinates + else: + return np.array([ + imu_to_world @ imu_coord + for imu_to_world, imu_coord in zip( + imu_to_world_matrices, imu_coordinates + ) + ]) +``` + +### Example: Heading Vectors in World Coordinates + +The `transform_imu_to_world` function can be used to calculate heading vectors of the IMU in world coordinates. The heading vector essentially describes the direction the IMU is facing. If we imagine the IMU inside the Neon module while it is worn on sombody's head, the heading vector describes the direction the wearer's face is pointing. + +The "forward-facing axis" is the y-axis, so we can calculate the heading vector by transforming the `(0, 1, 0)` vector. + +```python +def imu_heading_in_world(imu_quaternions): + heading_neutral_in_imu_coords = np.array([0.0, 1.0, 0.0]) + return transform_imu_to_world( + heading_neutral_in_imu_coords, imu_quaternions + ) +``` + +::: tip +Neutral orientation (i.e. an identity rotation in the quaternion) of the IMU would correspond to a heading vector that points at magnetic North and that is oriented perpendicular to the line of gravity. +::: + +### Example: Acceleration in World Coordinates + +The IMU’s translational acceleration data is given in the IMU's local coordinate system. To understand how the observer is accelerating through the world it can be helpful to transform the data into the world coordinate system: + +```python +accelerations_in_world = transform_imu_to_world( + imu_accelerations, imu_quaternions +) +``` + +## Scene to World Coordinates + +A lot of the data generated by Neon is provided in the scene camera's coordinate system, including e.g. gaze, fixation, and eye state data. This coordinate system is **not** equal to the IMU's coordinate system! There is a translation between them (simply because there is a physical distance between the camera and the IMU in the module) and also a rotation (because of how the scene camera's coordinate system is defined). + +The rotation is a 102 degree rotation around the x-axis of the IMU coordinate system and the translation is along the vector `(0.0 mm, -1.3 mm, -6.62 mm)`. + +![Diagrams showing the fixed 102 degree rotation offset between the IMU and scene camera coordinate systems.](./imu-scene_camera_offset-black.png) + +We can define a `transform_scene_to_imu` function that handles the rotation between the two coordinate systems. + +```python +def transform_scene_to_imu(coords_in_scene, translation_in_imu=np.array([0.0, -1.3, -6.62])): + imu_scene_rotation_diff = np.deg2rad(-90 - 12) + scene_to_imu = np.array( + [ + [1.0, 0.0, 0.0], + [ + 0.0, + np.cos(imu_scene_rotation_diff), + -np.sin(imu_scene_rotation_diff), + ], + [ + 0.0, + np.sin(imu_scene_rotation_diff), + np.cos(imu_scene_rotation_diff), + ], + ] + ) + + coords_in_imu = scene_to_imu @ coords_in_scene.T + + coords_in_imu[0, :] += translation_in_imu[0] + coords_in_imu[1, :] += translation_in_imu[1] + coords_in_imu[2, :] += translation_in_imu[2] + + return coords_in_imu.T +``` + +Combining the `transform_scene_to_imu` function with the `transform_imu_to_world` function allows us to go all the way from scene camera coordinate system to world coordinate system + +```python +def transform_scene_to_world(coords_in_scene, imu_quaternions, translation_in_imu=np.array([0.0, -1.3, -6.62])): + coords_in_imu = transform_scene_to_imu(coords_in_scene, translation_in_imu) + return transform_imu_to_world(coords_in_imu, imu_quaternions) +``` + +### Example: Eyestate in World Coordinates + +The `transform_scene_to_world` function allows us easily convert [eye state data](https://docs.pupil-labs.com/neon/data-collection/data-streams/#_3d-eye-states) given in scene camera coordinates to world coordinates. + +::: warning +Note, to do this right in practice you need to make sure you sample the quaternions and eye state data from the same timestamps. Since both data streams are generated independently and do not share the same set of timestamps, this is a challenge in itself. + +We are glossing over this here, but one possible solution to this is interpolating the IMU data to match the timestamps of the eye state data, which is demonstrated [here](http://docs.pupil-labs.com/alpha-lab/imu-transformations/#application-example). +::: + +```python +def eyestate_to_world(eyeball_centers, optical_axes, imu_quaternions): + """ + The eyeball_centers and optical_axes inputs are for the same eye. + """ + + # The eyeball centers are specified relative to the center of the scene + # camera, so we need to account for the position of the scene camera in + # the IMU coordinate system. Here, we express that position in millimeters. + eyeball_centers_in_world = transform_scene_to_world( + eyeball_centers, imu_quaternions + ) + + # The optical axes are unit vectors originating at the eyeball centers, + # so they should not be translated. + optical_axes_in_world = transform_scene_to_world( + optical_axes, imu_quaternions, translation=np.zeros(3) + ) + + return eyeball_centers_in_world, optical_axes_in_world +``` + +### Example: 3D Gaze Direction in World Coordinates +Neon provides 3D gaze directions in [spherical coordinates (i.e., `azimuth/elevation [deg]`)](https://docs.pupil-labs.com/neon/data-collection/data-format/#gaze-csv). The `transform_scene_to_world` function above expects 3D Cartesian coordinates, so we need to convert the data first. + +```python +def spherical_to_cartesian_scene(elevations, azimuths): + """ + Convert Neon's spherical representation of 3D gaze to Cartesian coordinates. + """ + + elevations_rad = np.deg2rad(elevations) + azimuths_rad = np.deg2rad(azimuths) + + # Elevation of 0 in Neon system corresponds to Y = 0, but + # an elevation of 0 in traditional spherical coordinates would + # correspond to Y = 1, so we convert elevation to the + # more traditional format. + elevations_rad += np.pi / 2 + + # Azimuth of 0 in Neon system corresponds to X = 0, but + # an azimuth of 0 in traditional spherical coordinates would + # correspond to X = 1. Also, azimuth to the right in Neon is + # more positive, whereas it is more negative in traditional + # spherical coordiantes. So, we convert azimuth to the more + # traditional format. + azimuths_rad *= -1.0 + azimuths_rad += np.pi / 2 + + return np.array( + [ + np.sin(elevations_rad) * np.cos(azimuths_rad), + np.cos(elevations_rad), + np.sin(elevations_rad) * np.sin(azimuths_rad), + ] + ).T +``` + +Now we can transform the data to world coordinates. Since we are dealing with 3D directions, rather than 3D points here, it does not make sense to apply the translation that we used in the `transform_scene_to_world` function above. We are thus setting it to zero here. + +```python +def gaze_3d_to_world(gaze_elevation, gaze_azimuth, imu_quaternions): + cart_gazes_in_scene = spherical_to_cartesian_scene(gaze_elevation, gaze_azimuth) + return transform_scene_to_world(cart_gazes_in_scene, imu_quaternions, translation_in_imu=np.zeros(3)) +``` + +## World Spherical Coordinates +Using the transformations introduced above, we can transform various data into cartesian world coordinates. For some things it is more intuitive to have the data in spherical coordinates though. For instance, you might want to know when someone’s gaze or heading deviates from parallel with the horizon, i.e. if they are looking/facing upwards or downwards. + +Converting data into spherical world coordinates makes these things obvious. When wearing Neon, an elevation and azimuth of 0 degrees corresponds to a neutral orientation: i.e., aimed at magnetic North and parallel to the horizon. A positive elevation corresponds to looking upwards, and a negative elevation corresponds to looking downwards. + +The [Euler angles from the IMU](https://docs.pupil-labs.com/neon/data-collection/data-streams/#euler-angles) are already in a compatible format. For gaze data in world coordinates, the `cartesian_to_spherical_world` function below will do the necessary transformation. + +```python +def cartesian_to_spherical_world(world_points_3d): + """ + Convert points in 3D Cartesian world coordinates to spherical coordinates. + + For elevation: + - Neutral orientation = 0 (i.e., parallel with horizon) + - Upwards is positive + - Downwards is negative + + For azimuth: + - Neutral orientation = 0 (i.e., aligned with magnetic North) + - Leftwards is positive + - Rightwards is negative + """ + + x = world_points_3d[:, 0] + y = world_points_3d[:, 1] + z = world_points_3d[:, 2] + + radii = np.sqrt(x**2 + y**2 + z**2) + + elevation = -(np.arccos(z / radii) - np.pi / 2) + azimuth = np.arctan2(y, x) - np.pi / 2 + + # Keep all azimuth values in the range of [-180, 180] to remain + # consistent with the yaw orientation values provided by the IMU. + azimuth[azimuth < -np.pi] += 2 * np.pi + azimuth[azimuth > np.pi] -= 2 * np.pi + + elevation = np.rad2deg(elevation) + azimuth = np.rad2deg(azimuth) + + return elevation, azimuth +``` + +## Application Example + +Below, we present a video showing how some of the functions in this article were used to visualize different combinations of head and eye movements in world coordinates. The code for producing the visualization [can be found here](https://github.com/pupil-labs/pupil-docs/tree/master/alpha-lab/imu-transformations/imu_heading_visualization.ipynb). + + + +## Related content + +It can also be helpful to try out our IMU visualization utility, [plimu](https://github.com/pupil-labs/plimu). This can assist in understanding the IMU data and the various coordinate systems. For example, some of the code in this article is [adapted from it](https://github.com/pupil-labs/plimu/blob/8b94302982363b203dddea2b15f43c6da60e787e/src/pupil_labs/plimu/visualizer.py#L274-L279). + +::: tip +Need assistance with the IMU code in this article? Or do you have something more custom in mind? Reach out to us via email at [info@pupil-labs.com](mailto:info@pupil-labs.com), on our [Discord server](https://pupil-labs.com/chat/), or visit our [Support Page](https://pupil-labs.com/products/support/) for dedicated support options. +::: diff --git a/alpha-lab/public/imu-transformations.webp b/alpha-lab/public/imu-transformations.webp new file mode 100644 index 000000000..14f10f5de Binary files /dev/null and b/alpha-lab/public/imu-transformations.webp differ diff --git a/neon/data-collection/data-format/index.md b/neon/data-collection/data-format/index.md index 67a625d6a..7fe76fdfb 100644 --- a/neon/data-collection/data-format/index.md +++ b/neon/data-collection/data-format/index.md @@ -1,172 +1,170 @@ # Recording Format -The page describes the data format when downloading Neon recordings from Pupil Cloud in the "Timeseries Data" and "Timeseries Data + Scene Video" formats. In this format the Data from the Neon Companion is augmented by adding gaze and eye state estimates whenever they had not been computed in realtime. Futhermore fixations and blink data as well as some IMU transformations are computed. -When downloading Native Recording Data from cloud or direclty [extracting it via usb from the companion device](/data-collection/transfer-recordings-via-usb/), use the [pl-neon-recording](https://github.com/pupil-labs/pl-neon-recording) python library to read and access the data. This data format will contain all video data as well as all data that had been computed in realtime on the companion device. +The page describes the data format when downloading Neon recordings from Pupil Cloud in the "Timeseries Data" and "Timeseries Data + Scene Video" formats. In this format the Data from the Neon Companion is augmented by adding gaze and eye state estimates whenever they had not been computed in realtime. Futhermore fixations and blink data as well as some IMU transformations are computed. +When downloading Native Recording Data from the Cloud or directly [extracting it via USB from the companion device](/data-collection/transfer-recordings-via-usb/), you can use the [pl-neon-recording](https://github.com/pupil-labs/pl-neon-recording) python library to read and access the data or load it onto [Neon Player](/neon-player/). This data format will contain all video data as well as all data that has been computed in realtime on the companion device. ## Recording Folders + The export contains one folder per recording following this naming scheme: -```-``` +`-` The files included in every folder are described in the following. ## info.json -This file contains meta-information on the recording. +This file contains meta-information on the recording. -| Field | Description | -| ------------------------- | -------- | -| **android_device_id** | Unique identifier of the Android device used as Companion. | -| **android_device_model** | Model name of the Companion device. | -| **android_device_name** | Device name of the Companion device. | -| **app_version** | Version of the Neon Companion app used to make the recording. | -| **calib_version** | Version of the offset correction used by the Neon Companion app. | -| **data_format_version** | Version of the data format used by the Neon Companion app. | -| **duration** | Duration of the recording in nanoseconds| -| **firmware_version** | Version numbers of the firmware and FPGA. | -| **frame_id** | Number identifying the type of frame used for this recording. | -| **frame_name** | Name of the frame used for this recording. | -| **gaze_offset** | Gaze offset applied to this recording using the offset correction. Values are in pixels.| -| **module_serial_number** | Serial number of the Neon module used for the recording. This number is encoded in the QR code on the back of the Neon module. | -| **os_version** | Version of the Android OS that was installed on the recording Companion device. | -| **pipeline_version** | Version of the gaze estimation pipeline used by the Neon Companion app. | -| **recording_id** | Unique identifier of the recording. | -| **start_time** | Timestamp of when the recording was started. Given as UTC timestamp in nanoseconds. | -| **template_data** | Data regarding the selected template for the recording as well as the response values. | -| **wearer_id** | Unique identifier of the wearer selected for this recording. | -| **wearer_name** | Name of the wearer selected for this recording. | -| **workspace_id** | The ID of the Pupil Cloud workspace this recording has been assigned to. | - +| Field | Description | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | +| **android_device_id** | Unique identifier of the Android device used as Companion. | +| **android_device_model** | Model name of the Companion device. | +| **android_device_name** | Device name of the Companion device. | +| **app_version** | Version of the Neon Companion app used to make the recording. | +| **calib_version** | Version of the offset correction used by the Neon Companion app. | +| **data_format_version** | Version of the data format used by the Neon Companion app. | +| **duration** | Duration of the recording in nanoseconds | +| **firmware_version** | Version numbers of the firmware and FPGA. | +| **frame_id** | Number identifying the type of frame used for this recording. | +| **frame_name** | Name of the frame used for this recording. | +| **gaze_offset** | Gaze offset applied to this recording using the offset correction. Values are in pixels. | +| **module_serial_number** | Serial number of the Neon module used for the recording. This number is encoded in the QR code on the back of the Neon module. | +| **os_version** | Version of the Android OS that was installed on the recording Companion device. | +| **pipeline_version** | Version of the gaze estimation pipeline used by the Neon Companion app. | +| **recording_id** | Unique identifier of the recording. | +| **start_time** | Timestamp of when the recording was started. Given as UTC timestamp in nanoseconds. | +| **template_data** | Data regarding the selected template for the recording as well as the response values. | +| **wearer_id** | Unique identifier of the wearer selected for this recording. | +| **wearer_name** | Name of the wearer selected for this recording. | +| **workspace_id** | The ID of the Pupil Cloud workspace this recording has been assigned to. | **Scene Video** Scene video is contained in a file following the following naming scheme: -```_
-
.mp4``` +`_
-
.mp4` ## scene_camera.json -This file contains the camera intrinsics of the used scene camera. The values are determined via calibration of every camera during manufacturing. - -| Field | Description | -| -------- | -------- | -| **camera_matrix** | The camera matrix of the scene camera. | -| **dist_coefs** | The distortion coefficients of the scene camera. The order of the values is `(k1, k2, p1, p2, k3, k4, k5, k6)` following [OpenCV's distortion model](https://docs.opencv.org/4.x/d9/d0c/group__calib3d.html#ga3207604e4b1a1758aa66acb6ed5aa65d). | -| **serial_number** | Serial number of Neon module used for the recording. This number is encoded in the QR code on the back of the Neon module. | -| **version** | The version of the intrinsics data format. | +This file contains the camera intrinsics of the used scene camera. The values are determined via calibration of every camera during manufacturing. +| Field | Description | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **camera_matrix** | The camera matrix of the scene camera. | +| **dist_coefs** | The distortion coefficients of the scene camera. The order of the values is `(k1, k2, p1, p2, k3, k4, k5, k6)` following [OpenCV's distortion model](https://docs.opencv.org/4.x/d9/d0c/group__calib3d.html#ga3207604e4b1a1758aa66acb6ed5aa65d). | +| **serial_number** | Serial number of Neon module used for the recording. This number is encoded in the QR code on the back of the Neon module. | +| **version** | The version of the intrinsics data format. | ## world_timestamps.csv + This file contains the timestamps of every world video frame. -| Field | Description | -| -------- | -------- | -| **section id** | Unique identifier of the corresponding section. | -| **recording id** | Unique identifier of the recording this sample belongs to. | +| Field | Description | +| ------------------ | -------------------------------------------------------------- | +| **section id** | Unique identifier of the corresponding section. | +| **recording id** | Unique identifier of the recording this sample belongs to. | | **timestamp [ns]** | UTC timestamp in nanoseconds of the corresponding world frame. | - ## events.csv -This file contains [event](/data-collection/events/) data for all recordings. It contains both event annotations from Pupil Cloud and real-time recording events. -| Field | Description | -| -------- | -------- | -| **recording id** | Unique identifier of the recording this event belongs to. | -| **timestamp [ns]** | UTC timestamp of the event. | -| **name** | Name of the event. | -| **type** | Type of the event. Possible values: cloud, recording | +This file contains [event](/data-collection/events/) data for all recordings. It contains both event annotations from Pupil Cloud and real-time recording events. +| Field | Description | +| ------------------ | --------------------------------------------------------- | +| **recording id** | Unique identifier of the recording this event belongs to. | +| **timestamp [ns]** | UTC timestamp of the event. | +| **name** | Name of the event. | +| **type** | Type of the event. Possible values: cloud, recording | ## gaze.csv -This file contains [gaze](/data-collection/data-streams/#gaze) data in world camera coordinates. - -| Field | Description | -| -------- | -------- | -| **section id** | Unique identifier of the corresponding section. | -| **recording id** | Unique identifier of the recording this sample belongs to. | -| **timestamp [ns]** | UTC timestamp in nanoseconds of the sample. Equal to the timestamp of the eye video frame this sample was generated with. | -| **gaze x [px]** | Float value representing the x-coordinate of the mapped gaze point in world camera pixel coordinates. -| **gaze y [px]** | Same as "gaze x [px]" but for the y-coordinate. | -| **worn** | These values indicate whether Neon has been worn by a subject at the respective point in time. `1.0` indicates that it has been worn, while `0.0` indicates that it has not been worn. | -| **fixation id** | If this gaze sample belongs to a fixation event, this is the corresponding id of the fixation. Otherwise, this field is empty. | -| **blink id** | If this gaze samples belongs to a blink event, this is the corresponding id of the blink. Otherwise this field is empty. | -| **azimuth [deg]** | The [azimuth](https://en.wikipedia.org/wiki/Horizontal_coordinate_system) of the gaze ray in relation to the scene camera in degrees. | -| **elevation [deg]** | The [elevation](https://en.wikipedia.org/wiki/Horizontal_coordinate_system) of the gaze ray in relation to the scene camera in degrees. | +This file contains [gaze](/data-collection/data-streams/#gaze) data in world camera coordinates. +| Field | Description | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **section id** | Unique identifier of the corresponding section. | +| **recording id** | Unique identifier of the recording this sample belongs to. | +| **timestamp [ns]** | UTC timestamp in nanoseconds of the sample. Equal to the timestamp of the eye video frame this sample was generated with. | +| **gaze x [px]** | Float value representing the x-coordinate of the mapped gaze point in world camera pixel coordinates. | +| **gaze y [px]** | Same as "gaze x [px]" but for the y-coordinate. | +| **worn** | These values indicate whether Neon has been worn by a subject at the respective point in time. `1.0` indicates that it has been worn, while `0.0` indicates that it has not been worn. | +| **fixation id** | If this gaze sample belongs to a fixation event, this is the corresponding id of the fixation. Otherwise, this field is empty. | +| **blink id** | If this gaze samples belongs to a blink event, this is the corresponding id of the blink. Otherwise this field is empty. | +| **azimuth [deg]** | The [azimuth](https://en.wikipedia.org/wiki/Horizontal_coordinate_system) of the gaze ray in relation to the scene camera in degrees. | +| **elevation [deg]** | The [elevation](https://en.wikipedia.org/wiki/Horizontal_coordinate_system) of the gaze ray in relation to the scene camera in degrees. | ## fixations.csv + This file contains [fixations](/data-collection/data-streams/#fixations-saccades) detected in the gaze data stream. The corresponding gaze samples that belong to each fixation can be determined from the `gaze.csv` file using the `fixation id` field. - -| Field | Description | -| -------- | -------- | -| **section id** | Unique identifier of the corresponding section. | -| **recording id** | Unique identifier of the recording this sample belongs to. | -| **fixation id** | Identifier of the fixation. The counter starts at the beginning of the recording. | -| **start timestamp [ns]** | UTC timestamp in nanoseconds of the start of the fixation. | -| **end timestamp [ns]** | UTC timestamp in nanoseconds of the end of the fixation. | -| **duration [ms]** | Duration of the fixation in milliseconds. | -| **fixation x [px]** | Float value representing the x-coordinate of the fixation in world camera pixel coordinates. This position is the average of all gaze samples within the fixation. | -| **fixation y [px]** | Same as "fixation x [px]" but for the y-coordinate. | -| **azimuth [deg]** | The [azimuth](https://en.wikipedia.org/wiki/Horizontal_coordinate_system) of the gaze ray corresponding to the fixation location in relation to the scene camera in degrees. | -| **elevation [deg]** | The [elevation](https://en.wikipedia.org/wiki/Horizontal_coordinate_system) of the gaze ray corresponding to the fixation location in relation to the scene camera in degrees. | +| Field | Description | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **section id** | Unique identifier of the corresponding section. | +| **recording id** | Unique identifier of the recording this sample belongs to. | +| **fixation id** | Identifier of the fixation. The counter starts at the beginning of the recording. | +| **start timestamp [ns]** | UTC timestamp in nanoseconds of the start of the fixation. | +| **end timestamp [ns]** | UTC timestamp in nanoseconds of the end of the fixation. | +| **duration [ms]** | Duration of the fixation in milliseconds. | +| **fixation x [px]** | Float value representing the x-coordinate of the fixation in world camera pixel coordinates. This position is the average of all gaze samples within the fixation. | +| **fixation y [px]** | Same as "fixation x [px]" but for the y-coordinate. | +| **azimuth [deg]** | The [azimuth](https://en.wikipedia.org/wiki/Horizontal_coordinate_system) of the gaze ray corresponding to the fixation location in relation to the scene camera in degrees. | +| **elevation [deg]** | The [elevation](https://en.wikipedia.org/wiki/Horizontal_coordinate_system) of the gaze ray corresponding to the fixation location in relation to the scene camera in degrees. | ## saccades.csv -This file contains [saccades](/data-collection/data-streams/#fixations-saccades) detected by the fixation detector. +This file contains [saccades](/data-collection/data-streams/#fixations-saccades) detected by the fixation detector. -| Field | Description | -| -------- | -------- | -| **section id** | Unique identifier of the corresponding section. | -| **recording id** | Unique identifier of the recording this sample belongs to. | -| **saccade id** | Identifier of the saccade. The counter starts at the beginning of the recording. | -| **start timestamp [ns]** | UTC timestamp in nanoseconds of the start of the saccade. | -| **end timestamp [ns]** | UTC timestamp in nanoseconds of the end of the saccade. | -| **duration [ms]** | Duration of the saccade in milliseconds. | -| **amplitude [px]** | Float value representing the amplitude of the saccade in world camera pixel coordinates. | -| **amplitude [deg]** | Float value representing the amplitude of the saccade in degrees of visual angle. | +| Field | Description | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------- | +| **section id** | Unique identifier of the corresponding section. | +| **recording id** | Unique identifier of the recording this sample belongs to. | +| **saccade id** | Identifier of the saccade. The counter starts at the beginning of the recording. | +| **start timestamp [ns]** | UTC timestamp in nanoseconds of the start of the saccade. | +| **end timestamp [ns]** | UTC timestamp in nanoseconds of the end of the saccade. | +| **duration [ms]** | Duration of the saccade in milliseconds. | +| **amplitude [px]** | Float value representing the amplitude of the saccade in world camera pixel coordinates. | +| **amplitude [deg]** | Float value representing the amplitude of the saccade in degrees of visual angle. | | **mean velocity [px/s]** | Float value representing the mean velocity of the saccade in world camera pixel coordinates per second. | | **peak velocity [px/s]** | Float value representing the peak velocity of the saccade in world camera pixel coordinates per second. | - ## 3d_eye_states.csv -This file contains [3D eye states](/data-collection/data-streams/#_3d-eye-states) as well as [pupil diameter](/data-collection/data-streams/#pupil-diameters) data. +This file contains [3D eye states](/data-collection/data-streams/#_3d-eye-states) as well as [pupil diameter](/data-collection/data-streams/#pupil-diameters) data. -| Field | Description | -| ------------------------- | -------- | -| **section id** | Unique identifier of the corresponding section. | -| **recording id** | Unique identifier of the recording this sample belongs to. | -| **timestamp [ns]** | UTC timestamp in nanoseconds of the sample. Equal to the timestamp of the eye video frame this sample was generated with. | -| **pupil diameter left [mm]** | Physical diameter of the pupil of the left eye. | -| **pupil diameter right [mm]** | Physical diameter of the pupil of the right eye. | -| **eye ball center left x [mm]**
**eye ball center left y [mm]**
**eye ball center left z [mm]**
**eye ball center right x [mm]**
**eye ball center right y [mm]**
**eye ball center right z [mm]** | Location of left and right eye ball centers in millimeters in relation to the scene camera of the Neon module. For details on the coordinate systems see [here](/data-collection/data-streams/#_3d-eye-states). | -| **optical axis left x**
**optical axis left y**
**optical axis left z**
**optical axis right x**
**optical axis right y**
**optical axis right z** | Directional vector describing the optical axis of the left and right eye, i.e. the vector pointing from eye ball center to pupil center of the respective eye. For details on the coordinate systems see [here](/data-collection/data-streams/#_3d-eye-states). | +| Field | Description | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **section id** | Unique identifier of the corresponding section. | +| **recording id** | Unique identifier of the recording this sample belongs to. | +| **timestamp [ns]** | UTC timestamp in nanoseconds of the sample. Equal to the timestamp of the eye video frame this sample was generated with. | +| **pupil diameter left [mm]** | Physical diameter of the pupil of the left eye. | +| **pupil diameter right [mm]** | Physical diameter of the pupil of the right eye. | +| **eye ball center left x [mm]**
**eye ball center left y [mm]**
**eye ball center left z [mm]**
**eye ball center right x [mm]**
**eye ball center right y [mm]**
**eye ball center right z [mm]** | Location of left and right eye ball centers in millimeters in relation to the scene camera of the Neon module. For details on the coordinate systems see [here](/data-collection/data-streams/#_3d-eye-states). | +| **optical axis left x**
**optical axis left y**
**optical axis left z**
**optical axis right x**
**optical axis right y**
**optical axis right z** | Directional vector describing the optical axis of the left and right eye, i.e. the vector pointing from eye ball center to pupil center of the respective eye. For details on the coordinate systems see [here](/data-collection/data-streams/#_3d-eye-states). | ## blinks.csv + This file contains [blinks](/data-collection/data-streams/#blinks) detected in the eye video. The corresponding gaze samples that belong to each blink can be determined from the `gaze.csv` file using the `blink id` field. - -| Field | Description | -| -------- | -------- | -| **section id** | Unique identifier of the corresponding section. | -| **recording id** | Unique identifier of the recording this sample belongs to. | -| **blink id** | Identifier of the blink. The counter starts at the beginning of the recording. | -| **start timestamp [ns]** | UTC timestamp in nanoseconds of the start of the blink. | -| **end timestamp [ns]** | UTC timestamp in nanoseconds of the end of the blink. | -| **duration [ms]** | Duration of the blink in milliseconds. | +| Field | Description | +| ---------------------------------- | ------------------------------------------------------------------------------ | +| **section id** | Unique identifier of the corresponding section. | +| **recording id** | Unique identifier of the recording this sample belongs to. | +| **blink id** | Identifier of the blink. The counter starts at the beginning of the recording. | +| **start timestamp [ns]** | UTC timestamp in nanoseconds of the start of the blink. | +| **end timestamp [ns]** | UTC timestamp in nanoseconds of the end of the blink. | +| **duration [ms]** | Duration of the blink in milliseconds. | ## imu.csv + This file contains data recorded by the integrated [IMU](/data-collection/data-streams/#movement-imu-data) (inertial measurement unit). -| Field | Description | -| -------- | -------- | -| **section id** | Unique identifier of the corresponding section. | -| **recording id** | Unique identifier of the recording this sample belongs to. | -| **timestamp [ns]** | UTC timestamp in nanoseconds of the sample. | -| **gyro x [deg/s]**
**gyro y [deg/s]**
**gyro z [deg/s]** | Rotation speed around x, y or z-axis respectively in degrees/s. | -| **acceleration x [g]**
**acceleration y [g]**
**acceleration z [g]** | Translational acceleration (in terms of [g-force](https://en.m.wikipedia.org/wiki/G-force)) along the x, y or z-axis respectively. Note `1 g = 9.80665 m/s^2`.| -| **roll [deg]** | Drift-free estimate of the roll (head tilt from side to side) in degrees. The output range is -180 to +180 degrees. Added in version 2 of this enrichment. | -| **pitch [deg]** | Drift-free estimate of the pitch (head tilt from front to back) in degrees. The output range is -90 to +90 degrees. Added in version 2 of this enrichment. | -| **yaw [deg]** | Drift-free estimate of the yaw (horizontal head rotation) in degrees. The output range is -180 to +180 degrees. Added in version 2 of this enrichment. | -| **quaternion w**
**quaternion x**
**quaternion y**
**quaternion z** | Quaternion describing the rotation of the Neon module. | +| Field | Description | +| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **section id** | Unique identifier of the corresponding section. | +| **recording id** | Unique identifier of the recording this sample belongs to. | +| **timestamp [ns]** | UTC timestamp in nanoseconds of the sample. | +| **gyro x [deg/s]**
**gyro y [deg/s]**
**gyro z [deg/s]** | Rotation speed around x, y or z-axis respectively in degrees/s. | +| **acceleration x [g]**
**acceleration y [g]**
**acceleration z [g]** | Translational acceleration (in terms of [g-force](https://en.m.wikipedia.org/wiki/G-force)) along the x, y or z-axis respectively. Note `1 g = 9.80665 m/s^2`. | +| **roll [deg]** | Drift-free estimate of the roll (head tilt from side to side) in degrees. The output range is -180 to +180 degrees. Added in version 2 of this enrichment. | +| **pitch [deg]** | Drift-free estimate of the pitch (head tilt from front to back) in degrees. The output range is -90 to +90 degrees. Added in version 2 of this enrichment. | +| **yaw [deg]** | Drift-free estimate of the yaw (horizontal head rotation) in degrees. The output range is -180 to +180 degrees. Added in version 2 of this enrichment. | +| **quaternion w**
**quaternion x**
**quaternion y**
**quaternion z** | Quaternion describing the rotation of the Neon module. | diff --git a/neon/data-collection/data-streams/imu-pitch-yaw-roll-black.png b/neon/data-collection/data-streams/imu-pitch-yaw-roll-black.png new file mode 100644 index 000000000..f35c581a6 Binary files /dev/null and b/neon/data-collection/data-streams/imu-pitch-yaw-roll-black.png differ diff --git a/neon/data-collection/data-streams/imu-scene_camera_offset-black.png b/neon/data-collection/data-streams/imu-scene_camera_offset-black.png new file mode 100644 index 000000000..808f96257 Binary files /dev/null and b/neon/data-collection/data-streams/imu-scene_camera_offset-black.png differ diff --git a/neon/data-collection/data-streams/index.md b/neon/data-collection/data-streams/index.md index 4e5855784..5174aad87 100644 --- a/neon/data-collection/data-streams/index.md +++ b/neon/data-collection/data-streams/index.md @@ -84,24 +84,22 @@ Audio recording is disabled in the Neon Companion app by default and can be enab Available in: Real-timePupil CloudNeon Player The Neon module is equipped with a 9-DoF [inertial measurement unit](https://invensense.tdk.com/products/motion-tracking/9-axis/icm-20948/) (IMU) featuring an accelerometer, gyroscope, and magnetometer. The accelerometer and gyroscope measure linear acceleration and angular velocity, respectively, and are provided as raw values. -A fusion engine also combines these values with magnetometer readings to estimate the module's absolute orientation relative to magnetic north and gravity as a quaternion. Note that in order to obtain precise absolute yaw readings, the magnetometer needs to be [calibrated](/data-collection/calibrating-the-imu/). +A fusion engine also combines these values with magnetometer readings to estimate the module's absolute orientation relative to magnetic north (positive world y-axis), gravity (negative world z-axis), and a rightward pointing vector (positive world x-axis) as a quaternion. We refer to this as the world coordinate system. It is important to note that this is not the same as the local IMU coordinate system. -The IMU is located in the top bar of the module and samples at 110 Hz. Its coordinate system is oriented with the x-axis pointing to the right, the y-axis pointing in front, and the z-axis pointing upwards. +The IMU is located in the top bar of the module and is sampled at 110 Hz. Its local coordinate system is oriented with the x-axis pointing to the right, the y-axis pointing in front, and the z-axis pointing upwards. ![IMU Coordinate System](./imu-xyz-black.jpg) -When relating data from the IMU to things visible in the scene camera, it may be necessary to align their respective 3D coordinate systems. The IMU's coordinate system is rotated by 102° around the x-axis in relation to the scene camera's coordinate system. +When relating data from the IMU to things visible in the scene camera, it may be necessary to align their respective 3D coordinate systems. The IMU's coordinate system is rotated by 102° around the x-axis in relation to the scene camera's coordinate system. See our [IMU Transformations article](https://docs.pupil-labs.com/alpha-lab/imu-transformations/) for a guide. -![IMU Scene Camera](./imu-scene-camera-black.jpg) - -::: tip -Note that leftward rotations in the IMU coordinate system are positive, whereas they are negative in the scene camera and 3D eye state coordinate systems. -::: +![IMU Scene Camera](./imu-scene_camera_offset-black.png) ### Euler Angles -When exporting recordings from Pupil Cloud or Neon Player the IMU's orientation in Euler angles (i.e. roll, pitch, and yaw) is also available. +When exporting recordings from Pupil Cloud or Neon Player the IMU's orientation in Euler angles (i.e. pitch, yaw, and roll) is also available: -Pitch is defined as a rotation around the x-axis with a value range of -90° to +90°. Yaw and roll are rotations around the y- and z-axis, respectively, with value ranges of -180° to +180°. +- Pitch is defined as a rotation around the world x-axis with a value range of -90° to +90°. Wearing Neon upright and looking parallel to the horizon roughly corresponds to 0° pitch. Backward tilt is positive, forward is negative. +- Yaw is a rotation around the world z-axis with a value range of -180° to +180°. With a calibrated IMU, a yaw of 0° indicates alignment with magnetic north. Leftward turn is positive, rightward is negative. +- Roll is a rotations around the world y-axis with a value range of -180° to +180°. Wearing Neon upright with a neutral head pose roughly corresponds to a roll of 0°. Rightward tilt is positive, leftward is negative. -![IMU Pitch, Yaw, Roll](./imu-pitch-yaw-roll-black.jpg) +![IMU Pitch, Yaw, Roll](./imu-pitch-yaw-roll-black.png)