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

# 

# LSST Data Management System 

# Copyright 2008-2017 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/>. 

# 

""" 

Measure intra-CCD crosstalk coefficients. 

""" 

 

__all__ = ["extractCrosstalkRatios", "measureCrosstalkCoefficients", 

"MeasureCrosstalkConfig", "MeasureCrosstalkTask"] 

 

 

import itertools 

import numpy as np 

 

from lsst.afw.detection import FootprintSet, Threshold 

from lsst.pex.config import Config, Field, ListField, ConfigurableField 

from lsst.pipe.base import CmdLineTask 

 

from .crosstalk import calculateBackground, extractAmp 

from .isrTask import IsrTask 

 

 

def extractCrosstalkRatios(exposure, threshold=30000, badPixels=["SAT", "BAD", "INTRP"]): 

"""Extract crosstalk ratios between different amplifiers 

 

For pixels above ``threshold``, we calculate the ratio between each 

target amp and source amp. We return a list of ratios for each pixel 

for each target/source combination, as a matrix of lists. 

 

Parameters 

---------- 

exposure : `lsst.afw.image.Exposure` 

Exposure for which to measure crosstalk. 

threshold : `float` 

Lower limit on pixels for which we measure crosstalk. 

badPixels : `list` of `str` 

Mask planes indicating a pixel is bad. 

 

Returns 

------- 

ratios : `list` of `list` of `numpy.ndarray` 

A matrix of pixel arrays. ``ratios[i][j]`` is an array of 

the fraction of the ``j``-th amp present on the ``i``-th amp. 

The value is `None` for the diagonal elements. 

""" 

mi = exposure.getMaskedImage() 

FootprintSet(mi, Threshold(threshold), "DETECTED") 

detected = mi.getMask().getPlaneBitMask("DETECTED") 

bad = mi.getMask().getPlaneBitMask(badPixels) 

bg = calculateBackground(mi, badPixels + ["DETECTED"]) 

 

ccd = exposure.getDetector() 

 

ratios = [[None for iAmp in ccd] for jAmp in ccd] 

 

for ii, iAmp in enumerate(ccd): 

iImage = mi.Factory(mi, iAmp.getBBox()) 

iMask = iImage.getMask().getArray() 

select = (iMask & detected > 0) & (iMask & bad == 0) & np.isfinite(iImage.getImage().getArray()) 

for jj, jAmp in enumerate(ccd): 

if ii == jj: 

continue 

jImage = extractAmp(mi.getImage(), jAmp, iAmp.getReadoutCorner()) 

ratios[jj][ii] = (jImage.getArray()[select] - bg)/iImage.getImage().getArray()[select] 

 

return ratios 

 

 

def measureCrosstalkCoefficients(ratios, rejIter=3, rejSigma=2.0): 

"""Measure crosstalk coefficients from the ratios 

 

Given a list of ratios for each target/source amp combination, 

we measure a robust mean and error. 

 

The coefficient errors returned are the (robust) standard deviation of 

the input ratios. 

 

Parameters 

---------- 

ratios : `list` of `list` of `numpy.ndarray` 

Matrix of arrays of ratios. 

rejIter : `int` 

Number of rejection iterations. 

rejSigma : `float` 

Rejection threshold (sigma). 

 

Returns 

------- 

coeff : `numpy.ndarray` 

Crosstalk coefficients. 

coeffErr : `numpy.ndarray` 

Crosstalk coefficient errors. 

coeffNum : `numpy.ndarray` 

Number of pixels for each measurement. 

""" 

numAmps = len(ratios) 

assert all(len(rr) == numAmps for rr in ratios) 

 

coeff = np.zeros((numAmps, numAmps)) 

coeffErr = np.zeros((numAmps, numAmps)) 

coeffNum = np.zeros((numAmps, numAmps), dtype=int) 

 

for ii, jj in itertools.product(range(numAmps), range(numAmps)): 

if ii == jj: 

continue 

values = np.array(ratios[ii][jj]) 

values = values[np.abs(values) < 1.0] # Discard unreasonable values 

for rej in range(rejIter): 

lo, med, hi = np.percentile(values, [25.0, 50.0, 75.0]) 

sigma = 0.741*(hi - lo) 

good = np.abs(values - med) < rejSigma*sigma 

130 ↛ 131line 130 didn't jump to line 131, because the condition on line 130 was never true if good.sum() == len(good): 

break 

values = values[good] 

 

coeff[ii][jj] = np.mean(values) 

coeffErr[ii][jj] = np.std(values) 

coeffNum[ii][jj] = len(values) 

 

return coeff, coeffErr, coeffNum 

 

 

class MeasureCrosstalkConfig(Config): 

"""Configuration for MeasureCrosstalkTask""" 

isr = ConfigurableField(target=IsrTask, doc="Instrument signature removal") 

threshold = Field(dtype=float, default=30000, doc="Minimum level for which to measure crosstalk") 

badMask = ListField(dtype=str, default=["SAT", "BAD", "INTRP"], doc="Mask planes to ignore") 

rejIter = Field(dtype=int, default=3, doc="Number of rejection iterations") 

rejSigma = Field(dtype=float, default=2.0, doc="Rejection threshold (sigma)") 

 

def setDefaults(self): 

Config.setDefaults(self) 

self.isr.doWrite = False 

self.isr.growSaturationFootprintSize = 0 # We want the saturation spillover: it's good signal 

 

 

class MeasureCrosstalkTask(CmdLineTask): 

"""Measure intra-CCD crosstalk 

 

This Task behaves in a scatter-gather fashion: 

* Scatter: get ratios for each CCD. 

* Gather: combine ratios to produce crosstalk coefficients. 

""" 

ConfigClass = MeasureCrosstalkConfig 

_DefaultName = "measureCrosstalk" 

 

def __init__(self, *args, **kwargs): 

CmdLineTask.__init__(self, *args, **kwargs) 

self.makeSubtask("isr") 

 

@classmethod 

def _makeArgumentParser(cls): 

parser = super(MeasureCrosstalkTask, cls)._makeArgumentParser() 

parser.add_argument("--dump-ratios", dest="dumpRatios", 

help="Name of pickle file to which to write crosstalk ratios") 

return parser 

 

@classmethod 

def parseAndRun(cls, *args, **kwargs): 

"""Implement scatter/gather 

 

Returns 

------- 

coeff : `numpy.ndarray` 

Crosstalk coefficients. 

coeffErr : `numpy.ndarray` 

Crosstalk coefficient errors. 

coeffNum : `numpy.ndarray` 

Number of pixels used for crosstalk measurement. 

""" 

kwargs["doReturnResults"] = True 

results = super(MeasureCrosstalkTask, cls).parseAndRun(*args, **kwargs) 

task = cls(config=results.parsedCmd.config, log=results.parsedCmd.log) 

resultList = [rr.result for rr in results.resultList] 

if results.parsedCmd.dumpRatios: 

import pickle 

pickle.dump(resultList, open(results.parsedCmd.dumpRatios, "w")) 

return task.reduce(resultList) 

 

def runDataRef(self, dataRef): 

"""Get crosstalk ratios for CCD 

 

Parameters 

---------- 

dataRef : `lsst.daf.peristence.ButlerDataRef` 

Data reference for CCD. 

 

Returns 

------- 

ratios : `list` of `list` of `numpy.ndarray` 

A matrix of pixel arrays. 

""" 

exposure = self.isr.runDataRef(dataRef).exposure 

ratios = extractCrosstalkRatios(exposure, self.config.threshold, list(self.config.badMask)) 

self.log.info("Extracted %d pixels from %s", 

sum(len(jj) for ii in ratios for jj in ii if jj is not None), dataRef.dataId) 

return ratios 

 

def reduce(self, ratioList): 

"""Combine ratios to produce crosstalk coefficients 

 

Parameters 

---------- 

ratioList : `list` of `list` of `list` of `numpy.ndarray` 

A list of matrices of arrays; a list of results from 

`extractCrosstalkRatios`. 

 

Returns 

------- 

coeff : `numpy.ndarray` 

Crosstalk coefficients. 

coeffErr : `numpy.ndarray` 

Crosstalk coefficient errors. 

coeffNum : `numpy.ndarray` 

Number of pixels used for crosstalk measurement. 

""" 

numAmps = len(ratioList[0]) 

assert all(len(rr) == numAmps for rr in ratioList) 

assert all(all(len(xx) == numAmps for xx in rr) for rr in ratioList) 

ratios = [[None for jj in range(numAmps)] for ii in range(numAmps)] 

for ii, jj in itertools.product(range(numAmps), range(numAmps)): 

if ii == jj: 

result = [] 

else: 

values = [rr[ii][jj] for rr in ratioList] 

num = sum(len(vv) for vv in values) 

245 ↛ 246line 245 didn't jump to line 246, because the condition on line 245 was never true if num == 0: 

raise RuntimeError("No values for matrix element %d,%d" % (ii, jj)) 

result = np.concatenate([vv for vv in values if len(vv) > 0]) 

ratios[ii][jj] = result 

coeff, coeffErr, coeffNum = measureCrosstalkCoefficients(ratios, self.config.rejIter, 

self.config.rejSigma) 

self.log.info("Coefficients:\n%s\n", coeff) 

self.log.info("Errors:\n%s\n", coeffErr) 

self.log.info("Numbers:\n%s\n", coeffNum) 

return coeff, coeffErr, coeffNum 

 

def _getConfigName(self): 

"""Disable config output""" 

return None 

 

def _getMetadataName(self): 

"""Disable metdata output""" 

return None