wristpy
wristpy

A Python package for wrist-worn accelerometer data processing.
Welcome to wristpy, a Python library designed for processing and analyzing wrist-worn accelerometer data. This library provides a set of tools for loading sensor information, calibrating raw accelerometer data, calculating various physical activity metrics, finding non-wear periods, and detecting sleep periods (onset and wakeup times). Additionally, we provide access to other sensor data that may be recorded by the watch, including; temperature, luminosity, capacitive sensing, battery voltage, and all metadata.
Supported formats & devices
The package currently supports the following formats:
Format | Manufacturer | Device | Implementation status |
---|---|---|---|
GT3X | Actigraph | wGT3X-BT | ✅ |
BIN | GENEActiv | GENEActiv | ✅ |
Special Note
The idle_sleep_mode
for Actigraph watches will lead to uneven sampling rates during periods of no motion (read about this here). Consequently, this causes issues when implementing wristpy's non-wear and sleep detection. As of this moment, we fill in the missing acceleration data with the assumption that the watch is perfectly idle in the face-up position (Acceleration vector = [0, 0, -1]). The data is filled in at the same sampling rate as the raw acceleration data. In the special circumstance when acceleration samples are not evenly spaced, the data is resampled to the highest effective sampling rate to ensure linearly sampled data.
Processing pipeline implementation
The main processing pipeline of the wristpy module can be described as follows:
- Data loading: sensor data is loaded using
actfast
, and aWatchData
object is created to store all sensor data. - Data calibration: A post-manufacturer calibration step can be applied, to ensure that the acceleration sensor is measuring 1g force during periods of no motion. There are three possible options:
None
,gradient
,ggir
. - Data imputation In the special case when dealing with the Actigraph
idle_sleep_mode == enabled
, the gaps in acceleration are filled in after calibration, to avoid biasing the calibration phase. - Metrics Calculation: Calculates various activity metrics on the calibrated data, namely ENMO (Euclidean norm, minus one), MAD (mean amplitude deviation) 1, Actigraph activity counts2, MIMS (monitor-independent movement summary) unit 3, and angle-Z (angle of acceleration relative to the x-y axis).
- Non-wear detection: We find periods of non-wear based on the acceleration data. Specifically, the standard deviation of the acceleration values in a given time window, along each axis, is used as a threshold to decide
wear
ornot wear
. Additionally, we can use the temperature sensor, when avaia\lable, to augment the acceleration data. This is used in the CTA (combined temperature and acceleration) algorithm 4, and in theskdh
DETACH algorithm 5. Furthermore, ensemble classification of non-wear periods is possible by providing a list (of any length) of non-wear algorithm options. - Sleep Detection: Using the HDCZ6 and HSPT7 algorithms to analyze changes in arm angle we are able to find periods of sleep. We find the sleep onset-wakeup times for all sleep windows detected. Any sleep periods that overlap with detected non-wear times are removed, and any remaining sleep periods shorter than 15 minutes (default value) are removed.
- Physical activity levels: Using the chosen physical activity metric (aggregated into time bins, 5 second default) we compute activity levels into the following categories: [
inactive
,light
,moderate
,vigorous
]. The threshold values can be defined by the user, while the default values are chosen based on the specific activity metric and the values found in the literature 8-10. - Data output: The output results can be saved in
.csv
or.parquet
data formats, with the run-time configuration parameters saved in a.json
dictionary.
Installation
⚠️ Important Note for macOS Users
wristpy depends on
libomp
, a system-level dependency that is not always installed by default on macOS. Install it via:brew install libomp
Install the wristpy
package from PyPI via:
pip install wristpy
Quick start
wristpy
provides three flexible interfaces: a command-line tool for direct execution, an importable Python library, and a Docker image for containerized deployment.
Using Wristpy through the command-line:
Run single files:
wristpy /input/file/path.gt3x -o /save/path/file_name.csv -c gradient
Run entire directories:
wristpy /path/to/files/input_dir -o /path/to/files/output_dir -c gradient -O .csv
For a full list of command line arguments:
wristpy --help
Using Wristpy through a python script or notebook:
Running single files:
from wristpy.core import orchestrator
# Define input file path and output location
# Support for saving as .csv and .parquet
input_path = '/path/to/your/file.gt3x'
output_path = '/path/to/save/file_name.csv'
# Run the orchestrator
results = orchestrator.run(
input=input_path,
output=output_path,
calibrator='gradient', # Choose between 'ggir', 'gradient', or 'none'
)
#Data available in results object
physical_activity_metric = results.physical_activity_metric
anglez = results.anglez
physical_activity_levels = results.physical_activity_levels
nonwear_array = results.nonwear_epoch
sleep_windows = results.sleep_windows_epoch
Running entire directories:
from wristpy.core import orchestrator
# Define input file path and output location
input_path = '/path/to/files/input_dir'
output_path = '/path/to/files/output_dir'
# Run the orchestrator
# Specify the output file type, support for saving as .csv and .parquet
results_dict = orchestrator.run(
input=input_path,
output=output_path,
calibrator='gradient', # Choose between 'ggir', 'gradient', or 'none'
output_filetype = '.csv'
)
#Data available in dictionary of results.
subject1 = results_dict['subject1']
physical_activity_metric = subject1.physical_activity_metric
anglez = subject1.anglez
physical_activity_levels = subject1.physical_activity_levels
nonwear_array = subject1.nonwear_epoch
sleep_windows = subject1.sleep_windows_epoch
Using Wristpy Through Docker
Install Docker: Ensure you have Docker installed on your system. Get Docker
Pull the Docker image:
docker pull cmidair/wristpy:main
Run the Docker image with your data:
docker run -it --rm \ -v "/local/path/to/data:/data" \ -v "/local/path/to/output:/output" \ cmidair/wristpy:main
Replace
/local/path/to/data
with the path to your input data directory and/local/path/to/output
with where you want results saved.To run a single file, we simply need to modify the mounting structure for the docker call slightly:
bash docker run -it --rm \ -v "/local/path/to/data/file.bin:/data/file.bin" \ -v "/local/path/to/output:/output" \ cmidair/wristpy:main
Customizing the Pipeline:
The Docker image supports multiple input variables to customize processing. You can set these by simply chaining these inputs as you would for the CLI input:
docker run -it --rm \
-v "/local/path/to/data/file.bin:/data/file.bin" \
-v "/local/path/to/output:/output" \
cmidair/wristpy:main /data --output /output --epoch-length 5 --nonwear-algorithm ggir --nonwear-algorithm detach --thresholds 0.1 0.2 0.4
For more details on available options, see the orchestrator documentation.
References
- Vähä-Ypyä H, Vasankari T, Husu P, Suni J, Sievänen H. A universal, accurate intensity-based classification of different physical activities using raw data of accelerometer. Clin Physiol Funct Imaging. 2015 Jan;35(1):64-70. doi: 10.1111/cpf.12127. Epub 2014 Jan 7. PMID: 24393233.
- A. Neishabouri et al., “Quantification of acceleration as activity counts in ActiGraph wearable,” Sci Rep, vol. 12, no. 1, Art. no. 1, Jul. 2022, doi: 10.1038/s41598-022-16003-x.
- John, D., Tang, Q., Albinali, F. and Intille, S., 2019. An Open-Source Monitor-Independent Movement Summary for Accelerometer Data Processing. Journal for the Measurement of Physical Behaviour, 2(4), pp.268-281.
- Zhou S, Hill RA, Morgan K, et al, Classification of accelerometer wear and non-wear events in seconds for monitoring free-living physical activityBMJ Open 2015; 5:e007447. doi: 10.1136/bmjopen-2014-007447.
- A. Vert et al., “Detecting accelerometer non-wear periods using change in acceleration combined with rate-of-change in temperature,” BMC Medical Research Methodology, vol. 22, no. 1, p. 147, May 2022, doi: 10.1186/s12874-022-01633-6.
- van Hees, V.T., Sabia, S., Jones, S.E. et al. Estimating sleep parameters using an accelerometer without sleep diary. Sci Rep 8, 12975 (2018). https://doi.org/10.1038/s41598-018-31266-z.
- van Hees, V. T., et al. A Novel, Open Access Method to Assess Sleep Duration Using a Wrist-Worn Accelerometer. PLoS One 10, e0142533 (2015). https://doi.org/10.1371/journal.pone.0142533.
- Hildebrand, M., et al. Age group comparability of raw accelerometer output from wrist- and hip-worn monitors. Medicine and Science in Sports and Exercise, 46(9), 1816-1824 (2014). https://doi.org/10.1249/mss.0000000000000289.
- Treuth MS, Schmitz K, Catellier DJ, McMurray RG, Murray DM, Almeida MJ, Going S, Norman JE, Pate R. Defining accelerometer thresholds for activity intensities in adolescent girls. Med Sci Sports Exerc. 2004 Jul;36(7):1259-66. PMID: 15235335; PMCID: PMC2423321.
- Aittasalo, M., Vähä-Ypyä, H., Vasankari, T. et al. Mean amplitude deviation calculated from raw acceleration data: a novel method for classifying the intensity of adolescents' physical activity irrespective of accelerometer brand. BMC Sports Sci Med Rehabil 7, 18 (2015). https://doi.org/10.1186/s13102-015-0010-0.
Wristpy Tutorial
Introduction
Wristpy is a Python library designed for processing and analyzing wrist-worn accelerometer data.
This tutorial will guide you through the basic steps of using wristpy
to analyze your accelerometer data. Specifically,
we will cover the following topics through a few examples:
- running the default processor, analyzing the output data, and visualizing the results.
- loading data and plotting the raw signals.
- how to calibrate the data, computing ENMO and angle-z from the calibrated data and then plotting those metrics.
- how to obtain non-wear windows and visualize them.
- how to obtain sleep windows and visualize them.
Example 1: Running the default processor
The orchestrator
module of wristpy contains the default processor that will run the entire wristpy processing pipeline. This can be called as simply as:
from wristpy.core import orchestrator
results = orchestrator.run(
input = '/path/to/your/file.gt3x',
output = 'path/to/save/file_name.csv'
)
This runs the processing pipeline with all the default arguments, creates an output .csv
file, a .json
file with the pipeline configuration parameters, and will create a results
object that contains the various output metrics (namely; the specified physical activity metric, angle-z, physical activity classification values, non-wear status, and sleep status).
The orchestrator can also process entire directories. The call to the orchestrator remains largely the same but now output is expected to be a directory and the desired filetype for the saved files must be specified:
from wristpy.core import orchestrator
results = orchestrator.run(
input = '/path/to/input/dir',
output = '/path/to/output/dir',
output_filetype = ".csv"
)
We can visualize some of the outputs within the results
object, directly, with the following scripts:
Plot the default physical activity metrics (ENMO) across the entire data set:
from matplotlib import pyplot as plt
plt.plot(results.physical_activity_metric.time, results.physical_activity_metric.measurements)
Plot the sleep windows with normalized angle-z data:
from matplotlib import pyplot as plt
plt.plot(results.anglez.time, results.anglez.measurements/90)
plt.plot(results.sleep_status.time, results.sleep_status.measurements)
plt.legend(['Angle Z', 'Sleep Windows'])
plt.show()
We can also view and process these outputs from the saved .csv
output file:
import polars as pl
import matplotlib.pyplot as plt
activity_mapping = {
"inactive": 0,
"light": 1,
"moderate": 2,
"vigorous": 3
}
phys_activity = output_results['physical_activity_levels'].replace(activity_mapping).cast(int)
plt.plot(output_results['time'], phys_activity)
It is also possible to do some analysis on these output variables, for example, if we want to find the percent of time spent inactive, or in light, moderate, or vigorous physical activity:
inactivity_count = sum(phys_activity == 0)
light_activity_count = sum(phys_activity == 1)
moderate_activity_count = sum(phys_activity == 2)
vigorous_activity_count = sum(phys_activity == 3)
total_activity_count = len(output_results['physical_activity_levels'])
print(f'Light activity percent: {light_activity_count*100/total_activity_count}')
print(f'Moderate activity percent: {moderate_activity_count*100/total_activity_count}')
print(f'Vigorous activity percent: {vigorous_activity_count*100/total_activity_count}')
print(f'Inactivity percent: {inactivity_count*100/total_activity_count}')
Light activity percent: 11.678738267689539
Moderate activity percent: 1.0778725410363006
Vigorous activity percent: 0.030000428577551107
Inactivity percent: 87.21338876269661
Configuring a custom pipeline
A custom processing pipeline can be easily created by modifying the input arguments to the
orchestrator.run
call.For example:
results = orchestrator.run(input = '/path/to/input/dir', output = '/path/to/output/dir', output_filetype = ".parquet", calibrator="gradient", activity_metric="ag_count", nonwear_algorithm=["detach"], epoch_length=10, thresholds=[0.05, 0.1, 0.3])
Complete documentation on these parameters can be found here.
Example 2: Loading data and plotting the raw signals
In this example we will go over the built-in functions to directly read the raw accelerometer and light data, and how to quickly visualize this information.
The built-in readers
module can be used to load all the sensor and metadata from one of the support wristwatches (.gt3x
or .bin
), the reader will automatically select the appropriate loading methodology.
from wristpy.io.readers import readers
watch_data = readers.read_watch_data('/path/to/geneactive/file.bin')
We can then visualize the raw accelerometer and light sensor values very easily as follows:
Plot the raw acceleration along the x-axis:
plt.plot(watch_data.acceleration.time, watch_data.acceleration.measurements[:,0])
Plot the light data:
plt.plot(watch_data.lux.time, watch_data.lux.measurements)
Example 3: Plot the epoch-level measurements
In this example we will expand on the skills learned in Example 2
: we will load the sensor data, calibrate, and then calculate the ENMO and angle-z data in 5s windows (epoch-level data).
from wristpy.io.readers import readers
from wristpy.processing import calibration, metrics
from wristpy.core import computations
watch_data = readers.read_watch_data('/path/to/geneactive/file.bin')
#Calibration phase
calibrator_object = calibration.ConstrainedMinimizationCalibration()
calibrated_data = calibrator_object.run_calibration(watch_data.acceleration)
#Compute the desired metrics
enmo = metrics.euclidean_norm_minus_one(calibrated_data)
anglez = metrics.angle_relative_to_horizontal(calibrated_data)
#Obtain the epoch-level data, default is 5s windows
enmo_epoch1 = computations.moving_mean(enmo)
anglez_epoch1 = computations.moving_mean(anglez)
We can then visualize the epoch1
measurements as:
fig, ax1 = plt.subplots()
ax1.plot(enmo_epoch1.time, enmo_epoch1.measurements, color='blue')
ax1.set_ylabel('ENMO', color='blue')
ax2 = ax1.twinx()
ax2.plot(anglez_epoch1.time, anglez_epoch1.measurements, color='red')
ax2.set_ylabel('Anglez', color='red')
plt.show()
Example 4: Visualize the detected non-wear times
In this example we will build on Example 3
by also solving for the non-wear periods, as follows:
from wristpy.io.readers import readers
from wristpy.processing import calibration, metrics
watch_data = readers.read_watch_data('/path/to/geneactive/file.bin')
calibrator_object = calibration.ConstrainedMinimizationCalibration()
calibrated_data = calibrator_object.run_calibration(watch_data.acceleration)
#Find non-wear periods, using the DETACH algorithm
non_wear_array = metrics.detect_nonwear(calibrated_data)
We can then visualize the non-wear periods, in comparison to movement (ENMO at the epoch-level):
from wristpy.core import computations
enmo = metrics.euclidean_norm_minus_one(calibrated_data)
enmo_epoch1 = computations.moving_mean(enmo)
plt.plot(enmo_epoch1.time, enmo_epoch1.measurements)
plt.plot(non_wear_array.time, non_wear_array.measurements)
plt.legend(['ENMO Epoch1', 'Non-wear'])
Example 5: Compute and plot the sleep windows
We can visualize the sleep periods in comparison to other metrics; in this example, we compare the sleep windows to the angle-z data and the non-wear periods. In the default pipeline any sleep periods that overlap with non-wear periods are filtered out. This plot shows the sleep periods visualized by a blue trace, non-wear periods are visualized with a green trace, and the angle-z data with the semi-transparent red trace. These are all accessible directly from the results object created with the custom pipeline:
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots()
ax1.plot(results.sleep_status.time, results.sleep_status.measurements, color='blue', label='Sleep Periods')
plt.plot(results.nonwear_status.time, results.nonwear_status.measurements, color='green')
ax2 = ax1.twinx()
ax2.plot(results.anglez.time, results.anglez.measurements, color='red', alpha=0.5)
ax2.set_ylabel('Anglez Epoch1', color='red')
ax1.set_ylabel('Sleep Period/Non-wear')
ax1.set_ylim(0, 1.5)
plt.show()