vspline 1.1.0
Generic C++11 Code for Uniform B-Splines
general_filter.h
Go to the documentation of this file.
1/************************************************************************/
2/* */
3/* vspline - a set of generic tools for creation and evaluation */
4/* of uniform b-splines */
5/* */
6/* Copyright 2015 - 2023 by Kay F. Jahnke */
7/* */
8/* The git repository for this software is at */
9/* */
10/* https://bitbucket.org/kfj/vspline */
11/* */
12/* Please direct questions, bug reports, and contributions to */
13/* */
14/* kfjahnke+vspline@gmail.com */
15/* */
16/* Permission is hereby granted, free of charge, to any person */
17/* obtaining a copy of this software and associated documentation */
18/* files (the "Software"), to deal in the Software without */
19/* restriction, including without limitation the rights to use, */
20/* copy, modify, merge, publish, distribute, sublicense, and/or */
21/* sell copies of the Software, and to permit persons to whom the */
22/* Software is furnished to do so, subject to the following */
23/* conditions: */
24/* */
25/* The above copyright notice and this permission notice shall be */
26/* included in all copies or substantial portions of the */
27/* Software. */
28/* */
29/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND */
30/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES */
31/* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND */
32/* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT */
33/* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
34/* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING */
35/* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR */
36/* OTHER DEALINGS IN THE SOFTWARE. */
37/* */
38/************************************************************************/
39
40/*! \file general_filter.h
41
42 \brief provides vspline's digital filtering capabilities without the
43 b-spline-specific aspects.
44
45 The two filters offered by this header are applying 'separated'
46 filters: If the signal is nD, a 1D kernel is applied along one or
47 all axes of the data. This requires the filter to be 'separable',
48 meaning that if, for example, your data is 2D, the 2D kernel
49 to be used on the data has to be a cross product of two 1D
50 vectors which are applied along the two axes of the data. You
51 can't use the code given here for stuff like radial basis functions
52 where the kernel is not separable.
53
54 vspline uses the code in filter.h for prefiltering and reconstruction.
55 In both cases, the filters are set up for the specific use with b-splines.
56 But the filtering code is more general, and it can be used for other
57 filtering tasks. This header provides convenient access to the two
58 filter types implemented in vspline:
59
60 - forward-backward n-pole recursive filters
61 - convolution with an arbitrary kernel
62
63 This code is currently not used in other parts of vspline - vspline uses
64 the code in filter.h directly.
65
66 The filtering routines will appply the same filter along all axes if
67 -1 is passed as 'axis'. Otherwise, the filter will only be applied to
68 the specific axis. This way filtering operations with different filters
69 along different axes can be achieved by calling the filtering routines
70 several times, once per axis, with per-axis arguments.
71*/
72
73#ifndef VSPLINE_GENERAL_FILTER_H
74
75#include "vspline.h"
76
77// TODO: now that we have the 'generalized' routines, we might make them part of
78// vspline and code 'prefilter' and 'restore' as using them, instead of directly
79// calling the code in filter.h.
80
81namespace vspline
82{
83
84/// forward_backward_recursive_filter applies one or more pairs of simple
85/// recursive filters to the input. Each pair consists of:
86/// 1. the forward filter: x[n] = g * x[n] + p * x[n-1]
87/// 2. the backward filter: x[n] = -p * x[n] + p * x[n+1]
88/// where 'p' is the 'pole' of the filter (see poles.h) and g is the
89/// 'gain', which is computed as g = ( 1 - p ) * ( 1 - 1 / p );
90/// multiplication the input with the gain in the first stage
91/// makes sure that subsequent convolution with the corresponding
92/// reconstruction kernel will restore the original signal.
93/// The pair of filters used here, in sum, has no phase shift
94/// (the phase shift of the forward filter is precisely matched by
95/// an inverse phase shift of the backward filter due to the reversal
96/// of the processing direction).
97/// input and output must be equally-sized arrays of compatible types
98/// (meaning, in_value_type and out_value_type must have the same number
99/// of channels). bcv holds a set of boundary conditions, one per
100/// dimension. pv is a std::vector holding the filter poles poles,
101/// 'tolerance' is the acceptable error, defaulting to
102/// a very conservative value. Try and resist from passing zero here,
103/// which results in great efforts to produce a 'mathematically correct'
104/// result - the result from using the conservative default will differ
105/// very little from the one obtained with 'zero tolerance', but is usually
106/// faster to compute.
107/// Next, a 'boost' value can be passed which will be applied once
108/// as a multiplicative factor. If any value apart from -1 is passed for
109/// 'axis', filtering will be limited to the indicated axis. Per default,
110/// the filters will be applied to all axes in turn.
111/// Finally, njobs defines how many jobs will be used to multithread
112/// the operation.
113/// While b-spline prefiltering uses 'poles' from poles.h, this generalized
114/// routine will take any poles you pass. But note that the magnitude of
115/// each pole should be below 1. If you use negative poles, the effect will
116/// be a high-pass filter, as it is used for b-spline prefiltering. Using
117/// positive poles will blur the signal very effectively. Also note that
118/// use of small negative poles will amplify the signal strongly. This
119/// effect can be seen when prefiltering high-degree b-splines: the resulting
120/// signal will have much higher energy. Make sure your data types can deal
121/// with the increased dynamic.
122
123template < unsigned int dimension ,
124 typename in_value_type ,
125 typename out_value_type ,
126 typename math_ele_type =
128 < in_value_type , out_value_type > ,
129 size_t vsize =
131 >
133 const
134 vigra::MultiArrayView
135 < dimension ,
136 in_value_type > & input ,
137 vigra::MultiArrayView
138 < dimension ,
139 out_value_type > & output ,
140 vigra::TinyVector
141 < bc_code ,
142 static_cast < int > ( dimension ) > bcv ,
143 std::vector < vspline::xlf_type > pv ,
144 xlf_type tolerance = -1 ,
145 xlf_type boost = xlf_type ( 1 ) ,
146 int axis = -1 , // -1: apply along all axes
147 int njobs = default_njobs )
148{
149 if ( output.shape() != input.shape() )
150 throw shape_mismatch
151 ( "forward_backward_recursive_filter: input and output shape must match" ) ;
152
153 if ( tolerance < 0 )
154 tolerance = std::numeric_limits < math_ele_type > :: epsilon() ;
155
156 int npoles = pv.size() ;
157
158 if ( npoles < 1 )
159 {
160 // if npoles < 1, there is no filter to apply, but we may need
161 // to apply 'boost' and/or copy input to output. We use 'amplify'
162 // for the purpose, which multithreads the operation (if it is at
163 // all necessary). I found this is (slightly) faster than doing the
164 // job in a single thread - the process is mainly memory-bound, so
165 // the gain is moderate.
166
167 amplify < dimension , in_value_type , out_value_type , math_ele_type >
168 ( input , output , math_ele_type ( boost ) ) ;
169
170 return ;
171 }
172
173
174 vspline::xlf_type poles [ npoles ] ;
175 for ( int i = 0 ; i < npoles ; i++ )
176 poles[i] = pv[i] ;
177
178 // KFJ 2018-05-08 with the automatic use of vectorization the
179 // distinction whether math_ele_type is 'vectorizable' or not
180 // is no longer needed: simdized_type will be a Vc::SimdArray
181 // if possible, a vspline::simd_type otherwise.
182
183 typedef typename vspline::bspl_prefilter
185 math_ele_type ,
186 vsize
187 > filter_type ;
188
189 // now call the 'wielding' code in filter.h
190
191 if ( axis == -1 )
192 {
193 // user has passed -1 for 'axis', apply the same filter along all axes
194
195 std::vector < vspline::iir_filter_specs > vspecs ;
196
197 // package the arguments to the filter; one set of arguments
198 // per axis of the data
199
200 for ( int axis = 0 ; axis < dimension ; axis++ )
201 {
202 vspecs.push_back
204 ( bcv [ axis ] , npoles , poles , tolerance , 1 ) ) ;
205 }
206
207 // 'boost' is only applied to dimension 0, since it is meant to
208 // affect the whole data set just once, not once per axis.
209
210 vspecs [ 0 ] . boost = boost ;
211
213 < in_value_type , out_value_type , dimension , filter_type >
214 ( input , output , vspecs , njobs ) ;
215 }
216 else
217 {
218 // user has passed a specific axis, apply filter only to this axis
219
220 assert ( axis >=0 && axis < dimension ) ;
221
223 < in_value_type , out_value_type , dimension , filter_type >
224 ( input , output , axis ,
226 bcv [ axis ] , npoles , poles , tolerance , boost ) ,
227 njobs ) ;
228 }
229}
230
231/// convolution_filter implements convolution of the input with a fixed-size
232/// convolution kernel. Note that vspline does *not* follow the DSP convention
233/// of using the kernel's coefficients in reverse order. The standard is to
234/// calculate sum ( ck * u(n-k) ), vspline uses sum ( ck * u(n+k-h) ) where
235/// 'h' is the 'headroom' of the kernel - the number of coefficients which
236/// are applied 'to 'past' values, so that for for a kernel with three
237/// coefficients and headroom 1, the sum at position n would be
238/// c0 * u(n-1) + c1 * u(n) + c2 * u(n+1), and for a kernel with four
239/// coefficients and headroom 2, you'd get
240/// c0 * u(n-2) + c1 * u(n-1) + c2 * u(n) + c3 * u(n+1)
241/// If you use a 'normal' kernel in vspline, you must reverse it (unless it's
242/// symmetric, of course), and you must state the 'headroom': if your kernel
243/// is odd-sized, this will normally be half the kernel size (integer division!),
244/// producing the phase-correct result, and with an even kernel you can't get
245/// the phase right, and you have to make up your mind which way the phase
246/// should shift. The 'headroom' notation leaves you free to pick your choice.
247/// The input, output, bcv, axis and njobs parameters are as in the routine
248/// above. Here what's needed here is 'kv', a std::vector holding the filter's
249/// coefficients, and the 'headroom', usually kv.size() / 2.
250///
251/// example: let's say you have data in 'image' and want to convolve in-place
252/// with [ 1 , 2 , 1 ], using mirror boundary conditions. Then call:
253///
254/// vspline::convolution_filter
255/// ( image ,
256/// image ,
257/// { vspline::MIRROR , vspline::MIRROR } ,
258/// { 1 , 2 , 1 } ,
259/// 1 ) ;
260
261
262template < unsigned int dimension ,
263 typename in_value_type ,
264 typename out_value_type ,
265 typename math_ele_type =
267 < in_value_type , out_value_type > ,
268 size_t vsize =
270 >
272 const
273 vigra::MultiArrayView
274 < dimension ,
275 in_value_type > & input ,
276 vigra::MultiArrayView
277 < dimension ,
278 out_value_type > & output ,
279 vigra::TinyVector
280 < bc_code ,
281 static_cast < int > ( dimension ) > bcv ,
282 std::vector < vspline::xlf_type > kv ,
283 int headroom ,
284 int axis = -1 , // -1: apply along all axes
285 int njobs = default_njobs )
286{
287 if ( output.shape() != input.shape() )
288 throw shape_mismatch
289 ( "convolution_filter: input and output shape must match" ) ;
290
291 size_t ksize = kv.size() ;
292
293 if ( ksize < 1 )
294 {
295 // we can handle the no-kernel case very efficiently,
296 // since we needn't apply a filter at all.
297
298 if ( (void*) ( input.data() ) != (void*) ( output.data() ) )
299 {
300 // operation is not in-place, copy data to output
301 output = input ;
302 }
303 return ;
304 }
305
306 vspline::xlf_type kernel [ ksize ] ;
307 for ( int i = 0 ; i < ksize ; i++ )
308 kernel[i] = kv[i] ;
309
310 typedef typename vspline::convolve
312 math_ele_type ,
313 vsize
314 > filter_type ;
315
316 if ( axis == -1 )
317 {
318 // user has passed -1 for 'axis', apply the same filter along all axes
319
320 std::vector < vspline::fir_filter_specs > vspecs ;
321
322 for ( int axis = 0 ; axis < dimension ; axis++ )
323 {
324 vspecs.push_back
326 ( bcv [ axis ] , ksize , headroom , kernel ) ) ;
327 }
328
330 < in_value_type , out_value_type , dimension , filter_type >
331 ( input , output , vspecs , njobs ) ;
332 }
333 else
334 {
335 // user has passed a specific axis, apply filter only to this axis
336
337 assert ( axis >=0 && axis < dimension ) ;
338
340 < in_value_type , out_value_type , dimension , filter_type >
341 ( input , output , axis ,
342 vspline::fir_filter_specs ( bcv [ axis ] , ksize , headroom , kernel ) ,
343 njobs ) ;
344 }
345}
346
347} ; // namespace vspline
348
349#define VSPLINE_GENERAL_FILTER_H
350#endif
@ vsize
Definition: eval.cc:96
Definition: basis.h:79
void filter(const vigra::MultiArrayView< D, in_type > &input, vigra::MultiArrayView< D, out_type > &output, types ... args)
vspline::filter is the common entry point for filter operations in vspline. This routine does not yet...
Definition: filter.h:1495
const int default_njobs
Definition: multithread.h:220
void convolution_filter(const vigra::MultiArrayView< dimension, in_value_type > &input, vigra::MultiArrayView< dimension, out_value_type > &output, vigra::TinyVector< bc_code, static_cast< int >(dimension) > bcv, std::vector< vspline::xlf_type > kv, int headroom, int axis=-1, int njobs=default_njobs)
convolution_filter implements convolution of the input with a fixed-size convolution kernel....
typename vigra::NumericTraits< promote_ele_type< T1, T2 > > ::RealPromote common_math_ele_type
Definition: common.h:192
long double xlf_type
Definition: common.h:102
typename vector_traits< T, N > ::type simdized_type
this alias is used as a shorthand to pick the vectorized type for a given type T and a size N from 'v...
Definition: vector.h:459
void forward_backward_recursive_filter(const vigra::MultiArrayView< dimension, in_value_type > &input, vigra::MultiArrayView< dimension, out_value_type > &output, vigra::TinyVector< bc_code, static_cast< int >(dimension) > bcv, std::vector< vspline::xlf_type > pv, xlf_type tolerance=-1, xlf_type boost=xlf_type(1), int axis=-1, int njobs=default_njobs)
forward_backward_recursive_filter applies one or more pairs of simple recursive filters to the input....
bc_code
This enumeration is used for codes connected to boundary conditions. There are two aspects to boundar...
Definition: common.h:71
class to provide b-spline prefiltering, using 'iir_filter' above. The actual filter object has to int...
Definition: prefilter.h:914
class convolve provides the combination of class fir_filter above with a vector-friendly buffer....
Definition: convolve.h:387
fir_filter_specs holds the parameters for a filter performing a convolution along a single axis....
Definition: convolve.h:93
structure to hold specifications for an iir_filter object. This set of parameters has to be passed th...
Definition: prefilter.h:163
shape mismatch is the exception which is thrown if the shapes of an input array and an output array d...
Definition: common.h:297
with the definition of 'simd_traits', we can proceed to implement 'vector_traits': struct vector_trai...
Definition: vector.h:344
includes all headers from vspline (most of them indirectly)