Coverage for python/lsst/ip/isr/assembleCcdTask.py: 25%

Shortcuts 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

59 statements  

1# This file is part of ip_isr. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

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

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

7# for details of code ownership. 

8# 

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

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

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

12# (at your option) any later version. 

13# 

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

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

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

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

21 

22import lsst.afw.cameraGeom as cameraGeom 

23import lsst.afw.cameraGeom.utils as cameraGeomUtils 

24import lsst.afw.display as afwDisplay 

25import lsst.afw.image as afwImage 

26import lsst.pex.config as pexConfig 

27import lsst.pipe.base as pipeBase 

28from lsstDebug import getDebugFrame 

29 

30__all__ = ["AssembleCcdTask"] 

31 

32 

33class AssembleCcdConfig(pexConfig.Config): 

34 doTrim = pexConfig.Field( 

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

36 dtype=bool, 

37 default=True, 

38 ) 

39 keysToRemove = pexConfig.ListField( 

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

41 dtype=str, 

42 default=(), 

43 ) 

44 

45## @addtogroup LSST_task_documentation 

46## @{ 

47## @page AssembleCcdTask 

48## @ref AssembleCcdTask_ "AssembleCcdTask" 

49## @copybrief AssembleCcdTask 

50## @} 

51 

52 

53class AssembleCcdTask(pipeBase.Task): 

54 r"""! 

55 @anchor AssembleCcdTask_ 

56 

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

58 

59 @section ip_isr_assemble_Contents Contents 

60 

61 - @ref ip_isr_assemble_Purpose 

62 - @ref ip_isr_assemble_Initialize 

63 - @ref ip_isr_assemble_IO 

64 - @ref ip_isr_assemble_Config 

65 - @ref ip_isr_assemble_Debug 

66 - @ref ip_isr_assemble_Example 

67 

68 @section ip_isr_assemble_Purpose Description 

69 

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

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

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

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

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

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

76 

77 @section ip_isr_assemble_Initialize Task initialization 

78 

79 @copydoc \_\_init\_\_ 

80 

81 @section ip_isr_assemble_IO Inputs/Outputs to the assembleCcd method 

82 

83 @copydoc assembleCcd 

84 

85 @section ip_isr_assemble_Config Configuration parameters 

86 

87 See @ref AssembleCcdConfig 

88 

89 @section ip_isr_assemble_Debug Debug variables 

90 

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

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

93 href="https://developer.lsst.io/stack/debug.html">Debugging Tasks with lsstDebug</a> for more 

94 about @b debug.py files. 

95 

96 The available variables in AssembleCcdTask are: 

97 <DL> 

98 <DT> @c display 

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

100 <DL> 

101 <DT> assembledExposure 

102 <DD> display assembled exposure 

103 </DL> 

104 </DL> 

105 

106 @section ip_isr_assemble_Example A complete example of using AssembleCcdTask 

107 

108 <HR> 

109 To investigate the @ref ip_isr_assemble_Debug, put something like 

110 @code{.py} 

111 import lsstDebug 

112 def DebugInfo(name): 

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

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

115 di.display = {'assembledExposure':2} 

116 return di 

117 

118 lsstDebug.Info = DebugInfo 

119 @endcode 

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

121 

122 

123 Conversion notes: 

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

125 """ 

126 ConfigClass = AssembleCcdConfig 

127 _DefaultName = "assembleCcd" 

128 

129 def __init__(self, **kwargs): 

130 """!Initialize the AssembleCcdTask 

131 

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

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

134 """ 

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

136 

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

138 

139 def assembleCcd(self, assembleInput): 

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

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

142 lsst.afw.image.Exposure containing all raw 

143 amps. If a dictionary of amp exposures, 

144 the key should be the amp name. 

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

146 

147 @throws TypeError with the following string: 

148 

149 <DL> 

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

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

152 </DL> 

153 

154 @throws RuntimeError with the following string: 

155 

156 <DL> 

157 <DT> No ccd detector found 

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

159 </DL> 

160 """ 

161 ccd = None 

162 if isinstance(assembleInput, dict): 

163 # assembleInput is a dictionary of amp name: amp exposure 

164 

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

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

167 

168 def getNextExposure(amp): 

169 return assembleInput[amp.getName()] 

170 elif hasattr(assembleInput, "getMaskedImage"): 

171 # assembleInput is a single exposure 

172 ccd = assembleInput.getDetector() 

173 

174 def getNextExposure(amp): 

175 return assembleInput 

176 else: 

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

178 

179 if ccd is None: 

180 raise RuntimeError("No ccd detector found") 

181 

182 if not self.config.doTrim: 

183 outBox = cameraGeomUtils.calcRawCcdBBox(ccd) 

184 else: 

185 outBox = ccd.getBBox() 

186 outExposure = afwImage.ExposureF(outBox) 

187 outMI = outExposure.getMaskedImage() 

188 

189 if self.config.doTrim: 

190 assemble = cameraGeom.assembleAmplifierImage 

191 else: 

192 assemble = cameraGeom.assembleAmplifierRawImage 

193 

194 for amp in ccd: 

195 inMI = getNextExposure(amp).getMaskedImage() 

196 assemble(outMI, inMI, amp) 

197 # 

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

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

200 # place in a single Detector image 

201 # 

202 if not self.config.doTrim: 

203 ccd = cameraGeom.makeUpdatedDetector(ccd) 

204 

205 outExposure.setDetector(ccd) 

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

207 

208 return outExposure 

209 

210 def postprocessExposure(self, outExposure, inExposure): 

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

212 

213 Call after assembling the pixels 

214 

215 @param[in,out] outExposure assembled exposure: 

216 - removes unwanted keywords 

217 - sets wcs, filter, and detector 

218 @param[in] inExposure input exposure 

219 """ 

220 if inExposure.hasWcs(): 

221 outExposure.setWcs(inExposure.getWcs()) 

222 

223 exposureMetadata = inExposure.getMetadata() 

224 for key in self.allKeysToRemove: 

225 if exposureMetadata.exists(key): 

226 exposureMetadata.remove(key) 

227 outExposure.setMetadata(exposureMetadata) 

228 

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

230 outExposure.setFilterLabel(inExposure.getFilterLabel()) 

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

232 

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

234 if frame: 

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