Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c86a254
update macos version for building
LukasAdamowicz Mar 25, 2026
e9dc61a
update macos version for building
LukasAdamowicz Mar 25, 2026
ff4494c
test using c_size_t
LukasAdamowicz Mar 27, 2026
8190d92
update ubuntu version
LukasAdamowicz Mar 30, 2026
26cd872
add option to trim star ttime to factor
LukasAdamowicz Apr 1, 2026
bfd7386
fix handling of none for trim keys
LukasAdamowicz Apr 1, 2026
43c467a
test using c_size_t
LukasAdamowicz Apr 1, 2026
a9da116
update version
LukasAdamowicz Apr 1, 2026
28cc641
add logging for trimming
LukasAdamowicz Apr 1, 2026
84ace62
fix stops being over day end after rounding
LukasAdamowicz Apr 8, 2026
a221778
fix stops being over day end after rounding
LukasAdamowicz Apr 8, 2026
1e27ab8
update moving mean to allow for returning values with partial windows
LukasAdamowicz May 4, 2026
02e80a4
add min acceleration metric and add M5/M10/L5 hour metrics to activity
LukasAdamowicz May 4, 2026
5a872b9
updating to use py_ssize_t/c_size_t for dimension/size variables
LukasAdamowicz May 5, 2026
284466f
updating to use py_ssize_t/c_size_t for dimension/size variables
LukasAdamowicz May 5, 2026
8a56ea7
fix var type
LukasAdamowicz May 5, 2026
77a0631
updating dimension variables to be size_t/npy_intp
LukasAdamowicz May 6, 2026
8247c1a
updating dimension variables to be size_t/npy_intp
LukasAdamowicz May 6, 2026
02c9c71
only upload to pypi if not a manual run
LukasAdamowicz May 6, 2026
4223728
fixing more type issues
LukasAdamowicz May 6, 2026
1d87247
fix typo
LukasAdamowicz May 6, 2026
c8ade0b
fixing type issues
LukasAdamowicz May 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/build_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ jobs:
- name: Build wheels
uses: pypa/cibuildwheel@v3.4.0
env:
CIBW_SKIP: pp* cp36-* cp37-* cp38-* cp39-* cp310-* *musllinux*
CIBW_SKIP: pp* cp38-* cp39-* cp310-* *musllinux*
CIBW_ARCHS_MACOS: auto64
CIBW_ARCHS_LINUX: auto64
CIBW_ARCHS_WINDOWS: auto64
CIBW_BEFORE_BUILD_WINDOWS: "pip install delvewheel"
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair -w {dest_dir} {wheel}"
MACOSX_DEPLOYMENT_TARGET: 15.0
with:
output-dir: wheelhouse
config-file: "{package}/pyproject.toml"
Expand All @@ -69,6 +70,7 @@ jobs:
path: dist/*.tar.gz

upload_pypi:
if: ${{ github.event_name != 'workflow_dispatch' }}
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
steps:
Expand Down
4 changes: 2 additions & 2 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ python:
extra_requirements:
- docs
build:
os: ubuntu-20.04
os: ubuntu-lts-latest
tools:
python: "3.10"
python: "3.13"
jobs:
pre_install:
- echo "\n\n[tool.meson-python.args]\nsetup = [\"-Dis_not_rtd_build=false\"]" >> pyproject.toml
2 changes: 1 addition & 1 deletion meson.build
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
project(
'scikit-digital-health',
'c',
version: '0.17.11',
version: '0.17.12',
license: 'MIT',
meson_version: '>=1.1',
)
Expand Down
4 changes: 4 additions & 0 deletions src/skdh/activity/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ def __init__(
self.wake_endpoints = [
ept.IntensityGradient(state="wake"),
ept.MaxAcceleration(self.max_acc_lens, state="wake"),
# M5hr, M10hr
ept.MaxAcceleration([300, 600], required_points=0.5, state="wake"),
# L5hr
ept.MinAcceleration([300], required_points=0.5, state="wake"),
]
self.wake_endpoints += [
ept.TotalIntensityTime(lvl, self.wlen, self.cutpoints, state="wake")
Expand Down
86 changes: 84 additions & 2 deletions src/skdh/activity/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
zeros,
full,
max,
min,
nanmax,
nanmin,
histogram,
log,
nan,
Expand Down Expand Up @@ -43,6 +45,7 @@
"ActivityEndpoint",
"IntensityGradient",
"MaxAcceleration",
"MinAcceleration",
"TotalIntensityTime",
"BoutIntensityTime",
"FragmentationEndpoints",
Expand Down Expand Up @@ -394,17 +397,23 @@ class MaxAcceleration(ActivityEndpoint):
----------
window_lengths : {list, int}
List of window lengths, or a single window length.
required_points : int, float, optional
Number of points required in the window to compute the endpoint. If float,
should be between 0 and 1 and will be interpreted as a fraction of the total
number of points in the window. Default is 1.0, meaning all points in the
window must be present.
state : {'wake', 'sleep}
State during which the endpoint is being computed.
"""

def __init__(self, window_lengths, state="wake"):
def __init__(self, window_lengths, required_points=1.0, state="wake"):
if isinstance(window_lengths, int):
window_lengths = [window_lengths]

super().__init__([f"max acc {i}min [g]" for i in window_lengths], state)

self.wlens = window_lengths
self.required_points = required_points

def predict(
self,
Expand Down Expand Up @@ -445,14 +454,87 @@ def predict(
# skipping more samples would introduce bias by random chance of
# where the windows start and stop
try:
tmp_max = max(moving_mean(accel_metric, n, 1))
tmp_max = max(moving_mean(accel_metric, n, 1, req_points=self.required_points))
except ValueError:
return # if the window length is too long for this block of data

# check that we don't have a larger result already for this day
results[name][i] = nanmax([tmp_max, results[name][i]])


class MinAcceleration(ActivityEndpoint):
"""
Compute the minimum acceleration over windows of the specified length.

Parameters
----------
window_lengths : {list, int}
List of window lengths, or a single window length.
required_points : int, float, optional
Number of points required in the window to compute the endpoint. If float,
should be between 0 and 1 and will be interpreted as a fraction of the total
number of points in the window. Default is 1.0, meaning all points in the
window must be present.
state : {'wake', 'sleep}
State during which the endpoint is being computed.
"""

def __init__(self, window_lengths, required_points=1.0, state="wake"):
if isinstance(window_lengths, int):
window_lengths = [window_lengths]

super().__init__([f"min acc {i}min [g]" for i in window_lengths], state)

self.wlens = window_lengths
self.required_points = required_points

def predict(
self,
results,
i,
accel_metric,
accel_metric_60,
epoch_s,
epochs_per_min,
**kwargs,
):
"""
Compute the minimum acceleration during this set of data, and compare it
to the previous smallest detected value.

Parameters
----------
results : dict
Dictionary containing the initialized results arrays. Keys in `results`
are taken from the names of endpoints.
i : int
Index of the day, used to index into individual result arrays, e.g.
`results[self.name][i] = 5.0`
accel_metric : numpy.ndarray
Computed acceleration metric (e.g. ENMO).
accel_metric_60 : numpy.ndarray
Computed acceleration metric for a 60 second window.
epoch_s : int
Duration in seconds of each sample of `accel_metric`.
epochs_per_min : int
Number of epochs per minute.
"""
super(MinAcceleration, self).predict()

for wlen, name in zip(self.wlens, self.name):
n = wlen * epochs_per_min
# skip 1 sample because we want the window with the smallest acceleration
# skipping more samples would introduce bias by random chance of
# where the windows start and stop
try:
tmp_min = min(moving_mean(accel_metric, n, 1, req_points=self.required_points))
except ValueError:
return # if the window length is too long for this block of data

# check that we don't have a smaller result already for this day
results[name][i] = nanmin([tmp_min, results[name][i]])


class TotalIntensityTime(ActivityEndpoint):
"""
Compute the total time spent in an intensity level.
Expand Down
22 changes: 11 additions & 11 deletions src/skdh/features/lib/extensions/_utility.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@
#include <stdlib.h>
#include <math.h>

extern void mean_sd_1d(long *, double *, double *, double *);
extern void unique(long *, double *, double *, long *, long *);
extern void gmean(long *, double *, double *);
extern void embed_sort(long *, long *, double *, long *, long *, long *);
extern void hist(long *, double *, long *, double *, double *, long *);
extern void histogram(long *, long *, double *, double *, long *);
extern void mean_sd_1d(Py_ssize_t *, double *, double *, double *);
extern void unique(Py_ssize_t *, double *, double *, long *, long *);
extern void gmean(Py_ssize_t *, double *, double *);
extern void embed_sort(Py_ssize_t *, long *, double *, long *, long *, long *);
extern void hist(Py_ssize_t *, double *, Py_ssize_t *, double *, double *, long *);
extern void histogram(Py_ssize_t *, Py_ssize_t *, double *, double *, long *);

extern void insertion_sort_2d(long *, long *, double *, long *);
extern void quick_argsort_(long *, double *, long *);
extern void quick_sort_(long *, double *);

extern void f_rfft(long *, double *, long *, double *);
extern void f_rfft(Py_ssize_t *, double *, long *, double *);
extern void destroy_plan(void);


Expand Down Expand Up @@ -194,10 +194,10 @@ PyObject * cf_embed_sort(PyObject *NPY_UNUSED(self), PyObject *args){

PyObject * cf_hist(PyObject *NPY_UNUSED(self), PyObject *args){
PyObject *x_;
long ncells = 1;
npy_intp ncells = 1;
double min_val = 0., max_val = 100.;

if (!PyArg_ParseTuple(args, "Oldd:cf_hist", &x_, &ncells, &min_val, &max_val)) return NULL;
if (!PyArg_ParseTuple(args, "Ondd:cf_hist", &x_, &ncells, &min_val, &max_val)) return NULL;

PyArrayObject *data = (PyArrayObject *)PyArray_FromAny(
x_, PyArray_DescrFromType(NPY_DOUBLE), 1, 0,
Expand Down Expand Up @@ -261,8 +261,8 @@ PyObject * cf_histogram(PyObject *NPY_UNUSED(self), PyObject *args){

npy_intp n_elem = PyArray_SIZE(data);

long n3 = 3;
long ncells = (long)ceil(sqrt(n_elem));
npy_intp n3 = 3;
npy_intp ncells = (npy_intp)ceil(sqrt(n_elem));

PyArrayObject *d = (PyArrayObject *)PyArray_EMPTY(1, &n3, NPY_DOUBLE, 0);
PyArrayObject *counts = (PyArrayObject *)PyArray_ZEROS(1, &ncells, NPY_LONG, 0);
Expand Down
12 changes: 6 additions & 6 deletions src/skdh/features/lib/extensions/entropy.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
#include <stdio.h>
#include <stdlib.h>

extern void signal_entropy_1d(long *, double *, double *);
extern void sample_entropy_1d(long *, double *, long *, double *, double *);
extern void permutation_entropy_1d(long *, double *, long *, long *, int *, double *);
extern void signal_entropy_1d(Py_ssize_t *, double *, double *);
extern void sample_entropy_1d(Py_ssize_t *, double *, long *, double *, double *);
extern void permutation_entropy_1d(Py_ssize_t *, double *, long *, long *, int *, double *);


PyObject * signal_entropy(PyObject *NPY_UNUSED(self), PyObject *args){
Expand Down Expand Up @@ -48,7 +48,7 @@ PyObject * signal_entropy(PyObject *NPY_UNUSED(self), PyObject *args){
double *dptr = (double *)PyArray_DATA(data);
double *rptr = (double *)PyArray_DATA(res);

long stride = ddims[ndim-1];
npy_intp stride = ddims[ndim-1];
int nrepeats = PyArray_SIZE(data) / stride;

for (int i = 0; i < nrepeats; ++i){
Expand Down Expand Up @@ -107,7 +107,7 @@ PyObject * sample_entropy(PyObject *NPY_UNUSED(self), PyObject *args){
double *dptr = (double *)PyArray_DATA(data);
double *rptr = (double *)PyArray_DATA(res);

long stride = ddims[ndim-1];
npy_intp stride = ddims[ndim-1];
int nrepeats = PyArray_SIZE(data) / stride;

for (int i = 0; i < nrepeats; ++i){
Expand Down Expand Up @@ -168,7 +168,7 @@ PyObject * permutation_entropy(PyObject *NPY_UNUSED(self), PyObject *args){
double *dptr = (double *)PyArray_DATA(data);
double *rptr = (double *)PyArray_DATA(res);

long stride = ddims[ndim-1];
npy_intp stride = ddims[ndim-1];
int nrepeats = PyArray_SIZE(data) / stride;

for (int i = 0; i < nrepeats; ++i){
Expand Down
3 changes: 2 additions & 1 deletion src/skdh/features/lib/extensions/f_rfft.f95
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ subroutine f_rfft(n, x, nfft, F) bind(C, name="f_rfft")
use, intrinsic :: iso_c_binding
use real_fft, only : execute_real_forward
implicit none
integer(c_long), intent(in) :: n, nfft
integer(c_size_t), intent(in) :: n
integer(c_long), intent(in) :: nfft
real(c_double), intent(in) :: x(n)
real(c_double), intent(out) :: F(2 * nfft + 2)
! local
Expand Down
Loading