Coverage for python/lsst/pipe/tasks/visualizeVisit.py: 48%

67 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-09-15 03:34 -0700

1# This file is part of pipe_tasks. 

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 <http://www.gnu.org/licenses/>. 

21import numpy as np 

22 

23import lsst.pex.config as pexConfig 

24import lsst.pipe.base as pipeBase 

25import lsst.pipe.base.connectionTypes as cT 

26import lsst.afw.math as afwMath 

27import lsst.afw.image as afwImage 

28import lsst.afw.cameraGeom.utils as afwUtils 

29 

30 

31__all__ = ['VisualizeBinExpConfig', 'VisualizeBinExpTask', 

32 'VisualizeMosaicExpConfig', 'VisualizeMosaicExpTask'] 

33 

34 

35class VisualizeBinExpConnections(pipeBase.PipelineTaskConnections, 

36 dimensions=("instrument", "detector")): 

37 inputExp = cT.Input( 

38 name="calexp", 

39 doc="Input exposure data to mosaic.", 

40 storageClass="ExposureF", 

41 dimensions=("instrument", "detector"), 

42 ) 

43 camera = cT.PrerequisiteInput( 

44 name="camera", 

45 doc="Input camera to use for mosaic geometry.", 

46 storageClass="Camera", 

47 dimensions=("instrument",), 

48 isCalibration=True, 

49 ) 

50 

51 outputExp = cT.Output( 

52 name="calexpBin", 

53 doc="Output binned image.", 

54 storageClass="ExposureF", 

55 dimensions=("instrument", "detector"), 

56 ) 

57 

58 

59class VisualizeBinExpConfig(pipeBase.PipelineTaskConfig, 

60 pipelineConnections=VisualizeBinExpConnections): 

61 """Configuration for focal plane visualization. 

62 """ 

63 binning = pexConfig.Field( 

64 dtype=int, 

65 default=8, 

66 doc="Binning factor to apply to each input exposure's image data.", 

67 ) 

68 detectorKeyword = pexConfig.Field( 

69 dtype=str, 

70 default='DET-ID', 

71 doc="Metadata keyword to use to find detector if not available from input.", 

72 ) 

73 

74 

75class VisualizeBinExpTask(pipeBase.PipelineTask): 

76 """Bin the detectors of an exposure. 

77 

78 The outputs of this task should be passed to 

79 VisualizeMosaicExpTask to be mosaicked into a full focal plane 

80 visualization image. 

81 """ 

82 ConfigClass = VisualizeBinExpConfig 

83 _DefaultName = 'VisualizeBinExp' 

84 

85 def run(self, inputExp, camera): 

86 """Bin input image, attach associated detector. 

87 

88 Parameters 

89 ---------- 

90 inputExp : `lsst.afw.image.Exposure` 

91 Input exposure data to bin. 

92 camera : `lsst.afw.cameraGeom.Camera` 

93 Input camera to use for mosaic geometry. 

94 

95 Returns 

96 ------- 

97 output : `lsst.pipe.base.Struct` 

98 Results struct with attribute: 

99 

100 ``outputExp`` 

101 Binned version of input image (`lsst.afw.image.Exposure`). 

102 """ 

103 if inputExp.getDetector() is None: 

104 detectorId = inputExp.getMetadata().get(self.config.detectorKeyword) 

105 if detectorId is not None: 

106 inputExp.setDetector(camera[detectorId]) 

107 

108 binned = inputExp.getMaskedImage() 

109 binned = afwMath.binImage(binned, self.config.binning) 

110 outputExp = afwImage.makeExposure(binned) 

111 

112 outputExp.setInfo(inputExp.getInfo()) 

113 

114 return pipeBase.Struct( 

115 outputExp=outputExp, 

116 ) 

117 

118 

119class VisualizeMosaicExpConnections(pipeBase.PipelineTaskConnections, 

120 dimensions=("instrument", )): 

121 inputExps = cT.Input( 

122 name="calexpBin", 

123 doc="Input binned images mosaic.", 

124 storageClass="ExposureF", 

125 dimensions=("instrument", "detector"), 

126 multiple=True, 

127 ) 

128 camera = cT.PrerequisiteInput( 

129 name="camera", 

130 doc="Input camera to use for mosaic geometry.", 

131 storageClass="Camera", 

132 dimensions=("instrument",), 

133 isCalibration=True, 

134 ) 

135 

136 outputData = cT.Output( 

137 name="calexpFocalPlane", 

138 doc="Output binned mosaicked frame.", 

139 storageClass="ImageF", 

140 dimensions=("instrument", ), 

141 ) 

142 

143 

144class VisualizeMosaicExpConfig(pipeBase.PipelineTaskConfig, 

145 pipelineConnections=VisualizeMosaicExpConnections): 

146 """Configuration for focal plane visualization. 

147 """ 

148 binning = pexConfig.Field( 

149 dtype=int, 

150 default=8, 

151 doc="Binning factor previously applied to input exposures.", 

152 ) 

153 

154 

155class VisualizeMosaicExpTask(pipeBase.PipelineTask): 

156 """Task to mosaic binned products. 

157 

158 The config.binning parameter must match that used in the 

159 VisualizeBinExpTask. Otherwise there will be a mismatch between 

160 the input image size and the expected size of that image in the 

161 full focal plane frame. 

162 """ 

163 ConfigClass = VisualizeMosaicExpConfig 

164 _DefaultName = 'VisualizeMosaicExp' 

165 

166 def makeCameraImage(self, inputExps, camera, binning): 

167 """Make an image of an entire focal plane. 

168 

169 Parameters 

170 ---------- 

171 exposures: `dict` [`int`, `lsst.afw.image.Exposure`] 

172 CCD exposures, binned by `binning`. The keys are the 

173 detectorIDs, with the values the binned image exposure. 

174 

175 Returns 

176 ------- 

177 image : `lsst.afw.image.Image` 

178 Image mosaicked from the individual binned images for each 

179 detector. 

180 """ 

181 image = afwUtils.makeImageFromCamera( 

182 camera, 

183 imageSource=ImageSource(inputExps), 

184 imageFactory=afwImage.ImageF, 

185 binSize=binning 

186 ) 

187 return image 

188 

189 def run(self, inputExps, camera): 

190 """Mosaic inputs together to create focal plane image. 

191 

192 Parameters 

193 ---------- 

194 inputExp : `list` [`lsst.afw.image.Exposure`] 

195 Input exposure data to bin. 

196 camera : `lsst.afw.cameraGeom.Camera` 

197 Input camera to use for mosaic geometry. 

198 

199 Returns 

200 ------- 

201 output : `lsst.pipe.base.Struct` 

202 Results struct with attribute: 

203 

204 ``outputExp`` 

205 Binned version of input image (`lsst.afw.image.Exposure`). 

206 """ 

207 expDict = {exp.getDetector().getId(): exp for exp in inputExps} 

208 image = self.makeCameraImage(expDict, camera, self.config.binning) 

209 

210 return pipeBase.Struct( 

211 outputData=image, 

212 ) 

213 

214 

215class ImageSource: 

216 """Source of images for makeImageFromCamera""" 

217 def __init__(self, exposures): 

218 self.exposures = exposures 

219 self.isTrimmed = True 

220 self.background = np.nan 

221 

222 def getCcdImage(self, detector, imageFactory, binSize): 

223 """Provide image of CCD to makeImageFromCamera 

224 

225 Parameters 

226 ---------- 

227 detector : `int` 

228 Detector ID to get image data for. 

229 imageFactory : `lsst.afw.image.Image` 

230 Type of image to construct. 

231 binSize : `int` 

232 Binsize to use to recompute dimensions. 

233 

234 Returns 

235 ------- 

236 image : `lsst.afw.image.Image` 

237 Appropriately rotated, binned, and transformed 

238 image to be mosaicked. 

239 detector : `lsst.afw.cameraGeom.Detector` 

240 Camera detector that the returned image data 

241 belongs to. 

242 """ 

243 detId = detector.getId() 

244 

245 if detId not in self.exposures: 

246 dims = detector.getBBox().getDimensions()/binSize 

247 image = imageFactory(*[int(xx) for xx in dims]) 

248 image.set(self.background) 

249 else: 

250 image = self.exposures[detector.getId()] 

251 if hasattr(image, "getMaskedImage"): 

252 image = image.getMaskedImage() 

253 if hasattr(image, "getMask"): 

254 mask = image.getMask() 

255 isBad = mask.getArray() & mask.getPlaneBitMask("NO_DATA") > 0 

256 image = image.clone() 

257 image.getImage().getArray()[isBad] = self.background 

258 if hasattr(image, "getImage"): 

259 image = image.getImage() 

260 

261 # afwMath.rotateImageBy90 checks NQuarter values, 

262 # so we don't need to here. 

263 image = afwMath.rotateImageBy90(image, detector.getOrientation().getNQuarter()) 

264 return image, detector