LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
LSST Data Management Base Package
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 
24 Output columns must be
25 as defined in the schema of the Apdb both in name and units.
26 """
27 
28 import functools
29 import warnings
30 
31 from astropy.stats import median_absolute_deviation
32 import numpy as np
33 import pandas as pd
34 from scipy.optimize import lsq_linear
35 
36 import lsst.geom as geom
37 import lsst.pex.config as pexConfig
38 import lsst.sphgeom as sphgeom
39 
40 from .diaCalculation import (
41  DiaObjectCalculationPluginConfig,
42  DiaObjectCalculationPlugin)
43 from .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 
64 def 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", "divide by zero"])
274  def calculate(self,
275  diaObjects,
276  diaSources,
277  filterDiaSources,
278  filterName,
279  **kwargs):
280  """Compute the weighted mean and mean error of the point source flux.
281 
282  Parameters
283  ----------
284  diaObject : `dict`
285  Summary object to store values in.
286  diaSources : `pandas.DataFrame`
287  DataFrame representing all diaSources associated with this
288  diaObject.
289  filterDiaSources : `pandas.DataFrame`
290  DataFrame representing diaSources associated with this
291  diaObject that are observed in the band pass ``filterName``.
292  filterName : `str`
293  Simple, string name of the filter for the flux being calculated.
294  **kwargs
295  Any additional keyword arguments that may be passed to the plugin.
296  """
297  meanName = "{}PSFluxMean".format(filterName)
298  errName = "{}PSFluxMeanErr".format(filterName)
299  nDataName = "{}PSFluxNdata".format(filterName)
300  if meanName not in diaObjects.columns:
301  diaObjects[meanName] = np.nan
302  if errName not in diaObjects.columns:
303  diaObjects[errName] = np.nan
304  if nDataName not in diaObjects.columns:
305  diaObjects[nDataName] = 0
306 
307  def _weightedMean(df):
308  tmpDf = df[~np.logical_or(np.isnan(df["psFlux"]),
309  np.isnan(df["psFluxErr"]))]
310  tot_weight = np.nansum(1 / tmpDf["psFluxErr"] ** 2)
311  fluxMean = np.nansum(tmpDf["psFlux"]
312  / tmpDf["psFluxErr"] ** 2)
313  fluxMean /= tot_weight
314  if tot_weight > 0:
315  fluxMeanErr = np.sqrt(1 / tot_weight)
316  else:
317  fluxMeanErr = np.nan
318  nFluxData = len(tmpDf)
319 
320  return pd.Series({meanName: fluxMean,
321  errName: fluxMeanErr,
322  nDataName: nFluxData},
323  dtype="object")
324 
325  diaObjects.loc[:, [meanName, errName, nDataName]] = \
326  filterDiaSources.apply(_weightedMean)
327 
328 
330  percentiles = pexConfig.ListField(
331  dtype=int,
332  default=[5, 25, 50, 75, 95],
333  doc="Percentiles to calculate to compute values for. Should be "
334  "integer values."
335  )
336 
337 
338 @register("ap_percentileFlux")
340  """Compute percentiles of diaSource fluxes.
341  """
342 
343  ConfigClass = PercentileDiaPsFluxConfig
344  # Output columns are created upon instantiation of the class.
345  outputCols = []
346  plugType = "multi"
347  needsFilter = True
348 
349  def __init__(self, config, name, metadata, **kwargs):
350  DiaObjectCalculationPlugin.__init__(self,
351  config,
352  name,
353  metadata,
354  **kwargs)
355  self.outputColsoutputColsoutputColsoutputCols = ["PSFluxPercentile{:02d}".format(percent)
356  for percent in self.configconfig.percentiles]
357 
358  @classmethod
359  def getExecutionOrder(cls):
360  return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
361 
362  @catchWarnings(warns=["All-NaN slice encountered"])
363  def calculate(self,
364  diaObjects,
365  diaSources,
366  filterDiaSources,
367  filterName,
368  **kwargs):
369  """Compute the percentile fluxes of the point source flux.
370 
371  Parameters
372  ----------
373  diaObject : `dict`
374  Summary object to store values in.
375  diaSources : `pandas.DataFrame`
376  DataFrame representing all diaSources associated with this
377  diaObject.
378  filterDiaSources : `pandas.DataFrame`
379  DataFrame representing diaSources associated with this
380  diaObject that are observed in the band pass ``filterName``.
381  filterName : `str`
382  Simple, string name of the filter for the flux being calculated.
383  **kwargs
384  Any additional keyword arguments that may be passed to the plugin.
385  """
386  pTileNames = []
387  for tilePercent in self.configconfig.percentiles:
388  pTileName = "{}PSFluxPercentile{:02d}".format(filterName,
389  tilePercent)
390  pTileNames.append(pTileName)
391  if pTileName not in diaObjects.columns:
392  diaObjects[pTileName] = np.nan
393 
394  def _fluxPercentiles(df):
395  pTiles = np.nanpercentile(df["psFlux"], self.configconfig.percentiles)
396  return pd.Series(
397  dict((tileName, pTile)
398  for tileName, pTile in zip(pTileNames, pTiles)))
399 
400  diaObjects.loc[:, pTileNames] = filterDiaSources.apply(_fluxPercentiles)
401 
402 
404  pass
405 
406 
407 @register("ap_sigmaFlux")
409  """Compute scatter of diaSource fluxes.
410  """
411 
412  ConfigClass = SigmaDiaPsFluxConfig
413  # Output columns are created upon instantiation of the class.
414  outputCols = ["PSFluxSigma"]
415  plugType = "multi"
416  needsFilter = True
417 
418  @classmethod
419  def getExecutionOrder(cls):
420  return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
421 
422  def calculate(self,
423  diaObjects,
424  diaSources,
425  filterDiaSources,
426  filterName,
427  **kwargs):
428  """Compute the sigma fluxes of the point source flux.
429 
430  Parameters
431  ----------
432  diaObject : `dict`
433  Summary object to store values in.
434  diaSources : `pandas.DataFrame`
435  DataFrame representing all diaSources associated with this
436  diaObject.
437  filterDiaSources : `pandas.DataFrame`
438  DataFrame representing diaSources associated with this
439  diaObject that are observed in the band pass ``filterName``.
440  filterName : `str`
441  Simple, string name of the filter for the flux being calculated.
442  **kwargs
443  Any additional keyword arguments that may be passed to the plugin.
444  """
445  # Set "delta degrees of freedom (ddf)" to 1 to calculate the unbiased
446  # estimator of scatter (i.e. 'N - 1' instead of 'N').
447  diaObjects.loc[:, "{}PSFluxSigma".format(filterName)] = \
448  filterDiaSources.psFlux.std()
449 
450 
452  pass
453 
454 
455 @register("ap_chi2Flux")
457  """Compute chi2 of diaSource fluxes.
458  """
459 
460  ConfigClass = Chi2DiaPsFluxConfig
461 
462  # Required input Cols
463  inputCols = ["PSFluxMean"]
464  # Output columns are created upon instantiation of the class.
465  outputCols = ["PSFluxChi2"]
466  plugType = "multi"
467  needsFilter = True
468 
469  @classmethod
470  def getExecutionOrder(cls):
471  return cls.FLUX_MOMENTS_CALCULATEDFLUX_MOMENTS_CALCULATED
472 
473  @catchWarnings(warns=["All-NaN slice encountered"])
474  def calculate(self,
475  diaObjects,
476  diaSources,
477  filterDiaSources,
478  filterName,
479  **kwargs):
480  """Compute the chi2 of the point source fluxes.
481 
482  Parameters
483  ----------
484  diaObject : `dict`
485  Summary object to store values in.
486  diaSources : `pandas.DataFrame`
487  DataFrame representing all diaSources associated with this
488  diaObject.
489  filterDiaSources : `pandas.DataFrame`
490  DataFrame representing diaSources associated with this
491  diaObject that are observed in the band pass ``filterName``.
492  filterName : `str`
493  Simple, string name of the filter for the flux being calculated.
494  **kwargs
495  Any additional keyword arguments that may be passed to the plugin.
496  """
497  meanName = "{}PSFluxMean".format(filterName)
498 
499  def _chi2(df):
500  delta = (df["psFlux"]
501  - diaObjects.at[df.diaObjectId.iat[0], meanName])
502  return np.nansum((delta / df["psFluxErr"]) ** 2)
503 
504  diaObjects.loc[:, "{}PSFluxChi2".format(filterName)] = \
505  filterDiaSources.apply(_chi2)
506 
507 
509  pass
510 
511 
512 @register("ap_madFlux")
514  """Compute median absolute deviation of diaSource fluxes.
515  """
516 
517  ConfigClass = MadDiaPsFluxConfig
518 
519  # Required input Cols
520  # Output columns are created upon instantiation of the class.
521  outputCols = ["PSFluxMAD"]
522  plugType = "multi"
523  needsFilter = True
524 
525  @classmethod
526  def getExecutionOrder(cls):
527  return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
528 
529  @catchWarnings(warns=["All-NaN slice encountered"])
530  def calculate(self,
531  diaObjects,
532  diaSources,
533  filterDiaSources,
534  filterName,
535  **kwargs):
536  """Compute the median absolute deviation of the point source fluxes.
537 
538  Parameters
539  ----------
540  diaObject : `dict`
541  Summary object to store values in.
542  diaSources : `pandas.DataFrame`
543  DataFrame representing all diaSources associated with this
544  diaObject.
545  filterDiaSources : `pandas.DataFrame`
546  DataFrame representing diaSources associated with this
547  diaObject that are observed in the band pass ``filterName``.
548  filterName : `str`
549  Simple, string name of the filter for the flux being calculated.
550  **kwargs
551  Any additional keyword arguments that may be passed to the plugin.
552  """
553  diaObjects.loc[:, "{}PSFluxMAD".format(filterName)] = \
554  filterDiaSources.psFlux.apply(median_absolute_deviation,
555  ignore_nan=True)
556 
557 
559  pass
560 
561 
562 @register("ap_skewFlux")
564  """Compute the skew of diaSource fluxes.
565  """
566 
567  ConfigClass = SkewDiaPsFluxConfig
568 
569  # Required input Cols
570  # Output columns are created upon instantiation of the class.
571  outputCols = ["PSFluxSkew"]
572  plugType = "multi"
573  needsFilter = True
574 
575  @classmethod
576  def getExecutionOrder(cls):
577  return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
578 
579  def calculate(self,
580  diaObjects,
581  diaSources,
582  filterDiaSources,
583  filterName,
584  **kwargs):
585  """Compute the skew of the point source fluxes.
586 
587  Parameters
588  ----------
589  diaObject : `dict`
590  Summary object to store values in.
591  diaSources : `pandas.DataFrame`
592  DataFrame representing all diaSources associated with this
593  diaObject.
594  filterDiaSources : `pandas.DataFrame`
595  DataFrame representing diaSources associated with this
596  diaObject that are observed in the band pass ``filterName``.
597  filterName : `str`
598  Simple, string name of the filter for the flux being calculated.
599  **kwargs
600  Any additional keyword arguments that may be passed to the plugin.
601  """
602  diaObjects.loc[:, "{}PSFluxSkew".format(filterName)] = \
603  filterDiaSources.psFlux.skew()
604 
605 
607  pass
608 
609 
610 @register("ap_minMaxFlux")
612  """Compute min/max of diaSource fluxes.
613  """
614 
615  ConfigClass = MinMaxDiaPsFluxConfig
616 
617  # Required input Cols
618  # Output columns are created upon instantiation of the class.
619  outputCols = ["PSFluxMin", "PSFluxMax"]
620  plugType = "multi"
621  needsFilter = True
622 
623  @classmethod
624  def getExecutionOrder(cls):
625  return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
626 
627  def calculate(self,
628  diaObjects,
629  diaSources,
630  filterDiaSources,
631  filterName,
632  **kwargs):
633  """Compute min/max of the point source fluxes.
634 
635  Parameters
636  ----------
637  diaObject : `dict`
638  Summary object to store values in.
639  diaSources : `pandas.DataFrame`
640  DataFrame representing all diaSources associated with this
641  diaObject.
642  filterDiaSources : `pandas.DataFrame`
643  DataFrame representing diaSources associated with this
644  diaObject that are observed in the band pass ``filterName``.
645  filterName : `str`
646  Simple, string name of the filter for the flux being calculated.
647  **kwargs
648  Any additional keyword arguments that may be passed to the plugin.
649  """
650  minName = "{}PSFluxMin".format(filterName)
651  if minName not in diaObjects.columns:
652  diaObjects[minName] = np.nan
653  maxName = "{}PSFluxMax".format(filterName)
654  if maxName not in diaObjects.columns:
655  diaObjects[maxName] = np.nan
656 
657  diaObjects.loc[:, minName] = filterDiaSources.psFlux.min()
658  diaObjects.loc[:, maxName] = filterDiaSources.psFlux.max()
659 
660 
662  pass
663 
664 
665 @register("ap_maxSlopeFlux")
667  """Compute the maximum ratio time ordered deltaFlux / deltaTime.
668  """
669 
670  ConfigClass = MinMaxDiaPsFluxConfig
671 
672  # Required input Cols
673  # Output columns are created upon instantiation of the class.
674  outputCols = ["PSFluxMaxSlope"]
675  plugType = "multi"
676  needsFilter = True
677 
678  @classmethod
679  def getExecutionOrder(cls):
680  return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
681 
682  def calculate(self,
683  diaObjects,
684  diaSources,
685  filterDiaSources,
686  filterName,
687  **kwargs):
688  """Compute the maximum ratio time ordered deltaFlux / deltaTime.
689 
690  Parameters
691  ----------
692  diaObject : `dict`
693  Summary object to store values in.
694  diaSources : `pandas.DataFrame`
695  DataFrame representing all diaSources associated with this
696  diaObject.
697  filterDiaSources : `pandas.DataFrame`
698  DataFrame representing diaSources associated with this
699  diaObject that are observed in the band pass ``filterName``.
700  filterName : `str`
701  Simple, string name of the filter for the flux being calculated.
702  **kwargs
703  Any additional keyword arguments that may be passed to the plugin.
704  """
705 
706  def _maxSlope(df):
707  tmpDf = df[~np.logical_or(np.isnan(df["psFlux"]),
708  np.isnan(df["midPointTai"]))]
709  if len(tmpDf) < 2:
710  return np.nan
711  times = tmpDf["midPointTai"].to_numpy()
712  timeArgs = times.argsort()
713  times = times[timeArgs]
714  fluxes = tmpDf["psFlux"].to_numpy()[timeArgs]
715  return (np.diff(fluxes) / np.diff(times)).max()
716 
717  diaObjects.loc[:, "{}PSFluxMaxSlope".format(filterName)] = \
718  filterDiaSources.apply(_maxSlope)
719 
720 
722  pass
723 
724 
725 @register("ap_meanErrFlux")
727  """Compute the mean of the dia source errors.
728  """
729 
730  ConfigClass = ErrMeanDiaPsFluxConfig
731 
732  # Required input Cols
733  # Output columns are created upon instantiation of the class.
734  outputCols = ["PSFluxErrMean"]
735  plugType = "multi"
736  needsFilter = True
737 
738  @classmethod
739  def getExecutionOrder(cls):
740  return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
741 
742  def calculate(self,
743  diaObjects,
744  diaSources,
745  filterDiaSources,
746  filterName,
747  **kwargs):
748  """Compute the mean of the dia source errors.
749 
750  Parameters
751  ----------
752  diaObject : `dict`
753  Summary object to store values in.
754  diaSources : `pandas.DataFrame`
755  DataFrame representing all diaSources associated with this
756  diaObject.
757  filterDiaSources : `pandas.DataFrame`
758  DataFrame representing diaSources associated with this
759  diaObject that are observed in the band pass ``filterName``.
760  filterName : `str`
761  Simple, string name of the filter for the flux being calculated.
762  **kwargs
763  Any additional keyword arguments that may be passed to the plugin.
764  """
765  diaObjects.loc[:, "{}PSFluxErrMean".format(filterName)] = \
766  filterDiaSources.psFluxErr.mean()
767 
768 
770  pass
771 
772 
773 @register("ap_linearFit")
775  """Compute fit a linear model to flux vs time.
776  """
777 
778  ConfigClass = LinearFitDiaPsFluxConfig
779 
780  # Required input Cols
781  # Output columns are created upon instantiation of the class.
782  outputCols = ["PSFluxLinearSlope", "PSFluxLinearIntercept"]
783  plugType = "multi"
784  needsFilter = True
785 
786  @classmethod
787  def getExecutionOrder(cls):
788  return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
789 
790  def calculate(self,
791  diaObjects,
792  diaSources,
793  filterDiaSources,
794  filterName,
795  **kwargs):
796  """Compute fit a linear model to flux vs time.
797 
798  Parameters
799  ----------
800  diaObject : `dict`
801  Summary object to store values in.
802  diaSources : `pandas.DataFrame`
803  DataFrame representing all diaSources associated with this
804  diaObject.
805  filterDiaSources : `pandas.DataFrame`
806  DataFrame representing diaSources associated with this
807  diaObject that are observed in the band pass ``filterName``.
808  filterName : `str`
809  Simple, string name of the filter for the flux being calculated.
810  **kwargs
811  Any additional keyword arguments that may be passed to the plugin.
812  """
813 
814  mName = "{}PSFluxLinearSlope".format(filterName)
815  if mName not in diaObjects.columns:
816  diaObjects[mName] = np.nan
817  bName = "{}PSFluxLinearIntercept".format(filterName)
818  if bName not in diaObjects.columns:
819  diaObjects[bName] = np.nan
820 
821  def _linearFit(df):
822  tmpDf = df[~np.logical_or(
823  np.isnan(df["psFlux"]),
824  np.logical_or(np.isnan(df["psFluxErr"]),
825  np.isnan(df["midPointTai"])))]
826  if len(tmpDf) < 2:
827  return pd.Series({mName: np.nan, bName: np.nan})
828  fluxes = tmpDf["psFlux"].to_numpy()
829  errors = tmpDf["psFluxErr"].to_numpy()
830  times = tmpDf["midPointTai"].to_numpy()
831  A = np.array([times / errors, 1 / errors]).transpose()
832  m, b = lsq_linear(A, fluxes / errors).x
833  return pd.Series({mName: m, bName: b})
834 
835  diaObjects.loc[:, [mName, bName]] = filterDiaSources.apply(_linearFit)
836 
837 
839  pass
840 
841 
842 @register("ap_stetsonJ")
844  """Compute the StetsonJ statistic on the DIA point source fluxes.
845  """
846 
847  ConfigClass = LinearFitDiaPsFluxConfig
848 
849  # Required input Cols
850  inputCols = ["PSFluxMean"]
851  # Output columns are created upon instantiation of the class.
852  outputCols = ["PSFluxStetsonJ"]
853  plugType = "multi"
854  needsFilter = True
855 
856  @classmethod
857  def getExecutionOrder(cls):
858  return cls.FLUX_MOMENTS_CALCULATEDFLUX_MOMENTS_CALCULATED
859 
860  def calculate(self,
861  diaObjects,
862  diaSources,
863  filterDiaSources,
864  filterName,
865  **kwargs):
866  """Compute the StetsonJ statistic on the DIA point source fluxes.
867 
868  Parameters
869  ----------
870  diaObject : `dict`
871  Summary object to store values in.
872  diaSources : `pandas.DataFrame`
873  DataFrame representing all diaSources associated with this
874  diaObject.
875  filterDiaSources : `pandas.DataFrame`
876  DataFrame representing diaSources associated with this
877  diaObject that are observed in the band pass ``filterName``.
878  filterName : `str`
879  Simple, string name of the filter for the flux being calculated.
880  **kwargs
881  Any additional keyword arguments that may be passed to the plugin.
882  """
883  meanName = "{}PSFluxMean".format(filterName)
884 
885  def _stetsonJ(df):
886  tmpDf = df[~np.logical_or(np.isnan(df["psFlux"]),
887  np.isnan(df["psFluxErr"]))]
888  if len(tmpDf) < 2:
889  return np.nan
890  fluxes = tmpDf["psFlux"].to_numpy()
891  errors = tmpDf["psFluxErr"].to_numpy()
892 
893  return self._stetson_J_stetson_J(
894  fluxes,
895  errors,
896  diaObjects.at[tmpDf.diaObjectId.iat[0], meanName])
897 
898  diaObjects.loc[:, "{}PSFluxStetsonJ".format(filterName)] = \
899  filterDiaSources.apply(_stetsonJ)
900 
901  def _stetson_J(self, fluxes, errors, mean=None):
902  """Compute the single band stetsonJ statistic.
903 
904  Parameters
905  ----------
906  fluxes : `numpy.ndarray` (N,)
907  Calibrated lightcurve flux values.
908  errors : `numpy.ndarray` (N,)
909  Errors on the calibrated lightcurve fluxes.
910  mean : `float`
911  Starting mean from previous plugin.
912 
913  Returns
914  -------
915  stetsonJ : `float`
916  stetsonJ statistic for the input fluxes and errors.
917 
918  References
919  ----------
920  .. [1] Stetson, P. B., "On the Automatic Determination of Light-Curve
921  Parameters for Cepheid Variables", PASP, 108, 851S, 1996
922  """
923  n_points = len(fluxes)
924  flux_mean = self._stetson_mean_stetson_mean(fluxes, errors, mean)
925  delta_val = (
926  np.sqrt(n_points / (n_points - 1)) * (fluxes - flux_mean) / errors)
927  p_k = delta_val ** 2 - 1
928 
929  return np.mean(np.sign(p_k) * np.sqrt(np.fabs(p_k)))
930 
931  def _stetson_mean(self,
932  values,
933  errors,
934  mean=None,
935  alpha=2.,
936  beta=2.,
937  n_iter=20,
938  tol=1e-6):
939  """Compute the stetson mean of the fluxes which down-weights outliers.
940 
941  Weighted biased on an error weighted difference scaled by a constant
942  (1/``a``) and raised to the power beta. Higher betas more harshly
943  penalize outliers and ``a`` sets the number of sigma where a weighted
944  difference of 1 occurs.
945 
946  Parameters
947  ----------
948  values : `numpy.dnarray`, (N,)
949  Input values to compute the mean of.
950  errors : `numpy.ndarray`, (N,)
951  Errors on the input values.
952  mean : `float`
953  Starting mean value or None.
954  alpha : `float`
955  Scalar down-weighting of the fractional difference. lower->more
956  clipping. (Default value is 2.)
957  beta : `float`
958  Power law slope of the used to down-weight outliers. higher->more
959  clipping. (Default value is 2.)
960  n_iter : `int`
961  Number of iterations of clipping.
962  tol : `float`
963  Fractional and absolute tolerance goal on the change in the mean
964  before exiting early. (Default value is 1e-6)
965 
966  Returns
967  -------
968  mean : `float`
969  Weighted stetson mean result.
970 
971  References
972  ----------
973  .. [1] Stetson, P. B., "On the Automatic Determination of Light-Curve
974  Parameters for Cepheid Variables", PASP, 108, 851S, 1996
975  """
976  n_points = len(values)
977  n_factor = np.sqrt(n_points / (n_points - 1))
978  inv_var = 1 / errors ** 2
979 
980  if mean is None:
981  mean = np.average(values, weights=inv_var)
982  for iter_idx in range(n_iter):
983  chi = np.fabs(n_factor * (values - mean) / errors)
984  tmp_mean = np.average(
985  values,
986  weights=inv_var / (1 + (chi / alpha) ** beta))
987  diff = np.fabs(tmp_mean - mean)
988  mean = tmp_mean
989  if diff / mean < tol and diff < tol:
990  break
991  return mean
992 
993 
995  pass
996 
997 
998 @register("ap_meanTotFlux")
1000  """Compute the weighted mean and mean error on the point source fluxes
1001  forced photometered at the DiaSource location in the calibrated image.
1002 
1003  Additionally store number of usable data points.
1004  """
1005 
1006  ConfigClass = WeightedMeanDiaPsFluxConfig
1007  outputCols = ["TOTFluxMean", "TOTFluxMeanErr"]
1008  plugType = "multi"
1009  needsFilter = True
1011  @classmethod
1012  def getExecutionOrder(cls):
1013  return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
1014 
1015  @catchWarnings(warns=["invalid value encountered", "divide by zero"])
1016  def calculate(self,
1017  diaObjects,
1018  diaSources,
1019  filterDiaSources,
1020  filterName,
1021  **kwargs):
1022  """Compute the weighted mean and mean error of the point source flux.
1023 
1024  Parameters
1025  ----------
1026  diaObject : `dict`
1027  Summary object to store values in.
1028  diaSources : `pandas.DataFrame`
1029  DataFrame representing all diaSources associated with this
1030  diaObject.
1031  filterDiaSources : `pandas.DataFrame`
1032  DataFrame representing diaSources associated with this
1033  diaObject that are observed in the band pass ``filterName``.
1034  filterName : `str`
1035  Simple, string name of the filter for the flux being calculated.
1036  **kwargs
1037  Any additional keyword arguments that may be passed to the plugin.
1038  """
1039  totMeanName = "{}TOTFluxMean".format(filterName)
1040  if totMeanName not in diaObjects.columns:
1041  diaObjects[totMeanName] = np.nan
1042  totErrName = "{}TOTFluxMeanErr".format(filterName)
1043  if totErrName not in diaObjects.columns:
1044  diaObjects[totErrName] = np.nan
1045 
1046  def _meanFlux(df):
1047  tmpDf = df[~np.logical_or(np.isnan(df["totFlux"]),
1048  np.isnan(df["totFluxErr"]))]
1049  tot_weight = np.nansum(1 / tmpDf["totFluxErr"] ** 2)
1050  fluxMean = np.nansum(tmpDf["totFlux"]
1051  / tmpDf["totFluxErr"] ** 2)
1052  fluxMean /= tot_weight
1053  fluxMeanErr = np.sqrt(1 / tot_weight)
1054 
1055  return pd.Series({totMeanName: fluxMean,
1056  totErrName: fluxMeanErr})
1057 
1058  diaObjects.loc[:, [totMeanName, totErrName]] = \
1059  filterDiaSources.apply(_meanFlux)
1060 
1061 
1063  pass
1065 
1066 @register("ap_sigmaTotFlux")
1068  """Compute scatter of diaSource fluxes.
1069  """
1070 
1071  ConfigClass = SigmaDiaPsFluxConfig
1072  # Output columns are created upon instantiation of the class.
1073  outputCols = ["TOTFluxSigma"]
1074  plugType = "multi"
1075  needsFilter = True
1077  @classmethod
1078  def getExecutionOrder(cls):
1079  return cls.DEFAULT_CATALOGCALCULATIONDEFAULT_CATALOGCALCULATION
1081  def calculate(self,
1082  diaObjects,
1083  diaSources,
1084  filterDiaSources,
1085  filterName,
1086  **kwargs):
1087  """Compute the sigma fluxes of the point source flux measured on the
1088  calibrated image.
1089 
1090  Parameters
1091  ----------
1092  diaObject : `dict`
1093  Summary object to store values in.
1094  diaSources : `pandas.DataFrame`
1095  DataFrame representing all diaSources associated with this
1096  diaObject.
1097  filterDiaSources : `pandas.DataFrame`
1098  DataFrame representing diaSources associated with this
1099  diaObject that are observed in the band pass ``filterName``.
1100  filterName : `str`
1101  Simple, string name of the filter for the flux being calculated.
1102  **kwargs
1103  Any additional keyword arguments that may be passed to the plugin.
1104  """
1105  # Set "delta degrees of freedom (ddf)" to 1 to calculate the unbiased
1106  # estimator of scatter (i.e. 'N - 1' instead of 'N').
1107  diaObjects.loc[:, "{}TOTFluxSigma".format(filterName)] = \
1108  filterDiaSources.totFlux.std()
1109 
int max
Point in an unspecified spherical coordinate system.
Definition: SpherePoint.h:57
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)
HtmPixelization provides HTM indexing of points and regions.
daf::base::PropertyList * list
Definition: fits.cc:913
SpherePoint averageSpherePoint(std::vector< SpherePoint > const &coords)
Return the average of a list of coordinates.
Definition: SpherePoint.cc:235
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174