LSST Applications g180d380827+6621f76652,g2079a07aa2+86d27d4dc4,g2305ad1205+f5a9e323a1,g2bbee38e9b+c6a8a0fb72,g337abbeb29+c6a8a0fb72,g33d1c0ed96+c6a8a0fb72,g3a166c0a6a+c6a8a0fb72,g3ddfee87b4+9a10e1fe7b,g48712c4677+c9a099281a,g487adcacf7+f2e03ea30b,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+aead732c78,g64a986408d+eddffb812c,g858d7b2824+eddffb812c,g864b0138d7+aa38e45daa,g974c55ee3d+f37bf00e57,g99cad8db69+119519a52d,g9c22b2923f+e2510deafe,g9ddcbc5298+9a081db1e4,ga1e77700b3+03d07e1c1f,gb0e22166c9+60f28cb32d,gb23b769143+eddffb812c,gba4ed39666+c2a2e4ac27,gbb8dafda3b+27317ec8e9,gbd998247f1+585e252eca,gc120e1dc64+5817c176a8,gc28159a63d+c6a8a0fb72,gc3e9b769f7+6707aea8b4,gcf0d15dbbd+9a10e1fe7b,gdaeeff99f8+f9a426f77a,ge6526c86ff+6a2e01d432,ge79ae78c31+c6a8a0fb72,gee10cc3b42+585e252eca,gff1a9f87cc+eddffb812c,v27.0.0.rc1
LSST Data Management Base Package
Loading...
Searching...
No Matches
makeKernel.py
Go to the documentation of this file.
1# This file is part of ip_diffim.
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__all__ = ["MakeKernelConfig", "MakeKernelTask"]
23
24import numpy as np
25
27import lsst.afw.image
28import lsst.afw.math
29import lsst.afw.table
30import lsst.daf.base
31import lsst.geom
32from lsst.meas.algorithms import SourceDetectionTask, SubtractBackgroundTask
33from lsst.meas.base import SingleFrameMeasurementTask
34from lsst.pex.exceptions import InvalidParameterError
35import lsst.pex.config
36import lsst.pipe.base
37
38from .makeKernelBasisList import makeKernelBasisList
39from .psfMatch import PsfMatchConfig, PsfMatchTask, PsfMatchConfigAL, PsfMatchConfigDF
40
41from . import diffimLib
42from .utils import evaluateMeanPsfFwhm, getPsfFwhm
43
44
47 doc="kernel type",
48 typemap=dict(
49 AL=PsfMatchConfigAL,
50 DF=PsfMatchConfigDF
51 ),
52 default="AL",
53 )
55 target=SourceDetectionTask,
56 doc="Initial detections used to feed stars to kernel fitting",
57 )
59 target=SingleFrameMeasurementTask,
60 doc="Initial measurements used to feed stars to kernel fitting",
61 )
62 fwhmExposureGrid = lsst.pex.config.Field(
63 doc="Grid size to compute the average PSF FWHM in an exposure",
64 dtype=int,
65 default=10,
66 )
67 fwhmExposureBuffer = lsst.pex.config.Field(
68 doc="Fractional buffer margin to be left out of all sides of the image during construction"
69 "of grid to compute average PSF FWHM in an exposure",
70 dtype=float,
71 default=0.05,
72 )
73
74 def setDefaults(self):
75 # High sigma detections only
76 self.selectDetection.reEstimateBackground = False
77 self.selectDetection.thresholdValue = 10.0
78 self.selectDetection.excludeMaskPlanes = ["EDGE"]
79
80 # Minimal set of measurments for star selection
81 self.selectMeasurement.algorithms.names.clear()
82 self.selectMeasurement.algorithms.names = ('base_SdssCentroid', 'base_PsfFlux', 'base_PixelFlags',
83 'base_SdssShape', 'base_GaussianFlux', 'base_SkyCoord')
84 self.selectMeasurement.slots.modelFlux = None
85 self.selectMeasurement.slots.apFlux = None
86 self.selectMeasurement.slots.calibFlux = None
87
88
90 """Construct a kernel for PSF matching two exposures.
91 """
92
93 ConfigClass = MakeKernelConfig
94 _DefaultName = "makeALKernel"
95
96 def __init__(self, *args, **kwargs):
97 PsfMatchTask.__init__(self, *args, **kwargs)
98 self.kConfigkConfig = self.config.kernel.active
99 # the background subtraction task uses a config from an unusual location,
100 # so cannot easily be constructed with makeSubtask
101 self.background = SubtractBackgroundTask(config=self.kConfigkConfig.afwBackgroundConfig, name="background",
102 parentTask=self)
105 self.makeSubtask("selectDetection", schema=self.selectSchema)
106 self.makeSubtask("selectMeasurement", schema=self.selectSchema, algMetadata=self.selectAlgMetadata)
107
108 def run(self, template, science, kernelSources, preconvolved=False):
109 """Solve for the kernel and background model that best match two
110 Exposures evaluated at the given source locations.
111
112 Parameters
113 ----------
114 template : `lsst.afw.image.Exposure`
115 Exposure that will be convolved.
116 science : `lsst.afw.image.Exposure`
117 The exposure that will be matched.
118 kernelSources : `lsst.afw.table.SourceCatalog`
119 Kernel candidate sources with appropriately sized footprints.
120 Typically the output of `MakeKernelTask.selectKernelSources`.
121 preconvolved : `bool`, optional
122 Was the science image convolved with its own PSF?
123
124 Returns
125 -------
126 results : `lsst.pipe.base.Struct`
127
128 ``psfMatchingKernel`` : `lsst.afw.math.LinearCombinationKernel`
129 Spatially varying Psf-matching kernel.
130 ``backgroundModel`` : `lsst.afw.math.Function2D`
131 Spatially varying background-matching function.
132 """
133 kernelCellSet = self._buildCellSet_buildCellSet(template.maskedImage, science.maskedImage, kernelSources)
134 # Calling getPsfFwhm on template.psf fails on some rare occasions when
135 # the template has no input exposures at the average position of the
136 # stars. So we try getPsfFwhm first on template, and if that fails we
137 # evaluate the PSF on a grid specified by fwhmExposure* fields.
138 # To keep consistent definitions for PSF size on the template and
139 # science images, we use the same method for both.
140 try:
141 templateFwhmPix = getPsfFwhm(template.psf)
142 scienceFwhmPix = getPsfFwhm(science.psf)
143 except InvalidParameterError:
144 self.log.debug("Unable to evaluate PSF at the average position. "
145 "Evaluting PSF on a grid of points."
146 )
147 templateFwhmPix = evaluateMeanPsfFwhm(template,
148 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
149 fwhmExposureGrid=self.config.fwhmExposureGrid
150 )
151 scienceFwhmPix = evaluateMeanPsfFwhm(science,
152 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
153 fwhmExposureGrid=self.config.fwhmExposureGrid
154 )
155
156 if preconvolved:
157 scienceFwhmPix *= np.sqrt(2)
158 basisList = self.makeKernelBasisList(templateFwhmPix, scienceFwhmPix,
159 metadata=self.metadata)
160 spatialSolution, psfMatchingKernel, backgroundModel = self._solve(kernelCellSet, basisList)
161 return lsst.pipe.base.Struct(
162 psfMatchingKernel=psfMatchingKernel,
163 backgroundModel=backgroundModel,
164 )
165
166 def selectKernelSources(self, template, science, candidateList=None, preconvolved=False):
167 """Select sources from a list of candidates, and extract footprints.
168
169 Parameters
170 ----------
171 template : `lsst.afw.image.Exposure`
172 Exposure that will be convolved.
173 science : `lsst.afw.image.Exposure`
174 The exposure that will be matched.
175 candidateList : `lsst.afw.table.SourceCatalog`
176 Sources to check as possible kernel candidates.
177 preconvolved : `bool`, optional
178 Was the science image convolved with its own PSF?
179
180 Returns
181 -------
182 kernelSources : `lsst.afw.table.SourceCatalog`
183 Kernel candidates with appropriate sized footprints.
184 """
185 # Calling getPsfFwhm on template.psf fails on some rare occasions when
186 # the template has no input exposures at the average position of the
187 # stars. So we try getPsfFwhm first on template, and if that fails we
188 # evaluate the PSF on a grid specified by fwhmExposure* fields.
189 # To keep consistent definitions for PSF size on the template and
190 # science images, we use the same method for both.
191 try:
192 templateFwhmPix = getPsfFwhm(template.psf)
193 scienceFwhmPix = getPsfFwhm(science.psf)
194 except InvalidParameterError:
195 self.log.debug("Unable to evaluate PSF at the average position. "
196 "Evaluting PSF on a grid of points."
197 )
198 templateFwhmPix = evaluateMeanPsfFwhm(template,
199 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
200 fwhmExposureGrid=self.config.fwhmExposureGrid
201 )
202 scienceFwhmPix = evaluateMeanPsfFwhm(science,
203 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
204 fwhmExposureGrid=self.config.fwhmExposureGrid
205 )
206 if preconvolved:
207 scienceFwhmPix *= np.sqrt(2)
208 kernelSize = self.makeKernelBasisList(templateFwhmPix, scienceFwhmPix)[0].getWidth()
209 kernelSources = self.makeCandidateList(template, science, kernelSize,
210 candidateList=candidateList,
211 preconvolved=preconvolved)
212 return kernelSources
213
214 def getSelectSources(self, exposure, sigma=None, doSmooth=True, idFactory=None):
215 """Get sources to use for Psf-matching.
216
217 This method runs detection and measurement on an exposure.
218 The returned set of sources will be used as candidates for
219 Psf-matching.
220
221 Parameters
222 ----------
223 exposure : `lsst.afw.image.Exposure`
224 Exposure on which to run detection/measurement
225 sigma : `float`, optional
226 PSF sigma, in pixels, used for smoothing the image for detection.
227 If `None`, the PSF width will be used.
228 doSmooth : `bool`
229 Whether or not to smooth the Exposure with Psf before detection
230 idFactory : `lsst.afw.table.IdFactory`
231 Factory for the generation of Source ids
232
233 Returns
234 -------
235 selectSources :
236 source catalog containing candidates for the Psf-matching
237 """
238 if idFactory:
239 table = lsst.afw.table.SourceTable.make(self.selectSchema, idFactory)
240 else:
242 mi = exposure.getMaskedImage()
243
244 imArr = mi.image.array
245 maskArr = mi.mask.array
246 miArr = np.ma.masked_array(imArr, mask=maskArr)
247 try:
248 fitBg = self.background.fitBackground(mi)
249 bkgd = fitBg.getImageF(self.background.config.algorithm,
250 self.background.config.undersampleStyle)
251 except Exception:
252 self.log.warning("Failed to get background model. Falling back to median background estimation")
253 bkgd = np.ma.median(miArr)
254
255 # Take off background for detection
256 mi -= bkgd
257 try:
258 table.setMetadata(self.selectAlgMetadata)
259 detRet = self.selectDetection.run(
260 table=table,
261 exposure=exposure,
262 sigma=sigma,
263 doSmooth=doSmooth
264 )
265 selectSources = detRet.sources
266 self.selectMeasurement.run(measCat=selectSources, exposure=exposure)
267 finally:
268 # Put back on the background in case it is needed down stream
269 mi += bkgd
270 del bkgd
271
272 self.log.info("Selected %d sources via detection measurement.", len(selectSources))
273 return selectSources
274
275 def makeCandidateList(self, convolved, reference, kernelSize,
276 candidateList, preconvolved=False):
277 """Make a list of acceptable KernelCandidates.
278
279 Generate a list of candidate sources for Psf-matching, remove sources
280 with bad pixel masks set or that extend off the image.
281
282 Parameters
283 ----------
284 convolved : `lsst.afw.image.Exposure`
285 Exposure that will be convolved. This is typically the template
286 image, and may have a large bbox than the reference exposure.
287 reference : `lsst.afw.image.Exposure`
288 Exposure that will be matched-to. This is typically the science
289 image.
290 kernelSize : `float`
291 Dimensions of the Psf-matching Kernel, used to set detection
292 footprints.
293 candidateList : `lsst.afw.table.SourceCatalog`
294 List of Sources to examine for kernel candidacy.
295 preconvolved : `bool`, optional
296 Was the science exposure already convolved with its PSF?
297
298 Returns
299 -------
300 candidates : `lsst.afw.table.SourceCatalog`
301 Candidates with footprints extended to a ``kernelSize`` box.
302
303 Raises
304 ------
305 RuntimeError
306 If ``candidateList`` is empty after sub-selection.
307 """
308 if candidateList is None:
309 candidateList = self.getSelectSources(reference, doSmooth=not preconvolved)
310 if len(candidateList) < 1:
311 raise RuntimeError("No kernel candidates after detection and measurement.")
312
313 bitmask = reference.mask.getPlaneBitMask(self.config.badMaskPlanes)
314 good = np.ones(len(candidateList), dtype=bool)
315 # Make all candidates have the same size footprint, based on kernelSize.
316 for i, candidate in enumerate(candidateList):
317 # Only use the brightest peak; the input should be pre-deblended!
318 peak = candidate.getFootprint().getPeaks()[0]
319 size = 2*kernelSize + 1 # ensure the resulting box is odd
320 bbox = lsst.geom.Box2I.makeCenteredBox(candidate.getCentroid(),
321 lsst.geom.Extent2I(size, size))
323 boxFootprint.addPeak(peak.getFx(), peak.getFy(), peak.getPeakValue())
324 candidate.setFootprint(boxFootprint)
325
326 # Reject footprints not contained in either image.
327 if not reference.getBBox().contains(bbox) or not convolved.getBBox().contains(bbox):
328 good[i] = False
329 continue
330 # Reject footprints with any bad mask bits set.
331 if (reference.subset(bbox).mask.array & bitmask).any():
332 good[i] = False
333 continue
334 candidates = candidateList[good].copy(deep=True)
335
336 self.log.info("Selected %d / %d sources as kernel candidates.", good.sum(), len(candidateList))
337
338 if len(candidates) < 1:
339 raise RuntimeError("No good kernel candidates available.")
340
341 return candidates
342
343 def makeKernelBasisList(self, targetFwhmPix=None, referenceFwhmPix=None,
344 basisDegGauss=None, basisSigmaGauss=None, metadata=None):
345 """Wrapper to set log messages for
346 `lsst.ip.diffim.makeKernelBasisList`.
347
348 Parameters
349 ----------
350 targetFwhmPix : `float`, optional
351 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
352 Not used for delta function basis sets.
353 referenceFwhmPix : `float`, optional
354 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
355 Not used for delta function basis sets.
356 basisDegGauss : `list` of `int`, optional
357 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
358 Not used for delta function basis sets.
359 basisSigmaGauss : `list` of `int`, optional
360 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
361 Not used for delta function basis sets.
362 metadata : `lsst.daf.base.PropertySet`, optional
363 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
364 Not used for delta function basis sets.
365
366 Returns
367 -------
368 basisList: `list` of `lsst.afw.math.kernel.FixedKernel`
369 List of basis kernels.
370 """
371 basisList = makeKernelBasisList(self.kConfigkConfig,
372 targetFwhmPix=targetFwhmPix,
373 referenceFwhmPix=referenceFwhmPix,
374 basisDegGauss=basisDegGauss,
375 basisSigmaGauss=basisSigmaGauss,
376 metadata=metadata)
377 if targetFwhmPix == referenceFwhmPix:
378 self.log.info("Target and reference psf fwhms are equal, falling back to config values")
379 elif referenceFwhmPix > targetFwhmPix:
380 self.log.info("Reference psf fwhm is the greater, normal convolution mode")
381 else:
382 self.log.info("Target psf fwhm is the greater, deconvolution mode")
383
384 return basisList
385
386 def _buildCellSet(self, convolved, reference, candidateList):
387 """Build a SpatialCellSet for use with the solve method.
388
389 Parameters
390 ----------
391 convolved : `lsst.afw.image.MaskedImage`
392 MaskedImage to PSF-matched to reference.
393 reference : `lsst.afw.image.MaskedImage`
394 Reference MaskedImage.
395 candidateList : `lsst.afw.table.SourceCatalog`
396 Kernel candidate sources with footprints.
397
398 Returns
399 -------
400 kernelCellSet : `lsst.afw.math.SpatialCellSet`
401 A SpatialCellSet for use with self._solve.
402 """
403 sizeCellX, sizeCellY = self._adaptCellSize(candidateList)
404
405 imageBBox = convolved.getBBox()
406 imageBBox.clip(reference.getBBox())
407 # Object to store the KernelCandidates for spatial modeling
408 kernelCellSet = lsst.afw.math.SpatialCellSet(imageBBox, sizeCellX, sizeCellY)
409
410 candidateConfig = lsst.pex.config.makePropertySet(self.kConfigkConfig)
411 # Place candidates within the spatial grid
412 for candidate in candidateList:
413 bbox = candidate.getFootprint().getBBox()
414 templateCutout = lsst.afw.image.MaskedImageF(convolved, bbox)
415 scienceCutout = lsst.afw.image.MaskedImageF(reference, bbox)
416
417 kernelCandidate = diffimLib.makeKernelCandidate(candidate,
418 templateCutout,
419 scienceCutout,
420 candidateConfig)
421
422 self.log.debug("Candidate %d at %.2f, %.2f rating=%f",
423 kernelCandidate.getId(),
424 kernelCandidate.getXCenter(),
425 kernelCandidate.getYCenter(),
426 kernelCandidate.getCandidateRating())
427 kernelCellSet.insertCandidate(kernelCandidate)
428
429 return kernelCellSet
430
431 def _adaptCellSize(self, candidateList):
432 """NOT IMPLEMENTED YET.
433
434 Parameters
435 ----------
436 candidateList : `list`
437 A list of footprints/maskedImages for kernel candidates;
438
439 Returns
440 -------
441 sizeCellX, sizeCellY : `int`
442 New dimensions to use for the kernel.
443 """
444 return self.kConfigkConfig.sizeCellX, self.kConfigkConfig.sizeCellY
Class to describe the properties of a detected object from an image.
Definition Footprint.h:63
A compact representation of a collection of pixels.
Definition SpanSet.h:78
A collection of SpatialCells covering an entire image.
static std::shared_ptr< SourceTable > make(Schema const &schema, std::shared_ptr< IdFactory > const &idFactory)
Construct a new table.
Definition Source.cc:400
static Schema makeMinimalSchema()
Return a minimal schema for Source tables and records.
Definition Source.h:258
Class for storing ordered metadata with comments.
static Box2I makeCenteredBox(Point2D const &center, Extent const &size)
Create a box centered as closely as possible on a particular point.
Definition Box.cc:97
makeCandidateList(self, convolved, reference, kernelSize, candidateList, preconvolved=False)
_buildCellSet(self, convolved, reference, candidateList)
makeKernelBasisList(self, targetFwhmPix=None, referenceFwhmPix=None, basisDegGauss=None, basisSigmaGauss=None, metadata=None)
selectKernelSources(self, template, science, candidateList=None, preconvolved=False)
run(self, template, science, kernelSources, preconvolved=False)
getSelectSources(self, exposure, sigma=None, doSmooth=True, idFactory=None)
_solve(self, kernelCellSet, basisList, returnOnExcept=False)
Definition psfMatch.py:894