Pages.py
Go to the documentation of this file.
1 """
2  Pages.py:
3  Generate individual validation site pages.
4 
5  Original author: J. Wolcott <jwolcott@fnal.gov>
6  (Refactored from code by M. Tamsett)
7  Date: September 2016
8 """
9 
10 import itertools
11 import operator
12 import os.path
13 import re
14 
15 from models.Organizational import PlotCollectionKey, PlotID
16 from models.PlotInfo import CanvasSummary
17 from models.PlotInfo import ComparisonSummary
18 from tools import PathTools
19 
21  """ Wrapper type to hold information about a page subdirectory:
22  especially, its name, and whether it's part of the current
23  run or whether it was discovered from a previous one. """
24 
25  def __init__(self, name, is_current=True):
26  self.name = name
27  self.is_current = is_current
28 
30  TEMPLATE_PAGE = os.path.join(os.path.dirname(__file__), "template.html")
31 
32  def __init__(self, top_url, base_url, static_url=None, **kwargs):
33  self.top_url = top_url
34  self.base_url = base_url
35  self.static_url = "/".join((self.top_url, "static_content")) if static_url is None else static_url
36  self.html = open( type(self).TEMPLATE_PAGE ).read() # the type(self) bit is so that we get the overridden version in child classes
37 
38  self.sidebar_index = -1
39 
40  self.AddContent(**kwargs)
41  ######
42 
43  def AddHTML(self, pattern, replacement, destructive=False):
44  if (not destructive) and (pattern not in replacement): replacement = replacement+"\n"+pattern
45  self.html = self.html.replace(pattern,replacement)
46 
47  def GetHTML(self):
48  return self.html
49 
50 
51  ######
52 
53  def GetURLTrail(self):
54  url = self.base_url.replace(self.top_url, "")
55  return url.strip("/").split("/")
56 
57  def AddBreadcrumb(self):
58  url_trail = self.GetURLTrail()
59 
60  breadcrumb_html = \
61  """
62  <li><a href="%s/index.html">Validation</a></li>
63  """ % self.top_url
64 
65  breadcrumb = self.top_url
66  for url_bit in url_trail:
67  if url_bit == "":
68  continue
69  breadcrumb = "/".join( (breadcrumb, url_bit) )
70  breadcrumb_html += '<li>'
71  # note that there's no index page in the DATASET_COMPARISON_DIRNAME itself
72  if url_bit != url_trail[-1] and url_bit != PathTools.DATASET_COMPARISON_DIRNAME:
73  breadcrumb_html += '<a href="%s">' % breadcrumb
74  breadcrumb_html += url_bit
75  if not url_bit == url_trail[-1]:
76  breadcrumb_html += '</a>'
77  breadcrumb_html += '</li>\n'
78 
79  self.AddHTML("<!--BREADCRUMB_HOOK-->", breadcrumb_html)
80 
81 
82  def AddContent(self):
83  """ Hook for child classes to add their custom content. Override as desired. """
84  pass
85 
86  def AddStaticContentLinks(self, additional_css=[], additional_js=[], insert_plot_js=False):
87  css_links = ["%(static_url)s/bootstrap.css", "%(static_url)s/dashboard.css",]
88  css_links += additional_css
89  for css_link in css_links:
90  css_link %= {"static_url": self.static_url}
91  self.AddHTML("<!--CSS_HOOK-->", """<link href="%(link)s" rel="stylesheet"> \n""" % {"link" : css_link} )
92 
93  html = ""
94  if insert_plot_js:
95  html += \
96  """
97  <script>
98  var VIEW = {};
99  VIEW.plot_selections = {};
100  var DATA = {}
101  DATA.statistics = {};
102  DATA.comparison_statistics = {};
103  // JS_HOOK
104 
105  </script>
106  <script src="http://nusoft.fnal.gov/nova/production/static/d3.v3.min.js"></script>
107  <script src="%(static_url)s/plot_page.js"></script>
108  """ % {"static_url": self.static_url}
109 
110  for js_link in additional_js:
111  js_link %= {"static_url": self.static_url}
112  html += '<script src="%(link)s"></script>\n' % {"link": js_link}
113 
114  self.AddHTML("<!--JS_HOOK-->", html)
115 
116  def AddNavbarHook(self):
117  html = \
118  """
119  <ul class="nav navbar-nav navbar-right">
120  <li class="dropdown">
121  <a href="#" class="dropdown-toggle" data-toggle="dropdown">Datasets <span class="caret"></span></a>
122  <ul class="dropdown-menu" role="menu">
123  <!--NAVBAR_DATASET_HOOK-->
124  </ul>
125  </li>
126  </ul>
127  """
128  self.AddHTML("<!--NAVBAR_HOOK-->", html)
129 
130  def AddNavbarDropdowns(self, info, dropdowns):
131  plot_groups = dropdowns[info[0]]
132  plot_groups.sort()
133  for plot_group in plot_groups:
134  if plot_group == info[1]:
135  suffix = ' class="active"'
136  else:
137  suffix = ''
138  html = '<li%s><a href="%s/%s/%s">%s</a></li>'%(suffix, self.url_base, info[0], plot_group.replace(" ","_")+".html", plot_group)
139  self.AddHTML("<!--NAVBAR_DATASET_HOOK-->", html)
140 
141  def AddSidebarHook(self):
142  self.AddHTML("<!--FOOTER_HOOK-->", "", destructive=True)
143  html = \
144  """
145  <div class="col-sm-3 col-md-2 sidebar">
146  <!--SIDEBAR_HOOK-->
147  </div> <!-- end of side bar -->
148  <div class="col-sm-offset-3 col-md-10 col-md-offset-2 main">
149  <!--MODAL_HOOK-->
150  <!--BODY_HOOK-->
151  </div> <!-- end of main body -->
152  """
153  self.AddHTML("<!--BODY_HOOK-->", html, destructive=True)
154 
155  def AddSidebarSection(self, category):
156  self.sidebar_index += 1
157  title = \
158  """
159  <h4>%s</h4>
160  <!--SIDEBAR_SECTION_%i_HOOK-->
161  <hr>
162  """%(category, self.sidebar_index)
163  self.AddHTML("<!--SIDEBAR_HOOK-->",title)
164 
165  def AddTitle(self):
166  url_trail = self.GetURLTrail()
167 
168  self.AddHTML("<!--TITLE_HOOK -->", " | ".join(reversed(url_trail)), destructive=True)
169 
170 #####################
171 
173  def AddContent(self, validation_subdirs):
174  self.AddStaticContentLinks()
175  self.AddTitle()
176  self.AddBreadcrumb()
177 
178  html = \
179  """
180  <div class="col-lg-12">
181  <h1 class="page-header">Validation projects</h1>
182  """
183  validation_subdirs.sort()
184 
185  # Make a list of contents based on these
186  for valid_dir in validation_subdirs:
187  inst_name = os.path.basename(valid_dir)
188  html += "<h2><a href=\"%s/%s\">%s</a></h2>" % (self.base_url, inst_name, inst_name)
189  html += "<hr />"
190  html += "</div>"
191  self.AddHTML("<!--BODY_HOOK-->",html)
192 
193 #####################
194 
196  def AddContent(self, validation_name, dataset_dirs, comparison_dirs):
197  self.AddStaticContentLinks()
198  self.AddTitle()
199  self.AddBreadcrumb()
200 
201  html = \
202  """
203  <div class="col-lg-12">
204  <h1 class="page-header">Validation: %(name)s</h1>
205  """ % {"name": validation_name}
206 
207  # Make the individual dataset links
208  html += \
209  """
210  <div class="page-header"><h2>Plots sorted by dataset</h2></div>
211  <div>
212  <ul>
213  """
214  for dataset_info in dataset_dirs:
215  line = """<li><a href="%(base_url)s/%(dataset)s">%(dataset)s</a></li>""" % { "base_url": self.base_url, "dataset": dataset_info.name}
216 
217  html += line
218 
219  html += \
220  """
221  </ul>
222  </div>
223  """
224 
225  # and now the comparisons
226  html += \
227  """
228  <div class="page-header"><h2>Dataset comparisons</h2></div>
229  <ul>
230  """
231  for comparison_dir_info in comparison_dirs:
232  comparison_datasets = sorted(comparison_dir_info.name.split("|")) # yep, that's a pipe character.
233 
234  links = \
235  """
236  <li><a href="%(base_url)s/%(comp_topdir)s/%(comp_subdir)s">%(comp_datasets)s</a>
237  """
238  if len(comparison_datasets) == 2:
239  links += """ (<a href="%(base_url)s/%(comp_topdir)s/%(comp_subdir)s/%(stat_pagename)s.html">comparisons statistics</a>)"""
240 
241  links += "</li>"
242 
243  links %= {
244  "base_url": self.base_url,
245  "comp_topdir": PathTools.DATASET_COMPARISON_DIRNAME,
246  "comp_subdir": comparison_dir_info.name,
247  "comp_datasets": ", ".join(comparison_datasets),
248  "stat_pagename": PathTools.STAT_PAGE_NAME,
249  }
250  html += links
251  html += \
252  """
253  </ul>
254  """
255 
256 
257  self.AddHTML("<!--BODY_HOOK-->",html)
258 
259 #####################
260 
262  TEMPLATE_PAGE = os.path.join(os.path.dirname(__file__), "plot_page_template.html")
263  INFORMATION_SEQUENCE = ["Name", "Exposure", "Entries", "Underflow", "Overflow", "Mean", "Peak location", "RMS"]
264  COMPARISON_INFO_SEQUENCE = ["Test", "Statistic", "p-value", "Other properties"]
265 
266 
267  DEFAULT_CATEGORY_ENTRIES = {
268  "Normalization": "raw",
269  "Log y": "disabled"
270  }
271 
272  def AddContent(self, plots, subdirs, comparison_info=None):
273  additional_css = [ "%(static_url)s/plot_page.css",]
274  json_overlay = plots and any(itertools.chain([key.path.endswith("json") or key.path.endswith("root") for key in p] for p in plots.itervalues()))
275  if json_overlay:
276  self.AddStaticContentLinks(additional_css=additional_css, additional_js=["%(static_url)s/jsroot_overlay.js",], insert_plot_js=True)
277  else:
278  self.AddStaticContentLinks(additional_css=additional_css, insert_plot_js=True)
279 
280  self.AddTitle()
281  self.AddBreadcrumb()
282  self.AddSidebarHook()
283 
284  if len(subdirs) > 0:
285  self.AddSidebarSection("Subcollections")
286  for subdir in subdirs:
287  self.AddSubdirLinks(subdir)
288 
289  if len(plots) > 0:
290  self.AddSidebarSection(self.base_url.split("/")[-1])
291  plot_names = sorted(plots)
292  for plot_name in plot_names:
293  plot_info_collection = plots[plot_name] # this is a PlotLibrary, remember, with CanvasSummary objects as the values
294  plot_comparison_keys = []
295  if comparison_info:
296  # find all the keys from the comparisons (which are as generic as possible for this plot)
297  # which 'contains' multiple keys from the plot collection.
298  # these correspond to comparisons we want to keep.
299  for key in comparison_info:
300  if [k.plot_id in key.plot_id for k in plot_info_collection].count(True) == 2:
301  plot_comparison_keys.append(key)
302 
303  # reorganize by category grouping
304  self.AddFigure(plot_name, plot_info_collection, {k: comparison_info[k] for k in plot_comparison_keys})
305 
306 
307  def AddFigure(self, plot_name, plot_info_library, comparison_info=None):
308  plot_title = [v for k, v in plot_info_library.iteritems() if not any(name in k.plot_id.categories for name in(PlotID.SUM_STRING, PlotID.OVERLAY_STRING))][0].titles[0]
309 
310  self.AddHTML("// JS_HOOK", 'VIEW.plot_selections["%s"] = {};' % plot_name)
311  category_options = []
312  for key in plot_info_library:
313  selection_bits = []
314  assert isinstance(key, PlotCollectionKey), "Unexpected key: %s" % key
315  for cat_idx, category in enumerate(key.plot_id.categories):
316  selection_bits.append('["%s"]' % category)
317  if cat_idx > len(category_options) - 1:
318  category_options.append(set())
319  category_options[cat_idx].add(category)
320 # selection_string = "".join(selection_bits)
321 # self.AddHTML("// JS_HOOK", 'VIEW.plot_selections["%s"]%s = ""; ' % (plot_name, selection_string))
322 
323  # it doesn't matter which one (it's just being used
324  # to construct a default anyway),
325  # but depending on 'key' still being in scope
326  # from the preceding loop sure is confusing
327  key = plot_info_library.keys()[0]
328 
329  # will be used to make the first plot shown,
330  # so choose the first of every category
331  # unless a default is specified
332  default_categories = []
333  for cat_idx, c in enumerate(category_options):
334  if cat_idx in key.plot_id.groups.values():
335  try:
336  default_categories.append(PlotPage.DEFAULT_CATEGORY_ENTRIES[key.plot_id.groups.keys()[key.plot_id.groups.values().index(cat_idx)]])
337  continue
338  # no default. just use the first one
339  except KeyError:
340  pass
341 
342  default_categories.append(sorted(c)[0])
343  new_id = PlotID({"name": key.plot_id.name, "categories": default_categories, "groups": key.plot_id.groups})
344  default_key = PlotCollectionKey(
345  plot_id=new_id,
346  data_set=key.data_set,
347  # remember: at this stage the plot_info_library key path is to a real file,
348  # so it contains the serialization of the plot_id
349  # (including all its categories etc.) need to make sure those are right.
350  path=key.path.replace(key.plot_id.id_str, new_id.id_str)
351  )
352  default_plot_info = plot_info_library[default_key]
353  matching_comp_keys = None
354  if comparison_info:
355  matching_comp_keys = []
356  for k in comparison_info:
357  if default_key.plot_id not in k.plot_id:
358  continue
359  # ugh. paths in keys from comparison_info are just subdir names,
360  # as opposed to the full-paths-to-file that are in the plot_info_library keys
361  # (which is where default_key inherits its path from).
362  # we need to check the paths in case there are plots with the same name
363  # but in different directories...
364  # I _think_ this 'endswith' test will work even with nested subdirs
365  # because even the stripped-down paths from comparison_info
366  # ought to be in dir1/dir2/dir3 form.
367  if not os.path.dirname(default_key.path).endswith(k.path):
368  continue
369  matching_comp_keys.append(k)
370  if matching_comp_keys and len(matching_comp_keys) != 1:
371  raise RuntimeError(("Comparison info provided (keys=%s)" +
372  " has %d matches for default plot key (%s) instead of 1")
373  % (comparison_info.keys(), len(matching_comp_keys), default_key))
374  default_comparison_info = comparison_info[matching_comp_keys[0]] if matching_comp_keys else None
375 
376  sidebar_link = '<p><a href="#row_%s">%s</a></p>' % (plot_name, plot_title)
377  self.AddHTML("<!--SIDEBAR_SECTION_%i_HOOK-->" % self.sidebar_index, sidebar_link)
378 
379  row = \
380  """
381  <div class="row" id="row_%(name)s">
382  <h3><a href="#row_%(name)s">%(title)s</a></h3>
383  <div class="col-xs-12 col-sm-12 col-md-12 col-lg-6" id="figure_%(name)s" value="%(name)s">
384  <a href="#%(name)s" data-toggle="modal" data-target="#%(name)s">
385  <img src="%(url)s/%(filename)s" class="img-responsive" alt="..." id="%(name)s">
386  </a>
387  <p class="text-right" id="download_%(name)s">
388  <text><a href="%(url)s/%(filename)s" download>svg</a> </text>
389  </p>
390  <!--RATIO_HOOK-->
391  </div>
392  <div class="col-xs-12 col-sm-12 col-md-12 col-lg-6">
393  <br>
394  <div class="panel panel-default">
395  <div class="panel-body text-left">
396 
397  <!--TEXT_HOOK-->
398  <!--TABLE_HOOK-->
399 
400  </div>
401  </div>
402  <div class="panel panel-default">
403  <div class="panel-heading text-left">
404  <h3 class="panel-title">Controls</h3>
405  </div>
406  <div class="panel-body text-left">
407  <!--PLOT_CONTROL_HOOK-->
408  </div>
409  </div>
410  <p class="text-right"><a href="#top">Top of page</a></p>
411  </div>
412  </div> <!-- end of this plot row -->
413  <hr>
414  """ % {
415  "title": plot_title,
416  "name": plot_name,
417  "url": self.base_url,
418  "filename": os.path.basename(default_key.path),
419  }
420 
421  modal = \
422  """
423  <div class="modal fade" id="%(name)s" tabindex="-1" role="dialog" aria-labelledby="%(name)sLabel" aria-hidden="true">
424  <div class="modal-dialog modal-lg">
425  <div class="modal-content">
426  <div class="modal-body">
427  <img src="%(url)s/%(filename)s" class="img-responsive" alt="..." id="%(name)s">
428  </div>
429  <div class="modal-footer">
430  <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
431  </div>
432  </div><!-- /.modal-content -->
433  </div><!-- /.modal-dialog -->
434  </div><!-- /.modal -->
435  """ % {
436  "name": plot_name,
437  "url": self.base_url,
438  "filename": os.path.basename(default_key.path)
439  }
440  self.AddHTML("<!--MODAL_HOOK-->",modal)
441 
442  for cat_idx, category_list in enumerate(category_options):
443  label_subst = {
444  "group": cat_idx,
445  "name": plot_name,
446  }
447 
448  if cat_idx in default_key.plot_id.groups.values():
449  group_name = default_key.plot_id.groups.keys()[default_key.plot_id.groups.values().index(cat_idx)]
450  else:
451  group_name = None
452 
453  if group_name:
454  label_subst["label"] = group_name + ":"
455  else:
456  # blank label just to keep things lined up correctly
457  label_subst["label"] = ""
458  label_string = '<label for="group%(group)d_control_%(name)s" id="label_group%(group)d_control_%(name)s" class="col-lg-4 control-label">%(label)s</label>' % label_subst
459 
460  control_html = """
461  <div class="row">
462  <div class="form-group">
463  %(label_str)s
464  <div class="col-lg-8">
465  <select id="group%(group_id)d_control_%(name)s" class="control plot_%(name)s">
466  <!--PLOT_CONTROL_OPTION_HOOK-->
467  </select>
468  </div>
469  </div>
470  </div>
471  """ % {"group_id": cat_idx, "name": plot_name, "label_str": label_string }
472 
473  for category in sorted(category_list):
474  if group_name and \
475  group_name in PlotPage.DEFAULT_CATEGORY_ENTRIES and \
476  category == PlotPage.DEFAULT_CATEGORY_ENTRIES[group_name]:
477  selected_text = ' selected="selected"'
478  else:
479  selected_text = ""
480  this_option = '<option value="%(category)s"%(selected)s>%(category)s</option>' % {"category": category, "selected": selected_text}
481  control_html = control_html.replace("<!--PLOT_CONTROL_OPTION_HOOK-->", "%s<!--PLOT_CONTROL_OPTION_HOOK-->" % this_option)
482  row = row.replace("<!--PLOT_CONTROL_HOOK-->", "%s<!--PLOT_CONTROL_HOOK-->" % control_html)
483 
484 
485  # plot information now
486  table_rows = ""
487  for information in PlotPage.SortedPlotInfo(default_plot_info):
488  table_item ='<tr class="content">'
489  table_item += "".join("<td class=\"%s\">%s</td>" % (info_name.replace(" ", ""), information[info_name]) for info_name in PlotPage.INFORMATION_SEQUENCE)
490  table_item += '</tr>\n'
491 
492  table_rows += table_item + "\n"
493 
494  info_table = \
495  """
496  <table class="table table-striped data" id="table_%(name)s">
497  <tbody>
498  <tr>
499  """
500  info_table += "".join("<th>%s</th>" % info_name for info_name in PlotPage.INFORMATION_SEQUENCE)
501  info_table += \
502  """
503  </tr>
504  %(rows)s
505  </tbody>
506  </table>
507  """
508  info_table = info_table % {"name": plot_name, "rows": table_rows}
509 
510  comparison_table = ""
511  if default_comparison_info:
512  comparison_table = \
513  """
514  <table class="table table-striped data" id="table_comparison_%(name)s">
515  <thead>
516  <tr> <th>Test</th><th>Statistic</th><th>p-value</th><th>Other properties</th> </tr>
517  </thead>
518  <tbody>
519  %(rows)s
520  </tbody>
521  </table>
522  """
523 
524  rows = ""
525  for stat_name, stat_properties in default_comparison_info.comparison_stats.iteritems():
526  rowspan = len(stat_properties.other_properties)
527  rowspan_text = "" if rowspan < 2 else " rowspan=\"%d\"" % rowspan
528  if len(stat_properties.other_properties) == 0:
529  other_props_text = "<td class=\"Otherproperties\"></td>"
530  else:
531  other_props_text = "".join("<td class=\"Otherproperties\">%s = %.1f</td>" % (propname, propval) for propname, propval in stat_properties.other_properties.iteritems())
532  rows += \
533  """
534  <tr class="content">
535  <td%(rowspan)s class="Test">%(stat_name)s</td>
536  <td%(rowspan)s class="Statistic">%(stat_val)s</td>
537  <td%(rowspan)s class="p-value">%(pval)s</td>
538  %(other_props)s
539  </tr>
540  """ % {
541  "stat_name": stat_name,
542  "stat_val": PlotPage.FormatOrNone(stat_properties.statistic, "%.1f"),
543  "pval": PlotPage.FormatOrNone(stat_properties.p_value, "%.3g"),
544  "other_props": other_props_text,
545  "rowspan": rowspan_text,
546  }
547 
548  comparison_table %= {
549  "name": plot_name,
550  "rows": rows,
551  }
552 
553  for table in info_table, comparison_table:
554  row = row.replace("<!--TABLE_HOOK-->", table + "<!--TABLE_HOOK-->")
555 
556  self.AddHTML("<!--BODY_HOOK-->",row)
557 
558  # add the javascript for the information
559  collections = [plot_info_library,]
560  if comparison_info:
561  collections.append(comparison_info)
562  for info_collection in collections:
563  for key, info in info_collection.iteritems():
564  self.AddPlotInformation(key.plot_id.id_str, info)
565 
566  def AddPlotInformation(self, name, information):
567  info_type = None
568  info_sequence = None
569  if isinstance(information, CanvasSummary):
570  info_type = "statistics"
571  info_sequence = PlotPage.INFORMATION_SEQUENCE
572  elif isinstance(information, ComparisonSummary):
573  info_type = "comparison_statistics"
574  info_sequence = PlotPage.COMPARISON_INFO_SEQUENCE
575  else:
576  raise RuntimeError("Unknown plot information type: %s" % information)
577 
578  self.AddHTML("// JS_HOOK", 'DATA.%s["%s"] = [];' % (info_type, name))
579 
580  for props in PlotPage.SortedPlotInfo(information):
581  info_core = ",".join(('"%s": %s' if hasattr(props[prop], "keys") else '"%s": "%s"') % (prop, props[prop]) for prop in info_sequence)
582  info = "{%s}" % info_core
583  self.AddHTML("// JS_HOOK",'DATA.%s["%s"].push(%s);' % (info_type, name, info))
584 
585  def AddGroupControls(self):
586  pass
587 
588  def AddSubdirLinks(self, subdir):
589  sidebar_link = '<p><a href="%s/%s">%s</a></p>' % (self.base_url, subdir, subdir)
590  self.AddHTML("<!--SIDEBAR_SECTION_%i_HOOK-->" % self.sidebar_index, sidebar_link)
591 
592  main_link = '<div><h4>Subcollection: <a href="%s/%s">%s</a></h4></div>' % (self.base_url, subdir, subdir)
593  self.AddHTML("<!--BODY_HOOK-->", main_link)
594 
595  @staticmethod
596  def FormatOrNone(num, fmt):
597  if num is not None:
598  return fmt % num
599  else:
600  return "&mdash;"
601 
602  @staticmethod
603  def SortedPlotInfo(info):
604  ret_info = []
605 
606  # not very Pythonic, but there are a lot of properties to query
607  if isinstance(info, CanvasSummary):
608  if any(info.labels):
609  curve_order = (info.labels.index(k) for k in sorted(info.labels))
610  else:
611  curve_order = range(info.num_plots)
612  for curve_idx in curve_order:
613  information = {}
614  information["Name"] = info.labels[curve_idx]
615  exp = info.exposures[curve_idx]
616  if exp:
617  information["Exposure"] = "%.3g POT" % exp.POT if exp.POT else ("%.2f s" % exp.livetime if exp.livetime else None)
618  else:
619  information["Exposure"] = "&mdash;"
620  information["Entries"] = "%d" % info.nums_entries[curve_idx]
621  information["Mean"] = PlotPage.FormatOrNone(info.means[curve_idx], "%.3g")
622  information["Peak location"] = PlotPage.FormatOrNone(info.peak_locations[curve_idx], "%.3g")
623  information["RMS"] = PlotPage.FormatOrNone(info.RMSs[curve_idx], "%.3g")
624  information["Underflow"] = PlotPage.FormatOrNone(info.underflows[curve_idx], "%d")
625  information["Overflow"] = PlotPage.FormatOrNone(info.overflows[curve_idx], "%d")
626 
627  ret_info.append(information)
628  elif isinstance(info, ComparisonSummary):
629  for stat_name, stat_info in info.comparison_stats.iteritems():
630  ret_info.append({
631  "Test": stat_name,
632  "Statistic": PlotPage.FormatOrNone(stat_info.statistic, "%.1f"),
633  "p-value": PlotPage.FormatOrNone(stat_info.p_value, "%.3g"),
634  "Other properties": stat_info.other_properties,
635  })
636 
637  return ret_info
638 
640  def AddContent(self, comparison_datasets, comparison_info):
642  additional_css=["%(static_url)s/comparison_table.css",],
643  additional_js=["%(static_url)s/comparison_table.js",],
644  )
645 
646  self.AddBreadcrumb()
647  self.AddTitle()
648 
649  header_text = \
650  """
651  <h2 style="text-align: center">Dataset statistical comparisons</h2>
652  <ul>
653  %s
654  </ul>
655  """ % "\n".join("<li><a href=\"../../%(dataset)s\">%(dataset)s</a></li>" % {"dataset": d} for d in comparison_datasets)
656 
657  self.AddHTML("<!--BODY_HOOK-->", header_text)
658 
659  self.AddTable(comparison_info)
660 
661  def AddTable(self, comparison_info):
662 
663 
664  table_html = \
665  """
666  <table class="table table-bordered table-striped tablesorter" id="stats_table">
667  <thead>
668  <tr> <th class="plotname">Plot</th><th data-sorter="false">Test</th><th>Statistic</th><th>p-value</th><th data-sorter="false">Other properties</th> </tr>
669  </thead>
670  <tbody>
671  %(rows)s
672  </tbody>
673  </table>
674  """
675 
676  sorted_comparisons = [(plot_key, info) for plot_key, info in comparison_info.iteritems()]
677  statistics_available = reduce(operator.and_, (set(statname for statname, stat in info.comparison_stats.iteritems() if stat is not None) for info in comparison_info.itervalues()))
678  stats_to_sort_by = [s for s in ComparisonSummary.SORT_STATISTIC_PRIORITY if s in statistics_available]
679  if len(stats_to_sort_by) > 0:
680  stat_to_sort_by = stats_to_sort_by[0]
681  # note that the negative cmp is intentional: want to sort in descending order
682  sorted_comparisons.sort(cmp=lambda pair1, pair2: -cmp(pair1[1].comparison_stats[stat_to_sort_by].statistic, pair2[1].comparison_stats[stat_to_sort_by].statistic))
683  else:
684  print "Couldn't sort comparisons by statistical measure (no common statistics across plots). Showing unsorted..."
685 
686  name_counts = {} # if there are duplicates, we want to add disambiguation info (categories)
687  for comparison_pair in sorted_comparisons:
688  name_counts.setdefault(comparison_pair[0].plot_id.name, 0)
689  name_counts[comparison_pair[0].plot_id.name] += 1
690 
691  rows = ""
692  for comparison_pair in sorted_comparisons:
693  plot_key, comparison_info = comparison_pair
694  display_name = plot_key.plot_id.name
695  if name_counts[display_name] > 1:
696  display_name += " " + "".join(["[%s]" % c for c in plot_key.plot_id.categories])
697  anchor_name = re.sub(r"\W", "_", display_name)
698 
699  toprow = True
700  rowspan_plotname = sum(max(1, len(st.other_properties)) for st in comparison_info.comparison_stats.itervalues())
701  # work in order of the sorting unless it's not doable
702  for stat_name in stats_to_sort_by or comparison_info.comparison_stats:
703  stat_properties = comparison_info.comparison_stats[stat_name]
704  plotname_cell = ""
705 
706  if toprow:
707  plotname_cell = "<td rowspan=\"%(rowspan)d\" class=\"plotname\"><a href=\"%(path)s/#row_%(anchor_name)s\">%(plot_name)s</a></td>" % {
708  "rowspan": rowspan_plotname,
709  "path": plot_key.path,
710  "anchor_name": anchor_name,
711  "plot_name": display_name,
712  }
713  row_classname = "toprow"
714  if (not toprow) or stat_name not in stats_to_sort_by:
715  row_classname = "tablesorter-childRow" # needed or the JS table sorter gets confused by the rowspan
716 
717  # disable so the remaining rows aren't regarded as 'top'
718  # (don't do this in the block above because the second 'if'
719  # shouldn't fire for the real top row)
720  if toprow:
721  toprow = False
722 
723  rowspan = len(stat_properties.other_properties)
724  rowspan_text = "" if rowspan < 2 else " rowspan=\"%d\"" % rowspan
725  if len(stat_properties.other_properties) == 0:
726  other_props_text = "<td></td>"
727  else:
728  other_props_text = "".join("<td>%s = %.1f</td>" % (propname, propval) for propname, propval in stat_properties.other_properties.iteritems())
729 
730  rows += \
731  """
732  <tr class="%(classname)s plot_%(plotname)s">
733  %(plotname_cell)s
734  <td%(rowspan)s>%(stat_name)s</td>
735  <td%(rowspan)s>%(stat_val)s</td>
736  <td%(rowspan)s>%(pval)s</td>
737  %(other_props)s
738  </tr>
739  """ % {
740  "classname": row_classname,
741  "plotname": anchor_name,
742  "plotname_cell": plotname_cell,
743  "stat_name": stat_name,
744  "stat_val": PlotPage.FormatOrNone(stat_properties.statistic, "%.1f"),
745  "pval": PlotPage.FormatOrNone(stat_properties.p_value, "%.3g"),
746  "other_props": other_props_text,
747  "rowspan": rowspan_text,
748  }
749 
750  table_html %= {"rows": rows}
751 
752  self.AddHTML("<!--BODY_HOOK-->", table_html)
753 
754  self.AddHTML("<!--JS_HOOK-->", "<script> var SORTABLE_STATS = [%s] </script>" % ", ".join('"%s"' % s for s in stats_to_sort_by))
void split(double tt, double *fr)
def AddTable(self, comparison_info)
Definition: Pages.py:661
def __init__(self, name, is_current=True)
Definition: Pages.py:25
def AddContent(self, plots, subdirs, comparison_info=None)
Definition: Pages.py:272
def __init__(self, top_url, base_url, static_url=None, kwargs)
Definition: Pages.py:32
def AddPlotInformation(self, name, information)
Definition: Pages.py:566
def AddContent(self, comparison_datasets, comparison_info)
Definition: Pages.py:640
Definition: novas.h:112
def AddSubdirLinks(self, subdir)
Definition: Pages.py:588
def AddStaticContentLinks(self, additional_css=[], additional_js=[], insert_plot_js=False)
Definition: Pages.py:86
procfile open("FD_BRL_v0.txt")
def AddContent(self, validation_subdirs)
Definition: Pages.py:173
def AddGroupControls(self)
Definition: Pages.py:585
def AddHTML(self, pattern, replacement, destructive=False)
Definition: Pages.py:43
ifstream in
Definition: comparison.C:7
def AddSidebarSection(self, category)
Definition: Pages.py:155
def FormatOrNone(num, fmt)
Definition: Pages.py:596
def AddNavbarDropdowns(self, info, dropdowns)
Definition: Pages.py:130
Double_t sum
Definition: plot.C:31
char name[SIZE_OF_OBJ_NAME]
Definition: novas.h:116
T max(sqlite3 *const db, std::string const &table_name, std::string const &column_name)
Definition: statistics.h:66
def AddFigure(self, plot_name, plot_info_library, comparison_info=None)
Definition: Pages.py:307
def AddContent(self, validation_name, dataset_dirs, comparison_dirs)
Definition: Pages.py:196