Coverage for python/lsst/sims/featureScheduler/features/features.py : 18%

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
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']
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
25 def __call__(self):
26 return self.feature
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
45class N_obs_count(BaseSurveyFeature):
46 """Count the number of observations. Total number, not tracked over sky
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
58 def add_observation(self, observation, indx=None):
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
75class N_obs_count_season(BaseSurveyFeature):
76 """Count the number of observations.
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
97 def add_observation(self, observation, indx=None):
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
118class N_obs_survey(BaseSurveyFeature):
119 """Count the number of observations.
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
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
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.
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()
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
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()
169 def add_observation(self, observation, indx=None):
170 if observation['survey_id'] in self.sequence_ids:
171 self.feature = observation
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}
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]
193class N_observations(BaseSurveyFeature):
194 """
195 Track the number of observations that have been made across the sky.
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
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()
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
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 """
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
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
237class N_observations_season(BaseSurveyFeature):
238 """
239 Track the number of observations that have been made across sky
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
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()
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
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 """
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
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)
296 def add_observation(self, observation, indx=None):
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']
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)
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
326 if self.filtername is None or observation['filter'][0] in self.filtername:
327 self.feature[indx] += 1
330class Coadded_depth(BaseSurveyFeature):
331 """
332 Track the co-added depth that has been reached accross the sky
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)
348 def add_observation(self, observation, indx=None):
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'])
356 self.feature[indx] = 1.25 * np.log10(10.**(0.8*self.feature[indx]) + 10.**(0.8*m5))
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()
368 self.filtername = filtername
369 self.feature = np.zeros(hp.nside2npix(nside), dtype=float)
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']
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.)
383 Parameters
384 ----------
385 filtername : string ('r')
386 Filter to track.
387 nside : int (32)
388 Scale of the healpix map
390 """
391 def __init__(self, filtername='r', nside=None):
392 if nside is None:
393 nside = utils.set_default_nside()
395 self.filtername = filtername
396 self.feature = np.zeros(hp.nside2npix(nside), dtype=int)
397 self.night = None
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
409class Pair_in_night(BaseSurveyFeature):
410 """
411 Track how many pairs have been observed within a night
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()
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 = []
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 = []
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))
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
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 """
471 """
472 if nside is None:
473 nside = utils.set_default_nside()
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)
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]