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 ==
"TASImage":
218 self.create_image_plot()
219 elif self.type ==
"TNtuple":
220 self.create_ntuple_table_json()
221 elif self.type ==
"meta":
225 "Tried to create histogram/n-tuple, but received" "invalid type"
230 @return Returns true
if this plotuple has the expert option
232 return not self._mop.has_option(
"shifter")
234 def perform_comparison(self):
236 Takes the reference (self.reference.object) and the newest revision
237 (self.newest.object)
and a canvas. Performs a comparison of the
242 tester = validationcomparison.get_comparison(
243 self._reference.object, self._newest.object, self._mop
246 self._comparison_result_long = tester.comparison_result_long.format(
247 revision1=self._reference.revision, revision2=self._newest.revision
249 self.comparison_result = tester.comparison_result
251 def _set_background(self, canvas):
268 "warning": ROOT.kOrange + 1,
269 "equal": ROOT.kGreen - 3,
270 "not_compared": ROOT.kAzure - 2,
273 "error": ROOT.kRed - 9,
274 "warning": ROOT.kOrange - 9,
275 "equal": ROOT.kGreen - 10,
276 "not_compared": ROOT.kAzure - 9,
280 color = colors_expert[self.comparison_result]
282 color = colors[self.comparison_result]
284 canvas.SetFillColor(color)
285 canvas.GetFrame().SetFillColor(ROOT.kWhite)
287 def _draw_ref(self, canvas):
289 Takes the reference RootObject (self.reference.object)
290 and a (sub)canvas
and plots it
with the correct line-style etc.
291 @param canvas: Reference to the canvas on which we will draw the
295 self._remove_stats_tf1(self._reference.object)
298 self._reference.object.SetLineColor(ROOT.kBlack)
299 self._reference.object.SetLineWidth(2)
300 self._reference.object.SetLineStyle(1)
303 self._reference.object.SetFillColor(ROOT.kGray)
304 self._reference.object.SetFillStyle(1001)
307 self._draw_root_object(
309 self._reference.object,
310 self._reference.object.GetOption(),
313 canvas.GetFrame().SetFillColor(ROOT.kWhite)
316 def _remove_stats_tf1(obj):
319 tf1 = obj.FindObject(
"FitAndStats")
321 function_list = obj.GetListOfFunctions()
322 function_list.Remove(tf1)
325 def create_image_plot(self):
327 Creates image plot for TASImage-objects.
333 if len(self._elements) > 4:
334 canvas = ROOT.TCanvas(
"",
"", 700, 1050)
337 canvas = ROOT.TCanvas(
"",
"", 700, 525)
344 if len(self._root_objects) == 1:
346 elif len(self._root_objects) == 2:
351 y = int(math.floor((len(self._root_objects) + 1) / 2))
356 pad.SetFillColor(ROOT.kWhite)
359 if self._reference
is not None:
363 items_to_plot_count = len(self._elements)
364 for plot
in reversed(self._elements):
367 index = index_from_revision(plot.revision, self._work_folder)
368 style = get_style(index, items_to_plot_count)
370 self._remove_stats_tf1(plot.object)
373 plot.object.SetLineColor(style.GetLineColor())
374 plot.object.SetLineWidth(style.GetLineWidth())
375 plot.object.SetLineStyle(style.GetLineStyle())
380 if self._reference
is not None:
385 pad = canvas.cd(self._elements.index(plot) + i)
386 pad.SetFillColor(ROOT.kWhite)
389 self._draw_root_object(
390 self.type, plot.object, plot.object.GetOption()
393 pad.GetFrame().SetFillColor(ROOT.kWhite)
396 title = pad.GetListOfPrimitives().FindObject(
"title")
398 title.SetTextColor(style.GetLineColor())
401 canvas.Print(os.path.join(self._plot_folder, self.get_png_filename()))
402 canvas.Print(os.path.join(self._plot_folder, self.get_pdf_filename()))
404 self._file = os.path.join(
406 f
"{strip_ext(self.rootfile)}_{self.key}",
409 def get_png_filename(self):
410 return f
"{strip_ext(self.rootfile)}_{self.key}.png"
412 def get_pdf_filename(self):
413 return f
"{strip_ext(self.rootfile)}_{self.key}.pdf"
416 def _draw_root_object(typ, obj, options):
418 Special handling of the ROOT Draw calls, as some
419 ROOT objects have a slightly different flavour.
422 if typ ==
"TEfficiency" or typ ==
"TGraph":
426 obj.DrawCopy(options)
428 def create_histogram_plot(self, mode):
430 Plots all histogram-objects in this Plotuple together
in one histogram,
431 which
is then given the name of the key.
432 @param mode: Determines whether it
is a one-
or
433 two-dimensional histogram.
434 Accepted values are
'1D' and '2D'
439 if mode
not in [
"1D",
"2D"]:
444 if mode ==
"2D" and len(self._elements) > 4:
448 canvas = ROOT.TCanvas(
"",
"", self._width, self._height)
451 legend = ROOT.TLegend(0.01, 0.01, 0.53, 0.06)
452 legend.SetNColumns(3)
453 legend.SetTextSize(0.02)
456 if self._mop.has_option(
"nostats"):
457 ROOT.gStyle.SetOptStat(
"")
459 ROOT.gStyle.SetOptStat(
"nemr")
465 self._reference
is not None
467 and not self._reference == self._newest
469 self.perform_comparison()
480 if not self._mop.has_option(
"nogrid"):
482 if self._mop.has_option(
"logx"):
484 if self._mop.has_option(
"logy"):
488 if self._reference
is not None:
489 self._draw_ref(canvas)
499 if len(self._root_objects) == 1:
501 elif len(self._root_objects) == 2:
506 y = int(math.floor((len(self._root_objects) + 1) / 2))
511 pad.SetFillColor(ROOT.kWhite)
514 if self._reference
is not None:
517 items_to_plot_count = len(self._elements)
520 for plot
in reversed(self._elements):
523 index = index_from_revision(plot.revision, self._work_folder)
524 style = get_style(index, items_to_plot_count)
526 self._remove_stats_tf1(plot.object)
529 plot.object.SetLineColor(style.GetLineColor())
530 plot.object.SetLineWidth(style.GetLineWidth())
531 plot.object.SetLineStyle(style.GetLineStyle())
538 additional_options = [
"C"]
539 additional_options = [
541 for option
in additional_options
542 if self._mop.has_option(option)
544 options_str = plot.object.GetOption() +
" ".join(
551 legend.AddEntry(plot.object, plot.revision)
553 self._draw_root_object(self.type, plot.object, options_str)
556 if not self._mop.has_option(
"nogrid"):
557 canvas.RedrawAxis(
"g")
560 canvas.GetFrame().SetFillColor(ROOT.kWhite)
567 if self._reference
is not None:
572 pad = canvas.cd(self._elements.index(plot) + i)
573 pad.SetFillColor(ROOT.kWhite)
576 additional_options =
""
577 for _
in [
"col",
"colz",
"cont",
"contz",
"box"]:
578 if self._mop.has_option(_):
579 additional_options +=
" " + _
582 self._draw_root_object(
585 plot.object.GetOption() + additional_options,
588 pad.GetFrame().SetFillColor(ROOT.kWhite)
591 title = pad.GetListOfPrimitives().FindObject(
"title")
593 title.SetTextColor(style.GetLineColor())
597 self._set_background(canvas)
599 canvas.GetFrame().SetFillColor(ROOT.kWhite)
602 if self._reference
is not None:
603 legend.AddEntry(self._reference.object,
'reference')
608 canvas.Print(os.path.join(self._plot_folder, self.get_png_filename()))
609 canvas.Print(os.path.join(self._plot_folder, self.get_pdf_filename()))
611 self._file = os.path.join(
613 f
"{strip_ext(self.rootfile)}_{self.key}",
616 def create_graph_plot(self):
618 Plots as TGraph/TGraphErrors
625 canvas = ROOT.TCanvas(
"",
"", self._width, self._height)
628 if self._mop.has_option(
"nostats"):
629 ROOT.gStyle.SetOptStat(
"")
631 ROOT.gStyle.SetOptStat(
"nemr")
637 self._reference
is not None
639 and not self._reference == self._newest
641 self.perform_comparison()
643 if not self._mop.has_option(
"nogrid"):
645 if self._mop.has_option(
"logx"):
647 if self._mop.has_option(
"logy"):
655 if self._reference
is not None:
656 self._draw_ref(canvas)
659 items_to_plot_count = len(self._elements)
661 for plot
in reversed(self._elements):
664 index = index_from_revision(plot.revision, self._work_folder)
665 style = get_style(index, items_to_plot_count)
670 plot.object.SetLineColor(style.GetLineColor())
671 plot.object.SetLineWidth(style.GetLineWidth())
672 plot.object.SetLineStyle(style.GetLineStyle())
679 additional_options =
""
681 if self._mop.has_option(_):
682 additional_options +=
" " + _
685 self._draw_root_object(
688 plot.object.GetOption() + additional_options,
692 self._draw_root_object(self.type, plot.object,
"SAME")
695 if not self._mop.has_option(
"nogrid"):
696 canvas.RedrawAxis(
"g")
699 canvas.GetFrame().SetFillColor(ROOT.kWhite)
703 self._set_background(canvas)
706 canvas.Print(os.path.join(self._plot_folder, self.get_png_filename()))
707 canvas.Print(os.path.join(self._plot_folder, self.get_pdf_filename()))
709 self._file = os.path.join(
711 f
"{strip_ext(self.rootfile)}_{self.key}",
714 def create_html_content(self):
717 self._html_content =
""
719 for elem
in self._elements:
720 self._html_content += (
721 "<p>" + elem.revision +
"</p>" + elem.object.GetTitle()
728 def get_meta_information(self):
729 assert self.type ==
"meta"
730 key = self._newest.object.GetName().strip().lower()
731 value = self._newest.object.GetTitle()
734 def create_ntuple_table_json(self):
736 If the Plotuple-object contains n-tuples, this will create the
737 a JSON file, which is later converted to HTML by the javascript
738 function fillNtupleTable.
755 precision = self._mop.int_value(
"float-precision", default=4)
756 format_str = f
"{{0:.{precision}f}}"
760 return format_str.format(obj)
763 for key
in list(self._newest.object.keys()):
764 colum_names.append(key)
768 if self._reference
and "reference" in self._revisions:
769 json_nutple[
"reference"] = []
771 key_list = list(self._reference.object.keys())
772 for column
in colum_names:
773 if column
in key_list:
774 value_str = value2str(self._reference.object[column])
775 json_nutple[
"reference"].append((column, value_str))
777 json_nutple[
"reference"].append((column,
None))
780 for ntuple
in self._elements:
781 if ntuple.revision
not in json_nutple:
782 json_nutple[ntuple.revision] = []
784 for column
in colum_names:
785 if column
in ntuple.object:
786 value_str = value2str(ntuple.object[column])
787 json_nutple[ntuple.revision].append((column, value_str))
789 json_nutple[ntuple.revision].append((column,
None))
791 json_ntuple_file = os.path.join(
793 f
"{strip_ext(self.rootfile)}_{self.key}.json",
796 with open(json_ntuple_file,
"w+")
as json_file:
797 json.dump(json_nutple, json_file)
799 self._file = json_ntuple_file
801 def get_plot_title(self):
803 return os.path.basename(self._file).replace(
".",
"_").strip()
808 def create_json_object(self):
809 if self.type ==
"TNtuple":
811 title=self.get_plot_title(),
812 description=self._description,
813 contact=self._contact,
815 is_expert=self.is_expert(),
816 json_file_path=os.path.relpath(
821 elif self.type ==
"TNamed":
823 title=self.get_plot_title(),
824 description=self._description,
825 contact=self._contact,
827 is_expert=self.is_expert(),
828 html_content=self._html_content,
830 elif self.type ==
"meta":
834 title=self.get_plot_title(),
835 comparison_result=self.comparison_result,
836 comparison_text=self._comparison_result_long,
837 description=self._description,
838 contact=self._contact,
842 is_expert=self.is_expert(),
843 plot_path=os.path.relpath(
848 png_filename=self.get_png_filename(),
849 pdf_filename=self.get_pdf_filename(),
850 warnings=self.warnings,
def get_html_folder(output_base_dir)
def get_html_plots_tag_comparison_folder(output_base_dir, tags)