Open Nav
engineering

Using raw motion data to build a Basic Sleep Detector in Python

Author avatar

Jakub Domaszewicz

Updated onDecember 3, 2021

Since the Aidlab SDK 1.0.3, we opened up the possibility to start playing with raw motion data (such as acceleration) from Aidlab Chest Strap. This tutorial will cover all the necessary details to connect, read, and analyze data, ending with creating a basic Sleep Detector.

  1. Create an empty directory with a basic-sleep-detector.py file.
  2. Install the Aidlab Python SDK.

Receiving data

To connect with Aidlab, all you have to do is to invoke the connect function from the Aidlab module, and set the receiver's callback:

# basic-sleep-detector.py

import Aidlab
from Aidlab.Signal import Signal
from time import sleep, time
from datetime import datetime

class MainManager(Aidlab.Aidlab):

    def __init__(self):
        super().__init__()
        self.startTimeOfSleepingPosition = 0
        self.isInSleepingPosition = False

    def did_connect(self, aidlab):
        print("Connected to:", aidlab.address)

    def did_disconnect(self, aidlab):
        print("Disconnected from:", aidlab.address)

    def did_receive_quaternion(self, aidlab, timestamp, qw, qx, qy, qz):
        self.naiveSleepDetector([qw, qx, qy, qz])

if __name__ == '__main__':

    signals = [Signal.orientation]

    main_manager = MainManager()
    main_manager.connect(signals)

    while True:
        pass

At this point, you are ready to listen for an upcoming stream of data from the 9-axis Inertial Motion Unit (IMU) sensor, at a 30Hz rate.

Getting the user's vertical orientation

Our sleep detection algorithm will use the user’s position to determine whether they are lying on the bed or not. To do so, we have to estimate their vertical orientation, e.g.: check whether the chest is parallel to the floor or not. Possible vertical orientations are:

  • Up - when the user is lying on his back
  • Down - the user is lying on his belly (or is doing push-ups)
  • Front - the user is standing, walking, sitting, etc.

First, let's define a function giving us sleep detection based on a naive method which takes the user's vertical position:

def did_receive_quaternion(self, aidlab, timestamp, qw, qx, qy, qz):
    self.naiveSleepDetector([qw, qx, qy, qz])

def naiveSleepDetector(self, value):

    quaternion = value[0:4]

    verticalOrientation = self.determineVerticalOrientation(
        quaternion[0], quaternion[1], quaternion[2], quaternion[3])

    # Sleep detection heuristic
    self.basicSleepDetector(verticalOrientation)

To determine the vertical orientation, we will use the given formula:

Quaternion x UpVector

having the Z from the normal, we might say:

  • if Z >= 0.5 then the vertical orientation is Up
  • if Z <= -0.5, then the vertical orientation is Down
  • else, the vertical orientation is Front

Coding such logic:

def determineVerticalOrientation(self, qW, qX, qY, qZ):

    normalVec = self.normalVectorToUp(qW, qX, qY, qZ)

    if normalVec[2] >= 0.5:
        return "OrientationDown"
    elif normalVec[2] <= -0.5:
        return "OrientationUp"
    else:
        return "OrientationFront"

The multiplication of quaternion and vector is represented as follows:

V' = Q * V * conjugate(Q)

where the vector V is being treated as a quaternion with w=0:

def normalVectorToUp(self, qW, qX, qY, qZ):

    quat = self.multQuat(qW, qX, qY, qZ, 0, 0, 0, 1)
    quat = self.multQuat(quat[0], quat[1], quat[2], quat[3], qW, -qX, -qY, -qZ)

    return [quat[1], quat[2], quat[3]]

def multQuat(self, w, x, y, z, qW, qX, qY, qZ):

    newW = w * qW - x * qX - y * qY - z * qZ
    newX = w * qX + x * qW + y * qZ - z * qY
    newY = w * qY + y * qW + z * qX - x * qZ
    newZ = w * qZ + z * qW + x * qY - y * qX

    return [newW, newX, newY, newZ]

Basic Sleep Detector

We have the verticalOrientation, so we could build the Basic Sleep Detector that will be based on the time period when we are in a sleeping position (OrientationDown or OrientationUp) for more than 10 minutes. The basic heuristic goes as follows: if a user is in a sleeping position for more than 10 minutes, then he or she is sleeping:

def __init__(self):
    super().__init__()
    self.startTimeOfSleepingPosition = 0
    self.isInSleepingPosition = False

def basicSleepDetector(self, verticalOrientation):

    if (verticalOrientation == 'OrientationUp' or verticalOrientation == 'OrientationDown') and self.isInSleepingPosition == False:
        self.isInSleepingPosition = True
        self.startTimeOfSleepingPosition = time()

    elif verticalOrientation == 'OrientationFront' and self.isInSleepingPosition:
        self.startTimeOfSleepingPosition = 0
        self.isInSleepingPosition = False

    # Sleep detection heuristic:
    # We are sleeping if we are in sleeping position for longer than 10 minutes
    if self.isInSleepingPosition and (time() - self.startTimeOfSleepingPosition > 10 * 60):
        print("I am sleeping")

Turn on Aidlab and start the script:

python basic-sleep-detector.py

Of course, the algorithm will be fooled in situations when the user is laying on his side (we could simply overcome this by using the RPY axes instead), but the main purpose for this article is to present the whole process, starting with connecting to Aidlab, through collecting raw data and creating the necessary math behind calculating the vertical orientation, to building the basic sleep detector. We encourage you to experiment by yourself to get the desired result and improvements in the sleep detection method. Using other signals such as heart rate, respiration, or even the slight changes of the skin temperature, will highly improve the quality of your sleep detector.

The full source code is available on our GitHub (see example_basic_sleep_detector.py file).


Cover image from Vecteezy.


Back to Blog

READ ALSO

Aidlab's Logo

Join our community for exclusive health tips, product updates, and more.

Aidlab™ is a registered trademark. Copyright © 2024