From 89af93ae01456b079353500f0bf7d330557c41d3 Mon Sep 17 00:00:00 2001 From: Aaron Viets <aaron.viets@ligo.org> Date: Mon, 23 Jul 2018 16:16:04 -0700 Subject: [PATCH] lal_transferfunction: Added ability to notch out selected frequencies to avoid effects of loud lines in h(t) that are not in witnesses. --- gstlal-calibration/bin/gstlal_compute_strain | 19 +++- .../gst/lal/gstlal_adaptivefirfilt.c | 11 ++- .../gst/lal/gstlal_transferfunction.c | 95 +++++++++++++++++-- .../gst/lal/gstlal_transferfunction.h | 3 +- .../python/calibration_parts.py | 4 +- 5 files changed, 112 insertions(+), 20 deletions(-) diff --git a/gstlal-calibration/bin/gstlal_compute_strain b/gstlal-calibration/bin/gstlal_compute_strain index 26072c0476..f2fc4960d2 100755 --- a/gstlal-calibration/bin/gstlal_compute_strain +++ b/gstlal-calibration/bin/gstlal_compute_strain @@ -284,7 +284,8 @@ parser.add_option("--witness-channel-sr", metavar = "Hz", type = int, default = parser.add_option("--witness-channel-fft-time", metavar = "seconds", type = float, default = 4.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 = 4.0)") parser.add_option("--num-witness-ffts", metavar = "integer", type = int, default = 128, help = "The number of ffts to take before averaging the witness -> h(t) transfer functions calculation. The average is taken after the ratio h(f) / witness(f). (Default = 128)") parser.add_option("--witness-fir-length", metavar = "seconds", type = float, default = 0.5, help = "The length in seconds of the filters applied to the witness channels before subtracting from h(t) (Default = 0.5)") -parser.add_option("--witness-frequency-resolution", metavar = "Hz", type = float, default = 5.0, help = "The frequency resolution of the filters applied to the witness channels before subtracting from h(t). It can be advantageous to lower the frequency resolution in order to average over excess noise. (Default = 5.0)") +parser.add_option("--witness-frequency-resolution", metavar = "Hz", type = float, default = 1.0, help = "The frequency resolution of the filters applied to the witness channels before subtracting from h(t). It can be advantageous to lower the frequency resolution in order to average over excess noise. (Default = 1.0)") +parser.add_option("--witness-notch-frequencies", metavar = "list", default = None, help = "List of minima and maxima of frequency ranges where the Fourier transform of h(t) will be replaced by a straight line in the calculation of transfer functions between witness channels and h(t) for noise subtraction. Here is an example using the expected format: --witness-notch-frequencies=\"498.0,505.5,997.0,1008.5\" This can be useful, e.g., if there are loud lines in the signal that are not present in the witness channels. (Default is to not use any notches.)") parser.add_option("--witness-tf-update-time", metavar = "seconds", type = int, default = 3600, help = "The amount of time after transfer functions between witness channels and h(t) are finished to begin the calculation of the next set of transfer functions (Default = 3600)") parser.add_option("--witness-filter-taper-time", metavar = "seconds", type = int, default = 10, help = "The amount of time to use to taper in newly computed FIR filters for witness channels being used for noise subtraction.") parser.add_option("--cleaning-check-rms-time", metavar = "seconds", type = float, default = 1.0, help = "The amount of data from h(t) and cleaned h(t) that is used to compute and compare the rms. This comparison between cleaned and uncleaned h(t) determines whether the HOFT_CLEAN bits of the calibration state vector are on or off.") @@ -1881,11 +1882,22 @@ if options.remove_powerlines: # Remove excess noise using any provided witness channels if options.witness_channel_list is not None: + # Length of ffts used to compute FIR filters witness_fft_samples = int(options.witness_channel_fft_time * options.witness_channel_sr) + # Overlap of ffts is half of fft length. The data is Hann-windowed before taking ffts. witness_fft_overlap = int(witness_fft_samples / 2) + # How many samples between filter updates (does not include the samples used to compute the FIR filters witness_tf_update_samples = int(options.witness_channel_sr) * int(options.witness_tf_update_time) + # Length of FIR filters witness_fir_samples = int(options.witness_fir_length * options.witness_channel_sr) + # Over how many samples should new FIR filters be tapered in? witness_filter_taper_length = int(options.witness_channel_sr * options.witness_filter_taper_time) + # Notch any troublesome frequencies + notch_frequencies = [] + if options.witness_notch_frequencies is not None: + notch_string_list = options.witness_notch_frequencies.split(",") + for i in range(0, len(notch_string_list)): + notch_frequencies.append(float(notch_string_list[i])) # Remove initial data from computation of transfer functions and wait until the filters and kappas settle witness_chop_time = filter_settle_time + (1.0 - options.filter_latency) * (options.demodulation_filter_time + options.median_smoothing_time + options.factors_averaging_time) # In high latency, make the witnesses wait to be filtered until new filters are ready @@ -1894,9 +1906,10 @@ if options.witness_channel_list is not None: witness_chop_increment = options.witness_filter_taper_time + (options.witness_channel_fft_time / 2.0 * (options.num_witness_ffts + 1.0) if not options.filter_latency else 0.0) # How much does the "wait_time" need to increase per iteration of cleaning? witness_wait_increment = (options.witness_filter_taper_time + options.witness_channel_fft_time / 2.0 * (options.num_witness_ffts + 1.0)) if options.filter_latency else 0.0 - + # If we haven't removed any lines, clean the regular h(t) data if not (options.remove_callines or options.remove_powerlines): clean_strain = straintee + # If possible, gate the data being used to compute transfer functions to be sure we are locked if options.no_dq_vector: obsreadytee = None for i in range(0, len(witness_channel_list)): @@ -1906,7 +1919,7 @@ if options.witness_channel_list is not None: 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, witness_filter_taper_length, obsready = obsreadytee, chop_time = witness_chop_time, wait_time = witness_wait_time, filename = "transfer_functions_%d.txt" % i) + 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, witness_filter_taper_length, notch_frequencies = notch_frequencies, obsready = obsreadytee, chop_time = witness_chop_time, wait_time = witness_wait_time, filename = "transfer_functions_%d.txt" % i) witness_chop_time += witness_chop_increment witness_wait_time += witness_wait_increment diff --git a/gstlal-calibration/gst/lal/gstlal_adaptivefirfilt.c b/gstlal-calibration/gst/lal/gstlal_adaptivefirfilt.c index 587cdc3f28..32bfa909a9 100644 --- a/gstlal-calibration/gst/lal/gstlal_adaptivefirfilt.c +++ b/gstlal-calibration/gst/lal/gstlal_adaptivefirfilt.c @@ -744,6 +744,7 @@ static void set_property(GObject *object, enum property id, const GValue *value, /* If no static filter is provided, the filter is just 1 */ if(element->static_filter_length == 0) { element->static_filter_length = 1; + element->static_filter = g_malloc(sizeof(*element->static_filter)); *element->static_filter = 1.0; } break; @@ -1011,7 +1012,7 @@ static void gstlal_adaptivefirfilt_class_init(GSTLALAdaptiveFIRFiltClass *klass) -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS ), - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT ); properties[ARG_STATIC_POLES] = g_param_spec_value_array( "static-poles", @@ -1026,7 +1027,7 @@ static void gstlal_adaptivefirfilt_class_init(GSTLALAdaptiveFIRFiltClass *klass) -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS ), - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT ); properties[ARG_PHASE_MEASUREMENT_FREQUENCY] = g_param_spec_double( "phase-measurement-frequency", @@ -1048,7 +1049,7 @@ static void gstlal_adaptivefirfilt_class_init(GSTLALAdaptiveFIRFiltClass *klass) -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS ), - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT ); properties[ARG_VARIABLE_FILTER_LENGTH] = g_param_spec_int64( "variable-filter-length", @@ -1082,7 +1083,7 @@ static void gstlal_adaptivefirfilt_class_init(GSTLALAdaptiveFIRFiltClass *klass) -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS ), - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS ); properties[ARG_ADAPTIVE_FILTER_LENGTH] = g_param_spec_int64( "adaptive-filter-length", @@ -1217,7 +1218,7 @@ static void gstlal_adaptivefirfilt_class_init(GSTLALAdaptiveFIRFiltClass *klass) static void gstlal_adaptivefirfilt_init(GSTLALAdaptiveFIRFilt *element) { - g_signal_connect(G_OBJECT(element), "notify::fir-filter", G_CALLBACK(rebuild_workspace_and_reset), NULL); + g_signal_connect(G_OBJECT(element), "notify::adaptive-filter", G_CALLBACK(rebuild_workspace_and_reset), NULL); element->rate = 0; element->unit_size = 0; element->channels = 0; diff --git a/gstlal-calibration/gst/lal/gstlal_transferfunction.c b/gstlal-calibration/gst/lal/gstlal_transferfunction.c index 4e481d4331..13e0d09564 100644 --- a/gstlal-calibration/gst/lal/gstlal_transferfunction.c +++ b/gstlal-calibration/gst/lal/gstlal_transferfunction.c @@ -120,6 +120,7 @@ enum property { ARG_FREQUENCY_RESOLUTION, ARG_HIGH_PASS, ARG_LOW_PASS, + ARG_NOTCH_FREQUENCIES, ARG_TRANSFER_FUNCTIONS, ARG_FIR_FILTERS, ARG_FAKE @@ -489,6 +490,17 @@ static gboolean find_transfer_functions_ ## DTYPE(GSTLALTransferFunction *elemen first_index = j * fd_fft_length; \ for(k = 0; k < fd_fft_length; k++) \ element->workspace.w ## S_OR_D ## pf.ffts[first_index + k] = element->workspace.w ## S_OR_D ## pf.fft[k]; \ + \ + /* Fill in any requested "notches" with straight lines */ \ + int n; \ + for(n = 0; n < element->num_notches; n++) { \ + gint64 notch_start = element->notch_indices[2 * n]; \ + gint64 notch_end = element->notch_indices[2 * n + 1]; \ + complex DTYPE fft_start = element->workspace.w ## S_OR_D ## pf.fft[element->notch_indices[2 * n]]; \ + complex DTYPE fft_end = element->workspace.w ## S_OR_D ## pf.fft[element->notch_indices[2 * n + 1]]; \ + for(k = notch_start + 1; k < notch_end; k++) \ + element->workspace.w ## S_OR_D ## pf.ffts[first_index + k] = fft_start * (k - notch_start) / (notch_end - notch_start) + fft_end * (notch_end - k) / (notch_end - notch_start); \ + } \ } \ \ /* Check the FFTs to see if their values will produce usable transfer functions */ \ @@ -556,6 +568,17 @@ static gboolean find_transfer_functions_ ## DTYPE(GSTLALTransferFunction *elemen first_index = j * fd_fft_length; \ for(k = 0; k < fd_fft_length; k++) \ element->workspace.w ## S_OR_D ## pf.ffts[first_index + k] = element->workspace.w ## S_OR_D ## pf.fft[k]; \ + \ + /* Fill in any requested "notches" with straight lines */ \ + int n; \ + for(n = 0; n < element->num_notches; n++) { \ + gint64 notch_start = element->notch_indices[2 * n]; \ + gint64 notch_end = element->notch_indices[2 * n + 1]; \ + complex DTYPE fft_start = element->workspace.w ## S_OR_D ## pf.fft[element->notch_indices[2 * n]]; \ + complex DTYPE fft_end = element->workspace.w ## S_OR_D ## pf.fft[element->notch_indices[2 * n + 1]]; \ + for(k = notch_start + 1; k < notch_end; k++) \ + element->workspace.w ## S_OR_D ## pf.ffts[first_index + k] = fft_start * (k - notch_start) / (notch_end - notch_start) + fft_end * (notch_end - k) / (notch_end - notch_start); \ + } \ } \ \ /* Check the FFTs to see if their values will produce usable transfer functions */ \ @@ -868,6 +891,17 @@ static gboolean set_caps(GstBaseSink *sink, GstCaps *caps) { if(element->make_fir_filters) element->fir_filters = g_malloc((element->channels - 1) * element->fir_length * sizeof(*element->fir_filters)); + /* Prepare to deal with any notches */ + if(element->num_notches && !element->notch_indices) { + int k; + double df = (double) element->rate / element->fft_length; + element->notch_indices = g_malloc(2 * element->num_notches * sizeof(*element->notch_indices)); + for(k = 0; k < element->num_notches; k++) { + element->notch_indices[2 * k] = (gint64) (element->notch_frequencies[2 * k] / df - 1.0); + element->notch_indices[2 * k + 1] = (gint64) (element->notch_frequencies[2 * k + 1] / df + 2.0); + } + } + /* Prepare workspace for finding transfer functions and FIR filters */ if(element->data_type == GSTLAL_TRANSFERFUNCTION_F32) { @@ -912,7 +946,7 @@ static gboolean set_caps(GstBaseSink *sink, GstCaps *caps) { /* To save memory, we use symmetry and record only half of the sinc table */ element->workspace.wspf.sinc_table = g_malloc((1 + element->workspace.wspf.sinc_length / 2) * sizeof(*element->workspace.wspf.sinc_table)); *element->workspace.wspf.sinc_table = 1.0; - gint64 i, j; + gint64 j; float sin_arg, normalization; for(i = 1; i <= element->workspace.wspf.sinc_length / 2; i++) { sin_arg = M_PI * i / taps_per_osc; @@ -1088,7 +1122,7 @@ static gboolean set_caps(GstBaseSink *sink, GstCaps *caps) { /* To save memory, we use symmetry and record only half of the sinc table */ element->workspace.wdpf.sinc_table = g_malloc((1 + element->workspace.wdpf.sinc_length / 2) * sizeof(*element->workspace.wdpf.sinc_table)); *element->workspace.wdpf.sinc_table = 1.0; - gint64 i, j; + gint64 j; double sin_arg, normalization; for(i = 1; i <= element->workspace.wdpf.sinc_length / 2; i++) { sin_arg = M_PI * i / taps_per_osc; @@ -1488,6 +1522,17 @@ static void set_property(GObject *object, enum property id, const GValue *value, element->low_pass = g_value_get_double(value); break; + case ARG_NOTCH_FREQUENCIES: + if(element->notch_frequencies) { + g_free(element->notch_frequencies); + element->notch_frequencies = NULL; + } + element->notch_frequencies = gstlal_doubles_from_g_value_array(g_value_get_boxed(value), NULL, &element->num_notches); + if(element->num_notches % 2) + GST_ERROR_OBJECT(element, "Array length for property notch_frequencies must be even"); + element->num_notches /= 2; + break; + case ARG_TRANSFER_FUNCTIONS: if(element->transfer_functions) { g_free(element->transfer_functions); @@ -1570,6 +1615,10 @@ static void get_property(GObject *object, enum property id, GValue *value, GPara g_value_set_double(value, element->low_pass); break; + case ARG_NOTCH_FREQUENCIES: + g_value_take_boxed(value, gstlal_g_value_array_from_doubles(element->notch_frequencies, 2 * element->num_notches)); + break; + case ARG_TRANSFER_FUNCTIONS: if(element->transfer_functions) { GValueArray *va; @@ -1626,6 +1675,10 @@ static void finalize(GObject *object) { g_free(element->fir_filters); element->fir_filters = NULL; } + if(element->notch_frequencies) { + g_free(element->notch_frequencies); + element->notch_frequencies = NULL; + } G_OBJECT_CLASS(gstlal_transferfunction_parent_class)->finalize(object); } @@ -1758,9 +1811,7 @@ static void gstlal_transferfunction_class_init(GSTLALTransferFunctionClass *klas "Frequency resolution", "Frequency resolution of the transfer functions and FIR filters in Hz.\n\t\t\t" "This must be greater than or equal to sample rate/fir-length and sample\n\t\t\t" - "rate/fft-length in order to be effective. When computing multiple FIR\n\t\t\t" - "filters simultaneously, it is recommended to set this to a value\n\t\t\t" - "significantly larger than 1/fft_length.", + "rate/fft-length in order to be effective.", 0, G_MAXDOUBLE, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT ); @@ -1782,6 +1833,22 @@ static void gstlal_transferfunction_class_init(GSTLALTransferFunctionClass *klas 0, G_MAXDOUBLE, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT ); + properties[ARG_NOTCH_FREQUENCIES] = g_param_spec_value_array( + "notch-frequencies", + "Notch Frequencies", + "Array of minima and maxima of frequency ranges where the Fourier transform\n\t\t\t" + "of the signal of interest will be replaced by a straight line. This can be\n\t\t\t" + "useful if there are loud lines in the signal that are not present in the\n\t\t\t" + "witness channels.", + g_param_spec_double( + "frequency", + "Frequency", + "A frequency in the array, either a minimum or maximum of a notch.", + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS + ), + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT + ); properties[ARG_TRANSFER_FUNCTIONS] = g_param_spec_value_array( "transfer-functions", "Transfer Functions", @@ -1799,7 +1866,7 @@ static void gstlal_transferfunction_class_init(GSTLALTransferFunctionClass *klas ), G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS ), - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS ); properties[ARG_FIR_FILTERS] = g_param_spec_value_array( "fir-filters", @@ -1818,7 +1885,7 @@ static void gstlal_transferfunction_class_init(GSTLALTransferFunctionClass *klas ), G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS ), - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS ); @@ -1882,6 +1949,11 @@ static void gstlal_transferfunction_class_init(GSTLALTransferFunctionClass *klas ARG_LOW_PASS, properties[ARG_LOW_PASS] ); + g_object_class_install_property( + gobject_class, + ARG_NOTCH_FREQUENCIES, + properties[ARG_NOTCH_FREQUENCIES] + ); g_object_class_install_property( gobject_class, ARG_TRANSFER_FUNCTIONS, @@ -1902,8 +1974,13 @@ static void gstlal_transferfunction_class_init(GSTLALTransferFunctionClass *klas static void gstlal_transferfunction_init(GSTLALTransferFunction *element) { - g_signal_connect(G_OBJECT(element), "notify::transfer-function", G_CALLBACK(rebuild_workspace_and_reset), NULL); - g_signal_connect(G_OBJECT(element), "notify::fir-filter", G_CALLBACK(rebuild_workspace_and_reset), NULL); + g_signal_connect(G_OBJECT(element), "notify::transfer-functions", G_CALLBACK(rebuild_workspace_and_reset), NULL); + g_signal_connect(G_OBJECT(element), "notify::fir-filters", G_CALLBACK(rebuild_workspace_and_reset), NULL); + element->transfer_functions = NULL; + element->fir_filters = NULL; + element->notch_frequencies = NULL; + element->notch_indices = NULL; + element->num_notches = 0; element->rate = 0; element->unit_size = 0; element->channels = 0; diff --git a/gstlal-calibration/gst/lal/gstlal_transferfunction.h b/gstlal-calibration/gst/lal/gstlal_transferfunction.h index 7fff5ddf10..b054a43216 100644 --- a/gstlal-calibration/gst/lal/gstlal_transferfunction.h +++ b/gstlal-calibration/gst/lal/gstlal_transferfunction.h @@ -141,7 +141,8 @@ struct _GSTLALTransferFunction { double frequency_resolution; int high_pass; int low_pass; - double notch_frequencies; + double *notch_frequencies; + gint64 *notch_indices; int num_notches; complex double *transfer_functions; double *fir_filters; diff --git a/gstlal-calibration/python/calibration_parts.py b/gstlal-calibration/python/calibration_parts.py index 69d0d3cdd7..f03225a736 100644 --- a/gstlal-calibration/python/calibration_parts.py +++ b/gstlal-calibration/python/calibration_parts.py @@ -758,7 +758,7 @@ def update_filters(filter_maker, arg, filter_taker, maker_prop_name, taker_prop_ firfilter = filter_maker.get_property(maker_prop_name)[filter_number][::-1] filter_taker.set_property(taker_prop_name, firfilter) -def clean_data(pipeline, signal, signal_rate, witnesses, witness_rate, fft_length, fft_overlap, num_ffts, update_samples, fir_length, frequency_resolution, filter_taper_length, obsready = None, chop_time = 0.0, wait_time = 0.0, filename = None): +def clean_data(pipeline, signal, signal_rate, witnesses, witness_rate, fft_length, fft_overlap, num_ffts, update_samples, fir_length, frequency_resolution, filter_taper_length, notch_frequencies = [], obsready = None, chop_time = 0.0, wait_time = 0.0, filename = None): # # Use witness channels that monitor the environment to remove environmental noise @@ -782,7 +782,7 @@ def clean_data(pipeline, signal, signal_rate, witnesses, witness_rate, fft_lengt transfer_functions = mkgate(pipeline, transfer_functions, obsready, 1, attack_length = -witness_rate * chop_time) if(chop_time): transfer_functions = pipeparts.mkgeneric(pipeline, transfer_functions, "lal_insertgap", chop_length = int(1000000000 * chop_time)) - transfer_functions = pipeparts.mkgeneric(pipeline, transfer_functions, "lal_transferfunction", fft_length = fft_length, fft_overlap = fft_overlap, num_ffts = num_ffts, update_samples = update_samples, make_fir_filters = -1, fir_length = fir_length, frequency_resolution = frequency_resolution, high_pass = 9, update_after_gap = True, filename = filename) + transfer_functions = pipeparts.mkgeneric(pipeline, transfer_functions, "lal_transferfunction", fft_length = fft_length, fft_overlap = fft_overlap, num_ffts = num_ffts, update_samples = update_samples, make_fir_filters = -1, fir_length = fir_length, frequency_resolution = frequency_resolution, high_pass = 9, update_after_gap = True, notch_frequencies = notch_frequencies, filename = filename) signal_minus_noise = [signal_tee] for i in range(0, len(witnesses)): minus_noise = pipeparts.mkgeneric(pipeline, mkqueue(pipeline, witness_tees[i], min_length = wait_time), "lal_tdwhiten", kernel = default_fir_filter, latency = fir_length / 2, taper_length = filter_taper_length) -- GitLab