From 065988d87f1fd17253b3fb7f5615ec686fe8cb54 Mon Sep 17 00:00:00 2001 From: Aaron Viets <aaron.viets@ligo.org> Date: Thu, 31 May 2018 14:34:07 -0700 Subject: [PATCH] lal_lpfilter: new element that takes complex input representing amplitude and phase error of a signal at a particular frequency and computes a linear-phase FIR filter to correct this error. --- gstlal-calibration/bin/gstlal_compute_strain | 2 +- gstlal-calibration/gst/lal/Makefile.am | 3 +- gstlal-calibration/gst/lal/gstlal_lpfilter.c | 734 ++++++++++++++++++ gstlal-calibration/gst/lal/gstlal_lpfilter.h | 107 +++ .../gst/lal/gstlal_transferfunction.h | 4 +- .../gst/lal/gstlalcalibration.c | 2 + .../python/calibration_parts.py | 2 +- .../tests/lal_transferfunction_test.py | 28 +- 8 files changed, 876 insertions(+), 6 deletions(-) create mode 100644 gstlal-calibration/gst/lal/gstlal_lpfilter.c create mode 100644 gstlal-calibration/gst/lal/gstlal_lpfilter.h diff --git a/gstlal-calibration/bin/gstlal_compute_strain b/gstlal-calibration/bin/gstlal_compute_strain index bfbbf345c6..e3139e23f0 100755 --- a/gstlal-calibration/bin/gstlal_compute_strain +++ b/gstlal-calibration/bin/gstlal_compute_strain @@ -1568,7 +1568,7 @@ if options.remove_powerlines: if not options.remove_callines: clean_strain = straintee powerlines = calibration_parts.caps_and_progress(pipeline, head_dict["powerlines"], "audio/x-raw, format=F64LE, channels=1, channel-mask=(bitmask)0x0", "powerlines") - clean_strain = calibration_parts.remove_harmonics_with_witness(pipeline, clean_strain, powerlines, 60, 5, options.powerlines_freq_var, options.filter_latency, compute_rate = options.compute_factors_sr, rate_out = hoftsr, num_avg = 320, obsready = None if options.no_dq_vector else obsreadytee) + clean_strain = calibration_parts.remove_harmonics_with_witness(pipeline, clean_strain, powerlines, 60, 5, options.powerlines_freq_var, options.filter_latency, compute_rate = options.compute_factors_sr, rate_out = hoftsr, num_avg = 2048, obsready = None if options.no_dq_vector else obsreadytee) # Remove excess noise using any provided witness channels if options.witness_channel_list is not None: diff --git a/gstlal-calibration/gst/lal/Makefile.am b/gstlal-calibration/gst/lal/Makefile.am index f21b69651e..2a7cb7263f 100644 --- a/gstlal-calibration/gst/lal/Makefile.am +++ b/gstlal-calibration/gst/lal/Makefile.am @@ -15,7 +15,8 @@ lib@GSTPLUGINPREFIX@gstlalcalibration_la_SOURCES = \ gstlal_insertgap.c gstlal_insertgap.h \ gstlal_fccupdate.c gstlal_fccupdate.h \ gstlal_transferfunction.c gstlal_transferfunction.h \ - gstlal_trackfrequency.c gstlal_trackfrequency.h + gstlal_trackfrequency.c gstlal_trackfrequency.h \ + gstlal_lpfilter.c gstlal_lpfilter.h lib@GSTPLUGINPREFIX@gstlalcalibration_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_CPPFLAGS) lib@GSTPLUGINPREFIX@gstlalcalibration_la_CFLAGS = $(AM_CFLAGS) $(LAL_CFLAGS) $(GSTLAL_CFLAGS) $(gstreamer_CFLAGS) $(gstreamer_audio_CFLAGS) lib@GSTPLUGINPREFIX@gstlalcalibration_la_LDFLAGS = $(AM_LDFLAGS) $(LAL_LIBS) $(GSTLAL_LIBS) $(PYTHON_LIBS) $(gstreamer_LIBS) $(gstreamer_audio_LIBS) $(GSTLAL_PLUGIN_LDFLAGS) diff --git a/gstlal-calibration/gst/lal/gstlal_lpfilter.c b/gstlal-calibration/gst/lal/gstlal_lpfilter.c new file mode 100644 index 0000000000..19da8188e4 --- /dev/null +++ b/gstlal-calibration/gst/lal/gstlal_lpfilter.c @@ -0,0 +1,734 @@ +/* + * Copyright (C) 2018 Aaron Viets <aaron.viets@ligo.org> + * + * 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. + */ + + +/* + * SECTION:gstlal_lpfilter + * @short_description: Compute transfer function between two or more + * time series. + */ + + +/* + * ======================================================================== + * + * Preamble + * + * ======================================================================== + */ + + +/* + * stuff from the C library + */ + + +#include <complex.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> + + +/* + * stuff from glib/gstreamer + */ + + +#include <glib.h> +#include <glib/gprintf.h> +#include <gst/gst.h> +#include <gst/base/gstbasesink.h> +#include <gst/audio/audio.h> +#include <gst/audio/audio-format.h> + + +/* + * stuff from FFTW and GSL + */ + + +#include <fftw3.h> +#include <gsl/gsl_complex.h> +#include <gsl/gsl_complex_math.h> +#include <gsl/gsl_vector.h> +#include <gsl/gsl_matrix.h> +#include <gsl/gsl_blas.h> +#include <gsl/gsl_linalg.h> +#include <gsl/gsl_permutation.h> + + +/* + * our own stuff + */ + + +#include <gstlal/gstlal.h> +#include <gstlal/gstlal_audio_info.h> +#include <gstlal/gstlal_debug.h> +#include <gstlal_lpfilter.h> + + +#define SINC_LENGTH 25 + + +/* + * ============================================================================ + * + * GStreamer Boilerplate + * + * ============================================================================ + */ + + +#define GST_CAT_DEFAULT gstlal_lpfilter_debug +GST_DEBUG_CATEGORY_STATIC(GST_CAT_DEFAULT); + + +G_DEFINE_TYPE_WITH_CODE( + GSTLALLPFilter, + gstlal_lpfilter, + GST_TYPE_BASE_SINK, + GST_DEBUG_CATEGORY_INIT(GST_CAT_DEFAULT, "lal_lpfilter", 0, "lal_lpfilter element") +); + + +enum property { + ARG_MEASUREMENT_FREQUENCY = 1, + ARG_UPDATE_SAMPLES, + ARG_AVERAGE_SAMPLES, + ARG_WRITE_TO_SCREEN, + ARG_FILENAME, + ARG_FIR_LENGTH, + ARG_FIR_SAMPLE_RATE, + ARG_FIR_FILTER, + ARG_FAKE +}; + + +static GParamSpec *properties[ARG_FAKE]; + + +/* + * ============================================================================ + * + * Utilities + * + * ============================================================================ + */ + + +static void rebuild_workspace_and_reset(GObject *object) { + return; +} + + +double min(double value1, double value2) { + return value1 < value2 ? value1 : value2; +} + + +double max(double value1, double value2) { + return value1 > value2 ? value1 : value2; \ +} + + +#define DEFINE_MINIMUM(size) \ +gint ## size min ## size(gint ## size value1, gint ## size value2) { \ + return value1 < value2 ? value1 : value2; \ +} + + +DEFINE_MINIMUM(8); +DEFINE_MINIMUM(16); +DEFINE_MINIMUM(32); +DEFINE_MINIMUM(64); + + +#define DEFINE_MAXIMUM(size) \ +gint ## size max ## size(gint ## size value1, gint ## size value2) { \ + return value1 > value2 ? value1 : value2; \ +} + + +DEFINE_MAXIMUM(8); +DEFINE_MAXIMUM(16); +DEFINE_MAXIMUM(32); +DEFINE_MAXIMUM(64); + + +static void write_fir_filter(double *filter, char *element_name, gint64 rows, gboolean write_to_screen, char *filename, gboolean free_name) { + gint64 i; + if(write_to_screen) { + g_print("================== FIR filter computed by %s ==================\n", element_name); + + for(i = 0; i < rows; i++) + g_print("%10e\n", filter[i]); + g_print("\n\n"); + } + + if(filename) { + FILE *fp; + fp = fopen(filename, "a"); + g_fprintf(fp, "================== FIR filter computed by %s ==================\n", element_name); + + for(i = 0; i < rows; i++) + g_fprintf(fp, "%10e\n", filter[i]); + g_fprintf(fp, "\n\n"); + fclose(fp); + } + if(free_name) + g_free(element_name); +} + + +static void update_fir_filter(complex double *fir_filter, fftw_plan fir_plan, complex double input_average, double measurement_frequency, gint64 fir_length, int fir_sample_rate) { + + gint64 n, fd_fir_length = fir_length / 2 + 1; + + /* + * Compute the filter in the frequency domain + */ + + double gain = cabs(input_average); + + /* + * At each point in frequency space, the filter is gain * exp(2 pi i f t). The value + * of f (= n * df) ranges from DC (0) to the Nyquist frequency (fir_sample_rate / 2) + * in evenly spaced increments. The rest of the parameters in the exp() are constant. + */ + + double two_pi_i_df_t = clog(input_average / gain) / measurement_frequency * fir_sample_rate / 2 / (fd_fir_length - 1); + for(n = 0; n < fd_fir_length; n += 2) + fir_filter[n] = gain * cexp(two_pi_i_df_t * n); + + /* Negating every other value adds a delay of half the length of the filter, centering it in time. */ + for(n = 1; n < fd_fir_length; n += 2) + fir_filter[n] = -gain * cexp(two_pi_i_df_t * n); + + /* Make sure the DC and Nyquist components are purely real */ + fir_filter[0] = creal(fir_filter[0]); + fir_filter[fd_fir_length - 1] = creal(fir_filter[fd_fir_length - 1]); + + /* Take the inverse Fourier transform */ + fftw_execute(fir_plan); + + return; +} + + +#define DEFINE_AVERAGE_INPUT_DATA(DTYPE) \ +static void average_input_data_ ## DTYPE(GSTLALLPFilter *element, complex DTYPE *src, guint64 src_size, guint64 pts) { \ + \ + gint64 start_sample, initial_samples, samples_to_add, i; \ + /* Find the location in src of the first sample that will go into the average */ \ + if(element->num_in_avg) \ + start_sample = 0; \ + else \ + start_sample = (gint64) (element->update_samples - gst_util_uint64_scale_int_round(pts, element->rate, GST_SECOND) % element->update_samples); \ + \ + /* How many samples from this buffer will we need to add into this average? */ \ + samples_to_add = min64(element->average_samples - element->num_in_avg, src_size - start_sample); \ + while(samples_to_add > 0) { \ + initial_samples = element->num_in_avg; \ + for(i = start_sample; i < start_sample + samples_to_add; i++) \ + element->input_average += src[i]; \ + element->num_in_avg += samples_to_add; \ + if(element->num_in_avg == element->update_samples) { \ + \ + /* We still need to divide by n to get the average */ \ + element->input_average /= element->num_in_avg; \ + \ + /* Update FIR filter */ \ + update_fir_filter(element->fir_filter, element->fir_plan, element->input_average, element->measurement_frequency, element->fir_length, element->fir_sample_rate); \ + GST_LOG_OBJECT(element, "Just computed new linear-phase FIR filter"); \ + \ + /* Let other elements know about the update */ \ + g_object_notify_by_pspec(G_OBJECT(element), properties[ARG_FIR_FILTER]); \ + \ + /* Write FIR filter to the screen or a file if we want */ \ + if(element->write_to_screen || element->filename) \ + write_fir_filter((double *) element->fir_filter, gst_element_get_name(element), element->fir_length, element->write_to_screen, element->filename, TRUE); \ + element->num_in_avg = 0; \ + element->input_average = 0.0; \ + } \ + start_sample += element->update_samples - initial_samples; \ + samples_to_add = min64(element->average_samples - element->num_in_avg, src_size - start_sample); \ + } \ + \ + return; \ +} + + +DEFINE_AVERAGE_INPUT_DATA(float); +DEFINE_AVERAGE_INPUT_DATA(double); + + +/* + * ============================================================================ + * + * GstBaseSink Overrides + * + * ============================================================================ + */ + + +/* + * start() + */ + + +static gboolean start(GstBaseSink *sink) { + + GSTLALLPFilter *element = GSTLAL_LPFILTER(sink); + + /* Timestamp bookkeeping */ + element->t0 = GST_CLOCK_TIME_NONE; + element->offset0 = GST_BUFFER_OFFSET_NONE; + element->next_in_offset = GST_BUFFER_OFFSET_NONE; + + /* Filter memory */ + element->input_average = 0.0; + element->num_in_avg = 0; + + /* If we are writing output to file, and a file already exists with the same name, remove it */ + if(element->filename) + remove(element->filename); + + /* Sanity check */ + if(element->average_samples > element->update_samples) + GST_ERROR_OBJECT(element, "average_samples cannot be greater than update_samples"); + + return TRUE; +} + + +/* + * set_caps() + */ + + +static gboolean set_caps(GstBaseSink *sink, GstCaps *caps) { + + GSTLALLPFilter *element = GSTLAL_LPFILTER(sink); + + gboolean success = TRUE; + + /* Parse the caps to find the format, sample rate, and number of channels */ + GstStructure *str = gst_caps_get_structure(caps, 0); + const gchar *name = gst_structure_get_string(str, "format"); + success &= (name != NULL); + success &= gst_structure_get_int(str, "rate", &element->rate); + + /* Record the data type and unit size */ + if(success) { + if(!strcmp(name, GST_AUDIO_NE(Z64))) { + element->data_type = GSTLAL_LPFILTER_Z64; + element->unit_size = 8; + } else if(!strcmp(name, GST_AUDIO_NE(Z128))) { + element->data_type = GSTLAL_LPFILTER_Z128; + element->unit_size = 16; + } else + g_assert_not_reached(); + } + + if(!element->fir_filter) { + /* Allocate memory for fftw to do an inverse Fourier transform of the filter. */ + gstlal_fftw_lock(); + + GST_LOG_OBJECT(element, "starting FFTW planning"); + + gint64 fd_fir_length = element->fir_length / 2 + 1; + element->fir_filter = (complex double *) fftw_malloc(fd_fir_length * sizeof(*element->fir_filter)); + element->fir_plan = fftw_plan_dft_c2r_1d(element->fir_length, element->fir_filter, (double *) element->fir_filter, FFTW_MEASURE); + + GST_LOG_OBJECT(element, "FFTW planning complete"); + + gstlal_fftw_unlock(); + + } + + return success; +} + + +/* + * render() + */ + + +static GstFlowReturn render(GstBaseSink *sink, GstBuffer *buffer) { + + GSTLALLPFilter *element = GSTLAL_LPFILTER(sink); + GstMapInfo mapinfo; + GstFlowReturn result = GST_FLOW_OK; + + /* + * check for discontinuity + */ + + if(G_UNLIKELY(GST_BUFFER_IS_DISCONT(buffer) || GST_BUFFER_OFFSET(buffer) != element->next_in_offset || !GST_CLOCK_TIME_IS_VALID(element->t0))) { + element->t0 = GST_BUFFER_PTS(buffer); + element->offset0 = GST_BUFFER_OFFSET(buffer); + } + element->next_in_offset = GST_BUFFER_OFFSET_END(buffer); + GST_DEBUG_OBJECT(element, "have buffer spanning %" GST_BUFFER_BOUNDARIES_FORMAT, GST_BUFFER_BOUNDARIES_ARGS(buffer)); + + /* Check if the data on this buffer is usable and if we plan to use it */ + gint64 next_start_sample = element->update_samples - gst_util_uint64_scale_int_round(GST_BUFFER_PTS(buffer), element->rate, GST_SECOND) % element->update_samples; + if(!GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_GAP) && mapinfo.size && (element->num_in_avg || next_start_sample < (gint64) gst_util_uint64_scale_int_round(GST_BUFFER_DURATION(buffer), element->rate, GST_SECOND))) { + /* Get the data from the buffer */ + gst_buffer_map(buffer, &mapinfo, GST_MAP_READ); + + if(element->data_type == GSTLAL_LPFILTER_Z64) + average_input_data_float(element, (complex float *) mapinfo.data, mapinfo.size / element->unit_size, GST_BUFFER_PTS(buffer)); + else if(element->data_type == GSTLAL_LPFILTER_Z128) + average_input_data_double(element, (complex double *) mapinfo.data, mapinfo.size / element->unit_size, GST_BUFFER_PTS(buffer)); + else + g_assert_not_reached(); + } + + return result; +} + + +/* + * stop() + */ + + +static gboolean stop(GstBaseSink *sink) { + + GSTLALLPFilter *element = GSTLAL_LPFILTER(sink); + + gstlal_fftw_lock(); + fftw_free(element->fir_filter); + element->fir_filter = NULL; + fftw_destroy_plan(element->fir_plan); + gstlal_fftw_unlock(); + + return TRUE; +} + + +/* + * ============================================================================ + * + * GObject Methods + * + * ============================================================================ + */ + + +/* + * properties + */ + + +static void set_property(GObject *object, enum property id, const GValue *value, GParamSpec *pspec) +{ + GSTLALLPFilter *element = GSTLAL_LPFILTER(object); + + GST_OBJECT_LOCK(element); + + switch(id) { + case ARG_MEASUREMENT_FREQUENCY: + element->measurement_frequency = g_value_get_double(value); + break; + + case ARG_UPDATE_SAMPLES: + element->update_samples = g_value_get_int64(value); + break; + + case ARG_AVERAGE_SAMPLES: + element->average_samples = g_value_get_int64(value); + break; + + case ARG_WRITE_TO_SCREEN: + element->write_to_screen = g_value_get_boolean(value); + break; + + case ARG_FILENAME: + element->filename = g_value_dup_string(value); + break; + + case ARG_FIR_LENGTH: + element->fir_length = g_value_get_int64(value); + break; + + case ARG_FIR_SAMPLE_RATE: + element->fir_sample_rate = g_value_get_int(value); + break; + + case ARG_FIR_FILTER: + if(element->fir_filter) + g_free(element->fir_filter); + int m; + gstlal_doubles_from_g_value_array(g_value_get_boxed(value), (double *) element->fir_filter, &m); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, id, pspec); + break; + } + + GST_OBJECT_UNLOCK(element); +} + + +static void get_property(GObject *object, enum property id, GValue *value, GParamSpec *pspec) +{ + GSTLALLPFilter *element = GSTLAL_LPFILTER(object); + + GST_OBJECT_LOCK(element); + + switch(id) { + case ARG_MEASUREMENT_FREQUENCY: + g_value_set_double(value, element->measurement_frequency); + break; + + case ARG_UPDATE_SAMPLES: + g_value_set_int64(value, element->update_samples); + break; + + case ARG_AVERAGE_SAMPLES: + g_value_set_int64(value, element->average_samples); + break; + + case ARG_WRITE_TO_SCREEN: + g_value_set_boolean(value, element->write_to_screen); + break; + + case ARG_FILENAME: + g_value_set_string(value, element->filename); + break; + + case ARG_FIR_LENGTH: + g_value_set_int64(value, element->fir_length); + break; + + case ARG_FIR_SAMPLE_RATE: + g_value_set_int(value, element->fir_sample_rate); + break; + + case ARG_FIR_FILTER: + if(element->fir_filter) + g_value_take_boxed(value, gstlal_g_value_array_from_doubles((double *) element->fir_filter, element->fir_length)); + else + g_value_take_boxed(value, g_value_array_new(0)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, id, pspec); + break; + } + + GST_OBJECT_UNLOCK(element); +} + + +/* + * finalize() + */ + + +static void finalize(GObject *object) { + + GSTLALLPFilter *element = GSTLAL_LPFILTER(object); + + if(element->fir_filter) { + g_free(element->fir_filter); + element->fir_filter = NULL; + } + G_OBJECT_CLASS(gstlal_lpfilter_parent_class)->finalize(object); +} + + +/* + * class_init() + */ + + +#define CAPS \ + "audio/x-raw, " \ + "rate = (int) [1, MAX], " \ + "channels = (int) 1, " \ + "format = (string) {"GST_AUDIO_NE(Z64)", "GST_AUDIO_NE(Z128)"}, " \ + "layout = (string) interleaved, " \ + "channel-mask = (bitmask) 0" + + +static void gstlal_lpfilter_class_init(GSTLALLPFilterClass *klass) { + + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS(klass); + + gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR(set_caps); + gstbasesink_class->start = GST_DEBUG_FUNCPTR(start); + gstbasesink_class->render = GST_DEBUG_FUNCPTR(render); + gstbasesink_class->stop = GST_DEBUG_FUNCPTR(stop); + + gobject_class->set_property = GST_DEBUG_FUNCPTR(set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR(get_property); + gobject_class->finalize = GST_DEBUG_FUNCPTR(finalize); + + gst_element_class_set_details_simple( + element_class, + "Compute Linear-phase FIR filter", + "Sink", + "Compute and update a linear-phase FIR filter using an input stream of complex\n\t\t\t " + "numbers representing the amplitude and phase at a chosen measurement frequency.\n\t\t\t " + "This filter applies only a gain and a frequency-independent time delay/advance.", + "Aaron Viets <aaron.viets@ligo.org>" + ); + + gst_element_class_add_pad_template( + element_class, + gst_pad_template_new( + "sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + gst_caps_from_string(CAPS) + ) + ); + + + properties[ARG_MEASUREMENT_FREQUENCY] = g_param_spec_double( + "measurement-frequency", + "Measurement Frequency", + "Frequency at which gain factor and timing offset are computed", + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT + ); + properties[ARG_UPDATE_SAMPLES] = g_param_spec_int64( + "update-samples", + "Update Samples", + "Number of input samples after which to update the linear-phase FIR filter", + 0, G_MAXINT64, 320, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT + ); + properties[ARG_AVERAGE_SAMPLES] = g_param_spec_int64( + "average-samples", + "Average Samples", + "Number of input samples to average before producing FIR filter", + 0, G_MAXINT64, 320, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT + ); + properties[ARG_WRITE_TO_SCREEN] = g_param_spec_boolean( + "write-to-screen", + "Write to Screen", + "Set to True in order to write linear-phase FIR filters to\n\t\t\t" + "the screen.", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT + ); + properties[ARG_FILENAME] = g_param_spec_string( + "filename", + "Filename", + "Name of file to write transfer functions and/or FIR filters to. If not\n\t\t\t" + "given, no file is produced.", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT + ); + properties[ARG_FIR_LENGTH] = g_param_spec_int64( + "fir-length", + "FIR filter length", + "Length in samples of FIR filters produced. Must be an even number.", + 1, G_MAXINT64, 16384, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT + ); + properties[ARG_FIR_SAMPLE_RATE] = g_param_spec_int( + "fir-sample-rate", + "FIR sample rate", + "The sample rate of the data being filtered", + 1, G_MAXINT, 16384, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT + ); + properties[ARG_FIR_FILTER] = g_param_spec_value_array( + "fir-filter", + "FIR Filter", + "The computed FIR filter", + g_param_spec_double( + "sample", + "Sample", + "A sample from the FIR filter", + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS + ), + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE + ); + + + g_object_class_install_property( + gobject_class, + ARG_MEASUREMENT_FREQUENCY, + properties[ARG_MEASUREMENT_FREQUENCY] + ); + g_object_class_install_property( + gobject_class, + ARG_UPDATE_SAMPLES, + properties[ARG_UPDATE_SAMPLES] + ); + g_object_class_install_property( + gobject_class, + ARG_AVERAGE_SAMPLES, + properties[ARG_AVERAGE_SAMPLES] + ); + g_object_class_install_property( + gobject_class, + ARG_WRITE_TO_SCREEN, + properties[ARG_WRITE_TO_SCREEN] + ); + g_object_class_install_property( + gobject_class, + ARG_FILENAME, + properties[ARG_FILENAME] + ); + g_object_class_install_property( + gobject_class, + ARG_FIR_LENGTH, + properties[ARG_FIR_LENGTH] + ); + g_object_class_install_property( + gobject_class, + ARG_FIR_SAMPLE_RATE, + properties[ARG_FIR_SAMPLE_RATE] + ); + g_object_class_install_property( + gobject_class, + ARG_FIR_FILTER, + properties[ARG_FIR_FILTER] + ); +} + + +/* + * init() + */ + + +static void gstlal_lpfilter_init(GSTLALLPFilter *element) { + + g_signal_connect(G_OBJECT(element), "notify::fir-filter", G_CALLBACK(rebuild_workspace_and_reset), NULL); + element->rate = 0; + element->unit_size = 0; + + gst_base_sink_set_sync(GST_BASE_SINK(element), FALSE); + gst_base_sink_set_async_enabled(GST_BASE_SINK(element), FALSE); +} + diff --git a/gstlal-calibration/gst/lal/gstlal_lpfilter.h b/gstlal-calibration/gst/lal/gstlal_lpfilter.h new file mode 100644 index 0000000000..8026a6d6c5 --- /dev/null +++ b/gstlal-calibration/gst/lal/gstlal_lpfilter.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018 Aaron Viets <aaron.viets@ligo.org> + * + * 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., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef __GSTLAL_LPFILTER_H__ +#define __GSTLAL_LPFILTER_H__ + + +#include <complex.h> + +#include <glib.h> +#include <gst/gst.h> +#include <gst/base/gstbasesink.h> + +#include <fftw3.h> + + +G_BEGIN_DECLS + + +#define GSTLAL_LPFILTER_TYPE \ + (gstlal_lpfilter_get_type()) +#define GSTLAL_LPFILTER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GSTLAL_LPFILTER_TYPE, GSTLALLPFilter)) +#define GSTLAL_LPFILTER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GSTLAL_LPFILTER_TYPE, GSTLALLPFilterClass)) +#define GST_IS_GSTLAL_LPFILTER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GSTLAL_LPFILTER_TYPE)) +#define GST_IS_GSTLAL_LPFILTER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GSTLAL_LPFILTER_TYPE)) + + +typedef struct _GSTLALLPFilter GSTLALLPFilter; +typedef struct _GSTLALLPFilterClass GSTLALLPFilterClass; + + +/** + * GSTLALLPFilter: + */ + + +struct _GSTLALLPFilter { + GstBaseSink basesink; + + /* stream info */ + gint rate; + gint unit_size; + enum gstlal_lpfilter_data_type { + GSTLAL_LPFILTER_Z64 = 0, + GSTLAL_LPFILTER_Z128, + } data_type; + + /* timestamp bookkeeping */ + GstClockTime t0; + guint64 offset0; + guint64 next_in_offset; + + /* FIR filter parameters */ + double input_average; + gint64 num_in_avg; + + /* properties */ + double measurement_frequency; + gint64 update_samples; + gint64 average_samples; + gboolean write_to_screen; + char *filename; + gint64 fir_length; + gint fir_sample_rate; + complex double *fir_filter; + fftw_plan fir_plan; +}; + + +/** + * GSTLALLPFilterClass: + * @parent_class: the parent class + */ + + +struct _GSTLALLPFilterClass { + GstBaseSinkClass parent_class; +}; + + +GType gstlal_lpfilter_get_type(void); + + +G_END_DECLS + + +#endif /* __GSTLAL_LPFILTER_H__ */ diff --git a/gstlal-calibration/gst/lal/gstlal_transferfunction.h b/gstlal-calibration/gst/lal/gstlal_transferfunction.h index 62f0fb7a56..e827215a8c 100644 --- a/gstlal-calibration/gst/lal/gstlal_transferfunction.h +++ b/gstlal-calibration/gst/lal/gstlal_transferfunction.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Aaron Viets <aaron.viets@ligo.org> + * Copyright (C) 2018 Aaron Viets <aaron.viets@ligo.org> * * 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 @@ -136,7 +136,7 @@ struct _GSTLALTransferFunction { gboolean update_after_gap; gboolean write_to_screen; char *filename; - int make_fir_filters; + double make_fir_filters; gint64 fir_length; double frequency_resolution; int high_pass; diff --git a/gstlal-calibration/gst/lal/gstlalcalibration.c b/gstlal-calibration/gst/lal/gstlalcalibration.c index f29298198e..575ee41fc7 100644 --- a/gstlal-calibration/gst/lal/gstlalcalibration.c +++ b/gstlal-calibration/gst/lal/gstlalcalibration.c @@ -64,6 +64,7 @@ #include <gstlal_fccupdate.h> #include <gstlal_transferfunction.h> #include <gstlal_trackfrequency.h> +#include <gstlal_lpfilter.h> /* @@ -95,6 +96,7 @@ static gboolean plugin_init(GstPlugin *plugin) {"lal_fcc_update", GSTLAL_FCC_UPDATE_TYPE}, {"lal_transferfunction", GSTLAL_TRANSFERFUNCTION_TYPE}, {"lal_trackfrequency", GSTLAL_TRACKFREQUENCY_TYPE}, + {"lal_lpfilter", GSTLAL_LPFILTER_TYPE}, {NULL, 0}, }; diff --git a/gstlal-calibration/python/calibration_parts.py b/gstlal-calibration/python/calibration_parts.py index 1b112ed625..ead42aa9bf 100644 --- a/gstlal-calibration/python/calibration_parts.py +++ b/gstlal-calibration/python/calibration_parts.py @@ -191,7 +191,7 @@ def remove_harmonics(pipeline, signal, f0, num_harmonics, f0_var, filter_latency return elem -def remove_harmonics_with_witness(pipeline, signal, witness, f0, num_harmonics, f0_var, filter_latency, compute_rate = 16, rate_out = 16384, num_avg = 320, obsready = None): +def remove_harmonics_with_witness(pipeline, signal, witness, f0, num_harmonics, f0_var, filter_latency, compute_rate = 16, rate_out = 16384, num_avg = 2048, obsready = None): # remove line(s) from a spectrum. filter length for demodulation (given in seconds) is adjustable # function argument caps must be complex caps diff --git a/gstlal-calibration/tests/lal_transferfunction_test.py b/gstlal-calibration/tests/lal_transferfunction_test.py index 5d414e0b13..07e01d60ac 100755 --- a/gstlal-calibration/tests/lal_transferfunction_test.py +++ b/gstlal-calibration/tests/lal_transferfunction_test.py @@ -170,6 +170,31 @@ def lal_transferfunction_03(pipeline, name): return pipeline +def lal_transferfunction_04(pipeline, name): + + # + # This test produces three-channel data to be read into lal_transferfunction + # + + rate = 16384 # Hz + buffer_length = 1.0 # seconds + test_duration = 500.0 # seconds + width = 64 # bits + channels = 1 + freq = 512 # Hz + + # + # 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 = pipeparts.mktee(pipeline, hoft) + shifted_hoft = pipeparts.mkgeneric(pipeline, hoft, "lal_shift", shift = 35024) + interleaved_data = calibration_parts.mkinterleave(pipeline, calibration_parts.list_srcs(pipeline, hoft, shifted_hoft)) + pipeparts.mkgeneric(pipeline, interleaved_data, "lal_transferfunction", fft_length = 4 * rate, fft_overlap = 2 * rate, num_ffts = 64, update_samples = rate * test_duration, make_fir_filters = 1, fir_length = rate, update_after_gap = True, filename = "lpfilter.txt") + + return pipeline + # # ============================================================================= # @@ -180,8 +205,9 @@ def lal_transferfunction_03(pipeline, name): #test_common.build_and_run(lal_transferfunction_01, "lal_transferfunction_01") -test_common.build_and_run(lal_transferfunction_02, "lal_transferfunction_02") +#test_common.build_and_run(lal_transferfunction_02, "lal_transferfunction_02") #test_common.build_and_run(lal_transferfunction_03, "lal_transferfunction_03") +test_common.build_and_run(lal_transferfunction_04, "lal_transferfunction_04") -- GitLab