Coverage for python/lsst/meas/algorithms/makePsfCandidates.py: 24%

55 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-24 01:33 -0800

1# 

2# LSST Data Management System 

3# 

4# Copyright 2008-2017 AURA/LSST. 

5# 

6# This product includes software developed by the 

7# LSST Project (http://www.lsst.org/). 

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 LSST License Statement and 

20# the GNU General Public License along with this program. If not, 

21# see <https://www.lsstcorp.org/LegalNotices/>. 

22# 

23__all__ = ["MakePsfCandidatesConfig", "MakePsfCandidatesTask"] 

24 

25import numpy as np 

26 

27from lsst.afw.table import SourceCatalog 

28import lsst.afw.math as afwMath 

29import lsst.pex.config as pexConfig 

30import lsst.pex.exceptions 

31import lsst.pipe.base as pipeBase 

32from . import makePsfCandidate 

33 

34 

35class MakePsfCandidatesConfig(pexConfig.Config): 

36 kernelSize = pexConfig.Field[int]( 

37 doc="Size of the postage stamp in pixels (excluding the border) around each star that is extracted " 

38 "for fitting. Should be odd and preferably at least 25.", 

39 default=25, 

40 ) 

41 borderWidth = pexConfig.Field[int]( 

42 doc="Number of pixels to ignore around the edge of PSF candidate postage stamps", 

43 default=0, 

44 ) 

45 

46 

47class MakePsfCandidatesTask(pipeBase.Task): 

48 """Create PSF candidates given an input catalog. 

49 """ 

50 ConfigClass = MakePsfCandidatesConfig 

51 _DefaultName = "makePsfCandidates" 

52 

53 def run(self, starCat, exposure, psfCandidateField=None): 

54 """Make a list of PSF candidates from a star catalog. 

55 

56 Parameters 

57 ---------- 

58 starCat : `lsst.afw.table.SourceCatalog` 

59 Catalog of stars, as returned by 

60 ``lsst.meas.algorithms.starSelector.run()``. 

61 exposure : `lsst.afw.image.Exposure` 

62 The exposure containing the sources. 

63 psfCandidateField : `str`, optional 

64 Name of flag field to set True for PSF candidates, or None to not 

65 set a field; the field is left unchanged for non-candidates. 

66 

67 Returns 

68 ------- 

69 struct : `lsst.pipe.base.Struct` 

70 Results struct containing: 

71 

72 - ``psfCandidates`` : List of PSF candidates 

73 (`list` of `lsst.meas.algorithms.PsfCandidate`). 

74 - ``goodStarCat`` : Subset of ``starCat`` that was successfully made 

75 into PSF candidates (`lsst.afw.table.SourceCatalog`). 

76 

77 """ 

78 psfResult = self.makePsfCandidates(starCat, exposure) 

79 

80 if psfCandidateField is not None: 

81 isStarKey = starCat.schema[psfCandidateField].asKey() 

82 for star in psfResult.goodStarCat: 

83 star.set(isStarKey, True) 

84 

85 return psfResult 

86 

87 def makePsfCandidates(self, starCat, exposure): 

88 """Make a list of PSF candidates from a star catalog. 

89 

90 Parameters 

91 ---------- 

92 starCat : `lsst.afw.table.SourceCatalog` 

93 Catalog of stars, as returned by 

94 ``lsst.meas.algorithms.starSelector.run()``. 

95 exposure : `lsst.afw.image.Exposure` 

96 The exposure containing the sources. 

97 

98 Returns 

99 ------- 

100 struct : `lsst.pipe.base.Struct` 

101 Results struct containing: 

102 

103 - ``psfCandidates`` : List of PSF candidates 

104 (`list` of `lsst.meas.algorithms.PsfCandidate`). 

105 - ``goodStarCat`` : Subset of ``starCat`` that was successfully made 

106 into PSF candidates (`lsst.afw.table.SourceCatalog`). 

107 """ 

108 goodStarCat = SourceCatalog(starCat.schema) 

109 

110 psfCandidateList = [] 

111 didSetSize = False 

112 for star in starCat: 

113 psfCandidate = makePsfCandidate(star, exposure) 

114 try: 

115 psfCandidate.setPsfColorValue(star['psf_color_value']) 

116 psfCandidate.setPsfColorType(star['psf_color_type']) 

117 except Exception: 

118 psfCandidate.setPsfColorValue(np.nan) 

119 psfCandidate.setPsfColorType("") 

120 try: 

121 # The setXXX methods are class static, but it's convenient to call them on 

122 # an instance as we don't know exposures's pixel type 

123 # (and hence psfCandidate's exact type) 

124 if not didSetSize: 

125 psfCandidate.setBorderWidth(self.config.borderWidth) 

126 psfCandidate.setWidth(self.config.kernelSize + 2*self.config.borderWidth) 

127 psfCandidate.setHeight(self.config.kernelSize + 2*self.config.borderWidth) 

128 didSetSize = True 

129 

130 im = psfCandidate.getMaskedImage().getImage() 

131 except lsst.pex.exceptions.LengthError: 

132 self.log.warning("Could not get stamp for psfCandidate with source id=%s: %s", 

133 star.getId(), psfCandidate) 

134 continue 

135 except lsst.pex.exceptions.Exception as e: 

136 self.log.error("%s raised making psfCandidate from source id=%s: %s", 

137 e.__class__.__name__, star.getId(), psfCandidate) 

138 self.log.error("psfCandidate exception: %s", e) 

139 continue 

140 

141 vmax = afwMath.makeStatistics(im, afwMath.MAX).getValue() 

142 if not np.isfinite(vmax): 

143 continue 

144 if 'psf_max_value' in star.schema: 

145 star['psf_max_value'] = vmax 

146 psfCandidateList.append(psfCandidate) 

147 goodStarCat.append(star) 

148 

149 return pipeBase.Struct( 

150 psfCandidates=psfCandidateList, 

151 goodStarCat=goodStarCat, 

152 )