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

60 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-01 02:30 -0800

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 

22__all__ = ["AssembleCcdTask"] 

23 

24import lsst.afw.cameraGeom as cameraGeom 

25import lsst.afw.cameraGeom.utils as cameraGeomUtils 

26import lsst.afw.display as afwDisplay 

27import lsst.afw.image as afwImage 

28import lsst.pex.config as pexConfig 

29import lsst.pipe.base as pipeBase 

30from lsstDebug import getDebugFrame 

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")