From 1c1a08b49b5b5b12728c91ab5abd1e5fdb43bb3e Mon Sep 17 00:00:00 2001
From: Aaron Viets <aaron.viets@ligo.org>
Date: Sat, 2 Dec 2017 21:32:12 -0800
Subject: [PATCH] gstlal_compute_strain:  in offline mode, apply timestamp
 shift to the kappas to compute them in time for lock stretches

---
 gstlal-calibration/bin/gstlal_compute_strain  | 85 ++++++++++---------
 gstlal-calibration/gst/lal/gstlal_insertgap.c | 66 ++++++++++++--
 gstlal-calibration/gst/lal/gstlal_insertgap.h |  5 ++
 .../python/calibration_parts.py               |  6 +-
 4 files changed, 113 insertions(+), 49 deletions(-)

diff --git a/gstlal-calibration/bin/gstlal_compute_strain b/gstlal-calibration/bin/gstlal_compute_strain
index 36efc55081..bbeb9b0da6 100644
--- a/gstlal-calibration/bin/gstlal_compute_strain
+++ b/gstlal-calibration/bin/gstlal_compute_strain
@@ -351,6 +351,15 @@ compute_calib_factors_complex_caps = "audio/x-raw, format=Z128LE, rate=%d, chann
 integration_samples = int(options.demodulation_filter_time) * options.compute_factors_sr
 factors_average_samples = int(options.factors_averaging_time) * options.compute_factors_sr
 median_smoothing_samples = int(options.median_smoothing_time) * options.compute_factors_sr
+# if we are calibrating offline using many short jobs in parallel, we want to be able to
+# compute the kappas in time for the first lock stretch. So we will shift the timestamps.
+kappas_delay = 0
+if options.data_source == "frames" and (options.apply_kappatst or options.apply_kappapu or options.apply_kappac or options.update_fcc):
+	kappas_delay += median_smoothing_samples + factors_average_samples + integration_samples
+	if options.update_fcc:
+		 kappas_delay += int(options.fcc_averaging_time) * options.compute_factors_sr
+# To keep the adder happy, we need to throw away any kappas with timestamps before the start-of-stream
+kappas_chop_length = int(1000000000 * kappas_delay / options.compute_factors_sr) # in nanoseconds
 
 # Set up string for the channels suffix and prefix as provided by the user
 if options.chan_suffix is not None:
@@ -709,12 +718,12 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappatst or not
 	derrtee = pipeparts.mktee(pipeline, darm_err)
 
 	# demodulate the PCAL channel and apply the PCAL correction factor at the DARM actuation line frequency
-	pcal_at_darm_act_freq = calibration_parts.demodulate(pipeline, pcaltee, darm_act_line_freq, td, compute_calib_factors_complex_caps, integration_samples, pcal_corr_at_darm_act_freq_real, pcal_corr_at_darm_act_freq_imag)
+	pcal_at_darm_act_freq = calibration_parts.demodulate(pipeline, pcaltee, darm_act_line_freq, td, compute_calib_factors_complex_caps, integration_samples, kappas_delay, kappas_chop_length, pcal_corr_at_darm_act_freq_real, pcal_corr_at_darm_act_freq_imag)
 	if not options.no_kappapu or not options.no_kappac or not options.no_fcc or not options.no_srcQ or not options.no_fs:
 		pcal_at_darm_act_freq = pipeparts.mktee(pipeline, pcal_at_darm_act_freq)
 
 	# demodulate DARM_ERR at the DARM actuation line frequency
-	derr_at_darm_act_freq = calibration_parts.demodulate(pipeline, derrtee, darm_act_line_freq, td, compute_calib_factors_complex_caps, integration_samples)
+	derr_at_darm_act_freq = calibration_parts.demodulate(pipeline, derrtee, darm_act_line_freq, td, compute_calib_factors_complex_caps, integration_samples, kappas_delay, kappas_chop_length)
 	if options.dewhitening:
 		# dewhiten DARM_ERR at the DARM actuation line frequency
 		derr_at_darm_act_freq = calibration_parts.complex_audioamplify(pipeline, derr_at_darm_act_freq, derr_dewhiten_at_darm_act_freq_real, derr_dewhiten_at_darm_act_freq_imag)
@@ -722,10 +731,10 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappatst or not
 		derr_at_darm_act_freq = pipeparts.mktee(pipeline, derr_at_darm_act_freq)
 
 	# demodulate the TST excitation channel at the ESD actuation line frequency
-	tstexc_at_esd_act_freq = calibration_parts.demodulate(pipeline, tstexc, esd_act_line_freq, td, compute_calib_factors_complex_caps, integration_samples)
+	tstexc_at_esd_act_freq = calibration_parts.demodulate(pipeline, tstexc, esd_act_line_freq, td, compute_calib_factors_complex_caps, integration_samples, kappas_delay, kappas_chop_length)
 
 	# demodulate DARM_ERR at the ESD actuation line frequency
-	derr_at_esd_act_freq = calibration_parts.demodulate(pipeline, derrtee, esd_act_line_freq, td, compute_calib_factors_complex_caps, integration_samples)
+	derr_at_esd_act_freq = calibration_parts.demodulate(pipeline, derrtee, esd_act_line_freq, td, compute_calib_factors_complex_caps, integration_samples, kappas_delay, kappas_chop_length)
 	if options.dewhitening:
 		# dewhiten DARM_ERR at the ESD actuation line frequency
 		derr_at_esd_act_freq = calibration_parts.complex_audioamplify(pipeline, derr_at_esd_act_freq, derr_dewhiten_at_esd_act_freq_real, derr_dewhiten_at_esd_act_freq_imag)
@@ -745,11 +754,11 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappatst or not
 
 		if not options.no_coherence:
 			# Gate kappa_tst with the coherence of the PCALY_line1 line
-			ktst_gated = calibration_parts.mkgate(pipeline, ktst, pcaly_line1_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
+			ktst_gated = calibration_parts.mkgate(pipeline, ktst, pcaly_line1_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
 			# Gate kappa_tst with the coherence of the suspension line
-			ktst_gated = calibration_parts.mkgate(pipeline, ktst_gated, sus_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
+			ktst_gated = calibration_parts.mkgate(pipeline, ktst_gated, sus_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
 			# Gate kappa_tst with the coherence of the DARM line
-			ktst_gated = calibration_parts.mkgate(pipeline, ktst_gated, darm_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
+			ktst_gated = calibration_parts.mkgate(pipeline, ktst_gated, darm_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
 
 			if not options.no_dq_vector:
 				ktst_gated = pipeparts.mktee(pipeline, ktst_gated)
@@ -771,10 +780,10 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappatst or not
 # If we're also computing \kappa_c, f_cc, or \kappa_pu, keep going
 if not options.no_kappac or not options.no_fcc or not options.no_kappapu or not options.no_srcQ or not options.no_fs:
 	# demodulate excitation channel at PU actuation line frequency
-	exc_at_pu_act_freq = calibration_parts.demodulate(pipeline, exc, pu_act_esd_line_freq, td, compute_calib_factors_complex_caps, integration_samples)
+	exc_at_pu_act_freq = calibration_parts.demodulate(pipeline, exc, pu_act_esd_line_freq, td, compute_calib_factors_complex_caps, integration_samples, kappas_delay, kappas_chop_length)
 
 	# demodulate DARM_ERR at PU actuation line frequency
-	derr_at_pu_act_freq = calibration_parts.demodulate(pipeline, derrtee, pu_act_esd_line_freq, td, compute_calib_factors_complex_caps, integration_samples)
+	derr_at_pu_act_freq = calibration_parts.demodulate(pipeline, derrtee, pu_act_esd_line_freq, td, compute_calib_factors_complex_caps, integration_samples, kappas_delay, kappas_chop_length)
 	if options.dewhitening:
 		# dewhiten DARM_ERR at the PU actuation line frequency
 		derr_at_pu_act_freq = calibration_parts.complex_audioamplify(pipeline, derr_at_pu_act_freq, derr_dewhiten_at_pu_act_freq_real, derr_dewhiten_at_pu_act_freq_imag)
@@ -802,11 +811,11 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappapu or not
 
 		if not options.no_coherence:
 			# Gate kappa_pu with the coherence of the DARM line
-			kpu_gated = calibration_parts.mkgate(pipeline, kpu, darm_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
+			kpu_gated = calibration_parts.mkgate(pipeline, kpu, darm_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
 			# Gate kappa_pu with the coherence of the PCALY_line1 line
-			kpu_gated = calibration_parts.mkgate(pipeline, kpu_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
+			kpu_gated = calibration_parts.mkgate(pipeline, kpu_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
 			# Gate kappa_pu with the coherence of the suspension coherence
-			kpu_gated = calibration_parts.mkgate(pipeline, kpu_gated, sus_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
+			kpu_gated = calibration_parts.mkgate(pipeline, kpu_gated, sus_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
 
 			if not options.no_dq_vector:
 				kpu_gated = pipeparts.mktee(pipeline, kpu_gated)
@@ -828,10 +837,10 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappapu or not
 	# Finally, compute \kappa_c and f_cc
 	if not options.no_kappac or not options.no_fcc or not options.no_srcQ or not options.no_fs:
 		# demodulate PCAL channel and apply the PCAL correction factor at optical gain and f_cc line frequency
-		pcal_at_opt_gain_freq = calibration_parts.demodulate(pipeline, pcaltee, opt_gain_fcc_line_freq, td, compute_calib_factors_complex_caps, integration_samples, pcal_corr_at_opt_gain_fcc_freq_real, pcal_corr_at_opt_gain_fcc_freq_imag)
+		pcal_at_opt_gain_freq = calibration_parts.demodulate(pipeline, pcaltee, opt_gain_fcc_line_freq, td, compute_calib_factors_complex_caps, integration_samples, kappas_delay, kappas_chop_length, pcal_corr_at_opt_gain_fcc_freq_real, pcal_corr_at_opt_gain_fcc_freq_imag)
 
 		# demodulate DARM_ERR at optical gain and f_cc line frequency
-		derr_at_opt_gain_freq = calibration_parts.demodulate(pipeline, derrtee, opt_gain_fcc_line_freq, td, compute_calib_factors_complex_caps, integration_samples)
+		derr_at_opt_gain_freq = calibration_parts.demodulate(pipeline, derrtee, opt_gain_fcc_line_freq, td, compute_calib_factors_complex_caps, integration_samples, kappas_delay, kappas_chop_length)
 		if options.dewhitening:
 			# dewhiten DARM_ERR at optical gain and f_cc line frequency
 			derr_at_opt_gain_freq = calibration_parts.complex_audioamplify(pipeline, derr_at_opt_gain_freq, derr_dewhiten_at_opt_gain_fcc_freq_real, derr_dewhiten_at_opt_gain_fcc_freq_imag)
@@ -863,10 +872,10 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappapu or not
 
 			if not options.no_coherence:
 				# Gate kappa_c with all four of the calibration lines
-				kc_gated = calibration_parts.mkgate(pipeline, kc, pcaly_line2_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
-				kc_gated = calibration_parts.mkgate(pipeline, kc_gated, darm_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
-				kc_gated = calibration_parts.mkgate(pipeline, kc_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
-				kc_gated = calibration_parts.mkgate(pipeline, kc_gated, sus_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
+				kc_gated = calibration_parts.mkgate(pipeline, kc, pcaly_line2_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
+				kc_gated = calibration_parts.mkgate(pipeline, kc_gated, darm_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
+				kc_gated = calibration_parts.mkgate(pipeline, kc_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
+				kc_gated = calibration_parts.mkgate(pipeline, kc_gated, sus_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
 
 				if not options.no_dq_vector:
 					kc_gated = pipeparts.mktee(pipeline, kc_gated)
@@ -891,10 +900,10 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappapu or not
 
 			if not options.no_coherence:
 				# Gate f_cc with all four of the calibration lines
-				fcc_gated = calibration_parts.mkgate(pipeline, fcc, pcaly_line2_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
-				fcc_gated = calibration_parts.mkgate(pipeline, fcc_gated, darm_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
-				fcc_gated = calibration_parts.mkgate(pipeline, fcc_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
-				fcc_gated = calibration_parts.mkgate(pipeline, fcc_gated, sus_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
+				fcc_gated = calibration_parts.mkgate(pipeline, fcc, pcaly_line2_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
+				fcc_gated = calibration_parts.mkgate(pipeline, fcc_gated, darm_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
+				fcc_gated = calibration_parts.mkgate(pipeline, fcc_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
+				fcc_gated = calibration_parts.mkgate(pipeline, fcc_gated, sus_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
 
 				if not options.no_dq_vector:
 					fcc_gated = pipeparts.mktee(pipeline, fcc_gated)
@@ -915,10 +924,10 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappapu or not
 	# compute f_s and Q
 	if not options.no_fs or not options.no_srcQ:
 		# demodulate PCAL channel and apply the PCAL correction factor at SRC detuning line frequency
-		pcal_at_src_freq = calibration_parts.demodulate(pipeline, pcaltee, src_pcal_line_freq, td, compute_calib_factors_complex_caps, integration_samples, pcal_corr_at_src_freq_real, pcal_corr_at_src_freq_imag)
+		pcal_at_src_freq = calibration_parts.demodulate(pipeline, pcaltee, src_pcal_line_freq, td, compute_calib_factors_complex_caps, integration_samples, kappas_delay, kappas_chop_length, pcal_corr_at_src_freq_real, pcal_corr_at_src_freq_imag)
 
 		# demodulate DARM_ERR at SRC detuning line frequency
-		derr_at_src_freq = calibration_parts.demodulate(pipeline, derrtee, src_pcal_line_freq, td, compute_calib_factors_complex_caps, integration_samples)
+		derr_at_src_freq = calibration_parts.demodulate(pipeline, derrtee, src_pcal_line_freq, td, compute_calib_factors_complex_caps, integration_samples, kappas_delay, kappas_chop_length)
 
 		# Compute the factor Xi which will be used for the f_s and src_Q calculations
 		if not options.factors_from_filters_file:
@@ -948,10 +957,10 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappapu or not
 
 			if not options.no_coherence:
 				# Gate f_s with all four of the calibration lines
-				fs_gated = calibration_parts.mkgate(pipeline, fs, pcaly_line2_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
-				fs_gated = calibration_parts.mkgate(pipeline, fs_gated, darm_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
-				fs_gated = calibration_parts.mkgate(pipeline, fs_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
-				fs_gated = calibration_parts.mkgate(pipeline, fs_gated, sus_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
+				fs_gated = calibration_parts.mkgate(pipeline, fs, pcaly_line2_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
+				fs_gated = calibration_parts.mkgate(pipeline, fs_gated, darm_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
+				fs_gated = calibration_parts.mkgate(pipeline, fs_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
+				fs_gated = calibration_parts.mkgate(pipeline, fs_gated, sus_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
 
 				if not options.no_dq_vector:
 					fs_gated = pipeparts.mktee(pipeline, fs_gated)
@@ -978,10 +987,10 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappapu or not
 
 			if not options.no_coherence:
 				# Gate SRC_Q with all four of the calibration lines
-				srcQ_inv_gated = calibration_parts.mkgate(pipeline, srcQ_inv, pcaly_line2_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
-				srcQ_inv_gated = calibration_parts.mkgate(pipeline, srcQ_inv_gated, darm_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
-				srcQ_inv_gated = calibration_parts.mkgate(pipeline, srcQ_inv_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
-				srcQ_inv_gated = calibration_parts.mkgate(pipeline, srcQ_inv_gated, sus_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = -integration_samples, invert_control = True)
+				srcQ_inv_gated = calibration_parts.mkgate(pipeline, srcQ_inv, pcaly_line2_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
+				srcQ_inv_gated = calibration_parts.mkgate(pipeline, srcQ_inv_gated, darm_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
+				srcQ_inv_gated = calibration_parts.mkgate(pipeline, srcQ_inv_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
+				srcQ_inv_gated = calibration_parts.mkgate(pipeline, srcQ_inv_gated, sus_coh, options.coherence_uncertainty_threshold, short_queue, long_queue, attack_length = kappas_delay - integration_samples, invert_control = True)
 
 				if not options.no_dq_vector:
 					srcQ_inv_gated = pipeparts.mktee(pipeline, srcQ_inv_gated)
@@ -1285,37 +1294,37 @@ if not options.no_dq_vector:
 	# KAPPATST BITS BRANCH
 	#
 	if not options.no_kappatst:
-		ktstSmoothInRange, ktstMedianUncorrupt = calibration_parts.compute_kappa_bits(pipeline, smooth_ktstRtee, smooth_ktstItee, smooth_ktstRdq, smooth_ktstIdq, options.expected_kappatst_real, options.expected_kappatst_imag, options.kappatst_real_ok_var, options.kappatst_imag_ok_var, int(median_smoothing_samples / 2) + factors_average_samples + integration_samples, long_queue, short_queue, status_out_smooth = 2048, status_out_median = 4096, starting_rate = options.compute_factors_sr, ending_rate = calibstatesr)
+		ktstSmoothInRange, ktstMedianUncorrupt = calibration_parts.compute_kappa_bits(pipeline, smooth_ktstRtee, smooth_ktstItee, smooth_ktstRdq, smooth_ktstIdq, options.expected_kappatst_real, options.expected_kappatst_imag, options.kappatst_real_ok_var, options.kappatst_imag_ok_var, int(median_smoothing_samples / 2) + factors_average_samples, long_queue, short_queue, status_out_smooth = 2048, status_out_median = 4096, starting_rate = options.compute_factors_sr, ending_rate = calibstatesr)
 
 	#
 	# KAPPAPU BITS BRANCH
 	#
 	if not options.no_kappapu:
-		kpuSmoothInRange, kpuMedianUncorrupt = calibration_parts.compute_kappa_bits(pipeline, smooth_kpuRtee, smooth_kpuItee, smooth_kpuRdq, smooth_kpuIdq, options.expected_kappapu_real, options.expected_kappapu_imag, options.kappapu_real_ok_var, options.kappapu_imag_ok_var, int(median_smoothing_samples / 2) + factors_average_samples + integration_samples, long_queue, short_queue, status_out_smooth = 8192, status_out_median = 16384, starting_rate = options.compute_factors_sr, ending_rate = calibstatesr)
+		kpuSmoothInRange, kpuMedianUncorrupt = calibration_parts.compute_kappa_bits(pipeline, smooth_kpuRtee, smooth_kpuItee, smooth_kpuRdq, smooth_kpuIdq, options.expected_kappapu_real, options.expected_kappapu_imag, options.kappapu_real_ok_var, options.kappapu_imag_ok_var, int(median_smoothing_samples / 2) + factors_average_samples, long_queue, short_queue, status_out_smooth = 8192, status_out_median = 16384, starting_rate = options.compute_factors_sr, ending_rate = calibstatesr)
 
 	#
 	# KAPPAC BITS BRANCH
 	#
 	if not options.no_kappac:
-		kcSmoothInRange, kcMedianUncorrupt = calibration_parts.compute_kappa_bits_only_real(pipeline, smooth_kctee, smooth_kcdq, options.expected_kappac, options.kappac_ok_var, int(median_smoothing_samples / 2) + factors_average_samples + integration_samples, status_out_smooth = 131072, status_out_median = 262144, starting_rate = options.compute_factors_sr, ending_rate = calibstatesr)
+		kcSmoothInRange, kcMedianUncorrupt = calibration_parts.compute_kappa_bits_only_real(pipeline, smooth_kctee, smooth_kcdq, options.expected_kappac, options.kappac_ok_var, int(median_smoothing_samples / 2) + factors_average_samples, status_out_smooth = 131072, status_out_median = 262144, starting_rate = options.compute_factors_sr, ending_rate = calibstatesr)
 
 	#
 	# FCC BITS BRANCH
 	#
 	if not options.no_fcc:
-		fccSmoothInRange, fccMedianUncorrupt = calibration_parts.compute_kappa_bits_only_real(pipeline, smooth_fcctee, smooth_fccdq, fcc_default, options.fcc_ok_var, int(median_smoothing_samples / 2) + factors_average_samples + integration_samples + options.fcc_averaging_time * options.compute_factors_sr, status_out_smooth = 524288, status_out_median = 1048576, starting_rate = options.compute_factors_sr, ending_rate = calibstatesr)
+		fccSmoothInRange, fccMedianUncorrupt = calibration_parts.compute_kappa_bits_only_real(pipeline, smooth_fcctee, smooth_fccdq, fcc_default, options.fcc_ok_var, int(median_smoothing_samples / 2) + factors_average_samples + options.fcc_averaging_time * options.compute_factors_sr, status_out_smooth = 524288, status_out_median = 1048576, starting_rate = options.compute_factors_sr, ending_rate = calibstatesr)
 
 	#
 	# FS BITS BRANCH
 	#
 	if not options.no_fs:
-		fsSmoothInRange, fsMedianUncorrupt = calibration_parts.compute_kappa_bits_only_real(pipeline, smooth_fs, smooth_fsdq, options.expected_fs, options.fs_ok_var, int(median_smoothing_samples / 2) + factors_average_samples + integration_samples, status_out_smooth = 67108864, status_out_median = 134217728, starting_rate = options.compute_factors_sr, ending_rate = calibstatesr)
+		fsSmoothInRange, fsMedianUncorrupt = calibration_parts.compute_kappa_bits_only_real(pipeline, smooth_fs, smooth_fsdq, options.expected_fs, options.fs_ok_var, int(median_smoothing_samples / 2) + factors_average_samples, status_out_smooth = 67108864, status_out_median = 134217728, starting_rate = options.compute_factors_sr, ending_rate = calibstatesr)
 
 	#
 	# SRCQ BITS BRANCH
 	#
 	if not options.no_srcQ:
-		srcQSmoothInRange, srcQMedianUncorrupt = calibration_parts.compute_kappa_bits_only_real(pipeline, smooth_srcQ_inv, smooth_srcQdq, options.expected_srcQ, options.srcQ_ok_var, int(median_smoothing_samples / 2) + factors_average_samples + integration_samples, status_out_smooth = 268435456, status_out_median = 536870912, starting_rate = options.compute_factors_sr, ending_rate = calibstatesr)
+		srcQSmoothInRange, srcQMedianUncorrupt = calibration_parts.compute_kappa_bits_only_real(pipeline, smooth_srcQ_inv, smooth_srcQdq, options.expected_srcQ, options.srcQ_ok_var, int(median_smoothing_samples / 2) + factors_average_samples, status_out_smooth = 268435456, status_out_median = 536870912, starting_rate = options.compute_factors_sr, ending_rate = calibstatesr)
 
 	#
 	# COHERENCE BITS BRANCH
diff --git a/gstlal-calibration/gst/lal/gstlal_insertgap.c b/gstlal-calibration/gst/lal/gstlal_insertgap.c
index b36255f2b7..edc520989a 100644
--- a/gstlal-calibration/gst/lal/gstlal_insertgap.c
+++ b/gstlal-calibration/gst/lal/gstlal_insertgap.c
@@ -149,22 +149,26 @@ static gboolean check_data(double complex data, double *bad_data_intervals, gint
 	gint i;
 	if(complex_data){
 		double data_im = cimag(data);
-		if(data_re <= bad_data_intervals[0] || data_re >= bad_data_intervals[array_length - 1] || data_im <= bad_data_intervals[0] || data_im >= bad_data_intervals[array_length - 1])
-			return TRUE;
-		for(i = 1; i < array_length - 1; i += 2) {
-			if((data_re >= bad_data_intervals[i] && data_re <= bad_data_intervals[i + 1]) || (data_im >= bad_data_intervals[i] && data_im <= bad_data_intervals[i + 1]))
+		if(bad_data_intervals) {
+			if(data_re <= bad_data_intervals[0] || data_re >= bad_data_intervals[array_length - 1] || data_im <= bad_data_intervals[0] || data_im >= bad_data_intervals[array_length - 1])
 				return TRUE;
+			for(i = 1; i < array_length - 1; i += 2) {
+				if((data_re >= bad_data_intervals[i] && data_re <= bad_data_intervals[i + 1]) || (data_im >= bad_data_intervals[i] && data_im <= bad_data_intervals[i + 1]))
+					return TRUE;
+			}
 		}
 		if(remove_nan && (isnan(data_re) || isnan(data_im)))
 			return TRUE;
 		if(remove_inf && (isinf(data_re) || isinf(data_im)))
 			return TRUE;
 	} else {
-		if(data_re <= bad_data_intervals[0] || data_re >= bad_data_intervals[array_length - 1])
-			return TRUE;
-		for(i = 1; i < array_length - 1; i += 2) {
-			if(data_re >= bad_data_intervals[i] && data_re <= bad_data_intervals[i + 1])
+		if(bad_data_intervals) {
+			if(data_re <= bad_data_intervals[0] || data_re >= bad_data_intervals[array_length - 1])
 				return TRUE;
+			for(i = 1; i < array_length - 1; i += 2) {
+				if(data_re >= bad_data_intervals[i] && data_re <= bad_data_intervals[i + 1])
+					return TRUE;
+			}
 		}
 		if(remove_nan && (isnan(data_re)))
 			return TRUE;
@@ -483,6 +487,26 @@ static GstFlowReturn chain(GstPad *pad, GstObject *parent, GstBuffer *sinkbuf)
 	GstFlowReturn result = GST_FLOW_OK;
 	GST_DEBUG_OBJECT(element, "received %" GST_BUFFER_BOUNDARIES_FORMAT, GST_BUFFER_BOUNDARIES_ARGS(sinkbuf));
 
+	if(GST_BUFFER_PTS_IS_VALID(sinkbuf)) {
+		/* Set the timestamp of the first output sample) */
+		if(element->t0 == GST_CLOCK_TIME_NONE)
+			element->t0 = GST_BUFFER_PTS(sinkbuf) + element->chop_length;
+
+		/* If we are throwing away any initial data, do it now */
+		if(GST_BUFFER_PTS(sinkbuf) + GST_BUFFER_DURATION(sinkbuf) < element->t0) {
+			gst_buffer_unref(sinkbuf);
+			goto done;
+		} else if(GST_BUFFER_PTS(sinkbuf) < element->t0) {
+			guint64 size_removed = element->unit_size * gst_util_uint64_scale_int_round(element->t0 - GST_BUFFER_PTS(sinkbuf), element->rate, 1000000000);
+			guint64 time_removed = gst_util_uint64_scale_int_round(size_removed / element->unit_size, 1000000000, element->rate);
+			guint64 newsize = element->unit_size * (GST_BUFFER_OFFSET_END(sinkbuf) - GST_BUFFER_OFFSET(sinkbuf)) - size_removed;
+			gst_buffer_resize(sinkbuf, size_removed, newsize);
+			GST_BUFFER_OFFSET(sinkbuf) = GST_BUFFER_OFFSET(sinkbuf) + size_removed / element->unit_size;
+			GST_BUFFER_PTS(sinkbuf) = GST_BUFFER_PTS(sinkbuf) + time_removed;
+			GST_BUFFER_DURATION(sinkbuf) = GST_BUFFER_DURATION(sinkbuf) - time_removed;
+		}
+	}
+
 	/* if buffer does not possess valid metadata or is zero length and we are not filling in discontinuities, push gap downstream */
 	if(!(GST_BUFFER_PTS_IS_VALID(sinkbuf) && GST_BUFFER_DURATION_IS_VALID(sinkbuf) && GST_BUFFER_OFFSET_IS_VALID(sinkbuf) && GST_BUFFER_OFFSET_END_IS_VALID(sinkbuf)) || (!element->fill_discont && (GST_BUFFER_DURATION(sinkbuf) == 0 || GST_BUFFER_OFFSET(sinkbuf) == GST_BUFFER_OFFSET_END(sinkbuf)))) {
 		GST_DEBUG_OBJECT(element, "pushing gap buffer at timestamp %lu seconds", (long unsigned) GST_TIME_AS_SECONDS(GST_BUFFER_PTS(sinkbuf)));
@@ -598,7 +622,8 @@ enum property {
 	ARG_FILL_DISCONT,
 	ARG_REPLACE_VALUE,
 	ARG_BAD_DATA_INTERVALS,
-	ARG_BLOCK_DURATION
+	ARG_BLOCK_DURATION,
+	ARG_CHOP_LENGTH
 };
 
 
@@ -632,11 +657,16 @@ static void set_property(GObject *object, enum property prop_id, const GValue *v
 		element->bad_data_intervals = g_malloc(16 * sizeof(double));
 		element->array_length = 1;
 		gstlal_doubles_from_g_value_array(va, element->bad_data_intervals, &element->array_length);
+		if(element->array_length % 2)
+			GST_ERROR_OBJECT(element, "Array length for property bad_data_intervals must be even");
 		break;
 	}
 	case ARG_BLOCK_DURATION:
 		element->block_duration = g_value_get_uint64(value);
 		break;
+	case ARG_CHOP_LENGTH:
+		element->chop_length = g_value_get_uint64(value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
 		break;
@@ -677,6 +707,9 @@ static void get_property(GObject *object, enum property prop_id, GValue *value,
 	case ARG_BLOCK_DURATION:
 		g_value_set_uint64(value, element->block_duration);
 		break;
+	case ARG_CHOP_LENGTH:
+		g_value_set_uint64(value, element->chop_length);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
 		break;
@@ -847,6 +880,18 @@ static void gstlal_insertgap_class_init(GSTLALInsertGapClass *klass)
 			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
 		)
 	);
+
+	g_object_class_install_property(
+		gobject_class,
+		ARG_CHOP_LENGTH,
+		g_param_spec_uint64(
+			"chop-length",
+			"Chop length",
+			"Amount of initial data to throw away before producing output data, in nanoseconds.",
+			0, G_MAXUINT64, 0,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
 }
 
 
@@ -878,6 +923,9 @@ static void gstlal_insertgap_init(GSTLALInsertGap *element)
 	element->srcpad = pad;
 
 	/* internal data */
+	element->t0 = GST_CLOCK_TIME_NONE;
+	element->bad_data_intervals = NULL;
+	element->array_length = 0;
 	element->rate = 0;
 	element->unit_size = 0;
 	element->last_sinkbuf_ets = 0;
diff --git a/gstlal-calibration/gst/lal/gstlal_insertgap.h b/gstlal-calibration/gst/lal/gstlal_insertgap.h
index 388b6bf8e9..b100193090 100644
--- a/gstlal-calibration/gst/lal/gstlal_insertgap.h
+++ b/gstlal-calibration/gst/lal/gstlal_insertgap.h
@@ -82,6 +82,10 @@ struct _GSTLALInsertGap {
 	guint64 discont_time;
 	guint64 empty_bufs;
 
+	/* timestamp bookkeeping */
+
+	GstClockTime t0;
+
 	/* properties */
 
 	gboolean insert_gap;
@@ -92,6 +96,7 @@ struct _GSTLALInsertGap {
 	double replace_value;
 	double *bad_data_intervals;
 	gint array_length;
+	guint64 chop_length;
 	GstClockTime block_duration;
 };
 
diff --git a/gstlal-calibration/python/calibration_parts.py b/gstlal-calibration/python/calibration_parts.py
index 4d0d670fa2..7e744685b8 100644
--- a/gstlal-calibration/python/calibration_parts.py
+++ b/gstlal-calibration/python/calibration_parts.py
@@ -272,12 +272,14 @@ def split_into_real(pipeline, complex_chan):
 	pipeparts.src_deferred_link(elem, "src_1", imag.get_static_pad("sink"))
 	return real, imag
 
-def demodulate(pipeline, head, freq, td, caps, integration_samples, prefactor_real = 1.0, prefactor_imag = 0.0):
+def demodulate(pipeline, head, freq, td, caps, integration_samples, delay, chop_length, prefactor_real = 1.0, prefactor_imag = 0.0):
 	# demodulate input at a given frequency freq
 
 	head = pipeparts.mkgeneric(pipeline, head, "lal_demodulate", line_frequency = freq, prefactor_real = prefactor_real, prefactor_imag = prefactor_imag)
 	head = mkresample(pipeline, head, 3, True, caps)
-	head = mkcomplexfirbank(pipeline, head, fir_matrix=[numpy.hanning(integration_samples + 1) * 2 / integration_samples], time_domain = td)
+	head = mkcomplexfirbank(pipeline, head, fir_matrix=[numpy.hanning(integration_samples + 1) * 2 / integration_samples], time_domain = td, latency = delay)
+	if chop_length != 0:
+		head = pipeparts.mkgeneric(pipeline, head, "lal_insertgap", chop_length = chop_length)
 
 	return head
 
-- 
GitLab