Back to home page

Project CMSSW displayed by LXR

 
 

    


File indexing completed on 2023-10-25 09:32:51

0001 #!/usr/bin/env python3
0002 
0003 import ROOT
0004 import os
0005 import sys
0006 from decimal import Decimal
0007 
0008 class DMRplotter:
0009     def __init__(self, args):
0010         self.args = args
0011         self.dataFiles = []
0012         self.dataDirs = []
0013         self.mcFiles = []
0014         self.fileBaseName = "OfflineValidationSummary.root" 
0015         self.outputDir = self.args['outputDir']
0016         self.cwd = os.getcwd() 
0017         self.objNameList = []
0018         self.MCobjects = []
0019         self.objNameListMC = []
0020         self.segments = ["BPIX","FPIX","TEC","TID","TIB","TOB"]
0021         self.varsX = {}
0022         self.legendOffset = 1.5 
0023         self.legendTextSize = 0.032 
0024         self.statBoxTextSize = 0.0365 
0025         self.segmentTextOffset = {'ymin' : 0.9, 'ymax' : 1.2 }
0026         self.maxEntriesPerColumn = 5
0027         
0028     def __log__(self,log_type="",text=""):
0029         #########################################################################################################################
0030         #Logger:
0031         #  INFO    = Informative text
0032         #  WARNING = Notify user about unpredictable changes or missing files which do not result in abort
0033         #  ERROR   = Error in logic results in abort. Can be fixed by user (missing input, settings clash ...)
0034         #  FATAL   = Fatal error results in abort. Cannot be fixed by user (the way how input is produced has changed or bug ...)
0035         #########################################################################################################################
0036 
0037         v = int(sys.version_info[0])
0038         source = "DMRplotter:                "
0039         text = str(text) 
0040         if v == 3:
0041             if "i" in log_type:
0042                 print(source,"[INFO]     ",text)
0043             elif "n" in log_type:
0044                 print("                  ",text)
0045             elif "w" in log_type:
0046                 print(source,"[WARNING]  ",text)
0047             elif "e" in log_type:
0048                 print(source,"[ERROR]    ",text)
0049             elif "f" in log_type:
0050                 print(source,"[FATAL]    ",text)
0051             else:
0052                 print(text)
0053 
0054     def _replaceMulti(self, mainString, toBeReplaced, newString):
0055         #################################
0056         #Auxiliary function to remove 
0057         #multiple substrings from string
0058         #################################
0059 
0060         for elem in toBeReplaced:
0061             if elem in mainString:
0062                 mainString = mainString.replace(elem, newString)    
0063         return  mainString
0064 
0065     def _styledTPaveText(self,x1,y1,x2,y2,var):
0066         ####################################
0067         #Auxiliary function returning styled
0068         #plain TPaveText.  
0069         ####################################
0070         
0071         textBox = ROOT.TPaveText(x1,y1,x2,y2)
0072         textBox.SetFillColor(ROOT.kWhite) 
0073         if "median" not in var or not self.args['useFit']:
0074             if self.args['showMeanError'] and self.args['showRMSError']:
0075                 textBox.SetTextSize(self.statBoxTextSize-0.008)
0076             elif self.args['showMean'] and self.args['showRMS'] and (self.args['showMeanError'] or self.args['showRMSError']):
0077                 textBox.SetTextSize(self.statBoxTextSize-0.005)
0078             else:
0079                 textBox.SetTextSize(self.statBoxTextSize)
0080         else:
0081             if self.args['useFitError']:
0082                 textBox.SetTextSize(self.statBoxTextSize-0.008)
0083             else:
0084                 textBox.SetTextSize(self.statBoxTextSize-0.005)
0085         textBox.SetTextFont(42)
0086         
0087         return textBox   
0088 
0089     def __createSingleArchitecture__(self):
0090         ########################################
0091         #Check if input files exist in input dir
0092         #and creatte output dir 
0093         ########################################
0094 
0095         duplicity_check = False 
0096         if len(self.dataDirs) != 0:
0097             if self.args['isDMR']:
0098                 #subdirectory
0099                 if not os.path.isdir(self.outputDir):
0100                     self.__log__("i","Creating subdirectory for single DMRs: "+self.outputDir)
0101                     os.system("mkdir "+self.outputDir)
0102                 else:
0103                     self.__log__("i","Results directory "+self.outputDir+" exists.") 
0104 
0105                 #per IoV directories/MC part directories
0106                 dirsToMake = [] 
0107                 for dataDir in self.dataDirs:  
0108                     for root,dirs,files in os.walk(dataDir):
0109                         for dir in dirs: 
0110                             if dir.startswith("offline"): dirsToMake.append(self.outputDir+"/"+dir)
0111                 for dir in dirsToMake:
0112                     if not os.path.isdir(dir):
0113                         os.system("mkdir "+dir) 
0114                     else:
0115                         duplicity_check = True
0116         else:
0117             self.__log__("e","No input directory found! No DATA or MC present.")
0118             sys.exit(0) 
0119         
0120         if duplicity_check:
0121             self.__log__("w","Duplicated file names found. Plots will be overwritten.")
0122 
0123     def __createArchitecture__(self):
0124         ###########################
0125         #Check if input file exists 
0126         #and create output dir 
0127         ###########################
0128             
0129         dataControl = True
0130         for datafile in self.dataFiles: 
0131             if not os.path.isfile(datafile):
0132                 dataControl = False 
0133         for mcfile in self.MCobjects:
0134             if not os.path.isfile(mcfile):
0135                 dataControl = False
0136 
0137         if dataControl and not (len(self.dataFiles) == 0 and len(self.MCobjects) == 0):
0138             if not os.path.isdir(self.outputDir): 
0139                 self.__log__("i","Final plots will be stored in: "+self.outputDir)
0140                 os.system("mkdir "+self.outputDir)
0141             else:
0142                 self.__log__("i","Results directory "+self.outputDir+" exists.")
0143         else:
0144             self.__log__("f","Results file NOT found! No DATA or MC present.")
0145             sys.exit(0) 
0146 
0147     def __defineSingleObjects__(self):
0148         #######################################################
0149         #Open each file separately and get object groups
0150         #######################################################
0151 
0152         objDicts = {'DATA' : [], 'MC' : []}
0153 
0154         #DATA
0155         for datafile in self.dataFiles:
0156             if not os.path.isfile(datafile): continue
0157             fInput  = ROOT.TFile.Open(datafile,'READ')
0158             keyList = ROOT.gDirectory.GetListOfKeys()
0159             _id = [ id for id in datafile.split("/") if "offline_" in id ]
0160             id = "0"
0161             if len(_id) > 0: 
0162                 id = str(_id[0].split("_")[-1]) 
0163             objDict = {} 
0164             objList = []
0165             objAreIgnored = []
0166             _objNameList = []
0167             for key in keyList:
0168                 obj = key.ReadObj()
0169                 if "TH1" in obj.ClassName():
0170                     objList.append(obj.Clone())
0171                     objName = obj.GetName()
0172                     #FIXME
0173                     skipHist = False
0174                     for tag in ["layer","disc","plus","minus"]:
0175                         if tag in objName: skipHist = True
0176                     if skipHist: continue
0177                     #END FIXME
0178                     if objName[-1] != "y":
0179                         generalObjName = self._replaceMulti(objName, [objName.split("_")[0]+"_","_"+objName.split("_")[-1]], "")
0180                         if len(self.args['objects']) == 0: #get different object names manually
0181                             if generalObjName not in _objNameList:
0182                                 _objNameList.append(generalObjName)
0183                         else: #get different object names from user command input
0184                             if generalObjName not in self.args['objects']:
0185                                 self.__log__("w","Object \""+generalObjName+"\" found but ignored for plotting!")
0186                                 objAreIgnored.append(generalObjName)
0187                             else:
0188                                 if generalObjName not in _objNameList:
0189                                     _objNameList.append(generalObjName)
0190                     self.objNameList = [ genObjName for genObjName in _objNameList ]
0191 
0192             #now fill objects to the structured dictionary
0193             for objName in self.objNameList:
0194                 objDict[objName] = []
0195                 for obj in objList:
0196                     if objName in obj.GetName():
0197                         segment = ""
0198                         var = ""
0199                         if obj.GetName()[-1] == "y":
0200                             segment = obj.GetName().split("_")[-2]
0201                             var = obj.GetName().split("_")[0]+"Y"
0202                         else:
0203                             segment = obj.GetName().split("_")[-1]
0204                             var = obj.GetName().split("_")[0]+"X"
0205                         obj.SetDirectory(0) #important to detach memory allocation
0206                         objDict[objName].append({ 'hist'    : obj,
0207                                                   'segment' : segment,
0208                                                   'var'     : var,
0209                                                   'id'      : id,
0210                                                   'type'    : "DATA"    
0211                                                 })
0212             fInput.Close()
0213             objDicts['DATA'].append(objDict)
0214 
0215         #ensure plotting order
0216         if len(self.args['objects']) != 0:
0217             order = []
0218             for genObjName in self.objNameList:
0219                 order.append(self.args['objects'].index(genObjName))
0220             orderedList = [self.objNameList[i] for i in order]
0221             self.objNameList = orderedList
0222 
0223         if len(self.objNameList) == 0 and len(self.dataFiles) !=0:
0224             self.__log__("e","Data object names (if specified) must correspond to names in given input file!")
0225             sys.exit(0)
0226         else:
0227             for genObjName in self.objNameList:
0228                 self.__log__("i","Object \""+genObjName+"\" found for plotting.")
0229 
0230         #MC
0231         for mcFile in self.mcFiles:
0232             fInputMC  = ROOT.TFile.Open(mcFile,'READ')
0233             keyListMC = ROOT.gDirectory.GetListOfKeys()
0234             objListMC = []
0235             objDictMC = {} 
0236             generalObjName = ""
0237             objIsIgnored = False
0238             for key in keyListMC:
0239                 obj = key.ReadObj()
0240                 if "TH1" in obj.ClassName():
0241                     objName = obj.GetName()
0242                     objListMC.append(obj.Clone(objName))
0243                     #FIXME
0244                     skipHist = False
0245                     for tag in ["layer","disc","plus","minus"]:
0246                         if tag in objName: skipHist = True
0247                     if skipHist: continue
0248                     #END FIXME
0249                     if objName[-1] != "y":
0250                         generalObjName = self._replaceMulti(objName, [objName.split("_")[0]+"_","_"+objName.split("_")[-1]], "")
0251                         if len(self.args['objects']) == 0: #get different object names manually
0252                              if generalObjName not in self.objNameListMC:
0253                                 self.objNameListMC.append(generalObjName)
0254                         else: #get different object names from user command input
0255                             if generalObjName not in self.args['objects']:
0256                                 self.__log__("w","Object \""+generalObjName+"\" found but ignored for plotting!")
0257                                 objIsIgnored = True
0258                             else:
0259                                 if generalObjName not in self.objNameListMC:
0260                                     self.objNameListMC.append(generalObjName)
0261 
0262             #now fill MC objects to the structured dictionary
0263             if not objIsIgnored:
0264                 objDictMC[generalObjName] = []
0265                 for obj in objListMC:
0266                     if generalObjName in obj.GetName():
0267                         segment = ""
0268                         var = ""
0269                         if obj.GetName()[-1] == "y":
0270                             segment = obj.GetName().split("_")[-2]
0271                             var = obj.GetName().split("_")[0]+"Y"
0272                         else:
0273                             segment = obj.GetName().split("_")[-1]
0274                             var = obj.GetName().split("_")[0]+"X"
0275                         obj.SetDirectory(0) #important to detach memory allocation
0276                         objDictMC[generalObjName].append({ 'hist'    : obj,
0277                                                      'segment' : segment,
0278                                                      'var'     : var,
0279                                                      'type'    : "MC"
0280                                                   })
0281             fInputMC.Close()
0282             objDicts['MC'].append(objDictMC)  
0283 
0284         if len(self.objNameListMC) == 0 and len(self.mcFiles) != 0:
0285             self.__log__("e","MC object names (if specified) must correspond to names in given input file!")
0286             sys.exit(0)
0287         else:
0288             for genObjName in self.objNameListMC:
0289                 self.__log__("i","Object \""+genObjName+"\" found for plotting.")
0290 
0291         return objDicts
0292 
0293     def __defineObjects__(self):
0294         #################################################################################
0295         #Open result file and get information about objects stored inside. In case
0296         #that input validation objects were not given as an argument, it will retrieve 
0297         #those names from histogram names. Otherwise it will search for particular object
0298         #names. Histograms are then stored for each module segment and each object.
0299         #################################################################################
0300 
0301         objDict = {}
0302         for datafile in self.dataFiles:
0303             fInput  = ROOT.TFile.Open(datafile,'READ')
0304             keyList = ROOT.gDirectory.GetListOfKeys()
0305             objList = []
0306             objAreIgnored = []
0307             _objNameList = []
0308             for key in keyList:
0309                 obj = key.ReadObj()
0310                 if "TH1" in obj.ClassName(): 
0311                     objList.append(obj.Clone())
0312                     objName = obj.GetName()
0313                     #FIXME if you want to average also subsegment histos
0314                     skipHist = False
0315                     for tag in ["layer","disc","plus","minus"]:
0316                         if tag in objName: skipHist = True 
0317                     if skipHist: continue
0318                     #END FIXME
0319                     if objName[-1] != "y":
0320                         generalObjName = self._replaceMulti(objName, [objName.split("_")[0]+"_","_"+objName.split("_")[-1]], "") 
0321                         if len(self.args['objects']) == 0: #get different object names manually
0322                             if generalObjName not in _objNameList:                          
0323                                 _objNameList.append(generalObjName) 
0324                         else: #get different object names from user command input
0325                             if generalObjName not in self.args['objects']: 
0326                                 self.__log__("w","Object \""+generalObjName+"\" found but ignored for plotting!")
0327                                 objAreIgnored.append(generalObjName) 
0328                             else:
0329                                 if generalObjName not in _objNameList:
0330                                     _objNameList.append(generalObjName)
0331             duplicates = [ genObjName for genObjName in _objNameList if genObjName in self.objNameList ]
0332             for dup in duplicates:
0333                 self.__log__("e","Duplicated object "+str(dup)+" was found! Please rename this object in your input file!")
0334                 sys.exit(0) 
0335             self.objNameList += [ genObjName for genObjName in _objNameList if genObjName not in self.objNameList ]
0336 
0337             #now fill objects to the structured dictionary
0338             for objName in _objNameList:
0339                 if objName in objAreIgnored: continue   
0340                 objDict[objName] = []
0341                 for obj in objList:
0342                     if objName in obj.GetName():
0343                         segment = ""
0344                         var = ""
0345                         if obj.GetName()[-1] == "y":
0346                             segment = obj.GetName().split("_")[-2]
0347                             var = obj.GetName().split("_")[0]+"Y"
0348                         else:
0349                             segment = obj.GetName().split("_")[-1]
0350                             var = obj.GetName().split("_")[0]+"X"
0351                         obj.SetDirectory(0) #important to detach memory allocation
0352                         objDict[objName].append({ 'hist'    : obj,
0353                                                   'segment' : segment,
0354                                                   'var'     : var,
0355                                                   'type'    : "DATA"
0356                                                 })
0357             fInput.Close()              
0358 
0359         #ensure plotting order
0360         '''
0361         if len(self.args['objects']) != 0:
0362             order = []
0363             for genObjName in self.objNameList:
0364                 order.append(self.args['objects'].index(genObjName))
0365             orderedList = [self.objNameList[i] for i in order]
0366             self.objNameList = orderedList
0367         '''
0368 
0369         if len(self.objNameList) == 0 and len(self.dataFiles) !=0:
0370             self.__log__("e","Data object names (if specified) must correspond to names in given input file!")
0371             sys.exit(0)
0372         else:
0373             for genObjName in self.objNameList:
0374                 self.__log__("i","Object \""+genObjName+"\" found for plotting.")
0375  
0376         #add MC objects
0377         for MCobject in self.MCobjects:
0378             fInputMC  = ROOT.TFile.Open(MCobject,'READ')
0379             keyListMC = ROOT.gDirectory.GetListOfKeys()
0380             objListMC = []
0381             #generalObjName = "" 
0382             #objIsIgnored = False
0383             objAreIgnored = []
0384             _objNameList = [] 
0385             for key in keyListMC:
0386                 obj = key.ReadObj()
0387                 if "TH1" in obj.ClassName():
0388                     objName = obj.GetName()
0389                     objListMC.append(obj.Clone(objName))
0390                     #FIXME
0391                     skipHist = False
0392                     for tag in ["layer","disc","plus","minus"]:
0393                         if tag in objName: skipHist = True
0394                     if skipHist: continue
0395                     #END FIXME
0396                     if objName[-1] != "y":
0397                         generalObjName = self._replaceMulti(objName, [objName.split("_")[0]+"_","_"+objName.split("_")[-1]], "")
0398                         if len(self.args['objects']) == 0: #get different object names manually
0399                              if generalObjName not in _objNameList:
0400                                 _objNameList.append(generalObjName)
0401                         else: #get different object names from user command input
0402                             if generalObjName not in self.args['objects']:
0403                                 self.__log__("w","Object \""+generalObjName+"\" found but ignored for plotting!")
0404                                 objAreIgnored.append(generalObjName)
0405                             else:
0406                                 if generalObjName not in _objNameList:
0407                                     _objNameList.append(generalObjName)
0408             duplicates = [ genObjName for genObjName in _objNameList if genObjName in self.objNameListMC ]
0409             for dup in duplicates:
0410                 self.__log__("e","Duplicated object "+str(dup)+" was found! Please rename this object in your input file!")
0411                 sys.exit(0)
0412             self.objNameListMC += [ genObjName for genObjName in _objNameList if genObjName not in self.objNameListMC ]
0413 
0414             #now fill MC objects to the structured dictionary
0415             for objName in _objNameList:
0416                 if objName in objAreIgnored: continue
0417                 objDict[objName] = []
0418                 for obj in objListMC:
0419                     if objName in obj.GetName():
0420                         segment = ""
0421                         var = ""
0422                         if obj.GetName()[-1] == "y":
0423                             segment = obj.GetName().split("_")[-2]
0424                             var = obj.GetName().split("_")[0]+"Y"
0425                         else:
0426                             segment = obj.GetName().split("_")[-1]
0427                             var = obj.GetName().split("_")[0]+"X"
0428                         obj.SetDirectory(0) #important to detach memory allocation
0429                         objDict[objName].append({    'hist'    : obj,
0430                                                      'segment' : segment,
0431                                                      'var'     : var,
0432                                                      'type'    : "MC"
0433                                                   })
0434             fInputMC.Close()
0435 
0436         if len(self.objNameListMC) == 0 and len(self.MCobjects) != 0:
0437             self.__log__("e","MC object names (if specified) must correspond to names in given input file!")
0438             sys.exit(0)
0439         else:
0440             for genObjName in self.objNameListMC:
0441                 self.__log__("i","Object \""+genObjName+"\" found for plotting.")
0442 
0443         #ensure plotting order
0444         self.objNameList += self.objNameListMC
0445         if len(self.args['objects']) != 0:
0446             order = []
0447             for genObjName in self.objNameList:
0448                 order.append(self.args['objects'].index(genObjName))
0449             orderedList = [self.objNameList[i] for i in order]
0450             self.objNameList = orderedList
0451         return objDict
0452 
0453     def __fitGauss__(self,hist):
0454         #######################################################################
0455         # 1. fits a Gauss function to the inner range of abs(2 rms)
0456         # 2. repeates the Gauss fit in a 3 sigma range around mean of first fit
0457         # returns mean and sigma from fit in micrometers   
0458         #######################################################################
0459  
0460         if not hist or hist.GetEntries() < 20: return 0
0461         self.__log__("i","Fitting histogram: "+hist.GetName())
0462 
0463         xScale = 10000. 
0464         mean = hist.GetMean(1)*xScale
0465         sigma = hist.GetRMS(1)*xScale
0466         funcName = "gaussian_"+hist.GetName()
0467         func = ROOT.TF1(funcName,"gaus",mean - 2.*sigma,mean + 2.*sigma)
0468         func.SetLineColor(ROOT.kMagenta)
0469         func.SetLineStyle(2) 
0470      
0471         #first fit
0472         if int(hist.Fit(func,"QNR")) == 0:
0473             mean = func.GetParameter(1)
0474             sigma = func.GetParameter(2)
0475             func.SetRange(mean - 3.*sigma, mean + 3.*sigma)
0476             # I: Integral gives more correct results if binning is too wide 
0477             # L: Likelihood can treat empty bins correctly (if hist not weighted...)
0478             #second fit
0479             if int(hist.Fit(func,"Q0ILR")) == 0:  
0480                 return func
0481             else:
0482                 return 0
0483         else:
0484             return 0 
0485 
0486     def __getStat__(self,hist,var):
0487         #############################################################################
0488         #Return label to be added to the legend for each object. Label describes 
0489         #statistical information about particular histogram: 
0490         #(mean+-meanerror) | (rms+-rmserror) for median and RMS plots
0491         #or fit parameters (mu and sigma) from gaussian fit +-std. deviation error)
0492         #############################################################################
0493 
0494         statLabel = ""
0495         delimeter = ""
0496         muScale = 1.
0497         muUnit = ""
0498         form = "{:.2g}"
0499         formScie = "{:.1e}"
0500         if "median" in var:
0501             muScale = 10000.    
0502             muUnit  = " #mum"
0503         if not self.args['useFit'] or "median" not in var:  
0504             if self.args['showMean'] and self.args['showRMS']:
0505                 delimeter = ", "
0506             if self.args['showMean']:
0507                 statLabel += "#mu="
0508                 if hist.GetMean(1) >= 0.:
0509                     statLabel += (" "+form).format(Decimal(str(hist.GetMean(1)*muScale)))
0510                 else:
0511                     statLabel += form.format(Decimal(str(hist.GetMean(1)*muScale)))   
0512                 if self.args['showMeanError']:
0513                     statLabel += " #pm "
0514                     statLabel += formScie.format(Decimal(str(hist.GetMeanError(1)*muScale)))
0515             statLabel += delimeter
0516             if self.args['showRMS']:
0517                 statLabel += "rms="
0518                 statLabel += (" "+form).format(Decimal(str(hist.GetRMS(1)*muScale)))
0519                 if self.args['showRMSError']:
0520                     statLabel += " #pm "
0521                     statLabel += form.format(Decimal(str(hist.GetRMSError(1)*muScale)))
0522             statLabel += muUnit
0523         else:
0524             fitResults = self.__fitGauss__(hist)
0525             if not isinstance(fitResults, int): 
0526                 delimeter = ", "
0527                 meanFit = fitResults.GetParameter(1)
0528                 meanFitError = fitResults.GetParError(1)
0529                 sigmaFit = fitResults.GetParameter(2)
0530                 sigmaFitError = fitResults.GetParError(2)
0531                 statLabel += "#mu="
0532                 if meanFit >= 0.:
0533                     statLabel += (" "+formScie).format(Decimal(str(meanFit)))
0534                     if self.args['useFitError']:
0535                         statLabel += " #pm "
0536                         statLabel += form.format(Decimal(str(meanFitError)))
0537                 else:
0538                     statLabel += formScie.format(Decimal(str(meanFit)))
0539                     if self.args['useFitError']:
0540                         statLabel += " #pm "
0541                         statLabel += form.format(Decimal(str(meanFitError)))  
0542                 statLabel += delimeter
0543                 statLabel += "#sigma="
0544                 statLabel += (" "+form).format(Decimal(str(sigmaFit)))
0545                 if self.args['useFitError']:
0546                     statLabel += " #pm "
0547                     statLabel += form.format(Decimal(str(sigmaFitError))) 
0548                 statLabel += muUnit
0549                         
0550         return statLabel
0551         
0552     def __setTHStyle__(self,objects):
0553         ##############################################################
0554         #Set histogram labels, axis titles, line color, stat bar, etc.
0555         ##############################################################
0556 
0557         #define DMR-specific properties
0558         varsX = {'medianX' : "median(x\'_{pred}-x\'_{hit})[#mum]",
0559                  'medianY' : "median(y\'_{pred}-y\'_{hit})[#mum]",
0560                  'DrmsNRX' : "RMS((x\'_{pred}-x\'_{hit})/#sigma)",
0561                  'DrmsNRY' : "RMS((y\'_{pred}-y\'_{hit})/#sigma)"
0562                 }
0563         self.varsX = varsX
0564         varsY = {'medianX' : "luminosity-weighted number of modules",
0565                  'medianY' : "luminosity-weighted number of modules", 
0566                  'DrmsNRX' : "luminosity-weighted number of modules",
0567                  'DrmsNRY' : "luminosity-weighted number of modules" 
0568                 }
0569         limitX = {'min' : 10000, 'max' : 10000} 
0570  
0571         #set specific style for DMRs
0572         for objName,objList in objects.items():
0573             for obj in objList:
0574                 #axis
0575                 scaleFactor ="" 
0576                 obj['hist'].GetXaxis().SetTitle(varsX[obj['var']])
0577                 obj['hist'].GetXaxis().SetTitleFont(obj['hist'].GetYaxis().GetTitleFont()) 
0578                 obj['hist'].GetYaxis().SetTitleSize(0.038) 
0579                 obj['hist'].GetYaxis().SetTitleOffset(1.7)
0580                 if "median" in obj['var']:
0581                     scaleFactor ="/"+'{:.2f}'.format((obj['hist'].GetXaxis().GetXmax()*limitX['max']-obj['hist'].GetXaxis().GetXmin()*limitX['min'])/obj['hist'].GetXaxis().GetNbins())+" #mum"
0582                     minX = obj['hist'].GetXaxis().GetXmin()
0583                     maxX = obj['hist'].GetXaxis().GetXmax()
0584                     obj['hist'].GetXaxis().SetLimits(minX*limitX['min'],maxX*limitX['max']) 
0585                 obj['hist'].GetYaxis().SetTitle(varsY[obj['var']]+scaleFactor)
0586 
0587                 #main title
0588                 obj['hist'].SetTitle("")
0589 
0590                 #line color & style
0591                 if len(self.args['objects']) != 0:
0592                     if obj['type'] == "MC":
0593                         obj['hist'].SetLineColor(self.args['colors'][self.args['objects'].index(objName)])  
0594                         obj['hist'].SetLineStyle(self.args['styles'][self.args['objects'].index(objName)])
0595                         obj['hist'].SetLineWidth(3) #2
0596                     elif obj['type'] == "DATA":
0597                         obj['hist'].SetMarkerColor(self.args['colors'][self.args['objects'].index(objName)])
0598                         obj['hist'].SetLineColor(self.args['colors'][self.args['objects'].index(objName)])
0599                         obj['hist'].SetMarkerStyle(self.args['styles'][self.args['objects'].index(objName)])
0600                         obj['hist'].SetMarkerSize(1.5)  
0601 
0602         #set general style for DMRs
0603         tStyle = ROOT.TStyle("StyleCMS","Style CMS")
0604 
0605         #zero horizontal error bars
0606         tStyle.SetErrorX(0)
0607 
0608         #canvas settings
0609         tStyle.SetCanvasBorderMode(0)
0610         tStyle.SetCanvasColor(ROOT.kWhite)
0611         tStyle.SetCanvasDefH(800) #800
0612         tStyle.SetCanvasDefW(800)
0613         tStyle.SetCanvasDefX(0)
0614         tStyle.SetCanvasDefY(0)
0615 
0616         #frame settings
0617         tStyle.SetFrameBorderMode(0)
0618         tStyle.SetFrameBorderSize(10)
0619         tStyle.SetFrameFillColor(ROOT.kBlack)
0620         tStyle.SetFrameFillStyle(0)
0621         tStyle.SetFrameLineColor(ROOT.kBlack)
0622         tStyle.SetFrameLineStyle(0)
0623         tStyle.SetFrameLineWidth(1)
0624         tStyle.SetLineWidth(2)
0625 
0626         #pad settings
0627         tStyle.SetPadBorderMode(0)
0628         tStyle.SetPadColor(ROOT.kWhite)
0629         tStyle.SetPadGridX(False)
0630         tStyle.SetPadGridY(False)
0631         tStyle.SetGridColor(0)
0632         tStyle.SetGridStyle(3)
0633         tStyle.SetGridWidth(1) 
0634 
0635         #margins
0636         tStyle.SetPadTopMargin(0.08)
0637         tStyle.SetPadBottomMargin(0.13)
0638         tStyle.SetPadLeftMargin(0.16)
0639         tStyle.SetPadRightMargin(0.05)
0640 
0641         #common histogram settings
0642         tStyle.SetHistLineStyle(0)
0643         tStyle.SetHistLineWidth(3)
0644         tStyle.SetMarkerSize(0.8)
0645         tStyle.SetEndErrorSize(4)
0646         tStyle.SetHatchesLineWidth(1)
0647 
0648         #stat box
0649         tStyle.SetOptFile(0) 
0650 
0651         #axis settings
0652         tStyle.SetAxisColor(1,"XYZ")
0653         tStyle.SetTickLength(0.03,"XYZ")
0654         tStyle.SetNdivisions(510,"XYZ")
0655         tStyle.SetPadTickX(1)
0656         tStyle.SetPadTickY(1)
0657         tStyle.SetStripDecimals(ROOT.kFALSE)
0658 
0659         #axis labels and titles
0660         tStyle.SetTitleColor(1,"XYZ")
0661         tStyle.SetLabelColor(1,"XYZ")
0662         tStyle.SetLabelFont(42,"XYZ")
0663         tStyle.SetLabelOffset(0.007,"XYZ")
0664         tStyle.SetLabelSize(0.04,"XYZ")
0665         tStyle.SetTitleFont(42,"XYZ")
0666         tStyle.SetTitleSize(0.047,"XYZ")
0667         tStyle.SetTitleXOffset(1.2)
0668         tStyle.SetTitleYOffset(1.7)
0669 
0670         #legend
0671         tStyle.SetLegendBorderSize(0)
0672         tStyle.SetLegendTextSize(self.legendTextSize)
0673         tStyle.SetLegendFont(42)
0674 
0675         #assign changes to gROOT current style
0676         tStyle.cd()
0677 
0678         return tStyle
0679 
0680     def __beautify__(self, canvas, CMSextraLabel, eraLabel):
0681         #################################
0682         #Add CMS and era labels to canvas
0683         #################################
0684 
0685         leftMargin = canvas.GetLeftMargin()
0686         rightMargin = canvas.GetRightMargin()
0687         topMargin = canvas.GetTopMargin() 
0688         canvas.cd() 
0689 
0690         #CMStext
0691         CMSlabel = "CMS"
0692         CMSextraOffset = 0.10  
0693         CMStext = ROOT.TLatex()
0694         CMSextra = ROOT.TLatex()
0695 
0696         CMStext.SetNDC()
0697         CMSextra.SetNDC()
0698 
0699         CMStext.SetTextAngle(0)
0700         CMSextra.SetTextAngle(0)
0701 
0702         CMStext.SetTextColor(ROOT.kBlack)
0703         CMSextra.SetTextColor(ROOT.kBlack) 
0704  
0705         CMStext.SetTextFont(61)
0706         CMSextra.SetTextFont(52)
0707   
0708         CMStext.SetTextAlign(11)
0709         CMStext.SetTextSize(0.045)
0710         CMSextra.SetTextSize(0.035)
0711 
0712         CMStext.DrawLatex(leftMargin,1.-topMargin+0.01,CMSlabel)
0713         CMSextra.DrawLatex(leftMargin+CMSextraOffset,1-topMargin+0.01,CMSextraLabel) 
0714 
0715         #Era text
0716         eraText = ROOT.TLatex()
0717         eraText.SetNDC()
0718         eraText.SetTextAngle(0)
0719         eraText.SetTextColor(ROOT.kBlack)
0720         eraText.SetTextFont(42)    
0721         eraText.SetTextAlign(33)
0722         eraText.SetTextSize(0.035)
0723         eraText.DrawLatex(1.-rightMargin,1.-topMargin+0.035,eraLabel)
0724 
0725         #Redraw axis
0726         canvas.RedrawAxis()  
0727 
0728     def __cleanSingle__(self):
0729         #####################
0730         #Move all final files 
0731         #to output directory
0732         #####################
0733 
0734         for dirpath,dirs,files in os.walk(self.cwd):
0735             if dirpath != self.cwd: continue
0736             for n_file in files:
0737                 if ".png" in n_file or ".pdf" in n_file or ".eps" in n_file:
0738                     self.__log__("i","File "+n_file+" was created.")
0739                     os.system("mv "+n_file+" "+self.outputDir)
0740         self.__log__("i","Done.")
0741 
0742     def __clean__(self):
0743         #####################
0744         #Move all final files 
0745         #to output directory
0746         #####################
0747        
0748         for datafile in self.dataFiles:  
0749             os.system("mv "+datafile+" "+self.outputDir)
0750         for mcfile in self.MCobjects:
0751             os.system("mv "+mcfile+" "+self.outputDir)
0752         for dirpath,dirs,files in os.walk(self.cwd):
0753             if dirpath != self.cwd: continue
0754             for n_file in files:
0755                 if ".png" in n_file or ".pdf" in n_file or ".eps" in n_file:
0756                     self.__log__("i","File "+n_file+" was created.")
0757                     os.system("mv "+n_file+" "+self.outputDir) 
0758         self.__log__("i","Done.")
0759 
0760     def __finalize__(self):
0761         ##########################################
0762         #List all created figures and say goodbye 
0763         ##########################################
0764 
0765         for dirpath,dirs,files in os.walk(self.outputDir):
0766             for n_file in files:
0767                 if ".png" in n_file or ".pdf" in n_file or ".eps" in n_file:
0768                     self.__log__("i","File "+n_file+" was created.")
0769         self.__log__("i","Done.")        
0770 
0771         
0772     def addDATA(self,filename):
0773         #############################################################
0774         #Add DATA objects in one file to be plotted together with MC
0775         #############################################################
0776         if os.path.isfile(str(filename)):
0777             self.__log__("i","DATA file: "+str(filename)+" was added for plotting.")
0778             self.dataFiles.append(str(filename)) 
0779         elif os.path.isfile(os.path.join(str(filename),self.fileBaseName)):
0780             self.__log__("i","DATA file: "+os.path.join(str(filename),self.fileBaseName)+" was added for plotting.")
0781             self.dataFiles.append(os.path.join(str(filename),self.fileBaseName))
0782         else:
0783             self.__log__("w","DATA file: "+os.path.join(str(filename),self.fileBaseName)+" NOT found.")
0784 
0785     def addDirDATA(self, dataDir):
0786         #####################################################################
0787         #Add directory of single DATA files to be plotted together with MC
0788         #####################################################################
0789         if os.path.isdir(dataDir):
0790             self.__log__("i","DATA dir: "+dataDir+" was added for plotting.")
0791             self.dataDirs.append(dataDir)
0792         else: 
0793             self.__log__("w","DATA dir: "+dataDir+" NOT found.")
0794 
0795         #Create list of dataFiles #FIXME for multiple DATA inputs
0796         if len(self.dataDirs) != 0:
0797             if self.args['isDMR']:   
0798                 for dataDir in self.dataDirs:
0799                     for root,dirs,files in os.walk(dataDir):
0800                         for dir in dirs:
0801                             if dir.startswith("offline"): 
0802                                 self.dataFiles.append(dataDir+"/"+dir+"/ExtendedOfflineValidation_Images/OfflineValidationSummary.root")
0803 
0804     def addDirMC(self, mcDir):
0805         #####################################################################
0806         #Add directory of single MC file to be plotted together with DATA
0807         #####################################################################
0808         if os.path.isdir(mcDir):
0809             self.__log__("i","MC dir: "+mcDir+" was added for plotting.")
0810             nFiles = 0
0811             for dirpath,dirs,files in os.walk(mcDir):
0812                 for file in files: 
0813                     if self.fileBaseName.replace(".root","") in file and file.endswith(".root"):
0814                         self.__log__("i","MC file: "+str(file)+" was added for plotting.")
0815                         self.MCobjects.append(os.path.join(dirpath,file))
0816                         nFiles += 1
0817             if nFiles == 0:
0818                 self.__log__("w","No MC file found in "+str(mcDir)+".")
0819         else:
0820             self.__log__("w","MC dir: "+mcDir+" NOT found.")
0821 
0822     def addMC(self,filename):
0823         #############################################################
0824         #Add MC objects in one file to be plotted together with DATA
0825         #############################################################
0826         if os.path.isfile(str(filename)):
0827             self.__log__("i","MC file: "+str(filename)+" was added for plotting.")
0828             self.MCobjects.append(str(filename))
0829         elif os.path.isfile(os.path.join(str(filename),self.fileBaseName)):
0830             self.__log__("i","MC file: "+os.path.join(str(filename),self.fileBaseName)+" was added for plotting.") 
0831             self.MCobjects.append(os.path.join(str(filename),self.fileBaseName))
0832         else:
0833             self.__log__("w","MC file: "+str(os.path.join(str(filename),self.fileBaseName))+" NOT found.")  
0834 
0835     def plotSingle(self):
0836         ##############################################
0837     #Auxiliary plotter for unweighted Data and MC
0838         ##############################################
0839 
0840         #check for input file and create output dir
0841         self.__createSingleArchitecture__()
0842 
0843         #access histograms in rootfiles, select different validation objects and store them separately
0844         objects = self.__defineSingleObjects__()
0845         objectsData = objects['DATA']
0846         objectsMC = objects['MC'] 
0847 
0848         #set histogram style
0849         for objDict in objectsData:
0850             self.__setTHStyle__(objDict)
0851         for objDict in objectsMC:
0852             self.__setTHStyle__(objDict)
0853 
0854         #really plot
0855         ROOT.gROOT.SetBatch(True) #turn off printing canvas on screen
0856         ROOT.gROOT.ProcessLine("gErrorIgnoreLevel = 1001;") #turn off printing messages on terminal
0857       
0858         for objDict in objectsData:
0859             for segment in self.segments:
0860                 for var in self.varsX:
0861                     id = "0"
0862                     for key in objDict.keys():
0863                         for _obj in objDict[key]:     
0864                             id = _obj['id']  
0865                     canvas = ROOT.TCanvas(id+"_"+var+"_"+segment)
0866                     canvas.cd()
0867 
0868                     #set labels positioning
0869                     segmentText = {'text' : segment, 'xmin' : 0.0, 'xmax' : 0.0}
0870                     statText = {'xmin' : 0.0, 'xmax' : 0.0}
0871                     if "median" in var:
0872                          segmentText['xmin'] = 2.5
0873                          segmentText['xmax'] = 3.5
0874                          statText['xmin'] = 0.20
0875                          statText['xmax'] = 0.85
0876                     else:
0877                          segmentText['xmin'] = 1.4
0878                          segmentText['xmax'] = 1.6
0879                          statText['xmin'] = 0.65
0880                          statText['xmax'] = 0.95
0881 
0882                     #order plots & prepare y-axis scale factors  
0883                     isEmpty = True
0884                     maxY = 0.0
0885                     objGroup = []
0886                     for objName in self.objNameList: #follow plotting order
0887                         for obj in objDict[objName]:
0888                             if obj['var'] == var and obj['segment'] == segment:
0889                                 if obj['hist'].GetBinContent(obj['hist'].GetMaximumBin()) >= maxY:
0890                                     maxY = obj['hist'].GetBinContent(obj['hist'].GetMaximumBin())
0891                                 isEmpty = False
0892                                 legendLabel = objName.replace("_"," ")
0893                                 if len(self.args['labels']) != 0:
0894                                     legendLabel = self.args['labels'][self.args['objects'].index(objName)]
0895 
0896                                 #Add MC for each data file
0897                                 histsMC = []
0898                                 labelsMC = []
0899                                 statsMC = []      
0900                                 for objDictMC in objectsMC:
0901                                     for objNameMC in self.objNameListMC:
0902                                         for objMC in objDictMC[objNameMC]:
0903                                             if objMC['var'] == var and objMC['segment'] == segment:
0904                                                 if objMC['hist'].GetBinContent(objMC['hist'].GetMaximumBin()) >= maxY:
0905                                                     maxY = objMC['hist'].GetBinContent(objMC['hist'].GetMaximumBin())
0906                                                 legendLabelMC = objNameMC.replace("_"," ")
0907                                                 if len(self.args['labels']) != 0:
0908                                                     legendLabelMC = self.args['labels'][self.args['objects'].index(objNameMC)]
0909                                                 objMC['hist'].SetDirectory(0)
0910                                                 histsMC.append(objMC['hist'])
0911                                                 labelsMC.append(legendLabelMC)
0912                                                 statsMC.append(self.__getStat__(objMC['hist'],var))
0913                                 objGroup.append({'hist'    : obj['hist'],
0914                                                  'histsMC' : histsMC,
0915                                                  'labelsMC': labelsMC,
0916                                                  'statsMC' : statsMC, 
0917                                                  'label'   : legendLabel,
0918                                                  'stat'    : self.__getStat__(obj['hist'],var)
0919                                                 })
0920                     #draw & save
0921                     if not isEmpty:
0922                         datasetType = "singlemuon" #FIXME make it an option
0923                         legMinY = (1./self.legendOffset)+(1.-1./self.legendOffset)*(self.maxEntriesPerColumn-len(objGroup))/(self.maxEntriesPerColumn*3)
0924                         nColumns = 1
0925                         if len(objGroup) > self.maxEntriesPerColumn:
0926                             nColumns = 2
0927                             legMinY = 1./self.legendOffset
0928                         leg = ROOT.TLegend(0.08,legMinY,0.45,0.88)
0929                         leg.SetNColumns(nColumns)
0930                         seg = ROOT.TLatex()
0931                         maxX = objGroup[0]['hist'].GetXaxis().GetXmax()
0932                         stat = self._styledTPaveText(maxX*statText['xmin'],(legMinY+0.025)*self.legendOffset*maxY,maxX*statText['xmax'],0.95*self.legendOffset*maxY,var)
0933                         for igroup,group in enumerate(objGroup):
0934                             group['hist'].GetYaxis().SetRangeUser(0,self.legendOffset*maxY)
0935                             leg.AddEntry(group['hist'],group['label'],"l")
0936                             stat.AddText(group['stat'])
0937                             group['hist'].Draw("HISTSAME")
0938                             #for last data group add also MC
0939                             if igroup == len(objGroup)-1:
0940                                 for ihist,histmc in enumerate(group['histsMC']):
0941                                     leg.AddEntry(histmc,group['labelsMC'][ihist],"l")
0942                                     stat.AddText(group['statsMC'][ihist])
0943                                     histmc.Draw("HISTSAME")         
0944                         leg.Draw("SAME")
0945                         seg.DrawLatex(segmentText['xmin'],self.segmentTextOffset['ymin']*maxY,segmentText['text'])
0946                         stat.Draw("SAME")
0947                         self.__beautify__(canvas,self.args['CMSlabel'],self.args['Rlabel'])
0948                         canvas.SaveAs(self.outputDir+"/offline_"+datasetType+"_"+str(id)+"/"+var+"_"+segment+".png")
0949                         canvas.SaveAs(self.outputDir+"/offline_"+datasetType+"_"+str(id)+"/"+var+"_"+segment+".pdf")
0950                         self.__log__("i","Saving "+self.outputDir+"/offline_"+datasetType+"_"+str(id)+"/"+var+"_"+segment)
0951 
0952             #finalize
0953             #self.__cleanSingle__()       
0954             self.__log__("i","Done.") 
0955 
0956     def plot(self):
0957         ##################
0958         #Main plotter part
0959         ##################
0960        
0961         #check for input file and create output dir if needed 
0962         self.__createArchitecture__()
0963 
0964         #access histograms in rootfiles, select different validation objects and store them separately
0965         objects = self.__defineObjects__() 
0966 
0967         #set histogram style
0968         currentStyle = self.__setTHStyle__(objects) #NOTE: for CMSSW_11 and higher, currentStyle must be returned to plotting function
0969 
0970         #really plot
0971         ROOT.gROOT.SetBatch(True) #turn off printing canvas on screen
0972         ROOT.gROOT.ProcessLine("gErrorIgnoreLevel = 1001;") #turn off printing messages on terminal
0973        
0974         for segment in self.segments:
0975             for var in self.varsX:
0976                 canvas = ROOT.TCanvas(var+"_"+segment)
0977                 canvas.cd()
0978 
0979                 #set labels positioning
0980                 segmentText = {'text' : segment, 'xmin' : 0.0, 'xmax' : 0.0}
0981                 statText = {'xmin' : 0.0, 'xmax' : 0.0}
0982                 if "median" in var:
0983                      segmentText['xmin'] = 2.5
0984                      segmentText['xmax'] = 3.5
0985                      statText['xmin'] = 0.27
0986                      statText['xmax'] = 0.92
0987                 else:
0988                      segmentText['xmin'] = 1.4
0989                      segmentText['xmax'] = 1.6
0990                      statText['xmin'] = 0.75
0991                      statText['xmax'] = 0.95
0992                 
0993                 #order plots & prepare y-axis scale factors  
0994                 isEmpty = True
0995                 maxY = 0.0
0996                 objGroup = [] 
0997                 for objName in self.objNameList: #follow plotting order
0998                     for obj in objects[objName]:
0999                         if obj['var'] == var and obj['segment'] == segment:
1000                             if obj['hist'].GetBinContent(obj['hist'].GetMaximumBin()) >= maxY:
1001                                 maxY = obj['hist'].GetBinContent(obj['hist'].GetMaximumBin())
1002                             isEmpty = False
1003                             legendLabel = objName.replace("_"," ")
1004                             if len(self.args['labels']) != 0:
1005                                 legendLabel = self.args['labels'][self.args['objects'].index(objName)]
1006                             drawStyle = ""
1007                             legStyle = ""
1008                             if obj['type'] == "MC": 
1009                                 drawStyle = "HIST SAME"
1010                                 legStyle = "l"
1011                             if obj['type'] == "DATA": 
1012                                 drawStyle += "P HIST SAME"
1013                                 legStyle = "p"     
1014                             objGroup.append({'hist'        : obj['hist'], 
1015                                              'label'       : legendLabel,
1016                                              'stat'        : self.__getStat__(obj['hist'],var),
1017                                              'drawStyle'   : drawStyle,
1018                                              'legStyle'    : legStyle  
1019                                             })
1020 
1021                 #draw & save
1022                 if not isEmpty:
1023                     legMinY = (1./self.legendOffset)+(1.-1./self.legendOffset)*(self.maxEntriesPerColumn-len(objGroup))/(self.maxEntriesPerColumn*3)
1024                     nColumns = 1   
1025                     if len(objGroup) > self.maxEntriesPerColumn:
1026                         nColumns = 2
1027                         legMinY = 1./self.legendOffset
1028                     leg = ROOT.TLegend(0.20,legMinY,0.50,0.88)
1029                     leg.SetNColumns(nColumns)
1030                     seg = ROOT.TLatex()
1031                     maxX = objGroup[0]['hist'].GetXaxis().GetXmax() 
1032                     stat = self._styledTPaveText(maxX*statText['xmin'],(legMinY+0.025)*self.legendOffset*maxY,maxX*statText['xmax'],0.95*self.legendOffset*maxY,var)
1033                     for group in objGroup:
1034                         group['hist'].GetYaxis().SetRangeUser(0,self.legendOffset*maxY)
1035                         leg.AddEntry(group['hist'],group['label'],group['legStyle'])
1036                         stat.AddText(group['stat']) 
1037                         group['hist'].Draw(group['drawStyle'])   
1038                     leg.Draw("SAME")  
1039                     seg.DrawLatex(segmentText['xmin'],self.segmentTextOffset['ymin']*maxY,segmentText['text'])
1040                     stat.Draw("SAME")
1041                     self.__beautify__(canvas,self.args['CMSlabel'],self.args['Rlabel'])
1042                     canvas.Print(self.outputDir+"/"+var+"_"+segment+".png")
1043                     canvas.Print(self.outputDir+"/"+var+"_"+segment+".pdf") 
1044         
1045         #finalize
1046         self.__finalize__()