lsst.ip.diffim g7a89bf037d+0a46ea7647
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
33import lsst.pex.config
34import lsst.pipe.base
35
36from .makeKernelBasisList import makeKernelBasisList
37from .psfMatch import PsfMatchConfig, PsfMatchTask, PsfMatchConfigAL, PsfMatchConfigDF
38
39from . import diffimLib
40from . import diffimTools
41from .utils import evaluateMeanPsfFwhm
42
43
45 kernel = lsst.pex.config.ConfigChoiceField(
46 doc="kernel type",
47 typemap=dict(
48 AL=PsfMatchConfigAL,
49 DF=PsfMatchConfigDF
50 ),
51 default="AL",
52 )
53 selectDetection = lsst.pex.config.ConfigurableField(
54 target=SourceDetectionTask,
55 doc="Initial detections used to feed stars to kernel fitting",
56 )
57 selectMeasurement = lsst.pex.config.ConfigurableField(
58 target=SingleFrameMeasurementTask,
59 doc="Initial measurements used to feed stars to kernel fitting",
60 )
61 fwhmExposureGrid = lsst.pex.config.Field(
62 doc="Grid size to compute the average PSF FWHM in an exposure",
63 dtype=int,
64 default=10,
65 )
66 fwhmExposureBuffer = lsst.pex.config.Field(
67 doc="Fractional buffer margin to be left out of all sides of the image during construction"
68 "of grid to compute average PSF FWHM in an exposure",
69 dtype=float,
70 default=0.05,
71 )
72
73 def setDefaults(self):
74 # High sigma detections only
75 self.selectDetection.reEstimateBackground = False
76 self.selectDetection.thresholdValue = 10.0
77
78 # Minimal set of measurments for star selection
79 self.selectMeasurement.algorithms.names.clear()
80 self.selectMeasurement.algorithms.names = ('base_SdssCentroid', 'base_PsfFlux', 'base_PixelFlags',
81 'base_SdssShape', 'base_GaussianFlux', 'base_SkyCoord')
82 self.selectMeasurement.slots.modelFlux = None
83 self.selectMeasurement.slots.apFlux = None
84 self.selectMeasurement.slots.calibFlux = None
85
86
88 """Construct a kernel for PSF matching two exposures.
89 """
90
91 ConfigClass = MakeKernelConfig
92 _DefaultName = "makeALKernel"
93
94 def __init__(self, *args, **kwargs):
95 PsfMatchTask.__init__(self, *args, **kwargs)
96 self.kConfigkConfig = self.config.kernel.active
97 # the background subtraction task uses a config from an unusual location,
98 # so cannot easily be constructed with makeSubtask
99 self.background = SubtractBackgroundTask(config=self.kConfigkConfig.afwBackgroundConfig, name="background",
100 parentTask=self)
103 self.makeSubtask("selectDetection", schema=self.selectSchema)
104 self.makeSubtask("selectMeasurement", schema=self.selectSchema, algMetadata=self.selectAlgMetadata)
105
106 def run(self, template, science, kernelSources, preconvolved=False):
107 """Solve for the kernel and background model that best match two
108 Exposures evaluated at the given source locations.
109
110 Parameters
111 ----------
112 template : `lsst.afw.image.Exposure`
113 Exposure that will be convolved.
114 science : `lsst.afw.image.Exposure`
115 The exposure that will be matched.
116 kernelSources : `list` of `dict`
117 A list of dicts having a "source" and "footprint"
118 field for the Sources deemed to be appropriate for Psf
119 matching. Can be the output from ``selectKernelSources``.
120 preconvolved : `bool`, optional
121 Was the science image convolved with its own PSF?
122
123 Returns
124 -------
125 results : `lsst.pipe.base.Struct`
126
127 ``psfMatchingKernel`` : `lsst.afw.math.LinearCombinationKernel`
128 Spatially varying Psf-matching kernel.
129 ``backgroundModel`` : `lsst.afw.math.Function2D`
130 Spatially varying background-matching function.
131 """
132 kernelCellSet = self._buildCellSet_buildCellSet(template.maskedImage, science.maskedImage, kernelSources)
133 templateFwhmPix = evaluateMeanPsfFwhm(template,
134 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
135 fwhmExposureGrid=self.config.fwhmExposureGrid
136 )
137 scienceFwhmPix = evaluateMeanPsfFwhm(science,
138 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
139 fwhmExposureGrid=self.config.fwhmExposureGrid
140 )
141 if preconvolved:
142 scienceFwhmPix *= np.sqrt(2)
143 basisList = self.makeKernelBasisList(templateFwhmPix, scienceFwhmPix,
144 metadata=self.metadata)
145 spatialSolution, psfMatchingKernel, backgroundModel = self._solve(kernelCellSet, basisList)
146 return lsst.pipe.base.Struct(
147 psfMatchingKernel=psfMatchingKernel,
148 backgroundModel=backgroundModel,
149 )
150
151 def selectKernelSources(self, template, science, candidateList=None, preconvolved=False):
152 """Select sources from a list of candidates, and extract footprints.
153
154 Parameters
155 ----------
156 template : `lsst.afw.image.Exposure`
157 Exposure that will be convolved.
158 science : `lsst.afw.image.Exposure`
159 The exposure that will be matched.
160 candidateList : `list`, optional
161 List of Sources to examine. Elements must be of type afw.table.Source
162 or a type that wraps a Source and has a getSource() method, such as
163 meas.algorithms.PsfCandidateF.
164 preconvolved : `bool`, optional
165 Was the science image convolved with its own PSF?
166
167 Returns
168 -------
169 kernelSources : `list` of `dict`
170 A list of dicts having a "source" and "footprint"
171 field for the Sources deemed to be appropriate for Psf
172 matching.
173 """
174 templateFwhmPix = evaluateMeanPsfFwhm(template,
175 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
176 fwhmExposureGrid=self.config.fwhmExposureGrid
177 )
178 scienceFwhmPix = evaluateMeanPsfFwhm(science,
179 fwhmExposureBuffer=self.config.fwhmExposureBuffer,
180 fwhmExposureGrid=self.config.fwhmExposureGrid
181 )
182 if preconvolved:
183 scienceFwhmPix *= np.sqrt(2)
184 kernelSize = self.makeKernelBasisList(templateFwhmPix, scienceFwhmPix)[0].getWidth()
185 kernelSources = self.makeCandidateList(template, science, kernelSize,
186 candidateList=candidateList,
187 preconvolved=preconvolved)
188 return kernelSources
189
190 def getSelectSources(self, exposure, sigma=None, doSmooth=True, idFactory=None):
191 """Get sources to use for Psf-matching.
192
193 This method runs detection and measurement on an exposure.
194 The returned set of sources will be used as candidates for
195 Psf-matching.
196
197 Parameters
198 ----------
199 exposure : `lsst.afw.image.Exposure`
200 Exposure on which to run detection/measurement
201 sigma : `float`, optional
202 PSF sigma, in pixels, used for smoothing the image for detection.
203 If `None`, the PSF width will be used.
204 doSmooth : `bool`
205 Whether or not to smooth the Exposure with Psf before detection
206 idFactory : `lsst.afw.table.IdFactory`
207 Factory for the generation of Source ids
208
209 Returns
210 -------
211 selectSources :
212 source catalog containing candidates for the Psf-matching
213 """
214 if idFactory:
215 table = lsst.afw.table.SourceTable.make(self.selectSchema, idFactory)
216 else:
218 mi = exposure.getMaskedImage()
219
220 imArr = mi.getImage().getArray()
221 maskArr = mi.getMask().getArray()
222 miArr = np.ma.masked_array(imArr, mask=maskArr)
223 try:
224 fitBg = self.background.fitBackground(mi)
225 bkgd = fitBg.getImageF(self.background.config.algorithm,
226 self.background.config.undersampleStyle)
227 except Exception:
228 self.log.warning("Failed to get background model. Falling back to median background estimation")
229 bkgd = np.ma.median(miArr)
230
231 # Take off background for detection
232 mi -= bkgd
233 try:
234 table.setMetadata(self.selectAlgMetadata)
235 detRet = self.selectDetection.run(
236 table=table,
237 exposure=exposure,
238 sigma=sigma,
239 doSmooth=doSmooth
240 )
241 selectSources = detRet.sources
242 self.selectMeasurement.run(measCat=selectSources, exposure=exposure)
243 finally:
244 # Put back on the background in case it is needed down stream
245 mi += bkgd
246 del bkgd
247 return selectSources
248
249 def makeCandidateList(self, templateExposure, scienceExposure, kernelSize,
250 candidateList=None, preconvolved=False):
251 """Make a list of acceptable KernelCandidates.
252
253 Accept or generate a list of candidate sources for
254 Psf-matching, and examine the Mask planes in both of the
255 images for indications of bad pixels
256
257 Parameters
258 ----------
259 templateExposure : `lsst.afw.image.Exposure`
260 Exposure that will be convolved
261 scienceExposure : `lsst.afw.image.Exposure`
262 Exposure that will be matched-to
263 kernelSize : `float`
264 Dimensions of the Psf-matching Kernel, used to grow detection footprints
265 candidateList : `list`, optional
266 List of Sources to examine. Elements must be of type afw.table.Source
267 or a type that wraps a Source and has a getSource() method, such as
268 meas.algorithms.PsfCandidateF.
269 preconvolved : `bool`, optional
270 Was the science exposure already convolved with its PSF?
271
272 Returns
273 -------
274 candidateList : `list` of `dict`
275 A list of dicts having a "source" and "footprint"
276 field for the Sources deemed to be appropriate for Psf
277 matching.
278
279 Raises
280 ------
281 RuntimeError
282 If ``candidateList`` is empty or contains incompatible types.
283 """
284 if candidateList is None:
285 candidateList = self.getSelectSources(scienceExposure, doSmooth=not preconvolved)
286
287 if len(candidateList) < 1:
288 raise RuntimeError("No candidates in candidateList")
289
290 listTypes = set(type(x) for x in candidateList)
291 if len(listTypes) > 1:
292 raise RuntimeError("Candidate list contains mixed types: %s" % [t for t in listTypes])
293
294 if not isinstance(candidateList[0], lsst.afw.table.SourceRecord):
295 try:
296 candidateList[0].getSource()
297 except Exception as e:
298 raise RuntimeError(f"Candidate List is of type: {type(candidateList[0])} "
299 "Can only make candidate list from list of afwTable.SourceRecords, "
300 f"measAlg.PsfCandidateF or other type with a getSource() method: {e}")
301 candidateList = [c.getSource() for c in candidateList]
302
303 candidateList = diffimTools.sourceToFootprintList(candidateList,
304 templateExposure, scienceExposure,
305 kernelSize,
306 self.kConfigkConfig.detectionConfig,
307 self.log)
308 if len(candidateList) == 0:
309 raise RuntimeError("Cannot find any objects suitable for KernelCandidacy")
310
311 return candidateList
312
313 def makeKernelBasisList(self, targetFwhmPix=None, referenceFwhmPix=None,
314 basisDegGauss=None, basisSigmaGauss=None, metadata=None):
315 """Wrapper to set log messages for
317
318 Parameters
319 ----------
320 targetFwhmPix : `float`, optional
321 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
322 Not used for delta function basis sets.
323 referenceFwhmPix : `float`, optional
324 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
325 Not used for delta function basis sets.
326 basisDegGauss : `list` of `int`, optional
327 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
328 Not used for delta function basis sets.
329 basisSigmaGauss : `list` of `int`, optional
330 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
331 Not used for delta function basis sets.
332 metadata : `lsst.daf.base.PropertySet`, optional
333 Passed on to `lsst.ip.diffim.generateAlardLuptonBasisList`.
334 Not used for delta function basis sets.
335
336 Returns
337 -------
338 basisList: `list` of `lsst.afw.math.kernel.FixedKernel`
339 List of basis kernels.
340 """
341 basisList = makeKernelBasisList(self.kConfigkConfig,
342 targetFwhmPix=targetFwhmPix,
343 referenceFwhmPix=referenceFwhmPix,
344 basisDegGauss=basisDegGauss,
345 basisSigmaGauss=basisSigmaGauss,
346 metadata=metadata)
347 if targetFwhmPix == referenceFwhmPix:
348 self.log.info("Target and reference psf fwhms are equal, falling back to config values")
349 elif referenceFwhmPix > targetFwhmPix:
350 self.log.info("Reference psf fwhm is the greater, normal convolution mode")
351 else:
352 self.log.info("Target psf fwhm is the greater, deconvolution mode")
353
354 return basisList
355
356 def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList):
357 """Build a SpatialCellSet for use with the solve method.
358
359 Parameters
360 ----------
361 templateMaskedImage : `lsst.afw.image.MaskedImage`
362 MaskedImage to PSF-matched to scienceMaskedImage
363 scienceMaskedImage : `lsst.afw.image.MaskedImage`
364 Reference MaskedImage
365 candidateList : `list`
366 A list of footprints/maskedImages for kernel candidates;
367
368 - Currently supported: list of Footprints or measAlg.PsfCandidateF
369
370 Returns
371 -------
372 kernelCellSet : `lsst.afw.math.SpatialCellSet`
373 a SpatialCellSet for use with self._solve
374
375 Raises
376 ------
377 RuntimeError
378 If no `candidateList` is supplied.
379 """
380 if not candidateList:
381 raise RuntimeError("Candidate list must be populated by makeCandidateList")
382
383 sizeCellX, sizeCellY = self._adaptCellSize(candidateList)
384
385 imageBBox = templateMaskedImage.getBBox()
386 imageBBox.clip(scienceMaskedImage.getBBox())
387 # Object to store the KernelCandidates for spatial modeling
388 kernelCellSet = lsst.afw.math.SpatialCellSet(imageBBox, sizeCellX, sizeCellY)
389
390 ps = lsst.pex.config.makePropertySet(self.kConfigkConfig)
391 # Place candidates within the spatial grid
392 for cand in candidateList:
393 if isinstance(cand, lsst.afw.detection.Footprint):
394 bbox = cand.getBBox()
395 else:
396 bbox = cand['footprint'].getBBox()
397 tmi = lsst.afw.image.MaskedImageF(templateMaskedImage, bbox)
398 smi = lsst.afw.image.MaskedImageF(scienceMaskedImage, bbox)
399
400 if not isinstance(cand, lsst.afw.detection.Footprint):
401 if 'source' in cand:
402 cand = cand['source']
403 xPos = cand.getCentroid()[0]
404 yPos = cand.getCentroid()[1]
405 cand = diffimLib.makeKernelCandidate(xPos, yPos, tmi, smi, ps)
406
407 self.log.debug("Candidate %d at %f, %f", cand.getId(), cand.getXCenter(), cand.getYCenter())
408 kernelCellSet.insertCandidate(cand)
409
410 return kernelCellSet
411
412 def _adaptCellSize(self, candidateList):
413 """NOT IMPLEMENTED YET.
414
415 Parameters
416 ----------
417 candidateList : `list`
418 A list of footprints/maskedImages for kernel candidates;
419
420 Returns
421 -------
422 sizeCellX, sizeCellY : `int`
423 New dimensions to use for the kernel.
424 """
425 return self.kConfigkConfig.sizeCellX, self.kConfigkConfig.sizeCellY
static std::shared_ptr< SourceTable > make(Schema const &schema, std::shared_ptr< IdFactory > const &idFactory)
static Schema makeMinimalSchema()
def selectKernelSources(self, template, science, candidateList=None, preconvolved=False)
Definition: makeKernel.py:151
def run(self, template, science, kernelSources, preconvolved=False)
Definition: makeKernel.py:106
def makeKernelBasisList(self, targetFwhmPix=None, referenceFwhmPix=None, basisDegGauss=None, basisSigmaGauss=None, metadata=None)
Definition: makeKernel.py:314
def getSelectSources(self, exposure, sigma=None, doSmooth=True, idFactory=None)
Definition: makeKernel.py:190
def makeCandidateList(self, templateExposure, scienceExposure, kernelSize, candidateList=None, preconvolved=False)
Definition: makeKernel.py:250
def _adaptCellSize(self, candidateList)
Definition: makeKernel.py:412
def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList)
Definition: makeKernel.py:356
def __init__(self, *args, **kwargs)
Definition: makeKernel.py:94
def _solve(self, kernelCellSet, basisList, returnOnExcept=False)
Definition: psfMatch.py:881