diff --git a/gstlal-calibration/bin/gstlal_compute_strain b/gstlal-calibration/bin/gstlal_compute_strain index 26072c0476da72d26d5d40c91152ddfe9ce641a5..f2fc4960d287e0fdcb696d52c48a481d7892fc6d 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 587cdc3f285be4fb356f938ebe3185cbb518baff..32bfa909a9c367b0d4cad8a0bee12e08c4bfcd4b 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 4e481d4331db7c84d0b2697afe759c53970d0e15..13e0d095640616fb4ed7cf928c17419a69796980 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 7fff5ddf10122f1be49fe1f48887495afb420b29..b054a43216bb17ebf1d16b105bbf1e53398cab6d 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 69d0d3cdd7d0f9feda81a0b4c41d5e17e1d54bf3..f03225a7361ba826ae2810f0997b7d7d8f2ec577 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)