LSST Applications g04e9c324dd+8c5ae1fdc5,g134cb467dc+b203dec576,g18429d2f64+358861cd2c,g199a45376c+0ba108daf9,g1fd858c14a+dd066899e3,g262e1987ae+ebfced1d55,g29ae962dfc+72fd90588e,g2cef7863aa+aef1011c0b,g35bb328faa+8c5ae1fdc5,g3fd5ace14f+b668f15bc5,g4595892280+3897dae354,g47891489e3+abcf9c3559,g4d44eb3520+fb4ddce128,g53246c7159+8c5ae1fdc5,g67b6fd64d1+abcf9c3559,g67fd3c3899+1f72b5a9f7,g74acd417e5+cb6b47f07b,g786e29fd12+668abc6043,g87389fa792+8856018cbb,g89139ef638+abcf9c3559,g8d7436a09f+bcf525d20c,g8ea07a8fe4+9f5ccc88ac,g90f42f885a+6054cc57f1,g97be763408+06f794da49,g9dd6db0277+1f72b5a9f7,ga681d05dcb+7e36ad54cd,gabf8522325+735880ea63,gac2eed3f23+abcf9c3559,gb89ab40317+abcf9c3559,gbf99507273+8c5ae1fdc5,gd8ff7fe66e+1f72b5a9f7,gdab6d2f7ff+cb6b47f07b,gdc713202bf+1f72b5a9f7,gdfd2d52018+8225f2b331,ge365c994fd+375fc21c71,ge410e46f29+abcf9c3559,geaed405ab2+562b3308c0,gf9a733ac38+8c5ae1fdc5,w.2025.35
LSST Data Management Base Package
Loading...
Searching...
No Matches
flatGradient.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/>.
21"""
22Flat gradient fit storage class.
23"""
24
25__all__ = ["FlatGradient"]
26
27from astropy.table import Table
28import numpy as np
29from scipy.interpolate import Akima1DInterpolator
30
31from lsst.ip.isr import IsrCalib
32
33
35 """Flat gradient measurements.
36
37 Parameters
38 ----------
39 log : `logging.Logger`, optional
40 Log to write messages to. If `None` a default logger will be used.
41 **kwargs :
42 Additional parameters.
43 """
44
45 _OBSTYPE = "flatGradient"
46 _SCHEMA = "FlatGradient"
47 _VERSION = 1.0
48
49 def __init__(self, **kwargs):
50
51 self.radialSplineNodes = np.zeros(1)
52 self.radialSplineValues = np.zeros(1)
53 self.itlRatio = 1.0
54 self.centroidX = 0.0
55 self.centroidY = 0.0
56 self.centroidDeltaX = 0.0
57 self.centroidDeltaY = 0.0
58 self.gradientX = 0.0
59 self.gradientY = 0.0
60 self.outerGradientX = 0.0
61 self.outerGradientY = 0.0
62 self.outerGradientRadius = np.inf
64
65 super().__init__(**kwargs)
66
67 self.requiredAttributes.update(
68 [
69 "radialSplineNodes",
70 "radialSplineValues",
71 "itlRatio",
72 "centroidX",
73 "centroidY",
74 "centroidDeltaX",
75 "centroidDeltaY",
76 "gradientX",
77 "gradientY",
78 "outerGradientX",
79 "outerGradientY",
80 "outerGradientRadius",
81 "normalizationFactor",
82 ],
83 )
84
85 self.updateMetadata(setCalibInfo=True, setCalibId=True, **kwargs)
86
88 self,
89 *,
90 radialSplineNodes,
91 radialSplineValues,
92 itlRatio=1.0,
93 centroidX=0.0,
94 centroidY=0.0,
95 centroidDeltaX=0.0,
96 centroidDeltaY=0.0,
97 gradientX=0.0,
98 gradientY=0.0,
99 outerGradientX=0.0,
100 outerGradientY=0.0,
101 outerGradientRadius=np.inf,
102 normalizationFactor=1.0,
103 ):
104 """Set the parameters for the gradient model.
105
106 Parameters
107 ----------
108 radialSplineNodes : `np.ndarray`
109 Array of spline nodes.
110 radialSplineValues : `np.ndarray`
111 Array of spline values (same length as ``radialSplineNodes``).
112 itlRatio : `float`, optional
113 Ratio of flat for ITL detectors to E2V detectors.
114 centroidX : `float`, optional
115 X centroid of the focal plane (mm). This will be used as the
116 pivot for the gradient plane.
117 centroidY : `float`, optional
118 Y centroid of the focal plane (mm). This will be used as the
119 pivot for the gradient plane.
120 centroidDeltaX : `float`, optional
121 Centroid offset (mm). This is used in the radial function to
122 allow for mis-centering in the illumination gradient.
123 centroidDeltaY : `float`, optional
124 Centroid offset (mm). This is used in the radial function to
125 allow for mis-centering in the illumination gradient.
126 gradientX : `float`, optional
127 Slope of gradient in x direction (throughput/mm).
128 gradientY : `float`, optional
129 Slope of gradient in y direction (throughput/mm).
130 outerGradientX : `float`, optional
131 Slope of additional gradient in x direction (throughput/mm)
132 that is only applied at focal plane radius greater than
133 ``outerGradientRadius``.
134 outerGradientY : `float`, optional
135 Slope of additional gradient in y direction (throughput/mm)
136 that is only applied at focal plane radius greater than
137 ``outerGradientRadius``.
138 outerGradientRadius : `float`, optional
139 Minimum radius (mm) where the outer gradient is used.
140 normalizationFactor : `float`, optional
141 Overall normalization factor (used to, e.g. make the
142 center of the focal plane equal to 1.0 vs. a focal-plane
143 average.
144 """
145 if len(radialSplineNodes) != len(radialSplineValues):
146 raise ValueError("The number of spline nodes and values must be equal.")
147
148 self.radialSplineNodes = radialSplineNodes
149 self.radialSplineValues = radialSplineValues
150 self.itlRatio = itlRatio
151 self.centroidX = centroidX
152 self.centroidY = centroidY
153 self.centroidDeltaX = centroidDeltaX
154 self.centroidDeltaY = centroidDeltaY
155 self.gradientX = gradientX
156 self.gradientY = gradientY
157 self.outerGradientX = outerGradientX
158 self.outerGradientY = outerGradientY
159 self.outerGradientRadius = outerGradientRadius
160 self.normalizationFactor = normalizationFactor
161
163 """Compute the radial spline model values from x/y.
164
165 The spline model is a 1D Akima spline. When computed, the values
166 from the model describe the radial function of the full focal
167 plane flat-field. Dividing by this model will yield a radially
168 flattened flat-field.
169
170 Parameters
171 ----------
172 x : `np.ndarray`
173 Array of focal plane x values (mm).
174 y : `np.ndarray`
175 Array of focal plane y values (mm).
176
177 Returns
178 -------
179 splineModel : `np.ndarray`
180 Spline model values at the x/y positions.
181 """
182 centroidX = self.centroidX + self.centroidDeltaX
183 centroidY = self.centroidY + self.centroidDeltaY
184
185 radius = np.sqrt((x - centroidX)**2. + (y - centroidY)**2.)
186
187 return self.computeRadialSplineModel(radius)
188
189 def computeRadialSplineModel(self, radius):
190 """Compute the radial spline model values from radii.
191
192 The spline model is a 1D Akima spline. When computed, the values
193 from the model describe the radial function of the full focal
194 plane flat-field. Dividing by this model will yield a radially
195 flattened flat-field.
196
197 Parameters
198 ----------
199 radius : `np.ndarray`
200 Array of focal plane radii (mm).
201
202 Returns
203 -------
204 splineModel : `np.ndarray`
205 Spline model values at the radius positions.
206 """
207 spl = Akima1DInterpolator(self.radialSplineNodes, self.radialSplineValues)
208
209 return spl(np.clip(radius, self.radialSplineNodes[0], self.radialSplineNodes[-1]))
210
211 def computeGradientModel(self, x, y):
212 """Compute the gradient model values.
213
214 The gradient model is a plane constrained to be 1.0 at the
215 ``centroidX``, ``centroidY`` values. The outer gradient model is
216 the same, except only applies at large radius (above the
217 ``outerGradientRadius``). Dividing by this model will remove
218 the planar gradient in a flat field. Note that the planar
219 gradient pivot is always at the same position, and does not
220 move with the radial gradient centroid so as to keep the
221 model fit more stable.
222
223 Parameters
224 ----------
225 x : `np.ndarray`
226 Array of focal plane x values (mm).
227 y : `np.ndarray`
228 Array of focal plane y values (mm).
229
230 Returns
231 -------
232 gradientModel : `np.ndarray`
233 Gradient model values at the x/y positions.
234 """
235 gradient = 1 + self.gradientX*(x - self.centroidX) + self.gradientY*(y - self.centroidY)
236
237 if np.isfinite(self.outerGradientRadius):
238 # This includes the centroid, but not the delta.
239 fpRadius = np.sqrt((x - self.centroidX)**2. + (y - self.centroidY)**2.)
240
241 outerGradient = np.ones(len(x))
242
243 outer = (fpRadius >= self.outerGradientRadius)
244 if outer.sum() > 0:
245 outerGradient[outer] = (
246 1
247 + self.outerGradientX*(x[outer] - self.centroidX)
248 + self.outerGradientY*(y[outer] - self.centroidY)
249 )
250
251 gradient *= outerGradient
252
253 return gradient
254
255 def computeFullModel(self, x, y, is_itl):
256 """Compute the full gradient model given x/y and itl booleans.
257
258 This returns the full model that can be applied directly
259 to data that was used in a fit.
260
261 Parameters
262 ----------
263 x : `np.ndarray`
264 Array of focal plane x values (mm).
265 y : `np.ndarray`
266 Array of focal plane y values (mm).
267 is_itl : `np.ndarray`
268 Boolean array of whether each point is from an ITL detector.
269
270 Returns
271 -------
272 model : `np.ndarray`
273 Model values at each position.
274 """
275 model = self.computeRadialSplineModelXY(x, y) / self.computeGradientModel(x, y)
276 model[is_itl] *= self.itlRatio
277
278 return model
279
280 @classmethod
281 def fromDict(cls, dictionary):
282 """Construct a FlatGradient from a dictionary of properties.
283
284 Parameters
285 ----------
286 dictionary : `dict`
287 Dictionary of properties.
288
289 Returns
290 -------
291 calib : `lsst.ip.isr.FlatGradient`
292 Constructed calibration.
293 """
294 calib = cls()
295
296 calib.setMetadata(dictionary["metadata"])
297
298 calib.radialSplineNodes = np.asarray(dictionary["radialSplineNodes"])
299 calib.radialSplineValues = np.asarray(dictionary["radialSplineValues"])
300 calib.itlRatio = dictionary["itlRatio"]
301 calib.centroidX = dictionary["centroidX"]
302 calib.centroidY = dictionary["centroidY"]
303 calib.centroidDeltaX = dictionary["centroidDeltaX"]
304 calib.centroidDeltaY = dictionary["centroidDeltaY"]
305 calib.gradientX = dictionary["gradientX"]
306 calib.gradientY = dictionary["gradientY"]
307 calib.outerGradientX = dictionary["outerGradientX"]
308 calib.outerGradientY = dictionary["outerGradientY"]
309 calib.outerGradientRadius = dictionary["outerGradientRadius"]
310 calib.normalizationFactor = dictionary["normalizationFactor"]
311
312 calib.updateMetadata()
313 return calib
314
315 def toDict(self):
316 """Return a dictionary containing the calibration properties.
317
318 Returns
319 -------
320 dictionary : `dict`
321 Dictionary of properties.
322 """
323 self.updateMetadata()
324
325 outDict = dict()
326 metadata = self.getMetadata()
327 outDict["metadata"] = metadata
328
329 outDict["radialSplineNodes"] = self.radialSplineNodes.tolist()
330 outDict["radialSplineValues"] = self.radialSplineValues.tolist()
331 outDict["itlRatio"] = float(self.itlRatio)
332 outDict["centroidX"] = float(self.centroidX)
333 outDict["centroidY"] = float(self.centroidY)
334 outDict["centroidDeltaX"] = float(self.centroidDeltaX)
335 outDict["centroidDeltaY"] = float(self.centroidDeltaY)
336 outDict["gradientX"] = float(self.gradientX)
337 outDict["gradientY"] = float(self.gradientY)
338 outDict["outerGradientX"] = float(self.outerGradientX)
339 outDict["outerGradientY"] = float(self.outerGradientY)
340 outDict["outerGradientRadius"] = float(self.outerGradientRadius)
341 outDict["normalizationFactor"] = float(self.normalizationFactor)
342
343 return outDict
344
345 @classmethod
346 def fromTable(cls, tableList):
347 """Construct a calibration from a list of tables.
348
349 Parameters
350 ----------
351 tableList : `list` [`astropy.table.Table`]
352 List of table(s) to use to construct the FlatGradient.
353
354 Returns
355 -------
356 calib : `lsst.ip.isr.FlatGradient`
357 The calibration defined in the table(s).
358 """
359 gradientTable = tableList[0]
360
361 metadata = gradientTable.meta
362 inDict = dict()
363 inDict["metadata"] = metadata
364 inDict["radialSplineNodes"] = np.array(gradientTable[0]["RADIAL_SPLINE_NODES"], dtype=np.float64)
365 inDict["radialSplineValues"] = np.array(gradientTable[0]["RADIAL_SPLINE_VALUES"], dtype=np.float64)
366 inDict["itlRatio"] = float(gradientTable[0]["ITL_RATIO"][0])
367 inDict["centroidX"] = float(gradientTable[0]["CENTROID_X"][0])
368 inDict["centroidY"] = float(gradientTable[0]["CENTROID_Y"][0])
369 inDict["centroidDeltaX"] = float(gradientTable[0]["CENTROID_DELTA_X"][0])
370 inDict["centroidDeltaY"] = float(gradientTable[0]["CENTROID_DELTA_Y"][0])
371 inDict["gradientX"] = float(gradientTable[0]["GRADIENT_X"][0])
372 inDict["gradientY"] = float(gradientTable[0]["GRADIENT_Y"][0])
373 inDict["outerGradientX"] = float(gradientTable[0]["OUTER_GRADIENT_X"][0])
374 inDict["outerGradientY"] = float(gradientTable[0]["OUTER_GRADIENT_Y"][0])
375 inDict["outerGradientRadius"] = float(gradientTable[0]["OUTER_GRADIENT_RADIUS"][0])
376 inDict["normalizationFactor"] = float(gradientTable[0]["NORMALIZATION_FACTOR"][0])
377
378 return cls().fromDict(inDict)
379
380 def toTable(self):
381 """Construct a list of table(s) containing the FlatGradient data.
382
383 Returns
384 -------
385 tableList : `list` [`astropy.table.Table`]
386 List of tables containing the FlatGradient information.
387 """
388 tableList = []
389 self.updateMetadata()
390
391 catalog = Table(
392 data=({
393 "RADIAL_SPLINE_NODES": self.radialSplineNodes,
394 "RADIAL_SPLINE_VALUES": self.radialSplineValues,
395 "ITL_RATIO": np.array([self.itlRatio]),
396 "CENTROID_X": np.array([self.centroidX]),
397 "CENTROID_Y": np.array([self.centroidY]),
398 "CENTROID_DELTA_X": np.array([self.centroidDeltaX]),
399 "CENTROID_DELTA_Y": np.array([self.centroidDeltaY]),
400 "GRADIENT_X": np.array([self.gradientX]),
401 "GRADIENT_Y": np.array([self.gradientY]),
402 "OUTER_GRADIENT_X": np.array([self.outerGradientX]),
403 "OUTER_GRADIENT_Y": np.array([self.outerGradientY]),
404 "OUTER_GRADIENT_RADIUS": np.array([self.outerGradientRadius]),
405 "NORMALIZATION_FACTOR": np.array([self.normalizationFactor]),
406 },)
407 )
408
409 inMeta = self.getMetadata().toDict()
410 outMeta = {k: v for k, v in inMeta.items() if v is not None}
411 outMeta.update({k: "" for k, v in inMeta.items() if v is None})
412 catalog.meta = outMeta
413 tableList.append(catalog)
414
415 return tableList
updateMetadata(self, camera=None, detector=None, filterName=None, setCalibId=False, setCalibInfo=False, setDate=False, **kwargs)
Definition calibType.py:210
setParameters(self, *, radialSplineNodes, radialSplineValues, itlRatio=1.0, centroidX=0.0, centroidY=0.0, centroidDeltaX=0.0, centroidDeltaY=0.0, gradientX=0.0, gradientY=0.0, outerGradientX=0.0, outerGradientY=0.0, outerGradientRadius=np.inf, normalizationFactor=1.0)