Inżynieria

Prosty detektor snu w Pythonie

Author avatar

Jakub Domaszewicz

Ostatnia aktualizacja3 grudnia 2021

Prosty detektor snu w Pythonie

Połącz się z Aidlabem i wykorzystaj dane o ułożeniu ciała do zbudowania prostego detektora. Pokazujemy kod i ograniczenia tej metody.

W tym poradniku zbudujemy prosty detektor snu na podstawie ułożenia ciała. Połączymy się z pasem piersiowym Aidlab, odczytamy dane o orientacji i sprawdzimy, jak długo użytkownik leży. Dostęp do surowych danych ruchowych pojawił się w Aidlab SDK 1.0.3.

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

Odbieranie danych

Wyszukaj urządzenia za pomocą AidlabManager i połącz się z wybranym Aidlabem. Klasa implementująca DeviceDelegate odbierze dane o orientacji, gdy rozpoczniesz pomiar:

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

Dane o orientacji pochodzą z dziewięcioosiowego czujnika inercyjnego (IMU). W tym przykładzie odbieramy je z częstotliwością 30 Hz.

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

Najpierw sprawdzimy, czy użytkownik leży. Wykorzystamy do tego orientację klatki piersiowej względem 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

Callback przekazuje kwaternion do funkcji naiveSleepDetector. Ta ustala orientację i wywołuje detektor:

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

Składowa Z obróconego wektora pozwala przypisać jedną z etykiet używanych w kodzie:

  • Z >= 0.5 — OrientationDown,
  • Z <= -0.5 — OrientationUp,
  • pozostałe wartości — OrientationFront.

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

Detektor mierzy czas spędzony w pozycji OrientationDown lub OrientationUp. Po dziesięciu minutach wyświetla komunikat o śnie. To uproszczone założenie: samo leżenie nie oznacza, że użytkownik zasnął.

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

Przykład nie rozpoznaje leżenia na boku. Aby uwzględnić tę pozycję, możesz rozbudować klasyfikację o kąty RPY (roll, pitch, yaw). Możesz też sprawdzić, czy dodatkowe sygnały — tętno, oddech lub temperatura skóry — pomagają odróżnić sen od spokojnego leżenia. Ten skrypt pokazuje punkt wyjścia do takich eksperymentów.


Ilustracja tytułowa: Vecteezy.