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', 'Survey_in_night']
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 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
53 def add_observation(self, observation, indx=None):
54 if observation['night'] != self.night:
55 self.night = observation['night']
56 self.feature = 0
58 if self.survey_str in observation['note']:
59 self.feature += 1
62class N_obs_count(BaseSurveyFeature):
63 """Count the number of observations. Total number, not tracked over sky
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
75 def add_observation(self, observation, indx=None):
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
92class N_obs_count_season(BaseSurveyFeature):
93 """Count the number of observations.
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
114 def add_observation(self, observation, indx=None):
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
135class N_obs_survey(BaseSurveyFeature):
136 """Count the number of observations.
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
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
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.
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()
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
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()
186 def add_observation(self, observation, indx=None):
187 if observation['survey_id'] in self.sequence_ids:
188 self.feature = observation
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}
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]
210class N_observations(BaseSurveyFeature):
211 """
212 Track the number of observations that have been made across the sky.
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
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()
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
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 """
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
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
254class N_observations_season(BaseSurveyFeature):
255 """
256 Track the number of observations that have been made across sky
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
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()
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
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 """
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
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)
313 def add_observation(self, observation, indx=None):
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']
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)
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
343 if self.filtername is None or observation['filter'][0] in self.filtername:
344 self.feature[indx] += 1
347class Coadded_depth(BaseSurveyFeature):
348 """
349 Track the co-added depth that has been reached accross the sky
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)
365 def add_observation(self, observation, indx=None):
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'])
373 self.feature[indx] = 1.25 * np.log10(10.**(0.8*self.feature[indx]) + 10.**(0.8*m5))
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()
385 self.filtername = filtername
386 self.feature = np.zeros(hp.nside2npix(nside), dtype=float) + fill
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']
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.)
400 Parameters
401 ----------
402 filtername : string ('r')
403 Filter to track.
404 nside : int (32)
405 Scale of the healpix map
407 """
408 def __init__(self, filtername='r', nside=None):
409 if nside is None:
410 nside = utils.set_default_nside()
412 self.filtername = filtername
413 self.feature = np.zeros(hp.nside2npix(nside), dtype=int)
414 self.night = None
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
426class Pair_in_night(BaseSurveyFeature):
427 """
428 Track how many pairs have been observed within a night
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()
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 = []
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 = []
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))
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
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 """
488 """
489 if nside is None:
490 nside = utils.set_default_nside()
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)
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]