def _make_segment_spectrum(self, lc, segment_size): if not isinstance(lc, lightcurve.Lightcurve): raise TypeError("lc must be a lightcurve.Lightcurve object") if self.gti is None: self.gti = lc.gti check_gtis(self.gti) start_inds, end_inds = \ bin_intervals_from_gtis(self.gti, segment_size, lc.time) power_all = [] nphots_all = [] for start_ind, end_ind in zip(start_inds, end_inds): time = lc.time[start_ind:end_ind] counts = lc.counts[start_ind:end_ind] counts_err = lc.counts_err[start_ind:end_ind] lc_seg = lightcurve.Lightcurve(time, counts, err=counts_err, err_dist=lc.err_dist.lower()) power_seg = Powerspectrum(lc_seg, norm=self.norm) power_all.append(power_seg) nphots_all.append(np.sum(lc_seg.counts)) return power_all, nphots_all
def _make_segment_spectrum(self, lc, segment_size): if not isinstance(lc, lightcurve.Lightcurve): raise TypeError("lc must be a lightcurve.Lightcurve object") if self.gti is None: self.gti = lc.gti check_gtis(self.gti) start_inds, end_inds = \ bin_intervals_from_gtis(self.gti, segment_size, lc.time) power_all = [] nphots_all = [] for start_ind, end_ind in zip(start_inds, end_inds): time = lc.time[start_ind:end_ind] counts = lc.counts[start_ind:end_ind] counts_err = lc.counts_err[start_ind: end_ind] lc_seg = lightcurve.Lightcurve(time, counts, err=counts_err, err_dist=lc.err_dist.lower()) power_seg = Powerspectrum(lc_seg, norm=self.norm) power_all.append(power_seg) nphots_all.append(np.sum(lc_seg.counts)) return power_all, nphots_all
def _make_segment_spectrum(self, lc1, lc2, segment_size): # TODO: need to update this for making cross spectra. assert isinstance(lc1, Lightcurve) assert isinstance(lc2, Lightcurve) if lc1.tseg != lc2.tseg: raise ValueError("Lightcurves do not have same tseg.") # If dt differs slightly, its propagated error must not be more than # 1/100th of the bin if not np.isclose(lc1.dt, lc2.dt, rtol=0.01 * lc1.dt / lc1.tseg): raise ValueError("Light curves do not have same time binning dt.") # In case a small difference exists, ignore it lc1.dt = lc2.dt if self.gti is None: self.gti = cross_two_gtis(lc1.gti, lc2.gti) lc1.gti = lc2.gti = self.gti lc1._apply_gtis() lc2._apply_gtis() check_gtis(self.gti) cs_all = [] nphots1_all = [] nphots2_all = [] start_inds, end_inds = \ bin_intervals_from_gtis(self.gti, segment_size, lc1.time, dt=lc1.dt) for start_ind, end_ind in zip(start_inds, end_inds): time_1 = lc1.time[start_ind:end_ind] counts_1 = lc1.counts[start_ind:end_ind] counts_1_err = lc1.counts_err[start_ind:end_ind] time_2 = lc2.time[start_ind:end_ind] counts_2 = lc2.counts[start_ind:end_ind] counts_2_err = lc2.counts_err[start_ind:end_ind] gti1 = np.array([[time_1[0] - lc1.dt / 2, time_1[-1] + lc1.dt / 2]]) gti2 = np.array([[time_2[0] - lc2.dt / 2, time_2[-1] + lc2.dt / 2]]) lc1_seg = Lightcurve(time_1, counts_1, err=counts_1_err, err_dist=lc1.err_dist, gti=gti1, dt=lc1.dt) lc2_seg = Lightcurve(time_2, counts_2, err=counts_2_err, err_dist=lc2.err_dist, gti=gti2, dt=lc2.dt) cs_seg = Crossspectrum(lc1_seg, lc2_seg, norm=self.norm) cs_all.append(cs_seg) nphots1_all.append(np.sum(lc1_seg.counts)) nphots2_all.append(np.sum(lc2_seg.counts)) return cs_all, nphots1_all, nphots2_all
def _make_crossspectrum(self, lc1, lc2): # make sure the inputs work! if not isinstance(lc1, lightcurve.Lightcurve): raise TypeError("lc1 must be a lightcurve.Lightcurve object") if not isinstance(lc2, lightcurve.Lightcurve): raise TypeError("lc2 must be a lightcurve.Lightcurve object") # Then check that GTIs make sense if self.gti is None: self.gti = cross_two_gtis(lc1.gti, lc2.gti) check_gtis(self.gti) if self.gti.shape[0] != 1: raise TypeError("Non-averaged Cross Spectra need " "a single Good Time Interval") lc1 = lc1.split_by_gti()[0] lc2 = lc2.split_by_gti()[0] # total number of photons is the sum of the # counts in the light curve self.nphots1 = np.float64(np.sum(lc1.counts)) self.nphots2 = np.float64(np.sum(lc2.counts)) self.meancounts1 = np.mean(lc1.counts) self.meancounts2 = np.mean(lc2.counts) # the number of data points in the light curve if lc1.n != lc2.n: raise StingrayError("Light curves do not have same number " "of time bins per segment.") if lc1.dt != lc2.dt: raise StingrayError("Light curves do not have " "same time binning dt.") self.n = lc1.n # the frequency resolution self.df = 1.0 / lc1.tseg # the number of averaged periodograms in the final output # This should *always* be 1 here self.m = 1 # make the actual Fourier transform and compute cross spectrum self.freq, self.unnorm_power = self._fourier_cross(lc1, lc2) # If co-spectrum is desired, normalize here. Otherwise, get raw back # with the imaginary part still intact. self.power = self._normalize_crossspectrum(self.unnorm_power, lc1.tseg)
def _make_crossspectrum(self, lc1, lc2): ## make sure the inputs work! if not isinstance(lc1, lightcurve.Lightcurve): raise TypeError("lc1 must be a lightcurve.Lightcurve object") if not isinstance(lc2, lightcurve.Lightcurve): raise TypeError("lc2 must be a lightcurve.Lightcurve object") # Then check that GTIs make sense if self.gti is None: self.gti = cross_two_gtis(lc1.gti, lc2.gti) check_gtis(self.gti) if self.gti.shape[0] != 1: raise TypeError("Non-averaged Cross Spectra need " "a single Good Time Interval") lc1 = lc1.split_by_gti()[0] lc2 = lc2.split_by_gti()[0] ## total number of photons is the sum of the ## counts in the light curve self.nphots1 = np.float64(np.sum(lc1.counts)) self.nphots2 = np.float64(np.sum(lc2.counts)) self.meancounts1 = np.mean(lc1.counts) self.meancounts2 = np.mean(lc2.counts) ## the number of data points in the light curve if lc1.counts.shape[0] != lc2.counts.shape[0]: raise StingrayError("Light curves do not have same number " "of time bins per segment.") if lc1.dt != lc2.dt: raise StingrayError("Light curves do not have " "same time binning dt.") self.n = lc1.counts.shape[0] ## the frequency resolution self.df = 1.0/lc1.tseg ## the number of averaged periodograms in the final output ## This should *always* be 1 here self.m = 1 ## make the actual Fourier transform and compute cross spectrum self.freq, self.unnorm_power = self._fourier_cross(lc1, lc2) ## If co-spectrum is desired, normalize here. Otherwise, get raw back ## with the imaginary part still intact. self.power = self._normalize_crossspectrum(self.unnorm_power, lc1.tseg)
def _make_segment_spectrum(self, lc, segment_size): """ Split the light curves into segments of size ``segment_size``, and calculate a power spectrum for each. Parameters ---------- lc : :class:`stingray.Lightcurve` objects\ The input light curve segment_size : ``numpy.float`` Size of each light curve segment to use for averaging. Returns ------- power_all : list of :class:`Powerspectrum` objects A list of power spectra calculated independently from each light curve segment nphots_all : ``numpy.ndarray`` List containing the number of photons for all segments calculated from ``lc`` """ if not isinstance(lc, lightcurve.Lightcurve): raise TypeError("lc must be a lightcurve.Lightcurve object") if self.gti is None: self.gti = lc.gti else: if not np.all(lc.gti == self.gti): self.gti = np.vstack([self.gti, lc.gti]) check_gtis(self.gti) start_inds, end_inds = \ bin_intervals_from_gtis(lc.gti, segment_size, lc.time, dt=lc.dt) power_all = [] nphots_all = [] for start_ind, end_ind in zip(start_inds, end_inds): time = lc.time[start_ind:end_ind] counts = lc.counts[start_ind:end_ind] counts_err = lc.counts_err[start_ind:end_ind] lc_seg = lightcurve.Lightcurve(time, counts, err=counts_err, err_dist=lc.err_dist.lower(), skip_checks=True, dt=lc.dt) power_seg = Powerspectrum(lc_seg, norm=self.norm) power_all.append(power_seg) nphots_all.append(np.sum(lc_seg.counts)) return power_all, nphots_all
def _apply_gtis(self): """Apply GTIs to a light curve after modification.""" check_gtis(self.gti) good = create_gti_mask(self.time, self.gti) self.time = self.time[good] self.counts = self.counts[good] self.counts_err = self.counts_err[good] self.countrate = self.countrate[good] self.countrate_err = self.countrate_err[good] self.meanrate = np.mean(self.countrate) self.meancounts = np.mean(self.counts) self.n = self.counts.shape[0]
def _make_segment_spectrum(self, lc1, lc2, segment_size): # TODO: need to update this for making cross spectra. assert isinstance(lc1, Lightcurve) assert isinstance(lc2, Lightcurve) if lc1.dt != lc2.dt: raise ValueError("Light curves do not have same time binning dt.") if lc1.tseg != lc2.tseg: raise ValueError("Lightcurves do not have same tseg.") if self.gti is None: self.gti = cross_two_gtis(lc1.gti, lc2.gti) check_gtis(self.gti) cs_all = [] nphots1_all = [] nphots2_all = [] start_inds, end_inds = \ bin_intervals_from_gtis(self.gti, segment_size, lc1.time) for start_ind, end_ind in zip(start_inds, end_inds): time_1 = lc1.time[start_ind:end_ind] counts_1 = lc1.counts[start_ind:end_ind] counts_1_err = lc1.counts_err[start_ind:end_ind] time_2 = lc2.time[start_ind:end_ind] counts_2 = lc2.counts[start_ind:end_ind] counts_2_err = lc2.counts_err[start_ind:end_ind] lc1_seg = Lightcurve(time_1, counts_1, err=counts_1_err, err_dist=lc1.err_dist) lc2_seg = Lightcurve(time_2, counts_2, err=counts_2_err, err_dist=lc2.err_dist) cs_seg = Crossspectrum(lc1_seg, lc2_seg, norm=self.norm) cs_all.append(cs_seg) nphots1_all.append(np.sum(lc1_seg.counts)) nphots2_all.append(np.sum(lc2_seg.counts)) return cs_all, nphots1_all, nphots2_all
def _make_segment_spectrum(self, lc, segment_size): """ Split the light curves into segments of size ``segment_size``, and calculate a power spectrum for each. Parameters ---------- lc : :class:`stingray.Lightcurve` objects\ The input light curve segment_size : ``numpy.float`` Size of each light curve segment to use for averaging. Returns ------- power_all : list of :class:`Powerspectrum` objects A list of power spectra calculated independently from each light curve segment nphots_all : ``numpy.ndarray`` List containing the number of photons for all segments calculated from ``lc`` """ if not isinstance(lc, lightcurve.Lightcurve): raise TypeError("lc must be a lightcurve.Lightcurve object") if self.gti is None: self.gti = lc.gti check_gtis(self.gti) start_inds, end_inds = \ bin_intervals_from_gtis(self.gti, segment_size, lc.time, dt=lc.dt) power_all = [] nphots_all = [] for start_ind, end_ind in zip(start_inds, end_inds): time = lc.time[start_ind:end_ind] counts = lc.counts[start_ind:end_ind] counts_err = lc.counts_err[start_ind: end_ind] lc_seg = lightcurve.Lightcurve(time, counts, err=counts_err, err_dist=lc.err_dist.lower()) power_seg = Powerspectrum(lc_seg, norm=self.norm) power_all.append(power_seg) nphots_all.append(np.sum(lc_seg.counts)) return power_all, nphots_all
def test_check_gtis_shape(self): with pytest.raises(TypeError): check_gtis([0, 1]) with pytest.raises(TypeError): check_gtis([[0, 1], [0]]) with pytest.raises(TypeError): check_gtis([[0, 1], [[0], [3]]]) with pytest.raises(TypeError): check_gtis([[0, 1, 4], [0, 3, 4]])
def _apply_gtis(self): """ Apply GTIs to a light curve. Filters the ``time``, ``counts``, ``countrate``, ``counts_err`` and ``countrate_err`` arrays for all bins that fall into Good Time Intervals and recalculates mean count(rate) and the number of bins. """ check_gtis(self.gti) good = create_gti_mask(self.time, self.gti, dt=self.dt) self.time = self.time[good] self.counts = self.counts[good] self.counts_err = self.counts_err[good] self.countrate = self.countrate[good] self.countrate_err = self.countrate_err[good] self.meanrate = np.mean(self.countrate) self.meancounts = np.mean(self.counts) self.n = self.counts.shape[0]
def _make_segment_spectrum(self, lc1, lc2, segment_size): # TODO: need to update this for making cross spectra. assert isinstance(lc1, lightcurve.Lightcurve) assert isinstance(lc2, lightcurve.Lightcurve) if lc1.dt != lc2.dt: raise ValueError("Light curves do not have same time binning dt.") if lc1.tseg != lc2.tseg: raise ValueError("Lightcurves do not have same tseg.") if self.gti is None: self.gti = cross_two_gtis(lc1.gti, lc2.gti) check_gtis(self.gti) cs_all = [] nphots1_all = [] nphots2_all = [] start_inds, end_inds = \ bin_intervals_from_gtis(self.gti, segment_size, lc1.time) for start_ind, end_ind in zip(start_inds, end_inds): time_1 = lc1.time[start_ind:end_ind] counts_1 = lc1.counts[start_ind:end_ind] time_2 = lc2.time[start_ind:end_ind] counts_2 = lc2.counts[start_ind:end_ind] lc1_seg = lightcurve.Lightcurve(time_1, counts_1) lc2_seg = lightcurve.Lightcurve(time_2, counts_2) cs_seg = Crossspectrum(lc1_seg, lc2_seg, norm=self.norm) cs_all.append(cs_seg) nphots1_all.append(np.sum(lc1_seg.counts)) nphots2_all.append(np.sum(lc2_seg.counts)) return cs_all, nphots1_all, nphots2_all
def __init__(self, time, counts, err=None, input_counts=True, gti=None, err_dist='poisson', mjdref=0, dt=None): """ Make a light curve object from an array of time stamps and an array of counts. Parameters ---------- time: iterable A list or array of time stamps for a light curve counts: iterable, optional, default None A list or array of the counts in each bin corresponding to the bins defined in `time` (note: use `input_counts=False` to input the count range, i.e. counts/second, otherwise use counts/bin). err: iterable, optional, default None: A list or array of the uncertainties in each bin corresponding to the bins defined in `time` (note: use `input_counts=False` to input the count rage, i.e. counts/second, otherwise use counts/bin). If None, we assume the data is poisson distributed and calculate the error from the average of the lower and upper 1-sigma confidence intervals for the Poissonian distribution with mean equal to `counts`. input_counts: bool, optional, default True If True, the code assumes that the input data in 'counts' is in units of counts/bin. If False, it assumes the data in 'counts' is in counts/second. gti: 2-d float array, default None [[gti0_0, gti0_1], [gti1_0, gti1_1], ...] Good Time Intervals. They are *not* applied to the data by default. They will be used by other methods to have an indication of the "safe" time intervals to use during analysis. err_dist: str, optional, default=None Statistic of the Lightcurve, it is used to calculate the uncertainties and other statistical values apropriately. Default makes no assumptions and keep errors equal to zero. mjdref: float MJD reference (useful in most high-energy mission data) Attributes ---------- time: numpy.ndarray The array of midpoints of time bins. bin_lo: The array of lower time stamp of time bins. bin_hi: The array of higher time stamp of time bins. counts: numpy.ndarray The counts per bin corresponding to the bins in `time`. counts_err: numpy.ndarray The uncertainties corresponding to `counts` countrate: numpy.ndarray The counts per second in each of the bins defined in `time`. countrate_err: numpy.ndarray The uncertainties corresponding to `countrate` meanrate: float The mean count rate of the light curve. meancounts: float The mean counts of the light curve. n: int The number of data points in the light curve. dt: float The time resolution of the light curve. mjdref: float MJD reference date (tstart / 86400 gives the date in MJD at the start of the observation) tseg: float The total duration of the light curve. tstart: float The start time of the light curve. gti: 2-d float array [[gti0_0, gti0_1], [gti1_0, gti1_1], ...] Good Time Intervals. They indicate the "safe" time intervals to be used during the analysis of the light curve. err_dist: string Statistic of the Lightcurve, it is used to calculate the uncertainties and other statistical values apropriately. It propagates to Spectrum classes. """ if not np.all(np.isfinite(time)): raise ValueError("There are inf or NaN values in " "your time array!") if not np.all(np.isfinite(counts)): raise ValueError("There are inf or NaN values in " "your counts array!") if len(time) != len(counts): raise StingrayError("time and counts array are not " "of the same length!") if len(time) <= 1: raise StingrayError("A single or no data points can not create " "a lightcurve!") if err is not None: if not np.all(np.isfinite(err)): raise ValueError("There are inf or NaN values in " "your err array") else: if err_dist.lower() not in valid_statistics: # err_dist set can be increased with other statistics raise StingrayError("Statistic not recognized." "Please select one of these: ", "{}".format(valid_statistics)) if err_dist.lower() == 'poisson': # Instead of the simple square root, we use confidence # intervals (should be valid for low fluxes too) err_low, err_high = poisson_conf_interval(np.asarray(counts), interval='frequentist-confidence', sigma=1) # calculate approximately symmetric uncertainties err_low -= np.asarray(counts) err_high -= np.asarray(counts) err = (np.absolute(err_low) + np.absolute(err_high))/2.0 # other estimators can be implemented for other statistics else: simon("Stingray only uses poisson err_dist at the moment, " "We are setting your errors to zero. " "Sorry for the inconvenience.") err = np.zeros_like(counts) self.mjdref = mjdref self.time = np.asarray(time) if dt is None: self.dt = np.median(self.time[1:] - self.time[:-1]) else: self.dt = dt self.bin_lo = self.time - 0.5 * self.dt self.bin_hi = self.time + 0.5 * self.dt self.err_dist = err_dist self.tstart = self.time[0] - 0.5*self.dt self.tseg = self.time[-1] - self.time[0] + self.dt self.gti = \ np.asarray(assign_value_if_none(gti, [[self.tstart, self.tstart + self.tseg]])) check_gtis(self.gti) good = create_gti_mask(self.time, self.gti) self.time = self.time[good] if input_counts: self.counts = np.asarray(counts)[good] self.countrate = self.counts / self.dt self.counts_err = np.asarray(err)[good] self.countrate_err = np.asarray(err)[good] / self.dt else: self.countrate = np.asarray(counts)[good] self.counts = self.countrate * self.dt self.counts_err = np.asarray(err)[good] * self.dt self.countrate_err = np.asarray(err)[good] self.meanrate = np.mean(self.countrate) self.meancounts = np.mean(self.counts) self.n = self.counts.shape[0] # Issue a warning if the input time iterable isn't regularly spaced, # i.e. the bin sizes aren't equal throughout. dt_array = np.diff(self.time) if not (np.allclose(dt_array, np.repeat(self.dt, dt_array.shape[0]))): simon("Bin sizes in input time array aren't equal throughout! " "This could cause problems with Fourier transforms. " "Please make the input time evenly sampled.")
def __init__(self, time, counts, input_counts=True, gti=None): """ Make a light curve object from an array of time stamps and an array of counts. Parameters ---------- time: iterable A list or array of time stamps for a light curve counts: iterable, optional, default None A list or array of the counts in each bin corresponding to the bins defined in `time` (note: **not** the count rate, i.e. counts/second, but the counts/bin). input_counts: bool, optional, default True If True, the code assumes that the input data in 'counts' is in units of counts/bin. If False, it assumes the data in 'counts' is in counts/second. gti: 2-d float array, default None [[gti0_0, gti0_1], [gti1_0, gti1_1], ...] Good Time Intervals. They are *not* applied to the data by default. They will be used by other methods to have an indication of the "safe" time intervals to use during analysis. Attributes ---------- time: numpy.ndarray The array of midpoints of time bins. bin_lo: The array of lower time stamp of time bins. bin_hi: The array of higher time stamp of time bins. counts: numpy.ndarray The counts per bin corresponding to the bins in `time`. countrate: numpy.ndarray The counts per second in each of the bins defined in `time`. meanrate: float The mean count rate of the light curve. meancounts: float The mean counts of the light curve. n: int The number of data points in the light curve. dt: float The time resolution of the light curve. tseg: float The total duration of the light curve. tstart: float The start time of the light curve. gti: 2-d float array [[gti0_0, gti0_1], [gti1_0, gti1_1], ...] Good Time Intervals. They indicate the "safe" time intervals to be used during the analysis of the light curve. """ if not np.all(np.isfinite(time)): raise ValueError("There are inf or NaN values in " "your time array!") if not np.all(np.isfinite(counts)): raise ValueError("There are inf or NaN values in " "your counts array!") if len(time) != len(counts): raise StingrayError("time and counts array are not " "of the same length!") if len(time) <= 1: raise StingrayError("A single or no data points can not create " "a lightcurve!") self.time = np.asarray(time) self.dt = time[1] - time[0] self.bin_lo = self.time - 0.5 * self.dt self.bin_hi = self.time + 0.5 * self.dt if input_counts: self.counts = np.asarray(counts) self.countrate = self.counts / self.dt else: self.countrate = np.asarray(counts) self.counts = self.countrate * self.dt self.meanrate = np.mean(self.countrate) self.meancounts = np.mean(self.counts) self.n = self.counts.shape[0] # Issue a warning if the input time iterable isn't regularly spaced, # i.e. the bin sizes aren't equal throughout. dt_array = np.diff(self.time) if not (np.allclose(dt_array, np.repeat(self.dt, dt_array.shape[0]))): simon("Bin sizes in input time array aren't equal throughout! " "This could cause problems with Fourier transforms. " "Please make the input time evenly sampled.") self.tseg = self.time[-1] - self.time[0] + self.dt self.tstart = self.time[0] - 0.5 * self.dt self.gti = \ np.asarray(assign_value_if_none(gti, [[self.tstart, self.tstart + self.tseg]])) check_gtis(self.gti)
def _make_segment_spectrum(self, lc1, lc2, segment_size): """ Split the light curves into segments of size ``segment_size``, and calculate a cross spectrum for each. Parameters ---------- lc1, lc2 : :class:`stingray.Lightcurve` objects Two light curves used for computing the cross spectrum. segment_size : ``numpy.float`` Size of each light curve segment to use for averaging. Returns ------- cs_all : list of :class:`Crossspectrum`` objects A list of cross spectra calculated independently from each light curve segment nphots1_all, nphots2_all : ``numpy.ndarray` for each of ``lc1`` and ``lc2`` Two lists containing the number of photons for all segments calculated from ``lc1`` and ``lc2``. """ # TODO: need to update this for making cross spectra. assert isinstance(lc1, Lightcurve) assert isinstance(lc2, Lightcurve) if lc1.tseg != lc2.tseg: simon("Lightcurves do not have same tseg. This means that the data" "from the two channels are not completely in sync. This " "might or might not be an issue. Keep an eye on it.") # If dt differs slightly, its propagated error must not be more than # 1/100th of the bin if not np.isclose(lc1.dt, lc2.dt, rtol=0.01 * lc1.dt / lc1.tseg): raise ValueError("Light curves do not have same time binning dt.") # In case a small difference exists, ignore it lc1.dt = lc2.dt gti = cross_two_gtis(lc1.gti, lc2.gti) lc1.apply_gtis() lc2.apply_gtis() if self.gti is None: self.gti = gti else: if not np.all(self.gti == gti): self.gti = np.vstack([self.gti, gti]) check_gtis(self.gti) cs_all = [] nphots1_all = [] nphots2_all = [] start_inds, end_inds = \ bin_intervals_from_gtis(gti, segment_size, lc1.time, dt=lc1.dt) simon("Errorbars on cross spectra are not thoroughly tested. " "Please report any inconsistencies.") for start_ind, end_ind in zip(start_inds, end_inds): time_1 = lc1.time[start_ind:end_ind] counts_1 = lc1.counts[start_ind:end_ind] counts_1_err = lc1.counts_err[start_ind:end_ind] time_2 = lc2.time[start_ind:end_ind] counts_2 = lc2.counts[start_ind:end_ind] counts_2_err = lc2.counts_err[start_ind:end_ind] gti1 = np.array([[time_1[0] - lc1.dt / 2, time_1[-1] + lc1.dt / 2]]) gti2 = np.array([[time_2[0] - lc2.dt / 2, time_2[-1] + lc2.dt / 2]]) lc1_seg = Lightcurve(time_1, counts_1, err=counts_1_err, err_dist=lc1.err_dist, gti=gti1, dt=lc1.dt, skip_checks=True) lc2_seg = Lightcurve(time_2, counts_2, err=counts_2_err, err_dist=lc2.err_dist, gti=gti2, dt=lc2.dt, skip_checks=True) with warnings.catch_warnings(record=True) as w: cs_seg = Crossspectrum(lc1_seg, lc2_seg, norm=self.norm, power_type=self.power_type) cs_all.append(cs_seg) nphots1_all.append(np.sum(lc1_seg.counts)) nphots2_all.append(np.sum(lc2_seg.counts)) return cs_all, nphots1_all, nphots2_all
def _make_crossspectrum(self, lc1, lc2): """ Auxiliary method computing the normalized cross spectrum from two light curves. This includes checking for the presence of and applying Good Time Intervals, computing the unnormalized Fourier cross-amplitude, and then renormalizing using the required normalization. Also computes an uncertainty estimate on the cross spectral powers. Parameters ---------- lc1, lc2 : :class:`stingray.Lightcurve` objects Two light curves used for computing the cross spectrum. """ # make sure the inputs work! if not isinstance(lc1, Lightcurve): raise TypeError("lc1 must be a lightcurve.Lightcurve object") if not isinstance(lc2, Lightcurve): raise TypeError("lc2 must be a lightcurve.Lightcurve object") if self.lc2.mjdref != self.lc1.mjdref: raise ValueError("MJDref is different in the two light curves") # Then check that GTIs make sense if self.gti is None: self.gti = cross_two_gtis(lc1.gti, lc2.gti) check_gtis(self.gti) if self.gti.shape[0] != 1: raise TypeError("Non-averaged Cross Spectra need " "a single Good Time Interval") lc1 = lc1.split_by_gti()[0] lc2 = lc2.split_by_gti()[0] # total number of photons is the sum of the # counts in the light curve self.nphots1 = np.float64(np.sum(lc1.counts)) self.nphots2 = np.float64(np.sum(lc2.counts)) self.meancounts1 = lc1.meancounts self.meancounts2 = lc2.meancounts # the number of data points in the light curve if lc1.n != lc2.n: raise StingrayError("Light curves do not have same number " "of time bins per segment.") # If dt differs slightly, its propagated error must not be more than # 1/100th of the bin if not np.isclose(lc1.dt, lc2.dt, rtol=0.01 * lc1.dt / lc1.tseg): raise StingrayError("Light curves do not have same time binning " "dt.") # In case a small difference exists, ignore it lc1.dt = lc2.dt self.n = lc1.n # the frequency resolution self.df = 1.0 / lc1.tseg # the number of averaged periodograms in the final output # This should *always* be 1 here self.m = 1 # make the actual Fourier transform and compute cross spectrum self.freq, self.unnorm_power = self._fourier_cross(lc1, lc2) # If co-spectrum is desired, normalize here. Otherwise, get raw back # with the imaginary part still intact. self.power = self._normalize_crossspectrum(self.unnorm_power, lc1.tseg) if lc1.err_dist.lower() != lc2.err_dist.lower(): simon("Your lightcurves have different statistics." "The errors in the Crossspectrum will be incorrect.") elif lc1.err_dist.lower() != "poisson": simon("Looks like your lightcurve statistic is not poisson." "The errors in the Powerspectrum will be incorrect.") if self.__class__.__name__ in ['Powerspectrum', 'AveragedPowerspectrum']: self.power_err = self.power / np.sqrt(self.m) elif self.__class__.__name__ in ['Crossspectrum', 'AveragedCrossspectrum']: # This is clearly a wild approximation. simon("Errorbars on cross spectra are not thoroughly tested. " "Please report any inconsistencies.") unnorm_power_err = np.sqrt(2) / np.sqrt(self.m) # Leahy-like unnorm_power_err /= (2 / np.sqrt(self.nphots1 * self.nphots2)) unnorm_power_err += np.zeros_like(self.power) self.power_err = \ self._normalize_crossspectrum(unnorm_power_err, lc1.tseg) else: self.power_err = np.zeros(len(self.power))
def _make_segment_spectrum(self, lc, segment_size, silent=False): """ Split the light curves into segments of size ``segment_size``, and calculate a power spectrum for each. Parameters ---------- lc : :class:`stingray.Lightcurve` objects\ The input light curve segment_size : ``numpy.float`` Size of each light curve segment to use for averaging. Other parameters ---------------- silent : bool, default False Suppress progress bars Returns ------- power_all : list of :class:`Powerspectrum` objects A list of power spectra calculated independently from each light curve segment nphots_all : ``numpy.ndarray`` List containing the number of photons for all segments calculated from ``lc`` """ if not isinstance(lc, Lightcurve): raise TypeError("lc must be a Lightcurve object") current_gtis = lc.gti if self.gti is None: self.gti = lc.gti else: if not np.allclose(lc.gti, self.gti): self.gti = np.vstack([self.gti, lc.gti]) check_gtis(self.gti) start_inds, end_inds = \ bin_intervals_from_gtis(current_gtis, segment_size, lc.time, dt=lc.dt) power_all = [] nphots_all = [] local_show_progress = show_progress if not self.show_progress or silent: def local_show_progress(a): return a for start_ind, end_ind in \ local_show_progress(zip(start_inds, end_inds)): time = lc.time[start_ind:end_ind] counts = lc.counts[start_ind:end_ind] counts_err = lc.counts_err[start_ind: end_ind] if np.sum(counts) == 0: warnings.warn( "No counts in interval {}--{}s".format(time[0], time[-1])) continue lc_seg = Lightcurve(time, counts, err=counts_err, err_dist=lc.err_dist.lower(), skip_checks=True, dt=lc.dt) power_seg = Powerspectrum(lc_seg, norm=self.norm) power_all.append(power_seg) nphots_all.append(np.sum(lc_seg.counts)) return power_all, nphots_all
def __init__(self, time, counts, err=None, input_counts=True, gti=None, err_dist='poisson', mjdref=0, dt=None): if not np.all(np.isfinite(time)): raise ValueError("There are inf or NaN values in " "your time array!") if not np.all(np.isfinite(counts)): raise ValueError("There are inf or NaN values in " "your counts array!") if len(time) != len(counts): raise StingrayError("time and counts array are not " "of the same length!") if len(time) <= 1: raise StingrayError("A single or no data points can not create " "a lightcurve!") if err is not None: if not np.all(np.isfinite(err)): raise ValueError("There are inf or NaN values in " "your err array") else: if err_dist.lower() not in valid_statistics: # err_dist set can be increased with other statistics raise StingrayError("Statistic not recognized." "Please select one of these: ", "{}".format(valid_statistics)) if err_dist.lower() == 'poisson': # Instead of the simple square root, we use confidence # intervals (should be valid for low fluxes too) err = poisson_symmetrical_errors(counts) else: simon("Stingray only uses poisson err_dist at the moment, " "We are setting your errors to zero. " "Sorry for the inconvenience.") err = np.zeros_like(counts) self.mjdref = mjdref self.time = np.asarray(time) dt_array = np.diff(np.sort(self.time)) dt_array_unsorted = np.diff(self.time) unsorted = np.any(dt_array_unsorted < 0) if dt is None: if unsorted: logging.warning("The light curve is unordered! This may cause " "unexpected behaviour in some methods! Use " "sort() to order the light curve in time and " "check that the time resolution `dt` is " "calculated correctly!") self.dt = np.median(dt_array) else: self.dt = dt self.bin_lo = self.time - 0.5 * self.dt self.bin_hi = self.time + 0.5 * self.dt self.err_dist = err_dist if unsorted: self.tstart = np.min(self.time) - 0.5 * self.dt self.tseg = np.max(self.time) - np.min(self.time) + self.dt else: self.tstart = self.time[0] - 0.5 * self.dt self.tseg = self.time[-1] - self.time[0] + self.dt self.gti = \ np.asarray(assign_value_if_none(gti, [[self.tstart, self.tstart + self.tseg]])) check_gtis(self.gti) good = create_gti_mask(self.time, self.gti, dt=self.dt) self.time = self.time[good] if input_counts: self.counts = np.asarray(counts)[good] self.countrate = self.counts / self.dt self.counts_err = np.asarray(err)[good] self.countrate_err = np.asarray(err)[good] / self.dt else: self.countrate = np.asarray(counts)[good] self.counts = self.countrate * self.dt self.counts_err = np.asarray(err)[good] * self.dt self.countrate_err = np.asarray(err)[good] self.meanrate = np.mean(self.countrate) self.meancounts = np.mean(self.counts) self.n = self.counts.shape[0] # Issue a warning if the input time iterable isn't regularly spaced, # i.e. the bin sizes aren't equal throughout. dt_array = [] for g in self.gti: mask = create_gti_mask(self.time, [g], dt=self.dt) t = self.time[mask] dt_array.extend(np.diff(t)) dt_array = np.asarray(dt_array) if not (np.allclose(dt_array, np.repeat(self.dt, dt_array.shape[0]))): simon("Bin sizes in input time array aren't equal throughout! " "This could cause problems with Fourier transforms. " "Please make the input time evenly sampled.")
def test_check_gti_fails_empty(self): with pytest.raises(ValueError) as excinfo: check_gtis([]) assert 'Empty' in str(excinfo.value)
def test_check_gtis_values(self): with pytest.raises(ValueError): check_gtis([[0, 2], [1, 3]]) with pytest.raises(ValueError): check_gtis([[1, 0]])
def _make_crossspectrum(self, lc1, lc2): # make sure the inputs work! if not isinstance(lc1, Lightcurve): raise TypeError("lc1 must be a lightcurve.Lightcurve object") if not isinstance(lc2, Lightcurve): raise TypeError("lc2 must be a lightcurve.Lightcurve object") if self.lc2.mjdref != self.lc1.mjdref: raise ValueError("MJDref is different in the two light curves") # Then check that GTIs make sense if self.gti is None: self.gti = cross_two_gtis(lc1.gti, lc2.gti) check_gtis(self.gti) if self.gti.shape[0] != 1: raise TypeError("Non-averaged Cross Spectra need " "a single Good Time Interval") lc1 = lc1.split_by_gti()[0] lc2 = lc2.split_by_gti()[0] # total number of photons is the sum of the # counts in the light curve self.nphots1 = np.float64(np.sum(lc1.counts)) self.nphots2 = np.float64(np.sum(lc2.counts)) self.meancounts1 = lc1.meancounts self.meancounts2 = lc2.meancounts # the number of data points in the light curve if lc1.n != lc2.n: raise StingrayError("Light curves do not have same number " "of time bins per segment.") # If dt differs slightly, its propagated error must not be more than # 1/100th of the bin if not np.isclose(lc1.dt, lc2.dt, rtol=0.01 * lc1.dt / lc1.tseg): raise StingrayError("Light curves do not have same time binning " "dt.") # In case a small difference exists, ignore it lc1.dt = lc2.dt self.n = lc1.n # the frequency resolution self.df = 1.0 / lc1.tseg # the number of averaged periodograms in the final output # This should *always* be 1 here self.m = 1 # make the actual Fourier transform and compute cross spectrum self.freq, self.unnorm_power = self._fourier_cross(lc1, lc2) # If co-spectrum is desired, normalize here. Otherwise, get raw back # with the imaginary part still intact. self.power = self._normalize_crossspectrum(self.unnorm_power, lc1.tseg) if lc1.err_dist.lower() != lc2.err_dist.lower(): simon("Your lightcurves have different statistics." "The errors in the Crossspectrum will be incorrect.") elif lc1.err_dist.lower() != "poisson": simon("Looks like your lightcurve statistic is not poisson." "The errors in the Powerspectrum will be incorrect.") if self.__class__.__name__ in ['Powerspectrum', 'AveragedPowerspectrum']: self.power_err = self.power / np.sqrt(self.m) elif self.__class__.__name__ in ['Crossspectrum', 'AveragedCrossspectrum']: # This is clearly a wild approximation. simon("Errorbars on cross spectra are not thoroughly tested. " "Please report any inconsistencies.") unnorm_power_err = np.sqrt(2) / np.sqrt(self.m) # Leahy-like unnorm_power_err /= (2 / np.sqrt(self.nphots1 * self.nphots2)) unnorm_power_err += np.zeros_like(self.power) self.power_err = \ self._normalize_crossspectrum(unnorm_power_err, lc1.tseg) else: self.power_err = np.zeros(len(self.power))
def _make_multitaper_periodogram(self, lc, NW=4, adaptive=False, jackknife=True, low_bias=True, lombscargle=False): """Compute the normalized multitaper spectral estimate. This includes checking for the presence of and applying Good Time Intervals, computing the a nitime inspired normalized power spectrum, unnormalizing it, and then renormalizing it using the required normalization. Parameters ---------- lc : :class:`stingray.Lightcurve` objects Two light curves used for computing the cross spectrum. NW: float, optional, default ``4`` The normalized half-bandwidth of the data tapers, indicating a multiple of the fundamental frequency of the DFT (Fs/N). Common choices are n/2, for n >= 4. adaptive: boolean, optional, default ``False`` Use an adaptive weighting routine to combine the PSD estimates of different tapers. jackknife: boolean, optional, default ``True`` Use the jackknife method to make an estimate of the PSD variance at each point. low_bias: boolean, optional, default ``True`` Rather than use 2NW tapers, only use the tapers that have better than 90% spectral concentration within the bandwidth (still using a maximum of 2NW tapers) """ if not isinstance(lc, Lightcurve): raise TypeError("lc must be a lightcurve.Lightcurve object") if self.gti is None: self.gti = cross_two_gtis(lc.gti, lc.gti) check_gtis(self.gti) if self.gti.shape[0] != 1: raise TypeError("Non-averaged Spectra need " "a single Good Time Interval") lc = lc.split_by_gti()[0] self.meancounts = lc.meancounts self.nphots = np.float64(np.sum(lc.counts)) self.err_dist = 'poisson' if lc.err_dist == 'poisson': self.var = lc.meancounts else: self.var = np.mean(lc.counts_err) ** 2 self.err_dist = 'gauss' self.dt = lc.dt self.n = lc.n # the frequency resolution self.df = 1.0 / lc.tseg # the number of averaged periodograms in the final output # This should *always* be 1 here self.m = 1 if lombscargle: self.freq, self.multitaper_norm_power = \ self._fourier_multitaper_lomb_scargle(lc, NW=NW, low_bias=low_bias) self.unnorm_power = self.multitaper_norm_power * lc.n * 2 else: self.freq, self.multitaper_norm_power = \ self._fourier_multitaper(lc, NW=NW, adaptive=adaptive, jackknife=jackknife, low_bias=low_bias) self.unnorm_power = self.multitaper_norm_power * lc.n / lc.dt self.power = \ self._normalize_multitaper(self.unnorm_power, lc.tseg) if lc.err_dist.lower() != "poisson": simon("Looks like your lightcurve statistic is not poisson." "The errors in the Powerspectrum will be incorrect.") self.power_err = self.power / np.sqrt(self.m)
def _make_crossspectrum(self, lc1, lc2): # make sure the inputs work! if not isinstance(lc1, Lightcurve): raise TypeError("lc1 must be a lightcurve.Lightcurve object") if not isinstance(lc2, Lightcurve): raise TypeError("lc2 must be a lightcurve.Lightcurve object") # Then check that GTIs make sense if self.gti is None: self.gti = cross_two_gtis(lc1.gti, lc2.gti) check_gtis(self.gti) if self.gti.shape[0] != 1: raise TypeError("Non-averaged Cross Spectra need " "a single Good Time Interval") lc1 = lc1.split_by_gti()[0] lc2 = lc2.split_by_gti()[0] # total number of photons is the sum of the # counts in the light curve self.nphots1 = np.float64(np.sum(lc1.counts)) self.nphots2 = np.float64(np.sum(lc2.counts)) self.meancounts1 = lc1.meancounts self.meancounts2 = lc2.meancounts # the number of data points in the light curve if lc1.n != lc2.n: raise StingrayError("Light curves do not have same number " "of time bins per segment.") if lc1.dt != lc2.dt: raise StingrayError("Light curves do not have " "same time binning dt.") self.n = lc1.n # the frequency resolution self.df = 1.0 / lc1.tseg # the number of averaged periodograms in the final output # This should *always* be 1 here self.m = 1 # make the actual Fourier transform and compute cross spectrum self.freq, self.unnorm_power = self._fourier_cross(lc1, lc2) # If co-spectrum is desired, normalize here. Otherwise, get raw back # with the imaginary part still intact. self.power = self._normalize_crossspectrum(self.unnorm_power, lc1.tseg) if lc1.err_dist.lower() != lc2.err_dist.lower(): simon("Your lightcurves have different statistics." "The errors in the Crossspectrum will be incorrect.") elif lc1.err_dist.lower() != "poisson": simon("Looks like your lightcurve statistic is not poisson." "The errors in the Powerspectrum will be incorrect.") if self.__class__.__name__ in [ 'Powerspectrum', 'AveragedPowerspectrum' ]: self.power_err = self.power / np.sqrt(self.m) elif self.__class__.__name__ in [ 'Crossspectrum', 'AveragedCrossspectrum' ]: # This is clearly a wild approximation. simon("Errorbars on cross spectra are not thoroughly tested. " "Please report any inconsistencies.") unnorm_power_err = np.sqrt(2) / np.sqrt(self.m) # Leahy-like unnorm_power_err /= (2 / np.sqrt(self.nphots1 * self.nphots2)) unnorm_power_err += np.zeros_like(self.power) self.power_err = \ self._normalize_crossspectrum(unnorm_power_err, lc1.tseg) else: self.power_err = np.zeros(len(self.power))
def _make_segment_spectrum(self, lc1, lc2, segment_size): """ Split the light curves into segments of size ``segment_size``, and calculate a cross spectrum for each. Parameters ---------- lc1, lc2 : :class:`stingray.Lightcurve` objects Two light curves used for computing the cross spectrum. segment_size : ``numpy.float`` Size of each light curve segment to use for averaging. Returns ------- cs_all : list of :class:`Crossspectrum`` objects A list of cross spectra calculated independently from each light curve segment nphots1_all, nphots2_all : ``numpy.ndarray` for each of ``lc1`` and ``lc2`` Two lists containing the number of photons for all segments calculated from ``lc1`` and ``lc2``. """ # TODO: need to update this for making cross spectra. assert isinstance(lc1, Lightcurve) assert isinstance(lc2, Lightcurve) if lc1.tseg != lc2.tseg: raise ValueError("Lightcurves do not have same tseg.") # If dt differs slightly, its propagated error must not be more than # 1/100th of the bin if not np.isclose(lc1.dt, lc2.dt, rtol=0.01 * lc1.dt / lc1.tseg): raise ValueError("Light curves do not have same time binning dt.") # In case a small difference exists, ignore it lc1.dt = lc2.dt if self.gti is None: self.gti = cross_two_gtis(lc1.gti, lc2.gti) lc1.gti = lc2.gti = self.gti lc1._apply_gtis() lc2._apply_gtis() check_gtis(self.gti) cs_all = [] nphots1_all = [] nphots2_all = [] start_inds, end_inds = \ bin_intervals_from_gtis(self.gti, segment_size, lc1.time, dt=lc1.dt) for start_ind, end_ind in zip(start_inds, end_inds): time_1 = lc1.time[start_ind:end_ind] counts_1 = lc1.counts[start_ind:end_ind] counts_1_err = lc1.counts_err[start_ind:end_ind] time_2 = lc2.time[start_ind:end_ind] counts_2 = lc2.counts[start_ind:end_ind] counts_2_err = lc2.counts_err[start_ind:end_ind] gti1 = np.array([[time_1[0] - lc1.dt / 2, time_1[-1] + lc1.dt / 2]]) gti2 = np.array([[time_2[0] - lc2.dt / 2, time_2[-1] + lc2.dt / 2]]) lc1_seg = Lightcurve(time_1, counts_1, err=counts_1_err, err_dist=lc1.err_dist, gti=gti1, dt=lc1.dt) lc2_seg = Lightcurve(time_2, counts_2, err=counts_2_err, err_dist=lc2.err_dist, gti=gti2, dt=lc2.dt) cs_seg = Crossspectrum(lc1_seg, lc2_seg, norm=self.norm, power_type=self.power_type) cs_all.append(cs_seg) nphots1_all.append(np.sum(lc1_seg.counts)) nphots2_all.append(np.sum(lc2_seg.counts)) return cs_all, nphots1_all, nphots2_all
def __init__(self, time, counts, input_counts=True, gti=None): """ Make a light curve object from an array of time stamps and an array of counts. Parameters ---------- time: iterable A list or array of time stamps for a light curve counts: iterable, optional, default None A list or array of the counts in each bin corresponding to the bins defined in `time` (note: **not** the count rate, i.e. counts/second, but the counts/bin). input_counts: bool, optional, default True If True, the code assumes that the input data in 'counts' is in units of counts/bin. If False, it assumes the data in 'counts' is in counts/second. gti: 2-d float array, default None [[gti0_0, gti0_1], [gti1_0, gti1_1], ...] Good Time Intervals. They are *not* applied to the data by default. They will be used by other methods to have an indication of the "safe" time intervals to use during analysis. Attributes ---------- time: numpy.ndarray The array of midpoints of time bins. counts: numpy.ndarray The counts per bin corresponding to the bins in `time`. countrate: numpy.ndarray The counts per second in each of the bins defined in `time`. meanrate: float The mean count rate of the light curve. meancounts: float The mean counts of the light curve. n: int The number of data points in the light curve. dt: float The time resolution of the light curve. tseg: float The total duration of the light curve. tstart: float The start time of the light curve. gti: 2-d float array [[gti0_0, gti0_1], [gti1_0, gti1_1], ...] Good Time Intervals. They indicate the "safe" time intervals to be used during the analysis of the light curve. """ if not np.all(np.isfinite(time)): raise ValueError("There are inf or NaN values in " "your time array!") if not np.all(np.isfinite(counts)): raise ValueError("There are inf or NaN values in " "your counts array!") if len(time) != len(counts): raise StingrayError("time are counts array are not " "of the same length!") if len(time) <= 1: raise StingrayError("A single or no data points can not create " "a lightcurve!") self.time = np.asarray(time) self.dt = time[1] - time[0] if input_counts: self.counts = np.asarray(counts) self.countrate = self.counts / self.dt else: self.countrate = np.asarray(counts) self.counts = self.countrate * self.dt self.meanrate = np.mean(self.countrate) self.meancounts = np.mean(self.counts) self.n = self.counts.shape[0] # Issue a warning if the input time iterable isn't regularly spaced, # i.e. the bin sizes aren't equal throughout. dt_array = np.diff(self.time) if not (np.allclose(dt_array, np.repeat(self.dt, dt_array.shape[0]))): simon("Bin sizes in input time array aren't equal throughout! " "This could cause problems with Fourier transforms. " "Please make the input time evenly sampled.") self.tseg = self.time[-1] - self.time[0] + self.dt self.tstart = self.time[0] - 0.5*self.dt self.gti = \ np.asarray(assign_value_if_none(gti, [[self.tstart, self.tstart + self.tseg]])) check_gtis(self.gti)