LSST Applications 26.0.0,g0265f82a02+6660c170cc,g07994bdeae+30b05a742e,g0a0026dc87+17526d298f,g0a60f58ba1+17526d298f,g0e4bf8285c+96dd2c2ea9,g0ecae5effc+c266a536c8,g1e7d6db67d+6f7cb1f4bb,g26482f50c6+6346c0633c,g2bbee38e9b+6660c170cc,g2cc88a2952+0a4e78cd49,g3273194fdb+f6908454ef,g337abbeb29+6660c170cc,g337c41fc51+9a8f8f0815,g37c6e7c3d5+7bbafe9d37,g44018dc512+6660c170cc,g4a941329ef+4f7594a38e,g4c90b7bd52+5145c320d2,g58be5f913a+bea990ba40,g635b316a6c+8d6b3a3e56,g67924a670a+bfead8c487,g6ae5381d9b+81bc2a20b4,g93c4d6e787+26b17396bd,g98cecbdb62+ed2cb6d659,g98ffbb4407+81bc2a20b4,g9ddcbc5298+7f7571301f,ga1e77700b3+99e9273977,gae46bcf261+6660c170cc,gb2715bf1a1+17526d298f,gc86a011abf+17526d298f,gcf0d15dbbd+96dd2c2ea9,gdaeeff99f8+0d8dbea60f,gdb4ec4c597+6660c170cc,ge23793e450+96dd2c2ea9,gf041782ebf+171108ac67
LSST Data Management Base Package
Loading...
Searching...
No Matches
fgcmFitCycle.py
Go to the documentation of this file.
1# See COPYRIGHT file at the top of the source tree.
2#
3# This file is part of fgcmcal.
4#
5# Developed for the LSST Data Management System.
6# This product includes software developed by the LSST Project
7# (https://www.lsst.org).
8# See the COPYRIGHT file at the top-level directory of this distribution
9# for details of code ownership.
10#
11# This program is free software: you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation, either version 3 of the License, or
14# (at your option) any later version.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with this program. If not, see <https://www.gnu.org/licenses/>.
23"""Perform a single fit cycle of FGCM.
24
25This task runs a single "fit cycle" of fgcm. Prior to running this task
26one must run both fgcmMakeLut (to construct the atmosphere and instrumental
27look-up-table) and fgcmBuildStars (to extract visits and star observations
28for the global fit).
29
30The fgcmFitCycle is meant to be run multiple times, and is tracked by the
31'cycleNumber'. After each run of the fit cycle, diagnostic plots should
32be inspected to set parameters for outlier rejection on the following
33cycle. Please see the fgcmcal Cookbook for details.
34"""
35
36import copy
37
38import numpy as np
39
40import lsst.pex.config as pexConfig
41import lsst.pipe.base as pipeBase
42from lsst.pipe.base import connectionTypes
43import lsst.afw.table as afwTable
44
45from .utilities import makeConfigDict, translateFgcmLut, translateVisitCatalog
46from .utilities import extractReferenceMags
47from .utilities import makeZptSchema, makeZptCat
48from .utilities import makeAtmSchema, makeAtmCat, makeStdSchema, makeStdCat
49from .sedterms import SedboundarytermDict, SedtermDict
50from .utilities import lookupStaticCalibrations
51from .focalPlaneProjector import FocalPlaneProjector
52
53import fgcm
54
55__all__ = ['FgcmFitCycleConfig', 'FgcmFitCycleTask']
56
57MULTIPLE_CYCLES_MAX = 10
58
59
60class FgcmFitCycleConnections(pipeBase.PipelineTaskConnections,
61 dimensions=("instrument",),
62 defaultTemplates={"previousCycleNumber": "-1",
63 "cycleNumber": "0"}):
64 camera = connectionTypes.PrerequisiteInput(
65 doc="Camera instrument",
66 name="camera",
67 storageClass="Camera",
68 dimensions=("instrument",),
69 lookupFunction=lookupStaticCalibrations,
70 isCalibration=True,
71 )
72
73 fgcmLookUpTable = connectionTypes.PrerequisiteInput(
74 doc=("Atmosphere + instrument look-up-table for FGCM throughput and "
75 "chromatic corrections."),
76 name="fgcmLookUpTable",
77 storageClass="Catalog",
78 dimensions=("instrument",),
79 deferLoad=True,
80 )
81
82 fgcmVisitCatalog = connectionTypes.Input(
83 doc="Catalog of visit information for fgcm",
84 name="fgcmVisitCatalog",
85 storageClass="Catalog",
86 dimensions=("instrument",),
87 deferLoad=True,
88 )
89
90 fgcmStarObservationsParquet = connectionTypes.Input(
91 doc=("Catalog of star observations for fgcm, in parquet format. "
92 "Used if useParquetCatalogFormat is True."),
93 name="fgcm_star_observations",
94 storageClass="ArrowAstropy",
95 dimensions=("instrument",),
96 deferLoad=True,
97 )
98
99 fgcmStarIdsParquet = connectionTypes.Input(
100 doc=("Catalog of fgcm calibration star IDs, in parquet format. "
101 "Used if useParquetCatalogFormat is True."),
102 name="fgcm_star_ids",
103 storageClass="ArrowAstropy",
104 dimensions=("instrument",),
105 deferLoad=True,
106 )
107
108 fgcmReferenceStarsParquet = connectionTypes.Input(
109 doc=("Catalog of fgcm-matched reference stars, in parquet format. "
110 "Used if useParquetCatalogFormat is True."),
111 name="fgcm_reference_stars",
112 storageClass="ArrowAstropy",
113 dimensions=("instrument",),
114 deferLoad=True,
115 )
116
117 fgcmStarObservations = connectionTypes.Input(
118 doc=("Catalog of star observations for fgcm; old format. "
119 "Used if useParquetCatalogFormat is False."),
120 name="fgcmStarObservations",
121 storageClass="Catalog",
122 dimensions=("instrument",),
123 deferLoad=True,
124 )
125
126 fgcmStarIds = connectionTypes.Input(
127 doc=("Catalog of fgcm calibration star IDs. "
128 "Used if useParquetCatalogFormat is False."),
129 name="fgcmStarIds",
130 storageClass="Catalog",
131 dimensions=("instrument",),
132 deferLoad=True,
133 )
134
135 fgcmStarIndices = connectionTypes.Input(
136 doc=("Catalog of fgcm calibration star indices; old format."
137 "Used if useParquetCatalogFormat is False."),
138 name="fgcmStarIndices",
139 storageClass="Catalog",
140 dimensions=("instrument",),
141 deferLoad=True,
142 )
143
144 fgcmReferenceStars = connectionTypes.Input(
145 doc=("Catalog of fgcm-matched reference stars; old format."
146 "Used if useParquetCatalogFormat is False."),
147 name="fgcmReferenceStars",
148 storageClass="Catalog",
149 dimensions=("instrument",),
150 deferLoad=True,
151 )
152
153 fgcmFlaggedStarsInput = connectionTypes.PrerequisiteInput(
154 doc="Catalog of flagged stars for fgcm calibration from previous fit cycle",
155 name="fgcmFlaggedStars{previousCycleNumber}",
156 storageClass="Catalog",
157 dimensions=("instrument",),
158 deferLoad=True,
159 )
160
161 fgcmFitParametersInput = connectionTypes.PrerequisiteInput(
162 doc="Catalog of fgcm fit parameters from previous fit cycle",
163 name="fgcmFitParameters{previousCycleNumber}",
164 storageClass="Catalog",
165 dimensions=("instrument",),
166 deferLoad=True,
167 )
168
169 fgcmFitParameters = connectionTypes.Output(
170 doc="Catalog of fgcm fit parameters from current fit cycle",
171 name="fgcmFitParameters{cycleNumber}",
172 storageClass="Catalog",
173 dimensions=("instrument",),
174 )
175
176 fgcmFlaggedStars = connectionTypes.Output(
177 doc="Catalog of flagged stars for fgcm calibration from current fit cycle",
178 name="fgcmFlaggedStars{cycleNumber}",
179 storageClass="Catalog",
180 dimensions=("instrument",),
181 )
182
183 fgcmZeropoints = connectionTypes.Output(
184 doc="Catalog of fgcm zeropoint data from current fit cycle",
185 name="fgcmZeropoints{cycleNumber}",
186 storageClass="Catalog",
187 dimensions=("instrument",),
188 )
189
190 fgcmAtmosphereParameters = connectionTypes.Output(
191 doc="Catalog of atmospheric fit parameters from current fit cycle",
192 name="fgcmAtmosphereParameters{cycleNumber}",
193 storageClass="Catalog",
194 dimensions=("instrument",),
195 )
196
197 fgcmStandardStars = connectionTypes.Output(
198 doc="Catalog of standard star magnitudes from current fit cycle",
199 name="fgcmStandardStars{cycleNumber}",
200 storageClass="SimpleCatalog",
201 dimensions=("instrument",),
202 )
203
204 # Add connections for running multiple cycles
205 # This uses vars() to alter the class __dict__ to programmatically
206 # write many similar outputs.
207 for cycle in range(MULTIPLE_CYCLES_MAX):
208 vars()[f"fgcmFitParameters{cycle}"] = connectionTypes.Output(
209 doc=f"Catalog of fgcm fit parameters from fit cycle {cycle}",
210 name=f"fgcmFitParameters{cycle}",
211 storageClass="Catalog",
212 dimensions=("instrument",),
213 )
214 vars()[f"fgcmFlaggedStars{cycle}"] = connectionTypes.Output(
215 doc=f"Catalog of flagged stars for fgcm calibration from fit cycle {cycle}",
216 name=f"fgcmFlaggedStars{cycle}",
217 storageClass="Catalog",
218 dimensions=("instrument",),
219 )
220 vars()[f"fgcmZeropoints{cycle}"] = connectionTypes.Output(
221 doc=f"Catalog of fgcm zeropoint data from fit cycle {cycle}",
222 name=f"fgcmZeropoints{cycle}",
223 storageClass="Catalog",
224 dimensions=("instrument",),
225 )
226 vars()[f"fgcmAtmosphereParameters{cycle}"] = connectionTypes.Output(
227 doc=f"Catalog of atmospheric fit parameters from fit cycle {cycle}",
228 name=f"fgcmAtmosphereParameters{cycle}",
229 storageClass="Catalog",
230 dimensions=("instrument",),
231 )
232 vars()[f"fgcmStandardStars{cycle}"] = connectionTypes.Output(
233 doc=f"Catalog of standard star magnitudes from fit cycle {cycle}",
234 name=f"fgcmStandardStars{cycle}",
235 storageClass="SimpleCatalog",
236 dimensions=("instrument",),
237 )
238
239 def __init__(self, *, config=None):
240 super().__init__(config=config)
241
242 if not config.doReferenceCalibration:
243 self.inputs.remove("fgcmReferenceStars")
244 self.inputs.remove("fgcmReferenceStarsParquet")
245
246 if config.useParquetCatalogFormat:
247 self.inputs.remove("fgcmStarObservations")
248 self.inputs.remove("fgcmStarIds")
249 self.inputs.remove("fgcmStarIndices")
250 if config.doReferenceCalibration:
251 self.inputs.remove("fgcmReferenceStars")
252 else:
253 self.inputs.remove("fgcmStarObservationsParquet")
254 self.inputs.remove("fgcmStarIdsParquet")
255 if config.doReferenceCalibration:
256 self.inputs.remove("fgcmReferenceStarsParquet")
257
258 if str(int(config.connections.cycleNumber)) != config.connections.cycleNumber:
259 raise ValueError("cycleNumber must be of integer format")
260 if str(int(config.connections.previousCycleNumber)) != config.connections.previousCycleNumber:
261 raise ValueError("previousCycleNumber must be of integer format")
262 if int(config.connections.previousCycleNumber) != (int(config.connections.cycleNumber) - 1):
263 raise ValueError("previousCycleNumber must be 1 less than cycleNumber")
264
265 if int(config.connections.cycleNumber) == 0:
266 self.prerequisiteInputs.remove("fgcmFlaggedStarsInput")
267 self.prerequisiteInputs.remove("fgcmFitParametersInput")
268
269 if not self.config.doMultipleCycles:
270 # Single-cycle run
271 if not self.config.isFinalCycle and not self.config.outputStandardsBeforeFinalCycle:
272 self.outputs.remove("fgcmStandardStars")
273
274 if not self.config.isFinalCycle and not self.config.outputZeropointsBeforeFinalCycle:
275 self.outputs.remove("fgcmZeropoints")
276 self.outputs.remove("fgcmAtmosphereParameters")
277
278 # Remove all multiple cycle outputs
279 for cycle in range(0, MULTIPLE_CYCLES_MAX):
280 self.outputs.remove(f"fgcmFitParameters{cycle}")
281 self.outputs.remove(f"fgcmFlaggedStars{cycle}")
282 self.outputs.remove(f"fgcmZeropoints{cycle}")
283 self.outputs.remove(f"fgcmAtmosphereParameters{cycle}")
284 self.outputs.remove(f"fgcmStandardStars{cycle}")
285
286 else:
287 # Multiple-cycle run
288 # Remove single-cycle outputs
289 self.outputs.remove("fgcmFitParameters")
290 self.outputs.remove("fgcmFlaggedStars")
291 self.outputs.remove("fgcmZeropoints")
292 self.outputs.remove("fgcmAtmosphereParameters")
293 self.outputs.remove("fgcmStandardStars")
294
295 # Remove outputs from cycles that are not used
296 for cycle in range(self.config.multipleCyclesFinalCycleNumber + 1,
297 MULTIPLE_CYCLES_MAX):
298 self.outputs.remove(f"fgcmFitParameters{cycle}")
299 self.outputs.remove(f"fgcmFlaggedStars{cycle}")
300 self.outputs.remove(f"fgcmZeropoints{cycle}")
301 self.outputs.remove(f"fgcmAtmosphereParameters{cycle}")
302 self.outputs.remove(f"fgcmStandardStars{cycle}")
303
304 # Remove non-final-cycle outputs if necessary
305 for cycle in range(self.config.multipleCyclesFinalCycleNumber):
306 if not self.config.outputZeropointsBeforeFinalCycle:
307 self.outputs.remove(f"fgcmZeropoints{cycle}")
308 self.outputs.remove(f"fgcmAtmosphereParameters{cycle}")
309 if not self.config.outputStandardsBeforeFinalCycle:
310 self.outputs.remove(f"fgcmStandardStars{cycle}")
311
312
313class FgcmFitCycleConfig(pipeBase.PipelineTaskConfig,
314 pipelineConnections=FgcmFitCycleConnections):
315 """Config for FgcmFitCycle"""
316
317 doMultipleCycles = pexConfig.Field(
318 doc="Run multiple fit cycles in one task",
319 dtype=bool,
320 default=False,
321 )
322 useParquetCatalogFormat = pexConfig.Field(
323 doc="Use parquet catalog format?",
324 dtype=bool,
325 default=True,
326 )
327 multipleCyclesFinalCycleNumber = pexConfig.RangeField(
328 doc=("Final cycle number in multiple cycle mode. The initial cycle "
329 "is 0, with limited parameters fit. The next cycle is 1 with "
330 "full parameter fit. The final cycle is a clean-up with no "
331 "parameters fit. There will be a total of "
332 "(multipleCycleFinalCycleNumber + 1) cycles run, and the final "
333 "cycle number cannot be less than 2."),
334 dtype=int,
335 default=5,
336 min=2,
337 max=MULTIPLE_CYCLES_MAX,
338 inclusiveMax=True,
339 )
340 bands = pexConfig.ListField(
341 doc="Bands to run calibration",
342 dtype=str,
343 default=[],
344 )
345 fitBands = pexConfig.ListField(
346 doc=("Bands to use in atmospheric fit. The bands not listed here will have "
347 "the atmosphere constrained from the 'fitBands' on the same night. "
348 "Must be a subset of `config.bands`"),
349 dtype=str,
350 default=[],
351 )
352 requiredBands = pexConfig.ListField(
353 doc=("Bands that are required for a star to be considered a calibration star. "
354 "Must be a subset of `config.bands`"),
355 dtype=str,
356 default=[],
357 )
358 physicalFilterMap = pexConfig.DictField(
359 doc="Mapping from 'physicalFilter' to band.",
360 keytype=str,
361 itemtype=str,
362 default={},
363 )
364 doReferenceCalibration = pexConfig.Field(
365 doc="Use reference catalog as additional constraint on calibration",
366 dtype=bool,
367 default=True,
368 )
369 refStarSnMin = pexConfig.Field(
370 doc="Reference star signal-to-noise minimum to use in calibration. Set to <=0 for no cut.",
371 dtype=float,
372 default=50.0,
373 )
374 refStarOutlierNSig = pexConfig.Field(
375 doc=("Number of sigma compared to average mag for reference star to be considered an outlier. "
376 "Computed per-band, and if it is an outlier in any band it is rejected from fits."),
377 dtype=float,
378 default=4.0,
379 )
380 applyRefStarColorCuts = pexConfig.Field(
381 doc=("Apply color cuts defined in ``starColorCuts`` to reference stars? "
382 "These cuts are in addition to any cuts defined in ``refStarColorCuts``"),
383 dtype=bool,
384 default=True,
385 )
386 useExposureReferenceOffset = pexConfig.Field(
387 doc=("Use per-exposure (visit) offsets between calibrated stars and reference stars "
388 "for final zeropoints? This may help uniformity for disjoint surveys."),
389 dtype=bool,
390 default=False,
391 )
392 nCore = pexConfig.Field(
393 doc="Number of cores to use",
394 dtype=int,
395 default=4,
396 )
397 nStarPerRun = pexConfig.Field(
398 doc="Number of stars to run in each chunk",
399 dtype=int,
400 default=200000,
401 )
402 nExpPerRun = pexConfig.Field(
403 doc="Number of exposures to run in each chunk",
404 dtype=int,
405 default=1000,
406 )
407 reserveFraction = pexConfig.Field(
408 doc="Fraction of stars to reserve for testing",
409 dtype=float,
410 default=0.1,
411 )
412 freezeStdAtmosphere = pexConfig.Field(
413 doc="Freeze atmosphere parameters to standard (for testing)",
414 dtype=bool,
415 default=False,
416 )
417 precomputeSuperStarInitialCycle = pexConfig.Field(
418 doc="Precompute superstar flat for initial cycle",
419 dtype=bool,
420 default=False,
421 )
422 superStarSubCcdDict = pexConfig.DictField(
423 doc=("Per-band specification on whether to compute superstar flat on sub-ccd scale. "
424 "Must have one entry per band."),
425 keytype=str,
426 itemtype=bool,
427 default={},
428 )
429 superStarSubCcdChebyshevOrder = pexConfig.Field(
430 doc=("Order of the 2D chebyshev polynomials for sub-ccd superstar fit. "
431 "Global default is first-order polynomials, and should be overridden "
432 "on a camera-by-camera basis depending on the ISR."),
433 dtype=int,
434 default=1,
435 )
436 superStarSubCcdTriangular = pexConfig.Field(
437 doc=("Should the sub-ccd superstar chebyshev matrix be triangular to "
438 "suppress high-order cross terms?"),
439 dtype=bool,
440 default=False,
441 )
442 superStarSigmaClip = pexConfig.Field(
443 doc="Number of sigma to clip outliers when selecting for superstar flats",
444 dtype=float,
445 default=5.0,
446 )
447 focalPlaneSigmaClip = pexConfig.Field(
448 doc="Number of sigma to clip outliers per focal-plane.",
449 dtype=float,
450 default=4.0,
451 )
452 ccdGraySubCcdDict = pexConfig.DictField(
453 doc=("Per-band specification on whether to compute achromatic per-ccd residual "
454 "('ccd gray') on a sub-ccd scale."),
455 keytype=str,
456 itemtype=bool,
457 default={},
458 )
459 ccdGraySubCcdChebyshevOrder = pexConfig.Field(
460 doc="Order of the 2D chebyshev polynomials for sub-ccd gray fit.",
461 dtype=int,
462 default=1,
463 )
464 ccdGraySubCcdTriangular = pexConfig.Field(
465 doc=("Should the sub-ccd gray chebyshev matrix be triangular to "
466 "suppress high-order cross terms?"),
467 dtype=bool,
468 default=True,
469 )
470 ccdGrayFocalPlaneDict = pexConfig.DictField(
471 doc=("Per-band specification on whether to compute focal-plane residual "
472 "('ccd gray') corrections."),
473 keytype=str,
474 itemtype=bool,
475 default={},
476 )
477 ccdGrayFocalPlaneFitMinCcd = pexConfig.Field(
478 doc=("Minimum number of 'good' CCDs required to perform focal-plane "
479 "gray corrections. If there are fewer good CCDs then the gray "
480 "correction is computed per-ccd."),
481 dtype=int,
482 default=1,
483 )
484 ccdGrayFocalPlaneChebyshevOrder = pexConfig.Field(
485 doc="Order of the 2D chebyshev polynomials for focal plane fit.",
486 dtype=int,
487 default=3,
488 )
489 cycleNumber = pexConfig.Field(
490 doc=("FGCM fit cycle number. This is automatically incremented after each run "
491 "and stage of outlier rejection. See cookbook for details."),
492 dtype=int,
493 default=None,
494 )
495 isFinalCycle = pexConfig.Field(
496 doc=("Is this the final cycle of the fitting? Will automatically compute final "
497 "selection of stars and photometric exposures, and will output zeropoints "
498 "and standard stars for use in fgcmOutputProducts"),
499 dtype=bool,
500 default=False,
501 )
502 maxIterBeforeFinalCycle = pexConfig.Field(
503 doc=("Maximum fit iterations, prior to final cycle. The number of iterations "
504 "will always be 0 in the final cycle for cleanup and final selection."),
505 dtype=int,
506 default=50,
507 )
508 deltaMagBkgOffsetPercentile = pexConfig.Field(
509 doc=("Percentile brightest stars on a visit/ccd to use to compute net "
510 "offset from local background subtraction."),
511 dtype=float,
512 default=0.25,
513 )
514 deltaMagBkgPerCcd = pexConfig.Field(
515 doc=("Compute net offset from local background subtraction per-ccd? "
516 "Otherwise, use computation per visit."),
517 dtype=bool,
518 default=False,
519 )
520 utBoundary = pexConfig.Field(
521 doc="Boundary (in UTC) from day-to-day",
522 dtype=float,
523 default=None,
524 )
525 washMjds = pexConfig.ListField(
526 doc="Mirror wash MJDs",
527 dtype=float,
528 default=(0.0,),
529 )
530 epochMjds = pexConfig.ListField(
531 doc="Epoch boundaries in MJD",
532 dtype=float,
533 default=(0.0,),
534 )
535 minObsPerBand = pexConfig.Field(
536 doc="Minimum good observations per band",
537 dtype=int,
538 default=2,
539 )
540 # TODO: When DM-16511 is done, it will be possible to get the
541 # telescope latitude directly from the camera.
542 latitude = pexConfig.Field(
543 doc="Observatory latitude",
544 dtype=float,
545 default=None,
546 )
547 defaultCameraOrientation = pexConfig.Field(
548 doc="Default camera orientation for QA plots.",
549 dtype=float,
550 default=None,
551 )
552 brightObsGrayMax = pexConfig.Field(
553 doc="Maximum gray extinction to be considered bright observation",
554 dtype=float,
555 default=0.15,
556 )
557 minStarPerCcd = pexConfig.Field(
558 doc=("Minimum number of good stars per CCD to be used in calibration fit. "
559 "CCDs with fewer stars will have their calibration estimated from other "
560 "CCDs in the same visit, with zeropoint error increased accordingly."),
561 dtype=int,
562 default=5,
563 )
564 minCcdPerExp = pexConfig.Field(
565 doc=("Minimum number of good CCDs per exposure/visit to be used in calibration fit. "
566 "Visits with fewer good CCDs will have CCD zeropoints estimated where possible."),
567 dtype=int,
568 default=5,
569 )
570 maxCcdGrayErr = pexConfig.Field(
571 doc="Maximum error on CCD gray offset to be considered photometric",
572 dtype=float,
573 default=0.05,
574 )
575 minStarPerExp = pexConfig.Field(
576 doc=("Minimum number of good stars per exposure/visit to be used in calibration fit. "
577 "Visits with fewer good stars will have CCD zeropoints estimated where possible."),
578 dtype=int,
579 default=600,
580 )
581 minExpPerNight = pexConfig.Field(
582 doc="Minimum number of good exposures/visits to consider a partly photometric night",
583 dtype=int,
584 default=10,
585 )
586 expGrayInitialCut = pexConfig.Field(
587 doc=("Maximum exposure/visit gray value for initial selection of possible photometric "
588 "observations."),
589 dtype=float,
590 default=-0.25,
591 )
592 expGrayPhotometricCutDict = pexConfig.DictField(
593 doc=("Per-band specification on maximum (negative) achromatic exposure residual "
594 "('gray term') for a visit to be considered photometric. Must have one "
595 "entry per band. Broad-band filters should be -0.05."),
596 keytype=str,
597 itemtype=float,
598 default={},
599 )
600 expGrayHighCutDict = pexConfig.DictField(
601 doc=("Per-band specification on maximum (positive) achromatic exposure residual "
602 "('gray term') for a visit to be considered photometric. Must have one "
603 "entry per band. Broad-band filters should be 0.2."),
604 keytype=str,
605 itemtype=float,
606 default={},
607 )
608 expGrayRecoverCut = pexConfig.Field(
609 doc=("Maximum (negative) exposure gray to be able to recover bad ccds via interpolation. "
610 "Visits with more gray extinction will only get CCD zeropoints if there are "
611 "sufficient star observations (minStarPerCcd) on that CCD."),
612 dtype=float,
613 default=-1.0,
614 )
615 expVarGrayPhotometricCutDict = pexConfig.DictField(
616 doc=("Per-band specification on maximum exposure variance to be considered possibly "
617 "photometric. Must have one entry per band. Broad-band filters should be "
618 "0.0005."),
619 keytype=str,
620 itemtype=float,
621 default={},
622 )
623 expGrayErrRecoverCut = pexConfig.Field(
624 doc=("Maximum exposure gray error to be able to recover bad ccds via interpolation. "
625 "Visits with more gray variance will only get CCD zeropoints if there are "
626 "sufficient star observations (minStarPerCcd) on that CCD."),
627 dtype=float,
628 default=0.05,
629 )
630 aperCorrFitNBins = pexConfig.Field(
631 doc=("Number of aperture bins used in aperture correction fit. When set to 0"
632 "no fit will be performed, and the config.aperCorrInputSlopes will be "
633 "used if available."),
634 dtype=int,
635 default=10,
636 )
637 aperCorrInputSlopeDict = pexConfig.DictField(
638 doc=("Per-band specification of aperture correction input slope parameters. These "
639 "are used on the first fit iteration, and aperture correction parameters will "
640 "be updated from the data if config.aperCorrFitNBins > 0. It is recommended "
641 "to set this when there is insufficient data to fit the parameters (e.g. "
642 "tract mode)."),
643 keytype=str,
644 itemtype=float,
645 default={},
646 )
647 sedboundaryterms = pexConfig.ConfigField(
648 doc="Mapping from bands to SED boundary term names used is sedterms.",
649 dtype=SedboundarytermDict,
650 )
651 sedterms = pexConfig.ConfigField(
652 doc="Mapping from terms to bands for fgcm linear SED approximations.",
653 dtype=SedtermDict,
654 )
655 sigFgcmMaxErr = pexConfig.Field(
656 doc="Maximum mag error for fitting sigma_FGCM",
657 dtype=float,
658 default=0.01,
659 )
660 sigFgcmMaxEGrayDict = pexConfig.DictField(
661 doc=("Per-band specification for maximum (absolute) achromatic residual (gray value) "
662 "for observations in sigma_fgcm (raw repeatability). Broad-band filters "
663 "should be 0.05."),
664 keytype=str,
665 itemtype=float,
666 default={},
667 )
668 ccdGrayMaxStarErr = pexConfig.Field(
669 doc=("Maximum error on a star observation to use in ccd gray (achromatic residual) "
670 "computation"),
671 dtype=float,
672 default=0.10,
673 )
674 approxThroughputDict = pexConfig.DictField(
675 doc=("Per-band specification of the approximate overall throughput at the start of "
676 "calibration observations. Must have one entry per band. Typically should "
677 "be 1.0."),
678 keytype=str,
679 itemtype=float,
680 default={},
681 )
682 sigmaCalRange = pexConfig.ListField(
683 doc="Allowed range for systematic error floor estimation",
684 dtype=float,
685 default=(0.001, 0.003),
686 )
687 sigmaCalFitPercentile = pexConfig.ListField(
688 doc="Magnitude percentile range to fit systematic error floor",
689 dtype=float,
690 default=(0.05, 0.15),
691 )
692 sigmaCalPlotPercentile = pexConfig.ListField(
693 doc="Magnitude percentile range to plot systematic error floor",
694 dtype=float,
695 default=(0.05, 0.95),
696 )
697 sigma0Phot = pexConfig.Field(
698 doc="Systematic error floor for all zeropoints",
699 dtype=float,
700 default=0.003,
701 )
702 mapLongitudeRef = pexConfig.Field(
703 doc="Reference longitude for plotting maps",
704 dtype=float,
705 default=0.0,
706 )
707 mapNSide = pexConfig.Field(
708 doc="Healpix nside for plotting maps",
709 dtype=int,
710 default=256,
711 )
712 outfileBase = pexConfig.Field(
713 doc="Filename start for plot output files",
714 dtype=str,
715 default=None,
716 )
717 starColorCuts = pexConfig.ListField(
718 doc=("Encoded star-color cuts (using calibration star colors). "
719 "This is a list with each entry a string of the format "
720 "``band1,band2,low,high`` such that only stars of color "
721 "low < band1 - band2 < high will be used for calibration."),
722 dtype=str,
723 default=("NO_DATA",),
724 )
725 refStarColorCuts = pexConfig.ListField(
726 doc=("Encoded star color cuts specifically to apply to reference stars. "
727 "This is a list with each entry a string of the format "
728 "``band1,band2,low,high`` such that only stars of color "
729 "low < band1 - band2 < high will be used as reference stars."),
730 dtype=str,
731 default=("NO_DATA",),
732 )
733 colorSplitBands = pexConfig.ListField(
734 doc="Band names to use to split stars by color. Must have 2 entries.",
735 dtype=str,
736 length=2,
737 default=('g', 'i'),
738 )
739 modelMagErrors = pexConfig.Field(
740 doc="Should FGCM model the magnitude errors from sky/fwhm? (False means trust inputs)",
741 dtype=bool,
742 default=True,
743 )
744 useQuadraticPwv = pexConfig.Field(
745 doc="Model PWV with a quadratic term for variation through the night?",
746 dtype=bool,
747 default=False,
748 )
749 instrumentParsPerBand = pexConfig.Field(
750 doc=("Model instrumental parameters per band? "
751 "Otherwise, instrumental parameters (QE changes with time) are "
752 "shared among all bands."),
753 dtype=bool,
754 default=False,
755 )
756 instrumentSlopeMinDeltaT = pexConfig.Field(
757 doc=("Minimum time change (in days) between observations to use in constraining "
758 "instrument slope."),
759 dtype=float,
760 default=20.0,
761 )
762 fitMirrorChromaticity = pexConfig.Field(
763 doc="Fit (intraband) mirror chromatic term?",
764 dtype=bool,
765 default=False,
766 )
767 coatingMjds = pexConfig.ListField(
768 doc="Mirror coating dates in MJD",
769 dtype=float,
770 default=(0.0,),
771 )
772 outputStandardsBeforeFinalCycle = pexConfig.Field(
773 doc="Output standard stars prior to final cycle? Used in debugging.",
774 dtype=bool,
775 default=False,
776 )
777 outputZeropointsBeforeFinalCycle = pexConfig.Field(
778 doc="Output standard stars prior to final cycle? Used in debugging.",
779 dtype=bool,
780 default=False,
781 )
782 useRepeatabilityForExpGrayCutsDict = pexConfig.DictField(
783 doc=("Per-band specification on whether to use star repeatability (instead of exposures) "
784 "for computing photometric cuts. Recommended for tract mode or bands with few visits."),
785 keytype=str,
786 itemtype=bool,
787 default={},
788 )
789 autoPhotometricCutNSig = pexConfig.Field(
790 doc=("Number of sigma for automatic computation of (low) photometric cut. "
791 "Cut is based on exposure gray width (per band), unless "
792 "useRepeatabilityForExpGrayCuts is set, in which case the star "
793 "repeatability is used (also per band)."),
794 dtype=float,
795 default=3.0,
796 )
797 autoHighCutNSig = pexConfig.Field(
798 doc=("Number of sigma for automatic computation of (high) outlier cut. "
799 "Cut is based on exposure gray width (per band), unless "
800 "useRepeatabilityForExpGrayCuts is set, in which case the star "
801 "repeatability is used (also per band)."),
802 dtype=float,
803 default=4.0,
804 )
805 quietMode = pexConfig.Field(
806 doc="Be less verbose with logging.",
807 dtype=bool,
808 default=False,
809 )
810 doPlots = pexConfig.Field(
811 doc="Make fgcm QA plots.",
812 dtype=bool,
813 default=True,
814 )
815 randomSeed = pexConfig.Field(
816 doc="Random seed for fgcm for consistency in tests.",
817 dtype=int,
818 default=None,
819 optional=True,
820 )
821 deltaAperFitMinNgoodObs = pexConfig.Field(
822 doc="Minimum number of good observations to use mean delta-aper values in fits.",
823 dtype=int,
824 default=2,
825 )
826 deltaAperFitPerCcdNx = pexConfig.Field(
827 doc=("Number of x bins per ccd when computing delta-aper background offsets. "
828 "Only used when ``doComputeDeltaAperPerCcd`` is True."),
829 dtype=int,
830 default=10,
831 )
832 deltaAperFitPerCcdNy = pexConfig.Field(
833 doc=("Number of y bins per ccd when computing delta-aper background offsets. "
834 "Only used when ``doComputeDeltaAperPerCcd`` is True."),
835 dtype=int,
836 default=10,
837 )
838 deltaAperFitSpatialNside = pexConfig.Field(
839 doc="Healpix nside to compute spatial delta-aper background offset maps.",
840 dtype=int,
841 default=64,
842 )
843 deltaAperInnerRadiusArcsec = pexConfig.Field(
844 doc=("Inner radius used to compute deltaMagAper (arcseconds). "
845 "Must be positive and less than ``deltaAperOuterRadiusArcsec`` if "
846 "any of ``doComputeDeltaAperPerVisit``, ``doComputeDeltaAperPerStar``, "
847 "``doComputeDeltaAperMap``, ``doComputeDeltaAperPerCcd`` are set."),
848 dtype=float,
849 default=0.0,
850 )
851 deltaAperOuterRadiusArcsec = pexConfig.Field(
852 doc=("Outer radius used to compute deltaMagAper (arcseconds). "
853 "Must be positive and greater than ``deltaAperInnerRadiusArcsec`` if "
854 "any of ``doComputeDeltaAperPerVisit``, ``doComputeDeltaAperPerStar``, "
855 "``doComputeDeltaAperMap``, ``doComputeDeltaAperPerCcd`` are set."),
856 dtype=float,
857 default=0.0,
858 )
859 doComputeDeltaAperPerVisit = pexConfig.Field(
860 doc=("Do the computation of delta-aper background offsets per visit? "
861 "Note: this option can be very slow when there are many visits."),
862 dtype=bool,
863 default=False,
864 )
865 doComputeDeltaAperPerStar = pexConfig.Field(
866 doc="Do the computation of delta-aper mean values per star?",
867 dtype=bool,
868 default=True,
869 )
870 doComputeDeltaAperMap = pexConfig.Field(
871 doc=("Do the computation of delta-aper spatial maps? "
872 "This is only used if ``doComputeDeltaAperPerStar`` is True,"),
873 dtype=bool,
874 default=False,
875 )
876 doComputeDeltaAperPerCcd = pexConfig.Field(
877 doc="Do the computation of per-ccd delta-aper background offsets?",
878 dtype=bool,
879 default=False,
880 )
881
882 def validate(self):
883 super().validate()
884
885 if self.connections.previousCycleNumber != str(self.cycleNumber - 1):
886 msg = "cycleNumber in template must be connections.previousCycleNumber + 1"
887 raise RuntimeError(msg)
888 if self.connections.cycleNumber != str(self.cycleNumber):
889 msg = "cycleNumber in template must be equal to connections.cycleNumber"
890 raise RuntimeError(msg)
891
892 for band in self.fitBands:
893 if band not in self.bands:
894 msg = 'fitBand %s not in bands' % (band)
895 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.fitBands, self, msg)
896 for band in self.requiredBands:
897 if band not in self.bands:
898 msg = 'requiredBand %s not in bands' % (band)
899 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.requiredBands, self, msg)
900 for band in self.colorSplitBands:
901 if band not in self.bands:
902 msg = 'colorSplitBand %s not in bands' % (band)
903 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.colorSplitBands, self, msg)
904 for band in self.bands:
905 if band not in self.superStarSubCcdDict:
906 msg = 'band %s not in superStarSubCcdDict' % (band)
907 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.superStarSubCcdDict,
908 self, msg)
909 if band not in self.ccdGraySubCcdDict:
910 msg = 'band %s not in ccdGraySubCcdDict' % (band)
911 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.ccdGraySubCcdDict,
912 self, msg)
913 if band not in self.expGrayPhotometricCutDict:
914 msg = 'band %s not in expGrayPhotometricCutDict' % (band)
915 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.expGrayPhotometricCutDict,
916 self, msg)
917 if band not in self.expGrayHighCutDict:
918 msg = 'band %s not in expGrayHighCutDict' % (band)
919 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.expGrayHighCutDict,
920 self, msg)
921 if band not in self.expVarGrayPhotometricCutDict:
922 msg = 'band %s not in expVarGrayPhotometricCutDict' % (band)
923 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.expVarGrayPhotometricCutDict,
924 self, msg)
925 if band not in self.sigFgcmMaxEGrayDict:
926 msg = 'band %s not in sigFgcmMaxEGrayDict' % (band)
927 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.sigFgcmMaxEGrayDict,
928 self, msg)
929 if band not in self.approxThroughputDict:
930 msg = 'band %s not in approxThroughputDict' % (band)
931 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.approxThroughputDict,
932 self, msg)
933 if band not in self.useRepeatabilityForExpGrayCutsDict:
934 msg = 'band %s not in useRepeatabilityForExpGrayCutsDict' % (band)
935 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.useRepeatabilityForExpGrayCutsDict,
936 self, msg)
937
938 if self.doComputeDeltaAperPerVisit or self.doComputeDeltaAperMap \
939 or self.doComputeDeltaAperPerCcd:
940 if self.deltaAperInnerRadiusArcsec <= 0.0:
941 msg = 'deltaAperInnerRadiusArcsec must be positive if deltaAper computations are turned on.'
942 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.deltaAperInnerRadiusArcsec,
943 self, msg)
944 if self.deltaAperOuterRadiusArcsec <= 0.0:
945 msg = 'deltaAperOuterRadiusArcsec must be positive if deltaAper computations are turned on.'
946 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.deltaAperOuterRadiusArcsec,
947 self, msg)
948 if self.deltaAperOuterRadiusArcsec <= self.deltaAperInnerRadiusArcsec:
949 msg = ('deltaAperOuterRadiusArcsec must be greater than deltaAperInnerRadiusArcsec if '
950 'deltaAper computations are turned on.')
951 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.deltaAperOuterRadiusArcsec,
952 self, msg)
953
954
955class FgcmFitCycleTask(pipeBase.PipelineTask):
956 """
957 Run Single fit cycle for FGCM global calibration
958 """
959
960 ConfigClass = FgcmFitCycleConfig
961 _DefaultName = "fgcmFitCycle"
962
963 def __init__(self, initInputs=None, **kwargs):
964 super().__init__(**kwargs)
965
966 def runQuantum(self, butlerQC, inputRefs, outputRefs):
967 camera = butlerQC.get(inputRefs.camera)
968
969 handleDict = {}
970
971 handleDict['fgcmLookUpTable'] = butlerQC.get(inputRefs.fgcmLookUpTable)
972 handleDict['fgcmVisitCatalog'] = butlerQC.get(inputRefs.fgcmVisitCatalog)
973
974 if self.config.useParquetCatalogFormat:
975 handleDict['fgcmStarObservations'] = butlerQC.get(inputRefs.fgcmStarObservationsParquet)
976 handleDict['fgcmStarIds'] = butlerQC.get(inputRefs.fgcmStarIdsParquet)
977 if self.config.doReferenceCalibration:
978 handleDict['fgcmReferenceStars'] = butlerQC.get(inputRefs.fgcmReferenceStarsParquet)
979 else:
980 handleDict['fgcmStarObservations'] = butlerQC.get(inputRefs.fgcmStarObservations)
981 handleDict['fgcmStarIds'] = butlerQC.get(inputRefs.fgcmStarIds)
982 handleDict['fgcmStarIndices'] = butlerQC.get(inputRefs.fgcmStarIndices)
983 if self.config.doReferenceCalibration:
984 handleDict['fgcmReferenceStars'] = butlerQC.get(inputRefs.fgcmReferenceStars)
985 if self.config.cycleNumber > 0:
986 handleDict['fgcmFlaggedStars'] = butlerQC.get(inputRefs.fgcmFlaggedStarsInput)
987 handleDict['fgcmFitParameters'] = butlerQC.get(inputRefs.fgcmFitParametersInput)
988
989 fgcmDatasetDict = None
990 if self.config.doMultipleCycles:
991 # Run multiple cycles at once.
992 config = copy.copy(self.config)
993 config.update(cycleNumber=0)
994 for cycle in range(self.config.multipleCyclesFinalCycleNumber + 1):
995 if cycle == self.config.multipleCyclesFinalCycleNumber:
996 config.update(isFinalCycle=True)
997
998 if cycle > 0:
999 handleDict['fgcmFlaggedStars'] = fgcmDatasetDict['fgcmFlaggedStars']
1000 handleDict['fgcmFitParameters'] = fgcmDatasetDict['fgcmFitParameters']
1001
1002 fgcmDatasetDict, config = self._fgcmFitCycle(camera, handleDict, config=config)
1003 butlerQC.put(fgcmDatasetDict['fgcmFitParameters'],
1004 getattr(outputRefs, f'fgcmFitParameters{cycle}'))
1005 butlerQC.put(fgcmDatasetDict['fgcmFlaggedStars'],
1006 getattr(outputRefs, f'fgcmFlaggedStars{cycle}'))
1007 if self.outputZeropoints:
1008 butlerQC.put(fgcmDatasetDict['fgcmZeropoints'],
1009 getattr(outputRefs, f'fgcmZeropoints{cycle}'))
1010 butlerQC.put(fgcmDatasetDict['fgcmAtmosphereParameters'],
1011 getattr(outputRefs, f'fgcmAtmosphereParameters{cycle}'))
1012 if self.outputStandards:
1013 butlerQC.put(fgcmDatasetDict['fgcmStandardStars'],
1014 getattr(outputRefs, f'fgcmStandardStars{cycle}'))
1015 else:
1016 # Run a single cycle
1017 fgcmDatasetDict, _ = self._fgcmFitCycle(camera, handleDict)
1018
1019 butlerQC.put(fgcmDatasetDict['fgcmFitParameters'], outputRefs.fgcmFitParameters)
1020 butlerQC.put(fgcmDatasetDict['fgcmFlaggedStars'], outputRefs.fgcmFlaggedStars)
1021 if self.outputZeropoints:
1022 butlerQC.put(fgcmDatasetDict['fgcmZeropoints'], outputRefs.fgcmZeropoints)
1023 butlerQC.put(fgcmDatasetDict['fgcmAtmosphereParameters'], outputRefs.fgcmAtmosphereParameters)
1024 if self.outputStandards:
1025 butlerQC.put(fgcmDatasetDict['fgcmStandardStars'], outputRefs.fgcmStandardStars)
1026
1027 def _fgcmFitCycle(self, camera, handleDict, config=None):
1028 """
1029 Run the fit cycle
1030
1031 Parameters
1032 ----------
1034 handleDict : `dict`
1035 All handles are `lsst.daf.butler.DeferredDatasetHandle`
1036 handle dictionary with keys:
1037
1038 ``"fgcmLookUpTable"``
1039 handle for the FGCM look-up table.
1040 ``"fgcmVisitCatalog"``
1041 handle for visit summary catalog.
1042 ``"fgcmStarObservations"``
1043 handle for star observation catalog.
1044 ``"fgcmStarIds"``
1045 handle for star id catalog.
1046 ``"fgcmStarIndices"``
1047 handle for star index catalog.
1048 ``"fgcmReferenceStars"``
1049 handle for matched reference star catalog.
1050 ``"fgcmFlaggedStars"``
1051 handle for flagged star catalog.
1052 ``"fgcmFitParameters"``
1053 handle for fit parameter catalog.
1054 config : `lsst.pex.config.Config`, optional
1055 Configuration to use to override self.config.
1056
1057 Returns
1058 -------
1059 fgcmDatasetDict : `dict`
1060 Dictionary of datasets to persist.
1061 """
1062 if config is not None:
1063 _config = config
1064 else:
1065 _config = self.config
1066
1067 # Set defaults on whether to output standards and zeropoints
1068 self.maxIter = _config.maxIterBeforeFinalCycle
1069 self.outputStandards = _config.outputStandardsBeforeFinalCycle
1070 self.outputZeropoints = _config.outputZeropointsBeforeFinalCycle
1071 self.resetFitParameters = True
1072
1073 if _config.isFinalCycle:
1074 # This is the final fit cycle, so we do not want to reset fit
1075 # parameters, we want to run a final "clean-up" with 0 fit iterations,
1076 # and we always want to output standards and zeropoints
1077 self.maxIter = 0
1078 self.outputStandards = True
1079 self.outputZeropoints = True
1080 self.resetFitParameters = False
1081
1082 lutCat = handleDict['fgcmLookUpTable'].get()
1083 fgcmLut, lutIndexVals, lutStd = translateFgcmLut(lutCat,
1084 dict(_config.physicalFilterMap))
1085 del lutCat
1086
1087 configDict = makeConfigDict(_config, self.log, camera,
1088 self.maxIter, self.resetFitParameters,
1089 self.outputZeropoints,
1090 lutIndexVals[0]['FILTERNAMES'])
1091
1092 # next we need the exposure/visit information
1093 visitCat = handleDict['fgcmVisitCatalog'].get()
1094 fgcmExpInfo = translateVisitCatalog(visitCat)
1095 del visitCat
1096
1097 focalPlaneProjector = FocalPlaneProjector(camera,
1098 self.config.defaultCameraOrientation)
1099
1100 noFitsDict = {'lutIndex': lutIndexVals,
1101 'lutStd': lutStd,
1102 'expInfo': fgcmExpInfo,
1103 'focalPlaneProjector': focalPlaneProjector}
1104
1105 # set up the fitter object
1106 fgcmFitCycle = fgcm.FgcmFitCycle(configDict, useFits=False,
1107 noFitsDict=noFitsDict, noOutput=True)
1108
1109 # create the parameter object
1110 if (fgcmFitCycle.initialCycle):
1111 # cycle = 0, initial cycle
1112 fgcmPars = fgcm.FgcmParameters.newParsWithArrays(fgcmFitCycle.fgcmConfig,
1113 fgcmLut,
1114 fgcmExpInfo)
1115 else:
1116 if isinstance(handleDict['fgcmFitParameters'], afwTable.BaseCatalog):
1117 parCat = handleDict['fgcmFitParameters']
1118 else:
1119 parCat = handleDict['fgcmFitParameters'].get()
1120 inParInfo, inParams, inSuperStar = self._loadParameters(parCat)
1121 del parCat
1122 fgcmPars = fgcm.FgcmParameters.loadParsWithArrays(fgcmFitCycle.fgcmConfig,
1123 fgcmExpInfo,
1124 inParInfo,
1125 inParams,
1126 inSuperStar)
1127
1128 # set up the stars...
1129 fgcmStars = fgcm.FgcmStars(fgcmFitCycle.fgcmConfig)
1130
1131 starObs = handleDict['fgcmStarObservations'].get()
1132 starIds = handleDict['fgcmStarIds'].get()
1133 if not self.config.useParquetCatalogFormat:
1134 starIndices = handleDict['fgcmStarIndices'].get()
1135 else:
1136 starIndices = None
1137
1138 # grab the flagged stars if available
1139 if 'fgcmFlaggedStars' in handleDict:
1140 if isinstance(handleDict['fgcmFlaggedStars'], afwTable.BaseCatalog):
1141 flaggedStars = handleDict['fgcmFlaggedStars']
1142 else:
1143 flaggedStars = handleDict['fgcmFlaggedStars'].get()
1144 flagId = flaggedStars['objId'][:]
1145 flagFlag = flaggedStars['objFlag'][:]
1146
1147 del flaggedStars
1148 elif self.config.useParquetCatalogFormat:
1149 # If we are using the parquet catalog format, then that means that
1150 # reserved stars have already been flagged. We extract the flags here
1151 # to input to fgcm, which will then be persisted (with additional
1152 # quality flags) as the fgcmFlaggedStars datatype in subsequent
1153 # fit cycles.
1154 (flagged,) = (starIds['obj_flag'] > 0).nonzero()
1155 flagId = starIds['fgcm_id'][flagged]
1156 flagFlag = starIds['obj_flag'][flagged]
1157 else:
1158 flagId = None
1159 flagFlag = None
1160
1161 if _config.doReferenceCalibration:
1162 refStars = handleDict['fgcmReferenceStars'].get()
1163
1164 refMag, refMagErr = extractReferenceMags(refStars,
1165 _config.bands,
1166 _config.physicalFilterMap)
1167
1168 refId = refStars['fgcm_id'][:]
1169 else:
1170 refStars = None
1171 refId = None
1172 refMag = None
1173 refMagErr = None
1174
1175 # match star observations to visits
1176 # Only those star observations that match visits from fgcmExpInfo['VISIT'] will
1177 # actually be transferred into fgcm using the indexing below.
1178 if self.config.useParquetCatalogFormat:
1179 visitIndex = np.searchsorted(fgcmExpInfo['VISIT'], starObs['visit'])
1180 else:
1181 visitIndex = np.searchsorted(fgcmExpInfo['VISIT'], starObs['visit'][starIndices['obsIndex']])
1182
1183 # The fgcmStars.loadStars method will copy all the star information into
1184 # special shared memory objects that will not blow up the memory usage when
1185 # used with python multiprocessing. Once all the numbers are copied,
1186 # it is necessary to release all references to the objects that previously
1187 # stored the data to ensure that the garbage collector can clear the memory,
1188 # and ensure that this memory is not copied when multiprocessing kicks in.
1189
1190 if self.config.useParquetCatalogFormat:
1191 # Note that the ra/dec coordinates for the parquet format are in
1192 # degrees, which is what fgcm expects.
1193 fgcmStars.loadStars(fgcmPars,
1194 starObs['visit'][:],
1195 starObs['detector'][:],
1196 starObs['ra'][:],
1197 starObs['dec'][:],
1198 starObs['inst_mag'][:],
1199 starObs['inst_mag_err'][:],
1200 fgcmExpInfo['FILTERNAME'][visitIndex],
1201 starIds['fgcm_id'][:],
1202 starIds['ra'][:],
1203 starIds['dec'][:],
1204 starIds['obs_arr_index'][:],
1205 starIds['n_obs'][:],
1206 obsX=starObs['x'][:],
1207 obsY=starObs['y'][:],
1208 obsDeltaMagBkg=starObs['delta_mag_bkg'][:],
1209 obsDeltaAper=starObs['delta_mag_aper'][:],
1210 refID=refId,
1211 refMag=refMag,
1212 refMagErr=refMagErr,
1213 flagID=flagId,
1214 flagFlag=flagFlag,
1215 computeNobs=True)
1216 else:
1217 # We determine the conversion from the native units (typically radians) to
1218 # degrees for the first star. This allows us to treat coord_ra/coord_dec as
1219 # numpy arrays rather than Angles, which would we approximately 600x slower.
1220 conv = starObs[0]['ra'].asDegrees() / float(starObs[0]['ra'])
1221
1222 fgcmStars.loadStars(fgcmPars,
1223 starObs['visit'][starIndices['obsIndex']],
1224 starObs['ccd'][starIndices['obsIndex']],
1225 starObs['ra'][starIndices['obsIndex']] * conv,
1226 starObs['dec'][starIndices['obsIndex']] * conv,
1227 starObs['instMag'][starIndices['obsIndex']],
1228 starObs['instMagErr'][starIndices['obsIndex']],
1229 fgcmExpInfo['FILTERNAME'][visitIndex],
1230 starIds['fgcm_id'][:],
1231 starIds['ra'][:],
1232 starIds['dec'][:],
1233 starIds['obsArrIndex'][:],
1234 starIds['nObs'][:],
1235 obsX=starObs['x'][starIndices['obsIndex']],
1236 obsY=starObs['y'][starIndices['obsIndex']],
1237 obsDeltaMagBkg=starObs['deltaMagBkg'][starIndices['obsIndex']],
1238 obsDeltaAper=starObs['deltaMagAper'][starIndices['obsIndex']],
1239 psfCandidate=starObs['psf_candidate'][starIndices['obsIndex']],
1240 refID=refId,
1241 refMag=refMag,
1242 refMagErr=refMagErr,
1243 flagID=flagId,
1244 flagFlag=flagFlag,
1245 computeNobs=True)
1246
1247 # Release all references to temporary objects holding star data (see above)
1248 del starObs
1249 del starIds
1250 del starIndices
1251 del flagId
1252 del flagFlag
1253 del refStars
1254 del refId
1255 del refMag
1256 del refMagErr
1257
1258 # and set the bits in the cycle object
1259 fgcmFitCycle.setLUT(fgcmLut)
1260 fgcmFitCycle.setStars(fgcmStars, fgcmPars)
1261 fgcmFitCycle.setPars(fgcmPars)
1262
1263 # finish the setup
1264 fgcmFitCycle.finishSetup()
1265
1266 # and run
1267 fgcmFitCycle.run()
1268
1269
1272
1273 fgcmDatasetDict = self._makeFgcmOutputDatasets(fgcmFitCycle)
1274
1275 # Output the config for the next cycle
1276 # We need to make a copy since the input one has been frozen
1277
1278 updatedPhotometricCutDict = {b: float(fgcmFitCycle.updatedPhotometricCut[i]) for
1279 i, b in enumerate(_config.bands)}
1280 updatedHighCutDict = {band: float(fgcmFitCycle.updatedHighCut[i]) for
1281 i, band in enumerate(_config.bands)}
1282
1283 outConfig = copy.copy(_config)
1284 outConfig.update(cycleNumber=(_config.cycleNumber + 1),
1285 precomputeSuperStarInitialCycle=False,
1286 freezeStdAtmosphere=False,
1287 expGrayPhotometricCutDict=updatedPhotometricCutDict,
1288 expGrayHighCutDict=updatedHighCutDict)
1289
1290 outConfig.connections.update(previousCycleNumber=str(_config.cycleNumber),
1291 cycleNumber=str(_config.cycleNumber + 1))
1292
1293 configFileName = '%s_cycle%02d_config.py' % (outConfig.outfileBase,
1294 outConfig.cycleNumber)
1295 outConfig.save(configFileName)
1296
1297 if _config.isFinalCycle == 1:
1298 # We are done, ready to output products
1299 self.log.info("Everything is in place to run fgcmOutputProducts.py")
1300 else:
1301 self.log.info("Saved config for next cycle to %s" % (configFileName))
1302 self.log.info("Be sure to look at:")
1303 self.log.info(" config.expGrayPhotometricCut")
1304 self.log.info(" config.expGrayHighCut")
1305 self.log.info("If you are satisfied with the fit, please set:")
1306 self.log.info(" config.isFinalCycle = True")
1307
1308 fgcmFitCycle.freeSharedMemory()
1309
1310 return fgcmDatasetDict, outConfig
1311
1312 def _loadParameters(self, parCat):
1313 """
1314 Load FGCM parameters from a previous fit cycle
1315
1316 Parameters
1317 ----------
1319 Parameter catalog in afw table form.
1320
1321 Returns
1322 -------
1323 inParInfo: `numpy.ndarray`
1324 Numpy array parameter information formatted for input to fgcm
1325 inParameters: `numpy.ndarray`
1326 Numpy array parameter values formatted for input to fgcm
1327 inSuperStar: `numpy.array`
1328 Superstar flat formatted for input to fgcm
1329 """
1330 parLutFilterNames = np.array(parCat[0]['lutFilterNames'].split(','))
1331 parFitBands = np.array(parCat[0]['fitBands'].split(','))
1332
1333 inParInfo = np.zeros(1, dtype=[('NCCD', 'i4'),
1334 ('LUTFILTERNAMES', parLutFilterNames.dtype.str,
1335 (parLutFilterNames.size, )),
1336 ('FITBANDS', parFitBands.dtype.str, (parFitBands.size, )),
1337 ('LNTAUUNIT', 'f8'),
1338 ('LNTAUSLOPEUNIT', 'f8'),
1339 ('ALPHAUNIT', 'f8'),
1340 ('LNPWVUNIT', 'f8'),
1341 ('LNPWVSLOPEUNIT', 'f8'),
1342 ('LNPWVQUADRATICUNIT', 'f8'),
1343 ('LNPWVGLOBALUNIT', 'f8'),
1344 ('O3UNIT', 'f8'),
1345 ('QESYSUNIT', 'f8'),
1346 ('FILTEROFFSETUNIT', 'f8'),
1347 ('HASEXTERNALPWV', 'i2'),
1348 ('HASEXTERNALTAU', 'i2')])
1349 inParInfo['NCCD'] = parCat['nCcd']
1350 inParInfo['LUTFILTERNAMES'][:] = parLutFilterNames
1351 inParInfo['FITBANDS'][:] = parFitBands
1352 inParInfo['HASEXTERNALPWV'] = parCat['hasExternalPwv']
1353 inParInfo['HASEXTERNALTAU'] = parCat['hasExternalTau']
1354
1355 inParams = np.zeros(1, dtype=[('PARALPHA', 'f8', (parCat['parAlpha'].size, )),
1356 ('PARO3', 'f8', (parCat['parO3'].size, )),
1357 ('PARLNTAUINTERCEPT', 'f8',
1358 (parCat['parLnTauIntercept'].size, )),
1359 ('PARLNTAUSLOPE', 'f8',
1360 (parCat['parLnTauSlope'].size, )),
1361 ('PARLNPWVINTERCEPT', 'f8',
1362 (parCat['parLnPwvIntercept'].size, )),
1363 ('PARLNPWVSLOPE', 'f8',
1364 (parCat['parLnPwvSlope'].size, )),
1365 ('PARLNPWVQUADRATIC', 'f8',
1366 (parCat['parLnPwvQuadratic'].size, )),
1367 ('PARQESYSINTERCEPT', 'f8',
1368 (parCat['parQeSysIntercept'].size, )),
1369 ('COMPQESYSSLOPE', 'f8',
1370 (parCat['compQeSysSlope'].size, )),
1371 ('PARFILTEROFFSET', 'f8',
1372 (parCat['parFilterOffset'].size, )),
1373 ('PARFILTEROFFSETFITFLAG', 'i2',
1374 (parCat['parFilterOffsetFitFlag'].size, )),
1375 ('PARRETRIEVEDLNPWVSCALE', 'f8'),
1376 ('PARRETRIEVEDLNPWVOFFSET', 'f8'),
1377 ('PARRETRIEVEDLNPWVNIGHTLYOFFSET', 'f8',
1378 (parCat['parRetrievedLnPwvNightlyOffset'].size, )),
1379 ('COMPABSTHROUGHPUT', 'f8',
1380 (parCat['compAbsThroughput'].size, )),
1381 ('COMPREFOFFSET', 'f8',
1382 (parCat['compRefOffset'].size, )),
1383 ('COMPREFSIGMA', 'f8',
1384 (parCat['compRefSigma'].size, )),
1385 ('COMPMIRRORCHROMATICITY', 'f8',
1386 (parCat['compMirrorChromaticity'].size, )),
1387 ('MIRRORCHROMATICITYPIVOT', 'f8',
1388 (parCat['mirrorChromaticityPivot'].size, )),
1389 ('COMPMEDIANSEDSLOPE', 'f8',
1390 (parCat['compMedianSedSlope'].size, )),
1391 ('COMPAPERCORRPIVOT', 'f8',
1392 (parCat['compAperCorrPivot'].size, )),
1393 ('COMPAPERCORRSLOPE', 'f8',
1394 (parCat['compAperCorrSlope'].size, )),
1395 ('COMPAPERCORRSLOPEERR', 'f8',
1396 (parCat['compAperCorrSlopeErr'].size, )),
1397 ('COMPAPERCORRRANGE', 'f8',
1398 (parCat['compAperCorrRange'].size, )),
1399 ('COMPMODELERREXPTIMEPIVOT', 'f8',
1400 (parCat['compModelErrExptimePivot'].size, )),
1401 ('COMPMODELERRFWHMPIVOT', 'f8',
1402 (parCat['compModelErrFwhmPivot'].size, )),
1403 ('COMPMODELERRSKYPIVOT', 'f8',
1404 (parCat['compModelErrSkyPivot'].size, )),
1405 ('COMPMODELERRPARS', 'f8',
1406 (parCat['compModelErrPars'].size, )),
1407 ('COMPEXPGRAY', 'f8',
1408 (parCat['compExpGray'].size, )),
1409 ('COMPVARGRAY', 'f8',
1410 (parCat['compVarGray'].size, )),
1411 ('COMPEXPDELTAMAGBKG', 'f8',
1412 (parCat['compExpDeltaMagBkg'].size, )),
1413 ('COMPNGOODSTARPEREXP', 'i4',
1414 (parCat['compNGoodStarPerExp'].size, )),
1415 ('COMPEXPREFOFFSET', 'f8',
1416 (parCat['compExpRefOffset'].size, )),
1417 ('COMPSIGFGCM', 'f8',
1418 (parCat['compSigFgcm'].size, )),
1419 ('COMPSIGMACAL', 'f8',
1420 (parCat['compSigmaCal'].size, )),
1421 ('COMPRETRIEVEDLNPWV', 'f8',
1422 (parCat['compRetrievedLnPwv'].size, )),
1423 ('COMPRETRIEVEDLNPWVRAW', 'f8',
1424 (parCat['compRetrievedLnPwvRaw'].size, )),
1425 ('COMPRETRIEVEDLNPWVFLAG', 'i2',
1426 (parCat['compRetrievedLnPwvFlag'].size, )),
1427 ('COMPRETRIEVEDTAUNIGHT', 'f8',
1428 (parCat['compRetrievedTauNight'].size, )),
1429 ('COMPEPSILON', 'f8',
1430 (parCat['compEpsilon'].size, )),
1431 ('COMPMEDDELTAAPER', 'f8',
1432 (parCat['compMedDeltaAper'].size, )),
1433 ('COMPGLOBALEPSILON', 'f4',
1434 (parCat['compGlobalEpsilon'].size, )),
1435 ('COMPEPSILONMAP', 'f4',
1436 (parCat['compEpsilonMap'].size, )),
1437 ('COMPEPSILONNSTARMAP', 'i4',
1438 (parCat['compEpsilonNStarMap'].size, )),
1439 ('COMPEPSILONCCDMAP', 'f4',
1440 (parCat['compEpsilonCcdMap'].size, )),
1441 ('COMPEPSILONCCDNSTARMAP', 'i4',
1442 (parCat['compEpsilonCcdNStarMap'].size, ))])
1443
1444 inParams['PARALPHA'][:] = parCat['parAlpha'][0, :]
1445 inParams['PARO3'][:] = parCat['parO3'][0, :]
1446 inParams['PARLNTAUINTERCEPT'][:] = parCat['parLnTauIntercept'][0, :]
1447 inParams['PARLNTAUSLOPE'][:] = parCat['parLnTauSlope'][0, :]
1448 inParams['PARLNPWVINTERCEPT'][:] = parCat['parLnPwvIntercept'][0, :]
1449 inParams['PARLNPWVSLOPE'][:] = parCat['parLnPwvSlope'][0, :]
1450 inParams['PARLNPWVQUADRATIC'][:] = parCat['parLnPwvQuadratic'][0, :]
1451 inParams['PARQESYSINTERCEPT'][:] = parCat['parQeSysIntercept'][0, :]
1452 inParams['COMPQESYSSLOPE'][:] = parCat['compQeSysSlope'][0, :]
1453 inParams['PARFILTEROFFSET'][:] = parCat['parFilterOffset'][0, :]
1454 inParams['PARFILTEROFFSETFITFLAG'][:] = parCat['parFilterOffsetFitFlag'][0, :]
1455 inParams['PARRETRIEVEDLNPWVSCALE'] = parCat['parRetrievedLnPwvScale']
1456 inParams['PARRETRIEVEDLNPWVOFFSET'] = parCat['parRetrievedLnPwvOffset']
1457 inParams['PARRETRIEVEDLNPWVNIGHTLYOFFSET'][:] = parCat['parRetrievedLnPwvNightlyOffset'][0, :]
1458 inParams['COMPABSTHROUGHPUT'][:] = parCat['compAbsThroughput'][0, :]
1459 inParams['COMPREFOFFSET'][:] = parCat['compRefOffset'][0, :]
1460 inParams['COMPREFSIGMA'][:] = parCat['compRefSigma'][0, :]
1461 inParams['COMPMIRRORCHROMATICITY'][:] = parCat['compMirrorChromaticity'][0, :]
1462 inParams['MIRRORCHROMATICITYPIVOT'][:] = parCat['mirrorChromaticityPivot'][0, :]
1463 inParams['COMPMEDIANSEDSLOPE'][:] = parCat['compMedianSedSlope'][0, :]
1464 inParams['COMPAPERCORRPIVOT'][:] = parCat['compAperCorrPivot'][0, :]
1465 inParams['COMPAPERCORRSLOPE'][:] = parCat['compAperCorrSlope'][0, :]
1466 inParams['COMPAPERCORRSLOPEERR'][:] = parCat['compAperCorrSlopeErr'][0, :]
1467 inParams['COMPAPERCORRRANGE'][:] = parCat['compAperCorrRange'][0, :]
1468 inParams['COMPMODELERREXPTIMEPIVOT'][:] = parCat['compModelErrExptimePivot'][0, :]
1469 inParams['COMPMODELERRFWHMPIVOT'][:] = parCat['compModelErrFwhmPivot'][0, :]
1470 inParams['COMPMODELERRSKYPIVOT'][:] = parCat['compModelErrSkyPivot'][0, :]
1471 inParams['COMPMODELERRPARS'][:] = parCat['compModelErrPars'][0, :]
1472 inParams['COMPEXPGRAY'][:] = parCat['compExpGray'][0, :]
1473 inParams['COMPVARGRAY'][:] = parCat['compVarGray'][0, :]
1474 inParams['COMPEXPDELTAMAGBKG'][:] = parCat['compExpDeltaMagBkg'][0, :]
1475 inParams['COMPNGOODSTARPEREXP'][:] = parCat['compNGoodStarPerExp'][0, :]
1476 inParams['COMPEXPREFOFFSET'][:] = parCat['compExpRefOffset'][0, :]
1477 inParams['COMPSIGFGCM'][:] = parCat['compSigFgcm'][0, :]
1478 inParams['COMPSIGMACAL'][:] = parCat['compSigmaCal'][0, :]
1479 inParams['COMPRETRIEVEDLNPWV'][:] = parCat['compRetrievedLnPwv'][0, :]
1480 inParams['COMPRETRIEVEDLNPWVRAW'][:] = parCat['compRetrievedLnPwvRaw'][0, :]
1481 inParams['COMPRETRIEVEDLNPWVFLAG'][:] = parCat['compRetrievedLnPwvFlag'][0, :]
1482 inParams['COMPRETRIEVEDTAUNIGHT'][:] = parCat['compRetrievedTauNight'][0, :]
1483 inParams['COMPEPSILON'][:] = parCat['compEpsilon'][0, :]
1484 inParams['COMPMEDDELTAAPER'][:] = parCat['compMedDeltaAper'][0, :]
1485 inParams['COMPGLOBALEPSILON'][:] = parCat['compGlobalEpsilon'][0, :]
1486 inParams['COMPEPSILONMAP'][:] = parCat['compEpsilonMap'][0, :]
1487 inParams['COMPEPSILONNSTARMAP'][:] = parCat['compEpsilonNStarMap'][0, :]
1488 inParams['COMPEPSILONCCDMAP'][:] = parCat['compEpsilonCcdMap'][0, :]
1489 inParams['COMPEPSILONCCDNSTARMAP'][:] = parCat['compEpsilonCcdNStarMap'][0, :]
1490
1491 inSuperStar = np.zeros(parCat['superstarSize'][0, :], dtype='f8')
1492 inSuperStar[:, :, :, :] = parCat['superstar'][0, :].reshape(inSuperStar.shape)
1493
1494 return (inParInfo, inParams, inSuperStar)
1495
1496 def _makeFgcmOutputDatasets(self, fgcmFitCycle):
1497 """
1498 Persist FGCM datasets through the butler.
1499
1500 Parameters
1501 ----------
1502 fgcmFitCycle: `lsst.fgcm.FgcmFitCycle`
1503 Fgcm Fit cycle object
1504 """
1505 fgcmDatasetDict = {}
1506
1507 # Save the parameters
1508 parInfo, pars = fgcmFitCycle.fgcmPars.parsToArrays()
1509
1510 parSchema = afwTable.Schema()
1511
1512 comma = ','
1513 lutFilterNameString = comma.join([n.decode('utf-8')
1514 for n in parInfo['LUTFILTERNAMES'][0]])
1515 fitBandString = comma.join([n.decode('utf-8')
1516 for n in parInfo['FITBANDS'][0]])
1517
1518 parSchema = self._makeParSchema(parInfo, pars, fgcmFitCycle.fgcmPars.parSuperStarFlat,
1519 lutFilterNameString, fitBandString)
1520 parCat = self._makeParCatalog(parSchema, parInfo, pars,
1521 fgcmFitCycle.fgcmPars.parSuperStarFlat,
1522 lutFilterNameString, fitBandString)
1523
1524 fgcmDatasetDict['fgcmFitParameters'] = parCat
1525
1526 # Save the indices of the flagged stars
1527 # (stars that have been (a) reserved from the fit for testing and
1528 # (b) bad stars that have failed quality checks.)
1529 flagStarSchema = self._makeFlagStarSchema()
1530 flagStarStruct = fgcmFitCycle.fgcmStars.getFlagStarIndices()
1531 flagStarCat = self._makeFlagStarCat(flagStarSchema, flagStarStruct)
1532
1533 fgcmDatasetDict['fgcmFlaggedStars'] = flagStarCat
1534
1535 # Save the zeropoint information and atmospheres only if desired
1536 if self.outputZeropoints:
1537 superStarChebSize = fgcmFitCycle.fgcmZpts.zpStruct['FGCM_FZPT_SSTAR_CHEB'].shape[1]
1538 zptChebSize = fgcmFitCycle.fgcmZpts.zpStruct['FGCM_FZPT_CHEB'].shape[1]
1539
1540 zptSchema = makeZptSchema(superStarChebSize, zptChebSize)
1541 zptCat = makeZptCat(zptSchema, fgcmFitCycle.fgcmZpts.zpStruct)
1542
1543 fgcmDatasetDict['fgcmZeropoints'] = zptCat
1544
1545 # Save atmosphere values
1546 # These are generated by the same code that generates zeropoints
1547 atmSchema = makeAtmSchema()
1548 atmCat = makeAtmCat(atmSchema, fgcmFitCycle.fgcmZpts.atmStruct)
1549
1550 fgcmDatasetDict['fgcmAtmosphereParameters'] = atmCat
1551
1552 # Save the standard stars (if configured)
1553 if self.outputStandards:
1554 stdStruct, goodBands = fgcmFitCycle.fgcmStars.retrieveStdStarCatalog(fgcmFitCycle.fgcmPars)
1555 stdSchema = makeStdSchema(len(goodBands))
1556 stdCat = makeStdCat(stdSchema, stdStruct, goodBands)
1557
1558 fgcmDatasetDict['fgcmStandardStars'] = stdCat
1559
1560 return fgcmDatasetDict
1561
1562 def _makeParSchema(self, parInfo, pars, parSuperStarFlat,
1563 lutFilterNameString, fitBandString):
1564 """
1565 Make the parameter persistence schema
1566
1567 Parameters
1568 ----------
1569 parInfo: `numpy.ndarray`
1570 Parameter information returned by fgcm
1571 pars: `numpy.ndarray`
1572 Parameter values returned by fgcm
1573 parSuperStarFlat: `numpy.array`
1574 Superstar flat values returned by fgcm
1575 lutFilterNameString: `str`
1576 Combined string of all the lutFilterNames
1577 fitBandString: `str`
1578 Combined string of all the fitBands
1579
1580 Returns
1581 -------
1582 parSchema: `afwTable.schema`
1583 """
1584
1585 parSchema = afwTable.Schema()
1586
1587 # parameter info section
1588 parSchema.addField('nCcd', type=np.int32, doc='Number of CCDs')
1589 parSchema.addField('lutFilterNames', type=str, doc='LUT Filter names in parameter file',
1590 size=len(lutFilterNameString))
1591 parSchema.addField('fitBands', type=str, doc='Bands that were fit',
1592 size=len(fitBandString))
1593 parSchema.addField('lnTauUnit', type=np.float64, doc='Step units for ln(AOD)')
1594 parSchema.addField('lnTauSlopeUnit', type=np.float64,
1595 doc='Step units for ln(AOD) slope')
1596 parSchema.addField('alphaUnit', type=np.float64, doc='Step units for alpha')
1597 parSchema.addField('lnPwvUnit', type=np.float64, doc='Step units for ln(pwv)')
1598 parSchema.addField('lnPwvSlopeUnit', type=np.float64,
1599 doc='Step units for ln(pwv) slope')
1600 parSchema.addField('lnPwvQuadraticUnit', type=np.float64,
1601 doc='Step units for ln(pwv) quadratic term')
1602 parSchema.addField('lnPwvGlobalUnit', type=np.float64,
1603 doc='Step units for global ln(pwv) parameters')
1604 parSchema.addField('o3Unit', type=np.float64, doc='Step units for O3')
1605 parSchema.addField('qeSysUnit', type=np.float64, doc='Step units for mirror gray')
1606 parSchema.addField('filterOffsetUnit', type=np.float64, doc='Step units for filter offset')
1607 parSchema.addField('hasExternalPwv', type=np.int32, doc='Parameters fit using external pwv')
1608 parSchema.addField('hasExternalTau', type=np.int32, doc='Parameters fit using external tau')
1609
1610 # parameter section
1611 parSchema.addField('parAlpha', type='ArrayD', doc='Alpha parameter vector',
1612 size=pars['PARALPHA'].size)
1613 parSchema.addField('parO3', type='ArrayD', doc='O3 parameter vector',
1614 size=pars['PARO3'].size)
1615 parSchema.addField('parLnTauIntercept', type='ArrayD',
1616 doc='ln(Tau) intercept parameter vector',
1617 size=pars['PARLNTAUINTERCEPT'].size)
1618 parSchema.addField('parLnTauSlope', type='ArrayD',
1619 doc='ln(Tau) slope parameter vector',
1620 size=pars['PARLNTAUSLOPE'].size)
1621 parSchema.addField('parLnPwvIntercept', type='ArrayD', doc='ln(pwv) intercept parameter vector',
1622 size=pars['PARLNPWVINTERCEPT'].size)
1623 parSchema.addField('parLnPwvSlope', type='ArrayD', doc='ln(pwv) slope parameter vector',
1624 size=pars['PARLNPWVSLOPE'].size)
1625 parSchema.addField('parLnPwvQuadratic', type='ArrayD', doc='ln(pwv) quadratic parameter vector',
1626 size=pars['PARLNPWVQUADRATIC'].size)
1627 parSchema.addField('parQeSysIntercept', type='ArrayD', doc='Mirror gray intercept parameter vector',
1628 size=pars['PARQESYSINTERCEPT'].size)
1629 parSchema.addField('compQeSysSlope', type='ArrayD', doc='Mirror gray slope parameter vector',
1630 size=pars[0]['COMPQESYSSLOPE'].size)
1631 parSchema.addField('parFilterOffset', type='ArrayD', doc='Filter offset parameter vector',
1632 size=pars['PARFILTEROFFSET'].size)
1633 parSchema.addField('parFilterOffsetFitFlag', type='ArrayI', doc='Filter offset parameter fit flag',
1634 size=pars['PARFILTEROFFSETFITFLAG'].size)
1635 parSchema.addField('parRetrievedLnPwvScale', type=np.float64,
1636 doc='Global scale for retrieved ln(pwv)')
1637 parSchema.addField('parRetrievedLnPwvOffset', type=np.float64,
1638 doc='Global offset for retrieved ln(pwv)')
1639 parSchema.addField('parRetrievedLnPwvNightlyOffset', type='ArrayD',
1640 doc='Nightly offset for retrieved ln(pwv)',
1641 size=pars['PARRETRIEVEDLNPWVNIGHTLYOFFSET'].size)
1642 parSchema.addField('compAbsThroughput', type='ArrayD',
1643 doc='Absolute throughput (relative to transmission curves)',
1644 size=pars['COMPABSTHROUGHPUT'].size)
1645 parSchema.addField('compRefOffset', type='ArrayD',
1646 doc='Offset between reference stars and calibrated stars',
1647 size=pars['COMPREFOFFSET'].size)
1648 parSchema.addField('compRefSigma', type='ArrayD',
1649 doc='Width of reference star/calibrated star distribution',
1650 size=pars['COMPREFSIGMA'].size)
1651 parSchema.addField('compMirrorChromaticity', type='ArrayD',
1652 doc='Computed mirror chromaticity terms',
1653 size=pars['COMPMIRRORCHROMATICITY'].size)
1654 parSchema.addField('mirrorChromaticityPivot', type='ArrayD',
1655 doc='Mirror chromaticity pivot mjd',
1656 size=pars['MIRRORCHROMATICITYPIVOT'].size)
1657 parSchema.addField('compMedianSedSlope', type='ArrayD',
1658 doc='Computed median SED slope (per band)',
1659 size=pars['COMPMEDIANSEDSLOPE'].size)
1660 parSchema.addField('compAperCorrPivot', type='ArrayD', doc='Aperture correction pivot',
1661 size=pars['COMPAPERCORRPIVOT'].size)
1662 parSchema.addField('compAperCorrSlope', type='ArrayD', doc='Aperture correction slope',
1663 size=pars['COMPAPERCORRSLOPE'].size)
1664 parSchema.addField('compAperCorrSlopeErr', type='ArrayD', doc='Aperture correction slope error',
1665 size=pars['COMPAPERCORRSLOPEERR'].size)
1666 parSchema.addField('compAperCorrRange', type='ArrayD', doc='Aperture correction range',
1667 size=pars['COMPAPERCORRRANGE'].size)
1668 parSchema.addField('compModelErrExptimePivot', type='ArrayD', doc='Model error exptime pivot',
1669 size=pars['COMPMODELERREXPTIMEPIVOT'].size)
1670 parSchema.addField('compModelErrFwhmPivot', type='ArrayD', doc='Model error fwhm pivot',
1671 size=pars['COMPMODELERRFWHMPIVOT'].size)
1672 parSchema.addField('compModelErrSkyPivot', type='ArrayD', doc='Model error sky pivot',
1673 size=pars['COMPMODELERRSKYPIVOT'].size)
1674 parSchema.addField('compModelErrPars', type='ArrayD', doc='Model error parameters',
1675 size=pars['COMPMODELERRPARS'].size)
1676 parSchema.addField('compExpGray', type='ArrayD', doc='Computed exposure gray',
1677 size=pars['COMPEXPGRAY'].size)
1678 parSchema.addField('compVarGray', type='ArrayD', doc='Computed exposure variance',
1679 size=pars['COMPVARGRAY'].size)
1680 parSchema.addField('compExpDeltaMagBkg', type='ArrayD',
1681 doc='Computed exposure offset due to background',
1682 size=pars['COMPEXPDELTAMAGBKG'].size)
1683 parSchema.addField('compNGoodStarPerExp', type='ArrayI',
1684 doc='Computed number of good stars per exposure',
1685 size=pars['COMPNGOODSTARPEREXP'].size)
1686 parSchema.addField('compExpRefOffset', type='ArrayD',
1687 doc='Computed per-visit median offset between standard stars and ref stars.',
1688 size=pars['COMPEXPREFOFFSET'].size)
1689 parSchema.addField('compSigFgcm', type='ArrayD', doc='Computed sigma_fgcm (intrinsic repeatability)',
1690 size=pars['COMPSIGFGCM'].size)
1691 parSchema.addField('compSigmaCal', type='ArrayD', doc='Computed sigma_cal (systematic error floor)',
1692 size=pars['COMPSIGMACAL'].size)
1693 parSchema.addField('compRetrievedLnPwv', type='ArrayD', doc='Retrieved ln(pwv) (smoothed)',
1694 size=pars['COMPRETRIEVEDLNPWV'].size)
1695 parSchema.addField('compRetrievedLnPwvRaw', type='ArrayD', doc='Retrieved ln(pwv) (raw)',
1696 size=pars['COMPRETRIEVEDLNPWVRAW'].size)
1697 parSchema.addField('compRetrievedLnPwvFlag', type='ArrayI', doc='Retrieved ln(pwv) Flag',
1698 size=pars['COMPRETRIEVEDLNPWVFLAG'].size)
1699 parSchema.addField('compRetrievedTauNight', type='ArrayD', doc='Retrieved tau (per night)',
1700 size=pars['COMPRETRIEVEDTAUNIGHT'].size)
1701 parSchema.addField('compEpsilon', type='ArrayD',
1702 doc='Computed epsilon background offset per visit (nJy/arcsec2)',
1703 size=pars['COMPEPSILON'].size)
1704 parSchema.addField('compMedDeltaAper', type='ArrayD',
1705 doc='Median delta mag aper per visit',
1706 size=pars['COMPMEDDELTAAPER'].size)
1707 parSchema.addField('compGlobalEpsilon', type='ArrayD',
1708 doc='Computed epsilon bkg offset (global) (nJy/arcsec2)',
1709 size=pars['COMPGLOBALEPSILON'].size)
1710 parSchema.addField('compEpsilonMap', type='ArrayD',
1711 doc='Computed epsilon maps (nJy/arcsec2)',
1712 size=pars['COMPEPSILONMAP'].size)
1713 parSchema.addField('compEpsilonNStarMap', type='ArrayI',
1714 doc='Number of stars per pixel in computed epsilon maps',
1715 size=pars['COMPEPSILONNSTARMAP'].size)
1716 parSchema.addField('compEpsilonCcdMap', type='ArrayD',
1717 doc='Computed epsilon ccd maps (nJy/arcsec2)',
1718 size=pars['COMPEPSILONCCDMAP'].size)
1719 parSchema.addField('compEpsilonCcdNStarMap', type='ArrayI',
1720 doc='Number of stars per ccd bin in epsilon ccd maps',
1721 size=pars['COMPEPSILONCCDNSTARMAP'].size)
1722 # superstarflat section
1723 parSchema.addField('superstarSize', type='ArrayI', doc='Superstar matrix size',
1724 size=4)
1725 parSchema.addField('superstar', type='ArrayD', doc='Superstar matrix (flattened)',
1726 size=parSuperStarFlat.size)
1727
1728 return parSchema
1729
1730 def _makeParCatalog(self, parSchema, parInfo, pars, parSuperStarFlat,
1731 lutFilterNameString, fitBandString):
1732 """
1733 Make the FGCM parameter catalog for persistence
1734
1735 Parameters
1736 ----------
1737 parSchema: `lsst.afw.table.Schema`
1738 Parameter catalog schema
1739 pars: `numpy.ndarray`
1740 FGCM parameters to put into parCat
1741 parSuperStarFlat: `numpy.array`
1742 FGCM superstar flat array to put into parCat
1743 lutFilterNameString: `str`
1744 Combined string of all the lutFilterNames
1745 fitBandString: `str`
1746 Combined string of all the fitBands
1747
1748 Returns
1749 -------
1750 parCat: `afwTable.BasicCatalog`
1751 Atmosphere and instrumental model parameter catalog for persistence
1752 """
1753
1754 parCat = afwTable.BaseCatalog(parSchema)
1755 parCat.reserve(1)
1756
1757 # The parameter catalog just has one row, with many columns for all the
1758 # atmosphere and instrument fit parameters
1759 rec = parCat.addNew()
1760
1761 # info section
1762 rec['nCcd'] = parInfo['NCCD']
1763 rec['lutFilterNames'] = lutFilterNameString
1764 rec['fitBands'] = fitBandString
1765 # note these are not currently supported here.
1766 rec['hasExternalPwv'] = 0
1767 rec['hasExternalTau'] = 0
1768
1769 # parameter section
1770
1771 scalarNames = ['parRetrievedLnPwvScale', 'parRetrievedLnPwvOffset']
1772
1773 arrNames = ['parAlpha', 'parO3', 'parLnTauIntercept', 'parLnTauSlope',
1774 'parLnPwvIntercept', 'parLnPwvSlope', 'parLnPwvQuadratic',
1775 'parQeSysIntercept', 'compQeSysSlope',
1776 'parRetrievedLnPwvNightlyOffset', 'compAperCorrPivot',
1777 'parFilterOffset', 'parFilterOffsetFitFlag',
1778 'compAbsThroughput', 'compRefOffset', 'compRefSigma',
1779 'compMirrorChromaticity', 'mirrorChromaticityPivot',
1780 'compAperCorrSlope', 'compAperCorrSlopeErr', 'compAperCorrRange',
1781 'compModelErrExptimePivot', 'compModelErrFwhmPivot',
1782 'compModelErrSkyPivot', 'compModelErrPars',
1783 'compExpGray', 'compVarGray', 'compNGoodStarPerExp', 'compSigFgcm',
1784 'compSigmaCal', 'compExpDeltaMagBkg', 'compMedianSedSlope',
1785 'compRetrievedLnPwv', 'compRetrievedLnPwvRaw', 'compRetrievedLnPwvFlag',
1786 'compRetrievedTauNight', 'compEpsilon', 'compMedDeltaAper',
1787 'compGlobalEpsilon', 'compEpsilonMap', 'compEpsilonNStarMap',
1788 'compEpsilonCcdMap', 'compEpsilonCcdNStarMap', 'compExpRefOffset']
1789
1790 for scalarName in scalarNames:
1791 rec[scalarName] = pars[scalarName.upper()]
1792
1793 for arrName in arrNames:
1794 rec[arrName][:] = np.atleast_1d(pars[0][arrName.upper()])[:]
1795
1796 # superstar section
1797 rec['superstarSize'][:] = parSuperStarFlat.shape
1798 rec['superstar'][:] = parSuperStarFlat.ravel()
1799
1800 return parCat
1801
1802 def _makeFlagStarSchema(self):
1803 """
1804 Make the flagged-stars schema
1805
1806 Returns
1807 -------
1808 flagStarSchema: `lsst.afw.table.Schema`
1809 """
1810
1811 flagStarSchema = afwTable.Schema()
1812
1813 flagStarSchema.addField('objId', type=np.int32, doc='FGCM object id')
1814 flagStarSchema.addField('objFlag', type=np.int32, doc='FGCM object flag')
1815
1816 return flagStarSchema
1817
1818 def _makeFlagStarCat(self, flagStarSchema, flagStarStruct):
1819 """
1820 Make the flagged star catalog for persistence
1821
1822 Parameters
1823 ----------
1824 flagStarSchema: `lsst.afw.table.Schema`
1825 Flagged star schema
1826 flagStarStruct: `numpy.ndarray`
1827 Flagged star structure from fgcm
1828
1829 Returns
1830 -------
1832 Flagged star catalog for persistence
1833 """
1834
1835 flagStarCat = afwTable.BaseCatalog(flagStarSchema)
1836 flagStarCat.resize(flagStarStruct.size)
1837
1838 flagStarCat['objId'][:] = flagStarStruct['OBJID']
1839 flagStarCat['objFlag'][:] = flagStarStruct['OBJFLAG']
1840
1841 return flagStarCat
An immutable representation of a camera.
Definition Camera.h:43
Defines the fields and offsets for a table.
Definition Schema.h:51