-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGaussianKernelsHelper.cs
144 lines (115 loc) · 5.3 KB
/
GaussianKernelsHelper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
using System;
using System.Runtime.CompilerServices;
namespace SevenBoldPencil.MathTools
{
public static class GaussianKernelsHelper
{
private struct Sample
{
public double X, Y;
}
public static void GetKernelBilinearSameSize(
double sigma, int kernelSize, out double[] weightsBilinear, out double[] samplePositionsBilinear, int sampleCount = 1000)
{
GetKernelBilinear(sigma, kernelSize * 2 - 1, out weightsBilinear, out samplePositionsBilinear, sampleCount);
}
public static void GetKernelBilinear(
double sigma, int kernelSize, out double[] weightsBilinear, out double[] samplePositionsBilinear, int sampleCount = 1000)
{
// Steve M comment at:
// https://dev.theomader.com/gaussian-kernel-calculator/
if ((kernelSize - 1) % 4 != 0) throw new ArgumentException("(kernelSize - 1) % 4 != 0");
GetKernel(sigma, kernelSize, out var weights, out var samplePositions, sampleCount);
var newSize = (kernelSize + 1) / 2;
var pairsPerSideAmount = (kernelSize - 1) / 4;
weightsBilinear = new double[newSize];
samplePositionsBilinear = new double[newSize];
weightsBilinear[pairsPerSideAmount] = weights[pairsPerSideAmount * 2];
for (var i = 0; i < pairsPerSideAmount; ++i)
{
var i_o = i * 2;
var newWeight = weights[i_o] + weights[i_o + 1];
weightsBilinear[i] = newWeight;
weightsBilinear[newSize - 1 - i] = newWeight;
var newPosition = samplePositions[i_o + 1] - weights[i_o] / newWeight;
samplePositionsBilinear[i] = newPosition;
samplePositionsBilinear[newSize - 1 - i] = -newPosition;
}
}
public static void GetKernel(
double sigma, int kernelSize, out double[] weights, out double[] samplePositions, int sampleCount = 1000)
{
// https://dev.theomader.com/gaussian-kernel-calculator/
if (kernelSize % 2 != 1) throw new ArgumentException("kernelSize % 2 != 1");
Func<double, double> GD = x => GaussianDistribution(x, 0, sigma);
weights = new double[kernelSize];
// need an even number of intervals for simpson integration => odd number of samples
var samplesPerBin = (int) Math.Ceiling((double) sampleCount / kernelSize);
if (samplesPerBin % 2 == 0) ++samplesPerBin;
// now sample kernel taps and calculate tap weights
var weightSum = 0d;
var kernelLeft = -Math.Floor(kernelSize / 2d);
var samplesCache = new Sample[samplesPerBin];
for (var tap = 0; tap < kernelSize; ++tap)
{
var left = kernelLeft - 0.5 + tap;
SampleInterval(GD, left, left + 1, samplesCache);
var tapWeight = IntegrateSimpson(samplesCache);
weights[tap] = tapWeight;
weightSum += tapWeight;
}
// normalize kernel
for (var i = 0; i < kernelSize; ++i) {
weights[i] /= weightSum;
}
samplePositions = GetSamplePositions(kernelSize);
}
private static readonly double Sqrt2PI = Math.Sqrt(2 * Math.PI);
private static double GaussianDistribution(double x, double mu, double sigma)
{
var d = x - mu;
return Math.Exp(-d * d / (2 * sigma * sigma)) / (Sqrt2PI * sigma);
}
private static void SampleInterval(Func<double, double> f, double minInclusive, double maxInclusive, Sample[] samplesCache)
{
var sampleCount = samplesCache.Length;
var stepSize = (maxInclusive - minInclusive) / (sampleCount - 1);
for (var s = 0; s < sampleCount; ++s)
{
var x = minInclusive + s * stepSize;
samplesCache[s].X = x;
samplesCache[s].Y = f(x);
}
}
private static double IntegrateSimpson(Sample[] samples)
{
var result = samples.First().Y + samples.Last().Y;
for (var s = 1; s < samples.Length - 1; ++s)
{
var sampleWeight = (s % 2 + 1) * 2;
result += sampleWeight * samples[s].Y;
}
var h = (samples.Last().X - samples.First().X) / (samples.Length - 1);
return result * h / 3.0;
}
private static double[] GetSamplePositions(int kernelSize)
{
var samplePositions = new double[kernelSize];
var kernelLeft = -(kernelSize / 2);
for (var i = 0; i < kernelSize; ++i) {
samplePositions[i] = kernelLeft + i;
}
return samplePositions;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ref T First<T>(this T[] array)
{
return ref array[0];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ref T Last<T>(this T[] array)
{
return ref array[^1];
}
}
}