This article presents spectral reversal, a technique to turn a low-pass filter into a high-pass filter. Spectral reversal is an alternative for spectral inversion, as described in How to Create a Simple High-Pass Filter.
The windowed-sinc filter that is described in this article is an example of a Finite Impulse Response (FIR) filter.
Spectral Reversal
Starting from the cutoff frequency \(f_c\) and the transition bandwidth (or roll-off) \(b\), first create a low-pass filter as described in How to Create a Simple Low-Pass Filter. The normalized low-pass filter from that article, again for \(f_c=0.1\) and \(b=0.08\), is shown as the left image in Figure 1.
The spectral reversal of a filter \(h[n]\) is computed by changing the sign of every other value in \(h[n]\), i.e., by multiplying \(h[n]\) with the sequence \(1,-1,1,-1,\ldots\) Hence, the coefficients of the new filter can be written elegantly as \((-1)^nh[n]\).
This produces the filter shown as the right image in Figure 1.
The frequency response of the high-pass filter is then as shown in Figure 2.
This frequency response is a “left-right flipped” version of the frequency response of the low-pass filter. In contrast, the spectral inversion filter from How to Create a Simple High-Pass Filter was an “upside down” version of the original low-pass filter.
Why Does Spectral Reversal Work?
The spectral reversal technique is based on the so-called shift theorem of the Fourier transform. Formulated for the discrete case, the shift theorem says that, for a Fourier transform pair \(x[n]\longleftrightarrow X[k]\), a shift by \(s\) samples in the frequency domain is equivalent with multiplying by a complex exponential in the time domain, as
\[x[n]e^{i2\pi ns/N}\longleftrightarrow X[k-s].\]
In general, the result of doing this will be complex. This is not a problem, but let’s investigate in which circumstances the result is not complex. This is may be easiest to see with the complex exponential rewritten using Euler’s Identity,
\[e^{i2\pi ns/N}=\cos(2\pi ns/N)+i\sin(2\pi ns/N).\]
This expression is real if the sine term is zero, which is the case if the angle is zero or \(\pi\).
We get this result if we shift by \(s=N/2\) samples, because then the angles become \(2\pi ns/N=n\pi\). Of course, in that case the complete expression becomes \(\cos(n\pi)\), which is exactly the sequence \(1,-1,1,-1,\ldots\)
To see why shifting by \(N/2\) samples turns a low-pass filter into a high-pass filter, we first have to look at a double sided spectrum of the original low-pass filter (Figure 3).
Normally, the negative frequencies are not very interesting for the real filters that we have been working with, since they are simply the mirror image of the positive frequencies. However, shifting the filter over a number of samples results in a complex filter, for which the frequency response is generally no longer symmetrical around zero. For example, Figure 4 shows the response of the filter with \(h[n]\) shifted by 10 samples through multiplying by the complex exponential given above. The filter has changed into a (complex) band-pass filter!
It just happens that a shift by \(N/2\) samples shifts this band-pass filter around the (normalized) frequency of 0.5 (half of the sampling frequency in general). This is shown in Figure 5, for a shift of 25.5 samples, since the length of \(h[n]\) is 51.
And this just happens to be a real frequency response again, since it is symmetrical around zero. If you look at the part between zero and 0.5, it is exactly the same as the plot in Figure 2. Hence, spectral reversal produces a high-pass filter if you start from a low-pass one.
Python Code
In Python, spectral reversal can be implemented concisely through
h *= (-1) ** np.arange(N)
I am aware that this is probably not the most efficient implementation, but efficiency is not really important here, and if that is the case, I’m normally in favor of following the mathematics as closely as possible in the implementation. There is a complete script in How to Create a Simple High-Pass Filter, where you can then simply replace the two last lines with the one line given above.
Great article again!
But I'm wonding what should be preferred: reversal or inversion?
I noticed with my own numpy scripts that the numerical error is different.
The high pass generated with spectral reversal looks indeed like the mirror of the low pass, but its impulse response does not sum up to 0 but more like to a value with an magnitude of 1e-6 or 1e-7. This is basically 0 in float32 but noticable in float64.
The inverted filter does sum up to basically 0 in float64: I get something like 1.5e-16.
When I plot both I can really see the zero point in the dB plot (i.e. the drop to \(-\inf\) dB) with the inverted one, but the reversal one still has some value, even if it is as low as -120dB.
I guess in the range the difference is negligible for basically all practical implementations, right?
Indeed, I don't think the difference matters from a practical point of view.
So glad I found your site. At last code that actually works and an explanation that I can understand. Regarding reversal or inversion and the number of taps in the filter: Inverting the coefficients and adding 1 to the centre value only works with an odd number of taps, but if you have an even number of taps the pair of central values are equal and adding 0.5 to both of them seems to work, is this valid? On the other hand with reversal the sum of the coefficients will be always be zero with an even number of taps, but if the number of taps are odd and the first and last coefficients are not close to zero ( as can happen if you have more taps than necessary for the cut off frequency ) then there can be a non zero sum which I found to give a negative offset to the values.
For instance, here is a 15 tap 0.2Hz low pass filter sampled at 2Hz:
-0.042256935
-0.030468926
7.62E-18
0.045703389
0.098599515
0.147899273
0.182813555
0.195420256
0.182813555
0.147899273
0.098599515
0.045703389
7.62E-18
-0.030468926
-0.042256935
Sum
1
And here is the corresponding high pass filter generated by reversal:
-0.042256935
0.030468926
7.62E-18
-0.045703389
0.098599515
-0.147899273
0.182813555
-0.195420256
0.182813555
-0.147899273
0.098599515
-0.045703389
7.62E-18
0.030468926
-0.042256935
Sum
-0.043375457
Add new comment