__all__ = (
"GapsMetric",
"TgapsMetric",
"TgapsPercentMetric",
"NightgapsMetric",
"NVisitsPerNightMetric",
"MaxGapMetric",
"NightTimespanMetric",
)
import numpy as np
from .base_metric import BaseMetric
[docs]
class GapsMetric(BaseMetric):
"""Compute the number of times a gaps of a specified timescale (+/- 1/2 timescale) is sampled.
Parameters
----------
times_col : `str` (observationStartMJD)
The column name for the exposure times. Values assumed to be in days.
time_scale : `float` (2)
Time scale to see how well it is sampled (hours). For example, the default of 2 hours means
observations spaced anywhere between 1 and 3 hours will count as a sample.
"""
def __init__(
self,
times_col="observationStartMJD",
time_scale=2.0,
units="N",
**kwargs,
):
self.times_col = times_col
# Convert to days and divide by two so we bin at the Nyquist frequency
self.bin_size = (time_scale / 24.0) / 2.0
super().__init__(col=[self.times_col], metric_dtype="float", units=units, **kwargs)
[docs]
def run(self, data_slice, slice_point=None):
if data_slice.size < 2:
return self.badval
times = np.sort(data_slice[self.times_col])
bins = np.arange(times.min() - self.bin_size, times.max() + self.bin_size, self.bin_size)
vals, _be = np.histogram(times, bins)
# Bins are at 1/2 desired timescale, so compare bin 0 to bin 2 to see
# if the timescale has been sampled.
mult = vals[2:] * vals[0:-2]
result = np.size(np.where(mult > 0)[0])
return result
[docs]
class TgapsMetric(BaseMetric):
"""Histogram the times of the gaps between observations.
Measure the gaps between observations. By default, only gaps
between neighboring visits are computed. If all_gaps is set to true, all gaps are
computed (i.e., if there are observations at 10, 20, 30 and 40 the default will
return a histogram of [10,10,10] while all_gaps returns a histogram of [10,10,10,20,20,30])
Parameters
----------
times_col : `str`, optional
The column name for the exposure times. Values assumed to be in days.
Default observationStartMJD.
all_gaps : `bool`, optional
Histogram the gaps between all observations (True) or just successive observations (False)?
Default is False. If all gaps are used, this metric can become significantly slower.
bins : `np.ndarray`, optional
The bins to use for the histogram of time gaps (in days, or same units as times_col).
Default values are bins from 0 to 2 hours, in 5 minute intervals.
Returns
-------
histogram : `np.ndarray`
Returns a histogram of the tgaps at each slice point;
these histograms can be combined and plotted using the 'SummaryHistogram plotter'.
"""
def __init__(
self,
times_col="observationStartMJD",
all_gaps=False,
bins=np.arange(0, 120.0, 5.0) / 60.0 / 24.0,
units="days",
**kwargs,
):
# Pass the same bins to the plotter.
self.bins = bins
self.times_col = times_col
super().__init__(col=[self.times_col], metric_dtype="object", units=units, **kwargs)
self.all_gaps = all_gaps
[docs]
def run(self, data_slice, slice_point=None):
if data_slice.size < 2:
return self.badval
times = np.sort(data_slice[self.times_col])
if self.all_gaps:
all_diffs = []
for i in np.arange(1, times.size, 1):
all_diffs.append((times - np.roll(times, i))[i:])
dts = np.concatenate(all_diffs)
else:
dts = np.diff(times)
result, bins = np.histogram(dts, self.bins)
return result
[docs]
class TgapsPercentMetric(BaseMetric):
"""Compute the fraction of the time gaps between observations that occur in a given time range.
Measure the gaps between observations. By default, only gaps
between neighboring visits are computed. If all_gaps is set to true, all gaps are
computed (i.e., if there are observations at 10, 20, 30 and 40 the default will
Compute the percent of gaps between specified endpoints.
This is different from the TgapsMetric in that this only looks at what percent of intervals fall
into the specified range, rather than histogramming the entire set of tgaps.
Parameters
----------
times_col : `str`, opt
The column name for the exposure times. Values assumed to be in days.
Default observationStartMJD.
all_gaps : `bool`, opt
Histogram the gaps between all observations (True) or just successive observations (False)?
Default is False. If all gaps are used, this metric can become significantly slower.
min_time : `float`, opt
Minimum time of gaps to include (days). Default 2/24 (2 hours).
max_time : `float`, opt
Max time of gaps to include (days). Default 14/24 (14 hours).
Returns
-------
percent : `float`
Returns a float percent of the CDF between cdfMinTime and cdfMaxTime -
(# of tgaps within min_time/max_time / # of all tgaps).
"""
def __init__(
self,
times_col="observationStartMJD",
all_gaps=False,
min_time=2.0 / 24,
max_time=14.0 / 24,
units="percent",
**kwargs,
):
self.times_col = times_col
assert min_time <= max_time
self.min_time = min_time
self.max_time = max_time
super().__init__(col=[self.times_col], metric_dtype="float", units=units, **kwargs)
self.all_gaps = all_gaps
[docs]
def run(self, data_slice, slice_point=None):
if data_slice.size < 2:
return self.badval
times = np.sort(data_slice[self.times_col])
if self.all_gaps:
all_diffs = []
for i in np.arange(1, times.size, 1):
all_diffs.append((times - np.roll(times, i))[i:])
dts = np.concatenate(all_diffs)
else:
dts = np.diff(times)
n_in_window = np.sum((dts >= self.min_time) & (dts <= self.max_time))
return n_in_window / len(dts) * 100.0
[docs]
class NightgapsMetric(BaseMetric):
"""Histogram the number of nights between observations.
Measure the gaps between observations. By default, only gaps
between neighboring visits are computed. If all_gaps is set to true, all gaps are
computed (i.e., if there are observations at 10, 20, 30 and 40 the default will
histogram [10,10,10] while all_gaps histograms [10,10,10,20,20,30])
Parameters
----------
night_col : `str`, optional
The column name for the night of each observation.
Default 'night'.
all_gaps : `bool`, optional
Histogram the gaps between all observations (True) or just successive observations (False)?
Default is False. If all gaps are used, this metric can become significantly slower.
bins : `np.ndarray`, optional
The bins to use for the histogram of time gaps (in days, or same units as timesCol).
Default values are bins from 0 to 10 days, in 1 day intervals.
Returns
-------
histogram : `np.ndarray`
Returns a histogram of the deltaT between nights at each slice point;
these histograms can be combined and plotted using the 'SummaryHistogram plotter'.
"""
def __init__(
self,
night_col="night",
all_gaps=False,
bins=np.arange(0, 10, 1),
units="nights",
**kwargs,
):
# Pass the same bins to the plotter.
self.bins = bins
self.night_col = night_col
super().__init__(col=[self.night_col], metric_dtype="object", units=units, **kwargs)
self.all_gaps = all_gaps
[docs]
def run(self, data_slice, slice_point=None):
if data_slice.size < 2:
return self.badval
nights = np.sort(np.unique(data_slice[self.night_col]))
if self.all_gaps:
all_diffs = []
for i in np.arange(1, nights.size, 1):
all_diffs.append((nights - np.roll(nights, i))[i:])
dnights = np.concatenate(all_diffs)
else:
dnights = np.diff(nights)
result, bins = np.histogram(dnights, self.bins)
return result
[docs]
class NVisitsPerNightMetric(BaseMetric):
"""Histogram the number of visits in each night.
Splits the visits by night, then histograms how many visits occur in each night.
Parameters
----------
night_col : `str`, optional
The column name for the night of each observation.
Default 'night'.
bins : `np.ndarray`, optional
The bins to use for the histogram of time gaps (in days, or same units as timesCol).
Default values are bins from 0 to 5 visits, in steps of 1.
Returns
-------
histogram : `np.ndarray`
Returns a histogram of the number of visits per night at each slice point;
these histograms can be combined and plotted using the 'SummaryHistogram plotter'.
"""
def __init__(self, night_col="night", bins=np.arange(0, 10, 1), units="#", **kwargs):
# Pass the same bins to the plotter.
self.bins = bins
self.night_col = night_col
super().__init__(col=[self.night_col], metric_dtype="object", units=units, **kwargs)
[docs]
def run(self, data_slice, slice_point=None):
n, counts = np.unique(data_slice[self.night_col], return_counts=True)
result, bins = np.histogram(counts, self.bins)
return result
[docs]
class MaxGapMetric(BaseMetric):
"""Find the maximum gap (in days) in between successive observations.
Useful for making sure there is an image within the last year that would make a good template image.
Parameters
----------
mjd_col : `str`, opt
The column name of the night of each observation.
Returns
-------
maxGap : `float`
The maximum gap (in days) between visits.
"""
def __init__(self, mjd_col="observationStartMJD", **kwargs):
self.mjd_col = mjd_col
units = "Days"
super(MaxGapMetric, self).__init__(col=[self.mjd_col], units=units, **kwargs)
[docs]
def run(self, data_slice, slice_point=None):
gaps = np.diff(np.sort(data_slice[self.mjd_col]))
if np.size(gaps) > 0:
result = np.max(gaps)
else:
result = self.badval
return result
[docs]
class NightTimespanMetric(BaseMetric):
"""Calculate the maximum time span covered in each night, report the `percentile` value of all timespans.
Parameters
----------
percentile : `float`, opt
Percentile value to report. Default 75th percentile.
night_col : `str`, opt
Name of the night column. Default 'night'.
mjd_col : `str`, opt
Name of the MJD visit column. Default 'observationStartMJD'.
"""
def __init__(self, percentile=75, night_col="night", mjd_col="observationStartMJD", **kwargs):
self.percentile = percentile
self.night_col = night_col
self.mjd_col = mjd_col
if "metric_name" in kwargs:
metric_name = kwargs["metric_name"]
del kwargs["metric_name"]
else:
metric_name = f"{percentile}th Percentile Intranight Timespan"
super().__init__(
col=[self.night_col, self.mjd_col],
units="minutes",
metric_name=metric_name,
**kwargs,
)
[docs]
def run(self, data_slice, slice_point=None):
data = np.sort(data_slice, order=self.mjd_col)
unights, counts = np.unique(data[self.night_col], return_counts=True)
unights = unights[np.where(counts > 1)]
if len(unights) == 0:
result = self.badval
else:
nstart = np.searchsorted(data[self.night_col], unights, side="left")
nend = np.searchsorted(data[self.night_col], unights, side="right") - 1
tspans = (data[self.mjd_col][nend] - data[self.mjd_col][nstart]) * 24.0 * 60.0
result = np.percentile(tspans, self.percentile)
return result