LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
vignette.py
Go to the documentation of this file.
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/>.
21import logging
22import numpy as np
23
24import lsst.geom as geom
25import lsst.afw.cameraGeom as cameraGeom
26import lsst.afw.geom as afwGeom
27
28from lsst.pex.config import Config, Field
29from lsst.pipe.base import Task
30
31
32__all__ = ('VignetteConfig', 'VignetteTask', 'setValidPolygonCcdIntersect',
33 'maskVignettedRegion')
34
35
37 """Settings to define vignetting pattern.
38 """
39 xCenter = Field(
40 dtype=float,
41 doc="Center of vignetting pattern, in focal plane x coordinates.",
42 default=0.0,
43 )
44 yCenter = Field(
45 dtype=float,
46 doc="Center of vignetting pattern, in focal plane y coordinates.",
47 default=0.0,
48 )
49 radius = Field(
50 dtype=float,
51 doc="Radius of vignetting pattern, in focal plane coordinates.",
52 default=100.0,
53 check=lambda x: x >= 0
54 )
55 numPolygonPoints = Field(
56 dtype=int,
57 doc="Number of points used to define the vignette polygon.",
58 default=100,
59 )
60 doWriteVignettePolygon = Field(
61 dtype=bool,
62 doc="Persist polygon used to define vignetted region?",
63 default=False,
64 deprecated=("Vignetted polygon is added to the exposure by default."
65 " This option is no longer used, and will be removed after v24.")
66 )
67
68
69class VignetteTask(Task):
70 """Define a simple circular vignette pattern and optionally update mask
71 plane.
72 """
73 ConfigClass = VignetteConfig
74 _DefaultName = "isrVignette"
75
76 def run(self, exposure=None, doUpdateMask=True, maskPlane="NO_DATA", vignetteValue=None, log=None):
77 """Generate circular vignette pattern.
78
79 Parameters
80 ----------
81 exposure : `lsst.afw.image.Exposure`, optional
82 Exposure to construct, apply, and optionally mask vignette for.
83 doUpdateMask : `bool`, optional
84 If true, the mask will be updated to mask the vignetted region.
85 maskPlane : `str`, optional
86 Mask plane to assign vignetted pixels to.
87 vignetteValue : `float` or `None`, optional
88 Value to assign to the image array pixels within the ``polygon``
89 region. If `None`, image pixel values are not replaced.
90 log : `logging.Logger`, optional
91 Log object to write to.
92
93 Returns
94 -------
95 polygon : `lsst.afw.geom.Polygon`
96 Polygon defining the boundary of the vignetted region.
97 """
98 theta = np.linspace(0, 2*np.pi, num=self.config.numPolygonPoints, endpoint=False)
99 x = self.config.radius*np.cos(theta) + self.config.xCenter
100 y = self.config.radius*np.sin(theta) + self.config.yCenter
101 points = np.array([x, y]).transpose()
102 fpPolygon = afwGeom.Polygon([geom.Point2D(x1, y1) for x1, y1 in reversed(points)])
103 if exposure is None:
104 return fpPolygon
105
106 # Exposure was provided, so attach the validPolygon associated with the
107 # vignetted region.
108 setValidPolygonCcdIntersect(exposure, fpPolygon, log=log)
109
110 if doUpdateMask:
111 polygon = exposure.getInfo().getValidPolygon()
112 maskVignettedRegion(exposure, polygon, maskPlane="NO_DATA", vignetteValue=vignetteValue, log=log)
113 return fpPolygon
114
115
116def setValidPolygonCcdIntersect(ccdExposure, fpPolygon, log=None):
117 """Set valid polygon on ccdExposure associated with focal plane polygon.
118
119 The ccd exposure's valid polygon is the intersection of fpPolygon,
120 a valid polygon in focal plane coordinates, and the ccd corners,
121 in ccd pixel coordinates.
122
123 Parameters
124 ----------
125 ccdExposure : `lsst.afw.image.Exposure`
126 Exposure to process.
127 fpPolygon : `lsst.afw.geom.Polygon`
128 Polygon in focal plane coordinates.
129 log : `logging.Logger`, optional
130 Log object to write to.
131 """
132 # Get ccd corners in focal plane coordinates
133 ccd = ccdExposure.getDetector()
134 fpCorners = ccd.getCorners(cameraGeom.FOCAL_PLANE)
135 ccdPolygon = afwGeom.Polygon(fpCorners)
136 # Get intersection of ccd corners with fpPolygon
137 try:
138 intersect = ccdPolygon.intersectionSingle(fpPolygon)
140 intersect = None
141 if intersect is not None:
142 # Transform back to pixel positions and build new polygon
143 ccdPoints = ccd.transform(intersect, cameraGeom.FOCAL_PLANE, cameraGeom.PIXELS)
144 validPolygon = afwGeom.Polygon(ccdPoints)
145 ccdExposure.getInfo().setValidPolygon(validPolygon)
146 else:
147 if log is not None:
148 log.info("Ccd exposure does not overlap with focal plane polygon. Not setting validPolygon.")
149
150
151def maskVignettedRegion(exposure, polygon, maskPlane="NO_DATA", vignetteValue=None, log=None):
152 """Add mask bit to image pixels according to vignetted polygon region.
153
154 NOTE: this function could be used to mask and replace pixels associated
155 with any polygon in the exposure pixel coordinates.
156
157 Parameters
158 ----------
159 exposure : `lsst.afw.image.Exposure`
160 Image whose mask plane is to be updated.
161 polygon : `lsst.afw.geom.Polygon`
162 Polygon region defining the vignetted region in the pixel coordinates
163 of ``exposure``.
164 maskPlane : `str`, optional
165 Mask plane to assign vignetted pixels to.
166 vignetteValue : `float` or `None`, optional
167 Value to assign to the image array pixels within the ``polygon``
168 region. If `None`, image pixel values are not replaced.
169 log : `logging.Logger`, optional
170 Log object to write to.
171
172 Raises
173 ------
174 RuntimeError
175 Raised if no valid polygon exists.
176 """
177 log = log if log else logging.getLogger(__name__)
178 if not polygon:
179 log.info("No polygon provided. Masking nothing.")
180 return
181
182 fullyIlluminated = True
183 for corner in exposure.getBBox().getCorners():
184 if not polygon.contains(geom.Point2D(corner)):
185 fullyIlluminated = False
186 log.info("Exposure is fully illuminated? %s", fullyIlluminated)
187
188 if not fullyIlluminated:
189 # Scan pixels.
190 mask = exposure.getMask()
191 numPixels = mask.getBBox().getArea()
192 xx, yy = np.meshgrid(np.arange(0, mask.getWidth(), dtype=int),
193 np.arange(0, mask.getHeight(), dtype=int))
194
195 vignMask = np.array([not polygon.contains(geom.Point2D(x, y)) for x, y in
196 zip(xx.reshape(numPixels), yy.reshape(numPixels))])
197 vignMask = vignMask.reshape(mask.getHeight(), mask.getWidth())
198
199 bitMask = mask.getPlaneBitMask(maskPlane)
200 maskArray = mask.getArray()
201 maskArray[vignMask] |= bitMask
202 log.info("Exposure contains {} vignetted pixels which are now masked with mask plane {}.".
203 format(np.count_nonzero(vignMask), maskPlane))
204
205 if vignetteValue is not None:
206 imageArray = exposure.getImage().getArray()
207 imageArray[vignMask] = vignetteValue
208 log.info("Vignetted pixels in image array have been replaced with {}.".format(vignetteValue))
Cartesian polygons.
Definition: Polygon.h:59
An exception that indicates the single-polygon assumption has been violated.
Definition: Polygon.h:53
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition: Exposure.h:72
def run(self, exposure=None, doUpdateMask=True, maskPlane="NO_DATA", vignetteValue=None, log=None)
Definition: vignette.py:76
def maskVignettedRegion(exposure, polygon, maskPlane="NO_DATA", vignetteValue=None, log=None)
Definition: vignette.py:151
def setValidPolygonCcdIntersect(ccdExposure, fpPolygon, log=None)
Definition: vignette.py:116
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174