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 builtins import zip 

2from builtins import range 

3# The base class for all spatial slicers. 

4# Slicers are 'data slicers' at heart; spatial slicers slice data by RA/Dec and 

5# return the relevant indices in the simData to the metric. 

6# The primary things added here are the methods to slice the data (for any spatial slicer) 

7# as this uses a KD-tree built on spatial (RA/Dec type) indexes. 

8 

9import warnings 

10import numpy as np 

11from functools import wraps 

12from lsst.sims.maf.plots.spatialPlotters import BaseHistogram, BaseSkyMap 

13 

14# For the footprint generation and conversion between galactic/equatorial coordinates. 

15from lsst.obs.lsst import LsstCamMapper 

16from lsst.sims.coordUtils import _chipNameFromRaDec 

17import lsst.sims.utils as simsUtils 

18 

19from .baseSlicer import BaseSlicer 

20 

21__all__ = ['BaseSpatialSlicer'] 

22 

23 

24class BaseSpatialSlicer(BaseSlicer): 

25 """Base spatial slicer object, contains additional functionality for spatial slicing, 

26 including setting up and traversing a kdtree containing the simulated data points. 

27 

28 Parameters 

29 ---------- 

30 lonCol : str, optional 

31 Name of the longitude (RA equivalent) column to use from the input data. 

32 Default fieldRA 

33 latCol : str, optional 

34 Name of the latitude (Dec equivalent) column to use from the input data. 

35 Default fieldDec 

36 latLonDeg : boolean, optional 

37 Flag indicating whether lat and lon values from input data are in degrees (True) or radians (False). 

38 Default True. 

39 verbose : boolean, optional 

40 Flag to indicate whether or not to write additional information to stdout during runtime. 

41 Default True. 

42 badval : float, optional 

43 Bad value flag, relevant for plotting. Default -666. 

44 leafsize : int, optional 

45 Leafsize value for kdtree. Default 100. 

46 radius : float, optional 

47 Radius for matching in the kdtree. Equivalent to the radius of the FOV. Degrees. 

48 Default 1.75. 

49 useCamera : boolean, optional 

50 Flag to indicate whether to use the LSST camera footprint or not. 

51 Default False. 

52 rotSkyPosColName : str, optional 

53 Name of the rotSkyPos column in the input data. Only used if useCamera is True. 

54 Describes the orientation of the camera orientation compared to the sky. 

55 Default rotSkyPos. 

56 mjdColName : str, optional 

57 Name of the exposure time column. Only used if useCamera is True. 

58 Default observationStartMJD. 

59 chipNames : array-like, optional 

60 List of chips to accept, if useCamera is True. This lets users turn 'on' only a subset of chips. 

61 Default 'all' - this uses all chips in the camera. 

62 scienceChips : bool (True) 

63 Do not include wavefront sensors when checking if a point landed on a chip. 

64 """ 

65 def __init__(self, lonCol='fieldRA', latCol='fieldDec', latLonDeg=True, 

66 verbose=True, badval=-666, leafsize=100, radius=1.75, 

67 useCamera=False, rotSkyPosColName='rotSkyPos', mjdColName='observationStartMJD', 

68 chipNames='all', scienceChips=True): 

69 super(BaseSpatialSlicer, self).__init__(verbose=verbose, badval=badval) 

70 self.lonCol = lonCol 

71 self.latCol = latCol 

72 self.latLonDeg = latLonDeg 

73 self.rotSkyPosColName = rotSkyPosColName 

74 self.mjdColName = mjdColName 

75 self.columnsNeeded = [lonCol, latCol] 

76 self.useCamera = useCamera 

77 if useCamera: 

78 self.columnsNeeded.append(rotSkyPosColName) 

79 self.columnsNeeded.append(mjdColName) 

80 self.slicer_init = {'lonCol': lonCol, 'latCol': latCol, 

81 'radius': radius, 'badval': badval, 

82 'useCamera': useCamera} 

83 self.radius = radius 

84 self.leafsize = leafsize 

85 self.useCamera = useCamera 

86 self.chipsToUse = chipNames 

87 self.scienceChips = scienceChips 

88 # RA and Dec are required slicePoint info for any spatial slicer. Slicepoint RA/Dec are in radians. 

89 self.slicePoints['sid'] = None 

90 self.slicePoints['ra'] = None 

91 self.slicePoints['dec'] = None 

92 self.nslice = None 

93 self.shape = None 

94 self.plotFuncs = [BaseHistogram, BaseSkyMap] 

95 

96 def setupSlicer(self, simData, maps=None): 

97 """Use simData[self.lonCol] and simData[self.latCol] (in radians) to set up KDTree. 

98 

99 Parameters 

100 ----------- 

101 simData : numpy.recarray 

102 The simulated data, including the location of each pointing. 

103 maps : list of lsst.sims.maf.maps objects, optional 

104 List of maps (such as dust extinction) that will run to build up additional metadata at each 

105 slicePoint. This additional metadata is available to metrics via the slicePoint dictionary. 

106 Default None. 

107 """ 

108 if maps is not None: 

109 if self.cacheSize != 0 and len(maps) > 0: 

110 warnings.warn('Warning: Loading maps but cache on.' 

111 'Should probably set useCache=False in slicer.') 

112 self._runMaps(maps) 

113 self._setRad(self.radius) 

114 if self.useCamera: 

115 self._setupLSSTCamera() 

116 self._presliceFootprint(simData) 

117 else: 

118 if self.latLonDeg: 

119 self._buildTree(np.radians(simData[self.lonCol]), 

120 np.radians(simData[self.latCol]), self.leafsize) 

121 else: 

122 self._buildTree(simData[self.lonCol], simData[self.latCol], self.leafsize) 

123 

124 @wraps(self._sliceSimData) 

125 def _sliceSimData(islice): 

126 """Return indexes for relevant opsim data at slicepoint 

127 (slicepoint=lonCol/latCol value .. usually ra/dec).""" 

128 

129 # Build dict for slicePoint info 

130 slicePoint = {} 

131 if self.useCamera: 

132 indices = self.sliceLookup[islice] 

133 slicePoint['chipNames'] = self.chipNames[islice] 

134 else: 

135 sx, sy, sz = simsUtils._xyz_from_ra_dec(self.slicePoints['ra'][islice], 

136 self.slicePoints['dec'][islice]) 

137 # Query against tree. 

138 indices = self.opsimtree.query_ball_point((sx, sy, sz), self.rad) 

139 

140 # Loop through all the slicePoint keys. If the first dimension of slicepoint[key] has 

141 # the same shape as the slicer, assume it is information per slicepoint. 

142 # Otherwise, pass the whole slicePoint[key] information. Useful for stellar LF maps 

143 # where we want to pass only the relevant LF and the bins that go with it. 

144 for key in self.slicePoints: 

145 if len(np.shape(self.slicePoints[key])) == 0: 

146 keyShape = 0 

147 else: 

148 keyShape = np.shape(self.slicePoints[key])[0] 

149 if (keyShape == self.nslice): 

150 slicePoint[key] = self.slicePoints[key][islice] 

151 else: 

152 slicePoint[key] = self.slicePoints[key] 

153 return {'idxs': indices, 'slicePoint': slicePoint} 

154 setattr(self, '_sliceSimData', _sliceSimData) 

155 

156 def _setupLSSTCamera(self): 

157 """If we want to include the camera chip gaps, etc""" 

158 mapper = LsstCamMapper() 

159 self.camera = mapper.camera 

160 self.epoch = 2000.0 

161 

162 def _presliceFootprint(self, simData): 

163 """Loop over each pointing and find which sky points are observed """ 

164 # Now to make a list of lists for looking up the relevant observations at each slicepoint 

165 self.sliceLookup = [[] for dummy in range(self.nslice)] 

166 self.chipNames = [[] for dummy in range(self.nslice)] 

167 # Make a kdtree for the _slicepoints_ 

168 # Using scipy 0.16 or later 

169 self._buildTree(self.slicePoints['ra'], self.slicePoints['dec'], leafsize=self.leafsize) 

170 

171 # Loop over each unique pointing position 

172 if self.latLonDeg: 

173 lat = np.radians(simData[self.latCol]) 

174 lon = np.radians(simData[self.lonCol]) 

175 else: 

176 lat = simData[self.latCol] 

177 lon = simData[self.lonCol] 

178 for ind, ra, dec, rotSkyPos, mjd in zip(np.arange(simData.size), lon, lat, 

179 simData[self.rotSkyPosColName], simData[self.mjdColName]): 

180 dx, dy, dz = simsUtils._xyz_from_ra_dec(ra, dec) 

181 # Find healpixels inside the FoV 

182 hpIndices = np.array(self.opsimtree.query_ball_point((dx, dy, dz), self.rad)) 

183 if hpIndices.size > 0: 

184 obs_metadata = simsUtils.ObservationMetaData(pointingRA=np.degrees(ra), 

185 pointingDec=np.degrees(dec), 

186 rotSkyPos=np.degrees(rotSkyPos), 

187 mjd=mjd) 

188 

189 chipNames = _chipNameFromRaDec(self.slicePoints['ra'][hpIndices], 

190 self.slicePoints['dec'][hpIndices], 

191 epoch=self.epoch, 

192 camera=self.camera, obs_metadata=obs_metadata) 

193 if self.scienceChips: 

194 # I think it's W for wavefront sensor 

195 good = np.flatnonzero(np.core.defchararray.find(chipNames.astype(str), 'W') == -1) 

196 chipNames = chipNames[good] 

197 hpIndices = hpIndices[good] 

198 # If we are using only a subset of chips 

199 if self.chipsToUse != 'all': 

200 checkedChipNames = [chipName in self.chipsToUse for chipName in chipNames] 

201 good = np.where(checkedChipNames)[0] 

202 chipNames = chipNames[good] 

203 hpIndices = hpIndices[good] 

204 # Find the healpixels that fell on a chip for this pointing 

205 good = np.where(chipNames != [None])[0] 

206 hpOnChip = hpIndices[good] 

207 for i, chipName in zip(hpOnChip, chipNames[good]): 

208 self.sliceLookup[i].append(ind) 

209 self.chipNames[i].append(chipName) 

210 

211 if self.verbose: 

212 "Created lookup table after checking for chip gaps." 

213 

214 def _buildTree(self, simDataRa, simDataDec, leafsize=100): 

215 """Build KD tree on simDataRA/Dec using utility function from mafUtils. 

216 

217 simDataRA, simDataDec = RA and Dec values (in radians). 

218 leafsize = the number of Ra/Dec pointings in each leaf node.""" 

219 self.opsimtree = simsUtils._buildTree(simDataRa, 

220 simDataDec, 

221 leafsize) 

222 

223 def _setRad(self, radius=1.75): 

224 """Set radius (in degrees) for kdtree search using utility function from mafUtils.""" 

225 self.rad = simsUtils.xyz_angular_radius(radius)