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

86 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 03:54 -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 "GENERIC_FILTER_DEFINITIONS", 

30 "UCD_FILTER_DEFINITIONS", 

31) 

32 

33from lsst.obs.base import FilterDefinition, FilterDefinitionCollection 

34from .translators.lsst import FILTER_DELIMITER 

35 

36 

37def addFilter(filter_dict, band, physical_filter): 

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

39 

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

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

42 if band not in filter_dict: 

43 filter_dict[band] = {} 

44 

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

46 band=band, alias=[], 

47 ) 

48 

49 

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

51NoFilterCollection = FilterDefinitionCollection( 

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

53 # the focal plane is unoccluded. 

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

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

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

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

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

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

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

61) 

62 

63# Generic filters used by PhoSim and UCDCam 

64LsstCamFiltersGeneric = FilterDefinitionCollection( 

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

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

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

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

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

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

71) 

72 

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

74LsstCamFiltersBaseline = FilterDefinitionCollection( 

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

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

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

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

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

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

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

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

83) 

84 

85# 

86# Define the filters present in the BOT. 

87# According to Tony Johnson the possible physical filters are 

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

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

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

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

92# where: 

93# ColorFWheel and SpotProjFWheel are mutually exclusive and 

94# both appear in FILTER, 

95# NeutralFWheel appears in FILTER2 

96# 

97# Experimentally we also see FILTER2 values of: 

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

99# 

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

101 

102# Update from 2023-11-18: 

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

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

105 

106# Map the BOT filters to corresponding band explicitly 

107BOT_filter_map = { 

108 "empty": "white", 

109 "SDSSu": "u", 

110 "SDSSg": "g", 

111 "SDSSr": "r", 

112 "SDSSi": "i", 

113 "SDSSz": "z", 

114 "SDSSY": "y", 

115 "480nm": "g", 

116 "650nm": "r", 

117 "750nm": "i", 

118 "870nm": "z", 

119 "950nm": "y", 

120 "970nm": "y", 

121 "grid": "grid", 

122 "spot": "spot", 

123 "sparsegrid": "sparsegrid", 

124 "streak": "streak", 

125 "ellipses": "ellipses" 

126} 

127 

128BOTFilters_dict = {} 

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

130 if physical_filter == "empty": 

131 pass # Already defined above 

132 else: 

133 addFilter(BOTFilters_dict, band, physical_filter) 

134 

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

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

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

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

139 

140 for nd in ndFilters: 

141 # fully qualified physical filter 

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

143 

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

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

146 if band == "white": 

147 # Use the ND on its own 

148 phys_plus_nd = nd 

149 

150 # Use a generic ND modifier for the band 

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

152 

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

154 

155BOTFilters = [ 

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

157] 

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

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

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

161 

162# 

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

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

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

166# filter definitions. 

167# 

168CCOB_filter_map = { 

169 "": "white", 

170 "HIGH": "white", 

171 "LOW": "white", 

172 "uv": "u", 

173 "blue": "g", 

174 "red": "r", 

175 "nm750": "i", 

176 "nm850": "z", 

177 "nm960": "y", 

178} 

179 

180CCOBFilters = [] 

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

182 lsstcam_filter = lsst_filter_def.physical_filter 

183 if lsstcam_filter == "empty": 

184 lsstcam_filter = "" 

185 lsstcam_band = lsst_filter_def.band 

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

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

188 # Skip disallowed filter combinations based on band values. 

189 continue 

190 if ccob_filter == "": 

191 # This would correspond to an entry already in 

192 # LSSTCamFilterBaseline 

193 continue 

194 filters = lsstcam_filter, ccob_filter 

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

196 if lsstcam_band == "white": 

197 band = ccob_band 

198 else: 

199 band = lsstcam_band 

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

201 physical_filter = "empty" 

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

203 

204# 

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

206# 

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

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

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

210# 

211LSSTCAM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

212 *NoFilterCollection, 

213 *LsstCamFiltersBaseline, 

214 *BOTFilters, 

215 *CCOBFilters, 

216) 

217 

218GENERIC_FILTER_DEFINITIONS = FilterDefinitionCollection( 

219 *NoFilterCollection, 

220 *LsstCamFiltersGeneric, 

221) 

222 

223# 

224# Filters in SLAC's Test Stand 3 

225# 

226TS3Filters = [ 

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

228 FilterDefinition(physical_filter="275CutOn"), 

229 FilterDefinition(physical_filter="550CutOn")] 

230 

231TS3_FILTER_DEFINITIONS = FilterDefinitionCollection( 

232 *NoFilterCollection, 

233 *LsstCamFiltersGeneric, 

234 *TS3Filters, 

235) 

236# 

237# Filters in SLAC's Test Stand 8 

238# 

239TS8Filters = [ 

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

241 FilterDefinition(physical_filter="275CutOn"), 

242 FilterDefinition(physical_filter="550CutOn")] 

243 

244TS8_FILTER_DEFINITIONS = FilterDefinitionCollection( 

245 *NoFilterCollection, 

246 *LsstCamFiltersGeneric, 

247 *LsstCamFiltersBaseline, 

248 *TS8Filters, 

249 *BOTFilters, 

250 *CCOBFilters, 

251) 

252# 

253# Filters in UC Davis Beam Simulator 

254# 

255UCDFilters = [ 

256 FilterDefinition(band="unknown", physical_filter="unknown")] 

257 

258UCD_FILTER_DEFINITIONS = FilterDefinitionCollection( 

259 *NoFilterCollection, 

260 *LsstCamFiltersGeneric, 

261 *UCDFilters, 

262) 

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

264# filters for each combination of filter+grating. 

265 

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

267# so we define them here to avoid duplication. 

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

269 band="u"), 

270 FilterDefinition(physical_filter="SDSSg_65mm", 

271 band="g"), 

272 FilterDefinition(physical_filter="SDSSr_65mm", 

273 band="r"), 

274 FilterDefinition(physical_filter="SDSSi_65mm", 

275 band="i"), 

276 FilterDefinition(physical_filter="SDSSz_65mm", 

277 band="z"), 

278 FilterDefinition(physical_filter="SDSSy_65mm", 

279 band="y"), 

280 ] 

281 

282_latiss_filters = ( 

283 FilterDefinition(physical_filter="empty", 

284 band="white", 

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

286 FilterDefinition(physical_filter="blank_bk7_wg05", 

287 band="white"), 

288 FilterDefinition(physical_filter="KPNO_1111_436nm", 

289 band="g"), 

290 FilterDefinition(physical_filter="KPNO_373A_677nm", 

291 band="r"), 

292 FilterDefinition(physical_filter="KPNO_406_828nm", 

293 band="z"), 

294 FilterDefinition(physical_filter="diffuser", 

295 band="diffuser"), 

296 FilterDefinition(physical_filter="unknown", 

297 band="unknown"), 

298 FilterDefinition(physical_filter="BG40", 

299 band="g", 

300 afw_name="bg"), 

301 FilterDefinition(physical_filter="BG40_65mm_1", 

302 band="g", 

303 afw_name="bg"), 

304 FilterDefinition(physical_filter="BG40_65mm_2", 

305 band="g", 

306 afw_name="bg"), 

307 FilterDefinition(physical_filter="quadnotch1", 

308 band="notch"), 

309 FilterDefinition(physical_filter="RG610", 

310 band="r", 

311 afw_name="rg"), 

312 FilterDefinition(physical_filter="OG550_65mm_1", 

313 band="g", 

314 afw_name="bg"), 

315 FilterDefinition(physical_filter="OG550_65mm_2", 

316 band="g", 

317 afw_name="bg"), 

318 FilterDefinition(physical_filter="FELH0600", 

319 band="r", 

320 afw_name="rg"), 

321 FilterDefinition(physical_filter="SDSSg", 

322 band="g"), 

323 FilterDefinition(physical_filter="SDSSr", 

324 band="r"), 

325 FilterDefinition(physical_filter="SDSSi", 

326 band="i"), 

327 FilterDefinition(physical_filter="collimator", 

328 band="white"), 

329 FilterDefinition(physical_filter="cyl_lens", 

330 band="white"), 

331 *sdss65mm_filters, 

332) 

333 

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

335# sdss65mm_filter set. 

336_latiss_gratings = ("ronchi90lpmm", 

337 "ronchi170lpmm", 

338 "empty", 

339 "unknown", 

340 "holo4_003", 

341 "blue300lpmm_qn1", 

342 "holo4_001", 

343 "pinhole_1_1000", 

344 "pinhole_1_0500", 

345 "pinhole_1_0200", 

346 "pinhole_1_0100", 

347 "pinhole_2_1_1000", 

348 "pinhole_2_1_0500", 

349 "pinhole_2_1_0200", 

350 "pinhole_2_1_0100", 

351 "pinhole_2_2_1000", 

352 "pinhole_2_2_0500", 

353 "pinhole_2_2_0200", 

354 "pinhole_2_2_0100", 

355 "pinhole_2_3_1000", 

356 "pinhole_2_3_0500", 

357 "pinhole_2_3_0200", 

358 "pinhole_2_3_0100", 

359 "pinhole_2_4_1000", 

360 "pinhole_2_4_0500", 

361 "pinhole_2_4_0200", 

362 "pinhole_2_4_0100", 

363 *sdss65mm_filters, 

364 ) 

365 

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

367# to retrieve a filter by an actual filter name 

368_latiss_filter_and_grating = [f for f in _latiss_filters] 

369 

370for filter in _latiss_filters: 

371 for grating in _latiss_gratings: 

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

373 # so skip it 

374 if filter.physical_filter == "diffuser": 

375 continue 

376 

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

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

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

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

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

382 # observations. 

383 if isinstance(grating, FilterDefinition): 

384 if filter.physical_filter != "empty": 

385 continue 

386 

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

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

389 

390 combo = FilterDefinition(physical_filter=new_name, 

391 band=grating.band, 

392 afw_name=grating.afw_name, 

393 alias=new_aliases) 

394 else: 

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

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

397 

398 combo = FilterDefinition(physical_filter=new_name, 

399 band=filter.band, 

400 afw_name=filter.afw_name, 

401 alias=new_aliases) 

402 

403 _latiss_filter_and_grating.append(combo) 

404 

405 

406LATISS_FILTER_DEFINITIONS = FilterDefinitionCollection(*_latiss_filter_and_grating) 

407 

408 

409LSSTCAM_IMSIM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

410 # These were computed using throughputs 1.4 and 

411 # lsst.sims.photUtils.BandpassSet. 

412 FilterDefinition(physical_filter="u_sim_1.4", 

413 band="u"), 

414 FilterDefinition(physical_filter="g_sim_1.4", 

415 band="g"), 

416 FilterDefinition(physical_filter="r_sim_1.4", 

417 band="r"), 

418 FilterDefinition(physical_filter="i_sim_1.4", 

419 band="i"), 

420 FilterDefinition(physical_filter="z_sim_1.4", 

421 band="z"), 

422 FilterDefinition(physical_filter="y_sim_1.4", 

423 band="y"), 

424 *LsstCamFiltersGeneric, 

425) 

426 

427# ########################################################################### 

428# 

429# ComCam 

430# 

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

432 

433ComCamFilters_dict = {} 

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

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

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

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

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

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

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

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

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

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

444 ]: 

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

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

447 

448 addFilter(ComCamFilters_dict, band, physical_filter) 

449 

450 

451ComCamFilters = [ 

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

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

454] 

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

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

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

458 

459COMCAM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

460 *ComCamFilters, 

461)