From 318562006c942478fe1320351bc1ca12e8e747ae Mon Sep 17 00:00:00 2001
From: Aaron Viets <aaron.viets@ligo.org>
Date: Wed, 18 Jul 2018 15:12:01 -0700
Subject: [PATCH] gstlal_compute_strain:  added ability to process witness
 channels separately for noise subtraction, if desired.

---
 gstlal-calibration/bin/gstlal_compute_strain  | 29 +++++++++++--------
 .../python/calibration_parts.py               |  4 +++
 .../tests/lal_transferfunction_test.py        | 13 +++++----
 3 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/gstlal-calibration/bin/gstlal_compute_strain b/gstlal-calibration/bin/gstlal_compute_strain
index ce52aed05c..b8c5bf484e 100755
--- a/gstlal-calibration/bin/gstlal_compute_strain
+++ b/gstlal-calibration/bin/gstlal_compute_strain
@@ -279,7 +279,7 @@ parser.add_option("--remove-powerlines", action = "store_true", help = "Set this
 parser.add_option("--powerlines-channel-name", metavar = "name", default = "PEM-EY_MAINSMON_EBAY_1_DQ", help = "Set the name of the channel used as input for 60 Hz power lines to be removed. (Default = PEM-EY_MAINSMON_EBAY_1_DQ)")
 parser.add_option("--powerlines-channel-sr", metavar = "Hz", type = int, default = 1024, help = "Sample rate of the powerlines channel (Default = 1024 Hz)")
 parser.add_option("--powerlines-freq-var", metavar = "Hz", type = float, default = 0.02, help = "Amount by which freqency of the power mains varies with time. Supposedly they are intentionally varied by +- 0.02 Hz. (Default = 0.02)")
-parser.add_option("--witness-channel-list", metavar = "name", default = None, help = "Comma-separated list of witness channels to use to subtract noise from h(t). (Default is to not use any witness channels.)")
+parser.add_option("--witness-channel-list", metavar = "name", default = None, help = "List of witness channels to use to subtract noise from h(t), given as a string. Use semicolons (;) to separate channels/groups of channels that should be processed separately. Use commas to separate channels that should be processed together. For example, --witness-channel-list=\"chan1,chan2,chan3;chan4,chan5\". This means the pipeline will process the first 3 channels together and the last 2 channels together. (Default is to not use any witness channels.)")
 parser.add_option("--witness-channel-sr", metavar = "Hz", type = int, default = 2048, help = "Sample rate at which transfer functions will be computed and witness channels will be filtered. (Default = 2048 Hz)")
 parser.add_option("--witness-channel-fft-length", metavar = "seconds", type = float, default = 1.0, help = "The length in seconds of the fast Fourier transforms used to compute transfer functions between witness channels and h(t). The fft's are windowed with Hann windows and overlapped. (Default = 1.0)")
 parser.add_option("--num-witness-ffts", type = int, default = 128, help = "The number of fft's to average for the witness -> h(t) transfer functions calculation. The average is taken after the ratio h(f) / witness(f). (Default = 128)")
@@ -692,10 +692,14 @@ if options.remove_powerlines:
 
 # If we are using witness channels to clean h(t), add those to the channel list
 if options.witness_channel_list is not None:
-	witness_channel_list = options.witness_channel_list.split(',')
-	for i in range(0, len(witness_channel_list)):
-		channel_list.append((instrument, witness_channel_list[i]))
-		headkeys.append(witness_channel_list[i])
+	witness_channel_list = []
+	split_witness_list = options.witness_channel_list.split(';')
+	for i in range(0, len(split_witness_list)):
+		witness_channels = split_witness_list[i].split(',')
+		witness_channel_list.append(witness_channels)
+		for j in range(0, len(witness_channels)):
+			channel_list.append((instrument, witness_channels[j]))
+			headkeys.append(witness_channels[j])
 
 
 ####################################################################################################
@@ -1886,13 +1890,14 @@ if options.witness_channel_list is not None:
 		clean_strain = straintee
 	if options.no_dq_vector:
 		obsreadytee = None
-	witnesses = []
-	for key in headkeys:
-		if key in witness_channel_list:
-			witnesses.append(calibration_parts.caps_and_progress(pipeline, head_dict[key], "audio/x-raw, format=F64LE, channels=1, channel-mask=(bitmask)0x0", key))
-	if len(witnesses) != len(witness_channel_list):
-		print("WARNING: Not all requested witness channels are being used")
-	clean_strain = calibration_parts.clean_data(pipeline, clean_strain, hoftsr, witnesses, options.witness_channel_sr, witness_fft_samples, witness_fft_overlap, options.num_witness_ffts, witness_tf_update_samples, witness_fir_samples, options.witness_frequency_resolution, obsready = None if options.no_dq_vector else obsreadytee, attack_length = -filter_settle_time * options.witness_channel_sr, filename = "transfer_functions.txt")
+	for i in range(0, len(witness_channel_list)):
+		witnesses = []
+		for key in headkeys:
+			if key in witness_channel_list[i]:
+				witnesses.append(calibration_parts.caps_and_progress(pipeline, head_dict[key], "audio/x-raw, format=F64LE, channels=1, channel-mask=(bitmask)0x0", key))
+		if len(witnesses) != len(witness_channel_list[i]):
+			print("WARNING: Not all requested witness channels are being used")
+		clean_strain = calibration_parts.clean_data(pipeline, clean_strain, hoftsr, witnesses, options.witness_channel_sr, witness_fft_samples, witness_fft_overlap, options.num_witness_ffts, witness_tf_update_samples, witness_fir_samples, options.witness_frequency_resolution, obsready = None if options.no_dq_vector else obsreadytee, attack_length = -filter_settle_time * options.witness_channel_sr, filename = "transfer_functions.txt")
 
 if options.remove_callines or options.remove_powerlines or options.witness_channel_list is not None:
 	clean_strain = pipeparts.mkprogressreport(pipeline, clean_strain, "progress_hoft_cleaned_%s" % instrument)
diff --git a/gstlal-calibration/python/calibration_parts.py b/gstlal-calibration/python/calibration_parts.py
index edf9c46d21..c979058dff 100644
--- a/gstlal-calibration/python/calibration_parts.py
+++ b/gstlal-calibration/python/calibration_parts.py
@@ -252,6 +252,8 @@ def remove_harmonics_with_witness(pipeline, signal, witness, f0, num_harmonics,
 
 		# Find transfer function between witness channel and signal at this frequency
 		tf_at_f = complex_division(pipeline, line_in_signal, line_in_witness)
+		# It may be necessary to remove the first few samples since denominator samples may have arrived before numerator samples, in which case the adder assumes the numerator is one.
+		tf_at_f = pipeparts.mkgeneric(pipeline, tf_at_f, "lal_insertgap", chop_length = 1000000000) # Removing one second
 
 		# Remove worthless data from computation of transfer function if we can
 		if obsready is not None:
@@ -263,6 +265,8 @@ def remove_harmonics_with_witness(pipeline, signal, witness, f0, num_harmonics,
 			reconstructed_line_in_signal = mkmultiplier(pipeline, list_srcs(pipeline, tf_at_f, line_in_witness))
 		else:
 			reconstructed_line_in_signal = mkmultiplier(pipeline, list_srcs(pipeline, tf_at_f, line_in_witness, phase_factor))
+		# It may be necessary to remove the first few samples since line_in_witness may have arrived first, in which case the result of the above multiplication would be line_in_witness
+		reconstructed_line_in_signal = pipeparts.mkgeneric(pipeline, reconstructed_line_in_signal, "lal_insertgap", chop_length = 1000000000)
 		reconstructed_line_in_signal = mkresample(pipeline, reconstructed_line_in_signal, upsample_quality, zero_latency, rate_out)
 		reconstructed_line_in_signal = pipeparts.mkgeneric(pipeline, reconstructed_line_in_signal, "lal_demodulate", line_frequency = -1.0 * n * f0, prefactor_real = -2.0)
 		reconstructed_line_in_signal = pipeparts.mkgeneric(pipeline, reconstructed_line_in_signal, "creal")
diff --git a/gstlal-calibration/tests/lal_transferfunction_test.py b/gstlal-calibration/tests/lal_transferfunction_test.py
index 43b7672910..31bd49b6aa 100755
--- a/gstlal-calibration/tests/lal_transferfunction_test.py
+++ b/gstlal-calibration/tests/lal_transferfunction_test.py
@@ -142,20 +142,21 @@ def lal_transferfunction_03(pipeline, name):
 	buffer_length = 1.0	# seconds
 	test_duration = 50.0	# seconds
 	width = 64		# bits
-	channels = 1
-	freq = 512
+	freq = 512		# Hz
+	num_witnesses = 2
+	witness_factor = 0.5	# Ratio of amplitudes of witness / signal
 
 	#
 	# build pipeline
 	#
 
-	hoft = test_common.test_src(pipeline, buffer_length = buffer_length, wave = 5, volume = 1, freq = freq, channels = channels, rate = rate, test_duration = test_duration, width = width, verbose = False)
+	hoft = test_common.test_src(pipeline, buffer_length = buffer_length, wave = 5, volume = 1, freq = freq, channels = 1, rate = rate, test_duration = test_duration, width = width, verbose = False)
 	hoft = pipeparts.mktee(pipeline, hoft)
 
 	noise = []
-	for i in range(0, 10):
-		difference = test_common.test_src(pipeline, buffer_length = buffer_length, wave = 5, volume = 0.001, channels = channels, rate = rate, test_duration = test_duration, width = width, verbose = False)
-		noisy_hoft = calibration_parts.mkadder(pipeline, calibration_parts.list_srcs(pipeline, hoft, difference))
+	for i in range(0, num_witnesses):
+		difference = test_common.test_src(pipeline, buffer_length = buffer_length, wave = 5, volume = 0.001, channels = 1, rate = rate, test_duration = test_duration, width = width, verbose = False)
+		noisy_hoft = calibration_parts.mkadder(pipeline, calibration_parts.list_srcs(pipeline, pipeparts.mkaudioamplify(pipeline, hoft, witness_factor), difference))
 		noisy_hoft = pipeparts.mkprogressreport(pipeline, noisy_hoft, "noisy_hoft_%d" % i)
 		noise.append(noisy_hoft)
 
-- 
GitLab