File Coverage

blib/lib/PDL/DSP/Fir.pm
Criterion Covered Total %
statement 62 90 68.8
branch 9 24 37.5
condition 0 6 0.0
subroutine 13 13 100.0
pod 4 4 100.0
total 88 137 64.2


line stmt bran cond sub pod time code
1             package PDL::DSP::Fir;
2              
3 3     3   222089 use 5.008;
  3         11  
4 3     3   17 use strict;
  3         6  
  3         71  
5 3     3   16 use warnings;
  3         5  
  3         148  
6              
7             our $VERSION = '0.005';
8              
9 3     3   17 use base 'Exporter';
  3         6  
  3         360  
10              
11 3     3   731 use PDL::LiteF;
  3         835  
  3         27  
12 3     3   190475 use PDL::NiceSlice;
  3         6  
  3         24  
13 3     3   12475 use PDL::Options;
  3         6  
  3         254  
14 3     3   18 use constant PI => 4 * atan2(1, 1);
  3         5  
  3         218  
15             #use PDL::Constants qw(PI);
16 3     3   3632 use PDL::DSP::Windows;
  3         117985  
  3         3219  
17              
18             our @ISA = qw(Exporter);
19             our @EXPORT_OK = qw( firwin ir_sinc ir_hisinc spectral_inverse spectral_reverse );
20              
21             $PDL::onlinedoc->scan(__FILE__) if $PDL::onlinedoc;
22              
23             =head1 NAME
24              
25             PDL::DSP::Fir - Finite impulse response filter kernels.
26              
27             =head1 SYNOPSIS
28              
29             use PDL;
30             use PDL::DSP::Fir qw( firwin );
31              
32             # return a 10 sample lowpass filter kernel
33             # with a cutoff at 90% of the Nyquist frequency.
34             $kernel = firwin( N => 10, fc => 0.9 );
35              
36             # Equivalent way of calling.
37             $kernel = firwin( { N => 10, fc => 0.9 } );
38              
39             =head1 DESCRIPTION
40              
41             This module provides routines to create one-dimensional finite impulse
42             response (FIR) filter kernels. This distribution inlcudes
43             a simple interface for filtering in L.
44              
45             The routine L returns a filter kernel constructed
46             from windowed sinc functions. Available filters are lowpass,
47             highpass, bandpass, and bandreject. The window functions are
48             in the module L.
49              
50             Below, the word B refers to the number of elements in the filter
51             kernel.
52              
53             No functions are exported be default.
54              
55             =head1 FUNCTIONS
56              
57             =head2 firwin
58              
59              
60             =head3 Usage
61              
62             =for usage
63              
64             $kern = firwin({OPTIONS});
65             $kern = firwin(OPTIONS);
66              
67             =for ref
68              
69             Returns a filter kernel (a finite impulse response function)
70             to be convolved with data.
71              
72             The kernel is built from windowed sinc functions. With the
73             option C 'window'> no sinc is used, rather the
74             kernel is just the window. The options may be passed as
75             a list of key-value pairs, or as an anonymous hash.
76              
77             =head3 OPTIONS
78              
79             =over
80              
81             =item N
82              
83             order of filter. This is the number of elements in
84             the returned kernel pdl.
85              
86             =item type
87              
88             Filter type. One of C, C, C,
89             C, C. Aliases for C are C and C.
90             Default is C. For C and C the number of samples
91             L must be odd.
92             If B is C, then the kernel returned is just the window function.
93              
94             =item fc
95              
96             Cutoff frequency for low- and highpass filters as a fraction of
97             the Nyquist frequency. Must be a number between
98             C<0> and C<1>. No default value.
99              
100             =item fclo, fchi
101              
102             Lower and upper cutoff frequencies for bandpass and bandstop filters.
103             No default values.
104              
105             =back
106              
107             All other options to L are passed to the function
108             L.
109              
110             =cut
111              
112             sub firwin {
113 3 50   3 1 510 barf 'PDL::DSP::Fir::firwin() called with no arguments.' unless @_;
114 3         7 my $iopts;
115 3 100       13 if (@_ == 1) {
116 2 50       11 barf "PDL::DSP::FIR::firwin: single argument not a hashref"
117             unless ref($_[0]) eq 'HASH';
118 2         6 $iopts = $_[0];
119             }
120             else {
121 1         5 my %hash = @_;
122 1         3 $iopts = \%hash;
123             }
124 3         47 my $opt = new PDL::Options(
125             {
126             N => undef,
127             type => 'lowpass',
128             window => undef,
129             fc => undef,
130             fclo => undef,
131             fchi => undef,
132             });
133 3         239 my $opts = $opt->options($iopts);
134 3         898 my $winopts = { N => $opts->{N} };
135 3 50       18 if (defined $opts->{window} ) {
136 0         0 my $w = $opts->{window};
137 0 0       0 if ( ref $w ) {
138 0         0 foreach my $wkey (keys %{$w}) {
  0         0  
139 0         0 $winopts->{$wkey} = $w->{$wkey};
140             }
141             }
142             else {
143 0         0 $winopts->{NAME} = $w;
144             }
145             }
146 3         9 my $type = $opts->{type};
147 3         16 my $win = PDL::DSP::Windows::window($winopts);
148 3         1927 my ($ir,$kernel);
149 3 50 0     14 if ($type eq 'lowpass') {
    0 0        
    0          
    0          
    0          
150 3         14 $ir = ir_sinc($opts->{fc},$opts->{N});
151 3         13 $kernel = $ir * $win;
152 3         61 $kernel /= $kernel->sum;
153             }
154             elsif ($type eq 'highpass') {
155 0         0 $ir = ir_sinc($opts->{fc},$opts->{N});
156 0         0 $kernel = $ir * $win;
157 0         0 $kernel /= $kernel->sum;
158 0         0 $kernel = spectral_inverse($kernel);
159             }
160             elsif ($type eq 'window') {
161 0         0 $kernel = $win/$win->sum;
162             }
163             elsif ($type eq 'bandpass') {
164 0         0 my $ir1 = ir_sinc($opts->{fclo},$opts->{N});
165 0         0 my $ir2 = ir_sinc($opts->{fchi},$opts->{N});
166 0         0 my $fir1 = $ir1 * $win;
167 0         0 $fir1 /= $fir1->sum;
168 0         0 my $fir2 = $ir2 * $win;
169 0         0 $fir2 /= $fir2->sum;
170 0         0 $fir2 = spectral_inverse($fir2);
171 0         0 $kernel = spectral_inverse($fir1 + $fir2);
172             }
173             elsif ($type eq 'bandstop' or $type eq 'bandreject' or $type eq 'notch') {
174 0         0 my $ir1 = ir_sinc($opts->{fclo},$opts->{N});
175 0         0 my $ir2 = ir_sinc($opts->{fchi},$opts->{N});
176 0         0 my $fir1 = $ir1 * $win;
177 0         0 $fir1 /= $fir1->sum;
178 0         0 my $fir2 = $ir2 * $win;
179 0         0 $fir2 /= $fir2->sum;
180 0         0 $fir2 = spectral_inverse($fir2);
181 0         0 $kernel = $fir1 + $fir2;
182             }
183             else {
184 0         0 barf "PDL::DSP::FIR::firwin: Unknown impulse response '$type'\n";
185             }
186 3         270 return $kernel;
187             }
188              
189             =pod
190              
191             The following three functions are called by the C, but
192             may also be useful by themselves, for instance, to construct more
193             complicated filters.
194              
195             =head2 ir_sinc
196              
197             =for usage
198              
199             $sinc = ir_sinc($f_cut, $N);
200              
201             =for ref
202              
203             Return an C<$N> point sinc function representing a lowpass filter
204             with cutoff frequency C<$f_cut>.
205              
206             C<$f_cut> must be between 0 and 1, with 1 being Nyquist freq.
207             The output pdl is the function C where
208             $x is pdl of C<$N> uniformly spaced values ranging from
209             C< - PI * ($N-1)/2> through C. For what it's
210             worth, a bit of efficiency is gained by computing the index
211             at which C<$x> is zero, rather than searching for it.
212              
213             =cut
214              
215             sub ir_sinc {
216 7     7 1 1994 my ($f_cut,$N) = @_;
217 7         21 my $lim = PI * ($N-1)/2;
218 7         28 my $x = zeroes($N)->xlinvals(-$lim,$lim);
219 7         1308 my $res = sin( $f_cut * $x ) / $x;
220 7 100       817 $res->slice(int($N/2)) .= $f_cut if $N % 2; # fix nan at x=0
221 7         220 $res;
222             }
223              
224             =head2 spectral_inverse
225              
226             =for usage
227              
228             $fir_inv = spectral_inverse($fir);
229              
230             =for ref
231              
232             Return output kernel whose spectrum is the inverse of the spectrum
233             of the input kernel.
234              
235             The number of samples in the input kernel must be odd.
236             Input C<$fir> and output C<$fir_inv> are real-space fir filter kernels.
237             The spectrum of the output kernel is the additive inverse
238             with respect to 1 of the spectrum of the input kernel.
239              
240             =cut
241              
242             sub spectral_inverse {
243 2     2 1 11 my ($fir) = @_;
244 2         10 my $L = $fir->nelem;
245 2 50       8 barf "spectral_inverse: L=$L is not odd\n" unless $L % 2;
246 2         5 my $mid = ($L-1)/2;
247 2         7 my $ifir = -$fir;
248 2         42 $ifir->slice($mid) += 1;
249 2         75 $ifir;
250             }
251              
252             =head2 spectral_reverse
253              
254             =for usage
255              
256             $fir_rev = spectral_reverse($fir);
257              
258             =for ref
259              
260             Return output kernel whose spectrum is the reverse of the spectrum
261             of the input kernel.
262              
263             That is, the spectrum is mirrored about the center frequency.
264            
265             =cut
266              
267             sub spectral_reverse {
268 4     4 1 491 my ($fir) = @_;
269 4         13 my $ofir = $fir->copy;
270 4         109 $ofir->slice('0:-1:2') *= -1;
271 4         147 $ofir;
272             }
273              
274             =head1 AUTHOR
275              
276             John Lapeyre, C<< >>
277              
278             =head1 ACKNOWLEDGEMENTS
279              
280             =head1 LICENSE AND COPYRIGHT
281              
282             Copyright 2012 John Lapeyre.
283              
284             This program is free software; you can redistribute it and/or modify it
285             under the terms of either: the GNU General Public License as published
286             by the Free Software Foundation; or the Artistic License.
287              
288             See http://dev.perl.org/licenses/ for more information.
289              
290              
291             =cut
292              
293             1; # End of PDL::DSP::Fir