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

60 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-08-18 12:16 -0700

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 page_AssembleCcdTask 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 command line task interface supports a flag @c -d to import @b debug.py from your 

95 @c PYTHONPATH; see <a 

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

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

98 

99 The available variables in AssembleCcdTask are: 

100 <DL> 

101 <DT> @c display 

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

103 as value. Valid keys are: 

104 <DL> 

105 <DT> assembledExposure 

106 <DD> display assembled exposure 

107 </DL> 

108 </DL> 

109 

110 @section ip_isr_assemble_Example A complete example of using 

111 AssembleCcdTask 

112 

113 <HR> 

114 To investigate the @ref ip_isr_assemble_Debug, put something like 

115 @code{.py} 

116 import lsstDebug 

117 def DebugInfo(name): 

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

119 # us recursively 

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

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

122 return di 

123 

124 lsstDebug.Info = DebugInfo 

125 @endcode 

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

127 flag. 

128 

129 

130 Conversion notes: 

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

132 controlling what is displayed. 

133 """ 

134 ConfigClass = AssembleCcdConfig 

135 _DefaultName = "assembleCcd" 

136 

137 def __init__(self, **kwargs): 

138 """!Initialize the AssembleCcdTask 

139 

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

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

142 """ 

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

144 

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

146 

147 def assembleCcd(self, assembleInput): 

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

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

150 lsst.afw.image.Exposures or a single 

151 lsst.afw.image.Exposure containing all raw 

152 amps. If a dictionary of amp exposures, 

153 the key should be the amp name. 

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

155 amp sections. 

156 

157 @throws TypeError with the following string: 

158 

159 <DL> 

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

161 exposure. 

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

163 required format. 

164 </DL> 

165 

166 @throws RuntimeError with the following string: 

167 

168 <DL> 

169 <DT> No ccd detector found 

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

171 </DL> 

172 """ 

173 ccd = None 

174 if isinstance(assembleInput, dict): 

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

176 

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

178 # an arbitrary amp. 

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

180 

181 def getNextExposure(amp): 

182 return assembleInput[amp.getName()] 

183 elif hasattr(assembleInput, "getMaskedImage"): 

184 # assembleInput is a single exposure 

185 ccd = assembleInput.getDetector() 

186 

187 def getNextExposure(amp): 

188 return assembleInput 

189 else: 

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

191 

192 if ccd is None: 

193 raise RuntimeError("No ccd detector found") 

194 

195 if not self.config.doTrim: 

196 outBox = cameraGeomUtils.calcRawCcdBBox(ccd) 

197 else: 

198 outBox = ccd.getBBox() 

199 outExposure = afwImage.ExposureF(outBox) 

200 outMI = outExposure.getMaskedImage() 

201 

202 if self.config.doTrim: 

203 assemble = cameraGeom.assembleAmplifierImage 

204 else: 

205 assemble = cameraGeom.assembleAmplifierRawImage 

206 

207 for amp in ccd: 

208 inMI = getNextExposure(amp).getMaskedImage() 

209 assemble(outMI, inMI, amp) 

210 # 

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

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

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

214 # place in a single Detector image 

215 # 

216 if not self.config.doTrim: 

217 ccd = cameraGeom.makeUpdatedDetector(ccd) 

218 

219 outExposure.setDetector(ccd) 

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

221 

222 return outExposure 

223 

224 def postprocessExposure(self, outExposure, inExposure): 

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

226 display exposure (if requested) 

227 

228 Call after assembling the pixels 

229 

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

231 - removes unwanted keywords 

232 - sets wcs, filter, and detector 

233 @param[in] inExposure input exposure 

234 """ 

235 if inExposure.hasWcs(): 

236 outExposure.setWcs(inExposure.getWcs()) 

237 

238 exposureMetadata = inExposure.getMetadata() 

239 for key in self.allKeysToRemove: 

240 if exposureMetadata.exists(key): 

241 exposureMetadata.remove(key) 

242 outExposure.setMetadata(exposureMetadata) 

243 

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

245 # raw data. 

246 outExposure.info.id = inExposure.info.id 

247 outExposure.setFilter(inExposure.getFilter()) 

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

249 

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

251 if frame: 

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