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

75 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-23 04:18 -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 

31from lsst.obs.base import FilterDefinition, FilterDefinitionCollection 

32from .translators.lsst import FILTER_DELIMITER 

33 

34 

35def addFilter(filter_dict, band, physical_filter): 

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

37 

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

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

40 if band not in filter_dict: 

41 filter_dict[band] = {} 

42 

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

44 band=band, alias=[], 

45 ) 

46 

47 

48EmptyFilter = FilterDefinition(physical_filter="empty", band="white", 

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

50 

51# Generic filters used by PhoSim and UCDCam 

52LsstCamFiltersGeneric = FilterDefinitionCollection( 

53 FilterDefinition(physical_filter="u", band="u"), 

54 FilterDefinition(physical_filter="g", band="g"), 

55 FilterDefinition(physical_filter="r", band="r"), 

56 FilterDefinition(physical_filter="i", band="i"), 

57 FilterDefinition(physical_filter="z", band="z"), 

58 FilterDefinition(physical_filter="y", band="y"), 

59) 

60 

61# The LSST Filters from Tony Johnson, 05/09/2023, in DM-38882. 

62LsstCamFiltersBaseline = FilterDefinitionCollection( 

63 FilterDefinition(physical_filter="ph_5", band="white"), # pinhole filter 

64 FilterDefinition(physical_filter="ef_43", band="white"), # "empty" filter 

65 FilterDefinition(physical_filter="u_24", band="u"), 

66 FilterDefinition(physical_filter="g_6", band="g"), 

67 FilterDefinition(physical_filter="r_57", band="r"), 

68 FilterDefinition(physical_filter="i_39", band="i"), 

69 FilterDefinition(physical_filter="z_20", band="z"), 

70 FilterDefinition(physical_filter="y_10", band="y"), 

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 if physical_filter == "empty": 

112 pass # Already defined above 

113 else: 

114 addFilter(BOTFilters_dict, band, physical_filter) 

115 

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

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

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

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

120 

121 for nd in ndFilters: 

122 # fully qualified physical filter 

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

124 

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

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

127 if band == "white": 

128 # Use the ND on its own 

129 phys_plus_nd = nd 

130 

131 # Use a generic ND modifier for the band 

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

133 

134 addFilter(BOTFilters_dict, band=ndband, physical_filter=phys_plus_nd) 

135 

136BOTFilters = [ 

137 FilterDefinition(band="unknown", physical_filter="unknown"), 

138] 

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

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

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

142 

143# 

144# These are the filters used by the CCOB for both TS8 and LSSTCam. 

145# For the testing of LSSTCam at SLAC, they will be combined with the 

146# real LSSTCam filters, so we include those combinations in the CCOB 

147# filter definitions. 

148# 

149CCOB_filter_map = { 

150 "": "white", 

151 "HIGH": "white", 

152 "LOW": "white", 

153 "uv": "u", 

154 "blue": "g", 

155 "red": "r", 

156 "nm750": "i", 

157 "nm850": "z", 

158 "nm960": "y", 

159} 

160 

161CCOBFilters = [] 

162for lsst_filter_def in (EmptyFilter, *LsstCamFiltersBaseline): 

163 lsstcam_filter = lsst_filter_def.physical_filter 

164 lsstcam_band = lsst_filter_def.band 

165 for ccob_filter, ccob_band in CCOB_filter_map.items(): 

166 if lsstcam_band != "white" and ccob_band != "white" and band != ccob_band: 

167 # Skip disallowed filter combinations based on band values. 

168 continue 

169 if ccob_filter == "": 

170 # This would correspond to an entry already in 

171 # LSSTCamFilterBaseline 

172 continue 

173 filters = lsstcam_filter, ccob_filter 

174 physical_filter = FILTER_DELIMITER.join(filters).strip(FILTER_DELIMITER) 

175 if lsstcam_band == "white": 

176 band = ccob_band 

177 else: 

178 band = lsstcam_band 

179 if physical_filter == "": 179 ↛ 180line 179 didn't jump to line 180, because the condition on line 179 was never true

180 physical_filter = "empty" 

181 CCOBFilters.append(FilterDefinition(band=band, physical_filter=physical_filter)) 

182 

183# 

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

185# 

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

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

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

189# 

190LSSTCAM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

191 EmptyFilter, 

192 *LsstCamFiltersBaseline, 

193 *BOTFilters, 

194 *CCOBFilters, 

195) 

196 

197GENERIC_FILTER_DEFINITIONS = FilterDefinitionCollection( 

198 EmptyFilter, 

199 *LsstCamFiltersGeneric, 

200) 

201 

202# 

203# Filters in SLAC's Test Stand 3 

204# 

205TS3Filters = [ 

206 FilterDefinition(band="unknown", physical_filter="unknown"), 

207 FilterDefinition(physical_filter="275CutOn"), 

208 FilterDefinition(physical_filter="550CutOn")] 

209 

210TS3_FILTER_DEFINITIONS = FilterDefinitionCollection( 

211 EmptyFilter, 

212 *LsstCamFiltersGeneric, 

213 *TS3Filters, 

214) 

215# 

216# Filters in SLAC's Test Stand 8 

217# 

218TS8Filters = [ 

219 FilterDefinition(band="unknown", physical_filter="unknown"), 

220 FilterDefinition(physical_filter="275CutOn"), 

221 FilterDefinition(physical_filter="550CutOn")] 

222 

223TS8_FILTER_DEFINITIONS = FilterDefinitionCollection( 

224 EmptyFilter, 

225 *LsstCamFiltersGeneric, 

226 *LsstCamFiltersBaseline, 

227 *TS8Filters, 

228 *CCOBFilters, 

229) 

230 

231 

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

233# filters for each combination of filter+grating. 

234_latiss_filters = ( 

235 FilterDefinition(physical_filter="empty", 

236 band="white", 

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

238 FilterDefinition(physical_filter="blank_bk7_wg05", 

239 band="white"), 

240 FilterDefinition(physical_filter="KPNO_1111_436nm", 

241 band="g"), 

242 FilterDefinition(physical_filter="KPNO_373A_677nm", 

243 band="r"), 

244 FilterDefinition(physical_filter="KPNO_406_828nm", 

245 band="z"), 

246 FilterDefinition(physical_filter="diffuser", 

247 band="diffuser"), 

248 FilterDefinition(physical_filter="unknown", 

249 band="unknown"), 

250 FilterDefinition(physical_filter="BG40", 

251 band="g", 

252 afw_name="bg"), 

253 FilterDefinition(physical_filter="BG40_65mm_1", 

254 band="g", 

255 afw_name="bg"), 

256 FilterDefinition(physical_filter="BG40_65mm_2", 

257 band="g", 

258 afw_name="bg"), 

259 FilterDefinition(physical_filter="quadnotch1", 

260 band="notch"), 

261 FilterDefinition(physical_filter="RG610", 

262 band="r", 

263 afw_name="rg"), 

264 FilterDefinition(physical_filter="OG550_65mm_1", 

265 band="g", 

266 afw_name="bg"), 

267 FilterDefinition(physical_filter="OG550_65mm_2", 

268 band="g", 

269 afw_name="bg"), 

270 FilterDefinition(physical_filter="FELH0600", 

271 band="r", 

272 afw_name="rg"), 

273 FilterDefinition(physical_filter="SDSSg", 

274 band="g"), 

275 FilterDefinition(physical_filter="SDSSr", 

276 band="r"), 

277 FilterDefinition(physical_filter="SDSSi", 

278 band="i"), 

279 FilterDefinition(physical_filter="SDSSu_65mm", 

280 band="u"), 

281 FilterDefinition(physical_filter="SDSSg_65mm", 

282 band="g"), 

283 FilterDefinition(physical_filter="SDSSr_65mm", 

284 band="r"), 

285 FilterDefinition(physical_filter="SDSSi_65mm", 

286 band="i"), 

287 FilterDefinition(physical_filter="SDSSz_65mm", 

288 band="z"), 

289 FilterDefinition(physical_filter="SDSSy_65mm", 

290 band="y"), 

291) 

292 

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

294_latiss_gratings = ("ronchi90lpmm", "ronchi170lpmm", "empty", "unknown", "holo4_003", "blue300lpmm_qn1") 

295 

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

297# to retrieve a filter by an actual filter name 

298_latiss_filter_and_grating = [f for f in _latiss_filters] 

299 

300for filter in _latiss_filters: 

301 for grating in _latiss_gratings: 

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

303 # so skip it 

304 if filter.physical_filter == "diffuser": 

305 continue 

306 

307 # FilterDefinition is a frozen dataclass 

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

309 

310 # Also need to update aliases 

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

312 

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

314 combo = FilterDefinition(physical_filter=new_name, 

315 band=filter.band, 

316 afw_name=filter.afw_name, 

317 alias=new_aliases) 

318 _latiss_filter_and_grating.append(combo) 

319 

320 

321LATISS_FILTER_DEFINITIONS = FilterDefinitionCollection(*_latiss_filter_and_grating) 

322 

323 

324LSSTCAM_IMSIM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

325 # These were computed using throughputs 1.4 and 

326 # lsst.sims.photUtils.BandpassSet. 

327 FilterDefinition(physical_filter="u_sim_1.4", 

328 band="u"), 

329 FilterDefinition(physical_filter="g_sim_1.4", 

330 band="g"), 

331 FilterDefinition(physical_filter="r_sim_1.4", 

332 band="r"), 

333 FilterDefinition(physical_filter="i_sim_1.4", 

334 band="i"), 

335 FilterDefinition(physical_filter="z_sim_1.4", 

336 band="z"), 

337 FilterDefinition(physical_filter="y_sim_1.4", 

338 band="y"), 

339) 

340 

341# ########################################################################### 

342# 

343# ComCam 

344# 

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

346 

347ComCamFilters_dict = {} 

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

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

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

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

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

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

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

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

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

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

358 ]: 

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

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

361 

362 addFilter(ComCamFilters_dict, band, physical_filter) 

363 

364 

365ComCamFilters = [ 

366 FilterDefinition(band="white", physical_filter="empty"), 

367 FilterDefinition(band="unknown", physical_filter="unknown"), 

368] 

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

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

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

372 

373COMCAM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

374 *ComCamFilters, 

375)