Created
October 6, 2024 05:05
-
-
Save andrewjbennett/1fd5c98640b48d2c1ac022709d0f3fb8 to your computer and use it in GitHub Desktop.
Convert the accelerometer data from the Polar H10 to the format generated by Somnopose, to import into OSCAR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python3 | |
| # | |
| # Usage: | |
| # ./convert-h10-acc-xyz-to-somnopose.py Polar_H10_9ED9422D_20241005_024048_ACC.txt > output-2024-10-05.csv | |
| # | |
| # OSCAR can import somnopose data, so we convert the Polar H10 data to the format generated by somnopose. | |
| # | |
| # Example in OSCAR (also with data from a Checkme O2 Max): https://i.imgur.com/SDLQJEY.png | |
| from typing import NamedTuple, List | |
| from datetime import datetime | |
| import sys | |
| import math | |
| """ | |
| $ head -5 Polar_H10_9ED9422D_20241005_024048_ACC.txt | |
| Phone timestamp;sensor timestamp [ns];X [mg];Y [mg];Z [mg] | |
| 2024-10-05T02:40:51.687;599616285252552704;-757;106;640 | |
| 2024-10-05T02:40:51.727;599616285292552704;-757;107;639 | |
| 2024-10-05T02:40:51.767;599616285332552704;-757;107;641 | |
| 2024-10-05T02:40:51.807;599616285372552704;-757;112;641 | |
| """ | |
| class Data(NamedTuple): | |
| phone_timestamp: str | |
| sensor_timestamp_nanos: int | |
| dt: datetime | |
| X: int | |
| Y: int | |
| Z: int | |
| pitch: float | |
| roll: float | |
| def main(): | |
| filename = sys.argv[1] | |
| data = load_data(filename) | |
| print_data(data) | |
| def print_data(data): | |
| prev_ts = 0 | |
| print("Timestamp,Orientation,Inclination") | |
| for d in data: | |
| ts_secs = int(d.dt.timestamp()) - 978307200 # convert unix epoch time to "iOS time" | |
| if prev_ts == ts_secs: | |
| continue # only output one value per second | |
| prev_ts = ts_secs | |
| print(f"{ts_secs},{d.roll:.2f},{d.pitch:.2f}") | |
| def load_data(filename): | |
| data: List[Data] = [] | |
| with open(filename) as fin: | |
| for line in fin: | |
| parts = line.split(";") | |
| if len(parts) != 5: | |
| print(f"line doesn't match expected format! got: {line}, {parts}", file=sys.stderr) | |
| continue | |
| if parts[0][0] != "2": | |
| print(f"skipping non-date line: {parts}", file=sys.stderr) | |
| continue | |
| # Phone timestamp;sensor timestamp [ns];X [mg];Y [mg];Z [mg] | |
| # 2024-10-05T02:40:51.687;599616285252552704;-757;106;640 | |
| phone_ts = parts[0].split(".")[0] # 2024-10-05T02:40:51 | |
| sensor_ts_nanos = int(parts[1]) # 599616285252552704 (not actually used) | |
| X, Y, Z = int(parts[2]), int(parts[3]), int(parts[4]) # -757;106;640 | |
| pitch, roll = calculate(X, Y, Z) | |
| dt = datetime.strptime(phone_ts, "%Y-%m-%dT%H:%M:%S") | |
| d = Data(phone_ts, sensor_ts_nanos, dt, X, Y, Z, pitch, roll) | |
| data.append(d) | |
| return data | |
| def calculate(X, Y, Z): | |
| # from https://stackoverflow.com/a/10320532 | |
| pitch = math.atan2(-X, math.sqrt(Y*Y + Z*Z)) * 180/math.pi | |
| roll = math.atan2(Y, Z) * 180 / math.pi | |
| return (pitch, roll) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment