CMS 3D CMS Logo

PhotonAnalyzer.py
Go to the documentation of this file.
1 import operator
2 import itertools
3 import copy
4 import types
5 import re
6 
7 from ROOT import TLorentzVector
8 from ROOT import heppy
9 
10 from PhysicsTools.Heppy.analyzers.core.Analyzer import Analyzer
11 from PhysicsTools.HeppyCore.framework.event import Event
12 from PhysicsTools.HeppyCore.statistics.counter import Counter, Counters
13 from PhysicsTools.Heppy.analyzers.core.AutoHandle import AutoHandle
14 from PhysicsTools.Heppy.physicsobjects.Photon import Photon
15 from PhysicsTools.Heppy.physicsutils.PhotonCalibrator import Run2PhotonCalibrator
16 
17 from PhysicsTools.HeppyCore.utils.deltar import deltaR, deltaPhi, bestMatch, matchObjectCollection3
18 
19 import PhysicsTools.HeppyCore.framework.config as cfg
20 
21 
22 class PhotonAnalyzer( Analyzer ):
23 
24 
25  def __init__(self, cfg_ana, cfg_comp, looperName ):
26  super(PhotonAnalyzer,self).__init__(cfg_ana,cfg_comp,looperName)
27  self.etaCentral = self.cfg_ana.etaCentral if hasattr(self.cfg_ana, 'etaCentral') else 9999
28  # footprint removed isolation
29  self.doFootprintRemovedIsolation = getattr(cfg_ana, 'doFootprintRemovedIsolation', False)
31  self.footprintRemovedIsolationPUCorr = self.cfg_ana.footprintRemovedIsolationPUCorr
33  #FIXME: only Embedded works
34  if self.cfg_ana.doPhotonScaleCorrections:
35  conf = cfg_ana.doPhotonScaleCorrections
36  self.photonEnergyCalibrator = Run2PhotonCalibrator(
37  conf['data'],
38  cfg_comp.isMC,
39  conf['isSync'] if 'isSync' in conf else False,
40  )
41 
42  def declareHandles(self):
43  super(PhotonAnalyzer, self).declareHandles()
44  self.handles['rhoPhoton'] = AutoHandle( self.cfg_ana.rhoPhoton, 'double')
45 
46  #----------------------------------------
47  # DECLARATION OF HANDLES OF PHOTONS STUFF
48  #----------------------------------------
49 
50  self.handles['photons'] = AutoHandle( self.cfg_ana.photons,'std::vector<pat::Photon>')
51  self.mchandles['packedGen'] = AutoHandle( 'packedGenParticles', 'std::vector<pat::PackedGenParticle>' )
52  self.mchandles['prunedGen'] = AutoHandle( 'prunedGenParticles', 'std::vector<reco::GenParticle>' )
53 
54  self.handles['packedCandidates'] = AutoHandle( 'packedPFCandidates', 'std::vector<pat::PackedCandidate>')
55  self.handles['jets'] = AutoHandle( "slimmedJets", 'std::vector<pat::Jet>' )
56 
57 
58  def beginLoop(self, setup):
59  super(PhotonAnalyzer,self).beginLoop(setup)
60  self.counters.addCounter('events')
61  count = self.counters.counter('events')
62  count.register('all events')
63  count.register('has >=1 gamma at preselection')
64  count.register('has >=1 selected gamma')
65 
66  def makePhotons(self, event):
67  event.allphotons = map( Photon, self.handles['photons'].product() )
68  event.allphotons.sort(key = lambda l : l.pt(), reverse = True)
69 
70  event.selectedPhotons = []
71  event.selectedPhotonsCentral = []
72 
74  # values are taken from EGamma implementation: https://github.com/cms-sw/cmssw/blob/CMSSW_7_6_X/RecoEgamma/PhotonIdentification/plugins/PhotonIDValueMapProducer.cc#L198-L199
75  self.IsolationComputer.setPackedCandidates(self.handles['packedCandidates'].product(), -1, 0.1, 0.2)
76 
77  # Photon scale calibrations
78  if self.cfg_ana.doPhotonScaleCorrections:
79  for gamma in event.allphotons:
80  self.photonEnergyCalibrator.correct(gamma, event.run)
81 
82  foundPhoton = False
83  for gamma in event.allphotons:
84  if gamma.pt() < self.cfg_ana.ptMin: continue
85  if abs(gamma.eta()) > self.cfg_ana.etaMax: continue
86  foundPhoton = True
87 
88  gamma.rho = float(self.handles['rhoPhoton'].product()[0])
89  # https://twiki.cern.ch/twiki/bin/view/CMS/CutBasedPhotonIdentificationRun2#Selection_implementation_details
90  if abs(gamma.eta()) < 1.0: gamma.EffectiveArea03 = [ 0.0234, 0.0053, 0.078 ]
91  elif abs(gamma.eta()) < 1.479: gamma.EffectiveArea03 = [ 0.0189, 0.0103, 0.0629 ]
92  elif abs(gamma.eta()) < 2.0: gamma.EffectiveArea03 = [ 0.0171, 0.0057, 0.0264 ]
93  elif abs(gamma.eta()) < 2.2: gamma.EffectiveArea03 = [ 0.0129, 0.0070, 0.0462 ]
94  elif abs(gamma.eta()) < 2.3: gamma.EffectiveArea03 = [ 0.0110, 0.0152, 0.0740 ]
95  elif abs(gamma.eta()) < 2.4: gamma.EffectiveArea03 = [ 0.0074, 0.0232, 0.0924 ]
96  else: gamma.EffectiveArea03 = [ 0.0035, 0.1709, 0.1484 ]
97 
100 
101  gamma.relIso = (max(gamma.chargedHadronIso()-gamma.rho*gamma.EffectiveArea03[0],0) + max(gamma.neutralHadronIso()-gamma.rho*gamma.EffectiveArea03[1],0) + max(gamma.photonIso() - gamma.rho*gamma.EffectiveArea03[2],0))/gamma.pt()
102 
103  def idWP(gamma,X):
104  """Create an integer equal to 1-2-3 for (loose,medium,tight)"""
105 
106  id=0
107  if gamma.photonID(X%"Loose"):
108  id=1
109  #if gamma.photonID(X%"Medium"):
110  # id=2
111  if gamma.photonID(X%"Tight"):
112  id=3
113  return id
114 
115  gamma.idCutBased = idWP(gamma, "PhotonCutBasedID%s")
116 
117 
118  keepThisPhoton = True
119 
120  if self.cfg_ana.gammaID=="PhotonCutBasedIDLoose_CSA14" or self.cfg_ana.gammaID=="PhotonCutBasedIDLoose_PHYS14" :
121  gamma.idCutBased = gamma.photonIDCSA14(self.cfg_ana.gammaID)
122  # we're keeing sigmaietaieta sidebands:
123  keepThisPhoton = gamma.photonIDCSA14(self.cfg_ana.gammaID, True)
124 
125  if gamma.hasPixelSeed():
126  keepThisPhoton = False
127  gamma.idCutBased = 0
128  elif "NoIso" in self.cfg_ana.gammaID:
129  idName = re.split('_NoIso',self.cfg_ana.gammaID)
130  keepThisPhoton = gamma.passPhotonID(idName[0],self.cfg_ana.conversionSafe_eleVeto)
131  basenameID = re.split('_looseSieie',idName[0])
132  gamma.idCutBased = gamma.passPhotonID(basenameID[0],self.cfg_ana.conversionSafe_eleVeto)
133  else:
134  # Reading from miniAOD directly
135  #keepThisPhoton = gamma.photonID(self.cfg_ana.gammaID)
136 
137  # implement cut based ID with CMGTools
138  keepThisPhoton = gamma.passPhotonID(self.cfg_ana.gammaID,self.cfg_ana.conversionSafe_eleVeto) and gamma.passPhotonIso(self.cfg_ana.gammaID,self.cfg_ana.gamma_isoCorr)
139 
140  if keepThisPhoton:
141  event.selectedPhotons.append(gamma)
142 
143  if keepThisPhoton and abs(gamma.eta()) < self.etaCentral:
144  event.selectedPhotonsCentral.append(gamma)
145 
146  event.selectedPhotons.sort(key = lambda l : l.pt(), reverse = True)
147  event.selectedPhotonsCentral.sort(key = lambda l : l.pt(), reverse = True)
148 
149  self.counters.counter('events').inc('all events')
150  if foundPhoton: self.counters.counter('events').inc('has >=1 gamma at preselection')
151  if len(event.selectedPhotons): self.counters.counter('events').inc('has >=1 selected gamma')
152 
153  def matchPhotons(self, event):
154  event.genPhotons = [ x for x in event.genParticles if x.status() == 1 and abs(x.pdgId()) == 22 ]
155  event.genPhotonsWithMom = [ x for x in event.genPhotons if x.numberOfMothers()>0 ]
156  event.genPhotonsWithoutMom = [ x for x in event.genPhotons if x.numberOfMothers()==0 ]
157  event.genPhotonsMatched = [ x for x in event.genPhotonsWithMom if abs(x.mother(0).pdgId())<23 or x.mother(0).pdgId()==2212 ]
158  match = matchObjectCollection3(event.allphotons, event.genPhotonsMatched, deltaRMax = 0.1)
159  matchNoMom = matchObjectCollection3(event.allphotons, event.genPhotonsWithoutMom, deltaRMax = 0.1)
160  packedGenParts = [ p for p in self.mchandles['packedGen'].product() if abs(p.eta()) < 3.1 ]
161  partons = [ p for p in self.mchandles['prunedGen'].product() if (p.status()==23 or p.status()==22) and abs(p.pdgId())<22 ]
162  for gamma in event.allphotons:
163  gen = match[gamma]
164  gamma.mcGamma = gen
165  if gen and gen.pt()>=0.5*gamma.pt() and gen.pt()<=2.*gamma.pt():
166  gamma.mcMatchId = 22
167  sumPt03 = 0.;
168  sumPt04 = 0.;
169  for part in packedGenParts:
170  if abs(part.pdgId())==12: continue # exclude neutrinos
171  if abs(part.pdgId())==14: continue
172  if abs(part.pdgId())==16: continue
173  if abs(part.pdgId())==18: continue
174  deltar = deltaR(gen.eta(), gen.phi(), part.eta(), part.phi())
175  if deltar <= 0.3:
176  sumPt03 += part.pt()
177  if deltar <= 0.4:
178  sumPt04 += part.pt()
179  sumPt03 -= gen.pt()
180  sumPt04 -= gen.pt()
181  if sumPt03<0. : sumPt03=0.
182  if sumPt04<0. : sumPt04=0.
183  gamma.genIso03 = sumPt03
184  gamma.genIso04 = sumPt04
185  # match to parton
186  deltaRmin = 999.
187  for p in partons:
188  deltar = deltaR(gen.eta(), gen.phi(), p.eta(), p.phi())
189  if deltar < deltaRmin:
190  deltaRmin = deltar
191  gamma.drMinParton = deltaRmin
192  else:
193  genNoMom = matchNoMom[gamma]
194  if genNoMom:
195  gamma.mcMatchId = 7
196  sumPt03 = 0.;
197  sumPt04 = 0.;
198  for part in packedGenParts:
199  if abs(part.pdgId())==12: continue # exclude neutrinos
200  if abs(part.pdgId())==14: continue
201  if abs(part.pdgId())==16: continue
202  if abs(part.pdgId())==18: continue
203  deltar = deltaR(genNoMom.eta(), genNoMom.phi(), part.eta(), part.phi())
204  if deltar <= 0.3:
205  sumPt03 += part.pt()
206  if deltar <= 0.4:
207  sumPt04 += part.pt()
208  sumPt03 -= genNoMom.pt()
209  sumPt04 -= genNoMom.pt()
210  if sumPt03<0. : sumPt03=0.
211  if sumPt04<0. : sumPt04=0.
212  gamma.genIso03 = sumPt03
213  gamma.genIso04 = sumPt04
214  # match to parton
215  deltaRmin = 999.
216  for p in partons:
217  deltar = deltaR(genNoMom.eta(), genNoMom.phi(), p.eta(), p.phi())
218  if deltar < deltaRmin:
219  deltaRmin = deltar
220  gamma.drMinParton = deltaRmin
221  else:
222  gamma.mcMatchId = 0
223  gamma.genIso03 = -1.
224  gamma.genIso04 = -1.
225  gamma.drMinParton = -1.
226 
227 
228 
229 
230 
231  def checkMatch( self, eta, phi, particles, deltar ):
232 
233  for part in particles:
234  if deltaR(eta, phi, part.eta(), part.phi()) < deltar:
235  return True
236 
237  return False
238 
239 
240 
241 
242 
243  def computeRandomCone( self, event, eta, phi, deltarmax, charged, jets, photons ):
244 
245  if self.checkMatch( eta, phi, jets, 2.*deltarmax ):
246  return -1.
247 
248  if self.checkMatch( eta, phi, photons, 2.*deltarmax ):
249  return -1.
250 
251  if self.checkMatch( eta, phi, event.selectedLeptons, deltarmax ):
252  return -1.
253 
254  iso = 0.
255 
256  for part in charged:
257  if deltaR(eta, phi, part.eta(), part.phi()) > deltarmax : continue
258  #if deltaR(eta, phi, part.eta(), part.phi()) < 0.02: continue
259  iso += part.pt()
260 
261  return iso
262 
263 
264 
265 
266 
267 
268  def randomCone( self, event ):
269 
270  patcands = self.handles['packedCandidates'].product()
271  jets = self.handles['jets'].product()
272 
273  charged = [ p for p in patcands if ( p.charge() != 0 and abs(p.pdgId())>20 and abs(p.dz())<=0.1 and p.fromPV()>1 and p.trackHighPurity() ) ]
274  photons10 = [ p for p in patcands if ( p.pdgId() == 22 and p.pt()>10. ) ]
275  jets20 = [ j for j in jets if j.pt() > 20 and abs(j.eta())<2.5 ]
276 
277  for gamma in event.allphotons:
278 
279  etaPhot = gamma.eta()
280  phiPhot = gamma.eta()
281  pi = 3.14159
282  phiRC = phiPhot + 0.5*pi
283  while phiRC>pi:
284  phiRC -= 2.*pi
285 
286 
287  gamma.chHadIsoRC03 = self.computeRandomCone( event, etaPhot, phiRC, 0.3, charged, jets20, photons10 )
288  gamma.chHadIsoRC04 = self.computeRandomCone( event, etaPhot, phiRC, 0.4, charged, jets20, photons10 )
289 
290 
291  #try other side
292  phiRC = phiPhot - 0.5*pi
293  while phiRC<-pi:
294  phiRC += 2.*pi
295 
296  if gamma.chHadIsoRC03<0. : gamma.chHadIsoRC03 = self.computeRandomCone( event, etaPhot, phiRC, 0.3, charged, jets20, photons10 )
297  if gamma.chHadIsoRC04<0. : gamma.chHadIsoRC04 = self.computeRandomCone( event, etaPhot, phiRC, 0.4, charged, jets20, photons10 )
298 
299 
301  # cone deltar=0.3
302  gamma.ftprAbsIsoCharged03 = self.IsolationComputer.chargedAbsIso(gamma.physObj, 0.3, 0, 0.0);
303  gamma.ftprAbsIsoPho03 = self.IsolationComputer.photonAbsIsoRaw( gamma.physObj, 0.3, 0, 0.0);
304  gamma.ftprAbsIsoNHad03 = self.IsolationComputer.neutralHadAbsIsoRaw( gamma.physObj, 0.3, 0, 0.0);
305  gamma.ftprAbsIso03 = gamma.ftprAbsIsoCharged03 + gamma.ftprAbsIsoPho03 + gamma.ftprAbsIsoNHad03
306  if self.cfg_ana.gamma_isoCorr == "rhoArea":
307  gamma.ftprAbsIso03 = (max(gamma.ftprAbsIsoCharged03-gamma.rho*gamma.EffectiveArea03[0],0) + max(gamma.ftprAbsIsoPho03-gamma.rho*gamma.EffectiveArea03[1],0) + max(gamma.ftprAbsIsoNHad03 - gamma.rho*gamma.EffectiveArea03[2],0))
308  elif self.cfg_ana.gamma_isoCorr != 'raw':
309  raise RuntimeError("Unsupported gamma_isoCorr name '" + str(self.cfg_ana.gamma_isoCorr) + "'! For now only 'rhoArea', 'raw' are supported.")
310  gamma.ftprRelIso03 = gamma.ftprAbsIso03/gamma.pt()
311 
312  def printInfo(self, event):
313  print '----------------'
314  if len(event.selectedPhotons)>0:
315  print 'lenght: ',len(event.selectedPhotons)
316  print 'gamma candidate pt: ',event.selectedPhotons[0].pt()
317  print 'gamma candidate eta: ',event.selectedPhotons[0].eta()
318  print 'gamma candidate phi: ',event.selectedPhotons[0].phi()
319  print 'gamma candidate mass: ',event.selectedPhotons[0].mass()
320  print 'gamma candidate HoE: ',event.selectedPhotons[0].hOVERe()
321  print 'gamma candidate r9: ',event.selectedPhotons[0].full5x5_r9()
322  print 'gamma candidate sigmaIetaIeta: ',event.selectedPhotons[0].full5x5_sigmaIetaIeta()
323  print 'gamma candidate had iso: ',event.selectedPhotons[0].chargedHadronIso()
324  print 'gamma candidate neu iso: ',event.selectedPhotons[0].neutralHadronIso()
325  print 'gamma candidate gamma iso: ',event.selectedPhotons[0].photonIso()
326  print 'gamma idCutBased',event.selectedPhotons[0].idCutBased
327 
328 
329  def process(self, event):
330  self.readCollections( event.input )
331  self.makePhotons(event)
332 # self.printInfo(event)
333 
334  if self.cfg_ana.do_randomCone:
335  self.randomCone(event)
336 
337  if not self.cfg_comp.isMC:
338  return True
339 
340  if self.cfg_ana.do_mc_match and hasattr(event, 'genParticles'):
341  self.matchPhotons(event)
342 
343 
344  return True
345 
346 
347 setattr(PhotonAnalyzer,"defaultConfig",cfg.Analyzer(
348  class_object=PhotonAnalyzer,
349  photons='slimmedPhotons',
350  ptMin = 20,
351  etaMax = 2.5,
352  # energy scale corrections (off by default)
353  doPhotonScaleCorrections=False,
354  gammaID = "PhotonCutBasedIDLoose_CSA14",
355  rhoPhoton = 'fixedGridRhoFastjetAll',
356  gamma_isoCorr = 'rhoArea',
357  # Footprint-removed isolation, removing all the footprint of the photon
358  doFootprintRemovedIsolation = False, # off by default since it requires access to all PFCandidates
359  packedCandidates = 'packedPFCandidates',
360  footprintRemovedIsolationPUCorr = 'rhoArea', # Allowed options: 'rhoArea', 'raw' (uncorrected)
361  conversionSafe_eleVeto = False,
362  do_mc_match = True,
363  do_randomCone = False,
364  )
365 )
366 
def attachFootprintRemovedIsolation(self, gamma)
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
def __init__(self, cfg_ana, cfg_comp, looperName)
double deltaR(double eta1, double eta2, double phi1, double phi2)
Definition: TreeUtility.cc:17
def computeRandomCone(self, event, eta, phi, deltarmax, charged, jets, photons)
def checkMatch(self, eta, phi, particles, deltar)
def matchObjectCollection3
Definition: deltar.py:41