Ejemplo n.º 1
0
    def prepare_psf(self):
        """Get metadata relevant to the instrument/detector as well as the
        pixel scale from either the mosaic file, the PSF file, or both.
        Read in or create the PSF that is representative of the mosaic
        data.
        """
        # Get the PSF associated with the mosaic
        if self.psf_file is None:

            # If no PSF file is given and there is no pixel scale listed in
            # the mosaic file, then we cannot continue
            if self.mosaic_metadata['pix_scale1'] is None:
                raise ValueError(
                    ("ERROR: pixel scale value not present in mosaic file "
                     "(in CD1_1 header keyword). This information is needed "
                     "to be able to convolve the mosaic with the proper PSF "
                     "kernel."))

            # Get the dimensions of the JWST PSF kernel representing the
            # final PSF to use in the simulation
            jwst_ydim, jwst_xdim = self.jwst_psf.shape

            # If no psf file is given, and the user requests a 2D Gaussian,
            # then make a 2D Gaussian
            if self.gaussian_psf:
                self.logger.info("Creating 2D Gaussian for mosiac PSF")

                # If a Gaussian FWHM value is given, then construct the
                # PSF using astropy's Gaussian2D kernel

                # Rough guess on dimensions to use
                scale_ratio1 = self.outscale1 / self.mosaic_metadata[
                    'pix_scale1']
                scale_ratio2 = self.outscale2 / self.mosaic_metadata[
                    'pix_scale2']
                gauss_xdim = int(np.round(jwst_xdim * scale_ratio1))
                gauss_ydim = int(np.round(jwst_ydim * scale_ratio2))

                # Make sure the array has an odd number of rows and columns
                if gauss_xdim % 2 == 0:
                    gauss_xdim += 1
                if gauss_ydim % 2 == 0:
                    gauss_ydim += 1

                self.logger.info('Mosaic pixel scale: {} x {}'.format(
                    self.mosaic_metadata['pix_scale1'],
                    self.mosaic_metadata['pix_scale2']))
                self.logger.info('JWST pixel scale: {} x {}'.format(
                    self.outscale1, self.outscale2))
                self.logger.info('scale ratios: {}, {}'.format(
                    scale_ratio1, scale_ratio2))
                self.logger.info('JWST PSF dims: {} x {}'.format(
                    jwst_xdim, jwst_ydim))
                self.logger.info('Gaussian PSF dimensions: {} x {}'.format(
                    gauss_xdim, gauss_ydim))

                # Create 2D Gaussian
                self.mosaic_psf = tools.gaussian_psf(self.mosaic_fwhm,
                                                     gauss_xdim, gauss_ydim)

                self.logger.debug('Temporarily saving psf for development')
                h0 = fits.PrimaryHDU(self.mosaic_psf)
                hlist = fits.HDUList([h0])
                hlist.writeto('gaussian_2d_psf.fits', overwrite=True)
            elif self.mosaic_metadata[
                    'telescope'] in KNOWN_PSF_TELESCOPES and not self.gaussian_psf:
                # If no PSF file is given and the user does not want a
                # generic 2D Gaussian (self.gaussian_psf is False),
                # check to see if the mosaic is from one of the telescopes
                # with a known PSF. If so, use the appropriate function to
                # construct a PSF
                if self.mosaic_metadata['telescope'] == 'HST':
                    self.logger.info('Creating HST PSF, using 2D Gaussian')
                    self.logger.info('HST FWHM in arcsec: {}'.format(
                        self.mosaic_fwhm * self.mosaic_metadata['pix_scale2']))
                    self.mosaic_psf = tools.get_HST_PSF(
                        self.mosaic_metadata, self.mosaic_fwhm)
                elif self.mosaic_psf_metadata['telescope'] == 'JWST':
                    self.logger.info("Retrieving JWST PSF")
                    self.mosaic_psf = tools.get_JWST_PSF(self.mosaic_metadata)
                elif self.mosaic_psf_metadata['instrument'] == 'IRAC':
                    self.logger.info("Retrieving IRAC PSF")
                    self.mosaic_psf = tools.get_IRAC_PSF(self.mosaic_metadata)
            else:
                raise ValueError((
                    "For telescopes other than {}, you must either provide a "
                    "representative PSF using the psf_file keyword, or set "
                    "gaussian_psf=True in order to construct a 2D Gaussian PSF."
                    "This is neeeded to create the proper PSF kernal to transform "
                    "to the appropriate JWST PSF."))
        else:
            # If a PSF file is provided, check for any metadata. Metadata
            # from the mosaic takes precidence over metadata in the PSF file.
            psf_metadata = tools.get_psf_metadata(psf_filename)

            # If the mosaic has no pixel scale info but the PSF file does,
            # use the value from the PSF file.
            if self.mosaic_metadata['pix_scale1'] is None:
                if psf_metadata['pix_scale1'] is not None:
                    self.mosaic_metadata['pix_scale1'] = psf_metadata[
                        'pix_scale1']
                else:
                    raise ValueError((
                        "ERROR: no pixel scale value present in mosaic file nor PSF "
                        "file metadata (in CD1_1 header keyword). This information is "
                        "needed to be able to convolve the mosaic with the proper PSF "
                        "kernel."))
            self.mosaic_psf = fits.getdata(psf_filename)
Ejemplo n.º 2
0
    def crop_and_blot(self):
        """MAIN FUNCTION

        Extract an area of the input mosaic fits file that is slightly larger
        than the desired aperture, and use the JWST pipeline version of blot (called
        resample) to resample this to the correct pixel scale and introduce the
        proper distortion for the requested detector
        """
        # Initialize log
        self.logger = logging.getLogger('mirage.seed_image.fits_seed_image')
        self.logger.info('\n\nRunning fits_seed_image....\n')
        self.logger.info('using parameter file: {}\n'.format(self.paramfile))
        self.logger.info(
            'Original log file name: ./{}'.format(STANDARD_LOGFILE_NAME))

        self.detector_channel_value()
        self.distortion_file_value()
        module = self.module_value()
        self.siaf = pysiaf.Siaf(self.instrument)

        # Get information on the aperture
        self.aperture_info()
        xstart = self.subarr_bounds['xstart']
        ystart = self.subarr_bounds['ystart']
        xdim = self.subarr_bounds['xend'] - xstart + 1
        ydim = self.subarr_bounds['yend'] - ystart + 1

        # Flux calibration information
        self.flux_cal_file = self.expand_config_paths(self.flux_cal_file,
                                                      'flux_cal')
        vegazp, self.photflam, self.photfnu, self.pivot = fluxcal_info(
            self.flux_cal_file, self.instrument, self.filter, self.pupil,
            self.detector, module)

        # Get information on the instrument used to create the mosaic
        self.mosaic_metadata = tools.get_psf_metadata(self.mosaic_file)

        # Check that the FWHM of the mosaic PSF is smaller than the PSF
        # of the JWST PSF. If the opposite is true, we can't create a
        # matching kernel
        if self.mosaic_fwhm_units == 'arcsec':
            mosaic_fwhm_arcsec = copy.deepcopy(self.mosaic_fwhm)
            self.mosaic_fwhm /= self.mosaic_metadata['pix_scale1']

        # Identify the correct PSF for the JWST instrument/detector
        # Let's just read in the psf_wings file, which contains the PSF
        # at the center of the detector. It doesn't make much sense to
        # worry about spatially-dependent PSFs in this case
        self.jwst_psf = get_psf_wings(
            self.instrument, self.detector, self.filter, self.pupil,
            self.params['simSignals']['psfwfe'],
            self.params['simSignals']['psfwfegroup'],
            os.path.join(self.params['simSignals']['psfpath'], 'psf_wings'))
        self.outscale1, self.outscale2 = self.find_jwst_pixel_scale()

        # Find the FWHM of the JWST PSF
        j_yd, j_xd = self.jwst_psf.shape
        mid_y = j_yd // 2
        mid_x = j_xd // 2
        box = self.jwst_psf[mid_y - 10:mid_y + 11, mid_x - 10:mid_x + 11]
        jwst_x_fwhm, jwst_y_fwhm = tools.measure_fwhm(box)
        jwst_y_fwhm_arcsec = jwst_y_fwhm * self.outscale2
        self.logger.info('JWST FWHM in pix: {}'.format(jwst_y_fwhm))
        self.logger.info('JWST FWHM in arcsec: {}'.format(jwst_y_fwhm_arcsec))

        # If the FWHM of the mosaic image is larger than that of the JWST
        # PSF, then we cannot continue because we cannot create a matching
        # PSF kernel to use for convolution
        if mosaic_fwhm_arcsec > jwst_y_fwhm_arcsec:
            raise ValueError(
                ("ERROR: FWHM of the mosaic image is larger than that of "
                 "the JWST PSF. Unable to create a matching PSF kernel "
                 "using photutils. \nMosaic FWHM: {}\nJWST FWHM: {}".format(
                     self.mosaic_fwhm, jwst_y_fwhm_arcsec)))

        # The JWST PSF as read in is large (399 x 399 pixels). Crop to
        # something reasonable so that the convolution doesn't take
        # forever
        half_width = 50
        self.jwst_psf = self.jwst_psf[mid_y - half_width:mid_y + half_width +
                                      1, mid_x - half_width:mid_x +
                                      half_width + 1]

        # Collect the metadata relating to the mosaic and (optionally) PSF
        # and read in/create the PSF
        self.prepare_psf()

        # Crop
        self.logger.info("Cropping subarray from mosaic")
        pixel_scale = self.siaf[self.aperture].XSciScale
        crop = crop_mosaic.Extraction(
            mosaicfile=self.mosaic_file,
            data_extension_number=self.data_extension_number,
            wcs_extension_number=self.wcs_extension_number,
            center_ra=self.crop_center_ra,
            center_dec=self.crop_center_dec,
            dimensions=(xdim, ydim),
            outfile=self.cropped_file,
            jwst_pixel_scale=pixel_scale)
        crop.extract()

        # Convolve the cropped image with the appropriate PSF
        self.logger.info("Convolving with PSF kernel")
        crop.cropped = self.psf_convolution(crop.cropped)

        # Blot
        self.logger.info("Resampling subarray onto JWST pixel grid")
        blot = blot_image.Blot(instrument=self.instrument,
                               aperture=self.aperture,
                               ra=[self.blot_center_ra],
                               dec=[self.blot_center_dec],
                               pav3=[self.blot_pav3],
                               blotfile=crop.cropped,
                               distortion_file=self.distortion_file)
        blot.blot()

        # Blot the PSF associated with the mosaic
        #blot_psf = blot_image.Blot(instrument=self.instrument, aperture=self.aperture,
        #                           ra=[self.blot_center_ra], dec=[self.blot_center_dec],
        #                           pav3=[self.blot_pav3], blotfile=mosaic_psf_model,
        #                           distortion_file=self.distortion_file)
        #blot_psf.blot()

        for dm in blot.blotted_datamodels:
            # Create segmentation map
            # (used only when dispsersing image for WFSS)
            self.seed_image = dm.data
            self.seed_segmap = self.make_segmap(dm)

            # Save
            if self.blotted_file is not None:
                self.blotted_file = os.path.join(self.outdir,
                                                 self.blotted_file)
            self.seed_file, self.seedinfo = save(
                dm.data,
                self.paramfile,
                self.params,
                self.photflam,
                self.photfnu,
                self.pivot,
                self.framesize,
                self.nominal_dims,
                self.coords,
                self.grism_direct_factor,
                segmentation_map=self.seed_segmap,
                filename=self.blotted_file,
                base_unit='e-')
            self.logger.info('Blotted image saved to: {}'.format(
                self.seed_file))

        self.logger.info('\nfits_seed_image complete')
        logging_functions.move_logfile_to_standard_location(
            self.paramfile,
            STANDARD_LOGFILE_NAME,
            yaml_outdir=self.params['Output']['directory'],
            log_type='fits_seed')