677 def _diagnostic(self, kernelCellSet, spatialSolution, spatialKernel, spatialBg):
678 """Provide logging diagnostics on quality of spatial kernel fit
682 kernelCellSet : `lsst.afw.math.SpatialCellSet`
683 Cellset that contains the KernelCandidates used in the fitting
684 spatialSolution : `lsst.ip.diffim.SpatialKernelSolution`
685 KernelSolution of best-fit
686 spatialKernel : `lsst.afw.math.LinearCombinationKernel`
687 Best-fit spatial Kernel model
688 spatialBg : `lsst.afw.math.Function2D`
689 Best-fit spatial background model
691 # What is the final kernel sum
692 kImage = afwImage.ImageD(spatialKernel.getDimensions())
693 kSum = spatialKernel.computeImage(kImage, False)
694 self.log.info("Final spatial kernel sum %.3f", kSum)
696 # Look at how well conditioned the matrix is
697 conditionNum = spatialSolution.getConditionNumber(
698 getattr(diffimLib.KernelSolution, self.kConfig.conditionNumberType))
699 self.log.info("Spatial model condition number %.3e", conditionNum)
701 if conditionNum < 0.0:
702 self.log.warning("Condition number is negative (%.3e)", conditionNum)
703 if conditionNum > self.kConfig.maxSpatialConditionNumber:
704 self.log.warning("Spatial solution exceeds max condition number (%.3e > %.3e)",
705 conditionNum, self.kConfig.maxSpatialConditionNumber)
707 self.metadata["spatialConditionNum"] = conditionNum
708 self.metadata["spatialKernelSum"] = kSum
710 # Look at how well the solution is constrained
711 nBasisKernels = spatialKernel.getNBasisKernels()
712 nKernelTerms = spatialKernel.getNSpatialParameters()
713 if nKernelTerms == 0: # order 0
717 nBgTerms = spatialBg.getNParameters()
719 if spatialBg.getParameters()[0] == 0.0:
725 for cell in kernelCellSet.getCellList():
726 for cand in cell.begin(False): # False = include bad candidates
728 if cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
730 if cand.getStatus() == afwMath.SpatialCellCandidate.BAD:
733 self.log.info("Doing stats of kernel candidates used in the spatial fit.")
735 # Counting statistics
737 self.log.warning("Many more candidates rejected than accepted; %d total, %d rejected, %d used",
740 self.log.info("%d candidates total, %d rejected, %d used", nTot, nBad, nGood)
742 # Some judgements on the quality of the spatial models
743 if nGood < nKernelTerms:
744 self.log.warning("Spatial kernel model underconstrained; %d candidates, %d terms, %d bases",
745 nGood, nKernelTerms, nBasisKernels)
746 self.log.warning("Consider lowering the spatial order")
747 elif nGood <= 2*nKernelTerms:
748 self.log.warning("Spatial kernel model poorly constrained; %d candidates, %d terms, %d bases",
749 nGood, nKernelTerms, nBasisKernels)
750 self.log.warning("Consider lowering the spatial order")
752 self.log.info("Spatial kernel model well constrained; %d candidates, %d terms, %d bases",
753 nGood, nKernelTerms, nBasisKernels)
756 self.log.warning("Spatial background model underconstrained; %d candidates, %d terms",
758 self.log.warning("Consider lowering the spatial order")
759 elif nGood <= 2*nBgTerms:
760 self.log.warning("Spatial background model poorly constrained; %d candidates, %d terms",
762 self.log.warning("Consider lowering the spatial order")
764 self.log.info("Spatial background model appears well constrained; %d candidates, %d terms",
767 def _displayDebug(self, kernelCellSet, spatialKernel, spatialBackground):
768 """Provide visualization of the inputs and ouputs to the Psf-matching code
772 kernelCellSet : `lsst.afw.math.SpatialCellSet`
773 The SpatialCellSet used in determining the matching kernel and background
774 spatialKernel : `lsst.afw.math.LinearCombinationKernel`
775 Spatially varying Psf-matching kernel
776 spatialBackground : `lsst.afw.math.Function2D`
777 Spatially varying background-matching function
780 displayCandidates = lsstDebug.Info(__name__).displayCandidates
781 displayKernelBasis = lsstDebug.Info(__name__).displayKernelBasis
782 displayKernelMosaic = lsstDebug.Info(__name__).displayKernelMosaic
783 plotKernelSpatialModel = lsstDebug.Info(__name__).plotKernelSpatialModel
784 plotKernelCoefficients = lsstDebug.Info(__name__).plotKernelCoefficients
785 showBadCandidates = lsstDebug.Info(__name__).showBadCandidates
786 maskTransparency = lsstDebug.Info(__name__).maskTransparency
787 if not maskTransparency:
789 afwDisplay.setDefaultMaskTransparency(maskTransparency)
791 if displayCandidates:
792 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground,
793 frame=lsstDebug.frame,
794 showBadCandidates=showBadCandidates)
796 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground,
797 frame=lsstDebug.frame,
798 showBadCandidates=showBadCandidates,
801 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground,
802 frame=lsstDebug.frame,
803 showBadCandidates=showBadCandidates,
807 if displayKernelBasis:
808 diutils.showKernelBasis(spatialKernel, frame=lsstDebug.frame)
811 if displayKernelMosaic:
812 diutils.showKernelMosaic(kernelCellSet.getBBox(), spatialKernel, frame=lsstDebug.frame)
815 if plotKernelSpatialModel:
816 diutils.plotKernelSpatialModel(spatialKernel, kernelCellSet, showBadCandidates=showBadCandidates)
818 if plotKernelCoefficients:
819 diutils.plotKernelCoefficients(spatialKernel, kernelCellSet)
821 def _createPcaBasis(self, kernelCellSet, nStarPerCell, ps):
822 """Create Principal Component basis
824 If a principal component analysis is requested, typically when using a delta function basis,
825 perform the PCA here and return a new basis list containing the new principal components.
829 kernelCellSet : `lsst.afw.math.SpatialCellSet`
830 a SpatialCellSet containing KernelCandidates, from which components are derived
832 the number of stars per cell to visit when doing the PCA
833 ps : `lsst.daf.base.PropertySet`
834 input property set controlling the single kernel visitor
839 number of KernelCandidates rejected during PCA loop
840 spatialBasisList : `list` of `lsst.afw.math.kernel.FixedKernel`
841 basis list containing the principal shapes as Kernels
846 If the Eigenvalues sum to zero.
848 nComponents = self.kConfig.numPrincipalComponents
849 imagePca = diffimLib.KernelPcaD()
850 importStarVisitor = diffimLib.KernelPcaVisitorF(imagePca)
851 kernelCellSet.visitCandidates(importStarVisitor, nStarPerCell)
852 if self.kConfig.subtractMeanForPca:
853 importStarVisitor.subtractMean()
856 eigenValues = imagePca.getEigenValues()
857 pcaBasisList = importStarVisitor.getEigenKernels()
859 eSum = np.sum(eigenValues)
861 raise RuntimeError("Eigenvalues sum to zero")
862 trace_logger = getTraceLogger(self.log.getChild("_solve"), 5)
863 for j in range(len(eigenValues)):
864 trace_logger.debug("Eigenvalue %d : %f (%f)", j, eigenValues[j], eigenValues[j]/eSum)
866 nToUse = min(nComponents, len(eigenValues))
868 for j in range(nToUse):
870 kimage = afwImage.ImageD(pcaBasisList[j].getDimensions())
871 pcaBasisList[j].computeImage(kimage, False)
872 if not (True in np.isnan(kimage.array)):
873 trimBasisList.append(pcaBasisList[j])
875 # Put all the power in the first kernel, which will not vary spatially
876 spatialBasisList = diffimLib.renormalizeKernelList(trimBasisList)
878 # New Kernel visitor for this new basis list (no regularization explicitly)
879 singlekvPca = diffimLib.BuildSingleKernelVisitorF(spatialBasisList, ps)
880 singlekvPca.setSkipBuilt(False)
881 kernelCellSet.visitCandidates(singlekvPca, nStarPerCell)
882 singlekvPca.setSkipBuilt(True)
883 nRejectedPca = singlekvPca.getNRejected()
885 return nRejectedPca, spatialBasisList
894 def _solve(self, kernelCellSet, basisList, returnOnExcept=False):
895 """Solve for the PSF matching kernel
899 kernelCellSet : `lsst.afw.math.SpatialCellSet`
900 a SpatialCellSet to use in determining the matching kernel
901 (typically as provided by _buildCellSet)
902 basisList : `list` of `lsst.afw.math.kernel.FixedKernel`
903 list of Kernels to be used in the decomposition of the spatially varying kernel
904 (typically as provided by makeKernelBasisList)
905 returnOnExcept : `bool`, optional
906 if True then return (None, None) if an error occurs, else raise the exception
910 psfMatchingKernel : `lsst.afw.math.LinearCombinationKernel`
911 Spatially varying Psf-matching kernel
912 backgroundModel : `lsst.afw.math.Function2D`
913 Spatially varying background-matching function
918 If unable to determine PSF matching kernel and ``returnOnExcept==False``.
922 display = lsstDebug.Info(__name__).display
924 maxSpatialIterations = self.kConfig.maxSpatialIterations
925 nStarPerCell = self.kConfig.nStarPerCell
926 usePcaForSpatialKernel = self.kConfig.usePcaForSpatialKernel
928 # Visitor for the single kernel fit
929 ps = pexConfig.makePropertySet(self.kConfig)
930 if self.useRegularization:
931 singlekv = diffimLib.BuildSingleKernelVisitorF(basisList, ps, self.hMat)
933 singlekv = diffimLib.BuildSingleKernelVisitorF(basisList, ps)
935 # Visitor for the kernel sum rejection
936 ksv = diffimLib.KernelSumVisitorF(ps)
939 trace_loggers = [getTraceLogger(self.log.getChild("_solve"), i) for i in range(5)]
943 while (thisIteration < maxSpatialIterations):
945 # Make sure there are no uninitialized candidates as active occupants of Cell
947 while (nRejectedSkf != 0):
948 trace_loggers[1].debug("Building single kernels...")
949 kernelCellSet.visitCandidates(singlekv, nStarPerCell, ignoreExceptions=True)
950 nRejectedSkf = singlekv.getNRejected()
951 trace_loggers[1].debug(
952 "Iteration %d, rejected %d candidates due to initial kernel fit",
953 thisIteration, nRejectedSkf
956 # Reject outliers in kernel sum
958 ksv.setMode(diffimLib.KernelSumVisitorF.AGGREGATE)
959 kernelCellSet.visitCandidates(ksv, nStarPerCell, ignoreExceptions=True)
960 ksv.processKsumDistribution()
961 ksv.setMode(diffimLib.KernelSumVisitorF.REJECT)
962 kernelCellSet.visitCandidates(ksv, nStarPerCell, ignoreExceptions=True)
964 nRejectedKsum = ksv.getNRejected()
965 trace_loggers[1].debug(
966 "Iteration %d, rejected %d candidates due to kernel sum",
967 thisIteration, nRejectedKsum
970 # Do we jump back to the top without incrementing thisIteration?
971 if nRejectedKsum > 0:
975 # At this stage we can either apply the spatial fit to
976 # the kernels, or we run a PCA, use these as a *new*
977 # basis set with lower dimensionality, and then apply
978 # the spatial fit to these kernels
980 if (usePcaForSpatialKernel):
981 trace_loggers[0].debug("Building Pca basis")
983 nRejectedPca, spatialBasisList = self._createPcaBasis(kernelCellSet, nStarPerCell, ps)
984 trace_loggers[1].debug(
985 "Iteration %d, rejected %d candidates due to Pca kernel fit",
986 thisIteration, nRejectedPca
989 # We don't want to continue on (yet) with the
990 # spatial modeling, because we have bad objects
991 # contributing to the Pca basis. We basically
992 # need to restart from the beginning of this loop,
993 # since the cell-mates of those objects that were
994 # rejected need their original Kernels built by
995 # singleKernelFitter.
997 # Don't count against thisIteration
998 if (nRejectedPca > 0):
1002 spatialBasisList = basisList
1004 # We have gotten on to the spatial modeling part
1005 regionBBox = kernelCellSet.getBBox()
1006 spatialkv = diffimLib.BuildSpatialKernelVisitorF(spatialBasisList, regionBBox, ps)
1007 kernelCellSet.visitCandidates(spatialkv, nStarPerCell)
1008 spatialkv.solveLinearEquation()
1009 trace_loggers[2].debug("Spatial kernel built with %d candidates", spatialkv.getNCandidates())
1010 spatialKernel, spatialBackground = spatialkv.getSolutionPair()
1012 # Check the quality of the spatial fit (look at residuals)
1013 assesskv = diffimLib.AssessSpatialKernelVisitorF(spatialKernel, spatialBackground, ps)
1014 kernelCellSet.visitCandidates(assesskv, nStarPerCell)
1015 nRejectedSpatial = assesskv.getNRejected()
1016 nGoodSpatial = assesskv.getNGood()
1017 trace_loggers[1].debug(
1018 "Iteration %d, rejected %d candidates due to spatial kernel fit",
1019 thisIteration, nRejectedSpatial
1021 trace_loggers[1].debug("%d candidates used in fit", nGoodSpatial)
1023 # If only nGoodSpatial == 0, might be other candidates in the cells
1024 if nGoodSpatial == 0 and nRejectedSpatial == 0:
1025 raise RuntimeError("No kernel candidates for spatial fit")
1027 if nRejectedSpatial == 0:
1028 # Nothing rejected, finished with spatial fit
1031 # Otherwise, iterate on...
1034 # Final fit if above did not converge
1035 if (nRejectedSpatial > 0) and (thisIteration == maxSpatialIterations):
1036 trace_loggers[1].debug("Final spatial fit")
1037 if (usePcaForSpatialKernel):
1038 nRejectedPca, spatialBasisList = self._createPcaBasis(kernelCellSet, nStarPerCell, ps)
1039 regionBBox = kernelCellSet.getBBox()
1040 spatialkv = diffimLib.BuildSpatialKernelVisitorF(spatialBasisList, regionBBox, ps)
1041 kernelCellSet.visitCandidates(spatialkv, nStarPerCell)
1042 spatialkv.solveLinearEquation()
1043 trace_loggers[2].debug("Spatial kernel built with %d candidates", spatialkv.getNCandidates())
1044 spatialKernel, spatialBackground = spatialkv.getSolutionPair()
1046 spatialSolution = spatialkv.getKernelSolution()
1049 trace_loggers[0].debug("Total time to compute the spatial kernel : %.2f s", (t1 - t0))
1052 self._displayDebug(kernelCellSet, spatialKernel, spatialBackground)
1054 self._diagnostic(kernelCellSet, spatialSolution, spatialKernel, spatialBackground)
1056 return spatialSolution, spatialKernel, spatialBackground