gstlal  0.8.1
 All Classes Namespaces Files Functions Variables Pages
plotpsd.py
Go to the documentation of this file.
1 # Copyright (C) 2013 Kipp Cannon
2 # Copyright (C) 2015 Chad Hanna
3 #
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License as published by the
6 # Free Software Foundation; either version 2 of the License, or (at your
7 # option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12 # Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 #
18 
19 ##
20 # @file
21 #
22 # A file that contains the psd plotting module code
23 #
24 # ### Review Status
25 #
26 ##
27 # @package python.plotpsd
28 #
29 # psd plotting module
30 #
31 
32 
33 import logging
34 import math
35 import matplotlib
36 from matplotlib import figure
37 from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
38 import numpy
39 from gstlal.plotutil import golden_ratio
40 from glue.ligolw import lsctables
41 from gstlal.reference_psd import horizon_distance
42 
43 def plot_psds(psds, coinc_xmldoc = None, plot_width = 640, colours = {"H1": "r", "H2": "b", "L1": "g", "V1": "m"}):
44  """!
45  Produces a matplotlib figure of PSDs.
46 
47  @param psds A dictionary of PSDs as REAL8FrequencySeries keyed by
48  instrument
49 
50  @param coinc_xmldoc An XML document containing a single event with all
51  of the metadata as would be uploaded to gracedb. This is optional.
52 
53  @param plot_width How wide to make the plot in pixels
54 
55  @param colours A misspelling of the word "colors" here used to indictate
56  the color of the PSD trace for each instrument
57 
58  """
59 
60  if coinc_xmldoc is not None:
61  coinc_event, = lsctables.CoincTable.get_table(coinc_xmldoc)
62  coinc_inspiral, = lsctables.CoincInspiralTable.get_table(coinc_xmldoc)
63  offset_vector = lsctables.TimeSlideTable.get_table(coinc_xmldoc).as_dict()[coinc_event.time_slide_id] if coinc_event.time_slide_id is not None else None
64  # FIXME: MBTA uploads are missing process table
65  #process, = lsctables.ProcessTable.get_table(coinc_xmldoc)
66  sngl_inspirals = dict((row.ifo, row) for row in lsctables.SnglInspiralTable.get_table(coinc_xmldoc))
67 
68  mass1 = sngl_inspirals.values()[0].mass1
69  mass2 = sngl_inspirals.values()[0].mass2
70  end_time = coinc_inspiral.get_end()
71  logging.info("%g Msun -- %g Msun event in %s at %.2f GPS" % (mass1, mass2, ", ".join(sorted(sngl_inspirals)), float(end_time)))
72  else:
73  # Use the cannonical BNS binary for horizon distance if an event wasn't given
74  sngl_inspirals = {}
75  mass1, mass2, end_time = 1.4, 1.4, 0
76 
77  fig = figure.Figure()
78  FigureCanvas(fig)
79  fig.set_size_inches(plot_width / float(fig.get_dpi()), int(round(plot_width / golden_ratio)) / float(fig.get_dpi()))
80  axes = fig.gca()
81  axes.grid(True)
82 
83  min_psds, max_psds = [], []
84  for instrument, psd in sorted(psds.items()):
85  if psd is None:
86  continue
87  psd_data = psd.data
88  f = psd.f0 + numpy.arange(len(psd_data)) * psd.deltaF
89  logging.info("found PSD for %s spanning [%g Hz, %g Hz]" % (instrument, f[0], f[-1]))
90  axes.loglog(f, psd_data, color = colours[instrument], alpha = 0.8, label = "%s (%.4g Mpc)" % (instrument, horizon_distance(psd, mass1, mass2, 8, 10)))
91  if instrument in sngl_inspirals:
92  logging.info("found %s event with SNR %g" % (instrument, sngl_inspirals[instrument].snr))
93  inspiral_spectrum = [None, None]
94  horizon_distance(psd, mass1, mass2, sngl_inspirals[instrument].snr, 10, inspiral_spectrum = inspiral_spectrum)
95  axes.loglog(inspiral_spectrum[0], inspiral_spectrum[1], color = colours[instrument], dashes = (5, 2), alpha = 0.8, label = "SNR = %.3g" % sngl_inspirals[instrument].snr)
96  # record the minimum from within the rage 10 Hz -- 1 kHz
97  min_psds.append(psd_data[int((10.0 - psd.f0) / psd.deltaF) : int((1000 - psd.f0) / psd.deltaF)].min())
98  # record the maximum from within the rage 1 Hz -- 1 kHz
99  max_psds.append(psd_data[int((1.0 - psd.f0) / psd.deltaF) : int((1000 - psd.f0) / psd.deltaF)].max())
100 
101  axes.set_xlim((1.0, 3000.0))
102  if min_psds:
103  axes.set_ylim((10**math.floor(math.log10(min(min_psds))), 10**math.ceil(math.log10(max(max_psds)))))
104  axes.set_title(r"Strain Noise Spectral Density for $%.3g\,\mathrm{M}_{\odot}$--$%.3g\,\mathrm{M}_{\odot}$ Merger at %.2f GPS" % (mass1, mass2, float(end_time)))
105  axes.set_xlabel(r"Frequency (Hz)")
106  axes.set_ylabel(r"Spectral Density ($\mathrm{strain}^2 / \mathrm{Hz}$)")
107  axes.legend(loc = "lower left")
108 
109  return fig