def SimulatorInit(filename, fast_load=False): """ SimulatorInit 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 SIMULATOR initialisation') # Load data spectrum spectrum = Spectrum(filename, fast_load=fast_load) # TELESCOPE TRANSMISSION # ------------------------ telescope = TelescopeTransmission(spectrum.filter_label) # DISPERSER TRANSMISSION # ------------------------ if not isinstance(spectrum.disperser, str): disperser = spectrum.disperser else: disperser = Hologram(spectrum.disperser) # STAR SPECTRUM # ------------------------ if not isinstance(spectrum.target, str): target = spectrum.target else: target = Target(spectrum.target) return spectrum, telescope, disperser, target
def __init__(self, x, y, yerr, file_name="", nwalkers=18, nsteps=1000, burnin=100, nbins=10, verbose=0, plot=False, live_fit=False, truth=None): FitWorkspace.__init__(self, file_name, nwalkers, nsteps, burnin, nbins, verbose, plot, live_fit, truth=truth) self.my_logger = set_logger(self.__class__.__name__) self.x = x self.data = y self.err = yerr self.a = 1 self.b = 1 self.p = np.array([self.a, self.b]) self.input_labels = ["a", "b"] self.axis_names = ["$a$", "$b$"] self.bounds = np.array([(-100, 100), (-100, 100)]) self.nwalkers = max(2 * self.ndim, nwalkers)
def __init__(self, spectrum, atmgrid, telescope, disperser, target, filename=""): """ Args: filename (:obj:`str`): path to the image """ self.my_logger = set_logger(self.__class__.__name__) self.spectrum = spectrum self.header = spectrum.header self.disperser = disperser self.target = target self.telescope = telescope self.atmgrid = atmgrid self.lambdas = self.atmgrid.atmgrid[0, self.atmgrid.index_atm_data:] self.lambdas_binwidths = np.gradient(self.lambdas) self.spectragrid = None self.filename = "" if filename != "": self.filename = filename self.spectrum.load_spectrum(filename)
def __init__(self, label, verbose=False): """Initialize ArcLamp class. Parameters ---------- label: str String label to name the lamp. verbose: bool, optional Set True to increase verbosity (default: False) Examples -------- Mercury-Argon lamp: >>> t = ArcLamp("HG-AR", verbose=False) >>> print([line.wavelength for line in t.lines.lines][:5]) [253.652, 296.728, 302.15, 313.155, 334.148] >>> print(t.emission_spectrum) True """ Target.__init__(self, label, verbose=verbose) self.my_logger = set_logger(self.__class__.__name__) self.emission_spectrum = True self.lines = Lines(HGAR_LINES, emission_spectrum=True, orders=[1, 2])
def __init__(self, level, frame=None): """Create a BackgroundModel instance. The background model size is set with the parameters.CCD_IMSIZE global keyword. Parameters ---------- level: float The mean level of the background in image units. frame: array_like, None (x, y, smooth) right and upper limits in pixels of a vignetting frame, and the smoothing gaussian width (default: None). Examples -------- >>> from spectractor import parameters >>> parameters.CCD_IMSIZE = 200 >>> bgd = BackgroundModel(10) >>> model = bgd.model() >>> np.all(model==10) True >>> model.shape (200, 200) >>> bgd = BackgroundModel(10, frame=(160, 180, 3)) >>> bgd.plot_model() """ self.my_logger = set_logger(self.__class__.__name__) self.level = level if self.level <= 0: self.my_logger.warning( '\n\tBackground level must be strictly positive.') else: self.my_logger.info(f'\n\tBackground set to {level:.3f} ADU/s.') self.frame = frame
def __init__(self, airmass, pressure, temperature): """Class to evaluate an atmospheric transmission using Libradtran. Parameters ---------- airmass: float Airmass of the source object. pressure: float Pressure of the atmosphere in hPa. temperature: float Temperature of the atmosphere in Celsius degrees. Examples -------- >>> a = Atmosphere(airmass=1.2, pressure=800, temperature=5) >>> print(a.airmass) 1.2 >>> print(a.pressure) 800 >>> print(a.temperature) 5 >>> print(a.transmission(500)) 1.0 """ self.my_logger = set_logger(self.__class__.__name__) self.airmass = airmass self.pressure = pressure self.temperature = temperature self.pwv = None self.ozone = None self.aerosols = None self.transmission = lambda x: np.ones_like(x).astype(float) self.title = "" self.label = ""
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 __init__(self, label, verbose=False): """Initialize Target class. Parameters ---------- label: str String label to name the target verbose: bool, optional Set True to increase verbosity (default: False) """ self.my_logger = set_logger(self.__class__.__name__) self.label = label self.type = None self.wavelengths = [] self.spectra = [] self.verbose = verbose self.emission_spectrum = False self.hydrogen_only = False self.sed = None self.lines = None self.radec_position = None self.radec_position_after_pm = None self.redshift = 0 self.image = None self.image_x0 = None self.image_y0 = None
def SpectrumSimulatorCore(spectrum, telescope, disperser, airmass=1.0, pressure=800, temperature=10, pwv=5, ozone=300, aerosols=0.05, A1=1.0, A2=0., reso=0, D=parameters.DISTANCE2CCD, shift=0.): """ SimulatorCore 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 SPECTRUMSIMULATOR core program') # SIMULATE ATMOSPHERE # ------------------- atmosphere = Atmosphere(airmass, pressure, temperature) # SPECTRUM SIMULATION # -------------------- spectrum_simulation = SpectrumSimulation(spectrum, atmosphere, telescope, disperser) spectrum_simulation.simulate(A1, A2, ozone, pwv, aerosols, reso, D, shift) if parameters.DEBUG: spectrum_simulation.plot_spectrum(force_lines=True) return spectrum_simulation
def __init__(self): self.my_logger = set_logger(self.__class__.__name__) self.p = np.array([]) self.param_names = ["amplitude", "x_c", "y_c", "saturation"] self.axis_names = ["$A$", r"$x_c$", r"$y_c$", "saturation"] self.bounds = [[]] self.p_default = np.array([1, 0, 0, 1]) self.max_half_width = np.inf
def SpectrumSimulator(filename, outputdir="", pwv=5, ozone=300, aerosols=0.05, A1=1., A2=0., reso=None, D=parameters.DISTANCE2CCD, shift=0.): """ Simulator 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 SPECTRACTORSIM') # Initialisation spectrum, telescope, disperser, target = SimulatorInit(filename) # SIMULATE SPECTRUM # ------------------- airmass = spectrum.header['AIRMASS'] pressure = spectrum.header['OUTPRESS'] temperature = spectrum.header['OUTTEMP'] spectrum_simulation = SpectrumSimulatorCore(spectrum, telescope, disperser, airmass, pressure, temperature, pwv, ozone, aerosols, A1=A1, A2=A2, reso=reso, D=D, shift=shift) # Save the spectrum spectrum_simulation.header['OZONE_T'] = ozone spectrum_simulation.header['PWV_T'] = pwv spectrum_simulation.header['VAOD_T'] = aerosols spectrum_simulation.header['A1_T'] = A1 spectrum_simulation.header['A2_T'] = A2 spectrum_simulation.header['RESO_T'] = reso spectrum_simulation.header['D2CCD_T'] = D spectrum_simulation.header['X0_T'] = shift output_filename = filename.replace('spectrum', 'sim') if outputdir != "": base_filename = filename.split('/')[-1] output_filename = os.path.join( outputdir, base_filename.replace('spectrum', 'sim')) spectrum_simulation.save_spectrum(output_filename, overwrite=True) return spectrum_simulation
def run_spectrum_minimisation(fit_workspace, method="newton"): """Interface function to fit spectrum simulation parameters to data. Parameters ---------- fit_workspace: SpectrumFitWorkspace An instance of the SpectrogramFitWorkspace class. method: str, optional Fitting method (default: 'newton'). Examples -------- >>> filename = 'tests/data/sim_20170530_134_spectrum.fits' >>> atmgrid_filename = filename.replace('sim', 'reduc').replace('spectrum', 'atmsim') >>> load_config("config/ctio.ini") >>> w = SpectrumFitWorkspace(filename, atmgrid_file_name=atmgrid_filename, verbose=1, plot=True, live_fit=False) >>> parameters.VERBOSE = True >>> run_spectrum_minimisation(w, method="newton") """ my_logger = set_logger(__name__) guess = np.asarray(fit_workspace.p) if method != "newton": run_minimisation(fit_workspace, method=method) else: # fit_workspace.simulation.fast_sim = True # costs = np.array([fit_workspace.chisq(guess)]) # if parameters.DISPLAY and (parameters.DEBUG or fit_workspace.live_fit): # fit_workspace.plot_fit() # params_table = np.array([guess]) my_logger.info(f"\n\tStart guess: {guess}\n\twith {fit_workspace.input_labels}") epsilon = 1e-4 * guess epsilon[epsilon == 0] = 1e-4 epsilon[-1] = 0.001 * np.max(fit_workspace.data) # fit_workspace.simulation.fast_sim = True # fit_workspace.simulation.fix_psf_cube = False # run_minimisation_sigma_clipping(fit_workspace, method="newton", epsilon=epsilon, fix=fit_workspace.fixed, # xtol=1e-4, ftol=1 / fit_workspace.data.size, sigma_clip=10, niter_clip=3, # verbose=False) fit_workspace.simulation.fast_sim = False run_minimisation_sigma_clipping(fit_workspace, method="newton", epsilon=epsilon, fix=fit_workspace.fixed, xtol=1e-6, ftol=1 / fit_workspace.data.size, sigma_clip=10, niter_clip=3, verbose=False) if fit_workspace.filename != "": parameters.SAVE = True ipar = np.array(np.where(np.array(fit_workspace.fixed).astype(int) == 0)[0]) fit_workspace.plot_correlation_matrix(ipar) header = f"{fit_workspace.spectrum.date_obs}\nchi2: {fit_workspace.costs[-1] / fit_workspace.data.size}" fit_workspace.save_parameters_summary(ipar, header=header) # save_gradient_descent(fit_workspace, costs, params_table) fit_workspace.plot_fit() parameters.SAVE = False
def __init__(self, spectrum, atmosphere, telescope, disperser, fast_sim=True): """Class to simulate cross spectrum. Parameters ---------- spectrum: Spectrum Spectrum instance to load main properties before simulation. atmosphere: Atmosphere Atmosphere or AtmosphereGrid instance to make the atmospheric simulation. telescope: TelescopeTransmission Telescope transmission. disperser: Grating Disperser instance. fast_sim: bool, optional If True, do a fast simulation without integrating within the wavelength bins (default: True). Examples -------- >>> spectrum, telescope, disperser, target = SimulatorInit("./tests/data/reduc_20170530_134_spectrum.fits") >>> atmosphere = Atmosphere(airmass=1.2, pressure=800, temperature=10) >>> sim = SpectrumSimulation(spectrum, atmosphere, telescope, disperser, fast_sim=True) """ Spectrum.__init__(self) for k, v in list(spectrum.__dict__.items()): self.__dict__[k] = copy.copy(v) self.my_logger = set_logger(self.__class__.__name__) self.disperser = disperser self.telescope = telescope self.atmosphere = atmosphere self.fast_sim = fast_sim # save original pixel distances to zero order # self.disperser.grating_lambda_to_pixel(self.lambdas, x0=self.x0, order=1) # now reset data self.lambdas = None self.lambdas_order2 = None self.err = None self.model = lambda x: np.zeros_like(x) self.model_err = lambda x: np.zeros_like(x) lbdas_sed = self.target.wavelengths[0] sub = np.where((lbdas_sed > parameters.LAMBDA_MIN) & (lbdas_sed < parameters.LAMBDA_MAX)) self.lambdas_step = min(parameters.LAMBDA_STEP, np.min(lbdas_sed[sub]))
def SpectrogramSimulatorCore(spectrum, telescope, disperser, airmass=1.0, pressure=800, temperature=10, pwv=5, ozone=300, aerosols=0.05, A1=1.0, A2=0., D=parameters.DISTANCE2CCD, shift_x=0., shift_y=0., shift_t=0., angle=0., B=1., psf_poly_params=None, with_background=True, fast_sim=False, full_image=False, with_adr=True): """ SimulatorCore 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 SPECTROGRAMSIMULATOR core program') # SIMULATE ATMOSPHERE # ------------------- atmosphere = Atmosphere(airmass, pressure, temperature) spectrum.rotation_angle = angle # SPECTRUM SIMULATION # -------------------- spectrogram_simulation = SpectrogramModel(spectrum, atmosphere, telescope, disperser, with_background=with_background, fast_sim=fast_sim, full_image=full_image, with_adr=with_adr) spectrogram_simulation.simulate(A1, A2, ozone, pwv, aerosols, D, shift_x, shift_y, angle, B, psf_poly_params) return spectrogram_simulation
def __init__(self, centroid_coords, psf, amplitude): """Create a StarModel instance. The model is based on an Astropy Fittable2DModel. The centroid and amplitude parameters of the given model are updated by the dedicated arguments. Parameters ---------- centroid_coords: array_like Tuple of (x,y) coordinates of the desired star centroid in pixels. psf: PSF PSF model amplitude: float The desired amplitude of the star in image units. Examples -------- >>> from spectractor.extractor.psf import Moffat >>> p = (100, 50, 50, 5, 2, 200) >>> psf = Moffat(p) >>> s = StarModel((20, 10), psf, 200) >>> s.plot_model() >>> s.x0 20 >>> s.y0 10 >>> s.amplitude 200 """ self.my_logger = set_logger(self.__class__.__name__) self.x0 = centroid_coords[0] self.y0 = centroid_coords[1] self.amplitude = amplitude # self.target = target self.psf = copy.deepcopy(psf) self.psf.p[1] = self.x0 self.psf.p[2] = self.y0 self.psf.p[0] = amplitude # to be realistic, usually fitted fwhm is too big, divide gamma by 2 self.fwhm = self.psf.p[3]
def __init__(self, label, verbose=False): """Initialize Star class. Parameters ---------- label: str String label to name the target verbose: bool, optional Set True to increase verbosity (default: False) Examples -------- Emission line object: >>> s = Star('3C273') >>> print(s.label) 3C273 >>> print(s.radec_position.dec) 2d03m08.598s >>> print(s.emission_spectrum) True Standard star: >>> s = Star('HD111980') >>> print(s.label) HD111980 >>> print(s.radec_position.dec) -18d31m20.009s >>> print(s.emission_spectrum) False """ Target.__init__(self, label, verbose=verbose) self.my_logger = set_logger(self.__class__.__name__) self.simbad = None self.load()
def __init__(self, label, verbose=False): """Initialize Monochromator class. Parameters ---------- label: str String label to name the monochromator. verbose: bool, optional Set True to increase verbosity (default: False) Examples -------- >>> t = Monochromator("XX", verbose=False) >>> print(t.label) XX >>> print(t.emission_spectrum) True """ Target.__init__(self, label, verbose=verbose) self.my_logger = set_logger(self.__class__.__name__) self.emission_spectrum = True self.lines = Lines([], emission_spectrum=True, orders=[1, 2])
def __init__(self, filter_label=""): """Transmission of the telescope as product of the following transmissions: - mirrors - throughput - quantum efficiency - Filter Parameters ---------- filter_label: str, optional The filter string name. Examples -------- >>> t = TelescopeTransmission() >>> t.plot_transmission() """ self.my_logger = set_logger(self.__class__.__name__) self.filter_label = filter_label self.transmission = None self.transmission_err = None self.load_transmission()
def __init__(self, filename, target_label=None): self.my_logger = set_logger(self.__class__.__name__) Image.__init__(self, filename, target_label=target_label) self.true_lambdas = None self.true_spectrum = None
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
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 __init__(self, image_filename="", filename="", airmass=1., pressure=800., temperature=10., pwv_grid=[0, 10, 10], ozone_grid=[100, 700, 7], aerosol_grid=[0, 0.1, 10]): """Class to load and interpolate grids of atmospheric transmission computed with Libradtran. Parameters ---------- image_filename: str, optional The original image fits file name from which the grid was computed or has to be computed (default: ""). filename: str, optional The file name of the atmospheric grid if it exists (default: ""). airmass: float, optional Airmass of the source object (default: 1). pressure: float, optional Pressure of the atmosphere in hPa (default: 800). temperature: float, optional Temperature of the atmosphere in Celsius degrees (default: 10). pwv_grid: list List of 3 numbers for the PWV quantity: min, max, number of simulations (default: [0, 10, 10]). ozone_grid: list List of 3 numbers for the ozone quantity: min, max, number of simulations (default: [100, 700, 7]). aerosol_grid: list List of 3 numbers for the aerosol quantity: min, max, number of simulations (default: [0, 0.1, 10]). Examples -------- >>> a = AtmosphereGrid(filename='./tests/data/reduc_20170530_134_atmsim.fits') >>> a.image_filename.split('/')[-1] 'reduc_20170530_134_spectrum.fits' """ Atmosphere.__init__(self, airmass, pressure, temperature) self.my_logger = set_logger(self.__class__.__name__) self.image_filename = image_filename self.filename = filename # ------------------------------------------------------------------------ # Definition of data format for the atmospheric grid # ----------------------------------------------------------------------------- # row 0 : count number # row 1 : aerosol value # row 2 : pwv value # row 3 : ozone value # row 4 : data start self.index_atm_count = 0 self.index_atm_aer = 1 self.index_atm_pwv = 2 self.index_atm_oz = 3 self.index_atm_data = 4 # specify parameters for the atmospheric grid self.atmgrid = None self.NB_ATM_HEADER = self.index_atm_data + 1 self.NB_ATM_DATA = len(parameters.LAMBDAS) - 1 self.NB_ATM_POINTS = 0 self.AER_Points = np.array([]) self.OZ_Points = np.array([]) self.PWV_Points = np.array([]) self.set_grid(pwv_grid=pwv_grid, ozone_grid=ozone_grid, aerosol_grid=aerosol_grid) # the interpolated grid self.lambdas = parameters.LAMBDAS self.model = None self.header = fits.Header() if filename != "": self.load_file(filename)
def __init__(self, file_name, atmgrid_file_name="", nwalkers=18, nsteps=1000, burnin=100, nbins=10, verbose=0, plot=False, live_fit=False, truth=None): """Class to fit a spectrum extracted with Spectractor. The spectrum is supposed to be the product of the star SED, the instrument throughput and the atmospheric transmission, contaminated eventually by a second order diffraction. The truth parameters are loaded from the file header if provided. If provided, the atmospheric grid is used for the atmospheric transmission simulations and interpolated with splines, otherwise Libradtran is called at each step (slower). Parameters ---------- file_name: str Spectrum file name. atmgrid_file_name: str, optional Atmospheric grid file name (default: ""). nwalkers: int, optional Number of walkers for MCMC fitting. nsteps: int, optional Number of steps for MCMC fitting. burnin: int, optional Number of burn-in steps for MCMC fitting. nbins: int, optional Number of bins for MCMC chains analysis. verbose: int, optional Verbosity level (default: 0). plot: bool, optional If True, many plots are produced (default: False). live_fit: bool, optional If True, many plots along the fitting procedure are produced to see convergence in live (default: False). truth: array_like, optional Array of truth parameters to compare with the best fit result (default: None). Examples -------- >>> filename = 'tests/data/reduc_20170530_134_spectrum.fits' >>> atmgrid_filename = filename.replace('spectrum', 'atmsim') >>> load_config("config/ctio.ini") >>> w = SpectrumFitWorkspace(filename, atmgrid_file_name=atmgrid_filename, nsteps=1000, ... burnin=2, nbins=10, verbose=1, plot=True, live_fit=False) >>> lambdas, model, model_err = w.simulate(*w.p) >>> w.plot_fit() """ FitWorkspace.__init__(self, file_name, nwalkers, nsteps, burnin, nbins, verbose, plot, live_fit, truth=truth) if "spectrum" not in file_name: raise ValueError("file_name argument must contain spectrum keyword and be an output from Spectractor.") self.my_logger = set_logger(self.__class__.__name__) self.spectrum, self.telescope, self.disperser, self.target = SimulatorInit(file_name) self.airmass = self.spectrum.header['AIRMASS'] self.pressure = self.spectrum.header['OUTPRESS'] self.temperature = self.spectrum.header['OUTTEMP'] if atmgrid_file_name == "": self.atmosphere = Atmosphere(self.airmass, self.pressure, self.temperature) else: self.use_grid = True self.atmosphere = AtmosphereGrid(file_name, atmgrid_file_name) if parameters.VERBOSE: self.my_logger.info(f'\n\tUse atmospheric grid models from file {atmgrid_file_name}. ') self.lambdas = self.spectrum.lambdas self.data = self.spectrum.data self.err = self.spectrum.err self.data_cov = self.spectrum.cov_matrix self.A1 = 1.0 self.A2 = 0 self.ozone = 400. self.pwv = 3 self.aerosols = 0.05 self.reso = -1 self.D = self.spectrum.header['D2CCD'] self.shift_x = self.spectrum.header['PIXSHIFT'] self.B = 0 self.p = np.array([self.A1, self.A2, self.ozone, self.pwv, self.aerosols, self.reso, self.D, self.shift_x, self.B]) self.fixed = [False] * self.p.size # self.fixed[0] = True self.fixed[1] = "A2_T" not in self.spectrum.header # fit A2 only on sims to evaluate extraction biases self.fixed[5] = True # self.fixed[6:8] = [True, True] self.fixed[7] = True self.fixed[8] = True # self.fixed[-1] = True self.input_labels = ["A1", "A2", "ozone", "PWV", "VAOD", "reso [pix]", r"D_CCD [mm]", r"alpha_pix [pix]", "B"] self.axis_names = ["$A_1$", "$A_2$", "ozone", "PWV", "VAOD", "reso [pix]", r"$D_{CCD}$ [mm]", r"$\alpha_{\mathrm{pix}}$ [pix]", "$B$"] bounds_D = (self.D - 5 * parameters.DISTANCE2CCD_ERR, self.D + 5 * parameters.DISTANCE2CCD_ERR) self.bounds = [(0, 2), (0, 2/parameters.GRATING_ORDER_2OVER1), (100, 700), (0, 10), (0, 0.1), (0.1, 10), bounds_D, (-2, 2), (-np.inf, np.inf)] if atmgrid_file_name != "": self.bounds[2] = (min(self.atmosphere.OZ_Points), max(self.atmosphere.OZ_Points)) self.bounds[3] = (min(self.atmosphere.PWV_Points), max(self.atmosphere.PWV_Points)) self.bounds[4] = (min(self.atmosphere.AER_Points), max(self.atmosphere.AER_Points)) self.nwalkers = max(2 * self.ndim, nwalkers) self.simulation = SpectrumSimulation(self.spectrum, self.atmosphere, self.telescope, self.disperser) self.amplitude_truth = None self.lambdas_truth = None self.output_file_name = file_name.replace('_spectrum', '_spectrum_A2=0') self.get_truth()
def SpectrogramSimulator(filename, outputdir="", pwv=5, ozone=300, aerosols=0.05, A1=1., A2=0., D=parameters.DISTANCE2CCD, shift_x=0., shift_y=0., shift_t=0., angle=0., B=1., psf_poly_params=None): """ Simulator 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 SPECTRACTORSIM') # Initialisation spectrum, telescope, disperser, target = SimulatorInit(filename) 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() # SIMULATE SPECTRUM # ------------------- airmass = spectrum.header['AIRMASS'] pressure = spectrum.header['OUTPRESS'] temperature = spectrum.header['OUTTEMP'] spectrogram_simulation = SpectrogramSimulatorCore( spectrum, telescope, disperser, airmass, pressure, temperature, pwv, ozone, aerosols, D=D, shift_x=shift_x, shift_y=shift_y, shift_t=shift_t, angle=angle, B=B, psf_poly_params=psf_poly_params) # Save the spectrum spectrogram_simulation.header['OZONE_T'] = ozone spectrogram_simulation.header['PWV_T'] = pwv spectrogram_simulation.header['VAOD_T'] = aerosols spectrogram_simulation.header['A1_T'] = A1 spectrogram_simulation.header['A2_T'] = A2 spectrogram_simulation.header['D2CCD_T'] = D spectrogram_simulation.header['X0_T'] = shift_x spectrogram_simulation.header['Y0_T'] = shift_y spectrogram_simulation.header['TSHIFT_T'] = shift_t spectrogram_simulation.header['ROTANGLE'] = angle output_filename = filename.replace('spectrum', 'sim') if outputdir != "": base_filename = filename.split('/')[-1] output_filename = os.path.join( outputdir, base_filename.replace('spectrum', 'sim')) # spectrogram_simulation.save_spectrum(output_filename, overwrite=True) return spectrogram_simulation
def __init__(self, file_name, atmgrid_file_name="", nwalkers=18, nsteps=1000, burnin=100, nbins=10, verbose=0, plot=False, live_fit=False, truth=None): """Class to fit a spectrogram extracted with Spectractor. First the spectrogram is cropped using the parameters.PIXWIDTH_SIGNAL parameter to increase speedness. The truth parameters are loaded from the file header if provided. If provided, the atmospheric grid is used for the atmospheric transmission simulations and interpolated with splines, otherwise Libradtran is called at each step (slower). Parameters ---------- file_name: str Spectrum file name. atmgrid_file_name: str, optional Atmospheric grid file name (default: ""). nwalkers: int, optional Number of walkers for MCMC fitting. nsteps: int, optional Number of steps for MCMC fitting. burnin: int, optional Number of burn-in steps for MCMC fitting. nbins: int, optional Number of bins for MCMC chains analysis. verbose: int, optional Verbosity level (default: 0). plot: bool, optional If True, many plots are produced (default: False). live_fit: bool, optional If True, many plots along the fitting procedure are produced to see convergence in live (default: False). truth: array_like, optional Array of truth parameters to compare with the best fit result (default: None). Examples -------- >>> filename = 'tests/data/reduc_20170530_134_spectrum.fits' >>> atmgrid_filename = filename.replace('spectrum', 'atmsim') >>> load_config("config/ctio.ini") >>> w = SpectrogramFitWorkspace(filename, atmgrid_file_name=atmgrid_filename, nsteps=1000, ... burnin=2, nbins=10, verbose=1, plot=True, live_fit=False) >>> lambdas, model, model_err = w.simulate(*w.p) >>> w.plot_fit() """ FitWorkspace.__init__(self, file_name, nwalkers, nsteps, burnin, nbins, verbose, plot, live_fit, truth=truth) if "spectrum" not in file_name: raise ValueError( "file_name argument must contain spectrum keyword and be an output from Spectractor." ) self.filename = self.filename.replace("spectrum", "spectrogram") self.spectrum, self.telescope, self.disperser, self.target = SimulatorInit( file_name) self.airmass = self.spectrum.header['AIRMASS'] self.pressure = self.spectrum.header['OUTPRESS'] self.temperature = self.spectrum.header['OUTTEMP'] self.my_logger = set_logger(self.__class__.__name__) if atmgrid_file_name == "": self.atmosphere = Atmosphere(self.airmass, self.pressure, self.temperature) else: self.use_grid = True self.atmosphere = AtmosphereGrid(file_name, atmgrid_file_name) if parameters.VERBOSE: self.my_logger.info( f'\n\tUse atmospheric grid models from file {atmgrid_file_name}. ' ) self.crop_spectrogram() self.lambdas = self.spectrum.lambdas self.Ny, self.Nx = self.spectrum.spectrogram.shape self.data = self.spectrum.spectrogram.flatten() self.err = self.spectrum.spectrogram_err.flatten() self.A1 = 1.0 self.A2 = 1.0 self.ozone = 400. self.pwv = 3 self.aerosols = 0.05 self.D = self.spectrum.header['D2CCD'] self.psf_poly_params = self.spectrum.chromatic_psf.from_table_to_poly_params( ) length = len(self.spectrum.chromatic_psf.table) self.psf_poly_params = self.psf_poly_params[length:] self.psf_poly_params_labels = np.copy( self.spectrum.chromatic_psf.poly_params_labels[length:]) self.psf_poly_params_names = np.copy( self.spectrum.chromatic_psf.poly_params_names[length:]) self.psf_poly_params_bounds = self.spectrum.chromatic_psf.set_bounds_for_minuit( data=None) self.spectrum.chromatic_psf.psf.apply_max_width_to_bounds( max_half_width=self.spectrum.spectrogram_Ny) psf_poly_params_bounds = self.spectrum.chromatic_psf.set_bounds() self.shift_x = self.spectrum.header['PIXSHIFT'] self.shift_y = 0. self.angle = self.spectrum.rotation_angle self.B = 1 self.saturation = self.spectrum.spectrogram_saturation self.p = np.array([ self.A1, self.A2, self.ozone, self.pwv, self.aerosols, self.D, self.shift_x, self.shift_y, self.angle, self.B ]) self.fixed_psf_params = np.array([0, 1, 2, 3, 4, 9]) self.atm_params_indices = np.array([2, 3, 4]) self.psf_params_start_index = self.p.size self.p = np.concatenate([self.p, self.psf_poly_params]) self.input_labels = [ "A1", "A2", "ozone [db]", "PWV [mm]", "VAOD", r"D_CCD [mm]", r"shift_x [pix]", r"shift_y [pix]", r"angle [deg]", "B" ] + list(self.psf_poly_params_labels) self.axis_names = [ "$A_1$", "$A_2$", "ozone [db]", "PWV [mm]", "VAOD", r"$D_{CCD}$ [mm]", r"$\Delta_{\mathrm{x}}$ [pix]", r"$\Delta_{\mathrm{y}}$ [pix]", r"$\theta$ [deg]", "$B$" ] + list(self.psf_poly_params_names) bounds_D = (self.D - 5 * parameters.DISTANCE2CCD_ERR, self.D + 5 * parameters.DISTANCE2CCD_ERR) self.bounds = np.concatenate([ np.array([(0, 2), (0, 2 / parameters.GRATING_ORDER_2OVER1), (100, 700), (0, 10), (0, 0.1), bounds_D, (-2, 2), (-10, 10), (-90, 90), (0.8, 1.2)]), psf_poly_params_bounds ]) self.fixed = [False] * self.p.size for k, par in enumerate(self.input_labels): if "x_c" in par or "saturation" in par or "y_c" in par: self.fixed[k] = True # A2 is free only if spectrogram is a simulation or if the order 2/1 ratio is not known and flat self.fixed[ 1] = "A2_T" not in self.spectrum.header # not self.spectrum.disperser.flat_ratio_order_2over1 # self.fixed[5:7] = [True, True] # DCCD, x0 self.fixed[6] = True # Delta x # self.fixed[7] = True # Delta y # self.fixed[8] = True # angle self.fixed[9] = True # B if atmgrid_file_name != "": self.bounds[2] = (min(self.atmosphere.OZ_Points), max(self.atmosphere.OZ_Points)) self.bounds[3] = (min(self.atmosphere.PWV_Points), max(self.atmosphere.PWV_Points)) self.bounds[4] = (min(self.atmosphere.AER_Points), max(self.atmosphere.AER_Points)) self.nwalkers = max(2 * self.ndim, nwalkers) self.simulation = SpectrogramModel(self.spectrum, self.atmosphere, self.telescope, self.disperser, with_background=True, fast_sim=False, with_adr=True) self.lambdas_truth = None self.amplitude_truth = None self.get_spectrogram_truth()
def __init__(self, lines, redshift=0, atmospheric_lines=True, hydrogen_only=False, emission_spectrum=False, orders=[1]): """ Main emission/absorption lines in nm. Sorted lines are sorted in self.lines. See http://www.pa.uky.edu/~peter/atomic/ or https://physics.nist.gov/PhysRefData/ASD/lines_form.html Parameters ---------- lines: list List of Line objects to gather and sort. redshift: float, optional Red shift the spectral lines. Must be positive or null (default: 0) atmospheric_lines: bool, optional Set True if the atmospheric spectral lines must be included (default: True) hydrogen_only: bool, optional Set True to gather only the hydrogen spectral lines, atmospheric lines still included (default: False) emission_spectrum: bool, optional Set True if the spectral line has to be detected in emission (default: False) orders: list, optional List of integers corresponding to the diffraction order to account for the line search and the wavelength calibration (default: [1]) Examples -------- The default first five lines: >>> lines = Lines(ISM_LINES+HYDROGEN_LINES, redshift=0, atmospheric_lines=False, hydrogen_only=False, emission_spectrum=False) >>> print([lines.lines[i].wavelength for i in range(5)]) [353.1, 388.8, 397.0, 410.2, 434.0] The four hydrogen lines only: >>> lines = Lines(ISM_LINES+HYDROGEN_LINES+ATMOSPHERIC_LINES, redshift=0, atmospheric_lines=False, hydrogen_only=True, emission_spectrum=True) >>> print([lines.lines[i].wavelength for i in range(4)]) [397.0, 410.2, 434.0, 486.3] >>> print(lines.emission_spectrum) True Redshift the hydrogen lines, the atmospheric lines stay unchanged: >>> lines = Lines(ISM_LINES+HYDROGEN_LINES+ATMOSPHERIC_LINES, redshift=1, atmospheric_lines=True, hydrogen_only=True, emission_spectrum=True) >>> print([lines.lines[i].wavelength for i in range(7)]) [687.472, 760.3, 763.1, 794.0, 820.4, 822.696, 868.0] Redshift all the spectral lines, except the atmospheric lines: >>> lines = Lines(ISM_LINES+HYDROGEN_LINES+ATMOSPHERIC_LINES, redshift=1, atmospheric_lines=True, hydrogen_only=False, emission_spectrum=True) >>> print([lines.lines[i].wavelength for i in range(5)]) [687.472, 706.2, 760.3, 763.1, 777.6] Hydrogen lines at order 1 and 2: >>> lines = Lines(HYDROGEN_LINES, redshift=0, atmospheric_lines=True, hydrogen_only=False, emission_spectrum=True, orders=[1, 2]) >>> print([lines.lines[i].wavelength for i in range(len(lines.lines))]) [397.0, 410.2, 434.0, 486.3, 656.3, 794.0, 820.4, 868.0, 972.6, 1312.6] Negative redshift: >>> lines = Lines(HYDROGEN_LINES, redshift=-0.5) """ self.my_logger = set_logger(self.__class__.__name__) if redshift < -1e-2: self.my_logger.error( f'\n\tRedshift must small in absolute value (|z|<0.01) or be positive or null. ' f'Got redshift={redshift}.') self.lines = [] self.orders = orders for order in orders: for line in lines: tmp_line = copy.deepcopy(line) tmp_line.wavelength *= order if order > 1: if line.label[-1] == "$": tmp_line.label = tmp_line.label[:-1] tmp_line.label += "^(2)" if line.label[-1] == "$": tmp_line.label += "$" self.lines.append(tmp_line) self.redshift = redshift self.atmospheric_lines = atmospheric_lines self.hydrogen_only = hydrogen_only self.emission_spectrum = emission_spectrum self.lines = self.sort_lines()
def __init__(self, psf, data, data_errors, bgd_model_func=None, file_name="", nwalkers=18, nsteps=1000, burnin=100, nbins=10, verbose=0, plot=False, live_fit=False, truth=None): """ Parameters ---------- psf data: array_like The data array (background subtracted) of dimension 1 or 2. data_errors bgd_model_func file_name nwalkers nsteps burnin nbins verbose plot live_fit truth Examples -------- Build a mock spectrogram with random Poisson noise: >>> p = np.array([100, 50, 50, 3, 2, -0.1, 2, 200]) >>> psf = MoffatGauss(p) >>> data = psf.evaluate(p) >>> data_errors = np.sqrt(data+1) Fit the data: >>> w = PSFFitWorkspace(psf, data, data_errors, bgd_model_func=None, verbose=True) """ FitWorkspace.__init__(self, file_name, nwalkers, nsteps, burnin, nbins, verbose=verbose, plot=plot, live_fit=live_fit, truth=truth) self.my_logger = set_logger(self.__class__.__name__) if data.shape != data_errors.shape: raise ValueError(f"Data and uncertainty arrays must have the same shapes. " f"Here data.shape={data.shape} and data_errors.shape={data_errors.shape}.") self.psf = psf self.data = data self.err = data_errors self.bgd_model_func = bgd_model_func self.p = np.copy(self.psf.p) # [1:]) self.guess = np.copy(self.psf.p) self.saturation = self.psf.p[-1] self.fixed = [False] * len(self.p) self.fixed[-1] = True # fix saturation parameter self.input_labels = list(np.copy(self.psf.param_names)) # [1:])) self.axis_names = list(np.copy(self.psf.axis_names)) # [1:])) self.bounds = self.psf.bounds # [1:] self.nwalkers = max(2 * self.ndim, nwalkers) # prepare the fit if data.ndim == 2: self.Ny, self.Nx = self.data.shape self.psf.apply_max_width_to_bounds(self.Ny) yy, xx = np.mgrid[:self.Ny, :self.Nx] self.pixels = np.asarray([xx, yy]) elif data.ndim == 1: self.Ny = self.data.size self.Nx = 1 self.psf.apply_max_width_to_bounds(self.Ny) self.pixels = np.arange(self.Ny) self.fixed[1] = True else: raise ValueError(f"Data array must have dimension 1 or 2. Here pixels.ndim={data.ndim}.") # update bounds self.bounds = self.psf.bounds # [1:] total_flux = np.sum(data) self.bounds[0] = (0.1 * total_flux, 2 * total_flux) # error matrix # here image uncertainties are assumed to be uncorrelated # (which is not exactly true in rotated images) self.W = 1. / (self.err * self.err) self.W = self.W.flatten() # np.diag(self.W.flatten()) self.W_dot_data = self.W * self.data.flatten()
def __init__(self, wavelength, label, atmospheric=False, emission=False, label_pos=[0.007, 0.02], width_bounds=[0.5, 6], use_for_calibration=False): """Class modeling the emission or absorption lines. lines attributes contains main spectral lines sorted in wavelength. Parameters ---------- wavelength: float Wavelength of the spectral line in nm label: str atmospheric: bool Set True if the spectral line is atmospheric (default: False) emission: bool Set True if the spectral line has to be detected in emission. Can't be true if the line is atmospheric. (default: False) label_pos: [float, float] Position of the label in the plot with respect to the vertical lin (default: [0.007,0.02]) width_bounds: [float, float] Minimum and maximum width (in nm) of the line for fitting procedures (default: [1,7]) use_for_calibration: bool Use this line for the dispersion relation calibration, bright line recommended (default: False) Examples -------- >>> l = Line(550, label='test', atmospheric=True, emission=True) >>> print(l.wavelength) 550 >>> print(l.label) test >>> print(l.atmospheric) True >>> print(l.emission) False """ self.my_logger = set_logger(self.__class__.__name__) self.wavelength = wavelength # in nm self.label = label self.label_pos = label_pos self.atmospheric = atmospheric self.emission = emission if self.atmospheric: self.emission = False self.width_bounds = width_bounds self.fitted = False self.use_for_calibration = use_for_calibration self.high_snr = False self.fit_lambdas = None self.fit_gauss = None self.fit_bgd = None self.fit_snr = None self.fit_fwhm = None self.fit_popt = None self.fit_pcov = None self.fit_popt_gaussian = None self.fit_pcov_gaussian = None self.fit_chisq = None self.fit_eqwidth_mod = None self.fit_eqwidth_data = None self.fit_bgd_npar = parameters.CALIB_BGD_NPARAMS