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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

# 

# LSST Data Management System 

# Copyright 2008, 2009, 2010, 2011, 2012 LSST Corporation. 

# 

# This product includes software developed by the 

# LSST Project (http://www.lsst.org/). 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# This program is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the LSST License Statement and 

# the GNU General Public License along with this program. If not, 

# see <http://www.lsstcorp.org/LegalNotices/>. 

# 

import numpy as np 

 

import lsst.pex.config 

import lsst.afw.table 

import lsst.afw.geom 

from lsst.afw.cameraGeom import PIXELS, FOCAL_PLANE 

import lsst.afw.image 

import lsst.afw.math 

import lsst.afw.detection 

import lsst.pipe.base 

from lsst.meas.base.apCorrRegistry import getApCorrNameSet 

from lsst.meas.algorithms.testUtils import makeRandomTransmissionCurve 

 

 

class MockObservationConfig(lsst.pex.config.Config): 

pixelScale = lsst.pex.config.Field( 

dtype=float, default=0.2, optional=False, 

doc="Pixel scale for mock WCSs in arcseconds/pixel" 

) 

doRotate = lsst.pex.config.Field( 

dtype=bool, default=True, optional=False, 

doc="Whether to randomly rotate observations relative to the tract Wcs" 

) 

fluxMag0 = lsst.pex.config.Field( 

dtype=float, default=1E11, optional=False, 

doc="Flux at zero magnitude used to define Calibs." 

) 

fluxMag0Err = lsst.pex.config.Field( 

dtype=float, default=100.0, optional=False, 

doc="Error on flux at zero magnitude used to define Calibs; used to add scatter as well." 

) 

expTime = lsst.pex.config.Field( 

dtype=float, default=60.0, optional=False, 

doc="Exposure time set in generated Calibs (does not affect flux or noise level)" 

) 

psfImageSize = lsst.pex.config.Field( 

dtype=int, default=21, optional=False, 

doc="Image width and height of generated Psfs." 

) 

psfMinSigma = lsst.pex.config.Field( 

dtype=float, default=1.5, optional=False, 

doc="Minimum radius for generated Psfs." 

) 

psfMaxSigma = lsst.pex.config.Field( 

dtype=float, default=3.0, optional=False, 

doc="Maximum radius for generated Psfs." 

) 

apCorrOrder = lsst.pex.config.Field( 

dtype=int, default=1, optional=False, 

doc="Polynomial order for aperture correction fields" 

) 

seed = lsst.pex.config.Field(dtype=int, default=1, doc="Seed for numpy random number generator") 

 

 

class MockObservationTask(lsst.pipe.base.Task): 

"""Task to generate mock Exposure parameters (Wcs, Psf, Calib), intended for use as a subtask 

of MockCoaddTask. 

 

@todo: 

- document "pa" in detail; angle of what to what? 

- document the catalog parameter of the run method 

""" 

 

ConfigClass = MockObservationConfig 

 

def __init__(self, **kwds): 

lsst.pipe.base.Task.__init__(self, **kwds) 

self.schema = lsst.afw.table.ExposureTable.makeMinimalSchema() 

self.ccdKey = self.schema.addField("ccd", type=np.int32, doc="CCD number") 

self.visitKey = self.schema.addField("visit", type=np.int32, doc="visit number") 

self.pointingKey = lsst.afw.table.CoordKey.addFields(self.schema, "pointing", "center of visit") 

self.filterKey = self.schema.addField("filter", type=str, doc="Bandpass filter name", size=16) 

self.rng = np.random.RandomState(self.config.seed) 

 

def run(self, butler, n, tractInfo, camera, catalog=None): 

"""Driver that generates an ExposureCatalog of mock observations. 

 

@param[in] butler: a data butler 

@param[in] n: number of pointings 

@param[in] camera: camera geometry (an lsst.afw.cameraGeom.Camera) 

@param[in] catalog: catalog to which to add observations (an ExposureCatalog); 

if None then a new catalog is created. 

 

@todo figure out what `pa` is and use that knowledge to set `boresightRotAng` and `rotType` 

""" 

107 ↛ 110line 107 didn't jump to line 110, because the condition on line 107 was never false if catalog is None: 

catalog = lsst.afw.table.ExposureCatalog(self.schema) 

else: 

if not catalog.getSchema().contains(self.schema): 

raise ValueError("Catalog schema does not match Task schema") 

visit = 1 

 

for position, pa in self.makePointings(n, tractInfo): 

visitInfo = lsst.afw.image.VisitInfo( 

exposureTime=self.config.expTime, 

date=lsst.daf.base.DateTime.now(), 

boresightRaDec=position, 

) 

for detector in camera: 

calib = self.buildCalib() 

record = catalog.addNew() 

record.setI(self.ccdKey, detector.getId()) 

record.setI(self.visitKey, visit) 

record.set(self.filterKey, 'r') 

record.set(self.pointingKey, position) 

record.setWcs(self.buildWcs(position, pa, detector)) 

record.setCalib(calib) 

record.setVisitInfo(visitInfo) 

record.setPsf(self.buildPsf(detector)) 

record.setApCorrMap(self.buildApCorrMap(detector)) 

record.setTransmissionCurve(self.buildTransmissionCurve(detector)) 

record.setBBox(detector.getBBox()) 

detectorId = detector.getId() 

obj = butler.get("ccdExposureId", visit=visit, ccd=detectorId, immediate=True) 

record.setId(obj) 

visit += 1 

return catalog 

 

def makePointings(self, n, tractInfo): 

"""Generate (celestial) positions and rotation angles that define field locations. 

 

Default implementation draws random pointings that are uniform in the tract's image 

coordinate system. 

 

@param[in] n: number of pointings 

@param[in] tractInfo: skymap tract (a lsst.skymap.TractInfo) 

@return a Python iterable over (coord, angle) pairs: 

- coord is an ICRS object position (an lsst.afw.geom.SpherePoint) 

- angle is a position angle (???) (an lsst.afw.geom.Angle) 

 

The default implementation returns an iterator (i.e. the function is a "generator"), 

but derived-class overrides may return any iterable. 

""" 

wcs = tractInfo.getWcs() 

bbox = lsst.afw.geom.Box2D(tractInfo.getBBox()) 

bbox.grow(lsst.afw.geom.Extent2D(-0.1 * bbox.getWidth(), -0.1 * bbox.getHeight())) 

for i in range(n): 

x = self.rng.rand() * bbox.getWidth() + bbox.getMinX() 

y = self.rng.rand() * bbox.getHeight() + bbox.getMinY() 

pa = 0.0 * lsst.afw.geom.radians 

162 ↛ 164line 162 didn't jump to line 164, because the condition on line 162 was never false if self.config.doRotate: 

pa = self.rng.rand() * 2.0 * np.pi * lsst.afw.geom.radians 

yield wcs.pixelToSky(x, y), pa 

 

def buildWcs(self, position, pa, detector): 

"""Build a simple TAN Wcs with no distortion and exactly-aligned CCDs. 

 

@param[in] position: ICRS object position on sky (on lsst.afw.geom.SpherePoint) 

@param[in] pa: position angle (an lsst.afw.geom.Angle) 

@param[in] detector: detector information (an lsst.afw.cameraGeom.Detector) 

""" 

crval = position 

pixelScale = (self.config.pixelScale * lsst.afw.geom.arcseconds).asDegrees() 

cd = (lsst.afw.geom.LinearTransform.makeScaling(pixelScale) * 

lsst.afw.geom.LinearTransform.makeRotation(pa)) 

crpix = detector.transform(lsst.afw.geom.Point2D(0, 0), FOCAL_PLANE, PIXELS) 

wcs = lsst.afw.geom.makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cd.getMatrix()) 

return wcs 

 

def buildCalib(self): 

"""Build a simple Calib object with exposure time fixed by config, fluxMag0 drawn from 

a Gaussian defined by config, and mid-time set to DateTime.now(). 

""" 

calib = lsst.afw.image.Calib() 

calib.setFluxMag0( 

self.rng.randn() * self.config.fluxMag0Err + self.config.fluxMag0, 

self.config.fluxMag0Err 

) 

return calib 

 

def buildPsf(self, detector): 

"""Build a simple Gaussian Psf with linearly-varying ellipticity and size. 

 

The Psf pattern increases sigma_x linearly along the x direction, and sigma_y 

linearly along the y direction. 

 

@param[in] detector: detector information (an lsst.afw.cameraGeom.Detector) 

@return a psf (an instance of lsst.meas.algorithms.KernelPsf) 

""" 

bbox = detector.getBBox() 

dx = (self.config.psfMaxSigma - self.config.psfMinSigma) / bbox.getWidth() 

dy = (self.config.psfMaxSigma - self.config.psfMinSigma) / bbox.getHeight() 

sigmaXFunc = lsst.afw.math.PolynomialFunction2D(1) 

sigmaXFunc.setParameter(0, self.config.psfMinSigma - dx * bbox.getMinX() - dy * bbox.getMinY()) 

sigmaXFunc.setParameter(1, dx) 

sigmaXFunc.setParameter(2, 0.0) 

sigmaYFunc = lsst.afw.math.PolynomialFunction2D(1) 

sigmaYFunc.setParameter(0, self.config.psfMinSigma) 

sigmaYFunc.setParameter(1, 0.0) 

sigmaYFunc.setParameter(2, dy) 

angleFunc = lsst.afw.math.PolynomialFunction2D(0) 

spatialFuncList = [] 

spatialFuncList.append(sigmaXFunc) 

spatialFuncList.append(sigmaYFunc) 

spatialFuncList.append(angleFunc) 

kernel = lsst.afw.math.AnalyticKernel( 

self.config.psfImageSize, self.config.psfImageSize, 

lsst.afw.math.GaussianFunction2D(self.config.psfMinSigma, self.config.psfMinSigma), 

spatialFuncList 

) 

return lsst.meas.algorithms.KernelPsf(kernel) 

 

def buildApCorrMap(self, detector): 

"""Build an ApCorrMap with random linearly-varying fields for all 

flux fields registered for aperture correction. 

 

These flux field names are used only as strings; there is no 

connection to any actual algorithms with those names or the PSF model. 

""" 

order = self.config.apCorrOrder 

 

def makeRandomBoundedField(): 

"""Make an upper-left triangular coefficient array appropriate 

for a 2-d polynomial.""" 

array = np.zeros((order + 1, order + 1), dtype=float) 

for n in range(order + 1): 

array[n, 0:order + 1 - n] = self.rng.randn(order + 1 - n) 

return lsst.afw.math.ChebyshevBoundedField(bbox, array) 

 

bbox = detector.getBBox() 

apCorrMap = lsst.afw.image.ApCorrMap() 

for name in getApCorrNameSet(): 

apCorrMap.set(name + "_instFlux", makeRandomBoundedField()) 

apCorrMap.set(name + "_instFluxErr", makeRandomBoundedField()) 

return apCorrMap 

 

def buildTransmissionCurve(self, detector): 

"""Build a random spacially-varying TransmissionCurve.""" 

bbox = detector.getBBox() 

return makeRandomTransmissionCurve(rng=self.rng, maxRadius=max(bbox.getWidth(), bbox.getHeight()))