I need to apply a 1d gaussian filter to a list of floats in c#, ie, to smooth a graph.
I got as far as simply averaging each value with n neighbors, but the result wasn't quite right and so I discovered that I need to apply a normal distribution weight to the contributions of the values per iteration.
I can't find a library like scipy that has a function for this, and I don't quite understand the algebraic formulas I have found for computing a gaussian kernal. Examples are generally geared towards a 2D implementation for images.
Can anyone suggest the modifications that would need to be made to the following code to achieve the proper gaussian effect?
public static List<float> MeanFloats(List<float> floats, int width)
{
List<float> results = new List<float>();
if (width % 2 == 0)
width -= 1; // make sure width is odd
int halfWidthMinus1 = width / 2; // width is known to be odd, divide by 2 will round down
for (int i = 0; i < floats.Count; i++) // iterate through all floats in list
{
float result = 0;
for (int j = 0; j < width; j++)
{
var index = i - halfWidthMinus1 + j;
index = math.max(index, 0); // clamp index - the first and last elements of the list will be used when the algorithm tries to access outside the bounds of the list
index = math.min(index, floats.Count-1);
result += floats[index]; // multiply with kernal here??
}
result /= width; // calculate mean
results.Add(result);
}
return results;
}
If relevant this is for use in a Unity game.
A 1-dimensional Gaussian Kernel is defined as
where sigma
is the standard deviation of your list, and x
is the index distance.
You then create a kernel by filling each of its array slots with a multiplier. Here is an (untested) example:
private static float[] GaussianKernel(int width, float sigma)
{
float[] kernel = new float[width + 1 + width];
for (int i = -width; i <= width; i++)
{
kernel[width + i] = Mathf.Exp(-(i * i) / (2 * sigma * sigma)) / (Math.PI * 2 * sigma * sigma);
}
return kernel;
}
In your smoothing function you apply this multiplier to the floats[index]
value. Finally, before adding the result, instead of dividing it by the width
, you divide it by the total sum of the kernel weights (the values of the kernel array).
You could compile the values of the current kernel weight during each iteration in your j-loop weightSum += kernel[j]
.