def parse_filter(args, analog=False, sample_rate=None): """Parse arbitrary input args into a TF or ZPK filter definition Parameters ---------- args : `tuple`, `~scipy.signal.lti` filter definition, normally just captured positional ``*args`` from a function call analog : `bool`, optional `True` if filter definition has analogue coefficients sample_rate : `float`, optional sampling frequency at which to convert analogue filter to digital via bilinear transform, required if ``analog=True`` Returns ------- ftype : `str` either ``'ba'`` or ``'zpk'`` filt : `tuple` the filter components for the returned `ftype`, either a 2-tuple for with transfer function components, or a 3-tuple for ZPK """ if analog and not sample_rate: raise ValueError("Must give sample_rate frequency to convert " "analog filter to digital") # unpack filter if isinstance(args, tuple) and len(args) == 1: # either packed defintion ((z, p, k)) or simple definition (lti,) args = args[0] # parse FIR filter if isinstance(args, numpy.ndarray) and args.ndim == 1: # fir b, a = args, [1.] if analog: return 'ba', signal.bilinear(b, a) return 'ba', (b, a) # parse IIR filter if isinstance(args, LinearTimeInvariant): lti = args elif (isinstance(args, numpy.ndarray) and args.ndim == 2 and args.shape[1] == 6): lti = signal.lti(*signal.sos2zpk(args)) else: lti = signal.lti(*args) # convert to zpk format lti = lti.to_zpk() # convert to digital components if analog: return 'zpk', bilinear_zpk(lti.zeros, lti.poles, lti.gain, fs=sample_rate) # return zpk return 'zpk', (lti.zeros, lti.poles, lti.gain)
def plot_sos(sos, worN=5000, fs=44.1e3, show=True): """ Visualize a second order system. --------------------------------------------------------------------- INPUTS --------------------------------------------------------------------- sos | as specified in scipy.signal --------------------------------------------------------------------- otherwise, see plot_zpk --------------------------------------------------------------------- """ z, p, k = sig.sos2zpk(sos) plot_zpk(z, p, k, worN=worN, fs=fs, show=show)
filer_str += (q_format_str + '(' +str(-sos[section_index, coef_index]) + '),') filer_str += ("\n") for section_index in range(sos.shape[0], max_biquad_order): for coef_index in range(5): filer_str += ('0.0,') filer_str += ("},\n") print (abs(z),abs(p)) q_sos = np.zeros(sos.shape) one =np.int64(1<<(q_factor+1)) + 1 for section_index in range(sos.shape[0]): for coef_index in range(sos.shape[1]): q_sos[section_index, coef_index]= np.float64(int(one*sos[section_index, coef_index])>>1) / np.float64(np.int64(1<<(q_factor))) try: z,p,k= signal.sos2zpk(q_sos) print (abs(z),abs(p)) print "good" except: print "bad" filer_str += ("},\n") q_factor_str += ("},\n") biquad_orders_str += ("},\n") filer_str += "};\n" q_factor_str += "};\n" biquad_orders_str+= "};\n" impl_file.write(filer_str) impl_file.write(biquad_orders_str)
def fil_convert(fil_dict, format_in): """ Convert between poles / zeros / gain, filter coefficients (polynomes) and second-order sections and store all formats not generated by the filter design routine in the passed dictionary 'fil_dict'. Parameters ---------- fil_dict : dictionary filter dictionary format_in : string or set of strings format(s) generated by the filter design routine. Must be one of 'zpk': [z,p,k] where z is the array of zeros, p the array of poles and k is a scalar with the gain 'ba' : [b, a] where b and a are the polynomial coefficients 'sos' : a list of second order sections """ if 'zpk' in format_in: # z, p, k have been generated,convert to other formats zpk = fil_dict['zpk'] if 'ba' not in format_in: fil_dict['ba'] = sig.zpk2tf(zpk[0], zpk[1], zpk[2]) if 'sos' not in format_in and SOS_AVAIL: try: fil_dict['sos'] = sig.zpk2sos(zpk[0], zpk[1], zpk[2]) except ValueError: fil_dict['sos'] = 'None' print( "WARN (pyfda_lib): Complex-valued coefficients, could not convert to SOS." ) elif 'sos' in format_in and SOS_AVAIL: if 'zpk' not in format_in: fil_dict['zpk'] = list(sig.sos2zpk(fil_dict['sos'])) # check whether sos conversion has created a additional (superfluous) # pole and zero at the origin and delete them: z_0 = np.where(fil_dict['zpk'][0] == 0)[0] p_0 = np.where(fil_dict['zpk'][1] == 0)[0] if p_0 and z_0: # eliminate z = 0 and p = 0 from list: fil_dict['zpk'][0] = np.delete(fil_dict['zpk'][0], z_0) fil_dict['zpk'][1] = np.delete(fil_dict['zpk'][1], p_0) if 'ba' not in format_in: fil_dict['ba'] = list(sig.sos2tf(fil_dict['sos'])) # check whether sos conversion has created additional (superfluous) # highest order polynomial with coefficient 0 and delete them if fil_dict['ba'][0][-1] == 0 and fil_dict['ba'][1][-1] == 0: fil_dict['ba'][0] = np.delete(fil_dict['ba'][0], -1) fil_dict['ba'][1] = np.delete(fil_dict['ba'][1], -1) elif 'ba' in format_in: # arg = [b,a] b, a = fil_dict['ba'][0], fil_dict['ba'][1] fil_dict['zpk'] = list(sig.tf2zpk(b, a)) if SOS_AVAIL: try: fil_dict['sos'] = sig.tf2sos(b, a) except ValueError: fil_dict['sos'] = 'None' print( "WARN (pyfda_lib): Complex-valued coefficients, could not convert to SOS." ) else: raise ValueError("Unknown input format {0:s}".format(format_in))
sos_e = signal.ellip(2 * n_ellip, ripple, stop, Fs_cut / Fs_in, btype='low', output='sos') if n_ellip > 1: k = np.sqrt(np.sum(sos_e[0, :3]**2)) k_n = k**(1 / (n_ellip - 1)) sos_e[0, :3] /= k sos_e[1:, :3] *= k_n sos = np.vstack((sos, sos_e)) sos_q = np.around((sos - 1e-9) * 2**23) / 2**23 z, p, k = signal.sos2zpk(sos) zq, pq, kq = signal.sos2zpk(sos_q) plt.figure() plt.plot(np.real(z), np.imag(z), 'o') plt.plot(np.real(p), np.imag(p), 'x') plt.plot(np.real(zq), np.imag(z), 'ro') plt.plot(np.real(pq), np.imag(p), 'rx') ucx = np.cos(np.linspace(0, 2 * np.pi, 1000)) ucy = np.sin(np.linspace(0, 2 * np.pi, 1000)) plt.plot(ucx, ucy, 'k-') plt.axis('equal') # Plot response b, a = signal.sos2tf(sos) bq, aq = signal.sos2tf(sos_q) w, h = signal.freqz(b, a=a)
w = np.logspace(np.log10(wmin), np.log10(wmax), num=num_w) # Filter design shelving_filters = [] zpks = [] H = np.zeros((len(Biquad_per_octave), num_w), dtype='complex') Gain = np.zeros(len(Biquad_per_octave)) for n, biquad_per_octave in enumerate(Biquad_per_octave): num_biquad, Gb, G = \ shelving_filter_parameters(biquad_per_octave=biquad_per_octave, Gd=Gd, BWd=BWd) Gain[n] = G sos = low_shelving_2nd_cascade(w0, Gb, num_biquad, biquad_per_octave) H[n] = sosfreqs(sos, worN=w)[1] shelving_filters.append(sos) zpks.append(sos2zpk(sos)) # desired response wl, wh = w0 * 2**(-BWd), w0 Hmag = np.clip(np.log2(w / w0) * slope, G, 0) Hmag = np.log2(w / w0) * slope # Plots Glim = -0.21, 0.21 philim = -3, 47 wlim = wmin, wmax wticks = 2**(np.arange( np.ceil(np.log2(w)[0] / 2) * 2, np.floor(np.log2(w[-1]) / 2) * 2 + 2, 2)) kw = {'lw': 2, 'alpha': 1, 'basex': 2} colors = cm.get_cmap('Blues')
zoh='Zero-Order Hold') num_freqs = 200 images_path = pathlib.Path('images/experiments/') images_path.mkdir(parents=True, exist_ok=True) for i, filter_type in enumerate(filter_list): for j, method in enumerate(method_list): # Get filter (meta)data filter_filename = f'type-{filter_type}_method-{method}.npz' filter_data = np.load(filter_filename, allow_pickle=True) sos = filter_data['sos'] sos_quant = filter_data['sos_quant'] final_spec = filter_data['final_spec'].item() discrete_system = signal.sos2zpk(sos) # Get filter experiment data experiment_filename = f'experiments/exp-{filter_type}_{method}.csv' data = pd.read_csv(experiment_filename).to_dict('series') # print(data['freq'].values, data['amp'].values) # exit(0) print( f'Analog filter: {filter_type} | Analog-to-Discrete Method: {method}' ) # Plot fig, ax = plt.subplots() # filtdesign.plot_digital(sos_quant, Qformat, fp, fs, Amax, Amin, magnitude=sinewave_amplitude, num_freqs=num_freqs, ax=ax, plot_focus='all')
# Time-domain evaluation fs = 48000 ws = 2 * np.pi * fs Lh = 1500 t = np.arange(Lh) / fs xin = unit_impulse(Lh) t = np.arange(Lh) / fs s2z = matchedz_zpk # s2z = bilinear_zpk # Analog filter H = np.zeros(num_w, dtype='complex') num_biquad, Gb, G = shelving_filter_parameters( biquad_per_octave=biquad_per_octave, slope=slope, BWd=BWd) sos_sdomain = low_shelving_2nd_cascade(w0, Gb, num_biquad, biquad_per_octave) zs, ps, ks = sos2zpk(sos_sdomain) # Digital filter zpk = s2z(zs * 2 * np.pi * fc, ps * 2 * np.pi * fc, ks, fs=fs) sos_zdomain = zpk2sos(*zpk) H = sosfreqz(sos_zdomain, worN=f, fs=fs)[1] h = sosfilt(sos_zdomain, xin) # Plots flim = fmin, fmax fticks = fc * 2.**np.arange(-8, 4, 2) fticklabels = ['7.8', '31.3', '125', '500', '2k', '8k'] fticks = 1000 * 2.**np.arange(-6, 6, 2) fticklabels = ['15.6', '62.5', '250', '1k', '4k', '16k'] kw = dict(c='C0', lw=2, alpha=1)
ax.plot(w, np.angle(h_t)) ax.set_xlabel('Frecuencia [Hz]') ax.set_ylabel('Fase [rad]') ax.grid(which='both', axis='both') ax = fig.add_subplot(313) ax.plot(t, im_min_n) ax.plot(t, im_min_err) ax.plot(t, im_t) ax.set_xlabel('Tiempo [s]') ax.set_ylabel('Amplitud [1]') ax.grid(which='both', axis='both') zeros_o, poles_o, gains_o = [], [], [] for iir in pool: z, p, k = signal.sos2zpk(fwliir.iir2sos(iir)) zeros_o.extend(z) poles_o.extend(p) gains_o.append(k) zeros_t, poles_t, gain_t = signal.tf2zpk(b_t, a_t) zeros_min_n, poles_min_n, gain_min_n = signal.sos2zpk(sos_min_n) zeros_min_err, poles_min_err, gain_min_err = signal.sos2zpk(sos_min_err) fig = plt.figure() ax = fig.add_subplot(311) # ax.scatter(x=np.real(zeros_o), y=np.imag(zeros_o), color='yellow') ax.scatter(x=np.real(zeros_t), y=np.imag(zeros_t), color='blue') ax.scatter(x=np.real(zeros_min_err), y=np.imag(zeros_min_err), color='red') ax.scatter(x=np.real(zeros_min_n), y=np.imag(zeros_min_n), color='green') ax.axis([-2, 2, -2, 2]) ax = fig.add_subplot(312)
# frequency response fig, ax = plt.subplots(figsize=(10, 4), ncols=2, gridspec_kw={'wspace': 0.1}) ax[0].semilogx(w, db(H_biquads.T), c='gray', **kw, zorder=2) ax[0].semilogx(w, Hmag, 'k:', **kw) ax[0].semilogx(w, db(H), c='C0', **kw, zorder=3) ax[1].plot([-1, 0], [1, 0], 'C7:', lw=1) # Pole zero plot kw_z = dict(c='C0', marker='o', ms=9, ls='none', mew=1, mfc='none', alpha=1) kw_p = dict(c='k', marker='x', ms=9, ls='none', mew=1) kw_dot = dict(marker='.', ms=10) ylim = ax[0].get_ylim() for n, sosi in enumerate(sos): z, p, _ = sos2zpk(sosi[np.newaxis, :]) z, p = z[0], p[0] wc = np.abs(np.sqrt(z * p)) ax[0].plot((wc, wc), (-3, 0), **kw_dotted) ax[0].plot(wc, Gb / 2, c='gray', zorder=2, **kw_dot) ax[1].plot(wc * -np.cos(np.pi / 4), wc * np.sin(np.pi / 4), c='gray', **kw_dot) ax[1].plot(np.real(p), np.imag(p), **kw_p) ax[1].plot(np.real(z), np.imag(z), **kw_z) circle = plt.Circle(xy=(0, 0), radius=wc, facecolor='none', **kw_artist) ax[1].add_artist(circle) ax[0].plot(np.sqrt(wh * wl), G / 2, 'C0', **kw_dot)
num_freqs = 200 images_path = pathlib.Path('images') images_path.mkdir(parents=True, exist_ok=True) for i, filter_type in enumerate(filter_list): for j, method in enumerate(method_list): # Get filter (meta)data filter_filename = f'type-{filter_type}_method-{method}.npz' filter_data = np.load(filter_filename, allow_pickle=True) sos = filter_data['sos'] sos_quant = filter_data['sos_quant'] final_spec = filter_data['final_spec'].item() discrete_system = signal.sos2zpk(sos) print(f'Analog filter: {filter_type} | Analog-to-Discrete Method: {method}') # print(f'Biquads:\n', sos) # print(f'Quantized biquads (Q{Qformat[0]}.{Qformat[1]}):\n', np.round(sos_quant * 2 ** Qformat[1]).astype(int)) print_biquads(sos_quant) zd, pd, kd = signal.sos2zpk(sos) zq, pq, kq = signal.sos2zpk(sos_quant) # Plot fig, ax = plt.subplots() unit_circle = patches.Circle((0,0), radius=1, fill=False, color='black', ls='solid', alpha=0.1) ax.add_patch(unit_circle)