diff --git a/gstlal-inspiral/bin/gstlal_inspiral_calc_snr b/gstlal-inspiral/bin/gstlal_inspiral_calc_snr index 6c10f65d7e9310a777255e979c105ddf99b910b8..194b46a1c1d637502cc11d4c49a6df9a6c8c76b8 100755 --- a/gstlal-inspiral/bin/gstlal_inspiral_calc_snr +++ b/gstlal-inspiral/bin/gstlal_inspiral_calc_snr @@ -67,6 +67,7 @@ from optparse import OptionParser, OptionGroup, IndentedHelpFormatter import os from gstlal import datasource +from gstlal import inspiral from gstlal import pipeparts from gstlal import reference_psd from gstlal import svd_bank @@ -119,7 +120,7 @@ def parse_command_line(): parser.add_option_group(group) group = OptionGroup(parser, "Template Options", "Choose a template from a SVD bank file / a single SnglInspiral Table") - group.add_option("--svd-bank", metavar = "filename", help = "A LIGO light-weight xml / xml.gz file containing svd bank information (require)." ) + group.add_option("--svd-bank", metavar = "filename", help = "A LIGO light-weight xml / xml.gz file containing svd bank information. These can be given as a comma separated list such as H1:file1,H2:file2,L1:file3 to analyze multiple instruments (require)." ) group.add_option("--sub-bank-id", type = "int", help = "Bank id is of the form <int>ID_<int>N where N is the sub bank id. (require).") group.add_option("--row-number", type = "int", help = "The row number of the template (optional). All the SNRs will be outputed if it is not given.") group.add_option("--table", metavar = "filename", help = "A LIGO light-weight xml.gz file containing SnglInspiral Table. Expecting one template for each instrument only.") @@ -138,19 +139,20 @@ def parse_command_line(): group = OptionGroup(parser, "GraceDb Event Options", "Produce SNR time series for gstlal gracedb event.") group.add_option("--gid", metavar = "gracedb event id", type = "str", help = "The gracedb event id.") - group.add_option("--observatory", metavar = "OBS", type = "str", help = "See gwdatafind.") - group.add_option("--type", metavar = "frame type", type = "str", help = "See gwdatafind.") + group.add_option("--observatory", metavar = "OBS", type = "str", action = "append", help = "See gwdatafind.") + group.add_option("--type", metavar = "frame type", type = "str", action = "append", help = "See gwdatafind.") group.add_option("--time-span", metavar = "seconds", type = "int", default = 1000, help = "The time span around the event's trigger time (default = 1000).") parser.add_option_group(group) group = OptionGroup(parser, "Output Control Options", "Control SNR output") - group.add_option("--outdir", metavar = "directory", type = "str", help = "Output directory for SNR(s) (require).") + group.add_option("--outdir", metavar = "directory", default = ".", type = "str", help = "Output directory for SNR(s) (default = .).") + group.add_option("--save", action = "store_true", default = False, help = "Save frame cache / svd bank / psd (default = True).") group.add_option("--mode", metavar = "method", type = "int", default = 0, help = "The method (0 = LLOID / 1 = FIR) that is used to calculate SNR (default = 0).") group.add_option("--complex", action = "store_true", help = "Choose whether to output the complex snr or not.") group.add_option("--start", metavar = "seconds", type = "float", help = "Start SNR time series at GPS time '--start' (require).") group.add_option("--end", metavar = "seconds", type = "float", help = "End SNR time series at GPS time '--end' (require).") group.add_option("--output-width", metavar = "bits", type = "int", default = 32, help = "The size of the output data, can only be 32 or 64 bits (default = 32 bits).") - group.add_option("--instrument", metavar = "name", help = "The detector from which the --reference-psd and --frame-cache are loaded (require).") + group.add_option("--instrument", metavar = "name", type = "str", action = "append", help = "The detector from which the --reference-psd and --frame-cache are loaded (require).") parser.add_option_group(group) parser.add_option("--verbose", action = "store_true", help = "Be verbsoe.") @@ -160,42 +162,52 @@ def parse_command_line(): # initialize gw_data_source_info gw_data_source_info = None + if options.outdir is None: + missing_required_options.append("--outdir") # Setting up GW data if options.gid is not None: - # Setting up files for GraceDb event + # Setting up inputs for GraceDb event, this will overwrite svd-bank/template/psd related inputs. if options.observatory is None or options.type is None: raise ValueError("When using --gid, --observatory and --type must be provided.") else: - trigger_time, gps_start_time, gps_end_time, channel_name = svd_bank_snr.gwdata_from_event(options.gid, options.observatory, options.type, time_span = options.time_span, outdir = options.outdir, verbose = options.verbose) + gwdata_metavars = svd_bank_snr.framecache_from_event(options.gid, options.observatory, options.type, time_span = options.time_span, outdir = options.outdir, verbose = options.verbose) # We can hardcoded here, since we know all the information from gracedb. - # This assume everything on the gracedb are correct and complete which could go wrong in future. + # This assume everything on the gracedb are correct and complete which could go wrong in the future. + # (e.g files are deleted) options.data_source = "frames" options.frame_cache = os.path.join(options.outdir, "frame.cache") - options.gps_start_time = gps_start_time - options.gps_end_time = gps_end_time - options.channel_name = [channel_name] + options.gps_start_time = gwdata_metavars["gps_start_time"][0] + options.gps_end_time = gwdata_metavars["gps_end_time"][0] + options.channel_name = gwdata_metavars["channels_name"] + options.instrument = gwdata_metavars["instruments"] gw_data_source_info = datasource.GWDataSourceInfo(options) - # FIXME: This could change in the future - obs2ifo = {"H": "H1", "L": "L1", "V": "V1"} - options.instrument = obs2ifo[options.observatory] - # FIXME: Adjustable parameters, hardcoded here for simplicity. + trigger_time = min(gwdata_metavars["trigger_times"]) options.start = trigger_time - 5 options.end = trigger_time + 5 - psd = svd_bank_snr.psd_from_event(options.gid) + # It must be using LLOID + options.mode = 0 - banks_dict, options.sub_bank_id, options.row_number = svd_bank_snr.svd_banks_from_event(options.gid, options.verbose) - bank = banks_dict[options.instrument][options.sub_bank_id] + psds_dict = svd_bank_snr.psd_from_event(options.gid, save = options.save, verbose = options.verbose) + banks_dict, options.sub_bank_id, options.row_number = svd_bank_snr.svd_banks_from_event(options.gid, save = options.save, verbose = options.verbose) - return options, gw_data_source_info, bank, psd + return options, gw_data_source_info, banks_dict, psds_dict else: # Setting up GW data for non-GraceDb event gw_data_source_info = datasource.GWDataSourceInfo(options) - if options.instrument not in gw_data_source_info.channel_dict.keys(): - raise ValueError("No such instrument: %s in GWDataSourceInfo: (%s)"% (options.instrument, ", ".join(gw_data_source_info.channel_dict.keys()))) + # Setting up how many instruments to analyze + if options.instrument is not None: + # Avoid repeated --instrument + options.instrument = list(set(options.instrument)) + else: + raise ValueError("Must provide at least one --instrument.") + # Check if --frame-cache contains the data from --instrument + for instrument in options.instrument: + if instrument not in gw_data_source_info.channel_dict.keys(): + raise ValueError("No such instrument: %s in GWDataSourceInfo: (%s)"% (instrument, ", ".join(gw_data_source_info.channel_dict.keys()))) # Check SNRs series output if options.start is None or options.end is None: @@ -210,8 +222,10 @@ def parse_command_line(): # Setting up PSD if options.reference_psd: psd = lal.series.read_psd_xmldoc(ligolw_utils.load_url(options.reference_psd, contenthandler = lal.series.PSDContentHandler)) - if options.instrument not in set(psd): - raise ValueError("No such instrument: %s in psd: (%s)"% (options.instrument, ", ".join(set(psd)))) + # Check if --reference-psd contains the psd from --instrument + for instrument in options.instrument: + if instrument not in set(psd): + raise ValueError("No such instrument: %s in psd: (%s)"% (instrument, ", ".join(set(psd)))) else: options.track_psd = True @@ -223,27 +237,26 @@ def parse_command_line(): missing_required_options.append("--svd-bank") if options.sub_bank_id is None: missing_required_options.append("--sub-bank-id") - if options.outdir is None: - missing_required_options.append("--outdir") - if options.instrument is None: - missing_required_options.append("--instrument") # Raise VauleError is required option(s) is/are missing if missing_required_options: raise ValueError("Missing required option(s) %s" % ", ".join(sorted(missing_required_options))) # Setting up SVD bank - banks = svd_bank.read_banks(options.svd_bank, svd_bank.DefaultContentHandler) - if banks is None: - raise ValueError("svd bank is empty: Invaild --svd-bank %s" % options.svd_bank) - else: - if 0 <= options.sub_bank_id < len(banks) : - bank = banks[options.sub_bank_id] - else: - raise ValueError("Invaild --sub-bank-id %d. Possible id [0-%d)\n" % (options.sub_bank_id, len(banks))) - if not( 0 <= options.row_number < len(bank.sngl_inspiral_table) ): - raise ValueError("No such template: Invaild --row-number %d. Possible range [0-%d)\n" % (options.row_number, len(bank.sngl_inspiral_table))) - - return options, gw_data_source_info, bank, psd + svdbanks_dict = inspiral.parse_svdbank_string(options.svd_bank) + # Check if --svd-bank contains svd banks from --instrument + for instrument in options.instrument: + if instrument not in set(svdbanks_dict): + raise ValueError("No SVD Banks for --instrument=%s." % instrument) + + banks = dict([(ifo, svd_bank.read_banks(svdbanks, svd_bank.DefaultContentHandler)) for ifo, svdbanks in svdbanks_dict.items()]) + # Check if --sub-bank-id and --row-number is valid + for bank in banks.values(): + if not (0 <= options.sub_bank_id < len(bank)) : + raise ValueError("Invaild --sub-bank-id %d. Possible id [0-%d)\n" % (options.sub_bank_id, len(bank))) + if options.row_number is not None and not (0 <= options.row_number < len(bank[options.sub_bank_id].sngl_inspiral_table)): + raise ValueError("No such template: Invaild --row-number %d. Possible range [0-%d)\n" % (options.row_number, len(bank[options.sub_bank_id].sngl_inspiral_table))) + + return options, gw_data_source_info, banks, psd # Use Finite Impulse Response elif options.mode == 1: @@ -253,10 +266,6 @@ def parse_command_line(): missing_required_options.append("--table") if options.approximant is None: missing_required_options.append("--approximant") - if options.outdir is None: - missing_required_options.append("--outdir") - if options.instrument is None: - missing_required_options.append("--instrument") if options.template_psd is None: missing_required_options.append("--template-psd") # Raise VauleError is required option(s) is/are missing @@ -272,7 +281,7 @@ def parse_command_line(): else: raise ValueError("Invalid mode: %d" % options.mode) -options, gw_data_source_info, template, psd = parse_command_line() +options, gw_data_source_info, template, psds_dict = parse_command_line() if options.veto_segments_file is not None: veto_segments = ligolw_segments.segmenttable_get_by_name(ligolw_utils.load_filename(options.veto_segments_file, verbose = options.verbose, contenthandler = LIGOLWContentHandler), options.veto_segments_name).coalesce() @@ -286,64 +295,61 @@ else: #==================================================================================================== if options.mode == 0: - bank = template - num_of_row = bank.bank_fragments[0].mix_matrix.shape[1] / 2 - - lloid_snr = svd_bank_snr.LLOID_SNR( - gw_data_source_info, - bank, - options.instrument, - options.row_number, - options.start, - options.end, - psd = psd, - psd_fft_length = options.psd_fft_length, - ht_gate_threshold = options.ht_gate_threshold, - veto_segments = veto_segments, - track_psd = options.track_psd, - width = options.output_width, - verbose = options.verbose - ) - - if options.row_number is None: - for index, snr in enumerate(lloid_snr(COMPLEX = options.complex)): - snr.epoch += bank.sngl_inspiral_table[index].end - snrdict = {options.instrument: [snr]} - svd_bank_snr.write_url(svd_bank_snr.make_xmldoc(snrdict), os.path.join(options.outdir, "%s-SNR_%d-%d-%d.xml.gz" % (options.instrument, index, int(snr.epoch), int(snr.data.length * snr.deltaT))), verbose = options.verbose) - else: - lloidsnr = lloid_snr(COMPLEX = options.complex) - lloidsnr[0].epoch += bank.sngl_inspiral_table[options.row_number].end - snrdict = {options.instrument: lloidsnr} - svd_bank_snr.write_url(svd_bank_snr.make_xmldoc(snrdict), os.path.join(options.outdir, "%s-SNR_%d-%d-%d.xml.gz" % (options.instrument, options.row_number, int(lloidsnr[0].epoch), int(lloidsnr[0].data.length * lloidsnr[0].deltaT))), verbose = options.verbose) - # - # uncomment to save all snrs in one single XML - # - #snrdict = {options.instrument : lloid_snr(COMPLEX = options.complex)} - #svd_bank_snr.write_url(svd_bank_snr.make_xmldoc(snrdict), os.path.join(options.outdir, "%s-SNR-%d-%d.xml.gz" % (options.instrument, int(snrdict.[options.instrument][0].epoch), int(snrdict[options.instrument][0].data.length * snrdict[options.instrument][0].deltaT))), verbose = options.verbose) + banks_dict = template + + for instrument in options.instrument: + lloid_snr = svd_bank_snr.LLOID_SNR( + gw_data_source_info, + banks_dict[instrument][options.sub_bank_id], + instrument, + options.row_number, + options.start, + options.end, + psd = psds_dict, + psd_fft_length = options.psd_fft_length, + ht_gate_threshold = options.ht_gate_threshold, + veto_segments = veto_segments, + track_psd = options.track_psd, + width = options.output_width, + verbose = options.verbose + ) + snrdict = {} + if options.row_number is None: + snrdict.setdefault(instrument, []) + for index, snr in enumerate(lloid_snr(COMPLEX = options.complex)): + snr.epoch += banks_dict[instrument][options.sub_bank_id].sngl_inspiral_table[index].end + snrdict[instrument].append(snr) + svd_bank_snr.write_url(svd_bank_snr.make_xmldoc(snrdict), os.path.join(options.outdir, "%s-SVD_BANK_SNR_%d-%d-%d.xml.gz" % (instrument, options.sub_bank_id, int(snr.epoch), int(snr.data.length * snr.deltaT))), verbose = options.verbose) + else: + snrdict = {instrument : lloid_snr(COMPLEX = options.complex)} + snrdict[instrument][0].epoch += banks_dict[instrument][options.sub_bank_id].sngl_inspiral_table[options.row_number].end + svd_bank_snr.write_url(svd_bank_snr.make_xmldoc(snrdict), os.path.join(options.outdir, "%s-SVD_BANK_SNR_%d_%d-%d-%d.xml.gz" % (instrument, options.sub_bank_id, options.row_number, int(snrdict[instrument][0].epoch), int(snrdict[instrument][0].data.length * snrdict[instrument][0].deltaT))), verbose = options.verbose) elif options.mode == 1: template_table = template - template, time_offset = svd_bank_snr.FIR_SNR.make_template(template_table, options.template_psd, options.sample_rate, options.approximant, options.instrument, options.f_low, f_high = options.f_high, verbose = options.verbose) - - #FIXME: proper handle for latency - fir_snr = svd_bank_snr.FIR_SNR( - gw_data_source_info, - template, - options.instrument, - options.sample_rate, - 0, - options.start, - options.end, - psd = psd, - psd_fft_length = options.psd_fft_length, - ht_gate_threshold = options.ht_gate_threshold, - veto_segments = veto_segments, - width = options.output_width, - track_psd = options.track_psd, - verbose = options.verbose - ) - firsnr = fir_snr(COMPLEX = options.complex) - firsnr[0].epoch += time_offset - snrdict = {options.instrument : firsnr} - svd_bank_snr.write_url(svd_bank_snr.make_xmldoc(snrdict),os.path.join(options.outdir, "%s-SNR-%d-%d.xml.gz" % (options.instrument, int(snrdict[options.instrument][0].epoch), int(snrdict[options.instrument][0].data.length * snrdict[options.instrument][0].deltaT))), verbose = options.verbose) + for instrument in options.instrument: + template, time_offset = svd_bank_snr.FIR_SNR.make_template(template_table, options.template_psd, options.sample_rate, options.approximant, instrument, options.f_low, f_high = options.f_high, verbose = options.verbose) + + #FIXME: proper handle for latency + fir_snr = svd_bank_snr.FIR_SNR( + gw_data_source_info, + template, + instrument, + options.sample_rate, + 0, + options.start, + options.end, + psd = psds_dict, + psd_fft_length = options.psd_fft_length, + ht_gate_threshold = options.ht_gate_threshold, + veto_segments = veto_segments, + width = options.output_width, + track_psd = options.track_psd, + verbose = options.verbose + ) + + firsnr = fir_snr(COMPLEX = options.complex) + snrdict = {instrument : firsnr} + snrdict[instrument][0].epoch += time_offset + svd_bank_snr.write_url(svd_bank_snr.make_xmldoc(snrdict),os.path.join(options.outdir, "%s-FIR_SNR-%d-%d.xml.gz" % (instrument, int(snrdict[instrument][0].epoch), int(snrdict[instrument][0].data.length * snrdict[instrument][0].deltaT))), verbose = options.verbose) diff --git a/gstlal-inspiral/python/svd_bank_snr.py b/gstlal-inspiral/python/svd_bank_snr.py index cebcabb6f6d7fc40dfe7da6944606d84ea210c63..c47ad1a57bdef69c62f723c840eba642818b1c93 100644 --- a/gstlal-inspiral/python/svd_bank_snr.py +++ b/gstlal-inspiral/python/svd_bank_snr.py @@ -5,6 +5,7 @@ A gstlal-based direct matched filter in time domain is also implemented. import os import sys +import shutil import numpy import threading @@ -273,9 +274,9 @@ class FIR_SNR(SNR_Pipeline): @staticmethod def make_template(template_table, template_psd, sample_rate, approximant, instrument, f_low, f_high = None, autocorrelation_length = None, verbose = False): - row = [row for row in template_table if row.ifo == instrument] + row = [row for row in template_table] if len(row) != 1 : - raise ValueError("Expecting only one template for instrument=%s or cannot find template for instrument=%s" % (instrument, instrument)) + raise ValueError("Expecting only one template or cannot find any template.") template_psd = lal.series.read_psd_xmldoc(ligolw_utils.load_url(template_psd, contenthandler = lal.series.PSDContentHandler)) if instrument not in set(template_psd): @@ -363,7 +364,7 @@ def read_url(filename, contenthandler = SNRContentHandler, verbose = False): # #============================================================================================= -def svd_banks_from_event(gid, verbose = False): +def svd_banks_from_event(gid, outdir = ".", save = True, verbose = False): gracedb_client = gracedb.GraceDb() coinc_xmldoc = lvalert_helper.get_coinc_xmldoc(gracedb_client, gid) eventid_trigger_dict = dict((row.event_id, row) for row in lsctables.SnglInspiralTable.get_table(coinc_xmldoc)) @@ -377,6 +378,17 @@ def svd_banks_from_event(gid, verbose = False): sys.stderr.write("Files Not Found! Make sure you are on the LIGO-Caltech Computing Cluster or check if file exist.\nAbortting...\n") sys.exit() + if save: + try: + for bank_url in bank_urls.values(): + outname =os.path.join(outdir, os.path.basename(bank_url)) + if verbose: + sys.stderr.write("saving SVD bank file to %s ...\n" % outname) + shutil.copyfile(bank_url, outname) + # FIXME: in python > 2.7, OSError will be raised if destination is not writable. + except IOError as e: + raise e + # Just get one of the template bank from any instrument, # the templates should all have the same template_id because they are exact-matched. sub_bank_id = None @@ -391,21 +403,35 @@ def svd_banks_from_event(gid, verbose = False): return banks_dict, sub_bank_id, row_number -def gwdata_from_event(gid, observatory, frame_type, time_span = 1000, outdir = ".", verbose = False): +def framecache_from_event(gid, observatories, frame_types, time_span = 1000, outdir = ".", filename = "frame.cache", verbose = False): assert time_span >= 1000., "Please use time_span larger or equal to 1000." obs2ifo = {"H": "H1", "L": "L1", "V": "V1"} + observatories = set(observatories) + frame_types = set(frame_types) + + if len(observatories) != len(frame_types): + raise ValueError("Must have as many frame_types as observatories.") + # FIXME: This is not reliable, have a better way to map frame_type to observatory? + obs_type_dict = dict([(obs, frame_type) for obs in observatories for frame_type in frame_types if obs == frame_type[0]]) + gracedb_client = gracedb.GraceDb() coinc_xmldoc = lvalert_helper.get_coinc_xmldoc(gracedb_client, gid) eventid_trigger_dict = dict((row.ifo, row) for row in lsctables.SnglInspiralTable.get_table(coinc_xmldoc)) channel_names_dict = dict([(row.value.split("=")[0], row.value) for row in lsctables.ProcessParamsTable.get_table(coinc_xmldoc) if row.param == "--channel-name"]) - trigger_time = eventid_trigger_dict[obs2ifo[observatory]].end - gps_start_time = int(trigger_time - time_span) - gps_end_time = int(trigger_time + time_span) + gwdata_metavar_headers = ["instruments", "trigger_times", "gps_start_time", "gps_end_time", "channels_name"] + gwdata_metavar_values = [] + urls = [] + for observatory, frame_type in obs_type_dict.items(): + trigger_time = eventid_trigger_dict[obs2ifo[observatory]].end + gps_start_time = int(trigger_time - time_span) + gps_end_time = int(trigger_time + time_span) + gwdata_metavar_values.append((obs2ifo[observatory], trigger_time, gps_start_time, gps_end_time, channel_names_dict[obs2ifo[observatory]])) + + urls += gwdatafind.find_urls(observatory, frame_type, gps_start_time, gps_end_time) - urls = gwdatafind.find_urls(observatory, frame_type, gps_start_time, gps_end_time) with open(os.path.join(outdir, "frame.cache"), "w") as cache: for url in urls: filename = str(CacheEntry.from_T050017(url)) @@ -415,9 +441,14 @@ def gwdata_from_event(gid, observatory, frame_type, time_span = 1000, outdir = " if verbose: sys.stderr.write("Done.\n") - return trigger_time, gps_start_time, gps_end_time, channel_names_dict[obs2ifo[observatory]] + return dict(zip(gwdata_metavar_headers, zip(*gwdata_metavar_values))) -def psd_from_event(gid, filename = "psd.xml.gz"): +def psd_from_event(gid, outdir = ".", save = True, filename = "psd.xml.gz", verbose = False): gracedb_client = gracedb.GraceDb() psd_fileobj = lvalert_helper.get_filename(gracedb_client, gid, filename) - return lal.series.read_psd_xmldoc(ligolw_utils.load_fileobj(psd_fileobj, contenthandler = lal.series.PSDContentHandler)) + xmldoc = ligolw_utils.load_fileobj(psd_fileobj, contenthandler = lal.series.PSDContentHandler) + if save: + if verbose: + sys.stderr.write("saving psd file to %s ...\n" % os.path.join(outdir, filename)) + ligolw_utils.write_filename(xmldoc, filename, gz = filename.endswith("gz")) + return lal.series.read_psd_xmldoc(xmldoc)