Coverage for python/lsst/obs/hsc/makeTransmissionCurves.py: 21%

58 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-26 12:25 +0000

1#!/usr/bin/env python 

2# 

3# LSST Data Management System 

4# 

5# Copyright 2018 AURA/LSST. 

6# 

7# This product includes software developed by the 

8# LSST Project (http://www.lsst.org/). 

9# 

10# This program is free software: you can redistribute it and/or modify 

11# it under the terms of the GNU General Public License as published by 

12# the Free Software Foundation, either version 3 of the License, or 

13# (at your option) any later version. 

14# 

15# This program is distributed in the hope that it will be useful, 

16# but WITHOUT ANY WARRANTY; without even the implied warranty of 

17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

18# GNU General Public License for more details. 

19# 

20# You should have received a copy of the LSST License Statement and 

21# the GNU General Public License along with this program. If not, 

22# see <https://www.lsstcorp.org/LegalNotices/>. 

23# 

24import os 

25import glob 

26import numpy as np 

27 

28from lsst.afw.image import TransmissionCurve 

29from lsst.utils import getPackageDir 

30 

31__all__ = ("getOpticsTransmission", "getSensorTransmission", "getAtmosphereTransmission", 

32 "getFilterTransmission",) 

33 

34DATA_DIR = os.path.join(getPackageDir("obs_subaru"), "hsc", "transmission") 

35 

36HSC_BEGIN = "2012-12-18" # initial date for curves valid for entire lifetime of HSC 

37 

38# This maps the old names used in afw.image.Filter and various NAOJ-side things 

39# to the physical_filter names we've now standardized on. It includes only 

40# broad-band filters; the narrow-band filters are easier to transform without 

41# an explicit mapping (see getPhysicalFilterName). 

42OLD_FILTER_NAME_MAP = {b: f"HSC-{b.upper()}" for b in "grizy"} 

43OLD_FILTER_NAME_MAP.update(r2="HSC-R2", i2="HSC-I2") 

44 

45 

46def getPhysicalFilterName(short): 

47 """Return an HSC physical_filter name (e.g. 'HSC-R') that's usable as a 

48 data ID value from various names used in the transmission data files. 

49 """ 

50 if short.startswith("HSC"): 

51 return short 

52 # Sometimes narrow-band and intermediate-band filters start with NB or IB, 

53 # and we don't want to try to enumerate all of those if we can avoid it. 

54 if short.startswith("NB") or short.startswith("IB"): 

55 num = int(short[2:].lstrip("0")) 

56 return "%s%04d" % (short[:2], num) 

57 # Sometimes the narrow-band filters start with just N instead. 

58 if short.startswith("N"): 

59 num = int(short[1:].lstrip("0")) 

60 return "NB%04d" % (num,) 

61 return OLD_FILTER_NAME_MAP.get(short, short) 

62 

63 

64def readTransmissionCurveFromFile(filename, unit="angstrom", atMin=None, atMax=None): 

65 """Load a spatial TransmissionCurve from a text file with wavelengths and 

66 throughputs in columns. 

67 

68 Parameters 

69 ---------- 

70 filename : `str` 

71 Name of the file to read. 

72 unit : `str` 

73 Wavelength unit; one of "nm" or "angstrom". 

74 atMin : `float` 

75 Throughput to use at wavelengths below the tabulated minimum. If 

76 ``None``, the tabulated throughput at the mininum will be used. 

77 atMax : `float` 

78 Throughput to use at wavelengths above the tabulated maximum. If 

79 ``None``, the tabulated throughput at the maximum will be used. 

80 """ 

81 wavelengths, throughput = np.loadtxt(os.path.join(DATA_DIR, filename), usecols=[0, 1], unpack=True) 

82 i = np.argsort(wavelengths) 

83 wavelengths = wavelengths[i] 

84 throughput = throughput[i] 

85 if unit == "nm": 

86 wavelengths *= 10 

87 elif unit != "angstrom": 

88 raise ValueError("Invalid wavelength unit") 

89 if atMin is None: 

90 atMin = throughput[0] 

91 if atMax is None: 

92 atMax = throughput[-1] 

93 return TransmissionCurve.makeSpatiallyConstant(throughput=throughput, wavelengths=wavelengths, 

94 throughputAtMin=atMin, throughputAtMax=atMax) 

95 

96 

97def getOpticsTransmission(): 

98 """Return a dictionary of TransmissionCurves describing the combined 

99 throughput of HSC and the Subaru primary mirror. 

100 

101 Dictionary keys are string dates (YYYY-MM-DD) indicating the beginning of 

102 the validity period for the curve stored as the associated dictionary 

103 value. If the curve is spatially varying, it will be defined in focal 

104 plane coordinates. 

105 

106 Dictionary values may be None to indicate that no TransmissionCurve is 

107 valid after the date provided in the key. 

108 """ 

109 mirror2010 = readTransmissionCurveFromFile("M1-2010s.txt", unit="nm") 

110 camera = readTransmissionCurveFromFile("throughput_popt2.txt") 

111 camera *= readTransmissionCurveFromFile("throughput_win.txt") 

112 return { 

113 HSC_BEGIN: mirror2010*camera, 

114 "2017-10-01": None # mirror recoating begins, approximately 

115 } 

116 

117 

118def getSensorTransmission(): 

119 """Return a nested dictionary of TransmissionCurves describing the 

120 throughput of each sensor. 

121 

122 Outer directionary keys are string dates (YYYY-MM-DD), with values 

123 a dictionary mapping CCD ID to TransmissionCurve. If the curve 

124 is spatially varying, it will be defined in pixel coordinates. 

125 

126 Outer dictionary values may be None to indicate that no TransmissionCurve 

127 is valid after the date provided in the key. 

128 """ 

129 qe = readTransmissionCurveFromFile("qe_ccd_HSC.txt", atMin=0.0, atMax=0.0) 

130 return {HSC_BEGIN: {n: qe for n in range(112)}} 

131 

132 

133def getAtmosphereTransmission(): 

134 """Return a dictionary of TransmissionCurves describing the atmospheric 

135 throughput at Mauna Kea. 

136 

137 Dictionary keys are string dates (YYYY-MM-DD) indicating the beginning of 

138 the validity period for the curve stored as the associated dictionary 

139 value. The curve is guaranteed not to be spatially-varying. 

140 

141 Dictionary values may be None to indicate that no TransmissionCurve is 

142 valid after the date provided in the key. 

143 """ 

144 average = readTransmissionCurveFromFile("modtran_maunakea_am12_pwv15_binned10ang.dat") 

145 return {HSC_BEGIN: average} 

146 

147 

148def getFilterTransmission(): 

149 """Return a nested dictionary of TransmissionCurves describing the 

150 throughput of each HSC filter. 

151 

152 Outer directionary keys are string dates (YYYY-MM-DD), with values 

153 a dictionary mapping filter name to TransmissionCurve. If the curve 

154 is spatially varying, it will be defined in pixel coordinates. 

155 

156 Filter curve names are in the long form used as data ID values (e.g. 

157 'HSC-I'). 

158 

159 Outer dictionary values may be None to indicate that no TransmissionCurve 

160 is valid after the date provided in the key. 

161 """ 

162 module = {} 

163 filename = os.path.join(DATA_DIR, "filterTraces.py") 

164 with open(filename) as file: 

165 exec(compile(file.read(), filename, mode='exec'), module) 

166 result = {} 

167 for band, data in module["FILTER_DATA"].items(): 

168 result[getPhysicalFilterName(band)] = TransmissionCurve.makeRadial( 

169 throughput=data["T"], wavelengths=data["lam"]*10, 

170 radii=data['radius']/module["PIXEL_SIZE"], 

171 throughputAtMin=0.0, throughputAtMax=0.0 

172 ) 

173 for filename in glob.glob(os.path.join(DATA_DIR, "wHSC-*.txt")): 

174 band = getPhysicalFilterName(os.path.split(filename)[1][len("wHSC-"): -len(".txt")]) 

175 if band not in result: 

176 result[band] = readTransmissionCurveFromFile(filename, atMin=0.0, atMax=0.0) 

177 return {HSC_BEGIN: result}