Back to home page

Project CMSSW displayed by LXR

 
 

    


File indexing completed on 2024-11-06 06:06:09

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