1

I am new to posting questions here, and trying to follow appropriate etiquette, so please help me learn from any omissions or mistakes in my question.

I am using specgram to analyze acoustic signals and am confused about 2 aspects of the results.

  1. Plots seem to have the end of the spectrogram placed at the time of the last value in the bin array, which is supposed to the middle of the last bin. I would expect the plot to end at the value of the end of the last bin.

  2. The bin-center values that are returned as the time axis appear not to be identical with those from Matlab, but it is possible that there’s a parameter setting that’s different in my trials.

These issues appear in one of the Matplotlib examples, from: http://matplotlib.org/1.4.0/examples/pylab_examples/specgram_demo.html

The bin values are: 0.256, 0.768, 1.28…..19.2, 19.712.

The most obvious issue is that the plot of the spectrogram ends at 19.712, rather than the expected value of 20.0.

Can anyone please help clarify? Does either of these issues seem to represent a bug? Or am I doing something wrong?

This is related to this question: How to make specgram fill entire figure area with matplotlib?

Thanks in advance for any guidance you can provide.

Community
  • 1
  • 1

2 Answers2

0

Yes, The plot does end at the middle of the last bin. This probably is not correct.

However, no matter what, it isn't going to be 2.0, for two reasons.

First, the endpoint is rarely going to perfectly match the last sample, because it is divided into NFFT-length segments with an overlap of noverlap, which is unlikely to perfectly fit into the length of the signal unless you very carefully pick the signal length, segment length, and overlap.

And even then, it is never going to go to 20.0, because numpy arange, as with other python ranges, excludes the last value. So t.max() is 20.0-dt, which is 19.9995. Again, this is just a different convention than what MATLAB uses.

Using MATLAB 2014b and the spectogram function, I ran it with the same parameters as the matplotlib example, making sure to take into account the end point of the range. I get the same time points as with matplotlib.

TheBlackCat
  • 9,791
  • 3
  • 24
  • 31
  • OK, point taken about the expected bin centers. I do understand the issues about the length of segments etc. I may have mismatched parameters between Matlab and matplotlib. Thanks. Plot scaling is still an issue. I am looking at workarounds. In Matlab, I would take the data and then replot myself using imagesc(). I have been struggling to take the Pxx or the im value returned form specgram() and plot them in a new window. I am having trouble understanding the "extent" and "aspect" arguments. Any pointers on how to replot the data with correct time scaling would be very much appreciated. – David J Perkel Apr 02 '15 at 16:11
  • You can look at the source code of the `specgram` method on matplotlib's github site to find out how it does it https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/axes/_axes.py . Beyond that, it is probably better to ask a new question here. – TheBlackCat Apr 03 '15 at 07:31
0

Please take a look at: Cutting of unused frequencies in specgram matplotlib

Here's a version of the above with different arguments illustrate their effects:

from pylab import *
    from matplotlib import *
    # 100, 200, and 400 Hz sine 'wave'
    # Using more sample points
    dt = 0.00005
    t = arange(0.0, 20.000, dt)
    s1 = sin(2*pi*100*t)
    s2 = 2*sin(2*pi*400*t)
    s3 = 2*sin(2*pi*200*t)

# create a transient "chirp"
mask = where(logical_and(t>10, t<12), 1.0, 0.0)
s2 = s2 * mask

# add some noise into the mix
nse = 0.01*randn(len(t))

x = s1 + s2 + +s3 + nse # the signal
#x = s1 + s2 + nse # the signal
# Longer window
NFFT = 2048       # the length of the windowing segments
Fs = int(1.0/dt)  # the sampling frequency

# modified specgram()
def my_specgram(x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
             window=mlab.window_hanning, noverlap=128,
             cmap=None, xextent=None, pad_to=None, sides='default',
             scale_by_freq=None, minfreq = None, maxfreq = None, **kwargs):
    """
    call signature::

      specgram(x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
               window=mlab.window_hanning, noverlap=128,
               cmap=None, xextent=None, pad_to=None, sides='default',
               scale_by_freq=None, minfreq = None, maxfreq = None, **kwargs)

    Compute a spectrogram of data in *x*.  Data are split into
    *NFFT* length segments and the PSD of each section is
    computed.  The windowing function *window* is applied to each
    segment, and the amount of overlap of each segment is
    specified with *noverlap*.

    %(PSD)s

      *Fc*: integer
        The center frequency of *x* (defaults to 0), which offsets
        the y extents of the plot to reflect the frequency range used
        when a signal is acquired and then filtered and downsampled to
        baseband.

      *cmap*:
        A :class:`matplotlib.cm.Colormap` instance; if *None* use
        default determined by rc

      *xextent*:
        The image extent along the x-axis. xextent = (xmin,xmax)
        The default is (0,max(bins)), where bins is the return
        value from :func:`mlab.specgram`

      *minfreq, maxfreq*
        Limits y-axis. Both required

      *kwargs*:

        Additional kwargs are passed on to imshow which makes the
        specgram image

      Return value is (*Pxx*, *freqs*, *bins*, *im*):

      - *bins* are the time points the spectrogram is calculated over
      - *freqs* is an array of frequencies
      - *Pxx* is a len(times) x len(freqs) array of power
      - *im* is a :class:`matplotlib.image.AxesImage` instance

    Note: If *x* is real (i.e. non-complex), only the positive
    spectrum is shown.  If *x* is complex, both positive and
    negative parts of the spectrum are shown.  This can be
    overridden using the *sides* keyword argument.

    **Example:**

    .. plot:: mpl_examples/pylab_examples/specgram_demo.py

    """

    #####################################
    # modified  axes.specgram() to limit
    # the frequencies plotted
    #####################################

    # this will fail if there isn't a current axis in the global scope
    ax = gca()
    Pxx, freqs, bins = mlab.specgram(x, NFFT, Fs, detrend,
         window, noverlap, pad_to, sides, scale_by_freq)

    # modified here
    #####################################
    if minfreq is not None and maxfreq is not None:
        Pxx = Pxx[(freqs >= minfreq) & (freqs <= maxfreq)]
        freqs = freqs[(freqs >= minfreq) & (freqs <= maxfreq)]
    #####################################

    Z = 10. * np.log10(Pxx)
    Z = np.flipud(Z)

    if xextent is None: xextent = 0, np.amax(bins)
    xmin, xmax = xextent
    freqs += Fc
    extent = xmin, xmax, freqs[0], freqs[-1]
    im = ax.imshow(Z, cmap, extent=extent, **kwargs)
    ax.axis('auto')

    return Pxx, freqs, bins, im

# plot
ax1 = subplot(211)
plot(t, x)
subplot(212, sharex=ax1)
# Windowing+greater overlap + limiting bandwidth to plot:
# the minfreq and maxfreq args will limit the frequencies 
Pxx, freqs, bins, im = my_specgram(x, NFFT=NFFT, Fs=Fs, noverlap=2000, window=numpy.kaiser(NFFT,1.0), cmap=cm.gist_heat, minfreq = 0, maxfreq = 1000)
show()
close()

Community
  • 1
  • 1