Blender V4.3
deriche_gaussian_coefficients.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5/* -------------------------------------------------------------------------------------------------
6 * Deriche Gaussian Coefficients.
7 *
8 * Computes the coefficients of the fourth order IIR filter approximating a Gaussian filter
9 * computed using Deriche's design method. This is based on the following paper:
10 *
11 * Deriche, Rachid. Recursively implementating the Gaussian and its derivatives. Diss. INRIA,
12 * 1993.
13 *
14 * But with corrections in the normalization scale from the following paper, as will be seen in the
15 * implementation:
16 *
17 * Farneback, Gunnar, and Carl-Fredrik Westin. Improving Deriche-style recursive Gaussian
18 * filters. Journal of Mathematical Imaging and Vision 26.3 (2006): 293-299.
19 *
20 * The Deriche filter is computed as the sum of a causal and a non causal sequence of second order
21 * difference equations as can be seen in Equation (30) in Deriche's paper, and the target of this
22 * class is to compute the feedback, causal feedforward, and non causal feedforward coefficients of
23 * the filter. */
24
25#include <cstdint>
26#include <memory>
27
28#include "BLI_hash.hh"
29#include "BLI_math_base.hh"
30#include "BLI_math_vector.hh"
31
32#include "COM_context.hh"
34
36
37/* --------------------------------------------------------------------
38 * Deriche Gaussian Coefficients Key.
39 */
40
42
47
49{
50 return a.sigma == b.sigma;
51}
52
53/* -------------------------------------------------------------------------------------------------
54 * Deriche Gaussian Coefficients.
55 */
56
57/* The base constant coefficients computed using Deriche's method with 10 digits of precision.
58 * Those are available in Deriche's paper by comparing Equations (19) and (38). */
59inline constexpr static double a0 = 1.6797292232361107;
60inline constexpr static double a1 = 3.7348298269103580;
61inline constexpr static double b0 = 1.7831906544515104;
62inline constexpr static double b1 = 1.7228297663338028;
63inline constexpr static double c0 = -0.6802783501806897;
64inline constexpr static double c1 = -0.2598300478959625;
65inline constexpr static double w0 = 0.6318113174569493;
66inline constexpr static double w1 = 1.9969276832487770;
67
68/* Computes n00 in Equation (21) in Deriche's paper. */
69static double compute_numerator_0()
70{
71 return a0 + c0;
72}
73
74/* Computes n11 in Equation (21) in Deriche's paper. */
75static double compute_numerator_1(float sigma)
76{
77 const double multiplier1 = math::exp(-b1 / sigma);
78 const double term1 = c1 * math::sin(w1 / sigma) - (c0 + 2.0 * a0) * math::cos(w1 / sigma);
79
80 const double multiplier2 = math::exp(-b0 / sigma);
81 const double term2 = a1 * math::sin(w0 / sigma) - (2.0 * c0 + a0) * math::cos(w0 / sigma);
82
83 return multiplier1 * term1 + multiplier2 * term2;
84}
85
86/* Computes n22 in Equation (21) in Deriche's paper. */
87static double compute_numerator_2(float sigma)
88{
89 const double multiplier1 = 2.0 * math::exp(-(b0 / sigma) - (b1 / sigma));
90 const double term11 = (a0 + c0) * math::cos(w1 / sigma) * math::cos(w0 / sigma);
91 const double term12 = math::cos(w1 / sigma) * a1 * math::sin(w0 / sigma);
92 const double term13 = math::cos(w0 / sigma) * c1 * math::sin(w1 / sigma);
93 const double term1 = term11 - term12 - term13;
94
95 const double term2 = c0 * math::exp(-2.0 * (b0 / sigma));
96 const double term3 = a0 * math::exp(-2.0 * (b1 / sigma));
97
98 return multiplier1 * term1 + term2 + term3;
99}
100
101/* Computes n33 in Equation (21) in Deriche's paper. */
102static double compute_numerator_3(float sigma)
103{
104 const double multiplier1 = math::exp(-(b1 / sigma) - 2.0 * (b0 / sigma));
105 const double term1 = c1 * math::sin(w1 / sigma) - math::cos(w1 / sigma) * c0;
106
107 const double multiplier2 = math::exp(-(b0 / sigma) - 2.0 * (b1 / sigma));
108 const double term2 = a1 * math::sin(w0 / sigma) - math::cos(w0 / sigma) * a0;
109
110 return multiplier1 * term1 + multiplier2 * term2;
111}
112
113/* Computes and packs the numerators in Equation (21) in Deriche's paper. */
114static double4 compute_numerator(float sigma)
115{
116 const double n0 = compute_numerator_0();
117 const double n1 = compute_numerator_1(sigma);
118 const double n2 = compute_numerator_2(sigma);
119 const double n3 = compute_numerator_3(sigma);
120
121 return double4(n0, n1, n2, n3);
122}
123
124/* Computes d11 in Equation (22) in Deriche's paper. */
125static double compute_denominator_1(float sigma)
126{
127 const double term1 = -2.0 * math::exp(-(b0 / sigma)) * math::cos(w0 / sigma);
128 const double term2 = 2.0 * math::exp(-(b1 / sigma)) * math::cos(w1 / sigma);
129
130 return term1 - term2;
131}
132
133/* Computes d22 in Equation (22) in Deriche's paper. */
134static double compute_denominator_2(float sigma)
135{
136 const double term1 = 4.0 * math::cos(w1 / sigma) * math::cos(w0 / sigma);
137 const double multiplier1 = math::exp(-(b0 / sigma) - (b1 / sigma));
138
139 const double term2 = math::exp(-2.0 * (b1 / sigma));
140 const double term3 = math::exp(-2.0 * (b0 / sigma));
141
142 return term1 * multiplier1 + term2 + term3;
143}
144
145/* Computes d33 in Equation (22) in Deriche's paper. */
146static double compute_denominator_3(float sigma)
147{
148 const double term1 = -2.0 * math::cos(w0 / sigma);
149 const double multiplier1 = math::exp(-(b0 / sigma) - 2.0 * (b1 / sigma));
150
151 const double term2 = 2.0 * math::cos(w1 / sigma);
152 const double multiplier2 = math::exp(-(b1 / sigma) - 2.0 * (b0 / sigma));
153
154 return term1 * multiplier1 - term2 * multiplier2;
155}
156
157/* Computes d44 in Equation (22) in Deriche's paper. */
158static double compute_denominator_4(float sigma)
159{
160 return math::exp(-2.0 * (b0 / sigma) - 2.0 * (b1 / sigma));
161}
162
163/* Computes and packs the denominators in Equation (22) in Deriche's paper. */
164static double4 compute_denominator(float sigma)
165{
166 const double d1 = compute_denominator_1(sigma);
167 const double d2 = compute_denominator_2(sigma);
168 const double d3 = compute_denominator_3(sigma);
169 const double d4 = compute_denominator_4(sigma);
170
171 return double4(d1, d2, d3, d4);
172}
173
174/* Computes the normalization scale that the feedforward coefficients should be divided by to
175 * match the unit integral of the Gaussian. The scaling factor proposed by Deriche's paper in
176 * Equation (50) is wrong due to missing terms. A correct scaling factor is presented in
177 * Farneback's paper in Equation (25), which is implemented in this method. */
178static float compute_normalization_scale(const double4 &causal_feedforward_coefficients,
179 const double4 &feedback_coefficients)
180{
181 const double causal_feedforwad_sum = math::reduce_add(causal_feedforward_coefficients);
182 const double feedback_sum = 1.0 + math::reduce_add(feedback_coefficients);
183 return 2.0 * (causal_feedforwad_sum / feedback_sum) - causal_feedforward_coefficients[0];
184}
185
186/* Computes the non causal feedforward coefficients from the feedback and causal feedforward
187 * coefficients based on Equation (31) in Deriche's paper. Notice that the equation is linear, so
188 * the coefficients can be computed after the normalization of the causal feedforward
189 * coefficients. */
191 const double4 &causal_feedforward_coefficients, const double4 &feedback_coefficients)
192{
193 const double n1 = causal_feedforward_coefficients[1] -
194 feedback_coefficients[0] * causal_feedforward_coefficients[0];
195 const double n2 = causal_feedforward_coefficients[2] -
196 feedback_coefficients[1] * causal_feedforward_coefficients[0];
197 const double n3 = causal_feedforward_coefficients[3] -
198 feedback_coefficients[2] * causal_feedforward_coefficients[0];
199 const double n4 = -feedback_coefficients[3] * causal_feedforward_coefficients[0];
200
201 return double4(n1, n2, n3, n4);
202}
203
204/* The IIR filter difference equation relies on previous outputs to compute new outputs, those
205 * previous outputs are not really defined at the start of the filter. To do Neumann boundary
206 * condition, we initialize the previous output with a special value that is a function of the
207 * boundary value. This special value is computed by multiply the boundary value with a coefficient
208 * to simulate an infinite stream of the boundary value.
209 *
210 * The function for the coefficient can be derived by substituting the boundary value for previous
211 * inputs, equating all current and previous outputs to the same value, and finally rearranging to
212 * compute that same output value.
213 *
214 * Start by the difference equation where b_i are the feedforward coefficients and a_i are the
215 * feedback coefficients:
216 *
217 * y[n] = \sum_{i = 0}^3 b_i x[n - i] - \sum_{i = 0}^3 a_i y[n - i]
218 *
219 * Assume all outputs are y and all inputs are x, which is the boundary value:
220 *
221 * y = \sum_{i = 0}^3 b_i x - \sum_{i = 0}^3 a_i y
222 *
223 * Now rearrange to compute y:
224 *
225 * y = x \sum_{i = 0}^3 b_i - y \sum_{i = 0}^3 a_i
226 * y + y \sum_{i = 0}^3 a_i = x \sum_{i = 0}^3 b_i
227 * y (1 + \sum_{i = 0}^3 a_i) = x \sum_{i = 0}^3 b_i
228 * y = x \cdot \frac{\sum_{i = 0}^3 b_i}{1 + \sum_{i = 0}^3 a_i}
229 *
230 * So our coefficient is the value that is multiplied by the boundary value x. Had x been zero,
231 * that is, we are doing Dirichlet boundary condition, the equations still hold. */
232static double compute_boundary_coefficient(const double4 &feedforward_coefficients,
233 const double4 &feedback_coefficients)
234{
235 return math::reduce_add(feedforward_coefficients) /
236 (1.0 + math::reduce_add(feedback_coefficients));
237}
238
239/* Computes the feedback, causal feedforward, and non causal feedforward coefficients given a
240 * target Gaussian sigma value as used in Equations (28) and (29) in Deriche's paper. */
242{
243 /* The numerator coefficients are the causal feedforward coefficients and the denominator
244 * coefficients are the feedback coefficients as can be seen in Equation (28). */
245 causal_feedforward_coefficients_ = compute_numerator(sigma);
246 feedback_coefficients_ = compute_denominator(sigma);
247
248 /* Normalize the feedforward coefficients as discussed in Section "5.4 Normalization" in
249 * Deriche's paper. Feedback coefficients do not need normalization. */
250 causal_feedforward_coefficients_ /= compute_normalization_scale(causal_feedforward_coefficients_,
251 feedback_coefficients_);
252
253 /* Compute the non causal feedforward coefficients from the feedback and normalized causal
254 * feedforward coefficients based on Equation (31) from Deriche's paper. Since the causal
255 * coefficients are already normalized, this doesn't need normalization. */
256 non_causal_feedforward_coefficients_ = compute_non_causal_feedforward_coefficients(
257 causal_feedforward_coefficients_, feedback_coefficients_);
258
259 /* Compute the boundary coefficient for both the causal and non causal filters. */
260 causal_boundary_coefficient_ = compute_boundary_coefficient(causal_feedforward_coefficients_,
261 feedback_coefficients_);
262 non_causal_boundary_coefficient_ = compute_boundary_coefficient(
263 non_causal_feedforward_coefficients_, feedback_coefficients_);
264}
265
267{
268 return feedback_coefficients_;
269}
270
272{
273 return causal_feedforward_coefficients_;
274}
275
277{
278 return non_causal_feedforward_coefficients_;
279}
280
282{
283 return causal_boundary_coefficient_;
284}
285
287{
288 return non_causal_boundary_coefficient_;
289}
290
291/* --------------------------------------------------------------------
292 * Deriche Gaussian Coefficients Container.
293 */
294
296{
297 /* First, delete all resources that are no longer needed. */
298 map_.remove_if([](auto item) { return !item.value->needed; });
299
300 /* Second, reset the needed status of the remaining resources to false to ready them to track
301 * their needed status for the next evaluation. */
302 for (auto &value : map_.values()) {
303 value->needed = false;
304 }
305}
306
308 float sigma)
309{
310 const DericheGaussianCoefficientsKey key(sigma);
311
312 auto &deriche_gaussian_coefficients = *map_.lookup_or_add_cb(
313 key, [&]() { return std::make_unique<DericheGaussianCoefficients>(context, sigma); });
314
315 deriche_gaussian_coefficients.needed = true;
316 return deriche_gaussian_coefficients;
317}
318
319} // namespace blender::realtime_compositor
DericheGaussianCoefficients & get(Context &context, float sigma)
local_group_size(16, 16) .push_constant(Type b
T cos(const AngleRadianBase< T > &a)
T exp(const T &x)
T sin(const AngleRadianBase< T > &a)
T reduce_add(const VecBase< T, Size > &a)
static float compute_normalization_scale(const double4 &causal_feedforward_coefficients, const double4 &feedback_coefficients)
static double compute_denominator_2(float sigma)
static double compute_numerator_2(float sigma)
static double4 compute_denominator(float sigma)
bool operator==(const BokehKernelKey &a, const BokehKernelKey &b)
static double compute_denominator_1(float sigma)
static double4 compute_numerator(float sigma)
static double compute_numerator_3(float sigma)
static double compute_boundary_coefficient(const double4 &feedforward_coefficients, const double4 &feedback_coefficients)
static double compute_numerator_1(float sigma)
static double4 compute_non_causal_feedforward_coefficients(const double4 &causal_feedforward_coefficients, const double4 &feedback_coefficients)
static double compute_denominator_3(float sigma)
static double compute_denominator_4(float sigma)
uint64_t get_default_hash(const T &v)
Definition BLI_hash.hh:219
VecBase< double, 4 > double4
unsigned __int64 uint64_t
Definition stdint.h:90