gstlal-inspiral  0.4.2
 All Classes Namespaces Files Functions Variables Pages
llweb.py
1 #!/usr/bin/env python
2 #
3 # Copyright (C) 2011 Chad Hanna
4 #
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 
19 import sys
20 import cgi
21 import cgitb
22 import os
23 import bisect
24 import math
25 
26 # This will run on a web server
27 os.environ["MPLCONFIGDIR"] = "/tmp"
28 import matplotlib
29 matplotlib.rcParams.update({
30  "text.usetex": True,
31 })
32 matplotlib.use('Agg')
33 import numpy
34 import matplotlib.pyplot as plt
35 import time
36 import StringIO
37 import base64
38 import urlparse
39 from gstlal import reference_psd
40 from glue.ligolw import ligolw
41 from glue.ligolw import array as ligolw_array
42 from glue.ligolw import lsctables
43 from glue.ligolw import param as ligolw_param
44 from glue.ligolw import utils as ligolw_utils
45 from pylal import series as lalseries
46 from gstlal import far
47 cgitb.enable()
48 
49 from gstlal import plotpsd
50 from gstlal import plotfar
51 
52 class LIGOLWContentHandler(ligolw.LIGOLWContentHandler):
53  pass
54 ligolw_array.use_in(LIGOLWContentHandler)
55 ligolw_param.use_in(LIGOLWContentHandler)
56 lsctables.use_in(LIGOLWContentHandler)
57 
58 def ceil10(x):
59  return 10**math.ceil(math.log10(x))
60 
61 def floor10(x):
62  return 10**math.floor(math.log10(x))
63 
64 css = """
65 <style type="text/css">
66 
67 a {
68  font: 12pt arial, sans-serif;
69 }
70 
71 .big {
72  font: 18pt arial, sans-serif;
73 }
74 
75 hr {
76  border: 0;
77  height: 0;
78  box-shadow: 0 0 4px 1px black;
79 }
80 
81 em.red {
82  font: 18pt arial, sans-serif;
83  color: red
84 }
85 
86 em.green {
87  font: 18pt arial, sans-serif;
88  color: green
89 }
90 
91 table {
92  font: 14pt arial, sans-serif;
93  width: 90%;
94 }
95 
96 th {
97  text-align: left;
98  border: 2pt outset;
99 }
100 
101 td {
102  padding: 3px;
103 }
104 
105 .topbox {
106  font: 14pt arial, sans-serif;
107  border-radius: 25px;
108  border: 2px solid gray;
109  background: white;
110  padding: 20px;
111  width: 97%;
112  box-shadow: 10px 10px 5px #888888;
113 }
114 
115 .tabs {
116  position: relative;
117  min-height: 7in; /* This part sucks */
118  clear: both;
119  margin: 25px 0;
120 }
121 
122 .tab {
123  float: left;
124 }
125 
126 .tab label {
127  background: #eee;
128  padding: 10px;
129  border: 1px solid #ccc;
130  margin-left: -1px;
131  position: relative;
132  left: 1px;
133 }
134 
135 .tab [type=radio] {
136  display: none;
137 }
138 
139 .content {
140  position: absolute;
141  top: 28px;
142  left: 0;
143  background: white;
144  right: 0;
145  bottom: 0;
146  padding: 20px;
147  border: 1px solid #ccc;
148 }
149 
150 [type=radio]:checked ~ label {
151  background: white;
152  border-bottom: 1px solid white;
153  z-index: 2;
154 }
155 
156 [type=radio]:checked ~ label ~ .content {
157  z-index: 1;
158 }
159 
160 </style>
161 """
162 
163 def now():
164  #FIXME use pylal when available
165  return time.time() - 315964785
166 
167 class GstlalWebSummary(object):
168  def __init__(self, form):
169  self.form = form
170  self.directory = form["dir"][0]
171  self.ifos = form["ifos"][0].split(",")
172  self.registry = {}
173  for id in [ '%04d' % (job,) for job in range(int(form["id"][0].split(",")[0]), 1+int(form["id"][0].split(",")[1]))]:
174  url = '%s/%s%s' % (self.directory, id, "_registry.txt")
175  try:
176  with open(url,"r") as tmp:
177  self.registry[id] = urlparse.urlparse(tmp.readline()).netloc
178  except IOError:
179  self.registry[id] = None
180 
181  # Class to automatically load data if it is not present
182  class Data(dict):
183  def __init__(s, *args, **kwargs):
184  dict.__init__(s, *args, **kwargs)
185  def __getitem__(s, k, web = self):
186  if k not in s:
187  web.load_data(k)
188  return s.get(k)
189 
190  self.found = Data()
191  self.missed = Data()
192 
193  def load_data(self, datatype):
194  self.found[datatype] = {}; self.missed[datatype] = {}
195  for id in sorted(self.registry):
196  fname = "%s/%s/%s" % (self.directory, id, datatype)
197  if datatype == "psds":
198  try:
199  self.found[datatype][id] = lalseries.read_psd_xmldoc(ligolw_utils.load_url("%s.xml" % fname, contenthandler = LIGOLWContentHandler))
200  except KeyError:
201  self.missed[datatype][id] = {}
202  elif datatype == "likelihood":
203  try:
204  self.found[datatype][id] = far.parse_likelihood_control_doc(ligolw_utils.load_filename("%s.xml" % fname, contenthandler = far.ThincaCoincParamsDistributions.LIGOLWContentHandler))
205  except KeyError:
206  self.missed[datatype][id] = {}
207  else:
208  try:
209  self.found[datatype][id] = numpy.loadtxt("%s.txt" % fname)
210  if len(self.found[datatype][id].shape) == 1:
211  self.found[datatype][id] = numpy.array([self.found[datatype][id],])
212  except IOError:
213  self.missed[datatype][id] = numpy.array([])
214  except ValueError:
215  self.missed[datatype][id] = numpy.array([])
216 
217  def status(self):
218  if not self.found["latency_history"]:
219  return "<em class=red>NO COINCIDENT EVENTS FOUND!</em>"
220 
221  def latency(self):
222  out = [l[0,1] for l in self.found["latency_history"].values()]
223  return "%.2f &pm; %.2f" % (numpy.mean(out), numpy.std(out))
224 
225  def time_since_last(self):
226  out = [now() - l[0,0] for l in self.found["latency_history"].values()]
227  return "%.2f &pm; %.2f" % (numpy.mean(out), numpy.std(out))
228 
229  def average_up_time(self):
230  out = {}
231  for ifo in self.ifos:
232  # FIXME a hack to deal with 16 Hz sample rate for LIGO
233  # statevector and 1 Hz for Virgo
234  # FIXME this should go in gstlal proper.
235  if ifo != "V1":
236  fac = 16.
237  else:
238  fac = 1.
239  out[ifo] = [sum(l[0,1:4])/fac for l in self.found["%s/state_vector_on_off_gap" % ifo].values()]
240  return "<br>".join(["%s=%.0e s" % (ifo, numpy.mean(v)) for ifo,v in out.items()])
241 
242  def setup_plot(self):
243  fig = plt.figure(figsize=(15, 4.0),)
244  fig.patch.set_alpha(0.0)
245  h = fig.add_subplot(111, axisbg = 'k')
246  plt.subplots_adjust(top = 0.93, left = .062, right = 0.98, bottom = 0.45)
247  return fig, h
248 
249  def finish_plot(self, ylim):
250  plt.grid(color=(0.1,0.4,0.5), linewidth=2)
251  ticks = ["%s : %s " % (id, reg) for (id, reg) in sorted(self.registry.items())]
252  plt.xticks(numpy.arange(len(ticks))+.3, ticks, rotation=90, fontsize = 10)
253  plt.xlim([0, len(ticks)])
254  plt.ylim(ylim)
255  tickpoints = numpy.linspace(ylim[0], ylim[1], 8)
256  ticks = ["%.1e" % (10.**t,) for t in tickpoints]
257  plt.yticks(tickpoints, ticks)
258  f = StringIO.StringIO()
259  plt.savefig(f, format="png")
260  out = '<img src="data:image/png;base64,' + base64.b64encode(f.getvalue()) + '"></img>'
261  f.close()
262  return out
263 
264  def to_png(self, fig = None):
265  f = StringIO.StringIO()
266  if fig:
267  fig.savefig(f, format="png")
268  else:
269  plt.savefig(f, format="png")
270  out = '<img src="data:image/png;base64,' + base64.b64encode(f.getvalue()) + '"></img>'
271  f.close()
272  return out
273 
274  def plot(self, datatype, ifo = None):
275  fig, h = self.setup_plot()
276  found = self.found[datatype]
277  missed = self.missed[datatype]
278  if datatype == "latency_history":
279  return self.plot_latency(fig, h, found, missed)
280  if datatype == "snr_history":
281  return self.plot_snr(fig, h, found, missed)
282  if "state_vector" in datatype:
283  return self.plot_livetime(fig, h, found, missed, ifo)
284  if "bank" in datatype:
285  return self.plot_single_col(fig, h, found, missed, col = 2, title = "Chirp Mass")
286  if "ram_history" in datatype:
287  return self.plot_ram(fig, h, found, missed)
288 
289  def plot_latency(self, fig, h, found, missed):
290  found_x = range(len(found))
291  found_y = numpy.log10(numpy.array([found[k][-1,1] for k in sorted(found)]))
292  time_y = numpy.log10(now() - numpy.array([found[k][-1,0] for k in sorted(found)]))
293  try:
294  max_y = max(time_y.max(), found_y.max())
295  except ValueError:
296  max_y = 1
297  missed_x = range(len(missed))
298  missed_y = numpy.ones(len(missed_x)) * max_y
299 
300  h.bar(missed_x, missed_y, color='r', alpha=0.9, linewidth=2)
301  h.bar(found_x, found_y, color='w', alpha=0.9, linewidth=2)
302  h.bar(found_x, time_y, color='w', alpha=0.7, linewidth=2)
303  plt.title("Time (s) since last event (gray) and latency (white)")
304  return self.finish_plot([0, max_y])
305 
306  def plot_snr(self, fig, h, found, missed):
307  found_x = range(len(found))
308  maxsnr_y = numpy.log10(numpy.array([found[k][:,1].max() for k in sorted(found)]))
309  mediansnr_y = numpy.log10(numpy.array([numpy.median(found[k][:,1]) for k in sorted(found)]))
310 
311  try:
312  max_y = max(maxsnr_y)
313  except ValueError:
314  max_y = 1
315  missed_x = range(len(missed))
316  missed_y = numpy.ones(len(missed_x)) * max_y
317 
318  h.bar(missed_x, missed_y, color='r', alpha=0.9, linewidth=2)
319  h.bar(found_x, mediansnr_y, color='w', alpha=0.9, linewidth=2)
320  h.bar(found_x, maxsnr_y, color='w', alpha=0.7, linewidth=2)
321  plt.title("SNR of last 1000 events: max (gray) and median (white)")
322  return self.finish_plot([numpy.log10(5.5), max_y])
323 
324 
325  def plot_livetime(self, fig, h, found, missed, ifo):
326  found_x = range(len(found))
327  # Handle log of 0 by setting it to max of (actual value, 1)
328  on_y = numpy.log10(numpy.array([max(found[k][0][1],1) for k in sorted(found)]))
329  off_y = numpy.log10(numpy.array([max(found[k][0][2],1) for k in sorted(found)]))
330  gap_y = numpy.log10(numpy.array([max(found[k][0][3],1) for k in sorted(found)]))
331  # FIXME Hack to adjust for high sample rate L1 and H1 state vector
332  if ifo != "V1":
333  on_y -= numpy.log10(16)
334  off_y -= numpy.log10(16)
335  gap_y -= numpy.log10(16)
336 
337  if len(found_x) > 0:
338  max_y = max(on_y.max(), off_y.max(), gap_y.max())
339  min_y = min(on_y.min(), off_y.min(), gap_y.min())
340  else:
341  max_y = 1
342  min_y = 0
343 
344  missed_x = range(len(missed))
345  missed_y = numpy.ones(len(missed_x)) * max_y
346 
347  h.bar(missed_x, missed_y, color='r', alpha=0.9, linewidth=2)
348  h.bar(found_x, off_y, color='w', alpha=0.7, linewidth=2)
349  h.bar(found_x, gap_y, color='b', alpha=0.5, linewidth=2)
350  h.bar(found_x, on_y, color='w', alpha=0.5, linewidth=2)
351  plt.title('%s Up time (gray) Down time (white) Dropped time (blue)' % (ifo,))
352  return self.finish_plot([min_y*.9, max_y])
353 
354  def plot_single_col(self, fig, h, found, missed, col = 2, title = ''):
355  found_x = range(len(found))
356  found_y = numpy.log10(numpy.array([found[k][0][col] for k in sorted(found)]))
357 
358  try:
359  max_y, min_y = max(found_y), min(found_y)
360  except ValueError:
361  max_y, min_y = (1,0)
362  missed_x = range(len(missed))
363  missed_y = numpy.ones(len(missed_x)) * max_y
364 
365  h.bar(missed_x, missed_y, color='r', alpha=0.9, linewidth=2)
366  h.bar(found_x, found_y, color='w', alpha=0.9, linewidth=2)
367  plt.title(title)
368  return self.finish_plot([0.9 * min_y, max_y])
369 
370  def plot_ram(self, fig, h, found, missed):
371 
372  found_x = range(len(found))
373  found_y = numpy.log10(numpy.array([found[k][0,1] for k in sorted(found)]))
374 
375  try:
376  max_y, min_y = max(found_y), min(found_y)
377  except ValueError:
378  max_y, min_y = (1,0)
379  missed_x = range(len(missed))
380  missed_y = numpy.ones(len(missed_x)) * max_y
381 
382  h.bar(missed_x, missed_y, color='r', alpha=0.9, linewidth=2)
383  h.bar(found_x, found_y, color='w', alpha=0.9, linewidth=2)
384  plt.title("max RAM usage (GB)")
385  return self.finish_plot([0.9 * min_y, max_y])
386 
387  #
388  # Single Node plots
389  #
390 
391  def livetime_pie(self):
392  out = ""
393  for id in self.registry:
394  for ifo in self.ifos:
395  fig = plt.figure(figsize=(5,3),)
396  fig.patch.set_alpha(0.0)
397  h = fig.add_subplot(111, axisbg = 'k', aspect='equal')
398  plt.subplots_adjust(bottom = 0, left = .25, top = 1, right = .75)
399  plt.grid(color="w")
400 
401  discontdata = self.found["%s/strain_add_drop" % ifo][id]
402  livetimedata = self.found["%s/state_vector_on_off_gap" % ifo][id]
403  dt = livetimedata[0,2]
404  lt = livetimedata[0,1]
405  discont = discontdata[0,1]
406  # FIXME Hack to adjust for high sample rate L1 and H1 state vector
407  if "V1" not in ifo:
408  dt /= 16
409  lt /= 16
410  discont /= 16
411  data = [dt, lt, discont]
412  explode = [0.0, 0, 0.15]
413  labels = ["OFF : %g (s)" % dt, "ON : %g (s)" % lt, "MIA : %g (s)" % discont]
414 
415  h.pie(data, shadow=True, explode = explode, labels = labels, autopct='%1.1f%%', colors = ('0.5', '1.0', (0.7, 0.7, 1.)))
416 
417  plt.title(ifo)
418  out += self.to_png()
419  return out
420 
421  def psdplot(self, fmin = 10., fmax = 2048.):
422  out = ""
423  for id in self.registry:
424  psds = self.found["psds"][id]
425  fig = plotpsd.plot_psds(psds)
426  out += self.to_png(fig = fig)
427  return out
428 
429  def snrchiplot(self, binnedarray_string):
430  out = ""
431  for id in self.registry:
432  likelihood, nu, nu = self.found["likelihood"][id]
433  for ifo in self.ifos:
434  fig = plotfar.plot_snr_chi_pdf(likelihood, ifo, binnedarray_string, 400)
435  out += self.to_png(fig = fig)
436  return out
437 
438  def rateplot(self):
439  out = ""
440  for id in self.registry:
441  likelihood, ranking_data, nu = self.found["likelihood"][id]
442  fig = plotfar.plot_rates(likelihood, ranking_data)
443  out += self.to_png(fig = fig)
444  return out
445 
446 
447  def plothistory(self, dataurl, xlabel = "", ylabel = "", title = ""):
448  out = ""
449  for id in self.registry:
450  try:
451  data = self.found[dataurl][id]
452  except KeyError:
453  out += "<em>Data not found</em>"
454  continue
455  fig = plt.figure(figsize=(5,3.5),)
456  fig.patch.set_alpha(0.0)
457  h = fig.add_subplot(111, axisbg = 'k')
458  plt.subplots_adjust(bottom = 0.2, left = .16)
459  plt.grid(color=(0.1,0.4,0.5), linewidth=2)
460 
461  h.semilogy(data[:,0] - data[-1,0], data[:,1], 'w', alpha=0.75, linewidth=2)
462  plt.ylim([min(data[:,1]), max(data[:,1])])
463  locs = [min(data[:,1]), numpy.median(data[:,1]), max(data[:,1])]
464  labels = ['%.2g' % lab for lab in locs]
465  plt.yticks(locs, labels)
466  plt.xlabel(xlabel)
467  plt.ylabel(ylabel)
468  plt.title(title)
469  out += self.to_png()
470  return out
471 
472  def plotccdf(self):
473  out = ""
474  for id in self.registry:
475  likelihood, ranking_data, nu = self.found["likelihood"][id]
476  ranking_data.finish()
477  fapfar = far.FAPFAR(ranking_data)
478  fig = plotfar.plot_likelihood_ratio_ccdf(fapfar, (-5, 25), "")
479  out += self.to_png(fig = fig)
480  return out