21__all__ = [
"SkyCorrectionTask",
"SkyCorrectionConfig"]
30from lsst.daf.butler
import DimensionGraph
31from lsst.pex.config import Config, Field, ConfigurableField, ConfigField
32import lsst.pipe.base.connectionTypes
as cT
34from .background
import (SkyMeasurementTask, FocalPlaneBackground,
35 FocalPlaneBackgroundConfig, MaskObjectsTask)
38__all__ = [
"SkyCorrectionConfig",
"SkyCorrectionTask"]
41def reorderAndPadList(inputList, inputKeys, outputKeys, padWith=None):
42 """Match the order of one list to another, padding if necessary
47 List to be reordered and padded. Elements can be any type.
49 Iterable of values to be compared
with outputKeys.
50 Length must match `inputList`
52 Iterable of values to be compared
with inputKeys.
54 Any value to be inserted where inputKey
not in outputKeys
59 Copy of inputList reordered per outputKeys
and padded
with `padWith`
60 so that the length matches length of outputKeys.
65 outputList.append(inputList[inputKeys.index(d)])
67 outputList.append(padWith)
71def _makeCameraImage(camera, exposures, binning):
72 """Make and write an image of an entire focal plane
79 CCD exposures, binned by `binning`.
81 Binning size that has been applied to images.
84 """Source of images for makeImageFromCamera"""
85 def __init__(self, exposures):
91 CCD exposures, already binned.
94 self.exposures = exposures
95 self.background = np.nan
97 def getCcdImage(self, detector, imageFactory, binSize):
98 """Provide image of CCD to makeImageFromCamera"""
99 detId = detector.getId()
100 if detId
not in self.exposures:
101 dims = detector.getBBox().getDimensions()/binSize
102 image = imageFactory(*[int(xx)
for xx
in dims])
103 image.set(self.background)
105 image = self.exposures[detector.getId()]
106 if hasattr(image,
"getMaskedImage"):
107 image = image.getMaskedImage()
108 if hasattr(image,
"getMask"):
109 mask = image.getMask()
110 isBad = mask.getArray() & mask.getPlaneBitMask(
"NO_DATA") > 0
111 image = image.clone()
112 image.getImage().getArray()[isBad] = self.background
113 if hasattr(image,
"getImage"):
114 image = image.getImage()
116 image = afwMath.rotateImageBy90(image, detector.getOrientation().getNQuarter())
118 return image, detector
120 image = makeImageFromCamera(
122 imageSource=ImageSource(exposures),
123 imageFactory=afwImage.ImageF,
130 """Make and write an image of an entire focal plane
137 List of detector ID
and CCD exposures (binned by `binning`).
138 filename : `str`, optional
141 Binning size that has been applied to images.
143 image = _makeCameraImage(camera, dict(exp for exp
in exposures
if exp
is not None), binning)
144 if filename
is not None:
145 image.writeFits(filename)
149def _skyLookup(datasetType, registry, quantumDataId, collections):
150 """Lookup function to identify sky frames
154 datasetType : `lsst.daf.butler.DatasetType`
156 registry : `lsst.daf.butler.Registry`
157 Butler registry to query.
158 quantumDataId : `lsst.daf.butler.DataCoordinate`
159 Data id to transform to find sky frames.
160 The ``detector`` entry will be stripped.
161 collections : `lsst.daf.butler.CollectionSearch`
162 Collections to search through.
166 results : `list` [`lsst.daf.butler.DatasetRef`]
167 List of datasets that will be used as sky calibration frames
169 newDataId = quantumDataId.subset(DimensionGraph(registry.dimensions, names=["instrument",
"visit"]))
171 for dataId
in registry.queryDataIds([
"visit",
"detector"], dataId=newDataId).expanded():
172 skyFrame = registry.findDataset(datasetType, dataId, collections=collections,
173 timespan=dataId.timespan)
174 skyFrames.append(skyFrame)
180 rawLinker = cT.Input(
181 doc=
"Raw data to provide exp-visit linkage to connect calExp inputs to camera/sky calibs.",
185 storageClass=
"Exposure",
186 dimensions=[
"instrument",
"exposure",
"detector"],
188 calExpArray = cT.Input(
189 doc=
"Input exposures to process",
192 storageClass=
"ExposureF",
193 dimensions=[
"instrument",
"visit",
"detector"],
195 calBkgArray = cT.Input(
196 doc=
"Input background files to use",
198 name=
"calexpBackground",
199 storageClass=
"Background",
200 dimensions=[
"instrument",
"visit",
"detector"],
202 camera = cT.PrerequisiteInput(
203 doc=
"Input camera to use.",
205 storageClass=
"Camera",
206 dimensions=[
"instrument"],
209 skyCalibs = cT.PrerequisiteInput(
210 doc=
"Input sky calibrations to use.",
213 storageClass=
"ExposureF",
214 dimensions=[
"instrument",
"physical_filter",
"detector"],
216 lookupFunction=_skyLookup,
218 calExpCamera = cT.Output(
219 doc=
"Output camera image.",
220 name=
'calexp_camera',
221 storageClass=
"ImageF",
222 dimensions=[
"instrument",
"visit"],
225 doc=
"Output sky corrected images.",
228 storageClass=
"Background",
229 dimensions=[
"instrument",
"visit",
"detector"],
234 """Configuration for SkyCorrectionTask"""
235 bgModel = ConfigField(dtype=FocalPlaneBackgroundConfig, doc=
"Background model")
236 bgModel2 = ConfigField(dtype=FocalPlaneBackgroundConfig, doc=
"2nd Background model")
237 sky = ConfigurableField(target=SkyMeasurementTask, doc=
"Sky measurement")
238 maskObjects = ConfigurableField(target=MaskObjectsTask, doc=
"Mask Objects")
239 doMaskObjects = Field(dtype=bool, default=
True, doc=
"Mask objects to find good sky?")
240 doBgModel = Field(dtype=bool, default=
True, doc=
"Do background model subtraction?")
241 doBgModel2 = Field(dtype=bool, default=
True, doc=
"Do cleanup background model subtraction?")
242 doSky = Field(dtype=bool, default=
True, doc=
"Do sky frame subtraction?")
243 binning = Field(dtype=int, default=8, doc=
"Binning factor for constructing focal-plane images")
244 calexpType = Field(dtype=str, default=
"calexp",
245 doc=
"Should be set to fakes_calexp if you want to process calexps with fakes in.")
248 Config.setDefaults(self)
257 """Correct sky over entire focal plane"""
258 ConfigClass = SkyCorrectionConfig
259 _DefaultName =
"skyCorr"
265 detectorOrder = [ref.dataId[
'detector']
for ref
in inputRefs.calExpArray]
267 inputRefs.calExpArray = reorderAndPadList(inputRefs.calExpArray,
268 [ref.dataId[
'detector']
for ref
in inputRefs.calExpArray],
270 inputRefs.skyCalibs = reorderAndPadList(inputRefs.skyCalibs,
271 [ref.dataId[
'detector']
for ref
in inputRefs.skyCalibs],
273 inputRefs.calBkgArray = reorderAndPadList(inputRefs.calBkgArray,
274 [ref.dataId[
'detector']
for ref
in inputRefs.calBkgArray],
276 outputRefs.skyCorr = reorderAndPadList(outputRefs.skyCorr,
277 [ref.dataId[
'detector']
for ref
in outputRefs.skyCorr],
279 inputs = butlerQC.get(inputRefs)
280 inputs.pop(
"rawLinker",
None)
281 outputs = self.
run(**inputs)
282 butlerQC.put(outputs, outputRefs)
287 self.makeSubtask(
"sky")
288 self.makeSubtask(
"maskObjects")
291 """Perform full focal-plane background subtraction
293 This method runs on the master node.
299 cacheExposures : `list` of `lsst.afw.image.Exposures`
300 List of loaded and processed input calExp.
301 idList : `list` of `int`
302 List of detector ids to iterate over.
303 config : `lsst.pipe.drivers.background.FocalPlaneBackgroundConfig`
304 Configuration to use
for background subtraction.
309 List of binned images,
for creating focal plane image.
310 newCacheBgList : `list` of `lsst.afwMath.backgroundList`
311 Background lists generated.
312 cacheBgModel : `FocalPlaneBackground`
313 Full focal plane background model.
315 bgModel = FocalPlaneBackground.fromCamera(config, camera)
316 data = [pipeBase.Struct(id=id, bgModel=bgModel.clone()) for id
in idList]
319 for nodeData, cacheExp
in zip(data, cacheExposures):
320 nodeData.bgModel.addCcd(cacheExp)
321 bgModelList.append(nodeData.bgModel)
323 for ii, bg
in enumerate(bgModelList):
324 self.log.info(
"Background %d: %d pixels", ii, bg._numbers.getArray().sum())
330 for cacheExp
in cacheExposures:
332 exposures.append(afwMath.binImage(nodeExp.getMaskedImage(), self.config.binning))
333 cacheBgModel.append(nodeBgModel)
334 newCacheBgList.append(nodeBgList)
336 return exposures, newCacheBgList, cacheBgModel
338 def run(self, calExpArray, calBkgArray, skyCalibs, camera):
339 """Performa sky correction on an exposure.
344 Array of detector input calExp images for the exposure to
346 calBkgArray : `list` of `lsst.afw.math.BackgroundList`
347 Array of detector input background lists matching the
350 Array of SKY calibrations
for the input detectors to be
353 Camera matching the input data to process.
357 results : `pipeBase.Struct` containing
359 Full camera image of the sky-corrected data.
360 skyCorr : `list` of `lsst.afw.math.BackgroundList`
361 Detector-level sky-corrected background lists.
378 idList = [exp.getDetector().getId()
for exp
in calExpArray]
385 for calExp, calBgModel
in zip(calExpArray, calBkgArray):
386 nodeExp, nodeBgList = self.
loadImageRun(calExp, calBgModel)
387 cacheExposures.append(nodeExp)
388 cacheBgList.append(nodeBgList)
389 exposures.append(afwMath.binImage(nodeExp.getMaskedImage(), self.config.binning))
391 if self.config.doBgModel:
394 camera, cacheExposures, idList, self.config.bgModel
396 for cacheBg, newBg
in zip(cacheBgList, newCacheBgList):
397 cacheBg.append(newBg)
399 if self.config.doSky:
405 for cacheExp, skyCalib
in zip(cacheExposures, skyCalibs):
406 skyExp = self.sky.exposureToBackground(skyCalib)
407 cacheSky.append(skyExp)
408 scale = self.sky.measureScale(cacheExp.getMaskedImage(), skyExp)
409 measScales.append(scale)
411 scale = self.sky.solveScales(measScales)
412 self.log.info(
"Sky frame scale: %s" % (scale, ))
418 for cacheExp, nodeSky, nodeBgList
in zip(cacheExposures, cacheSky, cacheBgList):
419 self.sky.subtractSkyFrame(cacheExp.getMaskedImage(), nodeSky, scale, nodeBgList)
420 exposures.append(afwMath.binImage(cacheExp.getMaskedImage(), self.config.binning))
422 if self.config.doBgModel2:
426 camera, cacheExposures, idList, self.config.bgModel2
428 for cacheBg, newBg
in zip(cacheBgList, newBgList):
429 cacheBg.append(newBg)
435 return pipeBase.Struct(
441 """Serial implementation of self.loadImage() for Gen3.
443 Load and restore background to calExp
and calExpBkg.
448 Detector level calExp image to process.
449 calExpBkg : `lsst.afw.math.BackgroundList`
450 Detector level background list associated
with the calExp.
455 Background restored calExp.
456 bgList : `lsst.afw.math.BackgroundList`
457 New background list containing the restoration background.
459 image = calExp.getMaskedImage()
461 for bgOld
in calExpBkg:
462 statsImage = bgOld[0].getStatsImage()
465 image -= calExpBkg.getImage()
466 bgList = afwMath.BackgroundList()
467 for bgData
in calExpBkg:
468 bgList.append(bgData)
470 if self.config.doMaskObjects:
471 self.maskObjects.findObjects(calExp)
473 return (calExp, bgList)
476 """Serial implementation of self.subtractModel() for Gen3.
478 Load and restore background to calExp
and calExpBkg.
483 Exposure to subtract the background model
from.
484 bgModel : `lsst.pipe.drivers.background.FocalPlaneBackground`
485 Full camera level background model.
490 Background subtracted input exposure.
491 bgModelCcd : `lsst.afw.math.BackgroundList`
492 Detector level realization of the full background model.
494 Background model
from the bgModelCcd realization.
496 image = exposure.getMaskedImage()
497 detector = exposure.getDetector()
498 bbox = image.getBBox()
499 bgModelCcd = bgModel.toCcdBackground(detector, bbox)
500 image -= bgModelCcd.getImage()
502 return (exposure, bgModelCcd, bgModelCcd[0])
def subtractModelRun(self, exposure, bgModel)
def __init__(self, *args, **kwargs)
def runQuantum(self, butlerQC, inputRefs, outputRefs)
def loadImageRun(self, calExp, calExpBkg)
def focalPlaneBackgroundRun(self, camera, cacheExposures, idList, config)
def run(self, calExpArray, calBkgArray, skyCalibs, camera)
def makeCameraImage(camera, exposures, filename=None, binning=8)