572 def _diagnostic(self, kernelCellSet, spatialSolution, spatialKernel, spatialBg):
573 """Provide logging diagnostics on quality of spatial kernel fit
577 kernelCellSet : `lsst.afw.math.SpatialCellSet`
578 Cellset that contains the KernelCandidates used in the fitting
579 spatialSolution : `lsst.ip.diffim.SpatialKernelSolution`
580 KernelSolution of best-fit
581 spatialKernel : `lsst.afw.math.LinearCombinationKernel`
582 Best-fit spatial Kernel model
583 spatialBg : `lsst.afw.math.Function2D`
584 Best-fit spatial background model
586 # What is the final kernel sum
587 kImage = afwImage.ImageD(spatialKernel.getDimensions())
588 kSum = spatialKernel.computeImage(kImage, False)
589 self.log.info("Final spatial kernel sum %.3f", kSum)
591 # Look at how well conditioned the matrix is
592 conditionNum = spatialSolution.getConditionNumber(
593 getattr(diffimLib.KernelSolution, self.kConfig.conditionNumberType))
594 self.log.info("Spatial model condition number %.3e", conditionNum)
596 if conditionNum < 0.0:
597 self.log.warning("Condition number is negative (%.3e)", conditionNum)
598 if conditionNum > self.kConfig.maxSpatialConditionNumber:
599 self.log.warning("Spatial solution exceeds max condition number (%.3e > %.3e)",
600 conditionNum, self.kConfig.maxSpatialConditionNumber)
602 self.metadata["spatialConditionNum"] = conditionNum
603 self.metadata["spatialKernelSum"] = kSum
605 # Look at how well the solution is constrained
606 nBasisKernels = spatialKernel.getNBasisKernels()
607 nKernelTerms = spatialKernel.getNSpatialParameters()
608 if nKernelTerms == 0: # order 0
612 nBgTerms = spatialBg.getNParameters()
614 if spatialBg.getParameters()[0] == 0.0:
620 for cell in kernelCellSet.getCellList():
621 for cand in cell.begin(False): # False = include bad candidates
623 if cand.getStatus() == afwMath.SpatialCellCandidate.GOOD:
625 if cand.getStatus() == afwMath.SpatialCellCandidate.BAD:
628 self.log.info("Doing stats of kernel candidates used in the spatial fit.")
630 # Counting statistics
632 self.log.warning("Many more candidates rejected than accepted; %d total, %d rejected, %d used",
635 self.log.info("%d candidates total, %d rejected, %d used", nTot, nBad, nGood)
637 # Some judgements on the quality of the spatial models
638 if nGood < nKernelTerms:
639 self.log.warning("Spatial kernel model underconstrained; %d candidates, %d terms, %d bases",
640 nGood, nKernelTerms, nBasisKernels)
641 self.log.warning("Consider lowering the spatial order")
642 elif nGood <= 2*nKernelTerms:
643 self.log.warning("Spatial kernel model poorly constrained; %d candidates, %d terms, %d bases",
644 nGood, nKernelTerms, nBasisKernels)
645 self.log.warning("Consider lowering the spatial order")
647 self.log.info("Spatial kernel model well constrained; %d candidates, %d terms, %d bases",
648 nGood, nKernelTerms, nBasisKernels)
651 self.log.warning("Spatial background model underconstrained; %d candidates, %d terms",
653 self.log.warning("Consider lowering the spatial order")
654 elif nGood <= 2*nBgTerms:
655 self.log.warning("Spatial background model poorly constrained; %d candidates, %d terms",
657 self.log.warning("Consider lowering the spatial order")
659 self.log.info("Spatial background model appears well constrained; %d candidates, %d terms",
662 def _displayDebug(self, kernelCellSet, spatialKernel, spatialBackground):
663 """Provide visualization of the inputs and ouputs to the Psf-matching code
667 kernelCellSet : `lsst.afw.math.SpatialCellSet`
668 The SpatialCellSet used in determining the matching kernel and background
669 spatialKernel : `lsst.afw.math.LinearCombinationKernel`
670 Spatially varying Psf-matching kernel
671 spatialBackground : `lsst.afw.math.Function2D`
672 Spatially varying background-matching function
675 displayCandidates = lsstDebug.Info(__name__).displayCandidates
676 displayKernelBasis = lsstDebug.Info(__name__).displayKernelBasis
677 displayKernelMosaic = lsstDebug.Info(__name__).displayKernelMosaic
678 plotKernelSpatialModel = lsstDebug.Info(__name__).plotKernelSpatialModel
679 plotKernelCoefficients = lsstDebug.Info(__name__).plotKernelCoefficients
680 showBadCandidates = lsstDebug.Info(__name__).showBadCandidates
681 maskTransparency = lsstDebug.Info(__name__).maskTransparency
682 if not maskTransparency:
684 afwDisplay.setDefaultMaskTransparency(maskTransparency)
686 if displayCandidates:
687 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground,
688 frame=lsstDebug.frame,
689 showBadCandidates=showBadCandidates)
691 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground,
692 frame=lsstDebug.frame,
693 showBadCandidates=showBadCandidates,
696 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground,
697 frame=lsstDebug.frame,
698 showBadCandidates=showBadCandidates,
702 if displayKernelBasis:
703 diutils.showKernelBasis(spatialKernel, frame=lsstDebug.frame)
706 if displayKernelMosaic:
707 diutils.showKernelMosaic(kernelCellSet.getBBox(), spatialKernel, frame=lsstDebug.frame)
710 if plotKernelSpatialModel:
711 diutils.plotKernelSpatialModel(spatialKernel, kernelCellSet, showBadCandidates=showBadCandidates)
713 if plotKernelCoefficients:
714 diutils.plotKernelCoefficients(spatialKernel, kernelCellSet)
716 def _createPcaBasis(self, kernelCellSet, nStarPerCell, ps):
717 """Create Principal Component basis
719 If a principal component analysis is requested, typically when using a delta function basis,
720 perform the PCA here and return a new basis list containing the new principal components.
724 kernelCellSet : `lsst.afw.math.SpatialCellSet`
725 a SpatialCellSet containing KernelCandidates, from which components are derived
727 the number of stars per cell to visit when doing the PCA
728 ps : `lsst.daf.base.PropertySet`
729 input property set controlling the single kernel visitor
734 number of KernelCandidates rejected during PCA loop
735 spatialBasisList : `list` of `lsst.afw.math.kernel.FixedKernel`
736 basis list containing the principal shapes as Kernels
741 If the Eigenvalues sum to zero.
743 nComponents = self.kConfig.numPrincipalComponents
744 imagePca = diffimLib.KernelPcaD()
745 importStarVisitor = diffimLib.KernelPcaVisitorF(imagePca)
746 kernelCellSet.visitCandidates(importStarVisitor, nStarPerCell)
747 if self.kConfig.subtractMeanForPca:
748 importStarVisitor.subtractMean()
751 eigenValues = imagePca.getEigenValues()
752 pcaBasisList = importStarVisitor.getEigenKernels()
754 eSum = np.sum(eigenValues)
756 raise RuntimeError("Eigenvalues sum to zero")
757 trace_logger = getTraceLogger(self.log.getChild("_solve"), 5)
758 for j in range(len(eigenValues)):
759 trace_logger.debug("Eigenvalue %d : %f (%f)", j, eigenValues[j], eigenValues[j]/eSum)
761 nToUse = min(nComponents, len(eigenValues))
763 for j in range(nToUse):
765 kimage = afwImage.ImageD(pcaBasisList[j].getDimensions())
766 pcaBasisList[j].computeImage(kimage, False)
767 if not (True in np.isnan(kimage.array)):
768 trimBasisList.append(pcaBasisList[j])
770 # Put all the power in the first kernel, which will not vary spatially
771 spatialBasisList = diffimLib.renormalizeKernelList(trimBasisList)
773 # New Kernel visitor for this new basis list (no regularization explicitly)
774 singlekvPca = diffimLib.BuildSingleKernelVisitorF(spatialBasisList, ps)
775 singlekvPca.setSkipBuilt(False)
776 kernelCellSet.visitCandidates(singlekvPca, nStarPerCell)
777 singlekvPca.setSkipBuilt(True)
778 nRejectedPca = singlekvPca.getNRejected()
780 return nRejectedPca, spatialBasisList
789 def _solve(self, kernelCellSet, basisList, returnOnExcept=False):
790 """Solve for the PSF matching kernel
794 kernelCellSet : `lsst.afw.math.SpatialCellSet`
795 a SpatialCellSet to use in determining the matching kernel
796 (typically as provided by _buildCellSet)
797 basisList : `list` of `lsst.afw.math.kernel.FixedKernel`
798 list of Kernels to be used in the decomposition of the spatially varying kernel
799 (typically as provided by makeKernelBasisList)
800 returnOnExcept : `bool`, optional
801 if True then return (None, None) if an error occurs, else raise the exception
805 psfMatchingKernel : `lsst.afw.math.LinearCombinationKernel`
806 Spatially varying Psf-matching kernel
807 backgroundModel : `lsst.afw.math.Function2D`
808 Spatially varying background-matching function
813 If unable to determine PSF matching kernel and ``returnOnExcept==False``.
817 display = lsstDebug.Info(__name__).display
819 maxSpatialIterations = self.kConfig.maxSpatialIterations
820 nStarPerCell = self.kConfig.nStarPerCell
821 usePcaForSpatialKernel = self.kConfig.usePcaForSpatialKernel
823 # Visitor for the single kernel fit
824 ps = pexConfig.makePropertySet(self.kConfig)
825 if self.useRegularization:
826 singlekv = diffimLib.BuildSingleKernelVisitorF(basisList, ps, self.hMat)
828 singlekv = diffimLib.BuildSingleKernelVisitorF(basisList, ps)
830 # Visitor for the kernel sum rejection
831 ksv = diffimLib.KernelSumVisitorF(ps)
834 trace_loggers = [getTraceLogger(self.log.getChild("_solve"), i) for i in range(5)]
838 while (thisIteration < maxSpatialIterations):
840 # Make sure there are no uninitialized candidates as active occupants of Cell
842 while (nRejectedSkf != 0):
843 trace_loggers[1].debug("Building single kernels...")
844 kernelCellSet.visitCandidates(singlekv, nStarPerCell, ignoreExceptions=True)
845 nRejectedSkf = singlekv.getNRejected()
846 trace_loggers[1].debug(
847 "Iteration %d, rejected %d candidates due to initial kernel fit",
848 thisIteration, nRejectedSkf
851 # Reject outliers in kernel sum
853 ksv.setMode(diffimLib.KernelSumVisitorF.AGGREGATE)
854 kernelCellSet.visitCandidates(ksv, nStarPerCell, ignoreExceptions=True)
855 ksv.processKsumDistribution()
856 ksv.setMode(diffimLib.KernelSumVisitorF.REJECT)
857 kernelCellSet.visitCandidates(ksv, nStarPerCell, ignoreExceptions=True)
859 nRejectedKsum = ksv.getNRejected()
860 trace_loggers[1].debug(
861 "Iteration %d, rejected %d candidates due to kernel sum",
862 thisIteration, nRejectedKsum
865 # Do we jump back to the top without incrementing thisIteration?
866 if nRejectedKsum > 0:
870 # At this stage we can either apply the spatial fit to
871 # the kernels, or we run a PCA, use these as a *new*
872 # basis set with lower dimensionality, and then apply
873 # the spatial fit to these kernels
875 if (usePcaForSpatialKernel):
876 trace_loggers[0].debug("Building Pca basis")
878 nRejectedPca, spatialBasisList = self._createPcaBasis(kernelCellSet, nStarPerCell, ps)
879 trace_loggers[1].debug(
880 "Iteration %d, rejected %d candidates due to Pca kernel fit",
881 thisIteration, nRejectedPca
884 # We don't want to continue on (yet) with the
885 # spatial modeling, because we have bad objects
886 # contributing to the Pca basis. We basically
887 # need to restart from the beginning of this loop,
888 # since the cell-mates of those objects that were
889 # rejected need their original Kernels built by
890 # singleKernelFitter.
892 # Don't count against thisIteration
893 if (nRejectedPca > 0):
897 spatialBasisList = basisList
899 # We have gotten on to the spatial modeling part
900 regionBBox = kernelCellSet.getBBox()
901 spatialkv = diffimLib.BuildSpatialKernelVisitorF(spatialBasisList, regionBBox, ps)
902 kernelCellSet.visitCandidates(spatialkv, nStarPerCell)
903 spatialkv.solveLinearEquation()
904 trace_loggers[2].debug("Spatial kernel built with %d candidates", spatialkv.getNCandidates())
905 spatialKernel, spatialBackground = spatialkv.getSolutionPair()
907 # Check the quality of the spatial fit (look at residuals)
908 assesskv = diffimLib.AssessSpatialKernelVisitorF(spatialKernel, spatialBackground, ps)
909 kernelCellSet.visitCandidates(assesskv, nStarPerCell)
910 nRejectedSpatial = assesskv.getNRejected()
911 nGoodSpatial = assesskv.getNGood()
912 trace_loggers[1].debug(
913 "Iteration %d, rejected %d candidates due to spatial kernel fit",
914 thisIteration, nRejectedSpatial
916 trace_loggers[1].debug("%d candidates used in fit", nGoodSpatial)
918 # If only nGoodSpatial == 0, might be other candidates in the cells
919 if nGoodSpatial == 0 and nRejectedSpatial == 0:
920 raise RuntimeError("No kernel candidates for spatial fit")
922 if nRejectedSpatial == 0:
923 # Nothing rejected, finished with spatial fit
926 # Otherwise, iterate on...
929 # Final fit if above did not converge
930 if (nRejectedSpatial > 0) and (thisIteration == maxSpatialIterations):
931 trace_loggers[1].debug("Final spatial fit")
932 if (usePcaForSpatialKernel):
933 nRejectedPca, spatialBasisList = self._createPcaBasis(kernelCellSet, nStarPerCell, ps)
934 regionBBox = kernelCellSet.getBBox()
935 spatialkv = diffimLib.BuildSpatialKernelVisitorF(spatialBasisList, regionBBox, ps)
936 kernelCellSet.visitCandidates(spatialkv, nStarPerCell)
937 spatialkv.solveLinearEquation()
938 trace_loggers[2].debug("Spatial kernel built with %d candidates", spatialkv.getNCandidates())
939 spatialKernel, spatialBackground = spatialkv.getSolutionPair()
941 spatialSolution = spatialkv.getKernelSolution()
944 trace_loggers[0].debug("Total time to compute the spatial kernel : %.2f s", (t1 - t0))
947 self._displayDebug(kernelCellSet, spatialKernel, spatialBackground)
949 self._diagnostic(kernelCellSet, spatialSolution, spatialKernel, spatialBackground)
951 return spatialSolution, spatialKernel, spatialBackground