lsst.pipe.tasks g0bc41560cc+0dcb0a3cae
Loading...
Searching...
No Matches
assembleChi2Coadd.py
Go to the documentation of this file.
1# This file is part of pipe_tasks.
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
22
23import logging
24import numpy as np
25
26from lsst.afw.detection import Psf
27import lsst.afw.math as afwMath
28import lsst.afw.image as afwImage
29import lsst.pex.config as pexConfig
30import lsst.pipe.base as pipeBase
31import lsst.pipe.base.connectionTypes as cT
32import lsst.utils as utils
33
34
35log = logging.getLogger(__name__)
36
37
38def calculateKernelSize(sigma: float, nSigmaForKernel: float = 7) -> int:
39 """Calculate the size of the smoothing kernel.
40
41 Parameters
42 ----------
43 sigma:
44 Gaussian sigma of smoothing kernel.
45 nSigmaForKernel:
46 The multiple of `sigma` to use to set the size of the kernel.
47 Note that that is the full width of the kernel bounding box
48 (so a value of 7 means 3.5 sigma on either side of center).
49 The value will be rounded up to the nearest odd integer.
50
51 Returns
52 -------
53 size:
54 Size of the smoothing kernel.
55 """
56 return (int(sigma * nSigmaForKernel + 0.5)//2)*2 + 1 # make sure it is odd
57
58
59def convolveImage(image: afwImage.Image, psf: Psf) -> afwImage.Image:
60 """Convolve an image with a psf
61
62 This methodm and the docstring, is based off the method in
64
65 We convolve the image with a Gaussian approximation to the PSF,
66 because this is separable and therefore fast. It's technically a
67 correlation rather than a convolution, but since we use a symmetric
68 Gaussian there's no difference.
69
70 Parameters
71 ----------
72 image:
73 The image to convovle.
74 psf:
75 The PSF to convolve the `image` with.
76
77 Returns
78 -------
79 convolved:
80 The result of convolving `image` with the `psf`.
81 """
82 sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
83 bbox = image.getBBox()
84
85 # Smooth using a Gaussian (which is separable, hence fast) of width sigma
86 # Make a SingleGaussian (separable) kernel with the 'sigma'
87 kWidth = calculateKernelSize(sigma)
88 gaussFunc = afwMath.GaussianFunction1D(sigma)
89 gaussKernel = afwMath.SeparableKernel(kWidth, kWidth, gaussFunc, gaussFunc)
90
91 convolvedImage = image.Factory(bbox)
92
93 afwMath.convolve(convolvedImage, image, gaussKernel, afwMath.ConvolutionControl())
94
95 return convolvedImage.Factory(convolvedImage, bbox, afwImage.PARENT, False)
96
97
98class AssembleChi2CoaddConnections(pipeBase.PipelineTaskConnections,
99 dimensions=("tract", "patch", "band", "skymap"),
100 defaultTemplates={"inputCoaddName": "deep",
101 "outputCoaddName": "deep"}):
102 inputCoadds = cT.Input(
103 doc="Exposure on which to run deblending",
104 name="{inputCoaddName}Coadd_calexp",
105 storageClass="ExposureF",
106 multiple=True,
107 dimensions=("tract", "patch", "band", "skymap")
108 )
109 chi2Coadd = cT.Output(
110 doc="Chi^2 exposure, produced by merging multiband coadds",
111 name="{outputCoaddName}Chi2Coadd",
112 storageClass="ExposureF",
113 dimensions=("tract", "patch", "skymap"),
114 )
115
116
117class AssembleChi2CoaddConfig(pipeBase.PipelineTaskConfig,
118 pipelineConnections=AssembleChi2CoaddConnections):
119 outputPixelatedVariance = pexConfig.Field(
120 dtype=bool,
121 default=False,
122 doc="Whether to output a pixelated variance map for the generated "
123 "chi^2 coadd, or to have a flat variance map defined by combining "
124 "the inverse variance maps of the coadds that were combined."
125 )
126
127 useUnionForMask = pexConfig.Field(
128 dtype=bool,
129 default=True,
130 doc="Whether to calculate the union of the mask plane in each band, "
131 "or the intersection of the mask plane in each band."
132 )
133
134
135class AssembleChi2CoaddTask(pipeBase.Task):
136 """Assemble a chi^2 (Kaiser) coadd from a collection of multi-band coadds
137
138 See Kaiser 2001 for more information.
139 """
140 ConfigClass = AssembleChi2CoaddConfig
141 _DefaultName = "assembleChi2Coadd"
142
143 def __init__(self, *args, **kwargs):
144 super().__init__(*args, **kwargs)
145
146 def combinedMasks(self, masks: list[afwImage.MaskX]) -> afwImage.MaskX:
147 """Combine the mask plane in each input coadd
148
149 Parameters
150 ----------
151 mMask:
152 The MultibandMask in each band.
153
154 Returns
155 -------
156 result:
157 The resulting single band mask.
158 """
159 refMask = masks[0]
160 bbox = refMask.getBBox()
161 mask = refMask.array
162 for _mask in masks[1:]:
163 if self.config.useUnionForMask:
164 mask = mask | _mask.array
165 else:
166 mask = mask & _mask.array
167 result = refMask.Factory(bbox)
168 result.array[:] = mask
169 return result
170
171 @utils.inheritDoc(pipeBase.PipelineTask)
172 def runQuantum(self, butlerQC, inputRefs, outputRefs):
173 inputs = butlerQC.get(inputRefs)
174 outputs = self.run(**inputs)
175 butlerQC.put(outputs, outputRefs)
176
177 def run(self, inputCoadds: list[afwImage.Exposure]) -> pipeBase.Struct:
178 """Assemble the chi2 coadd from the multiband coadds
179
180 Parameters
181 ----------
182 inputCoadds:
183 The coadds to combine into a single chi2 coadd.
184
185 Returns
186 -------
187 result:
188 The chi2 coadd created from the input coadds.
189 """
190 convControl = afwMath.ConvolutionControl()
191 convControl.setDoNormalize(False)
192 convControl.setDoCopyEdge(False)
193
194 # Set a reference exposure to use for creating the new coadd.
195 # It doesn't matter which exposure we use, since we just need the
196 # bounding box information and Factory to create a new expsure with
197 # the same dtype.
198 refExp = inputCoadds[0]
199 bbox = refExp.getBBox()
200
201 image = refExp.image.Factory(bbox)
202 variance_list = []
203 # Convovle the image in each band and weight by the median variance
204 for calexp in inputCoadds:
205 convolved = convolveImage(calexp.image, calexp.getPsf())
206 _variance = np.median(calexp.variance.array)
207 convolved.array[:] /= _variance
208 image += convolved
209 variance_list.append(_variance)
210
211 variance = refExp.variance.Factory(bbox)
212 if self.config.outputPixelatedVariance:
213 # Write the per pixel variance to the output coadd
214 variance.array[:] = np.sum([1/coadd.variance for coadd in inputCoadds], axis=0)
215 else:
216 # Use a flat variance in each band
217 variance.array[:] = np.sum(1/np.array(variance_list))
218 # Combine the masks planes to calculate the mask plae of the new coadd
219 mask = self.combinedMasks([coadd.mask for coadd in inputCoadds])
220 # Create the exposure
221 maskedImage = refExp.maskedImage.Factory(image, mask=mask, variance=variance)
222 chi2coadd = refExp.Factory(maskedImage, exposureInfo=refExp.getInfo())
223 return pipeBase.Struct(chi2Coadd=chi2coadd)
afwImage.Image convolveImage(afwImage.Image image, Psf psf)
int calculateKernelSize(float sigma, float nSigmaForKernel=7)