Inżynieria

Prosty detektor snu w Pythonie

Author avatar

Jakub Domaszewicz

Ostatnia aktualizacja3 grudnia 2021

Prosty detektor snu w Pythonie

Krok po kroku wykorzystujemy dane z Aidlaba, aby rozpoznać, kiedy użytkownik leży wystarczająco długo, aby uznać to za sen.

Od wersji Aidlab SDK 1.0.3 możesz korzystać z surowych danych ruchowych, takich jak przyspieszenie, pochodzących z pasa piersiowego Aidlab. W tym poradniku przejdziemy przez wszystkie kroki: nawiązanie połączenia, odczyt i analizę danych oraz stworzenie prostego detektora snu.

  1. Utwórz pusty katalog, a w nim plik basic-sleep-detector.py.
  2. Zainstaluj Aidlab Python SDK.

Odbieranie danych

Aby połączyć się z Aidlabem, wyszukaj urządzenia w pobliżu za pomocą AidlabManager, nawiąż połączenie z delegatem DeviceDelegate, a po jego ustanowieniu rozpocznij zbieranie danych o orientacji:

import asyncio
from time import time

from aidlab import AidlabManager, DataType, DeviceDelegate, DisconnectReason

class MainManager(DeviceDelegate):

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

    async def run(self):
        devices = await AidlabManager().scan()
        if len(devices) > 0:
            print("Connecting to:", devices[0].address)
            await devices[0].connect(self)
            while True:
                await asyncio.sleep(1)

    def did_connect(self, device):
        print("Connected to:", device.address)
        asyncio.create_task(device.collect([DataType.ORIENTATION], []))

    def did_disconnect(self, device, reason: DisconnectReason):
        print("Disconnected from:", device.address, reason)

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

if __name__ == '__main__':
    asyncio.run(MainManager().run())

Możesz teraz odbierać strumień danych z dziewięcioosiowego czujnika inercyjnego (IMU) z częstotliwością 30 Hz.

Określanie ułożenia ciała względem pionu

Nasz algorytm wykrywania snu będzie na podstawie pozycji użytkownika określał, czy leży on w łóżku. Musimy więc ustalić ułożenie jego ciała względem pionu, na przykład sprawdzić, czy klatka piersiowa jest równoległa do podłogi. Rozróżnimy trzy pozycje:

  • Up — użytkownik leży na plecach,
  • Down — użytkownik leży na brzuchu lub robi pompki,
  • Front — użytkownik stoi, chodzi, siedzi itp.
Ułożenie ciała względem pionu

Najpierw zdefiniujmy funkcję, która wykorzysta ułożenie ciała do wykrywania snu prostą metodą:

def did_receive_quaternion(self, device, 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)

Do określenia ułożenia ciała wykorzystamy następującą operację:

Quaternion x UpVector

Znając składową Z wektora normalnego, możemy przyjąć:

  • jeśli Z >= 0.5, pozycja to Up,
  • jeśli Z <= -0.5, pozycja to Down,
  • w pozostałych przypadkach pozycja to Front.

Zapiszmy tę logikę w kodzie:

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"

Obrót wektora za pomocą kwaternionu zapisujemy następująco:

V' = Q * V * conjugate(Q)

Wektor V traktujemy tu jako kwaternion, w którym 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]

Prosty detektor snu

Mamy już verticalOrientation, możemy więc zbudować prosty detektor snu. Będzie on sprawdzał, jak długo użytkownik pozostaje w pozycji leżącej (OrientationDown lub OrientationUp). Przyjmujemy prostą heurystykę: jeśli trwa to dłużej niż 10 minut, algorytm uznaje, że użytkownik śpi:

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")

Włącz Aidlaba i uruchom skrypt:

python basic-sleep-detector.py

Algorytm nie zadziała poprawnie, gdy użytkownik leży na boku. Można to rozwiązać, korzystając z kątów RPY (roll, pitch, yaw). Celem tego artykułu jest jednak pokazanie całego procesu: od połączenia z Aidlabem, przez zbieranie surowych danych i obliczanie ułożenia ciała, aż po stworzenie prostego detektora snu. Zachęcamy do własnych eksperymentów i udoskonalania tej metody. Wykorzystanie dodatkowych sygnałów, takich jak tętno, oddech czy nawet niewielkie zmiany temperatury skóry, może znacznie poprawić jakość wykrywania snu.


Ilustracja tytułowa: Vecteezy.