Coverage for python/lsst/obs/lsst/filters.py: 100%

62 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 03:41 -0700

1# This file is part of obs_lsst. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

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

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

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

12# (at your option) any later version. 

13# 

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

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

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

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <http://www.gnu.org/licenses/>. 

21 

22__all__ = ( 

23 "LSSTCAM_FILTER_DEFINITIONS", 

24 "LATISS_FILTER_DEFINITIONS", 

25 "LSSTCAM_IMSIM_FILTER_DEFINITIONS", 

26 "TS3_FILTER_DEFINITIONS", 

27 "TS8_FILTER_DEFINITIONS", 

28 "COMCAM_FILTER_DEFINITIONS", 

29) 

30 

31import re 

32from lsst.obs.base import FilterDefinition, FilterDefinitionCollection 

33from .translators.lsst import FILTER_DELIMITER 

34 

35 

36def addFilter(filter_dict, band, physical_filter, lambdaEff=0.0): 

37 """Define a filter in filter_dict, to be converted to a Filter later""" 

38 

39 # index by band but keep distinct physical filters by band 

40 # since there can be many physical filters for a single band 

41 if band not in filter_dict: 

42 filter_dict[band] = {} 

43 

44 filter_dict[band][physical_filter] = dict(physical_filter=physical_filter, 

45 band=band, lambdaEff=lambdaEff, alias=[], 

46 ) 

47 

48 

49# The LSST Filters from L. Jones 05/14/2020 - "Edges" = 5% of peak throughput 

50# See https://github.com/rhiannonlynne/notebooks/blob/master/Filter%20Characteristics.ipynb # noqa: W505 

51# 

52# N.b. DM-26623 requests that these physical names be updated once 

53# the camera team has decided upon the final values (CAP-617) 

54 

55LsstCamFiltersBaseline = FilterDefinitionCollection( 

56 FilterDefinition(physical_filter="empty", band="white", 

57 lambdaEff=0.0, 

58 alias={"no_filter", "open"}), 

59 FilterDefinition(physical_filter="u", band="u", 

60 lambdaEff=368.48, lambdaMin=320.00, lambdaMax=408.60), 

61 FilterDefinition(physical_filter="g", band="g", 

62 lambdaEff=480.20, lambdaMin=386.40, lambdaMax=567.00), 

63 FilterDefinition(physical_filter="r", band="r", 

64 lambdaEff=623.12, lambdaMin=537.00, lambdaMax=706.00), 

65 FilterDefinition(physical_filter="i", band="i", 

66 lambdaEff=754.17, lambdaMin=676.00, lambdaMax=833.00), 

67 FilterDefinition(physical_filter="z", band="z", 

68 lambdaEff=869.05, lambdaMin=803.00, lambdaMax=938.60), 

69 FilterDefinition(physical_filter="y", band="y", 

70 lambdaEff=973.64, lambdaMin=908.40, lambdaMax=1099.00), 

71) 

72 

73# 

74# Define the filters present in the BOT. 

75# According to Tony Johnson the possible physical filters are 

76# ColorFWheel [SDSSu,SDSSg,SDSSr,SDSSi,SDSSz,SDSSY, 

77# 480nm,650nm,750nm,870nm,950nm,970nm] 

78# SpotProjFWheel [grid,spot,empty3,empty4,empty5,empty6] 

79# NeutralFWheel [ND_OD1.0,ND_OD0.5,ND_OD0.3,empty,ND_OD2.0,ND_OD0.7] 

80# where: 

81# ColorFWheel and SpotProjFWheel are mutually exclusive and 

82# both appear in FILTER, 

83# NeutralFWheel appears in FILTER2 

84# 

85# Experimentally we also see FILTER2 values of: 

86# ['ND_OD0.01', 'ND_OD0.05', 'ND_OD0.4', 'ND_OD3.0', 'ND_OD4.0'] 

87# 

88# The band names are not yet defined, so I'm going to invent them 

89 

90# Map the BOT filters to corresponding band explicitly 

91BOT_filter_map = { 

92 "empty": "white", 

93 "SDSSu": "u", 

94 "SDSSg": "g", 

95 "SDSSr": "r", 

96 "SDSSi": "i", 

97 "SDSSz": "z", 

98 "SDSSY": "y", 

99 "480nm": "g", 

100 "650nm": "r", 

101 "750nm": "i", 

102 "870nm": "z", 

103 "950nm": "y", 

104 "970nm": "y", 

105 "grid": "grid", 

106 "spot": "spot", 

107} 

108 

109BOTFilters_dict = {} 

110for physical_filter, band in BOT_filter_map.items(): 

111 lambdaEff = 0.0 

112 lsstCamFilterMatches = [f for f in LsstCamFiltersBaseline if f.band == band] 

113 if lsstCamFilterMatches: 

114 lambdaEff = lsstCamFilterMatches[0].lambdaEff 

115 

116 if mat := re.match(r"(\d+)nm$", physical_filter): 

117 lambdaEff = float(mat.group(1)) 

118 

119 if physical_filter == "empty": 

120 pass # Already defined above 

121 else: 

122 addFilter(BOTFilters_dict, band, physical_filter, lambdaEff=lambdaEff) 

123 

124 # Empty ND is removed by metadata translator so is not needed here 

125 ndFilters = ["ND_OD0.1", "ND_OD0.3", "ND_OD0.5", "ND_OD0.7", "ND_OD1.0", "ND_OD2.0"] 

126 # We found these additional filters in BOT data files: 

127 ndFilters += ['ND_OD0.01', 'ND_OD0.05', 'ND_OD0.4', 'ND_OD3.0', 'ND_OD4.0'] 

128 

129 for nd in ndFilters: 

130 # fully qualified physical filter 

131 phys_plus_nd = f"{physical_filter}{FILTER_DELIMITER}{nd}" 

132 

133 # When one of the filters is empty we can just use the real filter 

134 # (e.g. "u" not "u~empty"); but we always need at least one "empty" 

135 if band == "white": 

136 # Use the ND on its own 

137 phys_plus_nd = nd 

138 

139 # Use a generic ND modifier for the band 

140 ndband = f"{band}{FILTER_DELIMITER}nd" 

141 

142 addFilter(BOTFilters_dict, band=ndband, physical_filter=phys_plus_nd, lambdaEff=lambdaEff) 

143 

144BOTFilters = [ 

145 FilterDefinition(band="unknown", physical_filter="unknown", lambdaEff=0.0), 

146] 

147for band, physical_filters in BOTFilters_dict.items(): 

148 for physical_filter, filter_defn in physical_filters.items(): 

149 BOTFilters.append(FilterDefinition(**filter_defn)) 

150 

151# 

152# The filters that we might see in the real LSSTCam (including in SLAC) 

153# 

154# Note that the filters we'll use on the sky, LsstCamFiltersBaseline, must 

155# come first as we're not allocating enough bits in _computeCoaddExposureId 

156# for all the BOT composite filters (i.e. "u~ND_OD1.0") 

157# 

158LSSTCAM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

159 *LsstCamFiltersBaseline, 

160 *BOTFilters, 

161) 

162 

163# 

164# Filters in SLAC's Test Stand 3 

165# 

166TS3Filters = [ 

167 FilterDefinition(band="unknown", physical_filter="unknown", lambdaEff=0.0), 

168 FilterDefinition(physical_filter="275CutOn", lambdaEff=0.0), 

169 FilterDefinition(physical_filter="550CutOn", lambdaEff=0.0)] 

170 

171TS3_FILTER_DEFINITIONS = FilterDefinitionCollection( 

172 *LsstCamFiltersBaseline, 

173 *TS3Filters, 

174) 

175# 

176# Filters in SLAC's Test Stand 8 

177# 

178TS8Filters = [ 

179 FilterDefinition(band="unknown", physical_filter="unknown", lambdaEff=0.0), 

180 FilterDefinition(physical_filter="275CutOn", lambdaEff=0.0), 

181 FilterDefinition(physical_filter="550CutOn", lambdaEff=0.0)] 

182 

183TS8_FILTER_DEFINITIONS = FilterDefinitionCollection( 

184 *LsstCamFiltersBaseline, 

185 *TS8Filters, 

186) 

187 

188 

189# LATISS filters include a grating in the name so we need to construct 

190# filters for each combination of filter+grating. 

191_latiss_filters = ( 

192 FilterDefinition(physical_filter="empty", 

193 band="white", 

194 lambdaEff=0.0, 

195 alias={"no_filter", "open"}), 

196 FilterDefinition(physical_filter="blank_bk7_wg05", 

197 band="white", 

198 lambdaEff=0.0), 

199 FilterDefinition(physical_filter="KPNO_1111_436nm", 

200 band="g", 

201 lambdaEff=436.0, lambdaMin=386.0, lambdaMax=486.0), 

202 FilterDefinition(physical_filter="KPNO_373A_677nm", 

203 band="r", 

204 lambdaEff=677.0, lambdaMin=624.0, lambdaMax=730.0), 

205 FilterDefinition(physical_filter="KPNO_406_828nm", 

206 band="z", 

207 lambdaEff=828.0, lambdaMin=738.5, lambdaMax=917.5), 

208 FilterDefinition(physical_filter="diffuser", 

209 band="diffuser", 

210 lambdaEff=0.0), 

211 FilterDefinition(physical_filter="unknown", 

212 band="unknown", 

213 lambdaEff=0.0), 

214 FilterDefinition(physical_filter="BG40", 

215 band="g", 

216 afw_name="bg", 

217 lambdaEff=472.0, lambdaMin=334.5, lambdaMax=609.5), 

218 FilterDefinition(physical_filter="quadnotch1", 

219 lambdaEff=0.0, 

220 band="notch"), 

221 FilterDefinition(physical_filter="RG610", 

222 lambdaEff=0.0, 

223 band="r", 

224 afw_name="rg"), 

225 FilterDefinition(physical_filter="FELH0600", 

226 lambdaEff=0.0, 

227 band="r", 

228 afw_name="rg"), 

229 FilterDefinition(physical_filter="SDSSg", 

230 lambdaEff=477.0, 

231 band="g"), 

232 FilterDefinition(physical_filter="SDSSr", 

233 lambdaEff=623.1, 

234 band="r"), 

235 FilterDefinition(physical_filter="SDSSi", 

236 lambdaEff=762.5, 

237 band="i"), 

238) 

239 

240# Form a new set of filter definitions from all the explicit gratings 

241_latiss_gratings = ("ronchi90lpmm", "ronchi170lpmm", "empty", "unknown", "holo4_003") 

242 

243# Include the filters without the grating in case someone wants 

244# to retrieve a filter by an actual filter name 

245_latiss_filter_and_grating = [f for f in _latiss_filters] 

246 

247for filter in _latiss_filters: 

248 for grating in _latiss_gratings: 

249 # The diffuser "filter" was never used with gratings 

250 # so skip it 

251 if filter.physical_filter == "diffuser": 

252 continue 

253 

254 # FilterDefinition is a frozen dataclass 

255 new_name = FILTER_DELIMITER.join([filter.physical_filter, grating]) 

256 

257 # Also need to update aliases 

258 new_aliases = {FILTER_DELIMITER.join([a, grating]) for a in filter.alias} 

259 

260 # For gratings set the band to the band of the filter 

261 combo = FilterDefinition(physical_filter=new_name, 

262 band=filter.band, 

263 lambdaEff=filter.lambdaEff, 

264 lambdaMin=filter.lambdaMin, 

265 lambdaMax=filter.lambdaMax, 

266 afw_name=filter.afw_name, 

267 alias=new_aliases) 

268 _latiss_filter_and_grating.append(combo) 

269 

270 

271LATISS_FILTER_DEFINITIONS = FilterDefinitionCollection(*_latiss_filter_and_grating) 

272 

273 

274LSSTCAM_IMSIM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

275 # These were computed using throughputs 1.4 and 

276 # lsst.sims.photUtils.BandpassSet. 

277 FilterDefinition(physical_filter="u_sim_1.4", 

278 band="u", 

279 lambdaEff=367.070, lambdaMin=308.0, lambdaMax=408.6), 

280 FilterDefinition(physical_filter="g_sim_1.4", 

281 band="g", 

282 lambdaEff=482.685, lambdaMin=386.5, lambdaMax=567.0), 

283 FilterDefinition(physical_filter="r_sim_1.4", 

284 band="r", 

285 lambdaEff=622.324, lambdaMin=537.0, lambdaMax=706.0), 

286 FilterDefinition(physical_filter="i_sim_1.4", 

287 band="i", 

288 lambdaEff=754.598, lambdaMin=676.0, lambdaMax=833.0), 

289 FilterDefinition(physical_filter="z_sim_1.4", 

290 band="z", 

291 lambdaEff=869.090, lambdaMin=803.0, lambdaMax=938.6), 

292 FilterDefinition(physical_filter="y_sim_1.4", 

293 band="y", 

294 lambdaEff=971.028, lambdaMin=908.4, lambdaMax=1096.3) 

295) 

296 

297# ########################################################################### 

298# 

299# ComCam 

300# 

301# See https://jira.lsstcorp.org/browse/DM-21706 

302 

303ComCamFilters_dict = {} 

304for band, sn in [("u", "SN-05"), # incorrect sub thickness 

305 ("u", "SN-02"), # not yet coated 

306 ("u", "SN-06"), # not yet coated 

307 ("g", "SN-07"), # bad cosmetics 

308 ("g", "SN-01"), 

309 ("r", "SN-03"), 

310 ("i", "SN-06"), 

311 ("z", "SN-03"), 

312 ("z", "SN-02"), # failed specs 

313 ("y", "SN-04"), 

314 ]: 

315 physical_filter = f"{band}_{sn[3:]}" 

316 lsstCamFilter = [f for f in LsstCamFiltersBaseline if f.band == band][0] 

317 lambdaEff = lsstCamFilter.lambdaEff 

318 

319 addFilter(ComCamFilters_dict, band, physical_filter, lambdaEff=lambdaEff) 

320 

321 

322ComCamFilters = [ 

323 FilterDefinition(band="white", physical_filter="empty", lambdaEff=0.0), 

324 FilterDefinition(band="unknown", physical_filter="unknown", lambdaEff=0.0), 

325] 

326for band, physical_filters in ComCamFilters_dict.items(): 

327 for physical_filter, filter_defn in physical_filters.items(): 

328 ComCamFilters.append(FilterDefinition(**filter_defn)) 

329 

330COMCAM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

331 *ComCamFilters, 

332)