LSST Applications g063fba187b+cac8b7c890,g0f08755f38+6aee506743,g1653933729+a8ce1bb630,g168dd56ebc+a8ce1bb630,g1a2382251a+b4475c5878,g1dcb35cd9c+8f9bc1652e,g20f6ffc8e0+6aee506743,g217e2c1bcf+73dee94bd0,g28da252d5a+1f19c529b9,g2bbee38e9b+3f2625acfc,g2bc492864f+3f2625acfc,g3156d2b45e+6e55a43351,g32e5bea42b+1bb94961c2,g347aa1857d+3f2625acfc,g35bb328faa+a8ce1bb630,g3a166c0a6a+3f2625acfc,g3e281a1b8c+c5dd892a6c,g3e8969e208+a8ce1bb630,g414038480c+5927e1bc1e,g41af890bb2+8a9e676b2a,g7af13505b9+809c143d88,g80478fca09+6ef8b1810f,g82479be7b0+f568feb641,g858d7b2824+6aee506743,g89c8672015+f4add4ffd5,g9125e01d80+a8ce1bb630,ga5288a1d22+2903d499ea,gb58c049af0+d64f4d3760,gc28159a63d+3f2625acfc,gcab2d0539d+b12535109e,gcf0d15dbbd+46a3f46ba9,gda6a2b7d83+46a3f46ba9,gdaeeff99f8+1711a396fd,ge79ae78c31+3f2625acfc,gef2f8181fd+0a71e47438,gf0baf85859+c1f95f4921,gfa517265be+6aee506743,gfa999e8aa5+17cd334064,w.2024.51
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, 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 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)
103
105 # Add coordinate error fields:
108 self.makeSubtask("selectDetection", schema=self.selectSchema)
109 self.makeSubtask("selectMeasurement", schema=self.selectSchema, algMetadata=self.selectAlgMetadata)
110
111 def run(self, template, science, kernelSources, preconvolved=False):
112 """Solve for the kernel and background model that best match two
113 Exposures evaluated at the given source locations.
114
115 Parameters
116 ----------
117 template : `lsst.afw.image.Exposure`
118 Exposure that will be convolved.
119 science : `lsst.afw.image.Exposure`
120 The exposure that will be matched.
121 kernelSources : `lsst.afw.table.SourceCatalog`
122 Kernel candidate sources with appropriately sized footprints.
123 Typically the output of `MakeKernelTask.selectKernelSources`.
124 preconvolved : `bool`, optional
125 Was the science image convolved with its own PSF?
126
127 Returns
128 -------
129 results : `lsst.pipe.base.Struct`
130
131 ``psfMatchingKernel`` : `lsst.afw.math.LinearCombinationKernel`
132 Spatially varying Psf-matching kernel.
133 ``backgroundModel`` : `lsst.afw.math.Function2D`
134 Spatially varying background-matching function.
135 """
136 kernelCellSet = self._buildCellSet_buildCellSet(template.maskedImage, science.maskedImage, kernelSources)
137 # Calling getPsfFwhm on template.psf fails on some rare occasions when
138 # the template has no input exposures at the average position of the
139 # stars. So we try getPsfFwhm first on template, and if that fails we
140 # evaluate the PSF on a grid specified by fwhmExposure* fields.
141 # To keep consistent definitions for PSF size on the template and
142 # science images, we use the same method for both.
143 try:
144 templateFwhmPix = getPsfFwhm(template.psf)
145 scienceFwhmPix = getPsfFwhm(science.psf)
146 except (InvalidParameterError, RangeError):
147 self.log.debug("Unable to evaluate PSF at the average position. "
148 "Evaluting PSF on a grid of points."
149 )
150 templateFwhmPix = evaluateMeanPsfFwhm(template,
151 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
152 fwhmExposureGrid=self.config.fwhmExposureGrid
153 )
154 scienceFwhmPix = evaluateMeanPsfFwhm(science,
155 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
156 fwhmExposureGrid=self.config.fwhmExposureGrid
157 )
158
159 if preconvolved:
160 scienceFwhmPix *= np.sqrt(2)
161 basisList = self.makeKernelBasisList(templateFwhmPix, scienceFwhmPix,
162 metadata=self.metadata)
163 spatialSolution, psfMatchingKernel, backgroundModel = self._solve(kernelCellSet, basisList)
164 return lsst.pipe.base.Struct(
165 psfMatchingKernel=psfMatchingKernel,
166 backgroundModel=backgroundModel,
167 )
168
169 def selectKernelSources(self, template, science, candidateList=None, preconvolved=False):
170 """Select sources from a list of candidates, and extract footprints.
171
172 Parameters
173 ----------
174 template : `lsst.afw.image.Exposure`
175 Exposure that will be convolved.
176 science : `lsst.afw.image.Exposure`
177 The exposure that will be matched.
178 candidateList : `lsst.afw.table.SourceCatalog`
179 Sources to check as possible kernel candidates.
180 preconvolved : `bool`, optional
181 Was the science image convolved with its own PSF?
182
183 Returns
184 -------
185 kernelSources : `lsst.afw.table.SourceCatalog`
186 Kernel candidates with appropriate sized footprints.
187 """
188 # Calling getPsfFwhm on template.psf fails on some rare occasions when
189 # the template has no input exposures at the average position of the
190 # stars. So we try getPsfFwhm first on template, and if that fails we
191 # evaluate the PSF on a grid specified by fwhmExposure* fields.
192 # To keep consistent definitions for PSF size on the template and
193 # science images, we use the same method for both.
194 try:
195 templateFwhmPix = getPsfFwhm(template.psf)
196 scienceFwhmPix = getPsfFwhm(science.psf)
197 except (InvalidParameterError, RangeError):
198 self.log.debug("Unable to evaluate PSF at the average position. "
199 "Evaluting PSF on a grid of points."
200 )
201 templateFwhmPix = evaluateMeanPsfFwhm(template,
202 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
203 fwhmExposureGrid=self.config.fwhmExposureGrid
204 )
205 scienceFwhmPix = evaluateMeanPsfFwhm(science,
206 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
207 fwhmExposureGrid=self.config.fwhmExposureGrid
208 )
209 if preconvolved:
210 scienceFwhmPix *= np.sqrt(2)
211 kernelSize = self.makeKernelBasisList(templateFwhmPix, scienceFwhmPix)[0].getWidth()
212 kernelSources = self.makeCandidateList(template, science, kernelSize,
213 candidateList=candidateList,
214 preconvolved=preconvolved)
215 return kernelSources
216
217 def getSelectSources(self, exposure, sigma=None, doSmooth=True, idFactory=None):
218 """Get sources to use for Psf-matching.
219
220 This method runs detection and measurement on an exposure.
221 The returned set of sources will be used as candidates for
222 Psf-matching.
223
224 Parameters
225 ----------
226 exposure : `lsst.afw.image.Exposure`
227 Exposure on which to run detection/measurement
228 sigma : `float`, optional
229 PSF sigma, in pixels, used for smoothing the image for detection.
230 If `None`, the PSF width will be used.
231 doSmooth : `bool`
232 Whether or not to smooth the Exposure with Psf before detection
233 idFactory : `lsst.afw.table.IdFactory`
234 Factory for the generation of Source ids
235
236 Returns
237 -------
238 selectSources :
239 source catalog containing candidates for the Psf-matching
240 """
241 if idFactory:
242 table = lsst.afw.table.SourceTable.make(self.selectSchema, idFactory)
243 else:
245 mi = exposure.getMaskedImage()
246
247 imArr = mi.image.array
248 maskArr = mi.mask.array
249 miArr = np.ma.masked_array(imArr, mask=maskArr)
250 try:
251 fitBg = self.background.fitBackground(mi)
252 bkgd = fitBg.getImageF(self.background.config.algorithm,
253 self.background.config.undersampleStyle)
254 except Exception:
255 self.log.warning("Failed to get background model. Falling back to median background estimation")
256 bkgd = np.ma.median(miArr)
257
258 # Take off background for detection
259 mi -= bkgd
260 try:
261 table.setMetadata(self.selectAlgMetadata)
262 detRet = self.selectDetection.run(
263 table=table,
264 exposure=exposure,
265 sigma=sigma,
266 doSmooth=doSmooth
267 )
268 selectSources = detRet.sources
269 self.selectMeasurement.run(measCat=selectSources, exposure=exposure)
270 finally:
271 # Put back on the background in case it is needed down stream
272 mi += bkgd
273 del bkgd
274
275 self.log.info("Selected %d sources via detection measurement.", len(selectSources))
276 return selectSources
277
278 def makeCandidateList(self, convolved, reference, kernelSize,
279 candidateList, preconvolved=False, sigma=None):
280 """Make a list of acceptable KernelCandidates.
281
282 Generate a list of candidate sources for Psf-matching, remove sources
283 with bad pixel masks set or that extend off the image.
284
285 Parameters
286 ----------
287 convolved : `lsst.afw.image.Exposure`
288 Exposure that will be convolved. This is typically the template
289 image, and may have a large bbox than the reference exposure.
290 reference : `lsst.afw.image.Exposure`
291 Exposure that will be matched-to. This is typically the science
292 image.
293 kernelSize : `float`
294 Dimensions of the Psf-matching Kernel, used to set detection
295 footprints.
296 candidateList : `lsst.afw.table.SourceCatalog`
297 List of Sources to examine for kernel candidacy.
298 preconvolved : `bool`, optional
299 Was the science exposure already convolved with its PSF?
300
301 Returns
302 -------
303 candidates : `lsst.afw.table.SourceCatalog`
304 Candidates with footprints extended to a ``kernelSize`` box.
305
306 Raises
307 ------
308 RuntimeError
309 If ``candidateList`` is empty after sub-selection.
310 """
311 if candidateList is None:
312 candidateList = self.getSelectSources(reference, doSmooth=not preconvolved, sigma=sigma)
313 if len(candidateList) < 1:
314 raise RuntimeError("No kernel candidates after detection and measurement.")
315
316 bitmask = reference.mask.getPlaneBitMask(self.config.badMaskPlanes)
317 good = np.ones(len(candidateList), dtype=bool)
318 # Make all candidates have the same size footprint, based on kernelSize.
319 for i, candidate in enumerate(candidateList):
320 # Only use the brightest peak; the input should be pre-deblended!
321 peak = candidate.getFootprint().getPeaks()[0]
322 size = 2*kernelSize + 1 # ensure the resulting box is odd
323 bbox = lsst.geom.Box2I.makeCenteredBox(candidate.getCentroid(),
324 lsst.geom.Extent2I(size, size))
326 boxFootprint.addPeak(peak.getFx(), peak.getFy(), peak.getPeakValue())
327 candidate.setFootprint(boxFootprint)
328
329 # Reject footprints not contained in either image.
330 if not reference.getBBox().contains(bbox) or not convolved.getBBox().contains(bbox):
331 good[i] = False
332 continue
333 # Reject footprints with any bad mask bits set.
334 if (reference.subset(bbox).mask.array & bitmask).any():
335 good[i] = False
336 continue
337 candidates = candidateList[good].copy(deep=True)
338
339 self.log.info("Selected %d / %d sources as kernel candidates.", good.sum(), len(candidateList))
340
341 if len(candidates) < 1:
342 raise RuntimeError("No good kernel candidates available.")
343
344 return candidates
345
346 def makeKernelBasisList(self, targetFwhmPix=None, referenceFwhmPix=None,
347 basisDegGauss=None, basisSigmaGauss=None, metadata=None):
348 """Wrapper to set log messages for
349 `lsst.ip.diffim.makeKernelBasisList`.
350
351 Parameters
352 ----------
353 targetFwhmPix : `float`, optional
354 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
355 Not used for delta function basis sets.
356 referenceFwhmPix : `float`, optional
357 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
358 Not used for delta function basis sets.
359 basisDegGauss : `list` of `int`, optional
360 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
361 Not used for delta function basis sets.
362 basisSigmaGauss : `list` of `int`, optional
363 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
364 Not used for delta function basis sets.
365 metadata : `lsst.daf.base.PropertySet`, optional
366 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
367 Not used for delta function basis sets.
368
369 Returns
370 -------
371 basisList: `list` of `lsst.afw.math.kernel.FixedKernel`
372 List of basis kernels.
373 """
374 basisList = makeKernelBasisList(self.kConfigkConfig,
375 targetFwhmPix=targetFwhmPix,
376 referenceFwhmPix=referenceFwhmPix,
377 basisDegGauss=basisDegGauss,
378 basisSigmaGauss=basisSigmaGauss,
379 metadata=metadata)
380 if targetFwhmPix == referenceFwhmPix:
381 self.log.info("Target and reference psf fwhms are equal, falling back to config values")
382 elif referenceFwhmPix > targetFwhmPix:
383 self.log.info("Reference psf fwhm is the greater, normal convolution mode")
384 else:
385 self.log.info("Target psf fwhm is the greater, deconvolution mode")
386
387 return basisList
388
389 def _buildCellSet(self, convolved, reference, candidateList):
390 """Build a SpatialCellSet for use with the solve method.
391
392 Parameters
393 ----------
394 convolved : `lsst.afw.image.MaskedImage`
395 MaskedImage to PSF-matched to reference.
396 reference : `lsst.afw.image.MaskedImage`
397 Reference MaskedImage.
398 candidateList : `lsst.afw.table.SourceCatalog`
399 Kernel candidate sources with footprints.
400
401 Returns
402 -------
403 kernelCellSet : `lsst.afw.math.SpatialCellSet`
404 A SpatialCellSet for use with self._solve.
405 """
406 sizeCellX, sizeCellY = self._adaptCellSize(candidateList)
407
408 imageBBox = convolved.getBBox()
409 imageBBox.clip(reference.getBBox())
410 # Object to store the KernelCandidates for spatial modeling
411 kernelCellSet = lsst.afw.math.SpatialCellSet(imageBBox, sizeCellX, sizeCellY)
412
413 candidateConfig = lsst.pex.config.makePropertySet(self.kConfigkConfig)
414 # Place candidates within the spatial grid
415 for candidate in candidateList:
416 bbox = candidate.getFootprint().getBBox()
417 templateCutout = lsst.afw.image.MaskedImageF(convolved, bbox)
418 scienceCutout = lsst.afw.image.MaskedImageF(reference, bbox)
419
420 kernelCandidate = diffimLib.makeKernelCandidate(candidate,
421 templateCutout,
422 scienceCutout,
423 candidateConfig)
424
425 self.log.debug("Candidate %d at %.2f, %.2f rating=%f",
426 kernelCandidate.getId(),
427 kernelCandidate.getXCenter(),
428 kernelCandidate.getYCenter(),
429 kernelCandidate.getCandidateRating())
430 kernelCellSet.insertCandidate(kernelCandidate)
431
432 return kernelCellSet
433
434 def _adaptCellSize(self, candidateList):
435 """NOT IMPLEMENTED YET.
436
437 Parameters
438 ----------
439 candidateList : `list`
440 A list of footprints/maskedImages for kernel candidates;
441
442 Returns
443 -------
444 sizeCellX, sizeCellY : `int`
445 New dimensions to use for the kernel.
446 """
447 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 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:797