lsst.meas.base g51c93253c0+8bb2015bcd
Loading...
Searching...
No Matches
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.pipe.base as pipeBase
39import lsst.sphgeom as sphgeom
40from astropy.timeseries import LombScargle
41from astropy.timeseries import LombScargleMultiband
42import math
43import statistics
44
45from .diaCalculation import (
46 DiaObjectCalculationPluginConfig,
47 DiaObjectCalculationPlugin)
48from .pluginRegistry import register
49
50
51__all__ = ("MeanDiaPositionConfig", "MeanDiaPosition",
52 "HTMIndexDiaPosition", "HTMIndexDiaPositionConfig",
53 "NumDiaSourcesDiaPlugin", "NumDiaSourcesDiaPluginConfig",
54 "SimpleSourceFlagDiaPlugin", "SimpleSourceFlagDiaPluginConfig",
55 "WeightedMeanDiaPsfFluxConfig", "WeightedMeanDiaPsfFlux",
56 "PercentileDiaPsfFlux", "PercentileDiaPsfFluxConfig",
57 "SigmaDiaPsfFlux", "SigmaDiaPsfFluxConfig",
58 "Chi2DiaPsfFlux", "Chi2DiaPsfFluxConfig",
59 "MadDiaPsfFlux", "MadDiaPsfFluxConfig",
60 "SkewDiaPsfFlux", "SkewDiaPsfFluxConfig",
61 "MinMaxDiaPsfFlux", "MinMaxDiaPsfFluxConfig",
62 "MaxSlopeDiaPsfFlux", "MaxSlopeDiaPsfFluxConfig",
63 "ErrMeanDiaPsfFlux", "ErrMeanDiaPsfFluxConfig",
64 "LinearFitDiaPsfFlux", "LinearFitDiaPsfFluxConfig",
65 "StetsonJDiaPsfFlux", "StetsonJDiaPsfFluxConfig",
66 "WeightedMeanDiaTotFlux", "WeightedMeanDiaTotFluxConfig",
67 "SigmaDiaTotFlux", "SigmaDiaTotFluxConfig",
68 "LombScarglePeriodogram", "LombScarglePeriodogramConfig",
69 "LombScarglePeriodogramMulti", "LombScarglePeriodogramMultiConfig",
70 "UnphysicalDiaSourceSeparation")
71
72
73def catchWarnings(_func=None, *, warns=[]):
74 """Decorator for generically catching numpy warnings.
75 """
76 def decoratorCatchWarnings(func):
77 @functools.wraps(func)
78 def wrapperCatchWarnings(*args, **kwargs):
79 with warnings.catch_warnings():
80 for val in warns:
81 warnings.filterwarnings("ignore", val)
82 return func(*args, **kwargs)
83 return wrapperCatchWarnings
84
85 if _func is None:
86 return decoratorCatchWarnings
87 else:
88 return decoratorCatchWarnings(_func)
89
90
92 target,
93 source,
94 columns,
95 default_dtype=np.float64,
96 int_fill_value=0,
97 # TODO DM-53254: Remove the force_int_to_float hack.
98 force_int_to_float=False,
99):
100 """
101 Assign from a source dataframe to a target dataframe in a type safe way.
102
103 Parameters
104 ----------
105 target : `pd.DataFrame`
106 Target pandas dataframe.
107 source : `pd.DataFrame` or `pd.Series`
108 Grouped source dataframe.
109 columns : `list` [`str`]
110 List of columns to transfer.
111 default_dtype : `np.dtype`, optional
112 Default datatype (if not in target).
113 int_fill_value : `int`, optional
114 Fill value for integer columns to avoid pandas insisting
115 that everything should be float-ified as nans.
116 force_int_to_float : `bool`, optional
117 Force integer columns to float columns? Use this option
118 for backwards compatibility for old pandas misfeatures which
119 are expected by some downstream processes.
120 """
121 is_series = isinstance(source, pd.Series)
122 for col in columns:
123 if is_series:
124 source_col = source
125 else:
126 source_col = source[col]
127
128 matched_length = False
129 if col in target.columns:
130 target_dtype = target[col].dtype
131 matched_length = len(target) == len(source)
132 else:
133 target_dtype = default_dtype
134
135 if (matched_length or pd.api.types.is_float_dtype(target_dtype)) and not force_int_to_float:
136 # If we have a matched length or float, we can do a
137 # straight assignment here.
138 target.loc[:, col] = source_col.astype(target_dtype)
139 else:
140 # With mis-matched integers, we must do this dance to preserve types.
141 # Note that this may lose precision with very large numbers.
142
143 # Convert to float
144 target[col] = target[col].astype(np.float64)
145 # Set the column, casting to float.
146 target.loc[:, col] = source_col.astype(np.float64)
147 if not force_int_to_float:
148 # Convert back to integer
149 target[col] = target[col].fillna(int_fill_value).astype(target_dtype)
150
151
152def compute_optimized_periodogram_grid(x0, oversampling_factor=5, nyquist_factor=100):
153 """
154 Computes an optimized periodogram frequency grid for a given time series.
155
156 Parameters
157 ----------
158 x0 : `array`
159 The input time axis.
160 oversampling_factor : `int`, optional
161 The oversampling factor for frequency grid.
162 nyquist_factor : `int`, optional
163 The Nyquist factor for frequency grid.
164
165 Returns
166 -------
167 frequencies : `array`
168 The computed optimized periodogram frequency grid.
169 """
170
171 num_points = len(x0)
172 baseline = np.max(x0) - np.min(x0)
173
174 # Calculate the frequency resolution based on oversampling factor and baseline
175 frequency_resolution = 1. / baseline / oversampling_factor
176
177 num_frequencies = int(
178 0.5 * oversampling_factor * nyquist_factor * num_points)
179 frequencies = frequency_resolution + \
180 frequency_resolution * np.arange(num_frequencies)
181
182 return frequencies
183
184
186 pass
187
188
189@register("ap_lombScarglePeriodogram")
191 """Compute the single-band period of a DiaObject given a set of DiaSources.
192 """
193 ConfigClass = LombScarglePeriodogramConfig
194
195 plugType = "multi"
196 outputCols = ["period", "power"]
197 needsFilter = True
198
199 @classmethod
202
203 @catchWarnings(warns=["All-NaN slice encountered"])
204 def calculate(self,
205 diaObjects,
206 diaSources,
207 filterDiaSources,
208 band):
209 """Compute the periodogram.
210
211 Parameters
212 ----------
213 diaObjects : `pandas.DataFrame`
214 Summary objects to store values in.
215 diaSources : `pandas.DataFrame` or `pandas.DataFrameGroupBy`
216 Catalog of DiaSources summarized by this DiaObject.
217 """
218
219 # Check and initialize output columns in diaObjects.
220 if (periodCol := f"{band}_period") not in diaObjects.columns:
221 diaObjects[periodCol] = np.nan
222 if (powerCol := f"{band}_power") not in diaObjects.columns:
223 diaObjects[powerCol] = np.nan
224
225 def _calculate_period(df, min_detections=5, nterms=1, oversampling_factor=5, nyquist_factor=100):
226 """Compute the Lomb-Scargle periodogram given a set of DiaSources.
227
228 Parameters
229 ----------
230 df : `pandas.DataFrame`
231 The input DataFrame.
232 min_detections : `int`, optional
233 The minimum number of detections.
234 nterms : `int`, optional
235 The number of terms in the Lomb-Scargle model.
236 oversampling_factor : `int`, optional
237 The oversampling factor for frequency grid.
238 nyquist_factor : `int`, optional
239 The Nyquist factor for frequency grid.
240
241 Returns
242 -------
243 pd_tab : `pandas.Series`
244 The output DataFrame with the Lomb-Scargle parameters.
245 """
246 tmpDf = df[~np.logical_or(np.isnan(df["psfFlux"]),
247 np.isnan(df["midpointMjdTai"]))]
248
249 if len(tmpDf) < min_detections:
250 return pd.Series({periodCol: np.nan, powerCol: np.nan})
251
252 time = tmpDf["midpointMjdTai"].to_numpy()
253 flux = tmpDf["psfFlux"].to_numpy()
254 flux_err = tmpDf["psfFluxErr"].to_numpy()
255
256 lsp = LombScargle(time, flux, dy=flux_err, nterms=nterms)
258 time, oversampling_factor=oversampling_factor, nyquist_factor=nyquist_factor)
259 period = 1/f_grid
260 power = lsp.power(f_grid)
261
262 return pd.Series({periodCol: period[np.argmax(power)],
263 powerCol: np.max(power)})
264
265 diaObjects.loc[:, [periodCol, powerCol]
266 ] = filterDiaSources.apply(_calculate_period)
267
268
270 pass
271
272
273@register("ap_lombScarglePeriodogramMulti")
275 """Compute the multi-band LombScargle periodogram of a DiaObject given a set of DiaSources.
276 """
277 ConfigClass = LombScarglePeriodogramMultiConfig
278
279 plugType = "multi"
280 outputCols = ["multiPeriod", "multiPower",
281 "multiFap", "multiAmp", "multiPhase"]
282 needsFilter = True
283
284 @classmethod
287
288 @staticmethod
289 def calculate_baluev_fap(time, n, maxPeriod, zmax):
290 """Calculate the False-Alarm probability using the Baluev approximation.
291
292 Parameters
293 ----------
294 time : `array`
295 The input time axis.
296 n : `int`
297 The number of detections.
298 maxPeriod : `float`
299 The maximum period in the grid.
300 zmax : `float`
301 The maximum power in the grid.
302
303 Returns
304 -------
305 fap_estimate : `float`
306 The False-Alarm probability Baluev approximation.
307
308 Notes
309 ----------
310 .. [1] Baluev, R. V. 2008, MNRAS, 385, 1279
311 .. [2] Süveges, M., Guy, L.P., Eyer, L., et al. 2015, MNRAS, 450, 2052
312 """
313 if n <= 2:
314 return np.nan
315
316 gam_ratio = math.factorial(int((n - 1)/2)) / math.factorial(int((n - 2)/2))
317 fu = 1/maxPeriod
318 return gam_ratio * np.sqrt(
319 4*np.pi*statistics.variance(time)
320 ) * fu * (1-zmax)**((n-4)/2) * np.sqrt(zmax)
321
322 @staticmethod
323 def generate_lsp_params(lsp_model, fbest, bands):
324 """Generate the Lomb-Scargle parameters.
325 Parameters
326 ----------
327 lsp_model : `astropy.timeseries.LombScargleMultiband`
328 The Lomb-Scargle model.
329 fbest : `float`
330 The best period.
331 bands : `array`
332 The bands of the time series.
333
334 Returns
335 -------
336 Amp : `array`
337 The amplitude of the time series.
338 Ph : `array`
339 The phase of the time series.
340
341 Notes
342 ----------
343 .. [1] VanderPlas, J. T., & Ivezic, Z. 2015, ApJ, 812, 18
344 """
345 best_params = lsp_model.model_parameters(fbest, units=True)
346
347 name_params = [f"theta_base_{i}" for i in range(3)]
348 name_params += [f"theta_band_{band}_{i}" for band in np.unique(bands) for i in range(3)]
349
350 df_params = pd.DataFrame([best_params], columns=name_params)
351
352 unique_bands = np.unique(bands)
353
354 amplitude_band = [np.sqrt(df_params[f"theta_band_{band}_1"]**2
355 + df_params[f"theta_band_{band}_2"]**2)
356 for band in unique_bands]
357 phase_bands = [np.arctan2(df_params[f"theta_band_{band}_2"],
358 df_params[f"theta_band_{band}_1"]) for band in unique_bands]
359
360 amp = [a[0] for a in amplitude_band]
361 ph = [p[0] for p in phase_bands]
362
363 return amp, ph
364
365 @catchWarnings(warns=["All-NaN slice encountered"])
366 def calculate(self,
367 diaObjects,
368 diaSources,
369 **kwargs):
370 """Compute the multi-band LombScargle periodogram of a DiaObject given
371 a set of DiaSources.
372
373 Parameters
374 ----------
375 diaObjects : `pandas.DataFrame`
376 Summary objects to store values in.
377 diaSources : `pandas.DataFrame` or `pandas.DataFrameGroupBy`
378 Catalog of DiaSources summarized by this DiaObject.
379 **kwargs : `dict`
380 Unused kwargs that are always passed to a plugin.
381 """
382
383 bands_arr = diaSources['band'].unique().values
384 unique_bands = np.unique(np.concatenate(bands_arr))
385 # Check and initialize output columns in diaObjects.
386 if (periodCol := "multiPeriod") not in diaObjects.columns:
387 diaObjects[periodCol] = np.nan
388 if (powerCol := "multiPower") not in diaObjects.columns:
389 diaObjects[powerCol] = np.nan
390 if (fapCol := "multiFap") not in diaObjects.columns:
391 diaObjects[fapCol] = np.nan
392 ampCol = "multiAmp"
393 phaseCol = "multiPhase"
394 for i in range(len(unique_bands)):
395 ampCol_band = f"{unique_bands[i]}_{ampCol}"
396 if ampCol_band not in diaObjects.columns:
397 diaObjects[ampCol_band] = np.nan
398 phaseCol_band = f"{unique_bands[i]}_{phaseCol}"
399 if phaseCol_band not in diaObjects.columns:
400 diaObjects[phaseCol_band] = np.nan
401
402 def _calculate_period_multi(df, all_unique_bands,
403 min_detections=9, oversampling_factor=5, nyquist_factor=100):
404 """Calculate the multi-band Lomb-Scargle periodogram.
405
406 Parameters
407 ----------
408 df : `pandas.DataFrame`
409 The input DataFrame.
410 all_unique_bands : `list` of `str`
411 List of all bands present in the diaSource table that is being worked on.
412 min_detections : `int`, optional
413 The minimum number of detections, including all bands.
414 oversampling_factor : `int`, optional
415 The oversampling factor for frequency grid.
416 nyquist_factor : `int`, optional
417 The Nyquist factor for frequency grid.
418
419 Returns
420 -------
421 pd_tab : `pandas.Series`
422 The output DataFrame with the Lomb-Scargle parameters.
423 """
424 tmpDf = df[~np.logical_or(np.isnan(df["psfFlux"]),
425 np.isnan(df["midpointMjdTai"]))]
426
427 if (len(tmpDf)) < min_detections:
428 pd_tab_nodet = pd.Series({periodCol: np.nan,
429 powerCol: np.nan,
430 fapCol: np.nan})
431 for band in all_unique_bands:
432 pd_tab_nodet[f"{band}_{ampCol}"] = np.nan
433 pd_tab_nodet[f"{band}_{phaseCol}"] = np.nan
434
435 return pd_tab_nodet
436
437 time = tmpDf["midpointMjdTai"].to_numpy()
438 flux = tmpDf["psfFlux"].to_numpy()
439 flux_err = tmpDf["psfFluxErr"].to_numpy()
440 bands = tmpDf["band"].to_numpy()
441
442 lsp = LombScargleMultiband(time, flux, bands, dy=flux_err,
443 nterms_base=1, nterms_band=1)
444
446 time, oversampling_factor=oversampling_factor, nyquist_factor=nyquist_factor)
447 period = 1/f_grid
448 power = lsp.power(f_grid)
449
450 fap_estimate = self.calculate_baluev_fap(
451 time, len(time), period[np.argmax(power)], np.max(power))
452
453 params_table_new = self.generate_lsp_params(lsp, f_grid[np.argmax(power)], bands)
454
455 pd_tab = pd.Series({periodCol: period[np.argmax(power)],
456 powerCol: np.max(power),
457 fapCol: fap_estimate
458 })
459
460 # Initialize the per-band amplitude/phase columns as NaNs
461 for band in all_unique_bands:
462 pd_tab[f"{band}_{ampCol}"] = np.nan
463 pd_tab[f"{band}_{phaseCol}"] = np.nan
464
465 # Populate the values of only the bands that have data for this diaSource
466 unique_bands = np.unique(bands)
467 for i in range(len(unique_bands)):
468 pd_tab[f"{unique_bands[i]}_{ampCol}"] = params_table_new[0][i]
469 pd_tab[f"{unique_bands[i]}_{phaseCol}"] = params_table_new[1][i]
470
471 return pd_tab
472
473 columns_list = [periodCol, powerCol, fapCol]
474 for i in range(len(unique_bands)):
475 columns_list.append(f"{unique_bands[i]}_{ampCol}")
476 columns_list.append(f"{unique_bands[i]}_{phaseCol}")
477
478 diaObjects.loc[:, columns_list
479 ] = diaSources.apply(_calculate_period_multi, unique_bands)
480
481
482class UnphysicalDiaSourceSeparation(pipeBase.AlgorithmError):
483 """Raised if associated DiaSources are unphysically separated.
484
485 Parameters
486 ----------
487 separation : `float`
488 Observed separation in arseconds.
489 max_allowed_separation : `float`
490 Configured maximum separation in arcseconds.
491 """
492
493 def __init__(self, separation, max_allowed_separation) -> None:
494 self._separation = separation
495 self._max_allowed_separation = max_allowed_separation
496 super().__init__(f"Observed DiaSource separation {separation} exceeds allowed value of "
497 f"{max_allowed_separation}")
498
499 @property
500 def metadata(self) -> dict:
501 return {
502 "separation": self._separation,
503 "max_allowed_separation": self._max_allowed_separation,
504 }
505
506
508 MaxAllowedDiaSourceSeparation = pexConfig.Field(
509 dtype=float,
510 default=3.0,
511 doc="Max allowed separation of associated DiaSources in arcsec. "
512 "Raises if unphysical separation is found. "
513 )
514
515
516@register("ap_meanPosition")
518 """Compute the mean position of a DiaObject given a set of DiaSources.
519 """
520
521 ConfigClass = MeanDiaPositionConfig
522
523 plugType = 'multi'
524
525 outputCols = ["ra", "dec"]
526 needsFilter = False
527
528 @classmethod
531
532 def calculate(self, diaObjects, diaSources, **kwargs):
533 """Compute the mean ra/dec position of the diaObject given the
534 diaSource locations.
535
536 Parameters
537 ----------
538 diaObjects : `pandas.DataFrame`
539 Summary objects to store values in.
540 diaSources : `pandas.DataFrame` or `pandas.DataFrameGroupBy`
541 Catalog of DiaSources summarized by this DiaObject.
542 **kwargs
543 Any additional keyword arguments that may be passed to the plugin.
544 """
545 for outCol in self.outputCols:
546 if outCol not in diaObjects.columns:
547 diaObjects[outCol] = np.nan
548
549 def _computeMeanPos(df):
550 coords = list(geom.SpherePoint(src["ra"], src["dec"], geom.degrees)
551 for idx, src in df.iterrows())
552 aveCoord = geom.averageSpherePoint(coords)
553
554 # We don't want the DIAObject position to move due to misassociated sources
555 maxSep = max(aveCoord.separation(coord).asArcseconds() for coord in coords)
556
557 if maxSep > self.config.MaxAllowedDiaSourceSeparation:
559 self.config.MaxAllowedDiaSourceSeparation)
560
561 return pd.Series({"ra": aveCoord.getRa().asDegrees(),
562 "dec": aveCoord.getDec().asDegrees()})
563
564 ans = diaSources.apply(_computeMeanPos)
565 typeSafePandasAssignment(diaObjects, ans, ["ra", "dec"])
566
567
569
570 htmLevel = pexConfig.Field(
571 dtype=int,
572 doc="Level of the HTM pixelization.",
573 default=20,
574 )
575
576
577@register("ap_HTMIndex")
579 """Compute the mean position of a DiaObject given a set of DiaSources.
580
581 Notes
582 -----
583 This plugin was implemented to satisfy requirements of old APDB interface
584 which required ``pixelId`` column in DiaObject with HTM20 index. APDB
585 interface had migrated to not need that information, but we keep this
586 plugin in case it may be useful for something else.
587 """
588 ConfigClass = HTMIndexDiaPositionConfig
589
590 plugType = 'single'
591
592 inputCols = ["ra", "dec"]
593 outputCols = ["pixelId"]
594 needsFilter = False
595
596 def __init__(self, config, name, metadata):
597 DiaObjectCalculationPlugin.__init__(self, config, name, metadata)
599
600 @classmethod
602 return cls.FLUX_MOMENTS_CALCULATED
603
604 def calculate(self, diaObjects, diaObjectId, **kwargs):
605 """Compute the mean position of a DiaObject given a set of DiaSources
606
607 Parameters
608 ----------
609 diaObjects : `pandas.dataFrame`
610 Summary objects to store values in and read ra/dec from.
611 diaObjectId : `int`
612 Id of the diaObject to update.
613 **kwargs
614 Any additional keyword arguments that may be passed to the plugin.
615 """
616 sphPoint = geom.SpherePoint(
617 diaObjects.at[diaObjectId, "ra"] * geom.degrees,
618 diaObjects.at[diaObjectId, "dec"] * geom.degrees)
619 diaObjects.at[diaObjectId, "pixelId"] = self.pixelator.index(
620 sphPoint.getVector())
621
622
624 pass
625
626
627@register("ap_nDiaSources")
629 """Compute the total number of DiaSources associated with this DiaObject.
630 """
631
632 ConfigClass = NumDiaSourcesDiaPluginConfig
633 outputCols = ["nDiaSources"]
634 plugType = "multi"
635 needsFilter = False
636
637 @classmethod
640
641 def calculate(self, diaObjects, diaSources, **kwargs):
642 """Compute the total number of DiaSources associated with this DiaObject.
643
644 Parameters
645 ----------
646 diaObject : `dict`
647 Summary object to store values in and read ra/dec from.
648 **kwargs
649 Any additional keyword arguments that may be passed to the plugin.
650 """
651 typeSafePandasAssignment(diaObjects, diaSources.diaObjectId.count(), ["nDiaSources"])
652
653
655 pass
656
657
658@register("ap_diaObjectFlag")
660 """Find if any DiaSource is flagged.
661
662 Set the DiaObject flag if any DiaSource is flagged.
663 """
664
665 ConfigClass = NumDiaSourcesDiaPluginConfig
666 outputCols = ["flags"]
667 plugType = "multi"
668 needsFilter = False
669
670 @classmethod
673
674 def calculate(self, diaObjects, diaSources, **kwargs):
675 """Find if any DiaSource is flagged.
676
677 Set the DiaObject flag if any DiaSource is flagged.
678
679 Parameters
680 ----------
681 diaObject : `dict`
682 Summary object to store values in and read ra/dec from.
683 **kwargs
684 Any additional keyword arguments that may be passed to the plugin.
685 """
686 typeSafePandasAssignment(diaObjects, diaSources.flags.any(), ["flags"], default_dtype=np.uint64)
687
688
695 """Compute the weighted mean and mean error on the point source fluxes
696 of the DiaSource measured on the difference image.
697
698 Additionally store number of usable data points.
699 """
700
701 ConfigClass = WeightedMeanDiaPsfFluxConfig
702 outputCols = ["psfFluxMean", "psfFluxMeanErr", "psfFluxNdata"]
703 plugType = "multi"
704 needsFilter = True
705
706 @classmethod
709
710 @catchWarnings(warns=["invalid value encountered",
711 "divide by zero"])
712 def calculate(self,
713 diaObjects,
714 diaSources,
715 filterDiaSources,
716 band,
717 **kwargs):
718 """Compute the weighted mean and mean error of the point source flux.
719
720 Parameters
721 ----------
722 diaObject : `dict`
723 Summary object to store values in.
724 diaSources : `pandas.DataFrame`
725 DataFrame representing all diaSources associated with this
726 diaObject.
727 filterDiaSources : `pandas.DataFrame`
728 DataFrame representing diaSources associated with this
729 diaObject that are observed in the band pass ``band``.
730 band : `str`
731 Simple, string name of the filter for the flux being calculated.
732 **kwargs
733 Any additional keyword arguments that may be passed to the plugin.
734 """
735 meanName = "{}_psfFluxMean".format(band)
736 errName = "{}_psfFluxMeanErr".format(band)
737 nDataName = "{}_psfFluxNdata".format(band)
738 if meanName not in diaObjects.columns:
739 diaObjects[meanName] = np.nan
740 if errName not in diaObjects.columns:
741 diaObjects[errName] = np.nan
742 if nDataName not in diaObjects.columns:
743 diaObjects[nDataName] = 0
744
745 def _weightedMean(df):
746 tmpDf = df[~np.logical_or(np.isnan(df["psfFlux"]),
747 np.isnan(df["psfFluxErr"]))]
748 tot_weight = np.nansum(1 / tmpDf["psfFluxErr"] ** 2)
749 fluxMean = np.nansum(tmpDf["psfFlux"]
750 / tmpDf["psfFluxErr"] ** 2)
751 fluxMean /= tot_weight
752 if tot_weight > 0:
753 fluxMeanErr = np.sqrt(1 / tot_weight)
754 else:
755 fluxMeanErr = np.nan
756 nFluxData = len(tmpDf)
757
758 return pd.Series({meanName: fluxMean,
759 errName: fluxMeanErr,
760 nDataName: nFluxData},
761 dtype="object")
762 df = filterDiaSources.apply(_weightedMean).astype(diaObjects.dtypes[[meanName, errName, nDataName]])
763 # TODO DM-53254: Remove the force_int_to_float hack.
764 typeSafePandasAssignment(diaObjects, df, [meanName, errName, nDataName], force_int_to_float=True)
765
766
768 percentiles = pexConfig.ListField(
769 dtype=int,
770 default=[5, 25, 50, 75, 95],
771 doc="Percentiles to calculate to compute values for. Should be "
772 "integer values."
773 )
774
775
776@register("ap_percentileFlux")
778 """Compute percentiles of diaSource fluxes.
779 """
780
781 ConfigClass = PercentileDiaPsfFluxConfig
782 # Output columns are created upon instantiation of the class.
783 outputCols = []
784 plugType = "multi"
785 needsFilter = True
786
787 def __init__(self, config, name, metadata, **kwargs):
788 DiaObjectCalculationPlugin.__init__(self,
789 config,
790 name,
791 metadata,
792 **kwargs)
793 self.outputCols = ["psfFluxPercentile{:02d}".format(percent)
794 for percent in self.config.percentiles]
795
796 @classmethod
799
800 @catchWarnings(warns=["All-NaN slice encountered"])
801 def calculate(self,
802 diaObjects,
803 diaSources,
804 filterDiaSources,
805 band,
806 **kwargs):
807 """Compute the percentile fluxes of the point source flux.
808
809 Parameters
810 ----------
811 diaObject : `dict`
812 Summary object to store values in.
813 diaSources : `pandas.DataFrame`
814 DataFrame representing all diaSources associated with this
815 diaObject.
816 filterDiaSources : `pandas.DataFrame`
817 DataFrame representing diaSources associated with this
818 diaObject that are observed in the band pass ``band``.
819 band : `str`
820 Simple, string name of the filter for the flux being calculated.
821 **kwargs
822 Any additional keyword arguments that may be passed to the plugin.
823 """
824 pTileNames = []
825 for tilePercent in self.config.percentiles:
826 pTileName = "{}_psfFluxPercentile{:02d}".format(band,
827 tilePercent)
828 pTileNames.append(pTileName)
829 if pTileName not in diaObjects.columns:
830 diaObjects[pTileName] = np.nan
831
832 def _fluxPercentiles(df):
833 pTiles = np.nanpercentile(df["psfFlux"], self.config.percentiles)
834 return pd.Series(
835 dict((tileName, pTile)
836 for tileName, pTile in zip(pTileNames, pTiles)))
837
839 diaObjects,
840 filterDiaSources.apply(_fluxPercentiles),
841 pTileNames,
842 )
843
844
846 pass
847
848
849@register("ap_sigmaFlux")
851 """Compute scatter of diaSource fluxes.
852 """
853
854 ConfigClass = SigmaDiaPsfFluxConfig
855 # Output columns are created upon instantiation of the class.
856 outputCols = ["psfFluxSigma"]
857 plugType = "multi"
858 needsFilter = True
859
860 @classmethod
863
864 def calculate(self,
865 diaObjects,
866 diaSources,
867 filterDiaSources,
868 band,
869 **kwargs):
870 """Compute the sigma fluxes of the point source flux.
871
872 Parameters
873 ----------
874 diaObject : `dict`
875 Summary object to store values in.
876 diaSources : `pandas.DataFrame`
877 DataFrame representing all diaSources associated with this
878 diaObject.
879 filterDiaSources : `pandas.DataFrame`
880 DataFrame representing diaSources associated with this
881 diaObject that are observed in the band pass ``band``.
882 band : `str`
883 Simple, string name of the filter for the flux being calculated.
884 **kwargs
885 Any additional keyword arguments that may be passed to the plugin.
886 """
887 # Set "delta degrees of freedom (ddf)" to 1 to calculate the unbiased
888 # estimator of scatter (i.e. 'N - 1' instead of 'N').
889 column = "{}_psfFluxSigma".format(band)
890
892 diaObjects,
893 filterDiaSources.psfFlux.std(),
894 [column],
895 )
896
897
899 pass
900
901
902@register("ap_chi2Flux")
904 """Compute chi2 of diaSource fluxes.
905 """
906
907 ConfigClass = Chi2DiaPsfFluxConfig
908
909 # Required input Cols
910 inputCols = ["psfFluxMean"]
911 # Output columns are created upon instantiation of the class.
912 outputCols = ["psfFluxChi2"]
913 plugType = "multi"
914 needsFilter = True
915
916 @classmethod
918 return cls.FLUX_MOMENTS_CALCULATED
919
920 @catchWarnings(warns=["All-NaN slice encountered"])
921 def calculate(self,
922 diaObjects,
923 diaSources,
924 filterDiaSources,
925 band,
926 **kwargs):
927 """Compute the chi2 of the point source fluxes.
928
929 Parameters
930 ----------
931 diaObject : `dict`
932 Summary object to store values in.
933 diaSources : `pandas.DataFrame`
934 DataFrame representing all diaSources associated with this
935 diaObject.
936 filterDiaSources : `pandas.DataFrame`
937 DataFrame representing diaSources associated with this
938 diaObject that are observed in the band pass ``band``.
939 band : `str`
940 Simple, string name of the filter for the flux being calculated.
941 **kwargs
942 Any additional keyword arguments that may be passed to the plugin.
943 """
944 meanName = "{}_psfFluxMean".format(band)
945 column = "{}_psfFluxChi2".format(band)
946
947 def _chi2(df):
948 delta = (df["psfFlux"]
949 - diaObjects.at[df.diaObjectId.iat[0], meanName])
950 return np.nansum((delta / df["psfFluxErr"]) ** 2)
951
953 diaObjects,
954 filterDiaSources.apply(_chi2),
955 [column],
956 )
957
958
960 pass
961
962
963@register("ap_madFlux")
965 """Compute median absolute deviation of diaSource fluxes.
966 """
967
968 ConfigClass = MadDiaPsfFluxConfig
969
970 # Required input Cols
971 # Output columns are created upon instantiation of the class.
972 outputCols = ["psfFluxMAD"]
973 plugType = "multi"
974 needsFilter = True
975
976 @classmethod
979
980 @catchWarnings(warns=["All-NaN slice encountered"])
981 def calculate(self,
982 diaObjects,
983 diaSources,
984 filterDiaSources,
985 band,
986 **kwargs):
987 """Compute the median absolute deviation of the point source fluxes.
988
989 Parameters
990 ----------
991 diaObject : `dict`
992 Summary object to store values in.
993 diaSources : `pandas.DataFrame`
994 DataFrame representing all diaSources associated with this
995 diaObject.
996 filterDiaSources : `pandas.DataFrame`
997 DataFrame representing diaSources associated with this
998 diaObject that are observed in the band pass ``band``.
999 band : `str`
1000 Simple, string name of the filter for the flux being calculated.
1001 **kwargs
1002 Any additional keyword arguments that may be passed to the plugin.
1003 """
1004 column = "{}_psfFluxMAD".format(band)
1005
1007 diaObjects,
1008 filterDiaSources.psfFlux.apply(median_absolute_deviation, ignore_nan=True),
1009 [column],
1010 )
1011
1012
1014 pass
1015
1016
1017@register("ap_skewFlux")
1019 """Compute the skew of diaSource fluxes.
1020 """
1021
1022 ConfigClass = SkewDiaPsfFluxConfig
1023
1024 # Required input Cols
1025 # Output columns are created upon instantiation of the class.
1026 outputCols = ["psfFluxSkew"]
1027 plugType = "multi"
1028 needsFilter = True
1029
1030 @classmethod
1033
1034 def calculate(self,
1035 diaObjects,
1036 diaSources,
1037 filterDiaSources,
1038 band,
1039 **kwargs):
1040 """Compute the skew of the point source fluxes.
1041
1042 Parameters
1043 ----------
1044 diaObject : `dict`
1045 Summary object to store values in.
1046 diaSources : `pandas.DataFrame`
1047 DataFrame representing all diaSources associated with this
1048 diaObject.
1049 filterDiaSources : `pandas.DataFrame`
1050 DataFrame representing diaSources associated with this
1051 diaObject that are observed in the band pass ``band``.
1052 band : `str`
1053 Simple, string name of the filter for the flux being calculated.
1054 **kwargs
1055 Any additional keyword arguments that may be passed to the plugin.
1056 """
1057 column = "{}_psfFluxSkew".format(band)
1058
1060 diaObjects,
1061 filterDiaSources.psfFlux.skew(),
1062 [column],
1063 )
1064
1065
1067 pass
1068
1069
1070@register("ap_minMaxFlux")
1072 """Compute min/max of diaSource fluxes.
1073 """
1074
1075 ConfigClass = MinMaxDiaPsfFluxConfig
1076
1077 # Required input Cols
1078 # Output columns are created upon instantiation of the class.
1079 outputCols = ["psfFluxMin", "psfFluxMax"]
1080 plugType = "multi"
1081 needsFilter = True
1082
1083 @classmethod
1086
1087 def calculate(self,
1088 diaObjects,
1089 diaSources,
1090 filterDiaSources,
1091 band,
1092 **kwargs):
1093 """Compute min/max of the point source fluxes.
1094
1095 Parameters
1096 ----------
1097 diaObject : `dict`
1098 Summary object to store values in.
1099 diaSources : `pandas.DataFrame`
1100 DataFrame representing all diaSources associated with this
1101 diaObject.
1102 filterDiaSources : `pandas.DataFrame`
1103 DataFrame representing diaSources associated with this
1104 diaObject that are observed in the band pass ``band``.
1105 band : `str`
1106 Simple, string name of the filter for the flux being calculated.
1107 **kwargs
1108 Any additional keyword arguments that may be passed to the plugin.
1109 """
1110 minName = "{}_psfFluxMin".format(band)
1111 if minName not in diaObjects.columns:
1112 diaObjects[minName] = np.nan
1113 maxName = "{}_psfFluxMax".format(band)
1114 if maxName not in diaObjects.columns:
1115 diaObjects[maxName] = np.nan
1116
1118 diaObjects,
1119 filterDiaSources.psfFlux.min(),
1120 [minName],
1121 )
1123 diaObjects,
1124 filterDiaSources.psfFlux.max(),
1125 [maxName],
1126 )
1127
1128
1130 pass
1131
1132
1133@register("ap_maxSlopeFlux")
1135 """Compute the maximum ratio time ordered deltaFlux / deltaTime.
1136 """
1137
1138 ConfigClass = MinMaxDiaPsfFluxConfig
1139
1140 # Required input Cols
1141 # Output columns are created upon instantiation of the class.
1142 outputCols = ["psfFluxMaxSlope"]
1143 plugType = "multi"
1144 needsFilter = True
1145
1146 @classmethod
1149
1150 def calculate(self,
1151 diaObjects,
1152 diaSources,
1153 filterDiaSources,
1154 band,
1155 **kwargs):
1156 """Compute the maximum ratio time ordered deltaFlux / deltaTime.
1157
1158 Parameters
1159 ----------
1160 diaObject : `dict`
1161 Summary object to store values in.
1162 diaSources : `pandas.DataFrame`
1163 DataFrame representing all diaSources associated with this
1164 diaObject.
1165 filterDiaSources : `pandas.DataFrame`
1166 DataFrame representing diaSources associated with this
1167 diaObject that are observed in the band pass ``band``.
1168 band : `str`
1169 Simple, string name of the filter for the flux being calculated.
1170 **kwargs
1171 Any additional keyword arguments that may be passed to the plugin.
1172 """
1173
1174 def _maxSlope(df):
1175 tmpDf = df[~np.logical_or(np.isnan(df["psfFlux"]),
1176 np.isnan(df["midpointMjdTai"]))]
1177 if len(tmpDf) < 2:
1178 return np.nan
1179 times = tmpDf["midpointMjdTai"].to_numpy()
1180 timeArgs = times.argsort()
1181 times = times[timeArgs]
1182 fluxes = tmpDf["psfFlux"].to_numpy()[timeArgs]
1183 return (np.diff(fluxes) / np.diff(times)).max()
1184
1185 column = "{}_psfFluxMaxSlope".format(band)
1186
1188 diaObjects,
1189 filterDiaSources.apply(_maxSlope),
1190 [column],
1191 )
1192
1193
1195 pass
1196
1197
1198@register("ap_meanErrFlux")
1200 """Compute the mean of the dia source errors.
1201 """
1202
1203 ConfigClass = ErrMeanDiaPsfFluxConfig
1204
1205 # Required input Cols
1206 # Output columns are created upon instantiation of the class.
1207 outputCols = ["psfFluxErrMean"]
1208 plugType = "multi"
1209 needsFilter = True
1210
1211 @classmethod
1214
1215 def calculate(self,
1216 diaObjects,
1217 diaSources,
1218 filterDiaSources,
1219 band,
1220 **kwargs):
1221 """Compute the mean of the dia source errors.
1222
1223 Parameters
1224 ----------
1225 diaObject : `dict`
1226 Summary object to store values in.
1227 diaSources : `pandas.DataFrame`
1228 DataFrame representing all diaSources associated with this
1229 diaObject.
1230 filterDiaSources : `pandas.DataFrame`
1231 DataFrame representing diaSources associated with this
1232 diaObject that are observed in the band pass ``band``.
1233 band : `str`
1234 Simple, string name of the filter for the flux being calculated.
1235 **kwargs
1236 Any additional keyword arguments that may be passed to the plugin.
1237 """
1238 column = "{}_psfFluxErrMean".format(band)
1239
1241 diaObjects,
1242 filterDiaSources.psfFluxErr.mean(),
1243 [column],
1244 # Note that the schemas expect this to be single-precision.
1245 default_dtype=np.float32,
1246 )
1247
1248
1250 pass
1251
1252
1253@register("ap_linearFit")
1255 """Compute fit a linear model to flux vs time.
1256 """
1257
1258 ConfigClass = LinearFitDiaPsfFluxConfig
1259
1260 # Required input Cols
1261 # Output columns are created upon instantiation of the class.
1262 outputCols = ["psfFluxLinearSlope", "psfFluxLinearIntercept"]
1263 plugType = "multi"
1264 needsFilter = True
1265
1266 @classmethod
1269
1270 def calculate(self,
1271 diaObjects,
1272 diaSources,
1273 filterDiaSources,
1274 band,
1275 **kwargs):
1276 """Compute fit a linear model to flux vs time.
1277
1278 Parameters
1279 ----------
1280 diaObject : `dict`
1281 Summary object to store values in.
1282 diaSources : `pandas.DataFrame`
1283 DataFrame representing all diaSources associated with this
1284 diaObject.
1285 filterDiaSources : `pandas.DataFrame`
1286 DataFrame representing diaSources associated with this
1287 diaObject that are observed in the band pass ``band``.
1288 band : `str`
1289 Simple, string name of the filter for the flux being calculated.
1290 **kwargs
1291 Any additional keyword arguments that may be passed to the plugin.
1292 """
1293
1294 mName = "{}_psfFluxLinearSlope".format(band)
1295 if mName not in diaObjects.columns:
1296 diaObjects[mName] = np.nan
1297 bName = "{}_psfFluxLinearIntercept".format(band)
1298 if bName not in diaObjects.columns:
1299 diaObjects[bName] = np.nan
1300 dtype = diaObjects[mName].dtype
1301
1302 def _linearFit(df):
1303 tmpDf = df[~np.logical_or(
1304 np.isnan(df["psfFlux"]),
1305 np.logical_or(np.isnan(df["psfFluxErr"]),
1306 np.isnan(df["midpointMjdTai"])))]
1307 if len(tmpDf) < 2:
1308 return pd.Series({mName: np.nan, bName: np.nan})
1309 fluxes = tmpDf["psfFlux"].to_numpy()
1310 errors = tmpDf["psfFluxErr"].to_numpy()
1311 times = tmpDf["midpointMjdTai"].to_numpy()
1312 A = np.array([times / errors, 1 / errors]).transpose()
1313 m, b = lsq_linear(A, fluxes / errors).x
1314 return pd.Series({mName: m, bName: b}, dtype=dtype)
1315
1317 diaObjects,
1318 filterDiaSources.apply(_linearFit),
1319 [mName, bName],
1320 )
1321
1322
1324 pass
1325
1326
1327@register("ap_stetsonJ")
1329 """Compute the StetsonJ statistic on the DIA point source fluxes.
1330 """
1331
1332 ConfigClass = LinearFitDiaPsfFluxConfig
1333
1334 # Required input Cols
1335 inputCols = ["psfFluxMean"]
1336 # Output columns are created upon instantiation of the class.
1337 outputCols = ["psfFluxStetsonJ"]
1338 plugType = "multi"
1339 needsFilter = True
1340
1341 @classmethod
1343 return cls.FLUX_MOMENTS_CALCULATED
1344
1345 def calculate(self,
1346 diaObjects,
1347 diaSources,
1348 filterDiaSources,
1349 band,
1350 **kwargs):
1351 """Compute the StetsonJ statistic on the DIA point source fluxes.
1352
1353 Parameters
1354 ----------
1355 diaObject : `dict`
1356 Summary object to store values in.
1357 diaSources : `pandas.DataFrame`
1358 DataFrame representing all diaSources associated with this
1359 diaObject.
1360 filterDiaSources : `pandas.DataFrame`
1361 DataFrame representing diaSources associated with this
1362 diaObject that are observed in the band pass ``band``.
1363 band : `str`
1364 Simple, string name of the filter for the flux being calculated.
1365 **kwargs
1366 Any additional keyword arguments that may be passed to the plugin.
1367 """
1368 meanName = "{}_psfFluxMean".format(band)
1369
1370 def _stetsonJ(df):
1371 tmpDf = df[~np.logical_or(np.isnan(df["psfFlux"]),
1372 np.isnan(df["psfFluxErr"]))]
1373 if len(tmpDf) < 2:
1374 return np.nan
1375 fluxes = tmpDf["psfFlux"].to_numpy()
1376 errors = tmpDf["psfFluxErr"].to_numpy()
1377
1378 return self._stetson_J(
1379 fluxes,
1380 errors,
1381 diaObjects.at[tmpDf.diaObjectId.iat[0], meanName])
1382
1383 column = "{}_psfFluxStetsonJ".format(band)
1385 diaObjects,
1386 filterDiaSources.apply(_stetsonJ),
1387 [column],
1388 )
1389
1390 def _stetson_J(self, fluxes, errors, mean=None):
1391 """Compute the single band stetsonJ statistic.
1392
1393 Parameters
1394 ----------
1395 fluxes : `numpy.ndarray` (N,)
1396 Calibrated lightcurve flux values.
1397 errors : `numpy.ndarray` (N,)
1398 Errors on the calibrated lightcurve fluxes.
1399 mean : `float`
1400 Starting mean from previous plugin.
1401
1402 Returns
1403 -------
1404 stetsonJ : `float`
1405 stetsonJ statistic for the input fluxes and errors.
1406
1407 References
1408 ----------
1409 .. [1] Stetson, P. B., "On the Automatic Determination of Light-Curve
1410 Parameters for Cepheid Variables", PASP, 108, 851S, 1996
1411 """
1412 n_points = len(fluxes)
1413 flux_mean = self._stetson_mean(fluxes, errors, mean)
1414 delta_val = (
1415 np.sqrt(n_points / (n_points - 1)) * (fluxes - flux_mean) / errors)
1416 p_k = delta_val ** 2 - 1
1417
1418 return np.mean(np.sign(p_k) * np.sqrt(np.fabs(p_k)))
1419
1421 values,
1422 errors,
1423 mean=None,
1424 alpha=2.,
1425 beta=2.,
1426 n_iter=20,
1427 tol=1e-6):
1428 """Compute the stetson mean of the fluxes which down-weights outliers.
1429
1430 Weighted biased on an error weighted difference scaled by a constant
1431 (1/``a``) and raised to the power beta. Higher betas more harshly
1432 penalize outliers and ``a`` sets the number of sigma where a weighted
1433 difference of 1 occurs.
1434
1435 Parameters
1436 ----------
1437 values : `numpy.dnarray`, (N,)
1438 Input values to compute the mean of.
1439 errors : `numpy.ndarray`, (N,)
1440 Errors on the input values.
1441 mean : `float`
1442 Starting mean value or None.
1443 alpha : `float`
1444 Scalar down-weighting of the fractional difference. lower->more
1445 clipping. (Default value is 2.)
1446 beta : `float`
1447 Power law slope of the used to down-weight outliers. higher->more
1448 clipping. (Default value is 2.)
1449 n_iter : `int`
1450 Number of iterations of clipping.
1451 tol : `float`
1452 Fractional and absolute tolerance goal on the change in the mean
1453 before exiting early. (Default value is 1e-6)
1454
1455 Returns
1456 -------
1457 mean : `float`
1458 Weighted stetson mean result.
1459
1460 References
1461 ----------
1462 .. [1] Stetson, P. B., "On the Automatic Determination of Light-Curve
1463 Parameters for Cepheid Variables", PASP, 108, 851S, 1996
1464 """
1465 n_points = len(values)
1466 n_factor = np.sqrt(n_points / (n_points - 1))
1467 inv_var = 1 / errors ** 2
1468
1469 if mean is None:
1470 mean = np.average(values, weights=inv_var)
1471 for iter_idx in range(n_iter):
1472 chi = np.fabs(n_factor * (values - mean) / errors)
1473 tmp_mean = np.average(
1474 values,
1475 weights=inv_var / (1 + (chi / alpha) ** beta))
1476 diff = np.fabs(tmp_mean - mean)
1477 mean = tmp_mean
1478 if diff / mean < tol and diff < tol:
1479 break
1480 return mean
1481
1482
1484 pass
1485
1486
1487@register("ap_meanTotFlux")
1489 """Compute the weighted mean and mean error on the point source fluxes
1490 forced photometered at the DiaSource location in the calibrated image.
1491
1492 Additionally store number of usable data points.
1493 """
1494
1495 ConfigClass = WeightedMeanDiaPsfFluxConfig
1496 outputCols = ["scienceFluxMean", "scienceFluxMeanErr"]
1497 plugType = "multi"
1498 needsFilter = True
1499
1500 @classmethod
1503
1504 @catchWarnings(warns=["invalid value encountered",
1505 "divide by zero"])
1506 def calculate(self,
1507 diaObjects,
1508 diaSources,
1509 filterDiaSources,
1510 band,
1511 **kwargs):
1512 """Compute the weighted mean and mean error of the point source flux.
1513
1514 Parameters
1515 ----------
1516 diaObject : `dict`
1517 Summary object to store values in.
1518 diaSources : `pandas.DataFrame`
1519 DataFrame representing all diaSources associated with this
1520 diaObject.
1521 filterDiaSources : `pandas.DataFrame`
1522 DataFrame representing diaSources associated with this
1523 diaObject that are observed in the band pass ``band``.
1524 band : `str`
1525 Simple, string name of the filter for the flux being calculated.
1526 **kwargs
1527 Any additional keyword arguments that may be passed to the plugin.
1528 """
1529 totMeanName = "{}_scienceFluxMean".format(band)
1530 if totMeanName not in diaObjects.columns:
1531 diaObjects[totMeanName] = np.nan
1532 totErrName = "{}_scienceFluxMeanErr".format(band)
1533 if totErrName not in diaObjects.columns:
1534 diaObjects[totErrName] = np.nan
1535
1536 def _meanFlux(df):
1537 tmpDf = df[~np.logical_or(np.isnan(df["scienceFlux"]),
1538 np.isnan(df["scienceFluxErr"]))]
1539 tot_weight = np.nansum(1 / tmpDf["scienceFluxErr"] ** 2)
1540 fluxMean = np.nansum(tmpDf["scienceFlux"]
1541 / tmpDf["scienceFluxErr"] ** 2)
1542 fluxMean /= tot_weight
1543 fluxMeanErr = np.sqrt(1 / tot_weight)
1544
1545 return pd.Series({totMeanName: fluxMean,
1546 totErrName: fluxMeanErr})
1547
1548 df = filterDiaSources.apply(_meanFlux).astype(diaObjects.dtypes[[totMeanName, totErrName]])
1549 typeSafePandasAssignment(diaObjects, df, [totMeanName, totErrName])
1550
1551
1553 pass
1554
1555
1556@register("ap_sigmaTotFlux")
1558 """Compute scatter of diaSource fluxes.
1559 """
1560
1561 ConfigClass = SigmaDiaPsfFluxConfig
1562 # Output columns are created upon instantiation of the class.
1563 outputCols = ["scienceFluxSigma"]
1564 plugType = "multi"
1565 needsFilter = True
1566
1567 @classmethod
1570
1571 def calculate(self,
1572 diaObjects,
1573 diaSources,
1574 filterDiaSources,
1575 band,
1576 **kwargs):
1577 """Compute the sigma fluxes of the point source flux measured on the
1578 calibrated image.
1579
1580 Parameters
1581 ----------
1582 diaObject : `dict`
1583 Summary object to store values in.
1584 diaSources : `pandas.DataFrame`
1585 DataFrame representing all diaSources associated with this
1586 diaObject.
1587 filterDiaSources : `pandas.DataFrame`
1588 DataFrame representing diaSources associated with this
1589 diaObject that are observed in the band pass ``band``.
1590 band : `str`
1591 Simple, string name of the filter for the flux being calculated.
1592 **kwargs
1593 Any additional keyword arguments that may be passed to the plugin.
1594 """
1595 # Set "delta degrees of freedom (ddf)" to 1 to calculate the unbiased
1596 # estimator of scatter (i.e. 'N - 1' instead of 'N').
1597 column = "{}_scienceFluxSigma".format(band)
1598 typeSafePandasAssignment(diaObjects, filterDiaSources.scienceFlux.std(), [column])
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
_stetson_mean(self, values, errors, mean=None, alpha=2., beta=2., n_iter=20, tol=1e-6)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
compute_optimized_periodogram_grid(x0, oversampling_factor=5, nyquist_factor=100)
typeSafePandasAssignment(target, source, columns, default_dtype=np.float64, int_fill_value=0, force_int_to_float=False)
register(name, shouldApCorr=False, apCorrList=())