24 """Calibration products production task code."""    25 from __future__ 
import absolute_import, division, print_function
    34 import lsst.eotest.sensor 
as sensorTest
    38     """Config class for the calibration products production (CP) task."""    40     ccdKey = pexConfig.Field(
    42         doc=
"The key by which to pull a detector from a dataId, e.g. 'ccd' or 'detector'",
    45     fe55 = pexConfig.ConfigurableField(
    46         target=sensorTest.Fe55Task,
    47         doc=
"The Fe55 analysis task.",
    49     doFe55 = pexConfig.Field(
    51         doc=
"Measure gains using Fe55?",
    54     readNoise = pexConfig.ConfigurableField(
    55         target=sensorTest.ReadNoiseTask,
    56         doc=
"The read noise task.",
    58     doReadNoise = pexConfig.Field(
    60         doc=
"Measure the read-noise?",
    63     brightPixels = pexConfig.ConfigurableField(
    64         target=sensorTest.BrightPixelsTask,
    65         doc=
"The bright pixel/column finding task.",
    67     doBrightPixels = pexConfig.Field(
    69         doc=
"Find bright pixels?",
    72     darkPixels = pexConfig.ConfigurableField(
    73         target=sensorTest.DarkPixelsTask,
    74         doc=
"The dark pixel/column finding task.",
    76     doDarkPixels = pexConfig.Field(
    78         doc=
"Find dark pixels?",
    81     traps = pexConfig.ConfigurableField(
    82         target=sensorTest.TrapTask,
    83         doc=
"The trap-finding task.",
    85     doTraps = pexConfig.Field(
    87         doc=
"Find traps using pocket-pumping exposures?",
    90     cte = pexConfig.ConfigurableField(
    91         target=sensorTest.CteTask,
    92         doc=
"The CTE analysis task.",
    94     doCTE = pexConfig.Field(
    96         doc=
"Measure the charge transfer efficiency?",
    99     ptc = pexConfig.ConfigurableField(
   100         target=sensorTest.PtcTask,
   101         doc=
"The PTC analysis task.",
   103     doPTC = pexConfig.Field(
   105         doc=
"Measure the photon transfer curve?",
   108     flatPair = pexConfig.ConfigurableField(
   109         target=sensorTest.FlatPairTask,
   110         doc=
"The flat-pair analysis task.",
   112     doFlatPair = pexConfig.Field(
   114         doc=
"Measure the detector response vs incident flux using flat pairs?",
   117     eotestOutputPath = pexConfig.Field(
   119         doc=
"Path to which to write the eotest output results. Madatory runtime arg for running eotest.",
   122     requireAllEOTests = pexConfig.Field(
   124         doc=
"If True, all tests are required to be runnable, and will Raise if data is missing. If False, "   125         "processing will continue if a previous part failed due to the input dataset being incomplete.",
   128     flatPairMaxPdFracDev = pexConfig.Field(
   130         doc=
"Maximum allowed fractional deviation between photodiode currents for the eotest flatPair task. "   131         "This value is passed to the task's run() method at runtime rather than being stored in the task's"   132         "own pexConfig field.",
   137         """Set default config options for the subTasks."""   139         self.
fe55.temp_set_point = -100
   140         self.
fe55.temp_set_point_tol = 20
   158         """Override of the valiate() method.   160         The pexConfigs of the subTasks here cannot be validated in the normal way, as they are the configs   161         for eotest, which does illegal things, and this would require an upstream PR to fix. Therefore, we   162         override the validate() method here, and use it to set the output directory for each of the tasks   163         based on the legal pexConfig parameter for the main task.   165         log = lsstLog.Log.getLogger(
"cp.pipe.runEotestConfig")
   167             raise RuntimeError(
"Must supply an output path for eotest data. "   168                                "Please set config.eotestOutputPath.")
   170         taskList = [
'fe55', 
'brightPixels', 
'darkPixels', 
'readNoise', 
'traps', 
'cte', 
'flatPair', 
'ptc']
   171         for task 
in taskList:
   172             if getattr(self, task).output_dir != 
'.':
   175                 log.warn(
"OVERWRITING: Found a user defined output path of %s for %sTask. "   176                          "This has been overwritten with %s, as individually specified output paths for "   177                          "subTasks are not supported at present" % (getattr(self, task).output_dir,
   184     Task to run test stand data through eotest using a butler.   186     This task is used to produce an eotest report (the project's sensor   187     acceptance testing package)   188     Examples of some of its operations are as follows:   189     * Given a set of flat-field images, find the dark pixels and columns.   190     * Given a set of darks, find the bright pixels and columns.   191     * Given a set of Fe55 exposures, calulate the gain of the readout chain,   193     * Given a set of Fe55 exposures, calulate the instrinsic PSF of the silicon,   194         and the degradation of   195     * the PSF due to CTE.   196     * Given a set of flat-pairs, measure the photon transfer curve (PTC).   197     * Given a set of bias frames, calculate the read noise of the system in e-.   198     * Given a set of pocket-pumping exposures, find charge-traps in the silicon.   200     The RunEotestTask.runEotestDirect() is only applicable to LSST sensors, and   201     only for a specific type of dataset. This method takes a   202     dafPersistance.Butler corresponding to a repository in which a full eotest   203     run has been taken and ingested, and runs each of the tasks in eotest   204     directly, allowing for bitwise comparison with results given by the camera   207     See http://ls.st/ldm-151 Chapter 4, Calibration Products Production for   208     further details regarding the inputs and outputs.   211     ConfigClass = RunEotestConfig
   212     _DefaultName = 
"runEotest"   215         """Constructor for the RunEotestTask."""   216         if 'lsst.eotest.sensor' not in sys.modules:  
   217             raise RuntimeError(
'eotest failed to import')
   219         pipeBase.CmdLineTask.__init__(self, *args, **kwargs)
   227         self.makeSubtask(
"fe55")
   228         self.makeSubtask(
"readNoise")
   229         self.makeSubtask(
"brightPixels")
   230         self.makeSubtask(
"darkPixels")
   231         self.makeSubtask(
"traps")
   232         self.makeSubtask(
"cte")
   233         self.makeSubtask(
"flatPair")
   234         self.makeSubtask(
"ptc")
   236     def _getMaskFiles(self, path, ccd):
   237         """Get all available eotest mask files for a given ccd.   239         Each stage of the processing generates more mask files, so this allows each to be picked up   240         as more and more tests run, and saves having to have clever logic for if some tasks fail.   245             Path on which to find the mask files   246         ccd : `string` or `int`   247             Name/identifier of the CCD   251         maskFiles : iterable of `str`   252             List of mask files, or an empty tuple if none are found   254         pattern = 
'*' + 
str(ccd) + 
'*mask*'     255         maskFiles = glob.glob(os.path.join(path, pattern))
   256         return maskFiles 
if len(maskFiles) > 0 
else ()  
   258     def _cleanupEotest(self, path):
   259         """Delete all the medianed files left behind after eotest has run.   261         Running eotest generates a lot of interim medianed files, so this just cleans them up.   266            Path on which to delete all the eotest medianed files.   268         for filename 
in glob.glob(os.path.join(path, 
'*_median_*.fits')):
   272         """After running eotest, generate pdf(s) of the results.   274         Generate a sensor test report from the output data in config.eotestOutputPath, one for each CCD.   275         The pdf file(s), along with the .tex file(s) and the individual plots are written   276         to the eotestOutputPath.   277         .pdf generation requires a TeX distro including pdflatex to be installed.   279         ccds = butler.queryMetadata(
'raw', self.config.ccdKey)
   281             self.log.
info(
"Starting test report generation for %s"%ccd)
   283                 plotPath = os.path.join(self.config.eotestOutputPath, 
'plots')
   284                 if not os.path.exists(plotPath):
   285                     os.makedirs(plotPath)
   286                 plots = sensorTest.EOTestPlots(ccd, self.config.eotestOutputPath, plotPath)
   287                 eoTestReport = sensorTest.EOTestReport(plots, wl_dir=
'')
   288                 eoTestReport.make_figures()
   289                 eoTestReport.make_pdf()
   290             except Exception 
as e:
   291                 self.log.
warn(
"Failed to make eotest report for %s: %s"%(ccd, e))
   292         self.log.
info(
"Finished test report generation.")
   297         Generate calibration products using eotest algorithms.   299         Generate all calibration products possible using the vanilla eotest implementation,   300         given a butler for a TS8 (raft-test) repo. It can contain multiple runs, but must correspond to   301         only a single raft/RTM.   303         - Run all eotest tasks possible, using the butler to gather the data   304         - Write outputs in eotest format   306         In order to replicate the canonical eotest analysis, the tasks should be run in a specific order.   307         This is given/defined in the "Steps" section here:   308         http://lsst-camera.slac.stanford.edu/eTraveler/exp/LSST-CAMERA/displayProcess.jsp?processPath=1179   310         But is replicated here for conveniece:   312         * CCD Read Noise Analysis   313         * Bright Defects Analysis   314         * Dark Defects Analysis   316         * Dark Current                  X - will not be implemented here   317         * Charge Transfer Efficiencies   318         * Photo-response analysis       X - will not be implemented here   319         * Flat Pairs Analysis   320         * Photon Transfer Curve   321         * Quantum Efficiency            X - will not be implemented here   323         List of tasks that exist in the eotest package but aren't mentioned on the above link:   330         # TODO: For each eotest task, find out what the standard raft testing does for the optional params.   331         i.e. many have optional params for gains, bias-frames etc - if we want bitwise identicallity then we   332         need to know what is typically provided to these tasks when the camera team runs this code.   333         This can probably be worked out from https://github.com/lsst-camera-dh/lcatr-harness   334         but it sounds like Jim Chiang doesn't recommend trying to do that.   339         butler : `lsst.daf.persistence.butler`   340             Butler for the repo containg the eotest data to be used   342             Optional run number, to be used for repos containing multiple runs   344         self.log.
info(
"Running eotest routines direct")
   347         runs = butler.queryMetadata(
'raw', [
'run'])
   352                 raise RuntimeError(
"Butler query found %s for runs. eotest datasets must have a run number,"   353                                    "and you must specify which run to use if a respoitory contains several."   358                 raise RuntimeError(
"Butler query found %s for runs, but the run specified (%s) "   359                                    "was not among them." % (runs, run))
   362         if not os.path.exists(self.config.eotestOutputPath):
   363             os.makedirs(self.config.eotestOutputPath)
   365         ccds = butler.queryMetadata(
'raw', self.config.ccdKey)
   366         imTypes = butler.queryMetadata(
'raw', [
'imageType'])
   367         testTypes = butler.queryMetadata(
'raw', [
'testType'])
   372         if self.config.doFe55:
   373             fe55TaskDataId = {
'run': run, 
'testType': 
'FE55', 
'imageType': 
'FE55'}
   374             self.log.
info(
"Starting Fe55 pixel task")
   376                 if 'FE55' not in testTypes:
   377                     msg = 
"No Fe55 tests found. Available data: %s" % testTypes
   378                     if self.config.requireAllEOTests:
   379                         raise RuntimeError(msg)
   381                         self.log.
warn(msg + 
"\nSkipping Fe55 task")
   383                 fe55Filenames = [butler.get(
'raw_filename', dataId={
'visit': visit,
   384                                                                     self.config.ccdKey: ccd})[0][:-3]
   385                                  for visit 
in butler.queryMetadata(
'raw', [
'visit'], dataId=fe55TaskDataId)]
   386                 self.log.
trace(
"Fe55Task: Processing %s with %s files" % (ccd, len(fe55Filenames)))
   387                 maskFiles = self.
_getMaskFiles(self.config.eotestOutputPath, ccd)
   388                 gains = self.fe55.
run(sensor_id=ccd, infiles=fe55Filenames, mask_files=maskFiles)
   392                 butler.put(gains, 
'eotest_gain', dataId={self.config.ccdKey: ccd, 
'run': run})
   403         if self.config.doReadNoise:
   405             self.log.
info(
"Starting readNoise task")
   406             noiseTaskDataId = {
'run': run, 
'testType': 
'FE55', 
'imageType': 
'BIAS'}
   408                 if (
'FE55' not in testTypes) 
or (
'BIAS' not in imTypes):
   409                     msg = 
"Required data for readNoise unavailable. Available data:\   410                            \ntestTypes: %s\nimageTypes: %s" % (testTypes, imTypes)
   411                     if self.config.requireAllEOTests:
   412                         raise RuntimeError(msg)
   414                         self.log.
warn(msg + 
"\nSkipping noise task")
   415                 noiseFilenames = [butler.get(
'raw_filename', dataId={
'visit': visit,
   416                                                                      self.config.ccdKey: ccd})[0][:-3]
   417                                   for visit 
in butler.queryMetadata(
'raw', [
'visit'],
   418                                                                     dataId=noiseTaskDataId)]
   419                 self.log.
trace(
"Fe55Task: Processing %s with %s files" % (ccd, len(noiseFilenames)))
   420                 maskFiles = self.
_getMaskFiles(self.config.eotestOutputPath, ccd)
   421                 gains = butler.get(
'eotest_gain', dataId={self.config.ccdKey: ccd, 
'run': run})
   422                 self.readNoise.
run(sensor_id=ccd, bias_files=noiseFilenames,
   423                                    gains=gains, mask_files=maskFiles)
   429         if self.config.doBrightPixels:
   430             self.log.
info(
"Starting bright pixel task")
   431             brightTaskDataId = {
'run': run, 
'testType': 
'DARK', 
'imageType': 
'DARK'}
   433                 if 'DARK' not in testTypes:
   434                     msg = 
"No dark tests found. Available data: %s" % testTypes
   435                     if self.config.requireAllEOTests:
   436                         raise RuntimeError(msg)
   438                         self.log.
warn(msg + 
"\nSkipping bright pixel task")
   440                 darkFilenames = [butler.get(
'raw_filename', dataId={
'visit': visit,
   441                                                                     self.config.ccdKey: ccd})[0][:-3]
   442                                  for visit 
in butler.queryMetadata(
'raw', [
'visit'],
   443                                                                    dataId=brightTaskDataId)]
   444                 self.log.
trace(
"BrightTask: Processing %s with %s files" % (ccd, len(darkFilenames)))
   445                 maskFiles = self.
_getMaskFiles(self.config.eotestOutputPath, ccd)
   446                 gains = butler.get(
'eotest_gain', dataId={self.config.ccdKey: ccd, 
'run': run})
   447                 self.brightPixels.
run(sensor_id=ccd, dark_files=darkFilenames,
   448                                       mask_files=maskFiles, gains=gains)
   454         if self.config.doDarkPixels:
   455             self.log.
info(
"Starting dark pixel task")
   456             darkTaskDataId = {
'run': run, 
'testType': 
'SFLAT_500', 
'imageType': 
'FLAT'}
   458                 if 'SFLAT_500' not in testTypes:
   459                     msg = 
"No superflats found. Available data: %s" % testTypes
   460                     if self.config.requireAllEOTests:
   461                         raise RuntimeError(msg)
   463                         self.log.
warn(msg + 
"\nSkipping dark pixel task")
   465                 sflatFilenames = [butler.get(
'raw_filename', dataId={
'visit': visit,
   466                                                                      self.config.ccdKey: ccd})[0][:-3]
   467                                   for visit 
in butler.queryMetadata(
'raw', [
'visit'],
   468                                                                     dataId=darkTaskDataId)]
   469                 self.log.
trace(
"DarkTask: Processing %s with %s files" % (ccd, len(sflatFilenames)))
   470                 maskFiles = self.
_getMaskFiles(self.config.eotestOutputPath, ccd)
   471                 self.darkPixels.
run(sensor_id=ccd, sflat_files=sflatFilenames, mask_files=maskFiles)
   477         if self.config.doTraps:
   478             self.log.
info(
"Starting trap task")
   479             trapTaskDataId = {
'run': run, 
'testType': 
'TRAP', 
'imageType': 
'PPUMP'}
   481                 if (
'TRAP' not in testTypes) 
and (
'PPUMP' not in imTypes):
   482                     msg = 
"No pocket pumping exposures found. Available data: %s" % testTypes
   483                     if self.config.requireAllEOTests:
   484                         raise RuntimeError(msg)
   486                         self.log.
warn(msg + 
"\nSkipping trap task")
   488                 trapFilenames = [butler.get(
'raw_filename', dataId={
'visit': visit,
   489                                                                     self.config.ccdKey: ccd})[0][:-3]
   490                                  for visit 
in butler.queryMetadata(
'raw', [
'visit'], dataId=trapTaskDataId)]
   491                 if len(trapFilenames) != 1:  
   492                     msg = 
"Trap Task: Found more than one ppump trap file: %s" % trapFilenames
   493                     msg += 
" Running using only the first one found."   495                 self.log.
trace(
"Trap Task: Processing %s with %s files" % (ccd, len(trapFilenames)))
   496                 maskFiles = self.
_getMaskFiles(self.config.eotestOutputPath, ccd)
   497                 gains = butler.get(
'eotest_gain', dataId={self.config.ccdKey: ccd, 
'run': run})
   498                 self.traps.
run(sensor_id=ccd, pocket_pumped_file=trapFilenames[0],
   499                                mask_files=maskFiles, gains=gains)
   505         if self.config.doCTE:
   506             self.log.
info(
"Starting CTE task")
   507             cteTaskDataId = {
'run': run, 
'testType': 
'SFLAT_500', 
'imageType': 
'FLAT'}
   509                 if 'SFLAT_500' not in testTypes:
   510                     msg = 
"No superflats found. Available data: %s" % testTypes
   511                     if self.config.requireAllEOTests:
   512                         raise RuntimeError(msg)
   514                         self.log.
warn(msg + 
"\nSkipping CTE task")
   516                 sflatFilenames = [butler.get(
'raw_filename', dataId={
'visit': visit,
   517                                                                      self.config.ccdKey: ccd})[0][:-3]
   518                                   for visit 
in butler.queryMetadata(
'raw', [
'visit'], dataId=cteTaskDataId)]
   519                 self.log.
trace(
"CTETask: Processing %s with %s files" % (ccd, len(sflatFilenames)))
   520                 maskFiles = self.
_getMaskFiles(self.config.eotestOutputPath, ccd)
   521                 self.cte.
run(sensor_id=ccd, superflat_files=sflatFilenames, mask_files=maskFiles)
   527         if self.config.doFlatPair:
   528             self.log.
info(
"Starting flatPair task")
   529             flatPairDataId = {
'run': run, 
'testType': 
'FLAT', 
'imageType': 
'FLAT'}
   531                 if 'FLAT' not in testTypes:
   532                     msg = 
"No dataset for flat_pairs found. Available data: %s" % testTypes
   533                     if self.config.requireAllEOTests:
   534                         raise RuntimeError(msg)
   536                         self.log.
warn(msg + 
"\nSkipping flatPair task")
   538                 flatPairFilenames = [butler.get(
'raw_filename', dataId={
'visit': visit,
   539                                                 self.config.ccdKey: ccd})[0][:-3]
   540                                      for visit 
in butler.queryMetadata(
'raw', [
'visit'],
   541                                                                        dataId=flatPairDataId)]
   550                 flatPairFilenames = [os.path.realpath(f) 
for f 
in flatPairFilenames 
if   551                                      os.path.realpath(f).find(
'flat1') != -1 
or   552                                      os.path.realpath(f).find(
'flat2') != -1]
   553                 if not flatPairFilenames:
   554                     raise RuntimeError(
"No flatPair files found.")
   555                 self.log.
trace(
"FlatPairTask: Processing %s with %s files" % (ccd, len(flatPairFilenames)))
   556                 maskFiles = self.
_getMaskFiles(self.config.eotestOutputPath, ccd)
   557                 gains = butler.get(
'eotest_gain', dataId={self.config.ccdKey: ccd, 
'run': run})
   558                 self.flatPair.
run(sensor_id=ccd, infiles=flatPairFilenames, mask_files=maskFiles,
   559                                   gains=gains, max_pd_frac_dev=self.config.flatPairMaxPdFracDev)
   565         if self.config.doPTC:
   566             self.log.
info(
"Starting PTC task")
   567             ptcDataId = {
'run': run, 
'testType': 
'FLAT', 
'imageType': 
'FLAT'}
   569                 if 'FLAT' not in testTypes:
   570                     msg = 
"No dataset for flat_pairs found. Available data: %s" % testTypes
   571                     if self.config.requireAllEOTests:
   572                         raise RuntimeError(msg)
   574                         self.log.
warn(msg + 
"\nSkipping PTC task")
   576                 ptcFilenames = [butler.get(
'raw_filename', dataId={
'visit': visit,
   577                                                                    self.config.ccdKey: ccd})[0][:-3]
   578                                 for visit 
in butler.queryMetadata(
'raw', [
'visit'], dataId=ptcDataId)]
   587                 ptcFilenames = [os.path.realpath(f) 
for f 
in ptcFilenames 
if   588                                 os.path.realpath(f).find(
'flat1') != -1 
or   589                                 os.path.realpath(f).find(
'flat2') != -1]
   591                     raise RuntimeError(
"No flatPair files found")
   592                 self.log.
trace(
"PTCTask: Processing %s with %s files" % (ccd, len(ptcFilenames)))
   593                 maskFiles = self.
_getMaskFiles(self.config.eotestOutputPath, ccd)
   594                 gains = butler.get(
'eotest_gain', dataId={self.config.ccdKey: ccd, 
'run': run})
   595                 self.ptc.
run(sensor_id=ccd, infiles=ptcFilenames, mask_files=maskFiles, gains=gains)
   599         self.log.
info(
"Finished running EOTest")
 
def __init__(self, args, kwargs)
def runEotestDirect(self, butler, run=None)
def _getMaskFiles(self, path, ccd)
def _cleanupEotest(self, path)
def makeEotestReport(self, butler)