diff --git a/paat/__init__.py b/paat/__init__.py index 7e79b7d..e5031b0 100644 --- a/paat/__init__.py +++ b/paat/__init__.py @@ -32,7 +32,7 @@ from .features import calculate_actigraph_counts, calculate_vector_magnitude, calculate_brond_counts from .io import read_gt3x, read_metadata from .calibration import calibrate -from .sleep import detect_sleep_weitz2022, detect_sleep_triaxial_weitz2022, detect_time_in_bed_weitz2024 +from .sleep import detect_time_in_bed_weitz2024 from .wear_time import detect_non_wear_time_naive, detect_non_wear_time_hees2011, detect_non_wear_time_syed2021 try: diff --git a/paat/models/SleepModel.pt b/paat/models/SleepModel.pt deleted file mode 100644 index c5ecf79..0000000 Binary files a/paat/models/SleepModel.pt and /dev/null differ diff --git a/paat/models/SleepModel_triaxial.pt b/paat/models/SleepModel_triaxial.pt deleted file mode 100644 index df902a5..0000000 Binary files a/paat/models/SleepModel_triaxial.pt and /dev/null differ diff --git a/paat/sleep.py b/paat/sleep.py index 90ae254..e6aed40 100644 --- a/paat/sleep.py +++ b/paat/sleep.py @@ -10,8 +10,6 @@ import pandas as pd import numpy as np -from torch import nn -import torch import tensorflow as tf from tensorflow.keras import models @@ -21,152 +19,6 @@ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' -from . import features - -class _SleepModel(nn.Module): - def __init__(self, input_dim, hid_dim, output_dim, n_layers, dropout, batch_first=False): - super().__init__() - - self.output_dim = output_dim - self.hid_dim = hid_dim - self.n_layers = n_layers - self.name = "LSTM" - - self.rnn = nn.LSTM(input_dim, hid_dim, n_layers, dropout=dropout, batch_first=batch_first) - - self.fc_out = nn.Linear(hid_dim, output_dim) - - self.sigmoid = nn.Sigmoid() - - self.dropout = nn.Dropout(dropout) - - def forward(self, X, lens): - """ - Performs model's forward pass - """ - - packed_input = nn.utils.rnn.pack_padded_sequence(X, lens.to('cpu'), batch_first=True, enforce_sorted=False) - packed_output, _ = self.rnn(packed_input) - output, lens = nn.utils.rnn.pad_packed_sequence(packed_output, batch_first=True) - - return self.sigmoid(self.fc_out(output)) - - -def detect_sleep_weitz2022(data, sample_freq, means=None, stds=None): - """ - Infer time in bed from raw acceleration signal using frequency features. - - .. warning:: - This method turned out to work not as accurately as initially thought. Use with care! - - Parameters - ---------- - data : DataFrame - a DataFrame containg the raw acceleration data - sample_freq : int - the sampling frequency in which the data was recorded - means : array_like (optional) - a numpy array with the channel means, will be calculated for the sample - if not specified - stds : array_like (optional) - a numpy array with the channel stds, will be calculated for the sample - if not specified - - Returns - ------- - is_sleep : np.array (n_samples,) - a numpy array indicating whether the values of the acceleration data is - sleep on minute resolution - - """ - - feature_vec = features.calculate_frequency_features(data) - - X = torch.from_numpy(feature_vec).float() - - # If no means and stds are given, calculate it - if not means or not stds: - means, stds = X.mean(axis=0), X.std(axis=0) - - # Normalize input - X = (X - means) / stds - - X = X.unsqueeze(0) - lengths = torch.Tensor([X.shape[1]]) - - # Load model hard coded. Should later be changed to ONNX or similar - model = _SleepModel(160, 4, 1, 1, dropout=0, batch_first=True) - model_path = os.path.join(os.path.pardir, os.path.dirname(__file__), 'models', 'SleepModel.pt') - model.load_state_dict(torch.load(model_path)) - model.eval() - - # Predict sleep periods - predictions = (model(X, lengths) >= .5).squeeze().numpy() - predictions = np.repeat(predictions, 60 * sample_freq) - - return predictions - - -def detect_sleep_triaxial_weitz2022(data, sample_freq, resampled_frequency="1min", means=None, stds=None, model=None): - """ - Infer time in bed from raw acceleration signal. - - .. warning:: - This method turned out to work not as accurately as initially thought. Use with care! - - Parameters - ---------- - data : DataFrame - a DataFrame containg the raw acceleration data - sample_freq : int - the sampling frequency in which the data was recorded - resampled_frequency : str (optional) - a str indicating to what frequency the data should be resampled. This depends - on the model used to predict, defaults to 1min. - means : array_like (optional) - a numpy array with the channel means, will be calculated for the sample - if not specified - stds : array_like (optional) - a numpy array with the channel stds, will be calculated for the sample - if not specified - model : nn.Module (optional) - a loaded pytorch custom model. - - Returns - ------- - predicted_time_in_bed : np.array (n_samples,) - a numpy array indicating whether the values of the acceleration data were spent in bed - - """ - if resampled_frequency: - data = data[['X', 'Y', 'Z']].resample(resampled_frequency).mean() - - X = torch.from_numpy(data[['X', 'Y', 'Z']].values).float() - - # If no means and stds are given, calculate subject's mean and std - # to normalize by this - if not means or not stds: - means, stds = X.mean(axis=0), X.std(axis=0) - - # Normalize input - X = (X - means) / stds - lengths = torch.Tensor(X.shape[0]).float().unsqueeze(0) - - X = X.unsqueeze(0) - lengths = torch.Tensor([X.shape[1]]) - - # Load model if not specified - if not model: - model = _SleepModel(3, 2, 1, 1, dropout=0, batch_first=True) - model_path = os.path.join(os.path.pardir, os.path.dirname(__file__), 'models', 'SleepModel_triaxial.pt') - model.load_state_dict(torch.load(model_path)) - model.eval() - - predictions = (model(X, lengths) >= .5).squeeze().numpy() - seconds = pd.Timedelta(resampled_frequency).seconds - predictions = np.repeat(predictions, seconds * sample_freq) - - return predictions def detect_time_in_bed_weitz2024(data, sample_freq, resampled_frequency="1min", means=None, stds=None, model=None): """ @@ -187,8 +39,8 @@ def detect_time_in_bed_weitz2024(data, sample_freq, resampled_frequency="1min", stds : array_like (optional) a numpy array with the channel stds, will be calculated for the sample if not specified - model : nn.Module (optional) - a loaded pytorch custom model. + model : keras.Model (optional) + a loaded keras custom model. Returns ------- @@ -214,7 +66,7 @@ def detect_time_in_bed_weitz2024(data, sample_freq, resampled_frequency="1min", model_path = os.path.join(os.path.pardir, os.path.dirname(__file__), 'models', 'TIB_model.h5') model = models.load_model(model_path) - predictions = (model.predict(X[np.newaxis], verbose=0).squeeze() >= .5) + predictions = (model.predict(X[np.newaxis], verbose=0).squeeze() > .5) seconds = pd.Timedelta(resampled_frequency).seconds predictions = np.repeat(predictions, seconds * sample_freq) diff --git a/pyproject.toml b/pyproject.toml index 77b6524..c3660c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,6 @@ joblib = "^1.0.1" numpy = "1.23.5" bitstring = "^3.1.7" tensorflow = ">=2.11.0,<2.16" -torch = "^1.10.1" agcounts = "^0.1.1" toml = "^0.10.2" tables = "^3.7.0" diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index 5df2632..6b01e10 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -19,7 +19,7 @@ def test_pipeline(): data.loc[:, "Non Wear Time"] = paat.detect_non_wear_time_syed2021(data, sample_freq) # Detect sleep episodes - data.loc[:, "Sleep"] = paat.detect_sleep_weitz2022(data, sample_freq) + data.loc[:, "Sleep"] = paat.detect_time_in_bed_weitz2024(data, sample_freq) # Classify moderate-to-vigorous and sedentary behavior data.loc[:, ["MVPA", "SB"]] = paat.calculate_pa_levels(data, sample_freq)