generated from childmindresearch/template-python-repository
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat/issue 102/handle idle sleep mode (#143)
* Initial support for reading idle_sleep_mode Get idle_sleep_mode flag from metadata Only available for .gt3x files. Add it to watch_Data class to access later * Create idle_sleep_mode_imputation.py Initial version of the idle_sleep_mode data imputation, resamples for unevenly spaced samples * Create test_idle_sleep_mode.py Initial unit tests to check effective sampling rate is correct * Update test_idle_sleep_mode.py Adding more unit tests * Update test_idle_sleep_mode.py Check filled value is assigned Spacing for AAA testing * Update idle_sleep_mode_imputation.py Fix mypy error with this ugly monstrosity * Update test_idle_sleep_mode.py Fix to ensure numerical result from time.diff().mean() * Update orchestrator.py Adding check for idle_sleep_mode into run_file() Might be hard to hit this line for testing * Update orchestrator.py Fix ruff format error * New sample data Added new sample data for idle_sleep_mode = true smoke test * Addressing PR comments Moving idle_sleep_mode imputation to after the calibration step Imputing acceleration to exactly (0,0,-1) * Update test_orchestrator.py Path error for the full_dir processing * Update README.md Added info about handling idle_sleep_mode * Update readers.py Added info to docstring about idle_sleep_mode_flag settings * Update idle_sleep_mode_imputation.py Fix docstring for new fill value * Update pyproject.toml Update version number * Update README.md Fix zenodo badge for DOI that resolves to latest version
- Loading branch information
Showing
13 changed files
with
227 additions
and
12 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
[tool.poetry] | ||
name = "wristpy" | ||
version = "0.1.0" | ||
version = "0.1.1" | ||
description = "wristpy is a Python package designed for processing and analyzing wrist-worn accelerometer data." | ||
authors = [ | ||
"Adam Santorelli <[email protected]>", | ||
|
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
"""Handle idle sleep mode special case.""" | ||
|
||
import numpy as np | ||
import polars as pl | ||
|
||
from wristpy.core import models | ||
|
||
|
||
def impute_idle_sleep_mode_gaps(acceleration: models.Measurement) -> models.Measurement: | ||
"""This function imputes the gaps in the idle sleep mode data. | ||
Gaps in the acceleration data are filled by assuming the watch is idle in a face up | ||
position. The acceleration data is filled in at a linear sampling rate, estimated | ||
based on the first 100 samples timestamps, with (0, 0, -1). | ||
In cases when the sampling rate leads to unevenly spaced samples within one second, | ||
eg. 30Hz sampling rate has samples spaced at 33333333ns and 33333343ns within one | ||
second, the entire data set will be resampled at the highest effective sampling rate | ||
that allows for for linearly spaced samples within one second, | ||
to nanosecond precision. | ||
Args: | ||
acceleration: The raw acceleration data. | ||
Returns: | ||
A Measurement object with the modified acceleration data. | ||
""" | ||
|
||
def _find_effective_sampling_rate(sampling_rate: int) -> int: | ||
"""Helper function to find the effective sampling rate. | ||
This function finds the new sampling rate that allows for linearly spaced | ||
samples within one second, to nanosecond precision. | ||
Args: | ||
sampling_rate: The original sampling rate. | ||
Returns: | ||
The new effective sampling rate. | ||
""" | ||
for effective_sr in range(sampling_rate, 1, -1): | ||
if 1e9 % (1e9 / effective_sr) == 0: | ||
return effective_sr | ||
return 1 | ||
|
||
acceleration_polars_df = pl.DataFrame( | ||
{ | ||
"X": acceleration.measurements[:, 0], | ||
"Y": acceleration.measurements[:, 1], | ||
"Z": acceleration.measurements[:, 2], | ||
"time": acceleration.time, | ||
} | ||
) | ||
|
||
sampling_space_nanosec = np.mean( | ||
acceleration.time[:100] | ||
.diff() | ||
.drop_nulls() | ||
.dt.total_nanoseconds() | ||
.to_numpy() | ||
.astype(dtype=float) | ||
) | ||
|
||
sampling_rate = int(1e9 / sampling_space_nanosec) | ||
|
||
effective_sampling_rate = _find_effective_sampling_rate(sampling_rate) | ||
effective_sampling_interval = int(1e9 / effective_sampling_rate) | ||
|
||
filled_acceleration = ( | ||
acceleration_polars_df.set_sorted("time") | ||
.group_by_dynamic("time", every=f"{effective_sampling_interval}ns") | ||
.agg(pl.exclude("time").mean()) | ||
.upsample("time", every=f"{effective_sampling_interval}ns", maintain_order=True) | ||
.with_columns( | ||
pl.col("X").fill_null(value=0), | ||
pl.col("Y").fill_null(value=0), | ||
pl.col("Z").fill_null(value=-1), | ||
) | ||
) | ||
|
||
return models.Measurement.from_data_frame(filled_acceleration) |
This file contains 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
Binary file not shown.
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
"""Testing the idle_sleep_mode functions.""" | ||
|
||
from datetime import datetime, timedelta | ||
|
||
import numpy as np | ||
import polars as pl | ||
import pytest | ||
|
||
from wristpy.core import models | ||
from wristpy.processing import idle_sleep_mode_imputation | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"sampling_rate, effective_sampling_rate", [(30, 25), (20, 20), (1, 1)] | ||
) | ||
def test_idle_sleep_mode_resampling( | ||
sampling_rate: int, effective_sampling_rate: int | ||
) -> None: | ||
"""Test the idle_sleep_mode function.""" | ||
num_samples = 10000 | ||
dummy_date = datetime(2024, 5, 2) | ||
dummy_datetime_list = [ | ||
dummy_date + timedelta(seconds=i / sampling_rate) for i in range(num_samples) | ||
] | ||
test_time = pl.Series("time", dummy_datetime_list, dtype=pl.Datetime("ns")) | ||
acceleration = models.Measurement( | ||
measurements=np.ones((num_samples, 3)), time=test_time | ||
) | ||
|
||
filled_acceleration = idle_sleep_mode_imputation.impute_idle_sleep_mode_gaps( | ||
acceleration | ||
) | ||
|
||
assert ( | ||
np.mean( | ||
filled_acceleration.time.diff() | ||
.drop_nulls() | ||
.dt.total_nanoseconds() | ||
.to_numpy() | ||
.astype(dtype=float) | ||
) | ||
== 1e9 / effective_sampling_rate | ||
) | ||
|
||
|
||
def test_idle_sleep_mode_gap_fill() -> None: | ||
"""Test the idle_sleep_mode gap fill functionality.""" | ||
num_samples = 10000 | ||
dummy_date = datetime(2024, 5, 2) | ||
dummy_datetime_list = [ | ||
dummy_date + timedelta(seconds=i) for i in range(num_samples // 2) | ||
] | ||
time_gap = dummy_date + timedelta(seconds=(1000)) | ||
dummy_datetime_list += [ | ||
time_gap + timedelta(seconds=i) for i in range(num_samples // 2, num_samples) | ||
] | ||
test_time = pl.Series("time", dummy_datetime_list, dtype=pl.Datetime("ns")) | ||
acceleration = models.Measurement( | ||
measurements=np.ones((num_samples, 3)), time=test_time | ||
) | ||
expected_acceleration = (0, 0, -1) | ||
|
||
filled_acceleration = idle_sleep_mode_imputation.impute_idle_sleep_mode_gaps( | ||
acceleration | ||
) | ||
|
||
assert len(filled_acceleration.measurements) > len(acceleration.measurements) | ||
assert ( | ||
np.mean( | ||
filled_acceleration.time.diff() | ||
.drop_nulls() | ||
.dt.total_nanoseconds() | ||
.to_numpy() | ||
.astype(dtype=float) | ||
) | ||
== 1e9 | ||
) | ||
assert np.all(filled_acceleration.measurements[5010] == expected_acceleration) |
This file contains 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
This file contains 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
This file contains 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