def niriss_background_scaling(param_dict, detector, module): """Determine the scaling factor needed to translate the pre-existing NIRISS WFSS background image to the requested signal level, which is one of "low", "medium", or "high", as with the ETC. Parameters ---------- param_dict : dict Dictionary of observation information from an input yaml file detector : str Name of detector, e.g. "NRCA1" module : str Name of module, e.g. "A" Returns ------- ratio : float Ratio of the background value at the pivot wavelength that corresponds to the requested level, ratioed to that for the "medium" level, which is what was used to create the NIRISS WFSS background images. """ # Generate background spectra for all days background = jbt.background(param_dict['Telescope']['ra'], param_dict['Telescope']['dec'], 4.) # Determine which optical element wheel contains the filter we want # to use for background estimation if param_dict['Readout']['pupil'][0].upper() == 'F': usefilt = 'pupil' else: usefilt = 'filter' # Get basic flux calibration information vegazp, photflam, photfnu, pivot_wavelength = fluxcal_info( param_dict, usefilt, detector, module) # Extract the spectrum value across all days at the pivot wavelength wave_diff = np.abs(background.bkg_data['wave_array'] - pivot_wavelength) bkgd_wave = np.where(wave_diff == np.min(wave_diff))[0][0] bkgd_at_pivot = background.bkg_data['total_bg'][:, bkgd_wave] # Now sort and determine the low/medium/high levels low, medium, high = find_low_med_high(bkgd_at_pivot) # Find the value based on the level in the yaml file background_level = param_dict['simSignals']['bkgdrate'].lower() if background_level == "low": level_value = low elif background_level == "medium": level_value = medium elif background_level == "high": level_value = high else: raise ValueError(( "ERROR: Unrecognized background value: {}. Must be low, medium, or high" .format(params_dict['simSignals']['bkgdrate']))) return level_value
def test_fluxcal_info(): """Test that zeropoint information for the exposure is correctly retrieved """ params = { 'Inst': { "instrument": 'NIRCAM' }, 'Readout': { 'filter': 'F200W', 'pupil': 'CLEAR' }, 'Reffiles': { 'flux_cal': os.path.join(CONFIG_DIR, 'NIRCam_zeropoints.list') } } detector = 'NRCA1' module = 'A' vegazp, photflam, photfnu, pivot = flux_cal.fluxcal_info( params['Reffiles']['flux_cal'], 'NIRCAM', params['Readout']['filter'], params['Readout']['pupil'], detector, module) assert vegazp == 25.3677 assert photflam == 4.1652e-21 assert photfnu == 5.4949e-31 assert pivot == 1.9887
def source_mags_to_ghost_mags(row, flux_cal_file, magnitude_system, gap_summary_file, log_skipped_filters=False): """Works only for NIRISS. Given a row from a source catalog, create a ghost source catalog containing the magnitudes of the ghost associated with the source in all filters contained in the original catalog. Parameters ---------- row : astropy.table.Row Row containing a source from a source catalog flux_cal_file : str Name of file containing the flux calibration information for all filters magnitude_system : str Magnitude system of the magnitudes in the input catalog gap_summary_file : str Name of file that controls ghost locations relative to sources log_skipped_filters : bool If False, notices of skipped magnitude translations will not be logged. This is convenient for catalogs with lots of sources, because otherwise there can be many repeats of the message. Returns ------- ghost_row : astropy.table.Table Single row table containing the ghost magnitudes in all filters skipped_non_niriss_cols : bool If True, non NIRISS magnitude columns were found and skipped """ logger = logging.getLogger( 'mirage.ghosts.niriss_ghosts.source_mags_to_ghost_mags') mag_cols = [key for key in row.colnames if 'magnitude' in key] ghost_row = Table() skipped_non_niriss_cols = False for mag_col in mag_cols: # Ghost magnitudes can currently be calcuated only for NIRISS if 'nircam' in mag_col.lower() or 'fgs' in mag_col.lower(): skipped_non_niriss_cols = True continue col_parts = mag_col.split('_') if len(col_parts) == 3: filt = col_parts[1] else: raise ValueError( 'Unsupported magnitude column name for ghost conversion: {}'. format(mag_col)) wave = int(filt[1:4]) if wave > 200: filter_value = filt pupil_value = 'CLEARP' else: filter_value = 'CLEAR' pupil_value = filt # Get basic flux calibration information for the filter vegazeropoint, photflam, photfnu, pivot = \ fluxcal_info(flux_cal_file, 'niriss', filter_value, pupil_value, 'NIS', 'N') # Convert source magnitude to count rate mag = float(row[mag_col]) countrate = magnitude_to_countrate('niriss', filt, magnitude_system, mag, photfnu=photfnu, photflam=photflam, vegamag_zeropoint=vegazeropoint) # Get the count rate associated with the ghost _, _, ghost_countrate = get_ghost( 1024, 1024, countrate, filter_value, pupil_value, gap_summary_file, log_skipped_filters=log_skipped_filters) # Convert count rate to magnitude ghost_mag = countrate_to_magnitude('niriss', filt, magnitude_system, ghost_countrate, photfnu=photfnu, photflam=photflam, vegamag_zeropoint=vegazeropoint) ghost_row[mag_col] = [ghost_mag] return ghost_row, skipped_non_niriss_cols
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')
def low_med_high_background_spectrum(param_dict, detector, module): """Call jwst_backgrounds in order to produce an estimate of background versus wavelength for a particular pointing that corresponds to one of "low", "medium", or "high", as with the ETC. Parameters ---------- param_dict : dict Dictionary of observation information from an input yaml file detector : str Name of detector, e.g. "NRCA1" module : str Name of module, e.g. "A" Returns ------- background_waves : numpy.ndarray 1D array with the wavelength values in microns associated with ``background_signals`` background_spec : numpy.ndarray 1D array containing background values in MJy/str """ # Generate background spectra for all days background = jbt.background(param_dict['Telescope']['ra'], param_dict['Telescope']['dec'], 4.) # Get basic flux calibration information vegazp, photflam, photfnu, pivot_wavelength = fluxcal_info( param_dict['Reffiles']['flux_cal'], param_dict['Inst']['instrument'], param_dict['Readout']['filter'], param_dict['Readout']['pupil'], detector, module) # Extract the spectrum value across all days at the pivot wavelength wave_diff = np.abs(background.bkg_data['wave_array'] - pivot_wavelength) bkgd_wave = np.where(wave_diff == np.min(wave_diff))[0][0] bkgd_at_pivot = background.bkg_data['total_bg'][:, bkgd_wave] # Now sort and determine the low/medium/high levels low, medium, high = find_low_med_high(bkgd_at_pivot) # Find the value based on the level in the yaml file background_level = param_dict['simSignals']['bkgdrate'].lower() if background_level == "low": level_value = low elif background_level == "medium": level_value = medium elif background_level == "high": level_value = high else: raise ValueError(( "ERROR: Unrecognized background value: {}. Must be low, mediumn, or high" .format(param_dict['simSignals']['bkgdrate']))) # Find the day with the background at the pivot wavelength that # is closest to the value associated with the requested level diff = np.abs(bkgd_at_pivot - level_value) mindiff = np.where(diff == np.min(diff))[0][0] background_spec = background.bkg_data['total_bg'][mindiff, :] return background.bkg_data['wave_array'], background_spec