Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from __future__ import absolute_import 

2from builtins import object 

3import numpy as np 

4import healpy as hp 

5from lsst.sims.featureScheduler import utils 

6from lsst.sims.utils import m5_flat_sed 

7from lsst.sims.featureScheduler.utils import int_rounded 

8 

9 

10__all__ = ['BaseFeature', 'BaseSurveyFeature', 'N_obs_count', 'N_obs_survey', 

11 'Last_observation', 'LastSequence_observation', 'LastFilterChange', 

12 'N_observations', 'Coadded_depth', 'Last_observed', 'N_obs_night', 'Pair_in_night', 

13 'Rotator_angle', 'N_observations_season', 'N_obs_count_season', 'N_observations_current_season', 

14 'Last_N_obs_times'] 

15 

16 

17class BaseFeature(object): 

18 """ 

19 Base class for features. 

20 """ 

21 def __init__(self, **kwargs): 

22 # self.feature should be a float, bool, or healpix size numpy array, or numpy masked array 

23 self.feature = None 

24 

25 def __call__(self): 

26 return self.feature 

27 

28 

29class BaseSurveyFeature(object): 

30 """ 

31 Feature that tracks progreess of the survey. Takes observations and updates self.feature 

32 """ 

33 def add_observation(self, observation, indx=None, **kwargs): 

34 """ 

35 Parameters 

36 ---------- 

37 obsevation : dict-like 

38 Object that contains the information about the observation (ra, dec, filter, mjd, etc) 

39 indx : ints (None) 

40 The healpixel indices that the observation overlaps. 

41 """ 

42 raise NotImplementedError 

43 

44 

45class N_obs_count(BaseSurveyFeature): 

46 """Count the number of observations. Total number, not tracked over sky 

47 

48 Parameters 

49 ---------- 

50 filtername : str (None) 

51 The filter to count (if None, all filters counted) 

52 """ 

53 def __init__(self, filtername=None, tag=None): 

54 self.feature = 0 

55 self.filtername = filtername 

56 self.tag = tag 

57 

58 def add_observation(self, observation, indx=None): 

59 

60 if (self.filtername is None) and (self.tag is None): 

61 # Track all observations 

62 self.feature += 1 

63 elif (self.filtername is not None) and (self.tag is None) and (observation['filter'][0] in self.filtername): 

64 # Track all observations on a specified filter 

65 self.feature += 1 

66 elif (self.filtername is None) and (self.tag is not None) and (observation['tag'][0] in self.tag): 

67 # Track all observations on a specified tag 

68 self.feature += 1 

69 elif ((self.filtername is None) and (self.tag is not None) and 

70 # Track all observations on a specified filter on a specified tag 

71 (observation['filter'][0] in self.filtername) and (observation['tag'][0] in self.tag)): 

72 self.feature += 1 

73 

74 

75class N_obs_count_season(BaseSurveyFeature): 

76 """Count the number of observations. 

77 

78 Parameters 

79 ---------- 

80 filtername : str (None) 

81 The filter to count (if None, all filters counted) 

82 """ 

83 def __init__(self, season, nside=None, filtername=None, tag=None, 

84 season_modulo=2, offset=None, max_season=None, season_length=365.25): 

85 self.feature = 0 

86 self.filtername = filtername 

87 self.tag = tag 

88 self.season = season 

89 self.season_modulo = season_modulo 

90 if offset is None: 

91 self.offset = np.zeros(hp.nside2npix(nside), dtype=int) 

92 else: 

93 self.offset = offset 

94 self.max_season = max_season 

95 self.season_length = season_length 

96 

97 def add_observation(self, observation, indx=None): 

98 

99 season = utils.season_calc(observation['night'], modulo=self.season_modulo, 

100 offset=self.offset[indx], max_season=self.max_season, 

101 season_length=self.season_length) 

102 if self.season in season: 

103 if (self.filtername is None) and (self.tag is None): 

104 # Track all observations 

105 self.feature += 1 

106 elif (self.filtername is not None) and (self.tag is None) and (observation['filter'][0] in self.filtername): 

107 # Track all observations on a specified filter 

108 self.feature += 1 

109 elif (self.filtername is None) and (self.tag is not None) and (observation['tag'][0] in self.tag): 

110 # Track all observations on a specified tag 

111 self.feature += 1 

112 elif ((self.filtername is None) and (self.tag is not None) and 

113 # Track all observations on a specified filter on a specified tag 

114 (observation['filter'][0] in self.filtername) and (observation['tag'][0] in self.tag)): 

115 self.feature += 1 

116 

117 

118class N_obs_survey(BaseSurveyFeature): 

119 """Count the number of observations. 

120 

121 Parameters 

122 ---------- 

123 note : str (None) 

124 Only count observations that have str in their note field 

125 """ 

126 def __init__(self, note=None): 

127 self.feature = 0 

128 self.note = note 

129 

130 def add_observation(self, observation, indx=None): 

131 # Track all observations 

132 if self.note is None: 

133 self.feature += 1 

134 else: 

135 if self.note == observation['note']: 

136 self.feature += 1 

137 

138 

139class Last_observation(BaseSurveyFeature): 

140 """Track the last observation. Useful if you want to see when the 

141 last time a survey took an observation. 

142 

143 Parameters 

144 ---------- 

145 survey_name : str (None) 

146 Only records if the survey name matches (or survey_name set to None) 

147 """ 

148 def __init__(self, survey_name=None): 

149 self.survey_name = survey_name 

150 # Start out with an empty observation 

151 self.feature = utils.empty_observation() 

152 

153 def add_observation(self, observation, indx=None): 

154 if self.survey_name is not None: 

155 if self.survey_name in observation['note']: 

156 self.feature = observation 

157 else: 

158 self.feature = observation 

159 

160 

161class LastSequence_observation(BaseSurveyFeature): 

162 """When was the last observation 

163 """ 

164 def __init__(self, sequence_ids=''): 

165 self.sequence_ids = sequence_ids # The ids of all sequence observations... 

166 # Start out with an empty observation 

167 self.feature = utils.empty_observation() 

168 

169 def add_observation(self, observation, indx=None): 

170 if observation['survey_id'] in self.sequence_ids: 

171 self.feature = observation 

172 

173 

174class LastFilterChange(BaseSurveyFeature): 

175 """Record when the filter last changed. 

176 """ 

177 def __init__(self): 

178 self.feature = {'mjd': 0., 

179 'previous_filter': None, 

180 'current_filter': None} 

181 

182 def add_observation(self, observation, indx=None): 

183 if self.feature['current_filter'] is None: 

184 self.feature['mjd'] = observation['mjd'][0] 

185 self.feature['previous_filter'] = None 

186 self.feature['current_filter'] = observation['filter'][0] 

187 elif observation['filter'][0] != self.feature['current_filter']: 

188 self.feature['mjd'] = observation['mjd'][0] 

189 self.feature['previous_filter'] = self.feature['current_filter'] 

190 self.feature['current_filter'] = observation['filter'][0] 

191 

192 

193class N_observations(BaseSurveyFeature): 

194 """ 

195 Track the number of observations that have been made across the sky. 

196 

197 Parameters 

198 ---------- 

199 filtername : str ('r') 

200 String or list that has all the filters that can count. 

201 nside : int (32) 

202 The nside of the healpixel map to use 

203 mask_indx : list of ints (None) 

204 List of healpixel indices to mask and interpolate over 

205 

206 """ 

207 def __init__(self, filtername=None, nside=None, mask_indx=None, survey_name=None): 

208 if nside is None: 

209 nside = utils.set_default_nside() 

210 

211 self.feature = np.zeros(hp.nside2npix(nside), dtype=float) 

212 self.filtername = filtername 

213 self.mask_indx = mask_indx 

214 self.survey_name = survey_name 

215 

216 def add_observation(self, observation, indx=None): 

217 """ 

218 Parameters 

219 ---------- 

220 indx : ints 

221 The indices of the healpixel map that have been observed by observation 

222 """ 

223 

224 if self.filtername is None or observation['filter'][0] in self.filtername: 

225 if self.survey_name is None or observation['note'] in self.survey_name: 

226 self.feature[indx] += 1 

227 

228 if self.mask_indx is not None: 

229 overlap = np.intersect1d(indx, self.mask_indx) 

230 if overlap.size > 0: 

231 # interpolate over those pixels that are DD fields. 

232 # XXX. Do I need to kdtree this? Maybe make a dict on init 

233 # to lookup the N closest non-masked pixels, then do weighted average. 

234 pass 

235 

236 

237class N_observations_season(BaseSurveyFeature): 

238 """ 

239 Track the number of observations that have been made across sky 

240 

241 Parameters 

242 ---------- 

243 season : int 

244 Only count observations in this season (year). 

245 filtername : str ('r') 

246 String or list that has all the filters that can count. 

247 nside : int (32) 

248 The nside of the healpixel map to use 

249 offset : int (0) 

250 The offset to use when computing the season (days) 

251 modulo : int (None) 

252 How to mod the years when computing season 

253 

254 """ 

255 def __init__(self, season, filtername=None, nside=None, offset=0, modulo=None, 

256 max_season=None, season_length=365.25): 

257 if offset is None: 

258 offset = np.zeros(hp.nside2npix(nside), dtype=int) 

259 if nside is None: 

260 nside = utils.set_default_nside() 

261 

262 self.feature = np.zeros(hp.nside2npix(nside), dtype=float) 

263 self.filtername = filtername 

264 self.offset = offset 

265 self.modulo = modulo 

266 self.season = season 

267 self.max_season = max_season 

268 self.season_length = season_length 

269 

270 def add_observation(self, observation, indx=None): 

271 """ 

272 Parameters 

273 ---------- 

274 indx : ints 

275 The indices of the healpixel map that have been observed by observation 

276 """ 

277 

278 observation_season = utils.season_calc(observation['night'], offset=self.offset[indx], 

279 modulo=self.modulo, max_season=self.max_season, 

280 season_length=self.season_length) 

281 if self.season in observation_season: 

282 if self.filtername is None or observation['filter'][0] in self.filtername: 

283 self.feature[indx] += 1 

284 

285 

286class Last_N_obs_times(BaseSurveyFeature): 

287 """Record the last three observations for each healpixel 

288 """ 

289 def __init__(self, filtername=None, n_obs=3, nside=None): 

290 self.filtername = filtername 

291 self.n_obs = n_obs 

292 if nside is None: 

293 nside = utils.set_default_nside() 

294 self.feature = np.zeros((n_obs, hp.nside2npix(nside)), dtype=float) 

295 

296 def add_observation(self, observation, indx=None): 

297 

298 if self.filtername is None or observation['filter'][0] in self.filtername: 

299 self.feature[0:-1, indx] = self.feature[1:, indx] 

300 self.feature[-1, indx] = observation['mjd'] 

301 

302 

303class N_observations_current_season(BaseSurveyFeature): 

304 """Track how many observations have been taken in the current season 

305 XXX--experimental 

306 """ 

307 def __init__(self, filtername=None, nside=None, offset=0, season_length=365.25): 

308 self.filtername = filtername 

309 if nside is None: 

310 nside = utils.set_default_nside() 

311 if offset is None: 

312 offset = np.zeros(hp.nside2npix(nside), dtype=int) 

313 self.offset = offset 

314 self.season_length = season_length 

315 self.season_map = utils.season_calc(0., offset=self.offset, season_length=season_length) 

316 self.feature = np.zeros(hp.nside2npix(nside), dtype=float) 

317 

318 def add_observation(self, observation, indx=None): 

319 current_season = utils.season_calc(observation['night'], offset=self.offset, 

320 season_length=self.season_length) 

321 # If the season has changed anywhere, set that count to zero 

322 new_season = np.where((self.season_map - current_season) != 0) 

323 self.feature[new_season] = 0 

324 self.season_map = current_season 

325 

326 if self.filtername is None or observation['filter'][0] in self.filtername: 

327 self.feature[indx] += 1 

328 

329 

330class Coadded_depth(BaseSurveyFeature): 

331 """ 

332 Track the co-added depth that has been reached accross the sky 

333 

334 Parameters 

335 ---------- 

336 FWHMeff_limit : float (100) 

337 The effective FWHM of the seeing (arcsecond). Images will only be added to the 

338 coadded depth if the observation FWHM is less than or equal to the limit. Default 100. 

339 """ 

340 def __init__(self, filtername='r', nside=None, FWHMeff_limit=100.): 

341 if nside is None: 

342 nside = utils.set_default_nside() 

343 self.filtername = filtername 

344 self.FWHMeff_limit = int_rounded(FWHMeff_limit) 

345 # Starting at limiting mag of zero should be fine. 

346 self.feature = np.zeros(hp.nside2npix(nside), dtype=float) 

347 

348 def add_observation(self, observation, indx=None): 

349 

350 if observation['filter'] == self.filtername: 

351 if int_rounded(observation['FWHMeff']) <= self.FWHMeff_limit: 

352 m5 = m5_flat_sed(observation['filter'], observation['skybrightness'], 

353 observation['FWHMeff'], observation['exptime'], 

354 observation['airmass']) 

355 

356 self.feature[indx] = 1.25 * np.log10(10.**(0.8*self.feature[indx]) + 10.**(0.8*m5)) 

357 

358 

359class Last_observed(BaseSurveyFeature): 

360 """ 

361 Track when a pixel was last observed. Assumes observations are added in chronological 

362 order. 

363 """ 

364 def __init__(self, filtername='r', nside=None): 

365 if nside is None: 

366 nside = utils.set_default_nside() 

367 

368 self.filtername = filtername 

369 self.feature = np.zeros(hp.nside2npix(nside), dtype=float) 

370 

371 def add_observation(self, observation, indx=None): 

372 if self.filtername is None: 

373 self.feature[indx] = observation['mjd'] 

374 elif observation['filter'][0] in self.filtername: 

375 self.feature[indx] = observation['mjd'] 

376 

377 

378class N_obs_night(BaseSurveyFeature): 

379 """ 

380 Track how many times something has been observed in a night 

381 (Note, even if there are two, it might not be a good pair.) 

382 

383 Parameters 

384 ---------- 

385 filtername : string ('r') 

386 Filter to track. 

387 nside : int (32) 

388 Scale of the healpix map 

389 

390 """ 

391 def __init__(self, filtername='r', nside=None): 

392 if nside is None: 

393 nside = utils.set_default_nside() 

394 

395 self.filtername = filtername 

396 self.feature = np.zeros(hp.nside2npix(nside), dtype=int) 

397 self.night = None 

398 

399 def add_observation(self, observation, indx=None): 

400 if observation['night'] != self.night: 

401 self.feature *= 0 

402 self.night = observation['night'] 

403 if observation['filter'][0] in self.filtername: 

404 self.feature[indx] += 1 

405 if (self.filtername == '') | (self.filtername is None): 

406 self.feature[indx] += 1 

407 

408 

409class Pair_in_night(BaseSurveyFeature): 

410 """ 

411 Track how many pairs have been observed within a night 

412 

413 Parameters 

414 ---------- 

415 gap_min : float (25.) 

416 The minimum time gap to consider a successful pair in minutes 

417 gap_max : float (45.) 

418 The maximum time gap to consider a successful pair (minutes) 

419 """ 

420 def __init__(self, filtername='r', nside=None, gap_min=25., gap_max=45.): 

421 if nside is None: 

422 nside = utils.set_default_nside() 

423 

424 self.filtername = filtername 

425 self.feature = np.zeros(hp.nside2npix(nside), dtype=float) 

426 self.indx = np.arange(self.feature.size) 

427 self.last_observed = Last_observed(filtername=filtername) 

428 self.gap_min = gap_min / (24.*60) # Days 

429 self.gap_max = gap_max / (24.*60) # Days 

430 self.night = 0 

431 # Need to keep a full record of times and healpixels observed in a night. 

432 self.mjd_log = [] 

433 self.hpid_log = [] 

434 

435 def add_observation(self, observation, indx=None): 

436 if observation['filter'][0] in self.filtername: 

437 if indx is None: 

438 indx = self.indx 

439 # Clear values if on a new night 

440 if self.night != observation['night']: 

441 self.feature *= 0. 

442 self.night = observation['night'] 

443 self.mjd_log = [] 

444 self.hpid_log = [] 

445 

446 # record the mjds and healpixels that were observed 

447 self.mjd_log.extend([np.max(observation['mjd'])]*np.size(indx)) 

448 self.hpid_log.extend(list(indx)) 

449 

450 # Look for the mjds that could possibly pair with observation 

451 tmin = observation['mjd'] - self.gap_max 

452 tmax = observation['mjd'] - self.gap_min 

453 mjd_log = np.array(self.mjd_log) 

454 left = np.searchsorted(mjd_log, tmin) 

455 right = np.searchsorted(mjd_log, tmax, side='right') 

456 # Now check if any of the healpixels taken in the time gap 

457 # match the healpixels of the observation. 

458 matches = np.in1d(indx, self.hpid_log[int(left):int(right)]) 

459 # XXX--should think if this is the correct (fastest) order to check things in. 

460 self.feature[indx[matches]] += 1 

461 

462 

463class Rotator_angle(BaseSurveyFeature): 

464 """ 

465 Track what rotation angles things are observed with. 

466 XXX-under construction 

467 """ 

468 def __init__(self, filtername='r', binsize=10., nside=None): 

469 """ 

470 

471 """ 

472 if nside is None: 

473 nside = utils.set_default_nside() 

474 

475 self.filtername = filtername 

476 # Actually keep a histogram at each healpixel 

477 self.feature = np.zeros((hp.nside2npix(nside), 360./binsize), dtype=float) 

478 self.bins = np.arange(0, 360+binsize, binsize) 

479 

480 def add_observation(self, observation, indx=None): 

481 if observation['filter'][0] == self.filtername: 

482 # I think this is how to broadcast things properly. 

483 self.feature[indx, :] += np.histogram(observation.rotSkyPos, bins=self.bins)[0]