1 /**
2  * A gaussian filter (aka gaussian blur) is a convolution
3  * (:d:mod:`daffodil.filter.convolve`) using a matrix created from a gaussian
4  * distribution.
5  */
6 module daffodil.filter.gaussian;
7 
8 import std.math;
9 
10 import daffodil.image;
11 import daffodil.filter;
12 
13 /**
14  * Evaluate the gaussian/normal distribution for a given ``x``, ``stDev`` and
15  * ``mean``.
16  */
17 real gaussianDistribution(real x, real stDev = 1, real mean = 0) {
18     return 1/(stDev * sqrt(2 * PI))* E.pow(-pow(x - mean, 2)/(2 * pow(stDev, 2)));
19 }
20 
21 @("gaussian distribution")
22 unittest {
23     static void assertEq(real a, real b) {
24         return assert(approxEqual(a, b));
25     }
26 
27     // Standard Normal Distribution
28     assertEq(gaussianDistribution(0), 0.398942);
29     assertEq(gaussianDistribution(1), 0.241971);
30     assertEq(gaussianDistribution(1), gaussianDistribution(-1));
31     assertEq(gaussianDistribution(2), 0.053991);
32     assertEq(gaussianDistribution(2), gaussianDistribution(-2));
33     assertEq(gaussianDistribution(3), 0.004432);
34     assertEq(gaussianDistribution(3), gaussianDistribution(-3));
35 
36     // Changed standard deviation
37     assertEq(gaussianDistribution(0, 2), 0.199471);
38     assertEq(gaussianDistribution(2, 2), 0.120985);
39 
40     // Changed mean
41     assertEq(gaussianDistribution(0, 2, 2), 0.120985);
42     assertEq(gaussianDistribution(2, 2, 2), 0.199471);
43     assertEq(gaussianDistribution(4, 2, 2), 0.120985);
44 }
45 
46 /**
47  * Create a 1D matrix of a discrete gaussian distribution with a given standard
48  * deviation and the number of standard deviations to stop generating at. The
49  * result is mirrored with guaranteed odd length.
50  *
51  * The result can be used to convolve a image.
52  */
53 real[] gaussianMatrix(real stDev = 1, real maxDev = 3) {
54     auto range = cast(uint)ceil(stDev * maxDev);
55     auto ret = new real[1 + 2*range];
56 
57     ret[range] = gaussianDistribution(0, stDev);
58     foreach (i; 1..range + 1) {
59         ret[range + i] = ret[range - i] = gaussianDistribution(i, stDev);
60     }
61 
62     return ret;
63 }
64 
65 @("gaussian matrix")
66 unittest {
67     const matrix = [0.004432, 0.053991, 0.241971, 0.398942, 0.241971, 0.053991, 0.004432];
68     assert(approxEqual(gaussianMatrix(), matrix));
69 
70     assert(gaussianMatrix(10).length == 61);
71 }
72 
73 /**
74  * Return a copy of ``image`` with a gaussian blur applied across axies
75  * ``axis`` with a given standard deviation and the number of standard
76  * deviations to stop at.
77  */
78 auto gaussianBlurred(string axis = "xy", V)(const Image!V image, real stDev = 1, real maxDev = 3) {
79     auto matrix = gaussianMatrix(stDev, maxDev);
80 
81     return image.convolved!axis(matrix);
82 }
83 
84 @("gaussian blur")
85 unittest {
86     import daffodil;
87 
88     auto image = new Image!ubyte(2, 2, 3, RGB);
89     image[0, 0] = [1f, 1f, 1f];
90     image[0, 1] = [1f, 0f, 0f];
91     image[1, 0] = [0f, 1f, 0f];
92     image[1, 1] = [0f, 0f, 1f];
93 
94     auto blurred = image.gaussianBlurred(2);
95     assert(blurred[0, 0] == [130, 130, 122]);
96     assert(blurred[0, 1] == [130, 117, 122]);
97     assert(blurred[1, 0] == [114, 130, 122]);
98     assert(blurred[1, 1] == [114, 117, 122]);
99 }