-
-
Notifications
You must be signed in to change notification settings - Fork 7k
Tutorial/Smoothing algorithm is a poor choice #3934
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I think introducing the shifting operator brings a extra difficulty to the example...I'm not sure if it is convenient in this case. Maybe adding this code as a "advanced method" in the same page could do the trick. |
IMO 2 filters make sense:
|
The shift isn't really important - that's a comparatively minor optimisation. Perhaps a note along the lines of "using a power of 2 divisor will allow the compiler to optimise the calculation". i.e. this works just as well:
|
Ah, yes, using the phrase "low pass filter" would also be good, to at least introduce the topic and give a good term for further research. |
actually average = ETA * (analogRead(inputPin)) + (1-ETA) * average; // ETA: filter weight, e.g. ETA = 0.9 |
Those 2 equations are identical, if 1/filterWeight is ETA. Simple algebra to factor and rearrange the terms. |
Indeed, same function. The 'filterWeight' version lends itself directly to a pure-integer shift-rather-than-division implementation; the traditional expression typically has 'ETA' (alpha) as a fraction (real). |
Agreed, from a practical numerical viewpoint, the original is far better. But for signed division, the complier usually doesn't replace powers of 2 by shifting. That tends to happen only for unsigned numbers. |
no, it's not identical IMO, because average is multiplied by (1-ETA) and aditionally (!) newvalue is multiplied by ETA. anyway, |
sigh Basic algebra.... average = ETA * (newvalue) + (1-ETA) * average |
you win! :) |
I also like his formular, although I dont know what ETA means, but i know what it is used for. I also use this here: |
float ETA by ETA=0.5 its just the arithmetic average (mean), by 0.9 the current readings are more strongly weighted, and by ETA < 0.5 it's just an extremely idle/dull filter, showing almost no peaks any more. |
Sure. I just wonder what E, T and A stand for. What is does i know for sure ;) |
eta is a Greek letter As it's a constant, I'm writing it in Capital letters as usual for constants in C programs. |
I never got above epsilon :D |
In New Common Greek it's pronounced "iita", not "eeeta" as in Ancient Classic Greek. But you may use Nevertheless, as ETA is from 0...1 only floats will work. In the _real_ world nothing is integer, every number is _real, i.e. (rational, irrational, or even transcendental) a _float. ;) |
@Vogonjeltzz, I don't know what is better. As Paul already mentioned, his solution can be implemented by bitshift, which is far more economical than your solution. So on paper you may win, in practice Paul is using processing power far better. |
ok, for speed on an AVR you are right, but for a tutorial I would prefer the float version I posted nevertheless. |
FWIW, I don't deserve any credit for the original code, even though I've independently used that same equation in several projects over the years. When evaluating these algorithms, with either integers or floats, the effects of limited precision should be investigated. Especially important is the case of a small AC or higher frequency signal with a large DC or very low frequency offset. The practical effect of limited numerical precision in the addition or subtraction is a point where special attention should be paid. I would caution anyone reading this to avoid investing too much confidence in arguments that amount to personal preference, without actual testing or rigorous mathematical analysis. |
for educational reasons, the approach by using floats is more intuitive, and additionally, always precise and accurate (related to the chosen value of ETA and the precision of 32bit floats in general):
For filters (of either kind), in general, by a scientific+ statistical point of view, the sensitivity, accuracy, and the evidence has to be proven of course by a statistical evaluation. |
That's quite an overly broad and confident assumption! Perhaps the word "always" is meant to include ARM chips, where "int" is 32 bits, and "float" uses a 23 bit mantissa? Again, I would caution anyone reading this thread from placing too much faith in some of these arguments, which amount to little more than casual conversation about personal preferences. |
no, "always precise and accurate" is related to the range of expectable results and digits over the whole range of floats even for integer parts ("Vorkommastellen") <1.0 or even <10.0, still providing 6-7 fraction digits, and it's related to the fact that no division by zero is possible which would result in nans ( related to average = average + (analogRead(inputPin) - average) / filterWeight;) Even more precise would be using double precision floats though. Anyway, (analogRead(inputPin) - average) >> filterWeight is a term which is absolutely misplaced in a beginner's tutorial. |
I never ever used a single float in any of my c/c++ programs and I probably never will unless I code for a PC and then I will avoid it anyways. If you ask me AVR should not support float at all because its just not the right platform to use it. But a simple smooth value from 0-255 is totally fine i'd say, which can be achieved with the formular too. |
yes, sure it's possible, and there are many more filters possible, too, e.g. just a mean average or a median filter or even a Kalman filter or a MonteCarlo filter. But this issue/ topic is about a tutorial of a low pass filter (CMIIW), and therefore the point is about an educational approach to make it capable for beginners. Floats or not floats is a complete different issue - to me I'm always using floats because my programs require floats. Just everyone to his special needs. |
you should explain: (english is not my native language, you're supposed to know what I mean nevertheless) |
Okay. You want me to play devil's advocate until we get the terms, I can do that. What is a constant factor? Why 0.9 and not, say, 32? Why 0.7 instead of, say, -1? What do you mean by weight? |
it's 0..1 by definition. Otherwise it won't work |
Who defined it and why did they define it that way? |
the author of the filter defined it for the filter to work. people who don't understand even that better don't start programming, at least no filters. using the term "confidence" instead of "filterweight" wouldn't make it better - just the opposite. |
It's a number which gives some elements more "weight" or influence on the 2015-10-12 19:36 GMT+02:00 tigoe notifications@github.com:
|
I disagree. I see 110new students every year, graduate students, and a good 20% of them are interested in programming, but have been turned off by math sometime in the past. Often it's because people told them they "should know". We made Arduino mainly to make it possible to explain programming to these kinds of people. And it is possible. I've seen it work. But it requires that we let go of the notion that someone "should know". I can show you engineering students who can run rings around many people in this thread but wouldn't know a gerund from a whole in the ground. And when they're in a room with writers who can't program, and they both let go of their assumptions and trust each other, they teach other and they both learn. That is why I'm pushing on this. I want to help people feel like they understand technology, not just those who are good at it. @q2dg, thank you. That is a much more usable and succinct answer. |
well, do want you think what would be best. average = filterweight * (sensorReading) + (1 - filterweight ) * average; // choose filterweight 0...1 could be misleading. But I know for sure what would be misleading using "confidence", especially for non-English speakers. you might also call it "prayer", with the same result. |
average = filterweight * (sensorReading) + (255 - filterweight ) * average; Basic math guys, basic math... I think students in that age will understand that. If not you are a bad teacher if you are not able to explain that in a minute. And if they dont understand they are in the wrong class or should read the if - else section again. |
do what you want, I only can give advices. |
Sorry, @vogonjeltz, I didn't mean to insult your experience. I get agitated on this issue. @q2dg laid it to rest for me above. I'm fine using "filterWeight" as long as the comments at the top of the sketch read something like that Wikipedia quote. Modified, I'd say: "filterWeight is a measure of the importance or "weight" of one sensor reading relative to the others. A reading's weight is measured on a scale from 0 (i.e. no value at all) to 1 (i.e. more important than all the others)." (I think I got that backwards, but that's the general idea.) I think what you said about 0.7 - 0.9 is useful, but I want to find more explicit language for it. |
first comes the equation, then comes the explanation. You might wish to work out the explanation, from time to time, as time goes by.... |
The filter recurrence relation provides a way to determine the output samples in terms of the input samples and the preceding output. The following pseudocode algorithm simulates the effect of a low-pass filter on a series of digital samples: // Return RC low-pass filter output samples, given input samples, The loop that calculates each of the n outputs can be refactored into the equivalent: for i from 1 to n That is, the change from one filter output to the next is proportional to the difference between the previous output and the next input. This exponential smoothing property matches the exponential decay seen in the continuous-time system. As expected, as the time constant \scriptstyle RC increases, the discrete-time smoothing parameter \scriptstyle \alpha decreases, and the output samples \scriptstyle (y_1,, y_2,, \ldots,, y_n) respond more slowly to a change in the input samples \scriptstyle (x_1,, x_2,, \ldots,, x_n); the system has more inertia. This filter is an infinite-impulse-response (IIR) single-pole low-pass filter. https://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization |
Right. I'll come back and turn that into something more digestible later on. |
remember, it's a formula. call the terms and multiplicants as you want but keep away from being suggestive / evocative. a factor is a factor is a factor. Explain what it does by which effecs, that's the simple point. |
Clearly we have different ways of teaching. I've found that being suggestive works very well, when the suggestion is in the students' experience. I spend much of my time looking for the right analogies. |
not if the suggestive words lead to misleading, such as "confidence" |
start with the equation, then we may work on the explanation. |
I don't think we're going to reach agreement on this. I'm therefore going to bow out of the conversation, so I don't offend anymore. |
call it filterfactor or smoothingfactor or just x. and keep in your mind: The earlier people internalize this, the better. So don't put your explanations into your equation identifiers. |
Okay, spent the evening testing @bdlow's int version and @vogonjeltz's float version using a 3G accelerometer. I did a graphing sketch in P5.js to test it out, you can find my sketch and tests here: https://github.com/tigoe/GraphingSketches/tree/master/WeightedAverageGraph. You can see some images of the float version in the directory as well. The int version gives more clearly defined abstractions of the signal, i.e. you can filter the transitions and peaks easier from the average. But the actual values are further off from the unfiltered values. The float version tends to change more closely with the sensor values, even with a high value. Of the two, I think I'd prefer the int version, after testing. I think in the long run, it's probably more useful to people looking to filter events out of their sensors. I'd recommend switching to that version, and leaving the filtering weight value at about 16 to 32. In trying to make sense of the math above, I stumbled upon a version I did in 2005, and modified again in 2012: http://www.tigoe.com/pcomp/code/arduinowiring/37/ And even better the paper on which I based that version, which includes a fairly readable explanation of what's going on: http://home.earthlink.net/~david.schultz/rnd/2002/KalmanApogee.pdf In that paper, p.8, "Filtering the Data", he explains what's going on better than anything else I could find. Ultimately, I'll leave it up to @ffissore, @agdl , and @cmaglie. If you think the change is worthwhile, feel free to use either of these, with credit to the appropriate authors. I'm happy with them both. |
what'd I say? Filtering the Data
where: |
Yes. He said it more accessibly than you. |
yes, I'm not English, and explaining it is your job, but the main point actually was the equation. I explained it above in short words
"by eta=0.5 its just the arithmetic average (mean), by 0.9 the current readings are more strongly weighted, and by eta < 0.5 it's just an extremely idle/dull filter, showing almost no peaks any more." He said the same but more verbose. Anyway, the main discussion was about the right formula to be taken. |
Agreed. For me, I need the more verbose description, which is why his helped me. Apologies for my shortcomings that way, I didn't mean to aggravate you. |
I'm a little bit scared in writing something here :) I wanted to write only too many comments and close the issue, but it is not too polite... The showed examples somewhere in the Arduino website are intended for people who firstly approach micro-controllers programming like said by @tigoe so it's a good idea to keep them more simple as possible. If someone wants to improve them can easily search the web, books or whatever, however I think the most of them are a good starting point. So based on this I will leave it as is. In this way everyone can say: "ok it sucks like @vogonjeltz" and no one can say "I can't understand why you chose the algorithm written by someone instead of the one written by someone else". Please don't start again a one way discussion about I'm right and you are wrong :) |
@agdl - what did you mean? Do you honestly expect a beginner to understand how this works?
as I am no advanced programmer, just a couple of years C-like programming, and I don't understand at all what this is about to do. And I wouldn't assume any beginner to understand what this is about. But again, honestly, this is the way one usually uses this filter, you can read it it either tuutorial or source reference:
|
Uh? The formula in now on-line is the basic average value avg = sum/n written as:
|
not the basic average (mean) - the weighted average, in this case the weighted floating average.
|
The algorithm used in the Smoothing tutorial (https://www.arduino.cc/en/Tutorial/Smoothing) uses a simple moving average of 10 samples, storing the samples in an array.
Whilst useful in some applications, for simply removing noise from an analog input a simple moving average (IIR filter) is a more efficient and less convoluted method. e.g.:
The text was updated successfully, but these errors were encountered: