33import lsst.pipe.base
as pipeBase
34import lsst.pipe.base.connectionTypes
as cT
37log = logging.getLogger(__name__)
41 """Calculate the size of the smoothing kernel.
46 Gaussian sigma of smoothing kernel.
48 The multiple of `sigma` to use to set the size of the kernel.
49 Note that that is the full width of the kernel bounding box
50 (so a value of 7 means 3.5 sigma on either side of center).
51 The value will be rounded up to the nearest odd integer.
56 Size of the smoothing kernel.
58 return (int(sigma * nSigmaForKernel + 0.5)//2)*2 + 1
62 """Convolve an image with a psf
64 This methodm and the docstring, is based off the method in
65 `~lsst.meas.algorithms.detection.SourceDetectionTask`.
67 We convolve the image with a Gaussian approximation to the PSF,
68 because this is separable and therefore fast. It's technically a
69 correlation rather than a convolution, but since we use a symmetric
70 Gaussian there's no difference.
75 The image to convovle.
77 The PSF to convolve the `image` with.
82 The result of convolving `image` with the `psf`.
84 sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
85 bbox = image.getBBox()
90 gaussFunc = afwMath.GaussianFunction1D(sigma)
91 gaussKernel = afwMath.SeparableKernel(kWidth, kWidth, gaussFunc, gaussFunc)
93 convolvedImage = image.Factory(bbox)
95 afwMath.convolve(convolvedImage, image, gaussKernel, afwMath.ConvolutionControl())
97 return convolvedImage.Factory(convolvedImage, bbox, afwImage.PARENT,
False)
101 dimensions=(
"tract",
"patch",
"skymap"),
102 defaultTemplates={
"inputCoaddName":
"deep",
103 "outputCoaddName":
"deepChi2"}):
104 inputCoadds = cT.Input(
105 doc=
"Exposure on which to run deblending",
106 name=
"{inputCoaddName}Coadd_calexp",
107 storageClass=
"ExposureF",
109 dimensions=(
"tract",
"patch",
"band",
"skymap")
111 chi2Coadd = cT.Output(
112 doc=
"Chi^2 exposure, produced by merging multiband coadds",
113 name=
"{outputCoaddName}Coadd_calexp",
114 storageClass=
"ExposureF",
115 dimensions=(
"tract",
"patch",
"skymap"),
119class AssembleChi2CoaddConfig(pipeBase.PipelineTaskConfig,
120 pipelineConnections=AssembleChi2CoaddConnections):
121 outputPixelatedVariance = pexConfig.Field(
124 doc=
"Whether to output a pixelated variance map for the generated "
125 "chi^2 coadd, or to have a flat variance map defined by combining "
126 "the inverse variance maps of the coadds that were combined."
129 useUnionForMask = pexConfig.Field(
132 doc=
"Whether to calculate the union of the mask plane in each band, "
133 "or the intersection of the mask plane in each band."
137class AssembleChi2CoaddTask(pipeBase.PipelineTask):
138 """Assemble a chi^2 coadd from a collection of multi-band coadds
142 .. [1] Szalay, A. S., Connolly, A. J., and Szokoly, G. P., “Simultaneous
143 Multicolor Detection of Faint Galaxies in the Hubble Deep Field”,
144 The Astronomical Journal, vol. 117, no. 1, pp. 68–74,
145 1999. doi:10.1086/300689.
147 .. [2] Kaiser 2001 whitepaper,
148 http://pan-starrs.ifa.hawaii.edu/project/people/kaiser/imageprocessing/im%2B%2B.pdf # noqa: E501
150 .. [3] https://dmtn-015.lsst.io/
152 .. [4] https://project.lsst.org/meetings/law/sites/lsst.org.meetings.law/files/Building%20and%20using%20coadds.pdf # noqa: E501
154 ConfigClass = AssembleChi2CoaddConfig
155 _DefaultName =
"assembleChi2Coadd"
157 def __init__(self, initInputs, **kwargs):
158 super().__init__(initInputs=initInputs, **kwargs)
160 def combinedMasks(self, masks: list[afwImage.MaskX]) -> afwImage.MaskX:
161 """Combine the mask plane in each input coadd
166 The MultibandMask in each band.
171 The resulting single band mask.
174 bbox = refMask.getBBox()
176 for _mask
in masks[1:]:
177 if self.config.useUnionForMask:
178 mask = mask | _mask.array
180 mask = mask & _mask.array
181 result = refMask.Factory(bbox)
182 result.array[:] = mask
185 def runQuantum(self, butlerQC, inputRefs, outputRefs):
186 inputs = butlerQC.get(inputRefs)
187 outputs = self.run(**inputs)
188 butlerQC.put(outputs, outputRefs)
190 def run(self, inputCoadds: list[afwImage.Exposure]) -> pipeBase.Struct:
191 """Assemble the chi2 coadd from the multiband coadds
196 The coadds to combine into a single chi2 coadd.
201 The chi2 coadd created from the input coadds.
203 convControl = afwMath.ConvolutionControl()
204 convControl.setDoNormalize(
False)
205 convControl.setDoCopyEdge(
False)
211 refExp = inputCoadds[0]
212 bbox = refExp.getBBox()
214 image = refExp.image.Factory(bbox)
217 for calexp
in inputCoadds:
219 _variance = np.median(calexp.variance.array)
220 convolved.array[:] /= _variance
222 variance_list.append(_variance)
224 variance = refExp.variance.Factory(bbox)
225 if self.config.outputPixelatedVariance:
227 variance.array[:] = np.sum([1/coadd.variance
for coadd
in inputCoadds], axis=0)
230 variance.array[:] = np.sum(1/np.array(variance_list))
232 mask = self.combinedMasks([coadd.mask
for coadd
in inputCoadds])
234 maskedImage = refExp.maskedImage.Factory(image, mask=mask, variance=variance)
235 chi2coadd = refExp.Factory(maskedImage, exposureInfo=refExp.getInfo())
236 chi2coadd.info.setFilter(
None)
237 return pipeBase.Struct(chi2Coadd=chi2coadd)
240class DetectChi2SourcesConnections(
241 pipeBase.PipelineTaskConnections,
242 dimensions=(
"tract",
"patch",
"skymap"),
244 "inputCoaddName":
"deepChi2",
245 "outputCoaddName":
"deepChi2"
248 detectionSchema = cT.InitOutput(
249 doc=
"Schema of the detection catalog",
250 name=
"{outputCoaddName}Coadd_det_schema",
251 storageClass=
"SourceCatalog",
254 doc=
"Exposure on which detections are to be performed",
255 name=
"{inputCoaddName}Coadd_calexp",
256 storageClass=
"ExposureF",
257 dimensions=(
"tract",
"patch",
"skymap"),
259 outputSources = cT.Output(
260 doc=
"Detected sources catalog",
261 name=
"{outputCoaddName}Coadd_det",
262 storageClass=
"SourceCatalog",
263 dimensions=(
"tract",
"patch",
"skymap"),
267class DetectChi2SourcesConfig(pipeBase.PipelineTaskConfig, pipelineConnections=DetectChi2SourcesConnections):
268 detection = pexConfig.ConfigurableField(
269 target=SourceDetectionTask,
270 doc=
"Detect sources in chi2 coadd"
273 idGenerator = SkyMapIdGeneratorConfig.make_field()
275 def setDefaults(self):
276 super().setDefaults()
277 self.detection.reEstimateBackground =
False
278 self.detection.thresholdValue = 3
281class DetectChi2SourcesTask(pipeBase.PipelineTask):
282 _DefaultName =
"detectChi2Sources"
283 ConfigClass = DetectChi2SourcesConfig
285 def __init__(self, schema=None, **kwargs):
288 super().__init__(**kwargs)
290 schema = afwTable.SourceTable.makeMinimalSchema()
292 self.makeSubtask(
"detection", schema=self.schema)
293 self.detectionSchema = afwTable.SourceCatalog(self.schema)
295 def runQuantum(self, butlerQC, inputRefs, outputRefs):
296 inputs = butlerQC.get(inputRefs)
297 idGenerator = self.config.idGenerator.apply(butlerQC.quantum.dataId)
298 inputs[
"idFactory"] = idGenerator.make_table_id_factory()
299 inputs[
"expId"] = idGenerator.catalog_id
300 outputs = self.run(**inputs)
301 butlerQC.put(outputs, outputRefs)
303 def run(self, exposure: afwImage.Exposure, idFactory: afwTable.IdFactory, expId: int) -> pipeBase.Struct:
304 """Run detection on a chi2 exposure.
309 Exposure on which to detect (may be backround-subtracted and scaled,
310 depending on configuration).
312 IdFactory to set source identifiers.
314 Exposure identifier (integer) for RNG seed.
318 result : `lsst.pipe.base.Struct`
319 Results as a struct with attributes:
321 Catalog of detections (`lsst.afw.table.SourceCatalog`).
323 table = afwTable.SourceTable.make(self.schema, idFactory)
327 detections = self.detection.run(table, exposure, expId=expId, doSmooth=
False)
328 sources = detections.sources
329 return pipeBase.Struct(outputSources=sources)
afwImage.Image convolveImage(afwImage.Image image, Psf psf)
int calculateKernelSize(float sigma, float nSigmaForKernel=7)