class Telescope(PersistentModel): """ The basic telescope class, which provides parameter storage for optimization. Attributes: #adapted from the original in Telescope.py name - The name of the telescope (string) aperture - The size of the primary telescope aperture, in meters (float) unobscured_fraction - The fraction of the primary mirror which is not obscured (float) temperature - instrument temperature, in Kelvin (float) ota_emissivity - emissivity factor for a TMA (float) diff_limit_wavelength - diffraction limit wavelength, in nm (float) _default_model - used by PersistentModel cameras - the Camera objects for this telescope """ _default_model = default_telescope cameras = [] spectrographs = [] name = '' aperture = pre_encode(0. * u.m) temperature = pre_encode(0. * u.K) ota_emissivity = pre_encode(0. * u.dimensionless_unscaled) diff_limit_wavelength = pre_encode(0. * u.nm) unobscured_fraction = pre_encode(1. * u.dimensionless_unscaled) @property def diff_limit_fwhm(self): """ Diffraction-limited PSF FWHM. """ diff_limit_wavelength, aperture = self.recover('diff_limit_wavelength', 'aperture') #result = (1.22 * u.rad * diff_limit_wavelength / aperture).to(u.arcsec) result = (1.03 * u.rad * diff_limit_wavelength / aperture).to(u.arcsec) return pre_encode(result) @property def effective_aperture(self): unobscured, aper = self.recover('unobscured_fraction', 'aperture') return pre_encode(np.sqrt(unobscured) * aper) def add_camera(self, camera): self.cameras.append(camera) camera.telescope = self def add_spectrograph(self, spectrograph): self.spectrographs.append(spectrograph) spectrograph.telescope = self
def update_exposure_params(self): """ Update the exposure parameters. """ new_aper = self.refs["ap_slider"].value * u.m new_expt = (self.refs["exp_slider"].value * u.h).to(u.s) new_snr = self.refs["snr_slider"].value * u.electron**0.5 self.aperture = pre_encode(new_aper) self.exptime = pre_encode(new_expt) self.snratio = pre_encode(new_snr) self.telescope.aperture = self.aperture
def _update_exptime(self): """ Calculate the exposure time to achieve the desired S/N for the given SED. """ self.camera._print_initcon(self.verbose) #We no longer need to check the inputs, since they are now tracked #attributes instead. #Convert JsonUnits to Quantities for calculations (_snr, _nexp) = self.recover('snr', 'n_exp') (_total_qe, _detector_rn, _dark_current) = self.recover('camera.total_qe', 'camera.detector_rn', 'camera.dark_current') snr2 = -(_snr**2) fstar = self._fstar fsky = self.camera._fsky(verbose=self.verbose) Npix = self.camera._sn_box(self.verbose) thermal = pre_decode(self.camera.c_thermal(verbose=self.verbose)) a = (_total_qe * fstar)**2 b = snr2 * (_total_qe * (fstar + fsky) + thermal + _dark_current * Npix) c = snr2 * _detector_rn**2 * Npix * _nexp texp = ((-b + np.sqrt(b**2 - 4 * a * c)) / (2 * a)).to(u.s) #serialize with JsonUnit for transportation self._exptime = pre_encode(texp) return True #completed successfully
def __init__(self): print('Initializing the parameters ...') self.grating = grating_pollux self.aperture = ap_pollux self.exptime = expt_pollux self.redshift = pre_encode(red_pollux[1]['value']) self.renorm_magnitude = mag_pollux self.spectrum_type = spectrum_pollux self.tool_preinit() self.photon_count = photon_count self.EM_gain = gain self.show_plot = show_plot self.file_name = file_name if snr_pollux == 1: self.make_image = False self.update_exposure() self.plot_snr() print('Saving the file at', basedir, '/pollux_tool/files/') np.savetxt( basedir + '/pollux_tool/files/' + self.file_name + '_' + self.grating + '_snr_data.txt', np.array((self.background_wave, self._snr)).T) if image_pollux == 1: self.make_image = True self.update_exposure() self.plot_image() print('Saving the file at', basedir, '/pollux_tool/files/') np.savetxt( basedir + '/pollux_tool/files/' + self.file_name + '_' + self.grating + '_2dimage_data.txt', self._final_image) np.savetxt( basedir + '/pollux_tool/files/' + self.file_name + '_' + self.grating + '_2dimage_wavelength.txt', self._wave_image)
def tool_preinit(self): """ Pre-initialize any required attributes for the interface. """ #initialize engine objects self.telescope = Telescope(temperature=pre_encode(280.0*u.K)) self.spectrograph = Spectrograph() self.exposure = Exposure() self.telescope.add_spectrograph(self.spectrograph) self.spectrograph.add_exposure(self.exposure) #set interface variables self.templates = ['flam', 'qso', 's99', 'o5v', 'g2v', 'g191b2b', 'gd71', 'gd153', 'ctts', 'mdwarf', 'orion', 'nodust', 'ebv6', 'hi1hei1', 'hi0hei1'] self.template_options = [SpectralLibrary[t] for t in self.templates] self.help_text = help_text self.gratings = self.spectrograph.modes self.grating_options = [self.spectrograph.descriptions[g] for g in self.gratings] self.dl_filename = "" #set default input values self.update_exposure() #Formatting & interface stuff: self.format_string = interface_format self.interface_file = os.path.join(script_dir, "interface.yaml") #For saving calculations self.current_savefile = "" self.overwrite_save = False
def sed_id(self, new_sed_id): if new_sed_id == self._sed_id: return self._sed_id = new_sed_id self._sed = pre_encode( SpectralLibrary.get(new_sed_id, SpectralLibrary.fab)) self.calculate(make_image=False)
def interpolated_sed(self): """ The exposure's SED interpolated at the camera bandpasses. """ if not self.camera: return self.sed sed = self.recover('sed') return pre_encode(self.camera.interpolate_at_bands(sed))
def mode(self, new_mode): """ Mode is used to set all the other parameters """ nmode = new_mode.upper() if self._mode == nmode or nmode not in self.modes: return self._mode = nmode table = QTable.read(self._lumos_default_file, nmode) self.R = pre_encode(table.meta['R'] * u.pix) self.wave = pre_encode(table['Wavelength']) self.bef = pre_encode(table['BEF'] / self.recover('delta_lambda')) self.aeff = pre_encode(table['A_Eff']) wrange = np.array([table.meta['WAVE_LO'], table.meta['WAVE_HI']]) * u.AA self.wrange = pre_encode(wrange)
def ab_zeropoint(self): """ AB-magnitude zero points as per Marc Postman's equation. """ pivotwave = self.recover('pivotwave').to(u.nm) abzp = 5509900. * (u.photon / u.s / u.cm**2) / pivotwave return pre_encode(abzp)
def store(self, **kw): """ We only want to store JsonUnit serializations of Quantities, we don't need the actual JsonUnit representations because they'll break too. """ for attr, quantity in kw.items(): setattr(self, attr, pre_encode(quantity))
def changed_value(self, ref, param, unit): new_val = self.refs[ref].value if unit is not None: new_val = new_val * unit if ref == "exp_slider": new_val = pre_encode(new_val.to(u.s)) elif ref == "age_slider": new_val = pre_encode(u.Dex(10.**new_val * u.Gyr)) elif ref == "metallicity_slider": new_val = pre_encode(u.Dex(new_val)) else: new_val = pre_encode(new_val) if ref == "crowding_slider": print(param, getattr(self, param), new_val) if getattr(self, param) != new_val: setattr(self, param, new_val) return True return False
def sed(self): """ Return a spectrum, redshifted if necessary. We don't just store the redshifted spectrum because pysynphot doesn't save the original, it just returns a new copy of the spectrum with redshifted wavelengths. """ sed = pre_decode(self._sed) z = self.recover('redshift') return pre_encode(sed.redshift(z))
def tool_preinit(self): """ Pre-initialize any required attributes for the interface. """ #initialize engine objects self.telescope = Telescope(temperature=pre_encode(280.0 * u.K)) self.spectrograph = Spectropolarimeter() self.exposure = Exposure() self.telescope.add_spectrograph(self.spectrograph) self.spectrograph.add_exposure(self.exposure)
def derived_bandpass(self): """ Calculate the bandpasses. """ #Convert to Quantity for calculations. pivotwave, bandpass_r = self.recover('pivotwave','bandpass_r') #serialize with JsonUnit for transportation. return pre_encode(pivotwave / bandpass_r)
def diff_limit_fwhm(self): """ Diffraction-limited PSF FWHM. """ diff_limit_wavelength, aperture = self.recover('diff_limit_wavelength', 'aperture') #result = (1.22 * u.rad * diff_limit_wavelength / aperture).to(u.arcsec) result = (1.03 * u.rad * diff_limit_wavelength / aperture).to(u.arcsec) return pre_encode(result)
def controller(self, attr, old, new): """ Callback to recalculate everything for the figures whenever an input's value is changed """ #Grab values from the inputs self.exptime = pre_encode(self.refs["exp_slider"].value * u.hour) self.renorm_magnitude = pre_encode(self.refs["mag_slider"].value * u.mag('AB')) self.redshift = pre_encode(self.refs["red_slider"].value) self.aperture = pre_encode(self.refs["ap_slider"].value * u.m) temp = self.template_options.index(self.refs["template_select"].value) self.spectrum_type = self.templates[temp] grat = self.grating_options.index(self.refs["grating_select"].value) self.grating = self.gratings[grat] print('grating: ',self.refs["grating_select"].value) print('template: ',self.refs["template_select"].value) print('options template :',self.template_options.index(self.refs["template_select"].value)) #update the exposure and grab all our relevant values. self.update_exposure() snr = self._snr #SNR at bwave bwave, bflux = self.background_wave, self.background_flux twave, tflux = self.template_wave, self.template_flux self.refs["flux_yrange"].start = 0. self.refs["flux_yrange"].end = 1.5 * tflux.max() #self.refs["flux_xrange"].start = min(bwave.min(), twave.min()) #self.refs["flux_xrange"].end = max(bwave.max(), twave.max()) self.refs["snr_yrange"].start = 0. self.refs["snr_yrange"].end = 1.5 * snr.max() #self.refs["snr_xrange"].start = min(bwave.min(), twave.min()) #self.refs["snr_xrange"].end = max(bwave.max(), twave.max()) self.refs["snr_source"].data = {'y': snr, 'x': bwave} self.refs["spectrum_template"].data = {'x': twave, 'y': tflux} self.refs["instrument_background"].data = {'x':bwave, 'y':bflux} self.refs["red_slider"].start = self.exposure.zmin self.refs["red_slider"].end = self.exposure.zmax
def load_pysfits(spec): fname = spec['file'] band = spec.get('band', 'johnson,v') if use_pathlib: path = Path(fname[0]) for f in fname[1:]: path = path / f abspath = str(path.resolve()) else: abspath = os.path.abspath(os.path.join(*fname)) sp = pys.FileSpectrum(abspath) sp = sp.renorm(30., 'abmag', pys.ObsBandpass(band)) sp.convert('abmag') sp.convert('nm') return pre_encode(sp)
def test_camera(): c = Camera() t = Telescope() t.aperture = pre_encode(15.08 * u.m) t.add_camera(c) sval = [49.70, 11.58, 257.88, 530.75, 962.04, 2075.26, 1819.15, 8058.30, 11806.49, 15819.07] expt = 3600. * u.s qe = c.recover('total_qe') fsky = c._fsky() wav = c.recover("pivotwave") print("Sky counts:") for i in range(c.n_bands): skc = (fsky[i] * expt * qe[i]).value rat = skc / sval[i] * 100. print("{:8.2f} ({:5.1f}% of spreadsheet value) at {:4.0f}".format(skc, rat, wav[i]))
def _ensure_array(self, quant): """ Ensure that the given Quantity is an array, propagating if necessary. """ q = pre_encode(quant) if len(q) < 2: import pdb pdb.set_trace() val = q[1]['value'] if not isinstance(val, list): if self.camera is None: nb = 1 else: nb = self.recover('camera.n_bands') q[1]['value'] = np.full(nb, val).tolist() return q
def fwhm_psf(self): """ Calculate the FWHM of the camera's PSF. """ #Convert to Quantity for calculations. pivotwave, aperture = self.recover('pivotwave', 'telescope.aperture') diff_limit, diff_fwhm = self.recover('telescope.diff_limit_wavelength', 'telescope.diff_limit_fwhm') #fwhm = (1.22 * u.rad * pivotwave / aperture).to(u.arcsec) fwhm = (1.03 * u.rad * pivotwave / aperture).to(u.arcsec) #only use these values where the wavelength is greater than the diffraction limit fwhm = np.where(pivotwave > diff_limit, fwhm, diff_fwhm) * u.arcsec #serialize with JsonUnit for transportation. return pre_encode(fwhm)
def pixel_size(self): """ Compute the pixel size as a function of pivot wavelength. Use the reference band for each channel as the fiducial: U-band for UV and optical, J-band for IR. """ pixsize = np.zeros(self.n_bands, dtype=float) #Convert from JsonUnit to Quantity for calculation purposes. fiducials, aperture = self.recover('fiducials', 'telescope.aperture') for ref, bands in enumerate(self.channels): pxs = (0.5 * fiducials[ref] * u.rad / aperture).to(u.arcsec).value pixsize[bands] = pxs #serialize with JsonUnit for transportation purposes. return pre_encode(pixsize * u.arcsec / u.pix)
def planck(self): """ Planck spectrum for the various wave bands. """ #Convert to Quantities for calculation pivotwave, temperature = self.recover('pivotwave', 'telescope.temperature') wave = pivotwave.to('cm') temp = temperature.to('K') h = const.h.to(u.erg * u.s) # Planck's constant erg s c = const.c.to(u.cm / u.s) # speed of light [cm / s] k = const.k_B.to(u.erg / u.K) # Boltzmann's constant [erg deg K^-1] x = 2. * h * c**2 / wave**5 exponent = (h * c / (wave * k * temp)) result = (x / (np.exp(exponent)-1.)).to(u.erg / u.s / u.cm**3) / u.sr #serialize with JsonUnit for transportation return pre_encode(result)
def load_txtfile(spec): fname = spec['file'] band = spec.get('band', 'johnson,v') if use_pathlib: path = Path(fname[0]) for f in fname[1:]: path = path / f abspath = str(path.resolve()) else: abspath = os.path.abspath(os.path.join(*fname)) tab = asc.read(abspath, names=['wave', 'flux']) sp = pys.ArraySpectrum(wave=tab['wave'], flux=tab['flux'], waveunits='Angstrom', fluxunits='flam') sp = sp.renorm(30., 'abmag', pys.ObsBandpass(band)) sp.convert('abmag') sp.convert('nm') return pre_encode(sp)
def _update_magnitude(self): """ Calculate the limiting magnitude given the desired S/N and exposure time. """ self.camera._print_initcon(self.verbose) #We no longer need to check the inputs, since they are now tracked #attributes instead. #Grab values for calculation (_snr, _exptime, _nexp) = self.recover('snr', 'exptime', 'n_exp') (f0, c_ap, D, dlam) = self.recover('camera.ab_zeropoint', 'camera.ap_corr', 'telescope.effective_aperture', 'camera.derived_bandpass') (QE, RN, DC) = self.recover('camera.total_qe', 'camera.detector_rn', 'camera.dark_current') exptime = _exptime.to(u.s) D = D.to(u.cm) fsky = self.camera._fsky(verbose=self.verbose) Npix = self.camera._sn_box(self.verbose) c_t = pre_decode(self.camera.c_thermal(verbose=self.verbose)) snr2 = -(_snr**2) a0 = (QE * exptime)**2 b0 = snr2 * QE * exptime c0 = snr2 * ((QE * fsky + c_t + Npix * DC) * exptime + (RN**2 * Npix * _nexp)) k = (-b0 + np.sqrt(b0**2 - 4. * a0 * c0)) / (2. * a0) flux = (4. * k) / (f0 * c_ap * np.pi * D**2 * dlam) self._magnitude = pre_encode(-2.5 * np.log10(flux.value) * u.mag('AB')) return True #completed successfully
def c_thermal(self, verbose=True): """ Calculate the thermal emission counts for the telescope. """ #Convert to Quantities for calculation. (bandpass, pivotwave, aperture, ota_emissivity, total_qe, pixel_size) = self.recover('derived_bandpass', 'pivotwave', 'telescope.effective_aperture', 'telescope.ota_emissivity', 'total_qe', 'pixel_size') box = self._sn_box(verbose) bandwidth = bandpass.to(u.cm) h = const.h.to(u.erg * u.s) # Planck's constant erg s c = const.c.to(u.cm / u.s) # speed of light [cm / s] energy_per_photon = h * c / pivotwave.to(u.cm) / u.ph D = aperture.to(u.cm) # telescope diameter in cm Omega = (pixel_size**2 * box * u.pix).to(u.sr) planck = pre_decode(self.planck) qepephot = total_qe * planck / energy_per_photon if verbose: print('Planck spectrum: {}'.format(nice_print(planck))) print('QE * Planck / E_phot: {}'.format(nice_print(qepephot))) print('E_phot: {}'.format(nice_print(energy_per_photon))) print('Omega: {}'.format(nice_print(Omega))) thermal = (ota_emissivity * planck / energy_per_photon * (np.pi / 4. * D**2) * total_qe * Omega * bandwidth ) #serialize with JsonUnit for transportation return pre_encode(thermal)
class HDI_ETC(SYOTool): tool_prefix = "hdi" save_models = ["telescope", "camera", "exposure"] save_params = { "exptime": None, #slider value "snratio": None, #slider value "renorm_magnitude": None, #slider value "spectrum_type": ("exposure", "sed_id"), "aperture": ("telescope", "aperture"), "user_prefix": None } save_dir = os.path.join(os.environ['LUVOIR_SIMTOOLS_DIR'], 'saves') #must include this to set defaults before the interface is constructed tool_defaults = { 'exptime': pre_encode(1.0 * u.hour), 'snratio': pre_encode(30.0 * u.electron**0.5), 'renorm_magnitude': pre_encode(30.0 * u.mag('AB')), 'aperture': pre_encode(12.0 * u.m), 'spectrum_type': 'fab' } def tool_preinit(self): """ Pre-initialize any required attributes for the interface. """ #initialize engine objects self.telescope = Telescope() self.camera = Camera() self.exposure = Exposure() self.telescope.add_camera(self.camera) self.camera.add_exposure(self.exposure) #set interface variables self.templates = [ 'fab', 'bb', 'o5v', 'b5v', 'g2v', 'm2v', 'orion', 'elliptical', 'sbc', 'starburst', 'ngc1068' ] self.template_options = [SpectralLibrary[t] for t in self.templates] self.help_text = help_text self.snr_hover_tooltip = hover_tooltip.format("S/N") self.mag_hover_tooltip = hover_tooltip.format("Magnitude") self.exp_hover_tooltip = hover_tooltip.format("Exptime") #update default exposure based on tool_defaults self.update_exposure() #Formatting & interface stuff: self.format_string = interface_format self.interface_file = os.path.join(script_dir, "interface.yaml") #For saving calculations self.current_savefile = "" self.overwrite_save = False def tool_postinit(self): """ Need to disable the SNR slider to start with. """ self.refs["snr_slider"].disabled = True #Control methods def tab_change(self, attr, old, new): """ Whenever tabs are switched: - Disable the appropriate input slider(s) - Set self.exposure.unknown, if appropriate """ active_tab = new['value'][0] all_inputs = [ "ap_slider", "exp_slider", "mag_slider", "snr_slider", "template_select" ] inactive = [("snr_slider", ), ("exp_slider", ), ("mag_slider", ), ("ap_slider", "exp_slider", "snr_slider")][active_tab] for inp in all_inputs: self.refs[inp].disabled = inp in inactive #set the correct exposure unknown: if active_tab < 3: self.exposure.unknown = ["snr", "exptime", "magnitude"][active_tab] self.controller(None, None, None) def controller(self, attr, old, new): #Grab values from the inputs self.exptime = pre_encode(self.refs["exp_slider"].value * u.hour) self.renorm_magnitude = pre_encode(self.refs["mag_slider"].value * u.mag('AB')) self.snratio = pre_encode(self.refs["snr_slider"].value * u.electron**0.5) self.aperture = pre_encode(self.refs["ap_slider"].value * u.m) temp = self.template_options.index(self.refs["template_select"].value) self.spectrum_type = self.templates[temp] self.update_exposure() snr = self._snr mag = self._mag exp = self._exp pwave = self._pivotwave #Update the plots' y-range bounds self.refs["snr_figure"].y_range.start = 0 self.refs["snr_figure"].y_range.end = max(1.3 * snr[:-1].max(), 5.) self.refs["exp_figure"].y_range.start = 0 self.refs["exp_figure"].y_range.end = max(1.3 * exp[:-1].max(), 2.) self.refs["mag_figure"].y_range.start = mag.max() + 5. self.refs["mag_figure"].y_range.end = mag.min() - 5. self.refs[ "sed_figure"].y_range.start = self.spectrum_template.flux.max( ) + 5. self.refs["sed_figure"].y_range.end = self.spectrum_template.flux.min( ) - 5. #Update source data self.refs["snr_source_blue"].data = { 'x': pwave[2:-3], 'y': snr[2:-3], 'desc': self.camera.bandnames[2:-3] } self.refs["exp_source_blue"].data = { 'x': pwave[2:-3], 'y': exp[2:-3], 'desc': self.camera.bandnames[2:-3] } self.refs["mag_source_blue"].data = { 'x': pwave[2:-3], 'y': mag[2:-3], 'desc': self.camera.bandnames[2:-3] } self.refs["snr_source_orange"].data = { 'x': pwave[:2], 'y': snr[:2], 'desc': self.camera.bandnames[:2] } self.refs["exp_source_orange"].data = { 'x': pwave[:2], 'y': exp[:2], 'desc': self.camera.bandnames[:2] } self.refs["mag_source_orange"].data = { 'x': pwave[:2], 'y': mag[:2], 'desc': self.camera.bandnames[:2] } self.refs["snr_source_red"].data = { 'x': pwave[-3:], 'y': snr[-3:], 'desc': self.camera.bandnames[-3:] } self.refs["exp_source_red"].data = { 'x': pwave[-3:], 'y': exp[-3:], 'desc': self.camera.bandnames[-3:] } self.refs["mag_source_red"].data = { 'x': pwave[-3:], 'y': mag[-3:], 'desc': self.camera.bandnames[-3:] } self.refs["spectrum_template"].data = { 'x': self.template_wave, 'y': self.template_flux } def update_exposure(self): """ Update the exposure's parameters and recalculate everything. """ #We turn off calculation at the beginning so we can update everything #at once without recalculating every time we change something self.exposure.disable() #Update all the parameters self.exposure.n_exp = 3 self.exposure.exptime = pre_decode(self.exptime) self.exposure.snr = pre_decode(self.snratio) self.exposure.sed_id = self.spectrum_type self.telescope.aperture = self.aperture self.exposure.renorm_sed(pre_decode(self.renorm_magnitude)) #Now we turn calculations back on and recalculate self.exposure.enable() #Set the spectrum template self.spectrum_template = pre_decode(self.exposure.sed) @property def template_wave(self): return self.exposure.recover('sed').wave @property def template_flux(self): return self.exposure.recover('sed').flux #Conversions to avoid Bokeh Server trying to serialize Quantities @property def _pivotwave(self): return self.camera.recover('pivotwave').value @property def _snr(self): return self.exposure.recover('snr').value @property def _mag(self): return self.exposure.recover('magnitude').value @property def _exp(self): exp = self.exposure.recover('exptime').to(u.h) return exp.value def update_toggle(self, active): if active: self.refs["user_prefix"].value = self.user_prefix self.refs["user_prefix"].disabled = True self.refs["save_button"].disabled = False self.overwrite_save = True else: self.refs["user_prefix"].disabled = False self.refs["save_button"].disabled = False self.overwrite_save = False #Save and Load def save(self): """ Save the current calculations. """ #Check for an existing save file if we're overwriting if self.overwrite_save and self.current_savefile: self.current_savefile = self.save_file(self.current_savefile, overwrite=True) else: #Set the user prefix from the bokeh interface prefix = self.refs["user_prefix"].value if not prefix.isalpha() or len(prefix) < 3: self.refs["save_message"].text = "Please include a prefix of at "\ "least 3 letters (and no other characters)." return self.user_prefix = prefix #Save the file: self.current_savefile = self.save_file() #Tell the user the filename or the error message. if not self.current_savefile: self.refs["save_message"].text = "Save unsuccessful; please " \ "contact the administrators." return self.refs["save_message"].text = "This calculation was saved with " \ "the ID {}".format(self.current_savefile) self.refs["update_save"].disabled = False def load(self): # Get the filename from the bokeh interface calcid = self.refs["load_filename"].value #Load the file code = self.load_file(calcid) if not code: #everything went fine #Update the interface self.refs["update_save"].disabled = False self.current_save = calcid self.refs["exp_slider"].value = pre_decode(self.exptime).value self.refs["mag_slider"].value = pre_decode( self.renorm_magnitude).value self.refs["ap_slider"].value = pre_decode(self.aperture).value self.refs["snr_slider"].value = pre_decode(self.snratio).value temp = self.templates.index(self.spectrum_type) self.refs["template_select"].value = self.template_options[temp] self.controller(None, None, None) errmsg = [ "Calculation ID {} loaded successfully.".format(calcid), "Calculation ID {} does not exist, please try again.".format( calcid), "Load unsuccessful; please contact the administrators.", "There was an error restoring the save state; please contact" " the administrators." ][code] if code == 0 and self.load_mismatch: errmsg += "<br><br><b><i>Load Mismatch Warning:</b></i> "\ "The saved model parameters did not match the " \ "parameter values saved for this tool. Saved " \ "model values were given priority." self.refs["load_message"].text = errmsg
def controller(self, attr, old, new): #Grab values from the inputs self.exptime = pre_encode(self.refs["exp_slider"].value * u.hour) self.renorm_magnitude = pre_encode(self.refs["mag_slider"].value * u.mag('AB')) self.snratio = pre_encode(self.refs["snr_slider"].value * u.electron**0.5) self.aperture = pre_encode(self.refs["ap_slider"].value * u.m) temp = self.template_options.index(self.refs["template_select"].value) self.spectrum_type = self.templates[temp] self.update_exposure() snr = self._snr mag = self._mag exp = self._exp pwave = self._pivotwave #Update the plots' y-range bounds self.refs["snr_figure"].y_range.start = 0 self.refs["snr_figure"].y_range.end = max(1.3 * snr[:-1].max(), 5.) self.refs["exp_figure"].y_range.start = 0 self.refs["exp_figure"].y_range.end = max(1.3 * exp[:-1].max(), 2.) self.refs["mag_figure"].y_range.start = mag.max() + 5. self.refs["mag_figure"].y_range.end = mag.min() - 5. self.refs[ "sed_figure"].y_range.start = self.spectrum_template.flux.max( ) + 5. self.refs["sed_figure"].y_range.end = self.spectrum_template.flux.min( ) - 5. #Update source data self.refs["snr_source_blue"].data = { 'x': pwave[2:-3], 'y': snr[2:-3], 'desc': self.camera.bandnames[2:-3] } self.refs["exp_source_blue"].data = { 'x': pwave[2:-3], 'y': exp[2:-3], 'desc': self.camera.bandnames[2:-3] } self.refs["mag_source_blue"].data = { 'x': pwave[2:-3], 'y': mag[2:-3], 'desc': self.camera.bandnames[2:-3] } self.refs["snr_source_orange"].data = { 'x': pwave[:2], 'y': snr[:2], 'desc': self.camera.bandnames[:2] } self.refs["exp_source_orange"].data = { 'x': pwave[:2], 'y': exp[:2], 'desc': self.camera.bandnames[:2] } self.refs["mag_source_orange"].data = { 'x': pwave[:2], 'y': mag[:2], 'desc': self.camera.bandnames[:2] } self.refs["snr_source_red"].data = { 'x': pwave[-3:], 'y': snr[-3:], 'desc': self.camera.bandnames[-3:] } self.refs["exp_source_red"].data = { 'x': pwave[-3:], 'y': exp[-3:], 'desc': self.camera.bandnames[-3:] } self.refs["mag_source_red"].data = { 'x': pwave[-3:], 'y': mag[-3:], 'desc': self.camera.bandnames[-3:] } self.refs["spectrum_template"].data = { 'x': self.template_wave, 'y': self.template_flux }
class POLLUX_IMAGE(SYOTool): tool_prefix = "pollux" save_models = [ "telescope", "camera", "spectrograph", "spectropolarimeter", "exposure" ] save_params = { "redshift": None, #slider value "renorm_magnitude": None, #slider value "exptime": None, #slider value "grating": ("spectropolarimeter", "mode"), #drop-down selection "aperture": ("telescope", "aperture"), #slider value "spectrum_type": ("exposure", "sed_id"), #drop-down selection "user_prefix": None } save_dir = os.path.join(os.environ['LUVOIR_SIMTOOLS_DIR'], 'saves') #must include this to set defaults before the interface is constructed tool_defaults = { 'redshift': pre_encode(0.25 * u.dimensionless_unscaled), 'renorm_magnitude': pre_encode(21.0 * u.mag('AB')), 'exptime': pre_encode(1.0 * u.hour), 'grating': "NUV_POL", 'aperture': pre_encode(15.0 * u.m), 'spectrum_type': 'qso' } def __init__(self): print('Initializing the parameters ...') self.grating = grating_pollux self.aperture = ap_pollux self.exptime = expt_pollux self.redshift = pre_encode(red_pollux[1]['value']) self.renorm_magnitude = mag_pollux self.spectrum_type = spectrum_pollux self.tool_preinit() self.photon_count = photon_count self.EM_gain = gain self.show_plot = show_plot self.file_name = file_name if snr_pollux == 1: self.make_image = False self.update_exposure() self.plot_snr() print('Saving the file at', basedir, '/pollux_tool/files/') np.savetxt( basedir + '/pollux_tool/files/' + self.file_name + '_' + self.grating + '_snr_data.txt', np.array((self.background_wave, self._snr)).T) if image_pollux == 1: self.make_image = True self.update_exposure() self.plot_image() print('Saving the file at', basedir, '/pollux_tool/files/') np.savetxt( basedir + '/pollux_tool/files/' + self.file_name + '_' + self.grating + '_2dimage_data.txt', self._final_image) np.savetxt( basedir + '/pollux_tool/files/' + self.file_name + '_' + self.grating + '_2dimage_wavelength.txt', self._wave_image) def tool_preinit(self): """ Pre-initialize any required attributes for the interface. """ #initialize engine objects self.telescope = Telescope(temperature=pre_encode(280.0 * u.K)) self.spectrograph = Spectropolarimeter() self.exposure = Exposure() self.telescope.add_spectrograph(self.spectrograph) self.spectrograph.add_exposure(self.exposure) #set interface variables tool_postinit = None def update_exposure(self): """ Update the exposure's parameters and recalculate everything. """ #We turn off calculation at the beginning so we can update everything #at once without recalculating every time we change something #Update all the parameters self.telescope.aperture = self.aperture self.spectrograph.mode = self.grating self.exposure.exptime = pre_decode(self.exptime) self.exposure.redshift = pre_decode(self.redshift) self.exposure.sed_id = self.spectrum_type self.exposure.renorm_sed(pre_decode(self.renorm_magnitude), bandpass='******') #Now we turn calculations back on and recalculate self.exposure.enable(make_image=self.make_image, photon_count=self.photon_count, gain=self.EM_gain) #Set the spectrum template self.spectrum_template = pre_decode(self.exposure.sed) def plot_snr(self): """ """ plt.figure(1) plt.subplot(211) plt.plot(self.template_wave, self.template_flux, '-b', label='source') plt.plot(self.background_wave, self.background_flux, '--k', label='background') plt.legend(loc='upper right', fontsize='large', frameon=False) plt.xlim(900, 4000.) #plt.xlabel('Wavelangth [A]', fontsize='x-large') plt.ylabel('Flux [erg/s/cm^2/A]', fontsize='x-large') plt.subplot(212) plt.plot(self.background_wave, self._snr, '-b') plt.xlim(900, 4000.) plt.xlabel('Wavelangth [A]', fontsize='x-large') plt.ylabel('S/N per resel', fontsize='x-large') if show_plot == 1: print('Saving the image at', basedir, '/pollux_tool/plots/') plt.savefig(basedir + '/pollux_tool/plots/' + self.file_name + '_' + self.grating + '_snr_plot.png', dpi=300) plt.show() def plot_image(self): """ """ #if self.grating[0] == 'F': #plt.imshow(np.log10(self._final_image), origin='lower left', cmap='hot', interpolation='none',aspect='auto') #else: plt.imshow(self._final_image, origin='lower left', cmap='hot', interpolation='none', aspect='auto') plt.colorbar() if show_plot == 1: print('Saving the image at', basedir, '/pollux_tool/plots/') plt.savefig(basedir + '/pollux_tool/plots/' + self.file_name + '_' + self.grating + '_2dimage_plot.png', dpi=300) plt.show() @property def template_wave(self): """ Easy SED wavelength access for the Bokeh widgets. """ return self.exposure.recover('sed').wave @property def template_flux(self): """ Easy SED flux access for the Bokeh widgets. """ sed = self.exposure.recover('sed') wave = sed.wave * u.Unit(sed.waveunits.name) if sed.fluxunits.name == "abmag": funit = u.ABmag elif sed.fluxunits.name == "photlam": funit = u.ph / u.s / u.cm**2 / u.AA else: funit = u.Unit(sed.fluxunits.name) flux = (sed.flux * funit).to(u.erg / u.s / u.cm**2 / u.AA, equivalencies=u.spectral_density(wave)) return flux.value @property def background_wave(self): """ Easy instrument wavelength access for the Bokeh widgets. """ bwave = self.spectrograph.recover('wave').to(u.AA) return bwave.value @property def background_flux(self): """ Easy instrument background flux access for the Bokeh widgets. """ bef = self.spectrograph.recover('bef').to(u.erg / u.s / u.cm**2 / u.AA) return bef.value @property def _snr(self): return np.nan_to_num(self.exposure.recover('snr').value) @property def _final_image(self): return np.nan_to_num(self.exposure.recover('final_image')) @property def _wave_image(self): return np.nan_to_num(self.exposure.recover('wave_image'))
"-p", "--photon_counting", help="Set the EMCCD in photon counting mode, 1 compute it, 0 does not", default='0') parser.add_option( "-g", "--EM_gain", help= "Set the EMCCD multiplication gain with numbers from 1 to 1000 (only implemented for the imaging mode). Gain 1 is without multiplication gain ", default='1000') #usage: python main_pollux.py -e 1 -i hr1886 -m 22 -c MUV_SPEC opts, args = parser.parse_args() #print "Loading data ..." expt_pollux = pre_encode(float(opts.exposure_time) * u.hour) ap_pollux = pre_encode(float(opts.aperture) * u.m) red_pollux = pre_encode(float(opts.redshift) * u.dimensionless_unscaled) mag_pollux = pre_encode(float(opts.ABmag) * u.mag('AB')) grating_pollux = str(opts.channel_mode) spectrum_pollux = str(opts.input_sed) snr_pollux = int(opts.snr) image_pollux = int(opts.full_image) photon_count = int(opts.photon_counting) gain = float(opts.EM_gain) show_plot = int(opts.show_plot) file_name = str(opts.file_name) class POLLUX_IMAGE(SYOTool):
class Camera(PersistentModel): """ The basic camera class, which provides parameter storage for optimization. Attributes: #adapted from the original in Telescope.py telescope - the Telescope object associated with the camera exposures - the list of Exposures taken with this camera name - name of the camera (string) n_bands - number of wavelength bands (int) n_channels - number of channels (int) pivotwave - central wavelengths for bands, in nanometers (float array) bandnames - names of bands (string list) channels - grouping of bands into channels [UV, Optical, IR], and indicating the reference band for pixel size (list of tuples) fiducials - fiducial wavelength of the band, for reference (float array) total_qe - total quantum efficiency in each band (float array) ap_corr - magnitude correction factor for aperture size (float array) bandpass_r - resolution in each bandpass (float array) dark_current - dark current values in each band (float array) detector_rn - read noise for the detector in each band (float array) sky_sigma - sky background emission (float array) _default_model - used by PersistentModel The following are attributes I haven't included, and the justification: R_effective - this doesn't seem to be used anywhere """ _default_model = default_camera telescope = None exposures = [] name = '' pivotwave = pre_encode(np.zeros(1, dtype=float) * u.nm) bandnames = [''] channels = [([],0)] fiducials = pre_encode(np.zeros(1, dtype=float) * u.nm) total_qe = pre_encode(np.zeros(1, dtype=float) * u.dimensionless_unscaled) ap_corr = pre_encode(np.zeros(1, dtype=float) * u.dimensionless_unscaled) bandpass_r = pre_encode(np.zeros(1, dtype=float) * u.dimensionless_unscaled) dark_current = pre_encode(np.zeros(1, dtype=float) * (u.electron / u.s / u.pixel)) detector_rn = pre_encode(np.zeros(1, dtype=float) * (u.electron / u.pixel)**0.5) sky_sigma = pre_encode(np.zeros(1, dtype=float) * u.dimensionless_unscaled) @property def pixel_size(self): """ Compute the pixel size as a function of pivot wavelength. Use the reference band for each channel as the fiducial: U-band for UV and optical, J-band for IR. """ pixsize = np.zeros(self.n_bands, dtype=float) #Convert from JsonUnit to Quantity for calculation purposes. fiducials, aperture = self.recover('fiducials', 'telescope.aperture') for ref, bands in enumerate(self.channels): pxs = (0.5 * fiducials[ref] * u.rad / aperture).to(u.arcsec).value pixsize[bands] = pxs #serialize with JsonUnit for transportation purposes. return pre_encode(pixsize * u.arcsec / u.pix) @property def n_bands(self): return len(self.bandnames) @property def n_channels(self): return len(self.channels) @property def derived_bandpass(self): """ Calculate the bandpasses. """ #Convert to Quantity for calculations. pivotwave, bandpass_r = self.recover('pivotwave','bandpass_r') #serialize with JsonUnit for transportation. return pre_encode(pivotwave / bandpass_r) @property def ab_zeropoint(self): """ AB-magnitude zero points as per Marc Postman's equation. """ pivotwave = self.recover('pivotwave').to(u.nm) abzp = 5509900. * (u.photon / u.s / u.cm**2) / pivotwave return pre_encode(abzp) @property def fwhm_psf(self): """ Calculate the FWHM of the camera's PSF. """ #Convert to Quantity for calculations. pivotwave, aperture = self.recover('pivotwave', 'telescope.aperture') diff_limit, diff_fwhm = self.recover('telescope.diff_limit_wavelength', 'telescope.diff_limit_fwhm') #fwhm = (1.22 * u.rad * pivotwave / aperture).to(u.arcsec) fwhm = (1.03 * u.rad * pivotwave / aperture).to(u.arcsec) #only use these values where the wavelength is greater than the diffraction limit fwhm = np.where(pivotwave > diff_limit, fwhm, diff_fwhm) * u.arcsec #serialize with JsonUnit for transportation. return pre_encode(fwhm) def _print_initcon(self, verbose): if verbose: #These are our initial conditions print('Telescope diffraction limit: {}'.format(pre_decode(self.telescope.diff_limit_wavelength))) print('Telescope aperture: {}'.format(pre_decode(self.telescope.aperture))) print('Telescope temperature: {}'.format(pre_decode(self.telescope.temperature))) print('Pivot waves: {}'.format(nice_print(self.pivotwave))) print('Pixel sizes: {}'.format(nice_print(self.pixel_size))) print('AB mag zero points: {}'.format(nice_print(self.ab_zeropoint))) print('Quantum efficiency: {}'.format(nice_print(self.total_qe))) print('Aperture correction: {}'.format(nice_print(self.ap_corr))) print('Bandpass resolution: {}'.format(nice_print(self.bandpass_r))) print('Derived_bandpass: {}'.format(nice_print(self.derived_bandpass))) print('Detector read noise: {}'.format(nice_print(self.detector_rn))) print('Dark rate: {}'.format(nice_print(self.dark_current))) def _fsky(self, verbose=True): """ Calculate the sky flux as per Eq 6 in the SNR equation paper. """ (f0, D, dlam, Phi, fwhm, Sigma) = self.recover('ab_zeropoint', 'telescope.effective_aperture', 'derived_bandpass', 'pixel_size', 'fwhm_psf', 'sky_sigma') D = D.to(u.cm) m = 10.**(-0.4 * Sigma) / u.arcsec**2 Npix = self._sn_box(False) if verbose: print('Sky brightness: {}'.format(nice_print(Sigma))) fsky = f0 * np.pi / 4. * D**2 * dlam * m * (Phi**2 * Npix) * u.pix return fsky def _sn_box(self, verbose): """ Calculate the number of pixels in the SNR computation box. """ (Phi, fwhm_psf) = self.recover('pixel_size', 'fwhm_psf') sn_box = np.round(3. * fwhm_psf / Phi) if verbose: print('PSF width: {}'.format(nice_print(fwhm_psf))) print('SN box width: {}'.format(nice_print(sn_box))) return sn_box**2 / u.pix #don't want pix**2 units def c_thermal(self, verbose=True): """ Calculate the thermal emission counts for the telescope. """ #Convert to Quantities for calculation. (bandpass, pivotwave, aperture, ota_emissivity, total_qe, pixel_size) = self.recover('derived_bandpass', 'pivotwave', 'telescope.effective_aperture', 'telescope.ota_emissivity', 'total_qe', 'pixel_size') box = self._sn_box(verbose) bandwidth = bandpass.to(u.cm) h = const.h.to(u.erg * u.s) # Planck's constant erg s c = const.c.to(u.cm / u.s) # speed of light [cm / s] energy_per_photon = h * c / pivotwave.to(u.cm) / u.ph D = aperture.to(u.cm) # telescope diameter in cm Omega = (pixel_size**2 * box * u.pix).to(u.sr) planck = pre_decode(self.planck) qepephot = total_qe * planck / energy_per_photon if verbose: print('Planck spectrum: {}'.format(nice_print(planck))) print('QE * Planck / E_phot: {}'.format(nice_print(qepephot))) print('E_phot: {}'.format(nice_print(energy_per_photon))) print('Omega: {}'.format(nice_print(Omega))) thermal = (ota_emissivity * planck / energy_per_photon * (np.pi / 4. * D**2) * total_qe * Omega * bandwidth ) #serialize with JsonUnit for transportation return pre_encode(thermal) @property def planck(self): """ Planck spectrum for the various wave bands. """ #Convert to Quantities for calculation pivotwave, temperature = self.recover('pivotwave', 'telescope.temperature') wave = pivotwave.to('cm') temp = temperature.to('K') h = const.h.to(u.erg * u.s) # Planck's constant erg s c = const.c.to(u.cm / u.s) # speed of light [cm / s] k = const.k_B.to(u.erg / u.K) # Boltzmann's constant [erg deg K^-1] x = 2. * h * c**2 / wave**5 exponent = (h * c / (wave * k * temp)) result = (x / (np.exp(exponent)-1.)).to(u.erg / u.s / u.cm**3) / u.sr #serialize with JsonUnit for transportation return pre_encode(result) def interpolate_at_bands(self, sed): """ Interpolate an SED to obtain magnitudes for the camera's wavebands. """ return mag_from_sed(sed, self) def create_exposure(self): new_exposure = PhotometricExposure() self.add_exposure(new_exposure) return new_exposure def add_exposure(self, exposure): self.exposures.append(exposure) exposure.camera = self exposure.telescope = self.telescope exposure.calculate()