Repository: scipy/scipy

Issue:
scipy.signal.flattop documentation doesn't list a source for the function
There are a whole bunch of flattop window functions for use in signal analysis, a "flattop" isn't descriptive enough, and another source isn't listed.  Does anyone know where this function is from?



Discussion:
I'm not one that does much image processing, but it appears to be here: https://github.com/scipy/scipy/blob/master/scipy/signal/windows.py#L458

@spott, was your question answered above?  I at least first read this as asking for a reference for the function used, not to the actual function definition.  Looking at the coefficients used, they seem to match Matlab's flat top window (http://www.mathworks.com/help/signal/ref/flattopwin.html, see "More About").  There are two references linked there that maybe the source for this window, but I don't have ready access to either of them. 

@insertinterestingnamehere:  I'm not looking for the source code, I'm looking for the source for the mathematical function.  This is a bug report asking for the source of the mathematical function to be cited in the source code comments.

@ewmoore:  It wasn't, you read correctly.  Matlab's flat top window looks close (so does the stanford research systems window on the wikipedia page for windows), but neither are exactly correct, which leaves me to believe there is another source.

@rgommers:  can you remove the invalid tag?  The meaning of my bug was misinterpreted (should I change the title?  is there something more appropriate?), and I think it is still important.

@spott Okay, I see what you're looking for now. I'll defer to someone with more specific experience to answer this.

When I see functions with short cryptic names in the `scipy.signal` module I assume they are intended to be matlab clones, but I may be wrong.

@spott done. I interpreted your question the same way as @insertinterestingnamehere, sorry. 

So the todo here is to add the math for each window function to each docstring, together with a reference if needed.

The Matlab documentation gives these coefficients:

```
a0  0.21557895
a1  0.41663158
a2  0.277263158
a3  0.083578947
a4  0.006947368
```

The values used in scipy, `a = [0.2156, 0.4160, 0.2781, 0.0836, 0.0069]` are close to
these.

Reference [2] from the Matlab documentation is available online as a PDF: http://www.bksv.com/doc/bv0031.pdf
Here's the flat top formula from that document:

```
w(t) = 1 - 1.93 cos(2πt/T) + 1.29 cos(4πt/T) - 0.388 cos(6πt/T) + 0.0322 cos(8πt/T)
```

To compare this to the Matlab and scipy coefficients, we have to scale the coefficients by a[0]:

```
In [15]: m  # Matlab coefficients
Out[15]: array([ 0.21557895,  0.41663158,  0.27726316,  0.08357895,  0.00694737])

In [16]: m/m[0]
Out[16]: array([ 1.        ,  1.93261717,  1.2861328 ,  0.38769531,  0.03222656])

In [17]: a  # scipy coefficients
Out[17]: array([ 0.2156,  0.416 ,  0.2781,  0.0836,  0.0069])

In [18]: a/a[0]
Out[18]: array([ 1.        ,  1.92949907,  1.28988868,  0.3877551 ,  0.03200371])
```

All the Matlab coefficients, and all but the last scipy coefficients, agree with the
formula from reference [2] to three significant digits.

The other Matlab reference (D'Antona, Gabriele, and A. Ferrero. Digital Signal Processing for Measurement Systems. New York: Springer Media, 2006, pp. 70–72) should be checked to see if it has more precise coefficients.  Does anyone have that text in their local library?

Also, the coefficients of the "Stanford Research" flat top window, as given in the wikipedia article on window functions (http://en.wikipedia.org/wiki/Window_function#Flat_top_window), and in the paper referenced by the wikipedia article, available at http://edoc.mpg.de/395068, are

```
 a0 = 1.0, a1 = 1.93, a2 = 1.29, a3 = 0.388, a4 = 0.028
```

Note that the last coefficient is significantly different from those used by matlab and scipy.  There are many more variations of the flat top window in that paper.

The formula for the flat top window should be added to the docstring.  A reference or two should also be added, but at this point, I don't know which one to prefer.  I also don't know what we should use as the "correct" set of coefficients.

@WarrenWeckesser:  Looks like it is the matlab window.  I thought I looked at it and it was wrong, I must have been mistaken.

The article at http://edoc.mpg.de/395068 is the reference I have been using for my own study of flat-top windows (it doesn't have the matlab window however).

I don't know if there is any interest, but I've coded up a number of the windows in that reference, which could be added to the scipy.signals package.  I'm not sure how this should be done however (or even IF it should be done), there are many many window functions (at least 20 in that reference, plus a few more that are already in the spicy.signals package but not in the reference), with many being variations on a theme (a whole bunch of "flattop" style windows for example).  Most (all?) them are the same style: a sum of cosine terms of the form `Σ c_k cos( k * 2 π t / T)`, with different `c_k` terms for different windows.

Any thoughts?  Should scipy.signals contain all those window functions?

All of them? Probably not.  I think we should be open to including other windows if there is good motivating argument for why they should be included.  The collection in scipy is already pretty big and given how simple most of them are (sums of cosine terms essentially) to implement if you need them, I think we should focus on providing a curated set of windows and providing adequate documentation.

@spott

> Should scipy.signals contain all those window functions?

Can they be combined into 1 or 2 functions with parameters?  Like instead of if `HFT90D(M=100)`, make it `hft(M=100, N=4, diff=True)`?

@WarrenWeckesser 

> The other Matlab reference (D'Antona, Gabriele, and A. Ferrero. Digital Signal Processing for Measurement Systems. New York: Springer Media, 2006, pp. 70–72) should be checked to see if it has more precise coefficients. Does anyone have that text in their local library?

It matches Matlab's coefficients:

> If a good performance is required for reducing both the short and long-range leakage errors, the window order must be increased. As an example, the fifth order flat-top window is considered, with the following coefficients: A<sub>0</sub> = 0.21557895, A<sub>1</sub> = -0.41663158, A<sub>2</sub> = 0.277263158, A<sub>3</sub> = -0.083578947, A<sub>4</sub> = 0.006947368. The magnitude of the Fourier transform (4.10) of this window, in the case of N = 16, is reported in Fig. 4.9.

and SciPy's are an approximation of this?

```
0.21557895  0.41663158  0.277263158  0.083578947 0.006947368
0.21557895 -0.41663158  0.277263158 -0.083578947 0.006947368
0.2156      0.4160      0.2781       0.0836      0.0069
```

Same as https://zone.ni.com/reference/en-XX/help/371361G-01/lvanls/flat_top_window/


Resolving patch:
diff --git a/scipy/signal/windows.py b/scipy/signal/windows.py
--- a/scipy/signal/windows.py
+++ b/scipy/signal/windows.py
@@ -37,10 +37,36 @@ def _truncate(w, needed):
         return w
 
 
+def _cos_win(M, a):
+    """
+    Generic weighted sum of cosine terms window
+
+    Parameters
+    ----------
+    M : int
+        Number of points in the output window
+    a : array_like
+        Sequence of weighting coefficients
+
+    References
+    ----------
+    .. [1] A. Nuttall, "Some windows with very good sidelobe behavior," IEEE
+           Transactions on Acoustics, Speech, and Signal Processing, vol. 29,
+           no. 1, pp. 84-91, Feb 1981. doi: 10.1109/TASSP.1981.1163506
+    """
+    n = np.arange(0, M)
+    fac = n * 2 * np.pi / (M - 1.0)
+    w = np.zeros(M)
+    for k in range(len(a)):
+        w += (-1)**k * a[k] * np.cos(k * fac)
+    return w
+
+
 def boxcar(M, sym=True):
     """Return a boxcar or rectangular window.
 
-    Included for completeness, this is equivalent to no window at all.
+    Also known as a rectangular window or Dirichlet window, this is equivalent
+    to no window at all.
 
     Parameters
     ----------
@@ -171,6 +197,11 @@ def parzen(M, sym=True):
         The window, with the maximum value normalized to 1 (though the value 1
         does not appear if `M` is even and `sym` is True).
 
+    References
+    ----------
+    .. [1] E. Parzen, "Mathematical Considerations in the Estimation of
+           Spectra", Technometrics,  Vol. 3, No. 2 (May, 1961), pp. 167-190
+
     Examples
     --------
     Plot the window and its frequency response:
@@ -297,6 +328,12 @@ def blackman(M, sym=True):
 
     .. math::  w(n) = 0.42 - 0.5 \cos(2\pi n/M) + 0.08 \cos(4\pi n/M)
 
+    The "exact Blackman" window was designed to null out the third and fourth
+    sidelobes, but has discontinuities at the boundaries, resulting in a
+    6 dB/oct fall-off.  This window is an approximation of the "exact" window,
+    which does not null the sidelobes as well, but is smooth at the edges,
+    improving the fall-off rate to 18 dB/oct. [3]_
+
     Most references to the Blackman window come from the signal processing
     literature, where it is used as one of many windowing functions for
     smoothing values.  It is also known as an apodization (which means
@@ -311,6 +348,9 @@ def blackman(M, sym=True):
            spectra, Dover Publications, New York.
     .. [2] Oppenheim, A.V., and R.W. Schafer. Discrete-Time Signal Processing.
            Upper Saddle River, NJ: Prentice-Hall, 1999, pp. 468-471.
+    .. [3] Harris, Fredric J. (Jan 1978). "On the use of Windows for Harmonic
+           Analysis with the Discrete Fourier Transform". Proceedings of the
+           IEEE 66 (1): 51-83. doi:10.1109/PROC.1978.10837
 
     Examples
     --------
@@ -342,9 +382,7 @@ def blackman(M, sym=True):
         return np.ones(M)
     M, needs_trunc = _extend(M, sym)
 
-    n = np.arange(0, M)
-    w = (0.42 - 0.5 * np.cos(2.0 * np.pi * n / (M - 1)) +
-         0.08 * np.cos(4.0 * np.pi * n / (M - 1)))
+    w = _cos_win(M, [0.42, 0.50, 0.08])
 
     return _truncate(w, needs_trunc)
 
@@ -352,6 +390,8 @@ def blackman(M, sym=True):
 def nuttall(M, sym=True):
     """Return a minimum 4-term Blackman-Harris window according to Nuttall.
 
+    This variation is called "Nuttall4c" by Heinzel. [2]_
+
     Parameters
     ----------
     M : int
@@ -368,6 +408,16 @@ def nuttall(M, sym=True):
         The window, with the maximum value normalized to 1 (though the value 1
         does not appear if `M` is even and `sym` is True).
 
+    References
+    ----------
+    .. [1] A. Nuttall, "Some windows with very good sidelobe behavior," IEEE
+           Transactions on Acoustics, Speech, and Signal Processing, vol. 29,
+           no. 1, pp. 84-91, Feb 1981. doi: 10.1109/TASSP.1981.1163506
+    .. [2] Heinzel G. et al., "Spectrum and spectral density estimation by the
+           Discrete Fourier transform (DFT), including a comprehensive list of
+           window functions and some new flat-top windows", February 15, 2002
+           https://holometer.fnal.gov/GH_FFT.pdf
+
     Examples
     --------
     Plot the window and its frequency response:
@@ -397,11 +447,7 @@ def nuttall(M, sym=True):
         return np.ones(M)
     M, needs_trunc = _extend(M, sym)
 
-    a = [0.3635819, 0.4891775, 0.1365995, 0.0106411]
-    n = np.arange(0, M)
-    fac = n * 2 * np.pi / (M - 1.0)
-    w = (a[0] - a[1] * np.cos(fac) +
-         a[2] * np.cos(2 * fac) - a[3] * np.cos(3 * fac))
+    w = _cos_win(M, [0.3635819, 0.4891775, 0.1365995, 0.0106411])
 
     return _truncate(w, needs_trunc)
 
@@ -454,11 +500,7 @@ def blackmanharris(M, sym=True):
         return np.ones(M)
     M, needs_trunc = _extend(M, sym)
 
-    a = [0.35875, 0.48829, 0.14128, 0.01168]
-    n = np.arange(0, M)
-    fac = n * 2 * np.pi / (M - 1.0)
-    w = (a[0] - a[1] * np.cos(fac) +
-         a[2] * np.cos(2 * fac) - a[3] * np.cos(3 * fac))
+    w = _cos_win(M, [0.35875, 0.48829, 0.14128, 0.01168])
 
     return _truncate(w, needs_trunc)
 
@@ -482,6 +524,20 @@ def flattop(M, sym=True):
         The window, with the maximum value normalized to 1 (though the value 1
         does not appear if `M` is even and `sym` is True).
 
+    Notes
+    -----
+    Flat top windows are used for taking accurate measurements of signal
+    amplitude in the frequency domain, with minimal scalloping error from the
+    center of a frequency bin to its edges, compared to others.  This is a
+    5th-order cosine window, with the 5 terms optimized to make the main lobe
+    maximally flat. [1]_
+
+    References
+    ----------
+    .. [1] D'Antona, Gabriele, and A. Ferrero, "Digital Signal Processing for
+           Measurement Systems", Springer Media, 2006, p. 70
+           doi: 10.1007/0-387-28666-7
+
     Examples
     --------
     Plot the window and its frequency response:
@@ -511,12 +567,8 @@ def flattop(M, sym=True):
         return np.ones(M)
     M, needs_trunc = _extend(M, sym)
 
-    a = [0.2156, 0.4160, 0.2781, 0.0836, 0.0069]
-    n = np.arange(0, M)
-    fac = n * 2 * np.pi / (M - 1.0)
-    w = (a[0] - a[1] * np.cos(fac) +
-         a[2] * np.cos(2 * fac) - a[3] * np.cos(3 * fac) +
-         a[4] * np.cos(4 * fac))
+    a = [0.21557895, 0.41663158, 0.277263158, 0.083578947, 0.006947368]
+    w = _cos_win(M, a)
 
     return _truncate(w, needs_trunc)
 
@@ -701,8 +753,7 @@ def hann(M, sym=True):
         return np.ones(M)
     M, needs_trunc = _extend(M, sym)
 
-    n = np.arange(0, M)
-    w = 0.5 - 0.5 * np.cos(2.0 * np.pi * n / (M - 1))
+    w = _cos_win(M, [0.5, 0.5])
 
     return _truncate(w, needs_trunc)
 
@@ -927,8 +978,7 @@ def hamming(M, sym=True):
         return np.ones(M)
     M, needs_trunc = _extend(M, sym)
 
-    n = np.arange(0, M)
-    w = 0.54 - 0.46 * np.cos(2.0 * np.pi * n / (M - 1))
+    w = _cos_win(M, [0.54, 0.46])
 
     return _truncate(w, needs_trunc)
 
@@ -977,8 +1027,8 @@ def kaiser(M, beta, sym=True):
     maximizes the energy in the main lobe of the window relative to total
     energy.
 
-    The Kaiser can approximate many other windows by varying the beta
-    parameter.
+    The Kaiser can approximate other windows by varying the beta parameter.
+    (Some literature uses alpha = beta/pi.) [4]_
 
     ====  =======================
     beta  Window shape
@@ -992,7 +1042,7 @@ def kaiser(M, beta, sym=True):
     A beta value of 14 is probably a good starting point. Note that as beta
     gets large, the window narrows, and so the number of samples needs to be
     large enough to sample the increasingly narrow spike, otherwise NaNs will
-    get returned.
+    be returned.
 
     Most references to the Kaiser window come from the signal processing
     literature, where it is used as one of many windowing functions for
@@ -1009,6 +1059,9 @@ def kaiser(M, beta, sym=True):
            University of Alberta Press, 1975, pp. 177-178.
     .. [3] Wikipedia, "Window function",
            http://en.wikipedia.org/wiki/Window_function
+    .. [4] F. J. Harris, "On the use of windows for harmonic analysis with the
+           discrete Fourier transform," Proceedings of the IEEE, vol. 66,
+           no. 1, pp. 51-83, Jan. 1978. doi: 10.1109/PROC.1978.10837
 
     Examples
     --------
@@ -1331,6 +1384,15 @@ def slepian(M, width, sym=True):
     w : ndarray
         The window, with the maximum value always normalized to 1
 
+    References
+    ----------
+    .. [1] D. Slepian & H. O. Pollak: "Prolate spheroidal wave functions,
+           Fourier analysis and uncertainty-I," Bell Syst. Tech. J., vol.40,
+           pp.43-63, 1961. https://archive.org/details/bstj40-1-43
+    .. [2] H. J. Landau & H. O. Pollak: "Prolate spheroidal wave functions,
+           Fourier analysis and uncertainty-II," Bell Syst. Tech. J. , vol.40,
+           pp.65-83, 1961. https://archive.org/details/bstj40-1-65
+
     Examples
     --------
     Plot the window and its frequency response:


Test patch:
diff --git a/scipy/signal/tests/test_windows.py b/scipy/signal/tests/test_windows.py
--- a/scipy/signal/tests/test_windows.py
+++ b/scipy/signal/tests/test_windows.py
@@ -5,8 +5,8 @@
 from numpy import array
 from numpy.testing import (assert_array_almost_equal, assert_array_equal,
                            run_module_suite, assert_raises, assert_allclose,
-                           assert_equal, assert_)
-from scipy import signal
+                           assert_equal, assert_, assert_array_less)
+from scipy import signal, fftpack
 
 
 window_funcs = [
@@ -34,6 +34,75 @@
     ]
 
 
+class TestBartHann(object):
+
+    def test_basic(self):
+        assert_allclose(signal.barthann(6, sym=True),
+                        [0, 0.35857354213752, 0.8794264578624801,
+                         0.8794264578624801, 0.3585735421375199, 0])
+        assert_allclose(signal.barthann(7),
+                        [0, 0.27, 0.73, 1.0, 0.73, 0.27, 0])
+        assert_allclose(signal.barthann(6, False),
+                        [0, 0.27, 0.73, 1.0, 0.73, 0.27])
+
+
+class TestBartlett(object):
+
+    def test_basic(self):
+        assert_allclose(signal.bartlett(6), [0, 0.4, 0.8, 0.8, 0.4, 0])
+        assert_allclose(signal.bartlett(7), [0, 1/3, 2/3, 1.0, 2/3, 1/3, 0])
+        assert_allclose(signal.bartlett(6, False),
+                        [0, 1/3, 2/3, 1.0, 2/3, 1/3])
+
+
+class TestBlackman(object):
+
+    def test_basic(self):
+        assert_allclose(signal.blackman(6, sym=False),
+                        [0, 0.13, 0.63, 1.0, 0.63, 0.13], atol=1e-14)
+        assert_allclose(signal.blackman(6),
+                        [0, 0.2007701432625305, 0.8492298567374694,
+                         0.8492298567374694, 0.2007701432625305, 0],
+                        atol=1e-14)
+        assert_allclose(signal.blackman(7, True),
+                        [0, 0.13, 0.63, 1.0, 0.63, 0.13, 0], atol=1e-14)
+
+
+class TestBlackmanHarris(object):
+
+    def test_basic(self):
+        assert_allclose(signal.blackmanharris(6, False),
+                        [6.0e-05, 0.055645, 0.520575, 1.0, 0.520575, 0.055645])
+        assert_allclose(signal.blackmanharris(6),
+                        [6.0e-05, 0.1030114893456638, 0.7938335106543362,
+                         0.7938335106543364, 0.1030114893456638, 6.0e-05])
+        assert_allclose(signal.blackmanharris(7, sym=True),
+                        [6.0e-05, 0.055645, 0.520575, 1.0, 0.520575, 0.055645,
+                         6.0e-05])
+
+
+class TestBohman(object):
+
+    def test_basic(self):
+        assert_allclose(signal.bohman(6),
+                        [0, 0.1791238937062839, 0.8343114522576858,
+                         0.8343114522576858, 0.1791238937062838, 0])
+        assert_allclose(signal.bohman(7, sym=True),
+                        [0, 0.1089977810442293, 0.6089977810442293, 1.0,
+                         0.6089977810442295, 0.1089977810442293, 0])
+        assert_allclose(signal.bohman(6, False),
+                        [0, 0.1089977810442293, 0.6089977810442293, 1.0,
+                         0.6089977810442295, 0.1089977810442293])
+
+
+class TestBoxcar(object):
+
+    def test_basic(self):
+        assert_allclose(signal.boxcar(6), [1, 1, 1, 1, 1, 1])
+        assert_allclose(signal.boxcar(7), [1, 1, 1, 1, 1, 1, 1])
+        assert_allclose(signal.boxcar(6, False), [1, 1, 1, 1, 1, 1])
+
+
 cheb_odd_true = array([0.200938, 0.107729, 0.134941, 0.165348,
                        0.198891, 0.235450, 0.274846, 0.316836,
                        0.361119, 0.407338, 0.455079, 0.503883,
@@ -71,6 +140,26 @@
 
 class TestChebWin(object):
 
+    def test_basic(self):
+        assert_allclose(signal.chebwin(6, 100),
+                        [0.1046401879356917, 0.5075781475823447, 1.0, 1.0,
+                         0.5075781475823447, 0.1046401879356917])
+        assert_allclose(signal.chebwin(7, 100),
+                        [0.05650405062850233, 0.316608530648474,
+                         0.7601208123539079, 1.0, 0.7601208123539079,
+                         0.316608530648474, 0.05650405062850233])
+        assert_allclose(signal.chebwin(6, 10),
+                        [1.0, 0.6071201674458373, 0.6808391469897297,
+                         0.6808391469897297, 0.6071201674458373, 1.0])
+        assert_allclose(signal.chebwin(7, 10),
+                        [1.0, 0.5190521247588651, 0.5864059018130382,
+                         0.6101519801307441, 0.5864059018130382,
+                         0.5190521247588651, 1.0])
+        assert_allclose(signal.chebwin(6, 10, False),
+                        [1.0, 0.5190521247588651, 0.5864059018130382,
+                         0.6101519801307441, 0.5864059018130382,
+                         0.5190521247588651])
+
     def test_cheb_odd_high_attenuation(self):
         with warnings.catch_warnings():
             warnings.simplefilter("ignore", UserWarning)
@@ -80,7 +169,7 @@ def test_cheb_odd_high_attenuation(self):
     def test_cheb_even_high_attenuation(self):
         with warnings.catch_warnings():
             warnings.simplefilter("ignore", UserWarning)
-            cheb_even = signal.chebwin(54, at=-40)
+            cheb_even = signal.chebwin(54, at=40)
         assert_array_almost_equal(cheb_even, cheb_even_true, decimal=4)
 
     def test_cheb_odd_low_attenuation(self):
@@ -89,7 +178,7 @@ def test_cheb_odd_low_attenuation(self):
                                       1.000000])
         with warnings.catch_warnings():
             warnings.simplefilter("ignore", UserWarning)
-            cheb_odd = signal.chebwin(7, at=-10)
+            cheb_odd = signal.chebwin(7, at=10)
         assert_array_almost_equal(cheb_odd, cheb_odd_low_at_true, decimal=4)
 
     def test_cheb_even_low_attenuation(self):
@@ -152,6 +241,133 @@ def test_exponential():
             assert_allclose(win, v, rtol=1e-14)
 
 
+class TestFlatTop(object):
+
+    def test_basic(self):
+        assert_allclose(signal.flattop(6, sym=False),
+                        [-0.000421051, -0.051263156, 0.19821053, 1.0,
+                         0.19821053, -0.051263156])
+        assert_allclose(signal.flattop(6),
+                        [-0.000421051, -0.0677142520762119, 0.6068721525762117,
+                         0.6068721525762117, -0.0677142520762119,
+                         -0.000421051])
+        assert_allclose(signal.flattop(7, True),
+                        [-0.000421051, -0.051263156, 0.19821053, 1.0,
+                         0.19821053, -0.051263156, -0.000421051])
+
+
+class TestGaussian(object):
+
+    def test_basic(self):
+        assert_allclose(signal.gaussian(6, 1.0),
+                        [0.04393693362340742, 0.3246524673583497,
+                         0.8824969025845955, 0.8824969025845955,
+                         0.3246524673583497, 0.04393693362340742])
+        assert_allclose(signal.gaussian(7, 1.2),
+                        [0.04393693362340742, 0.2493522087772962,
+                         0.7066482778577162, 1.0, 0.7066482778577162,
+                         0.2493522087772962, 0.04393693362340742])
+        assert_allclose(signal.gaussian(7, 3),
+                        [0.6065306597126334, 0.8007374029168081,
+                         0.9459594689067654, 1.0, 0.9459594689067654,
+                         0.8007374029168081, 0.6065306597126334])
+        assert_allclose(signal.gaussian(6, 3, False),
+                        [0.6065306597126334, 0.8007374029168081,
+                         0.9459594689067654, 1.0, 0.9459594689067654,
+                         0.8007374029168081])
+
+
+class TestHamming(object):
+
+    def test_basic(self):
+        assert_allclose(signal.hamming(6, False),
+                        [0.08, 0.31, 0.77, 1.0, 0.77, 0.31])
+        assert_allclose(signal.hamming(6),
+                        [0.08, 0.3978521825875242, 0.9121478174124757,
+                         0.9121478174124757, 0.3978521825875242, 0.08])
+        assert_allclose(signal.hamming(7, sym=True),
+                        [0.08, 0.31, 0.77, 1.0, 0.77, 0.31, 0.08])
+
+
+class TestHann(object):
+
+    def test_basic(self):
+        assert_allclose(signal.hann(6, sym=False),
+                        [0, 0.25, 0.75, 1.0, 0.75, 0.25])
+        assert_allclose(signal.hann(6, True),
+                        [0, 0.3454915028125263, 0.9045084971874737,
+                         0.9045084971874737, 0.3454915028125263, 0])
+        assert_allclose(signal.hann(7),
+                        [0, 0.25, 0.75, 1.0, 0.75, 0.25, 0])
+
+
+class TestKaiser(object):
+
+    def test_basic(self):
+        assert_allclose(signal.kaiser(6, 0.5),
+                        [0.9403061933191572, 0.9782962393705389,
+                         0.9975765035372042, 0.9975765035372042,
+                         0.9782962393705389, 0.9403061933191572])
+        assert_allclose(signal.kaiser(7, 0.5),
+                        [0.9403061933191572, 0.9732402256999829,
+                         0.9932754654413773, 1.0, 0.9932754654413773,
+                         0.9732402256999829, 0.9403061933191572])
+        assert_allclose(signal.kaiser(6, 2.7),
+                        [0.2603047507678832, 0.6648106293528054,
+                         0.9582099802511439, 0.9582099802511439,
+                         0.6648106293528054, 0.2603047507678832])
+        assert_allclose(signal.kaiser(7, 2.7),
+                        [0.2603047507678832, 0.5985765418119844,
+                         0.8868495172060835, 1.0, 0.8868495172060835,
+                         0.5985765418119844, 0.2603047507678832])
+        assert_allclose(signal.kaiser(6, 2.7, False),
+                        [0.2603047507678832, 0.5985765418119844,
+                         0.8868495172060835, 1.0, 0.8868495172060835,
+                         0.5985765418119844])
+
+
+class TestNuttall(object):
+
+    def test_basic(self):
+        assert_allclose(signal.nuttall(6, sym=False),
+                        [0.0003628, 0.0613345, 0.5292298, 1.0, 0.5292298,
+                         0.0613345])
+        assert_allclose(signal.nuttall(6),
+                        [0.0003628, 0.1105152530498718, 0.7982580969501282,
+                         0.7982580969501283, 0.1105152530498719, 0.0003628])
+        assert_allclose(signal.nuttall(7, True),
+                        [0.0003628, 0.0613345, 0.5292298, 1.0, 0.5292298,
+                         0.0613345, 0.0003628])
+
+
+class TestParzen(object):
+
+    def test_basic(self):
+        assert_allclose(signal.parzen(6),
+                        [0.009259259259259254, 0.25, 0.8611111111111112,
+                         0.8611111111111112, 0.25, 0.009259259259259254])
+        assert_allclose(signal.parzen(7, sym=True),
+                        [0.00583090379008747, 0.1574344023323616,
+                         0.6501457725947521, 1.0, 0.6501457725947521,
+                         0.1574344023323616, 0.00583090379008747])
+        assert_allclose(signal.parzen(6, False),
+                        [0.00583090379008747, 0.1574344023323616,
+                         0.6501457725947521, 1.0, 0.6501457725947521,
+                         0.1574344023323616])
+
+
+class TestTriang(object):
+
+    def test_basic(self):
+
+        assert_allclose(signal.triang(6, True),
+                        [1/6, 1/2, 5/6, 5/6, 1/2, 1/6])
+        assert_allclose(signal.triang(7),
+                        [1/4, 1/2, 3/4, 1, 3/4, 1/2, 1/4])
+        assert_allclose(signal.triang(6, sym=False),
+                        [1/4, 1/2, 3/4, 1, 3/4, 1/2])
+
+
 tukey_data = {
     (4, 0.5, True): array([0.0, 1.0, 1.0, 0.0]),
     (4, 0.9, True): array([0.0, 0.84312081893436686,
@@ -165,25 +381,42 @@ def test_exponential():
     (5, 0.8, True): array([0.0, 0.69134171618254492,
                            1.0, 0.69134171618254492, 0.0]),
     (5, 1.0, True): array([0.0, 0.5, 1.0, 0.5, 0.0]),
+
+    (6, 0): [1, 1, 1, 1, 1, 1],
+    (7, 0): [1, 1, 1, 1, 1, 1, 1],
+    (6, .25): [0, 1, 1, 1, 1, 0],
+    (7, .25): [0, 1, 1, 1, 1, 1, 0],
+    (6,): [0, 0.9045084971874737, 1.0, 1.0, 0.9045084971874735, 0],
+    (7,): [0, 0.75, 1.0, 1.0, 1.0, 0.75, 0],
+    (6, .75): [0, 0.5522642316338269, 1.0, 1.0, 0.5522642316338267, 0],
+    (7, .75): [0, 0.4131759111665348, 0.9698463103929542, 1.0,
+               0.9698463103929542, 0.4131759111665347, 0],
+    (6, 1): [0, 0.3454915028125263, 0.9045084971874737, 0.9045084971874737,
+             0.3454915028125263, 0],
+    (7, 1): [0, 0.25, 0.75, 1.0, 0.75, 0.25, 0],
 }
 
 
-def test_tukey():
-    # Test against hardcoded data
-    for k, v in tukey_data.items():
-        if v is None:
-            assert_raises(ValueError, signal.tukey, *k)
-        else:
-            win = signal.tukey(*k)
-            assert_allclose(win, v, rtol=1e-14)
+class TestTukey(object):
+
+    def test_basic(self):
+        # Test against hardcoded data
+        for k, v in tukey_data.items():
+            if v is None:
+                assert_raises(ValueError, signal.tukey, *k)
+            else:
+                win = signal.tukey(*k)
+                assert_allclose(win, v, rtol=1e-14)
 
-    # Test extremes of alpha correspond to boxcar and hann
-    tuk0 = signal.tukey(100, 0)
-    tuk1 = signal.tukey(100, 1)
-    box0 = signal.boxcar(100)
-    han1 = signal.hann(100)
-    assert_array_almost_equal(tuk0, box0)
-    assert_array_almost_equal(tuk1, han1)
+    def test_extremes(self):
+        # Test extremes of alpha correspond to boxcar and hann
+        tuk0 = signal.tukey(100, 0)
+        box0 = signal.boxcar(100)
+        assert_array_almost_equal(tuk0, box0)
+
+        tuk1 = signal.tukey(100, 1)
+        han1 = signal.hann(100)
+        assert_array_almost_equal(tuk1, han1)
 
 
 class TestGetWindow(object):
@@ -192,6 +425,10 @@ def test_boxcar(self):
         w = signal.get_window('boxcar', 12)
         assert_array_equal(w, np.ones_like(w))
 
+        # window is a tuple of len 1
+        w = signal.get_window(('boxcar',), 16)
+        assert_array_equal(w, np.ones_like(w))
+
     def test_cheb_odd(self):
         with warnings.catch_warnings():
             warnings.simplefilter("ignore", UserWarning)
@@ -201,9 +438,21 @@ def test_cheb_odd(self):
     def test_cheb_even(self):
         with warnings.catch_warnings():
             warnings.simplefilter("ignore", UserWarning)
-            w = signal.get_window(('chebwin', -40), 54, fftbins=False)
+            w = signal.get_window(('chebwin', 40), 54, fftbins=False)
         assert_array_almost_equal(w, cheb_even_true, decimal=4)
 
+    def test_kaiser_float(self):
+        win1 = signal.get_window(7.2, 64)
+        win2 = signal.kaiser(64, 7.2, False)
+        assert_allclose(win1, win2)
+
+    def test_invalid_inputs(self):
+        # Window is not a float, tuple, or string
+        assert_raises(ValueError, signal.get_window, set('hann'), 8)
+
+        # Unknown window type error
+        assert_raises(ValueError, signal.get_window, 'broken', 4)
+
     def test_array_as_window(self):
         # github issue 3603
         osfactor = 128
@@ -246,6 +495,16 @@ def test_windowfunc_basics():
             assert_(window(6, *params, sym=True).dtype == 'float')
             assert_(window(6, *params, sym=False).dtype == 'float')
 
+            # Check normalization
+            assert_array_less(window(10, *params, sym=True), 1.01)
+            assert_array_less(window(10, *params, sym=False), 1.01)
+            assert_array_less(window(9, *params, sym=True), 1.01)
+            assert_array_less(window(9, *params, sym=False), 1.01)
+
+            # Check periodic spectrum
+            assert_allclose(fftpack.fft(window(10, *params, sym=False)).imag,
+                            0, atol=1e-14)
+
 
 def test_needs_params():
     for winstr in ['kaiser', 'ksr', 'gaussian', 'gauss', 'gss',