diff --git a/sensors/box/sensor.json b/sensors/box/sensor.json index 40633fd..4617b9c 100644 --- a/sensors/box/sensor.json +++ b/sensors/box/sensor.json @@ -34,9 +34,8 @@ "hhd_downsample": 3 }, "filters": { - "enabled": true, - "flow_ema_alpha": 0.5, - "force_median_window": 3, + "flow_ema_alpha": 1.0, + "force_median_window": 1, "force_lowpass_cutoff_hz": 0.0, "force_lowpass_order": 2, "force_lowpass_sample_rate_hz": "auto" diff --git a/sensors/finger/sensor.json b/sensors/finger/sensor.json index f9c3f90..03960b3 100644 --- a/sensors/finger/sensor.json +++ b/sensors/finger/sensor.json @@ -25,9 +25,8 @@ "hhd_downsample": 2 }, "filters": { - "enabled": true, - "flow_ema_alpha": 0.5, - "force_median_window": 3, + "flow_ema_alpha": 1.0, + "force_median_window": 1, "force_lowpass_cutoff_hz": 0.0, "force_lowpass_order": 2, "force_lowpass_sample_rate_hz": "auto" diff --git a/sensors/mini/sensor.json b/sensors/mini/sensor.json index 40633fd..4617b9c 100644 --- a/sensors/mini/sensor.json +++ b/sensors/mini/sensor.json @@ -34,9 +34,8 @@ "hhd_downsample": 3 }, "filters": { - "enabled": true, - "flow_ema_alpha": 0.5, - "force_median_window": 3, + "flow_ema_alpha": 1.0, + "force_median_window": 1, "force_lowpass_cutoff_hz": 0.0, "force_lowpass_order": 2, "force_lowpass_sample_rate_hz": "auto" diff --git a/src/orisys/sensor.py b/src/orisys/sensor.py index 560bfee..1c901be 100644 --- a/src/orisys/sensor.py +++ b/src/orisys/sensor.py @@ -175,12 +175,26 @@ class Sensor: # These settings change the reported force values, so they are part of # the measurement-pipeline identity and are recorded in the calibration # metadata / mismatch check alongside dis_mode and hhd_downsample. - # Filtering is ON by default; set filters.enabled=false (or a per-key - # zero) to get the unfiltered chain. + # + # Every stage is switched by its **own value**, so the config states what + # runs: ``flow_ema_alpha >= 1``, ``force_median_window <= 1`` and + # ``force_lowpass_cutoff_hz <= 0`` each mean "stage off". Values are never + # overridden: there is no master switch that could make the file say 0.5 + # while the device runs 1.0. An invalid value falls back to **off**, never + # to a default that filters. ``enabled`` is kept only as a deprecated + # alias for "turn every stage off" and is honoured when it is explicitly + # false. ``Sensor.filters_state()`` reports what actually took effect. filters_cfg = self._config.get("filters") if not isinstance(filters_cfg, dict): filters_cfg = {} - self._filters_enabled = bool(filters_cfg.get("enabled", True)) + legacy_off = filters_cfg.get("enabled", None) is False + + def _cfg(key, default): + """The configured value, or the documented default when unset.""" + if legacy_off: + return default + value = filters_cfg.get(key, None) + return default if value is None else value self._lowpass_order = 2 self._lowpass_sample_rate_hz = float(DEFAULT_SAMPLE_RATE_HZ) @@ -195,34 +209,31 @@ class Sensor: self._config_fps = float(DEFAULT_SAMPLE_RATE_HZ) self._lowpass_sample_rate_hz = self._config_fps try: - self._lowpass_order = max(1, int(filters_cfg.get("force_lowpass_order", 2))) - rate_cfg = filters_cfg.get("force_lowpass_sample_rate_hz", "auto") - if isinstance(rate_cfg, str): - self._lowpass_rate_auto = rate_cfg.strip().lower() in ("auto", "", "measured") - else: - rate = float(rate_cfg) if rate_cfg is not None else 0.0 - self._lowpass_rate_auto = rate <= 0.0 - if not self._lowpass_rate_auto: - self._lowpass_sample_rate_hz = rate - if self._filters_enabled: - self._lowpass_cutoff_hz = float( - filters_cfg.get("force_lowpass_cutoff_hz", DEFAULT_FORCE_LOWPASS_HZ) - ) + self._lowpass_order = max(1, int(_cfg("force_lowpass_order", 2))) except (TypeError, ValueError): self._lowpass_order = 2 - self._lowpass_sample_rate_hz = self._config_fps - self._lowpass_rate_auto = True - self._lowpass_cutoff_hz = ( - DEFAULT_FORCE_LOWPASS_HZ if self._filters_enabled else 0.0 + rate_cfg = _cfg("force_lowpass_sample_rate_hz", "auto") + if isinstance(rate_cfg, str): + self._lowpass_rate_auto = rate_cfg.strip().lower() in ("auto", "", "measured") + else: + try: + rate = float(rate_cfg) + except (TypeError, ValueError): + rate = 0.0 + self._lowpass_rate_auto = rate <= 0.0 + if not self._lowpass_rate_auto: + self._lowpass_sample_rate_hz = rate + try: + self._lowpass_cutoff_hz = float( + _cfg("force_lowpass_cutoff_hz", DEFAULT_FORCE_LOWPASS_HZ) ) + except (TypeError, ValueError): + self._lowpass_cutoff_hz = 0.0 if self._lowpass_sample_rate_hz <= 0: self._lowpass_sample_rate_hz = self._config_fps + # An unusable cutoff is "off", never a silent jump to the default. if not (0.0 <= self._lowpass_cutoff_hz < 0.5 * self._lowpass_sample_rate_hz): - self._lowpass_cutoff_hz = ( - DEFAULT_FORCE_LOWPASS_HZ - if (self._filters_enabled and 0.0 < DEFAULT_FORCE_LOWPASS_HZ < 0.5 * self._lowpass_sample_rate_hz) - else 0.0 - ) + self._lowpass_cutoff_hz = 0.0 self._lowpass_enabled = self._lowpass_cutoff_hz > 0 self._force_filter = LowpassFilter( cutoff_hz=self._lowpass_cutoff_hz, @@ -234,25 +245,32 @@ class Sensor: self._force_rate = SampleRateEstimator(initial_hz=self._config_fps) self._force_rate_source = "config" - self._force_median_window = 1 - if self._filters_enabled: - try: - self._force_median_window = max( - 1, int(filters_cfg.get("force_median_window", DEFAULT_FORCE_MEDIAN_WINDOW)) - ) - except (TypeError, ValueError): - self._force_median_window = DEFAULT_FORCE_MEDIAN_WINDOW + try: + self._force_median_window = max(1, int(_cfg("force_median_window", DEFAULT_FORCE_MEDIAN_WINDOW))) + except (TypeError, ValueError): + self._force_median_window = 1 self._force_median = CausalMedianFilter(window=self._force_median_window, n_channels=3) self._flow_ema_alpha = 1.0 - if self._filters_enabled: - try: - alpha = float(filters_cfg.get("flow_ema_alpha", DEFAULT_FLOW_EMA_ALPHA)) - except (TypeError, ValueError): - alpha = DEFAULT_FLOW_EMA_ALPHA - if 0.0 < alpha < 1.0: - self._flow_ema_alpha = alpha + try: + alpha = float(_cfg("flow_ema_alpha", DEFAULT_FLOW_EMA_ALPHA)) + except (TypeError, ValueError): + alpha = 1.0 + if 0.0 < alpha < 1.0: + self._flow_ema_alpha = alpha self._ema_flow = None + if legacy_off and self.verbose: + print( + "Note: filters.enabled is deprecated and means 'all stages off'; " + "remove it and switch each stage by its own value " + "(flow_ema_alpha=1, force_median_window=1, force_lowpass_cutoff_hz=0)." + ) + # Derived, for callers that ask "is any stage filtering?". + self._filters_enabled = bool( + self._flow_ema_alpha < 1.0 + or self._force_median_window > 1 + or self._lowpass_enabled + ) if self.verbose: if self.calibration is not None and self.calibration.source_path is not None: