diff --git a/gstlal-calibration/bin/gstlal_compute_strain b/gstlal-calibration/bin/gstlal_compute_strain
index 2e4b9942dba49b6ae83ff00fb166dd7f23d8224d..71eae268fa6362232175d9650bc3f0acff88bb08 100755
--- a/gstlal-calibration/bin/gstlal_compute_strain
+++ b/gstlal-calibration/bin/gstlal_compute_strain
@@ -274,8 +274,8 @@ cleaning_check_range_low_max = float(DataCleaningConfigs["cleaningcheckrangelowm
 cleaning_check_range_mid_min = float(DataCleaningConfigs["cleaningcheckrangemidmin"])
 cleaning_check_range_mid_max = float(DataCleaningConfigs["cleaningcheckrangemidmax"])
 powerlines_tf_median_time = float(DataCleaningConfigs["powerlinestfmediantime"]) if "powerlinestfmediantime" in DataCleaningConfigs else 1
-powerlines_tf_averaging_time = float(DataCleaningConfigs["powerlinestfaveragingtime"])
-powerlines_freq_var = float(DataCleaningConfigs["powerlinesfreqvar"])
+powerlines_tf_averaging_time = float(DataCleaningConfigs["powerlinestfaveragingtime"]) if "powerlinestfaveragingtime" in DataCleaningConfigs else 128
+powerlines_freq_var = float(DataCleaningConfigs["powerlinesfreqvar"]) if "powerlinesfreqvar" in DataCleaningConfigs else 0.02
 witness_channel_fft_time = float(DataCleaningConfigs["witnesschannelffttime"])
 num_witness_ffts = int(DataCleaningConfigs["numwitnessffts"])
 min_witness_ffts = int(DataCleaningConfigs["minwitnessffts"])
@@ -357,7 +357,7 @@ compute_calib_statevector = Config.getboolean("CalibrationConfigurations", "comp
 factors_from_filters_file = Config.getboolean("TDCFConfigurations", "factorsfromfiltersfile")
 # Boolean for removing calibration lines, power lines, DC component, using median in witness transfer functions
 remove_cal_lines = Config.getboolean("DataCleaningConfigurations", "removecallines")
-remove_power_lines = Config.getboolean("DataCleaningConfigurations", "removepowerlines")
+remove_power_lines = Config.getboolean("DataCleaningConfigurations", "removepowerlines") if "removepowerlines" in DataCleaningConfigs else False
 remove_dc = Config.getboolean("DataCleaningConfigurations", "removedc")
 witness_tf_use_median = Config.getboolean("DataCleaningConfigurations", "witnesstfusemedian")
 witness_tf_parallel_mode = Config.getboolean("DataCleaningConfigurations", "witnesstfparallelmode") if "witnesstfparallelmode" in DataCleaningConfigs else False
@@ -910,12 +910,45 @@ elif CalibrationConfigs["calibrationmode"] == "Partial":
 	channel_list.extend(((instrument, ChannelNames["deltalreschannel"]), (instrument, ChannelNames["deltaltstchannel"]), (instrument, ChannelNames["deltalpumchannel"]), (instrument, ChannelNames["deltaluimchannel"])))
 	headkeys.extend(("res", "tst", "pum", "uim"))
 
-# If we are removing 60 Hz lines and harmonics, add the witness channel
-if remove_power_lines:
+# If we are removing lines using witness channels (e.g., 60 Hz lines and harmonics), add the witness channels
+line_witness_channel_list = []
+line_witness_fundamental_freqs = []
+line_witness_harmonics = []
+line_witness_tf_median_times = []
+line_witness_tf_averaging_times = []
+line_witness_freq_vars = []
+if (ChannelNames["linewitnesschannellist"] != "None") if "linewitnesschannellist" in ChannelNames else False:
+	line_witness_channel_list = ChannelNames["linewitnesschannellist"].split(';')
+	line_witness_fundamental_freqs = DataCleaningConfigs["linewitnessfundamentalfreqs"].split(';')
+	line_witness_harmonics = DataCleaningConfigs["linewitnessharmonics"].split(';')
+	line_witness_tf_median_times = DataCleaningConfigs["linewitnesstfmediantimes"].split(';')
+	line_witness_tf_averaging_times = DataCleaningConfigs["linewitnesstfaveragingtimes"].split(';')
+	line_witness_freq_vars = DataCleaningConfigs["linewitnessfreqvars"].split(';')
+	if len(line_witness_channel_list) != len(line_witness_fundamental_freqs) or len(line_witness_channel_list) != len(line_witness_harmonics) or len(line_witness_channel_list) != len(line_witness_tf_median_times) or len(line_witness_channel_list) != len(line_witness_tf_averaging_times) or len(line_witness_channel_list) != len(line_witness_freq_vars):
+		raise ValueError("LineWitnessChannelList, LineWitnessFundamentalFreqs, LineWitnessHarmonics, LineWitnessTFMedianTimes, LineWitnessTFAveragingTimes, LineWitnessFreqVars, and LineWitnessChannelSR must all be the same length, i.e., they must all have the same number of semicolons (;)")
+	for i in range(0, len(line_witness_channel_list)):
+		line_witness_channel_list[i] = line_witness_channel_list[i].split(',')
+		for j in range(0, len(line_witness_channel_list[i])):
+			channel_list.append((instrument, line_witness_channel_list[i][j]))
+			headkeys.append(line_witness_channel_list[i][j])
+		line_witness_fundamental_freqs[i] = float(line_witness_fundamental_freqs[i])
+		line_witness_harmonics[i] = int(line_witness_harmonics[i])
+		line_witness_tf_median_times[i] = float(line_witness_tf_median_times[i])
+		line_witness_tf_averaging_times[i] = float(line_witness_tf_averaging_times[i])
+		line_witness_freq_vars[i] = float(line_witness_freq_vars[i])
+
+if (ChannelNames["powerlineschannel"] not in headkeys) if remove_power_lines else False:
 	channel_list.append((instrument, ChannelNames["powerlineschannel"]))
-	headkeys.append("powerlines")
+	headkeys.append(ChannelNames["powerlineschannel"])
+	line_witness_channel_list.append([ChannelNames["powerlineschannel"]])
+	line_witness_fundamental_freqs.append(60.0)
+	line_witness_harmonics.append(5)
+	line_witness_tf_median_times.append(float(powerlines_tf_median_time))
+	line_witness_tf_averaging_times.append(float(powerlines_tf_averaging_time))
+	line_witness_freq_vars.append(float(powerlines_freq_var))
 
 # If we are using witness channels to clean h(t), add those to the channel list
+witness_channel_list = []
 if ChannelNames["witnesschannellist"] != "None":
 	witness_channel_list = ChannelNames["witnesschannellist"].split(';')
 	witness_notch_frequencies = DataCleaningConfigs["witnessnotchfrequencies"].split(';')
@@ -936,14 +969,12 @@ if ChannelNames["witnesschannellist"] != "None":
 		else:
 			for j in range(0, len(witness_notch_frequencies[i])):
 				witness_notch_frequencies[i][j] = float(witness_notch_frequencies[i][j])
-else:
-	witness_channel_list = None
 
 # If we are gating the noise or 60 Hz line subtraction with something other than the ODC state vector, add that channel
 if compute_calib_statevector:
 	noisesub_gate_channel = ChannelNames["noisesubgatechannel"] if "noisesubgatechannel" in ChannelNames else lownoise_channel_name 
 	noisesub_gate_bitmask = int(Bitmasks["noisesubgatebitmask"]) if "noisesubgatebitmask" in Bitmasks else lownoise_bitmask
-if compute_calib_statevector and (remove_power_lines or witness_channel_list is not None) and noisesub_gate_channel != obsintent_channel_name and noisesub_gate_channel != lownoise_channel_name and noisesub_gate_channel != hwinj_channel_name and noisesub_gate_bitmask >= 0:
+if compute_calib_statevector and (any(line_witness_channel_list) or any(witness_channel_list)) and noisesub_gate_channel != obsintent_channel_name and noisesub_gate_channel != lownoise_channel_name and noisesub_gate_channel != hwinj_channel_name and noisesub_gate_bitmask >= 0:
 	channel_list.append((instrument, noisesub_gate_channel))
 	headkeys.append("noisesubgatechannel")
 
@@ -1503,8 +1534,8 @@ if compute_fs or compute_srcq:
 		smooth_fs = pipeparts.mkaudioamplify(pipeline, smooth_sqrtXiR, src_pcal_line_freq)
 		smooth_fs_nogate = pipeparts.mkaudioamplify(pipeline, smooth_sqrtXiR_nogate, src_pcal_line_freq)
 		if src_pcal_line_freq == act_pcal_line_freq and "pcal1_linefreq" in head_dict:
-                        head_dict["pcal1_linefreq"].connect("notify::current-average", calibration_parts.update_property_simple, smooth_fs, "current_average", "amplification")
-                        head_dict["pcal1_linefreq"].connect("notify::current-average", calibration_parts.update_property_simple, smooth_fs_nogate, "current_average", "amplification")
+			head_dict["pcal1_linefreq"].connect("notify::current-average", calibration_parts.update_property_simple, smooth_fs, "current_average", "amplification")
+			head_dict["pcal1_linefreq"].connect("notify::current-average", calibration_parts.update_property_simple, smooth_fs_nogate, "current_average", "amplification")
 		elif src_pcal_line_freq != act_pcal_line_freq and "pcal4_linefreq" in head_dict:
 			head_dict["pcal4_linefreq"].connect("notify::current-average", calibration_parts.update_property_simple, smooth_fs, "current_average", "amplification")
 			head_dict["pcal4_linefreq"].connect("notify::current-average", calibration_parts.update_property_simple, smooth_fs_nogate, "current_average", "amplification")
@@ -2059,7 +2090,7 @@ strain = pipeparts.mkprogressreport(pipeline, strain, "progress_hoft_%s" % instr
 
 # Put the units back to strain before writing to frames
 straintagstr = "units=strain,channel-name=%sCALIB_STRAIN%s,instrument=%s" % (chan_prefix, chan_suffix, instrument)
-if remove_cal_lines or remove_power_lines or witness_channel_list is not None:
+if remove_cal_lines or any(line_witness_channel_list) or any(witness_channel_list):
 	straintee = pipeparts.mktee(pipeline, strain)
 	strain = pipeparts.mktaginject(pipeline, straintee, straintagstr)
 else:
@@ -2488,7 +2519,7 @@ if remove_cal_lines:
 	clean_strain = calibration_parts.mkadder(pipeline, calibration_parts.list_srcs(pipeline, straintee, calibration_lines))
 
 # Set up gating for the power mains and noise subtraction
-if compute_calib_statevector and (remove_power_lines or witness_channel_list is not None) and noisesub_gate_bitmask > 0:
+if compute_calib_statevector and (any(line_witness_channel_list) or any(witness_channel_list)) and noisesub_gate_bitmask > 0:
 	noisesubgate = obsintentchanneltee if noisesub_gate_channel == obsintent_channel_name else lownoisechanneltee if noisesub_gate_channel == lownoise_channel_name else hwinjtee if noisesub_gate_channel == hwinj_channel_name else calibration_parts.caps_and_progress(pipeline, head_dict["noisesubgatechannel"], "audio/x-raw, format=U32LE, channels=1, channel-mask=(bitmask)0x0", noisesub_gate_channel)
 	noisesubgate = pipeparts.mkgeneric(pipeline, noisesubgate, "lal_logicalundersample", required_on = noisesub_gate_bitmask, status_out = pow(2,28))
 	noisesubgate = pipeparts.mkcapsfilter(pipeline, noisesubgate, calibstate_caps)
@@ -2496,23 +2527,30 @@ if compute_calib_statevector and (remove_power_lines or witness_channel_list is
 else:
 	noisesubgatetee = None
 
-# Next, remove 60 Hz power lines and harmonics
-if remove_power_lines:
+# Next, remove lines using witness channels, such as 60 Hz power lines and harmonics
+if any(line_witness_channel_list):
 	if not remove_cal_lines:
 		clean_strain = straintee
-	powerlines = calibration_parts.caps_and_progress(pipeline, head_dict["powerlines"], "audio/x-raw, format=F64LE, channels=1, channel-mask=(bitmask)0x0", "powerlines")
-	if remove_cal_lines and filter_latency_factor > 0:
-		powerlines = pipeparts.mkgeneric(pipeline, powerlines, "lal_insertgap", chop_length = int(1000000000 * filter_latency_factor * demodulation_filter_time))
-	clean_strain = calibration_parts.remove_harmonics_with_witness(pipeline, clean_strain, powerlines, 60, 5, powerlines_freq_var, filter_latency_factor, compute_rate = compute_factors_sr, rate_out = hoft_sr, num_median = powerlines_tf_median_time, num_avg = powerlines_tf_averaging_time * compute_factors_sr, noisesub_gate_bit = noisesubgatetee)
+	for i in range(0, len(line_witness_channel_list)):
+		line_witnesses = []
+		for key in headkeys:
+			if key in line_witness_channel_list[i]:
+				line_witnesses.append(calibration_parts.caps_and_progress(pipeline, head_dict[key], "audio/x-raw, format=F64LE, channels=1, channel-mask=(bitmask)0x0", key))
+				if remove_cal_lines and filter_latency_factor > 0:
+					head_dict[key] = pipeparts.mkgeneric(pipeline, head_dict[key], "lal_insertgap", chop_length = int(1000000000 * filter_latency_factor * demodulation_filter_time))
+		if len(line_witnesses) != len(line_witness_channel_list[i]):
+			print "WARNING: Not all requested line witness channels are being used"
+
+		clean_strain = calibration_parts.remove_harmonics_with_witnesses(pipeline, clean_strain, line_witnesses, line_witness_fundamental_freqs[i], line_witness_harmonics[i], line_witness_freq_vars[i], filter_latency_factor, compute_rate = compute_factors_sr, rate_out = hoft_sr, num_median = line_witness_tf_median_times[i] * compute_factors_sr, num_avg = line_witness_tf_averaging_times[i] * compute_factors_sr, noisesub_gate_bit = noisesubgatetee)
 
 # Remove excess noise using any provided witness channels
-if witness_channel_list is not None:
+if any(witness_channel_list):
 	# Remove initial data from computation of transfer functions and wait until the filters and kappas settle
 	witness_delay_time = witness_tf_time_shift if witness_tf_parallel_mode else (filter_settle_time + (1.0 - filter_latency_factor) * (demodulation_filter_time + median_smoothing_samples / compute_factors_sr + factors_average_samples / compute_factors_sr))
 	# How much does the "delay_time" need to increase per iteration of cleaning?
 	witness_delay_increment = witness_filter_taper_time + witness_channel_fft_time / 2.0 * (num_witness_ffts + 1.0) if not filter_latency_factor else 0.0
 	# If we haven't removed any lines, clean the regular h(t) data
-	if not (remove_cal_lines or remove_power_lines):
+	if not (remove_cal_lines or any(line_witness_channel_list)):
 		clean_strain = straintee
 
 	for i in range(0, len(witness_channel_list)):
@@ -2536,7 +2574,7 @@ if witness_channel_list is not None:
 		clean_strain = calibration_parts.clean_data(pipeline, clean_strain, hoft_sr, witnesses, witness_rates[i], witness_fft_samples, witness_fft_overlap, num_witness_ffts, min_witness_ffts, witness_tf_update_samples, witness_fir_samples, witness_frequency_resolution, witness_filter_taper_length, use_median = witness_tf_use_median, parallel_mode = witness_tf_parallel_mode, notch_frequencies = witness_notch_frequencies[i], high_pass = witness_highpasses[i], noisesub_gate_bit = noisesubgatetee, delay_time = witness_delay_time, critical_lock_loss_time = critical_lock_loss_time, filename = None if witness_tf_filename is None else "%s_%d.txt" % (witness_tf_filename, i))
 		witness_delay_time += witness_delay_increment
 
-if remove_cal_lines or remove_power_lines or witness_channel_list is not None:
+if remove_cal_lines or any(line_witness_channel_list) or any(witness_channel_list):
 	clean_strain = pipeparts.mkprogressreport(pipeline, clean_strain, "progress_hoft_cleaned_%s" % instrument)
 	clean_straintagstr = "units=strain,channel-name=%sCALIB_STRAIN_CLEAN%s,instrument=%s" % (chan_prefix, chan_suffix, instrument)
 	if compute_calib_statevector:
@@ -2549,7 +2587,7 @@ if remove_cal_lines or remove_power_lines or witness_channel_list is not None:
 # CALIB_STATE_VECTOR: CALIB_STRAIN_CLEAN
 #
 
-if compute_calib_statevector and (remove_cal_lines or remove_power_lines or witness_channel_list is not None):
+if compute_calib_statevector and (remove_cal_lines or any(line_witness_channel_list) or any(witness_channel_list)):
 	low_rms_rate = pow(2, int(numpy.log(2 * cleaning_check_range_low_max) / numpy.log(2) + 1.01))
 	mid_rms_rate = pow(2, int(numpy.log(2 * cleaning_check_range_mid_max) / numpy.log(2) + 1.01))
 
@@ -2716,7 +2754,7 @@ if compute_calib_statevector:
 # Link the strain branch to the muxer
 channelmux_input_dict["%s:%sCALIB_STRAIN%s" % (instrument, chan_prefix, chan_suffix)] = calibration_parts.mkqueue(pipeline, strain)
 # Link the cleaned strain branch to the muxer if h(t) was cleaned in any way
-if remove_cal_lines or remove_power_lines or witness_channel_list is not None:
+if remove_cal_lines or any(line_witness_channel_list) or any(witness_channel_list):
 	channelmux_input_dict["%s:%sCALIB_STRAIN_CLEAN%s" % (instrument, chan_prefix, chan_suffix)] = calibration_parts.mkqueue(pipeline, clean_strain)
 # Link the real and imaginary parts of kappa_tst to the muxer
 if compute_kappatst:
diff --git a/gstlal-calibration/gst/lal/gstlal_matrixsolver.c b/gstlal-calibration/gst/lal/gstlal_matrixsolver.c
index d77dc11f6a983d680133027c283c130940025cc5..4466d06e2ed520b3e6735936a1ff5bc8b5dcc782 100644
--- a/gstlal-calibration/gst/lal/gstlal_matrixsolver.c
+++ b/gstlal-calibration/gst/lal/gstlal_matrixsolver.c
@@ -263,7 +263,7 @@ static GstCaps *transform_caps(GstBaseTransform *trans, GstPadDirection directio
 		 */
 		for(n = 0; n < gst_caps_get_size(caps); n++) {
 			GstStructure *str = gst_caps_get_structure(caps, n);
-			const GValue *v = gst_structure_get_value(gst_caps_get_structure(caps, 0), "channels");
+			const GValue *v = gst_structure_get_value(str, "channels");
 			if(GST_VALUE_HOLDS_INT_RANGE(v)) {
 				gint channels_in_min, channels_out_min, channels_out_max;
 				guint64 channels_in_max;
@@ -292,7 +292,7 @@ static GstCaps *transform_caps(GstBaseTransform *trans, GstPadDirection directio
 		 */
 		for(n = 0; n < gst_caps_get_size(caps); n++) {
 			GstStructure *str = gst_caps_get_structure(caps, n);
-			const GValue *v = gst_structure_get_value(gst_caps_get_structure(caps, 0), "channels");
+			const GValue *v = gst_structure_get_value(str, "channels");
 			if(GST_VALUE_HOLDS_INT_RANGE(v)) {
 				int channels_in_min, channels_in_max, channels_out_min, channels_out_max;
 				channels_in_min = gst_value_get_int_range_min(v);
diff --git a/gstlal-calibration/python/calibration_parts.py b/gstlal-calibration/python/calibration_parts.py
index b8a18c02093ab250bc742b2d60b9927df00275ed..0e5f6ef0f36f5e515edb3f045bdd622cf9677eab 100644
--- a/gstlal-calibration/python/calibration_parts.py
+++ b/gstlal-calibration/python/calibration_parts.py
@@ -193,11 +193,12 @@ def mkinterleave(pipeline, srcs, complex_data = False, queue_length = [0]):
 	#		pipeparts.mkqueue(pipeline, src).link(elem)
 	return elem
 
-def mkdeinterleave(pipeline, src, num_channels):
+def mkdeinterleave(pipeline, src, num_channels, complex_data = False):
+	complex_factor = 1 + int(complex_data)
 	head = pipeparts.mktee(pipeline, src)
 	streams = []
 	for i in range(0, num_channels):
-		matrix = numpy.transpose([numpy.zeros(num_channels)])
+		matrix = numpy.zeros((num_channels, complex_factor))
 		matrix[i][0] = 1.0
 		streams.append(pipeparts.mkmatrixmixer(pipeline, head, matrix = matrix))
 
@@ -283,7 +284,7 @@ def remove_harmonics(pipeline, signal, f0, num_harmonics, f0_var, filter_latency
 
 	return elem
 
-def remove_harmonics_with_witness(pipeline, signal, witness, f0, num_harmonics, f0_var, filter_latency, compute_rate = 16, rate_out = 16384, num_median = 2048, num_avg = 160, noisesub_gate_bit = None):
+def remove_harmonics_with_witnesses(pipeline, signal, witnesses, f0, num_harmonics, f0_var, filter_latency, compute_rate = 16, rate_out = 16384, num_median = 2048, num_avg = 160, noisesub_gate_bit = None):
 	# remove line(s) from a spectrum. filter length for demodulation (given in seconds) is adjustable
 	# function argument caps must be complex caps
 
@@ -293,7 +294,10 @@ def remove_harmonics_with_witness(pipeline, signal, witness, f0, num_harmonics,
 	resample_shift = 16.0 + 16.5
 	zero_latency = filter_latency == 0.0
 
-	witness = pipeparts.mktee(pipeline, witness)
+	a = resample_shift / compute_rate
+	b = f0_var / 2
+	for i in range(0, len(witnesses)):
+		witnesses[i] = pipeparts.mktee(pipeline, witnesses[i])
 	signal = pipeparts.mktee(pipeline, signal)
 
 	# If f0 strays from its nominal value and there is a timestamp shift in the signal
@@ -302,7 +306,7 @@ def remove_harmonics_with_witness(pipeline, signal, witness, f0, num_harmonics,
 	# frequency between that and the nominal frequency f0.
 	if filter_latency != 0.5:
 		# The low-pass and resampling filters are not centered in time
-		f0_measured = pipeparts.mkgeneric(pipeline, witness, "lal_trackfrequency", num_halfcycles = int(round((filter_param / f0_var / 2 + resample_shift / compute_rate) * f0)))
+		f0_measured = pipeparts.mkgeneric(pipeline, witnesses[0], "lal_trackfrequency", num_halfcycles = int(round((filter_param / f0_var / 2 + resample_shift / compute_rate) * f0)))
 		f0_measured = mkresample(pipeline, f0_measured, 3, zero_latency, compute_rate)
 		f0_measured = pipeparts.mkgeneric(pipeline, f0_measured, "lal_smoothkappas", array_size = 1, avg_array_size = int(round((filter_param / f0_var / 2 * compute_rate + resample_shift) / 2)), default_kappa_re = 0, default_to_median = True, filter_latency = filter_latency)
 		f0_beat_frequency = pipeparts.mkgeneric(pipeline, f0_measured, "lal_add_constant", value = -f0)
@@ -326,39 +330,70 @@ def remove_harmonics_with_witness(pipeline, signal, witness, f0, num_harmonics,
 			phase_shift = pipeparts.mktogglecomplex(pipeline, phase_shift)
 			phase_factor = pipeparts.mkgeneric(pipeline, phase_shift, "cexp")
 
-		# Find amplitude and phase of each harmonic in the witness channel
-		line_in_witness = pipeparts.mkgeneric(pipeline, witness, "lal_demodulate", line_frequency = n * f0)
-		line_in_witness = mkresample(pipeline, line_in_witness, downsample_quality, zero_latency, compute_rate)
-		line_in_witness = lowpass(pipeline, line_in_witness, compute_rate, length = filter_length, fcut = 0, filter_latency = filter_latency)
-		line_in_witness = pipeparts.mktee(pipeline, line_in_witness)
-
 		# Find amplitude and phase of line in signal
 		line_in_signal = pipeparts.mkgeneric(pipeline, signal, "lal_demodulate", line_frequency = n * f0)
 		line_in_signal = mkresample(pipeline, line_in_signal, downsample_quality, zero_latency, compute_rate)
 		line_in_signal = lowpass(pipeline, line_in_signal, compute_rate, length = filter_length, fcut = 0, filter_latency = filter_latency)
-
-		# Find transfer function between witness channel and signal at this frequency
-		tf_at_f = complex_division(pipeline, line_in_signal, line_in_witness)
-		# It may be necessary to remove the first few samples since denominator samples may have arrived before numerator samples, in which case the adder assumes the numerator is one.
-		tf_at_f = pipeparts.mkgeneric(pipeline, tf_at_f, "lal_insertgap", chop_length = 1000000000) # Removing one second
-
-		# Remove worthless data from computation of transfer function if we can
-		if noisesub_gate_bit is not None:
-			tf_at_f = mkgate(pipeline, tf_at_f, noisesub_gate_bit, 1, attack_length = -((1.0 - filter_latency) * filter_samples))
-		tf_at_f = pipeparts.mkgeneric(pipeline, tf_at_f, "lal_smoothkappas", default_kappa_re = 0, array_size = num_median, avg_array_size = num_avg, default_to_median = True, filter_latency = filter_latency)
-
-		# Use gated, averaged transfer function to reconstruct the sinusoid as it appears in the signal from the witness channel
-		if filter_latency == 0.5:
-			reconstructed_line_in_signal = mkmultiplier(pipeline, list_srcs(pipeline, tf_at_f, line_in_witness))
-		else:
-			reconstructed_line_in_signal = mkmultiplier(pipeline, list_srcs(pipeline, tf_at_f, line_in_witness, phase_factor))
-		# It may be necessary to remove the first few samples since line_in_witness may have arrived first, in which case the result of the above multiplication would be line_in_witness
-		reconstructed_line_in_signal = pipeparts.mkgeneric(pipeline, reconstructed_line_in_signal, "lal_insertgap", chop_length = 1000000000)
-		reconstructed_line_in_signal = mkresample(pipeline, reconstructed_line_in_signal, upsample_quality, zero_latency, rate_out)
-		reconstructed_line_in_signal = pipeparts.mkgeneric(pipeline, reconstructed_line_in_signal, "lal_demodulate", line_frequency = -1.0 * n * f0, prefactor_real = -2.0)
-		reconstructed_line_in_signal = pipeparts.mkgeneric(pipeline, reconstructed_line_in_signal, "creal")
-
-		signal_minus_lines.append(reconstructed_line_in_signal)
+		line_in_signal = pipeparts.mktee(pipeline, line_in_signal)
+
+		# Make ones for use in matrix equation
+		ones = pipeparts.mktee(pipeline, mkpow(pipeline, line_in_signal, exponent = 0.0))
+
+		line_in_witnesses = []
+		tfs_at_f = [None] * len(witnesses) * (len(witnesses) + 1)
+		for i in range(0, len(witnesses)):
+			# Find amplitude and phase of each harmonic in each witness channel
+			line_in_witness = pipeparts.mkgeneric(pipeline, witnesses[i], "lal_demodulate", line_frequency = n * f0)
+			line_in_witness = mkresample(pipeline, line_in_witness, downsample_quality, zero_latency, compute_rate)
+			line_in_witness = lowpass(pipeline, line_in_witness, compute_rate, length = filter_length, fcut = 0, filter_latency = filter_latency)
+			line_in_witness = pipeparts.mktee(pipeline, line_in_witness)
+			line_in_witnesses.append(line_in_witness)
+
+			# Find transfer function between witness channel and signal at this frequency
+			tf_at_f = complex_division(pipeline, line_in_signal, line_in_witness)
+			# It may be necessary to remove the first few samples since denominator
+			# samples may have arrived before numerator samples, in which case the
+			# adder assumes the numerator is one.
+			tf_at_f = pipeparts.mkgeneric(pipeline, tf_at_f, "lal_insertgap", chop_length = 1000000000) # Removing one second
+
+			# Remove worthless data from computation of transfer function if we can
+			if noisesub_gate_bit is not None:
+				tf_at_f = mkgate(pipeline, tf_at_f, noisesub_gate_bit, 1, attack_length = -((1.0 - filter_latency) * filter_samples), name = "powerlines_gate_%d_%d" % (n, i))
+			tfs_at_f[i] = pipeparts.mkgeneric(pipeline, tf_at_f, "lal_smoothkappas", default_kappa_re = 0, array_size = num_median, avg_array_size = num_avg, default_to_median = True, filter_latency = filter_latency)
+			tfs_at_f[(i + 1) * len(witnesses) + i] = ones
+
+		for i in range(0, len(witnesses)):
+			for j in range(0, len(witnesses)):
+				if(i != j):
+					# Find transfer function between 2 witness channels and at this frequency
+					tf_at_f = complex_division(pipeline, line_in_witnesses[j], line_in_witness[i])
+					# It may be necessary to remove the first few samples since
+					# denominator samples may have arrived before numerator samples,
+					# in which case the adder assumes the numerator is one.
+					tf_at_f = pipeparts.mkgeneric(pipeline, tf_at_f, "lal_insertgap", chop_length = 1000000000)
+
+					# Remove worthless data from computation of transfer function if we can
+					if noisesub_gate_bit is not None:
+						tf_at_f = mkgate(pipeline, tf_at_f, noisesub_gate_bit, 1, attack_length = -((1.0 - filter_latency) * filter_samples), name = "powerlines_gate_%d_%d_%d" % (n, i, j))
+					tfs_at_f[(i + 1) * len(witnesses) + j] = pipeparts.mkgeneric(pipeline, tf_at_f, "lal_smoothkappas", default_kappa_re = 0, array_size = num_median, avg_array_size = num_avg, default_to_median = True, filter_latency = filter_latency)
+
+		tfs_at_f = mkinterleave(pipeline, tfs_at_f, complex_data = True)
+		tfs_at_f = pipeparts.mkgeneric(pipeline, tfs_at_f, "lal_matrixsolver")
+		tfs_at_f = mkdeinterleave(pipeline, tfs_at_f, len(witnesses), complex_data = True)
+
+		for i in range(0, len(witnesses)):
+			# Use gated, averaged transfer function to reconstruct the sinusoid as it appears in the signal from the witness channel
+			if filter_latency == 0.5:
+				reconstructed_line_in_signal = mkmultiplier(pipeline, list_srcs(pipeline, tfs_at_f[i], line_in_witnesses[i]))
+			else:
+				reconstructed_line_in_signal = mkmultiplier(pipeline, list_srcs(pipeline, tfs_at_f[i], line_in_witnesses[i], phase_factor))
+			# It may be necessary to remove the first few samples since line_in_witness may have arrived first, in which case the result of the above multiplication would be line_in_witness
+			reconstructed_line_in_signal = pipeparts.mkgeneric(pipeline, reconstructed_line_in_signal, "lal_insertgap", chop_length = 1000000000)
+			reconstructed_line_in_signal = mkresample(pipeline, reconstructed_line_in_signal, upsample_quality, zero_latency, rate_out)
+			reconstructed_line_in_signal = pipeparts.mkgeneric(pipeline, reconstructed_line_in_signal, "lal_demodulate", line_frequency = -1.0 * n * f0, prefactor_real = -2.0)
+			reconstructed_line_in_signal = pipeparts.mkgeneric(pipeline, reconstructed_line_in_signal, "creal")
+
+			signal_minus_lines.append(reconstructed_line_in_signal)
 
 	clean_signal = mkadder(pipeline, tuple(signal_minus_lines))