Esempio n. 1
0
    def channel_shift(self, shift):
        """Shift the response array indices with an integer number provided by input parameter 'shift'.

        :param shift: Number of channels to shift the spectrum with.
        :type shift: int
        """

        # Check if input shift is indeed an integer
        if not isinstance(shift, int):
            message.error(
                "Entered shift is not an integer number. Not doing anything")
            return -1

        ic1 = np.zeros(self.eg1.size, dtype=int)
        ic2 = np.zeros(self.eg2.size, dtype=int)

        for i in np.arange(self.eg1.size):
            ic1[i] = self.ic1[i] + shift
            ic2[i] = self.ic2[i] + shift
            if self.ic2[i] > np.amax(self.nchan):
                message.error(
                    "Maximum channel number is larger than actual channel range!"
                )
                print("Aborting shift.")
                return -1

        # Save the shifted channel numbers
        self.ic1 = ic1
        self.ic2 = ic2

        return 0
Esempio n. 2
0
def gaussrsp(x, mu, fwhm, dfwhm):
    """Gaussian response function, optionally as a linear function of energy. The inputs are the energy value to
    calculate the response for (x), the center of the Gauss function (mu), the resolution of the detector
    (FWHM in eV at 1 keV), and the gradient of the FWHM as a function of energy.

    :param x: X value to calculate response for.
    :type x: float
    :param mu: Center of the Gauss function.
    :type mu: float
    :param fwhm: The full-width at half maximum of the detector resolution (FWHM in eV at 1 keV).
    :type fwhm: float
    :param dfwhm: Gradient of the detector resolution as a function of energy.
    :type dfwhm: float
    """

    # FWHM at the center energy of the response
    fwhm_mu = fwhm + dfwhm * (mu - 1.0)
    if fwhm_mu <= 0.:
        message.error(
            'The FWHM has become (less than) 0 in the provided energy range. '
            'Please check your input gradient.')
        return -1

    # Convert the FWHM to sigma
    sigma = fwhm_mu / (2.0 * math.sqrt(2.0 * math.log(2.0)))

    # Convert sigma to keV
    sigma = sigma * 1E-3

    # Calculate the response value for this response element x
    resp = 1. / (sigma * math.sqrt(2. * math.pi)) * math.exp(-(x - mu)**2 /
                                                             (2. * sigma**2))

    return resp
Esempio n. 3
0
    def write_all_regions(self,
                          spofile,
                          resfile,
                          exp_rate=False,
                          overwrite=False,
                          history=None):
        """Write all regions in the data object to spo and res. """
        tspo = Spo()
        tres = Res()

        i = 0
        for ireg in self.regions:
            tspo.add_spo_region(ireg.spo)
            tres.add_res_region(ireg.res,
                                isector=self.config[i, 0],
                                iregion=self.config[i, 1])
            i = i + 1

        stat = tspo.write_file(spofile,
                               exp_rate=exp_rate,
                               overwrite=overwrite,
                               history=history)
        if stat != 0:
            message.error("Writing SPO file failed.")
            return 1

        stat = tres.write_file(resfile, overwrite=overwrite, history=history)
        if stat != 0:
            message.error("Writing RES file failed.")
            return 1

        return 0
Esempio n. 4
0
    def create_dummy(self, resp):
        """Generate dummy spectrum based on rmf channel information."""

        if not isinstance(resp, Rmf):
            message.error(
                "Input response object is not the required Rmf object.")
            return

        # First copy the channel information to the PHA object
        self.Channel = resp.Channel
        self.FirstChannel = resp.Channel[0]
        self.DetChans = resp.NumberChannels

        # Generate a dummy spectrum (obviously not realistic, should be simulated in SPEX later)
        # Set exposure, statistic and type of spectrum
        self.Exposure = 1000.0
        self.Poisserr = True
        self.Spectrumtype = 'TOTAL'

        # Generate spectrum values and quality flags
        self.Rate = np.ones(self.DetChans, dtype=float) / self.Exposure
        self.StatError = np.ones(self.DetChans, dtype=float) / self.Exposure
        self.SysError = np.zeros(self.DetChans, dtype=float)
        self.Quality = np.zeros(self.DetChans, dtype=float)
        self.Grouping = np.zeros(self.DetChans, dtype=float)
        self.AreaScaling = np.ones(self.DetChans, dtype=float)
        self.BackScaling = np.ones(self.DetChans, dtype=float)
Esempio n. 5
0
    def read_region(self,
                    phafile,
                    rmffile,
                    bkgfile=None,
                    arffile=None,
                    corrfile=None,
                    grouping=False):
        """Add an OGIP spectrum and response to a SPEX region. The pha and rmf file names
        are mandatory. If needed, a background file and effective area file can be added.

        :param phafile: Name of the PHA file to read.
        :type phafile: str
        :param rmffile: Name of the RMF file to read.
        :type rmffile: str
        :param bkgfile: Name of the background PHA file to read (optional).
        :type bkgfile: str
        :param arffile: Name of the ARF file to read (optional).
        :type arffile: str
        :param corrfile: Name of the Correction file to read (optional).
        :type corrfile: str
        :param grouping: Keep the grouping information?
        :type grouping: bool
        """

        # Read the source PHA file
        self.read_source_pha(phafile)

        # Read the background PHA file if specified:
        if bkgfile is not None:
            self.read_background_pha(bkgfile)

        # Read a correction spectrum if specified:
        if corrfile is not None:
            self.read_corr(corrfile)

        # Read the response matrix
        self.read_rmf(rmffile)

        # Read the effective area
        if arffile is not None:
            self.read_arf(arffile)

        # Should the spectrum grouping remain?
        self.save_grouping = grouping

        if not self.input_back:
            self.back = None

        if not self.input_corr:
            self.corr = None

        if not self.input_area:
            self.area = None

        # Do the OGIP to SPEX conversion
        stat = self.ogip_to_spex()
        if stat != 0:
            message.error("OGIP to spex conversion failed.")
            return 1
Esempio n. 6
0
    def checkCompatibility(self, pha):
        """Check if another PHA object is compatible with the current one in terms of number of channels."""

        # Check equal number of channels
        if self.DetChans != pha.DetChans:
            message.error("Number of channels not equal for both PHA files.")
            return 1

        return 0
Esempio n. 7
0
    def combine_orders(self, grating):
        """Combine the orders for spectra from the same grating (1 = HETG, 2 = METG, 3 = LETG)."""

        # Select rows to combine
        tocombine = np.where(self.tg_part == grating)[0]

        if tocombine.size == 0:
            message.error("Grating number not found in dataset.")
            return 1

        if tocombine.size == 1:
            message.error(
                "Only a single order found. No combining will be done.")
            return 1

        # Create new PHA file to output (set first row as default).
        srcpha = self.phalist[tocombine[0]]
        bkgpha = Pha()
        bkgpha.StatError = np.zeros(srcpha.DetChans, dtype=float)

        for i in np.arange(tocombine.size):
            if i == 0:
                continue

            ipha = self.phalist[tocombine[i]]

            srcpha.Rate = srcpha.Rate + ipha.Rate
            bkgpha.Rate = srcpha.BackRate + ipha.BackRate

            for j in np.arange(srcpha.DetChans):
                if srcpha.StatError is not None:
                    srcpha.StatError[j] = math.sqrt(srcpha.StatError[j]**2 +
                                                    ipha.StatError[j]**2)
                    bkgpha.StatError[j] = math.sqrt(
                        srcpha.BackStatError[j]**2 + ipha.BackStatError[j]**2)
                srcpha.SysError[j] = math.sqrt(srcpha.SysError[j]**2 +
                                               ipha.SysError[j]**2)
                if ipha.Quality[j] != 0:
                    srcpha.Quality = 1

            # Remove grouping for now (maybe implemented later)
            srcpha.Grouping = 0 * srcpha.Grouping

            srcpha.AreaScaling = srcpha.AreaScaling + ipha.AreaScaling
            srcpha.BackScaling = srcpha.BackScaling + ipha.BackScaling

        # Calculate the average AreaScaling and BackScaling (Probably wrong!)
        srcpha.AreaScaling = srcpha.AreaScaling / tocombine.size
        srcpha.BackScaling = srcpha.BackScaling / tocombine.size

        bkgpha.AreaScaling = np.ones(srcpha.DetChans, dtype=float)
        bkgpha.BackScaling = srcpha.Pha2BackScal * np.ones(srcpha.DetChans,
                                                           dtype=float)
        bkgpha.Quality = np.zeros(srcpha.DetChans, dtype=int)
        bkgpha.Exposure = srcpha.Exposure

        return srcpha, bkgpha
Esempio n. 8
0
    def check(self):
        """Check if the basic information is read in."""
        if self.LowEnergy.size <= 0:
            message.error("Energy array has zero length.")
            return 1
        if self.EffArea.size <= 0:
            message.error("Effective area array has zero length.")
            return 1

        return 0
Esempio n. 9
0
 def read_rmf(self, rmffile):
     """Open rmf file containing the response matrix."""
     message.proc_start("Read RMF response matrix")
     stat = self.resp.read(rmffile)
     if stat != 0:
         message.proc_end(stat)
         message.error("Unable to read RMF/RSP file.")
         return
     else:
         self.input_resp = True
         message.proc_end(stat)
Esempio n. 10
0
    def checkCompatibility(self, pha):
        """Check if another PHA object is compatible with the current one in terms of number of channels.

        :param pha: PHA object to check compatibility for.
        :type pha: pyspextools.io.pha.Pha
        """

        # Check equal number of channels
        if self.DetChans != pha.DetChans:
            message.error("Number of channels not equal for both PHA files.")
            return 1

        return 0
Esempio n. 11
0
    def checkCompatibility(self, arf):

        # Check if arf is really an Arf object
        if not isinstance(arf, Arf):
            message.error("Input arf is not an Arf class instance.")
            return 1

        # Check if the size of the energy arrays is the same.
        if arf.LowEnergy.size != self.LowEnergy.size:
            message.error("Size of ARF and RMF are not the same.")
            return 1

        # Check if the numbers of the high energy column are the same
        # Here we check the high values, because sometimes the first low value is different...
        if arf.HighEnergy[0] != self.HighEnergy[0]:
            message.error(
                "First high-energy boundaries of arrays are not the same.")
            return 1

        # Check if the last values of the array are similar.
        size = arf.HighEnergy.size - 1

        if arf.HighEnergy[size] != self.HighEnergy[size]:
            message.error(
                "Last high-energy boudaries of arrays are not the same.")
            return 1

        return 0
Esempio n. 12
0
 def read_corr(self, corrfile):
     """Read correction file if specified."""
     if corrfile is not None:
         message.proc_start("Read correction spectrum")
         stat = self.corr.read(corrfile)
         if stat != 0:
             message.proc_end(stat)
             message.error("Unable to read CORR file.")
             return
         else:
             message.proc_end(stat)
             self.input_corr = True
     else:
         message.error("No correction file specified.")
Esempio n. 13
0
 def read_arf(self, arffile):
     """Read arf file containing the effective area."""
     if arffile is not None:
         message.proc_start("Read ARF effective area")
         stat = self.area.read(arffile)
         if stat != 0:
             message.proc_end(stat)
             message.error("Unable to read ARF file.")
             return
         else:
             self.input_area = True
             message.proc_end(stat)
     else:
         message.error("No effective area filename specified.")
Esempio n. 14
0
 def read_background_pha(self, bkgfile):
     """Open a pha file containing the background spectrum (if specified)."""
     if bkgfile is not None:
         message.proc_start("Read background PHA spectrum")
         stat = self.back.read(bkgfile)
         if stat != 0:
             message.proc_end(stat)
             message.error("Unable to read background PHA file.")
             return
         else:
             self.input_back = True
             message.proc_end(stat)
     else:
         message.error("No background filename specified.")
Esempio n. 15
0
    def checkCompatibility(self, arf):
        """Check whether the input arf object is really an ARF object with data and consistent with this RMF file.

        :param arf: Input arf object to check.
        :type arf: pyspextools.io.Arf
        """

        # Check if arf is really an Arf object
        if not isinstance(arf, Arf):
            message.error("Input arf is not an Arf class instance.")
            return 1

        # Check if the size of the energy arrays is the same.
        if arf.LowEnergy.size != self.matrix[0].LowEnergy.size:
            message.error("Size of ARF and RMF are not the same.")
            return 1

        # Check if the numbers of the high energy column are the same
        # Here we check the high values, because sometimes the first low value is different...
        if arf.HighEnergy[0] != self.matrix[0].HighEnergy[0]:
            message.error("First high-energy boundaries of arrays are not the same.")
            return 1

        # Check if the last values of the array are similar.
        size = arf.HighEnergy.size - 1

        if arf.HighEnergy[size] != self.matrix[0].HighEnergy[size]:
            message.error("Last high-energy boundaries of arrays are not the same.")
            return 1

        return 0
Esempio n. 16
0
 def update_config(self):
     self.config = np.empty(shape=[0, 2], dtype=int)
     pregion = 0  # Set previous region
     psector = 0  # Set previous sector
     for reg in self.regions:
         # Loop through components to find sector-region combinations
         if (reg.res.region[0] != pregion) or (reg.res.sector[0] !=
                                               psector):
             self.config = np.append(
                 self.config, [[reg.res.sector[0], reg.res.region[0]]],
                 axis=0)
             pregion = reg.res.region[0]
             psector = reg.res.sector[0]
         else:
             message.error("Double sector and region identification.")
Esempio n. 17
0
    def write(self, arffile, telescop=None, instrume=None, filter=None, overwrite=False):
        '''Write an OGIP compatible ARF file (Non-grating format).'''

        # Write the ARF arrays into FITS column format
        col1 = fits.Column(name='ENERG_LO', format='D', unit=self.EnergyUnits, array=self.LowEnergy)
        col2 = fits.Column(name='ENERG_HI', format='D', unit=self.EnergyUnits, array=self.HighEnergy)
        col3 = fits.Column(name='SPECRESP', format='D', unit=self.ARFUnits, array=self.EffArea)

        hdu = fits.BinTableHDU.from_columns([col1, col2, col3])

        hdr = hdu.header
        hdr.set('EXTNAME','SPECRESP')

        # Set the TELESCOP keyword (optional)
        if telescop == None:
            hdr.set('TELESCOP','None','Telescope name')
        else:
            hdr.set('TELESCOP',telescop,'Telescope name')

        # Set the INSTRUME keyword (optional)
        if instrume == None:
            hdr.set('INSTRUME','None','Instrument name')
        else:
            hdr.set('INSTRUME',instrume,'Instrument name')

        # Set the FILTER keyword (optional)
        if filter == None:
            hdr.set('FILTER','None','Filter setting')
        else:
            hdr.set('FILTER',filter,'Filter setting')

        hdr.set('DETNAM','None')
        hdr.set('HDUCLASS','OGIP')
        hdr.set('HDUCLAS1','RESPONSE')
        hdr.set('HDUCLAS2','SPECRESP')
        hdr.set('HDUVERS','1.1.0')
        hdr.set('ORIGIN','SRON')

        hdu.header['HISTORY'] = 'Created by pyspextools:'
        hdu.header['HISTORY'] = 'https://github.com/spex-xray/pyspextools'

        try:
            hdu.writeto(arffile, overwrite=overwrite)
        except IOError:
            message.error("File {0} already exists. I will not overwrite it!".format(arffile))
            return 1

        return 0
Esempio n. 18
0
    def read_source_pha(self, phafile):
        """Open a pha file containing the source spectrum."""
        message.proc_start("Read source PHA spectrum")
        stat = self.spec.read(phafile)
        if stat != 0:
            message.proc_end(stat)
            message.error("Unable to read source PHA file.")
            return
        else:
            self.input_spec = True
            message.proc_end(stat)

        # Check if first channel of spectrum is zero:
        if self.spec.FirstChannel == 0:
            self.first_channel_zero = True
        else:
            self.first_channel_zero = False
Esempio n. 19
0
    def write_all_regions(self,
                          spofile,
                          resfile,
                          exp_rate=True,
                          overwrite=False,
                          history=None):
        """Write all regions in the data object to spo and res.

        :param spofile: File name of the input .spo file.
        :type spofile: str
        :param resfile: File name of the input .res file.
        :type resfile: str
        :param exp_rate: Write an EXP_RATE column or not.
        :type exp_rate: bool
        :param overwrite: Should we overwrite existing files?
        :type overwrite: bool
        :param history: History information.
        :type history: List/Array of strings
        """
        tspo = Spo()
        tres = Res()

        i = 0
        for ireg in self.regions:
            tspo.add_spo_region(ireg.spo)
            tres.add_res_region(ireg.res,
                                isector=self.config[i, 0],
                                iregion=self.config[i, 1])
            i = i + 1

        stat = tspo.write_file(spofile,
                               exp_rate=exp_rate,
                               overwrite=overwrite,
                               history=history)
        if stat != 0:
            message.error("Writing SPO file failed.")
            return 1

        stat = tres.write_file(resfile, overwrite=overwrite, history=history)
        if stat != 0:
            message.error("Writing RES file failed.")
            return 1

        return 0
Esempio n. 20
0
    def check(self):
        """Check the RMF for internal consistency."""
        if self.ebounds.NumberChannels <= 0:
            message.error("Number of Channels in response is zero.")
            return 1

        for e in range(self.NumberMatrixExt):
            if self.matrix[e].NumberEnergyBins <= 0:
                message.error("Number of Energy bins in response is zero.")
                return 1

            c = 0
            r = 0
            # Check if matrix array is consistent with the indexing
            for i in np.arange(self.matrix[e].NumberEnergyBins):
                for j in np.arange(self.matrix[e].NumberGroups[i]):
                    for k in np.arange(self.matrix[e].NumberChannelsGroup[c]):
                        r = r + 1
                    c = c + 1

            if r != self.matrix[e].Matrix.size:
                message.error("Matrix size does not correspond to index arrays. Response inconsistent.")
                return 1

        return 0
Esempio n. 21
0
    def check(self, nregion=False):
        """Check whether spectrum and response are compatible
        and whether the arrays really consist of one region (if nregion flag is set).

        :param nregion: Flag to check whether the arrays just contain one region.
        :type nregion: bool
        """

        if self.res.nchan[0] != self.spo.nchan[0]:
            message.error(
                "Number of channels in spectrum is not equal to number of channels in response."
            )
            return -1

        if nregion:
            if self.spo.nchan.size != 1:
                message.error(
                    "SPO object consists of more than one region according to nchan array size."
                )
                return -1

            if self.spo.nregion != 1:
                message.error(
                    "SPO object consists of more than one region according to nregion parameter."
                )
                return -1

        return 0
Esempio n. 22
0
    def read_region(self, pha2file, rmflist, arflist, grating, bkgsubtract=True):
        """Add a Chandra spectrum and response to a SPEX region. The pha2 file and the rmf and arf file lists
        are mandatory. The grating option can be either HETG, METG or LETG"""

        self.grating = grating

        # Read the PHA2 file for a particular grating
        (src, bkg) = self.__read_pha2(pha2file, grating, bkgsubtract=bkgsubtract)
        if not isinstance(src,Pha):
            message.error("Failed to read spectrum file.")
            return 1

        # Convert the PHA2 file to spo

        rmf = Rmf()
        rmf.read(rmflist[0])

        self.spo = pha_to_spo(src, rmf, back=bkg)
        if not isinstance(self.spo,Spo):
            message.error("Failed to convert spectrum file.")
            return 1

        # Convert the responses to res
        self.res = self.__rmflist_to_res(rmflist, arflist)
        if not isinstance(self.res,Res):
            message.error("Failed to combine and convert response files.")
            return 1

        self.label = grating

        return 0
Esempio n. 23
0
    def __rmflist_to_res(self, rmflist, arflist):
        """Convert a list of compatible rmf and arf file into one res file. This is convenient for combining responses
        that are provided separately, like the Transmission Grating spectra from Chandra."""

        if len(rmflist) != len(arflist):
            message.error("ARF list and RMF list do not have the same length.")
            return 0

        rmfobjs = np.zeros(len(rmflist), dtype=object)
        arfobjs = np.zeros(len(arflist), dtype=object)
        rmf_orders = np.zeros(len(rmflist), dtype=int)
        arf_orders = np.zeros(len(arflist), dtype=int)

        i = 0
        for file in rmflist:
            message.proc_start("Reading response for order")
            rmf = Rmf()
            rmf.read(file)
            rmf_orders[i] = rmf.Order
            print(str(rmf_orders[i])+"  ", end='')
            if len(np.where(rmf_orders == rmf.Order)) != 1:
                message.error("There are two response files with the same order.")
                message.proc_end(1)
                return 1
            else:
                rmfobjs[i] = rmf
                message.proc_end(0)
            i = i + 1

        i=0
        for file in arflist:
            message.proc_start("Reading effective area for order")
            arf = Arf()
            arf.read(file)
            arf_orders[i] = arf.Order
            print(str(arf_orders[i])+"  ", end='')
            if len(np.where(arf_orders == arf.Order)) != 1:
                message.error("There are two effective area files for the same order.")
                message.proc_end(1)
                return 1
            else:
                arfobjs[i] = arf
                message.proc_end(0)
            i = i + 1

        arfsort = np.argsort(arf_orders)
        rmfsort = np.argsort(rmf_orders)

        # Calculate first response:
        res = rmf_to_res(rmfobjs[rmfsort[0]],arf=arfobjs[arfsort[0]])

        # Append the components from the other responses
        for i in np.arange(len(rmfsort)-1)+1:
            restmp = rmf_to_res(rmfobjs[rmfsort[i]],arf=arfobjs[arfsort[i]])
            res.append_component(restmp)

        return res
Esempio n. 24
0
    def __read_pha2(self, pha2file, grating, bkgsubtract=True):
        """Method to read a PHA type II file.

        :param pha2file: PHA type II file name to read.
        :type pha2file: str
        :param grating: Name of the grating to read (HETG, METG or LETG).
        :type grating: str
        :param bkgsubtract: Subtract the background?
        :type bkgsubtract: bool
        """

        # Initialize PHA2 file type

        spec = Pha2()

        # Is the source spectrum there?
        message.proc_start("Read source spectrum")
        if os.path.isfile(pha2file):
            stat = spec.read(pha2file, background=bkgsubtract)
            if stat != 0:
                message.proc_end(stat)
                message.error("Failed to read source spectrum.")
                return 1
            else:
                message.proc_end(stat)
        else:
            message.proc_end(1)
            message.error(
                "Spectrum file {0} not found in path.".format(pha2file))
            return 1

        # Convert grating name to number
        if grating == 'HETG':
            ngrating = 1
        elif grating == 'METG':
            ngrating = 2
        elif grating == 'LETG':
            ngrating = 3
        else:
            message.error("Unsupported grating: '{0}'.".format(grating))
            return 1

        # Combine spectra from a single grating
        message.proc_start("Combining orders of the spectrum")
        (src, bkg) = spec.combine_orders(ngrating)
        if isinstance(src, Pha) and isinstance(bkg, Pha):
            message.proc_end(0)
        else:
            message.proc_end(1)
            return 1

        return src, bkg
Esempio n. 25
0
    def check(self):
        """Check if the object contains the minimum required data."""
        # Check exposure value
        if self.Exposure <= 0.0:
            message.error("Exposure time of spectrum is zero or smaller.")
            return 1
        if self.DetChans <= 0:
            message.error("Number of channels is zero.")
            return 1
        if self.Rate.size <= 0:
            message.error("Size of rate array is zero.")
            return 1

        return 0
Esempio n. 26
0
    def read_region(self,
                    pha2file,
                    rmflist,
                    arflist,
                    grating,
                    bkgsubtract=True):
        """Add a Chandra spectrum and response to a SPEX region. The pha2 file and the rmf and arf file lists
        are mandatory. The grating option can be either HETG, METG or LETG.

        :param pha2file: PHA2 file name to read.
        :type pha2file: str
        :param rmflist: List of RMF response files.
        :type rmflist: list
        :param arflist: List of ARF effective area files.
        :type arflist: list
        :param grating: Grating name.
        :type grating: str
        :param bkgsubtract: Subtract the background?
        :type bkgsubtract: bool
        """

        self.grating = grating

        # Read the PHA2 file for a particular grating
        (src, bkg) = self.__read_pha2(pha2file,
                                      grating,
                                      bkgsubtract=bkgsubtract)
        if not isinstance(src, Pha):
            message.error("Failed to read spectrum file.")
            return 1

        # Convert the PHA2 file to spo

        rmf = Rmf()
        rmf.read(rmflist[0])

        self.spo = pha_to_spo(src, rmf, back=bkg)
        if not isinstance(self.spo, Spo):
            message.error("Failed to convert spectrum file.")
            return 1

        # Convert the responses to res
        self.res = self.__rmflist_to_res(rmflist, arflist)
        if not isinstance(self.res, Res):
            message.error("Failed to combine and convert response files.")
            return 1

        self.label = grating

        return 0
Esempio n. 27
0
    def write(self, rmffile, telescop=None, instrume=None, filterkey=None, overwrite=False):
        """Method to write an OGIP format RMF file.

        :param rmffile: RMF file name to write.
        :type rmffile: str
        :param telescop: Name of the telescope to be put in the TELESCOP keyword.
        :type telescop: str
        :param instrume: Name of the instrument to be put in the INSTRUME keyword.
        :type instrume: str
        :param filterkey: Name of the filter to be put in the FILTER keyword.
        :type filterkey: str
        :param overwrite: Overwrite existing file names? (True/False)
        :type overwrite: bool
        """

        #
        # Generate warning if there are multiple groups per energy
        #
        if np.amax(self.matrix[0].NumberGroups) != 1:
            message.warning("This method has not been tested for responses with multiple response groups per energy.")

        #
        # Create Primary HDU
        #
        primary = fits.PrimaryHDU()

        #
        # Create the EBOUNDS extension
        #
        ecol1 = fits.Column(name='CHANNEL', format='J', array=self.ebounds.Channel)
        ecol2 = fits.Column(name='E_MIN', format='D', unit=self.ebounds.EnergyUnits, array=self.ebounds.ChannelLowEnergy)
        ecol3 = fits.Column(name='E_MAX', format='D', unit=self.ebounds.EnergyUnits, array=self.ebounds.ChannelHighEnergy)

        ebnds = fits.BinTableHDU.from_columns([ecol1, ecol2, ecol3])

        ehdr = ebnds.header
        ehdr.set('EXTNAME', 'EBOUNDS')
        ehdr.set('DETCHANS', self.ebounds.NumberChannels)

        # Set the TELESCOP keyword (optional)
        if telescop is None:
            ehdr.set('TELESCOP', 'None', 'Telescope name')
        else:
            ehdr.set('TELESCOP', telescop, 'Telescope name')

        # Set the INSTRUME keyword (optional)
        if instrume is None:
            ehdr.set('INSTRUME', 'None', 'Instrument name')
        else:
            ehdr.set('INSTRUME', instrume, 'Instrument name')

        # Set the FILTER keyword (optional)
        if filterkey is None:
            ehdr.set('FILTER', 'None', 'Filter setting')
        else:
            ehdr.set('FILTER', filterkey, 'Filter setting')

        ehdr.set('DETNAM ', 'None')
        ehdr.set('CHANTYPE', 'PI')
        ehdr.set('HDUCLASS', 'OGIP')
        ehdr.set('HDUCLAS1', 'RESPONSE')
        ehdr.set('HDUCLAS2', 'EBOUNDS ')
        ehdr.set('HDUVERS1', '1.2.0')
        ehdr.set('ORIGIN ', 'SRON')

        hdu = fits.HDUList([primary, ebnds])

        #
        # Create SPECRESP MATRIX extension
        #
        for e in range(self.NumberMatrixExt):
            print("Writing matrix for matrix number: {0}".format(e))

            mcol1 = fits.Column(name='ENERG_LO', format='D', unit=self.matrix[e].EnergyUnits, array=self.matrix[e].LowEnergy)
            mcol2 = fits.Column(name='ENERG_HI', format='D', unit=self.matrix[e].EnergyUnits, array=self.matrix[e].HighEnergy)
            mcol3 = fits.Column(name='N_GRP', format='J', array=self.matrix[e].NumberGroups)
            mcol4 = fits.Column(name='F_CHAN', format='J', array=self.matrix[e].FirstChannelGroup)
            mcol5 = fits.Column(name='N_CHAN', format='J', array=self.matrix[e].NumberChannelsGroup)

            # Determine the width of the matrix
            width = np.amax(self.matrix[e].NumberChannelsGroup)
            formatstr = str(width)+'D'

            #
            # THIS PART COULD BE UPDATED TO OPTIMIZE THE SIZE USING VARIABLE SIZE ARRAYS IN FITS.
            #

            # Building the MATRIX column
            newmatrix = np.zeros(self.matrix[e].NumberEnergyBins * width, dtype=float).reshape(self.matrix[e].NumberEnergyBins, width)

            re = 0
            for i in np.arange(self.matrix[e].NumberEnergyBins):
                for j in np.arange(self.matrix[e].NumberGroups[i]):
                    for k in np.arange(self.matrix[e].NumberChannelsGroup[i]):
                        newmatrix[i, k] = self.matrix[e].Matrix[re]
                        re = re + 1

            mcol6 = fits.Column(name='MATRIX', format=formatstr, array=newmatrix)

            matrix = fits.BinTableHDU.from_columns([mcol1, mcol2, mcol3, mcol4, mcol5, mcol6])

            mhdr = matrix.header

            if self.matrix[e].AreaIncluded:
                mhdr.set('EXTNAME', 'SPECRESP MATRIX')
            else:
                mhdr.set('EXTNAME', 'MATRIX')

            # Set the TELESCOP keyword (optional)
            if telescop is None:
                mhdr.set('TELESCOP', 'None', 'Telescope name')
            else:
                mhdr.set('TELESCOP', telescop, 'Telescope name')

            # Set the INSTRUME keyword (optional)
            if instrume is None:
                mhdr.set('INSTRUME', 'None', 'Instrument name')
            else:
                mhdr.set('INSTRUME', instrume, 'Instrument name')

            # Set the FILTER keyword (optional)
            if filterkey is None:
                mhdr.set('FILTER', 'None', 'Filter setting')
            else:
                mhdr.set('FILTER', filterkey, 'Filter setting')

            mhdr.set('DETCHANS', self.ebounds.NumberChannels)
            mhdr.set('LO_THRES', self.matrix[e].ResponseThreshold)

            mhdr.set('CHANTYPE', 'PI')
            mhdr.set('HDUCLASS', 'OGIP')
            mhdr.set('HDUCLAS1', 'RESPONSE')
            mhdr.set('HDUCLAS2', 'RSP_MATRIX')

            if self.matrix[e].AreaIncluded:
                mhdr.set('HDUCLAS3', 'FULL')
            else:
                mhdr.set('HDUCLAS3', 'REDIST')
            mhdr.set('HDUVERS1', '1.3.0')
            mhdr.set('ORIGIN  ', 'SRON')

            matrix.header['HISTORY'] = 'Created by pyspextools:'
            matrix.header['HISTORY'] = 'https://github.com/spex-xray/pyspextools'

            hdu.append(matrix)

        try:
            hdu.writeto(rmffile, overwrite=overwrite)
        except IOError:
            message.error("File {0} already exists. I will not overwrite it!".format(rmffile))
            return 1

        return 0
Esempio n. 28
0
    def read(self, rmfhdu):

        # Read the Matrix table
        data = rmfhdu.data
        header = rmfhdu.header

        if rmfhdu.name == 'MATRIX':
            pass
        elif rmfhdu.name == 'SPECRESP MATRIX':
            message.warning("This is an RSP file with the effective area included.")
            print("Do not read an ARF file, unless you know what you are doing.")
            self.AreaIncluded = True
        else:
            message.error("MATRIX extension not successfully found in RMF file.")
            return

        self.LowEnergy = data['ENERG_LO']
        self.HighEnergy = data['ENERG_HI']
        self.NumberEnergyBins = self.LowEnergy.size
        self.EnergyUnits = header['TUNIT1']

        self.NumberGroups = data['N_GRP']
        self.NumberTotalGroups = np.sum(self.NumberGroups)

        self.FirstGroup = np.zeros(self.NumberEnergyBins, dtype=int)

        self.FirstChannelGroup = np.zeros(self.NumberTotalGroups, dtype=int)
        self.NumberChannelsGroup = np.zeros(self.NumberTotalGroups, dtype=int)
        self.FirstElement = np.zeros(self.NumberTotalGroups, dtype=int)

        self.Matrix = np.array([], dtype=float)

        try:
            self.Order = header['ORDER']
        except KeyError:
            pass

        fgroup = 0  # Count total number of groups
        felem = 0   # Count total number of response elements
        nelem = np.zeros(self.NumberEnergyBins, dtype=int)   # Count number of response elements per energy bin
        k = 0

        fchan_local = data['F_CHAN']
        nchan_local = data['N_CHAN']
        matrix_local = data['MATRIX']

        for i in np.arange(self.NumberEnergyBins):
            self.FirstGroup[i] = fgroup
            fgroup = fgroup + self.NumberGroups[i]

            if self.NumberGroups[i] != 0:
                for j in np.arange(self.NumberGroups[i]):
                    try:
                        self.FirstChannelGroup[k] = fchan_local[i][j]
                        self.NumberChannelsGroup[k] = nchan_local[i][j]
                    except IndexError:
                        self.FirstChannelGroup[k] = fchan_local[i]
                        self.NumberChannelsGroup[k] = nchan_local[i]
                    self.FirstElement[k] = felem
                    felem = felem + self.NumberChannelsGroup[k]
                    nelem[i] = nelem[i] + self.NumberChannelsGroup[k]

                    k = k + 1

        self.Matrix = np.zeros(felem, dtype=float)

        r = 0
        for i in np.arange(self.NumberEnergyBins):
            if nelem[i] != 0:
                for j in np.arange(nelem[i]):
                    self.Matrix[r] = matrix_local[i][j]
                    r = r + 1

        self.NumberTotalElements = self.Matrix.size
        self.ResponseThreshold = np.amin(self.Matrix)
Esempio n. 29
0
    def check(self):
        """Perform a number of checks to see if the response information is consistent."""
        # Check if the number of indexed channels is equal to the length of the response array
        if sum(self.nc) != self.resp.size:
            print("")
            message.error("Number of indexed channels not equal to response array.")
            print("Sum of channels in group:  {0}".format(sum(self.nc)))
            print("Length of response array:  {0}".format(self.resp.size))
            return -1

        # Check if the channel start and end bin are consistent with the number of channels
        for j in np.arange(len(self.neg)):
            check = self.ic2[j] - self.ic1[j] + 1
            if check != self.nc[j]:
                message.error("Number of group channels not consistent.")
                return -1

        # Check if energy grid is monotonous
        k = 0
        for i in np.arange(self.ncomp):
            for j in np.arange(self.neg[i]):
                if self.eg1[k] >= self.eg2[k]:
                    message.error("Energy bin size is not positive for"
                                  "bin {0} of component {1}.".format(j, i))
                    return -1
                if j > 1:
                    if self.eg1[k] < self.eg1[k-1]:
                        message.error("Energy grid is not increasing for"
                                      "bin {0} of component {1}.".format(j, i))
                        return -1
                if self.nc[k] > 0 and self.ic1[k] < 1:
                    message.error("For row {0} the first channel is {1}, which is not allowed.".format(k,self.ic1[k]))
                    return -1
                elif self.nc[k] > 0 and self.ic2[k] > self.nchan[i]:
                    message.error("For row {0} the last channel is larger than the number of channels.".format(k))
                    return -1
                elif self.ic2[k] < self.ic1[k]:
                    message.error("For row {0} the last channel is smaller than the first channel.".format(k))
                    return -1
                elif self.nc[k] > 0 and self.nc[k] != self.ic2[k] - self.ic1[k] + 1:
                    message.error("For row {0} the number of channels does not match the limits.".format(k))
                    return -1
                k = k + 1

        for i in np.arange(self.resp.size):
            if self.resp[i] < 0.0:
                message.error("Negative response value detected in matrix.")
                return -1

        return 0
Esempio n. 30
0
def clean_region(reg):
    """Remove bad channels and channels with zero response from the region.

    :param reg: Input Region object.
    :type reg: pyspextools.io.Region
    """

    if not isinstance(reg, Region):
        message.error("The input object is not of type Region.")
        return -1

    if reg.spo.empty:
        message.error("The input spo object is empty.")
        return -1

    if reg.res.empty:
        message.error("The input spo object is empty.")
        return -1

    message.proc_start("Identify bad channels in spectrum and response matrix and re-index matrix")

    (chanmask, groupmask, respmask) = __get_bad_channel_masks(reg)

    if not isinstance(chanmask, np.ndarray):
        return -1

    message.proc_end(0)

    # Print number of good and bad channels
    goodchan = np.sum(chanmask)
    badchan = chanmask.size - goodchan

    print("Number of good channels: {0}".format(goodchan))
    print("Number of bad channels:  {0}".format(badchan))

    if goodchan == 0:
        message.error("All channels appear to be bad. Please check your input files.")
        return -1

    message.proc_start("Removing bad channels from spectral region")

    # Fix binning issues first. Make sure bin ends before bad channel and starts after bad channel.
    for i in np.arange(reg.spo.nchan):
        if not chanmask[i]:
            if i != 0:
                reg.spo.last[i-1] = True
            if i != reg.spo.nchan - 1:
                reg.spo.first[i+1] = True

    spo = reg.spo

    spo.echan1 = reg.spo.echan1[chanmask]
    spo.echan2 = reg.spo.echan2[chanmask]
    spo.tints = reg.spo.tints[chanmask]
    spo.ochan = reg.spo.ochan[chanmask]
    spo.dochan = reg.spo.dochan[chanmask]
    spo.mbchan = reg.spo.mbchan[chanmask]
    spo.dbchan = reg.spo.dbchan[chanmask]
    spo.brat = reg.spo.brat[chanmask]
    spo.ssys = reg.spo.ssys[chanmask]
    spo.bsys = reg.spo.bsys[chanmask]
    spo.used = reg.spo.used[chanmask]
    spo.first = reg.spo.first[chanmask]
    spo.last = reg.spo.last[chanmask]

    # Count the number of good channels
    for i in np.arange(spo.nregion):
        spo.nchan[i] = np.sum(chanmask)

    # Check the consistency of the new object
    stat = spo.check()

    # Show result to user
    message.proc_end(stat)

    # Copy the filtered object to the original region
    reg.spo = spo

    # Print number of good and bad groups
    badgroup = groupmask.size - np.sum(groupmask)

    print("Number of original groups:       {0}".format(groupmask.size))
    print("Number of zero-response groups:  {0}".format(badgroup))

    # Print number of removed response elements
    badelements = respmask.size - np.sum(respmask)

    print("Number of original response elements:  {0}".format(respmask.size))
    print("Number of bad response elements:       {0}".format(badelements))

    message.proc_start("Removing bad channels from response matrix")

    # Mask response array
    reg.res.resp = reg.res.resp[respmask]
    if reg.res.resp_der:
        reg.res.dresp = reg.res.dresp[respmask]

    # Mask group arrays
    reg.res.eg1 = reg.res.eg1[groupmask]
    reg.res.eg2 = reg.res.eg2[groupmask]
    reg.res.ic1 = reg.res.ic1[groupmask]
    reg.res.ic2 = reg.res.ic2[groupmask]
    reg.res.nc = reg.res.nc[groupmask]
    if reg.res.area_scal:
        reg.res.relarea = reg.res.relarea[groupmask]

    eg_start = 0

    for icomp in np.arange(reg.res.ncomp):
        reg.res.nchan[icomp] = np.sum(chanmask)
        eg_end = reg.res.neg[icomp] + eg_start
        reg.res.neg[icomp] = np.sum(groupmask[eg_start:eg_end])
        eg_start = eg_end + 1

    stat = reg.res.check()

    message.proc_end(stat)

    return reg