예제 #1
0
    def photons_in_range(self,
                         wmin=None,
                         wmax=None,
                         area=1 * u.cm**2,
                         filter_curve=None):
        """
        Return the number of photons between wave_min and wave_max or within
        a bandpass (filter)

        Parameters
        ----------
        wmin :
            [Angstrom]
        wmax :
            [Angstrom]
        area : u.Quantity
            [cm2]
        filter_curve : str
                Name of a filter from
                - a generic filter name (see ``DEFAULT_FILTERS``)
                - a spanish-vo filter service reference (e.g. ``"Paranal/HAWKI.Ks"``)
                - a filter in the spextra database
                - the path to the file containing the filter (see ``Passband``)
                - a ``Passband`` or ``synphot.SpectralElement`` object

        Returns
        -------
        counts : u.Quantity array

        """
        if isinstance(area, u.Quantity):
            area = area.to(u.cm**2).value  #
        if isinstance(wmin, u.Quantity):
            wmin = wmin.to(u.Angstrom).value
        if isinstance(wmax, u.Quantity):
            wmin = wmax.to(u.Angstrom).value

        if filter_curve is None:
            # this makes a bandpass out of wmin and wmax
            try:
                mid_point = 0.5 * (wmin + wmax)
                width = abs(wmax - wmin)
                filter_curve = SpectralElement(Box1D,
                                               amplitude=1,
                                               x_0=mid_point,
                                               width=width)
            except ValueError("Please specify wmin/wmax or a filter"):
                raise

        elif os.path.exists(filter_curve):
            filter_curve = Passband.from_file(filename=filter_curve)
        elif isinstance(filter_curve, (Passband, SpectralElement)):
            filter_curve = filter_curve
        else:
            filter_curve = Passband(filter_curve)

        obs = Observation(self, filter_curve)
        counts = obs.countrate(area=area * u.cm**2)

        return counts
예제 #2
0
def calcMagFromSpec(bp, specPath, redden=None):
    sp = SourceSpectrum.from_file(specPath)
    if redden:
        sp = sp * redden
    obs = Observation(sp, bp)
    counts = obs.countrate(1.0)
    return -2.5 * np.log10(counts.value)
예제 #3
0
class CommCase(object):
    """Base class for commissioning tests."""
    obsmode = None  # Observation mode string
    spectrum = None  # SYNPHOT-like string to construct spectrum
    force = None

    # Default tables are the latest available as of 2016-07-25.
    tables = {
        'graphtable': os.path.join('mtab$OLD_FILES', '07r1502mm_tmg.fits'),
        'comptable': os.path.join('mtab$OLD_FILES', '07r1502nm_tmc.fits'),
        'thermtable': 'mtab$tae17277m_tmt.fits'
    }

    def setup_class(self):
        """Subclass needs to define ``obsmode`` and ``spectrum``
        class variables for this to work.

        """
        if not HAS_PYSYNPHOT:
            raise ImportError(
                'ASTROLIB PYSYNPHOT must be installed to run these tests')

        # Make sure both software use the same graph and component tables.

        conf.graphtable = self.tables['graphtable']
        conf.comptable = self.tables['comptable']
        conf.thermtable = self.tables['thermtable']

        S.setref(graphtable=self.tables['graphtable'],
                 comptable=self.tables['comptable'],
                 thermtable=self.tables['thermtable'])

        # Construct spectra for both software.

        self.sp = parse_spec(self.spectrum)
        self.bp = band(self.obsmode)

        # Astropy version has no prior knowledge of instrument-specific
        # binset, so it has to be set explicitly.
        if hasattr(self.bp, 'binset'):
            self.obs = Observation(self.sp,
                                   self.bp,
                                   force=self.force,
                                   binset=self.bp.binset)
        else:
            self.obs = Observation(self.sp, self.bp, force=self.force)

        # Astropy version does not assume a default waveset
        # (you either have it or you don't). If there is no
        # waveset, no point comparing obs waveset against ASTROLIB.
        if self.sp.waveset is None or self.bp.waveset is None:
            self._has_obswave = False
        else:
            self._has_obswave = True

        self.spref = old_parse_spec(self.spectrum)
        self.bpref = S.ObsBandpass(self.obsmode)
        self.obsref = S.Observation(self.spref, self.bpref, force=self.force)

        # Ensure we are comparing in the same units
        self.bpref.convert(self.bp._internal_wave_unit.name)
        self.spref.convert(self.sp._internal_wave_unit.name)
        self.spref.convert(self.sp._internal_flux_unit.name)
        self.obsref.convert(self.obs._internal_wave_unit.name)
        self.obsref.convert(self.obs._internal_flux_unit.name)

    @staticmethod
    def _get_new_wave(sp):
        """Astropy version does not assume a default waveset
        (you either have it or you don't). This is a convenience
        method to duck-type ASTROLIB waveset behavior.
        """
        wave = sp.waveset
        if wave is None:
            wave = conf.waveset_array
        else:
            wave = wave.value
        return wave

    def _assert_allclose(self,
                         actual,
                         desired,
                         rtol=1e-07,
                         atol=sys.float_info.min):
        """``assert_allclose`` only report percentage but we
        also want to know some extra info conveniently."""
        if isinstance(actual, Iterable):
            ntot = len(actual)
        else:
            ntot = 1

        n = np.count_nonzero(
            abs(actual - desired) > atol + rtol * abs(desired))
        msg = 'obsmode: {0}\nspectrum: {1}\n(mismatch {2}/{3})'.format(
            self.obsmode, self.spectrum, n, ntot)
        assert_allclose(actual, desired, rtol=rtol, atol=atol, err_msg=msg)

    # TODO: Confirm whether non-default atol is acceptable.
    #       Have to use this value to avoid AssertionError for very
    #       small non-zero flux values like 1.8e-26 to 2e-311.
    def _compare_nonzero(self, new, old, thresh=0.01, atol=1e-29):
        """Compare normally when results from both are non-zero."""
        i = (new != 0) & (old != 0)

        # Make sure non-zero atol is not too high, otherwise just let it fail.
        if atol > (thresh * min(new.max(), old.max())):
            atol = sys.float_info.min

        self._assert_allclose(new[i], old[i], rtol=thresh, atol=atol)

    def _compare_zero(self, new, old, thresh=0.01):
        """Special handling for comparison when one of the results
        is zero. This is because ``rtol`` will not work."""
        i = ((new == 0) | (old == 0)) & (new != old)
        try:
            self._assert_allclose(new[i], old[i], rtol=thresh)
        except AssertionError as e:
            pytest.xfail(str(e))  # TODO: Will revisit later

    def test_band_wave(self, thresh=0.01):
        """Test bandpass waveset."""
        wave = self._get_new_wave(self.bp)
        self._assert_allclose(wave, self.bpref.wave, rtol=thresh)

    def test_spec_wave(self, thresh=0.01):
        """Test source spectrum waveset."""
        wave = self._get_new_wave(self.sp)

        # TODO: Failure due to different wavesets for blackbody; Ignore?
        try:
            self._assert_allclose(wave, self.spref.wave, rtol=thresh)
        except (AssertionError, ValueError) as e:
            self._has_obswave = False  # Skip obs waveset tests
            if 'bb(' in self.spectrum:
                pytest.xfail('Blackbody waveset implementations are different')
            elif 'unit(' in self.spectrum:
                pytest.xfail('Flat does not use default waveset anymore')
            else:
                raise

    def test_obs_wave(self, thresh=0.01):
        """Test observation waveset."""
        if not self._has_obswave:  # Nothing to test
            return

        # Native
        wave = self.obs.waveset.value

        # TODO: Failure due to different wavesets for blackbody; Ignore?
        try:
            self._assert_allclose(wave, self.obsref.wave, rtol=thresh)
        except (AssertionError, ValueError) as e:
            if 'bb(' in self.spectrum:
                pytest.xfail('Blackbody waveset implementations are different')
            elif 'unit(' in self.spectrum:
                self._has_obswave = False  # Skip binned flux test
                pytest.xfail('Flat does not use default waveset anymore')
            else:
                raise

        # Binned
        binset = self.obs.binset.value
        self._assert_allclose(binset, self.obsref.binwave, rtol=thresh)

    @pytest.mark.parametrize('thrutype', ['zero', 'nonzero'])
    def test_band_thru(self, thrutype, thresh=0.01):
        """Test bandpass throughput, which is always between 0 and 1."""
        wave = self.bpref.wave
        thru = self.bp(wave).value

        if thrutype == 'zero':
            self._compare_zero(thru, self.bpref.throughput, thresh=thresh)
        else:  # nonzero
            self._compare_nonzero(thru, self.bpref.throughput, thresh=thresh)

    @pytest.mark.parametrize('fluxtype', ['zero', 'nonzero'])
    def test_spec_flux(self, fluxtype, thresh=0.01):
        """Test flux for source spectrum in PHOTLAM."""
        wave = self.spref.wave
        flux = self.sp(wave).value

        if fluxtype == 'zero':
            self._compare_zero(flux, self.spref.flux, thresh=thresh)
        else:  # nonzero
            self._compare_nonzero(flux, self.spref.flux, thresh=thresh)

    @pytest.mark.parametrize('fluxtype', ['zero', 'nonzero'])
    def test_obs_flux(self, fluxtype, thresh=0.01):
        """Test flux for observation in PHOTLAM."""
        wave = self.obsref.wave
        flux = self.obs(wave).value

        # Native
        if fluxtype == 'zero':
            self._compare_zero(flux, self.obsref.flux, thresh=thresh)
        else:  # nonzero
            self._compare_nonzero(flux, self.obsref.flux, thresh=thresh)

        if not self._has_obswave:  # Do not compare binned flux
            return

        # Binned (cannot be resampled)
        binflux = self.obs.binflux.value
        if fluxtype == 'zero':
            self._compare_zero(binflux, self.obsref.binflux, thresh=thresh)
        else:  # nonzero
            try:
                self._compare_nonzero(binflux,
                                      self.obsref.binflux,
                                      thresh=thresh)
            except AssertionError as e:
                if 'unit(' in self.spectrum:
                    pytest.xfail('Flat does not use default waveset anymore:\n'
                                 '{0}'.format(str(e)))
                else:
                    raise

    def test_countrate(self, thresh=0.01):
        """Test observation countrate calculations."""
        ans = self.obsref.countrate()

        # Astropy version does not assume a default area.
        val = self.obs.countrate(conf.area).value

        self._assert_allclose(val, ans, rtol=thresh)

    def test_efflam(self, thresh=0.01):
        """Test observation effective wavelength."""
        ans = self.obsref.efflam()
        val = self.obs.effective_wavelength().value
        self._assert_allclose(val, ans, rtol=thresh)

    def teardown_class(self):
        """Reset config for both software."""
        for cfgname in self.tables:
            conf.reset(cfgname)

        S.setref()
def test_spectra_rescaling():
    """Test the functionality for rescaling input spectra to a given
    magnitude in a given filter
    """
    # JWST primary mirror area in cm^2. Needed for countrate check
    # at the end
    primary_area = 25.326 * (u.m * u.m)

    # Create spectrum: one source to be normalized
    # and the other should not be
    waves = np.arange(0.4, 5.6, 0.01)
    flux = np.repeat(1e-16, len(waves))
    flux2 = np.repeat(4.24242424242e-18, len(waves))  # arbitrary value
    spectra = {
        1: {
            "wavelengths": waves * u.micron,
            "fluxes": flux * u.pct
        },
        2: {
            "wavelengths": waves * u.micron,
            "fluxes": flux2 * units.FLAM
        }
    }

    # Create source catalog containing scaling info
    catalog = Table()
    catalog['index'] = [1, 2]
    catalog['nircam_f322w2_magnitude'] = [18.] * 2
    catalog['niriss_f090w_magnitude'] = [18.] * 2
    #catalog['fgs_magnitude'] = [18.] * 2

    # Instrument info
    instrument = ['nircam', 'niriss']  # , 'fgs']
    filter_name = ['F322W2', 'F090W']  # , 'N/A']
    module = ['B', 'N']  # , 'F']
    detector = ['NRCA1', 'NIS']  # , 'GUIDER1']

    # Magnitude systems of renormalization magnitudes
    mag_sys = ['vegamag', 'abmag', 'stmag']

    # Loop over options and test each
    for inst, filt, mod, det in zip(instrument, filter_name, module, detector):

        # Extract the appropriate column from the catalog information
        magcol = [col for col in catalog.colnames if inst in col]
        sub_catalog = catalog['index', magcol[0]]

        # Filter throughput files
        filter_thru_file = get_filter_throughput_file(instrument=inst,
                                                      filter_name=filt,
                                                      nircam_module=mod,
                                                      fgs_detector=det)

        # Retrieve the correct gain value that goes with the fluxcal info
        if inst == 'nircam':
            gain = MEAN_GAIN_VALUES['nircam']['lwb']
        elif inst == 'niriss':
            gain = MEAN_GAIN_VALUES['niriss']
        elif inst == 'fgs':
            gain = MEAN_GAIN_VALUES['fgs'][det.lower()]

        # Create filter bandpass object, to be used in the final
        # comparison
        filter_tp = ascii.read(filter_thru_file)
        bp_waves = filter_tp['Wavelength_microns'].data * u.micron
        thru = filter_tp['Throughput'].data
        bandpass = SpectralElement(
            Empirical1D, points=bp_waves, lookup_table=thru) / gain

        # Check the renormalization in all photometric systems
        for magsys in mag_sys:
            rescaled_spectra = spec.rescale_normalized_spectra(
                spectra, sub_catalog, magsys, filter_thru_file, gain)

            # Calculate the countrate associated with the renormalized
            # spectrum through the requested filter
            for dataset in rescaled_spectra:
                if dataset == 1:
                    # This block is for the spectra that are rescaled
                    rescaled_spectrum = SourceSpectrum(
                        Empirical1D,
                        points=rescaled_spectra[dataset]['wavelengths'],
                        lookup_table=rescaled_spectra[dataset]['fluxes'])

                    obs = Observation(rescaled_spectrum,
                                      bandpass,
                                      binset=bandpass.waveset)
                    renorm_counts = obs.countrate(area=primary_area)

                    # Calculate the countrate associated with an object of
                    # matching magnitude
                    if inst != 'fgs':
                        mag_col = '{}_{}_magnitude'.format(
                            inst.lower(), filt.lower())
                    else:
                        mag_col = 'fgs_magnitude'
                    filt_info = spec.get_filter_info([mag_col], magsys)
                    magnitude = catalog[mag_col][dataset - 1]
                    photflam, photfnu, zeropoint, pivot = filt_info[mag_col]
                    check_counts = magnitude_to_countrate(
                        'imaging',
                        magsys,
                        magnitude,
                        photfnu=photfnu.value,
                        photflam=photflam.value,
                        vegamag_zeropoint=zeropoint)

                    if magsys != 'vegamag':
                        # As long as the correct gain is used, AB mag and ST mag
                        # count rates agree very well
                        tolerance = 0.0005
                    else:
                        # Vegamag count rates for NIRISS have a sligtly larger
                        # disagreement. Zeropoints were derived assuming Vega = 0.02
                        # magnitudes. This offset has been added to the rescaling
                        # function, but may not be exact.
                        tolerance = 0.0015

                    # This dataset has been rescaled, so check that the
                    # countrate from the rescaled spectrum matches that from
                    # the magnitude it was rescaled to
                    if isinstance(check_counts, u.quantity.Quantity):
                        check_counts = check_counts.value
                    if isinstance(renorm_counts, u.quantity.Quantity):
                        renorm_counts = renorm_counts.value

                    print(inst, filt, magsys, renorm_counts, check_counts,
                          renorm_counts / check_counts)
                    assert np.isclose(renorm_counts, check_counts, atol=0, rtol=tolerance), \
                        print('Failed assertion: ', inst, filt, magsys, renorm_counts, check_counts,
                              renorm_counts / check_counts)
                elif dataset == 2:
                    # Not rescaled. In this case Mirage ignores the magnitude
                    # value in the catalog, so we can't check against check_counts.
                    # Just make sure that the rescaling function did not
                    # change the spectrum at all
                    assert np.all(spectra[dataset]['fluxes'] ==
                                  rescaled_spectra[dataset]['fluxes'])
예제 #5
0
def calcMag(bp, temp):
    bb = SourceSpectrum(BlackBodyNorm1D, temperature=temp)
    obs = Observation(bb, bp)
    counts = obs.countrate(1.0)
    return -2.5 * np.log10(counts.value)
예제 #6
0
class CommCase(object):
    """Base class for commissioning tests."""
    obsmode = None  # Observation mode string
    spectrum = None  # SYNPHOT-like string to construct spectrum
    force = None

    # Default tables are the latest available as of 2016-07-25.
    tables = {
        'graphtable': os.path.join('mtab$OLD_FILES', '07r1502mm_tmg.fits'),
        'comptable': os.path.join('mtab$OLD_FILES', '07r1502nm_tmc.fits'),
        'thermtable': 'mtab$tae17277m_tmt.fits'}

    def setup_class(self):
        """Subclass needs to define ``obsmode`` and ``spectrum``
        class variables for this to work.

        """
        if not HAS_PYSYNPHOT:
            raise ImportError(
                'ASTROLIB PYSYNPHOT must be installed to run these tests')

        # Make sure both software use the same graph and component tables.

        conf.graphtable = self.tables['graphtable']
        conf.comptable = self.tables['comptable']
        conf.thermtable = self.tables['thermtable']

        S.setref(graphtable=self.tables['graphtable'],
                 comptable=self.tables['comptable'],
                 thermtable=self.tables['thermtable'])

        # Construct spectra for both software.

        self.sp = parse_spec(self.spectrum)
        self.bp = band(self.obsmode)

        # Astropy version has no prior knowledge of instrument-specific
        # binset, so it has to be set explicitly.
        if hasattr(self.bp, 'binset'):
            self.obs = Observation(self.sp, self.bp, force=self.force,
                                   binset=self.bp.binset)
        else:
            self.obs = Observation(self.sp, self.bp, force=self.force)

        # Astropy version does not assume a default waveset
        # (you either have it or you don't). If there is no
        # waveset, no point comparing obs waveset against ASTROLIB.
        if self.sp.waveset is None or self.bp.waveset is None:
            self._has_obswave = False
        else:
            self._has_obswave = True

        self.spref = old_parse_spec(self.spectrum)
        self.bpref = S.ObsBandpass(self.obsmode)
        self.obsref = S.Observation(self.spref, self.bpref, force=self.force)

        # Ensure we are comparing in the same units
        self.bpref.convert(self.bp._internal_wave_unit.name)
        self.spref.convert(self.sp._internal_wave_unit.name)
        self.spref.convert(self.sp._internal_flux_unit.name)
        self.obsref.convert(self.obs._internal_wave_unit.name)
        self.obsref.convert(self.obs._internal_flux_unit.name)

    @staticmethod
    def _get_new_wave(sp):
        """Astropy version does not assume a default waveset
        (you either have it or you don't). This is a convenience
        method to duck-type ASTROLIB waveset behavior.
        """
        wave = sp.waveset
        if wave is None:
            wave = conf.waveset_array
        else:
            wave = wave.value
        return wave

    def _assert_allclose(self, actual, desired, rtol=1e-07,
                         atol=sys.float_info.min):
        """``assert_allclose`` only report percentage but we
        also want to know some extra info conveniently."""
        if isinstance(actual, Iterable):
            ntot = len(actual)
        else:
            ntot = 1

        n = np.count_nonzero(
            abs(actual - desired) > atol + rtol * abs(desired))
        msg = 'obsmode: {0}\nspectrum: {1}\n(mismatch {2}/{3})'.format(
            self.obsmode, self.spectrum, n, ntot)
        assert_allclose(actual, desired, rtol=rtol, atol=atol, err_msg=msg)

    # TODO: Confirm whether non-default atol is acceptable.
    #       Have to use this value to avoid AssertionError for very
    #       small non-zero flux values like 1.8e-26 to 2e-311.
    def _compare_nonzero(self, new, old, thresh=0.01, atol=1e-29):
        """Compare normally when results from both are non-zero."""
        i = (new != 0) & (old != 0)

        # Make sure non-zero atol is not too high, otherwise just let it fail.
        if atol > (thresh * min(new.max(), old.max())):
            atol = sys.float_info.min

        self._assert_allclose(new[i], old[i], rtol=thresh, atol=atol)

    def _compare_zero(self, new, old, thresh=0.01):
        """Special handling for comparison when one of the results
        is zero. This is because ``rtol`` will not work."""
        i = ((new == 0) | (old == 0)) & (new != old)
        try:
            self._assert_allclose(new[i], old[i], rtol=thresh)
        except AssertionError as e:
            pytest.xfail(str(e))  # TODO: Will revisit later

    def test_band_wave(self, thresh=0.01):
        """Test bandpass waveset."""
        wave = self._get_new_wave(self.bp)
        self._assert_allclose(wave, self.bpref.wave, rtol=thresh)

    def test_spec_wave(self, thresh=0.01):
        """Test source spectrum waveset."""
        wave = self._get_new_wave(self.sp)

        # TODO: Failure due to different wavesets for blackbody; Ignore?
        try:
            self._assert_allclose(wave, self.spref.wave, rtol=thresh)
        except (AssertionError, ValueError):
            self._has_obswave = False  # Skip obs waveset tests
            if 'bb(' in self.spectrum:
                pytest.xfail('Blackbody waveset implementations are different')
            elif 'unit(' in self.spectrum:
                pytest.xfail('Flat does not use default waveset anymore')
            else:
                raise

    def test_obs_wave(self, thresh=0.01):
        """Test observation waveset."""
        if not self._has_obswave:  # Nothing to test
            return

        # Native
        wave = self.obs.waveset.value

        # TODO: Failure due to different wavesets for blackbody; Ignore?
        try:
            self._assert_allclose(wave, self.obsref.wave, rtol=thresh)
        except (AssertionError, ValueError):
            if 'bb(' in self.spectrum:
                pytest.xfail('Blackbody waveset implementations are different')
            elif 'unit(' in self.spectrum:
                self._has_obswave = False  # Skip binned flux test
                pytest.xfail('Flat does not use default waveset anymore')
            else:
                raise

        # Binned
        binset = self.obs.binset.value
        self._assert_allclose(binset, self.obsref.binwave, rtol=thresh)

    @pytest.mark.parametrize('thrutype', ['zero', 'nonzero'])
    def test_band_thru(self, thrutype, thresh=0.01):
        """Test bandpass throughput, which is always between 0 and 1."""
        wave = self.bpref.wave
        thru = self.bp(wave).value

        if thrutype == 'zero':
            self._compare_zero(thru, self.bpref.throughput, thresh=thresh)
        else:  # nonzero
            self._compare_nonzero(thru, self.bpref.throughput, thresh=thresh)

    @pytest.mark.parametrize('fluxtype', ['zero', 'nonzero'])
    def test_spec_flux(self, fluxtype, thresh=0.01):
        """Test flux for source spectrum in PHOTLAM."""
        wave = self.spref.wave
        flux = self.sp(wave).value

        if fluxtype == 'zero':
            self._compare_zero(flux, self.spref.flux, thresh=thresh)
        else:  # nonzero
            self._compare_nonzero(flux, self.spref.flux, thresh=thresh)

    @pytest.mark.parametrize('fluxtype', ['zero', 'nonzero'])
    def test_obs_flux(self, fluxtype, thresh=0.01):
        """Test flux for observation in PHOTLAM."""
        wave = self.obsref.wave
        flux = self.obs(wave).value

        # Native
        if fluxtype == 'zero':
            self._compare_zero(flux, self.obsref.flux, thresh=thresh)
        else:  # nonzero
            self._compare_nonzero(flux, self.obsref.flux, thresh=thresh)

        if not self._has_obswave:  # Do not compare binned flux
            return

        # Binned (cannot be resampled)
        binflux = self.obs.binflux.value
        if fluxtype == 'zero':
            self._compare_zero(binflux, self.obsref.binflux, thresh=thresh)
        else:  # nonzero
            try:
                self._compare_nonzero(binflux, self.obsref.binflux,
                                      thresh=thresh)
            except AssertionError as e:
                if 'unit(' in self.spectrum:
                    pytest.xfail('Flat does not use default waveset anymore:\n'
                                 '{0}'.format(str(e)))
                else:
                    raise

    def test_countrate(self, thresh=0.01):
        """Test observation countrate calculations."""
        ans = self.obsref.countrate()

        # Astropy version does not assume a default area.
        val = self.obs.countrate(conf.area).value

        self._assert_allclose(val, ans, rtol=thresh)

    def test_efflam(self, thresh=0.01):
        """Test observation effective wavelength."""
        ans = self.obsref.efflam()
        val = self.obs.effective_wavelength().value
        self._assert_allclose(val, ans, rtol=thresh)

    def teardown_class(self):
        """Reset config for both software."""
        for cfgname in self.tables:
            conf.reset(cfgname)

        S.setref()