ASCIIMath creating images

Sunday, May 7, 2017

NNResample package on PyPI

In the previous post, I described my filter generation method for scipy.signal.resample_poly, where the filter is designed such that the first null if the filter is on Nyquist.  For easier inclusion into my own projects, I made it into a package as a self-standing function, and then decided to make that my first submission to PyPI.  The package depends only on numpy and scipy>=0.18.0.

So, if you want to use my resampler, you can now simply do `pip install nnresample`. To report bugs, the link to the repository is https://github.com/jthiem/nnresample.

I've included one "special feature": since it takes a little time to generate the filter (to search for the null and regenerate the filter afterwards) the generated filter is cached in a global for re-use.

As for the name, since I'm calling it "Null-on-Nyquist Resampler", the obvious name should be non-resample, but I think that could be confusing...

Wednesday, May 3, 2017

Resampling in Python: Electric Bugaloo

In a previous post, I looked at some sample rate conversion methods for Python, at least for audio data.  I did some more digging into it, and thanks to a note by Prof. Christian Muenker from the Munich University of Applied Sciences I was made aware of scipy.signal.resample_poly (new in scipy since 0.18.0).  This lead me down a bit of a rabbit-hole and I ended up with a Jupyter Notebook which I'm not going to copy-paste here since there is quite a bit of code and some LaTeX in there.  Here is the link to it instead.

For the impatient, here are the interesting bits:

def resample_poly_filter(up, down, beta=5.0, L=16001):
    
    # *** this block STOLEN FROM scipy.signal.resample_poly ***
    # Determine our up and down factors
    # Use a rational approximation to save computation time on really long
    # signals
    g_ = gcd(up, down)
    up //= g_
    down //= g_
    max_rate = max(up, down)

    sfact = np.sqrt(1+(beta/np.pi)**2)
            
    # generate first filter attempt: with 6dB attenuation at f_c.
    filt = firwin(L, 1/max_rate, window=('kaiser', beta))
    
    N_FFT = 2**19
    NBINS = N_FFT/2+1
    paddedfilt = np.zeros(N_FFT)
    paddedfilt[:L] = filt
    ffilt = np.fft.rfft(paddedfilt)
    
    # now find the minimum between f_c and f_c+sqrt(1+(beta/pi)^2)/L
    bot = int(np.floor(NBINS/max_rate))
    top = int(np.ceil(NBINS*(1/max_rate + 2*sfact/L)))
    firstnull = (np.argmin(np.abs(ffilt[bot:top])) + bot)/NBINS
    
    # generate the proper shifted filter
    filt2 = firwin(L, -firstnull+2/max_rate, window=('kaiser', beta))
    
    return filt2

plt.figure(figsize=(15,3))
wfilt = resample_poly_filter(P, Q, L=2**16+1)
plt.specgram(scipy_signal.resample_poly(sig, P, Q, window=wfilt)*30, scale='dB', Fs=P, NFFT=256)
plt.colorbar()
plt.axis((0,2,0,Q/2))

Recycling my test sweep from the previous post, I get:
Sweep resampled using my own filter
But really, please read the Notebook.  Comments are welcome!