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
9__all__ = ['BaseFeature', 'BaseSurveyFeature', 'N_obs_count', 'N_obs_survey',
10 'Last_observation', 'LastSequence_observation', 'LastFilterChange',
11 'N_observations', 'Coadded_depth', 'Last_observed', 'N_obs_night', 'Pair_in_night',
12 'Rotator_angle', 'N_observations_season', 'N_obs_count_season', 'N_observations_current_season',
13 'Last_N_obs_times']
16class BaseFeature(object):
17 """
18 Base class for features.
19 """
20 def __init__(self, **kwargs):
21 # self.feature should be a float, bool, or healpix size numpy array, or numpy masked array
22 self.feature = None
24 def __call__(self):
25 return self.feature
28class BaseSurveyFeature(object):
29 """
30 Feature that tracks progreess of the survey. Takes observations and updates self.feature
31 """
32 def add_observation(self, observation, indx=None, **kwargs):
33 """
34 Parameters
35 ----------
36 obsevation : dict-like
37 Object that contains the information about the observation (ra, dec, filter, mjd, etc)
38 indx : ints (None)
39 The healpixel indices that the observation overlaps.
40 """
41 raise NotImplementedError
44class N_obs_count(BaseSurveyFeature):
45 """Count the number of observations. Total number, not tracked over sky
47 Parameters
48 ----------
49 filtername : str (None)
50 The filter to count (if None, all filters counted)
51 """
52 def __init__(self, filtername=None, tag=None):
53 self.feature = 0
54 self.filtername = filtername
55 self.tag = tag
57 def add_observation(self, observation, indx=None):
59 if (self.filtername is None) and (self.tag is None):
60 # Track all observations
61 self.feature += 1
62 elif (self.filtername is not None) and (self.tag is None) and (observation['filter'][0] in self.filtername):
63 # Track all observations on a specified filter
64 self.feature += 1
65 elif (self.filtername is None) and (self.tag is not None) and (observation['tag'][0] in self.tag):
66 # Track all observations on a specified tag
67 self.feature += 1
68 elif ((self.filtername is None) and (self.tag is not None) and
69 # Track all observations on a specified filter on a specified tag
70 (observation['filter'][0] in self.filtername) and (observation['tag'][0] in self.tag)):
71 self.feature += 1
74class N_obs_count_season(BaseSurveyFeature):
75 """Count the number of observations.
77 Parameters
78 ----------
79 filtername : str (None)
80 The filter to count (if None, all filters counted)
81 """
82 def __init__(self, season, nside=None, filtername=None, tag=None,
83 season_modulo=2, offset=None, max_season=None, season_length=365.25):
84 self.feature = 0
85 self.filtername = filtername
86 self.tag = tag
87 self.season = season
88 self.season_modulo = season_modulo
89 if offset is None:
90 self.offset = np.zeros(hp.nside2npix(nside), dtype=int)
91 else:
92 self.offset = offset
93 self.max_season = max_season
94 self.season_length = season_length
96 def add_observation(self, observation, indx=None):
98 season = utils.season_calc(observation['night'], modulo=self.season_modulo,
99 offset=self.offset[indx], max_season=self.max_season,
100 season_length=self.season_length)
101 if self.season in season:
102 if (self.filtername is None) and (self.tag is None):
103 # Track all observations
104 self.feature += 1
105 elif (self.filtername is not None) and (self.tag is None) and (observation['filter'][0] in self.filtername):
106 # Track all observations on a specified filter
107 self.feature += 1
108 elif (self.filtername is None) and (self.tag is not None) and (observation['tag'][0] in self.tag):
109 # Track all observations on a specified tag
110 self.feature += 1
111 elif ((self.filtername is None) and (self.tag is not None) and
112 # Track all observations on a specified filter on a specified tag
113 (observation['filter'][0] in self.filtername) and (observation['tag'][0] in self.tag)):
114 self.feature += 1
117class N_obs_survey(BaseSurveyFeature):
118 """Count the number of observations.
120 Parameters
121 ----------
122 note : str (None)
123 Only count observations that have str in their note field
124 """
125 def __init__(self, note=None):
126 self.feature = 0
127 self.note = note
129 def add_observation(self, observation, indx=None):
130 # Track all observations
131 if self.note is None:
132 self.feature += 1
133 else:
134 if self.note == observation['note']:
135 self.feature += 1
138class Last_observation(BaseSurveyFeature):
139 """Track the last observation. Useful if you want to see when the
140 last time a survey took an observation.
142 Parameters
143 ----------
144 survey_name : str (None)
145 Only records if the survey name matches (or survey_name set to None)
146 """
147 def __init__(self, survey_name=None):
148 self.survey_name = survey_name
149 # Start out with an empty observation
150 self.feature = utils.empty_observation()
152 def add_observation(self, observation, indx=None):
153 if self.survey_name is not None:
154 if self.survey_name in observation['note']:
155 self.feature = observation
156 else:
157 self.feature = observation
160class LastSequence_observation(BaseSurveyFeature):
161 """When was the last observation
162 """
163 def __init__(self, sequence_ids=''):
164 self.sequence_ids = sequence_ids # The ids of all sequence observations...
165 # Start out with an empty observation
166 self.feature = utils.empty_observation()
168 def add_observation(self, observation, indx=None):
169 if observation['survey_id'] in self.sequence_ids:
170 self.feature = observation
173class LastFilterChange(BaseSurveyFeature):
174 """Record when the filter last changed.
175 """
176 def __init__(self):
177 self.feature = {'mjd': 0.,
178 'previous_filter': None,
179 'current_filter': None}
181 def add_observation(self, observation, indx=None):
182 if self.feature['current_filter'] is None:
183 self.feature['mjd'] = observation['mjd'][0]
184 self.feature['previous_filter'] = None
185 self.feature['current_filter'] = observation['filter'][0]
186 elif observation['filter'][0] != self.feature['current_filter']:
187 self.feature['mjd'] = observation['mjd'][0]
188 self.feature['previous_filter'] = self.feature['current_filter']
189 self.feature['current_filter'] = observation['filter'][0]
192class N_observations(BaseSurveyFeature):
193 """
194 Track the number of observations that have been made across the sky.
196 Parameters
197 ----------
198 filtername : str ('r')
199 String or list that has all the filters that can count.
200 nside : int (32)
201 The nside of the healpixel map to use
202 mask_indx : list of ints (None)
203 List of healpixel indices to mask and interpolate over
205 """
206 def __init__(self, filtername=None, nside=None, mask_indx=None, survey_name=None):
207 if nside is None:
208 nside = utils.set_default_nside()
210 self.feature = np.zeros(hp.nside2npix(nside), dtype=float)
211 self.filtername = filtername
212 self.mask_indx = mask_indx
213 self.survey_name = survey_name
215 def add_observation(self, observation, indx=None):
216 """
217 Parameters
218 ----------
219 indx : ints
220 The indices of the healpixel map that have been observed by observation
221 """
223 if self.filtername is None or observation['filter'][0] in self.filtername:
224 if self.survey_name is None or observation['note'] in self.survey_name:
225 self.feature[indx] += 1
227 if self.mask_indx is not None:
228 overlap = np.intersect1d(indx, self.mask_indx)
229 if overlap.size > 0:
230 # interpolate over those pixels that are DD fields.
231 # XXX. Do I need to kdtree this? Maybe make a dict on init
232 # to lookup the N closest non-masked pixels, then do weighted average.
233 pass
236class N_observations_season(BaseSurveyFeature):
237 """
238 Track the number of observations that have been made across sky
240 Parameters
241 ----------
242 season : int
243 Only count observations in this season (year).
244 filtername : str ('r')
245 String or list that has all the filters that can count.
246 nside : int (32)
247 The nside of the healpixel map to use
248 offset : int (0)
249 The offset to use when computing the season (days)
250 modulo : int (None)
251 How to mod the years when computing season
253 """
254 def __init__(self, season, filtername=None, nside=None, offset=0, modulo=None,
255 max_season=None, season_length=365.25):
256 if offset is None:
257 offset = np.zeros(hp.nside2npix(nside), dtype=int)
258 if nside is None:
259 nside = utils.set_default_nside()
261 self.feature = np.zeros(hp.nside2npix(nside), dtype=float)
262 self.filtername = filtername
263 self.offset = offset
264 self.modulo = modulo
265 self.season = season
266 self.max_season = max_season
267 self.season_length = season_length
269 def add_observation(self, observation, indx=None):
270 """
271 Parameters
272 ----------
273 indx : ints
274 The indices of the healpixel map that have been observed by observation
275 """
277 observation_season = utils.season_calc(observation['night'], offset=self.offset[indx],
278 modulo=self.modulo, max_season=self.max_season,
279 season_length=self.season_length)
280 if self.season in observation_season:
281 if self.filtername is None or observation['filter'][0] in self.filtername:
282 self.feature[indx] += 1
285class Last_N_obs_times(BaseSurveyFeature):
286 """Record the last three observations for each healpixel
287 """
288 def __init__(self, filtername=None, n_obs=3, nside=None):
289 self.filtername = filtername
290 self.n_obs = n_obs
291 if nside is None:
292 nside = utils.set_default_nside()
293 self.feature = np.zeros((n_obs, hp.nside2npix(nside)), dtype=float)
295 def add_observation(self, observation, indx=None):
297 if self.filtername is None or observation['filter'][0] in self.filtername:
298 self.feature[0:-1, indx] = self.feature[1:, indx]
299 self.feature[-1, indx] = observation['mjd']
302class N_observations_current_season(BaseSurveyFeature):
303 """Track how many observations have been taken in the current season
304 XXX--experimental
305 """
306 def __init__(self, filtername=None, nside=None, offset=0, season_length=365.25):
307 self.filtername = filtername
308 if nside is None:
309 nside = utils.set_default_nside()
310 if offset is None:
311 offset = np.zeros(hp.nside2npix(nside), dtype=int)
312 self.offset = offset
313 self.season_length = season_length
314 self.season_map = utils.season_calc(0., offset=self.offset, season_length=season_length)
315 self.feature = np.zeros(hp.nside2npix(nside), dtype=float)
317 def add_observation(self, observation, indx=None):
318 current_season = utils.season_calc(observation['night'], offset=self.offset,
319 season_length=self.season_length)
320 # If the season has changed anywhere, set that count to zero
321 new_season = np.where((self.season_map - current_season) != 0)
322 self.feature[new_season] = 0
323 self.season_map = current_season
325 if self.filtername is None or observation['filter'][0] in self.filtername:
326 self.feature[indx] += 1
329class Coadded_depth(BaseSurveyFeature):
330 """
331 Track the co-added depth that has been reached accross the sky
333 Parameters
334 ----------
335 FWHMeff_limit : float (100)
336 The effective FWHM of the seeing (arcsecond). Images will only be added to the
337 coadded depth if the observation FWHM is less than or equal to the limit. Default 100.
338 """
339 def __init__(self, filtername='r', nside=None, FWHMeff_limit=100.):
340 if nside is None:
341 nside = utils.set_default_nside()
342 self.filtername = filtername
343 self.FWHMeff_limit = FWHMeff_limit
344 # Starting at limiting mag of zero should be fine.
345 self.feature = np.zeros(hp.nside2npix(nside), dtype=float)
347 def add_observation(self, observation, indx=None):
349 if observation['filter'] == self.filtername:
350 if observation['FWHMeff'] <= self.FWHMeff_limit:
351 m5 = m5_flat_sed(observation['filter'], observation['skybrightness'],
352 observation['FWHMeff'], observation['exptime'],
353 observation['airmass'])
355 self.feature[indx] = 1.25 * np.log10(10.**(0.8*self.feature[indx]) + 10.**(0.8*m5))
358class Last_observed(BaseSurveyFeature):
359 """
360 Track when a pixel was last observed. Assumes observations are added in chronological
361 order.
362 """
363 def __init__(self, filtername='r', nside=None):
364 if nside is None:
365 nside = utils.set_default_nside()
367 self.filtername = filtername
368 self.feature = np.zeros(hp.nside2npix(nside), dtype=float)
370 def add_observation(self, observation, indx=None):
371 if self.filtername is None:
372 self.feature[indx] = observation['mjd']
373 elif observation['filter'][0] in self.filtername:
374 self.feature[indx] = observation['mjd']
377class N_obs_night(BaseSurveyFeature):
378 """
379 Track how many times something has been observed in a night
380 (Note, even if there are two, it might not be a good pair.)
382 Parameters
383 ----------
384 filtername : string ('r')
385 Filter to track.
386 nside : int (32)
387 Scale of the healpix map
389 """
390 def __init__(self, filtername='r', nside=None):
391 if nside is None:
392 nside = utils.set_default_nside()
394 self.filtername = filtername
395 self.feature = np.zeros(hp.nside2npix(nside), dtype=int)
396 self.night = None
398 def add_observation(self, observation, indx=None):
399 if observation['night'] != self.night:
400 self.feature *= 0
401 self.night = observation['night']
402 if observation['filter'][0] in self.filtername:
403 self.feature[indx] += 1
404 if (self.filtername == '') | (self.filtername is None):
405 self.feature[indx] += 1
408class Pair_in_night(BaseSurveyFeature):
409 """
410 Track how many pairs have been observed within a night
412 Parameters
413 ----------
414 gap_min : float (25.)
415 The minimum time gap to consider a successful pair in minutes
416 gap_max : float (45.)
417 The maximum time gap to consider a successful pair (minutes)
418 """
419 def __init__(self, filtername='r', nside=None, gap_min=25., gap_max=45.):
420 if nside is None:
421 nside = utils.set_default_nside()
423 self.filtername = filtername
424 self.feature = np.zeros(hp.nside2npix(nside), dtype=float)
425 self.indx = np.arange(self.feature.size)
426 self.last_observed = Last_observed(filtername=filtername)
427 self.gap_min = gap_min / (24.*60) # Days
428 self.gap_max = gap_max / (24.*60) # Days
429 self.night = 0
430 # Need to keep a full record of times and healpixels observed in a night.
431 self.mjd_log = []
432 self.hpid_log = []
434 def add_observation(self, observation, indx=None):
435 if observation['filter'][0] in self.filtername:
436 if indx is None:
437 indx = self.indx
438 # Clear values if on a new night
439 if self.night != observation['night']:
440 self.feature *= 0.
441 self.night = observation['night']
442 self.mjd_log = []
443 self.hpid_log = []
445 # record the mjds and healpixels that were observed
446 self.mjd_log.extend([np.max(observation['mjd'])]*np.size(indx))
447 self.hpid_log.extend(list(indx))
449 # Look for the mjds that could possibly pair with observation
450 tmin = observation['mjd'] - self.gap_max
451 tmax = observation['mjd'] - self.gap_min
452 mjd_log = np.array(self.mjd_log)
453 left = np.searchsorted(mjd_log, tmin)
454 right = np.searchsorted(mjd_log, tmax, side='right')
455 # Now check if any of the healpixels taken in the time gap
456 # match the healpixels of the observation.
457 matches = np.in1d(indx, self.hpid_log[int(left):int(right)])
458 # XXX--should think if this is the correct (fastest) order to check things in.
459 self.feature[indx[matches]] += 1
462class Rotator_angle(BaseSurveyFeature):
463 """
464 Track what rotation angles things are observed with.
465 XXX-under construction
466 """
467 def __init__(self, filtername='r', binsize=10., nside=None):
468 """
470 """
471 if nside is None:
472 nside = utils.set_default_nside()
474 self.filtername = filtername
475 # Actually keep a histogram at each healpixel
476 self.feature = np.zeros((hp.nside2npix(nside), 360./binsize), dtype=float)
477 self.bins = np.arange(0, 360+binsize, binsize)
479 def add_observation(self, observation, indx=None):
480 if observation['filter'][0] == self.filtername:
481 # I think this is how to broadcast things properly.
482 self.feature[indx, :] += np.histogram(observation.rotSkyPos, bins=self.bins)[0]