Skip to content

Add activity index calculation from accelerometer data (fixes #642) - #859

Open
samerzumot wants to merge 2 commits into
brainflow-dev:masterfrom
samerzumot:feature/issue-642-activity-index
Open

Add activity index calculation from accelerometer data (fixes #642)#859
samerzumot wants to merge 2 commits into
brainflow-dev:masterfrom
samerzumot:feature/issue-642-activity-index

Conversation

@samerzumot

@samerzumot samerzumot commented Aug 23, 2026

Copy link
Copy Markdown
  • Implement get_activity_index in C++ core using multi-axis epoch variance
  • Add get_activity_index bindings across C++, Python, Java, C#, TypeScript, Rust, Swift, Julia, and MATLAB
  • Add tests activity_index.py

…ow-dev#642)

- Implement get_activity_index in C++ core using multi-axis epoch variance
- Add get_activity_index bindings across C++, Python, Java, C#, TypeScript, Rust, Swift, Julia, and MATLAB
- Add automated test activity_index.py

Copy link
Copy Markdown
Member

Thanks for working on this. I found several blockers that should be addressed before merge:

  1. The Rust binding does not compile. BrainFlowError::InvalidArguments does not exist, and the Windows CI job fails with E0599. Please use the existing error pattern, e.g. Err(Error::BrainFlowError(BrainFlowError::InvalidArgumentsError)), at both validation sites.

  2. The implementation is not the Activity Index referenced by add activity index calculated from accelerometer data #642. The core currently returns sqrt((var_x + var_y + var_z) / 3), while the referenced BIOBSS/Bai definition subtracts device/systematic-noise variance before clamping to zero. No baseline/noise parameter is exposed, so a stationary but noisy sensor produces non-zero activity. The published metric also aggregates longer epochs by summing adjacent one-second AIs instead of recomputing one variance over the full period.

  3. The scope/API does not match the design discussed in add activity index calculated from accelerometer data #642. That discussion places low-level IMU feature helpers in DataFilter and the final analytical/ML activity calculation in MLModel. Either re-scope/rename this API as an accelerometer variability helper and remove fixes #642, or implement the complete AI contract.

  4. Input validation is unsafe/inconsistent. The MATLAB binding does not check equal axis lengths before passing accel_x's length to native code, so shorter Y/Z buffers can be read out of bounds. Empty inputs divide by zero or throw unrelated exceptions in most bindings, and period > data_len is handled differently in C++ and the other languages. MATLAB/TypeScript should also reject non-integer periods.

  5. The new Python test is not run by CI. activity_index.py is not referenced from run_unix.yml. Please wire it into a workflow and cover a non-zero baseline/noise case, empty/invalid inputs, and epoch semantics.

Confirmed Rust CI failure: https://github.com/brainflow-dev/brainflow/actions/runs/32657395247/job/100051356398

References:

…iod validation, and baseline noise variance support
@samerzumot

Copy link
Copy Markdown
Author

Thanks for the detailed review and guidance, @Andrey1994! I have addressed all 5 blockers:

1. Rust Compilation Fix

  • Replaced BrainFlowError::InvalidArguments with Err(Error::BrainFlowError(BrainFlowError::InvalidArgumentsError)) at both validation sites in rust_package/brainflow/src/data_filter.rs.

2. Complete Bai et al. (2016) / BIOBSS Formula

  • Added sampling_rate and per-axis baseline rest noise variances (noise_var_x, noise_var_y, noise_var_z) across the C++ core and all language bindings.
  • Subdivided data into 1-second slices ($f_s$ samples, where $f_s$ is the sampling rate).
  • Subtracted device/systematic noise variance from each axis variance before zero-clamping:
    $$AI_t = \sqrt{ \max\left(0, \frac{1}{3} \sum_{i \in {x, y, z}} (\sigma_{i,t}^2 - \bar{\sigma}_i^2)\right) }$$
  • Implemented epoch aggregation by summing adjacent 1-second AIs over the epoch duration ($AI_{\text{epoch}} = \sum_{t=0}^{H-1} AI_t$), strictly following the Bai et al. (2016) additivity property instead of recomputing a single variance over the whole epoch.

3. Scope/API design and Issue #642

Between rescoping this as a variability helper or implementing the complete AI contract, **I chose to implement the complete AI contract in DataFilter.

Here is my reasoning (Let me know what you think):

  • In BrainFlow, all deterministic analytical metrics computed directly from continuous multi-channel raw sensor streams and sampling rates live in DataFilter / DataHandler for example, DataFilter.get_heart_rate(ppg_ir, ppg_red, sampling_rate, ...) and DataFilter.get_oxygen_level(ppg_ir, ppg_red, sampling_rate, ...).
  • MLModel is designed around evaluating pre-extracted 1D feature vectors (double *data, int data_len) using trained machine learning models (like MindfulnessClassifier with logistic regression coefficients or ONNX models). It does not take multi-channel raw data, sampling rates, or baseline noise parameters.
  • The analytical Activity Index from Bai et al. (2016) and BIOBSS (acc_activityindex.py) is fundamentally a signal-processing metric on raw accelerometer streams. By implementing the complete contract (per-axis baseline noise variance subtraction, 1-second base epoch variance, and epoch summing aggregation) this function fully provides the analytical Activity Index calculation requested in add activity index calculated from accelerometer data #642. If a trained classifier (e.g., limb-position-specific activity classification) is desired in the future, that can be added as a separate metric in MLModel, but DataFilter.get_activity_index delivers the complete analytical contract.

4. Input Validation Consistency & Safety

  • MATLAB: Added axis-length equality checks (size(accel_x, 2) == size(accel_y, 2) == size(accel_z, 2)) to eliminate any buffer overruns; rejects non-integer periods and sampling rates.
  • TypeScript: Enforced integer checks on period and samplingRate; rejects empty arrays.
  • Consistency across all languages: Empty inputs, sampling_rate <= 0, negative noise variances, and period > data_len are now uniformly rejected with INVALID_ARGUMENTS_ERROR across C++, Python, Rust, MATLAB, TypeScript, Java, C#, Swift, and Julia.

5. CI Workflow & Test Suite

  • Wired activity_index.py into .github/workflows/run_unix.yml.
  • Expanded activity_index.py to cover:
    1. Stationary noise cancellation: Verified that a stationary sensor with noise produces $AI \approx 0.0$ when baseline noise parameters are supplied.
    2. Theoretical accuracy: Verified known signal variance minus noise variance matches theoretical calculation.
    3. Epoch summing semantics: Verified that a 2-second epoch equals $AI_1 + AI_2$ and is distinct from recomputing variance across the combined 2-second window.
    4. Invalid argument coverage: Tested empty arrays, shape mismatches, negative noise variances, zero sampling rates, and period > data_len.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants