.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/preprocessing/automatic_sensor_alignment_details.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note Click :ref:`here ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_preprocessing_automatic_sensor_alignment_details.py: .. _example_automatic_sensor_alignment_detailed: Automatic sensor alignment (detailed) ===================================== This example illustrates all individual steps of an automatic sensor alignment pipeline, to make sure that the sensor coordinate frame is properly aligned with the foot, independent of the actual sensor attachment. This might be necessary e.g. in real-world datasets where participants attach and detach the sensor frequently and possibly place the sensor in unintended orientations like upside down or 90/180deg rotated. A minimal example of the automatic sensor alignment using only defaults can be found here: :ref:`example_automatic_sensor_alignment_simple` .. GENERATED FROM PYTHON SOURCE LINES 14-17 .. code-block:: default import matplotlib.pyplot as plt .. GENERATED FROM PYTHON SOURCE LINES 18-26 Getting some example data ------------------------- For this, we take some example data that contains stair ascending gait on a single, straight staircase of a healthy participant. The sensors were attached to the instep position. The dataset is already calibrated and conforms to the gaitmap sensor frame axis convention. Furthermore, as this was a supervised recording, the alignment of the sensor to the shoe/ foot was manually aligned before the recording. The data contains synchronized data from two sensors - one from the right and one from the left foot. .. GENERATED FROM PYTHON SOURCE LINES 26-31 .. code-block:: default from gaitmap.example_data import get_healthy_example_imu_data_stair_up from gaitmap.utils.consts import SF_ACC, SF_GYR example_dataset = get_healthy_example_imu_data_stair_up() sampling_rate_hz = 204.8 .. GENERATED FROM PYTHON SOURCE LINES 32-33 For simplicity, we will only look at one foot in this example. However, all functions work the same way on both feet. .. GENERATED FROM PYTHON SOURCE LINES 33-35 .. code-block:: default sensor = "right_sensor" .. GENERATED FROM PYTHON SOURCE LINES 36-41 Simulate some sensor misalignments ---------------------------------- First we simulate some heavily misaligned data by applying some static rotations around each axis of the sensor frame. Afterwards we will apply some of the gaitmap preprocessing functions to automatically correct for all those misalignments. Therefore, we have to apply multiple steps to correct for different types of misalignment! .. GENERATED FROM PYTHON SOURCE LINES 41-52 .. code-block:: default from scipy.spatial.transform import Rotation from gaitmap.utils.rotations import rotate_dataset # rotate the example data by some degree around each axis to simulate misalignment z_axis_rotation = Rotation.from_euler("z", 70, degrees=True) x_axis_rotation = Rotation.from_euler("x", 45, degrees=True) y_axis_rotation = Rotation.from_euler("y", -250, degrees=True) rotated_dataset = rotate_dataset(example_dataset, z_axis_rotation * x_axis_rotation * y_axis_rotation) .. GENERATED FROM PYTHON SOURCE LINES 53-54 Visualize the original and misaligned/ rotated dataset .. GENERATED FROM PYTHON SOURCE LINES 54-73 .. code-block:: default fig, axs = plt.subplots(2, 2, figsize=(13, 6)) axs[0, 0].plot(example_dataset[sensor].iloc[:5000][SF_ACC]) axs[0, 1].plot(rotated_dataset[sensor].iloc[:5000][SF_ACC]) for ax in axs[0]: ax.set_ylim([-150, 150]) ax.grid("on") axs[1, 0].plot(example_dataset[sensor].iloc[:5000][SF_GYR]) axs[1, 1].plot(rotated_dataset[sensor].iloc[:5000][SF_GYR]) for ax in axs[1]: ax.set_ylim([-850, 850]) ax.grid("on") axs[0, 0].set_title("Acceleration - original") axs[1, 0].set_title("Gyroscope - original") axs[0, 1].set_title("Acceleration - rotated") axs[1, 1].set_title("Gyroscope - rotated") fig.tight_layout() .. image-sg:: /auto_examples/preprocessing/images/sphx_glr_automatic_sensor_alignment_details_001.png :alt: Acceleration - original, Acceleration - rotated, Gyroscope - original, Gyroscope - rotated :srcset: /auto_examples/preprocessing/images/sphx_glr_automatic_sensor_alignment_details_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 74-83 Align to gravity ---------------- Firt we need to make sure sure that the z-axis is aligned with gravity (defined by [0,0,1]) as required by the gaitmap sensor-frame definition. This step will correct for rotations around the x- and y-axis. For this we will use a static-moment-detection to derive the absolute sensor orientation based on static accelerometer windows and find the shortest rotation to gravity. The sensor coordinate system will be finally rotated, such that all static accelerometer windows will be close to `acc = [0.0, 0.0, 9.81]`. .. GENERATED FROM PYTHON SOURCE LINES 83-90 .. code-block:: default from gaitmap.preprocessing import align_dataset_to_gravity gravity_aligned_data = align_dataset_to_gravity( rotated_dataset, sampling_rate_hz=sampling_rate_hz, window_length_s=0.1, static_signal_th=15 ) .. GENERATED FROM PYTHON SOURCE LINES 91-92 Visualize the result of the gravity alignment .. GENERATED FROM PYTHON SOURCE LINES 92-116 .. code-block:: default fig, axs = plt.subplots(2, 3, figsize=(13, 6)) axs[0, 0].plot(example_dataset[sensor].iloc[:1000][SF_ACC]) axs[0, 1].plot(rotated_dataset[sensor].iloc[:1000][SF_ACC]) axs[0, 2].plot(gravity_aligned_data[sensor].iloc[:1000][SF_ACC]) for ax in axs[0]: ax.set_ylim([-15, 15]) ax.grid("on") axs[1, 0].plot(example_dataset[sensor].iloc[1000:2000][SF_GYR]) axs[1, 1].plot(rotated_dataset[sensor].iloc[1000:2000][SF_GYR]) axs[1, 2].plot(gravity_aligned_data[sensor].iloc[1000:2000][SF_GYR]) for ax in axs[1]: ax.set_ylim([-850, 850]) ax.grid("on") axs[0, 0].set_title("Acceleration - original") axs[1, 0].set_title("Gyroscope - original") axs[0, 1].set_title("Acceleration - rotated") axs[1, 1].set_title("Gyroscope - rotated") axs[0, 2].set_title("Acceleration - gravity aligned") axs[1, 2].set_title("Gyroscope - gravity aligned") fig.tight_layout() .. image-sg:: /auto_examples/preprocessing/images/sphx_glr_automatic_sensor_alignment_details_002.png :alt: Acceleration - original, Acceleration - rotated, Acceleration - gravity aligned, Gyroscope - original, Gyroscope - rotated, Gyroscope - gravity aligned :srcset: /auto_examples/preprocessing/images/sphx_glr_automatic_sensor_alignment_details_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 117-138 Heading alignment ----------------- Now we have successfully aligned the z-axis with gravity, we still see some obvious misalignment within the gyroscope data. This can be explained by the fact the static accelerometer data (used for alignment of the z-axis) does not provide any heading information. To compensate for a potential heading misalignment we will make use of the fact that the main movement component during walking happens within the sagittal plane, which corresponds to the flexion/ "rolling" of the foot. To find the misalignment of the heading to the sagittal plane we will perform a Principle Component Analysis (PCA) of the gyroscope data in the x-y plane. We assume that the main component found by the PCA corresponds to the medio-lateral axis. However, the used PCA implementation will define the sign of this main component based on the distribution of your input data which does not necessarily fit the expected forward direction. While we normalize the PCA components to ensure that the first component always points into the positive x-direction, it is not guaranteed that this is the right direction for the current sensor alignment. Therefore, we cannot rely on the sign of the principal components and only use it to find the sagittal plane. But we will still miss the actual forward direction. Hence, we still might require a final 180 deg flip around the z-axis. Therefore, we will need to add an additionally processing step to reliably detect the actual forward direction, which will be demonstrated in the following sections. .. GENERATED FROM PYTHON SOURCE LINES 138-145 .. code-block:: default from gaitmap.preprocessing.sensor_alignment import PcaAlignment pca_alignment = PcaAlignment(target_axis="y", pca_plane_axis=("gyr_x", "gyr_y")) pca_alignment = pca_alignment.align(gravity_aligned_data) pca_aligned_data = pca_alignment.aligned_data_ .. GENERATED FROM PYTHON SOURCE LINES 146-147 Visualize the result of the pca/ heading alignment. .. GENERATED FROM PYTHON SOURCE LINES 147-173 .. code-block:: default _, axs = plt.subplots(2, 3, figsize=(13, 6)) axs[0, 0].plot(example_dataset[sensor].iloc[:1000][SF_ACC]) axs[0, 1].plot(gravity_aligned_data[sensor].iloc[:1000][SF_ACC]) axs[0, 2].plot(pca_aligned_data[sensor].iloc[:1000][SF_ACC]) for ax in axs[0]: ax.set_ylim([-15, 15]) ax.grid("on") axs[1, 0].plot(example_dataset[sensor].iloc[1000:2000][SF_GYR]) axs[1, 1].plot(gravity_aligned_data[sensor].iloc[1000:2000][SF_GYR]) axs[1, 2].plot(pca_aligned_data[sensor].iloc[1000:2000][SF_GYR]) for ax in axs[1]: ax.set_ylim([-850, 850]) ax.grid("on") axs[0, 0].set_title("Acceleration - original") axs[1, 0].set_title("Gyroscope - original") axs[0, 1].set_title("Acceleration - gravity aligned") axs[1, 1].set_title("Gyroscope - gravity aligned") axs[0, 2].set_title("Acceleration - PCA aligned") axs[1, 2].set_title("Gyroscope - PCA aligned") plt.tight_layout() plt.show() .. image-sg:: /auto_examples/preprocessing/images/sphx_glr_automatic_sensor_alignment_details_003.png :alt: Acceleration - original, Acceleration - gravity aligned, Acceleration - PCA aligned, Gyroscope - original, Gyroscope - gravity aligned, Gyroscope - PCA aligned :srcset: /auto_examples/preprocessing/images/sphx_glr_automatic_sensor_alignment_details_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 174-177 Visualize the process of the PCA alignment. We can clearly see that the PCA alignment found the desired sagittal plane but with an 180deg misalignment, as we know, that for the right sensor, the medio-lateral axis should correspond to -X in this configuration. .. GENERATED FROM PYTHON SOURCE LINES 177-256 .. code-block:: default fig, axs = plt.subplots(1, 2, figsize=(12, 6)) fig.suptitle("Birds Eye View", fontsize="x-large") axs[0].scatter(gravity_aligned_data[sensor]["gyr_x"], gravity_aligned_data[sensor]["gyr_y"], marker=".", alpha=0.5) axs[1].scatter(pca_aligned_data[sensor]["gyr_x"], pca_aligned_data[sensor]["gyr_y"], marker=".", alpha=0.5) for ax in axs: ax.arrow( 0.0, 0.0, 0, -500, head_width=25, head_length=25, linewidth=2, color="b", length_includes_head=True, ls=":", zorder=5, ) ax.arrow( 0.0, 0.0, -500, 0, head_width=25, head_length=25, linewidth=2, color="orange", length_includes_head=True, ls=":", zorder=5, ) ax.text(25, -550, 'Anterior Direction ("X")', c="b") ax.text(-700, 60, 'Medial Direction ("Y")', c="orange") ax.set_ylim([-800, 800]) ax.set_xlim([-800, 800]) ax.axvline(0, c="k", ls="--", alpha=0.5, zorder=-1) ax.axhline(0, c="k", ls="--", alpha=0.5, zorder=-1) ax.set_xlabel("gyr-x") ax.set_ylabel("gyr-y") axs[0].text(460, 200, "Sensor X", c="lime") axs[0].text(200, -450, "Sensor Y", c="r") axs[0].arrow( 0.0, 0.0, pca_alignment.normalized_pca_components_[sensor][0][0] * 500, pca_alignment.normalized_pca_components_[sensor][0][1] * 500, head_width=25, head_length=25, linewidth=2, color="lime", length_includes_head=True, ) axs[0].arrow( 0.0, 0.0, pca_alignment.normalized_pca_components_[sensor][1][0] * 500, pca_alignment.normalized_pca_components_[sensor][1][1] * 500, head_width=25, head_length=25, linewidth=2, color="r", length_includes_head=True, ) axs[1].arrow(0.0, 0.0, 0, 500, head_width=25, head_length=25, linewidth=2, color="lime", length_includes_head=True) axs[1].arrow(0.0, 0.0, 500, 0, head_width=25, head_length=25, linewidth=2, color="r", length_includes_head=True) axs[1].text(40, 540, "Sensor X", c="lime") axs[1].text(400, 40, "Sensor Y", c="r") axs[0].set_title("Before Alignment") axs[1].set_title("After PCA Alignment") plt.tight_layout() plt.show() .. image-sg:: /auto_examples/preprocessing/images/sphx_glr_automatic_sensor_alignment_details_004.png :alt: Birds Eye View, Before Alignment, After PCA Alignment :srcset: /auto_examples/preprocessing/images/sphx_glr_automatic_sensor_alignment_details_004.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 257-258 Check the resulting rotation object .. GENERATED FROM PYTHON SOURCE LINES 258-263 .. code-block:: default import numpy as np pca_rotation = pca_alignment.rotation_[sensor] .. GENERATED FROM PYTHON SOURCE LINES 264-267 Lets look at the rotation angles in degree. We see that the PCA alignment method applies a pure heading correction. Further the angle value, basically matches the rotation we applied in step 1 perfectly. .. GENERATED FROM PYTHON SOURCE LINES 267-271 .. code-block:: default rot_angles = np.rad2deg(pca_rotation.as_euler("xyz")) print(f"X-rot: {rot_angles[0]:.1f} deg, Y-rot: {rot_angles[1]:.1f} deg, Z-rot: {rot_angles[2]:.1f} deg") .. rst-class:: sphx-glr-script-out .. code-block:: none X-rot: 0.0 deg, Y-rot: 0.0 deg, Z-rot: 71.4 deg .. GENERATED FROM PYTHON SOURCE LINES 272-285 Forward direction sign alignment -------------------------------- As previously mentioned the sign of the PCA is considered more or less random and will rely on the distribution of your input data. Therefore, the PCA alignment might require an additional 180deg flip to fix the missing sing or aka align the sensor to the forward direction. To find the sign of the forward direction we will perform an actual trajectory reconstruction and consider the sign of the sensor velocity in the foot posterior-anterior direction. As we do not yet have any information about strides available we will use zero velocity detectors for drift compensation and apply a piecewise-linear-drift-compensation. To ensure that the forward direction is always aligned in the moving frame of the participant (i.e. always points to the anterior side, even when the participant turns), the heading component will be ignored during the transformation of the sensor- to the world-frame during trajectory reconstruction. .. GENERATED FROM PYTHON SOURCE LINES 285-309 .. code-block:: default from gaitmap.preprocessing.sensor_alignment import ForwardDirectionSignAlignment from gaitmap.trajectory_reconstruction.orientation_methods import MadgwickAHRS from gaitmap.trajectory_reconstruction.position_methods import PieceWiseLinearDedriftedIntegration from gaitmap.utils.consts import GRAV_VEC from gaitmap.zupt_detection import NormZuptDetector fdsa = ForwardDirectionSignAlignment( forward_direction="x", rotation_axis="z", baseline_velocity_threshold=0.2, ori_method=MadgwickAHRS(beta=0.1), zupt_detector_orientation_init=NormZuptDetector( sensor="acc", window_length_s=0.15, inactive_signal_threshold=0.01, metric="variance" ), pos_method=PieceWiseLinearDedriftedIntegration( NormZuptDetector(sensor="gyr", window_length_s=0.15, inactive_signal_threshold=15.0, metric="mean"), level_assumption=False, gravity=GRAV_VEC, ), ) fdsa = fdsa.align(pca_aligned_data, sampling_rate_hz=sampling_rate_hz) forward_aligned_data = fdsa.aligned_data_ .. GENERATED FROM PYTHON SOURCE LINES 310-311 Visualize the process of the forward direction alignment .. GENERATED FROM PYTHON SOURCE LINES 311-337 .. code-block:: default fig, axs = plt.subplots(2, 3, figsize=(13, 6)) axs[0, 0].plot(example_dataset[sensor].iloc[:1000][SF_ACC]) axs[0, 1].plot(pca_aligned_data[sensor].iloc[:1000][SF_ACC]) axs[0, 2].plot(forward_aligned_data[sensor].iloc[:1000][SF_ACC]) for ax in axs[0]: ax.set_ylim([-15, 15]) ax.grid("on") axs[1, 0].plot(example_dataset[sensor].iloc[1000:2000][SF_GYR]) axs[1, 1].plot(pca_aligned_data[sensor].iloc[1000:2000][SF_GYR]) axs[1, 2].plot(forward_aligned_data[sensor].iloc[1000:2000][SF_GYR]) for ax in axs[1]: ax.set_ylim([-850, 850]) ax.grid("on") axs[0, 0].set_title("Acceleration - original") axs[1, 0].set_title("Gyroscope - original") axs[0, 1].set_title("Acceleration - PCA aligned") axs[1, 1].set_title("Gyroscope - PCA aligned") axs[0, 2].set_title("Acceleration - forward aligned") axs[1, 2].set_title("Gyroscope - forward aligned") fig.tight_layout() plt.show() .. image-sg:: /auto_examples/preprocessing/images/sphx_glr_automatic_sensor_alignment_details_005.png :alt: Acceleration - original, Acceleration - PCA aligned, Acceleration - forward aligned, Gyroscope - original, Gyroscope - PCA aligned, Gyroscope - forward aligned :srcset: /auto_examples/preprocessing/images/sphx_glr_automatic_sensor_alignment_details_005.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 338-339 Check the resulting rotation object .. GENERATED FROM PYTHON SOURCE LINES 339-342 .. code-block:: default fdsa_rotation = fdsa.rotation_[sensor] .. GENERATED FROM PYTHON SOURCE LINES 343-345 Lets look at the rotation angles in degree. The forward direction sign alignment applied the required 180deg flip to the data. .. GENERATED FROM PYTHON SOURCE LINES 345-348 .. code-block:: default rot_angles = np.rad2deg(fdsa_rotation.as_euler("xyz")) print(f"X-rot: {rot_angles[0]:.1f} deg, Y-rot: {rot_angles[1]:.1f} deg, Z-rot: {rot_angles[2]:.1f} deg") .. rst-class:: sphx-glr-script-out .. code-block:: none X-rot: 0.0 deg, Y-rot: 0.0 deg, Z-rot: 180.0 deg .. GENERATED FROM PYTHON SOURCE LINES 349-353 As a final result of the automatic alignment pipeline all misalignment around all axis were subsequently fixed. Therefore, the sensor coordinate system is now ideally aligned with the foot and corresponds to the expected gaitmap coordinate system (:ref:`coordinate system definition`) This means the data can now be further processed by all other gaitmap algorithms. .. GENERATED FROM PYTHON SOURCE LINES 355-358 Troubleshooting --------------- Refer to :ref:`example_automatic_sensor_alignment_simple` .. rst-class:: sphx-glr-timing **Total running time of the script:** ( 0 minutes 5.008 seconds) **Estimated memory usage:** 19 MB .. _sphx_glr_download_auto_examples_preprocessing_automatic_sensor_alignment_details.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: automatic_sensor_alignment_details.py ` .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: automatic_sensor_alignment_details.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_