Coverage for python/lsst/pipe/tasks/visualizeVisit.py: 37%
69 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-21 02:14 -0700
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-21 02:14 -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 <https://www.gnu.org/licenses/>.
22__all__ = [
23 "VisualizeBinExpConfig",
24 "VisualizeBinExpTask",
25 "VisualizeMosaicExpConfig",
26 "VisualizeMosaicExpTask",
27]
29import lsst.afw.cameraGeom.utils as afwUtils
30import lsst.afw.image as afwImage
31import lsst.afw.math as afwMath
32import lsst.pex.config as pexConfig
33import lsst.pipe.base as pipeBase
34import lsst.pipe.base.connectionTypes as cT
35import numpy as np
38class VisualizeBinExpConnections(pipeBase.PipelineTaskConnections, dimensions=("instrument", "detector")):
39 inputExp = cT.Input(
40 name="calexp",
41 doc="Input exposure data to mosaic.",
42 storageClass="ExposureF",
43 dimensions=("instrument", "detector"),
44 )
45 camera = cT.PrerequisiteInput(
46 name="camera",
47 doc="Input camera to use for mosaic geometry.",
48 storageClass="Camera",
49 dimensions=("instrument",),
50 isCalibration=True,
51 )
53 outputExp = cT.Output(
54 name="calexpBin",
55 doc="Output binned image.",
56 storageClass="ExposureF",
57 dimensions=("instrument", "detector"),
58 )
61class VisualizeBinExpConfig(pipeBase.PipelineTaskConfig, pipelineConnections=VisualizeBinExpConnections):
62 """Configuration for focal plane visualization."""
64 binning = pexConfig.Field(
65 dtype=int,
66 default=8,
67 doc="Binning factor to apply to each input exposure's image data.",
68 )
69 detectorKeyword = pexConfig.Field(
70 dtype=str,
71 default="DET-ID",
72 doc="Metadata keyword to use to find detector if not available from input.",
73 )
76class VisualizeBinExpTask(pipeBase.PipelineTask):
77 """Bin the detectors of an exposure.
79 The outputs of this task should be passed to
80 VisualizeMosaicExpTask to be mosaicked into a full focal plane
81 visualization image.
82 """
84 ConfigClass = VisualizeBinExpConfig
85 _DefaultName = "VisualizeBinExp"
87 def run(self, inputExp, camera):
88 """Bin input image, attach associated detector.
90 Parameters
91 ----------
92 inputExp : `lsst.afw.image.Exposure`
93 Input exposure data to bin.
94 camera : `lsst.afw.cameraGeom.Camera`
95 Input camera to use for mosaic geometry.
97 Returns
98 -------
99 output : `lsst.pipe.base.Struct`
100 Results struct with attribute:
102 ``outputExp``
103 Binned version of input image (`lsst.afw.image.Exposure`).
104 """
105 if inputExp.getDetector() is None:
106 detectorId = inputExp.getMetadata().get(self.config.detectorKeyword)
107 if detectorId is not None:
108 inputExp.setDetector(camera[detectorId])
110 binned = inputExp.getMaskedImage()
111 binned = afwMath.binImage(binned, self.config.binning)
112 outputExp = afwImage.makeExposure(binned)
114 outputExp.setInfo(inputExp.getInfo())
116 return pipeBase.Struct(outputExp=outputExp)
119class VisualizeMosaicExpConnections(pipeBase.PipelineTaskConnections, dimensions=("instrument",)):
120 inputExps = cT.Input(
121 name="calexpBin",
122 doc="Input binned images mosaic.",
123 storageClass="ExposureF",
124 dimensions=("instrument", "detector"),
125 multiple=True,
126 )
127 camera = cT.PrerequisiteInput(
128 name="camera",
129 doc="Input camera to use for mosaic geometry.",
130 storageClass="Camera",
131 dimensions=("instrument",),
132 isCalibration=True,
133 )
135 outputData = cT.Output(
136 name="calexpFocalPlane",
137 doc="Output binned mosaicked frame.",
138 storageClass="ImageF",
139 dimensions=("instrument",),
140 )
143class VisualizeMosaicExpConfig(
144 pipeBase.PipelineTaskConfig, pipelineConnections=VisualizeMosaicExpConnections
145):
146 """Configuration for focal plane visualization."""
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 """
164 ConfigClass = VisualizeMosaicExpConfig
165 _DefaultName = "VisualizeMosaicExp"
167 def makeCameraImage(self, inputExps, camera, binning):
168 """Make an image of an entire focal plane.
170 Parameters
171 ----------
172 exposures: `dict` [`int`, `lsst.afw.image.Exposure`]
173 CCD exposures, binned by `binning`. The keys are the
174 detectorIDs, with the values the binned image exposure.
176 Returns
177 -------
178 image : `lsst.afw.image.Image`
179 Image mosaicked from the individual binned images for each
180 detector.
181 """
182 image = afwUtils.makeImageFromCamera(
183 camera, imageSource=ImageSource(inputExps), imageFactory=afwImage.ImageF, binSize=binning
184 )
185 return image
187 def run(self, inputExps, camera, inputIds=None):
188 """Mosaic inputs together to create focal plane image.
190 Parameters
191 ----------
192 inputExps : `list` [`lsst.afw.image.Exposure`]
193 Input exposure data to bin.
194 camera : `lsst.afw.cameraGeom.Camera`
195 Input camera to use for mosaic geometry.
196 inputIds : `list` [`int`], optional
197 Optional list providing exposure IDs corresponding to input
198 exposures. Will be generated via the exposure data `getDetector`
199 method if not provided.
201 Returns
202 -------
203 output : `lsst.pipe.base.Struct`
204 Results struct with attribute:
206 ``outputExp``
207 Binned version of input image (`lsst.afw.image.Exposure`).
208 """
209 if not inputIds:
210 inputIds = [exp.getDetector().getId() for exp in inputExps]
211 expDict = {id: exp for id, exp in zip(inputIds, inputExps)}
212 image = self.makeCameraImage(expDict, camera, self.config.binning)
214 return pipeBase.Struct(outputData=image)
217class ImageSource:
218 """Source of images for makeImageFromCamera"""
220 def __init__(self, exposures):
221 self.exposures = exposures
222 self.isTrimmed = True
223 self.background = np.nan
225 def getCcdImage(self, detector, imageFactory, binSize):
226 """Provide image of CCD to makeImageFromCamera
228 Parameters
229 ----------
230 detector : `int`
231 Detector ID to get image data for.
232 imageFactory : `lsst.afw.image.Image`
233 Type of image to construct.
234 binSize : `int`
235 Binsize to use to recompute dimensions.
237 Returns
238 -------
239 image : `lsst.afw.image.Image`
240 Appropriately rotated, binned, and transformed
241 image to be mosaicked.
242 detector : `lsst.afw.cameraGeom.Detector`
243 Camera detector that the returned image data
244 belongs to.
245 """
246 detId = detector.getId()
248 if detId not in self.exposures:
249 dims = detector.getBBox().getDimensions() / binSize
250 image = imageFactory(*[int(xx) for xx in dims])
251 image.set(self.background)
252 else:
253 image = self.exposures[detector.getId()]
254 if hasattr(image, "getMaskedImage"):
255 image = image.getMaskedImage()
256 if hasattr(image, "getMask"):
257 mask = image.getMask()
258 isBad = mask.getArray() & mask.getPlaneBitMask("NO_DATA") > 0
259 image = image.clone()
260 image.getImage().getArray()[isBad] = self.background
261 if hasattr(image, "getImage"):
262 image = image.getImage()
264 # afwMath.rotateImageBy90 checks NQuarter values,
265 # so we don't need to here.
266 image = afwMath.rotateImageBy90(image, detector.getOrientation().getNQuarter())
267 return image, detector