Exemplo n.º 1
0
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
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
    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)
Exemplo n.º 5
0
 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
Exemplo n.º 6
0
 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)
Exemplo n.º 7
0
 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))
Exemplo n.º 8
0
 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)
Exemplo n.º 9
0
 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)
Exemplo n.º 10
0
 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))
Exemplo n.º 11
0
 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
Exemplo n.º 12
0
 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))
Exemplo n.º 13
0
 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)
Exemplo n.º 14
0
 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)
Exemplo n.º 15
0
    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)
Exemplo n.º 16
0
 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
Exemplo n.º 17
0
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)
Exemplo n.º 18
0
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]))
Exemplo n.º 19
0
    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
Exemplo n.º 20
0
 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)
Exemplo n.º 21
0
 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)
Exemplo n.º 22
0
 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)
Exemplo n.º 23
0
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)
Exemplo n.º 24
0
    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
Exemplo n.º 25
0
 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) 
Exemplo n.º 26
0
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
Exemplo n.º 27
0
    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
        }
Exemplo n.º 28
0
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'))
Exemplo n.º 29
0
        "-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):
Exemplo n.º 30
0
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()