From 7b6100e6f83d037e2cdb7850a7cc37d702e300ca Mon Sep 17 00:00:00 2001
From: Cody Messick <cody.messick@ligo.org>
Date: Mon, 18 Feb 2019 11:53:55 -0800
Subject: [PATCH] All of the changes necessary to build and run itacac instead
 of itac. Note that itac will no longer work after this commit

---
 gstlal-inspiral/bin/gstlal_inspiral         |   3 +-
 gstlal-inspiral/gst/lal/Makefile.am         |   2 +-
 gstlal-inspiral/gst/lal/gstlalinspiral.c    |   8 +-
 gstlal-inspiral/lib/gstlal_snglinspiral.c   | 285 +++++++-----
 gstlal-inspiral/lib/gstlal_snglinspiral.h   |   5 +-
 gstlal-inspiral/lib/snglinspiralrowtype.c   |  14 +-
 gstlal-inspiral/lib/snglinspiralrowtype.h   |   7 +-
 gstlal-inspiral/python/inspiral.py          |  13 +-
 gstlal-inspiral/python/lloidhandler.py      |  44 +-
 gstlal-inspiral/python/lloidparts.py        |  29 +-
 gstlal-inspiral/python/snglinspiraltable.c  | 485 ++++++++++++++++++--
 gstlal-inspiral/python/snglinspiraltable.py | 115 ++++-
 gstlal/gst/gst/Makefile.am                  |   6 +-
 gstlal/lib/gstlal/gstlal_peakfinder.c       |   4 +
 gstlal/lib/gstlal/gstlal_peakfinder_top.h   |   1 +
 15 files changed, 825 insertions(+), 196 deletions(-)

diff --git a/gstlal-inspiral/bin/gstlal_inspiral b/gstlal-inspiral/bin/gstlal_inspiral
index eef8a1403f..abd079bace 100755
--- a/gstlal-inspiral/bin/gstlal_inspiral
+++ b/gstlal-inspiral/bin/gstlal_inspiral
@@ -843,7 +843,8 @@ for output_file_number, (svd_bank_url_dict, output_url, ranking_stat_output_url,
 	if options.verbose:
 		print >>sys.stderr, "attaching appsinks to pipeline ...",
 	appsync = pipeparts.AppSync(appsink_new_buffer = handler.appsink_new_buffer)
-	appsinks = set(appsync.add_sink(pipeline, src, caps = Gst.Caps.from_string("application/x-lal-snglinspiral"), name = "%s_sink_%d" % (instrument, n)) for instrument, srcs in triggersrc.items() for n, src in enumerate(srcs))
+	appsinks = set(appsync.add_sink(pipeline, src, caps = Gst.Caps.from_string("application/x-lal-snglinspiral"), name = "bank_%s_sink" % bank_id) for bank_id, src in triggersrc.items())
+
 	if options.verbose:
 		print >>sys.stderr, "attached %d, done" % len(appsinks)
 
diff --git a/gstlal-inspiral/gst/lal/Makefile.am b/gstlal-inspiral/gst/lal/Makefile.am
index 3de553fb2d..78b9d439f6 100644
--- a/gstlal-inspiral/gst/lal/Makefile.am
+++ b/gstlal-inspiral/gst/lal/Makefile.am
@@ -4,7 +4,7 @@ plugin_LTLIBRARIES = lib@GSTPLUGINPREFIX@gstlalinspiral.la
 
 lib@GSTPLUGINPREFIX@gstlalinspiral_la_SOURCES = \
 	gstlalinspiral.c \
-	gstlal_itac.h gstlal_itac.c
+	gstlal_itacac.h gstlal_itacac.c
 #gstlal_triggergen.h gstlal_triggergen.c
 #gstlal_blcbc_triggergen.h gstlal_blcbc_triggergen.c \
 #gstlal_coinc.h gstlal_coinc.c \
diff --git a/gstlal-inspiral/gst/lal/gstlalinspiral.c b/gstlal-inspiral/gst/lal/gstlalinspiral.c
index b895cdf3d7..c24e6d22d8 100644
--- a/gstlal-inspiral/gst/lal/gstlalinspiral.c
+++ b/gstlal-inspiral/gst/lal/gstlalinspiral.c
@@ -50,14 +50,15 @@
 
 
 #include <gstlal/gstlal_tags.h>
+#include <gstlal_itacac.h>
 /*
 #include <gstlal_coinc.h>
 #include <gstlal_skymap.h>
 #include <gstlal_triggergen.h>
 #include <gstlal_timeslicechisq.h>
 #include <gstlal_blcbc_triggergen.h>
-*/
 #include <gstlal_itac.h>
+*/
 
 
 /*
@@ -79,8 +80,9 @@ static gboolean plugin_init(GstPlugin *plugin)
 		{"lal_skymap", GSTLAL_SKYMAP_TYPE},
 		{"lal_triggergen", GSTLAL_TRIGGERGEN_TYPE},
 		{"lal_timeslicechisq", GSTLAL_TIMESLICECHISQUARE_TYPE},
-		{"lal_blcbctriggergen", GSTLAL_BLCBC_TRIGGERGEN_TYPE},*/
-		{"lal_itac", GSTLAL_ITAC_TYPE},
+		{"lal_blcbctriggergen", GSTLAL_BLCBC_TRIGGERGEN_TYPE},
+		{"lal_itac", GSTLAL_ITAC_TYPE}, */
+		{"lal_itacac", GSTLAL_ITACAC_TYPE},
 		{NULL, 0},
 	};
 
diff --git a/gstlal-inspiral/lib/gstlal_snglinspiral.c b/gstlal-inspiral/lib/gstlal_snglinspiral.c
index 9c1123f125..02a29f4053 100644
--- a/gstlal-inspiral/lib/gstlal_snglinspiral.c
+++ b/gstlal-inspiral/lib/gstlal_snglinspiral.c
@@ -276,6 +276,10 @@ parsefailed:
 	return -1;
 }
 
+void gstlal_snglinspiral_array_free(SnglInspiralTable *bankarray) {
+	free(bankarray);
+}
+
 int gstlal_set_channel_in_snglinspiral_array(SnglInspiralTable *bankarray, int length, char *channel)
 {
 	int i;
@@ -328,7 +332,165 @@ int gstlal_set_min_offset_in_snglinspiral_array(SnglInspiralTable *bankarray, in
 	return 0;
 }
 
-GstBuffer *gstlal_snglinspiral_new_buffer_from_peak(struct gstlal_peak_state *input, SnglInspiralTable *bankarray, GstPad *pad, guint64 offset, guint64 length, GstClockTime time, guint rate, void *chi2, gsl_matrix_complex_float_view *snr_matrix_view, GstClockTimeDiff timediff)
+int populate_snglinspiral_buffer(GstBuffer *srcbuf, struct gstlal_peak_state *input, SnglInspiralTable *bankarray, GstPad *pad, guint64 length, GstClockTime time, guint rate, void *chi2, gsl_matrix_complex_float_view *L1_snr_matrix_view, gsl_matrix_complex_float_view *H1_snr_matrix_view, gsl_matrix_complex_float_view *V1_snr_matrix_view, gsl_matrix_complex_float_view *K1_snr_matrix_view)
+{
+	guint channel;
+	guint L1_snr_timeseries_length, H1_snr_timeseries_length, V1_snr_timeseries_length, K1_snr_timeseries_length;
+	gboolean provided_empty_trigger = FALSE;
+	for(channel = 0; channel < input->channels; channel++) {
+		struct GSTLALSnglInspiral *event;
+		SnglInspiralTable *parent;
+		double complex maxdata_channel = 0;
+
+		switch (input->type)
+		{
+			case GSTLAL_PEAK_COMPLEX:
+			maxdata_channel = (double complex) input->interpvalues.as_float_complex[channel];
+			break;
+
+			case GSTLAL_PEAK_DOUBLE_COMPLEX:
+			maxdata_channel = (double complex) input->interpvalues.as_double_complex[channel];
+			break;
+
+			default:
+			g_assert(input->type == GSTLAL_PEAK_COMPLEX || input->type == GSTLAL_PEAK_DOUBLE_COMPLEX);
+		}
+
+		if (!maxdata_channel && !input->no_peaks_past_threshold)
+			continue;
+
+		/*
+		 * allocate new event structure
+		 */
+
+
+		/*
+		 * Populate the SNR snippet if available
+		 * FIXME: only supported for single precision at the moment
+		 */
+		gsl_vector_complex_float_view L1_snr_vector_view, H1_snr_vector_view, V1_snr_vector_view, K1_snr_vector_view;
+		gsl_vector_complex_float_view L1_snr_series_view, H1_snr_series_view, V1_snr_series_view, K1_snr_series_view;
+		if ((L1_snr_matrix_view || H1_snr_matrix_view || V1_snr_matrix_view || K1_snr_matrix_view) && !input->no_peaks_past_threshold)
+		{
+			/* Allocate a set of empty time series. The event takes ownership, so no need to free it*/
+			/* Get the columns of SNR we are interested in */
+			if(H1_snr_matrix_view != NULL) {
+				H1_snr_vector_view = gsl_matrix_complex_float_column(&(H1_snr_matrix_view->matrix), channel);
+				H1_snr_timeseries_length = H1_snr_vector_view.vector.size;
+			} else
+				H1_snr_timeseries_length = 0;
+			if(L1_snr_matrix_view != NULL) {
+				L1_snr_vector_view = gsl_matrix_complex_float_column(&(L1_snr_matrix_view->matrix), channel);
+				L1_snr_timeseries_length = L1_snr_vector_view.vector.size;
+			} else
+				L1_snr_timeseries_length = 0;
+			if(V1_snr_matrix_view != NULL) {
+				V1_snr_vector_view = gsl_matrix_complex_float_column(&(V1_snr_matrix_view->matrix), channel);
+				V1_snr_timeseries_length = V1_snr_vector_view.vector.size;
+			} else
+				V1_snr_timeseries_length = 0;
+			if(K1_snr_matrix_view != NULL) {
+				K1_snr_vector_view = gsl_matrix_complex_float_column(&(K1_snr_matrix_view->matrix), channel);
+				K1_snr_timeseries_length = K1_snr_vector_view.vector.size;
+			} else
+				K1_snr_timeseries_length = 0;
+
+			event = gstlal_snglinspiral_new(H1_snr_timeseries_length, L1_snr_timeseries_length, V1_snr_timeseries_length, K1_snr_timeseries_length);
+
+			if(H1_snr_matrix_view != NULL) {
+				/* Make a GSL view of the time series array data */
+				H1_snr_series_view = gsl_vector_complex_float_view_array((float *) event->snr, event->H1_length);
+				/* Use BLAS to do the copy */
+				gsl_blas_ccopy (&(H1_snr_vector_view.vector), &(H1_snr_series_view.vector));
+			}
+			if(L1_snr_matrix_view != NULL) {
+				/* Make a GSL view of the time series array data */
+				L1_snr_series_view = gsl_vector_complex_float_view_array((float *) &(event->snr[event->H1_length]), event->L1_length);
+				/* Use BLAS to do the copy */
+				gsl_blas_ccopy (&(L1_snr_vector_view.vector), &(L1_snr_series_view.vector));
+			}
+			if(V1_snr_matrix_view != NULL) {
+				/* Make a GSL view of the time series array data */
+				V1_snr_series_view = gsl_vector_complex_float_view_array((float *) &(event->snr[event->H1_length + event->L1_length]), event->V1_length);
+				/* Use BLAS to do the copy */
+				gsl_blas_ccopy (&(V1_snr_vector_view.vector), &(V1_snr_series_view.vector));
+			}
+			if(K1_snr_matrix_view != NULL) {
+				/* Make a GSL view of the time series array data */
+				K1_snr_series_view = gsl_vector_complex_float_view_array((float *) &(event->snr[event->H1_length + event->L1_length + event->V1_length]), event->K1_length);
+				/* Use BLAS to do the copy */
+				gsl_blas_ccopy (&(K1_snr_vector_view.vector), &(K1_snr_series_view.vector));
+			}
+		} else {
+			if(!provided_empty_trigger) {
+				//fprintf(stderr, "allocating snglinspiral with 0 length for %s event\n", bankarray[channel].ifo);
+				provided_empty_trigger = TRUE;
+				event = gstlal_snglinspiral_new(0,0,0,0);
+			} else
+				return 0;
+		}
+
+		if (!event) {
+			/* FIXME handle error */
+		}
+		/*
+		 * populate
+		 */
+
+		parent = (SnglInspiralTable *) event;
+		*parent = bankarray[channel];
+		parent->snr = cabs(maxdata_channel);
+		parent->coa_phase = carg(maxdata_channel);
+
+
+		XLALINT8NSToGPS(&event->epoch, time);
+		XLALGPSAddGPS(&event->epoch, &parent->end);
+		parent->end = event->epoch;
+		XLALGPSAdd(&parent->end, (double) input->interpsamples[channel] / rate);
+		XLALGPSAdd(&event->epoch, ((gint) input->samples[channel] - (gint) input->pad) / (double) rate);
+		event->deltaT = 1. / rate;
+
+		parent->end_time_gmst = XLALGreenwichMeanSiderealTime(&parent->end);
+		parent->eff_distance = gstlal_effective_distance(parent->snr, parent->sigmasq);
+		/* populate chi squared if we have it */
+		parent->chisq = 0.0;
+		parent->chisq_dof = 1;
+		switch (input->type)
+		{
+			case GSTLAL_PEAK_COMPLEX:
+			if (chi2) parent->chisq = (double) *(((float *) chi2 ) + channel);
+			break;
+
+			case GSTLAL_PEAK_DOUBLE_COMPLEX:
+			if (chi2) parent->chisq = (double) *(((double *) chi2 ) + channel);
+			break;
+
+			default:
+			g_assert(input->type == GSTLAL_PEAK_COMPLEX || input->type == GSTLAL_PEAK_DOUBLE_COMPLEX);
+		}
+
+
+		/*
+		 * add to buffer
+		 */
+
+		gst_buffer_append_memory(
+			srcbuf,
+			gst_memory_new_wrapped(
+				GST_MEMORY_FLAG_READONLY | GST_MEMORY_FLAG_PHYSICALLY_CONTIGUOUS,
+				event,
+				sizeof(*event) + (event->H1_length + event->L1_length + event->V1_length + event->K1_length) * sizeof(event->snr[0]),
+				0,
+				sizeof(*event) + (event->H1_length + event->L1_length + event->V1_length + event->K1_length) * sizeof(event->snr[0]),
+				event,
+				(GDestroyNotify) gstlal_snglinspiral_free
+			)
+		);
+	}
+	return 0;
+}
+
+GstBuffer *gstlal_snglinspiral_new_buffer_from_peak(struct gstlal_peak_state *input, SnglInspiralTable *bankarray, GstPad *pad, guint64 offset, guint64 length, GstClockTime time, guint rate, void *chi2, gsl_matrix_complex_float_view *L1_snr_matrix_view, gsl_matrix_complex_float_view *H1_snr_matrix_view, gsl_matrix_complex_float_view *V1_snr_matrix_view, gsl_matrix_complex_float_view *K1_snr_matrix_view, GstClockTimeDiff timediff)
 {
 	GstBuffer *srcbuf = gst_buffer_new();
 
@@ -349,109 +511,26 @@ GstBuffer *gstlal_snglinspiral_new_buffer_from_peak(struct gstlal_peak_state *in
 	GST_BUFFER_PTS(srcbuf) = time + timediff;
 	GST_BUFFER_DURATION(srcbuf) = (GstClockTime) gst_util_uint64_scale_int_round(GST_SECOND, length, rate);
 
-	if (input->num_events) {
-		guint channel;
-		for(channel = 0; channel < input->channels; channel++) {
-			struct GSTLALSnglInspiral *event;
-			SnglInspiralTable *parent;
-			double complex maxdata_channel = 0;
-
-			switch (input->type)
-			{
-				case GSTLAL_PEAK_COMPLEX:
-				maxdata_channel = (double complex) input->interpvalues.as_float_complex[channel];
-				break;
-
-				case GSTLAL_PEAK_DOUBLE_COMPLEX:
-				maxdata_channel = (double complex) input->interpvalues.as_double_complex[channel];
-				break;
-
-				default:
-				g_assert(input->type == GSTLAL_PEAK_COMPLEX || input->type == GSTLAL_PEAK_DOUBLE_COMPLEX);
-			}
-
-			if (!maxdata_channel)
-				continue;
-
-			/*
-			 * allocate new event structure
-			 */
-
-
-			/*
-			 * Populate the SNR snippet if available
-			 * FIXME: only supported for single precision at the moment
-			 */
-			if (snr_matrix_view)
-			{
-				/* Get the column of SNR we are interested in */
-				gsl_vector_complex_float_view snr_vector_view = gsl_matrix_complex_float_column(&(snr_matrix_view->matrix), channel);
-				/* Allocate an empty time series to hold it. The event takes ownership, so no need to free it*/
-				event = gstlal_snglinspiral_new(snr_vector_view.vector.size);
-				/* Make a GSL view of the time series array data */
-				gsl_vector_complex_float_view snr_series_view = gsl_vector_complex_float_view_array((float *) event->snr, event->length);
-				/* Use BLAS to do the copy */
-				gsl_blas_ccopy (&(snr_vector_view.vector), &(snr_series_view.vector));
-			} else
-				event = gstlal_snglinspiral_new(0);
-
-			parent = (SnglInspiralTable *) event;
-			if (!event) {
-				/* FIXME handle error */
-			}
-
-			/*
-			 * populate
-			 */
-
-			*parent = bankarray[channel];
-			parent->snr = cabs(maxdata_channel);
-			parent->coa_phase = carg(maxdata_channel);
-
-			XLALINT8NSToGPS(&event->epoch, time);
-			XLALGPSAddGPS(&event->epoch, &parent->end);
-			parent->end = event->epoch;
-			XLALGPSAdd(&parent->end, (double) input->interpsamples[channel] / rate);
-			XLALGPSAdd(&event->epoch, ((gint) input->samples[channel] - (gint) input->pad) / (double) rate);
-			event->deltaT = 1. / rate;
-
-			parent->end_time_gmst = XLALGreenwichMeanSiderealTime(&parent->end);
-			parent->eff_distance = gstlal_effective_distance(parent->snr, parent->sigmasq);
-			/* populate chi squared if we have it */
-			parent->chisq = 0.0;
-			parent->chisq_dof = 1;
-			switch (input->type)
-			{
-				case GSTLAL_PEAK_COMPLEX:
-				if (chi2) parent->chisq = (double) *(((float *) chi2 ) + channel);
-				break;
-
-				case GSTLAL_PEAK_DOUBLE_COMPLEX:
-				if (chi2) parent->chisq = (double) *(((double *) chi2 ) + channel);
-				break;
-
-				default:
-				g_assert(input->type == GSTLAL_PEAK_COMPLEX || input->type == GSTLAL_PEAK_DOUBLE_COMPLEX);
-			}
+	if (input->num_events || input->no_peaks_past_threshold) {
+		populate_snglinspiral_buffer(srcbuf, input, bankarray, pad, length, time, rate, chi2, L1_snr_matrix_view, H1_snr_matrix_view, V1_snr_matrix_view, K1_snr_matrix_view);
+	}
+	return srcbuf;
+}
 
-			/*
-			 * add to buffer
-			 */
-
-			gst_buffer_append_memory(
-				srcbuf,
-				gst_memory_new_wrapped(
-					GST_MEMORY_FLAG_READONLY | GST_MEMORY_FLAG_PHYSICALLY_CONTIGUOUS,
-					event,
-					sizeof(*event) + sizeof(event->snr[0]) * event->length,
-					0,
-					sizeof(*event) + sizeof(event->snr[0]) * event->length,
-					event,
-					(GDestroyNotify) gstlal_snglinspiral_free
-				)
-			);
-		}
+int gstlal_snglinspiral_append_peak_to_buffer(GstBuffer *srcbuf, struct gstlal_peak_state *input, SnglInspiralTable *bankarray, GstPad *pad, guint64 offset, guint64 length, GstClockTime time, guint rate, void *chi2, gsl_matrix_complex_float_view *L1_snr_matrix_view, gsl_matrix_complex_float_view *H1_snr_matrix_view, gsl_matrix_complex_float_view *V1_snr_matrix_view, gsl_matrix_complex_float_view *K1_snr_matrix_view)
+{
+	//
+	// Add peak information to a buffer, GST_BUFFER_OFFSET cannot be
+	// changed but GST_BUFFER_OFFSET_END can
+	//
+
+	/* Update the offset end and duration */
+	if(offset+length > GST_BUFFER_OFFSET_END(srcbuf)) {
+		GST_BUFFER_OFFSET_END(srcbuf) = offset + length;
+		GST_BUFFER_DURATION(srcbuf) = (GstClockTime) gst_util_uint64_scale_int_round(GST_SECOND, GST_BUFFER_OFFSET_END(srcbuf) - GST_BUFFER_OFFSET(srcbuf), rate);
 	}
 
-	return srcbuf;
+	populate_snglinspiral_buffer(srcbuf, input, bankarray, pad, length, time, rate, chi2, L1_snr_matrix_view, H1_snr_matrix_view, V1_snr_matrix_view, K1_snr_matrix_view);
+
+	return 0;
 }
diff --git a/gstlal-inspiral/lib/gstlal_snglinspiral.h b/gstlal-inspiral/lib/gstlal_snglinspiral.h
index 7ae2671515..07095ce626 100644
--- a/gstlal-inspiral/lib/gstlal_snglinspiral.h
+++ b/gstlal-inspiral/lib/gstlal_snglinspiral.h
@@ -47,10 +47,13 @@ int gstlal_set_instrument_in_snglinspiral_array(SnglInspiralTable *bankarray, in
 int gstlal_set_sigmasq_in_snglinspiral_array(SnglInspiralTable *bankarray, int length, double *sigmasq);
 int gstlal_set_min_offset_in_snglinspiral_array(SnglInspiralTable *bankarray, int length, GstClockTimeDiff *difftime);
 
+void gstlal_snglinspiral_array_free(SnglInspiralTable *bankarray);
+
 /*
  * FIXME: only support single precision SNR snippets at the moment
  */
-GstBuffer *gstlal_snglinspiral_new_buffer_from_peak(struct gstlal_peak_state *input, SnglInspiralTable *bankarray, GstPad *pad, guint64 offset, guint64 length, GstClockTime time, guint rate, void *chi2, gsl_matrix_complex_float_view *snr_matrix_view, GstClockTimeDiff);
+GstBuffer *gstlal_snglinspiral_new_buffer_from_peak(struct gstlal_peak_state *input, SnglInspiralTable *bankarray, GstPad *pad, guint64 offset, guint64 length, GstClockTime time, guint rate, void *chi2, gsl_matrix_complex_float_view *L1_snr_matrix_view, gsl_matrix_complex_float_view *H1_snr_matrix_view, gsl_matrix_complex_float_view *V1_snr_matrix_view, gsl_matrix_complex_float_view *K1_snr_matrix_view, GstClockTimeDiff);
+int gstlal_snglinspiral_append_peak_to_buffer(GstBuffer *srcbuf, struct gstlal_peak_state *input, SnglInspiralTable *bankarray, GstPad *pad, guint64 offset, guint64 L1, GstClockTime time, guint rate, void *chi2, gsl_matrix_complex_float_view *L1_snr_matrix_view, gsl_matrix_complex_float_view *H1_snr_matrix_view, gsl_matrix_complex_float_view *V1_snr_matrix_view, gsl_matrix_complex_float_view *K1_snr_matrix_view);
 
 
 G_END_DECLS
diff --git a/gstlal-inspiral/lib/snglinspiralrowtype.c b/gstlal-inspiral/lib/snglinspiralrowtype.c
index 366a38c3ae..7f7bb1fe37 100644
--- a/gstlal-inspiral/lib/snglinspiralrowtype.c
+++ b/gstlal-inspiral/lib/snglinspiralrowtype.c
@@ -37,6 +37,7 @@
 #include <snglinspiralrowtype.h>
 
 
+
 /**
  * Allocate a new struct GSTLALSnglInspiral
  *
@@ -44,11 +45,16 @@
  */
 
 
-struct GSTLALSnglInspiral *gstlal_snglinspiral_new(size_t length)
+struct GSTLALSnglInspiral *gstlal_snglinspiral_new(size_t H1_length, size_t L1_length, size_t V1_length, size_t K1_length)
 {
-	struct GSTLALSnglInspiral *row = calloc(1, sizeof(*row) + length * sizeof(row->snr[0]));
-	if (row)
-		row->length = length;
+	struct GSTLALSnglInspiral *row = calloc(1, sizeof(*row) + (H1_length + L1_length + V1_length + K1_length) * sizeof(row->snr[0]));
+	if (row) {
+		row->L1_length = L1_length;
+		row->H1_length = H1_length;
+		row->V1_length = V1_length;
+		row->K1_length = K1_length;
+	}
+
 
 	return row;
 }
diff --git a/gstlal-inspiral/lib/snglinspiralrowtype.h b/gstlal-inspiral/lib/snglinspiralrowtype.h
index cfcb362f51..54a251db9d 100644
--- a/gstlal-inspiral/lib/snglinspiralrowtype.h
+++ b/gstlal-inspiral/lib/snglinspiralrowtype.h
@@ -56,12 +56,15 @@ struct GSTLALSnglInspiral {
 	SnglInspiralTable parent;
 	LIGOTimeGPS epoch;
 	double deltaT;
-	size_t length;
+	size_t L1_length;
+	size_t H1_length;
+	size_t V1_length;
+	size_t K1_length;
 	float complex snr[];
 };
 
 
-struct GSTLALSnglInspiral *gstlal_snglinspiral_new(size_t length);
+struct GSTLALSnglInspiral *gstlal_snglinspiral_new(size_t L1_length, size_t H1_length, size_t V1_length, size_t K1_length);
 void gstlal_snglinspiral_free(struct GSTLALSnglInspiral *row);
 
 
diff --git a/gstlal-inspiral/python/inspiral.py b/gstlal-inspiral/python/inspiral.py
index 8c942bf7d8..90863117dd 100644
--- a/gstlal-inspiral/python/inspiral.py
+++ b/gstlal-inspiral/python/inspiral.py
@@ -555,6 +555,7 @@ class GracedBWrapper(object):
 
 			if self.verbose:
 				print >>sys.stderr, "sending %s to gracedb ..." % filename
+			print >>sys.stderr, "sending %s to gracedb ..." % filename
 			message = StringIO.StringIO()
 			xmldoc = last_coincs[coinc_event.coinc_event_id]
 			# give the alert all the standard inspiral
@@ -568,13 +569,19 @@ class GracedBWrapper(object):
 					# already has it
 					pass
 			# add SNR time series if available
+			# FIXME Probably only want one time series for each ifo
 			for event in last_coincs.sngl_inspirals(coinc_event.coinc_event_id):
-				snr_time_series = event.snr_time_series
-				if snr_time_series is not None:
-					xmldoc.childNodes[-1].appendChild(lalseries.build_COMPLEX8TimeSeries(snr_time_series)).appendChild(ligolw_param.Param.from_pyvalue(u"event_id", event.event_id))
+				for ifo in ("H1", "L1", "V1", "K1"):
+					snr_time_series = getattr(event, "%s_snr_time_series" % ifo)
+					if snr_time_series is not None:
+						snr_time_series_element = lalseries.build_COMPLEX8TimeSeries(snr_time_series)
+						snr_time_series_element.appendChild(ligolw_param.Param.from_pyvalue(u"event_id", event.event_id))
+						snr_time_series_element.appendChild(ligolw_param.Param.from_pyvalue(u"ifo", ifo))
+						xmldoc.childNodes[-1].appendChild(snr_time_series_element)
 			# translate IDs from integers to ilwd:char for
 			# backwards compatibility
 			ilwdify.do_it_to(xmldoc)
+
 			# serialize to XML
 			ligolw_utils.write_fileobj(xmldoc, message, gz = False)
 			xmldoc.unlink()
diff --git a/gstlal-inspiral/python/lloidhandler.py b/gstlal-inspiral/python/lloidhandler.py
index d12ca2f0da..873ba1b74c 100644
--- a/gstlal-inspiral/python/lloidhandler.py
+++ b/gstlal-inspiral/python/lloidhandler.py
@@ -950,7 +950,7 @@ class Handler(simplehandler.Handler):
 				memory.unmap(mapinfo)
 
 			# FIXME:  ugly way to get the instrument
-			instrument = elem.get_name().split("_", 1)[0]
+			instruments = set([event.ifo for event in events])
 
 			# extract segment.  move the segment's upper
 			# boundary to include all triggers.  ARGH the 1 ns
@@ -958,10 +958,7 @@ class Handler(simplehandler.Handler):
 			# "in" the segment (segments are open from above)
 			# FIXME:  is there another way?
 			buf_timestamp = LIGOTimeGPS(0, buf.pts)
-			if events:
-				buf_seg = segments.segment(buf_timestamp, max(buf_timestamp + LIGOTimeGPS(0, buf.duration), max(event.end for event in events) + 0.000000001))
-			else:
-				buf_seg = segments.segment(buf_timestamp, buf_timestamp + LIGOTimeGPS(0, buf.duration))
+			buf_seg = dict((instrument, segments.segment(buf_timestamp, max(buf_timestamp + LIGOTimeGPS(0, buf.duration), max(event.end for event in events if event.ifo == instrument) + 0.000000001))) for instrument in instruments)
 			buf_is_gap = bool(buf.mini_object.flags & Gst.BufferFlags.GAP)
 			# sanity check that gap buffers are empty
 			assert not (buf_is_gap and events)
@@ -1062,12 +1059,35 @@ class Handler(simplehandler.Handler):
 			# is aware of this buffer.
 			if not buf_is_gap:
 				snr_min = self.rankingstat.snr_min
-				self.rankingstat.denominator.triggerrates[instrument].add_ratebin(map(float, buf_seg), len([event for event in events if event.snr >= snr_min]))
+				for instrument in instruments:
+					# FIXME At the moment, empty triggers are added to
+					# inform the "how many instruments were on test", the
+					# correct thing to do is probably to add metadata to
+					# the buffer containing information about which
+					# instruments were on
+					self.rankingstat.denominator.triggerrates[instrument].add_ratebin(map(float, buf_seg[instrument]), len([event for event in events if event.snr >= snr_min and event.ifo == instrument]))
+
+			# FIXME At the moment, empty triggers are added to
+			# inform the "how many instruments were on test", the
+			# correct thing to do is probably to add metadata to
+			# the buffer containing information about which
+			# instruments were on
+			real_events = []
+			for event in events:
+				if event.snr >= snr_min:
+					real_events.append(event)
+
+			events = real_events
 
 			# run stream thinca.
 			for absent_instrument in self.absent_instruments:
 				self.stream_thinca.push(absent_instrument, (), buf_timestamp)
-			if self.stream_thinca.push(instrument, events, buf_timestamp):
+			for instrument in self.rankingstat.instruments:
+				if instrument in instruments or instrument in self.absent_instruments:
+					continue
+				self.stream_thinca.push(instrument, (), buf_timestamp)
+
+			if any([self.stream_thinca.push(instrument, [event for event in events if event.ifo == instrument], buf_timestamp) for instrument in instruments]):
 				flushed_sngls = self.stream_thinca.pull(self.rankingstat, fapfar = self.fapfar, zerolag_rankingstatpdf = self.zerolag_rankingstatpdf, coinc_sieve = self.rankingstat.fast_path_cut_from_triggers, cluster = self.cluster)
 				self.coincs_document.commit()
 
@@ -1080,7 +1100,10 @@ class Handler(simplehandler.Handler):
 				# that are too old to be used to form
 				# candidates.
 				for event in flushed_sngls:
-					del event.snr_time_series
+					del event.H1_snr_time_series
+					del event.L1_snr_time_series
+					del event.V1_snr_time_series
+					del event.K1_snr_time_series
 
 
 	def _record_horizon_distance(self, instrument, timestamp, horizon_distance):
@@ -1274,7 +1297,10 @@ class Handler(simplehandler.Handler):
 		# for the triggers that are too old to be used to form
 		# candidates.
 		for event in flushed_sngls:
-			del event.snr_time_series
+			del event.H1_snr_time_series
+			del event.L1_snr_time_series
+			del event.V1_snr_time_series
+			del event.K1_snr_time_series
 
 
 	def __do_gracedb_alerts(self, last_coincs):
diff --git a/gstlal-inspiral/python/lloidparts.py b/gstlal-inspiral/python/lloidparts.py
index c0dc7e8c88..de887c9e37 100644
--- a/gstlal-inspiral/python/lloidparts.py
+++ b/gstlal-inspiral/python/lloidparts.py
@@ -81,6 +81,7 @@ from glue import iterutils
 from gstlal import datasource
 from gstlal import multirate_datasource
 from gstlal import pipeparts
+from gstlal import pipeio
 
 
 #
@@ -656,8 +657,8 @@ def mkLLOIDmulti(pipeline, detectors, banks, psd, psd_fft_length = 32, ht_gate_t
 	# construct trigger generators
 	#
 
-	triggersrcs = dict((instrument, set()) for instrument in hoftdicts)
-	for instrument, bank in [(instrument, bank) for instrument, banklist in banks.items() for bank in banklist]:
+	itacac_dict = {}
+	for i, (instrument, bank) in enumerate([(instrument, bank) for instrument, banklist in banks.items() for bank in banklist]):
 		suffix = "%s%s" % (instrument, (bank.logname and "_%s" % bank.logname or ""))
 		if control_branch is not None:
 			if instrument != "H2":
@@ -695,10 +696,19 @@ def mkLLOIDmulti(pipeline, detectors, banks, psd, psd_fft_length = 32, ht_gate_t
 			# but 4 is about the lowest we can do stably for
 			# coincidence online...
 			nsamps_window = max(max(bank.get_rates()) / 4, 256) # FIXME stupid hack
-			head = pipeparts.mkitac(pipeline, snr, nsamps_window, bank.template_bank_filename, autocorrelation_matrix = bank.autocorrelation_bank, mask_matrix = bank.autocorrelation_mask, snr_thresh = 4.0, sigmasq = bank.sigmasq)
-			if verbose:
-				head = pipeparts.mkprogressreport(pipeline, head, "progress_xml_%s" % suffix)
-			triggersrcs[instrument].add(head)
+			if bank.bank_id not in itacac_dict:
+				itacac_dict[bank.bank_id] = pipeparts.mkgeneric(pipeline, None, "lal_itacac")
+
+			head = itacac_dict[bank.bank_id]
+			pad = head.get_request_pad("sink%d" % len(head.sinkpads))
+			if instrument == 'H1' or instrument == 'L1':
+				for prop, val in [("n", nsamps_window), ("snr-thresh", 4.0), ("bank_filename", bank.template_bank_filename), ("sigmasq", bank.sigmasq), ("autocorrelation_matrix", pipeio.repack_complex_array_to_real(bank.autocorrelation_bank)), ("autocorrelation_mask", bank.autocorrelation_mask)]:
+					pad.set_property(prop, val)
+				snr.srcpads[0].link(pad)
+			else:
+				for prop, val in [("n", nsamps_window), ("snr-thresh", 4.0), ("bank_filename", bank.template_bank_filename), ("sigmasq", bank.sigmasq), ("autocorrelation_matrix", pipeio.repack_complex_array_to_real(bank.autocorrelation_bank)), ("autocorrelation_mask", bank.autocorrelation_mask)]:
+					pad.set_property(prop, val)
+				snr.srcpads[0].link(pad)
 		else:
 			raise NotImplementedError("Currently only 'autochisq' is supported")
 		# FIXME:  find a way to use less memory without this hack
@@ -710,5 +720,8 @@ def mkLLOIDmulti(pipeline, detectors, banks, psd, psd_fft_length = 32, ht_gate_t
 	# done
 	#
 
-	assert any(triggersrcs.values())
-	return triggersrcs
+	assert any(itacac_dict.values())
+	if verbose:
+		for bank_id, head in itacac_dict.items():
+			itacac_dict[bank_id] = pipeparts.mkprogressreport(pipeline, head, "progress_xml_bank_%s" % bank_id)
+	return itacac_dict
diff --git a/gstlal-inspiral/python/snglinspiraltable.c b/gstlal-inspiral/python/snglinspiraltable.c
index 13e8f2f7c6..ee714e8272 100644
--- a/gstlal-inspiral/python/snglinspiraltable.c
+++ b/gstlal-inspiral/python/snglinspiraltable.c
@@ -38,6 +38,9 @@
 
 
 #include <snglinspiralrowtype.h>
+#include <gst/gst.h>
+#include <gsl/gsl_blas.h>
+#include <gsl/gsl_matrix_float.h>
 
 
 static PyObject *LIGOTimeGPSType = NULL;
@@ -55,7 +58,10 @@ static PyObject *LIGOTimeGPSType = NULL;
 typedef struct {
 	PyObject_HEAD
 	SnglInspiralTable row;
-	COMPLEX8TimeSeries *snr;
+	COMPLEX8TimeSeries *H1_snr;
+	COMPLEX8TimeSeries *L1_snr;
+	COMPLEX8TimeSeries *V1_snr;
+	COMPLEX8TimeSeries *K1_snr;
 } gstlal_GSTLALSnglInspiral;
 
 
@@ -225,36 +231,272 @@ static int template_id_set(PyObject *obj, PyObject *val, void *null)
 	return 0;
 }
 
+static PyObject *H1_snr_component_get(PyObject *obj, void *data)
+{
+	COMPLEX8TimeSeries *H1_snr = ((gstlal_GSTLALSnglInspiral *) obj)->H1_snr;
+	const char *name = data;
+
+	if(!H1_snr) {
+		PyErr_SetString(PyExc_ValueError, "no snr time series available");
+		return NULL;
+	}
+	if(!strcmp(name, "_H1_snr_name")) {
+		return PyString_FromString(H1_snr->name);
+	} else if(!strcmp(name, "_H1_snr_epoch_gpsSeconds")) {
+		return PyInt_FromLong(H1_snr->epoch.gpsSeconds);
+	} else if(!strcmp(name, "_H1_snr_epoch_gpsNanoSeconds")) {
+		return PyInt_FromLong(H1_snr->epoch.gpsNanoSeconds);
+	} else if(!strcmp(name, "_H1_snr_f0")) {
+		return PyFloat_FromDouble(H1_snr->f0);
+	} else if(!strcmp(name, "_H1_snr_deltaT")) {
+		return PyFloat_FromDouble(H1_snr->deltaT);
+	} else if(!strcmp(name, "_H1_snr_sampleUnits")) {
+		char *s = XLALUnitToString(&H1_snr->sampleUnits);
+		PyObject *result = PyString_FromString(s);
+		XLALFree(s);
+		return result;
+	} else if(!strcmp(name, "_H1_snr_data_length")) {
+		return PyInt_FromLong(H1_snr->data->length);
+	} else if(!strcmp(name, "_H1_snr_data")) {
+		npy_intp dims[] = {H1_snr->data->length};
+		PyObject *array = PyArray_SimpleNewFromData(1, dims, NPY_CFLOAT, H1_snr->data->data);
+		if(!array)
+			return NULL;
+		Py_INCREF(obj);
+		PyArray_SetBaseObject((PyArrayObject *) array, obj);
+		return array;
+	}
+	PyErr_BadArgument();
+	return NULL;
+}
+
+
+static PyObject *L1_snr_component_get(PyObject *obj, void *data)
+{
+	COMPLEX8TimeSeries *L1_snr = ((gstlal_GSTLALSnglInspiral *) obj)->L1_snr;
+	const char *name = data;
 
+	if(!L1_snr) {
+		PyErr_SetString(PyExc_ValueError, "no snr time series available");
+		return NULL;
+	}
+	if(!strcmp(name, "_L1_snr_name")) {
+		return PyString_FromString(L1_snr->name);
+	} else if(!strcmp(name, "_L1_snr_epoch_gpsSeconds")) {
+		return PyInt_FromLong(L1_snr->epoch.gpsSeconds);
+	} else if(!strcmp(name, "_L1_snr_epoch_gpsNanoSeconds")) {
+		return PyInt_FromLong(L1_snr->epoch.gpsNanoSeconds);
+	} else if(!strcmp(name, "_L1_snr_f0")) {
+		return PyFloat_FromDouble(L1_snr->f0);
+	} else if(!strcmp(name, "_L1_snr_deltaT")) {
+		return PyFloat_FromDouble(L1_snr->deltaT);
+	} else if(!strcmp(name, "_L1_snr_sampleUnits")) {
+		char *s = XLALUnitToString(&L1_snr->sampleUnits);
+		PyObject *result = PyString_FromString(s);
+		XLALFree(s);
+		return result;
+	} else if(!strcmp(name, "_L1_snr_data_length")) {
+		return PyInt_FromLong(L1_snr->data->length);
+	} else if(!strcmp(name, "_L1_snr_data")) {
+		npy_intp dims[] = {L1_snr->data->length};
+		PyObject *array = PyArray_SimpleNewFromData(1, dims, NPY_CFLOAT, L1_snr->data->data);
+		if(!array)
+			return NULL;
+		Py_INCREF(obj);
+		PyArray_SetBaseObject((PyArrayObject *) array, obj);
+		return array;
+	}
+	PyErr_BadArgument();
+	return NULL;
+}
+
+static PyObject *V1_snr_component_get(PyObject *obj, void *data)
+{
+	COMPLEX8TimeSeries *V1_snr = ((gstlal_GSTLALSnglInspiral *) obj)->V1_snr;
+	const char *name = data;
+
+	if(!V1_snr) {
+		PyErr_SetString(PyExc_ValueError, "no snr time series available");
+		return NULL;
+	}
+	if(!strcmp(name, "_V1_snr_name")) {
+		return PyString_FromString(V1_snr->name);
+	} else if(!strcmp(name, "_V1_snr_epoch_gpsSeconds")) {
+		return PyInt_FromLong(V1_snr->epoch.gpsSeconds);
+	} else if(!strcmp(name, "_V1_snr_epoch_gpsNanoSeconds")) {
+		return PyInt_FromLong(V1_snr->epoch.gpsNanoSeconds);
+	} else if(!strcmp(name, "_V1_snr_f0")) {
+		return PyFloat_FromDouble(V1_snr->f0);
+	} else if(!strcmp(name, "_V1_snr_deltaT")) {
+		return PyFloat_FromDouble(V1_snr->deltaT);
+	} else if(!strcmp(name, "_V1_snr_sampleUnits")) {
+		char *s = XLALUnitToString(&V1_snr->sampleUnits);
+		PyObject *result = PyString_FromString(s);
+		XLALFree(s);
+		return result;
+	} else if(!strcmp(name, "_V1_snr_data_length")) {
+		return PyInt_FromLong(V1_snr->data->length);
+	} else if(!strcmp(name, "_V1_snr_data")) {
+		npy_intp dims[] = {V1_snr->data->length};
+		PyObject *array = PyArray_SimpleNewFromData(1, dims, NPY_CFLOAT, V1_snr->data->data);
+		if(!array)
+			return NULL;
+		Py_INCREF(obj);
+		PyArray_SetBaseObject((PyArrayObject *) array, obj);
+		return array;
+	}
+	PyErr_BadArgument();
+	return NULL;
+}
+
+
+static PyObject *K1_snr_component_get(PyObject *obj, void *data)
+{
+	COMPLEX8TimeSeries *K1_snr = ((gstlal_GSTLALSnglInspiral *) obj)->K1_snr;
+	const char *name = data;
+
+	if(!K1_snr) {
+		PyErr_SetString(PyExc_ValueError, "no snr time series available");
+		return NULL;
+	}
+	if(!strcmp(name, "_K1_snr_name")) {
+		return PyString_FromString(K1_snr->name);
+	} else if(!strcmp(name, "_K1_snr_epoch_gpsSeconds")) {
+		return PyInt_FromLong(K1_snr->epoch.gpsSeconds);
+	} else if(!strcmp(name, "_K1_snr_epoch_gpsNanoSeconds")) {
+		return PyInt_FromLong(K1_snr->epoch.gpsNanoSeconds);
+	} else if(!strcmp(name, "_K1_snr_f0")) {
+		return PyFloat_FromDouble(K1_snr->f0);
+	} else if(!strcmp(name, "_K1_snr_deltaT")) {
+		return PyFloat_FromDouble(K1_snr->deltaT);
+	} else if(!strcmp(name, "_K1_snr_sampleUnits")) {
+		char *s = XLALUnitToString(&K1_snr->sampleUnits);
+		PyObject *result = PyString_FromString(s);
+		XLALFree(s);
+		return result;
+	} else if(!strcmp(name, "_K1_snr_data_length")) {
+		return PyInt_FromLong(K1_snr->data->length);
+	} else if(!strcmp(name, "_K1_snr_data")) {
+		npy_intp dims[] = {K1_snr->data->length};
+		PyObject *array = PyArray_SimpleNewFromData(1, dims, NPY_CFLOAT, K1_snr->data->data);
+		if(!array)
+			return NULL;
+		Py_INCREF(obj);
+		PyArray_SetBaseObject((PyArrayObject *) array, obj);
+		return array;
+	}
+	PyErr_BadArgument();
+	return NULL;
+}
+
+/*
 static PyObject *snr_component_get(PyObject *obj, void *data)
 {
-	COMPLEX8TimeSeries *snr = ((gstlal_GSTLALSnglInspiral *) obj)->snr;
+	COMPLEX8TimeSeries *H1_snr = ((gstlal_GSTLALSnglInspiral *) obj)->H1_snr;
+	COMPLEX8TimeSeries *L1_snr = ((gstlal_GSTLALSnglInspiral *) obj)->L1_snr;
+	COMPLEX8TimeSeries *V1_snr = ((gstlal_GSTLALSnglInspiral *) obj)->V1_snr;
+	COMPLEX8TimeSeries *K1_snr = ((gstlal_GSTLALSnglInspiral *) obj)->K1_snr;
 	const char *name = data;
 
-	if(!snr) {
+	if(!H1_snr && !L1_snr && !V1_snr && !K1_snr) {
 		PyErr_SetString(PyExc_ValueError, "no snr time series available");
 		return NULL;
 	}
-	if(!strcmp(name, "_snr_name")) {
-		return PyString_FromString(snr->name);
-	} else if(!strcmp(name, "_snr_epoch_gpsSeconds")) {
-		return PyInt_FromLong(snr->epoch.gpsSeconds);
-	} else if(!strcmp(name, "_snr_epoch_gpsNanoSeconds")) {
-		return PyInt_FromLong(snr->epoch.gpsNanoSeconds);
-	} else if(!strcmp(name, "_snr_f0")) {
-		return PyFloat_FromDouble(snr->f0);
-	} else if(!strcmp(name, "_snr_deltaT")) {
-		return PyFloat_FromDouble(snr->deltaT);
-	} else if(!strcmp(name, "_snr_sampleUnits")) {
-		char *s = XLALUnitToString(&snr->sampleUnits);
+	if(!strcmp(name, "_H1_snr_name")) {
+		return PyString_FromString(H1_snr->name);
+	} else if(!strcmp(name, "_L1_snr_name")) {
+		return PyString_FromString(L1_snr->name);
+	} else if(!strcmp(name, "_V1_snr_name")) {
+		return PyString_FromString(V1_snr->name);
+	} else if(!strcmp(name, "_K1_snr_name")) {
+		return PyString_FromString(K1_snr->name);
+	} else if(!strcmp(name, "_H1_snr_epoch_gpsSeconds")) {
+		return PyInt_FromLong(H1_snr->epoch.gpsSeconds);
+	} else if(!strcmp(name, "_L1_snr_epoch_gpsSeconds")) {
+		return PyInt_FromLong(L1_snr->epoch.gpsSeconds);
+	} else if(!strcmp(name, "_V1_snr_epoch_gpsSeconds")) {
+		return PyInt_FromLong(V1_snr->epoch.gpsSeconds);
+	} else if(!strcmp(name, "_K1_snr_epoch_gpsSeconds")) {
+		return PyInt_FromLong(K1_snr->epoch.gpsSeconds);
+	} else if(!strcmp(name, "_H1_snr_epoch_gpsNanoSeconds")) {
+		return PyInt_FromLong(H1_snr->epoch.gpsNanoSeconds);
+	} else if(!strcmp(name, "_L1_snr_epoch_gpsNanoSeconds")) {
+		return PyInt_FromLong(L1_snr->epoch.gpsNanoSeconds);
+	} else if(!strcmp(name, "_V1_snr_epoch_gpsNanoSeconds")) {
+		return PyInt_FromLong(V1_snr->epoch.gpsNanoSeconds);
+	} else if(!strcmp(name, "_K1_snr_epoch_gpsNanoSeconds")) {
+		return PyInt_FromLong(K1_snr->epoch.gpsNanoSeconds);
+	} else if(!strcmp(name, "_H1_snr_f0")) {
+		return PyFloat_FromDouble(H1_snr->f0);
+	} else if(!strcmp(name, "_L1_snr_f0")) {
+		return PyFloat_FromDouble(L1_snr->f0);
+	} else if(!strcmp(name, "_V1_snr_f0")) {
+		return PyFloat_FromDouble(V1_snr->f0);
+	} else if(!strcmp(name, "_K1_snr_f0")) {
+		return PyFloat_FromDouble(K1_snr->f0);
+	} else if(!strcmp(name, "_H1_snr_deltaT")) {
+		return PyFloat_FromDouble(H1_snr->deltaT);
+	} else if(!strcmp(name, "_L1_snr_deltaT")) {
+		return PyFloat_FromDouble(L1_snr->deltaT);
+	} else if(!strcmp(name, "_V1_snr_deltaT")) {
+		return PyFloat_FromDouble(V1_snr->deltaT);
+	} else if(!strcmp(name, "_K1_snr_deltaT")) {
+		return PyFloat_FromDouble(K1_snr->deltaT);
+	} else if(!strcmp(name, "_H1_snr_sampleUnits")) {
+		char *s = XLALUnitToString(&H1_snr->sampleUnits);
+		PyObject *result = PyString_FromString(s);
+		XLALFree(s);
+		return result;
+	} else if(!strcmp(name, "_L1_snr_sampleUnits")) {
+		char *s = XLALUnitToString(&L1_snr->sampleUnits);
+		PyObject *result = PyString_FromString(s);
+		XLALFree(s);
+		return result;
+	} else if(!strcmp(name, "_V1_snr_sampleUnits")) {
+		char *s = XLALUnitToString(&V1_snr->sampleUnits);
+		PyObject *result = PyString_FromString(s);
+		XLALFree(s);
+		return result;
+	} else if(!strcmp(name, "_K1_snr_sampleUnits")) {
+		char *s = XLALUnitToString(&K1_snr->sampleUnits);
 		PyObject *result = PyString_FromString(s);
 		XLALFree(s);
 		return result;
-	} else if(!strcmp(name, "_snr_data_length")) {
-		return PyInt_FromLong(snr->data->length);
-	} else if(!strcmp(name, "_snr_data")) {
-		npy_intp dims[] = {snr->data->length};
-		PyObject *array = PyArray_SimpleNewFromData(1, dims, NPY_CFLOAT, snr->data->data);
+	} else if(!strcmp(name, "_H1_snr_data_length")) {
+		return PyInt_FromLong(H1_snr->data->length);
+	} else if(!strcmp(name, "_L1_snr_data_length")) {
+		return PyInt_FromLong(L1_snr->data->length);
+	} else if(!strcmp(name, "_V1_snr_data_length")) {
+		return PyInt_FromLong(V1_snr->data->length);
+	} else if(!strcmp(name, "_K1_snr_data_length")) {
+		return PyInt_FromLong(K1_snr->data->length);
+	} else if(!strcmp(name, "_H1_snr_data")) {
+		npy_intp dims[] = {H1_snr->data->length};
+		PyObject *array = PyArray_SimpleNewFromData(1, dims, NPY_CFLOAT, H1_snr->data->data);
+		if(!array)
+			return NULL;
+		Py_INCREF(obj);
+		PyArray_SetBaseObject((PyArrayObject *) array, obj);
+		return array;
+	} else if(!strcmp(name, "_L1_snr_data")) {
+		npy_intp dims[] = {L1_snr->data->length};
+		PyObject *array = PyArray_SimpleNewFromData(1, dims, NPY_CFLOAT, L1_snr->data->data);
+		if(!array)
+			return NULL;
+		Py_INCREF(obj);
+		PyArray_SetBaseObject((PyArrayObject *) array, obj);
+		return array;
+	} else if(!strcmp(name, "_V1_snr_data")) {
+		npy_intp dims[] = {V1_snr->data->length};
+		PyObject *array = PyArray_SimpleNewFromData(1, dims, NPY_CFLOAT, V1_snr->data->data);
+		if(!array)
+			return NULL;
+		Py_INCREF(obj);
+		PyArray_SetBaseObject((PyArrayObject *) array, obj);
+		return array;
+	} else if(!strcmp(name, "_K1_snr_data")) {
+		npy_intp dims[] = {K1_snr->data->length};
+		PyObject *array = PyArray_SimpleNewFromData(1, dims, NPY_CFLOAT, K1_snr->data->data);
 		if(!array)
 			return NULL;
 		Py_INCREF(obj);
@@ -264,6 +506,7 @@ static PyObject *snr_component_get(PyObject *obj, void *data)
 	PyErr_BadArgument();
 	return NULL;
 }
+*/
 
 
 static struct PyGetSetDef getset[] = {
@@ -272,14 +515,38 @@ static struct PyGetSetDef getset[] = {
 	{"channel", pylal_inline_string_get, pylal_inline_string_set, "channel", &(struct pylal_inline_string_description) {offsetof(gstlal_GSTLALSnglInspiral, row.channel), LIGOMETA_CHANNEL_MAX}},
 	{"end", end_get, end_set, "end", NULL},
 	{"template_id", template_id_get, template_id_set, "template_id", NULL},
-	{"_snr_name", snr_component_get, NULL, ".snr.name", "_snr_name"},
-	{"_snr_epoch_gpsSeconds", snr_component_get, NULL, ".snr.epoch.gpsSeconds", "_snr_epoch_gpsSeconds"},
-	{"_snr_epoch_gpsNanoSeconds", snr_component_get, NULL, ".snr.epoch.gpsNanoSeconds", "_snr_epoch_gpsNanoSeconds"},
-	{"_snr_f0", snr_component_get, NULL, ".snr.f0", "_snr_f0"},
-	{"_snr_deltaT", snr_component_get, NULL, ".snr.deltaT", "_snr_deltaT"},
-	{"_snr_sampleUnits", snr_component_get, NULL, ".snr.sampleUnits", "_snr_sampleUnits"},
-	{"_snr_data_length", snr_component_get, NULL, ".snr.data.length", "_snr_data_length"},
-	{"_snr_data", snr_component_get, NULL, ".snr.data", "_snr_data"},
+	{"_H1_snr_name", H1_snr_component_get, NULL, ".H1_snr.name", "_H1_snr_name"},
+	{"_L1_snr_name", L1_snr_component_get, NULL, ".L1_snr.name", "_L1_snr_name"},
+	{"_V1_snr_name", V1_snr_component_get, NULL, ".V1_snr.name", "_V1_snr_name"},
+	{"_K1_snr_name", K1_snr_component_get, NULL, ".K1_snr.name", "_K1_snr_name"},
+	{"_H1_snr_epoch_gpsSeconds", H1_snr_component_get, NULL, ".H1_snr.epoch.gpsSeconds", "_H1_snr_epoch_gpsSeconds"},
+	{"_L1_snr_epoch_gpsSeconds", L1_snr_component_get, NULL, ".L1_snr.epoch.gpsSeconds", "_L1_snr_epoch_gpsSeconds"},
+	{"_V1_snr_epoch_gpsSeconds", V1_snr_component_get, NULL, ".V1_snr.epoch.gpsSeconds", "_V1_snr_epoch_gpsSeconds"},
+	{"_K1_snr_epoch_gpsSeconds", K1_snr_component_get, NULL, ".K1_snr.epoch.gpsSeconds", "_K1_snr_epoch_gpsSeconds"},
+	{"_H1_snr_epoch_gpsNanoSeconds", H1_snr_component_get, NULL, ".H1_snr.epoch.gpsNanoSeconds", "_H1_snr_epoch_gpsNanoSeconds"},
+	{"_L1_snr_epoch_gpsNanoSeconds", L1_snr_component_get, NULL, ".L1_snr.epoch.gpsNanoSeconds", "_L1_snr_epoch_gpsNanoSeconds"},
+	{"_V1_snr_epoch_gpsNanoSeconds", V1_snr_component_get, NULL, ".V1_snr.epoch.gpsNanoSeconds", "_V1_snr_epoch_gpsNanoSeconds"},
+	{"_K1_snr_epoch_gpsNanoSeconds", K1_snr_component_get, NULL, ".K1_snr.epoch.gpsNanoSeconds", "_K1_snr_epoch_gpsNanoSeconds"},
+	{"_H1_snr_f0", H1_snr_component_get, NULL, ".H1_snr.f0", "_H1_snr_f0"},
+	{"_L1_snr_f0", L1_snr_component_get, NULL, ".L1_snr.f0", "_L1_snr_f0"},
+	{"_V1_snr_f0", V1_snr_component_get, NULL, ".V1_snr.f0", "_V1_snr_f0"},
+	{"_K1_snr_f0", K1_snr_component_get, NULL, ".K1_snr.f0", "_K1_snr_f0"},
+	{"_H1_snr_deltaT", H1_snr_component_get, NULL, ".H1_snr.deltaT", "_H1_snr_deltaT"},
+	{"_L1_snr_deltaT", L1_snr_component_get, NULL, ".L1_snr.deltaT", "_L1_snr_deltaT"},
+	{"_V1_snr_deltaT", V1_snr_component_get, NULL, ".V1_snr.deltaT", "_V1_snr_deltaT"},
+	{"_K1_snr_deltaT", K1_snr_component_get, NULL, ".K1_snr.deltaT", "_K1_snr_deltaT"},
+	{"_H1_snr_sampleUnits", H1_snr_component_get, NULL, ".H1_snr.sampleUnits", "_H1_snr_sampleUnits"},
+	{"_L1_snr_sampleUnits", L1_snr_component_get, NULL, ".L1_snr.sampleUnits", "_L1_snr_sampleUnits"},
+	{"_V1_snr_sampleUnits", V1_snr_component_get, NULL, ".V1_snr.sampleUnits", "_V1_snr_sampleUnits"},
+	{"_K1_snr_sampleUnits", K1_snr_component_get, NULL, ".K1_snr.sampleUnits", "_K1_snr_sampleUnits"},
+	{"_H1_snr_data_length", H1_snr_component_get, NULL, ".H1_snr.data.length", "_H1_snr_data_length"},
+	{"_L1_snr_data_length", L1_snr_component_get, NULL, ".L1_snr.data.length", "_L1_snr_data_length"},
+	{"_V1_snr_data_length", V1_snr_component_get, NULL, ".V1_snr.data.length", "_V1_snr_data_length"},
+	{"_K1_snr_data_length", K1_snr_component_get, NULL, ".K1_snr.data.length", "_K1_snr_data_length"},
+	{"_H1_snr_data", H1_snr_component_get, NULL, ".H1_snr.data", "_H1_snr_data"},
+	{"_L1_snr_data", L1_snr_component_get, NULL, ".L1_snr.data", "_L1_snr_data"},
+	{"_V1_snr_data", V1_snr_component_get, NULL, ".V1_snr.data", "_V1_snr_data"},
+	{"_K1_snr_data", K1_snr_component_get, NULL, ".K1_snr.data", "_K1_snr_data"},
 	{NULL,}
 };
 
@@ -303,7 +570,14 @@ static PyObject *__new__(PyTypeObject *type, PyObject *args, PyObject *kwds)
 
 static void __del__(PyObject *self)
 {
-	XLALDestroyCOMPLEX8TimeSeries(((gstlal_GSTLALSnglInspiral *) self)->snr);
+	if(((gstlal_GSTLALSnglInspiral *) self)->H1_snr != NULL)
+		XLALDestroyCOMPLEX8TimeSeries(((gstlal_GSTLALSnglInspiral *) self)->H1_snr);
+	if(((gstlal_GSTLALSnglInspiral *) self)->L1_snr != NULL)
+		XLALDestroyCOMPLEX8TimeSeries(((gstlal_GSTLALSnglInspiral *) self)->L1_snr);
+	if(((gstlal_GSTLALSnglInspiral *) self)->V1_snr != NULL)
+		XLALDestroyCOMPLEX8TimeSeries(((gstlal_GSTLALSnglInspiral *) self)->V1_snr);
+	if(((gstlal_GSTLALSnglInspiral *) self)->K1_snr != NULL)
+		XLALDestroyCOMPLEX8TimeSeries(((gstlal_GSTLALSnglInspiral *) self)->K1_snr);
 	Py_TYPE(self)->tp_free(self);
 }
 
@@ -338,18 +612,72 @@ static PyObject *from_buffer(PyObject *cls, PyObject *args)
 			return NULL;
 		}
 		((gstlal_GSTLALSnglInspiral*)item)->row = gstlal_snglinspiral->parent;
+
+		if(gstlal_snglinspiral->H1_length)
+		{
+			const size_t H1_nbytes = sizeof(gstlal_snglinspiral->snr[0]) * gstlal_snglinspiral->H1_length;
+			if (data + H1_nbytes > end)
+			{
+				Py_DECREF(item);
+				Py_DECREF(result);
+				PyErr_SetString(PyExc_ValueError, "buffer overrun while copying H1 SNR time series");
+				return NULL;
+			}
+			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("H1_snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->H1_length);
+			if (!series)
+			{
+				Py_DECREF(item);
+				Py_DECREF(result);
+				PyErr_SetString(PyExc_MemoryError, "out of memory");
+				return NULL;
+			}
+
+			memcpy(series->data->data, gstlal_snglinspiral->snr, H1_nbytes);
+			data += H1_nbytes;
+			((gstlal_GSTLALSnglInspiral*)item)->H1_snr = series;
+		} else
+			((gstlal_GSTLALSnglInspiral*)item)->H1_snr = NULL;
+
+
 		/* duplicate the SNR time series */
-		if(gstlal_snglinspiral->length)
+		if(gstlal_snglinspiral->L1_length)
+		{
+			const size_t L1_nbytes = sizeof(gstlal_snglinspiral->snr[0]) * gstlal_snglinspiral->L1_length;
+			if (data + L1_nbytes > end)
+			{
+				Py_DECREF(item);
+				Py_DECREF(result);
+				PyErr_SetString(PyExc_ValueError, "buffer overrun while copying L1 SNR time series");
+				return NULL;
+			}
+			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("L1_snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->L1_length);
+			if (!series)
+			{
+				Py_DECREF(item);
+				Py_DECREF(result);
+				PyErr_SetString(PyExc_MemoryError, "out of memory");
+				return NULL;
+			}
+
+			memcpy(series->data->data, &(gstlal_snglinspiral->snr[gstlal_snglinspiral->H1_length]), L1_nbytes);
+			data += L1_nbytes;
+			((gstlal_GSTLALSnglInspiral*)item)->L1_snr = series;
+		} else
+			((gstlal_GSTLALSnglInspiral*)item)->L1_snr = NULL;
+
+
+		if(gstlal_snglinspiral->V1_length)
 		{
-			const size_t nbytes = sizeof(gstlal_snglinspiral->snr[0]) * gstlal_snglinspiral->length;
-			if (data + nbytes > end)
+			const size_t V1_nbytes = sizeof(gstlal_snglinspiral->snr[0]) * gstlal_snglinspiral->V1_length;
+			if (data + V1_nbytes > end)
 			{
 				Py_DECREF(item);
 				Py_DECREF(result);
 				PyErr_SetString(PyExc_ValueError, "buffer overrun while copying SNR time series");
 				return NULL;
 			}
-			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->length);
+
+			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("V1_snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->V1_length);
 			if (!series)
 			{
 				Py_DECREF(item);
@@ -357,10 +685,38 @@ static PyObject *from_buffer(PyObject *cls, PyObject *args)
 				PyErr_SetString(PyExc_MemoryError, "out of memory");
 				return NULL;
 			}
-			memcpy(series->data->data, gstlal_snglinspiral->snr, nbytes);
-			data += nbytes;
-			((gstlal_GSTLALSnglInspiral*)item)->snr = series;
-		}
+
+			memcpy(series->data->data, &(gstlal_snglinspiral->snr[gstlal_snglinspiral->H1_length + gstlal_snglinspiral->L1_length]), V1_nbytes);
+			data += V1_nbytes;
+			((gstlal_GSTLALSnglInspiral*)item)->V1_snr = series;
+		} else
+			((gstlal_GSTLALSnglInspiral*)item)->V1_snr = NULL;
+
+		if(gstlal_snglinspiral->K1_length)
+		{
+			const size_t K1_nbytes = sizeof(gstlal_snglinspiral->snr[0]) * gstlal_snglinspiral->K1_length;
+			if (data + K1_nbytes > end)
+			{
+				Py_DECREF(item);
+				Py_DECREF(result);
+				PyErr_SetString(PyExc_ValueError, "buffer overrun while copying SNR time series");
+				return NULL;
+			}
+			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("K1_snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->K1_length);
+			if (!series)
+			{
+				Py_DECREF(item);
+				Py_DECREF(result);
+				PyErr_SetString(PyExc_MemoryError, "out of memory");
+				return NULL;
+			}
+
+			memcpy(series->data->data, &(gstlal_snglinspiral->snr[gstlal_snglinspiral->H1_length + gstlal_snglinspiral->L1_length + gstlal_snglinspiral->V1_length]), K1_nbytes);
+			data += K1_nbytes;
+			((gstlal_GSTLALSnglInspiral*)item)->K1_snr = series;
+		} else
+			((gstlal_GSTLALSnglInspiral*)item)->K1_snr = NULL;
+
 
 		PyList_Append(result, item);
 		Py_DECREF(item);
@@ -379,10 +735,34 @@ static PyObject *from_buffer(PyObject *cls, PyObject *args)
 }
 
 
-static PyObject *_snr_time_series_deleter(PyObject *self, PyObject *args)
+static PyObject *_L1_snr_time_series_deleter(PyObject *self, PyObject *args)
 {
-	XLALDestroyCOMPLEX8TimeSeries(((gstlal_GSTLALSnglInspiral *) self)->snr);
-	((gstlal_GSTLALSnglInspiral *) self)->snr = NULL;
+	XLALDestroyCOMPLEX8TimeSeries(((gstlal_GSTLALSnglInspiral *) self)->L1_snr);
+	((gstlal_GSTLALSnglInspiral *) self)->L1_snr = NULL;
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyObject *_H1_snr_time_series_deleter(PyObject *self, PyObject *args)
+{
+	XLALDestroyCOMPLEX8TimeSeries(((gstlal_GSTLALSnglInspiral *) self)->H1_snr);
+	((gstlal_GSTLALSnglInspiral *) self)->H1_snr = NULL;
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyObject *_V1_snr_time_series_deleter(PyObject *self, PyObject *args)
+{
+	XLALDestroyCOMPLEX8TimeSeries(((gstlal_GSTLALSnglInspiral *) self)->V1_snr);
+	((gstlal_GSTLALSnglInspiral *) self)->V1_snr = NULL;
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyObject *_K1_snr_time_series_deleter(PyObject *self, PyObject *args)
+{
+	XLALDestroyCOMPLEX8TimeSeries(((gstlal_GSTLALSnglInspiral *) self)->K1_snr);
+	((gstlal_GSTLALSnglInspiral *) self)->K1_snr = NULL;
 	Py_INCREF(Py_None);
 	return Py_None;
 }
@@ -393,10 +773,22 @@ static PyObject *random_obj(PyObject *cls, PyObject *args)
 	gstlal_GSTLALSnglInspiral *new = (gstlal_GSTLALSnglInspiral *) __new__((PyTypeObject *) cls, NULL, NULL);
 	unsigned i;
 
-	new->snr = XLALCreateCOMPLEX8TimeSeries("", &(LIGOTimeGPS) {0, 0}, 0., 1. / 16384, &lalDimensionlessUnit, 16384);
+	new->L1_snr = XLALCreateCOMPLEX8TimeSeries("", &(LIGOTimeGPS) {0, 0}, 0., 1. / 16384, &lalDimensionlessUnit, 16384);
+	new->H1_snr = XLALCreateCOMPLEX8TimeSeries("", &(LIGOTimeGPS) {0, 0}, 0., 1. / 16384, &lalDimensionlessUnit, 16384);
+	new->V1_snr = XLALCreateCOMPLEX8TimeSeries("", &(LIGOTimeGPS) {0, 0}, 0., 1. / 16384, &lalDimensionlessUnit, 16384);
+	new->K1_snr = XLALCreateCOMPLEX8TimeSeries("", &(LIGOTimeGPS) {0, 0}, 0., 1. / 16384, &lalDimensionlessUnit, 16384);
+
+	for(i = 0; i < new->L1_snr->data->length; i++)
+		new->L1_snr->data->data[i] = 0.;
+
+	for(i = 0; i < new->H1_snr->data->length; i++)
+		new->H1_snr->data->data[i] = 0.;
+
+	for(i = 0; i < new->V1_snr->data->length; i++)
+		new->V1_snr->data->data[i] = 0.;
 
-	for(i = 0; i < new->snr->data->length; i++)
-		new->snr->data->data[i] = 0.;
+	for(i = 0; i < new->K1_snr->data->length; i++)
+		new->K1_snr->data->data[i] = 0.;
 
 	return (PyObject *) new;
 }
@@ -404,8 +796,11 @@ static PyObject *random_obj(PyObject *cls, PyObject *args)
 
 static struct PyMethodDef methods[] = {
 	{"from_buffer", from_buffer, METH_VARARGS | METH_CLASS, "Construct a tuple of GSTLALSnglInspiral objects from a buffer object.  The buffer is interpreted as a C array of GSTLALSnglInspiral structures.  All data is copied, the buffer can be deallocated afterwards."},
-	{"_snr_time_series_deleter", _snr_time_series_deleter, METH_NOARGS, "Release the SNR time series attached to the GSTLALSnglInspiral object."},
-	{"random", random_obj, METH_NOARGS | METH_CLASS, "Make a GSTLALSnglInspiral with an SNR time series attached to assist with writing test code."},
+	{"_L1_snr_time_series_deleter", _L1_snr_time_series_deleter, METH_NOARGS, "Release the L1 SNR time series attached to the GSTLALSnglInspiral object."},
+	{"_H1_snr_time_series_deleter", _H1_snr_time_series_deleter, METH_NOARGS, "Release the H1 SNR time series attached to the GSTLALSnglInspiral object."},
+	{"_V1_snr_time_series_deleter", _V1_snr_time_series_deleter, METH_NOARGS, "Release the V1 SNR time series attached to the GSTLALSnglInspiral object."},
+	{"_K1_snr_time_series_deleter", _K1_snr_time_series_deleter, METH_NOARGS, "Release the K1 SNR time series attached to the GSTLALSnglInspiral object."},
+	{"random", random_obj, METH_NOARGS | METH_CLASS, "Make a GSTLALSnglInspiral with an SNR time series attached to it for L1, H1, V1, and K1 to assist with writing test code."},
 	{NULL,}
 };
 
diff --git a/gstlal-inspiral/python/snglinspiraltable.py b/gstlal-inspiral/python/snglinspiraltable.py
index 74926fec2f..9477038ee1 100644
--- a/gstlal-inspiral/python/snglinspiraltable.py
+++ b/gstlal-inspiral/python/snglinspiraltable.py
@@ -30,20 +30,20 @@ class GSTLALSnglInspiral(_snglinspiraltable.GSTLALSnglInspiral):
 	spin2 = lsctables.SnglInspiral.spin2
 
 	@property
-	def snr_time_series(self):
+	def H1_snr_time_series(self):
 		try:
-			name = self._snr_name
+			name = self._H1_snr_name
 		except ValueError:
 			# C interface raises ValueError if the internal snr
 			# pointer is NULL
 			return None
 		series = lal.CreateCOMPLEX8TimeSeries(
 			name,
-			lal.LIGOTimeGPS(self._snr_epoch_gpsSeconds, self._snr_epoch_gpsNanoSeconds),
-			self._snr_f0,
-			self._snr_deltaT,
-			lal.Unit(self._snr_sampleUnits),
-			self._snr_data_length
+			lal.LIGOTimeGPS(self._H1_snr_epoch_gpsSeconds, self._H1_snr_epoch_gpsNanoSeconds),
+			self._H1_snr_f0,
+			self._H1_snr_deltaT,
+			lal.Unit(self._H1_snr_sampleUnits),
+			self._H1_snr_data_length
 		)
 		# we want to be able to keep the table row object in memory
 		# for an extended period of time so we need to be able to
@@ -53,9 +53,102 @@ class GSTLALSnglInspiral(_snglinspiraltable.GSTLALSnglInspiral):
 		# allowed references to the original memory to leak out
 		# into Python land we could never know if it's safe to free
 		# it
-		series.data.data[:] = self._snr_data
+		series.data.data[:] = self._H1_snr_data
 		return series
 
-	@snr_time_series.deleter
-	def snr_time_series(self):
-		self._snr_time_series_deleter()
+	@property
+	def L1_snr_time_series(self):
+		try:
+			name = self._L1_snr_name
+		except ValueError:
+			# C interface raises ValueError if the internal snr
+			# pointer is NULL
+			return None
+		series = lal.CreateCOMPLEX8TimeSeries(
+			name,
+			lal.LIGOTimeGPS(self._L1_snr_epoch_gpsSeconds, self._L1_snr_epoch_gpsNanoSeconds),
+			self._L1_snr_f0,
+			self._L1_snr_deltaT,
+			lal.Unit(self._L1_snr_sampleUnits),
+			self._L1_snr_data_length
+		)
+		# we want to be able to keep the table row object in memory
+		# for an extended period of time so we need to be able to
+		# release the memory used by the SNR time series when we no
+		# longer need it, and so we copy the data here instead of
+		# holding a reference to the original memory.  if we
+		# allowed references to the original memory to leak out
+		# into Python land we could never know if it's safe to free
+		# it
+		series.data.data[:] = self._L1_snr_data
+		return series
+
+	@property
+	def V1_snr_time_series(self):
+		try:
+			name = self._V1_snr_name
+		except ValueError:
+			# C interface raises ValueError if the internal snr
+			# pointer is NULL
+			return None
+		series = lal.CreateCOMPLEX8TimeSeries(
+			name,
+			lal.LIGOTimeGPS(self._V1_snr_epoch_gpsSeconds, self._V1_snr_epoch_gpsNanoSeconds),
+			self._V1_snr_f0,
+			self._V1_snr_deltaT,
+			lal.Unit(self._V1_snr_sampleUnits),
+			self._V1_snr_data_length
+		)
+		# we want to be able to keep the table row object in memory
+		# for an extended period of time so we need to be able to
+		# release the memory used by the SNR time series when we no
+		# longer need it, and so we copy the data here instead of
+		# holding a reference to the original memory.  if we
+		# allowed references to the original memory to leak out
+		# into Python land we could never know if it's safe to free
+		# it
+		series.data.data[:] = self._V1_snr_data
+		return series
+
+	@property
+	def K1_snr_time_series(self):
+		try:
+			name = self._K1_snr_name
+		except ValueError:
+			# C interface raises ValueError if the internal snr
+			# pointer is NULL
+			return None
+		series = lal.CreateCOMPLEX8TimeSeries(
+			name,
+			lal.LIGOTimeGPS(self._K1_snr_epoch_gpsSeconds, self._K1_snr_epoch_gpsNanoSeconds),
+			self._K1_snr_f0,
+			self._K1_snr_deltaT,
+			lal.Unit(self._K1_snr_sampleUnits),
+			self._K1_snr_data_length
+		)
+		# we want to be able to keep the table row object in memory
+		# for an extended period of time so we need to be able to
+		# release the memory used by the SNR time series when we no
+		# longer need it, and so we copy the data here instead of
+		# holding a reference to the original memory.  if we
+		# allowed references to the original memory to leak out
+		# into Python land we could never know if it's safe to free
+		# it
+		series.data.data[:] = self._K1_snr_data
+		return series
+
+	@H1_snr_time_series.deleter
+	def H1_snr_time_series(self):
+		self._H1_snr_time_series_deleter()
+
+	@L1_snr_time_series.deleter
+	def L1_snr_time_series(self):
+		self._L1_snr_time_series_deleter()
+
+	@V1_snr_time_series.deleter
+	def V1_snr_time_series(self):
+		self._V1_snr_time_series_deleter()
+
+	@K1_snr_time_series.deleter
+	def K1_snr_time_series(self):
+		self._K1_snr_time_series_deleter()
diff --git a/gstlal/gst/gst/Makefile.am b/gstlal/gst/gst/Makefile.am
index c3b3344a54..ce4494d28c 100644
--- a/gstlal/gst/gst/Makefile.am
+++ b/gstlal/gst/gst/Makefile.am
@@ -1,6 +1,6 @@
 AM_CPPFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib
 
-plugin_LTLIBRARIES = libgstlal_adder.la libgstpythonplugin.la
+plugin_LTLIBRARIES = libgstlal_adder.la
 
 libgstlal_adder_la_SOURCES = \
 	gstadder.h gstadder.c \
@@ -8,7 +8,3 @@ libgstlal_adder_la_SOURCES = \
 libgstlal_adder_la_CFLAGS = $(AM_CFLAGS) $(ORC_CFLAGS) $(gstreamer_CFLAGS) $(gstreamer_audio_CFLAGS) -DGST_PACKAGE_NAME=\"gstlal\" -DGST_PACKAGE_ORIGIN=\"\"
 libgstlal_adder_la_LIBADD = $(top_builddir)/lib/gstlal/libgstlal.la $(top_builddir)/lib/gstlal/libgstlaltypes.la
 libgstlal_adder_la_LDFLAGS = $(AM_LDFLAGS) $(ORC_LIBS) $(gstreamer_LIBS) $(gstreamer_audio_LIBS) $(GSTLAL_PLUGIN_LDFLAGS)
-
-libgstpythonplugin_la_SOURCES = gstpythonplugin.c
-libgstpythonplugin_la_CFLAGS = $(AM_CPPFLAGS) $(PYTHON_CPPFLAGS) $(PYGOBJECT_CFLAGS) $(gstreamer_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) -DPYTHON_VERSION="\"$(PYTHON_VERSION)\"" -DPY_LIB_LOC="\"$(PYTHON_LIB_LOC)\"" -DPY_ABI_FLAGS="\"$(PYTHON_ABI_FLAGS)\"" -DPY_LIB_SUFFIX="\"$(PYTHON_LIB_SUFFIX)\"" -DGST_API_VERSION="\"$(GSTREAMER_RELEASE)\"" -DPLUGINDIR="\"$(plugindir)\""
-libgstpythonplugin_la_LDFLAGS = $(AM_LDFLAGS) $(PYTHON_LIBS) $(gstreamer_LIBS) $(GSTLAL_PLUGIN_LDFLAGS) $(PYGOBJECT_LIBS)
diff --git a/gstlal/lib/gstlal/gstlal_peakfinder.c b/gstlal/lib/gstlal/gstlal_peakfinder.c
index 2411ee20c7..c7db8aeb88 100644
--- a/gstlal/lib/gstlal/gstlal_peakfinder.c
+++ b/gstlal/lib/gstlal/gstlal_peakfinder.c
@@ -25,6 +25,7 @@ struct gstlal_peak_state *gstlal_peak_state_new(guint channels, gstlal_peak_type
 	new->thresh = 0;
 	new->type = type;
 	new->is_gap = FALSE;
+	new->no_peaks_past_threshold = TRUE;
 
 	switch (new->type)
 	{
@@ -84,6 +85,9 @@ int gstlal_peak_state_clear(struct gstlal_peak_state *val)
 	memset(val->interpvalues.as_float, 0.0, val->channels * val->unit);
 	val->num_events = 0;
 	val->is_gap = FALSE;
+	// dont reset the value of no peaks past threshold, user is responsibile for that
+	// FIXME This will be removed eventually, see itacac for more info
+	//val->no_peaks_past_threshold = TRUE;
 	return 0;
 }
 
diff --git a/gstlal/lib/gstlal/gstlal_peakfinder_top.h b/gstlal/lib/gstlal/gstlal_peakfinder_top.h
index 1882d5f5a6..f6693648ee 100644
--- a/gstlal/lib/gstlal/gstlal_peakfinder_top.h
+++ b/gstlal/lib/gstlal/gstlal_peakfinder_top.h
@@ -48,6 +48,7 @@ struct gstlal_peak_state {
 	LanczosTriggerInterpolant *interp;
 
 	gboolean is_gap;
+	gboolean no_peaks_past_threshold;
 };
 
 
-- 
GitLab