LSSTApplications  16.0-10-g0ee56ad+5,16.0-11-ga33d1f2+5,16.0-12-g3ef5c14+3,16.0-12-g71e5ef5+18,16.0-12-gbdf3636+3,16.0-13-g118c103+3,16.0-13-g8f68b0a+3,16.0-15-gbf5c1cb+4,16.0-16-gfd17674+3,16.0-17-g7c01f5c+3,16.0-18-g0a50484+1,16.0-20-ga20f992+8,16.0-21-g0e05fd4+6,16.0-21-g15e2d33+4,16.0-22-g62d8060+4,16.0-22-g847a80f+4,16.0-25-gf00d9b8+1,16.0-28-g3990c221+4,16.0-3-gf928089+3,16.0-32-g88a4f23+5,16.0-34-gd7987ad+3,16.0-37-gc7333cb+2,16.0-4-g10fc685+2,16.0-4-g18f3627+26,16.0-4-g5f3a788+26,16.0-5-gaf5c3d7+4,16.0-5-gcc1f4bb+1,16.0-6-g3b92700+4,16.0-6-g4412fcd+3,16.0-6-g7235603+4,16.0-69-g2562ce1b+2,16.0-8-g14ebd58+4,16.0-8-g2df868b+1,16.0-8-g4cec79c+6,16.0-8-gadf6c7a+1,16.0-8-gfc7ad86,16.0-82-g59ec2a54a+1,16.0-9-g5400cdc+2,16.0-9-ge6233d7+5,master-g2880f2d8cf+3,v17.0.rc1
LSSTDataManagementBasePackage
psfMatch.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2016 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 
23 __all__ = ["DetectionConfig", "PsfMatchConfig", "PsfMatchConfigAL", "PsfMatchConfigDF", "PsfMatchTask"]
24 
25 import time
26 
27 import numpy as np
28 
29 import lsst.afw.image as afwImage
30 import lsst.pex.config as pexConfig
31 import lsst.afw.math as afwMath
32 import lsst.afw.display.ds9 as ds9
33 import lsst.log as log
34 import lsst.pipe.base as pipeBase
35 from lsst.meas.algorithms import SubtractBackgroundConfig
36 from . import utils as diutils
37 from . import diffimLib
38 
39 
40 class DetectionConfig(pexConfig.Config):
41  """Configuration for detecting sources on images for building a
42  PSF-matching kernel
43 
44  Configuration for turning detected lsst.afw.detection.FootPrints into an
45  acceptable (unmasked, high signal-to-noise, not too large or not too small)
46  list of `lsst.ip.diffim.KernelSources` that are used to build the
47  Psf-matching kernel"""
48 
49  detThreshold = pexConfig.Field(
50  dtype=float,
51  doc="Value of footprint detection threshold",
52  default=10.0,
53  check=lambda x: x >= 3.0
54  )
55  detThresholdType = pexConfig.ChoiceField(
56  dtype=str,
57  doc="Type of detection threshold",
58  default="pixel_stdev",
59  allowed={
60  "value": "Use counts as the detection threshold type",
61  "stdev": "Use standard deviation of image plane",
62  "variance": "Use variance of image plane",
63  "pixel_stdev": "Use stdev derived from variance plane"
64  }
65  )
66  detOnTemplate = pexConfig.Field(
67  dtype=bool,
68  doc="""If true run detection on the template (image to convolve);
69  if false run detection on the science image""",
70  default=True
71  )
72  badMaskPlanes = pexConfig.ListField(
73  dtype=str,
74  doc="""Mask planes that lead to an invalid detection.
75  Options: NO_DATA EDGE SAT BAD CR INTRP""",
76  default=("NO_DATA", "EDGE", "SAT")
77  )
78  fpNpixMin = pexConfig.Field(
79  dtype=int,
80  doc="Minimum number of pixels in an acceptable Footprint",
81  default=5,
82  check=lambda x: x >= 5
83  )
84  fpNpixMax = pexConfig.Field(
85  dtype=int,
86  doc="""Maximum number of pixels in an acceptable Footprint;
87  too big and the subsequent convolutions become unwieldy""",
88  default=500,
89  check=lambda x: x <= 500
90  )
91  fpGrowKernelScaling = pexConfig.Field(
92  dtype=float,
93  doc="""If config.scaleByFwhm, grow the footprint based on
94  the final kernelSize. Each footprint will be
95  2*fpGrowKernelScaling*kernelSize x
96  2*fpGrowKernelScaling*kernelSize. With the value
97  of 1.0, the remaining pixels in each KernelCandiate
98  after convolution by the basis functions will be
99  equal to the kernel size itself.""",
100  default=1.0,
101  check=lambda x: x >= 1.0
102  )
103  fpGrowPix = pexConfig.Field(
104  dtype=int,
105  doc="""Growing radius (in pixels) for each raw detection
106  footprint. The smaller the faster; however the
107  kernel sum does not converge if the stamp is too
108  small; and the kernel is not constrained at all if
109  the stamp is the size of the kernel. The grown stamp
110  is 2 * fpGrowPix pixels larger in each dimension.
111  This is overridden by fpGrowKernelScaling if scaleByFwhm""",
112  default=30,
113  check=lambda x: x >= 10
114  )
115  scaleByFwhm = pexConfig.Field(
116  dtype=bool,
117  doc="Scale fpGrowPix by input Fwhm?",
118  default=True,
119  )
120 
121 
122 class PsfMatchConfig(pexConfig.Config):
123  """Base configuration for Psf-matching
124 
125  The base configuration of the Psf-matching kernel, and of the warping, detection,
126  and background modeling subTasks."""
127 
128  warpingConfig = pexConfig.ConfigField("Config for warping exposures to a common alignment",
129  afwMath.warper.WarperConfig)
130  detectionConfig = pexConfig.ConfigField("Controlling the detection of sources for kernel building",
131  DetectionConfig)
132  afwBackgroundConfig = pexConfig.ConfigField("Controlling the Afw background fitting",
133  SubtractBackgroundConfig)
134 
135  useAfwBackground = pexConfig.Field(
136  dtype=bool,
137  doc="Use afw background subtraction instead of ip_diffim",
138  default=False,
139  )
140  fitForBackground = pexConfig.Field(
141  dtype=bool,
142  doc="Include terms (including kernel cross terms) for background in ip_diffim",
143  default=False,
144  )
145  kernelBasisSet = pexConfig.ChoiceField(
146  dtype=str,
147  doc="Type of basis set for PSF matching kernel.",
148  default="alard-lupton",
149  allowed={
150  "alard-lupton": """Alard-Lupton sum-of-gaussians basis set,
151  * The first term has no spatial variation
152  * The kernel sum is conserved
153  * You may want to turn off 'usePcaForSpatialKernel'""",
154  "delta-function": """Delta-function kernel basis set,
155  * You may enable the option useRegularization
156  * You should seriously consider usePcaForSpatialKernel, which will also
157  enable kernel sum conservation for the delta function kernels"""
158  }
159  )
160  kernelSize = pexConfig.Field(
161  dtype=int,
162  doc="""Number of rows/columns in the convolution kernel; should be odd-valued.
163  Modified by kernelSizeFwhmScaling if scaleByFwhm = true""",
164  default=21,
165  )
166  scaleByFwhm = pexConfig.Field(
167  dtype=bool,
168  doc="Scale kernelSize, alardGaussians by input Fwhm",
169  default=True,
170  )
171  kernelSizeFwhmScaling = pexConfig.Field(
172  dtype=float,
173  doc="""How much to scale the kernel size based on the largest AL Sigma""",
174  default=6.0,
175  check=lambda x: x >= 1.0
176  )
177  kernelSizeMin = pexConfig.Field(
178  dtype=int,
179  doc="""Minimum Kernel Size""",
180  default=21,
181  )
182  kernelSizeMax = pexConfig.Field(
183  dtype=int,
184  doc="""Maximum Kernel Size""",
185  default=35,
186  )
187  spatialModelType = pexConfig.ChoiceField(
188  dtype=str,
189  doc="Type of spatial functions for kernel and background",
190  default="chebyshev1",
191  allowed={
192  "chebyshev1": "Chebyshev polynomial of the first kind",
193  "polynomial": "Standard x,y polynomial",
194  }
195  )
196  spatialKernelOrder = pexConfig.Field(
197  dtype=int,
198  doc="Spatial order of convolution kernel variation",
199  default=2,
200  check=lambda x: x >= 0
201  )
202  spatialBgOrder = pexConfig.Field(
203  dtype=int,
204  doc="Spatial order of differential background variation",
205  default=1,
206  check=lambda x: x >= 0
207  )
208  sizeCellX = pexConfig.Field(
209  dtype=int,
210  doc="Size (rows) in pixels of each SpatialCell for spatial modeling",
211  default=128,
212  check=lambda x: x >= 32
213  )
214  sizeCellY = pexConfig.Field(
215  dtype=int,
216  doc="Size (columns) in pixels of each SpatialCell for spatial modeling",
217  default=128,
218  check=lambda x: x >= 32
219  )
220  nStarPerCell = pexConfig.Field(
221  dtype=int,
222  doc="Number of KernelCandidates in each SpatialCell to use in the spatial fitting",
223  default=3,
224  check=lambda x: x >= 1
225  )
226  maxSpatialIterations = pexConfig.Field(
227  dtype=int,
228  doc="Maximum number of iterations for rejecting bad KernelCandidates in spatial fitting",
229  default=3,
230  check=lambda x: x >= 1 and x <= 5
231  )
232  usePcaForSpatialKernel = pexConfig.Field(
233  dtype=bool,
234  doc="""Use Pca to reduce the dimensionality of the kernel basis sets.
235  This is particularly useful for delta-function kernels.
236  Functionally, after all Cells have their raw kernels determined, we run
237  a Pca on these Kernels, re-fit the Cells using the eigenKernels and then
238  fit those for spatial variation using the same technique as for Alard-Lupton kernels.
239  If this option is used, the first term will have no spatial variation and the
240  kernel sum will be conserved.""",
241  default=False,
242  )
243  subtractMeanForPca = pexConfig.Field(
244  dtype=bool,
245  doc="Subtract off the mean feature before doing the Pca",
246  default=True,
247  )
248  numPrincipalComponents = pexConfig.Field(
249  dtype=int,
250  doc="""Number of principal components to use for Pca basis, including the
251  mean kernel if requested.""",
252  default=5,
253  check=lambda x: x >= 3
254  )
255  singleKernelClipping = pexConfig.Field(
256  dtype=bool,
257  doc="Do sigma clipping on each raw kernel candidate",
258  default=True,
259  )
260  kernelSumClipping = pexConfig.Field(
261  dtype=bool,
262  doc="Do sigma clipping on the ensemble of kernel sums",
263  default=True,
264  )
265  spatialKernelClipping = pexConfig.Field(
266  dtype=bool,
267  doc="Do sigma clipping after building the spatial model",
268  default=True,
269  )
270  checkConditionNumber = pexConfig.Field(
271  dtype=bool,
272  doc="""Test for maximum condition number when inverting a kernel matrix.
273  Anything above maxConditionNumber is not used and the candidate is set as BAD.
274  Also used to truncate inverse matrix in estimateBiasedRisk. However,
275  if you are doing any deconvolution you will want to turn this off, or use
276  a large maxConditionNumber""",
277  default=False,
278  )
279  badMaskPlanes = pexConfig.ListField(
280  dtype=str,
281  doc="""Mask planes to ignore when calculating diffim statistics
282  Options: NO_DATA EDGE SAT BAD CR INTRP""",
283  default=("NO_DATA", "EDGE", "SAT")
284  )
285  candidateResidualMeanMax = pexConfig.Field(
286  dtype=float,
287  doc="""Rejects KernelCandidates yielding bad difference image quality.
288  Used by BuildSingleKernelVisitor, AssessSpatialKernelVisitor.
289  Represents average over pixels of (image/sqrt(variance)).""",
290  default=0.25,
291  check=lambda x: x >= 0.0
292  )
293  candidateResidualStdMax = pexConfig.Field(
294  dtype=float,
295  doc="""Rejects KernelCandidates yielding bad difference image quality.
296  Used by BuildSingleKernelVisitor, AssessSpatialKernelVisitor.
297  Represents stddev over pixels of (image/sqrt(variance)).""",
298  default=1.50,
299  check=lambda x: x >= 0.0
300  )
301  useCoreStats = pexConfig.Field(
302  dtype=bool,
303  doc="""Use the core of the footprint for the quality statistics, instead of the entire footprint.
304  WARNING: if there is deconvolution we probably will need to turn this off""",
305  default=False,
306  )
307  candidateCoreRadius = pexConfig.Field(
308  dtype=int,
309  doc="""Radius for calculation of stats in 'core' of KernelCandidate diffim.
310  Total number of pixels used will be (2*radius)**2.
311  This is used both for 'core' diffim quality as well as ranking of
312  KernelCandidates by their total flux in this core""",
313  default=3,
314  check=lambda x: x >= 1
315  )
316  maxKsumSigma = pexConfig.Field(
317  dtype=float,
318  doc="""Maximum allowed sigma for outliers from kernel sum distribution.
319  Used to reject variable objects from the kernel model""",
320  default=3.0,
321  check=lambda x: x >= 0.0
322  )
323  maxConditionNumber = pexConfig.Field(
324  dtype=float,
325  doc="Maximum condition number for a well conditioned matrix",
326  default=5.0e7,
327  check=lambda x: x >= 0.0
328  )
329  conditionNumberType = pexConfig.ChoiceField(
330  dtype=str,
331  doc="Use singular values (SVD) or eigen values (EIGENVALUE) to determine condition number",
332  default="EIGENVALUE",
333  allowed={
334  "SVD": "Use singular values",
335  "EIGENVALUE": "Use eigen values (faster)",
336  }
337  )
338  maxSpatialConditionNumber = pexConfig.Field(
339  dtype=float,
340  doc="Maximum condition number for a well conditioned spatial matrix",
341  default=1.0e10,
342  check=lambda x: x >= 0.0
343  )
344  iterateSingleKernel = pexConfig.Field(
345  dtype=bool,
346  doc="""Remake KernelCandidate using better variance estimate after first pass?
347  Primarily useful when convolving a single-depth image, otherwise not necessary.""",
348  default=False,
349  )
350  constantVarianceWeighting = pexConfig.Field(
351  dtype=bool,
352  doc="""Use constant variance weighting in single kernel fitting?
353  In some cases this is better for bright star residuals.""",
354  default=True,
355  )
356  calculateKernelUncertainty = pexConfig.Field(
357  dtype=bool,
358  doc="""Calculate kernel and background uncertainties for each kernel candidate?
359  This comes from the inverse of the covariance matrix.
360  Warning: regularization can cause problems for this step.""",
361  default=False,
362  )
363  useBicForKernelBasis = pexConfig.Field(
364  dtype=bool,
365  doc="""Use Bayesian Information Criterion to select the number of bases going into the kernel""",
366  default=False,
367  )
368 
369 
371  """The parameters specific to the "Alard-Lupton" (sum-of-Gaussian) Psf-matching basis"""
372 
373  def setDefaults(self):
374  PsfMatchConfig.setDefaults(self)
375  self.kernelBasisSet = "alard-lupton"
376  self.maxConditionNumber = 5.0e7
377 
378  alardNGauss = pexConfig.Field(
379  dtype=int,
380  doc="Number of Gaussians in alard-lupton basis",
381  default=3,
382  check=lambda x: x >= 1
383  )
384  alardDegGauss = pexConfig.ListField(
385  dtype=int,
386  doc="Polynomial order of spatial modification of Gaussians. Must in number equal alardNGauss",
387  default=(4, 2, 2),
388  )
389  alardSigGauss = pexConfig.ListField(
390  dtype=float,
391  doc="""Sigma in pixels of Gaussians (FWHM = 2.35 sigma). Must in number equal alardNGauss""",
392  default=(0.7, 1.5, 3.0),
393  )
394  alardGaussBeta = pexConfig.Field(
395  dtype=float,
396  doc="""Default scale factor between Gaussian sigmas """,
397  default=2.0,
398  check=lambda x: x >= 0.0,
399  )
400  alardMinSig = pexConfig.Field(
401  dtype=float,
402  doc="""Minimum Sigma (pixels) for Gaussians""",
403  default=0.7,
404  check=lambda x: x >= 0.25
405  )
406  alardDegGaussDeconv = pexConfig.Field(
407  dtype=int,
408  doc="""Degree of spatial modification of ALL gaussians in AL basis during deconvolution""",
409  default=3,
410  check=lambda x: x >= 1
411  )
412  alardMinSigDeconv = pexConfig.Field(
413  dtype=float,
414  doc="""Minimum Sigma (pixels) for Gaussians during deconvolution;
415  make smaller than alardMinSig as this is only indirectly used""",
416  default=0.4,
417  check=lambda x: x >= 0.25
418  )
419  alardNGaussDeconv = pexConfig.Field(
420  dtype=int,
421  doc="Number of Gaussians in AL basis during deconvolution",
422  default=3,
423  check=lambda x: x >= 1
424  )
425 
426 
428  """The parameters specific to the delta-function (one basis per-pixel) Psf-matching basis"""
429 
430  def setDefaults(self):
431  PsfMatchConfig.setDefaults(self)
432  self.kernelBasisSet = "delta-function"
433  self.maxConditionNumber = 5.0e6
435  self.subtractMeanForPca = True
436  self.useBicForKernelBasis = False
437 
438  useRegularization = pexConfig.Field(
439  dtype=bool,
440  doc="Use regularization to smooth the delta function kernels",
441  default=True,
442  )
443  regularizationType = pexConfig.ChoiceField(
444  dtype=str,
445  doc="Type of regularization.",
446  default="centralDifference",
447  allowed={
448  "centralDifference": "Penalize second derivative using 2-D stencil of central finite difference",
449  "forwardDifference": "Penalize first, second, third derivatives using forward finite differeces"
450  }
451  )
452  centralRegularizationStencil = pexConfig.ChoiceField(
453  dtype=int,
454  doc="Type of stencil to approximate central derivative (for centralDifference only)",
455  default=9,
456  allowed={
457  5: "5-point stencil including only adjacent-in-x,y elements",
458  9: "9-point stencil including diagonal elements"
459  }
460  )
461  forwardRegularizationOrders = pexConfig.ListField(
462  dtype=int,
463  doc="Array showing which order derivatives to penalize (for forwardDifference only)",
464  default=(1, 2),
465  itemCheck=lambda x: (x > 0) and (x < 4)
466  )
467  regularizationBorderPenalty = pexConfig.Field(
468  dtype=float,
469  doc="Value of the penalty for kernel border pixels",
470  default=3.0,
471  check=lambda x: x >= 0.0
472  )
473  lambdaType = pexConfig.ChoiceField(
474  dtype=str,
475  doc="How to choose the value of the regularization strength",
476  default="absolute",
477  allowed={
478  "absolute": "Use lambdaValue as the value of regularization strength",
479  "relative": "Use lambdaValue as fraction of the default regularization strength (N.R. 18.5.8)",
480  "minimizeBiasedRisk": "Minimize biased risk estimate",
481  "minimizeUnbiasedRisk": "Minimize unbiased risk estimate",
482  }
483  )
484  lambdaValue = pexConfig.Field(
485  dtype=float,
486  doc="Value used for absolute determinations of regularization strength",
487  default=0.2,
488  )
489  lambdaScaling = pexConfig.Field(
490  dtype=float,
491  doc="Fraction of the default lambda strength (N.R. 18.5.8) to use. 1e-4 or 1e-5",
492  default=1e-4,
493  )
494  lambdaStepType = pexConfig.ChoiceField(
495  dtype=str,
496  doc="""If a scan through lambda is needed (minimizeBiasedRisk, minimizeUnbiasedRisk),
497  use log or linear steps""",
498  default="log",
499  allowed={
500  "log": "Step in log intervals; e.g. lambdaMin, lambdaMax, lambdaStep = -1.0, 2.0, 0.1",
501  "linear": "Step in linear intervals; e.g. lambdaMin, lambdaMax, lambdaStep = 0.1, 100, 0.1",
502  }
503  )
504  lambdaMin = pexConfig.Field(
505  dtype=float,
506  doc="""If scan through lambda needed (minimizeBiasedRisk, minimizeUnbiasedRisk),
507  start at this value. If lambdaStepType = log:linear, suggest -1:0.1""",
508  default=-1.0,
509  )
510  lambdaMax = pexConfig.Field(
511  dtype=float,
512  doc="""If scan through lambda needed (minimizeBiasedRisk, minimizeUnbiasedRisk),
513  stop at this value. If lambdaStepType = log:linear, suggest 2:100""",
514  default=2.0,
515  )
516  lambdaStep = pexConfig.Field(
517  dtype=float,
518  doc="""If scan through lambda needed (minimizeBiasedRisk, minimizeUnbiasedRisk),
519  step in these increments. If lambdaStepType = log:linear, suggest 0.1:0.1""",
520  default=0.1,
521  )
522 
523 
524 class PsfMatchTask(pipeBase.Task):
525  """Base class for Psf Matching; should not be called directly
526 
527  Notes
528  -----
529  PsfMatchTask is a base class that implements the core functionality for matching the
530  Psfs of two images using a spatially varying Psf-matching lsst.afw.math.LinearCombinationKernel.
531  The Task requires the user to provide an instance of an lsst.afw.math.SpatialCellSet,
532  filled with lsst.ip.diffim.KernelCandidate instances, and a list of lsst.afw.math.Kernels
533  of basis shapes that will be used for the decomposition. If requested, the Task
534  also performs background matching and returns the differential background model as an
535  lsst.afw.math.Kernel.SpatialFunction.
536 
537  Invoking the Task
538 
539  As a base class, this Task is not directly invoked. However, run() methods that are
540  implemented on derived classes will make use of the core _solve() functionality,
541  which defines a sequence of lsst.afw.math.CandidateVisitor classes that iterate
542  through the KernelCandidates, first building up a per-candidate solution and then
543  building up a spatial model from the ensemble of candidates. Sigma clipping is
544  performed using the mean and standard deviation of all kernel sums (to reject
545  variable objects), on the per-candidate substamp diffim residuals
546  (to indicate a bad choice of kernel basis shapes for that particular object),
547  and on the substamp diffim residuals using the spatial kernel fit (to indicate a bad
548  choice of spatial kernel order, or poor constraints on the spatial model). The
549  _diagnostic() method logs information on the quality of the spatial fit, and also
550  modifies the Task metadata.
551 
552  .. list-table:: Quantities set in Metadata
553  :header-rows: 1
554 
555  * - Parameter
556  - Description
557  * - `spatialConditionNum`
558  - Condition number of the spatial kernel fit
559  * - `spatialKernelSum`
560  - Kernel sum (10^{-0.4 * ``Delta``; zeropoint}) of the spatial Psf-matching kernel
561  * - `ALBasisNGauss`
562  - If using sum-of-Gaussian basis, the number of gaussians used
563  * - `ALBasisDegGauss`
564  - If using sum-of-Gaussian basis, the deg of spatial variation of the Gaussians
565  * - `ALBasisSigGauss`
566  - If using sum-of-Gaussian basis, the widths (sigma) of the Gaussians
567  * - `ALKernelSize`
568  - If using sum-of-Gaussian basis, the kernel size
569  * - `NFalsePositivesTotal`
570  - Total number of diaSources
571  * - `NFalsePositivesRefAssociated`
572  - Number of diaSources that associate with the reference catalog
573  * - `NFalsePositivesRefAssociated`
574  - Number of diaSources that associate with the source catalog
575  * - `NFalsePositivesUnassociated`
576  - Number of diaSources that are orphans
577  * - `metric_MEAN`
578  - Mean value of substamp diffim quality metrics across all KernelCandidates,
579  for both the per-candidate (LOCAL) and SPATIAL residuals
580  * - `metric_MEDIAN`
581  - Median value of substamp diffim quality metrics across all KernelCandidates,
582  for both the per-candidate (LOCAL) and SPATIAL residuals
583  * - `metric_STDEV`
584  - Standard deviation of substamp diffim quality metrics across all KernelCandidates,
585  for both the per-candidate (LOCAL) and SPATIAL residuals
586 
587  Debug variables
588 
589  The lsst.pipe.base.cmdLineTask.CmdLineTask command line task interface supports a
590  flag -d/--debug to import @b debug.py from your PYTHONPATH. The relevant contents of debug.py
591  for this Task include:
592 
593  .. code-block:: py
594 
595  import sys
596  import lsstDebug
597  def DebugInfo(name):
598  di = lsstDebug.getInfo(name)
599  if name == "lsst.ip.diffim.psfMatch":
600  # enable debug output
601  di.display = True
602  # ds9 mask transparency
603  di.maskTransparency = 80
604  # show all the candidates and residuals
605  di.displayCandidates = True
606  # show kernel basis functions
607  di.displayKernelBasis = False
608  # show kernel realized across the image
609  di.displayKernelMosaic = True
610  # show coefficients of spatial model
611  di.plotKernelSpatialModel = False
612  # show the bad candidates (red) along with good (green)
613  di.showBadCandidates = True
614  return di
615  lsstDebug.Info = DebugInfo
616  lsstDebug.frame = 1
617 
618  Note that if you want addional logging info, you may add to your scripts:
619 
620  .. code-block:: py
621 
622  import lsst.log.utils as logUtils
623  logUtils.traceSetAt("ip.diffim", 4)
624  """
625  ConfigClass = PsfMatchConfig
626  _DefaultName = "psfMatch"
627 
628  def __init__(self, *args, **kwargs):
629  """Create the psf-matching Task
630 
631  Parameters
632  ----------
633  *args
634  Arguments to be passed to ``lsst.pipe.base.task.Task.__init__``
635  **kwargs
636  Keyword arguments to be passed to ``lsst.pipe.base.task.Task.__init__``
637 
638  Notes
639  -----
640  The initialization sets the Psf-matching kernel configuration using the value of
641  self.config.kernel.active. If the kernel is requested with regularization to moderate
642  the bias/variance tradeoff, currently only used when a delta function kernel basis
643  is provided, it creates a regularization matrix stored as member variable
644  self.hMat.
645  """
646  pipeBase.Task.__init__(self, *args, **kwargs)
647  self.kConfig = self.config.kernel.active
648 
649  if 'useRegularization' in self.kConfig:
650  self.useRegularization = self.kConfig.useRegularization
651  else:
652  self.useRegularization = False
653 
654  if self.useRegularization:
655  self.hMat = diffimLib.makeRegularizationMatrix(pexConfig.makePolicy(self.kConfig))
656 
657  def _diagnostic(self, kernelCellSet, spatialSolution, spatialKernel, spatialBg):
658  """Provide logging diagnostics on quality of spatial kernel fit
659 
660  Parameters
661  ----------
662  kernelCellSet : TYPE
663  Cellset that contains the KernelCandidates used in the fitting
664  spatialSolution : TYPE
665  KernelSolution of best-fit
666  spatialKernel : TYPE
667  Best-fit spatial Kernel model
668  spatialBg : TYPE
669  Best-fit spatial background model
670  """
671  # What is the final kernel sum
672  kImage = afwImage.ImageD(spatialKernel.getDimensions())
673  kSum = spatialKernel.computeImage(kImage, False)
674  self.log.info("Final spatial kernel sum %.3f" % (kSum))
675 
676  # Look at how well conditioned the matrix is
677  conditionNum = spatialSolution.getConditionNumber(
678  getattr(diffimLib.KernelSolution, self.kConfig.conditionNumberType))
679  self.log.info("Spatial model condition number %.3e" % (conditionNum))
680 
681  if conditionNum < 0.0:
682  self.log.warn("Condition number is negative (%.3e)" % (conditionNum))
683  if conditionNum > self.kConfig.maxSpatialConditionNumber:
684  self.log.warn("Spatial solution exceeds max condition number (%.3e > %.3e)" % (
685  conditionNum, self.kConfig.maxSpatialConditionNumber))
686 
687  self.metadata.set("spatialConditionNum", conditionNum)
688  self.metadata.set("spatialKernelSum", kSum)
689 
690  # Look at how well the solution is constrained
691  nBasisKernels = spatialKernel.getNBasisKernels()
692  nKernelTerms = spatialKernel.getNSpatialParameters()
693  if nKernelTerms == 0: # order 0
694  nKernelTerms = 1
695 
696  # Not fit for
697  nBgTerms = spatialBg.getNParameters()
698  if nBgTerms == 1:
699  if spatialBg.getParameters()[0] == 0.0:
700  nBgTerms = 0
701 
702  nGood = 0
703  nBad = 0
704  nTot = 0
705  for cell in kernelCellSet.getCellList():
706  for cand in cell.begin(False): # False = include bad candidates
707  nTot += 1
708  if cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
709  nGood += 1
710  if cand.getStatus() == afwMath.SpatialCellCandidate.BAD:
711  nBad += 1
712 
713  self.log.info("Doing stats of kernel candidates used in the spatial fit.")
714 
715  # Counting statistics
716  if nBad > 2*nGood:
717  self.log.warn("Many more candidates rejected than accepted; %d total, %d rejected, %d used" % (
718  nTot, nBad, nGood))
719  else:
720  self.log.info("%d candidates total, %d rejected, %d used" % (nTot, nBad, nGood))
721 
722  # Some judgements on the quality of the spatial models
723  if nGood < nKernelTerms:
724  self.log.warn("Spatial kernel model underconstrained; %d candidates, %d terms, %d bases" % (
725  nGood, nKernelTerms, nBasisKernels))
726  self.log.warn("Consider lowering the spatial order")
727  elif nGood <= 2*nKernelTerms:
728  self.log.warn("Spatial kernel model poorly constrained; %d candidates, %d terms, %d bases" % (
729  nGood, nKernelTerms, nBasisKernels))
730  self.log.warn("Consider lowering the spatial order")
731  else:
732  self.log.info("Spatial kernel model well constrained; %d candidates, %d terms, %d bases" % (
733  nGood, nKernelTerms, nBasisKernels))
734 
735  if nGood < nBgTerms:
736  self.log.warn("Spatial background model underconstrained; %d candidates, %d terms" % (
737  nGood, nBgTerms))
738  self.log.warn("Consider lowering the spatial order")
739  elif nGood <= 2*nBgTerms:
740  self.log.warn("Spatial background model poorly constrained; %d candidates, %d terms" % (
741  nGood, nBgTerms))
742  self.log.warn("Consider lowering the spatial order")
743  else:
744  self.log.info("Spatial background model appears well constrained; %d candidates, %d terms" % (
745  nGood, nBgTerms))
746 
747  def _displayDebug(self, kernelCellSet, spatialKernel, spatialBackground):
748  """Provide visualization of the inputs and ouputs to the Psf-matching code
749 
750  Parameters
751  ----------
752  kernelCellSet : TYPE
753  The SpatialCellSet used in determining the matching kernel and background
754  spatialKernel : TYPE
755  Spatially varying Psf-matching kernel
756  spatialBackground : TYPE
757  Spatially varying background-matching function
758  """
759  import lsstDebug
760  displayCandidates = lsstDebug.Info(__name__).displayCandidates
761  displayKernelBasis = lsstDebug.Info(__name__).displayKernelBasis
762  displayKernelMosaic = lsstDebug.Info(__name__).displayKernelMosaic
763  plotKernelSpatialModel = lsstDebug.Info(__name__).plotKernelSpatialModel
764  showBadCandidates = lsstDebug.Info(__name__).showBadCandidates
765  maskTransparency = lsstDebug.Info(__name__).maskTransparency
766  if not maskTransparency:
767  maskTransparency = 0
768  ds9.setMaskTransparency(maskTransparency)
769 
770  if displayCandidates:
771  diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground,
772  frame=lsstDebug.frame,
773  showBadCandidates=showBadCandidates)
774  lsstDebug.frame += 1
775  diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground,
776  frame=lsstDebug.frame,
777  showBadCandidates=showBadCandidates,
778  kernels=True)
779  lsstDebug.frame += 1
780  diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground,
781  frame=lsstDebug.frame,
782  showBadCandidates=showBadCandidates,
783  resids=True)
784  lsstDebug.frame += 1
785 
786  if displayKernelBasis:
787  diutils.showKernelBasis(spatialKernel, frame=lsstDebug.frame)
788  lsstDebug.frame += 1
789 
790  if displayKernelMosaic:
791  diutils.showKernelMosaic(kernelCellSet.getBBox(), spatialKernel, frame=lsstDebug.frame)
792  lsstDebug.frame += 1
793 
794  if plotKernelSpatialModel:
795  diutils.plotKernelSpatialModel(spatialKernel, kernelCellSet, showBadCandidates=showBadCandidates)
796 
797  def _createPcaBasis(self, kernelCellSet, nStarPerCell, policy):
798  """Create Principal Component basis
799 
800  If a principal component analysis is requested, typically when using a delta function basis,
801  perform the PCA here and return a new basis list containing the new principal components.
802 
803  Parameters
804  ----------
805  kernelCellSet : TYPE
806  a SpatialCellSet containing KernelCandidates, from which components are derived
807  nStarPerCell : TYPE
808  the number of stars per cell to visit when doing the PCA
809  policy : TYPE
810  input policy controlling the single kernel visitor
811 
812  Returns
813  -------
814  nRejectedPca : TYPE
815  number of KernelCandidates rejected during PCA loop
816  spatialBasisList : TYPE
817  basis list containing the principal shapes as Kernels
818 
819  Raises
820  ------
821  RuntimeError
822  If the Eigenvalues sum to zero.
823  """
824  nComponents = self.kConfig.numPrincipalComponents
825  imagePca = diffimLib.KernelPcaD()
826  importStarVisitor = diffimLib.KernelPcaVisitorF(imagePca)
827  kernelCellSet.visitCandidates(importStarVisitor, nStarPerCell)
828  if self.kConfig.subtractMeanForPca:
829  importStarVisitor.subtractMean()
830  imagePca.analyze()
831 
832  eigenValues = imagePca.getEigenValues()
833  pcaBasisList = importStarVisitor.getEigenKernels()
834 
835  eSum = np.sum(eigenValues)
836  if eSum == 0.0:
837  raise RuntimeError("Eigenvalues sum to zero")
838  for j in range(len(eigenValues)):
839  log.log("TRACE5." + self.log.getName() + "._solve", log.DEBUG,
840  "Eigenvalue %d : %f (%f)", j, eigenValues[j], eigenValues[j]/eSum)
841 
842  nToUse = min(nComponents, len(eigenValues))
843  trimBasisList = []
844  for j in range(nToUse):
845  # Check for NaNs?
846  kimage = afwImage.ImageD(pcaBasisList[j].getDimensions())
847  pcaBasisList[j].computeImage(kimage, False)
848  if not (True in np.isnan(kimage.getArray())):
849  trimBasisList.append(pcaBasisList[j])
850 
851  # Put all the power in the first kernel, which will not vary spatially
852  spatialBasisList = diffimLib.renormalizeKernelList(trimBasisList)
853 
854  # New Kernel visitor for this new basis list (no regularization explicitly)
855  singlekvPca = diffimLib.BuildSingleKernelVisitorF(spatialBasisList, policy)
856  singlekvPca.setSkipBuilt(False)
857  kernelCellSet.visitCandidates(singlekvPca, nStarPerCell)
858  singlekvPca.setSkipBuilt(True)
859  nRejectedPca = singlekvPca.getNRejected()
860 
861  return nRejectedPca, spatialBasisList
862 
863  def _buildCellSet(self, *args):
864  """Fill a SpatialCellSet with KernelCandidates for the Psf-matching process;
865  override in derived classes"""
866  return
867 
868  @pipeBase.timeMethod
869  def _solve(self, kernelCellSet, basisList, returnOnExcept=False):
870  """Solve for the PSF matching kernel
871 
872  Parameters
873  ----------
874  kernelCellSet : TYPE
875  a SpatialCellSet to use in determining the matching kernel
876  (typically as provided by _buildCellSet)
877  basisList : TYPE
878  list of Kernels to be used in the decomposition of the spatially varying kernel
879  (typically as provided by makeKernelBasisList)
880  returnOnExcept : `bool`, optional
881  if True then return (None, None) if an error occurs, else raise the exception
882 
883  Returns
884  -------
885  psfMatchingKernel : TYPE
886  PSF matching kernel
887  backgroundModel : TYPE
888  differential background model
889 
890  Raises
891  ------
892  Exception
893  if unable to determine PSF matching kernel and returnOnExcept False
894  """
895 
896  import lsstDebug
897  display = lsstDebug.Info(__name__).display
898 
899  maxSpatialIterations = self.kConfig.maxSpatialIterations
900  nStarPerCell = self.kConfig.nStarPerCell
901  usePcaForSpatialKernel = self.kConfig.usePcaForSpatialKernel
902 
903  # Visitor for the single kernel fit
904  policy = pexConfig.makePolicy(self.kConfig)
905  if self.useRegularization:
906  singlekv = diffimLib.BuildSingleKernelVisitorF(basisList, policy, self.hMat)
907  else:
908  singlekv = diffimLib.BuildSingleKernelVisitorF(basisList, policy)
909 
910  # Visitor for the kernel sum rejection
911  ksv = diffimLib.KernelSumVisitorF(policy)
912 
913  # Main loop
914  t0 = time.time()
915  try:
916  totalIterations = 0
917  thisIteration = 0
918  while (thisIteration < maxSpatialIterations):
919 
920  # Make sure there are no uninitialized candidates as active occupants of Cell
921  nRejectedSkf = -1
922  while (nRejectedSkf != 0):
923  log.log("TRACE1." + self.log.getName() + "._solve", log.DEBUG,
924  "Building single kernels...")
925  kernelCellSet.visitCandidates(singlekv, nStarPerCell)
926  nRejectedSkf = singlekv.getNRejected()
927  log.log("TRACE1." + self.log.getName() + "._solve", log.DEBUG,
928  "Iteration %d, rejected %d candidates due to initial kernel fit",
929  thisIteration, nRejectedSkf)
930 
931  # Reject outliers in kernel sum
932  ksv.resetKernelSum()
933  ksv.setMode(diffimLib.KernelSumVisitorF.AGGREGATE)
934  kernelCellSet.visitCandidates(ksv, nStarPerCell)
935  ksv.processKsumDistribution()
936  ksv.setMode(diffimLib.KernelSumVisitorF.REJECT)
937  kernelCellSet.visitCandidates(ksv, nStarPerCell)
938 
939  nRejectedKsum = ksv.getNRejected()
940  log.log("TRACE1." + self.log.getName() + "._solve", log.DEBUG,
941  "Iteration %d, rejected %d candidates due to kernel sum",
942  thisIteration, nRejectedKsum)
943 
944  # Do we jump back to the top without incrementing thisIteration?
945  if nRejectedKsum > 0:
946  totalIterations += 1
947  continue
948 
949  # At this stage we can either apply the spatial fit to
950  # the kernels, or we run a PCA, use these as a *new*
951  # basis set with lower dimensionality, and then apply
952  # the spatial fit to these kernels
953 
954  if (usePcaForSpatialKernel):
955  log.log("TRACE0." + self.log.getName() + "._solve", log.DEBUG,
956  "Building Pca basis")
957 
958  nRejectedPca, spatialBasisList = self._createPcaBasis(kernelCellSet, nStarPerCell, policy)
959  log.log("TRACE1." + self.log.getName() + "._solve", log.DEBUG,
960  "Iteration %d, rejected %d candidates due to Pca kernel fit",
961  thisIteration, nRejectedPca)
962 
963  # We don't want to continue on (yet) with the
964  # spatial modeling, because we have bad objects
965  # contributing to the Pca basis. We basically
966  # need to restart from the beginning of this loop,
967  # since the cell-mates of those objects that were
968  # rejected need their original Kernels built by
969  # singleKernelFitter.
970 
971  # Don't count against thisIteration
972  if (nRejectedPca > 0):
973  totalIterations += 1
974  continue
975  else:
976  spatialBasisList = basisList
977 
978  # We have gotten on to the spatial modeling part
979  regionBBox = kernelCellSet.getBBox()
980  spatialkv = diffimLib.BuildSpatialKernelVisitorF(spatialBasisList, regionBBox, policy)
981  kernelCellSet.visitCandidates(spatialkv, nStarPerCell)
982  spatialkv.solveLinearEquation()
983  log.log("TRACE2." + self.log.getName() + "._solve", log.DEBUG,
984  "Spatial kernel built with %d candidates", spatialkv.getNCandidates())
985  spatialKernel, spatialBackground = spatialkv.getSolutionPair()
986 
987  # Check the quality of the spatial fit (look at residuals)
988  assesskv = diffimLib.AssessSpatialKernelVisitorF(spatialKernel, spatialBackground, policy)
989  kernelCellSet.visitCandidates(assesskv, nStarPerCell)
990  nRejectedSpatial = assesskv.getNRejected()
991  nGoodSpatial = assesskv.getNGood()
992  log.log("TRACE1." + self.log.getName() + "._solve", log.DEBUG,
993  "Iteration %d, rejected %d candidates due to spatial kernel fit",
994  thisIteration, nRejectedSpatial)
995  log.log("TRACE1." + self.log.getName() + "._solve", log.DEBUG,
996  "%d candidates used in fit", nGoodSpatial)
997 
998  # If only nGoodSpatial == 0, might be other candidates in the cells
999  if nGoodSpatial == 0 and nRejectedSpatial == 0:
1000  raise RuntimeError("No kernel candidates for spatial fit")
1001 
1002  if nRejectedSpatial == 0:
1003  # Nothing rejected, finished with spatial fit
1004  break
1005 
1006  # Otherwise, iterate on...
1007  thisIteration += 1
1008 
1009  # Final fit if above did not converge
1010  if (nRejectedSpatial > 0) and (thisIteration == maxSpatialIterations):
1011  log.log("TRACE1." + self.log.getName() + "._solve", log.DEBUG, "Final spatial fit")
1012  if (usePcaForSpatialKernel):
1013  nRejectedPca, spatialBasisList = self._createPcaBasis(kernelCellSet, nStarPerCell, policy)
1014  regionBBox = kernelCellSet.getBBox()
1015  spatialkv = diffimLib.BuildSpatialKernelVisitorF(spatialBasisList, regionBBox, policy)
1016  kernelCellSet.visitCandidates(spatialkv, nStarPerCell)
1017  spatialkv.solveLinearEquation()
1018  log.log("TRACE2." + self.log.getName() + "._solve", log.DEBUG,
1019  "Spatial kernel built with %d candidates", spatialkv.getNCandidates())
1020  spatialKernel, spatialBackground = spatialkv.getSolutionPair()
1021 
1022  spatialSolution = spatialkv.getKernelSolution()
1023 
1024  except Exception as e:
1025  self.log.error("ERROR: Unable to calculate psf matching kernel")
1026 
1027  log.log("TRACE1." + self.log.getName() + "._solve", log.DEBUG, str(e))
1028  raise e
1029 
1030  t1 = time.time()
1031  log.log("TRACE0." + self.log.getName() + "._solve", log.DEBUG,
1032  "Total time to compute the spatial kernel : %.2f s", (t1 - t0))
1033 
1034  if display:
1035  self._displayDebug(kernelCellSet, spatialKernel, spatialBackground)
1036 
1037  self._diagnostic(kernelCellSet, spatialSolution, spatialKernel, spatialBackground)
1038 
1039  return spatialSolution, spatialKernel, spatialBackground
1040 
1041 
1042 PsfMatch = PsfMatchTask
def _diagnostic(self, kernelCellSet, spatialSolution, spatialKernel, spatialBg)
Definition: psfMatch.py:657
def __init__(self, args, kwargs)
Definition: psfMatch.py:628
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
daf::base::PropertySet * set
Definition: fits.cc:832
int min
Definition: Log.h:691
def _createPcaBasis(self, kernelCellSet, nStarPerCell, policy)
Definition: psfMatch.py:797
def _displayDebug(self, kernelCellSet, spatialKernel, spatialBackground)
Definition: psfMatch.py:747