¿Cómo escribir un filtro de paso bajo para la señal muestreada en Python?

16

Tengo alguna señal que muestrea cada 1 ns (1e-9 segundos) y tengo, digamos, 1e4 puntos. Necesito filtrar altas frecuencias de esta señal. Digamos que necesito filtrar frecuencias superiores a 10 MHz. Quiero que para frecuencias más bajas que la señal de frecuencia de corte se pase sin cambios. Significa que la ganancia del filtro será 1 para frecuencias inferiores a la frecuencia de corte. Me gustaría poder especificar el orden del filtro. Quiero decir, el filtro de primer orden tiene una pendiente de 20 db / década (reducción de potencia) después de la frecuencia de corte, el filtro de segundo orden tiene una pendiente de 40 db / dec después de la frecuencia de corte y así sucesivamente. El alto rendimiento del código es importante.

Alex
fuente

Respuestas:

19

La respuesta de frecuencia para el filtro diseñado con la función de mantequilla es:

Respuesta del filtro de Butterworth

Pero no hay razón para limitar el filtro a un diseño de filtro monotónico constante. Si desea una mayor atenuación en la banda de parada y la banda de transición más pronunciada, existen otras opciones. Para obtener más información sobre cómo especificar un filtro mediante iirdesing, consulte esto . Como se muestra en las gráficas de respuesta de frecuencia para el diseño de mantequilla , la frecuencia de corte (punto de -3dB) está lejos del objetivo. Esto puede aliviarse mediante un muestreo descendente antes del filtrado (las funciones de diseño tendrán dificultades con un filtro tan estrecho, el 2% del ancho de banda). Veamos cómo filtrar la frecuencia de muestreo original con el límite especificado.

import numpy as np
from scipy import signal
from matplotlib import pyplot as plt

from scipy.signal import fir_filter_design as ffd
from scipy.signal import filter_design as ifd

# setup some of the required parameters
Fs = 1e9           # sample-rate defined in the question, down-sampled

# remez (fir) design arguements
Fpass = 10e6       # passband edge
Fstop = 11.1e6     # stopband edge, transition band 100kHz
Wp = Fpass/(Fs)    # pass normalized frequency
Ws = Fstop/(Fs)    # stop normalized frequency

# iirdesign agruements
Wip = (Fpass)/(Fs/2)
Wis = (Fstop+1e6)/(Fs/2)
Rp = 1             # passband ripple
As = 42            # stopband attenuation

# Create a FIR filter, the remez function takes a list of 
# "bands" and the amplitude for each band.
taps = 4096
br = ffd.remez(taps, [0, Wp, Ws, .5], [1,0], maxiter=10000) 

# The iirdesign takes passband, stopband, passband ripple, 
# and stop attenuation.
bc, ac = ifd.iirdesign(Wip, Wis, Rp, As, ftype='ellip')  
bb, ab = ifd.iirdesign(Wip, Wis, Rp, As, ftype='cheby2') 

Filtros de frecuencia de muestreo originales

Como se mencionó, debido a que estamos tratando de filtrar un porcentaje tan pequeño del ancho de banda, el filtro no tendrá un corte brusco. En este caso, filtro de paso bajo, podemos reducir el ancho de banda para obtener un filtro más atractivo. La función de remuestreo python / scipy.signal se puede usar para reducir el ancho de banda.

Tenga en cuenta que la función de remuestreo realizará el filtrado para evitar el alias. El prefiltrado también se puede realizar (para reducir el aliasing) y en este caso podríamos simplemente volver a muestrear en 100 y listo , pero se hizo la pregunta sobre la creación de filtros. Para este ejemplo, reduciremos la muestra en 25 y crearemos un nuevo filtro

R = 25;            # how much to down sample by
Fsr = Fs/25.       # down-sampled sample rate
xs = signal.resample(x, len(x)/25.)

Si actualizamos los parámetros de diseño para el filtro FIR, la nueva respuesta es.

# Down sampled version, create new filter and plot spectrum
R = 25.             # how much to down sample by
Fsr = Fs/R          # down-sampled sample rate
Fstop = 11.1e6      # modified stopband
Wp = Fpass/(Fsr)    # pass normalized frequency
Ws = Fstop/(Fsr)    # stop normalized frequency
taps = 256
br = ffd.remez(taps, [0, Wp, Ws, .5], [1,0], maxiter=10000) 

Respuesta de filtro disminuida

El filtro que opera en los datos muestreados abajo tiene una mejor respuesta. Otro beneficio de usar un filtro FIR es que tendrá una respuesta de fase lineal.

Christopher Felton
fuente
1
Gracias. ¿Cómo se crea el gráfico del espectro de señal?
Alex
Muchas gracias por una excelente respuesta! Me pregunto si podría explicar cómo aplicar un filtro FIR basado en los coeficientes calculados usando Remez. Tengo problemas para entender lo que filtfiltquiere para el aparámetro.
ali_m
Una vez que tenga los coeficientes de un filtro de diseño, ( b FIR B y una de IIR) se puede usado un par de funciones diferentes para realizar el filtrado: lfilter , convolución , filtfilt . Por lo general, todas estas funciones funcionan de manera similar: y = filtfilt (b, a, x) Si tiene un filtro FIR, simplemente configure a = 1 , x es la señal de entrada, b son los coeficientes FIR. Esta publicación también podría ayudar.
Christopher Felton
5

¿Esto funciona?

from __future__ import division
from scipy.signal import butter, lfilter

fs = 1E9 # 1 ns -> 1 GHz
cutoff = 10E6 # 10 MHz
B, A = butter(1, cutoff / (fs / 2), btype='low') # 1st order Butterworth low-pass
filtered_signal = lfilter(B, A, signal, axis=0)

Sin embargo, tiene razón, la documentación no está muy completa. Parece que butteres un contenedor para iirfilter, que está mejor documentado :

N: int El orden del filtro. Wn: array_like Una secuencia escalar o de longitud 2 que proporciona las frecuencias críticas.

Sin embargo, la mayoría de estas cosas están clonadas de matlab, por lo que también puede consultar su documentación :

la frecuencia de corte normalizada Wn debe ser un número entre 0 y 1, donde 1 corresponde a la frecuencia de Nyquist, π radianes por muestra.

Actualizar:

Agregué documentación para estas funciones. :) Github lo hace fácil.

endolito
fuente
1

No estoy seguro de cuál es su aplicación, pero puede consultar Gnuradio: http://gnuradio.org/doc/doxygen/classgr__firdes.html

Los bloques de procesamiento de señal están escritos en C ++ (aunque los gráficos de flujo de Gnuradio están en Python), pero usted dijo que el alto rendimiento es importante.

envolturas
fuente
1

Estoy teniendo buenos resultados con este filtro FIR. Observa que aplica el filtro dos veces, yendo "hacia adelante" y "hacia atrás", para compensar el desplazamiento de la señal (la filtfiltfunción no funcionó, no sé por qué):

def firfilt(interval, freq, sampling_rate):
    nfreq = freq/(0.5*sampling_rate)
    taps =  sampling_rate + 1
    a = 1
    b = scipy.signal.firwin(taps, cutoff=nfreq)
    firstpass = scipy.signal.lfilter(b, a, interval)
    secondpass = scipy.signal.lfilter(b, a, firstpass[::-1])[::-1]
    return secondpass

ESTE es un gran recurso para filtrar el diseño y el uso, desde donde tomé este código y desde donde se pueden tomar ejemplos de filtro de paso de banda y de paso alto .

heltonbiker
fuente
No creo que haya muchos beneficios al filtrar hacia adelante y hacia atrás un filtro FIR. Un filtro IIR puede beneficiarse del avance / retroceso (filtfilt) porque puede obtener una fase lineal de un filtro de fase no lineal mediante el filtrado inverso.
Christopher Felton
2
@ChristopherFelton Acabo de invertir para sincronizar una señal electromiográfica RAW con la versión suavizada de sí misma. Sé que podría cambiar la señal, pero filtrar dos veces termina siendo menos problemático. Vale la pena notar que la segunda pasada casi no cambia la primera pasada ya filtrada ... ¡Gracias por tomar nota!
heltonbiker
Ahh si. Para eliminar la demora (demora grupal), buen punto.
Christopher Felton
1

No tengo derechos de comentario ...

@endolith: uso lo mismo que usted, excepto el uso de scipy.signal.filtfilt (B, A, x) donde x es el vector de entrada a filtrar, por ejemplo, numpy.random.normal (size = (N)) . filtfilt hace un avance y retroceso de la señal. En aras de la integridad (la mayoría es lo mismo que @endolith):

import numpy as np
import scipy.signal as sps

input = np.random.normal(size=(N)) # Random signal as example
bz, az = sps.butter(FiltOrder, Bandwidth/(SamplingFreq/2)) # Gives you lowpass Butterworth as default
output = sps.filtfilt(bz, az, input) # Makes forward/reverse filtering (linear phase filter)

filtfilt, como también lo sugiere @heltonbiker, requiere conjuntos de coeficientes, creo. En caso de que necesite realizar un filtrado de paso de banda en una banda base compleja, se necesita una configuración más complicada, pero esto no parece ser un problema aquí.

Lars1
fuente