From c4ee3bc7e9d4f71feaf8d023fc2d3ac2fba91e56 Mon Sep 17 00:00:00 2001
From: Kipp Cannon <kipp.cannon@ligo.org>
Date: Tue, 19 Jun 2018 17:01:20 +0900
Subject: [PATCH] multirate_datasrouce: enable FIR whitening phase adjustment

- allow the zero-latency whitening kernel's phase to be tweaked to approximate the phase response of the kernel corresponding to some reference PSD
---
 gstlal/python/multirate_datasource.py | 34 +++++++++++++++++++++++----
 1 file changed, 29 insertions(+), 5 deletions(-)

diff --git a/gstlal/python/multirate_datasource.py b/gstlal/python/multirate_datasource.py
index 12600bbc1f..e73b3c158a 100644
--- a/gstlal/python/multirate_datasource.py
+++ b/gstlal/python/multirate_datasource.py
@@ -66,7 +66,7 @@ except KeyError as e:
 	raise
 
 
-def mkwhitened_multirate_src(pipeline, src, rates, instrument, psd = None, psd_fft_length = 32, ht_gate_threshold = float("inf"), veto_segments = None, nxydump_segment = None, track_psd = False, block_duration = 1 * Gst.SECOND, zero_pad = None, width = 64, unit_normalize = True, statevector = None, dqvector = None):
+def mkwhitened_multirate_src(pipeline, src, rates, instrument, psd = None, psd_fft_length = 32, ht_gate_threshold = float("inf"), veto_segments = None, nxydump_segment = None, track_psd = False, block_duration = 1 * Gst.SECOND, zero_pad = None, width = 64, unit_normalize = True, statevector = None, dqvector = None, fir_whiten_reference_psd = None):
 	"""
 	Build pipeline stage to whiten and downsample h(t).
 
@@ -80,6 +80,7 @@ def mkwhitened_multirate_src(pipeline, src, rates, instrument, psd = None, psd_f
 	* veto_segments: segments to mark as gaps after whitening
 	* track_psd: decide whether to dynamically track the spectrum or use the fixed spectrum provided
 	* width: type convert to either 32 or 64 bit float
+	* fir_whiten_reference_psd: when using FIR whitener, use this PSD to define desired desired phase response
 
 	**Gstreamer graph describing this function**
 
@@ -219,7 +220,7 @@ def mkwhitened_multirate_src(pipeline, src, rates, instrument, psd = None, psd_f
 		head = pipeparts.mkfirbank(pipeline, head, fir_matrix = numpy.zeros((1, 1 + max(rates) * psd_fft_length), dtype=numpy.float64), block_stride = block_stride, time_domain = False, latency = 0)
 
 		# compute whitening kernel from PSD
-		def set_fir_psd(whiten, pspec, firbank, psd_fir_kernel):
+		def set_fir_psd(whiten, pspec, firelem, psd_fir_kernel):
 			psd_data = numpy.array(whiten.get_property("mean-psd"))
 			psd = lal.CreateREAL8FrequencySeries(
 				name = "psd",
@@ -231,9 +232,25 @@ def mkwhitened_multirate_src(pipeline, src, rates, instrument, psd = None, psd_f
 			)
 			psd.data.data = psd_data
 			kernel, latency, sample_rate = psd_fir_kernel.psd_to_linear_phase_whitening_fir_kernel(psd)
-			kernel, theta = psd_fir_kernel.linear_phase_fir_kernel_to_minimum_phase_whitening_fir_kernel(kernel, sample_rate)
-			firbank.set_property("fir-matrix", numpy.array(kernel, ndmin = 2))
-		whiten.connect_after("notify::mean-psd", set_fir_psd, head, reference_psd.PSDFirKernel())
+			kernel, phase = psd_fir_kernel.linear_phase_fir_kernel_to_minimum_phase_whitening_fir_kernel(kernel, sample_rate)
+			firelem.set_property("fir-matrix", numpy.array(kernel, ndmin = 2))
+		firkernel = reference_psd.PSDFirKernel()
+		if fir_whiten_reference_psd is not None:
+			assert fir_whiten_reference_psd.f0 == 0.
+			# interpolate the reference phase PSD if its
+			# resolution doesn't match what we'll eventually
+			# require it to be.
+			if psd_fft_length != round(1. / fir_whiten_reference_psd.deltaF):
+				fir_whiten_reference_psd = reference_psd.interpolate_psd(fir_whiten_reference_psd, 1. / psd_fft_length)
+			# confirm that the reference phase PSD's Nyquist is
+			# sufficiently high, then reduce it to the required
+			# Nyquist if needed.
+			assert (psd_fft_length * max(rates)) // 2 + 1 <= len(fir_whiten_reference_psd.data.data), "fir_whiten_reference_psd Nyquist too low"
+			if (psd_fft_length * max(rates)) // 2 + 1 < len(fir_whiten_reference_psd.data.data):
+				fir_whiten_reference_psd = lal.CutREAL8FrequencySeries(fir_whiten_reference_psd, 0, (psd_fft_length * max(rates)) // 2 + 1)
+			# set the reference phase PSD
+			firkernel.set_phase(fir_whiten_reference_psd)
+		whiten.connect_after("notify::mean-psd", set_fir_psd, head, firkernel)
 
 		# Gate after gaps
 		# FIXME the -max(rates) extra padding is for the high pass
@@ -247,6 +264,13 @@ def mkwhitened_multirate_src(pipeline, src, rates, instrument, psd = None, psd_f
 		head = pipeparts.mkchecktimestamps(pipeline, head, "%s_timestamps_fir" % instrument)
 		#head = pipeparts.mknxydumpsinktee(pipeline, head, filename = "after_mkfirbank.txt")
 	else:
+		# FIXME:  we should require fir_whiten_reference_psd to be
+		# None in this code path for safety, but that's hard to do
+		# since the calling code would need to know what
+		# environment variable is being used to select the mode,
+		# and we don't want to be duplicating that code all over
+		# the place
+
 		#
 		# add a reblock element.  the whitener's gap support isn't
 		# 100% yet and giving it smaller input buffers works around
-- 
GitLab