16 from typing
import List, Optional
23 import validationcomparison
25 from validationfunctions
import strip_ext, index_from_revision, get_style
27 from 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 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
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!)
65 root_objects: List[RootObject],
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!)
77 self._work_folder = work_folder
80 self._root_objects = root_objects
83 self._revisions = revisions
87 self.warnings: List[str] = []
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
98 if self._reference
is None:
99 self.warnings = [
"No reference object"]
107 self._elements = [ro
for ro
in root_objects
if not ro.is_reference]
109 key=
lambda ro: ro.date
if ro.date
else 0, reverse=
True
117 self._newest = self._elements[0]
119 self._newest = self._reference
125 self.key = self._newest.key
128 self.type = self._newest.type
130 if self.type ==
"TNamed":
134 meta_fields = [
"description"]
135 if self._newest.object.GetName().lower().strip()
in meta_fields:
140 self._description = self._newest.description
144 self._check = self._newest.check
148 self._contact = self._newest.contact
154 self.package = self._newest.package
157 self.rootfile = self._newest.rootfile
162 self._comparison_result_long =
"n/a"
164 self.comparison_result =
"not_compared"
167 self._file: Optional[str] =
None
169 self._html_content: Optional[str] =
None
173 self._width: Optional[int] =
None
177 self._height: Optional[int] =
None
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:
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")
193 self._plot_folder = os.path.join(
195 work_folder, tags=revisions
199 os.makedirs(self._plot_folder, exist_ok=
True)
201 def has_reference(self):
203 @return True if a reference file is found for this plotuple
205 return self._reference
is not None
207 def create_plotuple(self):
209 Creates the histogram/table/image that belongs to this Plotuble-object.
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")
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":
229 "Tried to create histogram/n-tuple, but received" "invalid type"
234 @return Returns true if this plotuple has the expert option
236 return not self._mop.has_option(
"shifter")
238 def perform_comparison(self):
240 Takes the reference (self.reference.object) and the newest revision
241 (self.newest.object) and a canvas. Performs a comparison of the
246 tester = validationcomparison.get_comparison(
247 self._reference.object, self._newest.object, self._mop
250 self._comparison_result_long = tester.comparison_result_long.format(
251 revision1=self._reference.revision, revision2=self._newest.revision
253 self.comparison_result = tester.comparison_result
255 def _set_background(self, canvas):
272 "warning": ROOT.kOrange + 1,
273 "equal": ROOT.kGreen - 3,
274 "not_compared": ROOT.kAzure - 2,
277 "error": ROOT.kRed - 9,
278 "warning": ROOT.kOrange - 9,
279 "equal": ROOT.kGreen - 10,
280 "not_compared": ROOT.kAzure - 9,
284 color = colors_expert[self.comparison_result]
286 color = colors[self.comparison_result]
288 canvas.SetFillColor(color)
289 canvas.GetFrame().SetFillColor(ROOT.kWhite)
291 def _draw_ref(self, canvas):
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
299 self._remove_stats_tf1(self._reference.object)
302 self._reference.object.SetLineColor(ROOT.kBlack)
303 self._reference.object.SetLineWidth(2)
304 self._reference.object.SetLineStyle(1)
307 self._reference.object.SetFillColor(ROOT.kGray)
308 self._reference.object.SetFillStyle(1001)
311 self._draw_root_object(
313 self._reference.object,
314 self._reference.object.GetOption(),
317 canvas.GetFrame().SetFillColor(ROOT.kWhite)
320 def _remove_stats_tf1(obj):
323 tf1 = obj.FindObject(
"FitAndStats")
325 function_list = obj.GetListOfFunctions()
326 function_list.Remove(tf1)
329 def create_image_plot(self):
331 Creates image plot for TASImage-objects.
337 if len(self._elements) > 4:
338 canvas = ROOT.TCanvas(
"",
"", 700, 1050)
341 canvas = ROOT.TCanvas(
"",
"", 700, 525)
348 if len(self._root_objects) == 1:
350 elif len(self._root_objects) == 2:
355 y = int(math.floor((len(self._root_objects) + 1) / 2))
360 pad.SetFillColor(ROOT.kWhite)
363 if self._reference
is not None:
367 items_to_plot_count = len(self._elements)
368 for plot
in reversed(self._elements):
371 index = index_from_revision(plot.revision, self._work_folder)
372 style = get_style(index, items_to_plot_count)
374 self._remove_stats_tf1(plot.object)
377 plot.object.SetLineColor(style.GetLineColor())
378 plot.object.SetLineWidth(style.GetLineWidth())
379 plot.object.SetLineStyle(style.GetLineStyle())
384 if self._reference
is not None:
389 pad = canvas.cd(self._elements.index(plot) + i)
390 pad.SetFillColor(ROOT.kWhite)
393 self._draw_root_object(
394 self.type, plot.object, plot.object.GetOption()
397 pad.GetFrame().SetFillColor(ROOT.kWhite)
400 title = pad.GetListOfPrimitives().FindObject(
"title")
402 title.SetTextColor(style.GetLineColor())
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()))
408 self._file = os.path.join(
410 "{}_{}".format(strip_ext(self.rootfile), self.key),
413 def get_png_filename(self):
414 return "{}_{}.png".format(strip_ext(self.rootfile), self.key)
416 def get_pdf_filename(self):
417 return "{}_{}.pdf".format(strip_ext(self.rootfile), self.key)
420 def _draw_root_object(typ, obj, options):
422 Special handling of the ROOT Draw calls, as some
423 ROOT objects have a slightly differen flavour.
426 if typ ==
"TEfficiency" or typ ==
"TGraph":
430 obj.DrawCopy(options)
432 def create_histogram_plot(self, mode):
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'
443 if mode
not in [
"1D",
"2D"]:
448 if mode ==
"2D" and len(self._elements) > 4:
452 canvas = ROOT.TCanvas(
"",
"", self._width, self._height)
455 legend = ROOT.TLegend(0.01, 0.01, 0.53, 0.06)
456 legend.SetNColumns(3)
457 legend.SetTextSize(0.02)
460 if self._mop.has_option(
"nostats"):
461 ROOT.gStyle.SetOptStat(
"")
463 ROOT.gStyle.SetOptStat(
"nemr")
469 self._reference
is not None
471 and not self._reference == self._newest
473 self.perform_comparison()
484 if not self._mop.has_option(
"nogrid"):
486 if self._mop.has_option(
"logx"):
488 if self._mop.has_option(
"logy"):
492 if self._reference
is not None:
493 self._draw_ref(canvas)
503 if len(self._root_objects) == 1:
505 elif len(self._root_objects) == 2:
510 y = int(math.floor((len(self._root_objects) + 1) / 2))
515 pad.SetFillColor(ROOT.kWhite)
518 if self._reference
is not None:
521 items_to_plot_count = len(self._elements)
524 for plot
in reversed(self._elements):
527 index = index_from_revision(plot.revision, self._work_folder)
528 style = get_style(index, items_to_plot_count)
530 self._remove_stats_tf1(plot.object)
533 plot.object.SetLineColor(style.GetLineColor())
534 plot.object.SetLineWidth(style.GetLineWidth())
535 plot.object.SetLineStyle(style.GetLineStyle())
542 additional_options = [
"C"]
543 additional_options = [
545 for option
in additional_options
546 if self._mop.has_option(option)
548 options_str = plot.object.GetOption() +
" ".join(
555 legend.AddEntry(plot.object, plot.revision)
557 self._draw_root_object(self.type, plot.object, options_str)
560 if not self._mop.has_option(
"nogrid"):
561 canvas.RedrawAxis(
"g")
564 canvas.GetFrame().SetFillColor(ROOT.kWhite)
571 if self._reference
is not None:
576 pad = canvas.cd(self._elements.index(plot) + i)
577 pad.SetFillColor(ROOT.kWhite)
580 additional_options =
""
581 for _
in [
"col",
"colz",
"cont",
"contz",
"box"]:
582 if self._mop.has_option(_):
583 additional_options +=
" " + _
586 self._draw_root_object(
589 plot.object.GetOption() + additional_options,
592 pad.GetFrame().SetFillColor(ROOT.kWhite)
595 title = pad.GetListOfPrimitives().FindObject(
"title")
597 title.SetTextColor(style.GetLineColor())
601 self._set_background(canvas)
603 canvas.GetFrame().SetFillColor(ROOT.kWhite)
606 if self._reference
is not None:
607 legend.AddEntry(self._reference.object,
'reference')
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()))
615 self._file = os.path.join(
617 "{}_{}".format(strip_ext(self.rootfile), self.key),
620 def create_graph_plot(self):
622 Plots as TGraph/TGraphErrors
629 canvas = ROOT.TCanvas(
"",
"", self._width, self._height)
632 if self._mop.has_option(
"nostats"):
633 ROOT.gStyle.SetOptStat(
"")
635 ROOT.gStyle.SetOptStat(
"nemr")
641 self._reference
is not None
643 and not self._reference == self._newest
645 self.perform_comparison()
647 if not self._mop.has_option(
"nogrid"):
649 if self._mop.has_option(
"logx"):
651 if self._mop.has_option(
"logy"):
659 if self._reference
is not None:
660 self._draw_ref(canvas)
663 items_to_plot_count = len(self._elements)
665 for plot
in reversed(self._elements):
668 index = index_from_revision(plot.revision, self._work_folder)
669 style = get_style(index, items_to_plot_count)
674 plot.object.SetLineColor(style.GetLineColor())
675 plot.object.SetLineWidth(style.GetLineWidth())
676 plot.object.SetLineStyle(style.GetLineStyle())
683 additional_options =
""
685 if self._mop.has_option(_):
686 additional_options +=
" " + _
689 self._draw_root_object(
692 plot.object.GetOption() + additional_options,
696 self._draw_root_object(self.type, plot.object,
"SAME")
699 if not self._mop.has_option(
"nogrid"):
700 canvas.RedrawAxis(
"g")
703 canvas.GetFrame().SetFillColor(ROOT.kWhite)
707 self._set_background(canvas)
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()))
713 self._file = os.path.join(
715 "{}_{}".format(strip_ext(self.rootfile), self.key),
718 def create_html_content(self):
721 self._html_content =
""
723 for elem
in self._elements:
724 self._html_content += (
725 "<p>" + elem.revision +
"</p>" + elem.object.GetTitle()
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()
738 def create_ntuple_table_json(self):
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.
759 precision = self._mop.int_value(
"float-precision", default=4)
760 format_str = f
"{{0:.{precision}f}}"
764 return format_str.format(obj)
767 for key
in list(self._newest.object.keys()):
768 colum_names.append(key)
772 if self._reference
and "reference" in self._revisions:
773 json_nutple[
"reference"] = []
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))
781 json_nutple[
"reference"].append((column,
None))
784 for ntuple
in self._elements:
785 if ntuple.revision
not in json_nutple:
786 json_nutple[ntuple.revision] = []
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))
793 json_nutple[ntuple.revision].append((column,
None))
795 json_ntuple_file = os.path.join(
797 "{}_{}.json".format(strip_ext(self.rootfile), self.key),
800 with open(json_ntuple_file,
"w+")
as json_file:
801 json.dump(json_nutple, json_file)
803 self._file = json_ntuple_file
805 def get_plot_title(self):
807 return os.path.basename(self._file).replace(
".",
"_").strip()
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,
819 is_expert=self.is_expert(),
820 json_file_path=os.path.relpath(
825 elif self.type ==
"TNamed":
827 title=self.get_plot_title(),
828 description=self._description,
829 contact=self._contact,
831 is_expert=self.is_expert(),
832 html_content=self._html_content,
834 elif self.type ==
"meta":
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,
846 is_expert=self.is_expert(),
847 plot_path=os.path.relpath(
852 png_filename=self.get_png_filename(),
853 pdf_filename=self.get_pdf_filename(),
854 warnings=self.warnings,
def get_html_plots_tag_comparison_folder(output_base_dir, tags)
def get_html_folder(output_base_dir)