def __init__(self, home=''): """Initialize the Libradtran settings for Spectractor. Parameters ---------- home: str, optional The path to the directory where libradtran directory is. If not specified $HOME is taken (default: ''). """ self.my_logger = set_logger(self.__class__.__name__) if home == '': self.home = os.environ['HOME'] else: self.home = home self.settings = {} # Definitions and configuration # ------------------------------------- # LibRadTran installation directory self.simulation_directory = 'simulations' ensure_dir(self.simulation_directory) self.libradtran_path = parameters.LIBRADTRAN_DIR # Filename : RT_LS_pp_us_sa_rt_z15_wv030_oz30.txt # : Prog_Obs_Rte_Atm_proc_Mod_zXX_wv_XX_oz_XX self.Prog = 'RT' # definition the simulation program is libRadTran self.proc = 'as' # Absoprtion + Rayleigh + aerosols special self.equation_solver = 'pp' # pp for parallel plane or ps for pseudo-spherical self.Atm = [ 'us' ] # short name of atmospheric sky here US standard and Subarctic winter self.Proc = 'sa' # light interaction processes : sc for pure scattering,ab for pure absorption # sa for scattering and absorption, ae with aerosols default, as with aerosol special self.Mod = 'rt' # Models for absorption bands : rt for REPTRAN, lt for LOWTRAN, k2 for Kato2
def SpectrumSimulatorSimGrid(filename, outputdir, pwv_grid=[0, 10, 5], ozone_grid=[100, 700, 7], aerosol_grid=[0, 0.1, 5]): """ SimulatorSimGrid Main function to evaluate several spectra A grid of spectra will be produced for a given target, airmass and pressure """ my_logger = set_logger(__name__) my_logger.info('\n\tStart SIMULATORGRID') # Initialisation spectrum, telescope, disperser, target = SimulatorInit(filename) # Set output path ensure_dir(outputdir) # extract the basename : simimar as os.path.basename(file) base_filename = filename.split('/')[-1] output_filename = os.path.join( outputdir, base_filename.replace('spectrum', 'spectrasim')) output_atmfilename = os.path.join( outputdir, base_filename.replace('spectrum', 'atmsim')) # SIMULATE ATMOSPHERE GRID # ------------------------ airmass = spectrum.header['AIRMASS'] pressure = spectrum.header['OUTPRESS'] temperature = spectrum.header['OUTTEMP'] atm = AtmosphereGrid(filename, airmass=airmass, pressure=pressure, temperature=temperature) atm.set_grid(pwv_grid, ozone_grid, aerosol_grid) # test if file already exists if os.path.exists(output_atmfilename ) and os.path.getsize(output_atmfilename) > 20000: filesize = os.path.getsize(output_atmfilename) infostring = " atmospheric simulation file %s of size %d already exists, thus load_image it ..." % ( output_atmfilename, filesize) my_logger.info(infostring) atm.load_file(output_atmfilename) else: my_logger.info( f"\n\tFile {output_atmfilename} does not exist yet. Compute it...") atm.compute() atm.save_file(filename=output_atmfilename) # libradtran.clean_simulation_directory() if parameters.DEBUG: infostring = '\n\t ========= Atmospheric simulation : ===============' my_logger.info(infostring) atm.plot_transmission() # plot all atm transp profiles atm.plot_transmission_image( ) # plot 2D image summary of atm simulations # SPECTRA-GRID # ------------- # in any case we re-calculate the spectra in case of change of spectrum function spectra = SpectrumSimGrid(spectrum, atm, telescope, disperser, target) spectra.compute() spectra.save_spectra(output_filename) if parameters.DEBUG: spectra.plot_spectra() spectra.plot_spectra_img()
def simulate(self, airmass, pwv, ozone, aerosol, pressure): """Simulate the atmosphere transmission with Libratran. Parameters ---------- airmass: float The airmass of the atmosphere. pwv: float Precipitable Water Vapor amount in mm. ozone: float Ozone quantity in Dobson units. aerosol: float VAOD of the aerosols. pressure: float Pressure of the atmosphere in hPa. Returns ------- output_file_name: str The output file name relative to the current directory. Examples -------- >>> parameters.DEBUG = True >>> lib = Libradtran() >>> output = lib.simulate(1.2, 2, 400, 0.07, 800) >>> print(output) simulations/pp/us/as/rt/in/RT_CTIO_pp_us_as_rt_z12_pwv20_oz40_aer7.OUT """ self.my_logger.debug( f'\n\t--------------------------------------------' f'\n\tevaluate' f'\n\t 1) airmass = {airmass}' f'\n\t 2) pwv = {pwv}' f'\n\t 3) ozone = {ozone}' f'\n\t 4) aer = {aerosol}' f'\n\t 5) pressure = {pressure}' f'\n\t--------------------------------------------') # build the part 1 of file_name base_filename_part1 = self.Prog + '_' + parameters.OBS_NAME + '_' + self.equation_solver + '_' aerosol_string = '500 ' + str(aerosol) # aerosol_str=str(wl0_num)+ ' '+str(tau0_num) aerosol_index = int(aerosol * 100.) # Set up type of run if self.proc == 'sc': runtype = 'no_absorption' elif self.proc == 'ab': runtype = 'no_scattering' elif self.proc == 'ae': runtype = 'aerosol_default' elif self.proc == 'as': runtype = 'aerosol_special' else: runtype = 'clearsky' # Selection of RTE equation solver if self.equation_solver == 'pp': # parallel plan equation_solver_equations = 'disort' elif self.equation_solver == 'ps': # pseudo spherical equation_solver_equations = 'sdisort' else: self.my_logger.error( f'Unknown RTE equation solver {self.equation_solver}.') sys.exit() # Selection of absorption model absorption_model = 'reptran' if self.Mod == 'rt': absorption_model = 'reptran' if self.Mod == 'lt': absorption_model = 'lowtran' if self.Mod == 'kt': absorption_model = 'kato' if self.Mod == 'k2': absorption_model = 'kato2' if self.Mod == 'fu': absorption_model = 'fu' if self.Mod == 'cr': absorption_model = 'crs' # for simulation select only two atmosphere # atmospheres = np.array(['afglus','afglms','afglmw','afglt','afglss','afglsw']) atmosphere_map = dict() # map atmospheric names to short names atmosphere_map['afglus'] = 'us' atmosphere_map['afglms'] = 'ms' atmosphere_map['afglmw'] = 'mw' atmosphere_map['afglt'] = 'tp' atmosphere_map['afglss'] = 'ss' atmosphere_map['afglsw'] = 'sw' atmospheres = [] for skyindex in self.Atm: if re.search('us', skyindex): atmospheres.append('afglus') if re.search('sw', skyindex): atmospheres.append('afglsw') output_directory, output_filename = None, None # 1) LOOP ON ATMOSPHERE for atmosphere in atmospheres: # if atmosphere != 'afglus': # just take us standard sky # break atmkey = atmosphere_map[atmosphere] # manage settings and output directories topdir = f'{self.simulation_directory}/{self.equation_solver}/{atmkey}/{self.proc}/{self.Mod}' ensure_dir(topdir) input_directory = topdir + '/' + 'in' ensure_dir(input_directory) output_directory = topdir + '/' + 'out' ensure_dir(output_directory) # loop on molecular model resolution # molecular_resolution = np.array(['coarse','medium','fine']) # select only COARSE Model molecular_resolution = 'coarse' # water vapor pwv_str = 'H2O ' + str(pwv) + ' MM' pwv_index = int(10 * pwv) # airmass airmass_index = int(airmass * 10) # Ozone oz_str = 'O3 ' + str(ozone) + ' DU' ozone_index = int(ozone / 10.) base_filename = f'{base_filename_part1}{atmkey}_{self.proc}_{self.Mod}_z{airmass_index}' \ f'_pwv{pwv_index}_oz{ozone_index}_aer{aerosol_index}' self.settings["data_files_path"] = self.libradtran_path + 'data' self.settings[ "atmosphere_file"] = self.libradtran_path + 'data/atmmod/' + atmosphere + '.dat' self.settings["albedo"] = '0.2' self.settings["rte_solver"] = equation_solver_equations if self.Mod == 'rt': self.settings[ "mol_abs_param"] = absorption_model + ' ' + molecular_resolution else: self.settings["mol_abs_param"] = absorption_model # Convert airmass into zenith angle sza = np.arccos(1. / airmass) * 180. / np.pi # Should be no_absorption if runtype == 'aerosol_default': self.settings["aerosol_default"] = '' elif runtype == 'aerosol_special': self.settings["aerosol_default"] = '' self.settings["aerosol_set_tau_at_wvl"] = aerosol_string if runtype == 'no_scattering': self.settings["no_scattering"] = '' if runtype == 'no_absorption': self.settings["no_absorption"] = '' # set up the ozone value self.settings["mol_modify"] = pwv_str self.settings["mol_modify2"] = oz_str # rescale pressure if reasonable pressure values are provided if 600. < pressure < 1015.: self.settings["pressure"] = pressure else: self.my_logger.error(f'\n\tcrazy pressure p={pressure} hPa') self.settings["output_user"] = '******' self.settings["altitude"] = str( parameters.OBS_ALTITUDE) # Altitude LSST observatory self.settings[ "source"] = 'solar ' + self.libradtran_path + 'data/solar_flux/kurudz_1.0nm.dat' self.settings["sza"] = str(sza) self.settings["phi0"] = '0' self.settings["wavelength"] = '250.0 1200.0' self.settings[ "output_quantity"] = 'reflectivity' # 'transmittance' # self.settings["quiet"] = '' input_filename = os.path.join(input_directory, base_filename + '.INP') output_filename = os.path.join(input_directory, base_filename + '.OUT') self.write_input(input_filename) self.run(input_filename, output_filename, path=self.libradtran_path) return output_filename
def ImageSim(image_filename, spectrum_filename, outputdir, pwv=5, ozone=300, aerosols=0.03, A1=1, A2=1, psf_poly_params=None, psf_type=None, with_rotation=True, with_stars=True, with_adr=True): """ The basic use of the extractor consists first to define: - the path to the fits image from which to extract the image, - the path of the output directory to save the extracted spectrum (created automatically if does not exist yet), - the rough position of the object in the image, - the name of the target (to search for the extra-atmospheric spectrum if available). Then different parameters and systematics can be set: - pwv: the pressure water vapor (in mm) - ozone: the ozone quantity (in XX) - aerosols: the vertical aerosol optical depth - A1: a global grey absorption parameter for the spectrum - A2: the relative amplitude of second order compared with first order - with_rotation: rotate the spectrum according to the disperser characteristics (True by default) - with_stars: include stars in the image field (True by default) - with_adr: include ADR effect (True by default) """ my_logger = set_logger(__name__) my_logger.info(f'\n\tStart IMAGE SIMULATOR') # Load reduced image spectrum, telescope, disperser, target = SimulatorInit(spectrum_filename) image = ImageModel(image_filename, target_label=target.label) guess = np.array([spectrum.header['TARGETX'], spectrum.header['TARGETY']]) if "CCDREBIN" in spectrum.header: guess *= spectrum.header["CCDREBIN"] if parameters.DEBUG: image.plot_image(scale='symlog', target_pixcoords=guess) # Fit the star 2D profile my_logger.info('\n\tSearch for the target in the image...') target_pixcoords = find_target(image, guess) # Background model my_logger.info('\n\tBackground model...') bgd_level = float(np.mean(spectrum.spectrogram_bgd)) background = BackgroundModel(level=bgd_level, frame=None) # (1600, 1650, 100)) if parameters.DEBUG: background.plot_model() # Target model my_logger.info('\n\tStar model...') # Spectrogram is simulated with spectrum.x0 target position: must be this position to simualte the target. star = StarModel(image.target_pixcoords, image.target_star2D, image.target_star2D.p[0]) # reso = star.fwhm if parameters.DEBUG: star.plot_model() # Star field model starfield = None if with_stars: my_logger.info('\n\tStar field model...') starfield = StarFieldModel(image) if parameters.DEBUG: image.plot_image(scale='symlog', target_pixcoords=starfield.pixcoords) starfield.plot_model() # Spectrum model my_logger.info('\n\tSpectrum model...') airmass = image.header['AIRMASS'] pressure = image.header['OUTPRESS'] temperature = image.header['OUTTEMP'] telescope = TelescopeTransmission(image.filter_label) # Rotation: useful only to fill the Dy_disp_axis column in PSF table if not with_rotation: rotation_angle = 0 else: rotation_angle = spectrum.rotation_angle # Load PSF if psf_type is not None: from spectractor.extractor.psf import load_PSF parameters.PSF_TYPE = psf_type psf = load_PSF(psf_type=psf_type) spectrum.psf = psf spectrum.chromatic_psf.psf = psf if psf_poly_params is None: my_logger.info('\n\tUse PSF parameters from _table.csv file.') psf_poly_params = spectrum.chromatic_psf.from_table_to_poly_params() else: spectrum.chromatic_psf.deg = (len(psf_poly_params) - 1) // ( len(spectrum.chromatic_psf.psf.param_names) - 2) - 1 spectrum.chromatic_psf.set_polynomial_degrees( spectrum.chromatic_psf.deg) if spectrum.chromatic_psf.deg == 0: # x_c must have deg >= 1 psf_poly_params.insert(1, 0) my_logger.info( f'\n\tUse PSF parameters {psf_poly_params} as polynoms of ' f'degree {spectrum.chromatic_psf.degrees}') if psf_type is not None and psf_poly_params is not None: spectrum.chromatic_psf.init_table() # Simulate spectrogram spectrogram = SpectrogramSimulatorCore(spectrum, telescope, disperser, airmass, pressure, temperature, pwv=pwv, ozone=ozone, aerosols=aerosols, A1=A1, A2=A2, D=spectrum.disperser.D, shift_x=0., shift_y=0., shift_t=0., B=1., psf_poly_params=psf_poly_params, angle=rotation_angle, with_background=False, fast_sim=False, full_image=True, with_adr=with_adr) # now we include effects related to the wrong extraction of the spectrum: # wrong estimation of the order 0 position and wrong DISTANCE2CCD # distance = spectrum.chromatic_psf.get_algebraic_distance_along_dispersion_axis() # spectrum.disperser.D = parameters.DISTANCE2CCD # spectrum.lambdas = spectrum.disperser.grating_pixel_to_lambda(distance, spectrum.x0, order=1) # Image model my_logger.info('\n\tImage model...') image.compute(star, background, spectrogram, starfield=starfield) # Recover true spectrum spectrogram.set_true_spectrum(spectrogram.lambdas, ozone, pwv, aerosols, shift_t=0) true_lambdas = np.copy(spectrogram.true_lambdas) true_spectrum = np.copy(spectrogram.true_spectrum) # Saturation effects saturated_pixels = np.where(spectrogram.data > image.saturation)[0] if len(saturated_pixels) > 0: my_logger.warning( f"\n\t{len(saturated_pixels)} saturated pixels detected above saturation " f"level at {image.saturation} ADU/s in the spectrogram." f"\n\tSpectrogram maximum is at {np.max(spectrogram.data)} ADU/s.") image.data[image.data > image.saturation] = image.saturation # Convert data from ADU/s in ADU image.convert_to_ADU_units() # Add Poisson and read-out noise image.add_poisson_and_read_out_noise() # Round float ADU into closest integers # image.data = np.around(image.data) # Plot if parameters.VERBOSE and parameters.DISPLAY: # pragma: no cover image.convert_to_ADU_rate_units() image.plot_image(scale="symlog", title="Image simulation", target_pixcoords=target_pixcoords, units=image.units) image.convert_to_ADU_units() # Set output path ensure_dir(outputdir) output_filename = image_filename.split('/')[-1] output_filename = (output_filename.replace('reduc', 'sim')).replace('trim', 'sim') output_filename = os.path.join(outputdir, output_filename) # Save images and parameters image.header['A1_T'] = A1 image.header['A2_T'] = A2 image.header['X0_T'] = spectrum.x0[0] image.header['Y0_T'] = spectrum.x0[1] image.header['D2CCD_T'] = spectrum.disperser.D image.header['OZONE_T'] = ozone image.header['PWV_T'] = pwv image.header['VAOD_T'] = aerosols image.header['ROT_T'] = rotation_angle image.header['ROTATION'] = int(with_rotation) image.header['STARS'] = int(with_stars) image.header['BKGD_LEV'] = background.level image.header['PSF_DEG'] = spectrum.spectrogram_deg image.header['PSF_TYPE'] = parameters.PSF_TYPE psf_poly_params_truth = np.array(psf_poly_params) if psf_poly_params_truth.size > spectrum.spectrogram_Nx: psf_poly_params_truth = psf_poly_params_truth[spectrum.spectrogram_Nx:] image.header['LBDAS_T'] = np.array_str(true_lambdas, max_line_width=1000000, precision=2) image.header['AMPLIS_T'] = np.array_str(true_spectrum, max_line_width=1000000, precision=2) image.header['PSF_P_T'] = np.array_str(psf_poly_params_truth, max_line_width=1000000, precision=4) image.save_image(output_filename, overwrite=True) return image