16from typing
import List, Optional
23import validationcomparison
25from validationfunctions
import strip_ext, index_from_revision, get_style
27from validationrootobject
import RootObject
36 A Plotuple is either a Plot
or an N-Tuple
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 occurred
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
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 _file: The file,
in which the histogram
or the HMTL-table (
for
56 n-tuples) are stored (without the file extension!)
61 root_objects: List[RootObject],
66 The default constructor for a Plotuple-object.
67 @param root_objects: A list of Root-objects which belong
68 together (i.e. should be drawn into one histogram
or one table)
69 @param revisions: The list of revisions (Duh!)
73 self._work_folder = work_folder
76 self._root_objects = root_objects
79 self._revisions = revisions
83 self.warnings: List[str] = []
87 self._reference: Optional[RootObject] =
None
88 for root_object
in self._root_objects:
89 if root_object.is_reference:
90 self._reference = root_object
94 if self._reference
is None:
95 self.warnings = [
"No reference object"]
103 self._elements = [ro
for ro
in root_objects
if not ro.is_reference]
105 key=
lambda ro: ro.date
if ro.date
else 0, reverse=
True
113 self._newest = self._elements[0]
115 self._newest = self._reference
121 self.key = self._newest.key
124 self.type = self._newest.type
126 if self.type ==
"TNamed":
130 meta_fields = [
"description"]
131 if self._newest.object.GetName().lower().strip()
in meta_fields:
136 self._description = self._newest.description
140 self._check = self._newest.check
144 self._contact = self._newest.contact
150 self.package = self._newest.package
153 self.rootfile = self._newest.rootfile
158 self._comparison_result_long =
"n/a"
160 self.comparison_result =
"not_compared"
163 self._file: Optional[str] =
None
165 self._html_content: Optional[str] =
None
169 self._width: Optional[int] =
None
173 self._height: Optional[int] =
None
176 if self._description ==
"" or self._description
is None:
177 self._description =
"n/a"
178 self.warnings.append(
"No description")
179 if self._check ==
"" or self._check
is None:
181 self.warnings.append(
"No Check")
182 if self._contact ==
"" or self._contact
is None:
183 self._contact =
"n/a"
184 self.warnings.append(
"No Contact Person")
189 self._plot_folder = os.path.join(
191 work_folder, tags=revisions
195 os.makedirs(self._plot_folder, exist_ok=
True)
197 def has_reference(self):
199 @return True if a reference file
is found
for this plotuple
201 return self._reference
is not None
203 def create_plotuple(self):
205 Creates the histogram/table/image that belongs to this Plotuble-object.
208 if self.type ==
"TH1" or self.type ==
"TEfficiency":
209 self.create_histogram_plot(
"1D")
210 elif self.type ==
"TGraph":
211 self.create_graph_plot()
212 elif self.type ==
"TH2":
213 self.create_histogram_plot(
"2D")
215 elif self.type ==
"TNamed":
216 self.create_html_content()
217 elif self.type ==
"TNtuple":
218 self.create_ntuple_table_json()
219 elif self.type ==
"meta":
223 "Tried to create histogram/n-tuple, but received" "invalid type"
228 @return Returns true
if this plotuple has the expert option
230 return not self._mop.has_option(
"shifter")
232 def perform_comparison(self):
234 Takes the reference (self.reference.object) and the newest revision
235 (self.newest.object)
and a canvas. Performs a comparison of the
240 tester = validationcomparison.get_comparison(
241 self._reference.object, self._newest.object, self._mop
244 self._comparison_result_long = tester.comparison_result_long.format(
245 revision1=self._reference.revision, revision2=self._newest.revision
247 self.comparison_result = tester.comparison_result
249 def _set_background(self, canvas):
266 "warning": ROOT.kOrange + 1,
267 "equal": ROOT.kGreen - 3,
268 "not_compared": ROOT.kAzure - 2,
271 "error": ROOT.kRed - 9,
272 "warning": ROOT.kOrange - 9,
273 "equal": ROOT.kGreen - 10,
274 "not_compared": ROOT.kAzure - 9,
278 color = colors_expert[self.comparison_result]
280 color = colors[self.comparison_result]
282 canvas.SetFillColor(color)
283 canvas.GetFrame().SetFillColor(ROOT.kWhite)
285 def _draw_ref(self, canvas):
287 Takes the reference RootObject (self.reference.object)
288 and a (sub)canvas
and plots it
with the correct line-style etc.
289 @param canvas: Reference to the canvas on which we will draw the
293 self._remove_stats_tf1(self._reference.object)
296 self._reference.object.SetLineColor(ROOT.kBlack)
297 self._reference.object.SetLineWidth(2)
298 self._reference.object.SetLineStyle(1)
301 self._reference.object.SetFillColor(ROOT.kGray)
302 self._reference.object.SetFillStyle(1001)
305 self._draw_root_object(
307 self._reference.object,
308 self._reference.object.GetOption(),
311 canvas.GetFrame().SetFillColor(ROOT.kWhite)
314 def _remove_stats_tf1(obj):
317 tf1 = obj.FindObject(
"FitAndStats")
319 function_list = obj.GetListOfFunctions()
320 function_list.Remove(tf1)
322 def get_png_filename(self):
323 return f
"{strip_ext(self.rootfile)}_{self.key}.png"
325 def get_pdf_filename(self):
326 return f
"{strip_ext(self.rootfile)}_{self.key}.pdf"
329 def _draw_root_object(typ, obj, options):
331 Special handling of the ROOT Draw calls, as some
332 ROOT objects have a slightly different flavour.
335 if typ ==
"TEfficiency" or typ ==
"TGraph":
339 obj.DrawCopy(options)
341 def create_histogram_plot(self, mode):
343 Plots all histogram-objects in this Plotuple together
in one histogram,
344 which
is then given the name of the key.
345 @param mode: Determines whether it
is a one-
or
346 two-dimensional histogram.
347 Accepted values are
'1D' and '2D'
352 if mode
not in [
"1D",
"2D"]:
357 if mode ==
"2D" and len(self._elements) > 4:
361 canvas = ROOT.TCanvas(
"",
"", self._width, self._height)
364 legend = ROOT.TLegend(0.01, 0.01, 0.53, 0.06)
365 legend.SetNColumns(3)
366 legend.SetTextSize(0.02)
369 if self._mop.has_option(
"nostats"):
370 ROOT.gStyle.SetOptStat(
"")
372 ROOT.gStyle.SetOptStat(
"nemr")
378 self._reference
is not None
380 and not self._reference == self._newest
382 self.perform_comparison()
393 if not self._mop.has_option(
"nogrid"):
395 if self._mop.has_option(
"logx"):
397 if self._mop.has_option(
"logy"):
401 if self._reference
is not None:
402 self._draw_ref(canvas)
412 if len(self._root_objects) == 1:
414 elif len(self._root_objects) == 2:
419 y = int(math.floor((len(self._root_objects) + 1) / 2))
424 pad.SetFillColor(ROOT.kWhite)
427 if self._reference
is not None:
430 items_to_plot_count = len(self._elements)
433 for plot
in reversed(self._elements):
436 index = index_from_revision(plot.revision, self._work_folder)
437 style = get_style(index, items_to_plot_count)
439 self._remove_stats_tf1(plot.object)
442 plot.object.SetLineColor(style.GetLineColor())
443 plot.object.SetLineWidth(style.GetLineWidth())
444 plot.object.SetLineStyle(style.GetLineStyle())
451 additional_options = [
"C"]
452 additional_options = [
454 for option
in additional_options
455 if self._mop.has_option(option)
457 options_str = plot.object.GetOption() +
" ".join(
464 legend.AddEntry(plot.object, plot.revision)
466 self._draw_root_object(self.type, plot.object, options_str)
469 if not self._mop.has_option(
"nogrid"):
470 canvas.RedrawAxis(
"g")
473 canvas.GetFrame().SetFillColor(ROOT.kWhite)
480 if self._reference
is not None:
485 pad = canvas.cd(self._elements.index(plot) + i)
486 pad.SetFillColor(ROOT.kWhite)
489 additional_options =
""
490 for _
in [
"col",
"colz",
"cont",
"contz",
"box"]:
491 if self._mop.has_option(_):
492 additional_options +=
" " + _
495 self._draw_root_object(
498 plot.object.GetOption() + additional_options,
501 pad.GetFrame().SetFillColor(ROOT.kWhite)
504 title = pad.GetListOfPrimitives().FindObject(
"title")
506 title.SetTextColor(style.GetLineColor())
510 self._set_background(canvas)
512 canvas.GetFrame().SetFillColor(ROOT.kWhite)
515 if self._reference
is not None:
516 legend.AddEntry(self._reference.object, self._reference.revision)
521 canvas.Print(os.path.join(self._plot_folder, self.get_png_filename()))
522 canvas.Print(os.path.join(self._plot_folder, self.get_pdf_filename()))
524 self._file = os.path.join(
526 f
"{strip_ext(self.rootfile)}_{self.key}",
529 def create_graph_plot(self):
531 Plots as TGraph/TGraphErrors
538 canvas = ROOT.TCanvas(
"",
"", self._width, self._height)
541 if self._mop.has_option(
"nostats"):
542 ROOT.gStyle.SetOptStat(
"")
544 ROOT.gStyle.SetOptStat(
"nemr")
550 self._reference
is not None
552 and not self._reference == self._newest
554 self.perform_comparison()
556 if not self._mop.has_option(
"nogrid"):
558 if self._mop.has_option(
"logx"):
560 if self._mop.has_option(
"logy"):
568 if self._reference
is not None:
569 self._draw_ref(canvas)
572 items_to_plot_count = len(self._elements)
574 for plot
in reversed(self._elements):
577 index = index_from_revision(plot.revision, self._work_folder)
578 style = get_style(index, items_to_plot_count)
583 plot.object.SetLineColor(style.GetLineColor())
584 plot.object.SetLineWidth(style.GetLineWidth())
585 plot.object.SetLineStyle(style.GetLineStyle())
592 additional_options =
""
594 if self._mop.has_option(_):
595 additional_options +=
" " + _
598 self._draw_root_object(
601 plot.object.GetOption() + additional_options,
605 self._draw_root_object(self.type, plot.object,
"SAME")
608 if not self._mop.has_option(
"nogrid"):
609 canvas.RedrawAxis(
"g")
612 canvas.GetFrame().SetFillColor(ROOT.kWhite)
616 self._set_background(canvas)
619 canvas.Print(os.path.join(self._plot_folder, self.get_png_filename()))
620 canvas.Print(os.path.join(self._plot_folder, self.get_pdf_filename()))
622 self._file = os.path.join(
624 f
"{strip_ext(self.rootfile)}_{self.key}",
627 def create_html_content(self):
630 self._html_content =
""
632 for elem
in self._elements:
633 self._html_content += (
634 "<p>" + elem.revision +
"</p>" + elem.object.GetTitle()
641 def get_meta_information(self):
642 assert self.type ==
"meta"
643 key = self._newest.object.GetName().strip().lower()
644 value = self._newest.object.GetTitle()
647 def create_ntuple_table_json(self):
649 If the Plotuple-object contains n-tuples, this will create the
650 a JSON file, which is later converted to HTML by the javascript
651 function fillNtupleTable.
668 precision = self._mop.int_value(
"float-precision", default=4)
669 format_str = f
"{{0:.{precision}f}}"
673 return format_str.format(obj)
676 for key
in list(self._newest.object.keys()):
677 colum_names.append(key)
681 if self._reference
and "reference" in self._revisions:
682 json_nutple[
"reference"] = []
684 key_list = list(self._reference.object.keys())
685 for column
in colum_names:
686 if column
in key_list:
687 value_str = value2str(self._reference.object[column])
688 json_nutple[
"reference"].append((column, value_str))
690 json_nutple[
"reference"].append((column,
None))
693 for ntuple
in self._elements:
694 if ntuple.revision
not in json_nutple:
695 json_nutple[ntuple.revision] = []
697 for column
in colum_names:
698 if column
in ntuple.object:
699 value_str = value2str(ntuple.object[column])
700 json_nutple[ntuple.revision].append((column, value_str))
702 json_nutple[ntuple.revision].append((column,
None))
704 json_ntuple_file = os.path.join(
706 f
"{strip_ext(self.rootfile)}_{self.key}.json",
709 with open(json_ntuple_file,
"w+")
as json_file:
710 json.dump(json_nutple, json_file)
712 self._file = json_ntuple_file
714 def get_plot_title(self):
716 return os.path.basename(self._file).replace(
".",
"_").strip()
721 def create_json_object(self):
722 if self.type ==
"TNtuple":
724 title=self.get_plot_title(),
725 description=self._description,
726 contact=self._contact,
728 is_expert=self.is_expert(),
729 json_file_path=os.path.relpath(
734 elif self.type ==
"TNamed":
736 title=self.get_plot_title(),
737 description=self._description,
738 contact=self._contact,
740 is_expert=self.is_expert(),
741 html_content=self._html_content,
743 elif self.type ==
"meta":
747 title=self.get_plot_title(),
748 comparison_result=self.comparison_result,
749 comparison_text=self._comparison_result_long,
750 description=self._description,
751 contact=self._contact,
755 is_expert=self.is_expert(),
756 plot_path=os.path.relpath(
761 png_filename=self.get_png_filename(),
762 pdf_filename=self.get_pdf_filename(),
763 warnings=self.warnings,
def get_html_folder(output_base_dir)
def get_html_plots_tag_comparison_folder(output_base_dir, tags)