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

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

# 

# LSST Data Management System 

# Copyright 2008-2016 AURA/LSST. 

# 

# 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 <https://www.lsstcorp.org/LegalNotices/>. 

# 

import re 

import numpy 

 

from . import getFCorImg, FluxFitParams, getJImg, calculateJacobian 

import lsst.afw.geom as afwGeom 

import lsst.afw.table as afwTable 

import lsst.afw.image as afwImage 

import lsst.afw.math as afwMath 

from lsst.pipe.base import Struct, TaskError 

from lsst.daf.persistence import NoResults 

from . import utils as mosaicUtils 

 

__all__ = ("applyMosaicResults", "getMosaicResults", "applyMosaicResultsExposure", 

"applyMosaicResultsCatalog", "applyCalib") 

 

 

def applyMosaicResults(dataRef, calexp=None): 

"""Deprecated function to apply the results to an exposure 

 

Deprecated, because the mosaic results can be applied to more than 

one kind of target, so it's worth changing the name to be specific. 

""" 

return applyMosaicResultsExposure(dataRef, calexp).exposure 

 

 

def applyMosaicResultsExposure(dataRef, calexp=None): 

"""Update an Exposure with the Wcs, Calib, and flux scaling from meas_mosaic. 

 

If None, the calexp will be loaded from the dataRef. Otherwise it is 

updated in-place. 

 

This assumes that the mosaic solution exists; an exception will be raised 

in the event that it does not. 

""" 

if calexp is None: 

calexp = dataRef.get("calexp", immediate=True) 

 

nQuarter = calexp.getDetector().getOrientation().getNQuarter() 

dims = calexp.getDimensions() 

hscRun = mosaicUtils.checkHscStack(calexp.getMetadata()) 

 

# Need the dimensions in coordinates used by meas_mosaic which defines 0,0 as the 

# lower-left hand corner on the sky 

if hscRun is None: 

if nQuarter%2 != 0: 

width, height = calexp.getDimensions() 

dims = afwGeom.Extent2I(height, width) 

 

# return results in meas_mosaic coordinate system 

mosaic = getMosaicResults(dataRef, dims) 

 

# rotate wcs back to LSST coordinate system 

if nQuarter%4 != 0 and hscRun is None: 

import lsst.meas.astrom as measAstrom 

mosaic.wcs = measAstrom.rotateWcsPixelsBy90(mosaic.wcs, 4 - nQuarter, dims) 

calexp.setWcs(mosaic.wcs) 

 

fluxMag0 = mosaic.calib.getInstFluxAtZeroMagnitude() 

calexp.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, 0.0)) 

 

mi = calexp.getMaskedImage() 

# rotate photometric correction to LSST coordiantes 

if nQuarter%4 != 0 and hscRun is None: 

mosaic.fcor = afwMath.rotateImageBy90(mosaic.fcor, 4 - nQuarter) 

mi *= mosaic.fcor 

 

return Struct(exposure=calexp, mosaic=mosaic) 

 

 

def getFluxFitParams(dataRef): 

"""Retrieve the flux correction parameters determined by meas_mosaic 

 

If the flux correction parameters do not exist, an exception will 

be raised. 

""" 

calexp_md = dataRef.get("calexp_md", immediate=True) 

hscRun = mosaicUtils.checkHscStack(calexp_md) 

if hscRun is not None: 

ffpHeader = dataRef.get("fcr_hsc_md", immediate=True) 

else: 

ffpHeader = dataRef.get("fcr_md", immediate=True) 

photoCalib = dataRef.get("fcr_photoCalib") 

ffp = FluxFitParams(ffpHeader) 

 

wcs = getWcs(dataRef) 

 

if hscRun is None: 

detector = dataRef.get("camera")[dataRef.dataId["ccd"]] 

nQuarter = detector.getOrientation().getNQuarter() 

if nQuarter%4 != 0: 

# Have to put this import here due to circular dependence in forcedPhotCcd.py in meas_base 

import lsst.meas.astrom as measAstrom 

dimensions = dataRef.get("calexp_bbox").getDimensions() 

wcs = measAstrom.rotateWcsPixelsBy90(wcs, nQuarter, dimensions) 

return Struct(ffp=ffp, calib=photoCalib, wcs=wcs) 

 

 

def getWcs(dataRef): 

"""Retrieve the Wcs determined by meas_mosaic 

 

If the Wcs does not exist, an exception will be raised. 

""" 

calexp_md = dataRef.get("calexp_md", immediate=True) 

hscRun = mosaicUtils.checkHscStack(calexp_md) 

if hscRun is not None: 

# Backwards compatibility with the very oldest meas_mosaic outputs 

return dataRef.get("wcs_hsc").getWcs() 

try: 

# Modern meas_mosaic outputs. 

return dataRef.get("jointcal_wcs") 

except NoResults: 

# Backwards compatibility with old meas_mosaic outputs 

return dataRef.get("wcs").getWcs() 

 

 

def getMosaicResults(dataRef, dims=None): 

"""Retrieve the results of meas_mosaic 

 

If None, the dims will be determined from the calexp header. 

""" 

ffp = getFluxFitParams(dataRef) 

 

if dims is None: 

bbox = dataRef.get("calexp_bbox") 

width, height = bbox.getWidth(), bbox.getHeight() 

else: 

width, height = dims 

 

fcor = getFCorImg(ffp.ffp, width, height) 

jcor = getJImg(ffp.wcs, width, height) 

fcor *= jcor 

del jcor 

 

return Struct(wcs=ffp.wcs, calib=ffp.calib, fcor=fcor) 

 

 

def applyMosaicResultsCatalog(dataRef, catalog, addCorrection=True): 

"""!Apply the results of meas_mosaic to a source catalog 

 

The coordinates and all fluxes are updated in-place with the meas_mosaic solution. 

 

This assumes that the mosaic solution exists; an exception will be raised 

in the event that it does not. 

""" 

ffp = getFluxFitParams(dataRef) 

calexp_md = dataRef.get("calexp_md", immediate=True) 

hscRun = mosaicUtils.checkHscStack(calexp_md) 

if hscRun is None: 

detector = dataRef.get("camera")[dataRef.dataId["ccd"]] 

nQuarter = detector.getOrientation().getNQuarter() 

if nQuarter%4 != 0: 

dimensions = dataRef.get("calexp_bbox").getDimensions() 

catalog = mosaicUtils.rotatePixelCoords(catalog, dimensions.getX(), dimensions.getY(), 

nQuarter) 

xx, yy = catalog.getX(), catalog.getY() 

corr = numpy.power(10.0, -0.4*ffp.ffp.eval(xx, yy))*calculateJacobian(ffp.wcs, xx, yy) 

 

if addCorrection: 

mapper = afwTable.SchemaMapper(catalog.schema, True) 

for s in catalog.schema: 

mapper.addMapping(s.key) 

corrField = afwTable.Field[float]("mosaic_corr", "Magnitude correction from meas_mosaic") 

corrKey = mapper.addOutputField(corrField) 

outCatalog = type(catalog)(mapper.getOutputSchema()) 

outCatalog.extend(catalog, mapper=mapper) 

outCatalog[corrKey][:] = corr 

catalog = outCatalog 

 

fluxKeys, errKeys = getFluxKeys(catalog.schema, hscRun=hscRun) 

for name, key in list(fluxKeys.items()) + list(errKeys.items()): 

# Note this skips correcting the aperture fluxes in HSC processed data, but that's ok because 

# we are using the flux_sinc as our comparison to base_CircularApertureFlux_12_0_flux 

if key.subfields is None: 

catalog[key][:] *= corr 

 

# Now rotate them back to the LSST coord system 

if hscRun is None: 

if nQuarter%4 != 0: 

catalog = mosaicUtils.rotatePixelCoordsBack(catalog, dimensions.getX(), 

dimensions.getY(), nQuarter) 

 

wcs = getWcs(dataRef) 

for rec in catalog: 

rec.updateCoord(wcs) 

 

return Struct(catalog=catalog, wcs=wcs, ffp=ffp) 

 

 

def applyCalib(catalog, photoCalib, hscRun=None): 

"""Convert all fluxes in a catalog to magnitudes 

 

The fluxes are converted in-place, so that the "_flux*" are now really 

magnitudes. 

""" 

fluxKeys, errKeys = getFluxKeys(catalog.schema, hscRun=hscRun) 

mapper = afwTable.SchemaMapper(catalog.schema, True) 

for item in catalog.schema: 

name = item.field.getName() 

if name in fluxKeys: 

continue 

mapper.addMapping(item.key) 

aliasMap = catalog.schema.getAliasMap() 

 

newFluxKeys = {} 

newErrKeys = {} 

for name in fluxKeys: 

fluxField = catalog.schema.find(name).field 

newName = name.replace("instFlux", "mag") 

newField = fluxField.__class__(newName, "Calibrated magnitude from %s (%s)" % 

(fluxField.getName(), fluxField.getDoc()), "mag") 

newFluxKeys[newName] = mapper.addMapping(fluxKeys[name], newField) 

 

errName = "Err" 

if hscRun is not None: 

errName = "_err" 

 

if name + errName in errKeys: 

errField = catalog.schema.find(name + errName).field 

newErrField = errField.__class__(newName + errName, 

"Calibrated magnitude error from %s (%s)" % 

(errField.getName(), errField.getDoc()), "mag") 

newErrKeys[newName] = mapper.addMapping(errKeys[name + errName], newErrField) 

aliasMap.set(name, newName) 

aliasMap.set(name + errName, newName + errName) 

 

newCatalog = afwTable.SourceCatalog(mapper.getOutputSchema()) 

newCatalog.extend(catalog, mapper=mapper) 

 

for name, key in newFluxKeys.items(): 

flux = newCatalog[key] 

if name in newErrKeys: 

result = photoCalib.instFluxToMagnitude(newCatalog, name.strip('_mag')) 

flux[:] = result[:, 0] 

newCatalog[newErrKeys[name]] = result[:, 1] 

else: 

flux[:] = numpy.array([photoCalib.instFluxToMagnitude(f) for f in flux]) 

 

return newCatalog 

 

 

def getFluxKeys(schema, hscRun=None): 

"""Retrieve the flux and flux error keys from a schema 

 

Both are returned as dicts indexed on the flux name (e.g. "base_PsfFlux" or "base_CmodelFlux"). 

""" 

if hscRun is None: 

fluxTypeStr = "_instFlux" 

fluxSchemaItems = schema.extract("*" + fluxTypeStr) 

# Do not include any flag fields (as determined by their type). Also exclude 

# slot fields, as these would effectively duplicate whatever they point to. 

fluxKeys = dict((name, schemaItem.key) for name, schemaItem in list(fluxSchemaItems.items()) if 

schemaItem.field.getTypeString() != "Flag" and 

not name.startswith("slot")) 

 

errSchemaItems = schema.extract("*" + fluxTypeStr + "Err") 

errKeys = dict((name, schemaItem.key) for name, schemaItem in list(errSchemaItems.items()) if 

name[:-len("Err")] in fluxKeys) 

else: 

schemaKeys = dict((s.field.getName(), s.key) for s in schema) 

fluxKeys = dict((name, key) for name, key in schemaKeys.items() if 

re.search(r"^(flux\_\w+|\w+\_instFlux)$", name) and not 

re.search(r"^(\w+\_apcorr)$", name) and name + "_err" in schemaKeys) 

errKeys = dict((name + "_err", schemaKeys[name + "_err"]) for name in fluxKeys if 

name + "_err" in schemaKeys) 

 

if len(fluxKeys) == 0: 

raise TaskError("No flux keys found") 

 

return fluxKeys, errKeys