Why is the number of frequencies decomposed in scipy.signal.stft() equal to the hop size? I asked this over on StackOverflow first but figured you fine people might know more about signal processing (duh).

I'm trying to calculate the spectrogram of an audio file in python. There's a nice library function for doing this in scipy.signal.stft(), but it's not behaving as I expect it to.

Since a spectrogram is just a Short-time Fourier Transform, I expect the library function to chop my audio file up into segments ('time frames'), and then do a Fourier transform on each time frame.

This is what the function does as far as I can tell, but the size of the output matrix is behaving strangely. I'll explain:

The parameters you can specify for the function is nperseg and noverlap, which are the number of audio samples in each time frame and how much each time frame should overlap with the previous respectively. From this, we can caluclate the hop size, i.e. how many samples should be between each time frame: H = nperseg - noverlap.

This is where the strangeness comes in. The number of frequencies 'measured' in the Fourier transform of each time frame is exactly equal to the hop size, and it's always evenly spaced between 0 Hz and half the audio sampling frequency.

Say I have an audio file sampled at 22 kHz and I've set the function parameters so that the hop size is, say, 5 audio samples between each time frame. Then the output will show 5 different frequencies evenly spaced between 0-11 kHz, like this: [0kHz  2.75kHz  5.5kHz  8.75kHz  11kHz]. 

Why only these 5? What if I want to have a hop size of 5 but also see what the Fourier transform looks like for more frequencies? What if I want to have a hop size of 706 samples and 149 measured frequencies between 50 kHz and 65 kHz? Is this impossible?
The number of frequencies 'measured' in the Fourier transform of each time frame is exactly equal to the hop size, 


The number of frequencies is not determined by the hop size, it's determined by the FFT size.  If you don't specify the FFT size, it uses nperseg as the FFT size.


  and it's always evenly spaced between 0 Hz 


Yes, FFT is always evenly spaced, and always starts at 0 Hz.  It doesn't implement Zoom FFT (since it's meant primarily for reconstruction with ISTFT, and Zoom FFT would throw away the required information).


  and half the audio sampling frequency.


Yes, your input is real, so the negative frequencies are redundant, so they are not returned.   nperseg is 100, so nfft is 100, so it returns 100//2+1=51 frequencies (including 0 and Nyquist).

If you give it a complex input, then len(f) will equal nperseg because it returns both the positive and negative frequencies.


  What if I want to have a hop size of 706 samples and 149 measured frequencies between 50 kHz and 65 kHz?


It doesn't support Zoom FFT, so, assuming your sample rate is 65*2 = 130 kHz, you'd need to calculate how many samples you want across the entire bandwidth (= 1292?) and enter that as nfft, and specify noverlap = nperseg - hop, and then ignore the frequencies below 50 kHz that are returned.  

Note that specifying nfft > nperseg returns redundant interpolated information that isn't necessary for reconstruction.