diff --git a/gstlal-burst/bin/gstlal_cs_triggergen b/gstlal-burst/bin/gstlal_cs_triggergen index 824c81b4629d4d99693da274572fbc869b960b67..e75cce94413dd255644d95c42efd8f3b423b7021 100755 --- a/gstlal-burst/bin/gstlal_cs_triggergen +++ b/gstlal-burst/bin/gstlal_cs_triggergen @@ -81,11 +81,35 @@ options, filenames = parse_command_line() # handler for obtaining psd # -class PSDHandler(simplehandler.Handler): - def __init__(self, mainloop, pipeline, firbank): - simplehandler.Handler.__init__(self,mainloop, pipeline) +class PipelineHandler(simplehandler.Handler): + def __init__(self, mainloop, pipeline, template_bank, firbank): + simplehandler.Handler.__init__(self, mainloop, pipeline) + self.template_bank = template_bank self.firbank = firbank self.triggergen = triggergen + # use central_freq to uniquely identify templates + self.sigma = dict((row.central_freq, 0.0) for row in template_bank) + # counter for controlling how often we update PSD + self.update_psd = 0 + + def appsink_new_buffer(self, elem): + buf = elem.emit("pull-sample").get_buffer() + events = [] + for i in range(buf.n_memory()): + memory = buf.peek_memory(i) + result, mapinfo = memory.map(Gst.MapFlags.READ) + assert result + if mapinfo.data: + events.extend(snglbursttable.GSTLALSnglBurst.from_buffer(mapinfo.data)) + memory.unmap(mapinfo) + # put info of each event in the sngl burst table + for event in events: + event.process_id = process.process_id + event.event_id = sngl_burst_table.get_next_id() + event.amplitude = event.snr / self.sigma[event.central_freq] + sngl_burst_table.append(event) + # event counter + search_summary.nevents += 1 def do_on_message(self, bus, message): if message.type == Gst.MessageType.ELEMENT and message.get_structure().get_name() == "spectrum": @@ -95,73 +119,87 @@ class PSDHandler(simplehandler.Handler): stability = float(message.src.get_property("n-samples")) / message.src.get_property("average-samples") if stability > 0.3: - print >> sys.stderr, "At GPS time", timestamp, "PSD stable" - template_t = [None] * len(template_bank_table) - autocorr = [None] * len(template_bank_table) - # make templates, whiten, put into firbank - for i, row in enumerate(template_bank_table): - # linearly polarized, so just use plus mode time series - template_t[i], _ = lalsimulation.GenerateStringCusp(1.0,row.central_freq,1.0/options.sample_rate) - # zero-pad it to 32 seconds to obtain same deltaF as the PSD - template_t[i] = lal.ResizeREAL8TimeSeries(template_t[i],-int(32*options.sample_rate - template_t[i].data.length)//2,int(32*options.sample_rate)) - # setup of frequency domain - length = template_t[i].data.length - duration = float(length) / options.sample_rate - epoch = -(length-1) // 2 /options.sample_rate - template_f = lal.CreateCOMPLEX16FrequencySeries("template_freq", LIGOTimeGPS(epoch), psd.f0, 1.0/duration, lal.Unit("strain s"), length // 2 + 1) - fplan = lal.CreateForwardREAL8FFTPlan(length,0) - # FFT to frequency domain - lal.REAL8TimeFreqFFT(template_f,template_t[i],fplan) - # set DC and Nyquist to zero - template_f.data.data[0] = 0.0 - template_f.data.data[template_f.data.length-1] = 0.0 - # whiten - template_f = lal.WhitenCOMPLEX16FrequencySeries(template_f,psd) - # obtain autocorr time series by squaring template and inverse FFT it - template_f_squared = lal.CreateCOMPLEX16FrequencySeries("whitened template_freq squared", LIGOTimeGPS(epoch), psd.f0, 1.0/duration, lal.Unit("s"), length // 2 + 1) - autocorr_t = lal.CreateREAL8TimeSeries("autocorr_time", LIGOTimeGPS(epoch), psd.f0, 1.0 / options.sample_rate, lal.Unit("strain"), length) - rplan = lal.CreateReverseREAL8FFTPlan(length,0) - template_f_squared.data.data = abs(template_f.data.data)**2 - lal.REAL8FreqTimeFFT(autocorr_t,template_f_squared,rplan) - # normalize autocorrelation by central (maximum) value - autocorr_t.data.data /= numpy.max(autocorr_t.data.data) - autocorr_t = autocorr_t.data.data - max_index = numpy.argmax(autocorr_t) - # find the index of the third extremum for the template with lowest high-f cutoff. - # we do this because we know that the template with the lowest high-f cutoff will have - # the largest chi2_index. - if i == 0: - extr_ctr = 0 - chi2_index = 0 - for j in range(max_index+1, len(autocorr_t)): - slope1 = autocorr_t[j+1] - autocorr_t[j] - slope0 = autocorr_t[j] - autocorr_t[j-1] - chi2_index += 1 - if(slope1 * slope0 < 0): - extr_ctr += 1 - if(extr_ctr == 2): - break - assert extr_ctr == 2, 'could not find 3rd extremum' - # extract the part within the third extremum, setting the peak to be the center. - autocorr[i] = numpy.concatenate((autocorr_t[1:(chi2_index+1)][::-1], autocorr_t[:(chi2_index+1)])) - assert len(autocorr[i])%2==1, 'autocorr must have odd number of samples' - # Inverse FFT template bank back to time domain - template_t[i] = lal.CreateREAL8TimeSeries("whitened template time", LIGOTimeGPS(epoch), psd.f0, 1.0 / options.sample_rate, lal.Unit("strain"), length) - lal.REAL8FreqTimeFFT(template_t[i],template_f,rplan) - # normalize - template_t[i].data.data /= numpy.sqrt(numpy.dot(template_t[i].data.data, template_t[i].data.data)) - template_t[i] = template_t[i].data.data - firbank.set_property("latency", -(len(template_t[0]) - 1) // 2) - firbank.set_property("fir_matrix", template_t) - triggergen.set_property("autocorrelation_matrix", autocorr) - self.firbank = firbank - self.triggergen = triggergen + if self.update_psd > 0: + # do nothing, just decrease the counter + self.update_psd -= 1 + else: + # PSD counter reached zero + print >> sys.stderr, "At GPS time", timestamp, "updating PSD" + # NOTE this initialization determines how often the PSD gets updated. This should be given by the user, or we can think of other fancier updates. + self.update_psd = 10 + template_t = [None] * len(self.template_bank) + autocorr = [None] * len(self.template_bank) + # make templates, whiten, put into firbank + # FIXME Currently works only for cusps. this for-loop needs to be revisited when searching for other sources (kinks, ...) + for i, row in enumerate(self.template_bank): + # Obtain cusp waveform. A cusp signal is linearly polarized, so just use plus mode time series + template_t[i], _ = lalsimulation.GenerateStringCusp(1.0,row.central_freq,1.0/options.sample_rate) + # zero-pad it to 32 seconds to obtain same deltaF as the PSD + template_t[i] = lal.ResizeREAL8TimeSeries(template_t[i],-int(32*options.sample_rate - template_t[i].data.length)//2,int(32*options.sample_rate)) + # setup of frequency domain + length = template_t[i].data.length + duration = float(length) / options.sample_rate + epoch = -(length-1) // 2 / options.sample_rate + template_f = lal.CreateCOMPLEX16FrequencySeries("template_freq", LIGOTimeGPS(epoch), psd.f0, 1.0/duration, lal.Unit("strain s"), length // 2 + 1) + fplan = lal.CreateForwardREAL8FFTPlan(length,0) + # FFT to frequency domain + lal.REAL8TimeFreqFFT(template_f,template_t[i],fplan) + # set DC and Nyquist to zero + template_f.data.data[0] = 0.0 + template_f.data.data[template_f.data.length-1] = 0.0 + # whiten + assert template_f.deltaF == psd.deltaF, "freq interval not same between template and PSD" + template_f = lal.WhitenCOMPLEX16FrequencySeries(template_f,psd) + # Obtain the normalization for getting the amplitude of signal from SNR + # Integrate over frequency range covered by template. Note that template_f is already whitened. + sigmasq = 0.0 + sigmasq = numpy.trapz(4.0 * template_f.data.data**2, dx = psd.deltaF) + self.sigma[row.central_freq] = numpy.sqrt(sigmasq.real) + # obtain autocorr time series by squaring template and inverse FFT it + template_f_squared = lal.CreateCOMPLEX16FrequencySeries("whitened template_freq squared", LIGOTimeGPS(epoch), psd.f0, 1.0/duration, lal.Unit("s"), length // 2 + 1) + autocorr_t = lal.CreateREAL8TimeSeries("autocorr_time", LIGOTimeGPS(epoch), psd.f0, 1.0 / options.sample_rate, lal.Unit("strain"), length) + rplan = lal.CreateReverseREAL8FFTPlan(length,0) + template_f_squared.data.data = abs(template_f.data.data)**2 + lal.REAL8FreqTimeFFT(autocorr_t,template_f_squared,rplan) + # normalize autocorrelation by central (maximum) value + autocorr_t.data.data /= numpy.max(autocorr_t.data.data) + autocorr_t = autocorr_t.data.data + max_index = numpy.argmax(autocorr_t) + # find the index of the third extremum for the template with lowest high-f cutoff. + # we don't want to do this for all templates, because we know that + # the template with the lowest high-f cutoff will have the largest chi2_index. + if i == 0: + extr_ctr = 0 + chi2_index = 0 + for j in range(max_index+1, len(autocorr_t)): + slope1 = autocorr_t[j+1] - autocorr_t[j] + slope0 = autocorr_t[j] - autocorr_t[j-1] + chi2_index += 1 + if(slope1 * slope0 < 0): + extr_ctr += 1 + if(extr_ctr == 2): + break + assert extr_ctr == 2, 'could not find 3rd extremum' + # extract the part within the third extremum, setting the peak to be the center. + autocorr[i] = numpy.concatenate((autocorr_t[1:(chi2_index+1)][::-1], autocorr_t[:(chi2_index+1)])) + assert len(autocorr[i])%2==1, 'autocorr must have odd number of samples' + # Inverse FFT template bank back to time domain + template_t[i] = lal.CreateREAL8TimeSeries("whitened template time", LIGOTimeGPS(epoch), psd.f0, 1.0 / options.sample_rate, lal.Unit("strain"), length) + lal.REAL8FreqTimeFFT(template_t[i],template_f,rplan) + # normalize + template_t[i].data.data /= numpy.sqrt(numpy.dot(template_t[i].data.data, template_t[i].data.data)) + template_t[i] = template_t[i].data.data + firbank.set_property("latency", -(len(template_t[0]) - 1) // 2) + firbank.set_property("fir_matrix", template_t) + triggergen.set_property("autocorrelation_matrix", autocorr) + self.firbank = firbank + self.triggergen = triggergen else: # use templates with all zeros during burn-in period, that way we won't get any triggers. print >> sys.stderr, "At GPS time", timestamp, "burn in period" - template = [None] * len(template_bank_table) - autocorr = [None] * len(template_bank_table) - for i, row in enumerate(template_bank_table): + template = [None] * len(self.template_bank) + autocorr = [None] * len(self.template_bank) + for i, row in enumerate(self.template_bank): template[i], _ = lalsimulation.GenerateStringCusp(1.0,30,1.0/options.sample_rate) template[i] = lal.ResizeREAL8TimeSeries(template[i], -int(32*options.sample_rate - template[i].data.length)//2 ,int(32*options.sample_rate)) template[i] = template[i].data.data @@ -252,6 +290,23 @@ process = ligolw_process.register_to_xmldoc(xmldoc, "StringSearch", options.__di sngl_burst_table = lsctables.New(lsctables.SnglBurstTable, ["process:process_id", "event_id","ifo","search","channel","start_time","start_time_ns","peak_time","peak_time_ns","duration","central_freq","bandwidth","amplitude","snr","confidence","chisq","chisq_dof"]) xmldoc.childNodes[-1].appendChild(sngl_burst_table) +# +# append search_summary table +# nevents will be filled out later +# + +search_summary_table = lsctables.New(lsctables.SearchSummaryTable, ["process:process_id", "comment", "ifos", "in_start_time", "in_start_time_ns", "in_end_time", "in_end_time_ns", "out_start_time", "out_start_time_ns", "out_end_time", "out_end_time_ns", "nevents", "nnodes"]) +xmldoc.childNodes[-1].appendChild(search_summary_table) +search_summary = lsctables.SearchSummary() +search_summary.process_id = process.process_id +if options.user_tag: + search_summary.comment = options.user_tag +search_summary.ifos = template_bank_table[0].ifo +search_summary.out_start = search_summary.in_start = LIGOTimeGPS(options.gps_start_time) +search_summary.out_end = search_summary.in_end = LIGOTimeGPS(options.gps_end_time) +search_summary.nnodes = 1 +search_summary.nevents = 0 + # # trigger generator @@ -260,26 +315,15 @@ xmldoc.childNodes[-1].appendChild(sngl_burst_table) head = triggergen = pipeparts.mkgeneric(pipeline, head, "lal_string_triggergen", threshold = options.threshold, cluster = options.cluster_events, bank_filename = options.template_bank, autocorrelation_matrix = numpy.zeros((len(template_bank_table), 403),dtype=numpy.float64)) +mainloop = GObject.MainLoop() +handler = PipelineHandler(mainloop, pipeline, template_bank_table, firbank) + + # # appsync # -def appsink_new_buffer(elem): - buf = elem.emit("pull-sample").get_buffer() - events = [] - for i in range(buf.n_memory()): - memory = buf.peek_memory(i) - result, mapinfo = memory.map(Gst.MapFlags.READ) - assert result - if mapinfo.data: - events.extend(snglbursttable.GSTLALSnglBurst.from_buffer(mapinfo.data)) - memory.unmap(mapinfo) - for event in events: - event.process_id = process.process_id - event.event_id = sngl_burst_table.get_next_id() - sngl_burst_table.append(event) - -appsync = pipeparts.AppSync(appsink_new_buffer = appsink_new_buffer) +appsync = pipeparts.AppSync(appsink_new_buffer = handler.appsink_new_buffer) appsync.add_sink(pipeline, head, caps = Gst.Caps.from_string("application/x-lal-snglburst")) @@ -295,18 +339,15 @@ options.gps_start_time = LIGOTimeGPS(options.gps_start_time) options.gps_end_time = LIGOTimeGPS(options.gps_end_time) datasource.pipeline_seek_for_gps(pipeline, options.gps_start_time, options.gps_end_time); - if pipeline.set_state(Gst.State.PLAYING) != Gst.StateChangeReturn.SUCCESS: raise RuntimeError("pipeline did not enter playing state") -mainloop = GObject.MainLoop() -handler = PSDHandler(mainloop, pipeline, firbank) mainloop.run() - # # write output to disk # +search_summary_table.append(search_summary) ligolw_utils.write_filename(xmldoc, options.output, gz = (options.output or "stdout").endswith(".gz"), verbose = options.verbose)