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 ap_association. 

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"""Plugins for use in DiaSource summary statistics. 

23 

24Output columns must be 

25as defined in the schema of the Apdb both in name and units. 

26""" 

27 

28import functools 

29import warnings 

30 

31from astropy.stats import median_absolute_deviation 

32import numpy as np 

33import pandas as pd 

34from scipy.optimize import lsq_linear 

35 

36import lsst.geom as geom 

37import lsst.pex.config as pexConfig 

38import lsst.sphgeom as sphgeom 

39 

40from .diaCalculation import ( 

41 DiaObjectCalculationPluginConfig, 

42 DiaObjectCalculationPlugin) 

43from lsst.meas.base.pluginRegistry import register 

44 

45__all__ = ("MeanDiaPositionConfig", "MeanDiaPosition", 

46 "HTMIndexDiaPosition", "HTMIndexDiaPositionConfig", 

47 "NumDiaSourcesDiaPlugin", "NumDiaSourcesDiaPluginConfig", 

48 "SimpleSourceFlagDiaPlugin", "SimpleSourceFlagDiaPluginConfig", 

49 "WeightedMeanDiaPsFluxConfig", "WeightedMeanDiaPsFlux", 

50 "PercentileDiaPsFlux", "PercentileDiaPsFluxConfig", 

51 "SigmaDiaPsFlux", "SigmaDiaPsFluxConfig", 

52 "Chi2DiaPsFlux", "Chi2DiaPsFluxConfig", 

53 "MadDiaPsFlux", "MadDiaPsFluxConfig", 

54 "SkewDiaPsFlux", "SkewDiaPsFluxConfig", 

55 "MinMaxDiaPsFlux", "MinMaxDiaPsFluxConfig", 

56 "MaxSlopeDiaPsFlux", "MaxSlopeDiaPsFluxConfig", 

57 "ErrMeanDiaPsFlux", "ErrMeanDiaPsFluxConfig", 

58 "LinearFitDiaPsFlux", "LinearFitDiaPsFluxConfig", 

59 "StetsonJDiaPsFlux", "StetsonJDiaPsFluxConfig", 

60 "WeightedMeanDiaTotFlux", "WeightedMeanDiaTotFluxConfig", 

61 "SigmaDiaTotFlux", "SigmaDiaTotFluxConfig") 

62 

63 

64def catchWarnings(_func=None, *, warns=[]): 

65 """Decorator for generically catching numpy warnings. 

66 """ 

67 def decoratorCatchWarnings(func): 

68 @functools.wraps(func) 

69 def wrapperCatchWarnings(*args, **kwargs): 

70 with warnings.catch_warnings(): 

71 for val in warns: 

72 warnings.filterwarnings("ignore", val) 

73 return func(*args, **kwargs) 

74 return wrapperCatchWarnings 

75 

76 if _func is None: 76 ↛ 79line 76 didn't jump to line 79, because the condition on line 76 was never false

77 return decoratorCatchWarnings 

78 else: 

79 return decoratorCatchWarnings(_func) 

80 

81 

82class MeanDiaPositionConfig(DiaObjectCalculationPluginConfig): 

83 pass 

84 

85 

86@register("ap_meanPosition") 

87class MeanDiaPosition(DiaObjectCalculationPlugin): 

88 """Compute the mean position of a DiaObject given a set of DiaSources. 

89 """ 

90 

91 ConfigClass = MeanDiaPositionConfig 

92 

93 plugType = 'multi' 

94 

95 outputCols = ["ra", "decl", "radecTai"] 

96 

97 @classmethod 

98 def getExecutionOrder(cls): 

99 return cls.DEFAULT_CATALOGCALCULATION 

100 

101 def calculate(self, diaObjects, diaSources, **kwargs): 

102 """Compute the mean ra/dec position of the diaObject given the 

103 diaSource locations. 

104 

105 Parameters 

106 ---------- 

107 diaObjects : `pandas.DataFrame` 

108 Summary objects to store values in. 

109 diaSources : `pandas.DataFrame` or `pandas.DataFrameGroupBy` 

110 Catalog of DiaSources summarized by this DiaObject. 

111 **kwargs 

112 Any additional keyword arguments that may be passed to the plugin. 

113 """ 

114 for outCol in self.outputCols: 

115 if outCol not in diaObjects.columns: 

116 diaObjects[outCol] = np.nan 

117 

118 def _computeMeanPos(df): 

119 aveCoord = geom.averageSpherePoint( 

120 list(geom.SpherePoint(src["ra"], src["decl"], geom.degrees) 

121 for idx, src in df.iterrows())) 

122 ra = aveCoord.getRa().asDegrees() 

123 decl = aveCoord.getDec().asDegrees() 

124 if np.isnan(ra) or np.isnan(decl): 

125 radecTai = np.nan 

126 else: 

127 radecTai = df["midPointTai"].max() 

128 

129 return pd.Series({"ra": aveCoord.getRa().asDegrees(), 

130 "decl": aveCoord.getDec().asDegrees(), 

131 "radecTai": radecTai}) 

132 

133 ans = diaSources.apply(_computeMeanPos) 

134 diaObjects.loc[:, ["ra", "decl", "radecTai"]] = ans 

135 

136 

137class HTMIndexDiaPositionConfig(DiaObjectCalculationPluginConfig): 

138 

139 htmLevel = pexConfig.Field( 

140 dtype=int, 

141 doc="Level of the HTM pixelization.", 

142 default=20, 

143 ) 

144 

145 

146@register("ap_HTMIndex") 

147class HTMIndexDiaPosition(DiaObjectCalculationPlugin): 

148 """Compute the mean position of a DiaObject given a set of DiaSources. 

149 """ 

150 ConfigClass = HTMIndexDiaPositionConfig 

151 

152 plugType = 'single' 

153 

154 inputCols = ["ra", "decl"] 

155 outputCols = ["pixelId"] 

156 

157 def __init__(self, config, name, metadata): 

158 DiaObjectCalculationPlugin.__init__(self, config, name, metadata) 

159 self.pixelator = sphgeom.HtmPixelization(self.config.htmLevel) 

160 

161 @classmethod 

162 def getExecutionOrder(cls): 

163 return cls.FLUX_MOMENTS_CALCULATED 

164 

165 def calculate(self, diaObjects, diaObjectId, **kwargs): 

166 """Compute the mean position of a DiaObject given a set of DiaSources 

167 

168 Parameters 

169 ---------- 

170 diaObjects : `pandas.dataFrame` 

171 Summary objects to store values in and read ra/decl from. 

172 diaObjectId : `int` 

173 Id of the diaObject to update. 

174 **kwargs 

175 Any additional keyword arguments that may be passed to the plugin. 

176 """ 

177 sphPoint = geom.SpherePoint( 

178 diaObjects.at[diaObjectId, "ra"] * geom.degrees, 

179 diaObjects.at[diaObjectId, "decl"] * geom.degrees) 

180 diaObjects.at[diaObjectId, "pixelId"] = self.pixelator.index( 

181 sphPoint.getVector()) 

182 

183 

184class NumDiaSourcesDiaPluginConfig(DiaObjectCalculationPluginConfig): 

185 pass 

186 

187 

188@register("ap_nDiaSources") 

189class NumDiaSourcesDiaPlugin(DiaObjectCalculationPlugin): 

190 """Compute the total number of DiaSources associated with this DiaObject. 

191 """ 

192 

193 ConfigClass = NumDiaSourcesDiaPluginConfig 

194 outputCols = ["nDiaSources"] 

195 plugType = "multi" 

196 

197 @classmethod 

198 def getExecutionOrder(cls): 

199 return cls.DEFAULT_CATALOGCALCULATION 

200 

201 def calculate(self, diaObjects, diaSources, **kwargs): 

202 """Compute the total number of DiaSources associated with this DiaObject. 

203 

204 Parameters 

205 ---------- 

206 diaObject : `dict` 

207 Summary object to store values in and read ra/decl from. 

208 **kwargs 

209 Any additional keyword arguments that may be passed to the plugin. 

210 """ 

211 diaObjects.loc[:, "nDiaSources"] = diaSources.diaObjectId.count() 

212 

213 

214class SimpleSourceFlagDiaPluginConfig(DiaObjectCalculationPluginConfig): 

215 pass 

216 

217 

218@register("ap_diaObjectFlag") 

219class SimpleSourceFlagDiaPlugin(DiaObjectCalculationPlugin): 

220 """Find if any DiaSource is flagged. 

221 

222 Set the DiaObject flag if any DiaSource is flagged. 

223 """ 

224 

225 ConfigClass = NumDiaSourcesDiaPluginConfig 

226 outputCols = ["flags"] 

227 plugType = "multi" 

228 

229 @classmethod 

230 def getExecutionOrder(cls): 

231 return cls.DEFAULT_CATALOGCALCULATION 

232 

233 def calculate(self, diaObjects, diaSources, **kwargs): 

234 """Find if any DiaSource is flagged. 

235 

236 Set the DiaObject flag if any DiaSource is flagged. 

237 

238 Parameters 

239 ---------- 

240 diaObject : `dict` 

241 Summary object to store values in and read ra/decl from. 

242 **kwargs 

243 Any additional keyword arguments that may be passed to the plugin. 

244 """ 

245 diaObjects.loc[:, "flags"] = diaSources.flags.any() 

246 

247 

248class WeightedMeanDiaPsFluxConfig(DiaObjectCalculationPluginConfig): 

249 pass 

250 

251 

252@register("ap_meanFlux") 

253class WeightedMeanDiaPsFlux(DiaObjectCalculationPlugin): 

254 """Compute the weighted mean and mean error on the point source fluxes 

255 of the DiaSource measured on the difference image. 

256 

257 Additionally store number of usable data points. 

258 """ 

259 

260 ConfigClass = WeightedMeanDiaPsFluxConfig 

261 outputCols = ["PSFluxMean", "PSFluxMeanErr", "PSFluxNdata"] 

262 plugType = "multi" 

263 

264 @classmethod 

265 def getExecutionOrder(cls): 

266 return cls.DEFAULT_CATALOGCALCULATION 

267 

268 @catchWarnings(warns=["invalid value encountered", 

269 "divide by zero"]) 

270 def calculate(self, 

271 diaObjects, 

272 diaSources, 

273 filterDiaSources, 

274 filterName, 

275 **kwargs): 

276 """Compute the weighted mean and mean error of the point source flux. 

277 

278 Parameters 

279 ---------- 

280 diaObject : `dict` 

281 Summary object to store values in. 

282 diaSources : `pandas.DataFrame` 

283 DataFrame representing all diaSources associated with this 

284 diaObject. 

285 filterDiaSources : `pandas.DataFrame` 

286 DataFrame representing diaSources associated with this 

287 diaObject that are observed in the band pass ``filterName``. 

288 filterName : `str` 

289 Simple, string name of the filter for the flux being calculated. 

290 **kwargs 

291 Any additional keyword arguments that may be passed to the plugin. 

292 """ 

293 meanName = "{}PSFluxMean".format(filterName) 

294 errName = "{}PSFluxMeanErr".format(filterName) 

295 nDataName = "{}PSFluxNdata".format(filterName) 

296 if meanName not in diaObjects.columns: 

297 diaObjects[meanName] = np.nan 

298 if errName not in diaObjects.columns: 

299 diaObjects[errName] = np.nan 

300 if nDataName not in diaObjects.columns: 

301 diaObjects[nDataName] = np.nan 

302 

303 def _weightedMean(df): 

304 tmpDf = df[~np.logical_or(np.isnan(df["psFlux"]), 

305 np.isnan(df["psFluxErr"]))] 

306 tot_weight = np.nansum(1 / tmpDf["psFluxErr"] ** 2) 

307 fluxMean = np.nansum(tmpDf["psFlux"] 

308 / tmpDf["psFluxErr"] ** 2) 

309 fluxMean /= tot_weight 

310 fluxMeanErr = np.sqrt(1 / tot_weight) 

311 nFluxData = len(tmpDf) 

312 

313 return pd.Series({meanName: fluxMean, 

314 errName: fluxMeanErr, 

315 nDataName: nFluxData}) 

316 

317 diaObjects.loc[:, [meanName, errName, nDataName]] = \ 

318 filterDiaSources.apply(_weightedMean) 

319 

320 

321class PercentileDiaPsFluxConfig(DiaObjectCalculationPluginConfig): 

322 percentiles = pexConfig.ListField( 

323 dtype=int, 

324 default=[5, 25, 50, 75, 95], 

325 doc="Percentiles to calculate to compute values for. Should be " 

326 "integer values." 

327 ) 

328 

329 

330@register("ap_percentileFlux") 

331class PercentileDiaPsFlux(DiaObjectCalculationPlugin): 

332 """Compute percentiles of diaSource fluxes. 

333 """ 

334 

335 ConfigClass = PercentileDiaPsFluxConfig 

336 # Output columns are created upon instantiation of the class. 

337 outputCols = [] 

338 plugType = "multi" 

339 

340 def __init__(self, config, name, metadata, **kwargs): 

341 DiaObjectCalculationPlugin.__init__(self, 

342 config, 

343 name, 

344 metadata, 

345 **kwargs) 

346 self.outputCols = ["PSFluxPercentile{:02d}".format(percent) 

347 for percent in self.config.percentiles] 

348 

349 @classmethod 

350 def getExecutionOrder(cls): 

351 return cls.DEFAULT_CATALOGCALCULATION 

352 

353 @catchWarnings(warns=["All-NaN slice encountered"]) 

354 def calculate(self, 

355 diaObjects, 

356 diaSources, 

357 filterDiaSources, 

358 filterName, 

359 **kwargs): 

360 """Compute the percentile fluxes of the point source flux. 

361 

362 Parameters 

363 ---------- 

364 diaObject : `dict` 

365 Summary object to store values in. 

366 diaSources : `pandas.DataFrame` 

367 DataFrame representing all diaSources associated with this 

368 diaObject. 

369 filterDiaSources : `pandas.DataFrame` 

370 DataFrame representing diaSources associated with this 

371 diaObject that are observed in the band pass ``filterName``. 

372 filterName : `str` 

373 Simple, string name of the filter for the flux being calculated. 

374 **kwargs 

375 Any additional keyword arguments that may be passed to the plugin. 

376 """ 

377 pTileNames = [] 

378 for tilePercent in self.config.percentiles: 

379 pTileName = "{}PSFluxPercentile{:02d}".format(filterName, 

380 tilePercent) 

381 pTileNames.append(pTileName) 

382 if pTileName not in diaObjects.columns: 

383 diaObjects[pTileName] = np.nan 

384 

385 def _fluxPercentiles(df): 

386 pTiles = np.nanpercentile(df["psFlux"], self.config.percentiles) 

387 return pd.Series( 

388 dict((tileName, pTile) 

389 for tileName, pTile in zip(pTileNames, pTiles))) 

390 

391 diaObjects.loc[:, pTileNames] = filterDiaSources.apply(_fluxPercentiles) 

392 

393 

394class SigmaDiaPsFluxConfig(DiaObjectCalculationPluginConfig): 

395 pass 

396 

397 

398@register("ap_sigmaFlux") 

399class SigmaDiaPsFlux(DiaObjectCalculationPlugin): 

400 """Compute scatter of diaSource fluxes. 

401 """ 

402 

403 ConfigClass = SigmaDiaPsFluxConfig 

404 # Output columns are created upon instantiation of the class. 

405 outputCols = ["PSFluxSigma"] 

406 plugType = "multi" 

407 

408 @classmethod 

409 def getExecutionOrder(cls): 

410 return cls.DEFAULT_CATALOGCALCULATION 

411 

412 def calculate(self, 

413 diaObjects, 

414 diaSources, 

415 filterDiaSources, 

416 filterName, 

417 **kwargs): 

418 """Compute the sigma fluxes of the point source flux. 

419 

420 Parameters 

421 ---------- 

422 diaObject : `dict` 

423 Summary object to store values in. 

424 diaSources : `pandas.DataFrame` 

425 DataFrame representing all diaSources associated with this 

426 diaObject. 

427 filterDiaSources : `pandas.DataFrame` 

428 DataFrame representing diaSources associated with this 

429 diaObject that are observed in the band pass ``filterName``. 

430 filterName : `str` 

431 Simple, string name of the filter for the flux being calculated. 

432 **kwargs 

433 Any additional keyword arguments that may be passed to the plugin. 

434 """ 

435 # Set "delta degrees of freedom (ddf)" to 1 to calculate the unbiased 

436 # estimator of scatter (i.e. 'N - 1' instead of 'N'). 

437 diaObjects.loc[:, "{}PSFluxSigma".format(filterName)] = \ 

438 filterDiaSources.psFlux.std() 

439 

440 

441class Chi2DiaPsFluxConfig(DiaObjectCalculationPluginConfig): 

442 pass 

443 

444 

445@register("ap_chi2Flux") 

446class Chi2DiaPsFlux(DiaObjectCalculationPlugin): 

447 """Compute chi2 of diaSource fluxes. 

448 """ 

449 

450 ConfigClass = Chi2DiaPsFluxConfig 

451 

452 # Required input Cols 

453 inputCols = ["PSFluxMean"] 

454 # Output columns are created upon instantiation of the class. 

455 outputCols = ["PSFluxChi2"] 

456 plugType = "multi" 

457 

458 @classmethod 

459 def getExecutionOrder(cls): 

460 return cls.FLUX_MOMENTS_CALCULATED 

461 

462 @catchWarnings(warns=["All-NaN slice encountered"]) 

463 def calculate(self, 

464 diaObjects, 

465 diaSources, 

466 filterDiaSources, 

467 filterName, 

468 **kwargs): 

469 """Compute the chi2 of the point source fluxes. 

470 

471 Parameters 

472 ---------- 

473 diaObject : `dict` 

474 Summary object to store values in. 

475 diaSources : `pandas.DataFrame` 

476 DataFrame representing all diaSources associated with this 

477 diaObject. 

478 filterDiaSources : `pandas.DataFrame` 

479 DataFrame representing diaSources associated with this 

480 diaObject that are observed in the band pass ``filterName``. 

481 filterName : `str` 

482 Simple, string name of the filter for the flux being calculated. 

483 **kwargs 

484 Any additional keyword arguments that may be passed to the plugin. 

485 """ 

486 meanName = "{}PSFluxMean".format(filterName) 

487 

488 def _chi2(df): 

489 delta = (df["psFlux"] 

490 - diaObjects.at[df.diaObjectId.iat[0], meanName]) 

491 return np.nansum((delta / df["psFluxErr"]) ** 2) 

492 

493 diaObjects.loc[:, "{}PSFluxChi2".format(filterName)] = \ 

494 filterDiaSources.apply(_chi2) 

495 

496 

497class MadDiaPsFluxConfig(DiaObjectCalculationPluginConfig): 

498 pass 

499 

500 

501@register("ap_madFlux") 

502class MadDiaPsFlux(DiaObjectCalculationPlugin): 

503 """Compute median absolute deviation of diaSource fluxes. 

504 """ 

505 

506 ConfigClass = MadDiaPsFluxConfig 

507 

508 # Required input Cols 

509 # Output columns are created upon instantiation of the class. 

510 outputCols = ["PSFluxMAD"] 

511 plugType = "multi" 

512 

513 @classmethod 

514 def getExecutionOrder(cls): 

515 return cls.DEFAULT_CATALOGCALCULATION 

516 

517 @catchWarnings(warns=["All-NaN slice encountered"]) 

518 def calculate(self, 

519 diaObjects, 

520 diaSources, 

521 filterDiaSources, 

522 filterName, 

523 **kwargs): 

524 """Compute the median absolute deviation of the point source fluxes. 

525 

526 Parameters 

527 ---------- 

528 diaObject : `dict` 

529 Summary object to store values in. 

530 diaSources : `pandas.DataFrame` 

531 DataFrame representing all diaSources associated with this 

532 diaObject. 

533 filterDiaSources : `pandas.DataFrame` 

534 DataFrame representing diaSources associated with this 

535 diaObject that are observed in the band pass ``filterName``. 

536 filterName : `str` 

537 Simple, string name of the filter for the flux being calculated. 

538 **kwargs 

539 Any additional keyword arguments that may be passed to the plugin. 

540 """ 

541 diaObjects.loc[:, "{}PSFluxMAD".format(filterName)] = \ 

542 filterDiaSources.psFlux.apply(median_absolute_deviation, 

543 ignore_nan=True) 

544 

545 

546class SkewDiaPsFluxConfig(DiaObjectCalculationPluginConfig): 

547 pass 

548 

549 

550@register("ap_skewFlux") 

551class SkewDiaPsFlux(DiaObjectCalculationPlugin): 

552 """Compute the skew of diaSource fluxes. 

553 """ 

554 

555 ConfigClass = SkewDiaPsFluxConfig 

556 

557 # Required input Cols 

558 # Output columns are created upon instantiation of the class. 

559 outputCols = ["PSFluxSkew"] 

560 plugType = "multi" 

561 

562 @classmethod 

563 def getExecutionOrder(cls): 

564 return cls.DEFAULT_CATALOGCALCULATION 

565 

566 def calculate(self, 

567 diaObjects, 

568 diaSources, 

569 filterDiaSources, 

570 filterName, 

571 **kwargs): 

572 """Compute the skew of the point source fluxes. 

573 

574 Parameters 

575 ---------- 

576 diaObject : `dict` 

577 Summary object to store values in. 

578 diaSources : `pandas.DataFrame` 

579 DataFrame representing all diaSources associated with this 

580 diaObject. 

581 filterDiaSources : `pandas.DataFrame` 

582 DataFrame representing diaSources associated with this 

583 diaObject that are observed in the band pass ``filterName``. 

584 filterName : `str` 

585 Simple, string name of the filter for the flux being calculated. 

586 **kwargs 

587 Any additional keyword arguments that may be passed to the plugin. 

588 """ 

589 diaObjects.loc[:, "{}PSFluxSkew".format(filterName)] = \ 

590 filterDiaSources.psFlux.skew() 

591 

592 

593class MinMaxDiaPsFluxConfig(DiaObjectCalculationPluginConfig): 

594 pass 

595 

596 

597@register("ap_minMaxFlux") 

598class MinMaxDiaPsFlux(DiaObjectCalculationPlugin): 

599 """Compute min/max of diaSource fluxes. 

600 """ 

601 

602 ConfigClass = MinMaxDiaPsFluxConfig 

603 

604 # Required input Cols 

605 # Output columns are created upon instantiation of the class. 

606 outputCols = ["PSFluxMin", "PSFluxMax"] 

607 plugType = "multi" 

608 

609 @classmethod 

610 def getExecutionOrder(cls): 

611 return cls.DEFAULT_CATALOGCALCULATION 

612 

613 def calculate(self, 

614 diaObjects, 

615 diaSources, 

616 filterDiaSources, 

617 filterName, 

618 **kwargs): 

619 """Compute min/max of the point source fluxes. 

620 

621 Parameters 

622 ---------- 

623 diaObject : `dict` 

624 Summary object to store values in. 

625 diaSources : `pandas.DataFrame` 

626 DataFrame representing all diaSources associated with this 

627 diaObject. 

628 filterDiaSources : `pandas.DataFrame` 

629 DataFrame representing diaSources associated with this 

630 diaObject that are observed in the band pass ``filterName``. 

631 filterName : `str` 

632 Simple, string name of the filter for the flux being calculated. 

633 **kwargs 

634 Any additional keyword arguments that may be passed to the plugin. 

635 """ 

636 minName = "{}PSFluxMin".format(filterName) 

637 if minName not in diaObjects.columns: 

638 diaObjects[minName] = np.nan 

639 maxName = "{}PSFluxMax".format(filterName) 

640 if maxName not in diaObjects.columns: 

641 diaObjects[maxName] = np.nan 

642 

643 diaObjects.loc[:, minName] = filterDiaSources.psFlux.min() 

644 diaObjects.loc[:, maxName] = filterDiaSources.psFlux.max() 

645 

646 

647class MaxSlopeDiaPsFluxConfig(DiaObjectCalculationPluginConfig): 

648 pass 

649 

650 

651@register("ap_maxSlopeFlux") 

652class MaxSlopeDiaPsFlux(DiaObjectCalculationPlugin): 

653 """Compute the maximum ratio time ordered deltaFlux / deltaTime. 

654 """ 

655 

656 ConfigClass = MinMaxDiaPsFluxConfig 

657 

658 # Required input Cols 

659 # Output columns are created upon instantiation of the class. 

660 outputCols = ["PSFluxMaxSlope"] 

661 plugType = "multi" 

662 

663 @classmethod 

664 def getExecutionOrder(cls): 

665 return cls.DEFAULT_CATALOGCALCULATION 

666 

667 def calculate(self, 

668 diaObjects, 

669 diaSources, 

670 filterDiaSources, 

671 filterName, 

672 **kwargs): 

673 """Compute the maximum ratio time ordered deltaFlux / deltaTime. 

674 

675 Parameters 

676 ---------- 

677 diaObject : `dict` 

678 Summary object to store values in. 

679 diaSources : `pandas.DataFrame` 

680 DataFrame representing all diaSources associated with this 

681 diaObject. 

682 filterDiaSources : `pandas.DataFrame` 

683 DataFrame representing diaSources associated with this 

684 diaObject that are observed in the band pass ``filterName``. 

685 filterName : `str` 

686 Simple, string name of the filter for the flux being calculated. 

687 **kwargs 

688 Any additional keyword arguments that may be passed to the plugin. 

689 """ 

690 

691 def _maxSlope(df): 

692 tmpDf = df[~np.logical_or(np.isnan(df["psFlux"]), 

693 np.isnan(df["midPointTai"]))] 

694 if len(tmpDf) < 2: 

695 return np.nan 

696 times = tmpDf["midPointTai"].to_numpy() 

697 timeArgs = times.argsort() 

698 times = times[timeArgs] 

699 fluxes = tmpDf["psFlux"].to_numpy()[timeArgs] 

700 return (np.diff(fluxes) / np.diff(times)).max() 

701 

702 diaObjects.loc[:, "{}PSFluxMaxSlope".format(filterName)] = \ 

703 filterDiaSources.apply(_maxSlope) 

704 

705 

706class ErrMeanDiaPsFluxConfig(DiaObjectCalculationPluginConfig): 

707 pass 

708 

709 

710@register("ap_meanErrFlux") 

711class ErrMeanDiaPsFlux(DiaObjectCalculationPlugin): 

712 """Compute the mean of the dia source errors. 

713 """ 

714 

715 ConfigClass = ErrMeanDiaPsFluxConfig 

716 

717 # Required input Cols 

718 # Output columns are created upon instantiation of the class. 

719 outputCols = ["PSFluxErrMean"] 

720 plugType = "multi" 

721 

722 @classmethod 

723 def getExecutionOrder(cls): 

724 return cls.DEFAULT_CATALOGCALCULATION 

725 

726 def calculate(self, 

727 diaObjects, 

728 diaSources, 

729 filterDiaSources, 

730 filterName, 

731 **kwargs): 

732 """Compute the mean of the dia source errors. 

733 

734 Parameters 

735 ---------- 

736 diaObject : `dict` 

737 Summary object to store values in. 

738 diaSources : `pandas.DataFrame` 

739 DataFrame representing all diaSources associated with this 

740 diaObject. 

741 filterDiaSources : `pandas.DataFrame` 

742 DataFrame representing diaSources associated with this 

743 diaObject that are observed in the band pass ``filterName``. 

744 filterName : `str` 

745 Simple, string name of the filter for the flux being calculated. 

746 **kwargs 

747 Any additional keyword arguments that may be passed to the plugin. 

748 """ 

749 diaObjects.loc[:, "{}PSFluxErrMean".format(filterName)] = \ 

750 filterDiaSources.psFluxErr.mean() 

751 

752 

753class LinearFitDiaPsFluxConfig(DiaObjectCalculationPluginConfig): 

754 pass 

755 

756 

757@register("ap_linearFit") 

758class LinearFitDiaPsFlux(DiaObjectCalculationPlugin): 

759 """Compute fit a linear model to flux vs time. 

760 """ 

761 

762 ConfigClass = LinearFitDiaPsFluxConfig 

763 

764 # Required input Cols 

765 # Output columns are created upon instantiation of the class. 

766 outputCols = ["PSFluxLinearSlope", "PSFluxLinearIntercept"] 

767 plugType = "multi" 

768 

769 @classmethod 

770 def getExecutionOrder(cls): 

771 return cls.DEFAULT_CATALOGCALCULATION 

772 

773 def calculate(self, 

774 diaObjects, 

775 diaSources, 

776 filterDiaSources, 

777 filterName, 

778 **kwargs): 

779 """Compute fit a linear model to flux vs time. 

780 

781 Parameters 

782 ---------- 

783 diaObject : `dict` 

784 Summary object to store values in. 

785 diaSources : `pandas.DataFrame` 

786 DataFrame representing all diaSources associated with this 

787 diaObject. 

788 filterDiaSources : `pandas.DataFrame` 

789 DataFrame representing diaSources associated with this 

790 diaObject that are observed in the band pass ``filterName``. 

791 filterName : `str` 

792 Simple, string name of the filter for the flux being calculated. 

793 **kwargs 

794 Any additional keyword arguments that may be passed to the plugin. 

795 """ 

796 

797 mName = "{}PSFluxLinearSlope".format(filterName) 

798 if mName not in diaObjects.columns: 

799 diaObjects[mName] = np.nan 

800 bName = "{}PSFluxLinearIntercept".format(filterName) 

801 if bName not in diaObjects.columns: 

802 diaObjects[bName] = np.nan 

803 

804 def _linearFit(df): 

805 tmpDf = df[~np.logical_or( 

806 np.isnan(df["psFlux"]), 

807 np.logical_or(np.isnan(df["psFluxErr"]), 

808 np.isnan(df["midPointTai"])))] 

809 if len(tmpDf) < 2: 

810 return pd.Series({mName: np.nan, bName: np.nan}) 

811 fluxes = tmpDf["psFlux"].to_numpy() 

812 errors = tmpDf["psFluxErr"].to_numpy() 

813 times = tmpDf["midPointTai"].to_numpy() 

814 A = np.array([times / errors, 1 / errors]).transpose() 

815 m, b = lsq_linear(A, fluxes / errors).x 

816 return pd.Series({mName: m, bName: b}) 

817 

818 diaObjects.loc[:, [mName, bName]] = filterDiaSources.apply(_linearFit) 

819 

820 

821class StetsonJDiaPsFluxConfig(DiaObjectCalculationPluginConfig): 

822 pass 

823 

824 

825@register("ap_stetsonJ") 

826class StetsonJDiaPsFlux(DiaObjectCalculationPlugin): 

827 """Compute the StetsonJ statistic on the DIA point source fluxes. 

828 """ 

829 

830 ConfigClass = LinearFitDiaPsFluxConfig 

831 

832 # Required input Cols 

833 inputCols = ["PSFluxMean"] 

834 # Output columns are created upon instantiation of the class. 

835 outputCols = ["PSFluxStetsonJ"] 

836 plugType = "multi" 

837 

838 @classmethod 

839 def getExecutionOrder(cls): 

840 return cls.FLUX_MOMENTS_CALCULATED 

841 

842 def calculate(self, 

843 diaObjects, 

844 diaSources, 

845 filterDiaSources, 

846 filterName, 

847 **kwargs): 

848 """Compute the StetsonJ statistic on the DIA point source fluxes. 

849 

850 Parameters 

851 ---------- 

852 diaObject : `dict` 

853 Summary object to store values in. 

854 diaSources : `pandas.DataFrame` 

855 DataFrame representing all diaSources associated with this 

856 diaObject. 

857 filterDiaSources : `pandas.DataFrame` 

858 DataFrame representing diaSources associated with this 

859 diaObject that are observed in the band pass ``filterName``. 

860 filterName : `str` 

861 Simple, string name of the filter for the flux being calculated. 

862 **kwargs 

863 Any additional keyword arguments that may be passed to the plugin. 

864 """ 

865 meanName = "{}PSFluxMean".format(filterName) 

866 

867 def _stetsonJ(df): 

868 tmpDf = df[~np.logical_or(np.isnan(df["psFlux"]), 

869 np.isnan(df["psFluxErr"]))] 

870 if len(tmpDf) < 2: 

871 return np.nan 

872 fluxes = tmpDf["psFlux"].to_numpy() 

873 errors = tmpDf["psFluxErr"].to_numpy() 

874 

875 return self._stetson_J( 

876 fluxes, 

877 errors, 

878 diaObjects.at[tmpDf.diaObjectId.iat[0], meanName]) 

879 

880 diaObjects.loc[:, "{}PSFluxStetsonJ".format(filterName)] = \ 

881 filterDiaSources.apply(_stetsonJ) 

882 

883 def _stetson_J(self, fluxes, errors, mean=None): 

884 """Compute the single band stetsonJ statistic. 

885 

886 Parameters 

887 ---------- 

888 fluxes : `numpy.ndarray` (N,) 

889 Calibrated lightcurve flux values. 

890 errors : `numpy.ndarray` (N,) 

891 Errors on the calibrated lightcurve fluxes. 

892 mean : `float` 

893 Starting mean from previous plugin. 

894 

895 Returns 

896 ------- 

897 stetsonJ : `float` 

898 stetsonJ statistic for the input fluxes and errors. 

899 

900 References 

901 ---------- 

902 .. [1] Stetson, P. B., "On the Automatic Determination of Light-Curve 

903 Parameters for Cepheid Variables", PASP, 108, 851S, 1996 

904 """ 

905 n_points = len(fluxes) 

906 flux_mean = self._stetson_mean(fluxes, errors, mean) 

907 delta_val = ( 

908 np.sqrt(n_points / (n_points - 1)) * (fluxes - flux_mean) / errors) 

909 p_k = delta_val ** 2 - 1 

910 

911 return np.mean(np.sign(p_k) * np.sqrt(np.fabs(p_k))) 

912 

913 def _stetson_mean(self, 

914 values, 

915 errors, 

916 mean=None, 

917 alpha=2., 

918 beta=2., 

919 n_iter=20, 

920 tol=1e-6): 

921 """Compute the stetson mean of the fluxes which down-weights outliers. 

922 

923 Weighted biased on an error weighted difference scaled by a constant 

924 (1/``a``) and raised to the power beta. Higher betas more harshly 

925 penalize outliers and ``a`` sets the number of sigma where a weighted 

926 difference of 1 occurs. 

927 

928 Parameters 

929 ---------- 

930 values : `numpy.dnarray`, (N,) 

931 Input values to compute the mean of. 

932 errors : `numpy.ndarray`, (N,) 

933 Errors on the input values. 

934 mean : `float` 

935 Starting mean value or None. 

936 alpha : `float` 

937 Scalar down-weighting of the fractional difference. lower->more 

938 clipping. (Default value is 2.) 

939 beta : `float` 

940 Power law slope of the used to down-weight outliers. higher->more 

941 clipping. (Default value is 2.) 

942 n_iter : `int` 

943 Number of iterations of clipping. 

944 tol : `float` 

945 Fractional and absolute tolerance goal on the change in the mean 

946 before exiting early. (Default value is 1e-6) 

947 

948 Returns 

949 ------- 

950 mean : `float` 

951 Weighted stetson mean result. 

952 

953 References 

954 ---------- 

955 .. [1] Stetson, P. B., "On the Automatic Determination of Light-Curve 

956 Parameters for Cepheid Variables", PASP, 108, 851S, 1996 

957 """ 

958 n_points = len(values) 

959 n_factor = np.sqrt(n_points / (n_points - 1)) 

960 inv_var = 1 / errors ** 2 

961 

962 if mean is None: 

963 mean = np.average(values, weights=inv_var) 

964 for iter_idx in range(n_iter): 

965 chi = np.fabs(n_factor * (values - mean) / errors) 

966 tmp_mean = np.average( 

967 values, 

968 weights=inv_var / (1 + (chi / alpha) ** beta)) 

969 diff = np.fabs(tmp_mean - mean) 

970 mean = tmp_mean 

971 if diff / mean < tol and diff < tol: 

972 break 

973 return mean 

974 

975 

976class WeightedMeanDiaTotFluxConfig(DiaObjectCalculationPluginConfig): 

977 pass 

978 

979 

980@register("ap_meanTotFlux") 

981class WeightedMeanDiaTotFlux(DiaObjectCalculationPlugin): 

982 """Compute the weighted mean and mean error on the point source fluxes 

983 forced photometered at the DiaSource location in the calibrated image. 

984 

985 Additionally store number of usable data points. 

986 """ 

987 

988 ConfigClass = WeightedMeanDiaPsFluxConfig 

989 outputCols = ["TOTFluxMean", "TOTFluxMeanErr"] 

990 plugType = "multi" 

991 

992 @classmethod 

993 def getExecutionOrder(cls): 

994 return cls.DEFAULT_CATALOGCALCULATION 

995 

996 @catchWarnings(warns=["invalid value encountered", 

997 "divide by zero"]) 

998 def calculate(self, 

999 diaObjects, 

1000 diaSources, 

1001 filterDiaSources, 

1002 filterName, 

1003 **kwargs): 

1004 """Compute the weighted mean and mean error of the point source flux. 

1005 

1006 Parameters 

1007 ---------- 

1008 diaObject : `dict` 

1009 Summary object to store values in. 

1010 diaSources : `pandas.DataFrame` 

1011 DataFrame representing all diaSources associated with this 

1012 diaObject. 

1013 filterDiaSources : `pandas.DataFrame` 

1014 DataFrame representing diaSources associated with this 

1015 diaObject that are observed in the band pass ``filterName``. 

1016 filterName : `str` 

1017 Simple, string name of the filter for the flux being calculated. 

1018 **kwargs 

1019 Any additional keyword arguments that may be passed to the plugin. 

1020 """ 

1021 totMeanName = "{}TOTFluxMean".format(filterName) 

1022 if totMeanName not in diaObjects.columns: 

1023 diaObjects[totMeanName] = np.nan 

1024 totErrName = "{}TOTFluxMeanErr".format(filterName) 

1025 if totErrName not in diaObjects.columns: 

1026 diaObjects[totErrName] = np.nan 

1027 

1028 def _meanFlux(df): 

1029 tmpDf = df[~np.logical_or(np.isnan(df["totFlux"]), 

1030 np.isnan(df["totFluxErr"]))] 

1031 tot_weight = np.nansum(1 / tmpDf["totFluxErr"] ** 2) 

1032 fluxMean = np.nansum(tmpDf["totFlux"] 

1033 / tmpDf["totFluxErr"] ** 2) 

1034 fluxMean /= tot_weight 

1035 fluxMeanErr = np.sqrt(1 / tot_weight) 

1036 

1037 return pd.Series({totMeanName: fluxMean, 

1038 totErrName: fluxMeanErr}) 

1039 

1040 diaObjects.loc[:, [totMeanName, totErrName]] = \ 

1041 filterDiaSources.apply(_meanFlux) 

1042 

1043 

1044class SigmaDiaTotFluxConfig(DiaObjectCalculationPluginConfig): 

1045 pass 

1046 

1047 

1048@register("ap_sigmaTotFlux") 

1049class SigmaDiaTotFlux(DiaObjectCalculationPlugin): 

1050 """Compute scatter of diaSource fluxes. 

1051 """ 

1052 

1053 ConfigClass = SigmaDiaPsFluxConfig 

1054 # Output columns are created upon instantiation of the class. 

1055 outputCols = ["TOTFluxSigma"] 

1056 plugType = "multi" 

1057 

1058 @classmethod 

1059 def getExecutionOrder(cls): 

1060 return cls.DEFAULT_CATALOGCALCULATION 

1061 

1062 def calculate(self, 

1063 diaObjects, 

1064 diaSources, 

1065 filterDiaSources, 

1066 filterName, 

1067 **kwargs): 

1068 """Compute the sigma fluxes of the point source flux measured on the 

1069 calibrated image. 

1070 

1071 Parameters 

1072 ---------- 

1073 diaObject : `dict` 

1074 Summary object to store values in. 

1075 diaSources : `pandas.DataFrame` 

1076 DataFrame representing all diaSources associated with this 

1077 diaObject. 

1078 filterDiaSources : `pandas.DataFrame` 

1079 DataFrame representing diaSources associated with this 

1080 diaObject that are observed in the band pass ``filterName``. 

1081 filterName : `str` 

1082 Simple, string name of the filter for the flux being calculated. 

1083 **kwargs 

1084 Any additional keyword arguments that may be passed to the plugin. 

1085 """ 

1086 # Set "delta degrees of freedom (ddf)" to 1 to calculate the unbiased 

1087 # estimator of scatter (i.e. 'N - 1' instead of 'N'). 

1088 diaObjects.loc[:, "{}TOTFluxSigma".format(filterName)] = \ 

1089 filterDiaSources.totFlux.std()