Coverage for python/lsst/analysis/drp/calcFunctors.py: 42%

Shortcuts 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

178 statements  

1__all__ = ["SNCalculator", "KronFluxDivPsfFlux", "MagDiff", "ColorDiff", "ColorDiffPull"] 

2 

3from lsst.pipe.tasks.configurableActions import ConfigurableActionField 

4from lsst.pipe.tasks.dataFrameActions import DataFrameAction, DivideColumns, MultiColumnAction 

5from lsst.pex.config import Field 

6from astropy import units as u 

7import numpy as np 

8 

9 

10class SNCalculator(DivideColumns): 

11 """Calculate the signal to noise by default the i band PSF flux is used""" 

12 

13 def setDefaults(self): 

14 super().setDefaults() 

15 self.colA.column = "i_psfFlux" 

16 self.colB.column = "i_psfFluxErr" 

17 

18 

19class KronFluxDivPsfFlux(DivideColumns): 

20 """Divide the Kron instFlux by the PSF instFlux""" 

21 

22 def setDefaults(self): 

23 super().setDefaults() 

24 self.colA.column = "i_kronFlux" 

25 self.colB.column = "i_psfFlux" 

26 

27 

28class MagDiff(MultiColumnAction): 

29 """Calculate the difference between two magnitudes; 

30 each magnitude is derived from a flux column. 

31 

32 Parameters 

33 ---------- 

34 df : `pandas.core.frame.DataFrame` 

35 The catalog to calculate the magnitude difference from. 

36 

37 Returns 

38 ------- 

39 The magnitude difference in milli mags. 

40 

41 Notes 

42 ----- 

43 The flux columns need to be in units (specifiable in 

44 the fluxUnits1 and 2 config options) that can be converted 

45 to janskies. This action doesn't have any calibration 

46 information and assumes that the fluxes are already 

47 calibrated. 

48 """ 

49 

50 col1 = Field(doc="Column to subtract from", dtype=str) 

51 fluxUnits1 = Field(doc="Units for col1", dtype=str, default="nanojansky") 

52 col2 = Field(doc="Column to subtract", dtype=str) 

53 fluxUnits2 = Field(doc="Units for col2", dtype=str, default="nanojansky") 

54 returnMillimags = Field(doc="Use millimags or not?", dtype=bool, default=True) 

55 

56 @property 

57 def columns(self): 

58 return (self.col1, self.col2) 

59 

60 def __call__(self, df): 

61 flux1 = df[self.col1].values * u.Unit(self.fluxUnits1) 

62 mag1 = flux1.to(u.ABmag) 

63 

64 flux2 = df[self.col2].values * u.Unit(self.fluxUnits2) 

65 mag2 = flux2.to(u.ABmag) 

66 

67 magDiff = mag1 - mag2 

68 

69 if self.returnMillimags: 

70 magDiff = magDiff*1000.0 

71 return magDiff 

72 

73 

74class CalcE(MultiColumnAction): 

75 """Calculate a complex value representation of the ellipticity 

76 

77 This is a shape measurement used for doing QA on the ellipticity 

78 of the sources. 

79 

80 The complex ellipticity is typically defined as 

81 E = ((ixx - iyy) + 1j*(2*ixy))/(ixx + iyy) = |E|exp(i*2*theta). 

82 

83 For plotting purposes we might want to plot |E|*exp(i*theta). 

84 If `halvePhaseAngle` config parameter is set to `True`, then 

85 the returned quantity therefore corresponds to |E|*exp(i*theta) 

86 """ 

87 

88 colXx = Field(doc="The column name to get the xx shape component from.", 

89 dtype=str, 

90 default="ixx") 

91 

92 colYy = Field(doc="The column name to get the yy shape component from.", 

93 dtype=str, 

94 default="iyy") 

95 

96 colXy = Field(doc="The column name to get the xy shape component from.", 

97 dtype=str, 

98 default="ixy") 

99 

100 halvePhaseAngle = Field(doc=("Divide the phase angle by 2? " 

101 "Suitable for quiver plots."), 

102 dtype=bool, 

103 default=False) 

104 

105 @property 

106 def columns(self): 

107 return (self.colXx, self.colYy, self.colXy) 

108 

109 def __call__(self, df): 

110 e = (df[self.colXx] - df[self.colYy]) + 1j*(2*df[self.colXy]) 

111 e /= (df[self.colXx] + df[self.colYy]) 

112 if self.halvePhaseAngle: 

113 # Ellipiticity is |e|*exp(i*2*theta), but we want to return 

114 # |e|*exp(i*theta). So we multiply by |e| and take its square root 

115 # instead of the more expensive trig calls. 

116 e *= np.abs(e) 

117 return np.sqrt(e) 

118 else: 

119 return e 

120 

121 

122class CalcEDiff(DataFrameAction): 

123 """Calculate the difference of two ellipticities as a complex quantity. 

124 

125 This is a shape measurement used for doing QA on the ellipticity 

126 of the sources. 

127 

128 The complex ellipticity difference between E_A and E_B is efined as 

129 dE = |dE|exp(i*2*theta). 

130 

131 For plotting purposes we might want to plot |dE|*exp(i*theta). 

132 If `halvePhaseAngle` config parameter is set to `True`, then 

133 the returned quantity therefore corresponds to |E|*exp(i*theta) 

134 """ 

135 colA = ConfigurableActionField(doc="Ellipticity to subtract from", 

136 dtype=MultiColumnAction, 

137 default=CalcE) 

138 

139 colB = ConfigurableActionField(doc="Ellipticity to subtract", 

140 dtype=MultiColumnAction, 

141 default=CalcE) 

142 

143 halvePhaseAngle = Field(doc=("Divide the phase angle by 2? " 

144 "Suitable for quiver plots."), 

145 dtype=bool, 

146 default=False) 

147 

148 @property 

149 def columns(self): 

150 yield from self.colA.columns 

151 yield from self.colB.columns 

152 

153 def __call__(self, df): 

154 eMeas = self.colA(df) 

155 ePSF = self.colB(df) 

156 eDiff = eMeas - ePSF 

157 if self.halvePhaseAngle: 

158 # Ellipiticity is |e|*exp(i*2*theta), but we want to return 

159 # |e|*exp(i*theta). So we multiply by |e| and take its square root 

160 # instead of the more expensive trig calls. 

161 eDiff *= np.abs(eDiff) 

162 return np.sqrt(eDiff) 

163 else: 

164 return eDiff 

165 

166 

167class CalcE1(MultiColumnAction): 

168 """Calculate E1: (ixx - iyy)/(ixx + iyy) 

169 This is a shape measurement used for doing QA on the ellipticity 

170 of the sources.""" 

171 

172 colXx = Field(doc="The column name to get the xx shape component from.", 

173 dtype=str, 

174 default="ixx") 

175 

176 colYy = Field(doc="The column name to get the yy shape component from.", 

177 dtype=str, 

178 default="iyy") 

179 

180 @property 

181 def columns(self): 

182 return (self.colXx, self.colYy) 

183 

184 def __call__(self, df): 

185 e1 = (df[self.colXx] - df[self.colYy])/(df[self.colXx] + df[self.colYy]) 

186 

187 return e1 

188 

189 

190class CalcE2(MultiColumnAction): 

191 """Calculate E2: 2ixy/(ixx+iyy) 

192 This is a shape measurement used for doing QA on the ellipticity 

193 of the sources.""" 

194 

195 colXx = Field(doc="The column name to get the xx shape component from.", 

196 dtype=str, 

197 default="ixx") 

198 

199 colYy = Field(doc="The column name to get the yy shape component from.", 

200 dtype=str, 

201 default="iyy") 

202 

203 colXy = Field(doc="The column name to get the xy shape component from.", 

204 dtype=str, 

205 default="ixy") 

206 

207 @property 

208 def columns(self): 

209 return (self.colXx, self.colYy, self.colXy) 

210 

211 def __call__(self, df): 

212 e2 = 2*df[self.colXy]/(df[self.colXx] + df[self.colYy]) 

213 return e2 

214 

215 

216class CalcShapeSize(MultiColumnAction): 

217 """Calculate a size: (ixx*iyy - ixy**2)**0.25 

218 This is a size measurement used for doing QA on the ellipticity 

219 of the sources.""" 

220 

221 colXx = Field(doc="The column name to get the xx shape component from.", 

222 dtype=str, 

223 default="ixx") 

224 

225 colYy = Field(doc="The column name to get the yy shape component from.", 

226 dtype=str, 

227 default="iyy") 

228 

229 colXy = Field(doc="The column name to get the xy shape component from.", 

230 dtype=str, 

231 default="ixy") 

232 

233 @property 

234 def columns(self): 

235 return (self.colXx, self.colYy, self.colXy) 

236 

237 def __call__(self, df): 

238 size = np.power(df[self.colXx]*df[self.colYy] - df[self.colXy]**2, 0.25) 

239 return size 

240 

241 

242class ColorDiff(MultiColumnAction): 

243 """Calculate the difference between two colors; 

244 each color is derived from two flux columns. 

245 

246 The color difference is computed as (color1 - color2) with: 

247 

248 color1 = color1_mag1 - color1_mag2 

249 color2 = color2_mag1 - color2_mag2 

250 

251 where color1_mag1 is the magnitude associated with color1_flux1, etc. 

252 

253 Parameters 

254 ---------- 

255 df : `pandas.core.frame.DataFrame` 

256 The catalog to calculate the color difference from. 

257 

258 Returns 

259 ------- 

260 The color difference in millimags. 

261 

262 Notes 

263 ----- 

264 The flux columns need to be in units that can be converted 

265 to janskies. This action doesn't have any calibration 

266 information and assumes that the fluxes are already 

267 calibrated. 

268 """ 

269 color1_flux1 = Field(doc="Column for flux1 to determine color1", 

270 dtype=str) 

271 color1_flux1_units = Field(doc="Units for color1_flux1", 

272 dtype=str, 

273 default="nanojansky") 

274 color1_flux2 = Field(doc="Column for flux2 to determine color1", 

275 dtype=str) 

276 color1_flux2_units = Field(doc="Units for color1_flux2", 

277 dtype=str, 

278 default="nanojansky") 

279 color2_flux1 = Field(doc="Column for flux1 to determine color2", 

280 dtype=str) 

281 color2_flux1_units = Field(doc="Units for color2_flux1", 

282 dtype=str, 

283 default="nanojansky") 

284 color2_flux2 = Field(doc="Column for flux2 to determine color2", 

285 dtype=str) 

286 color2_flux2_units = Field(doc="Units for color2_flux2", 

287 dtype=str, 

288 default="nanojansky") 

289 return_millimags = Field(doc="Use millimags or not?", 

290 dtype=bool, 

291 default=True) 

292 

293 @property 

294 def columns(self): 

295 return (self.color1_flux1, 

296 self.color1_flux2, 

297 self.color2_flux1, 

298 self.color2_flux2) 

299 

300 def __call__(self, df): 

301 color1_flux1 = df[self.color1_flux1].values*u.Unit(self.color1_flux1_units) 

302 color1_mag1 = color1_flux1.to(u.ABmag).value 

303 

304 color1_flux2 = df[self.color1_flux2].values*u.Unit(self.color1_flux2_units) 

305 color1_mag2 = color1_flux2.to(u.ABmag).value 

306 

307 color2_flux1 = df[self.color2_flux1].values*u.Unit(self.color2_flux1_units) 

308 color2_mag1 = color2_flux1.to(u.ABmag).value 

309 

310 color2_flux2 = df[self.color2_flux2].values*u.Unit(self.color2_flux2_units) 

311 color2_mag2 = color2_flux2.to(u.ABmag).value 

312 

313 color1 = color1_mag1 - color1_mag2 

314 color2 = color2_mag1 - color2_mag2 

315 

316 color_diff = color1 - color2 

317 

318 if self.return_millimags: 

319 color_diff *= 1000.0 

320 

321 return color_diff 

322 

323 

324class ColorDiffPull(ColorDiff): 

325 """Calculate the difference between two colors, scaled by the color error; 

326 Each color is derived from two flux columns. 

327 

328 The color difference is computed as (color1 - color2) with: 

329 

330 color1 = color1_mag1 - color1_mag2 

331 color2 = color2_mag1 - color2_mag2 

332 

333 where color1_mag1 is the magnitude associated with color1_flux1, etc. 

334 

335 The color difference (color1 - color2) is then scaled by the error on 

336 the color as computed from color1_flux1_err, color1_flux2_err, 

337 color2_flux1_err, color2_flux2_err. The errors on color2 may be omitted 

338 if the comparison is between an "observed" catalog and a "truth" catalog. 

339 

340 Parameters 

341 ---------- 

342 df : `pandas.core.frame.DataFrame` 

343 The catalog to calculate the color difference from. 

344 

345 Returns 

346 ------- 

347 The color difference scaled by the error. 

348 

349 Notes 

350 ----- 

351 The flux columns need to be in units that can be converted 

352 to janskies. This action doesn't have any calibration 

353 information and assumes that the fluxes are already 

354 calibrated. 

355 """ 

356 color1_flux1_err = Field(doc="Error column for flux1 for color1", 

357 dtype=str, 

358 default="") 

359 color1_flux2_err = Field(doc="Error column for flux2 for color1", 

360 dtype=str, 

361 default="") 

362 color2_flux1_err = Field(doc="Error column for flux1 for color2", 

363 dtype=str, 

364 default="") 

365 color2_flux2_err = Field(doc="Error column for flux2 for color2", 

366 dtype=str, 

367 default="") 

368 

369 def validate(self): 

370 super().validate() 

371 

372 color1_errors = False 

373 color2_errors = False 

374 

375 if self.color1_flux1_err and self.color1_flux2_err: 

376 color1_errors = True 

377 elif ((self.color1_flux1_err and not self.color1_flux2_err) 

378 or (not self.color1_flux1_err and self.color1_flux2_err)): 

379 raise ValueError("Must set both color1_flux1_err and color1_flux2_err if either is set.") 

380 if self.color2_flux1_err and self.color2_flux2_err: 

381 color2_errors = True 

382 elif ((self.color2_flux1_err and not self.color2_flux2_err) 

383 or (not self.color2_flux1_err and self.color2_flux2_err)): 

384 raise ValueError("Must set both color2_flux1_err and color2_flux2_err if either is set.") 

385 

386 if not color1_errors and not color2_errors: 

387 raise ValueError("Must configure flux errors for at least color1 or color2.") 

388 

389 @property 

390 def columns(self): 

391 columns = (self.color1_flux1, 

392 self.color1_flux2, 

393 self.color2_flux1, 

394 self.color2_flux2) 

395 

396 if self.color1_flux1_err: 

397 # Config validation ensures if one is set, both are set. 

398 columns = columns + (self.color1_flux1_err, 

399 self.color1_flux2_err) 

400 

401 if self.color2_flux1_err: 

402 # Config validation ensures if one is set, both are set. 

403 columns = columns + (self.color2_flux1_err, 

404 self.color2_flux2_err) 

405 

406 return columns 

407 

408 def __call__(self, df): 

409 k = 2.5/np.log(10.) 

410 

411 color1_flux1 = df[self.color1_flux1].values*u.Unit(self.color1_flux1_units) 

412 color1_mag1 = color1_flux1.to(u.ABmag).value 

413 if self.color1_flux1_err: 

414 color1_mag1_err = k*df[self.color1_flux1_err].values/df[self.color1_flux1].values 

415 else: 

416 color1_mag1_err = 0.0 

417 

418 color1_flux2 = df[self.color1_flux2].values*u.Unit(self.color1_flux2_units) 

419 color1_mag2 = color1_flux2.to(u.ABmag).value 

420 if self.color1_flux2_err: 

421 color1_mag2_err = k*df[self.color1_flux2_err].values/df[self.color1_flux2].values 

422 else: 

423 color1_mag2_err = 0.0 

424 

425 color2_flux1 = df[self.color2_flux1].values*u.Unit(self.color2_flux1_units) 

426 color2_mag1 = color2_flux1.to(u.ABmag).value 

427 if self.color2_flux1_err: 

428 color2_mag1_err = k*df[self.color2_flux1_err].values/df[self.color2_flux1].values 

429 else: 

430 color2_mag1_err = 0.0 

431 

432 color2_flux2 = df[self.color2_flux2].values*u.Unit(self.color2_flux2_units) 

433 color2_mag2 = color2_flux2.to(u.ABmag).value 

434 if self.color2_flux2_err: 

435 color2_mag2_err = k*df[self.color2_flux2_err].values/df[self.color2_flux2].values 

436 else: 

437 color2_mag2_err = 0.0 

438 

439 color1 = color1_mag1 - color1_mag2 

440 err1_sq = color1_mag1_err**2. + color1_mag2_err**2. 

441 color2 = color2_mag1 - color2_mag2 

442 err2_sq = color2_mag1_err**2. + color2_mag2_err**2. 

443 

444 color_diff = color1 - color2 

445 

446 pull = color_diff/np.sqrt(err1_sq + err2_sq) 

447 

448 return pull