19from typing
import Optional, Tuple
23 from pyfftw.interfaces
import scipy_fftpack
as fftpack
25 from scipy
import fftpack
28from lal
import LIGOTimeGPS
40 psd: lal.REAL8FrequencySeries,
46 Compute the phase response of zero-latency whitening filter
47 given a reference PSD.
54 f_psd = psd.f0 + numpy.arange(len(psd.data.data)) * psd.deltaF
55 horizon_distance = HorizonDistance(f_low, f_psd[-1], psd.deltaF, m1, m2)
56 f_model, model= horizon_distance(psd, 1.)[1]
60 kmin, kmax = f_psd.searchsorted(f_model[0]), f_psd.searchsorted(f_model[-1]) + 1
63 unit_snr2_density = numpy.zeros_like(phase)
64 unit_snr2_density[kmin:kmax] = model / psd.data.data[kmin:kmax]
74 unit_snr2_density = unit_snr2_density**(1./16)
75 unit_snr2_density /= unit_snr2_density.max()
83 psd: lal.REAL8FrequencySeries,
84 invert: Optional[bool] =
True,
85 nyquist: Optional[float] =
None
86 ) -> Tuple[numpy.ndarray, int, int]:
88 Compute an acausal finite impulse-response filter kernel
89 from a power spectral density conforming to the LAL
90 normalization convention, such that if colored Gaussian
91 random noise with the given PSD is fed into an FIR filter
92 using the kernel the filter's output will be zero-mean
93 unit-variance Gaussian random noise. The PSD must be
94 provided as a lal.REAL8FrequencySeries object.
96 The phase response of this filter is 0, just like whitening
97 done in the frequency domain.
101 lal.REAL8FrequencySeries, the reference PSD
103 bool, default true, whether to invert the kernel
105 float, disabled by default, whether to change
106 the Nyquist frequency.
109 Tuple[numpy.ndarray, int, int], the kernel, latency,
110 sample rate pair. The kernel is a numpy array containing
111 the filter kernel, the latency is the filter latency in
112 samples and the sample rate is in Hz. The kernel and
113 latency can be used, for example, with gstreamer's stock
114 audiofirfilter element.
127 data = psd.data.data / 2
128 sample_rate = 2 * int(round(psd.f0 + (len(data) - 1) * psd.deltaF))
141 if nyquist
is not None:
142 i = int(round((nyquist - psd.f0) / psd.deltaF))
145 sample_rate = 2 * int(round(psd.f0 + (len(data) - 1) * psd.deltaF))
152 data[0] = data[-1] = 0.0
154 data_nonzeros = (data != 0.)
155 data[data_nonzeros] = 1./data[data_nonzeros]
157 tmp = numpy.zeros((2 * len(data) - 1,), dtype = data.dtype)
158 tmp[len(data)-1:] = data
162 kernel_fseries = lal.CreateCOMPLEX16FrequencySeries(
163 name =
"double sided psd",
164 epoch = LIGOTimeGPS(0),
168 sampleUnits = lal.Unit(
"strain s")
171 kernel_tseries = lal.CreateCOMPLEX16TimeSeries(
172 name =
"timeseries of whitening kernel",
173 epoch = LIGOTimeGPS(0.),
175 deltaT = 1.0 / sample_rate,
177 sampleUnits = lal.Unit(
"strain")
182 self.
revplan = lal.CreateReverseCOMPLEX16FFTPlan(len(data), 1)
184 kernel_fseries.data.data = numpy.sqrt(data) + 0.j
185 lal.COMPLEX16FreqTimeFFT(kernel_tseries, kernel_fseries, self.
revplan)
186 kernel = kernel_tseries.data.data.real
187 kernel = numpy.roll(kernel, (len(data) - 1) // 2) / sample_rate * 2
194 norm_before = numpy.dot(kernel, kernel)
195 kernel *= lal.CreateTukeyREAL8Window(len(data), .5).data.data
196 kernel *= math.sqrt(norm_before / numpy.dot(kernel, kernel))
202 latency = (len(data) - 1) // 2
208 return kernel, latency, sample_rate
213 linear_phase_kernel: numpy.ndarray,
215 ) -> Tuple[numpy.ndarray, numpy.ndarray]:
217 Compute the minimum-phase response filter (zero latency)
218 associated with a linear-phase response filter (latency
219 equal to half the filter length).
221 From "Design of Optimal Minimum-Phase Digital FIR Filters
222 Using Discrete Hilbert Transforms", IEEE Trans. Signal
223 Processing, vol. 48, pp. 1491-1495, May 2000.
227 numpy.ndarray, the kernel to compute the minimum-phase kernel with
232 Tuple[numpy.ndarray. numpy.ndarray], the kernel and the phase response.
233 The kernel is a numpy array containing the filter kernel. The kernel
234 can be used, for example, with gstreamer's stock audiofirfilter element.
243 self.
fwdplan = lal.CreateForwardCOMPLEX16FFTPlan(len(linear_phase_kernel), 1)
245 self.
revplan = lal.CreateReverseCOMPLEX16FFTPlan(len(linear_phase_kernel), 1)
247 deltaT = 1. / sample_rate
248 deltaF = 1. / (len(linear_phase_kernel) * deltaT)
249 working_length = len(linear_phase_kernel)
251 kernel_tseries = lal.CreateCOMPLEX16TimeSeries(
252 name =
"timeseries of whitening kernel",
253 epoch = LIGOTimeGPS(0.),
256 length = working_length,
257 sampleUnits = lal.Unit(
"strain")
259 kernel_tseries.data.data = linear_phase_kernel
261 absX = lal.CreateCOMPLEX16FrequencySeries(
263 epoch = LIGOTimeGPS(0),
266 length = working_length,
267 sampleUnits = lal.Unit(
"strain s")
270 logabsX = lal.CreateCOMPLEX16FrequencySeries(
272 epoch = LIGOTimeGPS(0),
275 length = working_length,
276 sampleUnits = lal.Unit(
"strain s")
279 cepstrum = lal.CreateCOMPLEX16TimeSeries(
281 epoch = LIGOTimeGPS(0.),
284 length = working_length,
285 sampleUnits = lal.Unit(
"strain")
288 theta = lal.CreateCOMPLEX16FrequencySeries(
290 epoch = LIGOTimeGPS(0),
293 length = working_length,
294 sampleUnits = lal.Unit(
"strain s")
297 min_phase_kernel = lal.CreateCOMPLEX16TimeSeries(
298 name =
"min phase kernel",
299 epoch = LIGOTimeGPS(0.),
302 length = working_length,
303 sampleUnits = lal.Unit(
"strain")
306 lal.COMPLEX16TimeFreqFFT(absX, kernel_tseries, self.
fwdplan)
307 absX.data.data[:] = abs(absX.data.data)
314 logabsX.data.data[:] = numpy.log(absX.data.data)
315 lal.COMPLEX16FreqTimeFFT(cepstrum, logabsX, self.
revplan)
321 cepstrum.data.data[0] = 0.
322 cepstrum.data.data[working_length // 2] = 0.
323 cepstrum.data.data[working_length // 2 + 1:] = -cepstrum.data.data[working_length // 2 + 1:]
329 lal.COMPLEX16TimeFreqFFT(theta, cepstrum, self.
fwdplan)
337 theta_data = theta.data.data[working_length // 2:]
339 phase = -theta_data.imag
350 phase_adjustment = numpy.concatenate((phase_adjustment[1:][-1::-1].conj(), phase_adjustment))
357 theta.data.data += -1.j * phase_adjustment
366 absX.data.data *= numpy.exp(theta.data.data)
367 lal.COMPLEX16FreqTimeFFT(min_phase_kernel, absX, self.
revplan)
369 kernel = min_phase_kernel.data.data.real
376 kernel = kernel[-1::-1]
385def fir_whitener_kernel(
389 psd: lal.REAL8FrequencySeries
390) -> lal.COMPLEX16FrequencySeries:
391 """Create an FIR whitener kernel.
400 fwdplan_kernel = lal.CreateForwardCOMPLEX16FFTPlan(length, 1)
401 kernel_tseries = lal.CreateCOMPLEX16TimeSeries(
402 name =
"timeseries of whitening kernel",
403 epoch = LIGOTimeGPS(0.),
405 deltaT = 1.0 / sample_rate,
407 sampleUnits = lal.Unit(
"strain")
409 kernel_fseries = lal.CreateCOMPLEX16FrequencySeries(
410 name =
"freqseries of whitening kernel",
411 epoch = LIGOTimeGPS(0),
413 deltaF = 1.0 / duration,
415 sampleUnits = lal.Unit(
"strain s")
424 (kernel, latency, fir_rate) = psd_fir_kernel.psd_to_linear_phase_whitening_fir_kernel(psd, nyquist = sample_rate / 2.0)
425 (kernel, theta) = psd_fir_kernel.linear_phase_fir_kernel_to_minimum_phase_whitening_fir_kernel(kernel, fir_rate)
426 kernel = kernel[-1::-1]
429 if len(kernel) < length:
430 kernel = numpy.append(kernel, numpy.zeros(length - len(kernel)))
432 kernel = kernel[:length]
434 kernel_tseries.data.data = kernel
440 lal.COMPLEX16TimeFreqFFT(kernel_fseries, kernel_tseries, fwdplan_kernel)
442 return kernel_fseries
445def one_second_highpass_kernel(rate: int, cutoff: int = 12) -> numpy.ndarray:
446 """Create a one second high-pass kernel.
450 int, the sampling rate
452 int, the high-pass cutoff
455 numpy.ndarray, the high-passed kernel
458 highpass_filter_fd = numpy.ones(rate, dtype=complex)
459 highpass_filter_fd[:int(cutoff)] = 0.
460 highpass_filter_fd[-int(cutoff):] = 0.
461 highpass_filter_fd[(rate // 2 - 1):(rate // 2 + 1)] = 0.
462 highpass_filter_td = fftpack.ifft(highpass_filter_fd)
463 highpass_filter_td = numpy.roll(highpass_filter_td.real, rate // 2)
464 highpass_filter_kernel = numpy.zeros(len(highpass_filter_td) + 1)
465 highpass_filter_kernel[:-1] = highpass_filter_td[:]
466 x = numpy.arange(len(highpass_filter_kernel))
468 highpass_filter_kernel *= 1. - (x - mid)**2 / mid**2
469 return highpass_filter_kernel
472def fixed_duration_bandpass_kernel(
475 fhigh: float = numpy.inf,
476 duration: float = 1.0
478 """Create a fixed-duration band-pass kernel.
482 int, the sampling rate
484 float, default 0, the low frequency of the pass band
486 float, default +inf, the high frequency of the pass band
488 float, default 1.0, the duration of the kernel
491 numpy.ndarray, the band-passed kernel
494 deltaF = 1. / duration
495 nsamps = int(rate * duration) + 1
496 f = numpy.arange(nsamps) * deltaF - rate / 2.
497 filt = numpy.ones(len(f))
498 ix1 = numpy.logical_and(f <= -flow, f >= -fhigh)
499 ix2 = numpy.logical_and(f >= flow, f <= fhigh)
500 filt[numpy.logical_not(numpy.logical_or(ix1, ix2))] = 0.
501 filt = numpy.real(fftpack.ifft(fftpack.ifftshift(filt))) / nsamps
502 window = numpy.sinc(2 * f / rate)
503 out = numpy.roll(filt, nsamps // 2) * window
504 out /= (out**2).sum()**.5
Tuple[numpy.ndarray, numpy.ndarray] linear_phase_fir_kernel_to_minimum_phase_whitening_fir_kernel(self, numpy.ndarray linear_phase_kernel, int sample_rate)
None set_phase(self, lal.REAL8FrequencySeries psd, float f_low=10.0, float m1=1.4, float m2=1.4)
Tuple[numpy.ndarray, int, int] psd_to_linear_phase_whitening_fir_kernel(self, lal.REAL8FrequencySeries psd, Optional[bool] invert=True, Optional[float] nyquist=None)