ChrisVeigl / BrainBay

Open Source Biofeedback Software
http://brainbay.lo-res.org
Other
163 stars 52 forks source link

best practice: dynamic thresholding a value that can be negative or positive #23

Open ElliotMebane opened 5 years ago

ElliotMebane commented 5 years ago

I have a design that measures the balance of 2 channels. Values can be -100 through 100 as channel dominance fluctuates. When I apply a threshold at 90% and the average value for the interval is negative (e.g., -50) the calculation results in a new value of -45, a value greater than the average.

Here's the current calculation in threshold: to_input = avgsum/interval*smalladapt/100.0f;

My anticipated behavior is that the percentage is determined as the ratio of the max-min range (like the way globals.cpp's size_value method works)

Example of desired behavior: An average of -50 would be 25% of the -100 through 100 range and a 90% threshold calculation would result in a value of -55.

Desired calculation: (-50 - -100)/(100 - -100)0.9 = 0.225 (90% threshold is 0.225 into the max-min range) -100 + 0.225 (100 - -100) = -55 (90% of the average of -50 is -55)

I'm currently working around this by adding 100 to the input values so all my values used in the threshold are positive.

ChrisVeigl commented 5 years ago

hmmm .... i understand what you mean... but changing the behaviour of the threshold calculation would break compatibility with existing designs.

we could add an option (combobox) to change the threshold calculation behaviour in the way you describe, and keep the existing behaviour as default. main question is how to call the 2 methods in the selection box ;-)

ElliotMebane commented 5 years ago

After further consideration I no longer think the High/Low settings of the window should affect the calculation. Rather, an alternate Threshold element would use the high/low values reported in the prior time window.

This may be a can of worms that should be left for some future significant app revision and other interpretations of threshold may be considered, including an assessment of how other tools in the market interpret it. Or, if this seems like an important improvement a Threshold2 element could be a way to avoid confusion and backwards compatibility problems.

When I first explored this element my expectation was that a 90% setting would do something like look at the range of values covered during the last time window and place the threshold at 90% of that range. Effectively, I was expecting 90% to capture the lower 90% of the values from the last time window and reject the top 10%. (The High/Low settings of the Threshold window wouldn't come into play in this interpretation.) Instead, I discovered that the element considers the average values covered during the last time window as 100% and dips to 90% of that value.

In the current version if I want to encourage moving the needle upwards the threshold setting should be 100% or above. A setting of 100% pegs the threshold at 50% of the prior time window values, meaning the top half of values from the prior window would be encouraged. A setting of less than 100% includes encouragement of some values that move the needle downwards (values less than the prior window's average). There doesn't appear to be a way to specify encouragement of only the top 25% of the values from the prior time window.

Here are a few summaries: a) Simple average of the values reported in the prior time window (current version) b) Threshold as a percentage of the prior time window range (timeWindowHigh - timeWindowLow ) thresholdPercent. c) Threshold as more of a median type that limits the impact of erroneous high/low values that could throw off the result. Order the prior time window's values from low to high, grab the entry at thresholdPercent* of the way into the array. e.g., a 80% threshold setting and a sorted array of time window entries [ 5, 30, 35, 36, 38, 40, 42, 45, 50, 90 ] would be 45, capturing the bottom 80% (8) entries in the sorted array. d) Use standard deviation to help exclude erroneous entries on the far ends of the bell curve. (Perhaps this is effectively the same as option C?). A setting of 1 standard deviation would be like an 88% setting in option C.

ChrisVeigl commented 5 years ago

This may be a can of worms that should be left for some future significant app revision

The question is if such a significant app reveision will ever happen ;-)

When I first explored this element my expectation was that a 90% setting would do something like look at the range of values (...) Instead, I discovered that the element considers the average values covered during the last time window as 100% and dips to 90% of that value.

i agree that this is not obvious and should be better described in the user manual. in the latest revision i tried to improve the labels in the GUI to better indicate the function (e.g. "upper limit", "lower limit")

Here are a few summaries: a) Simple average of the values reported in the prior time window (current version) b) Threshold as a percentage of the prior time window range (timeWindowHigh - timeWindowLow ) * thresholdPercent. c) Threshold as more of a median type that limits the impact of erroneous high/low values that could throw off the result.

i propose to improve the existing threshold element by removing the checkbox "use median instead of average" by a combobox with above three options. to keep compatible with legacy desings, the element could detect if the checkbox setting is present when a design is loaded an select appropriate mode (c) in this case.

the threshold element has some other inconsistencies, e.g. signal preparation (gain) should affect the min/max settings for the upper / lower limit and also the output port range. (probably it would have been better to keep gain / average in separate elements ...)

e.g., a 80% threshold setting and a sorted array of time window entries [ 5, 30, 35, 36, 38, 40, 42, 45, 50, 90 ] would be 45, capturing the bottom 80% (8) entries in the sorted array.

this is what the existing method for median calculation tries to achieve, using a method with fixed bins ("buckets") to reduce the effort for sorting the array ...

ElliotMebane commented 5 years ago

I've been working mainly with the min number and percentages around 100% to move the threshold relative to it. In a recent design I started using the max number as well and found that I had to use percentage settings around 8% to move that line as I intend it to move. Looking at the calculations in ob_threshold.cpp it seems like it should work as I anticipate and I don't see what is causing the behavior I see. (this is using the percent setting, not median). I hope the new settings will make the behavior more intuitive without breaking legacy designs.