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 "FirstLastDiaSourceTimeConfig",
"FirstLastDiaSourceTime",
53 "HTMIndexDiaPosition",
"HTMIndexDiaPositionConfig",
54 "NumDiaSourcesDiaPlugin",
"NumDiaSourcesDiaPluginConfig",
55 "SimpleSourceFlagDiaPlugin",
"SimpleSourceFlagDiaPluginConfig",
56 "WeightedMeanDiaPsfFluxConfig",
"WeightedMeanDiaPsfFlux",
57 "PercentileDiaPsfFlux",
"PercentileDiaPsfFluxConfig",
58 "SigmaDiaPsfFlux",
"SigmaDiaPsfFluxConfig",
59 "Chi2DiaPsfFlux",
"Chi2DiaPsfFluxConfig",
60 "MadDiaPsfFlux",
"MadDiaPsfFluxConfig",
61 "SkewDiaPsfFlux",
"SkewDiaPsfFluxConfig",
62 "MinMaxDiaPsfFlux",
"MinMaxDiaPsfFluxConfig",
63 "MaxSlopeDiaPsfFlux",
"MaxSlopeDiaPsfFluxConfig",
64 "ErrMeanDiaPsfFlux",
"ErrMeanDiaPsfFluxConfig",
65 "LinearFitDiaPsfFlux",
"LinearFitDiaPsfFluxConfig",
66 "StetsonJDiaPsfFlux",
"StetsonJDiaPsfFluxConfig",
67 "WeightedMeanDiaTotFlux",
"WeightedMeanDiaTotFluxConfig",
68 "SigmaDiaTotFlux",
"SigmaDiaTotFluxConfig",
69 "LombScarglePeriodogram",
"LombScarglePeriodogramConfig",
70 "LombScarglePeriodogramMulti",
"LombScarglePeriodogramMultiConfig",
71 "UnphysicalDiaSourceSeparation")
75 """Decorator for generically catching numpy warnings.
77 def decoratorCatchWarnings(func):
78 @functools.wraps(func)
79 def wrapperCatchWarnings(*args, **kwargs):
80 with warnings.catch_warnings():
82 warnings.filterwarnings(
"ignore", val)
83 return func(*args, **kwargs)
84 return wrapperCatchWarnings
87 return decoratorCatchWarnings
89 return decoratorCatchWarnings(_func)
96 default_dtype=np.float64,
99 force_int_to_float=False,
102 Assign from a source dataframe to a target dataframe in a type safe way.
106 target : `pd.DataFrame`
107 Target pandas dataframe.
108 source : `pd.DataFrame` or `pd.Series`
109 Grouped source dataframe.
110 columns : `list` [`str`]
111 List of columns to transfer.
112 default_dtype : `np.dtype`, optional
113 Default datatype (if not in target).
114 int_fill_value : `int`, optional
115 Fill value for integer columns to avoid pandas insisting
116 that everything should be float-ified as nans.
117 force_int_to_float : `bool`, optional
118 Force integer columns to float columns? Use this option
119 for backwards compatibility for old pandas misfeatures which
120 are expected by some downstream processes.
122 is_series = isinstance(source, pd.Series)
127 source_col = source[col]
129 matched_length =
False
130 if col
in target.columns:
131 target_dtype = target[col].dtype
132 matched_length = len(target) == len(source)
134 target_dtype = default_dtype
136 if (matched_length
or pd.api.types.is_float_dtype(target_dtype))
and not force_int_to_float:
139 target.loc[:, col] = source_col.astype(target_dtype)
145 target[col] = target[col].astype(np.float64)
147 target.loc[:, col] = source_col.astype(np.float64)
148 if not force_int_to_float:
150 target[col] = target[col].fillna(int_fill_value).astype(target_dtype)
155 Computes an optimized periodogram frequency grid for a given time series.
161 oversampling_factor : `int`, optional
162 The oversampling factor for frequency grid.
163 nyquist_factor : `int`, optional
164 The Nyquist factor for frequency grid.
168 frequencies : `array`
169 The computed optimized periodogram frequency grid.
173 baseline = np.max(x0) - np.min(x0)
176 frequency_resolution = 1. / baseline / oversampling_factor
178 num_frequencies = int(
179 0.5 * oversampling_factor * nyquist_factor * num_points)
180 frequencies = frequency_resolution + \
181 frequency_resolution * np.arange(num_frequencies)
190@
register(
"ap_lombScarglePeriodogram")
192 """Compute the single-band period of a DiaObject given a set of DiaSources.
194 ConfigClass = LombScarglePeriodogramConfig
197 outputCols = [
"period",
"power"]
204 @catchWarnings(warns=["All-NaN slice encountered"])
210 """Compute the periodogram.
214 diaObjects : `pandas.DataFrame`
215 Summary objects to store values in.
216 diaSources : `pandas.DataFrame` or `pandas.DataFrameGroupBy`
217 Catalog of DiaSources summarized by this DiaObject.
221 if (periodCol := f
"{band}_period")
not in diaObjects.columns:
222 diaObjects[periodCol] = np.nan
223 if (powerCol := f
"{band}_power")
not in diaObjects.columns:
224 diaObjects[powerCol] = np.nan
226 def _calculate_period(df, min_detections=5, nterms=1, oversampling_factor=5, nyquist_factor=100):
227 """Compute the Lomb-Scargle periodogram given a set of DiaSources.
231 df : `pandas.DataFrame`
233 min_detections : `int`, optional
234 The minimum number of detections.
235 nterms : `int`, optional
236 The number of terms in the Lomb-Scargle model.
237 oversampling_factor : `int`, optional
238 The oversampling factor for frequency grid.
239 nyquist_factor : `int`, optional
240 The Nyquist factor for frequency grid.
244 pd_tab : `pandas.Series`
245 The output DataFrame with the Lomb-Scargle parameters.
247 tmpDf = df[~np.logical_or(np.isnan(df[
"psfFlux"]),
248 np.isnan(df[
"midpointMjdTai"]))]
250 if len(tmpDf) < min_detections:
251 return pd.Series({periodCol: np.nan, powerCol: np.nan})
253 time = tmpDf[
"midpointMjdTai"].to_numpy()
254 flux = tmpDf[
"psfFlux"].to_numpy()
255 flux_err = tmpDf[
"psfFluxErr"].to_numpy()
257 lsp = LombScargle(time, flux, dy=flux_err, nterms=nterms)
259 time, oversampling_factor=oversampling_factor, nyquist_factor=nyquist_factor)
261 power = lsp.power(f_grid)
263 return pd.Series({periodCol: period[np.argmax(power)],
264 powerCol: np.max(power)})
266 diaObjects.loc[:, [periodCol, powerCol]
267 ] = filterDiaSources.apply(_calculate_period)
274@
register(
"ap_lombScarglePeriodogramMulti")
276 """Compute the multi-band LombScargle periodogram of a DiaObject given a set of DiaSources.
278 ConfigClass = LombScarglePeriodogramMultiConfig
281 outputCols = [
"multiPeriod",
"multiPower",
282 "multiFap",
"multiAmp",
"multiPhase"]
291 """Calculate the False-Alarm probability using the Baluev approximation.
298 The number of detections.
300 The maximum period in the grid.
302 The maximum power in the grid.
306 fap_estimate : `float`
307 The False-Alarm probability Baluev approximation.
311 .. [1] Baluev, R. V. 2008, MNRAS, 385, 1279
312 .. [2] Süveges, M., Guy, L.P., Eyer, L., et al. 2015, MNRAS, 450, 2052
317 gam_ratio = math.factorial(int((n - 1)/2)) / math.factorial(int((n - 2)/2))
319 return gam_ratio * np.sqrt(
320 4*np.pi*statistics.variance(time)
321 ) * fu * (1-zmax)**((n-4)/2) * np.sqrt(zmax)
325 """Generate the Lomb-Scargle parameters.
328 lsp_model : `astropy.timeseries.LombScargleMultiband`
329 The Lomb-Scargle model.
333 The bands of the time series.
338 The amplitude of the time series.
340 The phase of the time series.
344 .. [1] VanderPlas, J. T., & Ivezic, Z. 2015, ApJ, 812, 18
346 best_params = lsp_model.model_parameters(fbest, units=
True)
348 name_params = [f
"theta_base_{i}" for i
in range(3)]
349 name_params += [f
"theta_band_{band}_{i}" for band
in np.unique(bands)
for i
in range(3)]
351 df_params = pd.DataFrame([best_params], columns=name_params)
353 unique_bands = np.unique(bands)
355 amplitude_band = [np.sqrt(df_params[f
"theta_band_{band}_1"]**2
356 + df_params[f
"theta_band_{band}_2"]**2)
357 for band
in unique_bands]
358 phase_bands = [np.arctan2(df_params[f
"theta_band_{band}_2"],
359 df_params[f
"theta_band_{band}_1"])
for band
in unique_bands]
361 amp = [a[0]
for a
in amplitude_band]
362 ph = [p[0]
for p
in phase_bands]
366 @catchWarnings(warns=["All-NaN slice encountered"])
371 """Compute the multi-band LombScargle periodogram of a DiaObject given
376 diaObjects : `pandas.DataFrame`
377 Summary objects to store values in.
378 diaSources : `pandas.DataFrame` or `pandas.DataFrameGroupBy`
379 Catalog of DiaSources summarized by this DiaObject.
381 Unused kwargs that are always passed to a plugin.
384 bands_arr = diaSources[
'band'].unique().values
385 unique_bands = np.unique(np.concatenate(bands_arr))
387 if (periodCol :=
"multiPeriod")
not in diaObjects.columns:
388 diaObjects[periodCol] = np.nan
389 if (powerCol :=
"multiPower")
not in diaObjects.columns:
390 diaObjects[powerCol] = np.nan
391 if (fapCol :=
"multiFap")
not in diaObjects.columns:
392 diaObjects[fapCol] = np.nan
394 phaseCol =
"multiPhase"
395 for i
in range(len(unique_bands)):
396 ampCol_band = f
"{unique_bands[i]}_{ampCol}"
397 if ampCol_band
not in diaObjects.columns:
398 diaObjects[ampCol_band] = np.nan
399 phaseCol_band = f
"{unique_bands[i]}_{phaseCol}"
400 if phaseCol_band
not in diaObjects.columns:
401 diaObjects[phaseCol_band] = np.nan
403 def _calculate_period_multi(df, all_unique_bands,
404 min_detections=9, oversampling_factor=5, nyquist_factor=100):
405 """Calculate the multi-band Lomb-Scargle periodogram.
409 df : `pandas.DataFrame`
411 all_unique_bands : `list` of `str`
412 List of all bands present in the diaSource table that is being worked on.
413 min_detections : `int`, optional
414 The minimum number of detections, including all bands.
415 oversampling_factor : `int`, optional
416 The oversampling factor for frequency grid.
417 nyquist_factor : `int`, optional
418 The Nyquist factor for frequency grid.
422 pd_tab : `pandas.Series`
423 The output DataFrame with the Lomb-Scargle parameters.
425 tmpDf = df[~np.logical_or(np.isnan(df[
"psfFlux"]),
426 np.isnan(df[
"midpointMjdTai"]))]
428 if (len(tmpDf)) < min_detections:
429 pd_tab_nodet = pd.Series({periodCol: np.nan,
432 for band
in all_unique_bands:
433 pd_tab_nodet[f
"{band}_{ampCol}"] = np.nan
434 pd_tab_nodet[f
"{band}_{phaseCol}"] = np.nan
438 time = tmpDf[
"midpointMjdTai"].to_numpy()
439 flux = tmpDf[
"psfFlux"].to_numpy()
440 flux_err = tmpDf[
"psfFluxErr"].to_numpy()
441 bands = tmpDf[
"band"].to_numpy()
443 lsp = LombScargleMultiband(time, flux, bands, dy=flux_err,
444 nterms_base=1, nterms_band=1)
447 time, oversampling_factor=oversampling_factor, nyquist_factor=nyquist_factor)
449 power = lsp.power(f_grid)
452 time, len(time), period[np.argmax(power)], np.max(power))
456 pd_tab = pd.Series({periodCol: period[np.argmax(power)],
457 powerCol: np.max(power),
462 for band
in all_unique_bands:
463 pd_tab[f
"{band}_{ampCol}"] = np.nan
464 pd_tab[f
"{band}_{phaseCol}"] = np.nan
467 unique_bands = np.unique(bands)
468 for i
in range(len(unique_bands)):
469 pd_tab[f
"{unique_bands[i]}_{ampCol}"] = params_table_new[0][i]
470 pd_tab[f
"{unique_bands[i]}_{phaseCol}"] = params_table_new[1][i]
474 columns_list = [periodCol, powerCol, fapCol]
475 for i
in range(len(unique_bands)):
476 columns_list.append(f
"{unique_bands[i]}_{ampCol}")
477 columns_list.append(f
"{unique_bands[i]}_{phaseCol}")
479 diaObjects.loc[:, columns_list
480 ] = diaSources.apply(_calculate_period_multi, unique_bands)
484 """Raised if associated DiaSources are unphysically separated.
489 Observed separation in arseconds.
490 max_allowed_separation : `float`
491 Configured maximum separation in arcseconds.
494 def __init__(self, separation, max_allowed_separation) -> None:
497 super().
__init__(f
"Observed DiaSource separation {separation} exceeds allowed value of "
498 f
"{max_allowed_separation}")
509 MaxAllowedDiaSourceSeparation = pexConfig.Field(
512 doc=
"Max allowed separation of associated DiaSources in arcsec. "
513 "Raises if unphysical separation is found. "
517@register("ap_meanPosition")
519 """Compute the mean position and position uncertainty of a DiaObject
520 from its associated DiaSources.
522 The reported (ra, dec) is the inverse-variance weighted spherical
523 mean of the per-source positions, using only the DiaSources whose
524 per-source coordinate errors are finite. The reported covariance
525 is the covariance of that weighted-mean estimator,
527 C_formal = (sum_i inv(C_i))^-1,
529 where ``C_i`` is the per-source 2x2 covariance, multiplied by a
530 *chi-squared scale factor* that inflates the result when the
531 empirical scatter is larger than the per-source errors predict:
533 chi2 = sum_i r_i^T inv(C_i) r_i (r_i = tangent-plane
534 residual from the mean)
536 C_obj = max(1, chi2 / dof) * C_formal
538 When the data agree with the per-source errors (chi2 ~ dof) the
539 scale factor is unity and ``C_obj == C_formal``. When the residuals
540 exceed what the errors predict, the covariance is inflated.
543 ConfigClass = MeanDiaPositionConfig
547 outputCols = [
"ra",
"dec",
"raErr",
"decErr",
"ra_dec_Cov"]
555 """Compute the mean ra/dec position and its uncertainty for each
560 diaObjects : `pandas.DataFrame`
561 Summary objects to store values in.
562 diaSources : `pandas.DataFrame` or `pandas.DataFrameGroupBy`
563 Catalog of DiaSources summarized by this DiaObject.
565 Any additional keyword arguments that may be passed to the plugin.
568 if outCol
not in diaObjects.columns:
569 diaObjects[outCol] = np.nan
571 maxAllowedSep = self.
config.MaxAllowedDiaSourceSeparation
573 def _column(df, name):
574 """Read a column as float64, or return all-NaN if absent."""
575 if name
in df.columns:
576 return df[name].to_numpy(dtype=np.float64)
577 return np.full(len(df), np.nan)
579 def _computeMeanPos(df):
581 for idx, src
in df.iterrows())
584 unweightedAvg = geom.averageSpherePoint(coords)
585 maxSep = max(unweightedAvg.separation(coord).asArcseconds()
for coord
in coords)
586 if maxSep > maxAllowedSep:
590 raErr = _column(df,
"raErr")
591 decErr = _column(df,
"decErr")
592 raDecCov = _column(df,
"ra_dec_Cov")
593 finiteDiag = (np.isfinite(raErr) & (raErr > 0)) & (np.isfinite(decErr) & (decErr > 0))
594 nDiag = int(finiteDiag.sum())
599 [[off.asDegrees()
for off
in unweightedAvg.getTangentPlaneOffset(coord)]
for coord
in coords]
600 )
if nSrc >= 1
else np.zeros((0, 2))
602 aveCoord = unweightedAvg
610 idx = np.where(finiteDiag)[0]
611 sigmaRaSq = raErr[idx]**2
612 sigmaDecSq = decErr[idx]**2
616 haveFullCov = bool(np.isfinite(raDecCov[idx]).all())
618 C = np.zeros((nDiag, 2, 2), dtype=np.float64)
619 C[:, 0, 0] = sigmaRaSq
620 C[:, 1, 1] = sigmaDecSq
622 C[:, 0, 1] = raDecCov[idx]
623 C[:, 1, 0] = raDecCov[idx]
626 except np.linalg.LinAlgError:
634 W_sum = W.sum(axis=0)
635 C_formal = np.linalg.inv(W_sum)
643 mu_offset = C_formal @ np.einsum(
'nij,nj->i', W, offsets[idx])
644 east, north = float(mu_offset[0]), float(mu_offset[1])
645 sep_deg = float(np.hypot(east, north))
647 bearing =
geom.Angle(float(np.arctan2(north, east)), geom.radians)
648 aveCoord = unweightedAvg.offset(bearing, sep_deg*geom.degrees)
650 varRaFormal = float(C_formal[0, 0])
651 varDecFormal = float(C_formal[1, 1])
652 covFormal = float(C_formal[0, 1])
if haveFullCov
else np.nan
657 varDec = varDecFormal
658 raDecCovObj = covFormal
663 residuals = offsets[idx] - mu_offset
666 chi2 = float(np.einsum(
'ni,nij,nj->', residuals, W, residuals))
668 chi2 = float(np.sum(residuals[:, 0]**2/sigmaRaSq + residuals[:, 1]**2/sigmaDecSq))
670 scale = max(1.0, chi2/dof)
671 varRa = varRaFormal*scale
672 varDec = varDecFormal*scale
673 raDecCovObj = (covFormal*scale
if np.isfinite(covFormal)
else np.nan)
680 "No DiaSources with finite coordinate errors; falling "
681 "back to the unweighted mean position with scatter-only "
686 sampleCov = np.cov(offsets, rowvar=
False, ddof=1)
687 semCov = sampleCov/nSrc
688 varRa = float(semCov[0, 0])
689 varDec = float(semCov[1, 1])
690 raDecCovObj = float(semCov[0, 1])
693 raErrObj = float(np.sqrt(varRa))
if np.isfinite(varRa)
else np.nan
694 decErrObj = float(np.sqrt(varDec))
if np.isfinite(varDec)
else np.nan
696 return pd.Series({
"ra": aveCoord.getRa().asDegrees(),
697 "dec": aveCoord.getDec().asDegrees(),
700 "ra_dec_Cov": raDecCovObj})
702 ans = diaSources.apply(_computeMeanPos)
710@
register(
"ap_firstLastDiaSourceTime")
712 """Determine the min and max time DiaSources have been observed.
715 ConfigClass = FirstLastDiaSourceTimeConfig
719 outputCols = [
"firstDiaSourceMjdTai",
"lastDiaSourceMjdTai"]
727 """Store the first and last DiaSource times per DiaObject.
729 Stores the smaller of the stored value of ``firstDiaSourceMjdTai``
730 and the minimum ``midpointMjdTai`` in the provided DiaSources, and
731 the larger of the stored value of ``lastDiaSourceMjdTai`` and the
732 maximum ``midpointMjdTai`` in the provided DiaSources. This allows
733 the stored values to persist even when only a shorter history of
734 DiaSources is loaded from the APDB. DiaSources with NaN
735 ``midpointMjdTai`` are ignored.
739 diaObjects : `pandas.DataFrame`
740 Summary objects to store values in, which may include prior
741 values of ``firstDiaSourceMjdTai`` and ``lastDiaSourceMjdTai``
743 diaSources : `pandas.DataFrame` or `pandas.DataFrameGroupBy`
744 Catalog of DiaSources summarized by these DiaObjects.
746 Any additional keyword arguments that may be passed to the plugin.
749 if outCol
not in diaObjects.columns:
750 diaObjects[outCol] = np.nan
752 times = diaSources[
"midpointMjdTai"]
754 firstTime = times.min()
755 lastTime = times.max()
759 storedFirst = diaObjects.loc[firstTime.index,
"firstDiaSourceMjdTai"].to_numpy(dtype=np.float64)
760 storedLast = diaObjects.loc[lastTime.index,
"lastDiaSourceMjdTai"].to_numpy(dtype=np.float64)
763 diaObjects.loc[firstTime.index,
"firstDiaSourceMjdTai"] = np.fmin(
764 storedFirst, firstTime.to_numpy(dtype=np.float64))
765 diaObjects.loc[lastTime.index,
"lastDiaSourceMjdTai"] = np.fmax(
766 storedLast, lastTime.to_numpy(dtype=np.float64))
771 htmLevel = pexConfig.Field(
773 doc=
"Level of the HTM pixelization.",
778@register("ap_HTMIndex")
780 """Compute the mean position of a DiaObject given a set of DiaSources.
784 This plugin was implemented to satisfy requirements of old APDB interface
785 which required ``pixelId`` column in DiaObject with HTM20 index. APDB
786 interface had migrated to not need that information, but we keep this
787 plugin in case it may be useful for something else.
789 ConfigClass = HTMIndexDiaPositionConfig
793 inputCols = [
"ra",
"dec"]
794 outputCols = [
"pixelId"]
798 DiaObjectCalculationPlugin.__init__(self, config, name, metadata)
806 """Compute the mean position of a DiaObject given a set of DiaSources
810 diaObjects : `pandas.dataFrame`
811 Summary objects to store values in and read ra/dec from.
813 Id of the diaObject to update.
815 Any additional keyword arguments that may be passed to the plugin.
818 diaObjects.at[diaObjectId,
"ra"] * geom.degrees,
819 diaObjects.at[diaObjectId,
"dec"] * geom.degrees)
820 diaObjects.at[diaObjectId,
"pixelId"] = self.
pixelator.index(
821 sphPoint.getVector())
830 """Compute the total number of DiaSources associated with this DiaObject.
833 ConfigClass = NumDiaSourcesDiaPluginConfig
834 outputCols = [
"nDiaSources"]
843 """Compute the total number of DiaSources associated with this DiaObject.
848 Summary object to store values in and read ra/dec from.
850 Any additional keyword arguments that may be passed to the plugin.
861 """Find if any DiaSource is flagged.
863 Set the DiaObject flag if any DiaSource is flagged.
866 ConfigClass = NumDiaSourcesDiaPluginConfig
867 outputCols = [
"flags"]
876 """Find if any DiaSource is flagged.
878 Set the DiaObject flag if any DiaSource is flagged.
883 Summary object to store values in and read ra/dec from.
885 Any additional keyword arguments that may be passed to the plugin.
896 """Compute the weighted mean and mean error on the point source fluxes
897 of the DiaSource measured on the difference image.
899 Additionally store number of usable data points.
902 ConfigClass = WeightedMeanDiaPsfFluxConfig
903 outputCols = [
"psfFluxMean",
"psfFluxMeanErr",
"psfFluxNdata"]
911 @catchWarnings(warns=[
"invalid value encountered",
919 """Compute the weighted mean and mean error of the point source flux.
924 Summary object to store values in.
925 diaSources : `pandas.DataFrame`
926 DataFrame representing all diaSources associated with this
928 filterDiaSources : `pandas.DataFrame`
929 DataFrame representing diaSources associated with this
930 diaObject that are observed in the band pass ``band``.
932 Simple, string name of the filter for the flux being calculated.
934 Any additional keyword arguments that may be passed to the plugin.
936 meanName =
"{}_psfFluxMean".format(band)
937 errName =
"{}_psfFluxMeanErr".format(band)
938 nDataName =
"{}_psfFluxNdata".format(band)
939 if meanName
not in diaObjects.columns:
940 diaObjects[meanName] = np.nan
941 if errName
not in diaObjects.columns:
942 diaObjects[errName] = np.nan
943 if nDataName
not in diaObjects.columns:
944 diaObjects[nDataName] = 0
946 def _weightedMean(df):
947 tmpDf = df[~np.logical_or(np.isnan(df[
"psfFlux"]),
948 np.isnan(df[
"psfFluxErr"]))]
949 tot_weight = np.nansum(1 / tmpDf[
"psfFluxErr"] ** 2)
950 fluxMean = np.nansum(tmpDf[
"psfFlux"]
951 / tmpDf[
"psfFluxErr"] ** 2)
952 fluxMean /= tot_weight
954 fluxMeanErr = np.sqrt(1 / tot_weight)
957 nFluxData = len(tmpDf)
959 return pd.Series({meanName: fluxMean,
960 errName: fluxMeanErr,
961 nDataName: nFluxData},
963 df = filterDiaSources.apply(_weightedMean).astype(diaObjects.dtypes[[meanName, errName, nDataName]])
969 percentiles = pexConfig.ListField(
971 default=[5, 25, 50, 75, 95],
972 doc=
"Percentiles to calculate to compute values for. Should be "
977@register("ap_percentileFlux")
979 """Compute percentiles of diaSource fluxes.
982 ConfigClass = PercentileDiaPsfFluxConfig
988 def __init__(self, config, name, metadata, **kwargs):
989 DiaObjectCalculationPlugin.__init__(self,
994 self.
outputCols = [
"psfFluxPercentile{:02d}".format(percent)
995 for percent
in self.
config.percentiles]
1001 @catchWarnings(warns=["All-NaN slice encountered"])
1008 """Compute the percentile fluxes of the point source flux.
1013 Summary object to store values in.
1014 diaSources : `pandas.DataFrame`
1015 DataFrame representing all diaSources associated with this
1017 filterDiaSources : `pandas.DataFrame`
1018 DataFrame representing diaSources associated with this
1019 diaObject that are observed in the band pass ``band``.
1021 Simple, string name of the filter for the flux being calculated.
1023 Any additional keyword arguments that may be passed to the plugin.
1026 for tilePercent
in self.
config.percentiles:
1027 pTileName =
"{}_psfFluxPercentile{:02d}".format(band,
1029 pTileNames.append(pTileName)
1030 if pTileName
not in diaObjects.columns:
1031 diaObjects[pTileName] = np.nan
1033 def _fluxPercentiles(df):
1034 pTiles = np.nanpercentile(df[
"psfFlux"], self.
config.percentiles)
1036 dict((tileName, pTile)
1037 for tileName, pTile
in zip(pTileNames, pTiles)))
1041 filterDiaSources.apply(_fluxPercentiles),
1052 """Compute scatter of diaSource fluxes.
1055 ConfigClass = SigmaDiaPsfFluxConfig
1057 outputCols = [
"psfFluxSigma"]
1071 """Compute the sigma fluxes of the point source flux.
1076 Summary object to store values in.
1077 diaSources : `pandas.DataFrame`
1078 DataFrame representing all diaSources associated with this
1080 filterDiaSources : `pandas.DataFrame`
1081 DataFrame representing diaSources associated with this
1082 diaObject that are observed in the band pass ``band``.
1084 Simple, string name of the filter for the flux being calculated.
1086 Any additional keyword arguments that may be passed to the plugin.
1090 column =
"{}_psfFluxSigma".format(band)
1094 filterDiaSources.psfFlux.std(),
1105 """Compute chi2 of diaSource fluxes.
1108 ConfigClass = Chi2DiaPsfFluxConfig
1111 inputCols = [
"psfFluxMean"]
1113 outputCols = [
"psfFluxChi2"]
1121 @catchWarnings(warns=["All-NaN slice encountered"])
1128 """Compute the chi2 of the point source fluxes.
1133 Summary object to store values in.
1134 diaSources : `pandas.DataFrame`
1135 DataFrame representing all diaSources associated with this
1137 filterDiaSources : `pandas.DataFrame`
1138 DataFrame representing diaSources associated with this
1139 diaObject that are observed in the band pass ``band``.
1141 Simple, string name of the filter for the flux being calculated.
1143 Any additional keyword arguments that may be passed to the plugin.
1145 meanName =
"{}_psfFluxMean".format(band)
1146 column =
"{}_psfFluxChi2".format(band)
1149 delta = (df[
"psfFlux"]
1150 - diaObjects.at[df.diaObjectId.iat[0], meanName])
1151 return np.nansum((delta / df[
"psfFluxErr"]) ** 2)
1155 filterDiaSources.apply(_chi2),
1166 """Compute median absolute deviation of diaSource fluxes.
1169 ConfigClass = MadDiaPsfFluxConfig
1173 outputCols = [
"psfFluxMAD"]
1181 @catchWarnings(warns=["All-NaN slice encountered"])
1188 """Compute the median absolute deviation of the point source fluxes.
1193 Summary object to store values in.
1194 diaSources : `pandas.DataFrame`
1195 DataFrame representing all diaSources associated with this
1197 filterDiaSources : `pandas.DataFrame`
1198 DataFrame representing diaSources associated with this
1199 diaObject that are observed in the band pass ``band``.
1201 Simple, string name of the filter for the flux being calculated.
1203 Any additional keyword arguments that may be passed to the plugin.
1205 column =
"{}_psfFluxMAD".format(band)
1209 filterDiaSources.psfFlux.apply(median_absolute_deviation, ignore_nan=
True),
1220 """Compute the skew of diaSource fluxes.
1223 ConfigClass = SkewDiaPsfFluxConfig
1227 outputCols = [
"psfFluxSkew"]
1241 """Compute the skew of the point source fluxes.
1246 Summary object to store values in.
1247 diaSources : `pandas.DataFrame`
1248 DataFrame representing all diaSources associated with this
1250 filterDiaSources : `pandas.DataFrame`
1251 DataFrame representing diaSources associated with this
1252 diaObject that are observed in the band pass ``band``.
1254 Simple, string name of the filter for the flux being calculated.
1256 Any additional keyword arguments that may be passed to the plugin.
1258 column =
"{}_psfFluxSkew".format(band)
1262 filterDiaSources.psfFlux.skew(),
1273 """Compute min/max of diaSource fluxes.
1276 ConfigClass = MinMaxDiaPsfFluxConfig
1280 outputCols = [
"psfFluxMin",
"psfFluxMax"]
1294 """Compute min/max of the point source fluxes.
1299 Summary object to store values in.
1300 diaSources : `pandas.DataFrame`
1301 DataFrame representing all diaSources associated with this
1303 filterDiaSources : `pandas.DataFrame`
1304 DataFrame representing diaSources associated with this
1305 diaObject that are observed in the band pass ``band``.
1307 Simple, string name of the filter for the flux being calculated.
1309 Any additional keyword arguments that may be passed to the plugin.
1311 minName =
"{}_psfFluxMin".format(band)
1312 if minName
not in diaObjects.columns:
1313 diaObjects[minName] = np.nan
1314 maxName =
"{}_psfFluxMax".format(band)
1315 if maxName
not in diaObjects.columns:
1316 diaObjects[maxName] = np.nan
1320 filterDiaSources.psfFlux.min(),
1325 filterDiaSources.psfFlux.max(),
1336 """Compute the maximum ratio time ordered deltaFlux / deltaTime.
1339 ConfigClass = MinMaxDiaPsfFluxConfig
1343 outputCols = [
"psfFluxMaxSlope"]
1357 """Compute the maximum ratio time ordered deltaFlux / deltaTime.
1362 Summary object to store values in.
1363 diaSources : `pandas.DataFrame`
1364 DataFrame representing all diaSources associated with this
1366 filterDiaSources : `pandas.DataFrame`
1367 DataFrame representing diaSources associated with this
1368 diaObject that are observed in the band pass ``band``.
1370 Simple, string name of the filter for the flux being calculated.
1372 Any additional keyword arguments that may be passed to the plugin.
1376 tmpDf = df[~np.logical_or(np.isnan(df[
"psfFlux"]),
1377 np.isnan(df[
"midpointMjdTai"]))]
1380 times = tmpDf[
"midpointMjdTai"].to_numpy()
1381 timeArgs = times.argsort()
1382 times = times[timeArgs]
1383 fluxes = tmpDf[
"psfFlux"].to_numpy()[timeArgs]
1384 return (np.diff(fluxes) / np.diff(times)).max()
1386 column =
"{}_psfFluxMaxSlope".format(band)
1390 filterDiaSources.apply(_maxSlope),
1401 """Compute the mean of the dia source errors.
1404 ConfigClass = ErrMeanDiaPsfFluxConfig
1408 outputCols = [
"psfFluxErrMean"]
1422 """Compute the mean of the dia source errors.
1427 Summary object to store values in.
1428 diaSources : `pandas.DataFrame`
1429 DataFrame representing all diaSources associated with this
1431 filterDiaSources : `pandas.DataFrame`
1432 DataFrame representing diaSources associated with this
1433 diaObject that are observed in the band pass ``band``.
1435 Simple, string name of the filter for the flux being calculated.
1437 Any additional keyword arguments that may be passed to the plugin.
1439 column =
"{}_psfFluxErrMean".format(band)
1443 filterDiaSources.psfFluxErr.mean(),
1446 default_dtype=np.float32,
1456 """Compute fit a linear model to flux vs time.
1459 ConfigClass = LinearFitDiaPsfFluxConfig
1463 outputCols = [
"psfFluxLinearSlope",
"psfFluxLinearIntercept"]
1477 """Compute fit a linear model to flux vs time.
1482 Summary object to store values in.
1483 diaSources : `pandas.DataFrame`
1484 DataFrame representing all diaSources associated with this
1486 filterDiaSources : `pandas.DataFrame`
1487 DataFrame representing diaSources associated with this
1488 diaObject that are observed in the band pass ``band``.
1490 Simple, string name of the filter for the flux being calculated.
1492 Any additional keyword arguments that may be passed to the plugin.
1495 mName =
"{}_psfFluxLinearSlope".format(band)
1496 if mName
not in diaObjects.columns:
1497 diaObjects[mName] = np.nan
1498 bName =
"{}_psfFluxLinearIntercept".format(band)
1499 if bName
not in diaObjects.columns:
1500 diaObjects[bName] = np.nan
1501 dtype = diaObjects[mName].dtype
1504 tmpDf = df[~np.logical_or(
1505 np.isnan(df[
"psfFlux"]),
1506 np.logical_or(np.isnan(df[
"psfFluxErr"]),
1507 np.isnan(df[
"midpointMjdTai"])))]
1509 return pd.Series({mName: np.nan, bName: np.nan})
1510 fluxes = tmpDf[
"psfFlux"].to_numpy()
1511 errors = tmpDf[
"psfFluxErr"].to_numpy()
1512 times = tmpDf[
"midpointMjdTai"].to_numpy()
1513 A = np.array([times / errors, 1 / errors]).transpose()
1514 m, b = lsq_linear(A, fluxes / errors).x
1515 return pd.Series({mName: m, bName: b}, dtype=dtype)
1519 filterDiaSources.apply(_linearFit),
1530 """Compute the StetsonJ statistic on the DIA point source fluxes.
1533 ConfigClass = LinearFitDiaPsfFluxConfig
1536 inputCols = [
"psfFluxMean"]
1538 outputCols = [
"psfFluxStetsonJ"]
1552 """Compute the StetsonJ statistic on the DIA point source fluxes.
1557 Summary object to store values in.
1558 diaSources : `pandas.DataFrame`
1559 DataFrame representing all diaSources associated with this
1561 filterDiaSources : `pandas.DataFrame`
1562 DataFrame representing diaSources associated with this
1563 diaObject that are observed in the band pass ``band``.
1565 Simple, string name of the filter for the flux being calculated.
1567 Any additional keyword arguments that may be passed to the plugin.
1569 meanName =
"{}_psfFluxMean".format(band)
1572 tmpDf = df[~np.logical_or(np.isnan(df[
"psfFlux"]),
1573 np.isnan(df[
"psfFluxErr"]))]
1576 fluxes = tmpDf[
"psfFlux"].to_numpy()
1577 errors = tmpDf[
"psfFluxErr"].to_numpy()
1582 diaObjects.at[tmpDf.diaObjectId.iat[0], meanName])
1584 column =
"{}_psfFluxStetsonJ".format(band)
1587 filterDiaSources.apply(_stetsonJ),
1592 """Compute the single band stetsonJ statistic.
1596 fluxes : `numpy.ndarray` (N,)
1597 Calibrated lightcurve flux values.
1598 errors : `numpy.ndarray` (N,)
1599 Errors on the calibrated lightcurve fluxes.
1601 Starting mean from previous plugin.
1606 stetsonJ statistic for the input fluxes and errors.
1610 .. [1] Stetson, P. B., "On the Automatic Determination of Light-Curve
1611 Parameters for Cepheid Variables", PASP, 108, 851S, 1996
1613 n_points = len(fluxes)
1616 np.sqrt(n_points / (n_points - 1)) * (fluxes - flux_mean) / errors)
1617 p_k = delta_val ** 2 - 1
1619 return np.mean(np.sign(p_k) * np.sqrt(np.fabs(p_k)))
1629 """Compute the stetson mean of the fluxes which down-weights outliers.
1631 Weighted biased on an error weighted difference scaled by a constant
1632 (1/``a``) and raised to the power beta. Higher betas more harshly
1633 penalize outliers and ``a`` sets the number of sigma where a weighted
1634 difference of 1 occurs.
1638 values : `numpy.dnarray`, (N,)
1639 Input values to compute the mean of.
1640 errors : `numpy.ndarray`, (N,)
1641 Errors on the input values.
1643 Starting mean value or None.
1645 Scalar down-weighting of the fractional difference. lower->more
1646 clipping. (Default value is 2.)
1648 Power law slope of the used to down-weight outliers. higher->more
1649 clipping. (Default value is 2.)
1651 Number of iterations of clipping.
1653 Fractional and absolute tolerance goal on the change in the mean
1654 before exiting early. (Default value is 1e-6)
1659 Weighted stetson mean result.
1663 .. [1] Stetson, P. B., "On the Automatic Determination of Light-Curve
1664 Parameters for Cepheid Variables", PASP, 108, 851S, 1996
1666 n_points = len(values)
1667 n_factor = np.sqrt(n_points / (n_points - 1))
1668 inv_var = 1 / errors ** 2
1671 mean = np.average(values, weights=inv_var)
1672 for iter_idx
in range(n_iter):
1673 chi = np.fabs(n_factor * (values - mean) / errors)
1674 tmp_mean = np.average(
1676 weights=inv_var / (1 + (chi / alpha) ** beta))
1677 diff = np.fabs(tmp_mean - mean)
1679 if diff / mean < tol
and diff < tol:
1690 """Compute the weighted mean and mean error on the point source fluxes
1691 forced photometered at the DiaSource location in the calibrated image.
1693 Additionally store number of usable data points.
1696 ConfigClass = WeightedMeanDiaPsfFluxConfig
1697 outputCols = [
"scienceFluxMean",
"scienceFluxMeanErr"]
1705 @catchWarnings(warns=[
"invalid value encountered",
1713 """Compute the weighted mean and mean error of the point source flux.
1718 Summary object to store values in.
1719 diaSources : `pandas.DataFrame`
1720 DataFrame representing all diaSources associated with this
1722 filterDiaSources : `pandas.DataFrame`
1723 DataFrame representing diaSources associated with this
1724 diaObject that are observed in the band pass ``band``.
1726 Simple, string name of the filter for the flux being calculated.
1728 Any additional keyword arguments that may be passed to the plugin.
1730 totMeanName =
"{}_scienceFluxMean".format(band)
1731 if totMeanName
not in diaObjects.columns:
1732 diaObjects[totMeanName] = np.nan
1733 totErrName =
"{}_scienceFluxMeanErr".format(band)
1734 if totErrName
not in diaObjects.columns:
1735 diaObjects[totErrName] = np.nan
1738 tmpDf = df[~np.logical_or(np.isnan(df[
"scienceFlux"]),
1739 np.isnan(df[
"scienceFluxErr"]))]
1740 tot_weight = np.nansum(1 / tmpDf[
"scienceFluxErr"] ** 2)
1741 fluxMean = np.nansum(tmpDf[
"scienceFlux"]
1742 / tmpDf[
"scienceFluxErr"] ** 2)
1743 fluxMean /= tot_weight
1744 fluxMeanErr = np.sqrt(1 / tot_weight)
1746 return pd.Series({totMeanName: fluxMean,
1747 totErrName: fluxMeanErr})
1749 df = filterDiaSources.apply(_meanFlux).astype(diaObjects.dtypes[[totMeanName, totErrName]])
1759 """Compute scatter of diaSource fluxes.
1762 ConfigClass = SigmaDiaPsfFluxConfig
1764 outputCols = [
"scienceFluxSigma"]
1778 """Compute the sigma fluxes of the point source flux measured on the
1784 Summary object to store values in.
1785 diaSources : `pandas.DataFrame`
1786 DataFrame representing all diaSources associated with this
1788 filterDiaSources : `pandas.DataFrame`
1789 DataFrame representing diaSources associated with this
1790 diaObject that are observed in the band pass ``band``.
1792 Simple, string name of the filter for the flux being calculated.
1794 Any additional keyword arguments that may be passed to the plugin.
1798 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, diaSources, **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=())