Skip to content

Commit 3783106

Browse files
authored
Merge pull request #84 from imglib/smoothen-gauss3
Fix Derivative of Gauss3 Kernel
2 parents 14a708f + 3f70a84 commit 3783106

File tree

6 files changed

+350
-171
lines changed

6 files changed

+350
-171
lines changed

src/main/java/net/imglib2/algorithm/convolution/fast_gauss/FastGaussCalculator.java

Lines changed: 58 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -15,65 +15,49 @@
1515
*/
1616
public class FastGaussCalculator
1717
{
18-
private final Parameters fc;
19-
// we will utilize ring buffers whose current positions will be driven
20-
// by the currently processed index inside the input array -- we will use
21-
// bit masking to achieve fast the effect of the operation modulo, furthermore
22-
// it does not suffer from sign effect: -5 % 8 = -5 in Java; I need it be +3
23-
// to make it work nicely for the ring buffer (which cannot have negative indices)
24-
//
25-
// remember current plus last two results for every yk-term (there's M of them),
26-
// so we need either 3*3 or 3*4=12 values => 4 bits = capacity for 16 values,
27-
//
28-
// actually, we will use 4 ring sub-buffers (each of length 4, 2 bits) for the
29-
// individual yks...: y1, y3, y5, y7 (the order in which they are touched during
30-
// the computation of the filter)
31-
//
32-
private final double[] yk0 = new double[ 4 ];
33-
34-
private final double[] yk1 = new double[ 4 ];
35-
36-
private final double[] yk2 = new double[ 4 ];
37-
38-
private final double[] yk3 = new double[ 4 ];
39-
40-
private int x;
18+
private double[] y_n = new double[ 4 ]; // stores y values for k=1,3,5,7 of the current step
19+
20+
private double[] y_n_minus_1 = new double[ 4 ]; // stores the y_n value of the previous step
21+
22+
private double[] y_n_minus_2 = new double[ 4 ]; // stores the y_n value of the step before the previous step
23+
24+
private final double[] nk_2;
25+
26+
private final double[] dk_1;
27+
28+
private final int M;
4129

4230
public FastGaussCalculator( final Parameters fc )
4331
{
44-
this.fc = fc;
32+
nk_2 = fc.nk_2;
33+
dk_1 = fc.dk_1;
34+
M = fc.M;
4535
}
4636

4737
public void initialize( final double boundaryValue )
4838
{
4939
//calculate yk that one would get on constant signal of 1.0
50-
//(Vlado's invention by solving eq. (35) assuming yk_n = yk_n-1 = yk_n-2)
51-
final double y1_mN = 2.0 * fc.nk_2[ 0 ] / ( fc.dk_1[ 0 ] + 2.0 );
52-
final double y3_mN = 2.0 * fc.nk_2[ 1 ] / ( fc.dk_1[ 1 ] + 2.0 );
53-
final double y5_mN = 2.0 * fc.nk_2[ 2 ] / ( fc.dk_1[ 2 ] + 2.0 );
54-
final double y7_mN = fc.M == 4 ? 2.0 * fc.nk_2[ 3 ] / ( fc.dk_1[ 3 ] + 2.0 ) : 0.0;
55-
56-
//calculate yk that one would get on constant signal of array[0],
57-
//and use this value for yk[x-2] and yk[x-1] when x=-Nm1
58-
x = -1;
59-
yk0[ x & 3 ] = yk0[ ( x - 1 ) & 3 ] = boundaryValue * y1_mN;
60-
yk1[ x & 3 ] = yk1[ ( x - 1 ) & 3 ] = boundaryValue * y3_mN;
61-
yk2[ x & 3 ] = yk2[ ( x - 1 ) & 3 ] = boundaryValue * y5_mN;
62-
yk3[ x & 3 ] = yk3[ ( x - 1 ) & 3 ] = boundaryValue * y7_mN;
40+
//(Vlado's invention by solving eq. (35) assuming y_n = y_n_minus_1 = y_n_minus_2)
41+
42+
for ( int i = 0; i < M; i++ )
43+
y_n[ i ] = y_n_minus_1[ i ] = boundaryValue * 2.0 * nk_2[ i ] / ( dk_1[ i ] + 2.0 );
6344
}
6445

6546
public void update( final double tmp )
6647
{
67-
++x;
68-
yk0[ x & 3 ] = fc.nk_2[ 0 ] * tmp - fc.dk_1[ 0 ] * yk0[ ( x - 1 ) & 3 ] - yk0[ ( x - 2 ) & 3 ];
69-
yk1[ x & 3 ] = fc.nk_2[ 1 ] * tmp - fc.dk_1[ 1 ] * yk1[ ( x - 1 ) & 3 ] - yk1[ ( x - 2 ) & 3 ];
70-
yk2[ x & 3 ] = fc.nk_2[ 2 ] * tmp - fc.dk_1[ 2 ] * yk2[ ( x - 1 ) & 3 ] - yk2[ ( x - 2 ) & 3 ];
71-
yk3[ x & 3 ] = fc.M == 4 ? fc.nk_2[ 3 ] * tmp - fc.dk_1[ 3 ] * yk3[ ( x - 1 ) & 3 ] - yk3[ ( x - 2 ) & 3 ] : 0;
48+
double[] t = y_n_minus_2;
49+
y_n_minus_2 = y_n_minus_1;
50+
y_n_minus_1 = y_n;
51+
y_n = t;
52+
y_n[ 0 ] = nk_2[ 0 ] * tmp - dk_1[ 0 ] * y_n_minus_1[ 0 ] - y_n_minus_2[ 0 ];
53+
y_n[ 1 ] = nk_2[ 1 ] * tmp - dk_1[ 1 ] * y_n_minus_1[ 1 ] - y_n_minus_2[ 1 ];
54+
y_n[ 2 ] = nk_2[ 2 ] * tmp - dk_1[ 2 ] * y_n_minus_1[ 2 ] - y_n_minus_2[ 2 ];
55+
y_n[ 3 ] = nk_2[ 3 ] * tmp - dk_1[ 3 ] * y_n_minus_1[ 3 ] - y_n_minus_2[ 3 ];
7256
}
7357

7458
public double getValue()
7559
{
76-
return yk0[ x & 3 ] + yk1[ x & 3 ] + yk2[ x & 3 ] + yk3[ x & 3 ];
60+
return y_n[ 0 ] + y_n[ 1 ] + y_n[ 2 ] + y_n[ 3 ];
7761
}
7862

7963
/**
@@ -100,7 +84,7 @@ public static class Parameters
10084
/**
10185
* the width of the filter
10286
*/
103-
public int N = 0;
87+
public final int N;
10488

10589
/**
10690
* the filtering coefficients
@@ -116,36 +100,31 @@ public static class Parameters
116100

117101
public static Parameters fast( final double sigma )
118102
{
119-
return new Parameters( 3, sigma );
103+
return new Parameters( 3, sigma, round( 3.2795 * sigma + 0.25460 ) );
120104
}
121105

122106
public static Parameters exact( final double sigma )
123107
{
124-
return new Parameters( 4, sigma );
108+
return new Parameters( 4, sigma, round( 3.7210 * sigma + 0.20157 ) );
125109
}
126110

127111
/**
128112
* Construct with the same accuracy as the Gauss1D for which you want to
129113
* use it.
130114
*/
131-
private Parameters( final int _M, final double sigma )
115+
private Parameters( final int _M, final double sigma, final int N )
132116
{
133117
if ( sigma <= 0 )
134118
throw new IllegalArgumentException( "Sigma must be positive." );
135119
M = ( _M == 3 || _M == 4 ) ? _M : 3;
136120
nk_2 = new double[ 4 ];
137121
dk_1 = new double[ 4 ];
138122

139-
// eq. (57), the filter width
140-
final double N1 = ( int ) ( ( M == 3 ? 3.2795 * sigma + 0.25460 // M==3
141-
: 3.7210 * sigma + 0.20157 // M==4
142-
) + 0.5 );
143-
144123
// Table I, 1st/top objective
145-
final double omega_1 = 1.0 * Math.PI / ( 2.0 * N1 );
146-
final double omega_3 = 3.0 * Math.PI / ( 2.0 * N1 );
147-
final double omega_5 = 5.0 * Math.PI / ( 2.0 * N1 );
148-
final double omega_7 = 7.0 * Math.PI / ( 2.0 * N1 );
124+
final double omega_1 = 1.0 * Math.PI / ( 2.0 * N );
125+
final double omega_3 = 3.0 * Math.PI / ( 2.0 * N );
126+
final double omega_5 = 5.0 * Math.PI / ( 2.0 * N );
127+
final double omega_7 = 7.0 * Math.PI / ( 2.0 * N );
149128

150129
// eq. (37) i=1,3,5,7
151130
final double p_1 = 1.0 / Math.tan( 0.5 * omega_1 );
@@ -161,10 +140,10 @@ private Parameters( final int _M, final double sigma )
161140

162141
// approximate rho_i:
163142
// eq. (50) i=1,3,5,7
164-
final double rho_1 = Math.exp( -0.5 * sigma * sigma * omega_1 * omega_1 ) / N1;
165-
final double rho_3 = Math.exp( -0.5 * sigma * sigma * omega_3 * omega_3 ) / N1;
166-
final double rho_5 = Math.exp( -0.5 * sigma * sigma * omega_5 * omega_5 ) / N1;
167-
final double rho_7 = Math.exp( -0.5 * sigma * sigma * omega_7 * omega_7 ) / N1;
143+
final double rho_1 = Math.exp( -0.5 * sigma * sigma * omega_1 * omega_1 ) / N;
144+
final double rho_3 = Math.exp( -0.5 * sigma * sigma * omega_3 * omega_3 ) / N;
145+
final double rho_5 = Math.exp( -0.5 * sigma * sigma * omega_5 * omega_5 ) / N;
146+
final double rho_7 = Math.exp( -0.5 * sigma * sigma * omega_7 * omega_7 ) / N;
168147
/*
169148
//accurate rho_i:
170149
// eq. (50) i=1,3,5,7
@@ -202,6 +181,8 @@ private Parameters( final int _M, final double sigma )
202181

203182
// get beta_k
204183
double beta_1, beta_3;
184+
double gamma_2 = N * N - sigma * sigma;
185+
double gamma_3 = C_15 * rho_1 + C_35 * rho_3 + rho_5;
205186
if ( M == 3 )
206187
{
207188
//build 6 minor 2x2 matrices to invA by excluding i-th row and j-th column from invA,
@@ -222,10 +203,8 @@ private Parameters( final int _M, final double sigma )
222203
final double det_invA = p_1 * D11 - r_1 * D21 + C_15 * D31;
223204

224205
// eq. (53), only first two rows of matrix A_N
225-
beta_1 = ( +D11 - ( N1 * N1 - sigma * sigma ) * D21
226-
+ ( C_15 * rho_1 + C_35 * rho_3 + rho_5 ) * D31 ) / det_invA;
227-
beta_3 = ( -D12 + ( N1 * N1 - sigma * sigma ) * D22
228-
- ( C_15 * rho_1 + C_35 * rho_3 + rho_5 ) * D32 ) / det_invA;
206+
beta_1 = ( +D11 - gamma_2 * D21 + gamma_3 * D31 ) / det_invA;
207+
beta_3 = ( -D12 + gamma_2 * D22 - gamma_3 * D32 ) / det_invA;
229208
}
230209
else
231210
{ // M == 4
@@ -239,7 +218,7 @@ private Parameters( final int _M, final double sigma )
239218

240219
final double D11 = r_3 - r_5 * C_35 - r_7 * C_37;
241220
final double D21 = p_3 - p_5 * C_35 - p_7 * C_37;
242-
final double D31 = p_3 * r_5 + p_5 * r_7 + C_37 - p_5 * r_3 - p_7 * r_5 * C_37;
221+
final double D31 = p_3 * r_5 + p_5 * r_7 * C_37 - p_5 * r_3 - p_7 * r_5 * C_37;
243222
final double D41 = p_5 * r_7 * C_35 + p_7 * r_3 - p_3 * r_7 - p_7 * r_5 * C_35;
244223

245224
final double D12 = r_1 - r_5 * C_15 - r_7 * C_17;
@@ -250,10 +229,9 @@ private Parameters( final int _M, final double sigma )
250229
final double det_invA = p_1 * D11 - r_1 * D21 + C_15 * D31 - C_17 * D41;
251230

252231
// eq. (53), only first two rows of matrix A_N
253-
beta_1 = ( +D11 - ( N1 * N1 - sigma * sigma ) * D21 + ( C_15 * rho_1 + C_35 * rho_3 + rho_5 ) * D31
254-
- ( C_17 * rho_1 + C_37 * rho_3 + rho_7 ) * D41 ) / det_invA;
255-
beta_3 = ( -D12 + ( N1 * N1 - sigma * sigma ) * D22 - ( C_15 * rho_1 + C_35 * rho_3 + rho_5 ) * D32
256-
+ ( C_17 * rho_1 + C_37 * rho_3 + rho_7 ) * D42 ) / det_invA;
232+
double gamma4 = C_17 * rho_1 + C_37 * rho_3 + rho_7;
233+
beta_1 = ( +D11 - gamma_2 * D21 + gamma_3 * D31 - gamma4 * D41 ) / det_invA;
234+
beta_3 = ( -D12 + gamma_2 * D22 - gamma_3 * D32 + gamma4 * D42 ) / det_invA;
257235
}
258236

259237
// eq. (49), since I didn't want to continue building A_N to be used in eq. (53),
@@ -263,25 +241,31 @@ private Parameters( final int _M, final double sigma )
263241
final double beta_7 = rho_7 + C_17 * ( rho_1 - beta_1 ) + C_37 * ( rho_3 - beta_3 );
264242

265243
// fill the output container FilteringCoeffs
266-
N = ( int ) N1;
244+
this.N = N;
267245

268-
nk_2[ 0 ] = -beta_1 * Math.cos( omega_1 * ( N1 + 1.0 ) );
269-
nk_2[ 1 ] = -beta_3 * Math.cos( omega_3 * ( N1 + 1.0 ) );
270-
nk_2[ 2 ] = -beta_5 * Math.cos( omega_5 * ( N1 + 1.0 ) );
246+
nk_2[ 0 ] = -beta_1 * Math.cos( omega_1 * ( N + 1 ) );
247+
nk_2[ 1 ] = -beta_3 * Math.cos( omega_3 * ( N + 1 ) );
248+
nk_2[ 2 ] = -beta_5 * Math.cos( omega_5 * ( N + 1 ) );
271249

272250
dk_1[ 0 ] = -2.0 * Math.cos( omega_1 );
273251
dk_1[ 1 ] = -2.0 * Math.cos( omega_3 );
274252
dk_1[ 2 ] = -2.0 * Math.cos( omega_5 );
275253

276254
if ( M == 4 )
277255
{
278-
nk_2[ 3 ] = -beta_7 * Math.cos( omega_7 * ( N1 + 1.0 ) );
256+
nk_2[ 3 ] = -beta_7 * Math.cos( omega_7 * ( N + 1 ) );
279257
dk_1[ 3 ] = -2.0 * Math.cos( omega_7 );
280258
// NB: array length was guarded at the beginning of this function
281259
}
282260

283261
// declare to what sigma the coefficients belong to
284262
Sigma = sigma;
285263
}
264+
265+
}
266+
267+
private static int round( double value )
268+
{
269+
return ( int ) ( value + 0.5 );
286270
}
287271
}

src/main/java/net/imglib2/algorithm/convolution/fast_gauss/FastGaussConvolverRealType.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ public void run()
9999
for ( int i = -offset; i < 0; ++i )
100100
{
101101
fg.update( boundaryValue + tmpE[ i + offset ] );
102-
in.fwd( d );
103102
}
104103

105104
for ( int i = 0; i < lineLength; ++i )

src/main/java/net/imglib2/algorithm/gauss3/Gauss3.java

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -226,26 +226,86 @@ public static int[] halfkernelsizes( final double[] sigma )
226226
return size;
227227
}
228228

229+
/**
230+
* Returns a gaussian half kernel with the given sigma and size.
231+
* <p>
232+
* The edges are smoothed by a second degree polynomial.
233+
* This improves the first derivative and the fourier spectrum
234+
* of the gaussian kernel.
235+
*/
229236
public static double[] halfkernel( final double sigma, final int size, final boolean normalize )
230237
{
231-
final double two_sq_sigma = 2 * sigma * sigma;
238+
final double two_sq_sigma = 2 * square( sigma );
232239
final double[] kernel = new double[ size ];
233240

234241
kernel[ 0 ] = 1;
235242
for ( int x = 1; x < size; ++x )
236-
kernel[ x ] = Math.exp( -( x * x ) / two_sq_sigma );
243+
kernel[ x ] = Math.exp( -square( x ) / two_sq_sigma );
244+
245+
smoothEdge( kernel );
237246

238247
if ( normalize )
239-
{
240-
double sum = 0.5;
241-
for ( int x = 1; x < size; ++x )
242-
sum += kernel[ x ];
243-
sum *= 2;
248+
normalizeHalfkernel( kernel );
244249

245-
for ( int x = 0; x < size; ++x )
246-
kernel[ x ] /= sum;
250+
return kernel;
251+
}
252+
253+
/**
254+
* This method smooths the truncated end of the gaussian kernel.
255+
* The code is taken from ImageJ1 "Gaussian Blur ...".
256+
* <p>
257+
* Detailed explanation:
258+
* <p>
259+
* The values kernel[x] for r < x < L are replaced by the values
260+
* of a polynomial p(x) = slope * (x - L) ^ 2.
261+
* Where L equals kernel.length. And the "slope" and "r" are chosen
262+
* such that there is a smooth transition between the kernel and
263+
* the polynomial. Thus their value and first derivative
264+
* match for x = r: p(r) = kernel[r] and p'(r) = kernel'[r].
265+
*/
266+
private static void smoothEdge( double[] kernel )
267+
{
268+
int L = kernel.length;
269+
double slope = Double.MAX_VALUE;
270+
int r = L;
271+
while ( r > L / 2 )
272+
{
273+
r--;
274+
double a = kernel[ r ] / square( L - r );
275+
if ( a < slope )
276+
slope = a;
277+
else
278+
{
279+
r++;
280+
break;
281+
}
247282
}
283+
for ( int x = r + 1; x < L; x++ )
284+
kernel[ x ] = slope * square( L - x );
285+
}
248286

249-
return kernel;
287+
/**
288+
* Normalizes a half kernel such that the values sum up to 1.
289+
*/
290+
private static void normalizeHalfkernel( double[] kernel )
291+
{
292+
double sum = 0.5 * kernel[ 0 ];
293+
for ( int x = 1; x < kernel.length; ++x )
294+
sum += kernel[ x ];
295+
sum *= 2;
296+
multiply( kernel, 1 / sum );
297+
}
298+
299+
// -- Helper methods --
300+
301+
private static double square( double x )
302+
{
303+
return x * x;
304+
}
305+
306+
private static void multiply( double[] values, double factor )
307+
{
308+
for ( int x = 0; x < values.length; ++x )
309+
values[ x ] *= factor;
250310
}
251311
}

0 commit comments

Comments
 (0)