Threshold

class gpype.backend.flow.threshold.Threshold[source]

Bases: IONode

Turns a continuous signal into a held 0/1 state.

This is the step from a measurement to a decision, and the two parameters that matter are the ones that stop it chattering. A bare comparison flips on every sample that grazes the level, which as a control signal means an output that switches tens of times a second while the user holds still.

Hysteresis gives the release its own, lower level, so leaving a state takes more than the noise that entered it. Dwell requires the new state to hold for a number of samples before it is believed.

The output is 0.0 or 1.0 in the pipeline’s own data type, not a boolean array, so it flows through the rest of the pipeline like any other channel.

class Configuration[source]

Bases: Configuration

Configuration class for Threshold parameters.

class Keys[source]

Bases: Keys

Configuration keys for Threshold settings.

LEVEL = 'level'

Level at which the state is entered

DIRECTION = 'direction'

Whether crossing upwards or downwards enters the state

class OptionalKeys[source]

Bases: OptionalKeys

Optional configuration keys.

RELEASE = 'release'

Level at which the state is left; defaults to LEVEL

DWELL = 'dwell'

Samples the new state must hold before it is accepted

ABOVE = 'above'

Enter the state when the signal rises above the level.

BELOW = 'below'

Enter the state when the signal falls below the level.

__init__(level, direction='above', release=None, dwell=1, **kwargs)[source]

Initialize the threshold.

Parameters:
  • level (float) – Level at which the state is entered.

  • direction (str) – ABOVE or BELOW.

  • release (Optional[float]) – Level at which the state is left. Defaults to level, i.e. no hysteresis. For direction ABOVE it must not be above level, and the other way round for BELOW.

  • dwell (int) – Samples the new state must hold before it is accepted.

  • **kwargs – Additional arguments for the parent IONode.

Raises:

ValueError – If level is missing, the direction is unknown, the release sits on the wrong side of the level, or dwell is below one.

setup(data, port_context_in)[source]

Allocate the per-channel state.

Parameters:
Return type:

dict[str, dict]

Returns:

Output port contexts.

get_control()[source]

Report the levels, so a caller can see before it sets.

Return type:

dict

Returns:

level, release and dwell as they stand.

set_control(arg)[source]

Move the levels while the pipeline runs.

This is the node the surface exists for. A threshold is tuned against a signal you are watching – a neurofeedback level is found by moving it until the subject can hold it, which cannot be done by stopping, editing and restarting, because the thing being tuned against is the person in the chair.

Safe to change mid-run because the level is read on every frame rather than cached at setup, and because it is not filter state: a new level takes effect on the next sample with no transient. The dwell counter is deliberately not reset – a candidate state part-way through its dwell keeps its progress, so nudging the level does not restart the debounce and make the output feel stuck.

Parameters:

arg (dict) – The whole resulting state; the framework merges before calling, so every key is present.

Return type:

dict

Returns:

The state actually in force, which is what a caller should display rather than what it sent.

Raises:

ValueError – If the combination cannot run. Refused as a whole rather than partly applied – a release above the level inverts the hysteresis and would latch the output on, which is worse than a rejected request.

step(data)[source]

Compare the frame against the level and hold the state.

Parameters:

data (dict[str, ndarray]) – Input frame.

Return type:

dict[str, ndarray]

Returns:

The state per channel, one row per input row.