Belle II Software  release-08-01-10
validationplotuple.py
1 #!/usr/bin/env python3
2 
3 
10 
11 
12 # std
13 import os.path
14 import math
15 import json
16 from typing import List, Optional
17 
18 # 3rd
19 import ROOT
20 
21 # ours
22 import metaoptions
23 import validationcomparison
24 import validationpath
25 from validationfunctions import strip_ext, index_from_revision, get_style
26 import json_objects
27 from validationrootobject import RootObject
28 
29 
30 # todo: [Ref, low prio, medium work] Refactor into class with uniform interface
31 # and subclasses implementing actual functionality for Plot/Tuple etc.
32 # /klieret
33 class Plotuple:
34 
35  """!
36  A Plotuple is either a Plot or an N-Tuple
37 
38  @var work_folder: the work folder containing the results and plots
39  @var root_objects: A list of Root-objects which belong
40  together (i.e. should be drawn into one histogram or one table)
41  @var revisions: The list of revisions
42  @var warnings: A list of warnings that occured while creating the
43  plots/tables for this Plotuple object
44  @var reference: The reference RootObject for this Plotuple
45  @var elements: The elements (RootObject of different revisions) for this
46  Plotuple
47  @var newest: The newest element in elements
48  @var key: The key of the object within the corresponding ROOT file
49  @var type: The type of the elements (TH1, TH2, TNtuple)
50  @var description: The description of this Plotuple object
51  @var check: Hint how the Plotuple object should look like
52  @var contact: The contact person for this Plotuple object
53  @var package: The package to which this Plotuple object belongs to
54  @var rootfile: The rootfile to which the Plotuple object belongs to
55  @var chi2test_result: The result of the Chi^2-Test. By default, there is no
56  such result. If the Chi^2-Test has been performed, this variable holds
57  the information between which objects it has been performed.
58  @var pvalue: The p-value that the Chi^2-Test returned
59  @var file: The file, in which the histogram or the HMTL-table (for
60  n-tuples) are stored (without the file extension!)
61  """
62 
63  def __init__(
64  self,
65  root_objects: List[RootObject],
66  revisions: List[str],
67  work_folder: str,
68  ):
69  """!
70  The default constructor for a Plotuple-object.
71  @param root_objects: A list of Root-objects which belong
72  together (i.e. should be drawn into one histogram or one table)
73  @param revisions: The list of revisions (Duh!)
74  """
75 
76  # the work folder containing the results and plots
77  self._work_folder = work_folder
78 
79  # The list of Root objects in this Plotuple-object
80  self._root_objects = root_objects
81 
82  # The list of revisions
83  self._revisions = revisions
84 
85  # A list of all problems that occured with this Plotuple,
86  # e.g. missing reference object, missing meta-information...
87  self.warnings: List[str] = []
88 
89  # Find the reference element. If we can't find one, set it to 'None'
90  # The reference-object for this Plotuple object
91  self._reference: Optional[RootObject] = None
92  for root_object in self._root_objects:
93  if root_object.is_reference:
94  self._reference = root_object
95  break
96 
97  # If we couldn't find a reference element, add that to warnings
98  if self._reference is None:
99  self.warnings = ["No reference object"]
100 
101  # All elements of the Plotuple that are not the reference-object
102  # Get the elements, i.e. all RootObjects except for the
103  # reference object. May be either histograms or n-tuples.
104  # Note that the reference doesn't have a date set (and if we only plot
105  # the reference, then is_reference is probably not set), so we have
106  # to be careful of how to sort
107  self._elements = [ro for ro in root_objects if not ro.is_reference]
108  self._elements.sort(
109  key=lambda ro: ro.date if ro.date else 0, reverse=True
110  )
111 
112  # The newest element, i.e. the element belonging the revision
113  # whose data were created most recently.
114  # Should always be self.element[0], except if there is only a
115  # reference object
116  if self._elements:
117  self._newest = self._elements[0]
118  else:
119  self._newest = self._reference
120 
121  # All available meta-information about the plotuple object:
122 
123  # The key (more precisely: the name of the key) that all elements of
124  # this Plotuple object share
125  self.key = self._newest.key
126 
127  # The type of the elements in this Plotuple object
128  self.type = self._newest.type
129 
130  if self.type == "TNamed":
131  # Sometimes, we use TNamed to encode extra information about the
132  # ROOT file. In order to avoid that this will be plotted, we
133  # catch it here and assign it the type 'Meta'
134  meta_fields = ["description"]
135  if self._newest.object.GetName().lower().strip() in meta_fields:
136  self.type = "meta"
137 
138  # The description of the histogram/n-tuple which this Plotuple object
139  # will yield
140  self._description = self._newest.description
141 
142  # The 'Check for ...'-guideline for the histogram/n-tuple which this
143  # Plotuple object will yield
144  self._check = self._newest.check
145 
146  # A contact person for the histogram/n-tuple which this Plotuple object
147  # will yield
148  self._contact = self._newest.contact
149 
150  # MetaOptionParser for the meta-options for this Plotuple object
151  self._mop = metaoptions.MetaOptionParser(self._newest.metaoptions)
152 
153  # The package to which the elements in this Plotuple object belong
154  self.package = self._newest.package
155 
156  # The root file to which the elements in this Plotuple object belong
157  self.rootfile = self._newest.rootfile
158 
159  # The result of the Chi^2-Test. By default, there is no such result.
160  # If the Chi^2-Test has been performed, this variable holds between
161  # which objects it has been performed.
162  self._comparison_result_long = "n/a"
163 
164  self.comparison_result = "not_compared"
165 
166  # The json file, in which the ntuple information is stored
167  self._file: Optional[str] = None
168 
169  self._html_content: Optional[str] = None
170 
171 
173  self._width: Optional[int] = None
174 
175 
177  self._height: Optional[int] = None
178 
179  # Deal with incomplete information
180  if self._description == "" or self._description is None:
181  self._description = "n/a"
182  self.warnings.append("No description")
183  if self._check == "" or self._check is None:
184  self._check = "n/a"
185  self.warnings.append("No Check")
186  if self._contact == "" or self._contact is None:
187  self._contact = "n/a"
188  self.warnings.append("No Contact Person")
189 
190 
193  self._plot_folder = os.path.join(
195  work_folder, tags=revisions
196  ),
197  self.package,
198  )
199  os.makedirs(self._plot_folder, exist_ok=True)
200 
201  def has_reference(self):
202  """!
203  @return True if a reference file is found for this plotuple
204  """
205  return self._reference is not None
206 
207  def create_plotuple(self):
208  """!
209  Creates the histogram/table/image that belongs to this Plotuble-object.
210  """
211 
212  if self.type == "TH1" or self.type == "TEfficiency":
213  self.create_histogram_plot("1D")
214  elif self.type == "TGraph":
215  self.create_graph_plot()
216  elif self.type == "TH2":
217  self.create_histogram_plot("2D")
218  # used to store HTML user content
219  elif self.type == "TNamed":
220  self.create_html_content()
221  elif self.type == "TASImage":
222  self.create_image_plot()
223  elif self.type == "TNtuple":
224  self.create_ntuple_table_json()
225  elif self.type == "meta":
226  pass
227  else:
228  raise ValueError(
229  "Tried to create histogram/n-tuple, but received" "invalid type"
230  )
231 
232  def is_expert(self):
233  """!
234  @return Returns true if this plotuple has the expert option
235  """
236  return not self._mop.has_option("shifter")
237 
238  def perform_comparison(self):
239  """!
240  Takes the reference (self.reference.object) and the newest revision
241  (self.newest.object) and a canvas. Performs a comparison of the
242  two objects.
243  @return: None
244  """
245 
246  tester = validationcomparison.get_comparison(
247  self._reference.object, self._newest.object, self._mop
248  )
249 
250  self._comparison_result_long = tester.comparison_result_long.format(
251  revision1=self._reference.revision, revision2=self._newest.revision
252  )
253  self.comparison_result = tester.comparison_result
254 
255  def _set_background(self, canvas):
256 
257  # kRed #FF0000 Red
258  # kRed - 9 #FF9999 Sweet pink
259 
260  # kOrange + 1 #FF9832 Sun
261  # kOrange - 9 #FFCC9A Manhattan
262 
263  # kGreen - 3 #33CC33 Lime green
264  # kGreen - 10 #CCFFCC Chinook
265 
266  # kAzure #0032FE Medium blue
267  # kAzure - 2 #3265FE Medium slate blue
268  # kAzure - 9 #98CBFF Jordy blue
269 
270  colors = {
271  "error": ROOT.kRed,
272  "warning": ROOT.kOrange + 1,
273  "equal": ROOT.kGreen - 3,
274  "not_compared": ROOT.kAzure - 2,
275  }
276  colors_expert = {
277  "error": ROOT.kRed - 9,
278  "warning": ROOT.kOrange - 9,
279  "equal": ROOT.kGreen - 10,
280  "not_compared": ROOT.kAzure - 9,
281  }
282 
283  if self.is_expert():
284  color = colors_expert[self.comparison_result]
285  else:
286  color = colors[self.comparison_result]
287 
288  canvas.SetFillColor(color)
289  canvas.GetFrame().SetFillColor(ROOT.kWhite)
290 
291  def _draw_ref(self, canvas):
292  """!
293  Takes the reference RootObject (self.reference.object)
294  and a (sub)canvas and plots it with the correct line-style etc.
295  @param canvas: Reference to the canvas on which we will draw the
296  reference object.
297  @return. None
298  """
299  self._remove_stats_tf1(self._reference.object)
300 
301  # Line is thick and black
302  self._reference.object.SetLineColor(ROOT.kBlack)
303  self._reference.object.SetLineWidth(2)
304  self._reference.object.SetLineStyle(1)
305 
306  # Area under the curve is solid gray
307  self._reference.object.SetFillColor(ROOT.kGray)
308  self._reference.object.SetFillStyle(1001)
309 
310  # Draw the reference on the canvas
311  self._draw_root_object(
312  self.type,
313  self._reference.object,
314  self._reference.object.GetOption(),
315  )
316  canvas.Update()
317  canvas.GetFrame().SetFillColor(ROOT.kWhite)
318 
319  @staticmethod
320  def _remove_stats_tf1(obj):
321  # removed TF1s which might have been added by validation scripts
322  # in tracking/scripts/tracking/validation/plot.py:1597
323  tf1 = obj.FindObject("FitAndStats")
324  if tf1:
325  function_list = obj.GetListOfFunctions()
326  function_list.Remove(tf1)
327 
328  # TODO: is this actually used or can it be removed ?
329  def create_image_plot(self):
330  """!
331  Creates image plot for TASImage-objects.
332  @return: None
333  """
334 
335  # Create a ROOT canvas on which we will draw our histograms
336  self._width = 700
337  if len(self._elements) > 4:
338  canvas = ROOT.TCanvas("", "", 700, 1050)
339  self._height = 1050
340  else:
341  canvas = ROOT.TCanvas("", "", 700, 525)
342  self._height = 525
343 
344  # Split the canvas into enough parts to fit all image_objects
345  # Find numbers x and y so that x*y = N (number of histograms to be
346  # plotted), and x,y close to sqrt(N)
347 
348  if len(self._root_objects) == 1:
349  x = y = 1
350  elif len(self._root_objects) == 2:
351  x = 2
352  y = 1
353  else:
354  x = 2
355  y = int(math.floor((len(self._root_objects) + 1) / 2))
356 
357  # Actually split the canvas and go to the first pad ('sub-canvas')
358  canvas.Divide(x, y)
359  pad = canvas.cd(1)
360  pad.SetFillColor(ROOT.kWhite)
361 
362  # If there is a reference object, plot it first
363  if self._reference is not None:
364  self._draw_ref(pad)
365 
366  # Now draw the normal plots
367  items_to_plot_count = len(self._elements)
368  for plot in reversed(self._elements):
369 
370  # Get the index of the current plot
371  index = index_from_revision(plot.revision, self._work_folder)
372  style = get_style(index, items_to_plot_count)
373 
374  self._remove_stats_tf1(plot.object)
375 
376  # Set line properties accordingly
377  plot.object.SetLineColor(style.GetLineColor())
378  plot.object.SetLineWidth(style.GetLineWidth())
379  plot.object.SetLineStyle(style.GetLineStyle())
380 
381  # Switch to the correct sub-panel of the canvas. If a ref-plot
382  # exists, we have to go one panel further compared to the
383  # no-ref-case
384  if self._reference is not None:
385  i = 2
386  else:
387  i = 1
388 
389  pad = canvas.cd(self._elements.index(plot) + i)
390  pad.SetFillColor(ROOT.kWhite)
391 
392  # Draw the reference on the canvas
393  self._draw_root_object(
394  self.type, plot.object, plot.object.GetOption()
395  )
396  pad.Update()
397  pad.GetFrame().SetFillColor(ROOT.kWhite)
398 
399  # Write the title in the correct color
400  title = pad.GetListOfPrimitives().FindObject("title")
401  if title:
402  title.SetTextColor(style.GetLineColor())
403 
404  # Save the plot as PNG and PDF
405  canvas.Print(os.path.join(self._plot_folder, self.get_png_filename()))
406  canvas.Print(os.path.join(self._plot_folder, self.get_pdf_filename()))
407 
408  self._file = os.path.join(
409  self._plot_folder,
410  "{}_{}".format(strip_ext(self.rootfile), self.key),
411  )
412 
413  def get_png_filename(self):
414  return "{}_{}.png".format(strip_ext(self.rootfile), self.key)
415 
416  def get_pdf_filename(self):
417  return "{}_{}.pdf".format(strip_ext(self.rootfile), self.key)
418 
419  @staticmethod
420  def _draw_root_object(typ, obj, options):
421  """
422  Special handling of the ROOT Draw calls, as some
423  ROOT objects have a slightly differen flavour.
424  """
425 
426  if typ == "TEfficiency" or typ == "TGraph":
427  # TEff does not provide DrawCopy
428  obj.Draw(options)
429  else:
430  obj.DrawCopy(options)
431 
432  def create_histogram_plot(self, mode):
433  """!
434  Plots all histogram-objects in this Plotuple together in one histogram,
435  which is then given the name of the key.
436  @param mode: Determines whether it is a one- or
437  two-dimensional histogram.
438  Accepted values are '1D' and '2D'
439  @return: None
440  """
441 
442  # If we don't get a valid 'mode', we can stop right here
443  if mode not in ["1D", "2D"]:
444  return
445 
446  # Create a ROOT canvas on which we will draw our histograms
447  self._width = 700
448  if mode == "2D" and len(self._elements) > 4:
449  self._height = 1050
450  else:
451  self._height = 525
452  canvas = ROOT.TCanvas("", "", self._width, self._height)
453 
454  # Create a ROOT Legend
455  legend = ROOT.TLegend(0.01, 0.01, 0.53, 0.06)
456  legend.SetNColumns(3)
457  legend.SetTextSize(0.02)
458 
459  # Allow possibility to turn off the stats box
460  if self._mop.has_option("nostats"):
461  ROOT.gStyle.SetOptStat("")
462  else:
463  ROOT.gStyle.SetOptStat("nemr")
464 
465  # If there is a reference object, and the list of plots is not empty,
466  # perform a Chi^2-Test on the reference object and the first object in
467  # the plot list:
468  if (
469  self._reference is not None
470  and self._newest
471  and not self._reference == self._newest
472  ):
473  self.perform_comparison()
474 
475  # A variable which holds whether we
476  # have drawn on the canvas already or not
477  # (only used for the 1D case)
478  drawn = False
479 
480  # Now we distinguish between 1D and 2D histograms
481  # If we have a 1D histogram
482  if mode == "1D":
483 
484  if not self._mop.has_option("nogrid"):
485  canvas.SetGrid()
486  if self._mop.has_option("logx"):
487  canvas.SetLogx()
488  if self._mop.has_option("logy"):
489  canvas.SetLogy()
490 
491  # If there is a reference object, plot it first
492  if self._reference is not None:
493  self._draw_ref(canvas)
494  drawn = True
495 
496  # If we have a 2D histogram
497  elif mode == "2D":
498 
499  # Split the canvas into enough parts to fit all histogram_objects
500  # Find numbers x and y so that x*y = N (number of histograms to be
501  # plotted), and x,y close to sqrt(N)
502 
503  if len(self._root_objects) == 1:
504  x = y = 1
505  elif len(self._root_objects) == 2:
506  x = 2
507  y = 1
508  else:
509  x = 2
510  y = int(math.floor((len(self._root_objects) + 1) / 2))
511 
512  # Actually split the canvas and go to the first pad ('sub-canvas')
513  canvas.Divide(x, y)
514  pad = canvas.cd(1)
515  pad.SetFillColor(ROOT.kWhite)
516 
517  # If there is a reference object, plot it first
518  if self._reference is not None:
519  self._draw_ref(pad)
520 
521  items_to_plot_count = len(self._elements)
522 
523  # Now draw the normal plots
524  for plot in reversed(self._elements):
525 
526  # Get the index of the current plot
527  index = index_from_revision(plot.revision, self._work_folder)
528  style = get_style(index, items_to_plot_count)
529 
530  self._remove_stats_tf1(plot.object)
531 
532  # Set line properties accordingly
533  plot.object.SetLineColor(style.GetLineColor())
534  plot.object.SetLineWidth(style.GetLineWidth())
535  plot.object.SetLineStyle(style.GetLineStyle())
536 
537  # If we have a one-dimensional histogram
538  if mode == "1D":
539  if not drawn:
540  # Get additional options for 1D histograms
541  # (Intersection with self.metaoptions)
542  additional_options = ["C"]
543  additional_options = [
544  option
545  for option in additional_options
546  if self._mop.has_option(option)
547  ]
548  options_str = plot.object.GetOption() + " ".join(
549  additional_options
550  )
551  drawn = True
552  else:
553  options_str = "SAME"
554 
555  legend.AddEntry(plot.object, plot.revision)
556 
557  self._draw_root_object(self.type, plot.object, options_str)
558 
559  # redraw grid ontop of histogram, if selected
560  if not self._mop.has_option("nogrid"):
561  canvas.RedrawAxis("g")
562 
563  canvas.Update()
564  canvas.GetFrame().SetFillColor(ROOT.kWhite)
565 
566  # If we have a two-dimensional histogram
567  elif mode == "2D":
568  # Switch to the correct sub-panel of the canvas. If a ref-plot
569  # exists, we have to go one panel further compared to the
570  # no-ref-case
571  if self._reference is not None:
572  i = 2
573  else:
574  i = 1
575 
576  pad = canvas.cd(self._elements.index(plot) + i)
577  pad.SetFillColor(ROOT.kWhite)
578 
579  # Get additional options for 2D histograms
580  additional_options = ""
581  for _ in ["col", "colz", "cont", "contz", "box"]:
582  if self._mop.has_option(_):
583  additional_options += " " + _
584 
585  # Draw the reference on the canvas
586  self._draw_root_object(
587  self.type,
588  plot.object,
589  plot.object.GetOption() + additional_options,
590  )
591  pad.Update()
592  pad.GetFrame().SetFillColor(ROOT.kWhite)
593 
594  # Write the title in the correct color
595  title = pad.GetListOfPrimitives().FindObject("title")
596  if title:
597  title.SetTextColor(style.GetLineColor())
598 
599  if self._newest:
600  # if there is at least one revision
601  self._set_background(canvas)
602 
603  canvas.GetFrame().SetFillColor(ROOT.kWhite)
604 
605  # Add reference legend entry last for neatness
606  if self._reference is not None:
607  legend.AddEntry(self._reference.object, 'reference')
608 
609  legend.Draw()
610 
611  # Save the plot as PNG and PDF
612  canvas.Print(os.path.join(self._plot_folder, self.get_png_filename()))
613  canvas.Print(os.path.join(self._plot_folder, self.get_pdf_filename()))
614 
615  self._file = os.path.join(
616  self._plot_folder,
617  "{}_{}".format(strip_ext(self.rootfile), self.key),
618  )
619 
620  def create_graph_plot(self):
621  """!
622  Plots as TGraph/TGraphErrors
623  @return: None
624  """
625 
626  # Create a ROOT canvas on which we will draw our plots
627  self._width = 700
628  self._height = 525
629  canvas = ROOT.TCanvas("", "", self._width, self._height)
630 
631  # Allow possibility to turn off the stats box
632  if self._mop.has_option("nostats"):
633  ROOT.gStyle.SetOptStat("")
634  else:
635  ROOT.gStyle.SetOptStat("nemr")
636 
637  # If there is a reference object, and the list of plots is not empty,
638  # perform a Chi^2-Test on the reference object and the first object in
639  # the plot list:
640  if (
641  self._reference is not None
642  and self._newest
643  and not self._reference == self._newest
644  ):
645  self.perform_comparison()
646 
647  if not self._mop.has_option("nogrid"):
648  canvas.SetGrid()
649  if self._mop.has_option("logx"):
650  canvas.SetLogx()
651  if self._mop.has_option("logy"):
652  canvas.SetLogy()
653 
654  # A variable which holds whether we
655  # have drawn on the canvas already or not
656  drawn = False
657 
658  # If there is a reference object, plot it first
659  if self._reference is not None:
660  self._draw_ref(canvas)
661  drawn = True
662 
663  items_to_plot_count = len(self._elements)
664  # Now draw the normal plots
665  for plot in reversed(self._elements):
666 
667  # Get the index of the current plot
668  index = index_from_revision(plot.revision, self._work_folder)
669  style = get_style(index, items_to_plot_count)
670 
671  # self.remove_stats_tf1(plot.object)
672 
673  # Set line properties accordingly
674  plot.object.SetLineColor(style.GetLineColor())
675  plot.object.SetLineWidth(style.GetLineWidth())
676  plot.object.SetLineStyle(style.GetLineStyle())
677 
678  # If we have a one-dimensional histogram
679  if not drawn:
680 
681  # todo: refactor like in plot hist
682  # Get additional options for 1D histograms
683  additional_options = ""
684  for _ in ["C"]:
685  if self._mop.has_option(_):
686  additional_options += " " + _
687 
688  # Draw the reference on the canvas
689  self._draw_root_object(
690  self.type,
691  plot.object,
692  plot.object.GetOption() + additional_options,
693  )
694  drawn = True
695  else:
696  self._draw_root_object(self.type, plot.object, "SAME")
697 
698  # redraw grid ontop of histogram, if selected
699  if not self._mop.has_option("nogrid"):
700  canvas.RedrawAxis("g")
701 
702  canvas.Update()
703  canvas.GetFrame().SetFillColor(ROOT.kWhite)
704 
705  if self._newest:
706  # if there is at least one revision
707  self._set_background(canvas)
708 
709  # Save the plot as PNG and PDF
710  canvas.Print(os.path.join(self._plot_folder, self.get_png_filename()))
711  canvas.Print(os.path.join(self._plot_folder, self.get_pdf_filename()))
712 
713  self._file = os.path.join(
714  self._plot_folder,
715  "{}_{}".format(strip_ext(self.rootfile), self.key),
716  )
717 
718  def create_html_content(self):
719 
720  # self.elements
721  self._html_content = ""
722 
723  for elem in self._elements:
724  self._html_content += (
725  "<p>" + elem.revision + "</p>" + elem.object.GetTitle()
726  )
727 
728  # there is no file storing this, because it is directly in the json
729  # file
730  self._file = None
731 
732  def get_meta_information(self):
733  assert self.type == "meta"
734  key = self._newest.object.GetName().strip().lower()
735  value = self._newest.object.GetTitle()
736  return key, value
737 
738  def create_ntuple_table_json(self):
739  """!
740  If the Plotuple-object contains n-tuples, this will create the
741  a JSON file, which is later converted to HTML by the javascript
742  function fillNtupleTable.
743  """
744 
745  json_nutple = {}
746 
747  # The dictionary will have the following form
748  # {
749  # "reference (if exist)": [
750  # ('variable 1', 'reference value for variable 1'),
751  # ('variable 2', 'reference value for variable 2'),
752  # ...
753  # ],
754  # "revision": [
755  # ...
756  # ]
757  # }
758 
759  precision = self._mop.int_value("float-precision", default=4)
760  format_str = f"{{0:.{precision}f}}"
761 
762  def value2str(obj):
763  # assuming that I have a float
764  return format_str.format(obj)
765 
766  colum_names = []
767  for key in list(self._newest.object.keys()):
768  colum_names.append(key)
769 
770  # If there is a reference object, print the reference values as the
771  # first row of the table
772  if self._reference and "reference" in self._revisions:
773  json_nutple["reference"] = []
774 
775  key_list = list(self._reference.object.keys())
776  for column in colum_names:
777  if column in key_list:
778  value_str = value2str(self._reference.object[column])
779  json_nutple["reference"].append((column, value_str))
780  else:
781  json_nutple["reference"].append((column, None))
782 
783  # Now print the values for all other revisions
784  for ntuple in self._elements:
785  if ntuple.revision not in json_nutple:
786  json_nutple[ntuple.revision] = []
787 
788  for column in colum_names:
789  if column in ntuple.object:
790  value_str = value2str(ntuple.object[column])
791  json_nutple[ntuple.revision].append((column, value_str))
792  else:
793  json_nutple[ntuple.revision].append((column, None))
794 
795  json_ntuple_file = os.path.join(
796  self._plot_folder,
797  "{}_{}.json".format(strip_ext(self.rootfile), self.key),
798  )
799 
800  with open(json_ntuple_file, "w+") as json_file:
801  json.dump(json_nutple, json_file)
802 
803  self._file = json_ntuple_file
804 
805  def get_plot_title(self):
806  if self._file:
807  return os.path.basename(self._file).replace(".", "_").strip()
808  else:
809  # this is for html content which is not stored in any file
810  return self.key
811 
812  def create_json_object(self):
813  if self.type == "TNtuple":
815  title=self.get_plot_title(),
816  description=self._description,
817  contact=self._contact,
818  check=self._check,
819  is_expert=self.is_expert(),
820  json_file_path=os.path.relpath(
821  self._file,
822  validationpath.get_html_folder(self._work_folder),
823  ),
824  )
825  elif self.type == "TNamed":
827  title=self.get_plot_title(),
828  description=self._description,
829  contact=self._contact,
830  check=self._check,
831  is_expert=self.is_expert(),
832  html_content=self._html_content,
833  )
834  elif self.type == "meta":
835  return None
836  else:
838  title=self.get_plot_title(),
839  comparison_result=self.comparison_result,
840  comparison_text=self._comparison_result_long,
841  description=self._description,
842  contact=self._contact,
843  check=self._check,
844  height=self._height,
845  width=self._width,
846  is_expert=self.is_expert(),
847  plot_path=os.path.relpath(
848  self._plot_folder,
849  validationpath.get_html_folder(self._work_folder),
850  )
851  + "/",
852  png_filename=self.get_png_filename(),
853  pdf_filename=self.get_pdf_filename(),
854  warnings=self.warnings,
855  )
def get_html_plots_tag_comparison_folder(output_base_dir, tags)
def get_html_folder(output_base_dir)