Skip to content

Conversation

@possibly-human
Copy link
Contributor

Summary

Adds an adaptive scaling utility for real-time signals with two operating modes: Min-Max or Quantile-based scaling. The module continuously tracks either absolute minimum/maximum values (Min-Max mode) or statistical quantiles (Quantile mode) to rescale input into a configurable output range. Both modes support either cumulative tracking (infinite window) or time-windowed adaptation (EMA decay).

Adapted from Sofian Audry's Plaquette implementation for Min-Max scaling and RobustScaler for quantile-based scaling.

Functionality

Inputs are :

  • Signal (int or float)

Parameters are:

  • Mode (enum) — Min_max (absolute range) or Quantile (statistical range)
  • Time window (s) (float) — EMA window for adaptation; dt inferred per tick; 0 / toggle → infinite
  • Infinite time window (bool) — cumulative mode (no decay)
  • Output low (float) — minimum value of output range
  • Output high (float) — maximum value of output range
  • Span (float) — Quantile mode only: coverage percentage of distribution (0.50-1.00)

Outputs are:

  • Scaled (float) — signal scaled into chosen output range

Modes Explained
Min-Max Mode:

  • Tracks absolute minimum and maximum values
  • Maps everything between min - max to output range
  • Sensitive to outliers — a single extreme value compresses everything else

Quantile Mode:

  • Tracks low and high quantiles (e.g., 1st and 99th percentiles)
  • Span controls what percentage of typical values to track (ex : 1.00 = full min-max (0th-100th percentile), 0.50 = middle 50% (25th-75th percentile, interquartile range))
  • Ignores extreme values outside the span for robust scaling

Implementation notes

Uses MinMaxScaler and QuantileScaler custom classes stored in 3rdparty/extras (to be reused in Respiration and EDA processes)

@possibly-human
Copy link
Contributor Author

Testing

Was tested on Linux (booted on a T2 chip, 2019 MacBook Pro)

Once with an LFO generator :
infinite time window
scaler_infinite

finite time window (adaptative)
scaler_finite.webm

Example file : https://drive.google.com/file/d/1r9T9dA49vtxOweFGMrmAgClVsx0gCF4l/view?usp=sharing

And once with live data from Sensors2OSC (device orientation). The live data was then recorded and saved with CSV recorder, and output from the Scaler object was compared to output from processing with similar algorithms in python, with the same parameters.
scaling with live data
scaler_livedata

Example file : https://drive.google.com/file/d/1mJXACJjnmmpcYewHWkozpven5L4NAJEj/view?usp=sharing

comparison of ossia object output and python output
scaler_comparison

Python file
scaler.py

@jcelerier jcelerier self-requested a review December 9, 2025 02:42
@@ -0,0 +1,32 @@
#pragma once
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is in all your PRs, I'd propose you to do the following:

1/ make a first PR & commit with just that file (and any common helper files you reuse across your PRs)
2/ rebase your PRs on top of this

what do you think?

static float spanToLowQuantileLevel(float span);

// Window configuration
bool _infinite;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will all need initialization to clean values just for future safety in case we add a new constructor and forget to init one

@possibly-human possibly-human merged commit 4461349 into master Dec 12, 2025
5 of 16 checks passed
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.

3 participants