예제 #1
0
파일: instrument.py 프로젝트: mirca/sherpa
    def __setattr__(self, name, val):
        arf = None
        try:
            arf = ARF1D.__getattribute__(self, '_arf')
        except:
            pass

        if arf is not None and hasattr(arf, name):
            DataARF.__setattr__(arf, name, val)
        else:
            NoNewAttributesAfterInit.__setattr__(self, name, val)
예제 #2
0
    def __setattr__(self, name, val):
        arf = None
        try:
            arf = ARF1D.__getattribute__(self, '_arf')
        except:
            pass

        if arf is not None and hasattr(arf, name):
            DataARF.__setattr__(arf, name, val)
        else:
            NoNewAttributesAfterInit.__setattr__(self, name, val)
예제 #3
0
def example_pha_data():
    """Create an example data set."""

    etime = 1201.0
    d = DataPHA('example',
                _data_chan.copy(),
                _data_counts.copy(),
                exposure=etime,
                backscal=0.2)

    a = DataARF('example-arf',
                _energies_lo.copy(),
                _energies_hi.copy(),
                _arf.copy(),
                exposure=etime)

    r = create_delta_rmf(_energies_lo.copy(),
                         _energies_hi.copy(),
                         e_min=_energies_lo.copy(),
                         e_max=_energies_hi.copy(),
                         offset=1,
                         name='example-rmf')

    d.set_arf(a)
    d.set_rmf(r)
    return d
예제 #4
0
def test_pha_get_filter_checks_ungrouped(chtype, expected, args):
    """Check we get the filter we expect

    chtype is channel, energy, or wavelength
    expected is the expected response
    args is a list of 3-tuples of (flag, loval, hival) where
    flag is True for notice and False for ignore; they define
    the filter to apply
    """

    chans = np.arange(1, 11, dtype=int)
    counts = np.ones(10, dtype=int)
    pha = DataPHA('data', chans, counts)

    # Use an ARF to create a channel to energy mapping
    # The 0.2-2.2 keV range maps to 5.636-61.992 Angstrom
    #
    egrid = 0.2 * np.arange(1, 12)
    arf = DataARF('arf', egrid[:-1], egrid[1:], np.ones(10))
    pha.set_arf(arf)

    pha.units = chtype
    for (flag, lo, hi) in args:
        if flag:
            pha.notice(lo, hi)
        else:
            pha.ignore(lo, hi)

    assert pha.get_filter(format='%.1f') == expected
예제 #5
0
def create_arf(elo, ehi, specresp=None, exposure=None, ethresh=None):
    """Create an ARF.

    Parameters
    ----------
    elo, ehi : array
        The energy bins (low and high, in keV) for the ARF. It is
        assumed that ehi_i > elo_i, elo_j > 0, the energy bins are
        either ascending - so elo_i+1 > elo_i - or descending
        (elo_i+1 < elo_i), and that there are no overlaps.
    specresp : None or array, optional
        The spectral response (in cm^2) for the ARF. It is assumed
        to be >= 0. If not given a flat response of 1.0 is used.
    exposure : number or None, optional
        If not None, the exposure of the ARF in seconds.
    ethresh : number or None, optional
        Passed through to the DataARF call. It controls whether
        zero-energy bins are replaced.

    Returns
    -------
    arf : DataARF instance

    """

    assert elo.size == ehi.size
    assert (exposure is None) or (exposure > 0.0)

    if specresp is None:
        specresp = np.ones(elo.size, dtype=np.float32)

    return DataARF('test-arf', energ_lo=elo, energ_hi=ehi,
                   specresp=specresp, exposure=exposure, ethresh=ethresh)
예제 #6
0
def derive_identity_arf(name, arf):
    """Create an "identity" ARF that has uniform sensitivity.

    *name*
      The name of the ARF object to be created; passed to Sherpa.
    *arf*
      An existing ARF object on which to base this one.
    Returns:
      A new ARF1D object that has a uniform spectral response vector.

    In many X-ray observations, the relevant background signal does not behave
    like an astrophysical source that is filtered through the telescope's
    response functions. However, I have been unable to get current Sherpa
    (version 4.9) to behave how I want when working with backround models that
    are *not* filtered through these response functions. This function
    constructs an "identity" ARF response function that has uniform sensitivity
    as a function of detector channel.

    """
    from sherpa.astro.data import DataARF
    from sherpa.astro.instrument import ARF1D

    darf = DataARF(
        name,
        arf.energ_lo,
        arf.energ_hi,
        np.ones(arf.specresp.shape),
        arf.bin_lo,
        arf.bin_hi,
        arf.exposure,
        header=None,
    )
    return ARF1D(darf, pha=arf._pha)
예제 #7
0
def read_arf(arg):
    """
    read_arf( filename )

    read_arf( ARFCrate )
    """
    data, filename = backend.get_arf_data(arg)
    return DataARF(filename, **data)
예제 #8
0
    def setUp(self):
        ui.dataspace1d(0.2, 10, 0.01, id=1)
        ui.dataspace1d(2, 5, 0.1, id="tst")
        ui.dataspace1d(0.1, 1, 0.1, id="not-used")
        ui.dataspace1d(0.1, 1, 0.1, id="no-arf")

        ui.dataspace1d(0.1, 11, 0.01, id='arf1', dstype=DataPHA)
        ui.dataspace1d(0.2, 10, 0.01, id='flatarf', dstype=DataPHA)

        # self.nbins = {}
        # for idval in [1, 'tst']:
        #     self.nbins[idval] = ui.get_data(1).xlo.size
        self.nbins = {1: 980, 'tst': 30, 'arf1': 1090,
                      'arf1-arf': 489}

        self.grid = {
            1: (0.2, 10, 0.01),
            'tst': (2.0, 5.0, 0.1),
            'arf1': (0.1, 11, 0.01),
            'arf1-arf': (0.2, 9.98, 0.02)  # note: ehigh is not 10.0
            }

        ui.set_source(1, ui.powlaw1d.pl1)
        ui.set_source("tst", ui.powlaw1d.pltst)
        ui.set_source('no-arf', pl1)
        ui.set_source('arf1', pltst)
        ui.set_source('flatarf', pltst)
        ui.set_source('no-arf-flat', ui.const1d.c1)

        pl1.gamma = 0.0
        pl1.ampl = 1.2
        pltst.gamma = -1.0
        pltst.ampl = 2.1

        arfgrid = np.arange(0.2, 10, 0.02)
        arflo = arfgrid[:-1]
        arfhi = arfgrid[1:]
        amid = (arflo + arfhi) / 2.0

        flatarf = DataARF('flat', energ_lo=arflo, energ_hi=arfhi,
                          specresp=arflo * 0 + 10.1)
        arf = DataARF('arf', energ_lo=arflo, energ_hi=arfhi,
                      specresp=20 - (4.5 - amid)**2)
        ui.set_arf('arf1', arf)
        ui.set_arf('flatarf', flatarf)
예제 #9
0
def test_arf_checks_energy_length():
    """Just check we error out"""

    elo = np.arange(1, 5)
    ehi = np.arange(2, 9)
    dummy = []

    with pytest.raises(ValueError) as ve:
        DataARF("dummy", elo, ehi, dummy)

    assert str(ve.value) == "The energy arrays must have the same size, not 4 and 7"
예제 #10
0
    def startup(self, cache):
        arf = self._arf  # original
        pha = self.pha

        # Create a view of original ARF
        self.arf = DataARF(arf.name, arf.energ_lo, arf.energ_hi, arf.specresp,
                           arf.bin_lo, arf.bin_hi, arf.exposure, arf.header)

        # Filter the view for current fitting session
        if numpy.iterable(pha.mask):
            mask = pha.get_mask()
            if len(mask) == len(self.arf.specresp):
                self.arf.notice(mask)

        self.filter()

        # Assume energy as default spectral coordinates
        self.xlo, self.xhi = self.elo, self.ehi
        if pha.units == 'wavelength':
            self.xlo, self.xhi = self.lo, self.hi

        ARFModel.startup(self, cache)
예제 #11
0
    def __getattr__(self, name):
        arf = None
        try:
            arf = ARF1D.__getattribute__(self, '_arf')
        except:
            pass

        if name in ('_arf', '_pha'):
            return self.__dict__[name]

        if arf is not None:
            return DataARF.__getattribute__(arf, name)

        return ARF1D.__getattribute__(self, name)
예제 #12
0
파일: instrument.py 프로젝트: mirca/sherpa
    def __getattr__(self, name):
        arf = None
        try:
            arf = ARF1D.__getattribute__(self, '_arf')
        except:
            pass

        if name in ('_arf', '_pha'):
            return self.__dict__[name]

        if arf is not None:
            return DataARF.__getattribute__(arf, name)

        return ARF1D.__getattribute__(self, name)
예제 #13
0
def create_arf(elo,
               ehi,
               specresp=None,
               exposure=None,
               ethresh=None,
               name='user-arf',
               header=None):
    """Create an ARF.

    .. versionadded:: 4.10.1

    Parameters
    ----------
    elo, ehi : numpy.ndarray
        The energy bins (low and high, in keV) for the ARF. It is
        assumed that ehi_i > elo_i, elo_j > 0, the energy bins are
        either ascending - so elo_i+1 > elo_i - or descending
        (elo_i+1 < elo_i), and that there are no overlaps.
    specresp : None or array, optional
        The spectral response (in cm^2) for the ARF. It is assumed
        to be >= 0. If not given a flat response of 1.0 is used.
    exposure : number or None, optional
        If not None, the exposure of the ARF in seconds.
    ethresh : number or None, optional
        Passed through to the DataARF call. It controls whether
        zero-energy bins are replaced.
    name : str, optional
        The name of the ARF data set
    header : dict
        Header for the created ARF

    Returns
    -------
    arf : sherpa.astro.data.DataARF instance

    See Also
    --------
    create_delta_rmf, create_non_delta_rmf
    """

    if specresp is None:
        specresp = numpy.ones(elo.size, dtype=numpy.float32)

    return DataARF(name,
                   energ_lo=elo,
                   energ_hi=ehi,
                   specresp=specresp,
                   exposure=exposure,
                   ethresh=ethresh,
                   header=header)
예제 #14
0
    def startup(self, cache):
        arf = self._arf
        rmf = self._rmf

        # Create a view of original RMF
        self.rmf = DataRMF(rmf.name, rmf.detchans, rmf.energ_lo, rmf.energ_hi,
                           rmf.n_grp, rmf.f_chan, rmf.n_chan, rmf.matrix,
                           rmf.offset, rmf.e_min, rmf.e_max, rmf.header)

        # Create a view of original ARF
        self.arf = DataARF(arf.name, arf.energ_lo, arf.energ_hi, arf.specresp,
                           arf.bin_lo, arf.bin_hi, arf.exposure, arf.header)

        # Filter the view for current fitting session
        _notice_resp(self.pha.get_noticed_channels(), self.arf, self.rmf)

        self.filter()

        # Assume energy as default spectral coordinates
        self.xlo, self.xhi = self.elo, self.ehi
        if self.pha.units == 'wavelength':
            self.xlo, self.xhi = self.lo, self.hi

        RSPModel.startup(self, cache)
예제 #15
0
    def to_sherpa(self, name):
        """Convert to `~sherpa.astro.data.DataARF`

        Parameters
        ----------
        name : str
            Instance name
        """
        from sherpa.astro.data import DataARF
        table = self.to_table()
        return DataARF(
            name=name,
            energ_lo=table['ENERG_LO'].quantity.to('keV').value,
            energ_hi=table['ENERG_HI'].quantity.to('keV').value,
            specresp=table['SPECRESP'].quantity.to('cm2').value,
        )
예제 #16
0
def read_arf(arg):
    """Create a DataARF object.

    Parameters
    ----------
    arg
        The name of the file or a representation of the file
        (the type depends on the I/O backend) containing the
        ARF data.

    Returns
    -------
    data : sherpa.astro.data.DataARF

    """
    data, filename = backend.get_arf_data(arg)
    return DataARF(filename, **data)
예제 #17
0
    def to_sherpa(self, name):
        """Convert to `~sherpa.astro.data.DataARF`

        Parameters
        ----------
        name : str
            Instance name
        """
        from sherpa.astro.data import DataARF

        table = self.to_table()
        return DataARF(
            name=name,
            energ_lo=table["ENERG_LO"].quantity.to_value("keV"),
            energ_hi=table["ENERG_HI"].quantity.to_value("keV"),
            specresp=table["SPECRESP"].quantity.to_value("cm2"),
        )
예제 #18
0
def make_arf(energ_lo, energ_hi, specresp=None, exposure=1.0, name='arf'):
    """A simple in-memory representation of an ARF.

    Parameters
    ----------
    energ_lo, energ_hi : array
        The energy grid over which the ARF is defined. The units are
        keV and each bin has energ_hi > energ_lo. The arrays are
        assumed to be ordered, but it is not clear yet whether they
        have to be in ascending order.
    specresp : array or None, optional
        The spectral response (effective area) for each bin, in cm^2.
        If not given then a value of 1.0 per bin is used.
    exposure : number, optional
        The exposure time, in seconds. It must be positive.
    name : str, optional
        The name to give to the ARF instance.

    Returns
    -------
    arf : sherpa.astro.data.DataARF
        The ARF.
    """

    elo = np.asarray(energ_lo)
    ehi = np.asarray(energ_hi)
    if elo.size != ehi.size:
        raise DataErr('mismatch', 'energ_lo', 'energ_hi')

    if specresp is None:
        specresp = np.ones(elo.size, dtype=np.float32)
    else:
        specresp = np.asarray(specresp)
        if specresp.size != elo.size:
            raise DataErr('mismatch', 'energy grid', 'effarea')

    if exposure <= 0.0:
        raise ArgumentErr('bad', 'exposure', 'value must be positive')

    return DataARF(name=name,
                   energ_lo=elo,
                   energ_hi=ehi,
                   specresp=specresp,
                   exposure=exposure)
예제 #19
0
    def startup(self):
        arf = self._arf # original
        pha = self.pha

        # Create a view of original ARF
        self.arf = DataARF(arf.name, arf.energ_lo, arf.energ_hi, arf.specresp,
                           arf.bin_lo, arf.bin_hi, arf.exposure, arf.header)

        # Filter the view for current fitting session
        if numpy.iterable(pha.mask):
            self.arf.notice(pha.get_mask())

        self.filter()

        # Assume energy as default spectral coordinates
        self.xlo, self.xhi = self.elo, self.ehi
        if pha.units == 'wavelength':
            self.xlo, self.xhi = self.lo, self.hi

        ARFModel.startup(self)
예제 #20
0
파일: __init__.py 프로젝트: jjlk/sherpa
def read_arf(arg):
    """Create a DataARF object.

    Parameters
    ----------
    arg
        The name of the file or a representation of the file
        (the type depends on the I/O backend) containing the
        ARF data.

    Returns
    -------
    data : sherpa.astro.data.DataARF

    """
    data, filename = backend.get_arf_data(arg)

    # It is unlikely that the backend will set this, but allow
    # it to override the config setting.
    #
    if 'emin' not in data:
        data['ethresh'] = ogip_emin

    return DataARF(filename, **data)
예제 #21
0
파일: instrument.py 프로젝트: mirca/sherpa
    def startup(self):
        arf = self._arf
        rmf = self._rmf

        # Create a view of original RMF
        self.rmf = DataRMF(rmf.name, rmf.detchans, rmf.energ_lo, rmf.energ_hi,
                           rmf.n_grp, rmf.f_chan, rmf.n_chan, rmf.matrix,
                           rmf.offset, rmf.e_min, rmf.e_max, rmf.header)

        # Create a view of original ARF
        self.arf = DataARF(arf.name, arf.energ_lo, arf.energ_hi, arf.specresp,
                           arf.bin_lo, arf.bin_hi, arf.exposure, arf.header)

        # Filter the view for current fitting session
        _notice_resp(self.pha.get_noticed_channels(), self.arf, self.rmf)

        self.filter()

        # Assume energy as default spectral coordinates
        self.xlo, self.xhi = self.elo, self.ehi
        if self.pha.units == 'wavelength':
            self.xlo, self.xhi = self.lo, self.hi

        RSPModel.startup(self)
예제 #22
0
class ARFModelPHA(ARFModel):
    """ARF convolution model with associated PHA data set.

    Notes
    -----
    Scaling by the AREASCAL setting (scalar or array) is included in
    this model. It is not yet clear if this is handled correctly.
    """
    def __init__(self, arf, pha, model):
        self.pha = pha
        self._arf = arf  # store a reference to original
        ARFModel.__init__(self, arf, model)

    def filter(self):

        ARFModel.filter(self)

        pha = self.pha
        # If PHA is a finer grid than ARF, evaluate model on PHA and
        # rebin down to the granularity that the ARF expects.
        if pha.bin_lo is not None and pha.bin_hi is not None:
            bin_lo, bin_hi = pha.bin_lo, pha.bin_hi

            # If PHA grid is in angstroms then convert to keV for
            # consistency
            if (bin_lo[0] > bin_lo[-1]) and (bin_hi[0] > bin_hi[-1]):
                bin_lo = DataPHA._hc / pha.bin_hi
                bin_hi = DataPHA._hc / pha.bin_lo

            # FIXME: What about filtered option?? bin_lo, bin_hi are
            # unfiltered??

            # Compare disparate grids in energy space
            self.arfargs = ((self.elo, self.ehi), (bin_lo, bin_hi))

            # FIXME: Assumes ARF grid is finest

        # Assume energy as default spectral coordinates
        self.xlo, self.xhi = self.elo, self.ehi
        if self.pha.units == 'wavelength':
            self.xlo, self.xhi = self.lo, self.hi

    def startup(self, cache):
        arf = self._arf  # original
        pha = self.pha

        # Create a view of original ARF
        self.arf = DataARF(arf.name, arf.energ_lo, arf.energ_hi, arf.specresp,
                           arf.bin_lo, arf.bin_hi, arf.exposure, arf.header)

        # Filter the view for current fitting session
        if numpy.iterable(pha.mask):
            mask = pha.get_mask()
            if len(mask) == len(self.arf.specresp):
                self.arf.notice(mask)

        self.filter()

        # Assume energy as default spectral coordinates
        self.xlo, self.xhi = self.elo, self.ehi
        if pha.units == 'wavelength':
            self.xlo, self.xhi = self.lo, self.hi

        ARFModel.startup(self, cache)

    def teardown(self):
        self.arf = self._arf  # restore original

        self.filter()
        ARFModel.teardown(self)

    def calc(self, p, x, xhi=None, *args, **kwargs):
        # x could be channels or x, xhi could be energy|wave

        src = self.model.calc(p, self.xlo, self.xhi)
        src = self.arf.apply_arf(src, *self.arfargs)

        return apply_areascal(src, self.pha, "ARF: {}".format(self.arf.name))
예제 #23
0
class RSPModelPHA(RSPModel):
    """RMF + ARF convolution model with associated PHA.

    Notes
    -----
    Scaling by the AREASCAL setting (scalar or array) is included in
    this model.
    """
    def __init__(self, arf, rmf, pha, model):
        self.pha = pha
        self._arf = arf
        self._rmf = rmf
        RSPModel.__init__(self, arf, rmf, model)

    def filter(self):

        RSPModel.filter(self)

        pha = self.pha
        # If PHA is a finer grid than RMF, evaluate model on PHA and
        # rebin down to the granularity that the RMF expects.
        if pha.bin_lo is not None and pha.bin_hi is not None:
            bin_lo, bin_hi = pha.bin_lo, pha.bin_hi

            # If PHA grid is in angstroms then convert to keV for
            # consistency
            if (bin_lo[0] > bin_lo[-1]) and (bin_hi[0] > bin_hi[-1]):
                bin_lo = DataPHA._hc / pha.bin_hi
                bin_hi = DataPHA._hc / pha.bin_lo

            # FIXME: What about filtered option?? bin_lo, bin_hi are
            # unfiltered??

            # Compare disparate grids in energy space
            self.arfargs = ((self.elo, self.ehi), (bin_lo, bin_hi))

            # FIXME: Assumes ARF grid is finest

        elo, ehi = self.rmf.get_indep()
        # self.elo, self.ehi are from ARF
        if len(elo) != len(self.elo) and len(ehi) != len(self.ehi):

            self.rmfargs = ((elo, ehi), (self.elo, self.ehi))

        # Assume energy as default spectral coordinates
        self.xlo, self.xhi = self.elo, self.ehi
        if self.pha.units == 'wavelength':
            self.xlo, self.xhi = self.lo, self.hi

    def startup(self, cache):
        arf = self._arf
        rmf = self._rmf

        # Create a view of original RMF
        self.rmf = DataRMF(rmf.name, rmf.detchans, rmf.energ_lo, rmf.energ_hi,
                           rmf.n_grp, rmf.f_chan, rmf.n_chan, rmf.matrix,
                           rmf.offset, rmf.e_min, rmf.e_max, rmf.header)

        # Create a view of original ARF
        self.arf = DataARF(arf.name, arf.energ_lo, arf.energ_hi, arf.specresp,
                           arf.bin_lo, arf.bin_hi, arf.exposure, arf.header)

        # Filter the view for current fitting session
        _notice_resp(self.pha.get_noticed_channels(), self.arf, self.rmf)

        self.filter()

        # Assume energy as default spectral coordinates
        self.xlo, self.xhi = self.elo, self.ehi
        if self.pha.units == 'wavelength':
            self.xlo, self.xhi = self.lo, self.hi

        RSPModel.startup(self, cache)

    def teardown(self):
        self.arf = self._arf  # restore originals
        self.rmf = self._rmf

        self.filter()
        RSPModel.teardown(self)

    def calc(self, p, x, xhi=None, *args, **kwargs):
        # x could be channels or x, xhi could be energy|wave

        src = self.model.calc(p, self.xlo, self.xhi)
        src = self.arf.apply_arf(src, *self.arfargs)
        src = self.rmf.apply_rmf(src, *self.rmfargs)

        # Assume any issues with the binning (between AREASCAL
        # and src) is related to the RMF rather than the ARF.
        return apply_areascal(src, self.pha, "RMF: {}".format(self.rmf.name))
예제 #24
0
파일: instrument.py 프로젝트: mirca/sherpa
class ARFModelPHA(ARFModel):

    """
    ARF convolution model with associated PHA
    """

    def __init__(self, arf, pha, model):
        self.pha = pha
        self._arf = arf  # store a reference to original
        ARFModel.__init__(self, arf, model)

    def filter(self):

        ARFModel.filter(self)

        pha = self.pha
        # If PHA is a finer grid than ARF, evaluate model on PHA and
        # rebin down to the granularity that the ARF expects.
        if pha.bin_lo is not None and pha.bin_hi is not None:
            bin_lo, bin_hi = pha.bin_lo, pha.bin_hi

            # If PHA grid is in angstroms then convert to keV for
            # consistency
            if (bin_lo[0] > bin_lo[-1]) and (bin_hi[0] > bin_hi[-1]):
                bin_lo = DataPHA._hc / pha.bin_hi
                bin_hi = DataPHA._hc / pha.bin_lo

            # FIXME: What about filtered option?? bin_lo, bin_hi are
            # unfiltered??

            # Compare disparate grids in energy space
            self.arfargs = ((self.elo, self.ehi), (bin_lo, bin_hi))

            # FIXME: Assumes ARF grid is finest

        # Assume energy as default spectral coordinates
        self.xlo, self.xhi = self.elo, self.ehi
        if self.pha.units == 'wavelength':
            self.xlo, self.xhi = self.lo, self.hi

    def startup(self):
        arf = self._arf  # original
        pha = self.pha

        # Create a view of original ARF
        self.arf = DataARF(arf.name, arf.energ_lo, arf.energ_hi, arf.specresp,
                           arf.bin_lo, arf.bin_hi, arf.exposure, arf.header)

        # Filter the view for current fitting session
        if numpy.iterable(pha.mask):
            mask = pha.get_mask()
            if len(mask) == len(self.arf.specresp):
                self.arf.notice(mask)

        self.filter()

        # Assume energy as default spectral coordinates
        self.xlo, self.xhi = self.elo, self.ehi
        if pha.units == 'wavelength':
            self.xlo, self.xhi = self.lo, self.hi

        ARFModel.startup(self)

    def teardown(self):
        self.arf = self._arf  # restore original

        self.filter()
        ARFModel.teardown(self)

    def calc(self, p, x, xhi=None, *args, **kwargs):
        # x could be channels or x, xhi could be energy|wave

        src = self.model.calc(p, self.xlo, self.xhi)
        return self.arf.apply_arf(src, *self.arfargs)
예제 #25
0
파일: instrument.py 프로젝트: mirca/sherpa
class RSPModelPHA(RSPModel):

    """
    RMF + ARF convolution model with associated PHA
    """

    def __init__(self, arf, rmf, pha, model):
        self.pha = pha
        self._arf = arf
        self._rmf = rmf
        RSPModel.__init__(self, arf, rmf, model)

    def filter(self):

        RSPModel.filter(self)

        pha = self.pha
        # If PHA is a finer grid than RMF, evaluate model on PHA and
        # rebin down to the granularity that the RMF expects.
        if pha.bin_lo is not None and pha.bin_hi is not None:
            bin_lo, bin_hi = pha.bin_lo, pha.bin_hi

            # If PHA grid is in angstroms then convert to keV for
            # consistency
            if (bin_lo[0] > bin_lo[-1]) and (bin_hi[0] > bin_hi[-1]):
                bin_lo = DataPHA._hc / pha.bin_hi
                bin_hi = DataPHA._hc / pha.bin_lo

            # FIXME: What about filtered option?? bin_lo, bin_hi are
            # unfiltered??

            # Compare disparate grids in energy space
            self.arfargs = ((self.elo, self.ehi), (bin_lo, bin_hi))

            # FIXME: Assumes ARF grid is finest

        elo, ehi = self.rmf.get_indep()
        # self.elo, self.ehi are from ARF
        if len(elo) != len(self.elo) and len(ehi) != len(self.ehi):

            self.rmfargs = ((elo, ehi), (self.elo, self.ehi))

        # Assume energy as default spectral coordinates
        self.xlo, self.xhi = self.elo, self.ehi
        if self.pha.units == 'wavelength':
            self.xlo, self.xhi = self.lo, self.hi

    def startup(self):
        arf = self._arf
        rmf = self._rmf

        # Create a view of original RMF
        self.rmf = DataRMF(rmf.name, rmf.detchans, rmf.energ_lo, rmf.energ_hi,
                           rmf.n_grp, rmf.f_chan, rmf.n_chan, rmf.matrix,
                           rmf.offset, rmf.e_min, rmf.e_max, rmf.header)

        # Create a view of original ARF
        self.arf = DataARF(arf.name, arf.energ_lo, arf.energ_hi, arf.specresp,
                           arf.bin_lo, arf.bin_hi, arf.exposure, arf.header)

        # Filter the view for current fitting session
        _notice_resp(self.pha.get_noticed_channels(), self.arf, self.rmf)

        self.filter()

        # Assume energy as default spectral coordinates
        self.xlo, self.xhi = self.elo, self.ehi
        if self.pha.units == 'wavelength':
            self.xlo, self.xhi = self.lo, self.hi

        RSPModel.startup(self)

    def teardown(self):
        self.arf = self._arf  # restore originals
        self.rmf = self._rmf

        self.filter()
        RSPModel.teardown(self)

    def calc(self, p, x, xhi=None, *args, **kwargs):
        # x could be channels or x, xhi could be energy|wave

        src = self.model.calc(p, self.xlo, self.xhi)
        src = self.arf.apply_arf(src, *self.arfargs)
        return self.rmf.apply_rmf(src, *self.rmfargs)