00001
00002
00003
00004
00005
00006 __all__ = [ "PmtTableWriterAlg", "mergeTables", "parseTable" ]
00007
00008
00009 from DybPython.DybPythonAlg import DybPythonAlg
00010 from GaudiPython import SUCCESS, FAILURE
00011 from GaudiPython import gbl
00012 import GaudiKernel.SystemOfUnits as units
00013 import math
00014
00015
00016 Detector = gbl.DayaBay.Detector
00017 Site = gbl.Site
00018 DetectorId = gbl.DetectorId
00019 Context = gbl.Context
00020 ServiceMode = gbl.ServiceMode
00021 PmtCalibData = gbl.DayaBay.PmtCalibData
00022 AdPmtSensor = gbl.DayaBay.AdPmtSensor
00023
00024
00025 class PmtTableWriterAlg(DybPythonAlg):
00026 "Generate a calibration table from calibration run statistics"
00027 def __init__(self,name):
00028 DybPythonAlg.__init__(self,name)
00029
00030 self.StatsPath = "/file0/pmtCalibLeadingEdge"
00031 self.OutputTableName = "pmtCalibTable.txt"
00032 self.IsDryAd = False
00033 self.SetDefaultCalib = True
00034 self.SourcePosition = [0.,0.,0.]
00035 return
00036
00037 def initialize(self):
00038 status = DybPythonAlg.initialize(self)
00039 if status.isFailure(): return status
00040 self.info("initializing")
00041
00042
00043 self.cableSvc = self.svc('ICableSvc','StaticCableSvc')
00044 if self.cableSvc == None:
00045 self.error("Failed to get StaticCableSvc")
00046 return FAILURE
00047 self.statsSvc = self.svc('IStatisticsSvc','StatisticsSvc')
00048 if self.statsSvc == None:
00049 self.error("Failed to get StatisticsSvc")
00050 return FAILURE
00051 self.pmtGeomSvc = self.svc('IPmtGeomInfoSvc','PmtGeomInfoSvc')
00052 if self.pmtGeomSvc == None:
00053 self.error("Failed to get PmtGeomInfoSvc")
00054 return FAILURE
00055 return status
00056
00057 def execute(self):
00058 self.info("executing")
00059 return SUCCESS
00060
00061 def finalize(self):
00062 self.info("finalizing")
00063
00064
00065 pmtCalibList = self.initializeCalibTable()
00066 self.info("I initialized the table")
00067
00068 status = self.updateCalibration(pmtCalibList)
00069 if not status.isSuccess(): return status
00070
00071 self.writeCalibTable(pmtCalibList)
00072 status = DybPythonAlg.finalize(self)
00073 return status
00074
00075 def initializeCalibTable(self):
00076
00077 context = Context()
00078 context.SetSite(Site.kAll)
00079 context.SetDetId(DetectorId.kAll)
00080 svcMode = ServiceMode(context, 0)
00081 adPmts = self.cableSvc.adPmtSensors(svcMode)
00082 poolPmts = self.cableSvc.poolPmtSensors(svcMode)
00083 adPmtCalibList = []
00084 for adPmt in adPmts:
00085 pmtCalib = PmtCalibData()
00086 pmtCalib.m_pmtId = adPmt
00087 if self.SetDefaultCalib: self.initializeCalib(pmtCalib)
00088 adPmtCalibList.append( pmtCalib )
00089 poolPmtCalibList = []
00090 for poolPmt in poolPmts:
00091 pmtCalib = PmtCalibData()
00092 pmtCalib.m_pmtId = poolPmt
00093 if self.SetDefaultCalib: self.initializeCalib(pmtCalib)
00094 poolPmtCalibList.append( pmtCalib )
00095 pmtCalibList = adPmtCalibList + poolPmtCalibList
00096 return pmtCalibList
00097
00098 def initializeCalib(self, pmtCalib):
00099
00100 pmtCalib.m_status = PmtCalibData.kGood
00101 pmtCalib.m_speHigh = 20.0
00102 pmtCalib.m_sigmaSpeHigh = 0.0
00103 pmtCalib.m_speLow = 2.0
00104 pmtCalib.m_timeOffset = 0.0
00105 pmtCalib.m_timeSpread = 0.0
00106 pmtCalib.m_efficiency = 1.0
00107 pmtCalib.m_prePulseProb = 0.0
00108 pmtCalib.m_afterPulseProb = 0.0
00109 pmtCalib.m_darkRate = 0.0
00110
00111 def updateCalibration(self, pmtCalibList):
00112
00113
00114 detDirs = self.statsSvc.getSubFolders(self.StatsPath)
00115 for detDir in detDirs:
00116 detector = Detector(detDir)
00117 if( detector.site() == Site.kUnknown
00118 or detector.detectorId() == DetectorId.kUnknown ):
00119 self.error("Unknown detector: "+detDir)
00120 return FAILURE
00121 else:
00122 self.info("Processing: "+detDir)
00123
00124 pmtCalibs = []
00125 for pmtCalib in pmtCalibList:
00126 pmtId = pmtCalib.m_pmtId
00127 if (pmtId.site() == detector.site()
00128 and pmtId.detectorId() == detector.detectorId()):
00129 pmtCalibs.append(pmtCalib)
00130 if detector.isAD():
00131 status = self.updateAdCalibration(detector, pmtCalibs)
00132 if not status.isSuccess(): return status
00133 elif detector.isWaterShield():
00134 status = self.updateWaterShieldCalibration(detector, pmtCalibs)
00135 if not status.isSuccess(): return status
00136 return SUCCESS
00137
00138 def updateAdCalibration(self, detector, pmtCalibList):
00139 print "I am in updateAdCalibration"
00140
00141 meanTimeOffset = 0.0
00142 nMeanTimeOffset = 0
00143 relativeEfficiency = {}
00144 for ring in range(1,9): relativeEfficiency[ring] = []
00145 for pmtCalib in pmtCalibList:
00146
00147 pmtId = pmtCalib.m_pmtId
00148 if pmtId.ring() < 1: continue
00149
00150 [occupancyH, tdcOffsetH, adcMeanH, adcSigmaH] = self.getAdCalibHists(pmtId)
00151 if occupancyH==None or tdcOffsetH==None or adcMeanH==None:
00152 self.error("Failed to get calibration histograms for "
00153 +detector.detName())
00154 return FAILURE
00155 occupancy = occupancyH.GetBinContent(pmtId.column())
00156 occupancyUncert = occupancyH.GetBinError(pmtId.column())
00157 tdcOffset = tdcOffsetH.GetBinContent(pmtId.column())
00158 tdcOffsetUncert = tdcOffsetH.GetBinError(pmtId.column())
00159 adcMean = adcMeanH.GetBinContent(pmtId.column())
00160 adcMeanUncert = adcMeanH.GetBinError(pmtId.column())
00161 adcReso = adcSigmaH.GetBinContent(pmtId.column())
00162 adcResoUncert = adcSigmaH.GetBinError(pmtId.column())
00163
00164 pmtCalib.m_status = PmtCalibData.kUnknown
00165 if( occupancy < 0.01 or occupancyUncert > 0.1 ):
00166 pmtCalib.m_status += PmtCalibData.kBadEfficiency
00167 if( tdcOffsetUncert > 20.0 ):
00168 pmtCalib.m_status += PmtCalibData.kBadTiming
00169 if( adcMean < 10.0 or adcMean > 50.0 or adcMeanUncert > 4.0 ):
00170 pmtCalib.m_status += PmtCalibData.kBadGain
00171
00172 if( pmtCalib.m_status == PmtCalibData.kUnknown ):
00173 pmtCalib.m_status = PmtCalibData.kGood
00174
00175 tdcToNs = 1.5625
00176 lightTravelTime = self.timeToAdPmt(self.SourcePosition, pmtId)
00177 timeOffset = -tdcOffset * tdcToNs - lightTravelTime
00178 timeSpread = tdcOffsetUncert*tdcToNs
00179 pmtCalib.m_timeOffset = timeOffset
00180 pmtCalib.m_timeSpread = timeSpread
00181
00182
00183
00184 pmtCalib.m_speHigh = adcMean
00185 pmtCalib.m_sigmaSpeHigh = adcReso
00186
00187
00188 pmtCalib.m_speLow = adcMean / 19.5
00189
00190 if not (pmtCalib.m_status & PmtCalibData.kBadTiming):
00191 meanTimeOffset += timeOffset
00192 nMeanTimeOffset += 1
00193
00194 if not (pmtCalib.m_status & PmtCalibData.kBadEfficiency):
00195 relativeEfficiency[pmtId.ring()].append(pmtCalib.m_efficiency)
00196
00197 if nMeanTimeOffset != 0:
00198 meanTimeOffset /= nMeanTimeOffset
00199 for pmtCalib in pmtCalibList:
00200 pmtId = pmtCalib.m_pmtId
00201 if pmtId.ring() < 1: continue
00202 pmtCalib.m_timeOffset -= meanTimeOffset
00203
00204 meanEfficiency = {}
00205 for ring in relativeEfficiency.keys():
00206 relEff = relativeEfficiency[ring]
00207 meanEfficiency[ring] = 0
00208 if len(relEff) > 0:
00209 meanEfficiency[ring] = sum(relEff)/len(relEff)
00210 for pmtCalib in pmtCalibList:
00211 pmtId = pmtCalib.m_pmtId
00212 if pmtId.ring() < 1: continue
00213 if meanEfficiency[pmtId.ring()] != 0:
00214 pmtCalib.m_efficiency /= meanEfficiency[pmtId.ring()]
00215 else:
00216 pmtCalib.m_efficiency = -1
00217
00218
00219
00220 return SUCCESS
00221
00222 def getAdCalibHists(self, pmtId):
00223
00224 path = self.StatsPath + "/" + pmtId.detName()
00225 path += "/ring_%d" % pmtId.ring()
00226 occupancyH = self.statsSvc.get(path + "/occupancy")
00227 tdcOffsetH = self.statsSvc.get(path + "/tdcOffset")
00228 if self.StatsPath=="/file0/pmtCalibLeadingEdge":
00229 adcMeanH = self.statsSvc.get(path + "/adcMean")
00230 else:
00231 adcMeanH = self.statsSvc.get(path + "/adcMedian")
00232 adcSigmaH = self.statsSvc.get(path + "/adcSigma")
00233 return [occupancyH, tdcOffsetH, adcMeanH, adcSigmaH]
00234
00235 def getPmtDistance(self, position, pmtId):
00236 "Return the distance from position to the pmt with ID pmtID"
00237
00238 site = pmtId.site()
00239 if site == Site.kSAB:
00240 site = Site.kDayaBay
00241 tmpPmtId = AdPmtSensor( pmtId.ring(), pmtId.column(), site, pmtId.detectorId() )
00242 pmtGeomInfo = self.pmtGeomSvc.get( tmpPmtId.fullPackedData() )
00243 Rpmt = pmtGeomInfo.localPosition()
00244 dR = [position[0] - Rpmt.x(),
00245 position[1] - Rpmt.y(),
00246 position[2] - Rpmt.z()]
00247 return math.sqrt(dR[0]*dR[0] + dR[1]*dR[1] + dR[2]*dR[2])
00248
00249 def timeToAdPmt(self, position, pmtId):
00250 "Calculate the light travel time from source to pmt"
00251 dr = self.getPmtDistance(position, pmtId)
00252 lightSpeedInVac = 299.79 * units.mm/units.ns
00253 liquidIndexOfRefrac = 1.45
00254 lightSpeed = lightSpeedInVac
00255 if not self.IsDryAd:
00256 lightSpeed /= liquidIndexOfRefrac
00257 return dr / lightSpeed
00258
00259
00260 def updateWaterShieldCalibration(self, detector, pmtCalibList):
00261
00262 self.error("Calibration of water shield not implemented")
00263 self.error("I am ignoring this for now")
00264 return SUCCESS
00265
00266 def calibTableLine(self, pmtCalib):
00267
00268 pmtLabel = None
00269 pmtId = pmtCalib.m_pmtId
00270 if pmtId.isAD():
00271 pmtLabel = "%s-ring%02d-column%02d" % (pmtId.detName(),
00272 pmtId.ring(),
00273 pmtId.column())
00274 elif pmtId.isWaterShield():
00275 inward = "in"
00276 if not pmtId.inwardFacing(): inward = "out"
00277 pmtLabel = "%s-wall%02d-spot%02d-%s" % (pmtId.detName(),
00278 pmtId.wallNumber(),
00279 pmtId.wallSpot(),
00280 inward)
00281 line=("%d %30s %2d %7.3f %5.3f %5.3f %7.3f %5.3f %5.3f %5.3f %5.3f %5.3f\n"
00282 % (pmtCalib.m_pmtId.fullPackedData(),
00283 pmtLabel,
00284 pmtCalib.m_status,
00285 pmtCalib.m_speHigh,
00286 pmtCalib.m_sigmaSpeHigh,
00287 pmtCalib.m_speLow,
00288 pmtCalib.m_timeOffset / units.ns,
00289 pmtCalib.m_timeSpread / units.ns,
00290 pmtCalib.m_efficiency,
00291 pmtCalib.m_prePulseProb,
00292 pmtCalib.m_afterPulseProb,
00293 pmtCalib.m_darkRate / units.hertz
00294 )
00295 )
00296 return line
00297
00298 def writeCalibTable(self, pmtCalibList):
00299
00300 tableLines = []
00301 tableLines.append("# Table of Calibration constants for each channel\n")
00302 tableLines.append("# [pmtID] [description] [status] [speHigh] [sigmaSpe] [speLow] [timeOffset] [timeSpread] [efficiency] [prePulse] [afterPulse] [darkRate]\n")
00303
00304
00305 idCalib = {}
00306 for pmtCalib in pmtCalibList:
00307 idCalib[ pmtCalib.m_pmtId.fullPackedData() ] = pmtCalib
00308
00309 for id in sorted( idCalib.keys() ):
00310 pmtCalib = idCalib[id]
00311 line = self.calibTableLine(pmtCalib)
00312 tableLines.append(line)
00313 outFile = open(self.OutputFileName,'w')
00314 outFile.writelines(tableLines)
00315 outFile.close()
00316
00317
00318
00319 def mergeTables(primaryFile, changeMap, outputFile):
00320 "Simple Non-NuWa python function for merging calibration text tables"
00321 inputTable = parseTable(primaryFile)
00322
00323 for changeFile in changeMap.keys():
00324 changeTable = parseTable(changeFile)
00325 changeFields = changeMap[changeFile]
00326 for fieldName in changeFields:
00327 fieldName = fieldName.strip()
00328 if not inputTable["columns"].has_key(fieldName):
00329 print "Field \"",fieldName,"\" doesn't exist in input table."
00330 return
00331 inputTable["columns"][fieldName] = changeTable["columns"][fieldName]
00332
00333 outputTableLines = []
00334 for comment in inputTable["comments"]:
00335 outputTableLines.append(comment)
00336 outputTableLines.append(inputTable["header"])
00337
00338 fieldLength = {}
00339 for fieldName in inputTable["fields"]:
00340 fieldLength[fieldName] = max( [len(data) for data in
00341 inputTable["columns"][fieldName] ] )
00342
00343 for rowIdx in range(inputTable["numberOfRows"]):
00344 line = ""
00345 for fieldName in inputTable["fields"]:
00346 format = "%"+str(fieldLength[fieldName])+"s "
00347 line += format % inputTable["columns"][fieldName][rowIdx]
00348 outputTableLines.append(line+"\n")
00349 open(outputFile,"w").writelines(outputTableLines)
00350
00351 def parseTable(filename):
00352 "Simple text table parser"
00353 tableLines = open(filename).readlines()
00354 fieldNames = []
00355 comments = []
00356 headerLine = None
00357 columns = {}
00358 nRows = 0
00359 for line in tableLines:
00360 data = line.strip()
00361
00362 if len(data)>0 and data[0]=='#':
00363 comment = data[1:].strip()
00364 if len(comment)>0 and comment[0] =='[':
00365
00366 if len(fieldNames) > 0:
00367 print "Headers already defined by line \"",line,"\""
00368 return None
00369 tokens = comment.split(']')
00370 if len(tokens[-1].strip())==0: tokens = tokens[:-1]
00371 for token in tokens:
00372 fieldStart = token.rfind('[')
00373 if fieldStart<0:
00374 print "Invalid token \"",token,"\" in line \"",line,"\""
00375 return None
00376 fieldName = token[fieldStart+1:].strip()
00377 fieldNames.append(fieldName)
00378 columns[fieldName] = []
00379 headerLine = line
00380 else:
00381 comments.append(line)
00382 continue
00383
00384 fields = data.split()
00385 if len(fields)==0: continue
00386 if len(fields) != len(columns):
00387 print "Invalid number of fields in line \"",line,"\""
00388 return None
00389 for fieldIdx in range(len(fields)):
00390 columns[fieldNames[fieldIdx]].append( fields[fieldIdx] )
00391 nRows += 1
00392 return { "fields":fieldNames,
00393 "comments":comments,
00394 "header":headerLine,
00395 "columns":columns,
00396 "numberOfRows":nRows}