def test_dispersion_spectrum_addition(loaded_response): rsp = loaded_response ebounds = ChannelSet.from_instrument_response(rsp) obs_spectrum_1 = BinnedSpectrumWithDispersion( counts=np.ones(len(ebounds)), count_errors=np.ones(len(ebounds)), exposure=1, response=rsp, is_poisson=False, ) obs_spectrum_2 = BinnedSpectrumWithDispersion( counts=np.ones(len(ebounds)), count_errors=np.ones(len(ebounds)), exposure=2, response=rsp, is_poisson=False, ) obs_spectrum_incompatible = None spectrum_addition( obs_spectrum_1, obs_spectrum_2, obs_spectrum_incompatible, lambda x, y: x + y, addition_proof_simple, ) spectrum_addition( obs_spectrum_1, obs_spectrum_2, obs_spectrum_incompatible, lambda x, y: x.add_inverse_variance_weighted(y), addition_proof_weighted, )
def test_dispersion_spectrum_constructor(loaded_response): rsp = loaded_response pl = Powerlaw() ps = PointSource("fake", 0, 0, spectral_shape=pl) model = Model(ps) obs_spectrum = BinnedSpectrumWithDispersion(counts=np.ones(128), exposure=1, response=rsp, is_poisson=True) bkg_spectrum = BinnedSpectrumWithDispersion(counts=np.ones(128), exposure=1, response=rsp, is_poisson=True) specLike = DispersionSpectrumLike("fake", observation=obs_spectrum, background=bkg_spectrum) specLike.set_model(model) specLike.get_model() specLike.write_pha("test_from_dispersion", overwrite=True) assert os.path.exists("test_from_dispersion.pha") assert os.path.exists("test_from_dispersion_bak.pha") os.remove("test_from_dispersion.pha") os.remove("test_from_dispersion_bak.pha")
def _build_fake_observation(fake_data, channel_set, source_errors, source_sys_errors, is_poisson, **kwargs): """ This is the fake observation builder for SpectrumLike which builds data for a binned spectrum without dispersion. It must be overridden in child classes. :param fake_data: series of values... they are ignored later :param channel_set: a channel set :param source_errors: :param source_sys_errors: :param is_poisson: :return: """ assert 'response' in kwargs, 'A response was not provided. Cannor build synthetic observation' response = kwargs.pop('response') observation = BinnedSpectrumWithDispersion( fake_data, exposure=1., response=response, count_errors=source_errors, sys_errors=source_sys_errors, quality=None, scale_factor=1., is_poisson=is_poisson, mission='fake_mission', instrument='fake_instrument', tstart=0., tstop=1.) return observation
def test_dispersion_spectrum_addition_poisson(loaded_response): rsp = loaded_response ebounds = ChannelSet.from_instrument_response(rsp) obs_spectrum_1 = BinnedSpectrumWithDispersion(counts=np.ones(len(ebounds)), exposure=1, response=rsp, is_poisson=True) obs_spectrum_2 = BinnedSpectrumWithDispersion(counts=np.ones(len(ebounds)), exposure=2, response=rsp, is_poisson=True) obs_spectrum_incompatible = None spectrum_addition(obs_spectrum_1, obs_spectrum_2, obs_spectrum_incompatible, lambda x, y: x + y, addition_proof_simple)
def test_dispersion_spectrum_clone(loaded_response): rsp = loaded_response obs_spectrum = BinnedSpectrumWithDispersion(counts=np.ones(128), exposure=1, response=rsp, is_poisson=True) obs_spectrum.clone(new_counts=np.zeros_like(obs_spectrum.counts), new_count_errors=None) obs_spectrum.clone()
def _build_fake_observation( fake_data, channel_set, source_errors, source_sys_errors, is_poisson, exposure, scale_factor, **kwargs ): """ This is the fake observation builder for SpectrumLike which builds data for a binned spectrum without dispersion. It must be overridden in child classes. :param fake_data: series of values... they are ignored later :param channel_set: a channel set :param source_errors: :param source_sys_errors: :param is_poisson: :return: """ if not ( "response" in kwargs): log.error("A response was not provided. Cannot build synthetic observation") raise RuntimeError() response = kwargs.pop("response") observation = BinnedSpectrumWithDispersion( fake_data, exposure=exposure, response=response, count_errors=source_errors, sys_errors=source_sys_errors, quality=None, scale_factor=scale_factor, is_poisson=is_poisson, mission="fake_mission", instrument="fake_instrument", tstart=0.0, tstop=exposure, ) return observation
def _create_timeseries(self): """ create all the time series for each detector :return: None """ self._time_series = collections.OrderedDict() for det_num in range(14): # detectors are arranged [time,det,channel] # for now just keep the normal exposure # we will create binned spectra for each time slice drm_gen = DRMGenTrig( self._qauts, self._sc_pos, det_num, # det number tstart=self._tstart, tstop=self._tstop, mat_type=2, time=0, occult=True ) # we will use a single response for each detector tmp_drm = BALROG_DRM(drm_gen, 0, 0) # extract the counts counts = self._rates[:, det_num, :] * self._time_intervals.widths.reshape( (len(self._time_intervals), 1) ) # now create a binned spectrum for each interval binned_spectrum_list = [] for c, start, stop in zip(counts, self._tstart, self._tstop): binned_spectrum_list.append( BinnedSpectrumWithDispersion( counts=c, exposure=stop - start, response=tmp_drm, tstart=start, tstop=stop, ) ) # make a binned spectrum set bss = BinnedSpectrumSet( binned_spectrum_list, reference_time=0.0, time_intervals=self._time_intervals, ) # convert that set to a series bss2 = BinnedSpectrumSeries(bss, first_channel=0) # now we need to get the name of the detector name = lu[det_num] if self._restore_poly_fit is not None: bkg_fit_file = self._restore_poly_fit.get(name, None) else: bkg_fit_file = None # create a time series builder which can produce plugins tsb = TimeSeriesBuilder( name, bss2, response=tmp_drm, verbose=self._verbose, poly_order=self._poly_order, restore_poly_fit=bkg_fit_file, ) # attach that to the full list self._time_series[name] = tsb
def __init__(self, pha_file_or_instance, file_type='observed', rsp_file=None, arf_file=None): """ A spectrum with dispersion build from an OGIP-compliant PHA FITS file. Both Type I & II files can be read. Type II spectra are selected either by specifying the spectrum_number or via the {spectrum_number} file name convention used in XSPEC. If the file_type is background, a 3ML InstrumentResponse or subclass must be passed so that the energy bounds can be obtained. :param pha_file_or_instance: either a PHA file name or threeML.plugins.OGIP.pha.PHAII instance :param spectrum_number: (optional) the spectrum number of the TypeII file to be used :param file_type: observed or background :param rsp_file: RMF filename or threeML.plugins.OGIP.response.InstrumentResponse instance :param arf_file: (optional) and ARF filename """ # extract the spectrum number if needed assert isinstance(pha_file_or_instance, str) or isinstance( pha_file_or_instance, PHAII), 'Must provide a FITS file name or PHAII instance' with fits.open(pha_file_or_instance) as f: try: HDUidx = f.index_of("SPECTRUM") except: raise RuntimeError("The input file %s is not in PHA format" % (pha2_file)) spectrum = f[HDUidx] data = spectrum.data if "COUNTS" in data.columns.names: has_rates = False data_column_name = "COUNTS" elif "RATE" in data.columns.names: has_rates = True data_column_name = "RATE" else: raise RuntimeError( "This file does not contain a RATE nor a COUNTS column. " "This is not a valid PHA file") # Determine if this is a PHA I or PHA II if len(data.field(data_column_name).shape) == 2: num_spectra = data.field(data_column_name).shape[0] else: raise RuntimeError( "This appears to be a PHA I and not PHA II file") pha_information = _read_pha_or_pha2_file(pha_file_or_instance, None, file_type, rsp_file, arf_file, treat_as_time_series=True) # default the grouping to all open bins # this will only be altered if the spectrum is rebinned self._grouping = np.ones_like(pha_information['counts']) # this saves the extra properties to the class self._gathered_keywords = pha_information['gathered_keywords'] self._file_type = file_type # need to see if we have count errors, tstart, tstop # if not, we create an list of None if pha_information['count_errors'] is None: count_errors = [None] * num_spectra else: count_errors = pha_information['count_errors'] if pha_information['tstart'] is None: tstart = [None] * num_spectra else: tstart = pha_information['tstart'] if pha_information['tstop'] is None: tstop = [None] * num_spectra else: tstop = pha_information['tstop'] # now build the list of binned spectra list_of_binned_spectra = [] with progress_bar(num_spectra, title='Loading PHAII spectra') as p: for i in range(num_spectra): list_of_binned_spectra.append( BinnedSpectrumWithDispersion( counts=pha_information['counts'][i], exposure=pha_information['exposure'][i, 0], response=pha_information['rsp'], count_errors=count_errors[i], sys_errors=pha_information['sys_errors'][i], is_poisson=pha_information['is_poisson'], quality=pha_information['quality'].get_slice(i), mission=pha_information['gathered_keywords'] ['mission'], instrument=pha_information['gathered_keywords'] ['instrument'], tstart=tstart[i], tstop=tstop[i])) p.increase() # now get the time intervals start_times = data.field('TIME') stop_times = data.field('ENDTIME') time_intervals = TimeIntervalSet.from_starts_and_stops( start_times, stop_times) reference_time = 0 # see if there is a reference time in the file if 'TRIGTIME' in spectrum.header: reference_time = spectrum.header['TRIGTIME'] for t_number in range(spectrum.header['TFIELDS']): if 'TZERO%d' % t_number in spectrum.header: reference_time = spectrum.header['TZERO%d' % t_number] super(PHASpectrumSet, self).__init__(list_of_binned_spectra, reference_time=reference_time, time_intervals=time_intervals)
def __init__(self, pha_file_or_instance: Union[str, Path, PHAII], file_type: str = "observed", rsp_file: Optional[str] = None, arf_file: Optional[str] = None): """ A spectrum with dispersion build from an OGIP-compliant PHA FITS file. Both Type I & II files can be read. Type II spectra are selected either by specifying the spectrum_number or via the {spectrum_number} file name convention used in XSPEC. If the file_type is background, a 3ML InstrumentResponse or subclass must be passed so that the energy bounds can be obtained. :param pha_file_or_instance: either a PHA file name or threeML.plugins.OGIP.pha.PHAII instance :param spectrum_number: (optional) the spectrum number of the TypeII file to be used :param file_type: observed or background :param rsp_file: RMF filename or threeML.plugins.OGIP.response.InstrumentResponse instance :param arf_file: (optional) and ARF filename """ # extract the spectrum number if needed for t in _valid_input_types: if isinstance(pha_file_or_instance, t): break else: log.error( f"Must provide a FITS file name or PHAII instance. Got {type(pha_file_or_instance)}" ) raise RuntimeError() with fits.open(pha_file_or_instance) as f: try: HDUidx = f.index_of("SPECTRUM") except: raise RuntimeError("The input file %s is not in PHA format" % (pha_file_or_instance)) spectrum = f[HDUidx] data = spectrum.data if "COUNTS" in data.columns.names: has_rates = False data_column_name = "COUNTS" elif "RATE" in data.columns.names: has_rates = True data_column_name = "RATE" else: log.error( "This file does not contain a RATE nor a COUNTS column. " "This is not a valid PHA file") raise RuntimeError() # Determine if this is a PHA I or PHA II if len(data.field(data_column_name).shape) == 2: num_spectra = data.field(data_column_name).shape[0] else: log.error("This appears to be a PHA I and not PHA II file") raise RuntimeError() pha_information = _read_pha_or_pha2_file( pha_file_or_instance, None, file_type, rsp_file, arf_file, treat_as_time_series=True, ) # default the grouping to all open bins # this will only be altered if the spectrum is rebinned self._grouping = np.ones_like(pha_information["counts"]) # this saves the extra properties to the class self._gathered_keywords = pha_information["gathered_keywords"] self._file_type = file_type # need to see if we have count errors, tstart, tstop # if not, we create an list of None if pha_information["count_errors"] is None: count_errors = [None] * num_spectra else: count_errors = pha_information["count_errors"] if pha_information["tstart"] is None: tstart = [None] * num_spectra else: tstart = pha_information["tstart"] if pha_information["tstop"] is None: tstop = [None] * num_spectra else: tstop = pha_information["tstop"] # now build the list of binned spectra list_of_binned_spectra = [] for i in trange(num_spectra, desc="Loading PHAII Spectra"): list_of_binned_spectra.append( BinnedSpectrumWithDispersion( counts=pha_information["counts"][i], exposure=pha_information["exposure"][i, 0], response=pha_information["rsp"], count_errors=count_errors[i], sys_errors=pha_information["sys_errors"][i], is_poisson=pha_information["is_poisson"], quality=pha_information["quality"].get_slice(i), mission=pha_information["gathered_keywords"]["mission"], instrument=pha_information["gathered_keywords"] ["instrument"], tstart=tstart[i], tstop=tstop[i], )) # now get the time intervals _allowed_time_keys = (("TIME", "ENDTIME"), ("TSTART", "TSTOP")) for keys in _allowed_time_keys: try: start_times = data.field(keys[0]) stop_times = data.field(keys[1]) break except (KeyError): pass else: log.error( f"Could not find times in {pha_file_or_instance}. Tried: {_allowed_time_keys}" ) raise RuntimeError() time_intervals = TimeIntervalSet.from_starts_and_stops( start_times, stop_times) reference_time = 0 # see if there is a reference time in the file if "TRIGTIME" in spectrum.header: reference_time = spectrum.header["TRIGTIME"] for t_number in range(spectrum.header["TFIELDS"]): if "TZERO%d" % t_number in spectrum.header: reference_time = spectrum.header["TZERO%d" % t_number] super(PHASpectrumSet, self).__init__( list_of_binned_spectra, reference_time=reference_time, time_intervals=time_intervals, )