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

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 up all the necessary details to connect, read, and analyze data, ending with creating basic Sleep Detector.

  1. Create an empty directory with file.
  2. Download the Aidlab SDK.
  3. Unpack it, and open the Python directory.
  4. Drag the Aidlab inside your project directory, so the structure should look similar to:
  |-- Aidlab/

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:


from Aidlab import AidlabBLECommunication as aidlab

def onReceivedCharacteristic(name, value):
    if name == "motion":
        print("Motion: {}".format(value))

aidlab.connect(["motion"], onReceivedCharacteristic)

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.

Passing the ["motion"] array means subscribing to motion characteristic from Aidlab, giving 7 values in return (the value array). First 4 float values (W, X, Y, Z) are components of the quaternion of user's orientation, while the last 3 components build the acceleration (X, Y, Z). Acceleration is pretty straightforward, as it's the difference between any linear acceleration in the accelerometer’s reference frame and the earth's gravitational field vector. Quaternion, on the contrary, represents the shortest path to get from one orientation to another. Either from some "forward" origin direction (in which case, it's an orientation) or the direction and magnitude to rotate another orientation (ie, you could have a quaternion that represents rotating 90 degrees clockwise about the Y-axis), in which case, it's a rotation.

Getting the user's vertical orientation

Our sleep detection algorithm will use the user’s position to determine whether he is lying on the bed or not. To do so, we have to estimate its 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 naive method which takes the user's vertical position:

def onReceivedCharacteristic(name, value):
    if name == "motion":

def naiveSleepDetector(value):
    quaternion = value[0:4]

    # Can be `OrientationDown`, `OrientationUp` or `OrientationFront`
    verticalOrientation = determineVerticalOrientation(
        quaternion[0], quaternion[1], quaternion[2], quaternion[3])

    # We are going to implement this later
    # 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(qW, qX, qY, qZ):

    normalVec = normalVectorToUp(qW, qX, qY, qZ)
    z = normalVec[2]

    if z >= 0.5:
        return "OrientationDown"
    elif z <= -0.5:
        return "OrientationUp"
        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 multQuatByVector(qW, qX, qY, qZ, x, y, z):

    # Q * V
    quat = multQuat(qW, qX, qY, qZ, 0, x, y, z)

    # (Q * V) * conjugate(Q)
    quat = multQuat(quat[0], quat[1], quat[2], quat[3], qW, -qX, -qY, -qZ)

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

def multQuat(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 sleeping position (OrientationDown or OrientationUp) for more than 10 minutes. The basic heuristic goes as follows: if a user is in a sleep position for more than 10 minutes, then he or she is sleeping:

startTimeOfSleepingPosition = 0
isInSleepingPosition = False

def basicSleepDetector(verticalOrientation):
    global startTimeOfSleepingPosition, isInSleepingPosition

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

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

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

Turn on Aidlab and start the script:


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 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.

results matching ""

    No results matching ""