22__all__ = [
"SkyCorrectionTask",
"SkyCorrectionConfig"]
31from lsst.daf.butler
import DimensionGraph
32from lsst.pex.config import Config, Field, ConfigurableField, ConfigField
33import lsst.pipe.base.connectionTypes
as cT
35from .background
import (SkyMeasurementTask, FocalPlaneBackground,
36 FocalPlaneBackgroundConfig, MaskObjectsTask)
39def reorderAndPadList(inputList, inputKeys, outputKeys, padWith=None):
40 """Match the order of one list to another, padding if necessary
45 List to be reordered and padded. Elements can be any type.
47 Iterable of values to be compared
with outputKeys.
48 Length must match `inputList`
50 Iterable of values to be compared
with inputKeys.
52 Any value to be inserted where inputKey
not in outputKeys
57 Copy of inputList reordered per outputKeys
and padded
with `padWith`
58 so that the length matches length of outputKeys.
63 outputList.append(inputList[inputKeys.index(d)])
65 outputList.append(padWith)
69def _makeCameraImage(camera, exposures, binning):
70 """Make and write an image of an entire focal plane
77 CCD exposures, binned by `binning`.
79 Binning size that has been applied to images.
82 """Source of images for makeImageFromCamera"""
83 def __init__(self, exposures):
89 CCD exposures, already binned.
92 self.exposures = exposures
93 self.background = np.nan
95 def getCcdImage(self, detector, imageFactory, binSize):
96 """Provide image of CCD to makeImageFromCamera"""
97 detId = detector.getId()
98 if detId
not in self.exposures:
99 dims = detector.getBBox().getDimensions()/binSize
100 image = imageFactory(*[int(xx)
for xx
in dims])
101 image.set(self.background)
103 image = self.exposures[detector.getId()]
104 if hasattr(image,
"getMaskedImage"):
105 image = image.getMaskedImage()
106 if hasattr(image,
"getMask"):
107 mask = image.getMask()
108 isBad = mask.getArray() & mask.getPlaneBitMask(
"NO_DATA") > 0
109 image = image.clone()
110 image.getImage().getArray()[isBad] = self.background
111 if hasattr(image,
"getImage"):
112 image = image.getImage()
116 return image, detector
118 image = makeImageFromCamera(
120 imageSource=ImageSource(exposures),
121 imageFactory=afwImage.ImageF,
127def makeCameraImage(camera, exposures, filename=None, binning=8):
128 """Make and write an image of an entire focal plane
135 List of detector ID
and CCD exposures (binned by `binning`).
136 filename : `str`, optional
139 Binning size that has been applied to images.
141 image = _makeCameraImage(camera, dict(exp for exp
in exposures
if exp
is not None), binning)
142 if filename
is not None:
143 image.writeFits(filename)
147def _skyLookup(datasetType, registry, quantumDataId, collections):
148 """Lookup function to identify sky frames
152 datasetType : `lsst.daf.butler.DatasetType`
154 registry : `lsst.daf.butler.Registry`
155 Butler registry to query.
156 quantumDataId : `lsst.daf.butler.DataCoordinate`
157 Data id to transform to find sky frames.
158 The ``detector`` entry will be stripped.
159 collections : `lsst.daf.butler.CollectionSearch`
160 Collections to search through.
164 results : `list` [`lsst.daf.butler.DatasetRef`]
165 List of datasets that will be used as sky calibration frames
167 newDataId = quantumDataId.subset(DimensionGraph(registry.dimensions, names=["instrument",
"visit"]))
169 for dataId
in registry.queryDataIds([
"visit",
"detector"], dataId=newDataId).expanded():
170 skyFrame = registry.findDataset(datasetType, dataId, collections=collections,
171 timespan=dataId.timespan)
172 skyFrames.append(skyFrame)
178 rawLinker = cT.Input(
179 doc=
"Raw data to provide exp-visit linkage to connect calExp inputs to camera/sky calibs.",
183 storageClass=
"Exposure",
184 dimensions=[
"instrument",
"exposure",
"detector"],
186 calExpArray = cT.Input(
187 doc=
"Input exposures to process",
190 storageClass=
"ExposureF",
191 dimensions=[
"instrument",
"visit",
"detector"],
193 calBkgArray = cT.Input(
194 doc=
"Input background files to use",
196 name=
"calexpBackground",
197 storageClass=
"Background",
198 dimensions=[
"instrument",
"visit",
"detector"],
200 camera = cT.PrerequisiteInput(
201 doc=
"Input camera to use.",
203 storageClass=
"Camera",
204 dimensions=[
"instrument"],
207 skyCalibs = cT.PrerequisiteInput(
208 doc=
"Input sky calibrations to use.",
211 storageClass=
"ExposureF",
212 dimensions=[
"instrument",
"physical_filter",
"detector"],
214 lookupFunction=_skyLookup,
216 calExpCamera = cT.Output(
217 doc=
"Output camera image.",
218 name=
'calexp_camera',
219 storageClass=
"ImageF",
220 dimensions=[
"instrument",
"visit"],
223 doc=
"Output sky corrected images.",
226 storageClass=
"Background",
227 dimensions=[
"instrument",
"visit",
"detector"],
232 """Configuration for SkyCorrectionTask"""
233 bgModel =
ConfigField(dtype=FocalPlaneBackgroundConfig, doc=
"Background model")
234 bgModel2 =
ConfigField(dtype=FocalPlaneBackgroundConfig, doc=
"2nd Background model")
237 doMaskObjects =
Field(dtype=bool, default=
True, doc=
"Mask objects to find good sky?")
238 doBgModel =
Field(dtype=bool, default=
True, doc=
"Do background model subtraction?")
239 doBgModel2 =
Field(dtype=bool, default=
True, doc=
"Do cleanup background model subtraction?")
240 doSky =
Field(dtype=bool, default=
True, doc=
"Do sky frame subtraction?")
241 binning =
Field(dtype=int, default=8, doc=
"Binning factor for constructing focal-plane images")
242 calexpType =
Field(dtype=str, default=
"calexp",
243 doc=
"Should be set to fakes_calexp if you want to process calexps with fakes in.")
246 Config.setDefaults(self)
255 """Correct sky over entire focal plane"""
256 ConfigClass = SkyCorrectionConfig
257 _DefaultName =
"skyCorr"
263 detectorOrder = [ref.dataId[
'detector']
for ref
in inputRefs.calExpArray]
265 inputRefs.calExpArray = reorderAndPadList(inputRefs.calExpArray,
266 [ref.dataId[
'detector']
for ref
in inputRefs.calExpArray],
268 inputRefs.skyCalibs = reorderAndPadList(inputRefs.skyCalibs,
269 [ref.dataId[
'detector']
for ref
in inputRefs.skyCalibs],
271 inputRefs.calBkgArray = reorderAndPadList(inputRefs.calBkgArray,
272 [ref.dataId[
'detector']
for ref
in inputRefs.calBkgArray],
274 outputRefs.skyCorr = reorderAndPadList(outputRefs.skyCorr,
275 [ref.dataId[
'detector']
for ref
in outputRefs.skyCorr],
277 inputs = butlerQC.get(inputRefs)
278 inputs.pop(
"rawLinker",
None)
279 outputs = self.
run(**inputs)
280 butlerQC.put(outputs, outputRefs)
282 def __init__(self, *args, **kwargs):
283 super().__init__(**kwargs)
285 self.makeSubtask(
"sky")
286 self.makeSubtask(
"maskObjects")
289 """Perform full focal-plane background subtraction
291 This method runs on the master node.
297 cacheExposures : `list` of `lsst.afw.image.Exposures`
298 List of loaded and processed input calExp.
299 idList : `list` of `int`
300 List of detector ids to iterate over.
302 Configuration to use
for background subtraction.
307 List of binned images,
for creating focal plane image.
308 newCacheBgList : `list` of `lsst.afwMath.backgroundList`
309 Background lists generated.
310 cacheBgModel : `FocalPlaneBackground`
311 Full focal plane background model.
313 bgModel = FocalPlaneBackground.fromCamera(config, camera)
314 data = [pipeBase.Struct(id=id, bgModel=bgModel.clone()) for id
in idList]
317 for nodeData, cacheExp
in zip(data, cacheExposures):
318 nodeData.bgModel.addCcd(cacheExp)
319 bgModelList.append(nodeData.bgModel)
321 for ii, bg
in enumerate(bgModelList):
322 self.log.info(
"Background %d: %d pixels", ii, bg._numbers.getArray().sum())
328 for cacheExp
in cacheExposures:
330 exposures.append(
afwMath.binImage(nodeExp.getMaskedImage(), self.config.binning))
331 cacheBgModel.append(nodeBgModel)
332 newCacheBgList.append(nodeBgList)
334 return exposures, newCacheBgList, cacheBgModel
336 def run(self, calExpArray, calBkgArray, skyCalibs, camera):
337 """Performa sky correction on an exposure.
342 Array of detector input calExp images for the exposure to
345 Array of detector input background lists matching the
348 Array of SKY calibrations
for the input detectors to be
351 Camera matching the input data to process.
355 results : `pipeBase.Struct` containing
357 Full camera image of the sky-corrected data.
359 Detector-level sky-corrected background lists.
376 idList = [exp.getDetector().getId()
for exp
in calExpArray]
383 for calExp, calBgModel
in zip(calExpArray, calBkgArray):
384 nodeExp, nodeBgList = self.
loadImageRun(calExp, calBgModel)
385 cacheExposures.append(nodeExp)
386 cacheBgList.append(nodeBgList)
387 exposures.append(
afwMath.binImage(nodeExp.getMaskedImage(), self.config.binning))
389 if self.config.doBgModel:
392 camera, cacheExposures, idList, self.config.bgModel
394 for cacheBg, newBg
in zip(cacheBgList, newCacheBgList):
395 cacheBg.append(newBg)
397 if self.config.doSky:
403 for cacheExp, skyCalib
in zip(cacheExposures, skyCalibs):
404 skyExp = self.sky.exposureToBackground(skyCalib)
405 cacheSky.append(skyExp)
406 scale = self.sky.measureScale(cacheExp.getMaskedImage(), skyExp)
407 measScales.append(scale)
409 scale = self.sky.solveScales(measScales)
410 self.log.info(
"Sky frame scale: %s" % (scale, ))
416 for cacheExp, nodeSky, nodeBgList
in zip(cacheExposures, cacheSky, cacheBgList):
417 self.sky.subtractSkyFrame(cacheExp.getMaskedImage(), nodeSky, scale, nodeBgList)
418 exposures.append(
afwMath.binImage(cacheExp.getMaskedImage(), self.config.binning))
420 if self.config.doBgModel2:
424 camera, cacheExposures, idList, self.config.bgModel2
426 for cacheBg, newBg
in zip(cacheBgList, newBgList):
427 cacheBg.append(newBg)
431 image = makeCameraImage(camera, zip(idList, exposures))
433 return pipeBase.Struct(
439 """Serial implementation of self.loadImage() for Gen3.
441 Load and restore background to calExp
and calExpBkg.
446 Detector level calExp image to process.
448 Detector level background list associated
with the calExp.
453 Background restored calExp.
455 New background list containing the restoration background.
457 image = calExp.getMaskedImage()
459 for bgOld
in calExpBkg:
460 statsImage = bgOld[0].getStatsImage()
463 image -= calExpBkg.getImage()
465 for bgData
in calExpBkg:
466 bgList.append(bgData)
468 if self.config.doMaskObjects:
469 self.maskObjects.findObjects(calExp)
471 return (calExp, bgList)
474 """Serial implementation of self.subtractModel() for Gen3.
476 Load and restore background to calExp
and calExpBkg.
481 Exposure to subtract the background model
from.
483 Full camera level background model.
488 Background subtracted input exposure.
490 Detector level realization of the full background model.
492 Background model
from the bgModelCcd realization.
494 image = exposure.getMaskedImage()
495 detector = exposure.getDetector()
496 bbox = image.getBBox()
497 bgModelCcd = bgModel.toCcdBackground(detector, bbox)
498 image -= bgModelCcd.getImage()
500 return (exposure, bgModelCcd, bgModelCcd[0])
afw::table::PointKey< int > dimensions
An immutable representation of a camera.
A class to contain the data, WCS, and other information needed to describe an image of the sky.
A class to represent a 2-dimensional array of pixels.
A class to manipulate images, masks, and variance as a single object.
def subtractModelRun(self, exposure, bgModel)
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)
std::shared_ptr< ImageT > rotateImageBy90(ImageT const &image, int nQuarter)
Rotate an image by an integral number of quarter turns.
std::shared_ptr< ImageT > binImage(ImageT const &inImage, int const binX, int const binY, lsst::afw::math::Property const flags=lsst::afw::math::MEAN)