diff --git a/galsim/roman/__init__.py b/galsim/roman/__init__.py index c754e5cb05..8d5aee8fd4 100644 --- a/galsim/roman/__init__.py +++ b/galsim/roman/__init__.py @@ -99,7 +99,7 @@ # Maxinum allowed angle from the telecope solar panels to the sun in degrees. max_sun_angle = 36. -from .roman_bandpass import getBandpasses +from .roman_bandpass import getBandpass, getBandpasses from .roman_backgrounds import getSkyLevel from .roman_psfs import getPSF from .roman_wcs import getWCS, findSCA, allowedPos, bestPA, convertCenter diff --git a/galsim/roman/roman_bandpass.py b/galsim/roman/roman_bandpass.py index 1673ede1dc..e5956d9a40 100644 --- a/galsim/roman/roman_bandpass.py +++ b/galsim/roman/roman_bandpass.py @@ -26,10 +26,11 @@ import os from .. import meta_data -from ..errors import galsim_warn +from ..errors import GalSimValueError, galsim_warn from .. import Bandpass, LookupTable -def getBandpasses(AB_zeropoint=True, default_thin_trunc=True, include_all_bands=False, **kwargs): +def getBandpasses(AB_zeropoint=True, default_thin_trunc=True, include_all_bands=False, bandnames=None, + **kwargs): """Utility to get a dictionary containing the Roman ST bandpasses used for imaging. This routine reads in a file containing a list of wavelengths and throughput for all Roman @@ -96,6 +97,9 @@ def getBandpasses(AB_zeropoint=True, default_thin_trunc=True, include_all_bands= There is currently no estimate for the thermal background for these bands and they are set to zero arbitrarily. [default: False] + bandnames: Iterable of bandpass names to get. If None, it gets all the imaging + bands if ``include_all_bands`` is False, or all bands if + ``include_all_bands`` is True. **kwargs: Other kwargs are passed to either `Bandpass.thin` or `Bandpass.truncate` as appropriate. @@ -109,11 +113,19 @@ def getBandpasses(AB_zeropoint=True, default_thin_trunc=True, include_all_bands= data = np.genfromtxt(datafile, names=True) wave = 1000.*data['Wave'] + if bandnames is None: + bandnames = data.dtype.names[1:] + elif (invalid_bandnames := set(bandnames).difference(data.dtype.names[1:])): + raise GalSimValueError("Invalid Roman bandpasses requested;", + value=invalid_bandnames, + allowed_values=data.dtype.names[1:], + ) + # Read in and manipulate the sky background info. sky_file = os.path.join(meta_data.share_dir, "roman", "roman_sky_backgrounds.txt") - sky_data = np.loadtxt(sky_file).transpose() - ecliptic_lat = sky_data[0, :] - ecliptic_lon = sky_data[1, :] + sky_data = np.genfromtxt(sky_file, names=True) + ecliptic_lat = sky_data['Latitude'] + ecliptic_lon = sky_data['Longitude'] # Parse kwargs for truncation, thinning, etc., and check for nonsense. truncate_kwargs = ['blue_limit', 'red_limit', 'relative_throughput'] @@ -138,7 +150,7 @@ def getBandpasses(AB_zeropoint=True, default_thin_trunc=True, include_all_bands= # Set up a dictionary. bandpass_dict = {} # Loop over the bands. - for index, bp_name in enumerate(data.dtype.names[1:]): + for bp_name in bandnames: if include_all_bands is False and bp_name in non_imaging_bands: continue @@ -160,10 +172,36 @@ def getBandpasses(AB_zeropoint=True, default_thin_trunc=True, include_all_bands= # Store the sky level information as an attribute. bp._ecliptic_lat = ecliptic_lat bp._ecliptic_lon = ecliptic_lon - bp._sky_level = sky_data[2+index, :] + bp._sky_level = sky_data[bp_name] # Add it to the dictionary. bp.name = bp_name if bp_name != 'W149' else 'W146' bandpass_dict[bp.name] = bp return bandpass_dict + +def getBandpass(bandname, AB_zeropoint=True, default_thin_trunc=True, **kwargs): + """Utility to get a single bandpass from the Roman ST bandpasses used. + + If you need to get more than one bandpass, use `getBandpasses` instead. + This function just provides a cleaner interface when only one bandpass + is needed. + + See also `getBandpasses`. + + Parameters: + bandname: Name of the bandpass to get. + AB_zeropoint: Should the routine set an AB zeropoint before returning the bandpass? + If False, then it is up to the user to set a zero point. [default: + True] + default_thin_trunc: Use the default thinning and truncation options? Users who wish to + use no thinning and truncation of bandpasses, or who want control over + the level of thinning and truncation, should have this be False. + [default: True] + **kwargs: Other kwargs are passed to either `Bandpass.thin` or + `Bandpass.truncate` as appropriate. + + @returns A Bandpass object for the specified band. + """ + return getBandpasses(AB_zeropoint=AB_zeropoint, default_thin_trunc=default_thin_trunc, + include_all_bands=True, bandnames=[bandname], **kwargs)[bandname] diff --git a/galsim/roman/roman_config.py b/galsim/roman/roman_config.py index 56ae7adf1d..9b1e522f50 100644 --- a/galsim/roman/roman_config.py +++ b/galsim/roman/roman_config.py @@ -20,7 +20,7 @@ from . import n_pix, exptime, dark_current, read_noise, gain from . import stray_light_fraction, thermal_backgrounds from .roman_psfs import getPSF -from .roman_bandpass import getBandpasses +from .roman_bandpass import getBandpass, getBandpasses from .roman_wcs import getWCS from .roman_backgrounds import getSkyLevel from ..config import ParseAberrations, BandpassBuilder, GetAllParams, GetRNG @@ -112,7 +112,7 @@ def buildBandpass(self, config, base, logger): from ..deprecated import depr depr('W149', 2.5, 'W146', 'Note: this is to match current Roman filter naming schemes') name = 'W146' - bandpass = getBandpasses()[name] + bandpass = getBandpass(name) return bandpass, safe @@ -200,7 +200,7 @@ def setup(self, config, base, image_num, obj_num, ignore, logger): def getBandpass(self, filter_name): if not hasattr(self, 'all_roman_bp'): - self.all_roman_bp = getBandpasses() + self.all_roman_bp = getBandpasses(include_all_bands=True) return self.all_roman_bp[filter_name] def addNoise(self, image, config, base, image_num, obj_num, current_var, logger): diff --git a/galsim/roman/roman_psfs.py b/galsim/roman/roman_psfs.py index 28a63ece4a..2e9a71db21 100644 --- a/galsim/roman/roman_psfs.py +++ b/galsim/roman/roman_psfs.py @@ -22,7 +22,7 @@ from . import pixel_scale, n_pix, pixel_scale_mm from . import n_pix, n_sca, longwave_bands, shortwave_bands from . import diameter, obscuration -from .roman_bandpass import getBandpasses +from .roman_bandpass import getBandpass from ..utilities import LRU_Cache from ..position import PositionD @@ -335,8 +335,7 @@ def _get_single_PSF(SCA, bandpass, SCA_pos, pupil_bin, aper=aper, gsparams=gsparams) if n_waves is not None: # To decide the range of wavelengths to use, check the bandpass. - bp_dict = getBandpasses() - bp = bp_dict[bandpass] + bp = getBandpass(bandpass) PSF = PSF.interpolate(waves=np.linspace(bp.blue_limit, bp.red_limit, n_waves), oversample_fac=1.5) else: diff --git a/share/roman/roman_sky_backgrounds.txt b/share/roman/roman_sky_backgrounds.txt index 4dd8431dce..9230987926 100644 --- a/share/roman/roman_sky_backgrounds.txt +++ b/share/roman/roman_sky_backgrounds.txt @@ -1,3 +1,4 @@ +# Latitude Longitude R062 Z087 Y106 J129 H158 F184 W146 K213 SNPrism Grism_1stOrder Grism_0thOrder 0.0000 0.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 1.3976 0.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 2.7960 0.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 -10.0000 diff --git a/tests/test_roman.py b/tests/test_roman.py index 0d949c6729..60b5a90c74 100644 --- a/tests/test_roman.py +++ b/tests/test_roman.py @@ -513,6 +513,43 @@ def test_roman_nonimaging_bandpass(): assert 'Grism_1stOrder' not in bp_imaging assert 'SNPrism' not in bp_imaging +@timer +def test_roman_bandpass_subsets(): + """Test that we can get a subset of Roman bandpasses. + """ + bandnames = ["J129", "H158"] + bp_dict = galsim.roman.getBandpasses(bandnames=bandnames) + + # Check that the imaging bandpasses are a subset of the all bandpasses + for bandname in bandnames: + assert bandname in bp_dict + bp_dict.pop(bandname) + + # Check that we have no bandpasses left + assert len(bp_dict) == 0 + + # Test that we get an error for invalid bandpasses + with assert_raises(galsim.GalSimValueError): + galsim.roman.getBandpasses(bandnames=["u", "g", "r", "i"]) + +@timer +def test_roman_single_bandpass(): + """Test getting a single bandpass from the Roman bandpass utility. + """ + bp = galsim.roman.getBandpass('F184') + assert bp.name == 'F184' + + # Check that the default values are the same for getBandpass and getBandpasses. + bp_dict = galsim.roman.getBandpasses() + assert bp == bp_dict[bp.name] + + # Explicitly check for the W-band, since that's a special case. + bp = galsim.roman.getBandpass("W146") + assert bp == bp_dict[bp.name] + + # Test for a non-imaging bandpass. + bp = galsim.roman.getBandpass("SNPrism") + assert bp.name == "SNPrism" @timer def test_roman_detectors():