Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of meas_algorithms. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

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 GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21# 

22"""Collection of small images (stamps), each centered on a bright star. 

23""" 

24 

25__all__ = ["BrightStarStamp", "BrightStarStamps"] 

26 

27from dataclasses import dataclass 

28from enum import Enum, auto 

29 

30from lsst.afw.image import MaskedImage 

31from .stamps import StampsBase, AbstractStamp, readFitsWithOptions 

32 

33 

34class RadiiEnum(Enum): 

35 INNER_RADIUS = auto() 

36 OUTER_RADIUS = auto() 

37 

38 def __str__(self): 

39 return self.name 

40 

41 

42@dataclass 

43class BrightStarStamp(AbstractStamp): 

44 """Single stamp centered on a bright star, normalized by its 

45 annularFlux. 

46 

47 Parameters 

48 ---------- 

49 stamp_im : `lsst.afw.image.MaskedImage` 

50 Pixel data for this postage stamp 

51 gaiaGMag : `float` 

52 Gaia G magnitude for the object in this stamp 

53 gaiaId : `int` 

54 Gaia object identifier 

55 annularFlux : `float` 

56 Flux in an annulus around the object 

57 """ 

58 stamp_im: MaskedImage 

59 gaiaGMag: float 

60 gaiaId: int 

61 annularFlux: float 

62 

63 @classmethod 

64 def factory(cls, stamp_im, metadata, idx): 

65 """This method is needed to service the FITS reader. 

66 We need a standard interface to construct objects like this. 

67 Parameters needed to construct this object are passed in via 

68 a metadata dictionary and then passed to the constructor of 

69 this class. This particular factory method requires keys: 

70 G_MAGS, GAIA_IDS, and ANNULAR_FLUXES. They should each 

71 point to lists of values. 

72 

73 Parameters 

74 ---------- 

75 stamp_im : `lsst.afw.image.MaskedImage` 

76 Pixel data to pass to the constructor 

77 metadata : `dict` 

78 Dictionary containing the information 

79 needed by the constructor. 

80 idx : `int` 

81 Index into the lists in ``metadata`` 

82 

83 Returns 

84 ------- 

85 brightstarstamp : `BrightStarStamp` 

86 An instance of this class 

87 """ 

88 return cls(stamp_im=stamp_im, 

89 gaiaGMag=metadata['G_MAGS'][idx], 

90 gaiaId=metadata['GAIA_IDS'][idx], 

91 annularFlux=metadata['ANNULAR_FLUXES'][idx]) 

92 

93 

94class BrightStarStamps(StampsBase): 

95 """Collection of bright star stamps and associated metadata. 

96 

97 Parameters 

98 ---------- 

99 starStamps : `collections.abc.Sequence` [`BrightStarStamp`] 

100 Sequence of star stamps. 

101 innerRadius : `int`, optional 

102 Inner radius value, in pixels. This and ``outerRadius`` define the 

103 annulus used to compute the ``"annularFlux"`` values within each 

104 ``starStamp``. Must be provided if ``"INNER_RADIUS"`` and 

105 ``"OUTER_RADIUS"`` are not present in ``metadata``. 

106 outerRadius : `int`, optional 

107 Outer radius value, in pixels. This and ``innerRadius`` define the 

108 annulus used to compute the ``"annularFlux"`` values within each 

109 ``starStamp``. Must be provided if ``"INNER_RADIUS"`` and 

110 ``"OUTER_RADIUS"`` are not present in ``metadata``. 

111 metadata : `lsst.daf.base.PropertyList`, optional 

112 Metadata associated with the bright stars. 

113 use_mask : `bool` 

114 If `True` read and write mask data. Default `True`. 

115 use_variance : `bool` 

116 If ``True`` read and write variance data. Default ``False``. 

117 

118 Raises 

119 ------ 

120 ValueError 

121 Raised if one of the star stamps provided does not contain the 

122 required keys. 

123 AttributeError 

124 Raised if the definition of the annulus used to compute each star's 

125 normalization factor are not provided, that is, if ``"INNER_RADIUS"`` 

126 and ``"OUTER_RADIUS"`` are not present in ``metadata`` _and_ 

127 ``innerRadius`` and ``outerRadius`` are not provided. 

128 

129 Notes 

130 ----- 

131 A (gen2) butler can be used to read only a part of the stamps, 

132 specified by a bbox: 

133 

134 >>> starSubregions = butler.get("brightStarStamps_sub", dataId, bbox=bbox) 

135 """ 

136 

137 def __init__(self, starStamps, innerRadius=None, outerRadius=None, 

138 metadata=None, use_mask=True, use_variance=False): 

139 super().__init__(starStamps, metadata, use_mask, use_variance) 

140 # Add inner and outer radii to metadata 

141 self._checkRadius(innerRadius, RadiiEnum.INNER_RADIUS) 

142 self._innerRadius = innerRadius 

143 self._checkRadius(outerRadius, RadiiEnum.OUTER_RADIUS) 

144 self._outerRadius = outerRadius 

145 

146 def _refresh_metadata(self): 

147 """Refresh the metadata. Should be called before writing this object out. 

148 """ 

149 # add full list of Gaia magnitudes, IDs and annularFlxes to shared 

150 # metadata 

151 self._metadata["G_MAGS"] = self.getMagnitudes() 

152 self._metadata["GAIA_IDS"] = self.getGaiaIds() 

153 self._metadata["ANNULAR_FLUXES"] = self.getAnnularFluxes() 

154 return None 

155 

156 @classmethod 

157 def readFits(cls, filename): 

158 """Build an instance of this class from a file. 

159 

160 Parameters 

161 ---------- 

162 filename : `str` 

163 Name of the file to read 

164 """ 

165 return cls.readFitsWithOptions(filename, None) 

166 

167 @classmethod 

168 def readFitsWithOptions(cls, filename, options): 

169 """Build an instance of this class with options. 

170 

171 Parameters 

172 ---------- 

173 filename : `str` 

174 Name of the file to read 

175 options : `PropertyList` 

176 Collection of metadata parameters 

177 """ 

178 stamps, metadata = readFitsWithOptions(filename, BrightStarStamp.factory, options) 

179 return cls(stamps, metadata=metadata, use_mask=metadata['HAS_MASK'], 

180 use_variance=metadata['HAS_VARIANCE']) 

181 

182 def append(self, item, innerRadius, outerRadius): 

183 """Add an additional bright star stamp. 

184 

185 Parameters 

186 ---------- 

187 item : `BrightStarStamp` 

188 Bright star stamp to append. 

189 innerRadius : `int` 

190 Inner radius value, in pixels. This and ``outerRadius`` define the 

191 annulus used to compute the ``"annularFlux"`` values within each 

192 ``starStamp``. 

193 outerRadius : `int`, optional 

194 Outer radius value, in pixels. This and ``innerRadius`` define the 

195 annulus used to compute the ``"annularFlux"`` values within each 

196 ``starStamp``. 

197 """ 

198 if not isinstance(item, BrightStarStamp): 

199 raise ValueError(f"Can only add instances of BrightStarStamp, got {type(item)}.") 

200 self._checkRadius(innerRadius, RadiiEnum.INNER_RADIUS) 

201 self._checkRadius(outerRadius, RadiiEnum.OUTER_RADIUS) 

202 self._stamps.append(item) 

203 return None 

204 

205 def extend(self, bss): 

206 """Extend BrightStarStamps instance by appending elements from another 

207 instance. 

208 

209 Parameters 

210 ---------- 

211 bss : `BrightStarStamps` 

212 Other instance to concatenate. 

213 """ 

214 if not isinstance(bss, BrightStarStamps): 

215 raise ValueError('Can only extend with a BrightStarStamps object. ' 

216 f'Got {type(bss)}.') 

217 self._checkRadius(bss._innerRadius, RadiiEnum.INNER_RADIUS) 

218 self._checkRadius(bss._outerRadius, RadiiEnum.OUTER_RADIUS) 

219 self._stamps += bss._stamps 

220 

221 def getMagnitudes(self): 

222 """Retrieve Gaia G magnitudes for each star. 

223 

224 Returns 

225 ------- 

226 gaiaGMags : `list` [`float`] 

227 """ 

228 return [stamp.gaiaGMag for stamp in self._stamps] 

229 

230 def getGaiaIds(self): 

231 """Retrieve Gaia IDs for each star. 

232 

233 Returns 

234 ------- 

235 gaiaIds : `list` [`int`] 

236 """ 

237 return [stamp.gaiaId for stamp in self._stamps] 

238 

239 def getAnnularFluxes(self): 

240 """Retrieve normalization factors for each star. 

241 

242 These are computed by integrating the flux in annulus centered on the 

243 bright star, far enough from center to be beyond most severe ghosts and 

244 saturation. The inner and outer radii that define the annulus can be 

245 recovered from the metadata. 

246 

247 Returns 

248 ------- 

249 annularFluxes : `list` [`float`] 

250 """ 

251 return [stamp.annularFlux for stamp in self._stamps] 

252 

253 def selectByMag(self, magMin=None, magMax=None): 

254 """Return the subset of bright star stamps for objects with specified 

255 magnitude cuts (in Gaia G). 

256 

257 Parameters 

258 ---------- 

259 magMin : `float`, optional 

260 Keep only stars fainter than this value. 

261 magMax : `float`, optional 

262 Keep only stars brighter than this value. 

263 """ 

264 subset = [stamp for stamp in self._stamps 

265 if (magMin is None or stamp.gaiaGMag > magMin) 

266 and (magMax is None or stamp.gaiaGMag < magMax)] 

267 # This is an optimization to save looping over the init argument when 

268 # it is already guaranteed to be the correct type 

269 instance = BrightStarStamps((), metadata=self._metadata) 

270 instance._stamps = subset 

271 return instance 

272 

273 def _checkRadius(self, radiusValue, metadataEnum): 

274 """Ensure provided annulus radius is consistent with that present 

275 in metadata. If metadata does not contain annulus radius, add it. 

276 """ 

277 # if a radius value is already present in metadata, ensure it matches 

278 # the one given 

279 metadataName = str(metadataEnum) 

280 if self._metadata.exists(metadataName): 

281 if radiusValue is not None: 

282 if self._metadata[metadataName] != radiusValue: 

283 raise AttributeError("BrightStarStamps instance already contains different annulus radii " 

284 + f"values ({metadataName}).") 

285 # if not already in metadata, a value must be provided 

286 elif radiusValue is None: 

287 raise AttributeError("No radius value provided for the AnnularFlux measurement " 

288 + f"({metadataName}), and none present in metadata.") 

289 else: 

290 self._metadata[metadataName] = radiusValue 

291 return None