Raw File
pycbc_plot_singles_timefreq
#!/usr/bin/env python

# Copyright (C) 2015 Tito Dal Canton
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

"""
Plot single-detector inspiral triggers in the time-frequency plane along with
a spectrogram of the strain data.
"""

import sys
import logging
import argparse
import numpy as np
import matplotlib
matplotlib.use('agg')
from six.moves import range
import pylab as pl
import matplotlib.mlab as mlab
from matplotlib.colors import LogNorm
from matplotlib.ticker import LogLocator
from pycbc.io import HFile
import pycbc.events
import pycbc.pnutils
import pycbc.strain
import pycbc.results
import pycbc.version
import pycbc.waveform


parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--version", action="version",
                    version=pycbc.version.git_verbose_msg)
parser.add_argument('--trig-file', required=True,
                    help='HDF5 file containing single triggers')
parser.add_argument('--output-file', required=True, help='Output plot')
parser.add_argument('--bank-file', required=True,
                    help='HDF5 file containing template bank')
parser.add_argument('--veto-file', help='LIGOLW file containing veto segments')
parser.add_argument('--f-low', type=float, default=30,
                    help='Low-frequency cutoff')
parser.add_argument('--rank', choices=['snr', 'newsnr'], default='newsnr',
                    help='Ranking statistic for sorting triggers')
parser.add_argument('--num-loudest', type=int, default=1000,
                    help='Number of loudest triggers to plot')
parser.add_argument('--interesting-trig', type=int,
                    help='Index of interesting trigger to highlight')
parser.add_argument('--detector', type=str, required=True)
parser.add_argument('--center-time', type=float,
                    help='Center plot on the given GPS time')
# add the approximant argument
pycbc.waveform.bank.add_approximant_arg(parser,
                    help='Waveform model used for computing inspiral tracks.'
                         ' Python expressions can be used to specify a '
                         'parameter-dependent approximant as done in '
                         'pycbc_inspiral. Default is to use the approximant '
                         'specified in the bank file.')
pycbc.strain.insert_strain_option_group(parser)
opts = parser.parse_args()

logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)


if opts.center_time is None:
    center_time = (opts.gps_start_time + opts.gps_end_time) / 2.
else:
    center_time = opts.center_time

fig = pl.figure(figsize=(11,5.5))
fig.subplots_adjust(left=0.06, right=0.95, bottom=0.09, top=0.95)
ax = fig.gca()

if center_time == -1.0:
    title = '%s - no triggers' % opts.detector
    ax.set_xlim(opts.gps_start_time - center_time, opts.gps_end_time - center_time)
    ax.set_ylim(opts.f_low, opts.sample_rate / 2)
    ax.set_yscale('log')
    ax.grid(ls='solid', alpha=0.2)
    ax.set_xlabel('Time - %.3f (s)' % center_time)
    ax.set_ylabel('Frequency (Hz)')
    ax.set_title(title)
    caption = ("This plot shows the power spectrogram of the strain data, "
               "normalized to its median over time, as a heatmap. The "
               "time-frequency evolution of each single trigger is shown as a blue "
               "curve. The light-blue curve is the loudest trigger by %s. Only the "
               "loudest %d triggers by %s are shown. Green hatched areas denote "
               "gated strain data. Red hatched areas denote vetoed time.") % \
    (opts.rank, opts.num_loudest, opts.rank)
    pycbc.results.save_fig_with_metadata(
        fig, opts.output_file, cmd=' '.join(sys.argv),
        title='Strain spectrogram and inspiral tracks for %s' % opts.detector,
        caption=caption, fig_kwds={'dpi': 100})

    logging.info('Done')
    sys.exit(0)

opts.low_frequency_cutoff = opts.f_low
strain = pycbc.strain.from_cli(opts, pycbc.DYN_RANGE_FAC)
logging.info('Plotting strain spectrogram')
Pxx, freq, t = mlab.specgram(strain, NFFT=1024, noverlap=1000,
                             Fs=opts.sample_rate, mode='psd')
del strain
median_psd = np.median(Pxx, axis=1)
median_psd_tile = np.tile(np.array([median_psd]).T, (1, len(t)))
del median_psd
Pxx /= median_psd_tile
del median_psd_tile
norm = LogNorm()
pc = ax.pcolormesh(t + opts.gps_start_time - center_time, freq, Pxx,
                   vmin=1, vmax=1000, norm=norm, cmap='afmhot_r')
del freq, Pxx

logging.info('Loading trigs')
trig_f = HFile(opts.trig_file, 'r')
trigs = trig_f[opts.detector]

def rough_filter(snr, chisq, chisq_dof, end_time, tmp_id, tmp_dur):
    return np.logical_and(end_time > opts.gps_start_time,
                          end_time < opts.gps_end_time + tmp_dur)

indices, snr, chisq, chisq_dof, end_time, template_ids, template_duration = \
        trig_f.select(rough_filter, opts.detector + '/snr',
                      opts.detector + '/chisq', opts.detector + '/chisq_dof',
                      opts.detector + '/end_time',
                      opts.detector + '/template_id',
                      opts.detector + '/template_duration',
                      return_indices=True)

if len(indices) > 0:
    if opts.veto_file:
        logging.info('Loading veto segments')
        locs, _ = pycbc.events.veto.indices_outside_segments(
            end_time, [opts.veto_file], ifo=opts.detector)
        end_time = end_time[locs]
        snr = snr[locs]
        chisq = chisq[locs]
        chisq_dof = chisq_dof[locs]
        template_ids = template_ids[locs]
        indices = indices[locs]
        del locs

    if opts.rank == 'snr':
        rank = snr
    elif opts.rank == 'newsnr':
        rchisq = chisq / (chisq_dof * 2 - 2)
        rank = pycbc.events.ranking.newsnr(snr, rchisq)
        if type(rank) in [np.float32, np.float64]:
            rank = np.array([rank])
        del rchisq
    del snr, chisq, chisq_dof

    sorter = np.argsort(rank)[::-1][:opts.num_loudest]
    sorted_end_time = end_time[sorter]
    sorted_rank = rank[sorter]
    sorted_template_ids = template_ids[sorter]

    try:
        max_rank = max([sorted_rank[i] for i in range(len(sorted_rank)) \
                        if sorted_end_time[i] <= opts.gps_end_time])
    except ValueError:
        max_rank = None

    logging.info('Loading bank')
    bank = pycbc.waveform.bank.TemplateBank(opts.bank_file,
            approximant=opts.approximant,
            parameters=['mass1', 'mass2', 'spin1z', 'spin2z', 'approximant'],
            load_compressed=False)
    tmplts = bank.table

    logging.info('Plotting %d trigs', len(sorted_end_time))
    for tc, rho, tid in zip(sorted_end_time, sorted_rank, sorted_template_ids):
        track_t, track_f = pycbc.pnutils.get_inspiral_tf(
                    tc - center_time, tmplts.mass1[tid], tmplts.mass2[tid],
                    tmplts.spin1z[tid], tmplts.spin2z[tid],
                    opts.f_low, approximant=bank.approximant(tid))
        if max_rank and rho == max_rank:
            ax.plot(track_t, track_f, '-', color='#0080ff', zorder=3, lw=2)
        else:
            ax.plot(track_t, track_f, '-', color='#0000ff', zorder=2, lw=0.5, alpha=0.5)

    if opts.interesting_trig is not None and opts.interesting_trig in indices:
        interesting_id = np.where(indices == opts.interesting_trig)[0]
        tc = end_time[interesting_id]
        rho = rank[interesting_id]
        interesting_trig_rank = rho
        tid = template_ids[interesting_id]
        track_t, track_f = pycbc.pnutils.get_inspiral_tf(
            tc - center_time, tmplts.mass1[tid], tmplts.mass2[tid],
            tmplts.spin1z[tid], tmplts.spin2z[tid],
            opts.f_low, approximant=bank.approximant(tid))
        ax.plot(track_t, track_f, '-', color='#00f000', zorder=3, lw=2)
    else:
        interesting_trig_rank = None

    if max_rank:
        title = '%s - loudest %d triggers by %s - max %s = %.2f (light blue curve)' \
        % (opts.channel_name, opts.num_loudest, opts.rank, opts.rank, max_rank)
    else:
        title = '%s - loudest %d triggers by %s' \
        % (opts.channel_name, opts.num_loudest, opts.rank)
    if interesting_trig_rank is not None:
        title += ' - selected %s = %.2f (green curve)' \
        % (opts.rank, interesting_trig_rank)
else:
    title = '%s - no triggers' % opts.channel_name

logging.info('Loading and plotting gates')
for gate_type, hatch_style in [('file', '\\'), ('auto', '/')]:
    try:
        gate_time = trigs['gating/' + gate_type + '/time'][:]
        gate_width = trigs['gating/' + gate_type + '/width'][:]
        gate_pad = trigs['gating/' + gate_type + '/pad'][:]
    except KeyError:
        continue
    gate_unique = list(frozenset(zip(gate_time, gate_width, gate_pad)))
    for gt, gw, gp in gate_unique:
        # only plot gates within the plot time window
        if gt + gw < opts.gps_start_time or gt - gw > opts.gps_end_time:
            continue
        ax.axvspan(gt - gw - center_time, gt + gw - center_time,
                   hatch=hatch_style, facecolor='none', edgecolor='#00ff00')

if opts.veto_file:
    logging.info('Loading and plotting veto segments')
    veto_segs = pycbc.events.veto.select_segments_by_definer(
        opts.veto_file, ifo=opts.detector)
    veto_segs.coalesce()
    for seg in veto_segs:
        if seg[0] > opts.gps_end_time or seg[1] < opts.gps_start_time:
            continue
        ax.axvspan(seg[0] - center_time, seg[1] - center_time,
                   hatch='x', facecolor='none', edgecolor='#ff0000')

ax.set_xlim(opts.gps_start_time - center_time, opts.gps_end_time - center_time)
ax.set_ylim(opts.f_low, opts.sample_rate / 2)
ax.set_yscale('log')
ax.grid(ls='solid', alpha=0.2)
ax.set_xlabel('Time - %.3f (s)' % center_time)
ax.set_ylabel('Frequency (Hz)')
ax.set_title(title)
cb = fig.colorbar(pc, fraction=0.04, pad=0.01,
                  ticks=LogLocator(subs=range(10)))
cb.set_label('Power density (normalized to its median over time)')

caption = ("This plot shows the power spectrogram of the strain data, "
           "normalized to its median over time, as a heatmap. The "
           "time-frequency evolution of each single trigger is shown as a blue "
           "curve. The light-blue curve is the loudest trigger by %s. Only the "
           "loudest %d triggers by %s are shown. Green hatched areas denote "
           "gated strain data. Red hatched areas denote vetoed time.") % \
        (opts.rank, opts.num_loudest, opts.rank)
pycbc.results.save_fig_with_metadata(
    fig, opts.output_file, cmd=' '.join(sys.argv),
    title='Strain spectrogram and inspiral tracks for %s' % opts.detector,
    caption=caption, fig_kwds={'dpi': 100})

logging.info('Done')
back to top