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', 'Survey_in_night'] 

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 Survey_in_night(BaseSurveyFeature): 

46 """Keep track of how many times a survey has executed in a night. 

47 """ 

48 def __init__(self, survey_str=''): 

49 self.feature = 0 

50 self.survey_str = survey_str 

51 self.night = -1 

52 

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

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

55 self.night = observation['night'] 

56 self.feature = 0 

57 

58 if self.survey_str in observation['note']: 

59 self.feature += 1 

60 

61 

62class N_obs_count(BaseSurveyFeature): 

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

64 

65 Parameters 

66 ---------- 

67 filtername : str (None) 

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

69 """ 

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

71 self.feature = 0 

72 self.filtername = filtername 

73 self.tag = tag 

74 

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

76 

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

78 # Track all observations 

79 self.feature += 1 

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

81 # Track all observations on a specified filter 

82 self.feature += 1 

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

84 # Track all observations on a specified tag 

85 self.feature += 1 

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

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

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

89 self.feature += 1 

90 

91 

92class N_obs_count_season(BaseSurveyFeature): 

93 """Count the number of observations. 

94 

95 Parameters 

96 ---------- 

97 filtername : str (None) 

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

99 """ 

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

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

102 self.feature = 0 

103 self.filtername = filtername 

104 self.tag = tag 

105 self.season = season 

106 self.season_modulo = season_modulo 

107 if offset is None: 

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

109 else: 

110 self.offset = offset 

111 self.max_season = max_season 

112 self.season_length = season_length 

113 

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

115 

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

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

118 season_length=self.season_length) 

119 if self.season in season: 

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

121 # Track all observations 

122 self.feature += 1 

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

124 # Track all observations on a specified filter 

125 self.feature += 1 

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

127 # Track all observations on a specified tag 

128 self.feature += 1 

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

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

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

132 self.feature += 1 

133 

134 

135class N_obs_survey(BaseSurveyFeature): 

136 """Count the number of observations. 

137 

138 Parameters 

139 ---------- 

140 note : str (None) 

141 Only count observations that have str in their note field 

142 """ 

143 def __init__(self, note=None): 

144 self.feature = 0 

145 self.note = note 

146 

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

148 # Track all observations 

149 if self.note is None: 

150 self.feature += 1 

151 else: 

152 if self.note in observation['note']: 

153 self.feature += 1 

154 

155 

156class Last_observation(BaseSurveyFeature): 

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

158 last time a survey took an observation. 

159 

160 Parameters 

161 ---------- 

162 survey_name : str (None) 

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

164 """ 

165 def __init__(self, survey_name=None): 

166 self.survey_name = survey_name 

167 # Start out with an empty observation 

168 self.feature = utils.empty_observation() 

169 

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

171 if self.survey_name is not None: 

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

173 self.feature = observation 

174 else: 

175 self.feature = observation 

176 

177 

178class LastSequence_observation(BaseSurveyFeature): 

179 """When was the last observation 

180 """ 

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

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

183 # Start out with an empty observation 

184 self.feature = utils.empty_observation() 

185 

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

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

188 self.feature = observation 

189 

190 

191class LastFilterChange(BaseSurveyFeature): 

192 """Record when the filter last changed. 

193 """ 

194 def __init__(self): 

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

196 'previous_filter': None, 

197 'current_filter': None} 

198 

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

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

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

202 self.feature['previous_filter'] = None 

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

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

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

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

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

208 

209 

210class N_observations(BaseSurveyFeature): 

211 """ 

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

213 

214 Parameters 

215 ---------- 

216 filtername : str ('r') 

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

218 nside : int (32) 

219 The nside of the healpixel map to use 

220 mask_indx : list of ints (None) 

221 List of healpixel indices to mask and interpolate over 

222 

223 """ 

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

225 if nside is None: 

226 nside = utils.set_default_nside() 

227 

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

229 self.filtername = filtername 

230 self.mask_indx = mask_indx 

231 self.survey_name = survey_name 

232 

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

234 """ 

235 Parameters 

236 ---------- 

237 indx : ints 

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

239 """ 

240 

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

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

243 self.feature[indx] += 1 

244 

245 if self.mask_indx is not None: 

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

247 if overlap.size > 0: 

248 # interpolate over those pixels that are DD fields. 

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

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

251 pass 

252 

253 

254class N_observations_season(BaseSurveyFeature): 

255 """ 

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

257 

258 Parameters 

259 ---------- 

260 season : int 

261 Only count observations in this season (year). 

262 filtername : str ('r') 

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

264 nside : int (32) 

265 The nside of the healpixel map to use 

266 offset : int (0) 

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

268 modulo : int (None) 

269 How to mod the years when computing season 

270 

271 """ 

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

273 max_season=None, season_length=365.25): 

274 if offset is None: 

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

276 if nside is None: 

277 nside = utils.set_default_nside() 

278 

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

280 self.filtername = filtername 

281 self.offset = offset 

282 self.modulo = modulo 

283 self.season = season 

284 self.max_season = max_season 

285 self.season_length = season_length 

286 

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

288 """ 

289 Parameters 

290 ---------- 

291 indx : ints 

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

293 """ 

294 

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

296 modulo=self.modulo, max_season=self.max_season, 

297 season_length=self.season_length) 

298 if self.season in observation_season: 

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

300 self.feature[indx] += 1 

301 

302 

303class Last_N_obs_times(BaseSurveyFeature): 

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

305 """ 

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

307 self.filtername = filtername 

308 self.n_obs = n_obs 

309 if nside is None: 

310 nside = utils.set_default_nside() 

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

312 

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

314 

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

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

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

318 

319 

320class N_observations_current_season(BaseSurveyFeature): 

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

322 XXX--experimental 

323 """ 

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

325 self.filtername = filtername 

326 if nside is None: 

327 nside = utils.set_default_nside() 

328 if offset is None: 

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

330 self.offset = offset 

331 self.season_length = season_length 

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

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

334 

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

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

337 season_length=self.season_length) 

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

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

340 self.feature[new_season] = 0 

341 self.season_map = current_season 

342 

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

344 self.feature[indx] += 1 

345 

346 

347class Coadded_depth(BaseSurveyFeature): 

348 """ 

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

350 

351 Parameters 

352 ---------- 

353 FWHMeff_limit : float (100) 

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

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

356 """ 

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

358 if nside is None: 

359 nside = utils.set_default_nside() 

360 self.filtername = filtername 

361 self.FWHMeff_limit = int_rounded(FWHMeff_limit) 

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

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

364 

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

366 

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

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

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

370 observation['FWHMeff'], observation['exptime'], 

371 observation['airmass']) 

372 

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

374 

375 

376class Last_observed(BaseSurveyFeature): 

377 """ 

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

379 order. 

380 """ 

381 def __init__(self, filtername='r', nside=None, fill=np.nan): 

382 if nside is None: 

383 nside = utils.set_default_nside() 

384 

385 self.filtername = filtername 

386 self.feature = np.zeros(hp.nside2npix(nside), dtype=float) + fill 

387 

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

389 if self.filtername is None: 

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

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

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

393 

394 

395class N_obs_night(BaseSurveyFeature): 

396 """ 

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

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

399 

400 Parameters 

401 ---------- 

402 filtername : string ('r') 

403 Filter to track. 

404 nside : int (32) 

405 Scale of the healpix map 

406 

407 """ 

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

409 if nside is None: 

410 nside = utils.set_default_nside() 

411 

412 self.filtername = filtername 

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

414 self.night = None 

415 

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

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

418 self.feature *= 0 

419 self.night = observation['night'] 

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

421 self.feature[indx] += 1 

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

423 self.feature[indx] += 1 

424 

425 

426class Pair_in_night(BaseSurveyFeature): 

427 """ 

428 Track how many pairs have been observed within a night 

429 

430 Parameters 

431 ---------- 

432 gap_min : float (25.) 

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

434 gap_max : float (45.) 

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

436 """ 

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

438 if nside is None: 

439 nside = utils.set_default_nside() 

440 

441 self.filtername = filtername 

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

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

444 self.last_observed = Last_observed(filtername=filtername) 

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

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

447 self.night = 0 

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

449 self.mjd_log = [] 

450 self.hpid_log = [] 

451 

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

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

454 if indx is None: 

455 indx = self.indx 

456 # Clear values if on a new night 

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

458 self.feature *= 0. 

459 self.night = observation['night'] 

460 self.mjd_log = [] 

461 self.hpid_log = [] 

462 

463 # record the mjds and healpixels that were observed 

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

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

466 

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

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

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

470 mjd_log = np.array(self.mjd_log) 

471 left = np.searchsorted(mjd_log, tmin) 

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

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

474 # match the healpixels of the observation. 

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

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

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

478 

479 

480class Rotator_angle(BaseSurveyFeature): 

481 """ 

482 Track what rotation angles things are observed with. 

483 XXX-under construction 

484 """ 

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

486 """ 

487 

488 """ 

489 if nside is None: 

490 nside = utils.set_default_nside() 

491 

492 self.filtername = filtername 

493 # Actually keep a histogram at each healpixel 

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

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

496 

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

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

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

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

501 

502