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 if self._mop.has_option(
"nostats"):
456 ROOT.gStyle.SetOptStat(
"")
458 ROOT.gStyle.SetOptStat(
"nemr")
464 self._reference
is not None
466 and not self._reference == self._newest
468 self.perform_comparison()
479 if not self._mop.has_option(
"nogrid"):
481 if self._mop.has_option(
"logx"):
483 if self._mop.has_option(
"logy"):
487 if self._reference
is not None:
488 self._draw_ref(canvas)
498 if len(self._root_objects) == 1:
500 elif len(self._root_objects) == 2:
505 y = int(math.floor((len(self._root_objects) + 1) / 2))
510 pad.SetFillColor(ROOT.kWhite)
513 if self._reference
is not None:
516 items_to_plot_count = len(self._elements)
518 for plot
in reversed(self._elements):
521 index = index_from_revision(plot.revision, self._work_folder)
522 style = get_style(index, items_to_plot_count)
524 self._remove_stats_tf1(plot.object)
527 plot.object.SetLineColor(style.GetLineColor())
528 plot.object.SetLineWidth(style.GetLineWidth())
529 plot.object.SetLineStyle(style.GetLineStyle())
536 additional_options = [
"C"]
537 additional_options = [
539 for option
in additional_options
540 if self._mop.has_option(option)
542 options_str = plot.object.GetOption() +
" ".join(
549 self._draw_root_object(self.type, plot.object, options_str)
552 if not self._mop.has_option(
"nogrid"):
553 canvas.RedrawAxis(
"g")
556 canvas.GetFrame().SetFillColor(ROOT.kWhite)
563 if self._reference
is not None:
568 pad = canvas.cd(self._elements.index(plot) + i)
569 pad.SetFillColor(ROOT.kWhite)
572 additional_options =
""
573 for _
in [
"col",
"colz",
"cont",
"contz",
"box"]:
574 if self._mop.has_option(_):
575 additional_options +=
" " + _
578 self._draw_root_object(
581 plot.object.GetOption() + additional_options,
584 pad.GetFrame().SetFillColor(ROOT.kWhite)
587 title = pad.GetListOfPrimitives().FindObject(
"title")
589 title.SetTextColor(style.GetLineColor())
593 self._set_background(canvas)
595 canvas.GetFrame().SetFillColor(ROOT.kWhite)
598 canvas.Print(os.path.join(self._plot_folder, self.get_png_filename()))
599 canvas.Print(os.path.join(self._plot_folder, self.get_pdf_filename()))
601 self._file = os.path.join(
603 "{}_{}".format(strip_ext(self.rootfile), self.key),
606 def create_graph_plot(self):
608 Plots as TGraph/TGraphErrors
615 canvas = ROOT.TCanvas(
"",
"", self._width, self._height)
618 if self._mop.has_option(
"nostats"):
619 ROOT.gStyle.SetOptStat(
"")
621 ROOT.gStyle.SetOptStat(
"nemr")
627 self._reference
is not None
629 and not self._reference == self._newest
631 self.perform_comparison()
633 if not self._mop.has_option(
"nogrid"):
635 if self._mop.has_option(
"logx"):
637 if self._mop.has_option(
"logy"):
645 if self._reference
is not None:
646 self._draw_ref(canvas)
649 items_to_plot_count = len(self._elements)
651 for plot
in reversed(self._elements):
654 index = index_from_revision(plot.revision, self._work_folder)
655 style = get_style(index, items_to_plot_count)
660 plot.object.SetLineColor(style.GetLineColor())
661 plot.object.SetLineWidth(style.GetLineWidth())
662 plot.object.SetLineStyle(style.GetLineStyle())
669 additional_options =
""
671 if self._mop.has_option(_):
672 additional_options +=
" " + _
675 self._draw_root_object(
678 plot.object.GetOption() + additional_options,
682 self._draw_root_object(self.type, plot.object,
"SAME")
685 if not self._mop.has_option(
"nogrid"):
686 canvas.RedrawAxis(
"g")
689 canvas.GetFrame().SetFillColor(ROOT.kWhite)
693 self._set_background(canvas)
696 canvas.Print(os.path.join(self._plot_folder, self.get_png_filename()))
697 canvas.Print(os.path.join(self._plot_folder, self.get_pdf_filename()))
699 self._file = os.path.join(
701 "{}_{}".format(strip_ext(self.rootfile), self.key),
704 def create_html_content(self):
707 self._html_content =
""
709 for elem
in self._elements:
710 self._html_content += (
711 "<p>" + elem.revision +
"</p>" + elem.object.GetTitle()
718 def get_meta_information(self):
719 assert self.type ==
"meta"
720 key = self._newest.object.GetName().strip().lower()
721 value = self._newest.object.GetTitle()
724 def create_ntuple_table_json(self):
726 If the Plotuple-object contains n-tuples, this will create the
727 a JSON file, which is later converted to HTML by the javascript
728 function fillNtupleTable.
745 precision = self._mop.int_value(
"float-precision", default=4)
746 format_str = f
"{{0:.{precision}f}}"
750 return format_str.format(obj)
753 for key
in list(self._newest.object.keys()):
754 colum_names.append(key)
758 if self._reference
and "reference" in self._revisions:
759 json_nutple[
"reference"] = []
761 key_list = list(self._reference.object.keys())
762 for column
in colum_names:
763 if column
in key_list:
764 value_str = value2str(self._reference.object[column])
765 json_nutple[
"reference"].append((column, value_str))
767 json_nutple[
"reference"].append((column,
None))
770 for ntuple
in self._elements:
771 if ntuple.revision
not in json_nutple:
772 json_nutple[ntuple.revision] = []
774 for column
in colum_names:
775 if column
in ntuple.object:
776 value_str = value2str(ntuple.object[column])
777 json_nutple[ntuple.revision].append((column, value_str))
779 json_nutple[ntuple.revision].append((column,
None))
781 json_ntuple_file = os.path.join(
783 "{}_{}.json".format(strip_ext(self.rootfile), self.key),
786 with open(json_ntuple_file,
"w+")
as json_file:
787 json.dump(json_nutple, json_file)
789 self._file = json_ntuple_file
791 def get_plot_title(self):
793 return os.path.basename(self._file).replace(
".",
"_").strip()
798 def create_json_object(self):
799 if self.type ==
"TNtuple":
801 title=self.get_plot_title(),
802 description=self._description,
803 contact=self._contact,
805 is_expert=self.is_expert(),
806 json_file_path=os.path.relpath(
811 elif self.type ==
"TNamed":
813 title=self.get_plot_title(),
814 description=self._description,
815 contact=self._contact,
817 is_expert=self.is_expert(),
818 html_content=self._html_content,
820 elif self.type ==
"meta":
824 title=self.get_plot_title(),
825 comparison_result=self.comparison_result,
826 comparison_text=self._comparison_result_long,
827 description=self._description,
828 contact=self._contact,
832 is_expert=self.is_expert(),
833 plot_path=os.path.relpath(
838 png_filename=self.get_png_filename(),
839 pdf_filename=self.get_pdf_filename(),
840 warnings=self.warnings,
def get_html_plots_tag_comparison_folder(output_base_dir, tags)
def get_html_folder(output_base_dir)