def __init__(self, events, freq_interval, energy_spec, ref_band=None, bin_time=1, use_pi=False, segment_size=None, events2=None): self.events1 = events self.events2 = assign_value_if_none(events2, events) self.freq_interval = freq_interval self.use_pi = use_pi self.bin_time = bin_time if isinstance(energy_spec, tuple): energies = _decode_energy_specification(energy_spec) else: energies = np.asarray(energy_spec) self.energy_intervals = list(zip(energies[0:-1], energies[1:])) self.ref_band = np.asarray(assign_value_if_none(ref_band, [0, np.inf])) if len(self.ref_band.shape) <= 1: self.ref_band = np.asarray([self.ref_band]) self.segment_size = segment_size self.spectrum, self.spectrum_error = self._spectrum_function()
def lcurve_from_txt(txt_file, outfile=None, noclobber=False, outdir=None, mjdref=None, gti=None): """ Load a lightcurve from a text file. Parameters ---------- txt_file : str File name of the input light curve in text format. Assumes two columns: time, counts. Times are seconds from MJDREF 55197.00076601852 (NuSTAR) if not otherwise specified. Returns ------- outfile : [str] Returned as a list with a single element for consistency with `lcurve_from_events` Other Parameters ---------------- outfile : str Output file name noclobber : bool If True, do not overwrite existing files mjdref : float, default 55197.00076601852 the MJD time reference gti : [[gti0_0, gti0_1], [gti1_0, gti1_1], ...] Good Time Intervals """ import numpy as np if mjdref is None: mjdref = np.longdouble('55197.00076601852') outfile = assign_value_if_none(outfile, hen_root(txt_file) + '_lc') outfile = outfile.replace(HEN_FILE_EXTENSION, '') + HEN_FILE_EXTENSION outdir = assign_value_if_none( outdir, os.path.dirname(os.path.abspath(txt_file))) _, outfile = os.path.split(outfile) mkdir_p(outdir) outfile = os.path.join(outdir, outfile) if noclobber and os.path.exists(outfile): warnings.warn('File exists, and noclobber option used. Skipping') return [outfile] time, counts = np.genfromtxt(txt_file, delimiter=' ', unpack=True) time = np.array(time, dtype=np.longdouble) counts = np.array(counts, dtype=np.float) lc = Lightcurve(time=time, counts=counts, gti=gti, mjdref=mjdref) lc.instr = 'EXTERN' logging.info('Saving light curve to %s' % outfile) save_lcurve(lc, outfile) return [outfile]
def __getitem__(self, index): """ Return the corresponding count value at the index or a new Lightcurve object upon slicing. This method adds functionality to retrieve the count value at a particular index. This also can be used for slicing and generating a new Lightcurve object. GTIs are recalculated based on the new light curve segment If the slice object is of kind start:stop:step, GTIs are also sliced, and rewritten as zip(time - self.dt /2, time + self.dt / 2) Parameters ---------- index : int or slice instance Index value of the time array or a slice object. Examples -------- >>> time = [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> count = [11, 22, 33, 44, 55, 66, 77, 88, 99] >>> lc = Lightcurve(time, count) >>> lc[2] 33 >>> lc[:2].counts array([11, 22]) """ if isinstance(index, int): return self.counts[index] elif isinstance(index, slice): start = assign_value_if_none(index.start, 0) stop = assign_value_if_none(index.stop, len(self.counts)) step = assign_value_if_none(index.step, 1) new_counts = self.counts[start:stop:step] new_time = self.time[start:stop:step] new_gti = [[ self.time[start] - 0.5 * self.dt, self.time[stop - 1] + 0.5 * self.dt ]] new_gti = np.asarray(new_gti) if step > 1: new_gt1 = np.array( list(zip(new_time - self.dt / 2, new_time + self.dt / 2))) new_gti = cross_two_gtis(new_gti, new_gt1) new_gti = cross_two_gtis(self.gti, new_gti) return Lightcurve(new_time, new_counts, mjdref=self.mjdref, gti=new_gti, dt=self.dt) else: raise IndexError("The index must be either an integer or a slice " "object !")
def __getitem__(self, index): """ Return the corresponding count value at the index or a new Lightcurve object upon slicing. This method adds functionality to retrieve the count value at a particular index. This also can be used for slicing and generating a new Lightcurve object. GTIs are recalculated based on the new light curve segment If the slice object is of kind start:stop:step, GTIs are also sliced, and rewritten as zip(time - self.dt /2, time + self.dt / 2) Parameters ---------- index : int or slice instance Index value of the time array or a slice object. Examples -------- >>> time = [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> count = [11, 22, 33, 44, 55, 66, 77, 88, 99] >>> lc = Lightcurve(time, count) >>> lc[2] 33 >>> lc[:2].counts array([11, 22]) """ if isinstance(index, int): return self.counts[index] elif isinstance(index, slice): start = assign_value_if_none(index.start, 0) stop = assign_value_if_none(index.stop, len(self.counts)) step = assign_value_if_none(index.step, 1) new_counts = self.counts[start:stop:step] new_time = self.time[start:stop:step] new_gti = [[self.time[start] - 0.5 * self.dt, self.time[stop - 1] + 0.5 * self.dt]] new_gti = np.asarray(new_gti) if step > 1: new_gt1 = np.array(list(zip(new_time - self.dt / 2, new_time + self.dt / 2))) new_gti = cross_two_gtis(new_gti, new_gt1) new_gti = cross_two_gtis(self.gti, new_gti) return Lightcurve(new_time, new_counts, mjdref=self.mjdref, gti=new_gti, dt=self.dt) else: raise IndexError("The index must be either an integer or a slice " "object !")
def _construct_lightcurves(self, channel_band, tstart=None, tstop=None, exclude=True, only_base=False): if self.use_pi: energies1 = self.events1.pi energies2 = self.events2.pi else: energies2 = self.events2.energy energies1 = self.events1.energy gti = cross_two_gtis(self.events1.gti, self.events2.gti) tstart = assign_value_if_none(tstart, gti[0, 0]) tstop = assign_value_if_none(tstop, gti[-1, -1]) good = (energies1 >= channel_band[0]) & (energies1 < channel_band[1]) base_lc = Lightcurve.make_lightcurve(self.events1.time[good], self.bin_time, tstart=tstart, tseg=tstop - tstart, gti=gti, mjdref=self.events1.mjdref) if only_base: return base_lc if exclude: ref_intervals = self._decide_ref_intervals(channel_band, self.ref_band) else: ref_intervals = self.ref_band ref_lc = Lightcurve(base_lc.time, np.zeros_like(base_lc.counts), gti=base_lc.gti, mjdref=base_lc.mjdref, err_dist='gauss') for i in ref_intervals: good = (energies2 >= i[0]) & (energies2 < i[1]) new_lc = Lightcurve.make_lightcurve(self.events2.time[good], self.bin_time, tstart=tstart, tseg=tstop - tstart, gti=base_lc.gti, mjdref=self.events2.mjdref) ref_lc = ref_lc + new_lc ref_lc.err_dist = base_lc.err_dist return base_lc, ref_lc
def _construct_lightcurves(self, channel_band, tstart=None, tstop=None, exclude=True, only_base=False): if self.use_pi: energies1 = self.events1.pi energies2 = self.events2.pi else: energies2 = self.events2.energy energies1 = self.events1.energy gti = cross_two_gtis(self.events1.gti, self.events2.gti) tstart = assign_value_if_none(tstart, gti[0, 0]) tstop = assign_value_if_none(tstop, gti[-1, -1]) good = (energies1 >= channel_band[0]) & (energies1 < channel_band[1]) base_lc = Lightcurve.make_lightcurve(self.events1.time[good], self.bin_time, tstart=tstart, tseg=tstop - tstart, gti=gti, mjdref=self.events1.mjdref) if only_base: return base_lc if exclude: ref_intervals = self._decide_ref_intervals(channel_band, self.ref_band) else: ref_intervals = self.ref_band ref_lc = Lightcurve(base_lc.time, np.zeros_like(base_lc.counts), gti=base_lc.gti, mjdref=base_lc.mjdref, err_dist='gauss') for i in ref_intervals: good = (energies2 >= i[0]) & (energies2 < i[1]) new_lc = Lightcurve.make_lightcurve(self.events2.time[good], self.bin_time, tstart=tstart, tseg=tstop - tstart, gti=base_lc.gti, mjdref=self.events2.mjdref) ref_lc = ref_lc + new_lc ref_lc.err_dist = base_lc.err_dist return base_lc, ref_lc
def __init__( self, events, freq_interval, energy_spec, ref_band=None, bin_time=1, use_pi=False, segment_size=None, events2=None, return_complex=False, ): self.events1 = events self.events2 = assign_value_if_none(events2, events) self._analyze_inputs() # This will be set to True in ComplexCovariance self.return_complex = return_complex self.freq_interval = freq_interval self.use_pi = use_pi self.bin_time = bin_time if isinstance(energy_spec, tuple): energies = _decode_energy_specification(energy_spec) else: energies = np.asarray(energy_spec) self.energy_intervals = list(zip(energies[0:-1], energies[1:])) self.ref_band = np.asarray(assign_value_if_none(ref_band, [0, np.inf])) if len(self.ref_band.shape) <= 1: self.ref_band = np.asarray([self.ref_band]) self.segment_size = self.delta_nu = None if segment_size is not None: self.segment_size = segment_size self.delta_nu = 1 / self.segment_size self._create_empty_spectrum() if len(events.time) == 0: simon("There are no events in your event list!" + "Can't make a spectrum!") else: self._spectrum_function()
def _load_and_prepare_TOAs(mjds, errs_us=None, ephem="DE405"): errs_us = assign_value_if_none(errs_us, np.zeros_like(mjds)) toalist = [None] * len(mjds) for i, m in enumerate(mjds): toalist[i] = \ toa.TOA(m, error=errs_us[i], obs='Barycenter', scale='tdb') toalist = toa.TOAs(toalist=toalist) if 'tdb' not in toalist.table.colnames: toalist.compute_TDBs() if 'ssb_obs_pos' not in toalist.table.colnames: toalist.compute_posvels(ephem, False) return toalist
def load_gtis(fits_file, gtistring=None): """Load GTI from HDU EVENTS of file fits_file.""" from astropy.io import fits as pf import numpy as np gtistring = assign_value_if_none(gtistring, 'GTI') logging.info("Loading GTIS from file %s" % fits_file) lchdulist = pf.open(fits_file, checksum=True) lchdulist.verify('warn') gtitable = lchdulist[gtistring].data gti_list = np.array( [[a, b] for a, b in zip(gtitable.field('START'), gtitable.field('STOP'))], dtype=np.longdouble) lchdulist.close() return gti_list
def load_events_and_gtis(fits_file, additional_columns=None, gtistring='GTI,STDGTI', gti_file=None, hduname='EVENTS', column='TIME'): """Load event lists and GTIs from one or more files. Loads event list from HDU EVENTS of file fits_file, with Good Time intervals. Optionally, returns additional columns of data from the same HDU of the events. Parameters ---------- fits_file : str return_limits: bool, optional Return the TSTART and TSTOP keyword values additional_columns: list of str, optional A list of keys corresponding to the additional columns to extract from the event HDU (ex.: ['PI', 'X']) Returns ------- ev_list : array-like gtis: [[gti0_0, gti0_1], [gti1_0, gti1_1], ...] additional_data: dict A dictionary, where each key is the one specified in additional_colums. The data are an array with the values of the specified column in the fits file. t_start : float t_stop : float """ from astropy.io import fits as pf gtistring = assign_value_if_none(gtistring, 'GTI,STDGTI') lchdulist = pf.open(fits_file) # Load data table try: lctable = lchdulist[hduname].data except: # pragma: no cover logging.warning('HDU %s not found. Trying first extension' % hduname) lctable = lchdulist[1].data hduname = 1 # Read event list ev_list = np.array(lctable.field(column), dtype=np.longdouble) detector_id = _get_detector_id(lctable) det_number = None if detector_id is None else list(set(detector_id)) header = lchdulist[1].header # Read TIMEZERO keyword and apply it to events try: timezero = np.longdouble(header['TIMEZERO']) except: # pragma: no cover logging.warning("No TIMEZERO in file") timezero = np.longdouble(0.) try: instr = header['INSTRUME'] except: instr = 'unknown' ev_list += timezero # Read TSTART, TSTOP from header try: t_start = np.longdouble(header['TSTART']) t_stop = np.longdouble(header['TSTOP']) except: # pragma: no cover logging.warning("Tstart and Tstop error. using defaults") t_start = ev_list[0] t_stop = ev_list[-1] mjdref = np.longdouble(high_precision_keyword_read(header, 'MJDREF')) # Read and handle GTI extension accepted_gtistrings = gtistring.split(',') if gti_file is None: # Select first GTI with accepted name try: gti_list = \ _get_gti_from_all_extensions( lchdulist, accepted_gtistrings=accepted_gtistrings, det_numbers=det_number) except: # pragma: no cover warnings.warn("No extensions found with a valid name. " "Please check the `accepted_gtistrings` values.") gti_list = np.array([[t_start, t_stop]], dtype=np.longdouble) else: gti_list = load_gtis(gti_file, gtistring) if additional_columns is None: additional_columns = ['PI'] if 'PI' not in additional_columns: additional_columns.append('PI') additional_data = _get_additional_data(lctable, additional_columns) lchdulist.close() # Sort event list order = np.argsort(ev_list) ev_list = ev_list[order] additional_data = _order_list_of_arrays(additional_data, order) pi = additional_data['PI'][order] additional_data.pop('PI') returns = _empty() returns.ev_list = EventList(ev_list, gti=gti_list, pi=pi) returns.ev_list.instr = instr returns.ev_list.mjdref = mjdref returns.ev_list.header = header.tostring() returns.additional_data = additional_data returns.t_start = t_start returns.t_stop = t_stop returns.detector_id = detector_id return returns
def treat_event_file(filename, noclobber=False, gti_split=False, min_length=4, gtistring=None): """Read data from an event file, with no external GTI information. Parameters ---------- filename : str Other Parameters ---------------- noclobber: bool if a file is present, do not overwrite it gtistring: str comma-separated set of GTI strings to consider gti_split: bool split the file in multiple chunks, containing one GTI each min_length: float minimum length of GTIs accepted (only if gti_split is True) """ gtistring = assign_value_if_none(gtistring, 'GTI,STDGTI') logging.info('Opening %s' % filename) instr = read_header_key(filename, 'INSTRUME') mission = read_header_key(filename, 'TELESCOP') data = load_events_and_gtis(filename, gtistring=gtistring) events = data.ev_list gtis = events.gti detector_id = data.detector_id if detector_id is not None: detectors = list(set(detector_id)) else: detectors = [None] outfile_root = \ hen_root(filename) + '_' + mission.lower() + '_' + instr.lower() for d in detectors: if d is not None: good_det = data.detector_id outroot_local = \ '{0}_det{1:02d}'.format(outfile_root, d) else: good_det = np.ones_like(events.time, dtype=bool) outroot_local = outfile_root outfile = outroot_local + '_ev' + HEN_FILE_EXTENSION if noclobber and os.path.exists(outfile) and (not gti_split): warnings.warn( '{0} exists, and noclobber option used. Skipping'.format( outfile)) return if gti_split: for ig, g in enumerate(gtis): length = g[1] - g[0] if length < min_length: print("GTI shorter than {} s; skipping".format(min_length)) continue outfile_local = \ '{0}_gti{1}_ev'.format(outroot_local, ig) + HEN_FILE_EXTENSION if noclobber and os.path.exists(outfile_local): warnings.warn('{0} exists, '.format(outfile_local) + 'and noclobber option used. Skipping') return good = np.logical_and(events.time >= g[0], events.time < g[1]) all_good = good_det & good events_filt = EventList(events.time[all_good], pi=events.pi[all_good], gti=np.array([g], dtype=np.longdouble), mjdref=events.mjdref) events_filt.instr = events.instr events_filt.header = events.header save_events(events_filt, outfile_local) pass else: events_filt = EventList(events.time[good_det], pi=events.pi[good_det], gti=events.gti, mjdref=events.mjdref) events_filt.instr = events.instr events_filt.header = events.header save_events(events_filt, outfile)
def lcurve_from_fits(fits_file, gtistring='GTI', timecolumn='TIME', ratecolumn=None, ratehdu=1, fracexp_limit=0.9, outfile=None, noclobber=False, outdir=None): """ Load a lightcurve from a fits file and save it in HENDRICS format. .. note :: FITS light curve handling is still under testing. Absolute times might be incorrect depending on the light curve format. Parameters ---------- fits_file : str File name of the input light curve in FITS format Returns ------- outfile : [str] Returned as a list with a single element for consistency with `lcurve_from_events` Other Parameters ---------------- gtistring : str Name of the GTI extension in the FITS file timecolumn : str Name of the column containing times in the FITS file ratecolumn : str Name of the column containing rates in the FITS file ratehdu : str or int Name or index of the FITS extension containing the light curve fracexp_limit : float Minimum exposure fraction allowed outfile : str Output file name noclobber : bool If True, do not overwrite existing files """ logging.warning( """WARNING! FITS light curve handling is still under testing. Absolute times might be incorrect.""") # TODO: # treat consistently TDB, UTC, TAI, etc. This requires some documentation # reading. For now, we assume TDB from astropy.io import fits as pf from astropy.time import Time import numpy as np from .base import create_gti_from_condition outfile = assign_value_if_none(outfile, hen_root(fits_file) + '_lc') outfile = outfile.replace(HEN_FILE_EXTENSION, '') + HEN_FILE_EXTENSION outdir = assign_value_if_none( outdir, os.path.dirname(os.path.abspath(fits_file))) _, outfile = os.path.split(outfile) mkdir_p(outdir) outfile = os.path.join(outdir, outfile) if noclobber and os.path.exists(outfile): warnings.warn('File exists, and noclobber option used. Skipping') return [outfile] lchdulist = pf.open(fits_file) lctable = lchdulist[ratehdu].data # Units of header keywords tunit = lchdulist[ratehdu].header['TIMEUNIT'] try: mjdref = high_precision_keyword_read(lchdulist[ratehdu].header, 'MJDREF') mjdref = Time(mjdref, scale='tdb', format='mjd') except: mjdref = None try: instr = lchdulist[ratehdu].header['INSTRUME'] except: instr = 'EXTERN' # ---------------------------------------------------------------- # Trying to comply with all different formats of fits light curves. # It's a madness... try: tstart = high_precision_keyword_read(lchdulist[ratehdu].header, 'TSTART') tstop = high_precision_keyword_read(lchdulist[ratehdu].header, 'TSTOP') except: raise(Exception('TSTART and TSTOP need to be specified')) # For nulccorr lcs this whould work timezero = high_precision_keyword_read(lchdulist[ratehdu].header, 'TIMEZERO') # Sometimes timezero is "from tstart", sometimes it's an absolute time. # This tries to detect which case is this, and always consider it # referred to tstart timezero = assign_value_if_none(timezero, 0) # for lcurve light curves this should instead work if tunit == 'd': # TODO: # Check this. For now, I assume TD (JD - 2440000.5). # This is likely wrong timezero = Time(2440000.5 + timezero, scale='tdb', format='jd') tstart = Time(2440000.5 + tstart, scale='tdb', format='jd') tstop = Time(2440000.5 + tstop, scale='tdb', format='jd') # if None, use NuSTAR defaulf MJDREF mjdref = assign_value_if_none( mjdref, Time(np.longdouble('55197.00076601852'), scale='tdb', format='mjd')) timezero = (timezero - mjdref).to('s').value tstart = (tstart - mjdref).to('s').value tstop = (tstop - mjdref).to('s').value if timezero > tstart: timezero -= tstart time = np.array(lctable.field(timecolumn), dtype=np.longdouble) if time[-1] < tstart: time += timezero + tstart else: time += timezero try: dt = high_precision_keyword_read(lchdulist[ratehdu].header, 'TIMEDEL') if tunit == 'd': dt *= 86400 except: warnings.warn('Assuming that TIMEDEL is the difference between the' ' first two times of the light curve') dt = time[1] - time[0] # ---------------------------------------------------------------- ratecolumn = assign_value_if_none( ratecolumn, _look_for_array_in_array(['RATE', 'RATE1', 'COUNTS'], lctable.names)) rate = np.array(lctable.field(ratecolumn), dtype=np.float) try: rate_e = np.array(lctable.field('ERROR'), dtype=np.longdouble) except: rate_e = np.zeros_like(rate) if 'RATE' in ratecolumn: rate *= dt rate_e *= dt try: fracexp = np.array(lctable.field('FRACEXP'), dtype=np.longdouble) except: fracexp = np.ones_like(rate) good_intervals = (rate == rate) * (fracexp >= fracexp_limit) * \ (fracexp <= 1) rate[good_intervals] /= fracexp[good_intervals] rate_e[good_intervals] /= fracexp[good_intervals] rate[np.logical_not(good_intervals)] = 0 try: gtitable = lchdulist[gtistring].data gti_list = np.array([[a, b] for a, b in zip(gtitable.field('START'), gtitable.field('STOP'))], dtype=np.longdouble) except: gti_list = create_gti_from_condition(time, good_intervals) lchdulist.close() lc = Lightcurve(time=time, counts=rate, err=rate_e, gti=gti_list, mjdref=mjdref.mjd) lc.instr = instr lc.header = lchdulist[ratehdu].header.tostring() logging.info('Saving light curve to %s' % outfile) save_lcurve(lc, outfile) return [outfile]
def lcurve_from_events(f, safe_interval=0, pi_interval=None, e_interval=None, min_length=0, gti_split=False, ignore_gtis=False, bintime=1., outdir=None, outfile=None, noclobber=False): """Bin an event list in a light curve. Parameters ---------- f : str Input event file name bintime : float The bin time of the output light curve Returns ------- outfiles : list List of output light curves Other Parameters ---------------- safe_interval : float or [float, float] Seconds to filter out at the start and end of each GTI. If single float, these safe windows are equal, otherwise the two numbers refer to the start and end of the GTI respectively pi_interval : [int, int] PI channel interval to select. Default None, meaning that all PI channels are used e_interval : [float, float] Energy interval to select (only works if event list is calibrated with `calibrate`). Default None min_length : float GTIs below this length will be filtered out gti_split : bool If True, create one light curve for each good time interval ignore_gtis : bool Ignore good time intervals, and get a single light curve that includes possible gaps outdir : str Output directory outfile : str Output file noclobber : bool If True, do not overwrite existing files """ logging.info("Loading file %s..." % f) evdata = load_events(f) logging.info("Done.") if bintime < 0: bintime = 2 ** (bintime) bintime = np.longdouble(bintime) tag = '' gtis = evdata.gti tstart = np.min(gtis) tstop = np.max(gtis) events = evdata.time if hasattr(evdata, 'instr') and evdata.instr is not None: instr = evdata.instr else: instr = "unknown" if ignore_gtis: gtis = np.array([[tstart, tstop]]) evdata.gtis = gtis total_lc = evdata.to_lc(100) total_lc.instr = instr # Then, apply filters if pi_interval is not None and np.all(np.array(pi_interval) > 0): pis = evdata.pi good = np.logical_and(pis > pi_interval[0], pis <= pi_interval[1]) events = events[good] tag = '_PI%g-%g' % (pi_interval[0], pi_interval[1]) elif e_interval is not None and np.all(np.array(e_interval) > 0): if not hasattr(evdata, 'energy') or evdata.energy is None: raise \ ValueError("No energy information is present in the file." + " Did you run HENcalibrate?") es = evdata.energy good = np.logical_and(es > e_interval[0], es <= e_interval[1]) events = events[good] tag = '_E%g-%g' % (e_interval[0], e_interval[1]) else: pass if tag != "": save_lcurve(total_lc, hen_root(f) + '_std_lc' + HEN_FILE_EXTENSION) # Assign default value if None outfile = assign_value_if_none(outfile, hen_root(f) + tag + '_lc') # Take out extension from name, if present, then give extension. This # avoids multiple extensions outfile = outfile.replace(HEN_FILE_EXTENSION, '') + HEN_FILE_EXTENSION outdir = assign_value_if_none( outdir, os.path.dirname(os.path.abspath(f))) _, outfile = os.path.split(outfile) mkdir_p(outdir) outfile = os.path.join(outdir, outfile) if noclobber and os.path.exists(outfile): warnings.warn('File exists, and noclobber option used. Skipping') return [outfile] lc = Lightcurve.make_lightcurve(events, bintime, tstart=tstart, tseg=tstop-tstart, mjdref=evdata.mjdref) lc.instr = instr lc = filter_lc_gtis(lc, safe_interval=safe_interval, delete=False, min_length=min_length) if len(lc.gti) == 0: warnings.warn( "No GTIs above min_length ({0}s) found.".format(min_length)) return if gti_split: lcs = lc.split_by_gti() outfiles = [] for ib, l0 in enumerate(lcs): local_tag = tag + '_gti%d' % ib outf = hen_root(outfile) + local_tag + '_lc' + HEN_FILE_EXTENSION if noclobber and os.path.exists(outf): warnings.warn( 'File exists, and noclobber option used. Skipping') outfiles.append(outf) l0.instr = lc.instr save_lcurve(l0, outf) outfiles.append(outf) else: logging.info('Saving light curve to %s' % outfile) save_lcurve(lc, outfile) outfiles = [outfile] # For consistency in return value return outfiles
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 test_assign_value_if_none(self): assert utils.assign_value_if_none(None, 2) == 2 assert utils.assign_value_if_none(1, 2) == 1
def _construct_lightcurves(self, channel_band, tstart=None, tstop=None, exclude=True, only_base=False): """ Construct light curves from event data, for each band of interest. Parameters ---------- channel_band : iterable of type ``[elow, ehigh]`` The lower/upper limits of the energies to be contained in the band of interest tstart : float, optional, default ``None`` A common start time (if start of observation is different from the first recorded event) tstop : float, optional, default ``None`` A common stop time (if start of observation is different from the first recorded event) exclude : bool, optional, default ``True`` if ``True``, exclude the band of interest from the reference band only_base : bool, optional, default ``False`` if ``True``, only return the light curve of the channel of interest, not that of the reference band Returns ------- base_lc : :class:`Lightcurve` object The light curve of the channels of interest ref_lc : :class:`Lightcurve` object (only returned if ``only_base`` is ``False``) The reference light curve for comparison with ``base_lc`` """ if self.use_pi: energies1 = self.events1.pi energies2 = self.events2.pi else: energies2 = self.events2.energy energies1 = self.events1.energy gti = cross_two_gtis(self.events1.gti, self.events2.gti) tstart = assign_value_if_none(tstart, gti[0, 0]) tstop = assign_value_if_none(tstop, gti[-1, -1]) good = (energies1 >= channel_band[0]) & (energies1 < channel_band[1]) base_lc = Lightcurve.make_lightcurve( self.events1.time[good], self.bin_time, tstart=tstart, tseg=tstop - tstart, gti=gti, mjdref=self.events1.mjdref, ) if only_base: return base_lc if exclude: ref_intervals = get_non_overlapping_ref_band( channel_band, self.ref_band) else: ref_intervals = self.ref_band ref_lc = Lightcurve( base_lc.time, np.zeros_like(base_lc.counts), gti=base_lc.gti, mjdref=base_lc.mjdref, dt=base_lc.dt, err_dist=base_lc.err_dist, skip_checks=True, ) for i in ref_intervals: good = (energies2 >= i[0]) & (energies2 < i[1]) new_lc = Lightcurve.make_lightcurve( self.events2.time[good], self.bin_time, tstart=tstart, tseg=tstop - tstart, gti=base_lc.gti, mjdref=self.events2.mjdref, ) ref_lc = ref_lc + new_lc ref_lc.err_dist = base_lc.err_dist return base_lc, ref_lc
def __init__(self, ev_times, freq, nph=128, nt=128, test=False, fdot=0, fddot=0, mjdref=None, pepoch=None, gti=None, label="phaseogram", norm=None, position=None, object=None, plot_only=False, time_corr=None, **kwargs): """Init BasePhaseogram class. Parameters ---------- ev_times : array-like Event times freq : float Frequency of pulsation Other parameters ---------------- nph : int Number of phase bins in the profile nt : int Number of time bins in the profile pepoch : float, default None Epoch of timing solution, in the same units as ev_times mjdref : float, default None Reference MJD fdot : float First frequency derivative fddot : float Second frequency derivative label : str Label for windows norm : str Normalization position : `astropy.Skycoord` object Position of the pulsar object : str Name of the pulsar **kwargs : keyword args additional arguments to pass to `self._construct_widgets` """ self.fdot = fdot self.fddot = fddot self.nt = nt self.nph = nph self.mjdref = mjdref self.gti = gti self.label = label self.test = test self.pepoch = assign_value_if_none(pepoch, ev_times[0]) self.time_corr = \ assign_value_if_none(time_corr, np.zeros_like(ev_times)) self.ev_times = ev_times self.freq = freq self.norm = norm self.position = position self.object = object self.timing_model_string = "" self.time_corr_fun = interp1d(self.ev_times, self.time_corr, bounds_error=False, fill_value="extrapolate") self.time_corr_mjd_fun = interp1d(self.ev_times / 86400 + mjdref, self.time_corr / 86400, bounds_error=False, fill_value="extrapolate") self.fig = plt.figure(label, figsize=plt.figaspect(1.2)) from matplotlib.gridspec import GridSpec gs = GridSpec(2, 2, width_ratios=[15, 1], height_ratios=[2, 3]) plt.subplots_adjust(left=0.25, bottom=0.30) ax = plt.subplot(gs[1, 0]) self.profax = plt.subplot(gs[0, 0], sharex=ax) self.profax.set_xticks([]) colorbax = plt.subplot(gs[1, 1]) corrected_times = self.ev_times - self._delay_fun(self.ev_times) self.unnorm_phaseogr, phases, times, additional_info = \ normalized_phaseogram(None, corrected_times, freq, return_plot=True, nph=nph, nt=nt, fdot=fdot, fddot=fddot, plot=False, pepoch=pepoch) self.phaseogr, phases, times, additional_info = \ normalized_phaseogram(self.norm, corrected_times, freq, return_plot=True, nph=nph, nt=nt, fdot=fdot, fddot=fddot, plot=False, pepoch=pepoch) self.phases, self.times = phases, times self.pcolor = ax.pcolormesh(phases, times, self.phaseogr.T, cmap=DEFAULT_COLORMAP) self.colorbar = plt.colorbar(self.pcolor, cax=colorbax) ax.set_xlabel('Phase') ax.set_ylabel('Time') # plt.colorbar() self.lines = [] self.line_phases = np.arange(-2, 3, 0.5) for ph0 in self.line_phases: newline, = ax.plot(np.zeros_like(times) + ph0, times, zorder=10, lw=2, color='w') self.lines.append(newline) ax.set_xlim([0, 2]) axcolor = '#ff8888' self.slider_axes = [] def newax_fn(*args, **kwargs): try: ax = plt.axes(*args, facecolor=axcolor) except AttributeError: # MPL < 2 ax = plt.axes(*args, axis_bgcolor=axcolor) return ax self.slider_axes.append( newax_fn([0.25, 0.1, 0.5, 0.03], facecolor=axcolor)) self.slider_axes.append( newax_fn([0.25, 0.15, 0.5, 0.03], facecolor=axcolor)) self.slider_axes.append( newax_fn([0.25, 0.2, 0.5, 0.03], facecolor=axcolor)) self._construct_widgets(**kwargs) self.closeax = plt.axes([0.15, 0.020, 0.15, 0.04]) self.button_close = Button(self.closeax, 'Quit', color=axcolor, hovercolor='0.8') self.recalcax = plt.axes([0.3, 0.020, 0.15, 0.04]) self.button_recalc = Button(self.recalcax, 'Recalculate', color=axcolor, hovercolor='0.975') self.resetax = plt.axes([0.45, 0.020, 0.15, 0.04]) self.button_reset = Button(self.resetax, 'Reset', color=axcolor, hovercolor='0.975') self.zoominax = plt.axes([0.6, 0.020, 0.1, 0.04]) self.button_zoomin = Button(self.zoominax, '+Zoom', color=axcolor, hovercolor='0.975') self.zoomoutax = plt.axes([0.7, 0.020, 0.1, 0.04]) self.button_zoomout = Button(self.zoomoutax, '-Zoom', color=axcolor, hovercolor='0.975') self.toaax = plt.axes([0.8, 0.020, 0.1, 0.04]) self.button_toa = Button(self.toaax, 'TOA', color=axcolor, hovercolor='0.975') self.button_reset.on_clicked(self.reset) self.button_zoomin.on_clicked(self.zoom_in) self.button_zoomout.on_clicked(self.zoom_out) self.button_toa.on_clicked(self.toa) self.button_recalc.on_clicked(self.recalculate) self.button_close.on_clicked(self.quit) #self.profax = plt.axes([0.25, 0.75, 0.5, 0.2]) prof = np.sum(np.nan_to_num(self.unnorm_phaseogr), axis=1) nbin = len(prof) phas = np.linspace(0, 2, nbin + 1)[:-1] self.profile_fixed, = \ self.profax.plot(phas, prof, drawstyle='steps-mid', color='grey') self.profile, = \ self.profax.plot(phas, prof, drawstyle='steps-mid', color='k') mean = np.mean(prof) low, high = \ poisson_conf_interval(mean, interval='frequentist-confidence', sigma=2) self.profax.fill_between(phas, low, high, alpha=0.5) z2_label = get_z2_label(phas, prof) self.proftext = self.profax.text(0.1, 0.8, z2_label, transform=self.profax.transAxes) if not test and not plot_only: plt.show() if plot_only: plt.savefig(self.label + '_{:.10f}Hz.png'.format(self.freq))
def main(args=None): """Main function called by the `HENfake` command line script.""" import argparse description = ( 'Create an event file in FITS format from an event list, or simulating' ' it. If input event list is not specified, generates the events ' 'randomly') parser = argparse.ArgumentParser(description=description) parser.add_argument("-e", "--event-list", type=str, default=None, help="File containint event list") parser.add_argument("-l", "--lc", type=str, default=None, help="File containing light curve") parser.add_argument("-c", "--ctrate", type=float, default=None, help="Count rate for simulated events") parser.add_argument("-o", "--outname", type=str, default='events.evt', help="Output file name") parser.add_argument("-i", "--instrument", type=str, default='FPMA', help="Instrument name") parser.add_argument("-m", "--mission", type=str, default='NUSTAR', help="Mission name") parser.add_argument("--tstart", type=float, default=None, help="Start time of the observation (s from MJDREF)") parser.add_argument("--tstop", type=float, default=None, help="End time of the observation (s from MJDREF)") parser.add_argument("--mjdref", type=float, default=55197.00076601852, help="Reference MJD") parser.add_argument("--deadtime", type=float, default=None, nargs='+', help="Dead time magnitude. Can be specified as a " "single number, or two. In this last case, the " "second value is used as sigma of the dead time " "distribution") parser.add_argument("--loglevel", help=("use given logging level (one between INFO, " "WARNING, ERROR, CRITICAL, DEBUG; " "default:WARNING)"), default='WARNING', type=str) parser.add_argument("--debug", help="use DEBUG logging level", default=False, action='store_true') args = parser.parse_args(args) if args.debug: args.loglevel = 'DEBUG' numeric_level = getattr(logging, args.loglevel.upper(), None) logging.basicConfig(filename='HENfake.log', level=numeric_level, filemode='w') additional_columns = {} livetime = None if args.lc is None and args.ctrate is None and args.event_list is not None: event_list = _read_event_list(args.event_list) elif args.lc is not None or args.ctrate is not None: event_list = EventList() if args.lc is not None: lc = _read_light_curve(args.lc) elif args.ctrate is not None: tstart = assign_value_if_none(args.tstart, 0) tstop = assign_value_if_none(args.tstop, 1025) t = np.arange(tstart, tstop) lc = Lightcurve(time=t, counts=args.ctrate + np.zeros_like(t)) event_list.simulate_times(lc) nevents = len(event_list.time) event_list.pi = np.zeros(nevents, dtype=int) print('{} events generated'.format(nevents)) else: event_list = None if args.deadtime is not None and event_list is not None: deadtime = args.deadtime[0] deadtime_sigma = None if len(args.deadtime) > 1: deadtime_sigma = args.deadtime[1] print(deadtime, deadtime_sigma) event_list, info = filter_for_deadtime(event_list, deadtime, dt_sigma=deadtime_sigma, return_all=True) print('{} events after filter'.format(len(event_list.time))) prior = np.zeros_like(event_list.time) prior[1:] = np.diff(event_list.time) - info.deadtime[:-1] additional_columns["PRIOR"] = {"data": prior, "format": "D"} additional_columns["KIND"] = {"data": info.is_event, "format": "L"} livetime = np.sum(prior) generate_fake_fits_observation(event_list=event_list, filename=args.outname, instr=args.instrument, mission=args.mission, tstart=args.tstart, tstop=args.tstop, mjdref=args.mjdref, livetime=livetime, additional_columns=additional_columns)
def generate_fake_fits_observation(event_list=None, filename=None, instr='FPMA', gti=None, tstart=None, tstop=None, mission='NUSTAR', mjdref=55197.00076601852, livetime=None, additional_columns={}): """Generate fake NuSTAR data. Takes an event list (as a list of floats) All inputs are None by default, and can be set during the call. Parameters ---------- event_list : list-like :class:`stingray.events.Eventlist` object. If left None, 1000 random events will be generated, for a total length of 1025 s or the difference between tstop and tstart. filename : str Output file name Returns ------- hdulist : FITS hdu list FITS hdu list of the output file Other Parameters ---------------- mjdref : float Reference MJD. Default is 55197.00076601852 (NuSTAR) pi : list-like The PI channel of each event tstart : float Start of the observation (s from mjdref) tstop : float End of the observation (s from mjdref) instr : str Name of the instrument. Default is 'FPMA' livetime : float Total livetime. Default is tstop - tstart """ from astropy.io import fits import numpy.random as ra if event_list is None: tstart = assign_value_if_none(tstart, 8e+7) tstop = assign_value_if_none(tstop, tstart + 1025) ev_list = sorted(ra.uniform(tstart, tstop, 1000)) else: ev_list = event_list.time if hasattr(event_list, 'pi'): pi = event_list.pi else: pi = ra.randint(0, 1024, len(ev_list)) tstart = assign_value_if_none(tstart, np.floor(ev_list[0])) tstop = assign_value_if_none(tstop, np.ceil(ev_list[-1])) gti = assign_value_if_none(gti, np.array([[tstart, tstop]])) filename = assign_value_if_none(filename, 'events.evt') livetime = assign_value_if_none(livetime, tstop - tstart) if livetime > tstop - tstart: raise ValueError('Livetime must be equal or smaller than ' 'tstop - tstart') # Create primary header prihdr = fits.Header() prihdr['OBSERVER'] = 'Edwige Bubble' prihdr['TELESCOP'] = (mission, 'Telescope (mission) name') prihdr['INSTRUME'] = (instr, 'Instrument name') prihdu = fits.PrimaryHDU(header=prihdr) # Write events to table col1 = fits.Column(name='TIME', format='1D', array=ev_list) col2 = fits.Column(name='PI', format='1J', array=pi) allcols = [col1, col2] if mission.lower().strip() == 'xmm': ccdnr = np.zeros(len(ev_list)) + 1 ccdnr[1] = 2 # Make it less trivial ccdnr[10] = 7 allcols.append(fits.Column(name='CCDNR', format='1J', array=ccdnr)) for c in additional_columns.keys(): col = fits.Column(name=c, array=additional_columns[c]["data"], format=additional_columns[c]["format"]) allcols.append(col) cols = fits.ColDefs(allcols) tbhdu = fits.BinTableHDU.from_columns(cols) tbhdu.name = 'EVENTS' # ---- Fake lots of information ---- tbheader = tbhdu.header tbheader['OBSERVER'] = 'Edwige Bubble' tbheader['COMMENT'] = ("FITS (Flexible Image Transport System) format is" " defined in 'Astronomy and Astrophysics', volume" " 376, page 359; bibcode: 2001A&A...376..359H") tbheader['TELESCOP'] = (mission, 'Telescope (mission) name') tbheader['INSTRUME'] = (instr, 'Instrument name') tbheader['OBS_ID'] = ('00000000001', 'Observation ID') tbheader['TARG_ID'] = (0, 'Target ID') tbheader['OBJECT'] = ('Fake X-1', 'Name of observed object') tbheader['RA_OBJ'] = (0.0, '[deg] R.A. Object') tbheader['DEC_OBJ'] = (0.0, '[deg] Dec Object') tbheader['RA_NOM'] = (0.0, 'Right Ascension used for barycenter corrections') tbheader['DEC_NOM'] = (0.0, 'Declination used for barycenter corrections') tbheader['RA_PNT'] = (0.0, '[deg] RA pointing') tbheader['DEC_PNT'] = (0.0, '[deg] Dec pointing') tbheader['PA_PNT'] = (0.0, '[deg] Position angle (roll)') tbheader['EQUINOX'] = (2.000E+03, 'Equinox of celestial coord system') tbheader['RADECSYS'] = ('FK5', 'Coordinate Reference System') tbheader['TASSIGN'] = ('SATELLITE', 'Time assigned by onboard clock') tbheader['TIMESYS'] = ('TDB', 'All times in this file are TDB') tbheader['MJDREFI'] = (int(mjdref), 'TDB time reference; Modified Julian Day (int)') tbheader['MJDREFF'] = (mjdref - int(mjdref), 'TDB time reference; Modified Julian Day (frac)') tbheader['TIMEREF'] = ('SOLARSYSTEM', 'Times are pathlength-corrected to barycenter') tbheader['CLOCKAPP'] = (False, 'TRUE if timestamps corrected by gnd sware') tbheader['COMMENT'] = ("MJDREFI+MJDREFF = epoch of Jan 1, 2010, in TT " "time system.") tbheader['TIMEUNIT'] = ('s', 'unit for time keywords') tbheader['TSTART'] = (tstart, 'Elapsed seconds since MJDREF at start of file') tbheader['TSTOP'] = (tstop, 'Elapsed seconds since MJDREF at end of file') tbheader['LIVETIME'] = (livetime, 'On-source time') tbheader['TIMEZERO'] = (0.000000E+00, 'Time Zero') tbheader['COMMENT'] = ("Generated with HENDRICS by {0}".format( os.getenv('USER'))) # ---- END Fake lots of information ---- # Fake GTIs start = gti[:, 0] stop = gti[:, 1] col1 = fits.Column(name='START', format='1D', array=start) col2 = fits.Column(name='STOP', format='1D', array=stop) allcols = [col1, col2] cols = fits.ColDefs(allcols) gtinames = ['GTI'] if mission.lower().strip() == 'xmm': gtinames = ['STDGTI01', 'STDGTI02', 'STDGTI07'] all_new_hdus = [prihdu, tbhdu] for name in gtinames: gtihdu = fits.BinTableHDU.from_columns(cols) gtihdu.name = name all_new_hdus.append(gtihdu) thdulist = fits.HDUList(all_new_hdus) thdulist.writeto(filename, clobber=True) return thdulist
def __init__(self, events, freq_interval, energy_spec, ref_band=None, bin_time=1, use_pi=False, segment_size=None, events2=None): """Base variability-energy spectrum. This class is only a base for the various variability spectra, and it's not to be instantiated by itself. Parameters ---------- events : stingray.events.EventList object event list freq_interval : [f0, f1], floats the frequency range over which calculating the variability quantity energy_spec : list or tuple (emin, emax, N, type) if a list is specified, this is interpreted as a list of bin edges; if a tuple is provided, this will encode the minimum and maximum energies, the number of intervals, and "lin" or "log". Other Parameters ---------------- ref_band : [emin, emax], floats; default None minimum and maximum energy of the reference band. If None, the full band is used. use_pi : boolean, default False Use channel instead of energy events2 : stingray.events.EventList object event list for the second channel, if not the same. Useful if the reference band has to be taken from another detector. Attributes ---------- events1 : array-like list of events used to produce the spectrum events2 : array-like if the spectrum requires it, second list of events freq_interval : array-like interval of frequencies used to calculate the spectrum energy_intervals : [[e00, e01], [e10, e11], ...] energy intervals used for the spectrum spectrum : array-like the spectral values, corresponding to each energy interval spectrum_error : array-like the errorbars corresponding to spectrum """ self.events1 = events self.events2 = assign_value_if_none(events2, events) self.freq_interval = freq_interval self.use_pi = use_pi self.bin_time = bin_time if isinstance(energy_spec, tuple): energies = _decode_energy_specification(energy_spec) else: energies = np.asarray(energy_spec) self.energy_intervals = list(zip(energies[0: -1], energies[1:])) self.ref_band = np.asarray(assign_value_if_none(ref_band, [0, np.inf])) if len(self.ref_band.shape) <= 1: self.ref_band = np.asarray([self.ref_band]) self.segment_size = segment_size self.spectrum, self.spectrum_error = self._spectrum_function()
def __init__(self, ev_times, freq, nph=128, nt=128, test=False, pepoch=None, fdot=0, fddot=0, **kwargs): self.fdot = fdot self.fddot = fddot self.nt = nt self.nph = nph self.pepoch = assign_value_if_none(pepoch, ev_times[0]) self.ev_times = ev_times self.freq = freq self.fig, ax = plt.subplots() plt.subplots_adjust(left=0.25, bottom=0.30) corrected_times = self.ev_times - self._delay_fun(self.ev_times) self.phaseogr, phases, times, additional_info = \ phaseogram(corrected_times, freq, return_plot=True, nph=nph, nt=nt, fdot=fdot, fddot=fddot, plot=False, pepoch=pepoch) self.phases, self.times = phases, times self.pcolor = plt.pcolormesh(phases, times, self.phaseogr.T, cmap='magma') plt.xlabel('Phase') plt.ylabel('Time') plt.colorbar() self.lines = [] self.line_phases = np.arange(-2, 3, 0.5) for ph0 in self.line_phases: newline, = plt.plot(np.zeros_like(times) + ph0, times, zorder=10, lw=2, color='w') self.lines.append(newline) plt.xlim([0, 2]) axcolor = '#ff8888' self.slider_axes = [] self.slider_axes.append( plt.axes([0.25, 0.1, 0.5, 0.03], facecolor=axcolor)) self.slider_axes.append( plt.axes([0.25, 0.15, 0.5, 0.03], facecolor=axcolor)) self.slider_axes.append( plt.axes([0.25, 0.2, 0.5, 0.03], facecolor=axcolor)) self._construct_widgets(**kwargs) self.closeax = plt.axes([0.15, 0.020, 0.15, 0.04]) self.button_close = Button(self.closeax, 'Quit', color=axcolor, hovercolor='0.8') self.recalcax = plt.axes([0.3, 0.020, 0.15, 0.04]) self.button_recalc = Button(self.recalcax, 'Recalculate', color=axcolor, hovercolor='0.975') self.resetax = plt.axes([0.45, 0.020, 0.15, 0.04]) self.button_reset = Button(self.resetax, 'Reset', color=axcolor, hovercolor='0.975') self.zoominax = plt.axes([0.6, 0.020, 0.15, 0.04]) self.button_zoomin = Button(self.zoominax, 'Zoom in', color=axcolor, hovercolor='0.975') self.zoomoutax = plt.axes([0.75, 0.020, 0.15, 0.04]) self.button_zoomout = Button(self.zoomoutax, 'Zoom out', color=axcolor, hovercolor='0.975') self.button_reset.on_clicked(self.reset) self.button_zoomin.on_clicked(self.zoom_in) self.button_zoomout.on_clicked(self.zoom_out) self.button_recalc.on_clicked(self.recalculate) self.button_close.on_clicked(self.quit) if not test: plt.show()
def get_TOAs_from_events(events, folding_length, *frequency_derivatives, **kwargs): """Get TOAs of pulsation. Parameters ---------- events : array-like event arrival times folding_length : float length of sub-intervals to fold *frequency_derivatives : floats pulse frequency, first derivative, second derivative, etc. Other parameters ---------------- pepoch : float, default None Epoch of timing solution, in the same units as ev_times. If none, the first event time is used. mjdref : float, default None Reference MJD template : array-like, default None The pulse template nbin : int, default 16 The number of bins in the profile (overridden by the dimension of the template) timfile : str, default 'out.tim' file to save the TOAs to (if PINT is installed) gti: [[g0_0, g0_1], [g1_0, g1_1], ...] Good time intervals. Defaults to None quick: bool If True, use a quicker fitting algorithms for TOAs. Defaults to False position: `astropy.SkyCoord` object Position of the object Returns ------- toas : array-like list of times of arrival. If ``mjdref`` is specified, they are expressed as MJDs, otherwise in MET toa_err : array-like errorbars on TOAs, in the same units as TOAs. """ import matplotlib.pyplot as plt template = kwargs['template'] if 'template' in kwargs else None mjdref = kwargs['mjdref'] if 'mjdref' in kwargs else None nbin = kwargs['nbin'] if 'nbin' in kwargs else 16 pepoch = kwargs['pepoch'] if 'pepoch' in kwargs else None timfile = kwargs['timfile'] if 'timfile' in kwargs else 'out.tim' gti = kwargs['gti'] if 'gti' in kwargs else None label = kwargs['label'] if 'label' in kwargs else None quick = kwargs['quick'] if 'quick' in kwargs else False position = kwargs['position'] if 'position' in kwargs else None pepoch = assign_value_if_none(pepoch, events[0]) gti = assign_value_if_none(gti, [[events[0], events[-1]]]) # run exposure correction only if there are less than 1000 pulsations # in the interval length = gti.max() - gti.min() expocorr = folding_length < (1000 / frequency_derivatives[0]) if template is not None: nbin = len(template) additional_phase = np.argmax(template) / nbin else: phase, profile, profile_err = \ fold_events(copy.deepcopy(events), *frequency_derivatives, ref_time=pepoch, gtis=copy.deepcopy(gti), expocorr=expocorr, nbin=nbin) fit_pars_save, _, _ = \ fit_profile_with_sinusoids(profile, profile_err, nperiods=1, baseline=True) template = std_fold_fit_func(fit_pars_save, phase) fig = plt.figure() plt.plot(phase, profile, drawstyle='steps-mid') plt.plot(phase, template, drawstyle='steps-mid') plt.savefig(timfile.replace('.tim', '') + '.png') plt.close(fig) # start template from highest bin! # template = np.roll(template, -np.argmax(template)) template *= folding_length / length template_fine = std_fold_fit_func(fit_pars_save, np.arange(0, 1, 0.001)) additional_phase = np.argmax(template_fine) / len(template_fine) starts = np.arange(gti[0, 0], gti[-1, 1], folding_length) toas = [] toa_errs = [] for start in show_progress(starts): stop = start + folding_length good = (events >= start) & (events < stop) events_tofold = events[good] if len(events_tofold) < nbin: continue gtis_tofold = \ copy.deepcopy(gti[(gti[:, 0] < stop) & (gti[:, 1] > start)]) gtis_tofold[0, 0] = start gtis_tofold[-1, 1] = stop local_f = frequency_derivatives[0] for i_f, f in enumerate(frequency_derivatives[1:]): local_f += 1 / np.math.factorial(i_f + 1) * (start - pepoch)**(i_f + 1) * f fder = copy.deepcopy(list(frequency_derivatives)) fder[0] = local_f phase, profile, profile_err = \ fold_events(events_tofold, *fder, ref_time=start, gtis=gtis_tofold, expocorr=expocorr, nbin=nbin) # BAD!BAD!BAD! # [[Pay attention to time reference here. # We are folding wrt pepoch, and calculating TOAs wrt start]] toa, toaerr = \ get_TOA(profile, 1/frequency_derivatives[0], start, template=template, additional_phase=additional_phase, quick=quick, debug=True) toas.append(toa) toa_errs.append(toaerr) toas, toa_errs = np.array(toas), np.array(toa_errs) if mjdref is not None: toas = toas / 86400 + mjdref toa_errs = toa_errs * 1e6 if HAS_PINT: label = assign_value_if_none(label, 'hendrics') toa_list = _load_and_prepare_TOAs(toas, errs_us=toa_errs) # workaround until PR #368 is accepted in pint toa_list.table['clkcorr'] = 0 toa_list.write_TOA_file(timfile, name=label, format='Tempo2') print('TOA(MJD) TOAerr(us)') else: print('TOA(MET) TOAerr(us)') for t, e in zip(toas, toa_errs): print(t, e) return toas, toa_errs
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 __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 treat_event_file(filename, noclobber=False, gti_split=False, min_length=4, gtistring=None, length_split=None): """Read data from an event file, with no external GTI information. Parameters ---------- filename : str Other Parameters ---------------- noclobber: bool if a file is present, do not overwrite it gtistring: str comma-separated set of GTI strings to consider gti_split: bool split the file in multiple chunks, containing one GTI each length_split: float, default None split the file in multiple chunks, with approximately this length min_length: float minimum length of GTIs accepted (only if gti_split is True or length_split is not None) """ gtistring = assign_value_if_none(gtistring, 'GTI,STDGTI') logging.info('Opening %s' % filename) instr = read_header_key(filename, 'INSTRUME') mission = read_header_key(filename, 'TELESCOP') data = load_events_and_gtis(filename, gtistring=gtistring) events = data.ev_list gtis = events.gti detector_id = data.detector_id if detector_id is not None: detectors = np.array(list(set(detector_id))) else: detectors = [None] outfile_root = \ hen_root(filename) + '_' + mission.lower() + '_' + instr.lower() for d in detectors: if d is not None: good_det = d == data.detector_id outroot_local = \ '{0}_det{1:02d}'.format(outfile_root, d) else: good_det = np.ones_like(events.time, dtype=bool) outroot_local = outfile_root outfile = outroot_local + '_ev' + HEN_FILE_EXTENSION if noclobber and os.path.exists(outfile) and (not (gti_split or length_split)): warnings.warn( '{0} exists and using noclobber. Skipping'.format(outfile)) return if gti_split or (length_split is not None): lengths = np.array([g1 - g0 for (g0, g1) in gtis]) gtis = gtis[lengths >= min_length] if length_split: gti0 = np.arange(gtis[0, 0], gtis[-1, 1], length_split) gti1 = gti0 + length_split gti_chunks = np.array([[g0, g1] for (g0, g1) in zip(gti0, gti1)]) label = 'chunk' else: gti_chunks = gtis label = 'gti' for ig, g in enumerate(gti_chunks): outfile_local = \ '{0}_{1}{2:03d}_ev'.format(outroot_local, label, ig) + HEN_FILE_EXTENSION good_gtis = cross_two_gtis([g], gtis) if noclobber and os.path.exists(outfile_local): warnings.warn('{0} exists, '.format(outfile_local) + 'and noclobber option used. Skipping') return good = np.logical_and(events.time >= g[0], events.time < g[1]) all_good = good_det & good if len(events.time[all_good]) < 1: continue events_filt = EventList(events.time[all_good], pi=events.pi[all_good], gti=good_gtis, mjdref=events.mjdref) events_filt.instr = events.instr events_filt.header = events.header save_events(events_filt, outfile_local) pass else: events_filt = EventList(events.time[good_det], pi=events.pi[good_det], gti=events.gti, mjdref=events.mjdref) events_filt.instr = events.instr events_filt.header = events.header save_events(events_filt, outfile)
def test_assign_value_if_none(self): assert utils.assign_value_if_none(None, 2) == 2 assert utils.assign_value_if_none(1, 2) == 1
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)
def join_lightcurves(lcfilelist, outfile='out_lc' + HEN_FILE_EXTENSION): """Join light curves from different files. Light curves from different instruments are put in different channels. Parameters ---------- lcfilelist : outfile : See Also -------- scrunch_lightcurves : Create a single light curve from input light curves. """ lcdatas = [] for lfc in lcfilelist: logging.info("Loading file %s..." % lfc) lcdata = load_lcurve(lfc) logging.info("Done.") lcdatas.append(lcdata) del lcdata # --------------- Check consistency of data -------------- lcdts = [lcdata.dt for lcdata in lcdatas] # Find unique elements. If multiple bin times are used, throw an exception lcdts = list(set(lcdts)) assert len(lcdts) == 1, 'Light curves must have same dt for scrunching' instrs = [lcdata.instr for lcdata in lcdatas if hasattr(lcdata, 'instr')] # Find unique elements. A lightcurve will be produced for each instrument instrs = list(set(instrs)) if instrs == []: instrs = ['unknown'] outlcs = {} for instr in instrs: outlcs[instr] = None # ------------------------------------------------------- for lcdata in lcdatas: instr = assign_value_if_none(lcdata.instr, 'unknown') if outlcs[instr] is None: outlcs[instr] = lcdata else: outlcs[instr] = outlcs[instr].join(lcdata) if outfile is not None: for instr in instrs: if len(instrs) == 1: tag = "" else: tag = instr logging.info('Saving joined light curve to %s' % outfile) dname, fname = os.path.split(outfile) save_lcurve(outlcs[instr], os.path.join(dname, tag + fname)) return outlcs
def save_as_qdp(arrays, errors=None, filename="out.qdp", mode='w'): """Save arrays in a QDP file. Saves an array of variables, and possibly their errors, to a QDP file. Parameters ---------- arrays: [array1, array2] List of variables. All variables must be arrays and of the same length. errors: [array1, array2] List of errors. The order has to be the same of arrays; the value can be: - None if no error is assigned - an array of same length of variable for symmetric errors - an array of len-2 lists for non-symmetric errors (e.g. [[errm1, errp1], [errm2, errp2], [errm3, errp3], ...]) Other parameters ---------------- mode : str the file access mode, to be passed to the open() function. Can be 'w' or 'a' """ import numpy as np errors = assign_value_if_none(errors, [None for i in arrays]) data_to_write = [] list_of_errs = [] for ia, ar in enumerate(arrays): data_to_write.append(ar) if errors[ia] is None: continue shape = np.shape(errors[ia]) assert shape[0] == len(ar), \ 'Errors and arrays must have same length' if len(shape) == 1: list_of_errs.append([ia, 'S']) data_to_write.append(errors[ia]) elif shape[1] == 2: list_of_errs.append([ia, 'T']) mine = [k[0] for k in errors[ia]] maxe = [k[1] for k in errors[ia]] data_to_write.append(mine) data_to_write.append(maxe) print_header = True if os.path.exists(filename) and mode == 'a': print_header = False outfile = open(filename, mode) if print_header: for l in list_of_errs: i, kind = l print('READ %s' % kind + 'ERR %d' % (i + 1), file=outfile) length = len(data_to_write[0]) for i in range(length): for idw, d in enumerate(data_to_write): print(d[i], file=outfile, end=" ") print("", file=outfile) outfile.close()
def __init__(self, events, freq_interval, energy_spec, ref_band=None, bin_time=1, use_pi=False, segment_size=None, events2=None): """Base variability-energy spectrum. This class is only a base for the various variability spectra, and it's not to be instantiated by itself. Parameters ---------- events : stingray.events.EventList object event list freq_interval : [f0, f1], floats the frequency range over which calculating the variability quantity energy_spec : list or tuple (emin, emax, N, type) if a list is specified, this is interpreted as a list of bin edges; if a tuple is provided, this will encode the minimum and maximum energies, the number of intervals, and "lin" or "log". Other Parameters ---------------- ref_band : [emin, emax], floats; default None minimum and maximum energy of the reference band. If None, the full band is used. use_pi : boolean, default False Use channel instead of energy events2 : stingray.events.EventList object event list for the second channel, if not the same. Useful if the reference band has to be taken from another detector. Attributes ---------- events1 : array-like list of events used to produce the spectrum events2 : array-like if the spectrum requires it, second list of events freq_interval : array-like interval of frequencies used to calculate the spectrum energy_intervals : [[e00, e01], [e10, e11], ...] energy intervals used for the spectrum spectrum : array-like the spectral values, corresponding to each energy interval spectrum_error : array-like the errorbars corresponding to spectrum """ self.events1 = events self.events2 = assign_value_if_none(events2, events) self.freq_interval = freq_interval self.use_pi = use_pi self.bin_time = bin_time if isinstance(energy_spec, tuple): energies = _decode_energy_specification(energy_spec) else: energies = np.asarray(energy_spec) self.energy_intervals = list(zip(energies[0:-1], energies[1:])) self.ref_band = np.asarray(assign_value_if_none(ref_band, [0, np.inf])) if len(self.ref_band.shape) <= 1: self.ref_band = np.asarray([self.ref_band]) self.segment_size = segment_size self.spectrum, self.spectrum_error = self._spectrum_function()
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.")