lsst.afw gf03f0b42f3+f620a9ee49
Loading...
Searching...
No Matches
_exposureSummaryStats.py
Go to the documentation of this file.
1# This file is part of afw.
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/>.
21from __future__ import annotations
22
23import dataclasses
24from typing import TYPE_CHECKING
25import yaml
26import warnings
27
28import numpy as np
29
30from ..typehandling import Storable, StorableHelperFactory
31
32if TYPE_CHECKING:
33 from ..table import BaseRecord, Schema
34
35__all__ = ("ExposureSummaryStats", )
36
37
39 return [float("nan")] * 4
40
41
42@dataclasses.dataclass
44 _persistence_name = 'ExposureSummaryStats'
45
46 _factory = StorableHelperFactory(__name__, _persistence_name)
47
48 version: int = 0
49
50 psfSigma: float = float('nan')
51 """PSF determinant radius (pixels)."""
52
53 psfArea: float = float('nan')
54 """PSF effective area (pixels**2)."""
55
56 psfIxx: float = float('nan')
57 """PSF shape Ixx (pixels**2)."""
58
59 psfIyy: float = float('nan')
60 """PSF shape Iyy (pixels**2)."""
61
62 psfIxy: float = float('nan')
63 """PSF shape Ixy (pixels**2)."""
64
65 ra: float = float('nan')
66 """Bounding box center Right Ascension (degrees)."""
67
68 dec: float = float('nan')
69 """Bounding box center Declination (degrees)."""
70
71 pixelScale: float = float('nan')
72 """Measured detector pixel scale (arcsec/pixel)."""
73
74 zenithDistance: float = float('nan')
75 """Bounding box center zenith distance (degrees)."""
76
77 expTime: float = float('nan')
78 """Exposure time of the exposure (seconds)."""
79
80 zeroPoint: float = float('nan')
81 """Mean zeropoint in detector (mag)."""
82
83 skyBg: float = float('nan')
84 """Average sky background (ADU)."""
85
86 skyNoise: float = float('nan')
87 """Average sky noise (ADU)."""
88
89 meanVar: float = float('nan')
90 """Mean variance of the weight plane (ADU**2)."""
91
92 raCorners: list[float] = dataclasses.field(default_factory=_default_corners)
93 """Right Ascension of bounding box corners (degrees)."""
94
95 decCorners: list[float] = dataclasses.field(default_factory=_default_corners)
96 """Declination of bounding box corners (degrees)."""
97
98 psfAdaptiveThresholdValue: float = float('nan')
99 """Threshold value used in the adaptive threshold detection pass for PSF modelling."""
100
101 psfAdaptiveIncludeThresholdMultiplier: float = float('nan')
102 """Threshold multiplier used in the adaptive threshold detection pass for PSF modelling."""
103
104 nShapeletsStar: int = 0
105 """Number of sources used in the shapelet decomposition."""
106
107 shapeletsOnlyIqScore: float = float('nan')
108 """The dimensionless image quality score as determined from the shapelets decomposition
109 that includes power only from the non-atmospheric decomposition coefficients. The
110 score spans the range [0.0, 1.0] with lower values indicating better image quality.
111 """
112
113 shapeletsIqScore: float = float('nan')
114 """The dimensionless image quality score as determined from the shapelets decomposition
115 that includes power from the median centroid offset between those used in the decomposition
116 and those of the centroid slot in addition to non-atmospheric decomposition coefficients.
117 The score spans the range [0.0, 1.0] with lower values indicating better image quality.
118 """
119
120 shapeletsCoeffs: list[float] = dataclasses.field(default_factory=list)
121 """List of coefficients from the PSF star shapelet decomposition."""
122
123 centroidDiffShapeletsVsSlotMedian: float = float('nan')
124 """Median centroid difference (sqrt((slot_x - shapelet_x)**2 + (slot_y - shapelet_y)**2)) for
125 sources used in the shapelet decomposition (pixels).
126 """
127
128 shapeletsStarEMedian: float = float('nan')
129 """Median ellipticity (sqrt(starE1**2.0 + starE2**2.0)) of the sources used in the
130 shapelet decomposition.
131 """
132
133 shapeletsStarUnNormalizedEMedian: float = float('nan')
134 """Median un-normalized ellipticity (sqrt((starXX - starYY)**2.0 + (2.0*starXY)**2.0))
135 of the sources used in the shapelet decomposition (pixels**2).
136 """
137
138 refCatSourceDensity: float = float('nan')
139 """Source density for the detector region as computed from the loaded reference catalog
140 (number per degrees**2).
141 """
142
143 astromOffsetMean: float = float('nan')
144 """Astrometry match offset mean."""
145
146 astromOffsetStd: float = float('nan')
147 """Astrometry match offset stddev."""
148
149 nPsfStar: int = 0
150 """Number of stars used for psf model."""
151
152 psfStarDeltaE1Median: float = float('nan')
153 """Psf stars median E1 residual (starE1 - psfE1)."""
154
155 psfStarDeltaE2Median: float = float('nan')
156 """Psf stars median E2 residual (starE2 - psfE2)."""
157
158 psfStarDeltaE1Scatter: float = float('nan')
159 """Psf stars MAD E1 scatter (starE1 - psfE1)."""
160
161 psfStarDeltaE2Scatter: float = float('nan')
162 """Psf stars MAD E2 scatter (starE2 - psfE2)."""
163
164 psfStarDeltaSizeMedian: float = float('nan')
165 """Psf stars median size residual (starSize - psfSize)."""
166
167 psfStarDeltaSizeScatter: float = float('nan')
168 """Psf stars MAD size scatter (starSize - psfSize)."""
169
170 psfStarScaledDeltaSizeScatter: float = float('nan')
171 """Psf stars MAD size scatter scaled by psfSize**2."""
172
173 psfTraceRadiusDelta: float = float('nan')
174 """Delta (max - min) of the model psf trace radius values evaluated on a
175 grid of unmasked pixels (pixels).
176 """
177
178 psfApFluxDelta: float = float('nan')
179 """Delta (max - min) of the model psf aperture flux (with aperture radius of
180 max(2, 3*psfSigma)) values evaluated on a grid of unmasked pixels.
181 """
182
183 psfApCorrSigmaScaledDelta: float = float('nan')
184 """Delta (max - min) of the psf flux aperture correction factors scaled (divided)
185 by the psfSigma evaluated on a grid of unmasked pixels.
186 """
187
188 maxDistToNearestPsf: float = float('nan')
189 """Maximum distance of an unmasked pixel to its nearest model psf star
190 (pixels).
191 """
192
193 starEMedian: float = float('nan')
194 """Median ellipticity (sqrt(starE1**2.0 + starE2**2.0)) of the stars used
195 in the PSF model.
196 """
197
198 starUnNormalizedEMedian: float = float('nan')
199 """Median un-normalized ellipticity (sqrt((starXX - starYY)**2.0 + (2.0*starXY)**2.0))
200 of the stars used in the PSF model (pixel**2).
201 """
202
203 starComa1Median: float = float('nan')
204 """Coma-like higher-order moment combination: median M30 + M12
205 of the stars used in the PSF model.
206 """
207
208 starComa2Median: float = float('nan')
209 """Coma-like higher-order moment combination: median M21 + M03
210 of the stars used in the PSF model.
211 """
212
213 starTrefoil1Median: float = float('nan')
214 """Trefoil-like higher-order moment combination: median M30 - 3*M12
215 of the stars used in the PSF model.
216 """
217
218 starTrefoil2Median: float = float('nan')
219 """Trefoil-like higher-order moment combination: median 3*M21 - M03
220 of the stars used in the PSF model.
221 """
222
223 starKurtosisMedian: float = float('nan')
224 """Kurtosis-like higher-order moment combination: median M40 + 2*M22 + M04
225 of the stars used in the PSF model.
226 """
227
228 starE41Median: float = float('nan')
229 """Fourth-order ellipticity-like higher-order moment combination: median M40 - M04
230 of the stars used in the PSF model.
231 """
232
233 starE42Median: float = float('nan')
234 """Fourth-order ellipticity-like higher-order moment combination: median 2*(M31 + M13)
235 of the stars used in the PSF model.
236 """
237
238 effTime: float = float('nan')
239 """Effective exposure time calculated from psfSigma, skyBg, and
240 zeroPoint (seconds).
241 """
242
243 effTimePsfSigmaScale: float = float('nan')
244 """PSF scaling of the effective exposure time."""
245
246 effTimeSkyBgScale: float = float('nan')
247 """Sky background scaling of the effective exposure time."""
248
249 effTimeZeroPointScale: float = float('nan')
250 """Zeropoint scaling of the effective exposure time."""
251
252 magLim: float = float('nan')
253 """Magnitude limit at fixed SNR (default SNR=5) calculated from psfSigma, skyBg,
254 zeroPoint, and readNoise.
255 """
256
257 def __post_init__(self):
258 Storable.__init__(self)
259
260 def isPersistable(self):
261 return True
262
264 return self._persistence_name
265
267 return __name__
268
269 def _write(self):
270 return yaml.dump(dataclasses.asdict(self), encoding='utf-8')
271
272 @staticmethod
273 def _read(bytes):
274 yamlDict = yaml.load(bytes, Loader=yaml.SafeLoader)
275
276 # Special list of fields to forward to new names.
277 forwardFieldDict = {"decl": "dec"}
278
279 # For forwards compatibility, filter out any fields that are
280 # not defined in the dataclass.
281 droppedFields = []
282 for _field in list(yamlDict.keys()):
283 if _field not in ExposureSummaryStats.__dataclass_fields__:
284 if _field in forwardFieldDict and forwardFieldDict[_field] not in yamlDict:
285 yamlDict[forwardFieldDict[_field]] = yamlDict[_field]
286 else:
287 droppedFields.append(_field)
288 yamlDict.pop(_field)
289 if len(droppedFields) > 0:
290 droppedFieldString = ", ".join([str(f) for f in droppedFields])
291 plural = "s" if len(droppedFields) != 1 else ""
292 them = "them" if len(droppedFields) > 1 else "it"
293 warnings.warn(
294 f"Summary field{plural} [{droppedFieldString}] not recognized by this software version;"
295 f" ignoring {them}.",
296 FutureWarning,
297 stacklevel=2,
298 )
299 return ExposureSummaryStats(**yamlDict)
300
301 @classmethod
302 def update_schema(cls, schema: Schema) -> None:
303 """Update an schema to includes for all summary statistic fields.
304
305 Parameters
306 -------
307 schema : `lsst.afw.table.Schema`
308 Schema to add which fields will be added.
309 """
310 schema.addField(
311 "psfSigma",
312 type="F",
313 doc="PSF model second-moments determinant radius (center of chip) (pixel)",
314 units="pixel",
315 )
316 schema.addField(
317 "psfArea",
318 type="F",
319 doc="PSF model effective area (center of chip) (pixel**2)",
320 units='pixel**2',
321 )
322 schema.addField(
323 "psfIxx",
324 type="F",
325 doc="PSF model Ixx (center of chip) (pixel**2)",
326 units='pixel**2',
327 )
328 schema.addField(
329 "psfIyy",
330 type="F",
331 doc="PSF model Iyy (center of chip) (pixel**2)",
332 units='pixel**2',
333 )
334 schema.addField(
335 "psfIxy",
336 type="F",
337 doc="PSF model Ixy (center of chip) (pixel**2)",
338 units='pixel**2',
339 )
340 schema.addField(
341 "raCorners",
342 type="ArrayD",
343 size=4,
344 doc="Right Ascension of bounding box corners (degrees)",
345 units="degree",
346 )
347 schema.addField(
348 "decCorners",
349 type="ArrayD",
350 size=4,
351 doc="Declination of bounding box corners (degrees)",
352 units="degree",
353 )
354 schema.addField(
355 "ra",
356 type="D",
357 doc="Right Ascension of bounding box center (degrees)",
358 units="degree",
359 )
360 schema.addField(
361 "dec",
362 type="D",
363 doc="Declination of bounding box center (degrees)",
364 units="degree",
365 )
366 schema.addField(
367 "zenithDistance",
368 type="F",
369 doc="Zenith distance of bounding box center (degrees)",
370 units="degree",
371 )
372 schema.addField(
373 "pixelScale",
374 type="F",
375 doc="Measured detector pixel scale (arcsec/pixel)",
376 units="arcsec/pixel",
377 )
378 schema.addField(
379 "expTime",
380 type="F",
381 doc="Exposure time of the exposure (seconds)",
382 units="second",
383 )
384 schema.addField(
385 "zeroPoint",
386 type="F",
387 doc="Mean zeropoint in detector (mag)",
388 units="mag",
389 )
390 schema.addField(
391 "skyBg",
392 type="F",
393 doc="Average sky background (ADU)",
394 units="adu",
395 )
396 schema.addField(
397 "skyNoise",
398 type="F",
399 doc="Average sky noise (ADU)",
400 units="adu",
401 )
402 schema.addField(
403 "meanVar",
404 type="F",
405 doc="Mean variance of the weight plane (ADU**2)",
406 units="adu**2"
407 )
408 schema.addField(
409 "psfAdaptiveThresholdValue",
410 type="F",
411 doc="Threshold value used in the adaptive threshold detection pass for PSF modelling.",
412 units="",
413 )
414 schema.addField(
415 "psfAdaptiveIncludeThresholdMultiplier",
416 type="F",
417 doc="Threshold multiplier used in the adaptive threshold detection pass for PSF modelling.",
418 units="",
419 )
420 schema.addField(
421 "nShapeletsStar",
422 type="I",
423 doc="Number of sources used in the shapelet decomposition.",
424 units="count",
425 )
426 schema.addField(
427 "shapeletsOnlyIqScore",
428 type="F",
429 doc="The dimensionless image quality score as determined from the shapelets "
430 "decomposition that includes power only from the non-atmospheric decomposition "
431 "coefficients. The score spans the range [0.0, 1.0] with lower values indicating "
432 "better image quality.",
433 units="",
434 )
435 schema.addField(
436 "shapeletsIqScore",
437 type="F",
438 doc="The dimensionless image quality score as determined from the shapelets "
439 "decomposition that includes power from the median centroid offset between those "
440 "used in the decomposition and those of the centroid slot in addition to "
441 "non-atmospheric decomposition coefficients. The score spans the range [0.0, 1.0] "
442 "with lower values indicating better image quality.",
443 units="",
444 )
445 schema.addField(
446 "shapeletsCoeffs",
447 type="ArrayD",
448 size=0, # dynamic size
449 doc="List of coefficients from the PSF star shapelet decomposition.",
450 units="",
451 )
452 schema.addField(
453 "centroidDiffShapeletsVsSlotMedian",
454 type="F",
455 doc="Median centroid difference (sqrt((slot_x - shapelet_x)**2 + (slot_y - shapelet_y)**2)) "
456 "for sources used in the shapelet decomposition.",
457 units="pixel",
458 )
459 schema.addField(
460 "shapeletsStarEMedian",
461 type="F",
462 doc="Median ellipticity (sqrt(starE1**2.0 + starE2**2.0)) of the stars used in the "
463 "shapelet decomposition.",
464 units="",
465 )
466 schema.addField(
467 "shapeletsStarUnNormalizedEMedian",
468 type="F",
469 doc="Median un-normalized ellipticity (sqrt((starXX - starYY)**2.0 + (2.0*starXY)**2.0)) "
470 "of the stars used in the shapelet decomposition.",
471 units="pixel**2",
472 )
473 schema.addField(
474 "refCatSourceDensity",
475 type="F",
476 doc="Source density for the detector region as computed from the loaded reference catalog "
477 "(number per degrees**2)",
478 units="degree**-2",
479 )
480 schema.addField(
481 "astromOffsetMean",
482 type="F",
483 doc="Mean offset of astrometric calibration matches (arcsec)",
484 units="arcsec",
485 )
486 schema.addField(
487 "astromOffsetStd",
488 type="F",
489 doc="Standard deviation of offsets of astrometric calibration matches (arcsec)",
490 units="arcsec",
491 )
492 schema.addField("nPsfStar", type="I", doc="Number of stars used for PSF model")
493 schema.addField(
494 "psfStarDeltaE1Median",
495 type="F",
496 doc="Median E1 residual (starE1 - psfE1) for psf stars",
497 )
498 schema.addField(
499 "psfStarDeltaE2Median",
500 type="F",
501 doc="Median E2 residual (starE2 - psfE2) for psf stars",
502 )
503 schema.addField(
504 "psfStarDeltaE1Scatter",
505 type="F",
506 doc="Scatter (via MAD) of E1 residual (starE1 - psfE1) for psf stars",
507 )
508 schema.addField(
509 "psfStarDeltaE2Scatter",
510 type="F",
511 doc="Scatter (via MAD) of E2 residual (starE2 - psfE2) for psf stars",
512 )
513 schema.addField(
514 "psfStarDeltaSizeMedian",
515 type="F",
516 doc="Median size residual (starSize - psfSize) for psf stars (pixel)",
517 units="pixel",
518 )
519 schema.addField(
520 "psfStarDeltaSizeScatter",
521 type="F",
522 doc="Scatter (via MAD) of size residual (starSize - psfSize) for psf stars (pixel)",
523 units="pixel",
524 )
525 schema.addField(
526 "psfStarScaledDeltaSizeScatter",
527 type="F",
528 doc="Scatter (via MAD) of size residual scaled by median size squared",
529 )
530 schema.addField(
531 "psfTraceRadiusDelta",
532 type="F",
533 doc="Delta (max - min) of the model psf trace radius values evaluated on a grid of "
534 "unmasked pixels (pixel).",
535 units="pixel",
536 )
537 schema.addField(
538 "psfApFluxDelta",
539 type="F",
540 doc="Delta (max - min) of the model psf aperture flux (with aperture radius of "
541 "max(2, 3*psfSigma)) values evaluated on a grid of unmasked pixels.",
542 )
543 schema.addField(
544 "psfApCorrSigmaScaledDelta",
545 type="F",
546 doc="Delta (max - min) of the model psf aperture correction factors scaled (divided) "
547 "by the psfSigma evaluated on a grid of unmasked pixels.",
548 )
549 schema.addField(
550 "maxDistToNearestPsf",
551 type="F",
552 doc="Maximum distance of an unmasked pixel to its nearest model psf star (pixel).",
553 units="pixel",
554 )
555 schema.addField(
556 "starEMedian",
557 type="F",
558 doc="Median ellipticity (sqrt(starE1**2.0 + starE2**2.0)) of the stars used in "
559 "the PSF model.",
560 )
561 schema.addField(
562 "starUnNormalizedEMedian",
563 type="F",
564 doc="Median un-normalized ellipticity (sqrt((starXX - starYY)**2.0 + (2.0*starXY)**2.0)) "
565 "of the stars used in the PSF model.",
566 )
567 schema.addField(
568 "starComa1Median",
569 type="F",
570 doc="Coma-like higher-order moment combination: median M30 + M12 "
571 "of the stars used in the PSF model.",
572 )
573 schema.addField(
574 "starComa2Median",
575 type="F",
576 doc="Coma-like higher-order moment combination: median M21 + M03 "
577 "of the stars used in the PSF model.",
578 )
579 schema.addField(
580 "starTrefoil1Median",
581 type="F",
582 doc="Trefoil-like higher-order moment combination: median M30 - 3*M12 "
583 "of the stars used in the PSF model.",
584 )
585 schema.addField(
586 "starTrefoil2Median",
587 type="F",
588 doc="Trefoil-like higher-order moment combination: median 3*M21 - M03 "
589 "of the stars used in the PSF model.",
590 )
591 schema.addField(
592 "starKurtosisMedian",
593 type="F",
594 doc="Kurtosis-like higher-order moment combination: median M40 + 2*M22 + M04 "
595 "of the stars used in the PSF model.",
596 )
597 schema.addField(
598 "starE41Median",
599 type="F",
600 doc="Fourth-order ellipticity-like higher-order moment combination: median M40 - M04 "
601 "of the stars used in the PSF model.",
602 )
603 schema.addField(
604 "starE42Median",
605 type="F",
606 doc="Fourth-order ellipticity-like higher-order moment combination: median 2*(M31 + M13) "
607 "of the stars used in the PSF model.",
608 )
609 schema.addField(
610 "effTime",
611 type="F",
612 doc="Effective exposure time calculated from psfSigma, skyBg, and "
613 "zeroPoint (seconds).",
614 units="second",
615 )
616 schema.addField(
617 "effTimePsfSigmaScale",
618 type="F",
619 doc="PSF scaling of the effective exposure time."
620 )
621 schema.addField(
622 "effTimeSkyBgScale",
623 type="F",
624 doc="Sky background scaling of the effective exposure time."
625 )
626 schema.addField(
627 "effTimeZeroPointScale",
628 type="F",
629 doc="Zeropoint scaling of the effective exposure time."
630 )
631 schema.addField(
632 "magLim",
633 type="F",
634 doc="Magnitude limit at SNR=5 (M5) calculated from psfSigma, "
635 "skyBg, zeroPoint, and readNoise.",
636 units="mag",
637 )
638
639 def update_record(self, record: BaseRecord) -> None:
640 """Write summary-statistic columns into a record.
641
642 Parameters
643 ----------
644 record : `lsst.afw.table.BaseRecord`
645 Record to update. This is expected to frequently be an
646 `ExposureRecord` instance (with higher-level code adding other
647 columns and objects), but this method can work with any record
648 type.
649 """
650 for field in dataclasses.fields(self):
651 value = getattr(self, field.name)
652 if field.name == "version":
653 continue
654 elif field.type.startswith("list"):
655 record[field.name] = np.array(value, dtype=record[field.name].dtype)
656 else:
657 record[field.name] = value
658
659 @classmethod
660 def from_record(cls, record: BaseRecord) -> ExposureSummaryStats:
661 """Read summary-statistic columns from a record into ``self``.
662
663 Parameters
664 ----------
665 record : `lsst.afw.table.BaseRecord`
666 Record to read from. This is expected to frequently be an
667 `ExposureRecord` instance (with higher-level code adding other
668 columns and objects), but this method can work with any record
669 type, ignoring any attributes or columns it doesn't recognize.
670
671 Returns
672 -------
673 summary : `ExposureSummaryStats`
674 Summary statistics object created from the given record.
675 """
676 return cls(
677 **{
678 field.name: (
679 record[field.name] if not field.type.startswith("list")
680 else [float(v) for v in record[field.name]]
681 )
682 for field in dataclasses.fields(cls)
683 if field.name != "version"
684 }
685 )
ExposureSummaryStats from_record(cls, BaseRecord record)
virtual bool isPersistable() const noexcept
Return true if this particular object can be persisted using afw::table::io.
Interface supporting iteration over heterogenous containers.
Definition Storable.h:58