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

84 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-30 14:16 +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 

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

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

51 

52# Generic filters used by PhoSim and UCDCam 

53LsstCamFiltersGeneric = FilterDefinitionCollection( 

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

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

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

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

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

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

60) 

61 

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

63LsstCamFiltersBaseline = FilterDefinitionCollection( 

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

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

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

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

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

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

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

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

72) 

73 

74# 

75# Define the filters present in the BOT. 

76# According to Tony Johnson the possible physical filters are 

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

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

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

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

81# where: 

82# ColorFWheel and SpotProjFWheel are mutually exclusive and 

83# both appear in FILTER, 

84# NeutralFWheel appears in FILTER2 

85# 

86# Experimentally we also see FILTER2 values of: 

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

88# 

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

90 

91# Update from 2023-11-18: 

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

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

94 

95# Map the BOT filters to corresponding band explicitly 

96BOT_filter_map = { 

97 "empty": "white", 

98 "SDSSu": "u", 

99 "SDSSg": "g", 

100 "SDSSr": "r", 

101 "SDSSi": "i", 

102 "SDSSz": "z", 

103 "SDSSY": "y", 

104 "480nm": "g", 

105 "650nm": "r", 

106 "750nm": "i", 

107 "870nm": "z", 

108 "950nm": "y", 

109 "970nm": "y", 

110 "grid": "grid", 

111 "spot": "spot", 

112 "sparsegrid": "sparsegrid", 

113 "streak": "streak", 

114 "ellipses": "ellipses" 

115} 

116 

117BOTFilters_dict = {} 

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

119 if physical_filter == "empty": 

120 pass # Already defined above 

121 else: 

122 addFilter(BOTFilters_dict, band, physical_filter) 

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) 

143 

144BOTFilters = [ 

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

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# These are the filters used by the CCOB for both TS8 and LSSTCam. 

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

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

155# filter definitions. 

156# 

157CCOB_filter_map = { 

158 "": "white", 

159 "HIGH": "white", 

160 "LOW": "white", 

161 "uv": "u", 

162 "blue": "g", 

163 "red": "r", 

164 "nm750": "i", 

165 "nm850": "z", 

166 "nm960": "y", 

167} 

168 

169CCOBFilters = [] 

170for lsst_filter_def in (EmptyFilter, *LsstCamFiltersBaseline): 

171 lsstcam_filter = lsst_filter_def.physical_filter 

172 if lsstcam_filter == "empty": 

173 lsstcam_filter = "" 

174 lsstcam_band = lsst_filter_def.band 

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

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

177 # Skip disallowed filter combinations based on band values. 

178 continue 

179 if ccob_filter == "": 

180 # This would correspond to an entry already in 

181 # LSSTCamFilterBaseline 

182 continue 

183 filters = lsstcam_filter, ccob_filter 

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

185 if lsstcam_band == "white": 

186 band = ccob_band 

187 else: 

188 band = lsstcam_band 

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

190 physical_filter = "empty" 

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

192 

193# 

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

195# 

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

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

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

199# 

200LSSTCAM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

201 EmptyFilter, 

202 *LsstCamFiltersBaseline, 

203 *BOTFilters, 

204 *CCOBFilters, 

205) 

206 

207GENERIC_FILTER_DEFINITIONS = FilterDefinitionCollection( 

208 EmptyFilter, 

209 *LsstCamFiltersGeneric, 

210) 

211 

212# 

213# Filters in SLAC's Test Stand 3 

214# 

215TS3Filters = [ 

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

217 FilterDefinition(physical_filter="275CutOn"), 

218 FilterDefinition(physical_filter="550CutOn")] 

219 

220TS3_FILTER_DEFINITIONS = FilterDefinitionCollection( 

221 EmptyFilter, 

222 *LsstCamFiltersGeneric, 

223 *TS3Filters, 

224) 

225# 

226# Filters in SLAC's Test Stand 8 

227# 

228TS8Filters = [ 

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

230 FilterDefinition(physical_filter="275CutOn"), 

231 FilterDefinition(physical_filter="550CutOn")] 

232 

233TS8_FILTER_DEFINITIONS = FilterDefinitionCollection( 

234 EmptyFilter, 

235 *LsstCamFiltersGeneric, 

236 *LsstCamFiltersBaseline, 

237 *TS8Filters, 

238 *BOTFilters, 

239 *CCOBFilters, 

240) 

241 

242 

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

244# filters for each combination of filter+grating. 

245 

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

247# so we define them here to avoid duplication. 

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

249 band="u"), 

250 FilterDefinition(physical_filter="SDSSg_65mm", 

251 band="g"), 

252 FilterDefinition(physical_filter="SDSSr_65mm", 

253 band="r"), 

254 FilterDefinition(physical_filter="SDSSi_65mm", 

255 band="i"), 

256 FilterDefinition(physical_filter="SDSSz_65mm", 

257 band="z"), 

258 FilterDefinition(physical_filter="SDSSy_65mm", 

259 band="y"), 

260 ] 

261 

262_latiss_filters = ( 

263 FilterDefinition(physical_filter="empty", 

264 band="white", 

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

266 FilterDefinition(physical_filter="blank_bk7_wg05", 

267 band="white"), 

268 FilterDefinition(physical_filter="KPNO_1111_436nm", 

269 band="g"), 

270 FilterDefinition(physical_filter="KPNO_373A_677nm", 

271 band="r"), 

272 FilterDefinition(physical_filter="KPNO_406_828nm", 

273 band="z"), 

274 FilterDefinition(physical_filter="diffuser", 

275 band="diffuser"), 

276 FilterDefinition(physical_filter="unknown", 

277 band="unknown"), 

278 FilterDefinition(physical_filter="BG40", 

279 band="g", 

280 afw_name="bg"), 

281 FilterDefinition(physical_filter="BG40_65mm_1", 

282 band="g", 

283 afw_name="bg"), 

284 FilterDefinition(physical_filter="BG40_65mm_2", 

285 band="g", 

286 afw_name="bg"), 

287 FilterDefinition(physical_filter="quadnotch1", 

288 band="notch"), 

289 FilterDefinition(physical_filter="RG610", 

290 band="r", 

291 afw_name="rg"), 

292 FilterDefinition(physical_filter="OG550_65mm_1", 

293 band="g", 

294 afw_name="bg"), 

295 FilterDefinition(physical_filter="OG550_65mm_2", 

296 band="g", 

297 afw_name="bg"), 

298 FilterDefinition(physical_filter="FELH0600", 

299 band="r", 

300 afw_name="rg"), 

301 FilterDefinition(physical_filter="SDSSg", 

302 band="g"), 

303 FilterDefinition(physical_filter="SDSSr", 

304 band="r"), 

305 FilterDefinition(physical_filter="SDSSi", 

306 band="i"), 

307 FilterDefinition(physical_filter="collimator", 

308 band="white"), 

309 FilterDefinition(physical_filter="cyl_lens", 

310 band="white"), 

311 *sdss65mm_filters, 

312) 

313 

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

315# sdss65mm_filter set. 

316_latiss_gratings = ("ronchi90lpmm", 

317 "ronchi170lpmm", 

318 "empty", 

319 "unknown", 

320 "holo4_003", 

321 "blue300lpmm_qn1", 

322 "holo4_001", 

323 "pinhole_1_1000", 

324 "pinhole_1_0500", 

325 "pinhole_1_0200", 

326 "pinhole_1_0100", 

327 "pinhole_2_1_1000", 

328 "pinhole_2_1_0500", 

329 "pinhole_2_1_0200", 

330 "pinhole_2_1_0100", 

331 "pinhole_2_2_1000", 

332 "pinhole_2_2_0500", 

333 "pinhole_2_2_0200", 

334 "pinhole_2_2_0100", 

335 "pinhole_2_3_1000", 

336 "pinhole_2_3_0500", 

337 "pinhole_2_3_0200", 

338 "pinhole_2_3_0100", 

339 "pinhole_2_4_1000", 

340 "pinhole_2_4_0500", 

341 "pinhole_2_4_0200", 

342 "pinhole_2_4_0100", 

343 *sdss65mm_filters, 

344 ) 

345 

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

347# to retrieve a filter by an actual filter name 

348_latiss_filter_and_grating = [f for f in _latiss_filters] 

349 

350for filter in _latiss_filters: 

351 for grating in _latiss_gratings: 

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

353 # so skip it 

354 if filter.physical_filter == "diffuser": 

355 continue 

356 

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

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

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

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

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

362 # observations. 

363 if isinstance(grating, FilterDefinition): 

364 if filter.physical_filter != "empty": 

365 continue 

366 

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

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

369 

370 combo = FilterDefinition(physical_filter=new_name, 

371 band=grating.band, 

372 afw_name=grating.afw_name, 

373 alias=new_aliases) 

374 else: 

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

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

377 

378 combo = FilterDefinition(physical_filter=new_name, 

379 band=filter.band, 

380 afw_name=filter.afw_name, 

381 alias=new_aliases) 

382 

383 _latiss_filter_and_grating.append(combo) 

384 

385 

386LATISS_FILTER_DEFINITIONS = FilterDefinitionCollection(*_latiss_filter_and_grating) 

387 

388 

389LSSTCAM_IMSIM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

390 # These were computed using throughputs 1.4 and 

391 # lsst.sims.photUtils.BandpassSet. 

392 FilterDefinition(physical_filter="u_sim_1.4", 

393 band="u"), 

394 FilterDefinition(physical_filter="g_sim_1.4", 

395 band="g"), 

396 FilterDefinition(physical_filter="r_sim_1.4", 

397 band="r"), 

398 FilterDefinition(physical_filter="i_sim_1.4", 

399 band="i"), 

400 FilterDefinition(physical_filter="z_sim_1.4", 

401 band="z"), 

402 FilterDefinition(physical_filter="y_sim_1.4", 

403 band="y"), 

404 *LsstCamFiltersGeneric, 

405) 

406 

407# ########################################################################### 

408# 

409# ComCam 

410# 

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

412 

413ComCamFilters_dict = {} 

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

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

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

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

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

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

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

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

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

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

424 ]: 

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

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

427 

428 addFilter(ComCamFilters_dict, band, physical_filter) 

429 

430 

431ComCamFilters = [ 

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

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

434] 

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

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

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

438 

439COMCAM_FILTER_DEFINITIONS = FilterDefinitionCollection( 

440 *ComCamFilters, 

441)