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

84 statements  

« prev     ^ index     » next       coverage.py v7.3.3, created at 2023-12-16 14:32 +0000

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 "GENERIC_FILTER_DEFINITIONS", 

30) 

31 

32from lsst.obs.base import FilterDefinition, FilterDefinitionCollection 

33from .translators.lsst import FILTER_DELIMITER 

34 

35 

36def addFilter(filter_dict, band, physical_filter): 

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, alias=[], 

46 ) 

47 

48 

49# Collection to handle the distinct cases where no filter is being used. 

50NoFilterCollection = FilterDefinitionCollection( 

51 # For this case, no filter is being used and the optical path to 

52 # the focal plane is unoccluded. 

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

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

55 # For this case, all filters are returned to the carousel and the 

56 # auto-changer partially occludes the focal plane. See Tony 

57 # Johnson's comment at https://jira.lsstcorp.org/browse/DM-41675. 

58 FilterDefinition(physical_filter="none", band="white", 

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

60) 

61 

62# Generic filters used by PhoSim and UCDCam 

63LsstCamFiltersGeneric = FilterDefinitionCollection( 

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

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

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

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

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

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

70) 

71 

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

73LsstCamFiltersBaseline = FilterDefinitionCollection( 

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

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

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

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

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

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

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

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

82) 

83 

84# 

85# Define the filters present in the BOT. 

86# According to Tony Johnson the possible physical filters are 

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

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

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

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

91# where: 

92# ColorFWheel and SpotProjFWheel are mutually exclusive and 

93# both appear in FILTER, 

94# NeutralFWheel appears in FILTER2 

95# 

96# Experimentally we also see FILTER2 values of: 

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

98# 

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

100 

101# Update from 2023-11-18: 

102# in DM-41675 Yousuke notes the following update to the BOT filters: 

103# SpotProjFWheel: [grid, spot, sparsegrid, streak, ellipses, empty6] 

104 

105# Map the BOT filters to corresponding band explicitly 

106BOT_filter_map = { 

107 "empty": "white", 

108 "SDSSu": "u", 

109 "SDSSg": "g", 

110 "SDSSr": "r", 

111 "SDSSi": "i", 

112 "SDSSz": "z", 

113 "SDSSY": "y", 

114 "480nm": "g", 

115 "650nm": "r", 

116 "750nm": "i", 

117 "870nm": "z", 

118 "950nm": "y", 

119 "970nm": "y", 

120 "grid": "grid", 

121 "spot": "spot", 

122 "sparsegrid": "sparsegrid", 

123 "streak": "streak", 

124 "ellipses": "ellipses" 

125} 

126 

127BOTFilters_dict = {} 

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

129 if physical_filter == "empty": 

130 pass # Already defined above 

131 else: 

132 addFilter(BOTFilters_dict, band, physical_filter) 

133 

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

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

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

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

138 

139 for nd in ndFilters: 

140 # fully qualified physical filter 

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

142 

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

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

145 if band == "white": 

146 # Use the ND on its own 

147 phys_plus_nd = nd 

148 

149 # Use a generic ND modifier for the band 

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

151 

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

153 

154BOTFilters = [ 

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

156] 

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

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

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

160 

161# 

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

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

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

165# filter definitions. 

166# 

167CCOB_filter_map = { 

168 "": "white", 

169 "HIGH": "white", 

170 "LOW": "white", 

171 "uv": "u", 

172 "blue": "g", 

173 "red": "r", 

174 "nm750": "i", 

175 "nm850": "z", 

176 "nm960": "y", 

177} 

178 

179CCOBFilters = [] 

180for lsst_filter_def in (*NoFilterCollection, *LsstCamFiltersBaseline): 

181 lsstcam_filter = lsst_filter_def.physical_filter 

182 if lsstcam_filter == "empty": 

183 lsstcam_filter = "" 

184 lsstcam_band = lsst_filter_def.band 

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

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

187 # Skip disallowed filter combinations based on band values. 

188 continue 

189 if ccob_filter == "": 

190 # This would correspond to an entry already in 

191 # LSSTCamFilterBaseline 

192 continue 

193 filters = lsstcam_filter, ccob_filter 

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

195 if lsstcam_band == "white": 

196 band = ccob_band 

197 else: 

198 band = lsstcam_band 

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

200 physical_filter = "empty" 

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

202 

203# 

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

205# 

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

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

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

209# 

210LSSTCAM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

211 *NoFilterCollection, 

212 *LsstCamFiltersBaseline, 

213 *BOTFilters, 

214 *CCOBFilters, 

215) 

216 

217GENERIC_FILTER_DEFINITIONS = FilterDefinitionCollection( 

218 *NoFilterCollection, 

219 *LsstCamFiltersGeneric, 

220) 

221 

222# 

223# Filters in SLAC's Test Stand 3 

224# 

225TS3Filters = [ 

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

227 FilterDefinition(physical_filter="275CutOn"), 

228 FilterDefinition(physical_filter="550CutOn")] 

229 

230TS3_FILTER_DEFINITIONS = FilterDefinitionCollection( 

231 *NoFilterCollection, 

232 *LsstCamFiltersGeneric, 

233 *TS3Filters, 

234) 

235# 

236# Filters in SLAC's Test Stand 8 

237# 

238TS8Filters = [ 

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

240 FilterDefinition(physical_filter="275CutOn"), 

241 FilterDefinition(physical_filter="550CutOn")] 

242 

243TS8_FILTER_DEFINITIONS = FilterDefinitionCollection( 

244 *NoFilterCollection, 

245 *LsstCamFiltersGeneric, 

246 *LsstCamFiltersBaseline, 

247 *TS8Filters, 

248 *BOTFilters, 

249 *CCOBFilters, 

250) 

251 

252 

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

254# filters for each combination of filter+grating. 

255 

256# This set of filters can be installed in either the grating or filter wheel, 

257# so we define them here to avoid duplication. 

258sdss65mm_filters = [FilterDefinition(physical_filter="SDSSu_65mm", 

259 band="u"), 

260 FilterDefinition(physical_filter="SDSSg_65mm", 

261 band="g"), 

262 FilterDefinition(physical_filter="SDSSr_65mm", 

263 band="r"), 

264 FilterDefinition(physical_filter="SDSSi_65mm", 

265 band="i"), 

266 FilterDefinition(physical_filter="SDSSz_65mm", 

267 band="z"), 

268 FilterDefinition(physical_filter="SDSSy_65mm", 

269 band="y"), 

270 ] 

271 

272_latiss_filters = ( 

273 FilterDefinition(physical_filter="empty", 

274 band="white", 

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

276 FilterDefinition(physical_filter="blank_bk7_wg05", 

277 band="white"), 

278 FilterDefinition(physical_filter="KPNO_1111_436nm", 

279 band="g"), 

280 FilterDefinition(physical_filter="KPNO_373A_677nm", 

281 band="r"), 

282 FilterDefinition(physical_filter="KPNO_406_828nm", 

283 band="z"), 

284 FilterDefinition(physical_filter="diffuser", 

285 band="diffuser"), 

286 FilterDefinition(physical_filter="unknown", 

287 band="unknown"), 

288 FilterDefinition(physical_filter="BG40", 

289 band="g", 

290 afw_name="bg"), 

291 FilterDefinition(physical_filter="BG40_65mm_1", 

292 band="g", 

293 afw_name="bg"), 

294 FilterDefinition(physical_filter="BG40_65mm_2", 

295 band="g", 

296 afw_name="bg"), 

297 FilterDefinition(physical_filter="quadnotch1", 

298 band="notch"), 

299 FilterDefinition(physical_filter="RG610", 

300 band="r", 

301 afw_name="rg"), 

302 FilterDefinition(physical_filter="OG550_65mm_1", 

303 band="g", 

304 afw_name="bg"), 

305 FilterDefinition(physical_filter="OG550_65mm_2", 

306 band="g", 

307 afw_name="bg"), 

308 FilterDefinition(physical_filter="FELH0600", 

309 band="r", 

310 afw_name="rg"), 

311 FilterDefinition(physical_filter="SDSSg", 

312 band="g"), 

313 FilterDefinition(physical_filter="SDSSr", 

314 band="r"), 

315 FilterDefinition(physical_filter="SDSSi", 

316 band="i"), 

317 FilterDefinition(physical_filter="collimator", 

318 band="white"), 

319 FilterDefinition(physical_filter="cyl_lens", 

320 band="white"), 

321 *sdss65mm_filters, 

322) 

323 

324# Form a new set of filter definitions from all the explicit gratings, and add 

325# sdss65mm_filter set. 

326_latiss_gratings = ("ronchi90lpmm", 

327 "ronchi170lpmm", 

328 "empty", 

329 "unknown", 

330 "holo4_003", 

331 "blue300lpmm_qn1", 

332 "holo4_001", 

333 "pinhole_1_1000", 

334 "pinhole_1_0500", 

335 "pinhole_1_0200", 

336 "pinhole_1_0100", 

337 "pinhole_2_1_1000", 

338 "pinhole_2_1_0500", 

339 "pinhole_2_1_0200", 

340 "pinhole_2_1_0100", 

341 "pinhole_2_2_1000", 

342 "pinhole_2_2_0500", 

343 "pinhole_2_2_0200", 

344 "pinhole_2_2_0100", 

345 "pinhole_2_3_1000", 

346 "pinhole_2_3_0500", 

347 "pinhole_2_3_0200", 

348 "pinhole_2_3_0100", 

349 "pinhole_2_4_1000", 

350 "pinhole_2_4_0500", 

351 "pinhole_2_4_0200", 

352 "pinhole_2_4_0100", 

353 *sdss65mm_filters, 

354 ) 

355 

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

357# to retrieve a filter by an actual filter name 

358_latiss_filter_and_grating = [f for f in _latiss_filters] 

359 

360for filter in _latiss_filters: 

361 for grating in _latiss_gratings: 

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

363 # so skip it 

364 if filter.physical_filter == "diffuser": 

365 continue 

366 

367 # If the grating is a FilterDefinition, use the band and alias 

368 # attributes defined in the grating for the new, combined 

369 # FilterDefinition. In addition, filter out any combinations that do 

370 # not use the "empty" filter in combination with a grating that is a 

371 # FitlerDefintion as we should never combine multiple filters in real 

372 # observations. 

373 if isinstance(grating, FilterDefinition): 

374 if filter.physical_filter != "empty": 

375 continue 

376 

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

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

379 

380 combo = FilterDefinition(physical_filter=new_name, 

381 band=grating.band, 

382 afw_name=grating.afw_name, 

383 alias=new_aliases) 

384 else: 

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

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

387 

388 combo = FilterDefinition(physical_filter=new_name, 

389 band=filter.band, 

390 afw_name=filter.afw_name, 

391 alias=new_aliases) 

392 

393 _latiss_filter_and_grating.append(combo) 

394 

395 

396LATISS_FILTER_DEFINITIONS = FilterDefinitionCollection(*_latiss_filter_and_grating) 

397 

398 

399LSSTCAM_IMSIM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

400 # These were computed using throughputs 1.4 and 

401 # lsst.sims.photUtils.BandpassSet. 

402 FilterDefinition(physical_filter="u_sim_1.4", 

403 band="u"), 

404 FilterDefinition(physical_filter="g_sim_1.4", 

405 band="g"), 

406 FilterDefinition(physical_filter="r_sim_1.4", 

407 band="r"), 

408 FilterDefinition(physical_filter="i_sim_1.4", 

409 band="i"), 

410 FilterDefinition(physical_filter="z_sim_1.4", 

411 band="z"), 

412 FilterDefinition(physical_filter="y_sim_1.4", 

413 band="y"), 

414 *LsstCamFiltersGeneric, 

415) 

416 

417# ########################################################################### 

418# 

419# ComCam 

420# 

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

422 

423ComCamFilters_dict = {} 

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

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

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

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

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

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

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

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

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

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

434 ]: 

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

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

437 

438 addFilter(ComCamFilters_dict, band, physical_filter) 

439 

440 

441ComCamFilters = [ 

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

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

444] 

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

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

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

448 

449COMCAM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

450 *ComCamFilters, 

451)