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
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)
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'])
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)
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()