lsst.pipe.drivers  18.0.0+14
skyCorrection.py
Go to the documentation of this file.
1 # This file is part of pipe_drivers.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (https://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 from __future__ import absolute_import, division, print_function
22 
23 import lsst.afw.math as afwMath
24 import lsst.afw.image as afwImage
25 import lsst.pipe.base as pipeBase
26 
27 from lsst.pipe.base import ArgumentParser, ConfigDatasetType
28 from lsst.pex.config import Config, Field, ConfigurableField, ConfigField
29 from lsst.ctrl.pool.pool import Pool
30 from lsst.ctrl.pool.parallel import BatchPoolTask
31 from lsst.pipe.drivers.background import (SkyMeasurementTask, FocalPlaneBackground,
32  FocalPlaneBackgroundConfig, MaskObjectsTask)
33 import lsst.pipe.drivers.visualizeVisit as visualizeVisit
34 
35 __all__ = ["SkyCorrectionConfig", "SkyCorrectionTask"]
36 
37 DEBUG = False # Debugging outputs?
38 
39 
40 def makeCameraImage(camera, exposures, filename=None, binning=8):
41  """Make and write an image of an entire focal plane
42 
43  Parameters
44  ----------
45  camera : `lsst.afw.cameraGeom.Camera`
46  Camera description.
47  exposures : `list` of `tuple` of `int` and `lsst.afw.image.Exposure`
48  List of detector ID and CCD exposures (binned by `binning`).
49  filename : `str`, optional
50  Output filename.
51  binning : `int`
52  Binning size that has been applied to images.
53  """
54  image = visualizeVisit.makeCameraImage(camera, dict(exp for exp in exposures if exp is not None), binning)
55  if filename is not None:
56  image.writeFits(filename)
57  return image
58 
59 
60 class SkyCorrectionConfig(pipeBase.PipelineTaskConfig, Config):
61  """Configuration for SkyCorrectionTask"""
62 
63  rawLinker = pipeBase.InputDatasetField(
64  doc="Raw data to provide exp-visit linkage to connect calExp inputs to camera/sky calibs.",
65  name="raw",
66  scalar=False,
67  storageClass="ExposureU",
68  dimensions=["instrument", "exposure", "detector"],
69  )
70 
71  calExpArray = pipeBase.InputDatasetField(
72  doc="Input exposures to process",
73  name="calexp",
74  scalar=False,
75  storageClass="ExposureF",
76  dimensions=["instrument", "visit", "detector"],
77  )
78  calBkgArray = pipeBase.InputDatasetField(
79  doc="Input background files to use",
80  name="calexpBackground",
81  scalar=False,
82  storageClass="Background",
83  dimensions=["instrument", "visit", "detector"],
84  )
85 
86  camera = pipeBase.InputDatasetField(
87  doc="Input camera to use.",
88  name="camera",
89  scalar=True,
90  storageClass="TablePersistableCamera",
91  dimensions=["instrument", "calibration_label"],
92  )
93  skyCalibs = pipeBase.InputDatasetField(
94  doc="Input sky calibrations to use.",
95  name="sky",
96  scalar=False,
97  storageClass="ExposureF",
98  dimensions=["instrument", "physical_filter", "detector", "calibration_label"],
99  )
100 
101  calExpCamera = pipeBase.OutputDatasetField(
102  doc="Output camera image.",
103  name='calexp_camera',
104  scalar=True,
105  storageClass="ImageF",
106  dimensions=["instrument", "visit"],
107  )
108  skyCorr = pipeBase.OutputDatasetField(
109  doc="Output sky corrected images.",
110  name='skyCorr',
111  scalar=False,
112  storageClass="Background",
113  dimensions=["instrument", "visit", "detector"],
114  )
115 
116  bgModel = ConfigField(dtype=FocalPlaneBackgroundConfig, doc="Background model")
117  bgModel2 = ConfigField(dtype=FocalPlaneBackgroundConfig, doc="2nd Background model")
118  sky = ConfigurableField(target=SkyMeasurementTask, doc="Sky measurement")
119  maskObjects = ConfigurableField(target=MaskObjectsTask, doc="Mask Objects")
120  doMaskObjects = Field(dtype=bool, default=True, doc="Mask objects to find good sky?")
121  doBgModel = Field(dtype=bool, default=True, doc="Do background model subtraction?")
122  doBgModel2 = Field(dtype=bool, default=True, doc="Do cleanup background model subtraction?")
123  doSky = Field(dtype=bool, default=True, doc="Do sky frame subtraction?")
124  binning = Field(dtype=int, default=8, doc="Binning factor for constructing focal-plane images")
125  calexpType = Field(dtype=str, default="calexp",
126  doc="Should be set to fakes_calexp if you want to process calexps with fakes in.")
127 
128  def setDefaults(self):
129  Config.setDefaults(self)
130  self.bgModel2.doSmooth = True
131  self.bgModel2.minFrac = 0.5
132  self.bgModel2.xSize = 256
133  self.bgModel2.ySize = 256
134  self.bgModel2.smoothScale = 1.0
135 
136  self.quantum.dimensions = ("instrument", "visit")
137 
138 
139 class SkyCorrectionTask(pipeBase.PipelineTask, BatchPoolTask):
140  """Correct sky over entire focal plane"""
141  ConfigClass = SkyCorrectionConfig
142  _DefaultName = "skyCorr"
143 
144  def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler):
145  inputData.pop("rawLinker", None)
146  return super().adaptArgsAndRun(inputData, inputDataIds, outputDataIds, butler)
147 
148  @classmethod
149  def getPrerequisiteDatasetTypes(cls, config):
150  names = set()
151  names.add('skyCalibs')
152  names.add('camera')
153  return names
154 
155  @classmethod
156  def getPerDatasetTypeDimensions(cls, config):
157  return frozenset(["calibration_label"])
158 
159  def __init__(self, *args, **kwargs):
160  super().__init__(**kwargs)
161 
162  self.makeSubtask("sky")
163  self.makeSubtask("maskObjects")
164 
165  @classmethod
166  def _makeArgumentParser(cls, *args, **kwargs):
167  kwargs.pop("doBatch", False)
168  datasetType = ConfigDatasetType(name="calexpType")
169  parser = ArgumentParser(name="skyCorr", *args, **kwargs)
170  parser.add_id_argument("--id", datasetType=datasetType, level="visit",
171  help="data ID, e.g. --id visit=12345")
172  return parser
173 
174  @classmethod
175  def batchWallTime(cls, time, parsedCmd, numCores):
176  """Return walltime request for batch job
177 
178  Subclasses should override if the walltime should be calculated
179  differently (e.g., addition of some serial time).
180 
181  Parameters
182  ----------
183  time : `float`
184  Requested time per iteration.
185  parsedCmd : `argparse.Namespace`
186  Results of argument parsing.
187  numCores : `int`
188  Number of cores.
189  """
190  numTargets = len(cls.RunnerClass.getTargetList(parsedCmd))
191  return time*numTargets
192 
193  def runDataRef(self, expRef):
194  """Perform sky correction on an exposure
195 
196  We restore the original sky, and remove it again using multiple
197  algorithms. We optionally apply:
198 
199  1. A large-scale background model.
200  This step removes very-large-scale sky such as moonlight.
201  2. A sky frame.
202  3. A medium-scale background model.
203  This step removes residual sky (This is smooth on the focal plane).
204 
205  Only the master node executes this method. The data is held on
206  the slave nodes, which do all the hard work.
207 
208  Parameters
209  ----------
210  expRef : `lsst.daf.persistence.ButlerDataRef`
211  Data reference for exposure.
212 
213  See Also
214  --------
215  ~lsst.pipe.drivers.SkyCorrectionTask.run
216  """
217  if DEBUG:
218  extension = "-%(visit)d.fits" % expRef.dataId
219 
220  with self.logOperation("processing %s" % (expRef.dataId,)):
221  pool = Pool()
222  pool.cacheClear()
223  pool.storeSet(butler=expRef.getButler())
224  camera = expRef.get("camera")
225 
226  dataIdList = [ccdRef.dataId for ccdRef in expRef.subItems("ccd") if
227  ccdRef.datasetExists(self.config.calexpType)]
228 
229  exposures = pool.map(self.loadImage, dataIdList)
230  if DEBUG:
231  makeCameraImage(camera, exposures, "restored" + extension)
232  exposures = pool.mapToPrevious(self.collectOriginal, dataIdList)
233  makeCameraImage(camera, exposures, "original" + extension)
234  exposures = pool.mapToPrevious(self.collectMask, dataIdList)
235  makeCameraImage(camera, exposures, "mask" + extension)
236 
237  if self.config.doBgModel:
238  exposures = self.focalPlaneBackground(camera, pool, dataIdList, self.config.bgModel)
239 
240  if self.config.doSky:
241  measScales = pool.mapToPrevious(self.measureSkyFrame, dataIdList)
242  scale = self.sky.solveScales(measScales)
243  self.log.info("Sky frame scale: %s" % (scale,))
244 
245  exposures = pool.mapToPrevious(self.subtractSkyFrame, dataIdList, scale)
246  if DEBUG:
247  makeCameraImage(camera, exposures, "skysub" + extension)
248  calibs = pool.mapToPrevious(self.collectSky, dataIdList)
249  makeCameraImage(camera, calibs, "sky" + extension)
250 
251  if self.config.doBgModel2:
252  exposures = self.focalPlaneBackground(camera, pool, dataIdList, self.config.bgModel2)
253 
254  # Persist camera-level image of calexp
255  image = makeCameraImage(camera, exposures)
256  expRef.put(image, "calexp_camera")
257 
258  pool.mapToPrevious(self.write, dataIdList)
259 
260  def focalPlaneBackground(self, camera, pool, dataIdList, config):
261  """Perform full focal-plane background subtraction
262 
263  This method runs on the master node.
264 
265  Parameters
266  ----------
267  camera : `lsst.afw.cameraGeom.Camera`
268  Camera description.
269  pool : `lsst.ctrl.pool.Pool`
270  Process pool.
271  dataIdList : iterable of `dict`
272  List of data identifiers for the CCDs.
273  config : `lsst.pipe.drivers.background.FocalPlaneBackgroundConfig`
274  Configuration to use for background subtraction.
275 
276  Returns
277  -------
278  exposures : `list` of `lsst.afw.image.Image`
279  List of binned images, for creating focal plane image.
280  """
281  bgModel = FocalPlaneBackground.fromCamera(config, camera)
282  data = [pipeBase.Struct(dataId=dataId, bgModel=bgModel.clone()) for dataId in dataIdList]
283  bgModelList = pool.mapToPrevious(self.accumulateModel, data)
284  for ii, bg in enumerate(bgModelList):
285  self.log.info("Background %d: %d pixels", ii, bg._numbers.array.sum())
286  bgModel.merge(bg)
287  return pool.mapToPrevious(self.subtractModel, dataIdList, bgModel)
288 
289  def focalPlaneBackgroundRun(self, camera, cacheExposures, idList, config):
290  """Perform full focal-plane background subtraction
291 
292  This method runs on the master node.
293 
294  Parameters
295  ----------
296  camera : `lsst.afw.cameraGeom.Camera`
297  Camera description.
298  cacheExposures : `list` of `lsst.afw.image.Exposures`
299  List of loaded and processed input calExp.
300  idList : `list` of `int`
301  List of detector ids to iterate over.
302  config : `lsst.pipe.drivers.background.FocalPlaneBackgroundConfig`
303  Configuration to use for background subtraction.
304 
305  Returns
306  -------
307  exposures : `list` of `lsst.afw.image.Image`
308  List of binned images, for creating focal plane image.
309  newCacheBgList : `list` of `lsst.afwMath.backgroundList`
310  Background lists generated.
311  cacheBgModel : `FocalPlaneBackground`
312  Full focal plane background model.
313  """
314  bgModel = FocalPlaneBackground.fromCamera(config, camera)
315  data = [pipeBase.Struct(id=id, bgModel=bgModel.clone()) for id in idList]
316 
317  bgModelList = []
318  for nodeData, cacheExp in zip(data, cacheExposures):
319  nodeData.bgModel.addCcd(cacheExp)
320  bgModelList.append(nodeData.bgModel)
321 
322  for ii, bg in enumerate(bgModelList):
323  self.log.info("Background %d: %d pixels", ii, bg._numbers.getArray().sum())
324  bgModel.merge(bg)
325 
326  exposures = []
327  newCacheBgList = []
328  cacheBgModel = []
329  for cacheExp in cacheExposures:
330  nodeExp, nodeBgModel, nodeBgList = self.subtractModelRun(cacheExp, bgModel)
331  exposures.append(afwMath.binImage(nodeExp.getMaskedImage(), self.config.binning))
332  cacheBgModel.append(nodeBgModel)
333  newCacheBgList.append(nodeBgList)
334 
335  return exposures, newCacheBgList, cacheBgModel
336 
337  def run(self, calExpArray, calBkgArray, skyCalibs, camera):
338  """Duplicate runDataRef method without ctrl_pool for Gen3.
339 
340  Parameters
341  ----------
342  calExpArray : `list` of `lsst.afw.image.Exposure`
343  Array of detector input calExp images for the exposure to
344  process.
345  calBkgArray : `list` of `lsst.afw.math.BackgroundList`
346  Array of detector input background lists matching the
347  calExps to process.
348  skyCalibs : `list` of `lsst.afw.image.Exposure`
349  Array of SKY calibrations for the input detectors to be
350  processed.
351  camera : `lsst.afw.cameraGeom.Camera`
352  Camera matching the input data to process.
353 
354  Returns
355  -------
356  results : `pipeBase.Struct` containing
357  calExpCamera : `lsst.afw.image.Exposure`
358  Full camera image of the sky-corrected data.
359  skyCorr : `list` of `lsst.afw.math.BackgroundList`
360  Detector-level sky-corrected background lists.
361 
362  See Also
363  --------
364  ~lsst.pipe.drivers.SkyCorrectionTask.runDataRef()
365  """
366  # To allow SkyCorrectionTask to run in the Gen3 butler
367  # environment, a new run() method was added that performs the
368  # same operations in a serial environment (pipetask processing
369  # does not support MPI processing as of 2019-05-03). Methods
370  # used in runDataRef() are used as appropriate in run(), but
371  # some have been rewritten in serial form. Please ensure that
372  # any updates to runDataRef() or the methods it calls with
373  # pool.mapToPrevious() are duplicated in run() and its
374  # methods.
375  #
376  # Variable names here should match those in runDataRef() as
377  # closely as possible. Variables matching data stored in the
378  # pool cache have a prefix indicating this. Variables that
379  # would be local to an MPI processing client have a prefix
380  # "node".
381  idList = [exp.getDetector().getId() for exp in calExpArray]
382 
383  # Construct arrays that match the cache in self.runDataRef() after
384  # self.loadImage() is map/reduced.
385  cacheExposures = []
386  cacheBgList = []
387  exposures = []
388  for calExp, calBgModel in zip(calExpArray, calBkgArray):
389  nodeExp, nodeBgList = self.loadImageRun(calExp, calBgModel)
390  cacheExposures.append(nodeExp)
391  cacheBgList.append(nodeBgList)
392  exposures.append(afwMath.binImage(nodeExp.getMaskedImage(), self.config.binning))
393 
394  if self.config.doBgModel:
395  # Generate focal plane background, updating backgrounds in the "cache".
396  exposures, newCacheBgList, cacheBgModel = self.focalPlaneBackgroundRun(
397  camera, cacheExposures, idList, self.config.bgModel
398  )
399  for cacheBg, newBg in zip(cacheBgList, newCacheBgList):
400  cacheBg.append(newBg)
401 
402  if self.config.doSky:
403  # Measure the sky frame scale on all inputs. Results in
404  # values equal to self.measureSkyFrame() and
405  # self.sky.solveScales() in runDataRef().
406  cacheSky = []
407  measScales = []
408  for cacheExp, skyCalib in zip(cacheExposures, skyCalibs):
409  skyExp = self.sky.exposureToBackground(skyCalib)
410  cacheSky.append(skyExp)
411  scale = self.sky.measureScale(cacheExp.getMaskedImage(), skyExp)
412  measScales.append(scale)
413 
414  scale = self.sky.solveScales(measScales)
415  self.log.info("Sky frame scale: %s" % (scale, ))
416 
417  # Subtract sky frame, as in self.subtractSkyFrame(), with
418  # appropriate scale from the "cache".
419  exposures = []
420  newBgList = []
421  for cacheExp, nodeSky, nodeBgList in zip(cacheExposures, cacheSky, cacheBgList):
422  self.sky.subtractSkyFrame(cacheExp.getMaskedImage(), nodeSky, scale, nodeBgList)
423  exposures.append(afwMath.binImage(cacheExp.getMaskedImage(), self.config.binning))
424 
425  if self.config.doBgModel2:
426  # As above, generate a focal plane background model and
427  # update the cache models.
428  exposures, newBgList, cacheBgModel = self.focalPlaneBackgroundRun(
429  camera, cacheExposures, idList, self.config.bgModel2
430  )
431  for cacheBg, newBg in zip(cacheBgList, newBgList):
432  cacheBg.append(newBg)
433 
434  # Generate camera-level image of calexp and return it along
435  # with the list of sky corrected background models.
436  image = makeCameraImage(camera, zip(idList, exposures))
437 
438  return pipeBase.Struct(
439  calExpCamera=image,
440  skyCorr=cacheBgList,
441  )
442 
443  def loadImage(self, cache, dataId):
444  """Load original image and restore the sky
445 
446  This method runs on the slave nodes.
447 
448  Parameters
449  ----------
450  cache : `lsst.pipe.base.Struct`
451  Process pool cache.
452  dataId : `dict`
453  Data identifier.
454 
455  Returns
456  -------
457  exposure : `lsst.afw.image.Exposure`
458  Resultant exposure.
459  """
460  cache.dataId = dataId
461  cache.exposure = cache.butler.get(self.config.calexpType, dataId, immediate=True).clone()
462  bgOld = cache.butler.get("calexpBackground", dataId, immediate=True)
463  image = cache.exposure.getMaskedImage()
464 
465  # We're removing the old background, so change the sense of all its components
466  for bgData in bgOld:
467  statsImage = bgData[0].getStatsImage()
468  statsImage *= -1
469 
470  image -= bgOld.getImage()
471  cache.bgList = afwMath.BackgroundList()
472  for bgData in bgOld:
473  cache.bgList.append(bgData)
474 
475  if self.config.doMaskObjects:
476  self.maskObjects.findObjects(cache.exposure)
477 
478  return self.collect(cache)
479 
480  def loadImageRun(self, calExp, calExpBkg):
481  """Serial implementation of self.loadImage() for Gen3.
482 
483  Load and restore background to calExp and calExpBkg.
484 
485  Parameters
486  ----------
487  calExp : `lsst.afw.image.Exposure`
488  Detector level calExp image to process.
489  calExpBkg : `lsst.afw.math.BackgroundList`
490  Detector level background list associated with the calExp.
491 
492  Returns
493  -------
494  calExp : `lsst.afw.image.Exposure`
495  Background restored calExp.
496  bgList : `lsst.afw.math.BackgroundList`
497  New background list containing the restoration background.
498  """
499  image = calExp.getMaskedImage()
500 
501  for bgOld in calExpBkg:
502  statsImage = bgOld[0].getStatsImage()
503  statsImage *= -1
504 
505  image -= calExpBkg.getImage()
506  bgList = afwMath.BackgroundList()
507  for bgData in calExpBkg:
508  bgList.append(bgData)
509 
510  if self.config.doMaskObjects:
511  self.maskObjects.findObjects(calExp)
512 
513  return (calExp, bgList)
514 
515  def measureSkyFrame(self, cache, dataId):
516  """Measure scale for sky frame
517 
518  This method runs on the slave nodes.
519 
520  Parameters
521  ----------
522  cache : `lsst.pipe.base.Struct`
523  Process pool cache.
524  dataId : `dict`
525  Data identifier.
526 
527  Returns
528  -------
529  scale : `float`
530  Scale for sky frame.
531  """
532  assert cache.dataId == dataId
533  cache.sky = self.sky.getSkyData(cache.butler, dataId)
534  scale = self.sky.measureScale(cache.exposure.getMaskedImage(), cache.sky)
535  return scale
536 
537  def subtractSkyFrame(self, cache, dataId, scale):
538  """Subtract sky frame
539 
540  This method runs on the slave nodes.
541 
542  Parameters
543  ----------
544  cache : `lsst.pipe.base.Struct`
545  Process pool cache.
546  dataId : `dict`
547  Data identifier.
548  scale : `float`
549  Scale for sky frame.
550 
551  Returns
552  -------
553  exposure : `lsst.afw.image.Exposure`
554  Resultant exposure.
555  """
556  assert cache.dataId == dataId
557  self.sky.subtractSkyFrame(cache.exposure.getMaskedImage(), cache.sky, scale, cache.bgList)
558  return self.collect(cache)
559 
560  def accumulateModel(self, cache, data):
561  """Fit background model for CCD
562 
563  This method runs on the slave nodes.
564 
565  Parameters
566  ----------
567  cache : `lsst.pipe.base.Struct`
568  Process pool cache.
569  data : `lsst.pipe.base.Struct`
570  Data identifier, with `dataId` (data identifier) and `bgModel`
571  (background model) elements.
572 
573  Returns
574  -------
575  bgModel : `lsst.pipe.drivers.background.FocalPlaneBackground`
576  Background model.
577  """
578  assert cache.dataId == data.dataId
579  data.bgModel.addCcd(cache.exposure)
580  return data.bgModel
581 
582  def subtractModel(self, cache, dataId, bgModel):
583  """Subtract background model
584 
585  This method runs on the slave nodes.
586 
587  Parameters
588  ----------
589  cache : `lsst.pipe.base.Struct`
590  Process pool cache.
591  dataId : `dict`
592  Data identifier.
593  bgModel : `lsst.pipe.drivers.background.FocalPlaneBackround`
594  Background model.
595 
596  Returns
597  -------
598  exposure : `lsst.afw.image.Exposure`
599  Resultant exposure.
600  """
601  assert cache.dataId == dataId
602  exposure = cache.exposure
603  image = exposure.getMaskedImage()
604  detector = exposure.getDetector()
605  bbox = image.getBBox()
606  cache.bgModel = bgModel.toCcdBackground(detector, bbox)
607  image -= cache.bgModel.getImage()
608  cache.bgList.append(cache.bgModel[0])
609  return self.collect(cache)
610 
611  def subtractModelRun(self, exposure, bgModel):
612  """Serial implementation of self.subtractModel() for Gen3.
613 
614  Load and restore background to calExp and calExpBkg.
615 
616  Parameters
617  ----------
618  exposure : `lsst.afw.image.Exposure`
619  Exposure to subtract the background model from.
620  bgModel : `lsst.pipe.drivers.background.FocalPlaneBackground`
621  Full camera level background model.
622 
623  Returns
624  -------
625  exposure : `lsst.afw.image.Exposure`
626  Background subtracted input exposure.
627  bgModelCcd : `lsst.afw.math.BackgroundList`
628  Detector level realization of the full background model.
629  bgModelMaskedImage : `lsst.afw.image.MaskedImage`
630  Background model from the bgModelCcd realization.
631  """
632  image = exposure.getMaskedImage()
633  detector = exposure.getDetector()
634  bbox = image.getBBox()
635  bgModelCcd = bgModel.toCcdBackground(detector, bbox)
636  image -= bgModelCcd.getImage()
637 
638  return (exposure, bgModelCcd, bgModelCcd[0])
639 
640  def realiseModel(self, cache, dataId, bgModel):
641  """Generate an image of the background model for visualisation
642 
643  Useful for debugging.
644 
645  Parameters
646  ----------
647  cache : `lsst.pipe.base.Struct`
648  Process pool cache.
649  dataId : `dict`
650  Data identifier.
651  bgModel : `lsst.pipe.drivers.background.FocalPlaneBackround`
652  Background model.
653 
654  Returns
655  -------
656  detId : `int`
657  Detector identifier.
658  image : `lsst.afw.image.MaskedImage`
659  Binned background model image.
660  """
661  assert cache.dataId == dataId
662  exposure = cache.exposure
663  detector = exposure.getDetector()
664  bbox = exposure.getMaskedImage().getBBox()
665  image = bgModel.toCcdBackground(detector, bbox).getImage()
666  return self.collectBinnedImage(exposure, image)
667 
668  def collectBinnedImage(self, exposure, image):
669  """Return the binned image required for visualization
670 
671  This method just helps to cut down on boilerplate.
672 
673  Parameters
674  ----------
675  image : `lsst.afw.image.MaskedImage`
676  Image to go into visualisation.
677 
678  Returns
679  -------
680  detId : `int`
681  Detector identifier.
682  image : `lsst.afw.image.MaskedImage`
683  Binned image.
684  """
685  return (exposure.getDetector().getId(), afwMath.binImage(image, self.config.binning))
686 
687  def collect(self, cache):
688  """Collect exposure for potential visualisation
689 
690  This method runs on the slave nodes.
691 
692  Parameters
693  ----------
694  cache : `lsst.pipe.base.Struct`
695  Process pool cache.
696 
697  Returns
698  -------
699  detId : `int`
700  Detector identifier.
701  image : `lsst.afw.image.MaskedImage`
702  Binned image.
703  """
704  return self.collectBinnedImage(cache.exposure, cache.exposure.maskedImage)
705 
706  def collectOriginal(self, cache, dataId):
707  """Collect original image for visualisation
708 
709  This method runs on the slave nodes.
710 
711  Parameters
712  ----------
713  cache : `lsst.pipe.base.Struct`
714  Process pool cache.
715  dataId : `dict`
716  Data identifier.
717 
718  Returns
719  -------
720  detId : `int`
721  Detector identifier.
722  image : `lsst.afw.image.MaskedImage`
723  Binned image.
724  """
725  exposure = cache.butler.get("calexp", dataId, immediate=True)
726  return self.collectBinnedImage(exposure, exposure.maskedImage)
727 
728  def collectSky(self, cache, dataId):
729  """Collect original image for visualisation
730 
731  This method runs on the slave nodes.
732 
733  Parameters
734  ----------
735  cache : `lsst.pipe.base.Struct`
736  Process pool cache.
737  dataId : `dict`
738  Data identifier.
739 
740  Returns
741  -------
742  detId : `int`
743  Detector identifier.
744  image : `lsst.afw.image.MaskedImage`
745  Binned image.
746  """
747  return self.collectBinnedImage(cache.exposure, cache.sky.getImage())
748 
749  def collectMask(self, cache, dataId):
750  """Collect mask for visualisation
751 
752  This method runs on the slave nodes.
753 
754  Parameters
755  ----------
756  cache : `lsst.pipe.base.Struct`
757  Process pool cache.
758  dataId : `dict`
759  Data identifier.
760 
761  Returns
762  -------
763  detId : `int`
764  Detector identifier.
765  image : `lsst.afw.image.Image`
766  Binned image.
767  """
768  # Convert Mask to floating-point image, because that's what's required for focal plane construction
769  image = afwImage.ImageF(cache.exposure.maskedImage.getBBox())
770  image.array[:] = cache.exposure.maskedImage.mask.array
771  return self.collectBinnedImage(cache.exposure, image)
772 
773  def write(self, cache, dataId):
774  """Write resultant background list
775 
776  This method runs on the slave nodes.
777 
778  Parameters
779  ----------
780  cache : `lsst.pipe.base.Struct`
781  Process pool cache.
782  dataId : `dict`
783  Data identifier.
784  """
785  cache.butler.put(cache.bgList, "skyCorr", dataId)
786 
787  def _getMetadataName(self):
788  """There's no metadata to write out"""
789  return None
def subtractModel(self, cache, dataId, bgModel)
def batchWallTime(cls, time, parsedCmd, numCores)
def makeCameraImage(camera, exposures, filename=None, binning=8)
def focalPlaneBackground(self, camera, pool, dataIdList, config)
def run(self, calExpArray, calBkgArray, skyCalibs, camera)
def realiseModel(self, cache, dataId, bgModel)
def focalPlaneBackgroundRun(self, camera, cacheExposures, idList, config)
def subtractSkyFrame(self, cache, dataId, scale)
def logOperation(self, operation, catch=False, trace=True)
def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler)