LSST Applications 26.0.0,g0265f82a02+6660c170cc,g07994bdeae+30b05a742e,g0a0026dc87+17526d298f,g0a60f58ba1+17526d298f,g0e4bf8285c+96dd2c2ea9,g0ecae5effc+c266a536c8,g1e7d6db67d+6f7cb1f4bb,g26482f50c6+6346c0633c,g2bbee38e9b+6660c170cc,g2cc88a2952+0a4e78cd49,g3273194fdb+f6908454ef,g337abbeb29+6660c170cc,g337c41fc51+9a8f8f0815,g37c6e7c3d5+7bbafe9d37,g44018dc512+6660c170cc,g4a941329ef+4f7594a38e,g4c90b7bd52+5145c320d2,g58be5f913a+bea990ba40,g635b316a6c+8d6b3a3e56,g67924a670a+bfead8c487,g6ae5381d9b+81bc2a20b4,g93c4d6e787+26b17396bd,g98cecbdb62+ed2cb6d659,g98ffbb4407+81bc2a20b4,g9ddcbc5298+7f7571301f,ga1e77700b3+99e9273977,gae46bcf261+6660c170cc,gb2715bf1a1+17526d298f,gc86a011abf+17526d298f,gcf0d15dbbd+96dd2c2ea9,gdaeeff99f8+0d8dbea60f,gdb4ec4c597+6660c170cc,ge23793e450+96dd2c2ea9,gf041782ebf+171108ac67
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
31from lsst.meas.algorithms import SourceDetectionTask, SubtractBackgroundTask
32from lsst.meas.base import SingleFrameMeasurementTask
33from lsst.pex.exceptions import InvalidParameterError
34import lsst.pex.config
35import lsst.pipe.base
36
37from .makeKernelBasisList import makeKernelBasisList
38from .psfMatch import PsfMatchConfig, PsfMatchTask, PsfMatchConfigAL, PsfMatchConfigDF
39
40from . import diffimLib
41from . import diffimTools
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 : `list` of `dict`
119 A list of dicts having a "source" and "footprint"
120 field for the Sources deemed to be appropriate for Psf
121 matching. Can be the output from ``selectKernelSources``.
122 preconvolved : `bool`, optional
123 Was the science image convolved with its own PSF?
124
125 Returns
126 -------
127 results : `lsst.pipe.base.Struct`
128
129 ``psfMatchingKernel`` : `lsst.afw.math.LinearCombinationKernel`
130 Spatially varying Psf-matching kernel.
131 ``backgroundModel`` : `lsst.afw.math.Function2D`
132 Spatially varying background-matching function.
133 """
134 kernelCellSet = self._buildCellSet_buildCellSet(template.maskedImage, science.maskedImage, kernelSources)
135 # Calling getPsfFwhm on template.psf fails on some rare occasions when
136 # the template has no input exposures at the average position of the
137 # stars. So we try getPsfFwhm first on template, and if that fails we
138 # evaluate the PSF on a grid specified by fwhmExposure* fields.
139 # To keep consistent definitions for PSF size on the template and
140 # science images, we use the same method for both.
141 try:
142 templateFwhmPix = getPsfFwhm(template.psf)
143 scienceFwhmPix = getPsfFwhm(science.psf)
144 except InvalidParameterError:
145 self.log.debug("Unable to evaluate PSF at the average position. "
146 "Evaluting PSF on a grid of points."
147 )
148 templateFwhmPix = evaluateMeanPsfFwhm(template,
149 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
150 fwhmExposureGrid=self.config.fwhmExposureGrid
151 )
152 scienceFwhmPix = evaluateMeanPsfFwhm(science,
153 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
154 fwhmExposureGrid=self.config.fwhmExposureGrid
155 )
156
157 if preconvolved:
158 scienceFwhmPix *= np.sqrt(2)
159 basisList = self.makeKernelBasisList(templateFwhmPix, scienceFwhmPix,
160 metadata=self.metadata)
161 spatialSolution, psfMatchingKernel, backgroundModel = self._solve(kernelCellSet, basisList)
162 return lsst.pipe.base.Struct(
163 psfMatchingKernel=psfMatchingKernel,
164 backgroundModel=backgroundModel,
165 )
166
167 def selectKernelSources(self, template, science, candidateList=None, preconvolved=False):
168 """Select sources from a list of candidates, and extract footprints.
169
170 Parameters
171 ----------
172 template : `lsst.afw.image.Exposure`
173 Exposure that will be convolved.
174 science : `lsst.afw.image.Exposure`
175 The exposure that will be matched.
176 candidateList : `list`, optional
177 List of Sources to examine. Elements must be of type afw.table.Source
178 or a type that wraps a Source and has a getSource() method, such as
180 preconvolved : `bool`, optional
181 Was the science image convolved with its own PSF?
182
183 Returns
184 -------
185 kernelSources : `list` of `dict`
186 A list of dicts having a "source" and "footprint"
187 field for the Sources deemed to be appropriate for Psf
188 matching.
189 """
190 # Calling getPsfFwhm on template.psf fails on some rare occasions when
191 # the template has no input exposures at the average position of the
192 # stars. So we try getPsfFwhm first on template, and if that fails we
193 # evaluate the PSF on a grid specified by fwhmExposure* fields.
194 # To keep consistent definitions for PSF size on the template and
195 # science images, we use the same method for both.
196 try:
197 templateFwhmPix = getPsfFwhm(template.psf)
198 scienceFwhmPix = getPsfFwhm(science.psf)
199 except InvalidParameterError:
200 self.log.debug("Unable to evaluate PSF at the average position. "
201 "Evaluting PSF on a grid of points."
202 )
203 templateFwhmPix = evaluateMeanPsfFwhm(template,
204 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
205 fwhmExposureGrid=self.config.fwhmExposureGrid
206 )
207 scienceFwhmPix = evaluateMeanPsfFwhm(science,
208 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
209 fwhmExposureGrid=self.config.fwhmExposureGrid
210 )
211 if preconvolved:
212 scienceFwhmPix *= np.sqrt(2)
213 kernelSize = self.makeKernelBasisList(templateFwhmPix, scienceFwhmPix)[0].getWidth()
214 kernelSources = self.makeCandidateList(template, science, kernelSize,
215 candidateList=candidateList,
216 preconvolved=preconvolved)
217 return kernelSources
218
219 def getSelectSources(self, exposure, sigma=None, doSmooth=True, idFactory=None):
220 """Get sources to use for Psf-matching.
221
222 This method runs detection and measurement on an exposure.
223 The returned set of sources will be used as candidates for
224 Psf-matching.
225
226 Parameters
227 ----------
228 exposure : `lsst.afw.image.Exposure`
229 Exposure on which to run detection/measurement
230 sigma : `float`, optional
231 PSF sigma, in pixels, used for smoothing the image for detection.
232 If `None`, the PSF width will be used.
233 doSmooth : `bool`
234 Whether or not to smooth the Exposure with Psf before detection
235 idFactory : `lsst.afw.table.IdFactory`
236 Factory for the generation of Source ids
237
238 Returns
239 -------
240 selectSources :
241 source catalog containing candidates for the Psf-matching
242 """
243 if idFactory:
244 table = lsst.afw.table.SourceTable.make(self.selectSchema, idFactory)
245 else:
247 mi = exposure.getMaskedImage()
248
249 imArr = mi.image.array
250 maskArr = mi.mask.array
251 miArr = np.ma.masked_array(imArr, mask=maskArr)
252 try:
253 fitBg = self.background.fitBackground(mi)
254 bkgd = fitBg.getImageF(self.background.config.algorithm,
255 self.background.config.undersampleStyle)
256 except Exception:
257 self.log.warning("Failed to get background model. Falling back to median background estimation")
258 bkgd = np.ma.median(miArr)
259
260 # Take off background for detection
261 mi -= bkgd
262 try:
263 table.setMetadata(self.selectAlgMetadata)
264 detRet = self.selectDetection.run(
265 table=table,
266 exposure=exposure,
267 sigma=sigma,
268 doSmooth=doSmooth
269 )
270 selectSources = detRet.sources
271 self.selectMeasurement.run(measCat=selectSources, exposure=exposure)
272 finally:
273 # Put back on the background in case it is needed down stream
274 mi += bkgd
275 del bkgd
276 return selectSources
277
278 def makeCandidateList(self, templateExposure, scienceExposure, kernelSize,
279 candidateList=None, preconvolved=False):
280 """Make a list of acceptable KernelCandidates.
281
282 Accept or generate a list of candidate sources for
283 Psf-matching, and examine the Mask planes in both of the
284 images for indications of bad pixels
285
286 Parameters
287 ----------
288 templateExposure : `lsst.afw.image.Exposure`
289 Exposure that will be convolved
290 scienceExposure : `lsst.afw.image.Exposure`
291 Exposure that will be matched-to
292 kernelSize : `float`
293 Dimensions of the Psf-matching Kernel, used to grow detection footprints
294 candidateList : `list`, optional
295 List of Sources to examine. Elements must be of type afw.table.Source
296 or a type that wraps a Source and has a getSource() method, such as
298 preconvolved : `bool`, optional
299 Was the science exposure already convolved with its PSF?
300
301 Returns
302 -------
303 candidateList : `list` of `dict`
304 A list of dicts having a "source" and "footprint"
305 field for the Sources deemed to be appropriate for Psf
306 matching.
307
308 Raises
309 ------
310 RuntimeError
311 If ``candidateList`` is empty or contains incompatible types.
312 """
313 if candidateList is None:
314 candidateList = self.getSelectSources(scienceExposure, doSmooth=not preconvolved)
315
316 if len(candidateList) < 1:
317 raise RuntimeError("No candidates in candidateList")
318
319 listTypes = set(type(x) for x in candidateList)
320 if len(listTypes) > 1:
321 raise RuntimeError("Candidate list contains mixed types: %s" % [t for t in listTypes])
322
323 if not isinstance(candidateList[0], lsst.afw.table.SourceRecord):
324 try:
325 candidateList[0].getSource()
326 except Exception as e:
327 raise RuntimeError(f"Candidate List is of type: {type(candidateList[0])} "
328 "Can only make candidate list from list of afwTable.SourceRecords, "
329 f"measAlg.PsfCandidateF or other type with a getSource() method: {e}")
330 candidateList = [c.getSource() for c in candidateList]
331
332 candidateList = diffimTools.sourceToFootprintList(candidateList,
333 templateExposure, scienceExposure,
334 kernelSize,
335 self.kConfigkConfig.detectionConfig,
336 self.log)
337 if len(candidateList) == 0:
338 raise RuntimeError("Cannot find any objects suitable for KernelCandidacy")
339
340 return candidateList
341
342 def makeKernelBasisList(self, targetFwhmPix=None, referenceFwhmPix=None,
343 basisDegGauss=None, basisSigmaGauss=None, metadata=None):
344 """Wrapper to set log messages for
346
347 Parameters
348 ----------
349 targetFwhmPix : `float`, optional
350 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
351 Not used for delta function basis sets.
352 referenceFwhmPix : `float`, optional
353 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
354 Not used for delta function basis sets.
355 basisDegGauss : `list` of `int`, optional
356 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
357 Not used for delta function basis sets.
358 basisSigmaGauss : `list` of `int`, optional
359 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
360 Not used for delta function basis sets.
361 metadata : `lsst.daf.base.PropertySet`, optional
362 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
363 Not used for delta function basis sets.
364
365 Returns
366 -------
367 basisList: `list` of `lsst.afw.math.kernel.FixedKernel`
368 List of basis kernels.
369 """
370 basisList = makeKernelBasisList(self.kConfigkConfig,
371 targetFwhmPix=targetFwhmPix,
372 referenceFwhmPix=referenceFwhmPix,
373 basisDegGauss=basisDegGauss,
374 basisSigmaGauss=basisSigmaGauss,
375 metadata=metadata)
376 if targetFwhmPix == referenceFwhmPix:
377 self.log.info("Target and reference psf fwhms are equal, falling back to config values")
378 elif referenceFwhmPix > targetFwhmPix:
379 self.log.info("Reference psf fwhm is the greater, normal convolution mode")
380 else:
381 self.log.info("Target psf fwhm is the greater, deconvolution mode")
382
383 return basisList
384
385 def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList):
386 """Build a SpatialCellSet for use with the solve method.
387
388 Parameters
389 ----------
390 templateMaskedImage : `lsst.afw.image.MaskedImage`
391 MaskedImage to PSF-matched to scienceMaskedImage
392 scienceMaskedImage : `lsst.afw.image.MaskedImage`
393 Reference MaskedImage
394 candidateList : `list`
395 A list of footprints/maskedImages for kernel candidates;
396
397 - Currently supported: list of Footprints or measAlg.PsfCandidateF
398
399 Returns
400 -------
401 kernelCellSet : `lsst.afw.math.SpatialCellSet`
402 a SpatialCellSet for use with self._solve
403
404 Raises
405 ------
406 RuntimeError
407 If no `candidateList` is supplied.
408 """
409 if not candidateList:
410 raise RuntimeError("Candidate list must be populated by makeCandidateList")
411
412 sizeCellX, sizeCellY = self._adaptCellSize(candidateList)
413
414 imageBBox = templateMaskedImage.getBBox()
415 imageBBox.clip(scienceMaskedImage.getBBox())
416 # Object to store the KernelCandidates for spatial modeling
417 kernelCellSet = lsst.afw.math.SpatialCellSet(imageBBox, sizeCellX, sizeCellY)
418
419 ps = lsst.pex.config.makePropertySet(self.kConfigkConfig)
420 # Place candidates within the spatial grid
421 for cand in candidateList:
422 if isinstance(cand, lsst.afw.detection.Footprint):
423 bbox = cand.getBBox()
424 else:
425 bbox = cand['footprint'].getBBox()
426 tmi = lsst.afw.image.MaskedImageF(templateMaskedImage, bbox)
427 smi = lsst.afw.image.MaskedImageF(scienceMaskedImage, bbox)
428
429 if not isinstance(cand, lsst.afw.detection.Footprint):
430 if 'source' in cand:
431 cand = cand['source']
432 xPos = cand.getCentroid()[0]
433 yPos = cand.getCentroid()[1]
434 cand = diffimLib.makeKernelCandidate(xPos, yPos, tmi, smi, ps)
435
436 self.log.debug("Candidate %d at %f, %f", cand.getId(), cand.getXCenter(), cand.getYCenter())
437 kernelCellSet.insertCandidate(cand)
438
439 return kernelCellSet
440
441 def _adaptCellSize(self, candidateList):
442 """NOT IMPLEMENTED YET.
443
444 Parameters
445 ----------
446 candidateList : `list`
447 A list of footprints/maskedImages for kernel candidates;
448
449 Returns
450 -------
451 sizeCellX, sizeCellY : `int`
452 New dimensions to use for the kernel.
453 """
454 return self.kConfigkConfig.sizeCellX, self.kConfigkConfig.sizeCellY
table::Key< int > type
Definition Detector.cc:163
Class to describe the properties of a detected object from an image.
Definition Footprint.h:63
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition Exposure.h:72
A class to manipulate images, masks, and variance as a single object.
Definition MaskedImage.h:74
A kernel that is a linear combination of fixed basis kernels.
Definition Kernel.h:704
A collection of SpatialCells covering an entire image.
A polymorphic functor base class for generating record IDs for a table.
Definition IdFactory.h:21
Record class that contains measurements made on a single exposure.
Definition Source.h:78
static std::shared_ptr< SourceTable > make(Schema const &schema, std::shared_ptr< IdFactory > const &idFactory)
Construct a new table.
Definition Source.cc:382
static Schema makeMinimalSchema()
Return a minimal schema for Source tables and records.
Definition Source.h:258
Class for storing ordered metadata with comments.
Class for storing generic metadata.
Definition PropertySet.h:66
_buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList)
makeKernelBasisList(self, targetFwhmPix=None, referenceFwhmPix=None, basisDegGauss=None, basisSigmaGauss=None, metadata=None)
selectKernelSources(self, template, science, candidateList=None, preconvolved=False)
makeCandidateList(self, templateExposure, scienceExposure, kernelSize, candidateList=None, preconvolved=False)
getSelectSources(self, exposure, sigma=None, doSmooth=True, idFactory=None)
_solve(self, kernelCellSet, basisList, returnOnExcept=False)
Definition psfMatch.py:881
daf::base::PropertySet * set
Definition fits.cc:927