Ejemplo n.º 1
0
    def load_STIS_data(self):
        """Load the right data according to `instr_config` for HST/STIS
        instrument"""

        # define pixel scales; values obtained from STScI
        # these values must be consistent with the given LSFs
        pixel_scale_dict = {
            'G140L': 0.60 * u.AA,
            'G140M': 0.05 * u.AA,
            'G230L': 1.58 * u.AA,
            'G230M': 0.09 * u.AA,
            'E140M': 'lambda/91700',
            'E140H': 'lambda/228000',
            'E230M': 'lambda/60000',
            'E230H': 'lambda/228000',
            'G230LB': 1.35 * u.AA,
            'G230MB': 0.15 * u.AA,
            'G430L': 2.73 * u.AA,
            'G430M': 0.28 * u.AA,
            'G750L': 4.92 * u.AA,
            'G750M': 0.56 * u.AA
        }
        # define channel based on grating name
        channel_dict = {
            'G140L': 'FUV-MAMA',
            'G140M': 'FUV-MAMA',
            'G230L': 'NUV-MAMA',
            'G230M': 'NUV_MAMA',
            'E140M': 'FUV-MAMA',
            'E140H': 'FUV-MAMA',
            'E230M': 'NUV-MAMA',
            'E230H': 'NUV-MAMA',
            'G230LB': 'CCD',
            'G230MB': 'CCD',
            'G430L': 'CCD',
            'G430M': 'CCD',
            'G750L': 'CCD',
            'G750M': 'CCD'
        }
        # also need slits
        available_slits = {
            'G140L': ['52x0.1', '52x0.2', '52x0.5', '52x2.0'],
            'G140M': ['52x0.1', '52x0.2', '52x0.5', '52x2.0'],
            'G230L': ['52x0.1', '52x0.2', '52x0.5', '52x2.0'],
            'G230M': ['52x0.1', '52x0.2', '52x0.5', '52x2.0'],
            'E140H': ['0.1x0.03', '0.2x0.09', '0.2x0.2', '6x0.2'],
            'E140M': ['0.1x0.03', '0.2x0.06', '0.2x0.2', '6x0.2'],
            'E230H': ['0.1x0.03', '0.1x0.09', '0.1x0.2', '6x0.2'],
            'E230M': ['0.1x0.03', '0.2x0.06', '0.2x0.2', '6x0.2'],
            'G430L': ['52x0.1', '52x0.2', '52x0.5', '52x2.0'],
            'G750L': ['52x0.1', '52x0.2', '52x0.5', '52x2.0']
        }

        try:
            grating = self.instr_config['grating']
        except:
            raise SyntaxError(
                '`grating` keyword missing in `instr_config` dictionary.')
        if grating not in channel_dict.keys():
            raise NotImplementedError(
                'Not ready for this HST/STIS grating: {}. '
                'Available gratings for HST/STIS are: {}'.format(
                    grating, channel_dict.keys()))
        if grating in ['G750M', 'G430M', 'G430L']:
            raise NotImplementedError(
                '{} not implemented yet; coming soon...'.format(grating))

        # We also need to know the slit width
        try:
            slit = self.instr_config['slit']
        except:
            raise SyntaxError(
                '`slit` keyword missing in `instr_config` dictionary.')
        if slit not in available_slits[grating]:
            raise NotImplementedError(
                'Not ready for this HST/STIS slit: {}. '
                'Available slits for HST/STIS grating {} are: {}'.format(
                    slit, grating, available_slits[grating]))

        # now we need to read the right files (new ones for echelle modes)
        if grating[0] == 'E':
            lsf_files = glob.glob(
                lt_path +
                '/data/lsf/STIS/stis_LSF_{}_????_LTmod.txt'.format(grating))
            # figure relevant wavelengths from file names
            wa_names = [
                fname.split('/')[-1].split('_')[-2].split('.')[0]
                for fname in lsf_files
            ]
        else:
            lsf_files = glob.glob(
                lt_path +
                '/data/lsf/STIS/stis_LSF_{}_????.txt'.format(grating))
            # figure relevant wavelengths from file names
            wa_names = [
                fname.split('/')[-1].split('_')[-1].split('.')[0]
                for fname in lsf_files
            ]

        # sort them
        sorted_inds = np.argsort(wa_names)
        lsf_files = np.array(lsf_files)[sorted_inds]
        wa_names = np.array(wa_names)[sorted_inds]

        # read the relevant kernels; they may have different rel_pix values depending on wave
        kernels_dict = dict()
        for ii, file_name in enumerate(lsf_files):
            # get the column names
            f = open(file_name, 'r')
            lines = f.readlines()  # get all lines
            f.close()
            col_names = lines[1]  # column names are in second line
            # get rid of '\n'
            col_names = col_names.split('\n')[0]
            # split by blank space(s) and remove the first element
            col_names = col_names.split()[1:]
            # rename new first column
            col_names[0] = 'rel_pix'

            # get original data
            data_aux = ascii.read(file_name, data_start=2, names=col_names)
            # reformat rel_pix
            data_aux['rel_pix'] = self.check_and_reformat_relpix(
                data_aux['rel_pix'])

            #TODO: remove the following block upon testing
            '''
            # handle asymmetric STIS LSF
            if data_aux['rel_pix'][len(data_aux['rel_pix']) // 2] == 0.:
                pass
            elif data_aux['rel_pix'][(len(data_aux['rel_pix']) // 2) - 1 ] ==0.:
                data_aux.insert_row(0,data_aux[-1])
                data_aux['rel_pix'][0]=-data_aux['rel_pix'][-1]
            elif data_aux['rel_pix'][(len(data_aux['rel_pix']) // 2) + 1 ] ==0.:
                data_aux.add_row(data_aux[0])
                data_aux['rel_pix'][-1]=-data_aux['rel_pix'][0]
            '''

            # create column with absolute wavelength based on pixel scales
            if isinstance(pixel_scale_dict[grating], np.unicode):
                # deal with wavelength-dependent pixel scale (i.e., STIS echelle)
                scalefac = 1. / float(pixel_scale_dict[grating].split('/')[-1])
                wave_aux = float(wa_names[ii]) * u.AA * (
                    1. + data_aux['rel_pix'] * scalefac)
            else:
                wave_aux = float(wa_names[ii]) * u.AA + data_aux[
                    'rel_pix'] * pixel_scale_dict[grating]
            data_aux[
                'wv'] = wave_aux  # not used for now, but may be useful with a different approach
            # create column with normalized kernel for relevant slit
            kernel = data_aux[slit] / np.sum(data_aux[slit])
            data_aux['kernel'] = kernel

            # store only rel_pix, wv, kernel as Table
            kernels_dict[wa_names[ii]] = data_aux['rel_pix', 'wv', 'kernel']

        # at this point the kernels_dict have the original information as given by the STScI
        # tables, in their various formats...

        # project to a single rel_pix scale using interpolation; for simplicity use a
        # custom rel_pix because STScI sometimes provide them as non-constant pixel fractions.
        # This new rel_pix grid will apply to all the wavelengths

        rel_pix = kernels_dict[wa_names[0]][
            'rel_pix']  # use the first one as reference
        rel_pix = np.linspace(-1 * np.max(rel_pix), np.max(rel_pix),
                              len(rel_pix))  # impose them linear and symmetric
        rel_pix = self.check_and_reformat_relpix(
            rel_pix)  # again just in case something odd happened
        data_table = Table()
        data_table['rel_pix'] = rel_pix
        for wa_name in wa_names:
            kernel_aux = interp_Akima(data_table['rel_pix'],
                                      kernels_dict[wa_name]['rel_pix'],
                                      kernels_dict[wa_name]['kernel'])
            data_table['{}A'.format(wa_name)] = kernel_aux

        pixel_scale = pixel_scale_dict[
            grating]  # read from dictionary defined above
        # import pdb; pdb.set_trace()
        # todo: work out a cleverer approach to this whole issue of having different rel_pix, pixel_scales, etc
        return pixel_scale, data_table
Ejemplo n.º 2
0
    def interpolate_to_wv_array(self, wv_array, kind='Akima', debug=False):
        """ Interpolate an LSF to a wavelength array.
        
        Given `wv_array` this function interpolates an LSF
        to match both scale and extent of `wv_array` using the 
        Akima or cubic-spline interpolators (default is Akima). 
        Some checks are performed too.

        Parameters
        ----------
        wv_array : Quantity numpy.ndarray, shape(N,)
            Wavelength array for which the LSF kernel is defined. The 
            central wavelength value of `wv_array` define the wavelength
            at which the LSF is defined, while the limits of `wv_array` 
            define the extent of the kernel.
        kind : str, optional
            Specifies the kind of interpolation as a string either 
            ('cubic', 'Akima'); default is `Akima`.

        Returns
        -------
        lsf_table : Table
            The interpolated lsf using at the central wavelength of 
            `wv_array`, using the same pixel scale as `wv_array`. 
            This table has two columns: 'wv' and 'kernel'. (lst_table['wv'] 
            is equal to `wv_array` by construction.)

        """
        # Check correct format
        if not ((isinstance(wv_array, np.ndarray)) or
                (isinstance(wv_array, Quantity))):
            raise SyntaxError('`wv_array` must be Quantity numpy.ndarray')
        elif len(wv_array.shape) != 1:
            raise SyntaxError(
                '`wv_array` must be of shape(N,), i.e. 1-dimensional array')
        if kind not in ['cubic', 'Akima', 'akima']:
            raise ValueError(
                'Only `cubic` or `Akima` interpolation available.')

        # define useful quantities
        wv_min = np.min(wv_array)
        wv_max = np.max(wv_array)
        wv0 = 0.5 * (wv_max + wv_min)

        # interpolate over tabulated LSF values if provided
        if len(self._data.colnames) > 2.:
            lsf_tab = self.interpolate_to_wv0(wv0)
        else:
            lsf_tab = self.shift_to_wv0(wv0)

        # make sure the wv_array is dense enough to sample the LSF kernel
        kernel_wvmin = np.min(lsf_tab['wv']) * u.AA
        kernel_wvmax = np.max(lsf_tab['wv']) * u.AA
        cond = (wv_array >= kernel_wvmin) & (wv_array <= kernel_wvmax)
        if np.sum(
                cond) < 10:  # this number is somewhat arbitrary but reasonable
            raise ValueError(
                'The input `wv_array` is undersampling the LSF kernel! Try a finer grid.'
            )

        # convert to Angstroms
        wv_array_AA = np.array([wv.to('AA').value for wv in wv_array])

        # interpolate to wv_array
        if kind == 'cubic':
            f = interp1d(lsf_tab['wv'],
                         lsf_tab['kernel'],
                         kind='cubic',
                         bounds_error=False,
                         fill_value=0)
            lsf_vals = f(wv_array_AA)
        elif kind in ('Akima', 'akima'):
            # f = Akima1DInterpolator(lsf_tab['wv'],lsf_tab['kernel'])
            # NT: I tried Akima interpolator from scipy.interpolate
            # and is not robust in extreme situations where the
            # wv_array is large compared to the kernel FWHM.
            # Let's try linetools.analysis.interp Akima version
            lsf_vals = interp_Akima(wv_array_AA, lsf_tab['wv'],
                                    lsf_tab['kernel'])

        # make sure the kernel is never negative
        cond = lsf_vals < 0
        if np.sum(cond) > 0:
            warnings.warn(
                'The interpolated kernel has negative values; imposing them to be 0.'
            )
            if debug:
                import matplotlib.pyplot as plt
                plt.plot(wv_array_AA, lsf_vals, 'k-')
                # import pdb; pdb.set_trace()
            lsf_vals = np.where(lsf_vals < 0, 0., lsf_vals)

        # normalize
        lsf_vals /= np.sum(lsf_vals)

        # re-define Table
        lsf_tab = Table()
        lsf_tab.add_column(Column(name='wv', data=wv_array))
        lsf_tab.add_column(Column(name='kernel', data=lsf_vals))

        return lsf_tab
Ejemplo n.º 3
0
    def interpolate_to_wv_array(self,wv_array, kind='Akima'):
        """ Interpolate an LSF to a wavelength array.
        
        Given `wv_array` this function interpolates an LSF
        to match both scale and extent of `wv_array` using the 
        Akima or cubic-spline interpolators (default is Akima). 
        Some checks are performed too.

        Parameters
        ----------
        wv_array : Quantity numpy.ndarray, shape(N,)
            Wavelength array for which the LSF kernel is defined. The 
            central wavelength value of `wv_array` define the wavelength
            at which the LSF is defined, while the limits of `wv_array` 
            define the extent of the kernel.
        kind : str, optional
            Specifies the kind of interpolation as a string either 
            ('cubic', 'Akima'); default is `Akima`.

        Returns
        -------
        lsf_table : Table
            The interpolated lsf using at the central wavelength of 
            `wv_array`, using the same pixel scale as `wv_array`. 
            This table has two columns: 'wv' and 'kernel'. (lst_table['wv'] 
            is equal to `wv_array` by construction.)

        """
        # Check correct format
        if not ((isinstance(wv_array, np.ndarray)) or (isinstance(wv_array, Quantity))):
            raise SyntaxError('`wv_array` must be Quantity numpy.ndarray')
        elif len(wv_array.shape) != 1:
            raise SyntaxError('`wv_array` must be of shape(N,), i.e. 1-dimensional array')
        if kind not in ['cubic','Akima','akima']:
            raise ValueError('Only `cubic` or `Akima` interpolation available.')

        #define useful quantities
        wv_min = np.min(wv_array)
        wv_max = np.max(wv_array)
        wv0 = 0.5 * (wv_max + wv_min)
        
        lsf_tab = self.interpolate_to_wv0(wv0)

        #convert to Angstroms
        wv_array_AA = np.array([wv.to('AA').value for wv in wv_array])
        
        #interpolate to wv_array
        if kind == 'cubic':
            f = interp1d(lsf_tab['wv'],lsf_tab['kernel'],kind='cubic',bounds_error=False,fill_value=0)
            lsf_vals =  f(wv_array_AA)
        elif kind in ('Akima','akima'):
            # f = Akima1DInterpolator(lsf_tab['wv'],lsf_tab['kernel']) 
            # NT: I tried Akima interpolator from scipy.interpolate
            # and is not robust in extreme situations where the
            # wv_array is large compared to the kernel FWHM.
            #Let's try linetools.analysis.interp Akima version 
            lsf_vals = interp_Akima(wv_array_AA,lsf_tab['wv'],lsf_tab['kernel'])

        #normalize
        lsf_vals /= np.max(lsf_vals)

        #re-define Table
        lsf_tab = Table()
        lsf_tab.add_column(Column(name='wv',data=wv_array))
        lsf_tab.add_column(Column(name='kernel',data=lsf_vals))

        return lsf_tab