Loading [MathJax]/extensions/tex2jax.js
LSST Applications g0fba68d861+849f694866,g1fd858c14a+7a7b9dd5ed,g2c84ff76c0+5cb23283cf,g30358e5240+f0e04ebe90,g35bb328faa+fcb1d3bbc8,g436fd98eb5+bdc6fcdd04,g4af146b050+742274f7cd,g4d2262a081+9d5bd0394b,g4e0f332c67+cb09b8a5b6,g53246c7159+fcb1d3bbc8,g5a012ec0e7+477f9c599b,g60b5630c4e+bdc6fcdd04,g67b6fd64d1+2218407a0c,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g87b7deb4dc+777438113c,g8852436030+ebf28f0d95,g89139ef638+2218407a0c,g9125e01d80+fcb1d3bbc8,g989de1cb63+2218407a0c,g9f33ca652e+42fb53f4c8,g9f7030ddb1+11b9b6f027,ga2b97cdc51+bdc6fcdd04,gab72ac2889+bdc6fcdd04,gabe3b4be73+1e0a283bba,gabf8522325+3210f02652,gb1101e3267+9c79701da9,gb58c049af0+f03b321e39,gb89ab40317+2218407a0c,gcf25f946ba+ebf28f0d95,gd6cbbdb0b4+e8f9c9c900,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+47bbabaf80,gded526ad44+8c3210761e,ge278dab8ac+3ef3db156b,ge410e46f29+2218407a0c,gf67bdafdda+2218407a0c,v29.0.0.rc3
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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, RangeError
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 super().__init__(*args, **kwargs)
98 # the background subtraction task uses a config from an unusual location,
99 # so cannot easily be constructed with makeSubtask
100 self.background = SubtractBackgroundTask(config=self.kConfig.afwBackgroundConfig, name="background",
101 parentTask=self)
102
104 # Add coordinate error fields:
107 self.makeSubtask("selectDetection", schema=self.selectSchema)
108 self.makeSubtask("selectMeasurement", schema=self.selectSchema, algMetadata=self.selectAlgMetadata)
109
110 def run(self, template, science, kernelSources, preconvolved=False):
111 """Solve for the kernel and background model that best match two
112 Exposures evaluated at the given source locations.
113
114 Parameters
115 ----------
116 template : `lsst.afw.image.Exposure`
117 Exposure that will be convolved.
118 science : `lsst.afw.image.Exposure`
119 The exposure that will be matched.
120 kernelSources : `lsst.afw.table.SourceCatalog`
121 Kernel candidate sources with appropriately sized footprints.
122 Typically the output of `MakeKernelTask.selectKernelSources`.
123 preconvolved : `bool`, optional
124 Was the science image convolved with its own PSF?
125
126 Returns
127 -------
128 results : `lsst.pipe.base.Struct`
129
130 ``psfMatchingKernel`` : `lsst.afw.math.LinearCombinationKernel`
131 Spatially varying Psf-matching kernel.
132 ``backgroundModel`` : `lsst.afw.math.Function2D`
133 Spatially varying background-matching function.
134 """
135 kernelCellSet = self._buildCellSet(template.maskedImage, science.maskedImage, kernelSources)
136 # Calling getPsfFwhm on template.psf fails on some rare occasions when
137 # the template has no input exposures at the average position of the
138 # stars. So we try getPsfFwhm first on template, and if that fails we
139 # evaluate the PSF on a grid specified by fwhmExposure* fields.
140 # To keep consistent definitions for PSF size on the template and
141 # science images, we use the same method for both.
142 try:
143 templateFwhmPix = getPsfFwhm(template.psf)
144 scienceFwhmPix = getPsfFwhm(science.psf)
145 except (InvalidParameterError, RangeError):
146 self.log.debug("Unable to evaluate PSF at the average position. "
147 "Evaluting PSF on a grid of points."
148 )
149 templateFwhmPix = evaluateMeanPsfFwhm(template,
150 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
151 fwhmExposureGrid=self.config.fwhmExposureGrid
152 )
153 scienceFwhmPix = evaluateMeanPsfFwhm(science,
154 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
155 fwhmExposureGrid=self.config.fwhmExposureGrid
156 )
157
158 if preconvolved:
159 scienceFwhmPix *= np.sqrt(2)
160 basisList = self.makeKernelBasisList(templateFwhmPix, scienceFwhmPix,
161 metadata=self.metadata)
162 spatialSolution, psfMatchingKernel, backgroundModel = self._solve(kernelCellSet, basisList)
163 return lsst.pipe.base.Struct(
164 psfMatchingKernel=psfMatchingKernel,
165 backgroundModel=backgroundModel,
166 )
167
168 def selectKernelSources(self, template, science, candidateList=None, preconvolved=False):
169 """Select sources from a list of candidates, and extract footprints.
170
171 Parameters
172 ----------
173 template : `lsst.afw.image.Exposure`
174 Exposure that will be convolved.
175 science : `lsst.afw.image.Exposure`
176 The exposure that will be matched.
177 candidateList : `lsst.afw.table.SourceCatalog`
178 Sources to check as possible kernel candidates.
179 preconvolved : `bool`, optional
180 Was the science image convolved with its own PSF?
181
182 Returns
183 -------
184 kernelSources : `lsst.afw.table.SourceCatalog`
185 Kernel candidates with appropriate sized footprints.
186 """
187 # Calling getPsfFwhm on template.psf fails on some rare occasions when
188 # the template has no input exposures at the average position of the
189 # stars. So we try getPsfFwhm first on template, and if that fails we
190 # evaluate the PSF on a grid specified by fwhmExposure* fields.
191 # To keep consistent definitions for PSF size on the template and
192 # science images, we use the same method for both.
193 try:
194 templateFwhmPix = getPsfFwhm(template.psf)
195 scienceFwhmPix = getPsfFwhm(science.psf)
196 except (InvalidParameterError, RangeError):
197 self.log.debug("Unable to evaluate PSF at the average position. "
198 "Evaluting PSF on a grid of points."
199 )
200 templateFwhmPix = evaluateMeanPsfFwhm(template,
201 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
202 fwhmExposureGrid=self.config.fwhmExposureGrid
203 )
204 scienceFwhmPix = evaluateMeanPsfFwhm(science,
205 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
206 fwhmExposureGrid=self.config.fwhmExposureGrid
207 )
208 if preconvolved:
209 scienceFwhmPix *= np.sqrt(2)
210 kernelSize = self.makeKernelBasisList(templateFwhmPix, scienceFwhmPix)[0].getWidth()
211 kernelSources = self.makeCandidateList(template, science, kernelSize,
212 candidateList=candidateList,
213 preconvolved=preconvolved)
214 return kernelSources
215
216 def getSelectSources(self, exposure, sigma=None, doSmooth=True, idFactory=None):
217 """Get sources to use for Psf-matching.
218
219 This method runs detection and measurement on an exposure.
220 The returned set of sources will be used as candidates for
221 Psf-matching.
222
223 Parameters
224 ----------
225 exposure : `lsst.afw.image.Exposure`
226 Exposure on which to run detection/measurement
227 sigma : `float`, optional
228 PSF sigma, in pixels, used for smoothing the image for detection.
229 If `None`, the PSF width will be used.
230 doSmooth : `bool`
231 Whether or not to smooth the Exposure with Psf before detection
232 idFactory : `lsst.afw.table.IdFactory`
233 Factory for the generation of Source ids
234
235 Returns
236 -------
237 selectSources :
238 source catalog containing candidates for the Psf-matching
239 """
240 if idFactory:
241 table = lsst.afw.table.SourceTable.make(self.selectSchema, idFactory)
242 else:
244 mi = exposure.getMaskedImage()
245
246 imArr = mi.image.array
247 maskArr = mi.mask.array
248 miArr = np.ma.masked_array(imArr, mask=maskArr)
249 try:
250 fitBg = self.background.fitBackground(mi)
251 bkgd = fitBg.getImageF(self.background.config.algorithm,
252 self.background.config.undersampleStyle)
253 except Exception:
254 self.log.warning("Failed to get background model. Falling back to median background estimation")
255 bkgd = np.ma.median(miArr)
256
257 # Take off background for detection
258 mi -= bkgd
259 try:
260 table.setMetadata(self.selectAlgMetadata)
261 detRet = self.selectDetection.run(
262 table=table,
263 exposure=exposure,
264 sigma=sigma,
265 doSmooth=doSmooth
266 )
267 selectSources = detRet.sources
268 self.selectMeasurement.run(measCat=selectSources, exposure=exposure)
269 finally:
270 # Put back on the background in case it is needed down stream
271 mi += bkgd
272 del bkgd
273
274 self.log.info("Selected %d sources via detection measurement.", len(selectSources))
275 return selectSources
276
277 def makeCandidateList(self, convolved, reference, kernelSize,
278 candidateList, preconvolved=False, sigma=None):
279 """Make a list of acceptable KernelCandidates.
280
281 Generate a list of candidate sources for Psf-matching, remove sources
282 with bad pixel masks set or that extend off the image.
283
284 Parameters
285 ----------
286 convolved : `lsst.afw.image.Exposure`
287 Exposure that will be convolved. This is typically the template
288 image, and may have a large bbox than the reference exposure.
289 reference : `lsst.afw.image.Exposure`
290 Exposure that will be matched-to. This is typically the science
291 image.
292 kernelSize : `float`
293 Dimensions of the Psf-matching Kernel, used to set detection
294 footprints.
295 candidateList : `lsst.afw.table.SourceCatalog`
296 List of Sources to examine for kernel candidacy.
297 preconvolved : `bool`, optional
298 Was the science exposure already convolved with its PSF?
299
300 Returns
301 -------
302 candidates : `lsst.afw.table.SourceCatalog`
303 Candidates with footprints extended to a ``kernelSize`` box.
304
305 Raises
306 ------
307 RuntimeError
308 If ``candidateList`` is empty after sub-selection.
309 """
310 if candidateList is None:
311 candidateList = self.getSelectSources(reference, doSmooth=not preconvolved, sigma=sigma)
312 if len(candidateList) < 1:
313 raise RuntimeError("No kernel candidates after detection and measurement.")
314
315 bitmask = reference.mask.getPlaneBitMask(self.config.badMaskPlanes)
316 good = np.ones(len(candidateList), dtype=bool)
317 # Make all candidates have the same size footprint, based on kernelSize.
318 for i, candidate in enumerate(candidateList):
319 # Only use the brightest peak; the input should be pre-deblended!
320 peak = candidate.getFootprint().getPeaks()[0]
321 size = 2*kernelSize + 1 # ensure the resulting box is odd
322 bbox = lsst.geom.Box2I.makeCenteredBox(candidate.getCentroid(),
323 lsst.geom.Extent2I(size, size))
325 boxFootprint.addPeak(peak.getFx(), peak.getFy(), peak.getPeakValue())
326 candidate.setFootprint(boxFootprint)
327
328 # Reject footprints not contained in either image.
329 if not reference.getBBox().contains(bbox) or not convolved.getBBox().contains(bbox):
330 good[i] = False
331 continue
332 # Reject footprints with any bad mask bits set.
333 if (reference.subset(bbox).mask.array & bitmask).any():
334 good[i] = False
335 continue
336 candidates = candidateList[good].copy(deep=True)
337
338 self.log.info("Selected %d / %d sources as kernel candidates.", good.sum(), len(candidateList))
339
340 if len(candidates) < 1:
341 raise RuntimeError("No good kernel candidates available.")
342
343 return candidates
344
345 def makeKernelBasisList(self, targetFwhmPix=None, referenceFwhmPix=None,
346 basisDegGauss=None, basisSigmaGauss=None, metadata=None):
347 """Wrapper to set log messages for
348 `lsst.ip.diffim.makeKernelBasisList`.
349
350 Parameters
351 ----------
352 targetFwhmPix : `float`, optional
353 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
354 Not used for delta function basis sets.
355 referenceFwhmPix : `float`, optional
356 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
357 Not used for delta function basis sets.
358 basisDegGauss : `list` of `int`, optional
359 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
360 Not used for delta function basis sets.
361 basisSigmaGauss : `list` of `int`, optional
362 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
363 Not used for delta function basis sets.
364 metadata : `lsst.daf.base.PropertySet`, optional
365 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
366 Not used for delta function basis sets.
367
368 Returns
369 -------
370 basisList: `list` of `lsst.afw.math.kernel.FixedKernel`
371 List of basis kernels.
372 """
373 basisList = makeKernelBasisList(self.kConfig,
374 targetFwhmPix=targetFwhmPix,
375 referenceFwhmPix=referenceFwhmPix,
376 basisDegGauss=basisDegGauss,
377 basisSigmaGauss=basisSigmaGauss,
378 metadata=metadata)
379 if targetFwhmPix == referenceFwhmPix:
380 self.log.info("Target and reference psf fwhms are equal, falling back to config values")
381 elif referenceFwhmPix > targetFwhmPix:
382 self.log.info("Reference psf fwhm is the greater, normal convolution mode")
383 else:
384 self.log.info("Target psf fwhm is the greater, deconvolution mode")
385
386 return basisList
387
388 def _buildCellSet(self, convolved, reference, candidateList):
389 """Build a SpatialCellSet for use with the solve method.
390
391 Parameters
392 ----------
393 convolved : `lsst.afw.image.MaskedImage`
394 MaskedImage to PSF-matched to reference.
395 reference : `lsst.afw.image.MaskedImage`
396 Reference MaskedImage.
397 candidateList : `lsst.afw.table.SourceCatalog`
398 Kernel candidate sources with footprints.
399
400 Returns
401 -------
402 kernelCellSet : `lsst.afw.math.SpatialCellSet`
403 A SpatialCellSet for use with self._solve.
404 """
405 sizeCellX, sizeCellY = self._adaptCellSize(candidateList)
406
407 imageBBox = convolved.getBBox()
408 imageBBox.clip(reference.getBBox())
409 # Object to store the KernelCandidates for spatial modeling
410 kernelCellSet = lsst.afw.math.SpatialCellSet(imageBBox, sizeCellX, sizeCellY)
411
412 candidateConfig = lsst.pex.config.makePropertySet(self.kConfig)
413 # Place candidates within the spatial grid
414 for candidate in candidateList:
415 bbox = candidate.getFootprint().getBBox()
416 templateCutout = lsst.afw.image.MaskedImageF(convolved, bbox)
417 scienceCutout = lsst.afw.image.MaskedImageF(reference, bbox)
418
419 kernelCandidate = diffimLib.makeKernelCandidate(candidate,
420 templateCutout,
421 scienceCutout,
422 candidateConfig)
423
424 self.log.debug("Candidate %d at %.2f, %.2f rating=%f",
425 kernelCandidate.getId(),
426 kernelCandidate.getXCenter(),
427 kernelCandidate.getYCenter(),
428 kernelCandidate.getCandidateRating())
429 kernelCellSet.insertCandidate(kernelCandidate)
430
431 return kernelCellSet
432
433 def _adaptCellSize(self, candidateList):
434 """NOT IMPLEMENTED YET.
435
436 Parameters
437 ----------
438 candidateList : `list`
439 A list of footprints/maskedImages for kernel candidates;
440
441 Returns
442 -------
443 sizeCellX, sizeCellY : `int`
444 New dimensions to use for the kernel.
445 """
446 return self.kConfig.sizeCellX, self.kConfig.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 ErrorKey addErrorFields(Schema &schema)
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, sigma=None)
_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:789