Belle II Software development
validationplotuple.py
1#!/usr/bin/env python3
2
3
10
11
12# std
13import os.path
14import math
15import json
16from typing import List, Optional
17
18# 3rd
19import ROOT
20
21# ours
22import metaoptions
23import validationcomparison
24import validationpath
25from validationfunctions import strip_ext, index_from_revision, get_style
26import json_objects
27from validationrootobject import RootObject
28
29
30# todo: [Ref, low prio, medium work] Refactor into class with uniform interface
31# and subclasses implementing actual functionality for Plot/Tuple etc.
32# /klieret
33class Plotuple:
34
35 """!
36 A Plotuple is either a Plot or an N-Tuple
37
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
46 Plotuple
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!)
57 """
58
59 def __init__(
60 self,
61 root_objects: List[RootObject],
62 revisions: List[str],
63 work_folder: str,
64 ):
65 """!
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!)
70 """
71
72 # the work folder containing the results and plots
73 self._work_folder = work_folder
74
75 # The list of Root objects in this Plotuple-object
76 self._root_objects = root_objects
77
78 # The list of revisions
79 self._revisions = revisions
80
81 # A list of all problems that occurred with this Plotuple,
82 # e.g. missing reference object, missing meta-information...
83 self.warnings: List[str] = []
84
85 # Find the reference element. If we can't find one, set it to 'None'
86 # The reference-object for this Plotuple object
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
91 break
92
93 # If we couldn't find a reference element, add that to warnings
94 if self._reference is None:
95 self.warnings = ["No reference object"]
96
97 # All elements of the Plotuple that are not the reference-object
98 # Get the elements, i.e. all RootObjects except for the
99 # reference object. May be either histograms or n-tuples.
100 # Note that the reference doesn't have a date set (and if we only plot
101 # the reference, then is_reference is probably not set), so we have
102 # to be careful of how to sort
103 self._elements = [ro for ro in root_objects if not ro.is_reference]
104 self._elements.sort(
105 key=lambda ro: ro.date if ro.date else 0, reverse=True
106 )
107
108 # The newest element, i.e. the element belonging the revision
109 # whose data were created most recently.
110 # Should always be self.element[0], except if there is only a
111 # reference object
112 if self._elements:
113 self._newest = self._elements[0]
114 else:
115 self._newest = self._reference
116
117 # All available meta-information about the plotuple object:
118
119 # The key (more precisely: the name of the key) that all elements of
120 # this Plotuple object share
121 self.key = self._newest.key
122
123 # The type of the elements in this Plotuple object
124 self.type = self._newest.type
125
126 if self.type == "TNamed":
127 # Sometimes, we use TNamed to encode extra information about the
128 # ROOT file. In order to avoid that this will be plotted, we
129 # catch it here and assign it the type 'Meta'
130 meta_fields = ["description"]
131 if self._newest.object.GetName().lower().strip() in meta_fields:
132 self.type = "meta"
133
134 # The description of the histogram/n-tuple which this Plotuple object
135 # will yield
136 self._description = self._newest.description
137
138 # The 'Check for ...'-guideline for the histogram/n-tuple which this
139 # Plotuple object will yield
140 self._check = self._newest.check
141
142 # A contact person for the histogram/n-tuple which this Plotuple object
143 # will yield
144 self._contact = self._newest.contact
145
146 # MetaOptionParser for the meta-options for this Plotuple object
147 self._mop = metaoptions.MetaOptionParser(self._newest.metaoptions)
148
149 # The package to which the elements in this Plotuple object belong
150 self.package = self._newest.package
151
152 # The root file to which the elements in this Plotuple object belong
153 self.rootfile = self._newest.rootfile
154
155 # The result of the Chi^2-Test. By default, there is no such result.
156 # If the Chi^2-Test has been performed, this variable holds between
157 # which objects it has been performed.
158 self._comparison_result_long = "n/a"
159
160 self.comparison_result = "not_compared"
161
162 # The json file, in which the ntuple information is stored
163 self._file: Optional[str] = None
164
165 self._html_content: Optional[str] = None
166
167
169 self._width: Optional[int] = None
170
171
173 self._height: Optional[int] = None
174
175 # Deal with incomplete information
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:
180 self._check = "n/a"
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")
185
186
189 self._plot_folder = os.path.join(
191 work_folder, tags=revisions
192 ),
193 self.package,
194 )
195 os.makedirs(self._plot_folder, exist_ok=True)
196
197 def has_reference(self):
198 """!
199 @return True if a reference file is found for this plotuple
200 """
201 return self._reference is not None
202
203 def create_plotuple(self):
204 """!
205 Creates the histogram/table/image that belongs to this Plotuble-object.
206 """
207
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")
214 # used to store HTML user content
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":
222 pass
223 else:
224 raise ValueError(
225 "Tried to create histogram/n-tuple, but received" "invalid type"
226 )
227
228 def is_expert(self):
229 """!
230 @return Returns true if this plotuple has the expert option
231 """
232 return not self._mop.has_option("shifter")
233
234 def perform_comparison(self):
235 """!
236 Takes the reference (self.reference.object) and the newest revision
237 (self.newest.object) and a canvas. Performs a comparison of the
238 two objects.
239 @return: None
240 """
241
242 tester = validationcomparison.get_comparison(
243 self._reference.object, self._newest.object, self._mop
244 )
245
246 self._comparison_result_long = tester.comparison_result_long.format(
247 revision1=self._reference.revision, revision2=self._newest.revision
248 )
249 self.comparison_result = tester.comparison_result
250
251 def _set_background(self, canvas):
252
253 # kRed #FF0000 Red
254 # kRed - 9 #FF9999 Sweet pink
255
256 # kOrange + 1 #FF9832 Sun
257 # kOrange - 9 #FFCC9A Manhattan
258
259 # kGreen - 3 #33CC33 Lime green
260 # kGreen - 10 #CCFFCC Chinook
261
262 # kAzure #0032FE Medium blue
263 # kAzure - 2 #3265FE Medium slate blue
264 # kAzure - 9 #98CBFF Jordy blue
265
266 colors = {
267 "error": ROOT.kRed,
268 "warning": ROOT.kOrange + 1,
269 "equal": ROOT.kGreen - 3,
270 "not_compared": ROOT.kAzure - 2,
271 }
272 colors_expert = {
273 "error": ROOT.kRed - 9,
274 "warning": ROOT.kOrange - 9,
275 "equal": ROOT.kGreen - 10,
276 "not_compared": ROOT.kAzure - 9,
277 }
278
279 if self.is_expert():
280 color = colors_expert[self.comparison_result]
281 else:
282 color = colors[self.comparison_result]
283
284 canvas.SetFillColor(color)
285 canvas.GetFrame().SetFillColor(ROOT.kWhite)
286
287 def _draw_ref(self, canvas):
288 """!
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
292 reference object.
293 @return. None
294 """
295 self._remove_stats_tf1(self._reference.object)
296
297 # Line is thick and black
298 self._reference.object.SetLineColor(ROOT.kBlack)
299 self._reference.object.SetLineWidth(2)
300 self._reference.object.SetLineStyle(1)
301
302 # Area under the curve is solid gray
303 self._reference.object.SetFillColor(ROOT.kGray)
304 self._reference.object.SetFillStyle(1001)
305
306 # Draw the reference on the canvas
307 self._draw_root_object(
308 self.type,
309 self._reference.object,
310 self._reference.object.GetOption(),
311 )
312 canvas.Update()
313 canvas.GetFrame().SetFillColor(ROOT.kWhite)
314
315 @staticmethod
316 def _remove_stats_tf1(obj):
317 # removed TF1s which might have been added by validation scripts
318 # in tracking/scripts/tracking/validation/plot.py:1597
319 tf1 = obj.FindObject("FitAndStats")
320 if tf1:
321 function_list = obj.GetListOfFunctions()
322 function_list.Remove(tf1)
323
324 # TODO: is this actually used or can it be removed ?
325 def create_image_plot(self):
326 """!
327 Creates image plot for TASImage-objects.
328 @return: None
329 """
330
331 # Create a ROOT canvas on which we will draw our histograms
332 self._width = 700
333 if len(self._elements) > 4:
334 canvas = ROOT.TCanvas("", "", 700, 1050)
335 self._height = 1050
336 else:
337 canvas = ROOT.TCanvas("", "", 700, 525)
338 self._height = 525
339
340 # Split the canvas into enough parts to fit all image_objects
341 # Find numbers x and y so that x*y = N (number of histograms to be
342 # plotted), and x,y close to sqrt(N)
343
344 if len(self._root_objects) == 1:
345 x = y = 1
346 elif len(self._root_objects) == 2:
347 x = 2
348 y = 1
349 else:
350 x = 2
351 y = int(math.floor((len(self._root_objects) + 1) / 2))
352
353 # Actually split the canvas and go to the first pad ('sub-canvas')
354 canvas.Divide(x, y)
355 pad = canvas.cd(1)
356 pad.SetFillColor(ROOT.kWhite)
357
358 # If there is a reference object, plot it first
359 if self._reference is not None:
360 self._draw_ref(pad)
361
362 # Now draw the normal plots
363 items_to_plot_count = len(self._elements)
364 for plot in reversed(self._elements):
365
366 # Get the index of the current plot
367 index = index_from_revision(plot.revision, self._work_folder)
368 style = get_style(index, items_to_plot_count)
369
370 self._remove_stats_tf1(plot.object)
371
372 # Set line properties accordingly
373 plot.object.SetLineColor(style.GetLineColor())
374 plot.object.SetLineWidth(style.GetLineWidth())
375 plot.object.SetLineStyle(style.GetLineStyle())
376
377 # Switch to the correct sub-panel of the canvas. If a ref-plot
378 # exists, we have to go one panel further compared to the
379 # no-ref-case
380 if self._reference is not None:
381 i = 2
382 else:
383 i = 1
384
385 pad = canvas.cd(self._elements.index(plot) + i)
386 pad.SetFillColor(ROOT.kWhite)
387
388 # Draw the reference on the canvas
389 self._draw_root_object(
390 self.type, plot.object, plot.object.GetOption()
391 )
392 pad.Update()
393 pad.GetFrame().SetFillColor(ROOT.kWhite)
394
395 # Write the title in the correct color
396 title = pad.GetListOfPrimitives().FindObject("title")
397 if title:
398 title.SetTextColor(style.GetLineColor())
399
400 # Save the plot as PNG and PDF
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()))
403
404 self._file = os.path.join(
405 self._plot_folder,
406 f"{strip_ext(self.rootfile)}_{self.key}",
407 )
408
409 def get_png_filename(self):
410 return f"{strip_ext(self.rootfile)}_{self.key}.png"
411
412 def get_pdf_filename(self):
413 return f"{strip_ext(self.rootfile)}_{self.key}.pdf"
414
415 @staticmethod
416 def _draw_root_object(typ, obj, options):
417 """
418 Special handling of the ROOT Draw calls, as some
419 ROOT objects have a slightly different flavour.
420 """
421
422 if typ == "TEfficiency" or typ == "TGraph":
423 # TEff does not provide DrawCopy
424 obj.Draw(options)
425 else:
426 obj.DrawCopy(options)
427
428 def create_histogram_plot(self, mode):
429 """!
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'
435 @return: None
436 """
437
438 # If we don't get a valid 'mode', we can stop right here
439 if mode not in ["1D", "2D"]:
440 return
441
442 # Create a ROOT canvas on which we will draw our histograms
443 self._width = 700
444 if mode == "2D" and len(self._elements) > 4:
445 self._height = 1050
446 else:
447 self._height = 525
448 canvas = ROOT.TCanvas("", "", self._width, self._height)
449
450 # Create a ROOT Legend
451 legend = ROOT.TLegend(0.01, 0.01, 0.53, 0.06)
452 legend.SetNColumns(3)
453 legend.SetTextSize(0.02)
454
455 # Allow possibility to turn off the stats box
456 if self._mop.has_option("nostats"):
457 ROOT.gStyle.SetOptStat("")
458 else:
459 ROOT.gStyle.SetOptStat("nemr")
460
461 # If there is a reference object, and the list of plots is not empty,
462 # perform a Chi^2-Test on the reference object and the first object in
463 # the plot list:
464 if (
465 self._reference is not None
466 and self._newest
467 and not self._reference == self._newest
468 ):
469 self.perform_comparison()
470
471 # A variable which holds whether we
472 # have drawn on the canvas already or not
473 # (only used for the 1D case)
474 drawn = False
475
476 # Now we distinguish between 1D and 2D histograms
477 # If we have a 1D histogram
478 if mode == "1D":
479
480 if not self._mop.has_option("nogrid"):
481 canvas.SetGrid()
482 if self._mop.has_option("logx"):
483 canvas.SetLogx()
484 if self._mop.has_option("logy"):
485 canvas.SetLogy()
486
487 # If there is a reference object, plot it first
488 if self._reference is not None:
489 self._draw_ref(canvas)
490 drawn = True
491
492 # If we have a 2D histogram
493 elif mode == "2D":
494
495 # Split the canvas into enough parts to fit all histogram_objects
496 # Find numbers x and y so that x*y = N (number of histograms to be
497 # plotted), and x,y close to sqrt(N)
498
499 if len(self._root_objects) == 1:
500 x = y = 1
501 elif len(self._root_objects) == 2:
502 x = 2
503 y = 1
504 else:
505 x = 2
506 y = int(math.floor((len(self._root_objects) + 1) / 2))
507
508 # Actually split the canvas and go to the first pad ('sub-canvas')
509 canvas.Divide(x, y)
510 pad = canvas.cd(1)
511 pad.SetFillColor(ROOT.kWhite)
512
513 # If there is a reference object, plot it first
514 if self._reference is not None:
515 self._draw_ref(pad)
516
517 items_to_plot_count = len(self._elements)
518
519 # Now draw the normal plots
520 for plot in reversed(self._elements):
521
522 # Get the index of the current plot
523 index = index_from_revision(plot.revision, self._work_folder)
524 style = get_style(index, items_to_plot_count)
525
526 self._remove_stats_tf1(plot.object)
527
528 # Set line properties accordingly
529 plot.object.SetLineColor(style.GetLineColor())
530 plot.object.SetLineWidth(style.GetLineWidth())
531 plot.object.SetLineStyle(style.GetLineStyle())
532
533 # If we have a one-dimensional histogram
534 if mode == "1D":
535 if not drawn:
536 # Get additional options for 1D histograms
537 # (Intersection with self.metaoptions)
538 additional_options = ["C"]
539 additional_options = [
540 option
541 for option in additional_options
542 if self._mop.has_option(option)
543 ]
544 options_str = plot.object.GetOption() + " ".join(
545 additional_options
546 )
547 drawn = True
548 else:
549 options_str = "SAME"
550
551 legend.AddEntry(plot.object, plot.revision)
552
553 self._draw_root_object(self.type, plot.object, options_str)
554
555 # redraw grid on top of histogram, if selected
556 if not self._mop.has_option("nogrid"):
557 canvas.RedrawAxis("g")
558
559 canvas.Update()
560 canvas.GetFrame().SetFillColor(ROOT.kWhite)
561
562 # If we have a two-dimensional histogram
563 elif mode == "2D":
564 # Switch to the correct sub-panel of the canvas. If a ref-plot
565 # exists, we have to go one panel further compared to the
566 # no-ref-case
567 if self._reference is not None:
568 i = 2
569 else:
570 i = 1
571
572 pad = canvas.cd(self._elements.index(plot) + i)
573 pad.SetFillColor(ROOT.kWhite)
574
575 # Get additional options for 2D histograms
576 additional_options = ""
577 for _ in ["col", "colz", "cont", "contz", "box"]:
578 if self._mop.has_option(_):
579 additional_options += " " + _
580
581 # Draw the reference on the canvas
582 self._draw_root_object(
583 self.type,
584 plot.object,
585 plot.object.GetOption() + additional_options,
586 )
587 pad.Update()
588 pad.GetFrame().SetFillColor(ROOT.kWhite)
589
590 # Write the title in the correct color
591 title = pad.GetListOfPrimitives().FindObject("title")
592 if title:
593 title.SetTextColor(style.GetLineColor())
594
595 if self._newest:
596 # if there is at least one revision
597 self._set_background(canvas)
598
599 canvas.GetFrame().SetFillColor(ROOT.kWhite)
600
601 # Add reference legend entry last for neatness
602 if self._reference is not None:
603 legend.AddEntry(self._reference.object, 'reference')
604
605 legend.Draw()
606
607 # Save the plot as PNG and PDF
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()))
610
611 self._file = os.path.join(
612 self._plot_folder,
613 f"{strip_ext(self.rootfile)}_{self.key}",
614 )
615
616 def create_graph_plot(self):
617 """!
618 Plots as TGraph/TGraphErrors
619 @return: None
620 """
621
622 # Create a ROOT canvas on which we will draw our plots
623 self._width = 700
624 self._height = 525
625 canvas = ROOT.TCanvas("", "", self._width, self._height)
626
627 # Allow possibility to turn off the stats box
628 if self._mop.has_option("nostats"):
629 ROOT.gStyle.SetOptStat("")
630 else:
631 ROOT.gStyle.SetOptStat("nemr")
632
633 # If there is a reference object, and the list of plots is not empty,
634 # perform a Chi^2-Test on the reference object and the first object in
635 # the plot list:
636 if (
637 self._reference is not None
638 and self._newest
639 and not self._reference == self._newest
640 ):
641 self.perform_comparison()
642
643 if not self._mop.has_option("nogrid"):
644 canvas.SetGrid()
645 if self._mop.has_option("logx"):
646 canvas.SetLogx()
647 if self._mop.has_option("logy"):
648 canvas.SetLogy()
649
650 # A variable which holds whether we
651 # have drawn on the canvas already or not
652 drawn = False
653
654 # If there is a reference object, plot it first
655 if self._reference is not None:
656 self._draw_ref(canvas)
657 drawn = True
658
659 items_to_plot_count = len(self._elements)
660 # Now draw the normal plots
661 for plot in reversed(self._elements):
662
663 # Get the index of the current plot
664 index = index_from_revision(plot.revision, self._work_folder)
665 style = get_style(index, items_to_plot_count)
666
667 # self.remove_stats_tf1(plot.object)
668
669 # Set line properties accordingly
670 plot.object.SetLineColor(style.GetLineColor())
671 plot.object.SetLineWidth(style.GetLineWidth())
672 plot.object.SetLineStyle(style.GetLineStyle())
673
674 # If we have a one-dimensional histogram
675 if not drawn:
676
677 # todo: refactor like in plot hist
678 # Get additional options for 1D histograms
679 additional_options = ""
680 for _ in ["C"]:
681 if self._mop.has_option(_):
682 additional_options += " " + _
683
684 # Draw the reference on the canvas
685 self._draw_root_object(
686 self.type,
687 plot.object,
688 plot.object.GetOption() + additional_options,
689 )
690 drawn = True
691 else:
692 self._draw_root_object(self.type, plot.object, "SAME")
693
694 # redraw grid on top of histogram, if selected
695 if not self._mop.has_option("nogrid"):
696 canvas.RedrawAxis("g")
697
698 canvas.Update()
699 canvas.GetFrame().SetFillColor(ROOT.kWhite)
700
701 if self._newest:
702 # if there is at least one revision
703 self._set_background(canvas)
704
705 # Save the plot as PNG and PDF
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()))
708
709 self._file = os.path.join(
710 self._plot_folder,
711 f"{strip_ext(self.rootfile)}_{self.key}",
712 )
713
714 def create_html_content(self):
715
716 # self.elements
717 self._html_content = ""
718
719 for elem in self._elements:
720 self._html_content += (
721 "<p>" + elem.revision + "</p>" + elem.object.GetTitle()
722 )
723
724 # there is no file storing this, because it is directly in the json
725 # file
726 self._file = None
727
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()
732 return key, value
733
734 def create_ntuple_table_json(self):
735 """!
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.
739 """
740
741 json_nutple = {}
742
743 # The dictionary will have the following form
744 # {
745 # "reference (if exist)": [
746 # ('variable 1', 'reference value for variable 1'),
747 # ('variable 2', 'reference value for variable 2'),
748 # ...
749 # ],
750 # "revision": [
751 # ...
752 # ]
753 # }
754
755 precision = self._mop.int_value("float-precision", default=4)
756 format_str = f"{{0:.{precision}f}}"
757
758 def value2str(obj):
759 # assuming that I have a float
760 return format_str.format(obj)
761
762 colum_names = []
763 for key in list(self._newest.object.keys()):
764 colum_names.append(key)
765
766 # If there is a reference object, print the reference values as the
767 # first row of the table
768 if self._reference and "reference" in self._revisions:
769 json_nutple["reference"] = []
770
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))
776 else:
777 json_nutple["reference"].append((column, None))
778
779 # Now print the values for all other revisions
780 for ntuple in self._elements:
781 if ntuple.revision not in json_nutple:
782 json_nutple[ntuple.revision] = []
783
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))
788 else:
789 json_nutple[ntuple.revision].append((column, None))
790
791 json_ntuple_file = os.path.join(
792 self._plot_folder,
793 f"{strip_ext(self.rootfile)}_{self.key}.json",
794 )
795
796 with open(json_ntuple_file, "w+") as json_file:
797 json.dump(json_nutple, json_file)
798
799 self._file = json_ntuple_file
800
801 def get_plot_title(self):
802 if self._file:
803 return os.path.basename(self._file).replace(".", "_").strip()
804 else:
805 # this is for html content which is not stored in any file
806 return self.key
807
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,
814 check=self._check,
815 is_expert=self.is_expert(),
816 json_file_path=os.path.relpath(
817 self._file,
818 validationpath.get_html_folder(self._work_folder),
819 ),
820 )
821 elif self.type == "TNamed":
823 title=self.get_plot_title(),
824 description=self._description,
825 contact=self._contact,
826 check=self._check,
827 is_expert=self.is_expert(),
828 html_content=self._html_content,
829 )
830 elif self.type == "meta":
831 return None
832 else:
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,
839 check=self._check,
840 height=self._height,
841 width=self._width,
842 is_expert=self.is_expert(),
843 plot_path=os.path.relpath(
844 self._plot_folder,
845 validationpath.get_html_folder(self._work_folder),
846 )
847 + "/",
848 png_filename=self.get_png_filename(),
849 pdf_filename=self.get_pdf_filename(),
850 warnings=self.warnings,
851 )
def get_html_folder(output_base_dir)
def get_html_plots_tag_comparison_folder(output_base_dir, tags)