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

# This file is part of ip_isr. 

# 

# Developed for the LSST Data Management System. 

# This product includes software developed by the LSST Project 

# (https://www.lsst.org). 

# See the COPYRIGHT file at the top-level directory of this distribution 

# for details of code ownership. 

# 

# 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 GNU General Public License 

# along with this program. If not, see <https://www.gnu.org/licenses/>. 

 

import lsst.afw.cameraGeom as cameraGeom 

import lsst.afw.cameraGeom.utils as cameraGeomUtils 

import lsst.afw.display as afwDisplay 

import lsst.afw.image as afwImage 

import lsst.pex.config as pexConfig 

import lsst.pipe.base as pipeBase 

from lsstDebug import getDebugFrame 

 

__all__ = ["AssembleCcdTask"] 

 

 

class AssembleCcdConfig(pexConfig.Config): 

doTrim = pexConfig.Field( 

doc="trim out non-data regions?", 

dtype=bool, 

default=True, 

) 

keysToRemove = pexConfig.ListField( 

doc="FITS headers to remove (in addition to DATASEC, BIASSEC, TRIMSEC and perhaps GAIN)", 

dtype=str, 

default=(), 

) 

 

## @addtogroup LSST_task_documentation 

## @{ 

## @page AssembleCcdTask 

## @ref AssembleCcdTask_ "AssembleCcdTask" 

## @copybrief AssembleCcdTask 

## @} 

 

 

class AssembleCcdTask(pipeBase.Task): 

r"""! 

@anchor AssembleCcdTask_ 

 

@brief Assemble a set of amplifier images into a full detector size set of pixels. 

 

@section ip_isr_assemble_Contents Contents 

 

- @ref ip_isr_assemble_Purpose 

- @ref ip_isr_assemble_Initialize 

- @ref ip_isr_assemble_IO 

- @ref ip_isr_assemble_Config 

- @ref ip_isr_assemble_Debug 

- @ref ip_isr_assemble_Example 

 

@section ip_isr_assemble_Purpose Description 

 

This task assembles sections of an image into a larger mosaic. The sub-sections 

are typically amplifier sections and are to be assembled into a detector size pixel grid. 

The assembly is driven by the entries in the raw amp information. The task can be configured 

to return a detector image with non-data (e.g. overscan) pixels included. The task can also 

renormalize the pixel values to a nominal gain of 1. The task also removes exposure metadata that 

has context in raw amps, but not in trimmed detectors (e.g. 'BIASSEC'). 

 

@section ip_isr_assemble_Initialize Task initialization 

 

@copydoc \_\_init\_\_ 

 

@section ip_isr_assemble_IO Inputs/Outputs to the assembleCcd method 

 

@copydoc assembleCcd 

 

@section ip_isr_assemble_Config Configuration parameters 

 

See @ref AssembleCcdConfig 

 

@section ip_isr_assemble_Debug Debug variables 

 

The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a 

flag @c -d to import @b debug.py from your @c PYTHONPATH; see <a 

href="http://lsst-web.ncsa.illinois.edu/~buildbot/doxygen/x_masterDoxyDoc/base_debug.html"> 

Using lsstDebug to control debugging output</a> for more about @b debug.py files. 

 

The available variables in AssembleCcdTask are: 

<DL> 

<DT> @c display 

<DD> A dictionary containing debug point names as keys with frame number as value. Valid keys are: 

<DL> 

<DT> assembledExposure 

<DD> display assembled exposure 

</DL> 

</DL> 

 

@section ip_isr_assemble_Example A complete example of using AssembleCcdTask 

 

This code is in runAssembleTask.py in the examples directory, and can be run as @em e.g. 

@code 

python examples/runAssembleTask.py 

@endcode 

 

@dontinclude runAssembleTask.py 

Import the task. There are other imports. Read the source file for more info. 

@skipline AssembleCcdTask 

 

@dontinclude exampleUtils.py 

Create some input images with the help of some utilities in examples/exampleUtils.py 

@skip makeAssemblyInput 

@until inputData 

The above numbers can be changed. The assumption that the readout corner is flipped on every other amp is 

hardcoded in createDetector. 

 

@dontinclude runAssembleTask.py 

Run the assembler task 

@skip runAssembler 

@until frame += 1 

 

<HR> 

To investigate the @ref ip_isr_assemble_Debug, put something like 

@code{.py} 

import lsstDebug 

def DebugInfo(name): 

di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively 

if name == "lsst.ip.isr.assembleCcdTask": 

di.display = {'assembledExposure':2} 

return di 

 

lsstDebug.Info = DebugInfo 

@endcode 

into your debug.py file and run runAssembleTask.py with the @c --debug flag. 

 

 

Conversion notes: 

Display code should be updated once we settle on a standard way of controlling what is displayed. 

""" 

ConfigClass = AssembleCcdConfig 

_DefaultName = "assembleCcd" 

 

def __init__(self, **kwargs): 

"""!Initialize the AssembleCcdTask 

 

The keys for removal specified in the config are added to a default set: 

('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN') 

""" 

pipeBase.Task.__init__(self, **kwargs) 

 

self.allKeysToRemove = ('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN') + tuple(self.config.keysToRemove) 

 

def assembleCcd(self, assembleInput): 

"""!Assemble a set of amps into a single CCD size image 

@param[in] assembleInput -- Either a dictionary of amp lsst.afw.image.Exposures or a single 

lsst.afw.image.Exposure containing all raw 

amps. If a dictionary of amp exposures, 

the key should be the amp name. 

@return assembledCcd -- An lsst.afw.image.Exposure of the assembled amp sections. 

 

@throws TypeError with the following string: 

 

<DL> 

<DT> Expected either a dictionary of amp exposures or a single raw exposure 

<DD> The input exposures to be assembled do not adhere to the required format. 

</DL> 

 

@throws RuntimeError with the following string: 

 

<DL> 

<DT> No ccd detector found 

<DD> The detector set on the input exposure is not set. 

</DL> 

""" 

ccd = None 

if isinstance(assembleInput, dict): 

# assembleInput is a dictionary of amp name: amp exposure 

 

# Assume all amps have the same detector, so get the detector from an arbitrary amp 

ccd = next(iter(assembleInput.values())).getDetector() 

 

def getNextExposure(amp): 

return assembleInput[amp.getName()] 

elif hasattr(assembleInput, "getMaskedImage"): 

# assembleInput is a single exposure 

ccd = assembleInput.getDetector() 

 

def getNextExposure(amp): 

return assembleInput 

else: 

raise TypeError("Expected either a dictionary of amp exposures or a single raw exposure") 

 

if ccd is None: 

raise RuntimeError("No ccd detector found") 

 

if not self.config.doTrim: 

outBox = cameraGeomUtils.calcRawCcdBBox(ccd) 

else: 

outBox = ccd.getBBox() 

outExposure = afwImage.ExposureF(outBox) 

outMI = outExposure.getMaskedImage() 

 

if self.config.doTrim: 

assemble = cameraGeom.assembleAmplifierImage 

else: 

assemble = cameraGeom.assembleAmplifierRawImage 

 

for amp in ccd: 

inMI = getNextExposure(amp).getMaskedImage() 

assemble(outMI, inMI, amp) 

# 

# If we are returning an "untrimmed" image (with overscans and extended register) we 

# need to update the ampInfo table in the Detector as we've moved the amp images into 

# place in a single Detector image 

# 

if not self.config.doTrim: 

ccd = cameraGeom.makeUpdatedDetector(ccd) 

 

outExposure.setDetector(ccd) 

self.postprocessExposure(outExposure=outExposure, inExposure=getNextExposure(ccd[0])) 

 

return outExposure 

 

def postprocessExposure(self, outExposure, inExposure): 

"""Set exposure non-image attributes, including wcs and metadata and display exposure (if requested) 

 

Call after assembling the pixels 

 

@param[in,out] outExposure assembled exposure: 

- removes unwanted keywords 

- sets photoCalib, filter, and detector 

@param[in] inExposure input exposure 

""" 

self.setWcs(outExposure=outExposure, inExposure=inExposure) 

 

exposureMetadata = inExposure.getMetadata() 

for key in self.allKeysToRemove: 

if exposureMetadata.exists(key): 

exposureMetadata.remove(key) 

outExposure.setMetadata(exposureMetadata) 

 

# note: don't copy PhotoCalib, because it is assumed to be unknown in raw data 

outExposure.setFilter(inExposure.getFilter()) 

outExposure.getInfo().setVisitInfo(inExposure.getInfo().getVisitInfo()) 

 

frame = getDebugFrame(self._display, "assembledExposure") 

if frame: 

afwDisplay.Display(frame=frame).mtv(outExposure, title="postprocessExposure") 

 

def setWcs(self, outExposure, inExposure): 

"""Set output WCS = input WCS, adjusted as required for datasecs not starting at lower left corner 

 

@param[in,out] outExposure assembled exposure; wcs is set 

@param[in] inExposure input exposure 

""" 

if inExposure.hasWcs(): 

wcs = inExposure.getWcs() 

ccd = outExposure.getDetector() 

amp0 = ccd[0] 

if amp0 is None: 

raise RuntimeError("No amplifier detector information found") 

adjustedWcs = cameraGeomUtils.prepareWcsData(wcs, amp0, isTrimmed=self.config.doTrim) 

outExposure.setWcs(adjustedWcs)