1 /*
2 * $Id: convol.i,v 1.3 2010-03-05 04:13:56 dhmunro Exp $
3 * convolution of two vectors using fft
4 */
5 /* Copyright (c) 2005, The Regents of the University of California.
6 * All rights reserved.
7 * This file is part of yorick (http://yorick.sourceforge.net).
8 * Read the accompanying LICENSE file for details.
9 */
10
11 func convol(a,b,n0=,n1=)
12 /* DOCUMENT convol(a,b)
13
14 returns convolution of vector a with vector b, a vector
15 of length na+nb-1 where na=numberof(a), nb=numberof(b).
16
17 In detail, for i=[1 to na+nb-1]
18 result(i) = sum j=[max(1,1+i-nb) to min(na,i)] (a(j)*b(1+i-j))
19
20 The n0= and n1= keywords can be used to control the section of
21 the full array that is actually returned, 1<=n0<n1<=na+nb-1.
22
23 Use the convoln function for multi-dimensional convolution, and
24 see gaussm and boxcar for smoothing array data.
25
26 SEE ALSO: fft_good, fft, convoln, gaussm, boxcar
27 */
28 {
29 na= numberof(a);
30 nb= numberof(b);
31 nc= na+nb-1;
32 nt= fft_good(nc);
33 at= bt= array(0i, nt);
34 at(1:na)= a;
35 bt(1:nb)= b;
36 work= fft_setup(dimsof(at));
37 fft_inplace, at, 1, setup=work;
38 fft_inplace, bt, 1, setup=work;
39 c= at*bt;
40 fft_inplace, c, -1, setup=work;
41 if (is_void(n0)) n0= 1;
42 if (is_void(n1)) n1= nc;
43 return double(c(n0:n1))/nt;
44 }
45
fft_good(n)46 func fft_good(n)
47 /* DOCUMENT fft_good(n)
48
49 returns the smallest number of the form 2^x*3^y*5^z greater
50 than or equal to n. An fft of this length will be much faster
51 than a number with larger prime factors; the speed difference
52 can be an order of magnitude or more.
53
54 For n>100, the worst cases result in a little over a 11% increase
55 in n; for n>1000, the worst are a bit over 6%; still larger n are
56 better yet. The median increase for n<=10000 is about 1.5%.
57
58 SEE ALSO: fft, fft_setup, convol
59 */
60 {
61 if (n<7) return max(n,1);
62 logn= log(n);
63 n5= 5.^indgen(0:long(logn/log(5.) + 1.e-6)); /* exact integers */
64 n3= 3.^indgen(0:long(logn/log(3.) + 1.e-6)); /* exact integers */
65 n35= n3*n5(-,); /* fewer than 300 numbers for n<5e9 */
66 n35= n35(where(n35<=n));
67 n235= 2^long((logn-log(n35))/log(2.) + 0.999999) * long(n35);
68 return min(n235);
69 }
70
71 /*
72 func convol_check(a,b)
73 {
74 na= numberof(a); nb= numberof(b); c= array(0.,na+nb-1);
75 for (i=1 ; i<na+nb ; i++)
76 for (j=max(1,1+i-nb) ; j<=min(na,i) ; j++) c(i)+= a(j)*b(1+i-j);
77 return c;
78 }
79 */
80
81 func convoln(a, b, n0=, n1=, zc=)
82 /* DOCUMENT convoln(a, b)
83
84 returns convolution of array A with array B. This is naturally
85 of length na+nb-1 where na=length of A, nb=length of B. However,
86 convoln returns a result the same size as A, which is extracted
87 from the center of this full array. Typically, B will be a much
88 smaller array than A, which you are using to smooth A. If the
89 dimensions of B are odd, then the elements of the returned array
90 will be centered as you would expect.
91
92 In detail, for i=[1 to na+nb-1]
93 result(i) = sum j=[max(1,1+i-nb) to min(na,i)] (A(j)*B(1+i-j))
94 with this operation repeated along each dimension of A and B.
95
96 The n0= and n1= keywords can be used to control the section of
97 the full array that is actually returned, 1<=n0<n1<=na+nb-1.
98 Both n0 and n1 must be vectors with length equal to the number
99 of dimensions of A and B. By default,
100 n0 = (1+nb)/2
101 n1 = na + n0 - 1
102 which gives the expected centering when nb is odd. If nb is
103 even, you might want to consider returning a result of length
104 na-1 which is "zone centered" relative to A. You can do this
105 by setting the zc=1 keyword, which gives n0 = 1 + nb/2 and
106 n1 = n1 = na + n0 - 2 when nb is even.
107
108 Note that if some dimension of B is length 1, there will be
109 no smoothing along that dimension of A. The pseudo-index -
110 is useful for generating such dimensions. For example, if x
111 is a 2D array and y is a 1D smoothing function which you want
112 to apply to the second dimension of x, use convoln(x, y(-,)).
113
114 SEE ALSO: convol, gaussm, boxcar
115 */
116 {
117 na = dimsof(a);
118 nb = dimsof(b);
119 ndims = nb(1);
120 if (na(1) != ndims)
121 error, "a and b must have same numberof of dimensions";
122 na = na(2:);
123 nb = nb(2:);
124 nc = na+nb-1;
125 nt = array(ndims, 1+ndims);
126 for (i=1 ; i<=ndims ; ++i) nt(1+i) = fft_good(nc(i));
127 at = bt = array(complex, nt);
128 nt = nt(2:);
129
130 ia = indgen(0:na(ndims)-1);
131 ib = indgen(0:nb(ndims)-1);
132 for (i=ndims-1 ; i>=1 ; --i) {
133 ia = nt(i)*ia(-,..) + indgen(0:na(i)-1);
134 ib = nt(i)*ib(-,..) + indgen(0:nb(i)-1);
135 }
136 ia += 1;
137 ib += 1;
138 at(ia) = a;
139 bt(ib) = b;
140 ia = ib = [];
141
142 work = fft_setup(dimsof(at));
143 fft_inplace, at, 1, setup=work;
144 fft_inplace, bt, 1, setup=work;
145 at *= bt;
146 bt = [];
147 fft_inplace, at, -1, setup=work;
148
149 if (is_void(n0)) n0 = (1+nb)/2;
150 if (is_void(n1)) n1 = na + n0 - 1;
151 if (numberof(n0)!=ndims || numberof(n1)!=ndims)
152 error, "n0=, n1= keywords must have length = number of dimensions";
153 if (zc) n0 += (1+nb)%2;
154 ia = indgen(n0(ndims)-1:n1(ndims)-1);
155 for (i=ndims-1 ; i>=1 ; --i)
156 ia = nt(i)*ia(-,..) + indgen(n0(i)-1:n1(i)-1);
157 ia += 1;
158 nt = numberof(at);
159 return double(at(ia)) / nt;
160 }
161
162 func gaussm(a, n, fwhm=)
163 /* DOCUMENT gaussm(a, n)
164
165 returns array A smoothed by a Gaussian with a sigma of N pixels.
166 If A is multi-dimensional, N may be a vector with as many
167 components as A has dimensions, specifying how many pixels
168 to smooth in that dimension. N may be shorter than the number
169 of dimensions of A; unspecified dimensions are unsmoothed
170 (as if N were 0.0). If A is multi-dimensional and N is scalar,
171 that N is applied to all dimensions.
172
173 With the fwhm=1 keyword, N is the full width at half maximum
174 of the Guassian. The fwhm= keyword may also be a vector of
175 the same length as N, 1 where N is to be interpreted as a FWHM
176 and 0 where N is to be interpreted as a sigma.
177
178 SEE ALSO: convoln, boxcar
179 */
180 {
181 rank = dimsof(a)(1);
182 n = abs(double(n));
183 if (numberof(fwhm) <= 1) {
184 if (!is_void(fwhm) && fwhm(1)) n *= sqrt(0.125/log(2.));
185 } else if (numberof(fhwm) == numberof(n)) {
186 list = where(fwhm);
187 if (numberof(list)) n(list) *= sqrt(0.125/log(2.));
188 } else {
189 error, "fwhm= keyword must be scalar or same length as sigma";
190 }
191 if (!dimsof(n)(1)) n = array(n, rank);
192 else if (numberof(n) < rank) grow, n, array(0., rank-numberof(n));
193 else if (numberof(n) > rank) error, "length of sigma exceeds rank of a";
194
195 rsig = 1.0/(n + (n<0.1)); /* rsig multiplied by 0 if n<0.1 */
196 n = max(long(4.*n), n>=0.1);
197 g = (rsig(rank)*indgen(-n(rank):n(rank)))^2;
198 for (i=rank-1 ; i>=1 ; --i) g = g(-,..) + (rsig(i)*indgen(-n(i):n(i)))^2;
199 g = exp(-0.5*g);
200 g *= 1./sum(g);
201
202 return convoln(a, g);
203 }
204
boxcar(a,n)205 func boxcar(a, n)
206 /* DOCUMENT boxcar(a, n)
207
208 returns array A smoothed by a boxcar of 2*N+1 pixels.
209 If A is multi-dimensional, N may be a vector with as many
210 components as A has dimensions, specifying how many pixels
211 to smooth in that dimension. N may be shorter than the number
212 of dimensions of A; unspecified dimensions are unsmoothed
213 (as if N were 0). If A is multi-dimensional and N is scalar,
214 that N is applied to all dimensions.
215
216 Each pixel of the result is averaged with the N pixels to
217 its left and N pixels to its right (so N=0 means no averaging).
218 For pixels less than N from the left edge, the averaging includes
219 fewer pixels on the left, but still N on the right, and similarly
220 for pixels less than N from the right edge. Hence, the effective
221 smoothing is reduced and the centering is skewed, near the edges
222 of the array.
223
224 SEE ALSO: convoln, gaussm
225 */
226 {
227 na = dimsof(a);
228 rank = na(1);
229 if (!rank) return a;
230 na = na(2:);
231 if (structof(n+0)!=long || anyof(n<0)) error, "n must be positive integer";
232 if (numberof(n) > rank) error, "length of n exceeds rank of a";
233 else if (!dimsof(n)(1)) n = array(n, rank);
234 else if (numberof(n) < rank) rank = numberof(n);
235 if (rank > 6) error, "only first six dimensions can be smoothed";
236 n = min(na(1:rank)/2, n);
237
238 local n0, n1;
239 i = 0;
240 if (rank > i++) {
241 x = a(cum,..);
242 a = _boxcar_(i)*(x(n1,..) - x(n0,..));
243 if (rank > i++) {
244 x = a(,cum,..);
245 a = _boxcar_(i)(-,)*(x(,n1,..) - x(,n0,..));
246 if (rank > i++) {
247 x = a(,,cum,..);
248 a = _boxcar_(i)(-,-,)*(x(,,n1,..) - x(,,n0,..));
249 if (rank > i++) {
250 x = a(,,,cum,..);
251 a = _boxcar_(i)(-,-,-,)*(x(,,,n1,..) - x(,,,n0,..));
252 if (rank > i++) {
253 x = a(,,,,cum,..);
254 a = _boxcar_(i)(-,-,-,-,)*(x(,,,,n1,..) - x(,,,,n0,..));
255 if (rank > i++) {
256 x = a(,,,,,cum,..);
257 a = _boxcar_(i)(-,-,-,-,-,)*(x(,,,,,n1,..) - x(,,,,,n0,..));
258 }
259 }
260 }
261 }
262 }
263 }
264 return a;
265 }
266
267 /* worker for boxcar */
_boxcar_(i)268 func _boxcar_(i)
269 {
270 extern n0, n1;
271 ni = n(i);
272 m = na(i);
273 list = indgen(m);
274 n0 = max(list-ni, 1);
275 n1 = min(list+ni, m) + 1;
276 return 1./(n1-n0);
277 }
278