lsst.meas.base 23.0.0+1c9213783a
diaCalculationPlugins.py
Go to the documentation of this file.
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 .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:
77 return decoratorCatchWarnings
78 else:
79 return decoratorCatchWarnings(_func)
80
81
83 pass
84
85
86@register("ap_meanPosition")
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 needsFilter = False
97
98 @classmethod
100 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
101
102 def calculate(self, diaObjects, diaSources, **kwargs):
103 """Compute the mean ra/dec position of the diaObject given the
104 diaSource locations.
105
106 Parameters
107 ----------
108 diaObjects : `pandas.DataFrame`
109 Summary objects to store values in.
110 diaSources : `pandas.DataFrame` or `pandas.DataFrameGroupBy`
111 Catalog of DiaSources summarized by this DiaObject.
112 **kwargs
113 Any additional keyword arguments that may be passed to the plugin.
114 """
115 for outCol in self.outputColsoutputColsoutputCols:
116 if outCol not in diaObjects.columns:
117 diaObjects[outCol] = np.nan
118
119 def _computeMeanPos(df):
120 aveCoord = geom.averageSpherePoint(
121 list(geom.SpherePoint(src["ra"], src["decl"], geom.degrees)
122 for idx, src in df.iterrows()))
123 ra = aveCoord.getRa().asDegrees()
124 decl = aveCoord.getDec().asDegrees()
125 if np.isnan(ra) or np.isnan(decl):
126 radecTai = np.nan
127 else:
128 radecTai = df["midPointTai"].max()
129
130 return pd.Series({"ra": aveCoord.getRa().asDegrees(),
131 "decl": aveCoord.getDec().asDegrees(),
132 "radecTai": radecTai})
133
134 ans = diaSources.apply(_computeMeanPos)
135 diaObjects.loc[:, ["ra", "decl", "radecTai"]] = ans
136
137
139
140 htmLevel = pexConfig.Field(
141 dtype=int,
142 doc="Level of the HTM pixelization.",
143 default=20,
144 )
145
146
147@register("ap_HTMIndex")
149 """Compute the mean position of a DiaObject given a set of DiaSources.
150 """
151 ConfigClass = HTMIndexDiaPositionConfig
152
153 plugType = 'single'
154
155 inputCols = ["ra", "decl"]
156 outputCols = ["pixelId"]
157 needsFilter = False
158
159 def __init__(self, config, name, metadata):
160 DiaObjectCalculationPlugin.__init__(self, config, name, metadata)
161 self.pixelatorpixelator = sphgeom.HtmPixelization(self.configconfig.htmLevel)
162
163 @classmethod
165 return cls.FLUX_MOMENTS_CALCULATEDFLUX_MOMENTS_CALCULATED
166
167 def calculate(self, diaObjects, diaObjectId, **kwargs):
168 """Compute the mean position of a DiaObject given a set of DiaSources
169
170 Parameters
171 ----------
172 diaObjects : `pandas.dataFrame`
173 Summary objects to store values in and read ra/decl from.
174 diaObjectId : `int`
175 Id of the diaObject to update.
176 **kwargs
177 Any additional keyword arguments that may be passed to the plugin.
178 """
179 sphPoint = geom.SpherePoint(
180 diaObjects.at[diaObjectId, "ra"] * geom.degrees,
181 diaObjects.at[diaObjectId, "decl"] * geom.degrees)
182 diaObjects.at[diaObjectId, "pixelId"] = self.pixelatorpixelator.index(
183 sphPoint.getVector())
184
185
187 pass
188
189
190@register("ap_nDiaSources")
192 """Compute the total number of DiaSources associated with this DiaObject.
193 """
194
195 ConfigClass = NumDiaSourcesDiaPluginConfig
196 outputCols = ["nDiaSources"]
197 plugType = "multi"
198 needsFilter = False
199
200 @classmethod
202 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
203
204 def calculate(self, diaObjects, diaSources, **kwargs):
205 """Compute the total number of DiaSources associated with this DiaObject.
206
207 Parameters
208 ----------
209 diaObject : `dict`
210 Summary object to store values in and read ra/decl from.
211 **kwargs
212 Any additional keyword arguments that may be passed to the plugin.
213 """
214 diaObjects.loc[:, "nDiaSources"] = diaSources.diaObjectId.count()
215
216
218 pass
219
220
221@register("ap_diaObjectFlag")
223 """Find if any DiaSource is flagged.
224
225 Set the DiaObject flag if any DiaSource is flagged.
226 """
227
228 ConfigClass = NumDiaSourcesDiaPluginConfig
229 outputCols = ["flags"]
230 plugType = "multi"
231 needsFilter = False
232
233 @classmethod
235 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
236
237 def calculate(self, diaObjects, diaSources, **kwargs):
238 """Find if any DiaSource is flagged.
239
240 Set the DiaObject flag if any DiaSource is flagged.
241
242 Parameters
243 ----------
244 diaObject : `dict`
245 Summary object to store values in and read ra/decl from.
246 **kwargs
247 Any additional keyword arguments that may be passed to the plugin.
248 """
249 diaObjects.loc[:, "flags"] = diaSources.flags.any().astype(np.uint64)
250
251
253 pass
254
255
256@register("ap_meanFlux")
258 """Compute the weighted mean and mean error on the point source fluxes
259 of the DiaSource measured on the difference image.
260
261 Additionally store number of usable data points.
262 """
263
264 ConfigClass = WeightedMeanDiaPsFluxConfig
265 outputCols = ["PSFluxMean", "PSFluxMeanErr", "PSFluxNdata"]
266 plugType = "multi"
267 needsFilter = True
268
269 @classmethod
271 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
272
273 @catchWarnings(warns=["invalid value encountered",
274 "divide by zero"])
275 def calculate(self,
276 diaObjects,
277 diaSources,
278 filterDiaSources,
279 filterName,
280 **kwargs):
281 """Compute the weighted mean and mean error of the point source flux.
282
283 Parameters
284 ----------
285 diaObject : `dict`
286 Summary object to store values in.
287 diaSources : `pandas.DataFrame`
288 DataFrame representing all diaSources associated with this
289 diaObject.
290 filterDiaSources : `pandas.DataFrame`
291 DataFrame representing diaSources associated with this
292 diaObject that are observed in the band pass ``filterName``.
293 filterName : `str`
294 Simple, string name of the filter for the flux being calculated.
295 **kwargs
296 Any additional keyword arguments that may be passed to the plugin.
297 """
298 meanName = "{}PSFluxMean".format(filterName)
299 errName = "{}PSFluxMeanErr".format(filterName)
300 nDataName = "{}PSFluxNdata".format(filterName)
301 if meanName not in diaObjects.columns:
302 diaObjects[meanName] = np.nan
303 if errName not in diaObjects.columns:
304 diaObjects[errName] = np.nan
305 if nDataName not in diaObjects.columns:
306 diaObjects[nDataName] = 0
307
308 def _weightedMean(df):
309 tmpDf = df[~np.logical_or(np.isnan(df["psFlux"]),
310 np.isnan(df["psFluxErr"]))]
311 tot_weight = np.nansum(1 / tmpDf["psFluxErr"] ** 2)
312 fluxMean = np.nansum(tmpDf["psFlux"]
313 / tmpDf["psFluxErr"] ** 2)
314 fluxMean /= tot_weight
315 if tot_weight > 0:
316 fluxMeanErr = np.sqrt(1 / tot_weight)
317 else:
318 fluxMeanErr = np.nan
319 nFluxData = len(tmpDf)
320
321 return pd.Series({meanName: fluxMean,
322 errName: fluxMeanErr,
323 nDataName: nFluxData},
324 dtype="object")
325
326 diaObjects.loc[:, [meanName, errName, nDataName]] = \
327 filterDiaSources.apply(_weightedMean)
328
329
331 percentiles = pexConfig.ListField(
332 dtype=int,
333 default=[5, 25, 50, 75, 95],
334 doc="Percentiles to calculate to compute values for. Should be "
335 "integer values."
336 )
337
338
339@register("ap_percentileFlux")
341 """Compute percentiles of diaSource fluxes.
342 """
343
344 ConfigClass = PercentileDiaPsFluxConfig
345 # Output columns are created upon instantiation of the class.
346 outputCols = []
347 plugType = "multi"
348 needsFilter = True
349
350 def __init__(self, config, name, metadata, **kwargs):
351 DiaObjectCalculationPlugin.__init__(self,
352 config,
353 name,
354 metadata,
355 **kwargs)
356 self.outputColsoutputColsoutputColsoutputCols = ["PSFluxPercentile{:02d}".format(percent)
357 for percent in self.configconfig.percentiles]
358
359 @classmethod
361 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
362
363 @catchWarnings(warns=["All-NaN slice encountered"])
364 def calculate(self,
365 diaObjects,
366 diaSources,
367 filterDiaSources,
368 filterName,
369 **kwargs):
370 """Compute the percentile fluxes of the point source flux.
371
372 Parameters
373 ----------
374 diaObject : `dict`
375 Summary object to store values in.
376 diaSources : `pandas.DataFrame`
377 DataFrame representing all diaSources associated with this
378 diaObject.
379 filterDiaSources : `pandas.DataFrame`
380 DataFrame representing diaSources associated with this
381 diaObject that are observed in the band pass ``filterName``.
382 filterName : `str`
383 Simple, string name of the filter for the flux being calculated.
384 **kwargs
385 Any additional keyword arguments that may be passed to the plugin.
386 """
387 pTileNames = []
388 for tilePercent in self.configconfig.percentiles:
389 pTileName = "{}PSFluxPercentile{:02d}".format(filterName,
390 tilePercent)
391 pTileNames.append(pTileName)
392 if pTileName not in diaObjects.columns:
393 diaObjects[pTileName] = np.nan
394
395 def _fluxPercentiles(df):
396 pTiles = np.nanpercentile(df["psFlux"], self.configconfig.percentiles)
397 return pd.Series(
398 dict((tileName, pTile)
399 for tileName, pTile in zip(pTileNames, pTiles)))
400
401 diaObjects.loc[:, pTileNames] = filterDiaSources.apply(_fluxPercentiles)
402
403
405 pass
406
407
408@register("ap_sigmaFlux")
410 """Compute scatter of diaSource fluxes.
411 """
412
413 ConfigClass = SigmaDiaPsFluxConfig
414 # Output columns are created upon instantiation of the class.
415 outputCols = ["PSFluxSigma"]
416 plugType = "multi"
417 needsFilter = True
418
419 @classmethod
421 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
422
423 def calculate(self,
424 diaObjects,
425 diaSources,
426 filterDiaSources,
427 filterName,
428 **kwargs):
429 """Compute the sigma fluxes of the point source flux.
430
431 Parameters
432 ----------
433 diaObject : `dict`
434 Summary object to store values in.
435 diaSources : `pandas.DataFrame`
436 DataFrame representing all diaSources associated with this
437 diaObject.
438 filterDiaSources : `pandas.DataFrame`
439 DataFrame representing diaSources associated with this
440 diaObject that are observed in the band pass ``filterName``.
441 filterName : `str`
442 Simple, string name of the filter for the flux being calculated.
443 **kwargs
444 Any additional keyword arguments that may be passed to the plugin.
445 """
446 # Set "delta degrees of freedom (ddf)" to 1 to calculate the unbiased
447 # estimator of scatter (i.e. 'N - 1' instead of 'N').
448 diaObjects.loc[:, "{}PSFluxSigma".format(filterName)] = \
449 filterDiaSources.psFlux.std()
450
451
453 pass
454
455
456@register("ap_chi2Flux")
458 """Compute chi2 of diaSource fluxes.
459 """
460
461 ConfigClass = Chi2DiaPsFluxConfig
462
463 # Required input Cols
464 inputCols = ["PSFluxMean"]
465 # Output columns are created upon instantiation of the class.
466 outputCols = ["PSFluxChi2"]
467 plugType = "multi"
468 needsFilter = True
469
470 @classmethod
472 return cls.FLUX_MOMENTS_CALCULATEDFLUX_MOMENTS_CALCULATED
473
474 @catchWarnings(warns=["All-NaN slice encountered"])
475 def calculate(self,
476 diaObjects,
477 diaSources,
478 filterDiaSources,
479 filterName,
480 **kwargs):
481 """Compute the chi2 of the point source fluxes.
482
483 Parameters
484 ----------
485 diaObject : `dict`
486 Summary object to store values in.
487 diaSources : `pandas.DataFrame`
488 DataFrame representing all diaSources associated with this
489 diaObject.
490 filterDiaSources : `pandas.DataFrame`
491 DataFrame representing diaSources associated with this
492 diaObject that are observed in the band pass ``filterName``.
493 filterName : `str`
494 Simple, string name of the filter for the flux being calculated.
495 **kwargs
496 Any additional keyword arguments that may be passed to the plugin.
497 """
498 meanName = "{}PSFluxMean".format(filterName)
499
500 def _chi2(df):
501 delta = (df["psFlux"]
502 - diaObjects.at[df.diaObjectId.iat[0], meanName])
503 return np.nansum((delta / df["psFluxErr"]) ** 2)
504
505 diaObjects.loc[:, "{}PSFluxChi2".format(filterName)] = \
506 filterDiaSources.apply(_chi2)
507
508
510 pass
511
512
513@register("ap_madFlux")
515 """Compute median absolute deviation of diaSource fluxes.
516 """
517
518 ConfigClass = MadDiaPsFluxConfig
519
520 # Required input Cols
521 # Output columns are created upon instantiation of the class.
522 outputCols = ["PSFluxMAD"]
523 plugType = "multi"
524 needsFilter = True
525
526 @classmethod
528 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
529
530 @catchWarnings(warns=["All-NaN slice encountered"])
531 def calculate(self,
532 diaObjects,
533 diaSources,
534 filterDiaSources,
535 filterName,
536 **kwargs):
537 """Compute the median absolute deviation of the point source fluxes.
538
539 Parameters
540 ----------
541 diaObject : `dict`
542 Summary object to store values in.
543 diaSources : `pandas.DataFrame`
544 DataFrame representing all diaSources associated with this
545 diaObject.
546 filterDiaSources : `pandas.DataFrame`
547 DataFrame representing diaSources associated with this
548 diaObject that are observed in the band pass ``filterName``.
549 filterName : `str`
550 Simple, string name of the filter for the flux being calculated.
551 **kwargs
552 Any additional keyword arguments that may be passed to the plugin.
553 """
554 diaObjects.loc[:, "{}PSFluxMAD".format(filterName)] = \
555 filterDiaSources.psFlux.apply(median_absolute_deviation,
556 ignore_nan=True)
557
558
560 pass
561
562
563@register("ap_skewFlux")
565 """Compute the skew of diaSource fluxes.
566 """
567
568 ConfigClass = SkewDiaPsFluxConfig
569
570 # Required input Cols
571 # Output columns are created upon instantiation of the class.
572 outputCols = ["PSFluxSkew"]
573 plugType = "multi"
574 needsFilter = True
575
576 @classmethod
578 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
579
580 def calculate(self,
581 diaObjects,
582 diaSources,
583 filterDiaSources,
584 filterName,
585 **kwargs):
586 """Compute the skew of the point source fluxes.
587
588 Parameters
589 ----------
590 diaObject : `dict`
591 Summary object to store values in.
592 diaSources : `pandas.DataFrame`
593 DataFrame representing all diaSources associated with this
594 diaObject.
595 filterDiaSources : `pandas.DataFrame`
596 DataFrame representing diaSources associated with this
597 diaObject that are observed in the band pass ``filterName``.
598 filterName : `str`
599 Simple, string name of the filter for the flux being calculated.
600 **kwargs
601 Any additional keyword arguments that may be passed to the plugin.
602 """
603 diaObjects.loc[:, "{}PSFluxSkew".format(filterName)] = \
604 filterDiaSources.psFlux.skew()
605
606
608 pass
609
610
611@register("ap_minMaxFlux")
613 """Compute min/max of diaSource fluxes.
614 """
615
616 ConfigClass = MinMaxDiaPsFluxConfig
617
618 # Required input Cols
619 # Output columns are created upon instantiation of the class.
620 outputCols = ["PSFluxMin", "PSFluxMax"]
621 plugType = "multi"
622 needsFilter = True
623
624 @classmethod
626 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
627
628 def calculate(self,
629 diaObjects,
630 diaSources,
631 filterDiaSources,
632 filterName,
633 **kwargs):
634 """Compute min/max of the point source fluxes.
635
636 Parameters
637 ----------
638 diaObject : `dict`
639 Summary object to store values in.
640 diaSources : `pandas.DataFrame`
641 DataFrame representing all diaSources associated with this
642 diaObject.
643 filterDiaSources : `pandas.DataFrame`
644 DataFrame representing diaSources associated with this
645 diaObject that are observed in the band pass ``filterName``.
646 filterName : `str`
647 Simple, string name of the filter for the flux being calculated.
648 **kwargs
649 Any additional keyword arguments that may be passed to the plugin.
650 """
651 minName = "{}PSFluxMin".format(filterName)
652 if minName not in diaObjects.columns:
653 diaObjects[minName] = np.nan
654 maxName = "{}PSFluxMax".format(filterName)
655 if maxName not in diaObjects.columns:
656 diaObjects[maxName] = np.nan
657
658 diaObjects.loc[:, minName] = filterDiaSources.psFlux.min()
659 diaObjects.loc[:, maxName] = filterDiaSources.psFlux.max()
660
661
663 pass
664
665
666@register("ap_maxSlopeFlux")
668 """Compute the maximum ratio time ordered deltaFlux / deltaTime.
669 """
670
671 ConfigClass = MinMaxDiaPsFluxConfig
672
673 # Required input Cols
674 # Output columns are created upon instantiation of the class.
675 outputCols = ["PSFluxMaxSlope"]
676 plugType = "multi"
677 needsFilter = True
678
679 @classmethod
681 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
682
683 def calculate(self,
684 diaObjects,
685 diaSources,
686 filterDiaSources,
687 filterName,
688 **kwargs):
689 """Compute the maximum ratio time ordered deltaFlux / deltaTime.
690
691 Parameters
692 ----------
693 diaObject : `dict`
694 Summary object to store values in.
695 diaSources : `pandas.DataFrame`
696 DataFrame representing all diaSources associated with this
697 diaObject.
698 filterDiaSources : `pandas.DataFrame`
699 DataFrame representing diaSources associated with this
700 diaObject that are observed in the band pass ``filterName``.
701 filterName : `str`
702 Simple, string name of the filter for the flux being calculated.
703 **kwargs
704 Any additional keyword arguments that may be passed to the plugin.
705 """
706
707 def _maxSlope(df):
708 tmpDf = df[~np.logical_or(np.isnan(df["psFlux"]),
709 np.isnan(df["midPointTai"]))]
710 if len(tmpDf) < 2:
711 return np.nan
712 times = tmpDf["midPointTai"].to_numpy()
713 timeArgs = times.argsort()
714 times = times[timeArgs]
715 fluxes = tmpDf["psFlux"].to_numpy()[timeArgs]
716 return (np.diff(fluxes) / np.diff(times)).max()
717
718 diaObjects.loc[:, "{}PSFluxMaxSlope".format(filterName)] = \
719 filterDiaSources.apply(_maxSlope)
720
721
723 pass
724
725
726@register("ap_meanErrFlux")
728 """Compute the mean of the dia source errors.
729 """
730
731 ConfigClass = ErrMeanDiaPsFluxConfig
732
733 # Required input Cols
734 # Output columns are created upon instantiation of the class.
735 outputCols = ["PSFluxErrMean"]
736 plugType = "multi"
737 needsFilter = True
738
739 @classmethod
741 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
742
743 def calculate(self,
744 diaObjects,
745 diaSources,
746 filterDiaSources,
747 filterName,
748 **kwargs):
749 """Compute the mean of the dia source errors.
750
751 Parameters
752 ----------
753 diaObject : `dict`
754 Summary object to store values in.
755 diaSources : `pandas.DataFrame`
756 DataFrame representing all diaSources associated with this
757 diaObject.
758 filterDiaSources : `pandas.DataFrame`
759 DataFrame representing diaSources associated with this
760 diaObject that are observed in the band pass ``filterName``.
761 filterName : `str`
762 Simple, string name of the filter for the flux being calculated.
763 **kwargs
764 Any additional keyword arguments that may be passed to the plugin.
765 """
766 diaObjects.loc[:, "{}PSFluxErrMean".format(filterName)] = \
767 filterDiaSources.psFluxErr.mean()
768
769
771 pass
772
773
774@register("ap_linearFit")
776 """Compute fit a linear model to flux vs time.
777 """
778
779 ConfigClass = LinearFitDiaPsFluxConfig
780
781 # Required input Cols
782 # Output columns are created upon instantiation of the class.
783 outputCols = ["PSFluxLinearSlope", "PSFluxLinearIntercept"]
784 plugType = "multi"
785 needsFilter = True
786
787 @classmethod
789 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
790
791 def calculate(self,
792 diaObjects,
793 diaSources,
794 filterDiaSources,
795 filterName,
796 **kwargs):
797 """Compute fit a linear model to flux vs time.
798
799 Parameters
800 ----------
801 diaObject : `dict`
802 Summary object to store values in.
803 diaSources : `pandas.DataFrame`
804 DataFrame representing all diaSources associated with this
805 diaObject.
806 filterDiaSources : `pandas.DataFrame`
807 DataFrame representing diaSources associated with this
808 diaObject that are observed in the band pass ``filterName``.
809 filterName : `str`
810 Simple, string name of the filter for the flux being calculated.
811 **kwargs
812 Any additional keyword arguments that may be passed to the plugin.
813 """
814
815 mName = "{}PSFluxLinearSlope".format(filterName)
816 if mName not in diaObjects.columns:
817 diaObjects[mName] = np.nan
818 bName = "{}PSFluxLinearIntercept".format(filterName)
819 if bName not in diaObjects.columns:
820 diaObjects[bName] = np.nan
821
822 def _linearFit(df):
823 tmpDf = df[~np.logical_or(
824 np.isnan(df["psFlux"]),
825 np.logical_or(np.isnan(df["psFluxErr"]),
826 np.isnan(df["midPointTai"])))]
827 if len(tmpDf) < 2:
828 return pd.Series({mName: np.nan, bName: np.nan})
829 fluxes = tmpDf["psFlux"].to_numpy()
830 errors = tmpDf["psFluxErr"].to_numpy()
831 times = tmpDf["midPointTai"].to_numpy()
832 A = np.array([times / errors, 1 / errors]).transpose()
833 m, b = lsq_linear(A, fluxes / errors).x
834 return pd.Series({mName: m, bName: b})
835
836 diaObjects.loc[:, [mName, bName]] = filterDiaSources.apply(_linearFit)
837
838
840 pass
841
842
843@register("ap_stetsonJ")
845 """Compute the StetsonJ statistic on the DIA point source fluxes.
846 """
847
848 ConfigClass = LinearFitDiaPsFluxConfig
849
850 # Required input Cols
851 inputCols = ["PSFluxMean"]
852 # Output columns are created upon instantiation of the class.
853 outputCols = ["PSFluxStetsonJ"]
854 plugType = "multi"
855 needsFilter = True
856
857 @classmethod
859 return cls.FLUX_MOMENTS_CALCULATEDFLUX_MOMENTS_CALCULATED
860
861 def calculate(self,
862 diaObjects,
863 diaSources,
864 filterDiaSources,
865 filterName,
866 **kwargs):
867 """Compute the StetsonJ statistic on the DIA point source fluxes.
868
869 Parameters
870 ----------
871 diaObject : `dict`
872 Summary object to store values in.
873 diaSources : `pandas.DataFrame`
874 DataFrame representing all diaSources associated with this
875 diaObject.
876 filterDiaSources : `pandas.DataFrame`
877 DataFrame representing diaSources associated with this
878 diaObject that are observed in the band pass ``filterName``.
879 filterName : `str`
880 Simple, string name of the filter for the flux being calculated.
881 **kwargs
882 Any additional keyword arguments that may be passed to the plugin.
883 """
884 meanName = "{}PSFluxMean".format(filterName)
885
886 def _stetsonJ(df):
887 tmpDf = df[~np.logical_or(np.isnan(df["psFlux"]),
888 np.isnan(df["psFluxErr"]))]
889 if len(tmpDf) < 2:
890 return np.nan
891 fluxes = tmpDf["psFlux"].to_numpy()
892 errors = tmpDf["psFluxErr"].to_numpy()
893
894 return self._stetson_J_stetson_J(
895 fluxes,
896 errors,
897 diaObjects.at[tmpDf.diaObjectId.iat[0], meanName])
898
899 diaObjects.loc[:, "{}PSFluxStetsonJ".format(filterName)] = \
900 filterDiaSources.apply(_stetsonJ)
901
902 def _stetson_J(self, fluxes, errors, mean=None):
903 """Compute the single band stetsonJ statistic.
904
905 Parameters
906 ----------
907 fluxes : `numpy.ndarray` (N,)
908 Calibrated lightcurve flux values.
909 errors : `numpy.ndarray` (N,)
910 Errors on the calibrated lightcurve fluxes.
911 mean : `float`
912 Starting mean from previous plugin.
913
914 Returns
915 -------
916 stetsonJ : `float`
917 stetsonJ statistic for the input fluxes and errors.
918
919 References
920 ----------
921 .. [1] Stetson, P. B., "On the Automatic Determination of Light-Curve
922 Parameters for Cepheid Variables", PASP, 108, 851S, 1996
923 """
924 n_points = len(fluxes)
925 flux_mean = self._stetson_mean_stetson_mean(fluxes, errors, mean)
926 delta_val = (
927 np.sqrt(n_points / (n_points - 1)) * (fluxes - flux_mean) / errors)
928 p_k = delta_val ** 2 - 1
929
930 return np.mean(np.sign(p_k) * np.sqrt(np.fabs(p_k)))
931
932 def _stetson_mean(self,
933 values,
934 errors,
935 mean=None,
936 alpha=2.,
937 beta=2.,
938 n_iter=20,
939 tol=1e-6):
940 """Compute the stetson mean of the fluxes which down-weights outliers.
941
942 Weighted biased on an error weighted difference scaled by a constant
943 (1/``a``) and raised to the power beta. Higher betas more harshly
944 penalize outliers and ``a`` sets the number of sigma where a weighted
945 difference of 1 occurs.
946
947 Parameters
948 ----------
949 values : `numpy.dnarray`, (N,)
950 Input values to compute the mean of.
951 errors : `numpy.ndarray`, (N,)
952 Errors on the input values.
953 mean : `float`
954 Starting mean value or None.
955 alpha : `float`
956 Scalar down-weighting of the fractional difference. lower->more
957 clipping. (Default value is 2.)
958 beta : `float`
959 Power law slope of the used to down-weight outliers. higher->more
960 clipping. (Default value is 2.)
961 n_iter : `int`
962 Number of iterations of clipping.
963 tol : `float`
964 Fractional and absolute tolerance goal on the change in the mean
965 before exiting early. (Default value is 1e-6)
966
967 Returns
968 -------
969 mean : `float`
970 Weighted stetson mean result.
971
972 References
973 ----------
974 .. [1] Stetson, P. B., "On the Automatic Determination of Light-Curve
975 Parameters for Cepheid Variables", PASP, 108, 851S, 1996
976 """
977 n_points = len(values)
978 n_factor = np.sqrt(n_points / (n_points - 1))
979 inv_var = 1 / errors ** 2
980
981 if mean is None:
982 mean = np.average(values, weights=inv_var)
983 for iter_idx in range(n_iter):
984 chi = np.fabs(n_factor * (values - mean) / errors)
985 tmp_mean = np.average(
986 values,
987 weights=inv_var / (1 + (chi / alpha) ** beta))
988 diff = np.fabs(tmp_mean - mean)
989 mean = tmp_mean
990 if diff / mean < tol and diff < tol:
991 break
992 return mean
993
994
996 pass
997
998
999@register("ap_meanTotFlux")
1001 """Compute the weighted mean and mean error on the point source fluxes
1002 forced photometered at the DiaSource location in the calibrated image.
1003
1004 Additionally store number of usable data points.
1005 """
1006
1007 ConfigClass = WeightedMeanDiaPsFluxConfig
1008 outputCols = ["TOTFluxMean", "TOTFluxMeanErr"]
1009 plugType = "multi"
1010 needsFilter = True
1011
1012 @classmethod
1014 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
1015
1016 @catchWarnings(warns=["invalid value encountered",
1017 "divide by zero"])
1018 def calculate(self,
1019 diaObjects,
1020 diaSources,
1021 filterDiaSources,
1022 filterName,
1023 **kwargs):
1024 """Compute the weighted mean and mean error of the point source flux.
1025
1026 Parameters
1027 ----------
1028 diaObject : `dict`
1029 Summary object to store values in.
1030 diaSources : `pandas.DataFrame`
1031 DataFrame representing all diaSources associated with this
1032 diaObject.
1033 filterDiaSources : `pandas.DataFrame`
1034 DataFrame representing diaSources associated with this
1035 diaObject that are observed in the band pass ``filterName``.
1036 filterName : `str`
1037 Simple, string name of the filter for the flux being calculated.
1038 **kwargs
1039 Any additional keyword arguments that may be passed to the plugin.
1040 """
1041 totMeanName = "{}TOTFluxMean".format(filterName)
1042 if totMeanName not in diaObjects.columns:
1043 diaObjects[totMeanName] = np.nan
1044 totErrName = "{}TOTFluxMeanErr".format(filterName)
1045 if totErrName not in diaObjects.columns:
1046 diaObjects[totErrName] = np.nan
1047
1048 def _meanFlux(df):
1049 tmpDf = df[~np.logical_or(np.isnan(df["totFlux"]),
1050 np.isnan(df["totFluxErr"]))]
1051 tot_weight = np.nansum(1 / tmpDf["totFluxErr"] ** 2)
1052 fluxMean = np.nansum(tmpDf["totFlux"]
1053 / tmpDf["totFluxErr"] ** 2)
1054 fluxMean /= tot_weight
1055 fluxMeanErr = np.sqrt(1 / tot_weight)
1056
1057 return pd.Series({totMeanName: fluxMean,
1058 totErrName: fluxMeanErr})
1059
1060 diaObjects.loc[:, [totMeanName, totErrName]] = \
1061 filterDiaSources.apply(_meanFlux)
1062
1063
1065 pass
1066
1067
1068@register("ap_sigmaTotFlux")
1070 """Compute scatter of diaSource fluxes.
1071 """
1072
1073 ConfigClass = SigmaDiaPsFluxConfig
1074 # Output columns are created upon instantiation of the class.
1075 outputCols = ["TOTFluxSigma"]
1076 plugType = "multi"
1077 needsFilter = True
1078
1079 @classmethod
1081 return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
1082
1083 def calculate(self,
1084 diaObjects,
1085 diaSources,
1086 filterDiaSources,
1087 filterName,
1088 **kwargs):
1089 """Compute the sigma fluxes of the point source flux measured on the
1090 calibrated image.
1091
1092 Parameters
1093 ----------
1094 diaObject : `dict`
1095 Summary object to store values in.
1096 diaSources : `pandas.DataFrame`
1097 DataFrame representing all diaSources associated with this
1098 diaObject.
1099 filterDiaSources : `pandas.DataFrame`
1100 DataFrame representing diaSources associated with this
1101 diaObject that are observed in the band pass ``filterName``.
1102 filterName : `str`
1103 Simple, string name of the filter for the flux being calculated.
1104 **kwargs
1105 Any additional keyword arguments that may be passed to the plugin.
1106 """
1107 # Set "delta degrees of freedom (ddf)" to 1 to calculate the unbiased
1108 # estimator of scatter (i.e. 'N - 1' instead of 'N').
1109 diaObjects.loc[:, "{}TOTFluxSigma".format(filterName)] = \
1110 filterDiaSources.totFlux.std()
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)
def calculate(self, diaObjects, diaObjectId, **kwargs)
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)
def calculate(self, diaObjects, diaSources, **kwargs)
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)
def __init__(self, config, name, metadata, **kwargs)
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)
def _stetson_mean(self, values, errors, mean=None, alpha=2., beta=2., n_iter=20, tol=1e-6)
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)
def calculate(self, diaObjects, diaSources, filterDiaSources, filterName, **kwargs)