22"""Plugins for use in DiaSource summary statistics.
25as defined in the schema of the Apdb both in name and units.
31from astropy.stats
import median_absolute_deviation
34from scipy.optimize
import lsq_linear
40from astropy.timeseries
import LombScargle
41from astropy.timeseries
import LombScargleMultiband
45from .diaCalculation
import (
46 DiaObjectCalculationPluginConfig,
47 DiaObjectCalculationPlugin)
48from .pluginRegistry
import register
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")
74 """Decorator for generically catching numpy warnings.
76 def decoratorCatchWarnings(func):
77 @functools.wraps(func)
78 def wrapperCatchWarnings(*args, **kwargs):
79 with warnings.catch_warnings():
81 warnings.filterwarnings(
"ignore", val)
82 return func(*args, **kwargs)
83 return wrapperCatchWarnings
86 return decoratorCatchWarnings
88 return decoratorCatchWarnings(_func)
95 default_dtype=np.float64,
98 force_int_to_float=False,
101 Assign from a source dataframe to a target dataframe in a type safe way.
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.
121 is_series = isinstance(source, pd.Series)
126 source_col = source[col]
128 matched_length =
False
129 if col
in target.columns:
130 target_dtype = target[col].dtype
131 matched_length = len(target) == len(source)
133 target_dtype = default_dtype
135 if (matched_length
or pd.api.types.is_float_dtype(target_dtype))
and not force_int_to_float:
138 target.loc[:, col] = source_col.astype(target_dtype)
144 target[col] = target[col].astype(np.float64)
146 target.loc[:, col] = source_col.astype(np.float64)
147 if not force_int_to_float:
149 target[col] = target[col].fillna(int_fill_value).astype(target_dtype)
154 Computes an optimized periodogram frequency grid for a given time series.
160 oversampling_factor : `int`, optional
161 The oversampling factor for frequency grid.
162 nyquist_factor : `int`, optional
163 The Nyquist factor for frequency grid.
167 frequencies : `array`
168 The computed optimized periodogram frequency grid.
172 baseline = np.max(x0) - np.min(x0)
175 frequency_resolution = 1. / baseline / oversampling_factor
177 num_frequencies = int(
178 0.5 * oversampling_factor * nyquist_factor * num_points)
179 frequencies = frequency_resolution + \
180 frequency_resolution * np.arange(num_frequencies)
189@
register(
"ap_lombScarglePeriodogram")
191 """Compute the single-band period of a DiaObject given a set of DiaSources.
193 ConfigClass = LombScarglePeriodogramConfig
196 outputCols = [
"period",
"power"]
203 @catchWarnings(warns=["All-NaN slice encountered"])
209 """Compute the periodogram.
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.
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
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.
230 df : `pandas.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.
243 pd_tab : `pandas.Series`
244 The output DataFrame with the Lomb-Scargle parameters.
246 tmpDf = df[~np.logical_or(np.isnan(df[
"psfFlux"]),
247 np.isnan(df[
"midpointMjdTai"]))]
249 if len(tmpDf) < min_detections:
250 return pd.Series({periodCol: np.nan, powerCol: np.nan})
252 time = tmpDf[
"midpointMjdTai"].to_numpy()
253 flux = tmpDf[
"psfFlux"].to_numpy()
254 flux_err = tmpDf[
"psfFluxErr"].to_numpy()
256 lsp = LombScargle(time, flux, dy=flux_err, nterms=nterms)
258 time, oversampling_factor=oversampling_factor, nyquist_factor=nyquist_factor)
260 power = lsp.power(f_grid)
262 return pd.Series({periodCol: period[np.argmax(power)],
263 powerCol: np.max(power)})
265 diaObjects.loc[:, [periodCol, powerCol]
266 ] = filterDiaSources.apply(_calculate_period)
273@
register(
"ap_lombScarglePeriodogramMulti")
275 """Compute the multi-band LombScargle periodogram of a DiaObject given a set of DiaSources.
277 ConfigClass = LombScarglePeriodogramMultiConfig
280 outputCols = [
"multiPeriod",
"multiPower",
281 "multiFap",
"multiAmp",
"multiPhase"]
290 """Calculate the False-Alarm probability using the Baluev approximation.
297 The number of detections.
299 The maximum period in the grid.
301 The maximum power in the grid.
305 fap_estimate : `float`
306 The False-Alarm probability Baluev approximation.
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
316 gam_ratio = math.factorial(int((n - 1)/2)) / math.factorial(int((n - 2)/2))
318 return gam_ratio * np.sqrt(
319 4*np.pi*statistics.variance(time)
320 ) * fu * (1-zmax)**((n-4)/2) * np.sqrt(zmax)
324 """Generate the Lomb-Scargle parameters.
327 lsp_model : `astropy.timeseries.LombScargleMultiband`
328 The Lomb-Scargle model.
332 The bands of the time series.
337 The amplitude of the time series.
339 The phase of the time series.
343 .. [1] VanderPlas, J. T., & Ivezic, Z. 2015, ApJ, 812, 18
345 best_params = lsp_model.model_parameters(fbest, units=
True)
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)]
350 df_params = pd.DataFrame([best_params], columns=name_params)
352 unique_bands = np.unique(bands)
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]
360 amp = [a[0]
for a
in amplitude_band]
361 ph = [p[0]
for p
in phase_bands]
365 @catchWarnings(warns=["All-NaN slice encountered"])
370 """Compute the multi-band LombScargle periodogram of a DiaObject given
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.
380 Unused kwargs that are always passed to a plugin.
383 bands_arr = diaSources[
'band'].unique().values
384 unique_bands = np.unique(np.concatenate(bands_arr))
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
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
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.
408 df : `pandas.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.
421 pd_tab : `pandas.Series`
422 The output DataFrame with the Lomb-Scargle parameters.
424 tmpDf = df[~np.logical_or(np.isnan(df[
"psfFlux"]),
425 np.isnan(df[
"midpointMjdTai"]))]
427 if (len(tmpDf)) < min_detections:
428 pd_tab_nodet = pd.Series({periodCol: 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
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()
442 lsp = LombScargleMultiband(time, flux, bands, dy=flux_err,
443 nterms_base=1, nterms_band=1)
446 time, oversampling_factor=oversampling_factor, nyquist_factor=nyquist_factor)
448 power = lsp.power(f_grid)
451 time, len(time), period[np.argmax(power)], np.max(power))
455 pd_tab = pd.Series({periodCol: period[np.argmax(power)],
456 powerCol: np.max(power),
461 for band
in all_unique_bands:
462 pd_tab[f
"{band}_{ampCol}"] = np.nan
463 pd_tab[f
"{band}_{phaseCol}"] = np.nan
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]
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}")
478 diaObjects.loc[:, columns_list
479 ] = diaSources.apply(_calculate_period_multi, unique_bands)
483 """Raised if associated DiaSources are unphysically separated.
488 Observed separation in arseconds.
489 max_allowed_separation : `float`
490 Configured maximum separation in arcseconds.
493 def __init__(self, separation, max_allowed_separation) -> None:
496 super().
__init__(f
"Observed DiaSource separation {separation} exceeds allowed value of "
497 f
"{max_allowed_separation}")
508 MaxAllowedDiaSourceSeparation = pexConfig.Field(
511 doc=
"Max allowed separation of associated DiaSources in arcsec. "
512 "Raises if unphysical separation is found. "
516@register("ap_meanPosition")
518 """Compute the mean position and position uncertainty of a DiaObject
519 from its associated DiaSources.
521 The reported (ra, dec) is the inverse-variance weighted spherical
522 mean of the per-source positions, using only the DiaSources whose
523 per-source coordinate errors are finite. The reported covariance
524 is the covariance of that weighted-mean estimator,
526 C_formal = (sum_i inv(C_i))^-1,
528 where ``C_i`` is the per-source 2x2 covariance, multiplied by a
529 *chi-squared scale factor* that inflates the result when the
530 empirical scatter is larger than the per-source errors predict:
532 chi2 = sum_i r_i^T inv(C_i) r_i (r_i = tangent-plane
533 residual from the mean)
535 C_obj = max(1, chi2 / dof) * C_formal
537 When the data agree with the per-source errors (chi2 ~ dof) the
538 scale factor is unity and ``C_obj == C_formal``. When the residuals
539 exceed what the errors predict, the covariance is inflated.
542 ConfigClass = MeanDiaPositionConfig
546 outputCols = [
"ra",
"dec",
"raErr",
"decErr",
"ra_dec_Cov"]
554 """Compute the mean ra/dec position and its uncertainty for each
559 diaObjects : `pandas.DataFrame`
560 Summary objects to store values in.
561 diaSources : `pandas.DataFrame` or `pandas.DataFrameGroupBy`
562 Catalog of DiaSources summarized by this DiaObject.
564 Any additional keyword arguments that may be passed to the plugin.
567 if outCol
not in diaObjects.columns:
568 diaObjects[outCol] = np.nan
570 maxAllowedSep = self.
config.MaxAllowedDiaSourceSeparation
572 def _column(df, name):
573 """Read a column as float64, or return all-NaN if absent."""
574 if name
in df.columns:
575 return df[name].to_numpy(dtype=np.float64)
576 return np.full(len(df), np.nan)
578 def _computeMeanPos(df):
580 for idx, src
in df.iterrows())
583 unweightedAvg = geom.averageSpherePoint(coords)
584 maxSep = max(unweightedAvg.separation(coord).asArcseconds()
for coord
in coords)
585 if maxSep > maxAllowedSep:
589 raErr = _column(df,
"raErr")
590 decErr = _column(df,
"decErr")
591 raDecCov = _column(df,
"ra_dec_Cov")
592 finiteDiag = (np.isfinite(raErr) & (raErr > 0)) & (np.isfinite(decErr) & (decErr > 0))
593 nDiag = int(finiteDiag.sum())
598 [[off.asDegrees()
for off
in unweightedAvg.getTangentPlaneOffset(coord)]
for coord
in coords]
599 )
if nSrc >= 1
else np.zeros((0, 2))
601 aveCoord = unweightedAvg
609 idx = np.where(finiteDiag)[0]
610 sigmaRaSq = raErr[idx]**2
611 sigmaDecSq = decErr[idx]**2
615 haveFullCov = bool(np.isfinite(raDecCov[idx]).all())
617 C = np.zeros((nDiag, 2, 2), dtype=np.float64)
618 C[:, 0, 0] = sigmaRaSq
619 C[:, 1, 1] = sigmaDecSq
621 C[:, 0, 1] = raDecCov[idx]
622 C[:, 1, 0] = raDecCov[idx]
625 except np.linalg.LinAlgError:
633 W_sum = W.sum(axis=0)
634 C_formal = np.linalg.inv(W_sum)
642 mu_offset = C_formal @ np.einsum(
'nij,nj->i', W, offsets[idx])
643 east, north = float(mu_offset[0]), float(mu_offset[1])
644 sep_deg = float(np.hypot(east, north))
646 bearing =
geom.Angle(float(np.arctan2(north, east)), geom.radians)
647 aveCoord = unweightedAvg.offset(bearing, sep_deg*geom.degrees)
649 varRaFormal = float(C_formal[0, 0])
650 varDecFormal = float(C_formal[1, 1])
651 covFormal = float(C_formal[0, 1])
if haveFullCov
else np.nan
656 varDec = varDecFormal
657 raDecCovObj = covFormal
662 residuals = offsets[idx] - mu_offset
665 chi2 = float(np.einsum(
'ni,nij,nj->', residuals, W, residuals))
667 chi2 = float(np.sum(residuals[:, 0]**2/sigmaRaSq + residuals[:, 1]**2/sigmaDecSq))
669 scale = max(1.0, chi2/dof)
670 varRa = varRaFormal*scale
671 varDec = varDecFormal*scale
672 raDecCovObj = (covFormal*scale
if np.isfinite(covFormal)
else np.nan)
679 "No DiaSources with finite coordinate errors; falling "
680 "back to the unweighted mean position with scatter-only "
685 sampleCov = np.cov(offsets, rowvar=
False, ddof=1)
686 semCov = sampleCov/nSrc
687 varRa = float(semCov[0, 0])
688 varDec = float(semCov[1, 1])
689 raDecCovObj = float(semCov[0, 1])
692 raErrObj = float(np.sqrt(varRa))
if np.isfinite(varRa)
else np.nan
693 decErrObj = float(np.sqrt(varDec))
if np.isfinite(varDec)
else np.nan
695 return pd.Series({
"ra": aveCoord.getRa().asDegrees(),
696 "dec": aveCoord.getDec().asDegrees(),
699 "ra_dec_Cov": raDecCovObj})
701 ans = diaSources.apply(_computeMeanPos)
707 htmLevel = pexConfig.Field(
709 doc=
"Level of the HTM pixelization.",
714@register("ap_HTMIndex")
716 """Compute the mean position of a DiaObject given a set of DiaSources.
720 This plugin was implemented to satisfy requirements of old APDB interface
721 which required ``pixelId`` column in DiaObject with HTM20 index. APDB
722 interface had migrated to not need that information, but we keep this
723 plugin in case it may be useful for something else.
725 ConfigClass = HTMIndexDiaPositionConfig
729 inputCols = [
"ra",
"dec"]
730 outputCols = [
"pixelId"]
734 DiaObjectCalculationPlugin.__init__(self, config, name, metadata)
742 """Compute the mean position of a DiaObject given a set of DiaSources
746 diaObjects : `pandas.dataFrame`
747 Summary objects to store values in and read ra/dec from.
749 Id of the diaObject to update.
751 Any additional keyword arguments that may be passed to the plugin.
754 diaObjects.at[diaObjectId,
"ra"] * geom.degrees,
755 diaObjects.at[diaObjectId,
"dec"] * geom.degrees)
756 diaObjects.at[diaObjectId,
"pixelId"] = self.
pixelator.index(
757 sphPoint.getVector())
766 """Compute the total number of DiaSources associated with this DiaObject.
769 ConfigClass = NumDiaSourcesDiaPluginConfig
770 outputCols = [
"nDiaSources"]
779 """Compute the total number of DiaSources associated with this DiaObject.
784 Summary object to store values in and read ra/dec from.
786 Any additional keyword arguments that may be passed to the plugin.
797 """Find if any DiaSource is flagged.
799 Set the DiaObject flag if any DiaSource is flagged.
802 ConfigClass = NumDiaSourcesDiaPluginConfig
803 outputCols = [
"flags"]
812 """Find if any DiaSource is flagged.
814 Set the DiaObject flag if any DiaSource is flagged.
819 Summary object to store values in and read ra/dec from.
821 Any additional keyword arguments that may be passed to the plugin.
832 """Compute the weighted mean and mean error on the point source fluxes
833 of the DiaSource measured on the difference image.
835 Additionally store number of usable data points.
838 ConfigClass = WeightedMeanDiaPsfFluxConfig
839 outputCols = [
"psfFluxMean",
"psfFluxMeanErr",
"psfFluxNdata"]
847 @catchWarnings(warns=[
"invalid value encountered",
855 """Compute the weighted mean and mean error of the point source flux.
860 Summary object to store values in.
861 diaSources : `pandas.DataFrame`
862 DataFrame representing all diaSources associated with this
864 filterDiaSources : `pandas.DataFrame`
865 DataFrame representing diaSources associated with this
866 diaObject that are observed in the band pass ``band``.
868 Simple, string name of the filter for the flux being calculated.
870 Any additional keyword arguments that may be passed to the plugin.
872 meanName =
"{}_psfFluxMean".format(band)
873 errName =
"{}_psfFluxMeanErr".format(band)
874 nDataName =
"{}_psfFluxNdata".format(band)
875 if meanName
not in diaObjects.columns:
876 diaObjects[meanName] = np.nan
877 if errName
not in diaObjects.columns:
878 diaObjects[errName] = np.nan
879 if nDataName
not in diaObjects.columns:
880 diaObjects[nDataName] = 0
882 def _weightedMean(df):
883 tmpDf = df[~np.logical_or(np.isnan(df[
"psfFlux"]),
884 np.isnan(df[
"psfFluxErr"]))]
885 tot_weight = np.nansum(1 / tmpDf[
"psfFluxErr"] ** 2)
886 fluxMean = np.nansum(tmpDf[
"psfFlux"]
887 / tmpDf[
"psfFluxErr"] ** 2)
888 fluxMean /= tot_weight
890 fluxMeanErr = np.sqrt(1 / tot_weight)
893 nFluxData = len(tmpDf)
895 return pd.Series({meanName: fluxMean,
896 errName: fluxMeanErr,
897 nDataName: nFluxData},
899 df = filterDiaSources.apply(_weightedMean).astype(diaObjects.dtypes[[meanName, errName, nDataName]])
905 percentiles = pexConfig.ListField(
907 default=[5, 25, 50, 75, 95],
908 doc=
"Percentiles to calculate to compute values for. Should be "
913@register("ap_percentileFlux")
915 """Compute percentiles of diaSource fluxes.
918 ConfigClass = PercentileDiaPsfFluxConfig
924 def __init__(self, config, name, metadata, **kwargs):
925 DiaObjectCalculationPlugin.__init__(self,
930 self.
outputCols = [
"psfFluxPercentile{:02d}".format(percent)
931 for percent
in self.
config.percentiles]
937 @catchWarnings(warns=["All-NaN slice encountered"])
944 """Compute the percentile fluxes of the point source flux.
949 Summary object to store values in.
950 diaSources : `pandas.DataFrame`
951 DataFrame representing all diaSources associated with this
953 filterDiaSources : `pandas.DataFrame`
954 DataFrame representing diaSources associated with this
955 diaObject that are observed in the band pass ``band``.
957 Simple, string name of the filter for the flux being calculated.
959 Any additional keyword arguments that may be passed to the plugin.
962 for tilePercent
in self.
config.percentiles:
963 pTileName =
"{}_psfFluxPercentile{:02d}".format(band,
965 pTileNames.append(pTileName)
966 if pTileName
not in diaObjects.columns:
967 diaObjects[pTileName] = np.nan
969 def _fluxPercentiles(df):
970 pTiles = np.nanpercentile(df[
"psfFlux"], self.
config.percentiles)
972 dict((tileName, pTile)
973 for tileName, pTile
in zip(pTileNames, pTiles)))
977 filterDiaSources.apply(_fluxPercentiles),
988 """Compute scatter of diaSource fluxes.
991 ConfigClass = SigmaDiaPsfFluxConfig
993 outputCols = [
"psfFluxSigma"]
1007 """Compute the sigma fluxes of the point source flux.
1012 Summary object to store values in.
1013 diaSources : `pandas.DataFrame`
1014 DataFrame representing all diaSources associated with this
1016 filterDiaSources : `pandas.DataFrame`
1017 DataFrame representing diaSources associated with this
1018 diaObject that are observed in the band pass ``band``.
1020 Simple, string name of the filter for the flux being calculated.
1022 Any additional keyword arguments that may be passed to the plugin.
1026 column =
"{}_psfFluxSigma".format(band)
1030 filterDiaSources.psfFlux.std(),
1041 """Compute chi2 of diaSource fluxes.
1044 ConfigClass = Chi2DiaPsfFluxConfig
1047 inputCols = [
"psfFluxMean"]
1049 outputCols = [
"psfFluxChi2"]
1057 @catchWarnings(warns=["All-NaN slice encountered"])
1064 """Compute the chi2 of the point source fluxes.
1069 Summary object to store values in.
1070 diaSources : `pandas.DataFrame`
1071 DataFrame representing all diaSources associated with this
1073 filterDiaSources : `pandas.DataFrame`
1074 DataFrame representing diaSources associated with this
1075 diaObject that are observed in the band pass ``band``.
1077 Simple, string name of the filter for the flux being calculated.
1079 Any additional keyword arguments that may be passed to the plugin.
1081 meanName =
"{}_psfFluxMean".format(band)
1082 column =
"{}_psfFluxChi2".format(band)
1085 delta = (df[
"psfFlux"]
1086 - diaObjects.at[df.diaObjectId.iat[0], meanName])
1087 return np.nansum((delta / df[
"psfFluxErr"]) ** 2)
1091 filterDiaSources.apply(_chi2),
1102 """Compute median absolute deviation of diaSource fluxes.
1105 ConfigClass = MadDiaPsfFluxConfig
1109 outputCols = [
"psfFluxMAD"]
1117 @catchWarnings(warns=["All-NaN slice encountered"])
1124 """Compute the median absolute deviation of the point source fluxes.
1129 Summary object to store values in.
1130 diaSources : `pandas.DataFrame`
1131 DataFrame representing all diaSources associated with this
1133 filterDiaSources : `pandas.DataFrame`
1134 DataFrame representing diaSources associated with this
1135 diaObject that are observed in the band pass ``band``.
1137 Simple, string name of the filter for the flux being calculated.
1139 Any additional keyword arguments that may be passed to the plugin.
1141 column =
"{}_psfFluxMAD".format(band)
1145 filterDiaSources.psfFlux.apply(median_absolute_deviation, ignore_nan=
True),
1156 """Compute the skew of diaSource fluxes.
1159 ConfigClass = SkewDiaPsfFluxConfig
1163 outputCols = [
"psfFluxSkew"]
1177 """Compute the skew of the point source fluxes.
1182 Summary object to store values in.
1183 diaSources : `pandas.DataFrame`
1184 DataFrame representing all diaSources associated with this
1186 filterDiaSources : `pandas.DataFrame`
1187 DataFrame representing diaSources associated with this
1188 diaObject that are observed in the band pass ``band``.
1190 Simple, string name of the filter for the flux being calculated.
1192 Any additional keyword arguments that may be passed to the plugin.
1194 column =
"{}_psfFluxSkew".format(band)
1198 filterDiaSources.psfFlux.skew(),
1209 """Compute min/max of diaSource fluxes.
1212 ConfigClass = MinMaxDiaPsfFluxConfig
1216 outputCols = [
"psfFluxMin",
"psfFluxMax"]
1230 """Compute min/max of the point source fluxes.
1235 Summary object to store values in.
1236 diaSources : `pandas.DataFrame`
1237 DataFrame representing all diaSources associated with this
1239 filterDiaSources : `pandas.DataFrame`
1240 DataFrame representing diaSources associated with this
1241 diaObject that are observed in the band pass ``band``.
1243 Simple, string name of the filter for the flux being calculated.
1245 Any additional keyword arguments that may be passed to the plugin.
1247 minName =
"{}_psfFluxMin".format(band)
1248 if minName
not in diaObjects.columns:
1249 diaObjects[minName] = np.nan
1250 maxName =
"{}_psfFluxMax".format(band)
1251 if maxName
not in diaObjects.columns:
1252 diaObjects[maxName] = np.nan
1256 filterDiaSources.psfFlux.min(),
1261 filterDiaSources.psfFlux.max(),
1272 """Compute the maximum ratio time ordered deltaFlux / deltaTime.
1275 ConfigClass = MinMaxDiaPsfFluxConfig
1279 outputCols = [
"psfFluxMaxSlope"]
1293 """Compute the maximum ratio time ordered deltaFlux / deltaTime.
1298 Summary object to store values in.
1299 diaSources : `pandas.DataFrame`
1300 DataFrame representing all diaSources associated with this
1302 filterDiaSources : `pandas.DataFrame`
1303 DataFrame representing diaSources associated with this
1304 diaObject that are observed in the band pass ``band``.
1306 Simple, string name of the filter for the flux being calculated.
1308 Any additional keyword arguments that may be passed to the plugin.
1312 tmpDf = df[~np.logical_or(np.isnan(df[
"psfFlux"]),
1313 np.isnan(df[
"midpointMjdTai"]))]
1316 times = tmpDf[
"midpointMjdTai"].to_numpy()
1317 timeArgs = times.argsort()
1318 times = times[timeArgs]
1319 fluxes = tmpDf[
"psfFlux"].to_numpy()[timeArgs]
1320 return (np.diff(fluxes) / np.diff(times)).max()
1322 column =
"{}_psfFluxMaxSlope".format(band)
1326 filterDiaSources.apply(_maxSlope),
1337 """Compute the mean of the dia source errors.
1340 ConfigClass = ErrMeanDiaPsfFluxConfig
1344 outputCols = [
"psfFluxErrMean"]
1358 """Compute the mean of the dia source errors.
1363 Summary object to store values in.
1364 diaSources : `pandas.DataFrame`
1365 DataFrame representing all diaSources associated with this
1367 filterDiaSources : `pandas.DataFrame`
1368 DataFrame representing diaSources associated with this
1369 diaObject that are observed in the band pass ``band``.
1371 Simple, string name of the filter for the flux being calculated.
1373 Any additional keyword arguments that may be passed to the plugin.
1375 column =
"{}_psfFluxErrMean".format(band)
1379 filterDiaSources.psfFluxErr.mean(),
1382 default_dtype=np.float32,
1392 """Compute fit a linear model to flux vs time.
1395 ConfigClass = LinearFitDiaPsfFluxConfig
1399 outputCols = [
"psfFluxLinearSlope",
"psfFluxLinearIntercept"]
1413 """Compute fit a linear model to flux vs time.
1418 Summary object to store values in.
1419 diaSources : `pandas.DataFrame`
1420 DataFrame representing all diaSources associated with this
1422 filterDiaSources : `pandas.DataFrame`
1423 DataFrame representing diaSources associated with this
1424 diaObject that are observed in the band pass ``band``.
1426 Simple, string name of the filter for the flux being calculated.
1428 Any additional keyword arguments that may be passed to the plugin.
1431 mName =
"{}_psfFluxLinearSlope".format(band)
1432 if mName
not in diaObjects.columns:
1433 diaObjects[mName] = np.nan
1434 bName =
"{}_psfFluxLinearIntercept".format(band)
1435 if bName
not in diaObjects.columns:
1436 diaObjects[bName] = np.nan
1437 dtype = diaObjects[mName].dtype
1440 tmpDf = df[~np.logical_or(
1441 np.isnan(df[
"psfFlux"]),
1442 np.logical_or(np.isnan(df[
"psfFluxErr"]),
1443 np.isnan(df[
"midpointMjdTai"])))]
1445 return pd.Series({mName: np.nan, bName: np.nan})
1446 fluxes = tmpDf[
"psfFlux"].to_numpy()
1447 errors = tmpDf[
"psfFluxErr"].to_numpy()
1448 times = tmpDf[
"midpointMjdTai"].to_numpy()
1449 A = np.array([times / errors, 1 / errors]).transpose()
1450 m, b = lsq_linear(A, fluxes / errors).x
1451 return pd.Series({mName: m, bName: b}, dtype=dtype)
1455 filterDiaSources.apply(_linearFit),
1466 """Compute the StetsonJ statistic on the DIA point source fluxes.
1469 ConfigClass = LinearFitDiaPsfFluxConfig
1472 inputCols = [
"psfFluxMean"]
1474 outputCols = [
"psfFluxStetsonJ"]
1488 """Compute the StetsonJ statistic on the DIA point source fluxes.
1493 Summary object to store values in.
1494 diaSources : `pandas.DataFrame`
1495 DataFrame representing all diaSources associated with this
1497 filterDiaSources : `pandas.DataFrame`
1498 DataFrame representing diaSources associated with this
1499 diaObject that are observed in the band pass ``band``.
1501 Simple, string name of the filter for the flux being calculated.
1503 Any additional keyword arguments that may be passed to the plugin.
1505 meanName =
"{}_psfFluxMean".format(band)
1508 tmpDf = df[~np.logical_or(np.isnan(df[
"psfFlux"]),
1509 np.isnan(df[
"psfFluxErr"]))]
1512 fluxes = tmpDf[
"psfFlux"].to_numpy()
1513 errors = tmpDf[
"psfFluxErr"].to_numpy()
1518 diaObjects.at[tmpDf.diaObjectId.iat[0], meanName])
1520 column =
"{}_psfFluxStetsonJ".format(band)
1523 filterDiaSources.apply(_stetsonJ),
1528 """Compute the single band stetsonJ statistic.
1532 fluxes : `numpy.ndarray` (N,)
1533 Calibrated lightcurve flux values.
1534 errors : `numpy.ndarray` (N,)
1535 Errors on the calibrated lightcurve fluxes.
1537 Starting mean from previous plugin.
1542 stetsonJ statistic for the input fluxes and errors.
1546 .. [1] Stetson, P. B., "On the Automatic Determination of Light-Curve
1547 Parameters for Cepheid Variables", PASP, 108, 851S, 1996
1549 n_points = len(fluxes)
1552 np.sqrt(n_points / (n_points - 1)) * (fluxes - flux_mean) / errors)
1553 p_k = delta_val ** 2 - 1
1555 return np.mean(np.sign(p_k) * np.sqrt(np.fabs(p_k)))
1565 """Compute the stetson mean of the fluxes which down-weights outliers.
1567 Weighted biased on an error weighted difference scaled by a constant
1568 (1/``a``) and raised to the power beta. Higher betas more harshly
1569 penalize outliers and ``a`` sets the number of sigma where a weighted
1570 difference of 1 occurs.
1574 values : `numpy.dnarray`, (N,)
1575 Input values to compute the mean of.
1576 errors : `numpy.ndarray`, (N,)
1577 Errors on the input values.
1579 Starting mean value or None.
1581 Scalar down-weighting of the fractional difference. lower->more
1582 clipping. (Default value is 2.)
1584 Power law slope of the used to down-weight outliers. higher->more
1585 clipping. (Default value is 2.)
1587 Number of iterations of clipping.
1589 Fractional and absolute tolerance goal on the change in the mean
1590 before exiting early. (Default value is 1e-6)
1595 Weighted stetson mean result.
1599 .. [1] Stetson, P. B., "On the Automatic Determination of Light-Curve
1600 Parameters for Cepheid Variables", PASP, 108, 851S, 1996
1602 n_points = len(values)
1603 n_factor = np.sqrt(n_points / (n_points - 1))
1604 inv_var = 1 / errors ** 2
1607 mean = np.average(values, weights=inv_var)
1608 for iter_idx
in range(n_iter):
1609 chi = np.fabs(n_factor * (values - mean) / errors)
1610 tmp_mean = np.average(
1612 weights=inv_var / (1 + (chi / alpha) ** beta))
1613 diff = np.fabs(tmp_mean - mean)
1615 if diff / mean < tol
and diff < tol:
1626 """Compute the weighted mean and mean error on the point source fluxes
1627 forced photometered at the DiaSource location in the calibrated image.
1629 Additionally store number of usable data points.
1632 ConfigClass = WeightedMeanDiaPsfFluxConfig
1633 outputCols = [
"scienceFluxMean",
"scienceFluxMeanErr"]
1641 @catchWarnings(warns=[
"invalid value encountered",
1649 """Compute the weighted mean and mean error of the point source flux.
1654 Summary object to store values in.
1655 diaSources : `pandas.DataFrame`
1656 DataFrame representing all diaSources associated with this
1658 filterDiaSources : `pandas.DataFrame`
1659 DataFrame representing diaSources associated with this
1660 diaObject that are observed in the band pass ``band``.
1662 Simple, string name of the filter for the flux being calculated.
1664 Any additional keyword arguments that may be passed to the plugin.
1666 totMeanName =
"{}_scienceFluxMean".format(band)
1667 if totMeanName
not in diaObjects.columns:
1668 diaObjects[totMeanName] = np.nan
1669 totErrName =
"{}_scienceFluxMeanErr".format(band)
1670 if totErrName
not in diaObjects.columns:
1671 diaObjects[totErrName] = np.nan
1674 tmpDf = df[~np.logical_or(np.isnan(df[
"scienceFlux"]),
1675 np.isnan(df[
"scienceFluxErr"]))]
1676 tot_weight = np.nansum(1 / tmpDf[
"scienceFluxErr"] ** 2)
1677 fluxMean = np.nansum(tmpDf[
"scienceFlux"]
1678 / tmpDf[
"scienceFluxErr"] ** 2)
1679 fluxMean /= tot_weight
1680 fluxMeanErr = np.sqrt(1 / tot_weight)
1682 return pd.Series({totMeanName: fluxMean,
1683 totErrName: fluxMeanErr})
1685 df = filterDiaSources.apply(_meanFlux).astype(diaObjects.dtypes[[totMeanName, totErrName]])
1695 """Compute scatter of diaSource fluxes.
1698 ConfigClass = SigmaDiaPsfFluxConfig
1700 outputCols = [
"scienceFluxSigma"]
1714 """Compute the sigma fluxes of the point source flux measured on the
1720 Summary object to store values in.
1721 diaSources : `pandas.DataFrame`
1722 DataFrame representing all diaSources associated with this
1724 filterDiaSources : `pandas.DataFrame`
1725 DataFrame representing diaSources associated with this
1726 diaObject that are observed in the band pass ``band``.
1728 Simple, string name of the filter for the flux being calculated.
1730 Any additional keyword arguments that may be passed to the plugin.
1734 column =
"{}_scienceFluxSigma".format(band)
float FLUX_MOMENTS_CALCULATED
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaObjectId, **kwargs)
__init__(self, config, name, metadata)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band)
calculate_baluev_fap(time, n, maxPeriod, zmax)
generate_lsp_params(lsp_model, fbest, bands)
calculate(self, diaObjects, diaSources, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, **kwargs)
__init__(self, config, name, metadata, **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, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
_stetson_J(self, fluxes, errors, mean=None)
_stetson_mean(self, values, errors, mean=None, alpha=2., beta=2., n_iter=20, tol=1e-6)
None __init__(self, separation, max_allowed_separation)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
calculate(self, diaObjects, diaSources, filterDiaSources, band, **kwargs)
float DEFAULT_CATALOGCALCULATION
typeSafePandasAssignment(target, source, columns, default_dtype=np.float64, int_fill_value=0, force_int_to_float=False)
catchWarnings(_func=None, *, warns=[])
compute_optimized_periodogram_grid(x0, oversampling_factor=5, nyquist_factor=100)
register(name, shouldApCorr=False, apCorrList=())