580 def _diagnostic(self, kernelCellSet, spatialSolution, spatialKernel, spatialBg):
581 """Provide logging diagnostics on quality of spatial kernel fit
585 kernelCellSet : `lsst.afw.math.SpatialCellSet`
586 Cellset that contains the KernelCandidates used in the fitting
587 spatialSolution : `lsst.ip.diffim.SpatialKernelSolution`
588 KernelSolution of best-fit
589 spatialKernel : `lsst.afw.math.LinearCombinationKernel`
590 Best-fit spatial Kernel model
591 spatialBg : `lsst.afw.math.Function2D`
592 Best-fit spatial background model
594 # What is the final kernel sum
595 kImage = afwImage.ImageD(spatialKernel.getDimensions())
596 kSum = spatialKernel.computeImage(kImage, False)
597 self.log.info("Final spatial kernel sum %.3f", kSum)
599 # Look at how well conditioned the matrix is
600 conditionNum = spatialSolution.getConditionNumber(
601 getattr(diffimLib.KernelSolution, self.kConfig.conditionNumberType))
602 self.log.info("Spatial model condition number %.3e", conditionNum)
604 if conditionNum < 0.0:
605 self.log.warning("Condition number is negative (%.3e)", conditionNum)
606 if conditionNum > self.kConfig.maxSpatialConditionNumber:
607 self.log.warning("Spatial solution exceeds max condition number (%.3e > %.3e)",
608 conditionNum, self.kConfig.maxSpatialConditionNumber)
610 self.metadata["spatialConditionNum"] = conditionNum
611 self.metadata["spatialKernelSum"] = kSum
613 # Look at how well the solution is constrained
614 nBasisKernels = spatialKernel.getNBasisKernels()
615 nKernelTerms = spatialKernel.getNSpatialParameters()
616 if nKernelTerms == 0: # order 0
620 nBgTerms = spatialBg.getNParameters()
622 if spatialBg.getParameters()[0] == 0.0:
628 for cell in kernelCellSet.getCellList():
629 for cand in cell.begin(False): # False = include bad candidates
631 if cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
633 if cand.getStatus() == afwMath.SpatialCellCandidate.BAD:
636 self.log.info("Doing stats of kernel candidates used in the spatial fit.")
638 # Counting statistics
640 self.log.warning("Many more candidates rejected than accepted; %d total, %d rejected, %d used",
643 self.log.info("%d candidates total, %d rejected, %d used", nTot, nBad, nGood)
645 # Some judgements on the quality of the spatial models
646 if nGood < nKernelTerms:
647 self.log.warning("Spatial kernel model underconstrained; %d candidates, %d terms, %d bases",
648 nGood, nKernelTerms, nBasisKernels)
649 self.log.warning("Consider lowering the spatial order")
650 elif nGood <= 2*nKernelTerms:
651 self.log.warning("Spatial kernel model poorly constrained; %d candidates, %d terms, %d bases",
652 nGood, nKernelTerms, nBasisKernels)
653 self.log.warning("Consider lowering the spatial order")
655 self.log.info("Spatial kernel model well constrained; %d candidates, %d terms, %d bases",
656 nGood, nKernelTerms, nBasisKernels)
659 self.log.warning("Spatial background model underconstrained; %d candidates, %d terms",
661 self.log.warning("Consider lowering the spatial order")
662 elif nGood <= 2*nBgTerms:
663 self.log.warning("Spatial background model poorly constrained; %d candidates, %d terms",
665 self.log.warning("Consider lowering the spatial order")
667 self.log.info("Spatial background model appears well constrained; %d candidates, %d terms",
670 def _displayDebug(self, kernelCellSet, spatialKernel, spatialBackground):
671 """Provide visualization of the inputs and ouputs to the Psf-matching code
675 kernelCellSet : `lsst.afw.math.SpatialCellSet`
676 The SpatialCellSet used in determining the matching kernel and background
677 spatialKernel : `lsst.afw.math.LinearCombinationKernel`
678 Spatially varying Psf-matching kernel
679 spatialBackground : `lsst.afw.math.Function2D`
680 Spatially varying background-matching function
683 displayCandidates = lsstDebug.Info(__name__).displayCandidates
684 displayKernelBasis = lsstDebug.Info(__name__).displayKernelBasis
685 displayKernelMosaic = lsstDebug.Info(__name__).displayKernelMosaic
686 plotKernelSpatialModel = lsstDebug.Info(__name__).plotKernelSpatialModel
687 plotKernelCoefficients = lsstDebug.Info(__name__).plotKernelCoefficients
688 showBadCandidates = lsstDebug.Info(__name__).showBadCandidates
689 maskTransparency = lsstDebug.Info(__name__).maskTransparency
690 if not maskTransparency:
692 afwDisplay.setDefaultMaskTransparency(maskTransparency)
694 if displayCandidates:
695 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground,
696 frame=lsstDebug.frame,
697 showBadCandidates=showBadCandidates)
699 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground,
700 frame=lsstDebug.frame,
701 showBadCandidates=showBadCandidates,
704 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground,
705 frame=lsstDebug.frame,
706 showBadCandidates=showBadCandidates,
710 if displayKernelBasis:
711 diutils.showKernelBasis(spatialKernel, frame=lsstDebug.frame)
714 if displayKernelMosaic:
715 diutils.showKernelMosaic(kernelCellSet.getBBox(), spatialKernel, frame=lsstDebug.frame)
718 if plotKernelSpatialModel:
719 diutils.plotKernelSpatialModel(spatialKernel, kernelCellSet, showBadCandidates=showBadCandidates)
721 if plotKernelCoefficients:
722 diutils.plotKernelCoefficients(spatialKernel, kernelCellSet)
724 def _createPcaBasis(self, kernelCellSet, nStarPerCell, ps):
725 """Create Principal Component basis
727 If a principal component analysis is requested, typically when using a delta function basis,
728 perform the PCA here and return a new basis list containing the new principal components.
732 kernelCellSet : `lsst.afw.math.SpatialCellSet`
733 a SpatialCellSet containing KernelCandidates, from which components are derived
735 the number of stars per cell to visit when doing the PCA
736 ps : `lsst.daf.base.PropertySet`
737 input property set controlling the single kernel visitor
742 number of KernelCandidates rejected during PCA loop
743 spatialBasisList : `list` of `lsst.afw.math.kernel.FixedKernel`
744 basis list containing the principal shapes as Kernels
749 If the Eigenvalues sum to zero.
751 nComponents = self.kConfig.numPrincipalComponents
752 imagePca = diffimLib.KernelPcaD()
753 importStarVisitor = diffimLib.KernelPcaVisitorF(imagePca)
754 kernelCellSet.visitCandidates(importStarVisitor, nStarPerCell)
755 if self.kConfig.subtractMeanForPca:
756 importStarVisitor.subtractMean()
759 eigenValues = imagePca.getEigenValues()
760 pcaBasisList = importStarVisitor.getEigenKernels()
762 eSum = np.sum(eigenValues)
764 raise RuntimeError("Eigenvalues sum to zero")
765 trace_logger = getTraceLogger(self.log.getChild("_solve"), 5)
766 for j in range(len(eigenValues)):
767 trace_logger.debug("Eigenvalue %d : %f (%f)", j, eigenValues[j], eigenValues[j]/eSum)
769 nToUse = min(nComponents, len(eigenValues))
771 for j in range(nToUse):
773 kimage = afwImage.ImageD(pcaBasisList[j].getDimensions())
774 pcaBasisList[j].computeImage(kimage, False)
775 if not (True in np.isnan(kimage.array)):
776 trimBasisList.append(pcaBasisList[j])
778 # Put all the power in the first kernel, which will not vary spatially
779 spatialBasisList = diffimLib.renormalizeKernelList(trimBasisList)
781 # New Kernel visitor for this new basis list (no regularization explicitly)
782 singlekvPca = diffimLib.BuildSingleKernelVisitorF(spatialBasisList, ps)
783 singlekvPca.setSkipBuilt(False)
784 kernelCellSet.visitCandidates(singlekvPca, nStarPerCell)
785 singlekvPca.setSkipBuilt(True)
786 nRejectedPca = singlekvPca.getNRejected()
788 return nRejectedPca, spatialBasisList
797 def _solve(self, kernelCellSet, basisList, returnOnExcept=False):
798 """Solve for the PSF matching kernel
802 kernelCellSet : `lsst.afw.math.SpatialCellSet`
803 a SpatialCellSet to use in determining the matching kernel
804 (typically as provided by _buildCellSet)
805 basisList : `list` of `lsst.afw.math.kernel.FixedKernel`
806 list of Kernels to be used in the decomposition of the spatially varying kernel
807 (typically as provided by makeKernelBasisList)
808 returnOnExcept : `bool`, optional
809 if True then return (None, None) if an error occurs, else raise the exception
813 psfMatchingKernel : `lsst.afw.math.LinearCombinationKernel`
814 Spatially varying Psf-matching kernel
815 backgroundModel : `lsst.afw.math.Function2D`
816 Spatially varying background-matching function
821 If unable to determine PSF matching kernel and ``returnOnExcept==False``.
825 display = lsstDebug.Info(__name__).display
827 maxSpatialIterations = self.kConfig.maxSpatialIterations
828 nStarPerCell = self.kConfig.nStarPerCell
829 usePcaForSpatialKernel = self.kConfig.usePcaForSpatialKernel
831 # Visitor for the single kernel fit
832 ps = pexConfig.makePropertySet(self.kConfig)
833 if self.useRegularization:
834 singlekv = diffimLib.BuildSingleKernelVisitorF(basisList, ps, self.hMat)
836 singlekv = diffimLib.BuildSingleKernelVisitorF(basisList, ps)
838 # Visitor for the kernel sum rejection
839 ksv = diffimLib.KernelSumVisitorF(ps)
842 trace_loggers = [getTraceLogger(self.log.getChild("_solve"), i) for i in range(5)]
846 while (thisIteration < maxSpatialIterations):
848 # Make sure there are no uninitialized candidates as active occupants of Cell
850 while (nRejectedSkf != 0):
851 trace_loggers[1].debug("Building single kernels...")
852 kernelCellSet.visitCandidates(singlekv, nStarPerCell, ignoreExceptions=True)
853 nRejectedSkf = singlekv.getNRejected()
854 trace_loggers[1].debug(
855 "Iteration %d, rejected %d candidates due to initial kernel fit",
856 thisIteration, nRejectedSkf
859 # Reject outliers in kernel sum
861 ksv.setMode(diffimLib.KernelSumVisitorF.AGGREGATE)
862 kernelCellSet.visitCandidates(ksv, nStarPerCell, ignoreExceptions=True)
863 ksv.processKsumDistribution()
864 ksv.setMode(diffimLib.KernelSumVisitorF.REJECT)
865 kernelCellSet.visitCandidates(ksv, nStarPerCell, ignoreExceptions=True)
867 nRejectedKsum = ksv.getNRejected()
868 trace_loggers[1].debug(
869 "Iteration %d, rejected %d candidates due to kernel sum",
870 thisIteration, nRejectedKsum
873 # Do we jump back to the top without incrementing thisIteration?
874 if nRejectedKsum > 0:
878 # At this stage we can either apply the spatial fit to
879 # the kernels, or we run a PCA, use these as a *new*
880 # basis set with lower dimensionality, and then apply
881 # the spatial fit to these kernels
883 if (usePcaForSpatialKernel):
884 trace_loggers[0].debug("Building Pca basis")
886 nRejectedPca, spatialBasisList = self._createPcaBasis(kernelCellSet, nStarPerCell, ps)
887 trace_loggers[1].debug(
888 "Iteration %d, rejected %d candidates due to Pca kernel fit",
889 thisIteration, nRejectedPca
892 # We don't want to continue on (yet) with the
893 # spatial modeling, because we have bad objects
894 # contributing to the Pca basis. We basically
895 # need to restart from the beginning of this loop,
896 # since the cell-mates of those objects that were
897 # rejected need their original Kernels built by
898 # singleKernelFitter.
900 # Don't count against thisIteration
901 if (nRejectedPca > 0):
905 spatialBasisList = basisList
907 # We have gotten on to the spatial modeling part
908 regionBBox = kernelCellSet.getBBox()
909 spatialkv = diffimLib.BuildSpatialKernelVisitorF(spatialBasisList, regionBBox, ps)
910 kernelCellSet.visitCandidates(spatialkv, nStarPerCell)
911 spatialkv.solveLinearEquation()
912 trace_loggers[2].debug("Spatial kernel built with %d candidates", spatialkv.getNCandidates())
913 spatialKernel, spatialBackground = spatialkv.getSolutionPair()
915 # Check the quality of the spatial fit (look at residuals)
916 assesskv = diffimLib.AssessSpatialKernelVisitorF(spatialKernel, spatialBackground, ps)
917 kernelCellSet.visitCandidates(assesskv, nStarPerCell)
918 nRejectedSpatial = assesskv.getNRejected()
919 nGoodSpatial = assesskv.getNGood()
920 trace_loggers[1].debug(
921 "Iteration %d, rejected %d candidates due to spatial kernel fit",
922 thisIteration, nRejectedSpatial
924 trace_loggers[1].debug("%d candidates used in fit", nGoodSpatial)
926 # If only nGoodSpatial == 0, might be other candidates in the cells
927 if nGoodSpatial == 0 and nRejectedSpatial == 0:
928 raise RuntimeError("No kernel candidates for spatial fit")
930 if nRejectedSpatial == 0:
931 # Nothing rejected, finished with spatial fit
934 # Otherwise, iterate on...
937 # Final fit if above did not converge
938 if (nRejectedSpatial > 0) and (thisIteration == maxSpatialIterations):
939 trace_loggers[1].debug("Final spatial fit")
940 if (usePcaForSpatialKernel):
941 nRejectedPca, spatialBasisList = self._createPcaBasis(kernelCellSet, nStarPerCell, ps)
942 regionBBox = kernelCellSet.getBBox()
943 spatialkv = diffimLib.BuildSpatialKernelVisitorF(spatialBasisList, regionBBox, ps)
944 kernelCellSet.visitCandidates(spatialkv, nStarPerCell)
945 spatialkv.solveLinearEquation()
946 trace_loggers[2].debug("Spatial kernel built with %d candidates", spatialkv.getNCandidates())
947 spatialKernel, spatialBackground = spatialkv.getSolutionPair()
949 spatialSolution = spatialkv.getKernelSolution()
952 trace_loggers[0].debug("Total time to compute the spatial kernel : %.2f s", (t1 - t0))
955 self._displayDebug(kernelCellSet, spatialKernel, spatialBackground)
957 self._diagnostic(kernelCellSet, spatialSolution, spatialKernel, spatialBackground)
959 return spatialSolution, spatialKernel, spatialBackground