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