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

60 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-26 10:32 +0000

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 

58 pixels. 

59 

60 @section ip_isr_assemble_Contents Contents 

61 

62 - @ref ip_isr_assemble_Purpose 

63 - @ref ip_isr_assemble_Initialize 

64 - @ref ip_isr_assemble_IO 

65 - @ref ip_isr_assemble_Config 

66 - @ref ip_isr_assemble_Debug 

67 - @ref ip_isr_assemble_Example 

68 

69 @section ip_isr_assemble_Purpose Description 

70 

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

72 sub-sections are typically amplifier sections and are to be assembled 

73 into a detector size pixel grid. The assembly is driven by the entries in 

74 the raw amp information. The task can be configured to return a detector 

75 image with non-data (e.g. overscan) pixels included. The task can also 

76 renormalize the pixel values to a nominal gain of 1. The task also 

77 removes exposure metadata that has context in raw amps, but not in trimmed 

78 detectors (e.g. 'BIASSEC'). 

79 

80 @section ip_isr_assemble_Initialize Task initialization 

81 

82 @copydoc \_\_init\_\_ 

83 

84 @section ip_isr_assemble_IO Inputs/Outputs to the assembleCcd method 

85 

86 @copydoc assembleCcd 

87 

88 @section ip_isr_assemble_Config Configuration parameters 

89 

90 See @ref AssembleCcdConfig 

91 

92 @section ip_isr_assemble_Debug Debug variables 

93 

94 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink 

95 interface supports a flag @c -d to import @b debug.py from your 

96 @c PYTHONPATH; see <a 

97 href="https://developer.lsst.io/stack/debug.html">Debugging Tasks with 

98 lsstDebug</a> for more about @b debug.py files. 

99 

100 The available variables in AssembleCcdTask are: 

101 <DL> 

102 <DT> @c display 

103 <DD> A dictionary containing debug point names as keys with frame number 

104 as value. Valid keys are: 

105 <DL> 

106 <DT> assembledExposure 

107 <DD> display assembled exposure 

108 </DL> 

109 </DL> 

110 

111 @section ip_isr_assemble_Example A complete example of using 

112 AssembleCcdTask 

113 

114 <HR> 

115 To investigate the @ref ip_isr_assemble_Debug, put something like 

116 @code{.py} 

117 import lsstDebug 

118 def DebugInfo(name): 

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

120 # us recursively 

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

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

123 return di 

124 

125 lsstDebug.Info = DebugInfo 

126 @endcode 

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

128 flag. 

129 

130 

131 Conversion notes: 

132 Display code should be updated once we settle on a standard way of 

133 controlling what is displayed. 

134 """ 

135 ConfigClass = AssembleCcdConfig 

136 _DefaultName = "assembleCcd" 

137 

138 def __init__(self, **kwargs): 

139 """!Initialize the AssembleCcdTask 

140 

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

142 set: ('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN') 

143 """ 

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

145 

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

147 

148 def assembleCcd(self, assembleInput): 

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

150 @param[in] assembleInput -- Either a dictionary of amp 

151 lsst.afw.image.Exposures or a single 

152 lsst.afw.image.Exposure containing all raw 

153 amps. If a dictionary of amp exposures, 

154 the key should be the amp name. 

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

156 amp sections. 

157 

158 @throws TypeError with the following string: 

159 

160 <DL> 

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

162 exposure. 

163 <DD> The input exposures to be assembled do not adhere to the 

164 required format. 

165 </DL> 

166 

167 @throws RuntimeError with the following string: 

168 

169 <DL> 

170 <DT> No ccd detector found 

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

172 </DL> 

173 """ 

174 ccd = None 

175 if isinstance(assembleInput, dict): 

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

177 

178 # Assume all amps have the same detector, so get the detector from 

179 # an arbitrary amp. 

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

181 

182 def getNextExposure(amp): 

183 return assembleInput[amp.getName()] 

184 elif hasattr(assembleInput, "getMaskedImage"): 

185 # assembleInput is a single exposure 

186 ccd = assembleInput.getDetector() 

187 

188 def getNextExposure(amp): 

189 return assembleInput 

190 else: 

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

192 

193 if ccd is None: 

194 raise RuntimeError("No ccd detector found") 

195 

196 if not self.config.doTrim: 

197 outBox = cameraGeomUtils.calcRawCcdBBox(ccd) 

198 else: 

199 outBox = ccd.getBBox() 

200 outExposure = afwImage.ExposureF(outBox) 

201 outMI = outExposure.getMaskedImage() 

202 

203 if self.config.doTrim: 

204 assemble = cameraGeom.assembleAmplifierImage 

205 else: 

206 assemble = cameraGeom.assembleAmplifierRawImage 

207 

208 for amp in ccd: 

209 inMI = getNextExposure(amp).getMaskedImage() 

210 assemble(outMI, inMI, amp) 

211 # 

212 # If we are returning an "untrimmed" image (with overscans and 

213 # extended register) we need to update the ampInfo table in the 

214 # Detector as we've moved the amp images into 

215 # place in a single Detector image 

216 # 

217 if not self.config.doTrim: 

218 ccd = cameraGeom.makeUpdatedDetector(ccd) 

219 

220 outExposure.setDetector(ccd) 

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

222 

223 return outExposure 

224 

225 def postprocessExposure(self, outExposure, inExposure): 

226 """Set exposure non-image attributes, including wcs and metadata and 

227 display exposure (if requested) 

228 

229 Call after assembling the pixels 

230 

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

232 - removes unwanted keywords 

233 - sets wcs, filter, and detector 

234 @param[in] inExposure input exposure 

235 """ 

236 if inExposure.hasWcs(): 

237 outExposure.setWcs(inExposure.getWcs()) 

238 

239 exposureMetadata = inExposure.getMetadata() 

240 for key in self.allKeysToRemove: 

241 if exposureMetadata.exists(key): 

242 exposureMetadata.remove(key) 

243 outExposure.setMetadata(exposureMetadata) 

244 

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

246 # raw data. 

247 outExposure.info.id = inExposure.info.id 

248 outExposure.setFilterLabel(inExposure.getFilterLabel()) 

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

250 

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

252 if frame: 

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