diff --git a/gstlal-calibration/bin/gstlal_compute_strain b/gstlal-calibration/bin/gstlal_compute_strain index f277bdc4e410bb291c25611bef53281d8a1aa4db..6c106367665e0d048d52d2c574f94db1e2a3dc00 100755 --- a/gstlal-calibration/bin/gstlal_compute_strain +++ b/gstlal-calibration/bin/gstlal_compute_strain @@ -283,6 +283,11 @@ parser.add_option("--powerlines-channel-sr", metavar = "Hz", type = int, default 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-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)") +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-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("--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.") parser.add_option("--cleaning-check-range-low-min", metavar = "Hz", type = int, default = 15, help = "Minimum of a range of frequencies in which we expect line/noise subtraction to be impactful. The HOFT_CLEAN_LOWFREQ_OK bit of the calibration state vector is determined based on whether rms of the cleaned data is less than that of uncleaned h(t) in this range. (Default = 15 Hz)") parser.add_option("--cleaning-check-range-low-max", metavar = "Hz", type = int, default = 40, help = "Maximum of a range of frequencies in which we expect line/noise subtraction to be impactful. The HOFT_CLEAN_LOWFREQ_OK bit of the calibration state vector is determined based on whether rms of the cleaned data is less than that of uncleaned h(t) in this range. (Default = 40 Hz)") @@ -1939,6 +1944,11 @@ if options.remove_powerlines: # Remove excess noise using any provided witness channels if options.witness_channel_list is not None: + witness_fft_samples = int(options.witness_channel_fft_length * options.witness_channel_sr) + witness_fft_overlap = int(witness_fft_samples / 2) + witness_tf_update_samples = int(options.witness_channel_sr) * int(options.witness_tf_update_time) + witness_fir_samples = int(options.witness_fir_length * options.witness_channel_sr) + if not (options.remove_callines or options.remove_powerlines): clean_strain = straintee if options.no_dq_vector: @@ -1949,7 +1959,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): print("WARNING: Not all requested witness channels are being used") - clean_strain = calibration_parts.clean_data(pipeline, clean_strain, hoftsr, witnesses, int(options.witness_channel_sr), int(options.witness_channel_sr), int(options.witness_channel_sr) / 2, 128, int(options.witness_channel_sr) * 3600, int(options.witness_channel_sr) / 2, 10, obsready = None if options.no_dq_vector else obsreadytee) + 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 989812163e240b7de0db7568985f447dc3709bff..edf9c46d21442c7a85301ef76274f21ae0b2b3b0 100644 --- a/gstlal-calibration/python/calibration_parts.py +++ b/gstlal-calibration/python/calibration_parts.py @@ -754,7 +754,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, obsready = None, filename = None): +def clean_data(pipeline, signal, signal_rate, witnesses, witness_rate, fft_length, fft_overlap, num_ffts, update_samples, fir_length, frequency_resolution, obsready = None, attack_length = 0, filename = None): # # Use witness channels that monitor the environment to remove environmental noise @@ -775,7 +775,7 @@ def clean_data(pipeline, signal, signal_rate, witnesses, witness_rate, fft_lengt resampled_signal = mkresample(pipeline, signal_tee, 5, False, witness_rate) transfer_functions = mkinterleave(pipeline, numpy.insert(witness_tees, 0, resampled_signal, axis = 0)) if obsready is not None: - transfer_functions = mkgate(pipeline, transfer_functions, obsready, 1) + transfer_functions = mkgate(pipeline, transfer_functions, obsready, 1, attack_length = attack_length) 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) signal_minus_noise = [signal_tee] for i in range(0, len(witnesses)): diff --git a/gstlal-calibration/tests/lal_gate_test.py b/gstlal-calibration/tests/lal_gate_test.py new file mode 100755 index 0000000000000000000000000000000000000000..215834ecf17b089b057e0f1a3479f4b1f76099f5 --- /dev/null +++ b/gstlal-calibration/tests/lal_gate_test.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# Copyright (C) 2016 Aaron Viets +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# +# ============================================================================= +# +# Preamble +# +# ============================================================================= +# + + +import numpy +import sys +from gstlal import pipeparts +from gstlal import calibration_parts +import test_common + + +# +# ============================================================================= +# +# Pipelines +# +# ============================================================================= +# + + +def lal_gate_01(pipeline, name): + # + # This pipeline tests the behavior of lal_gate with respect to attack_length, start-of-stream, etc. + # + + rate = 512 # Hz + buffer_length = 1.0 # seconds + test_duration = 100 # seconds + frequency = 0.1 # Hz + attack_length = -3 # seconds + hold_length = 0 # seconds + threshold = 1.0 + DC_offset = 1.0 + + # + # build pipeline + # + + # Make a sine wave + src = test_common.test_src(pipeline, buffer_length = buffer_length, rate = rate, test_duration = test_duration, wave = 0, volume = 1.0, freq = frequency, width = 64) + # Add a DC offset + head = pipeparts.mkgeneric(pipeline, src, "lal_add_constant", value = DC_offset) + head = pipeparts.mktee(pipeline, head) + pipeparts.mknxydumpsink(pipeline, head, "%s_in.txt" % name) + + # Gate it + head = calibration_parts.mkgate(pipeline, head, head, threshold, attack_length = int(attack_length * rate), hold_length = int(hold_length * rate)) + pipeparts.mknxydumpsink(pipeline, head, "%s_out.txt" % name) + + # + # done + # + + return pipeline + +# +# ============================================================================= +# +# Main +# +# ============================================================================= +# + + +test_common.build_and_run(lal_gate_01, "lal_gate_01") +