lsst.jointcal
g29ae962dfc+04632301ee
Toggle main menu visibility
Loading...
Searching...
No Matches
python
lsst
jointcal
cameraGeometry.py
Go to the documentation of this file.
1
# This file is part of jointcal.
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
"""Code to convert jointcal's output WCS models to distortion maps that can be
23
used by afw CameraGeom.
24
"""
25
__all__ = [
"CameraModel"
]
26
27
import
logging
28
import
numpy
as
np
29
30
from
lsst.afw
import
cameraGeom
31
import
lsst.afw.geom
32
import
astshim
as
ast
33
from
lsst.geom
import
SpherePoint, Point2D, radians
34
35
_LOG = logging.getLogger(__name__)
36
37
38
class
CameraModel
:
39
"""Convert a jointcal `~lsst.afw.geom.SkyWcs` into a distortion model and
40
detector positions (TODO) that can be used by `~lsst.afw.cameraGeom`.
41
42
Because this code only operates on the WCS, it is independent of the
43
format of the persisted output (e.g. gen2 separate files vs. gen3 bundled
44
visits).
45
46
Parameters
47
----------
48
wcsList : `list` [`lsst.afw.geom.SkyWcs`]
49
The WCS to use to compute the distortion model from, preferably from
50
multiple visits on the same tract.
51
detectors : `list` [`int`]
52
Detector ids that correspond one-to-one with ``wcsList``.
53
camera : `lsst.afw.cameraGeom.Camera`
54
The camera these WCS were fit for.
55
n : `int`
56
Number of points to compute the pixel scale at, along the +y axis.
57
"""
58
def
__init__
(self, wcsList, detectors, camera, n=100):
59
self.
wcsList
= wcsList
60
self.
camera
= camera
61
self.
detectors
= detectors
62
self.
maxFocalRadius
= self.
camera
.computeMaxFocalPlaneRadius()
63
self.
n
= n
64
# the computed radius and pixel scales
65
self.
fieldAngle
=
None
# degrees
66
self.
radialScale
=
None
# arcsec
67
self.
tangentialScale
=
None
# arcsec
68
# the computed values for every input wcs
69
self.
fieldAngles
=
None
70
self.
radialScales
=
None
71
self.
tangentialScales
=
None
72
self.
fieldAngleStd
=
None
73
self.
radialScaleStd
=
None
74
self.
tangentialScaleStd
=
None
75
76
self.
log
= _LOG.getChild(
"CameraModel"
)
77
78
def
computeDistortionModel
(self):
79
"""Calculate the afw cameraGeom distortion model to be included in an
80
on-disk camera model.
81
82
PLACEHOLDER: This may be as simple as running `computePixelScale` and
83
then doing a numpy polynomial fit to it for the cameraGeom input.
84
However, we need to check details of how that distortion model is
85
stored in a Camera. e.g.:
86
np.polyfit(self.fieldAngle, self.radialScale, poly_degree)
87
"""
88
raise
NotImplementedError(
"not yet!"
)
89
90
def
computePixelScale
(self):
91
"""Compute the radial and tangential pixel scale by averaging over
92
multiple jointcal WCS models.
93
94
Also computes the standard deviation and logs any WCS that are
95
significant outliers.
96
The calculations are stored in the ``fieldAngle[s]``,
97
``radialScale[s]``, and ``tangentialScale[s]`` member variables.
98
"""
99
self.
fieldAngles
= []
100
self.
radialScales
= []
101
self.
tangentialScales
= []
102
for
id, wcs
in
zip(self.
detectors
, self.
wcsList
):
103
fieldAngle, radial, tangential = self.
_computeDetectorPixelScale
(id, wcs)
104
self.
fieldAngles
.append(fieldAngle)
105
self.
radialScales
.append(radial)
106
self.
tangentialScales
.append(tangential)
107
# TODO: For now, don't worry about small differences in the computed
108
# field angles vs. their respective radial/tangential scales, just
109
# assume all fieldAngle positions are "close enough" and warn if not.
110
self.
fieldAngle
= np.mean(self.
fieldAngles
, axis=0)
111
self.
fieldAngleStd
= np.std(self.
fieldAngles
, axis=0)
112
if
self.
fieldAngleStd
.
max
() > 1e-4:
113
self.
log
.warning(
"Large stddev in computed field angles between visits (max: %s degree)."
,
114
self.
fieldAngleStd
.
max
())
115
# import os; print(os.getpid()); import ipdb; ipdb.set_trace();
116
self.
radialScale
= np.mean(self.
radialScales
, axis=0)
117
self.
radialScaleStd
= np.std(self.
radialScales
, axis=0)
118
if
self.
radialScaleStd
.
max
() > 1e-4:
119
self.
log
.warning(
"Large stddev in computed radial scales between visits (max: %s arcsec)."
,
120
self.
radialScaleStd
.
max
())
121
self.
tangentialScale
= np.mean(self.
tangentialScales
, axis=0)
122
self.
tangentialScaleStd
= np.std(self.
tangentialScales
, axis=0)
123
if
self.
tangentialScaleStd
.
max
() > 1e-4:
124
self.
log
.warning(
"Large stddev in computed tangential scales between visits (max: %s arcsec)."
,
125
self.
tangentialScaleStd
.
max
())
126
127
def
computeCameraPixelScale
(self, detector_id=30):
128
"""Compute the radial and tangential pixel scales using the distortion
129
model supplied with the camera.
130
131
This is designed to be directly comparable with the results of
132
`~CameraModel.computePixelScale`.
133
134
Parameters
135
----------
136
detector_id: `int`
137
Detector identifier for the detector_id to use for the calculation.
138
139
Returns
140
-------
141
fieldAngle : `numpy.ndarray`
142
Field angles in degrees.
143
radialScale : `numpy.ndarray`
144
Radial direction pixel scales in arcseconds/pixel.
145
tangentialScale : `numpy.ndarray`
146
Tangential direction pixel scales in arcseconds/pixel.
147
"""
148
# Make a trivial SkyWcs to get a field angle->sky transform from.
149
iwcToSkyWcs =
lsst.afw.geom.makeSkyWcs
(
Point2D
(0, 0),
SpherePoint
(0, 0, radians),
150
lsst.afw.geom.makeCdMatrix
(1.0 * radians, 0 * radians,
True
))
151
iwcToSkyMap = iwcToSkyWcs.getFrameDict().getMapping(
"PIXELS"
,
"SKY"
)
152
skyFrame = iwcToSkyWcs.getFrameDict().getFrame(
"SKY"
)
153
154
# Extract the transforms that are defined just on the camera.
155
pixSys = self.
camera
[detector_id].makeCameraSys(cameraGeom.PIXELS)
156
pixelsToFocal = self.
camera
.getTransform(pixSys, cameraGeom.FOCAL_PLANE)
157
focalToField = self.
camera
.getTransform(cameraGeom.FOCAL_PLANE, cameraGeom.FIELD_ANGLE)
158
159
# Build a SkyWcs that combines each of the above components.
160
pixelFrame =
ast.Frame
(2,
"Domain=PIXELS"
)
161
focalFrame =
ast.Frame
(2,
"Domain=FOCAL"
)
162
iwcFrame =
ast.Frame
(2,
"Domain=IWC"
)
163
frameDict =
ast.FrameDict
(pixelFrame)
164
frameDict.addFrame(
"PIXELS"
, pixelsToFocal.getMapping(), focalFrame)
165
frameDict.addFrame(
"FOCAL"
, focalToField.getMapping(), iwcFrame)
166
frameDict.addFrame(
"IWC"
, iwcToSkyMap, skyFrame)
167
wcs =
lsst.afw.geom.SkyWcs
(frameDict)
168
169
return
self.
_computeDetectorPixelScale
(detector_id, wcs)
170
171
def
_computeDetectorPixelScale
(self, detector_id, wcs):
172
"""Compute pixel scale in radial and tangential directions as a
173
function of field angle.
174
175
Parameters
176
----------
177
detector_id: `int`
178
Detector identifier for the detector of this wcs.
179
wcs : `lsst.afw.geom.SkyWcs`
180
Full focal-plane model to compute pixel scale on.
181
182
Returns
183
-------
184
fieldAngle : `numpy.ndarray`
185
Field angles in degrees.
186
radialScale : `numpy.ndarray`
187
Radial direction pixel scales in arcseconds/pixel.
188
tangentialScale : `numpy.ndarray`
189
Tangential direction pixel scales in arcseconds/pixel.
190
191
Notes
192
-----
193
Pixel scales are calculated from finite differences only along the +y
194
focal plane direction.
195
"""
196
focalToSky = wcs.getFrameDict().getMapping(
'FOCAL'
,
'SKY'
)
197
mmPerPixel = self.
camera
[detector_id].getPixelSize()
198
199
focalToPixels = wcs.getFrameDict().getMapping(
'FOCAL'
,
'PIXELS'
)
200
trans = wcs.getTransform()
# Pixels to Sky as Point2d -> SpherePoint
201
boresight = trans.applyForward(
Point2D
(focalToPixels.applyForward([0, 0])))
202
203
rs = np.linspace(0, self.
maxFocalRadius
, self.
n
)
# focal plane units
204
fieldAngle = np.zeros_like(rs)
205
radialScale = np.zeros_like(rs)
206
tangentialScale = np.zeros_like(rs)
207
for
i, r
in
enumerate(rs):
208
# point on the sky at position r along the focal plane +y axis
209
sp1 =
SpherePoint
(*focalToSky.applyForward(
Point2D
([0, r])), radians)
210
# point on the sky one pixel further along the focal plane +y axis
211
sp2 =
SpherePoint
(*focalToSky.applyForward(
Point2D
([0, r + mmPerPixel.getY()])), radians)
212
# point on the sky one pixel off of the focal plane +y axis at r
213
sp3 =
SpherePoint
(*focalToSky.applyForward(
Point2D
([mmPerPixel.getX(), r])), radians)
214
fieldAngle[i] = boresight.separation(sp1).asDegrees()
215
radialScale[i] = sp1.separation(sp2).asArcseconds()
216
tangentialScale[i] = sp1.separation(sp3).asArcseconds()
217
return
fieldAngle, radialScale, tangentialScale
ast::FrameDict
ast::Frame
lsst::afw::geom::SkyWcs
lsst::geom::Point< double, 2 >
lsst::geom::SpherePoint
lsst::jointcal.cameraGeometry.CameraModel
Definition
cameraGeometry.py:38
lsst::jointcal.cameraGeometry.CameraModel._computeDetectorPixelScale
_computeDetectorPixelScale(self, detector_id, wcs)
Definition
cameraGeometry.py:171
lsst::jointcal.cameraGeometry.CameraModel.camera
camera
Definition
cameraGeometry.py:60
lsst::jointcal.cameraGeometry.CameraModel.n
n
Definition
cameraGeometry.py:63
lsst::jointcal.cameraGeometry.CameraModel.computePixelScale
computePixelScale(self)
Definition
cameraGeometry.py:90
lsst::jointcal.cameraGeometry.CameraModel.tangentialScales
list tangentialScales
Definition
cameraGeometry.py:71
lsst::jointcal.cameraGeometry.CameraModel.log
log
Definition
cameraGeometry.py:76
lsst::jointcal.cameraGeometry.CameraModel.__init__
__init__(self, wcsList, detectors, camera, n=100)
Definition
cameraGeometry.py:58
lsst::jointcal.cameraGeometry.CameraModel.wcsList
wcsList
Definition
cameraGeometry.py:59
lsst::jointcal.cameraGeometry.CameraModel.detectors
detectors
Definition
cameraGeometry.py:61
lsst::jointcal.cameraGeometry.CameraModel.maxFocalRadius
maxFocalRadius
Definition
cameraGeometry.py:62
lsst::jointcal.cameraGeometry.CameraModel.radialScale
radialScale
Definition
cameraGeometry.py:66
lsst::jointcal.cameraGeometry.CameraModel.radialScales
list radialScales
Definition
cameraGeometry.py:70
lsst::jointcal.cameraGeometry.CameraModel.tangentialScale
tangentialScale
Definition
cameraGeometry.py:67
lsst::jointcal.cameraGeometry.CameraModel.fieldAngleStd
fieldAngleStd
Definition
cameraGeometry.py:72
lsst::jointcal.cameraGeometry.CameraModel.computeDistortionModel
computeDistortionModel(self)
Definition
cameraGeometry.py:78
lsst::jointcal.cameraGeometry.CameraModel.fieldAngles
list fieldAngles
Definition
cameraGeometry.py:69
lsst::jointcal.cameraGeometry.CameraModel.tangentialScaleStd
tangentialScaleStd
Definition
cameraGeometry.py:74
lsst::jointcal.cameraGeometry.CameraModel.computeCameraPixelScale
computeCameraPixelScale(self, detector_id=30)
Definition
cameraGeometry.py:127
lsst::jointcal.cameraGeometry.CameraModel.radialScaleStd
radialScaleStd
Definition
cameraGeometry.py:73
lsst::jointcal.cameraGeometry.CameraModel.fieldAngle
fieldAngle
Definition
cameraGeometry.py:65
std::max
T max(T... args)
lsst::afw::geom
lsst::afw::geom::makeSkyWcs
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
lsst::afw::geom::makeCdMatrix
Eigen::Matrix2d makeCdMatrix(lsst::geom::Angle const &scale, lsst::geom::Angle const &orientation=0 *lsst::geom::degrees, bool flipX=false)
lsst::afw
lsst::geom
Generated on
for lsst.jointcal by
1.17.0