8 from typing
import List, Optional
15 import validationcomparison
17 from validationfunctions
import strip_ext, index_from_revision, get_style
19 from validationrootobject
import RootObject
28 A Plotuple is either a Plot or an N-Tuple
30 @var work_folder: the work folder containing the results and plots
31 @var root_objects: A list of Root-objects which belong
32 together (i.e. should be drawn into one histogram or one table)
33 @var revisions: The list of revisions
34 @var warnings: A list of warnings that occured while creating the
35 plots/tables for this Plotuple object
36 @var reference: The reference RootObject for this Plotuple
37 @var elements: The elements (RootObject of different revisions) for this
39 @var newest: The newest element in elements
40 @var key: The key of the object within the corresponding ROOT file
41 @var type: The type of the elements (TH1, TH2, TNtuple)
42 @var description: The description of this Plotuple object
43 @var check: Hint how the Plotuple object should look like
44 @var contact: The contact person for this Plotuple object
45 @var package: The package to which this Plotuple object belongs to
46 @var rootfile: The rootfile to which the Plotuple object belongs to
47 @var chi2test_result: The result of the Chi^2-Test. By default, there is no
48 such result. If the Chi^2-Test has been performed, this variable holds
49 the information between which objects it has been performed.
50 @var pvalue: The p-value that the Chi^2-Test returned
51 @var file: The file, in which the histogram or the HMTL-table (for
52 n-tuples) are stored (without the file extension!)
57 root_objects: List[RootObject],
62 The default constructor for a Plotuple-object.
63 @param root_objects: A list of Root-objects which belong
64 together (i.e. should be drawn into one histogram or one table)
65 @param revisions: The list of revisions (Duh!)
69 self._work_folder = work_folder
72 self._root_objects = root_objects
75 self._revisions = revisions
83 self._reference =
None
84 for root_object
in self._root_objects:
85 if root_object.is_reference:
86 self._reference = root_object
90 if self._reference
is None:
91 self.warnings = [
'No reference object']
99 self._elements = [ro
for ro
in root_objects
if not ro.is_reference]
101 key=
lambda ro: ro.date
if ro.date
else 0,
110 self._newest = self._elements[0]
112 self._newest = self._reference
118 self.key = self._newest.key
121 self.type = self._newest.type
123 if self.type ==
"TNamed":
127 meta_fields = [
"description"]
128 if self._newest.object.GetName().lower().strip()
in meta_fields:
133 self._description = self._newest.description
137 self._check = self._newest.check
141 self._contact = self._newest.contact
147 self.package = self._newest.package
150 self.rootfile = self._newest.rootfile
155 self._comparison_result_long =
'n/a'
157 self.comparison_result =
"not_compared"
162 self._html_content =
None
173 if self._description ==
'' or self._description
is None:
174 self._description =
'n/a'
175 self.warnings.append(
'No description')
176 if self._check ==
'' or self._check
is None:
178 self.warnings.append(
'No Check')
179 if self._contact ==
'' or self._contact
is None:
180 self._contact =
'n/a'
181 self.warnings.append(
'No Contact Person')
186 self._plot_folder = os.path.join(
188 work_folder, tags=revisions
192 os.makedirs(self._plot_folder, exist_ok=
True)
194 def has_reference(self):
196 @return True if a reference file is found for this plotuple
198 return self._reference
is not None
200 def create_plotuple(self):
202 Creates the histogram/table/image that belongs to this Plotuble-object.
205 if self.type ==
'TH1' or self.type ==
'TEfficiency':
206 self.create_histogram_plot(
'1D')
207 elif self.type ==
'TGraph':
208 self.create_graph_plot()
209 elif self.type ==
'TH2':
210 self.create_histogram_plot(
'2D')
212 elif self.type ==
'TNamed':
213 self.create_html_content()
214 elif self.type ==
'TASImage':
215 self.create_image_plot()
216 elif self.type ==
'TNtuple':
217 self.create_ntuple_table_json()
218 elif self.type ==
"meta":
221 raise ValueError(
'Tried to create histogram/n-tuple, but received'
226 @return Returns true if this plotuple has the expert option
228 return not self._mop.has_option(
"shifter")
230 def perform_comparison(self):
232 Takes the reference (self.reference.object) and the newest revision
233 (self.newest.object) and a canvas. Performs a comparison of the
238 tester = validationcomparison.get_comparison(
239 self._reference.object,
244 self._comparison_result_long = tester.comparison_result_long.format(
245 revision1=self._reference.revision,
246 revision2=self._newest.revision
248 self.comparison_result = tester.comparison_result
250 def _set_background(self, canvas):
267 "warning": ROOT.kOrange + 1,
268 "equal": ROOT.kGreen - 3,
269 "not_compared": ROOT.kAzure - 2
272 "error": ROOT.kRed - 9,
273 "warning": ROOT.kOrange - 9,
274 "equal": ROOT.kGreen - 10,
275 "not_compared": ROOT.kAzure - 9
279 color = colors_expert[self.comparison_result]
281 color = colors[self.comparison_result]
283 canvas.SetFillColor(color)
284 canvas.GetFrame().SetFillColor(ROOT.kWhite)
286 def _draw_ref(self, canvas):
288 Takes the reference RootObject (self.reference.object)
289 and a (sub)canvas and plots it with the correct line-style etc.
290 @param canvas: Reference to the canvas on which we will draw the
294 self._remove_stats_tf1(self._reference.object)
297 self._reference.object.SetLineColor(ROOT.kBlack)
298 self._reference.object.SetLineWidth(2)
299 self._reference.object.SetLineStyle(1)
302 self._reference.object.SetFillColor(ROOT.kGray)
303 self._reference.object.SetFillStyle(1001)
306 self._draw_root_object(
308 self._reference.object,
309 self._reference.object.GetOption()
312 canvas.GetFrame().SetFillColor(ROOT.kWhite)
315 def _remove_stats_tf1(obj):
318 tf1 = obj.FindObject(
"FitAndStats")
320 function_list = obj.GetListOfFunctions()
321 function_list.Remove(tf1)
324 def create_image_plot(self):
326 Creates image plot for TASImage-objects.
332 if len(self._elements) > 4:
333 canvas = ROOT.TCanvas(
'',
'', 700, 1050)
336 canvas = ROOT.TCanvas(
'',
'', 700, 525)
343 if len(self._root_objects) == 1:
345 elif len(self._root_objects) == 2:
350 y = int(math.floor((len(self._root_objects) + 1) / 2))
355 pad.SetFillColor(ROOT.kWhite)
358 if self._reference
is not None:
362 items_to_plot_count = len(self._elements)
363 for plot
in reversed(self._elements):
366 index = index_from_revision(plot.revision, self._work_folder)
367 style = get_style(index, items_to_plot_count)
369 self._remove_stats_tf1(plot.object)
372 plot.object.SetLineColor(style.GetLineColor())
373 plot.object.SetLineWidth(style.GetLineWidth())
374 plot.object.SetLineStyle(style.GetLineStyle())
379 if self._reference
is not None:
384 pad = canvas.cd(self._elements.index(plot) + i)
385 pad.SetFillColor(ROOT.kWhite)
388 self._draw_root_object(
389 self.type, plot.object,
390 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(
407 strip_ext(self.rootfile),
412 def get_png_filename(self):
413 return '{}_{}.png'.format(strip_ext(self.rootfile), self.key)
415 def get_pdf_filename(self):
416 return '{}_{}.pdf'.format(strip_ext(self.rootfile), self.key)
419 def _draw_root_object(typ, obj, options):
421 Special handling of the ROOT Draw calls, as some
422 ROOT objects have a slightly differen flavour.
425 if typ ==
'TEfficiency' or typ ==
"TGraph":
429 obj.DrawCopy(options)
431 def create_histogram_plot(self, mode):
433 Plots all histogram-objects in this Plotuple together in one histogram,
434 which is then given the name of the key.
435 @param mode: Determines whether it is a one- or
436 two-dimensional histogram.
437 Accepted values are '1D' and '2D'
442 if mode
not in [
'1D',
'2D']:
447 if mode ==
'2D' and len(self._elements) > 4:
451 canvas = ROOT.TCanvas(
'',
'', self._width, self._height)
454 if self._mop.has_option(
'nostats'):
455 ROOT.gStyle.SetOptStat(
"")
457 ROOT.gStyle.SetOptStat(
"nemr")
462 if self._reference
is not None and self._newest \
463 and not self._reference == self._newest:
464 self.perform_comparison()
475 if not self._mop.has_option(
'nogrid'):
477 if self._mop.has_option(
'logx'):
479 if self._mop.has_option(
'logy'):
483 if self._reference
is not None:
484 self._draw_ref(canvas)
494 if len(self._root_objects) == 1:
496 elif len(self._root_objects) == 2:
501 y = int(math.floor((len(self._root_objects) + 1) / 2))
506 pad.SetFillColor(ROOT.kWhite)
509 if self._reference
is not None:
512 items_to_plot_count = len(self._elements)
514 for plot
in reversed(self._elements):
517 index = index_from_revision(plot.revision, self._work_folder)
518 style = get_style(index, items_to_plot_count)
520 self._remove_stats_tf1(plot.object)
523 plot.object.SetLineColor(style.GetLineColor())
524 plot.object.SetLineWidth(style.GetLineWidth())
525 plot.object.SetLineStyle(style.GetLineStyle())
532 additional_options = [
'C']
533 additional_options = [
534 option
for option
in additional_options
535 if self._mop.has_option(option)
537 options_str = plot.object.GetOption() + \
538 ' '.join(additional_options)
543 self._draw_root_object(self.type, plot.object, options_str)
546 if not self._mop.has_option(
'nogrid'):
547 canvas.RedrawAxis(
"g")
550 canvas.GetFrame().SetFillColor(ROOT.kWhite)
557 if self._reference
is not None:
562 pad = canvas.cd(self._elements.index(plot) + i)
563 pad.SetFillColor(ROOT.kWhite)
566 additional_options =
''
567 for _
in [
'col',
'colz',
'cont',
'contz',
'box']:
568 if self._mop.has_option(_):
569 additional_options +=
' ' + _
572 self._draw_root_object(
575 plot.object.GetOption() + additional_options
578 pad.GetFrame().SetFillColor(ROOT.kWhite)
581 title = pad.GetListOfPrimitives().FindObject(
'title')
583 title.SetTextColor(style.GetLineColor())
587 self._set_background(canvas)
589 canvas.GetFrame().SetFillColor(ROOT.kWhite)
592 canvas.Print(os.path.join(self._plot_folder, self.get_png_filename()))
593 canvas.Print(os.path.join(self._plot_folder, self.get_pdf_filename()))
595 self._file = os.path.join(
598 strip_ext(self.rootfile),
603 def create_graph_plot(self):
605 Plots as TGraph/TGraphErrors
612 canvas = ROOT.TCanvas(
'',
'', self._width, self._height)
615 if self._mop.has_option(
'nostats'):
616 ROOT.gStyle.SetOptStat(
"")
618 ROOT.gStyle.SetOptStat(
"nemr")
623 if self._reference
is not None and self._newest \
624 and not self._reference == self._newest:
625 self.perform_comparison()
627 if not self._mop.has_option(
'nogrid'):
629 if self._mop.has_option(
'logx'):
631 if self._mop.has_option(
'logy'):
639 if self._reference
is not None:
640 self._draw_ref(canvas)
643 items_to_plot_count = len(self._elements)
645 for plot
in reversed(self._elements):
648 index = index_from_revision(plot.revision, self._work_folder)
649 style = get_style(index, items_to_plot_count)
654 plot.object.SetLineColor(style.GetLineColor())
655 plot.object.SetLineWidth(style.GetLineWidth())
656 plot.object.SetLineStyle(style.GetLineStyle())
663 additional_options =
''
665 if self._mop.has_option(_):
666 additional_options +=
' ' + _
669 self._draw_root_object(
672 plot.object.GetOption() + additional_options
676 self._draw_root_object(self.type, plot.object,
"SAME")
679 if not self._mop.has_option(
'nogrid'):
680 canvas.RedrawAxis(
"g")
683 canvas.GetFrame().SetFillColor(ROOT.kWhite)
687 self._set_background(canvas)
690 canvas.Print(os.path.join(self._plot_folder, self.get_png_filename()))
691 canvas.Print(os.path.join(self._plot_folder, self.get_pdf_filename()))
693 self._file = os.path.join(
696 strip_ext(self.rootfile),
701 def create_html_content(self):
704 self._html_content =
""
706 for elem
in self._elements:
707 self._html_content +=
"<p>" + \
710 elem.object.GetTitle()
716 def get_meta_information(self):
717 assert self.type ==
"meta"
718 key = self._newest.object.GetName().strip().lower()
719 value = self._newest.object.GetTitle()
722 def create_ntuple_table_json(self):
724 If the Plotuple-object contains n-tuples, this will create the
725 a JSON file, which is later converted to HTML by the javascript
726 function fillNtupleTable.
743 precision = self._mop.int_value(
"float-precision", default=4)
744 format_str =
"{{0:.{}f}}".format(precision)
748 return format_str.format(obj)
751 for key
in list(self._newest.object.keys()):
752 colum_names.append(key)
756 if self._reference
and 'reference' in self._revisions:
757 json_nutple[
'reference'] = []
759 key_list = list(self._reference.object.keys())
760 for column
in colum_names:
761 if column
in key_list:
762 value_str = value2str(self._reference.object[column])
763 json_nutple[
'reference'].append(
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(
781 json_nutple[ntuple.revision].append((column,
None))
783 json_ntuple_file = os.path.join(
786 strip_ext(self.rootfile),
791 with open(json_ntuple_file,
'w+')
as json_file:
792 json.dump(json_nutple, json_file)
794 self._file = json_ntuple_file
796 def get_plot_title(self):
798 return os.path.basename(self._file).replace(
".",
"_").strip()
803 def create_json_object(self):
804 if self.type ==
'TNtuple':
806 title=self.get_plot_title(),
807 description=self._description,
808 contact=self._contact,
810 is_expert=self.is_expert(),
811 json_file_path=os.path.relpath(
816 elif self.type ==
'TNamed':
818 title=self.get_plot_title(),
819 description=self._description,
820 contact=self._contact,
822 is_expert=self.is_expert(),
823 html_content=self._html_content
825 elif self.type ==
"meta":
829 title=self.get_plot_title(),
830 comparison_result=self.comparison_result,
831 comparison_text=self._comparison_result_long,
832 description=self._description,
833 contact=self._contact,
837 is_expert=self.is_expert(),
838 plot_path=os.path.relpath(
842 png_filename=self.get_png_filename(),
843 pdf_filename=self.get_pdf_filename(),
844 warnings=self.warnings