Coverage for python/lsst/pipe/tasks/visualizeVisit.py: 40%
67 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-09 02:42 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-09 02:42 -0800
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 <https://www.gnu.org/licenses/>.
22__all__ = ['VisualizeBinExpConfig', 'VisualizeBinExpTask',
23 'VisualizeMosaicExpConfig', 'VisualizeMosaicExpTask']
25import numpy as np
27import lsst.pex.config as pexConfig
28import lsst.pipe.base as pipeBase
29import lsst.pipe.base.connectionTypes as cT
30import lsst.afw.math as afwMath
31import lsst.afw.image as afwImage
32import lsst.afw.cameraGeom.utils as afwUtils
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 )
51 outputExp = cT.Output(
52 name="calexpBin",
53 doc="Output binned image.",
54 storageClass="ExposureF",
55 dimensions=("instrument", "detector"),
56 )
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 )
75class VisualizeBinExpTask(pipeBase.PipelineTask):
76 """Bin the detectors of an exposure.
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'
85 def run(self, inputExp, camera):
86 """Bin input image, attach associated detector.
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.
95 Returns
96 -------
97 output : `lsst.pipe.base.Struct`
98 Results struct with attribute:
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])
108 binned = inputExp.getMaskedImage()
109 binned = afwMath.binImage(binned, self.config.binning)
110 outputExp = afwImage.makeExposure(binned)
112 outputExp.setInfo(inputExp.getInfo())
114 return pipeBase.Struct(
115 outputExp=outputExp,
116 )
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 )
136 outputData = cT.Output(
137 name="calexpFocalPlane",
138 doc="Output binned mosaicked frame.",
139 storageClass="ImageF",
140 dimensions=("instrument", ),
141 )
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 )
155class VisualizeMosaicExpTask(pipeBase.PipelineTask):
156 """Task to mosaic binned products.
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'
166 def makeCameraImage(self, inputExps, camera, binning):
167 """Make an image of an entire focal plane.
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.
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
189 def run(self, inputExps, camera):
190 """Mosaic inputs together to create focal plane image.
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.
199 Returns
200 -------
201 output : `lsst.pipe.base.Struct`
202 Results struct with attribute:
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)
210 return pipeBase.Struct(
211 outputData=image,
212 )
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
222 def getCcdImage(self, detector, imageFactory, binSize):
223 """Provide image of CCD to makeImageFromCamera
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.
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()
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()
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