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

60 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-03 02:15 -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 

22__all__ = ["AssembleCcdTask", "AssembleCcdConfig"] 

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 

46class AssembleCcdTask(pipeBase.Task): 

47 """Assemble a set of amplifier images into a full detector size set of 

48 pixels. 

49 

50 The keys for removal specified in 

51 `lsst.ip.isr.AssembleCcdConfig.keysToRemove` are added to a default set: 

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

53 """ 

54 ConfigClass = AssembleCcdConfig 

55 _DefaultName = "assembleCcd" 

56 

57 def __init__(self, **kwargs): 

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

59 

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

61 

62 def assembleCcd(self, assembleInput): 

63 """Assemble a set of amps into a single CCD size image. 

64 

65 Parameters 

66 ---------- 

67 assembleInput : `dict` [`str`, `lsst.afw.image.Exposure`] or \ 

68 `lsst.afw.image.Exposure` 

69 Either a dictionary of amp exposures, or a single exposure 

70 containing all raw amps. If a dictionary of amp exposures, the key 

71 should be the amp name. 

72 

73 Returns 

74 ------- 

75 assembledCcd : `lsst.afw.image.Exposure` 

76 An exposure of the assembled amp sections. 

77 

78 Raises 

79 ------ 

80 TypeError 

81 Raised if the input exposures to be assembled do not adhere to the 

82 required format. 

83 RuntimeError 

84 Raised if the detector set on the input exposure is not set. 

85 """ 

86 ccd = None 

87 if isinstance(assembleInput, dict): 

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

89 

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

91 # an arbitrary amp. 

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

93 

94 def getNextExposure(amp): 

95 return assembleInput[amp.getName()] 

96 elif hasattr(assembleInput, "getMaskedImage"): 

97 # assembleInput is a single exposure 

98 ccd = assembleInput.getDetector() 

99 

100 def getNextExposure(amp): 

101 return assembleInput 

102 else: 

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

104 

105 if ccd is None: 

106 raise RuntimeError("No ccd detector found") 

107 

108 if not self.config.doTrim: 

109 outBox = cameraGeomUtils.calcRawCcdBBox(ccd) 

110 else: 

111 outBox = ccd.getBBox() 

112 outExposure = afwImage.ExposureF(outBox) 

113 outMI = outExposure.getMaskedImage() 

114 

115 if self.config.doTrim: 

116 assemble = cameraGeom.assembleAmplifierImage 

117 else: 

118 assemble = cameraGeom.assembleAmplifierRawImage 

119 

120 for amp in ccd: 

121 inMI = getNextExposure(amp).getMaskedImage() 

122 assemble(outMI, inMI, amp) 

123 # 

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

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

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

127 # place in a single Detector image 

128 # 

129 if not self.config.doTrim: 

130 ccd = cameraGeom.makeUpdatedDetector(ccd) 

131 

132 outExposure.setDetector(ccd) 

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

134 

135 return outExposure 

136 

137 def postprocessExposure(self, outExposure, inExposure): 

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

139 display exposure (if requested). 

140 

141 Call after assembling the pixels. 

142 

143 Parameters 

144 ---------- 

145 outExposure : `lsst.afw.image.Exposure` 

146 The exposure to modify by copying metadata (after removing unwanted 

147 keywords), wcs, filter, and detector from ``inExposure``. 

148 inExposure : `lsst.afw.image.Exposure` 

149 The input exposure providing metadata, wcs, filter, and detector. 

150 """ 

151 if inExposure.hasWcs(): 

152 outExposure.setWcs(inExposure.getWcs()) 

153 

154 exposureMetadata = inExposure.getMetadata() 

155 for key in self.allKeysToRemove: 

156 if exposureMetadata.exists(key): 

157 exposureMetadata.remove(key) 

158 outExposure.setMetadata(exposureMetadata) 

159 

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

161 # raw data. 

162 outExposure.info.id = inExposure.info.id 

163 outExposure.setFilter(inExposure.getFilter()) 

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

165 

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

167 if frame: 

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