Loading [MathJax]/extensions/tex2jax.js
LSST Applications g04a91732dc+7fec47d7bc,g07dc498a13+5ab4d22ec3,g0fba68d861+565de8e5d5,g1409bbee79+5ab4d22ec3,g1a7e361dbc+5ab4d22ec3,g1fd858c14a+11200c7927,g20f46db602+25d63fd678,g35bb328faa+fcb1d3bbc8,g4d2262a081+61302e889d,g4d39ba7253+d05e267ece,g4e0f332c67+5d362be553,g53246c7159+fcb1d3bbc8,g60b5630c4e+d05e267ece,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g8048e755c2+a1301e4c20,g8852436030+163ceb82d8,g89139ef638+5ab4d22ec3,g89e1512fd8+fbb808ebce,g8d6b6b353c+d05e267ece,g9125e01d80+fcb1d3bbc8,g989de1cb63+5ab4d22ec3,g9f33ca652e+8abe617c77,ga9baa6287d+d05e267ece,gaaedd4e678+5ab4d22ec3,gabe3b4be73+1e0a283bba,gb1101e3267+fefe9ce5b1,gb58c049af0+f03b321e39,gb90eeb9370+824c420ec4,gc741bbaa4f+77ddc33078,gcf25f946ba+163ceb82d8,gd315a588df+0f88d5218e,gd6cbbdb0b4+c8606af20c,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+e6bd566e97,ge278dab8ac+932305ba37,ge82c20c137+76d20ab76d,w.2025.10
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 .focalPlaneProjector import FocalPlaneProjector
51
52import fgcm
53
54__all__ = ['FgcmFitCycleConfig', 'FgcmFitCycleTask']
55
56MULTIPLE_CYCLES_MAX = 10
57
58
59class FgcmFitCycleConnections(pipeBase.PipelineTaskConnections,
60 dimensions=("instrument",),
61 defaultTemplates={"previousCycleNumber": "-1",
62 "cycleNumber": "0"}):
63 camera = connectionTypes.PrerequisiteInput(
64 doc="Camera instrument",
65 name="camera",
66 storageClass="Camera",
67 dimensions=("instrument",),
68 isCalibration=True,
69 )
70
71 fgcmLookUpTable = connectionTypes.PrerequisiteInput(
72 doc=("Atmosphere + instrument look-up-table for FGCM throughput and "
73 "chromatic corrections."),
74 name="fgcmLookUpTable",
75 storageClass="Catalog",
76 dimensions=("instrument",),
77 deferLoad=True,
78 )
79
80 fgcmVisitCatalog = connectionTypes.Input(
81 doc="Catalog of visit information for fgcm",
82 name="fgcmVisitCatalog",
83 storageClass="Catalog",
84 dimensions=("instrument",),
85 deferLoad=True,
86 )
87
88 fgcmStarObservationsParquet = connectionTypes.Input(
89 doc=("Catalog of star observations for fgcm, in parquet format. "
90 "Used if useParquetCatalogFormat is True."),
91 name="fgcm_star_observations",
92 storageClass="ArrowAstropy",
93 dimensions=("instrument",),
94 deferLoad=True,
95 )
96
97 fgcmStarIdsParquet = connectionTypes.Input(
98 doc=("Catalog of fgcm calibration star IDs, in parquet format. "
99 "Used if useParquetCatalogFormat is True."),
100 name="fgcm_star_ids",
101 storageClass="ArrowAstropy",
102 dimensions=("instrument",),
103 deferLoad=True,
104 )
105
106 fgcmReferenceStarsParquet = connectionTypes.Input(
107 doc=("Catalog of fgcm-matched reference stars, in parquet format. "
108 "Used if useParquetCatalogFormat is True."),
109 name="fgcm_reference_stars",
110 storageClass="ArrowAstropy",
111 dimensions=("instrument",),
112 deferLoad=True,
113 )
114
115 fgcmStarObservations = connectionTypes.Input(
116 doc=("Catalog of star observations for fgcm; old format. "
117 "Used if useParquetCatalogFormat is False."),
118 name="fgcmStarObservations",
119 storageClass="Catalog",
120 dimensions=("instrument",),
121 deferLoad=True,
122 )
123
124 fgcmStarIds = connectionTypes.Input(
125 doc=("Catalog of fgcm calibration star IDs. "
126 "Used if useParquetCatalogFormat is False."),
127 name="fgcmStarIds",
128 storageClass="Catalog",
129 dimensions=("instrument",),
130 deferLoad=True,
131 )
132
133 fgcmStarIndices = connectionTypes.Input(
134 doc=("Catalog of fgcm calibration star indices; old format."
135 "Used if useParquetCatalogFormat is False."),
136 name="fgcmStarIndices",
137 storageClass="Catalog",
138 dimensions=("instrument",),
139 deferLoad=True,
140 )
141
142 fgcmReferenceStars = connectionTypes.Input(
143 doc=("Catalog of fgcm-matched reference stars; old format."
144 "Used if useParquetCatalogFormat is False."),
145 name="fgcmReferenceStars",
146 storageClass="Catalog",
147 dimensions=("instrument",),
148 deferLoad=True,
149 )
150
151 def __init__(self, *, config=None):
152 super().__init__(config=config)
153
154 # The connection parameters ``cycleNumber`` and ``previousCycleNumber``
155 # always comes in as strings, and doing a round-trip via
156 # integer-to-string ensures that they actually are integers.
157 if str(int(config.connections.cycleNumber)) != config.connections.cycleNumber:
158 raise ValueError("cycleNumber must be of integer format")
159 if str(int(config.connections.previousCycleNumber)) != config.connections.previousCycleNumber:
160 raise ValueError("previousCycleNumber must be of integer format")
161 # We additionally confirm that there is a delta of 1 between them.
162 # Note that this math cannot be done in the parameters because they are
163 # strings.
164 if int(config.connections.previousCycleNumber) != (int(config.connections.cycleNumber) - 1):
165 raise ValueError("previousCycleNumber must be 1 less than cycleNumber")
166
167 instDims = ("instrument",)
168 bandDims = ("instrument", "band")
169 filterDims = ("instrument", "physical_filter")
170
171 inputAndOutputConnections = [
172 ("FitParameters", "Catalog", "Catalog of fgcm fit parameters.", instDims),
173 ("FlaggedStars", "Catalog", "Catalog of flagged stars for fgcm calibration.", instDims),
174 ]
175 multicycleOutputConnections = [
176 ("OutputConfig", "Config", "Configuration for next fgcm fit cycle.", instDims),
177 ]
178 optionalZpOutputConnections = [
179 ("Zeropoints", "Catalog", "Catalog of fgcm zeropoint data.", instDims),
180 ("AtmosphereParameters", "Catalog", "Catalog of atmospheric fit parameters.", instDims),
181 ]
182 optionalStarOutputConnections = [
183 ("StandardStars", "SimpleCatalog", "Catalog of standard star magnitudes.", instDims),
184 ]
185
186 epochs = [f"epoch{i}" for i in range(len(config.epochMjds))]
187
188 # All names will be preceeded with ``fgcm_CycleN_``.
189 plotConnections = [
190 ("Zeropoints_Plot", "Plot", "Plot of fgcm zeropoints.", instDims),
191 ("ExpgrayDeep_Plot",
192 "Plot",
193 "Plot of gray term per exposure per time for deep fields.",
194 instDims),
195 ("NightlyAlpha_Plot", "Plot", "Plot of nightly AOD alpha term.", instDims),
196 ("NightlyTau_Plot", "Plot", "Plot of nightly aerosol optical depth (tau).", instDims),
197 ("NightlyPwv_Plot", "Plot", "Plot of nightly water vapor.", instDims),
198 ("NightlyO3_Plot", "Plot", "Plot of nightly ozone.", instDims),
199 ("FilterOffsets_Plot", "Plot", "Plot of in-band filter offsets.", instDims),
200 ("AbsThroughputs_Plot", "Plot", "Plot of absolute throughput fractions.", instDims),
201 ("QESysWashesInitial_Plot", "Plot", "Plot of initial system QE with mirror washes.", instDims),
202 ("QESysWashesFinal_Plot", "Plot", "Plot of final system QE with mirror washes.", instDims),
203 ("RpwvVsRpwvInput_Plot",
204 "Plot",
205 "Plot of change in per-visit ``retrieved`` PWV from previous fit cycle.",
206 instDims),
207 ("RpwvVsRpwvSmooth_Plot",
208 "Plot",
209 "Plot of per-visit ``retrieved`` PWV vs. smoothed PWV.",
210 instDims),
211 ("ModelPwvVsRpwv_Plot",
212 "Plot",
213 "Plot of model PWV vs. per-visit ``retrieved`` PWV.",
214 instDims),
215 ("ChisqFit_Plot",
216 "Plot",
217 "Plot of chisq as a function of iteration.",
218 instDims),
219 ]
220
221 plotConnections.extend(
222 [
223 ("Apercorr_Plot", "Plot", "Plot of fgcm aperture corrections.", bandDims),
224 ("EpsilonGlobal_Plot",
225 "Plot",
226 "Plot of global background over/undersubtraction.",
227 bandDims),
228 ("EpsilonMap_Plot",
229 "Plot",
230 "Map of spatially varying background over/undersubtraction.",
231 bandDims),
232 ("ExpgrayInitial_Plot",
233 "Plot",
234 "Histogram of initial gray term per exposure.",
235 bandDims),
236 ("CompareRedblueExpgray_Plot",
237 "Plot",
238 "Plot of red/blue split gray term per exposure",
239 bandDims),
240 ("Expgray_Plot", "Plot", "Histogram of gray term per exposure.", bandDims),
241 ("ExpgrayAirmass_Plot",
242 "Plot",
243 "Plot of exposure gray term as a function of airmass.",
244 bandDims),
245 ("ExpgrayCompareMjdRedblue_Plot",
246 "Plot",
247 "Plot of red/blue split gray term per exposure as a function of time.",
248 bandDims),
249 ("ExpgrayUT_Plot",
250 "Plot",
251 "Plot of grey term per exposure as a function of time of night.",
252 bandDims),
253 ("ExpgrayCompareBands_Plot",
254 "Plot",
255 "Plot of gray term per exposure between bands nearby in time.",
256 bandDims),
257 ("ExpgrayReference_Plot",
258 "Plot",
259 "Histogram of gray term per exposure compared to reference mags.",
260 bandDims),
261 ("QESysRefstarsStdInitial_Plot",
262 "Plot",
263 "Plot of reference mag - calibrated (standard) mag vs. time (before fit).",
264 bandDims),
265 ("QESysRefstarsStdFinal_Plot",
266 "Plot",
267 "Plot of reference mag - calibrated (standard) mag vs. time (after fit).",
268 bandDims),
269 ("QESysRefstarsObsInitial_Plot",
270 "Plot",
271 "Plot of reference mag - observed (instrumental) mag vs. time (before fit).",
272 bandDims),
273 ("QESysRefstarsObsFinal_Plot",
274 "Plot",
275 "Plot of reference mag - observed (instrumental) mag vs. time (after fit).",
276 bandDims),
277 ("ModelMagerrInitial_Plot",
278 "Plot",
279 "Plots for magnitude error model, initial estimate.",
280 bandDims),
281 ("ModelMagerrPostfit_Plot",
282 "Plot",
283 "Plots for magnitude error model, after fitting.",
284 bandDims),
285 ("SigmaFgcmAllStars_Plot",
286 "Plot",
287 "Histograms for intrinsic scatter for all bright stars.",
288 bandDims),
289 ("SigmaFgcmReservedStars_Plot",
290 "Plot",
291 "Histograms for intrinsic scatter for reserved bright stars.",
292 bandDims),
293 ("SigmaFgcmReservedStarsCrunched_Plot",
294 "Plot",
295 "Histograms for intrinsic scatter for reserved bright stars (after gray correction).",
296 bandDims),
297 ("SigmaFgcmPullsAllStars_Plot",
298 "Plot",
299 "Histograms for pulls for all bright stars.",
300 bandDims),
301 ("SigmaFgcmPullsReservedStars_Plot",
302 "Plot",
303 "Histograms for pulls for reserved bright stars.",
304 bandDims),
305 ("SigmaFgcmPullsReservedStarsCrunched_Plot",
306 "Plot",
307 "Histograms for pulls for reserved bright stars (after gray correction).",
308 bandDims),
309 ("SigmaCal_Plot",
310 "Plot",
311 "Plot showing scatter as a function of systematic error floor.",
312 bandDims),
313 ("SigmaRef_Plot",
314 "Plot",
315 "Histograms of scatter with respect to reference stars.",
316 bandDims),
317 ("RefResidVsColorAll_Plot",
318 "Plot",
319 "Plot of reference star residuals vs. color (all stars).",
320 bandDims),
321 ("RefResidVsColorCut_Plot",
322 "Plot",
323 "Plot of reference star residuals vs. color (reference star color cuts).",
324 bandDims),
325 ]
326 )
327
328 plotConnections.extend(
329 [
330 ("I1R1_Plot", "Plot", "Plot of fgcm R1 vs. I1.", filterDims),
331 ("I1_Plot", "Plot", "Focal plane map of fgcm I1.", filterDims),
332 ("R1_Plot", "Plot", "Focal plane map of fgcm R1.", filterDims),
333 ("R1mI1_Plot", "Plot", "Focal plane map of fgcm R1 - I1.", filterDims),
334 ("R1mI1_vs_mjd_Plot", "Plot", "R1 - I1 residuals vs. mjd.", filterDims),
335 ("CompareRedblueMirrorchrom_Plot",
336 "Plot",
337 "Comparison of mirror chromaticity residuals for red/blue stars.",
338 filterDims),
339 ("CcdChromaticity_Plot",
340 "Plot",
341 "Focal plane map of fgcm ccd chromaticity.",
342 filterDims),
343 ("EpsilonDetector_Plot",
344 "Plot",
345 "Focal plane map of background over/undersubtraction.",
346 filterDims),
347 ("EpsilonDetectorMatchscale_Plot",
348 "Plot",
349 "Focal plane map of background over/undersubtraction.",
350 filterDims),
351 ]
352 )
353 for epoch in epochs:
354 plotConnections.extend(
355 [
356 (
357 f"Superstar_{epoch}_Plot",
358 "Plot",
359 "Plot of illumination Correction.",
360 filterDims,
361 )
362 ]
363 )
364
365 if config.doMultipleCycles:
366 # Multiple cycle run.
367
368 # All but the final cycle get appended here.
369 for cycle in range(config.multipleCyclesFinalCycleNumber):
370 outputConnections = copy.copy(inputAndOutputConnections)
371 outputConnections.extend(multicycleOutputConnections)
372 if config.outputZeropointsBeforeFinalCycle:
373 outputConnections.extend(optionalZpOutputConnections)
374 if config.outputStandardsBeforeFinalCycle:
375 outputConnections.extend(optionalStarOutputConnections)
376
377 if config.doPlots:
378 # We will make the plots if doPlots is True and either
379 # it is the next-to-last cycle or the
380 # doPlotsBeforeFinalCycles configuration is set which
381 # means we want plots for all cycles.
382 # The final cycle doPlots is configured below.
383 if cycle == (config.multipleCyclesFinalCycleNumber - 1) \
384 or config.doPlotsBeforeFinalCycles:
385 outputConnections.extend(plotConnections)
386
387 for (name, storageClass, doc, dims) in outputConnections:
388 connectionName = f"fgcm_Cycle{cycle}_{name}"
389 storageName = connectionName
390 outConnection = connectionTypes.Output(
391 name=storageName,
392 storageClass=storageClass,
393 doc=doc,
394 dimensions=dims,
395 multiple=(len(dims) > 1),
396 )
397 setattr(self, connectionName, outConnection)
398
399 # The final cycle has everything.
400 outputConnections = copy.copy(inputAndOutputConnections)
401 outputConnections.extend(multicycleOutputConnections)
402 outputConnections.extend(optionalZpOutputConnections)
403 outputConnections.extend(optionalStarOutputConnections)
404 if config.doPlots:
405 outputConnections.extend(plotConnections)
406 for (name, storageClass, doc, dims) in outputConnections:
407 connectionName = f"fgcm_Cycle{config.multipleCyclesFinalCycleNumber}_{name}"
408 storageName = connectionName
409 outConnection = connectionTypes.Output(
410 name=storageName,
411 storageClass=storageClass,
412 doc=doc,
413 dimensions=dims,
414 multiple=(len(dims) > 1),
415 )
416 setattr(self, connectionName, outConnection)
417 else:
418 # Single cycle run.
419 if config.cycleNumber > 0:
420 inputConnections = copy.copy(inputAndOutputConnections)
421 else:
422 inputConnections = []
423 outputConnections = copy.copy(inputAndOutputConnections)
424 # The following configurations are also useful for runs
425 # where fit cycles are run one-at-a-time since it is
426 # not typical to look at these outputs for intermediate
427 # steps.
428 if config.isFinalCycle or config.outputZeropointsBeforeFinalCycle:
429 outputConnections.extend(optionalZpOutputConnections)
430 if config.isFinalCycle or config.outputStandardsBeforeFinalCycle:
431 outputConnections.extend(optionalStarOutputConnections)
432
433 if config.doPlots:
434 outputConnections.extend(plotConnections)
435
436 for (name, storageClass, doc, dims) in inputConnections:
437 connectionName = f"fgcm{name}Input"
438 storageName = f"fgcm_Cycle{config.cycleNumber - 1}_{name}"
439 inConnection = connectionTypes.PrerequisiteInput(
440 name=storageName,
441 storageClass=storageClass,
442 doc=doc,
443 dimensions=dims,
444 )
445 setattr(self, connectionName, inConnection)
446
447 for (name, storageClass, doc, dims) in outputConnections:
448 connectionName = f"fgcm{name}"
449 storageName = f"fgcm_Cycle{config.cycleNumber}_{name}"
450 # Plots have unique names as well.
451 if storageClass == "Plot":
452 connectionName = storageName
453 outConnection = connectionTypes.Output(
454 name=storageName,
455 storageClass=storageClass,
456 doc=doc,
457 dimensions=dims,
458 multiple=(len(dims) > 1),
459 )
460 setattr(self, connectionName, outConnection)
461
462 if not config.doReferenceCalibration:
463 self.inputs.remove("fgcmReferenceStars")
464 self.inputs.remove("fgcmReferenceStarsParquet")
465
466 if config.useParquetCatalogFormat:
467 self.inputs.remove("fgcmStarObservations")
468 self.inputs.remove("fgcmStarIds")
469 self.inputs.remove("fgcmStarIndices")
470 if config.doReferenceCalibration:
471 self.inputs.remove("fgcmReferenceStars")
472 else:
473 self.inputs.remove("fgcmStarObservationsParquet")
474 self.inputs.remove("fgcmStarIdsParquet")
475 if config.doReferenceCalibration:
476 self.inputs.remove("fgcmReferenceStarsParquet")
477
478
479class FgcmFitCycleConfig(pipeBase.PipelineTaskConfig,
480 pipelineConnections=FgcmFitCycleConnections):
481 """Config for FgcmFitCycle"""
482
483 doMultipleCycles = pexConfig.Field(
484 doc="Run multiple fit cycles in one task",
485 dtype=bool,
486 default=False,
487 )
488 useParquetCatalogFormat = pexConfig.Field(
489 doc="Use parquet catalog format?",
490 dtype=bool,
491 default=True,
492 )
493 multipleCyclesFinalCycleNumber = pexConfig.RangeField(
494 doc=("Final cycle number in multiple cycle mode. The initial cycle "
495 "is 0, with limited parameters fit. The next cycle is 1 with "
496 "full parameter fit. The final cycle is a clean-up with no "
497 "parameters fit. There will be a total of "
498 "(multipleCycleFinalCycleNumber + 1) cycles run, and the final "
499 "cycle number cannot be less than 2."),
500 dtype=int,
501 default=5,
502 min=2,
503 max=MULTIPLE_CYCLES_MAX,
504 inclusiveMax=True,
505 )
506 bands = pexConfig.ListField(
507 doc="Bands to run calibration",
508 dtype=str,
509 default=[],
510 )
511 fitBands = pexConfig.ListField(
512 doc=("Bands to use in atmospheric fit. The bands not listed here will have "
513 "the atmosphere constrained from the 'fitBands' on the same night. "
514 "Must be a subset of `config.bands`"),
515 dtype=str,
516 default=[],
517 )
518 requiredBands = pexConfig.ListField(
519 doc=("Bands that are required for a star to be considered a calibration star. "
520 "Must be a subset of `config.bands`"),
521 dtype=str,
522 default=[],
523 )
524 physicalFilterMap = pexConfig.DictField(
525 doc="Mapping from 'physicalFilter' to band.",
526 keytype=str,
527 itemtype=str,
528 default={},
529 )
530 doReferenceCalibration = pexConfig.Field(
531 doc="Use reference catalog as additional constraint on calibration",
532 dtype=bool,
533 default=True,
534 )
535 refStarSnMin = pexConfig.Field(
536 doc="Reference star signal-to-noise minimum to use in calibration. Set to <=0 for no cut.",
537 dtype=float,
538 default=50.0,
539 )
540 refStarOutlierNSig = pexConfig.Field(
541 doc=("Number of sigma compared to average mag for reference star to be considered an outlier. "
542 "Computed per-band, and if it is an outlier in any band it is rejected from fits."),
543 dtype=float,
544 default=4.0,
545 )
546 applyRefStarColorCuts = pexConfig.Field(
547 doc=("Apply color cuts defined in ``starColorCuts`` to reference stars? "
548 "These cuts are in addition to any cuts defined in ``refStarColorCuts``"),
549 dtype=bool,
550 default=True,
551 )
552 refStarMaxFracUse = pexConfig.Field(
553 doc=("Maximum fraction of reference stars to use in the fit. Remainder will "
554 "be used only for validation."),
555 dtype=float,
556 default=0.5,
557 )
558 useExposureReferenceOffset = pexConfig.Field(
559 doc=("Use per-exposure (visit) offsets between calibrated stars and reference stars "
560 "for final zeropoints? This may help uniformity for disjoint surveys."),
561 dtype=bool,
562 default=False,
563 )
564 nCore = pexConfig.Field(
565 doc="Number of cores to use",
566 dtype=int,
567 default=4,
568 deprecated="Number of cores is deprecated as a config, and will be removed after v27. "
569 "Please use ``pipetask run --cores-per-quantum`` instead.",
570 )
571 nStarPerRun = pexConfig.Field(
572 doc="Number of stars to run in each chunk",
573 dtype=int,
574 default=200000,
575 )
576 nExpPerRun = pexConfig.Field(
577 doc="Number of exposures to run in each chunk",
578 dtype=int,
579 default=1000,
580 )
581 reserveFraction = pexConfig.Field(
582 doc="Fraction of stars to reserve for testing",
583 dtype=float,
584 default=0.1,
585 )
586 freezeStdAtmosphere = pexConfig.Field(
587 doc="Freeze atmosphere parameters to standard (for testing)",
588 dtype=bool,
589 default=False,
590 )
591 precomputeSuperStarInitialCycle = pexConfig.Field(
592 doc="Precompute superstar flat for initial cycle",
593 dtype=bool,
594 default=False,
595 )
596 superStarSubCcdDict = pexConfig.DictField(
597 doc=("Per-band specification on whether to compute superstar flat on sub-ccd scale. "
598 "Must have one entry per band."),
599 keytype=str,
600 itemtype=bool,
601 default={},
602 )
603 superStarSubCcdChebyshevOrder = pexConfig.Field(
604 doc=("Order of the 2D chebyshev polynomials for sub-ccd superstar fit. "
605 "Global default is first-order polynomials, and should be overridden "
606 "on a camera-by-camera basis depending on the ISR."),
607 dtype=int,
608 default=1,
609 )
610 superStarSubCcdTriangular = pexConfig.Field(
611 doc=("Should the sub-ccd superstar chebyshev matrix be triangular to "
612 "suppress high-order cross terms?"),
613 dtype=bool,
614 default=False,
615 )
616 superStarSigmaClip = pexConfig.Field(
617 doc="Number of sigma to clip outliers when selecting for superstar flats",
618 dtype=float,
619 default=5.0,
620 )
621 superStarPlotCcdResiduals = pexConfig.Field(
622 doc="If plotting is enabled, should per-detector residuals be plotted? "
623 "This may produce a lot of output, and should be used only for "
624 "debugging purposes.",
625 dtype=bool,
626 default=False,
627 )
628 superStarForceZeroMean = pexConfig.Field(
629 doc="When computing the super-star flat, force the focal-plane mean to "
630 "zero (per band)? This should only be used when computing stand-alone "
631 "illumination corrections.",
632 dtype=bool,
633 default=False,
634 )
635 focalPlaneSigmaClip = pexConfig.Field(
636 doc="Number of sigma to clip outliers per focal-plane.",
637 dtype=float,
638 default=4.0,
639 )
640 ccdGraySubCcdDict = pexConfig.DictField(
641 doc=("Per-band specification on whether to compute achromatic per-ccd residual "
642 "('ccd gray') on a sub-ccd scale."),
643 keytype=str,
644 itemtype=bool,
645 default={},
646 )
647 ccdGraySubCcdChebyshevOrder = pexConfig.Field(
648 doc="Order of the 2D chebyshev polynomials for sub-ccd gray fit.",
649 dtype=int,
650 default=1,
651 )
652 ccdGraySubCcdTriangular = pexConfig.Field(
653 doc=("Should the sub-ccd gray chebyshev matrix be triangular to "
654 "suppress high-order cross terms?"),
655 dtype=bool,
656 default=True,
657 )
658 ccdGrayFocalPlaneDict = pexConfig.DictField(
659 doc=("Per-band specification on whether to compute focal-plane residual "
660 "('ccd gray') corrections."),
661 keytype=str,
662 itemtype=bool,
663 default={},
664 )
665 ccdGrayFocalPlaneFitMinCcd = pexConfig.Field(
666 doc=("Minimum number of 'good' CCDs required to perform focal-plane "
667 "gray corrections. If there are fewer good CCDs then the gray "
668 "correction is computed per-ccd."),
669 dtype=int,
670 default=1,
671 )
672 ccdGrayFocalPlaneChebyshevOrder = pexConfig.Field(
673 doc="Order of the 2D chebyshev polynomials for focal plane fit.",
674 dtype=int,
675 default=3,
676 )
677 cycleNumber = pexConfig.Field(
678 doc=("FGCM fit cycle number. This is automatically incremented after each run "
679 "and stage of outlier rejection. See cookbook for details."),
680 dtype=int,
681 default=None,
682 )
683 isFinalCycle = pexConfig.Field(
684 doc=("Is this the final cycle of the fitting? Will automatically compute final "
685 "selection of stars and photometric exposures, and will output zeropoints "
686 "and standard stars for use in fgcmOutputProducts"),
687 dtype=bool,
688 default=False,
689 )
690 maxIterBeforeFinalCycle = pexConfig.Field(
691 doc=("Maximum fit iterations, prior to final cycle. The number of iterations "
692 "will always be 0 in the final cycle for cleanup and final selection."),
693 dtype=int,
694 default=50,
695 )
696 deltaMagBkgOffsetPercentile = pexConfig.Field(
697 doc=("Percentile brightest stars on a visit/ccd to use to compute net "
698 "offset from local background subtraction."),
699 dtype=float,
700 default=0.25,
701 )
702 deltaMagBkgPerCcd = pexConfig.Field(
703 doc=("Compute net offset from local background subtraction per-ccd? "
704 "Otherwise, use computation per visit."),
705 dtype=bool,
706 default=False,
707 )
708 utBoundary = pexConfig.Field(
709 doc="Boundary (in UTC) from day-to-day",
710 dtype=float,
711 default=None,
712 )
713 washMjds = pexConfig.ListField(
714 doc="Mirror wash MJDs",
715 dtype=float,
716 default=(0.0,),
717 )
718 epochMjds = pexConfig.ListField(
719 doc="Epoch boundaries in MJD",
720 dtype=float,
721 default=(0.0,),
722 )
723 minObsPerBand = pexConfig.Field(
724 doc="Minimum good observations per band",
725 dtype=int,
726 default=2,
727 )
728 # TODO: When DM-16511 is done, it will be possible to get the
729 # telescope latitude directly from the camera.
730 latitude = pexConfig.Field(
731 doc="Observatory latitude",
732 dtype=float,
733 default=None,
734 )
735 mirrorArea = pexConfig.Field(
736 doc="Mirror area (square meters) of telescope. If not set, will "
737 "try to estimate from camera.telescopeDiameter.",
738 dtype=float,
739 default=None,
740 optional=True,
741 )
742 cameraGain = pexConfig.Field(
743 doc="Average camera gain. If not set, will use the median of the "
744 "camera model/detector/amplifier gains.",
745 dtype=float,
746 default=None,
747 optional=True,
748 )
749 defaultCameraOrientation = pexConfig.Field(
750 doc="Default camera orientation for QA plots.",
751 dtype=float,
752 default=None,
753 )
754 brightObsGrayMax = pexConfig.Field(
755 doc="Maximum gray extinction to be considered bright observation",
756 dtype=float,
757 default=0.15,
758 )
759 minStarPerCcd = pexConfig.Field(
760 doc=("Minimum number of good stars per CCD to be used in calibration fit. "
761 "CCDs with fewer stars will have their calibration estimated from other "
762 "CCDs in the same visit, with zeropoint error increased accordingly."),
763 dtype=int,
764 default=5,
765 )
766 minCcdPerExp = pexConfig.Field(
767 doc=("Minimum number of good CCDs per exposure/visit to be used in calibration fit. "
768 "Visits with fewer good CCDs will have CCD zeropoints estimated where possible."),
769 dtype=int,
770 default=5,
771 )
772 maxCcdGrayErr = pexConfig.Field(
773 doc="Maximum error on CCD gray offset to be considered photometric",
774 dtype=float,
775 default=0.05,
776 )
777 minStarPerExp = pexConfig.Field(
778 doc=("Minimum number of good stars per exposure/visit to be used in calibration fit. "
779 "Visits with fewer good stars will have CCD zeropoints estimated where possible."),
780 dtype=int,
781 default=600,
782 )
783 minExpPerNight = pexConfig.Field(
784 doc="Minimum number of good exposures/visits to consider a partly photometric night",
785 dtype=int,
786 default=10,
787 )
788 expGrayInitialCut = pexConfig.Field(
789 doc=("Maximum exposure/visit gray value for initial selection of possible photometric "
790 "observations."),
791 dtype=float,
792 default=-0.25,
793 )
794 expFwhmCutDict = pexConfig.DictField(
795 doc=("Per-band specification on maximum exposure FWHM (arcseconds) that will "
796 "be considered for the model fit. Exposures with median FWHM larger "
797 "than this threshold will get zeropoints based on matching to good "
798 "stars."),
799 keytype=str,
800 itemtype=float,
801 default={},
802 )
803 expGrayPhotometricCutDict = pexConfig.DictField(
804 doc=("Per-band specification on maximum (negative) achromatic exposure residual "
805 "('gray term') for a visit to be considered photometric. Must have one "
806 "entry per band. Broad-band filters should be -0.05."),
807 keytype=str,
808 itemtype=float,
809 default={},
810 )
811 expGrayHighCutDict = pexConfig.DictField(
812 doc=("Per-band specification on maximum (positive) achromatic exposure residual "
813 "('gray term') for a visit to be considered photometric. Must have one "
814 "entry per band. Broad-band filters should be 0.2."),
815 keytype=str,
816 itemtype=float,
817 default={},
818 )
819 expGrayRecoverCut = pexConfig.Field(
820 doc=("Maximum (negative) exposure gray to be able to recover bad ccds via interpolation. "
821 "Visits with more gray extinction will only get CCD zeropoints if there are "
822 "sufficient star observations (minStarPerCcd) on that CCD."),
823 dtype=float,
824 default=-1.0,
825 )
826 expVarGrayPhotometricCutDict = pexConfig.DictField(
827 doc=("Per-band specification on maximum exposure variance to be considered possibly "
828 "photometric. Must have one entry per band. Broad-band filters should be "
829 "0.0005."),
830 keytype=str,
831 itemtype=float,
832 default={},
833 )
834 expGrayErrRecoverCut = pexConfig.Field(
835 doc=("Maximum exposure gray error to be able to recover bad ccds via interpolation. "
836 "Visits with more gray variance will only get CCD zeropoints if there are "
837 "sufficient star observations (minStarPerCcd) on that CCD."),
838 dtype=float,
839 default=0.05,
840 )
841 aperCorrFitNBins = pexConfig.Field(
842 doc=("Number of aperture bins used in aperture correction fit. When set to 0"
843 "no fit will be performed, and the config.aperCorrInputSlopes will be "
844 "used if available."),
845 dtype=int,
846 default=10,
847 )
848 aperCorrInputSlopeDict = pexConfig.DictField(
849 doc=("Per-band specification of aperture correction input slope parameters. These "
850 "are used on the first fit iteration, and aperture correction parameters will "
851 "be updated from the data if config.aperCorrFitNBins > 0. It is recommended "
852 "to set this when there is insufficient data to fit the parameters (e.g. "
853 "tract mode)."),
854 keytype=str,
855 itemtype=float,
856 default={},
857 )
858 sedboundaryterms = pexConfig.ConfigField(
859 doc="Mapping from bands to SED boundary term names used is sedterms.",
860 dtype=SedboundarytermDict,
861 )
862 sedterms = pexConfig.ConfigField(
863 doc="Mapping from terms to bands for fgcm linear SED approximations.",
864 dtype=SedtermDict,
865 )
866 sigFgcmMaxErr = pexConfig.Field(
867 doc="Maximum mag error for fitting sigma_FGCM",
868 dtype=float,
869 default=0.01,
870 )
871 sigFgcmMaxEGrayDict = pexConfig.DictField(
872 doc=("Per-band specification for maximum (absolute) achromatic residual (gray value) "
873 "for observations in sigma_fgcm (raw repeatability). Broad-band filters "
874 "should be 0.05."),
875 keytype=str,
876 itemtype=float,
877 default={},
878 )
879 ccdGrayMaxStarErr = pexConfig.Field(
880 doc=("Maximum error on a star observation to use in ccd gray (achromatic residual) "
881 "computation"),
882 dtype=float,
883 default=0.10,
884 )
885 approxThroughputDict = pexConfig.DictField(
886 doc=("Per-band specification of the approximate overall throughput at the start of "
887 "calibration observations. Must have one entry per band. Typically should "
888 "be 1.0."),
889 keytype=str,
890 itemtype=float,
891 default={},
892 )
893 sigmaCalRange = pexConfig.ListField(
894 doc="Allowed range for systematic error floor estimation",
895 dtype=float,
896 default=(0.001, 0.003),
897 )
898 sigmaCalFitPercentile = pexConfig.ListField(
899 doc="Magnitude percentile range to fit systematic error floor",
900 dtype=float,
901 default=(0.05, 0.15),
902 )
903 sigmaCalPlotPercentile = pexConfig.ListField(
904 doc="Magnitude percentile range to plot systematic error floor",
905 dtype=float,
906 default=(0.05, 0.95),
907 )
908 sigma0Phot = pexConfig.Field(
909 doc="Systematic error floor for all zeropoints",
910 dtype=float,
911 default=0.003,
912 )
913 mapLongitudeRef = pexConfig.Field(
914 doc="Reference longitude for plotting maps",
915 dtype=float,
916 default=0.0,
917 )
918 mapNSide = pexConfig.Field(
919 doc="Healpix nside for plotting maps",
920 dtype=int,
921 default=256,
922 )
923 outfileBase = pexConfig.Field(
924 doc="Filename start for plot output files",
925 dtype=str,
926 default=None,
927 )
928 starColorCuts = pexConfig.ListField(
929 doc=("Encoded star-color cuts (using calibration star colors). "
930 "This is a list with each entry a string of the format "
931 "``band1,band2,low,high`` such that only stars of color "
932 "low < band1 - band2 < high will be used for calibration."),
933 dtype=str,
934 default=("NO_DATA",),
935 )
936 refStarColorCuts = pexConfig.ListField(
937 doc=("Encoded star color cuts specifically to apply to reference stars. "
938 "This is a list with each entry a string of the format "
939 "``band1,band2,low,high`` such that only stars of color "
940 "low < band1 - band2 < high will be used as reference stars."),
941 dtype=str,
942 default=("NO_DATA",),
943 )
944 colorSplitBands = pexConfig.ListField(
945 doc="Band names to use to split stars by color. Must have 2 entries.",
946 dtype=str,
947 length=2,
948 default=('g', 'i'),
949 )
950 modelMagErrors = pexConfig.Field(
951 doc="Should FGCM model the magnitude errors from sky/fwhm? (False means trust inputs)",
952 dtype=bool,
953 default=True,
954 )
955 useQuadraticPwv = pexConfig.Field(
956 doc="Model PWV with a quadratic term for variation through the night?",
957 dtype=bool,
958 default=False,
959 )
960 instrumentParsPerBand = pexConfig.Field(
961 doc=("Model instrumental parameters per band? "
962 "Otherwise, instrumental parameters (QE changes with time) are "
963 "shared among all bands."),
964 dtype=bool,
965 default=False,
966 )
967 instrumentSlopeMinDeltaT = pexConfig.Field(
968 doc=("Minimum time change (in days) between observations to use in constraining "
969 "instrument slope."),
970 dtype=float,
971 default=20.0,
972 )
973 fitMirrorChromaticity = pexConfig.Field(
974 doc="Fit (intraband) mirror chromatic term?",
975 dtype=bool,
976 default=False,
977 )
978 fitCcdChromaticityDict = pexConfig.DictField(
979 doc="Specification on whether to compute first-order quantum efficiency (QE) "
980 "adjustments. Key is band, and value will be True or False. Any band "
981 "not explicitly specified will default to False.",
982 keytype=str,
983 itemtype=bool,
984 default={},
985 )
986 coatingMjds = pexConfig.ListField(
987 doc="Mirror coating dates in MJD",
988 dtype=float,
989 default=(0.0,),
990 )
991 outputStandardsBeforeFinalCycle = pexConfig.Field(
992 doc="Output standard stars prior to final cycle? Used in debugging.",
993 dtype=bool,
994 default=False,
995 )
996 outputZeropointsBeforeFinalCycle = pexConfig.Field(
997 doc="Output standard stars prior to final cycle? Used in debugging.",
998 dtype=bool,
999 default=False,
1000 )
1001 useRepeatabilityForExpGrayCutsDict = pexConfig.DictField(
1002 doc=("Per-band specification on whether to use star repeatability (instead of exposures) "
1003 "for computing photometric cuts. Recommended for tract mode or bands with few visits."),
1004 keytype=str,
1005 itemtype=bool,
1006 default={},
1007 )
1008 autoPhotometricCutNSig = pexConfig.Field(
1009 doc=("Number of sigma for automatic computation of (low) photometric cut. "
1010 "Cut is based on exposure gray width (per band), unless "
1011 "useRepeatabilityForExpGrayCuts is set, in which case the star "
1012 "repeatability is used (also per band)."),
1013 dtype=float,
1014 default=3.0,
1015 )
1016 autoHighCutNSig = pexConfig.Field(
1017 doc=("Number of sigma for automatic computation of (high) outlier cut. "
1018 "Cut is based on exposure gray width (per band), unless "
1019 "useRepeatabilityForExpGrayCuts is set, in which case the star "
1020 "repeatability is used (also per band)."),
1021 dtype=float,
1022 default=4.0,
1023 )
1024 quietMode = pexConfig.Field(
1025 doc="Be less verbose with logging.",
1026 dtype=bool,
1027 default=False,
1028 )
1029 doPlots = pexConfig.Field(
1030 doc="Make fgcm QA plots.",
1031 dtype=bool,
1032 default=True,
1033 )
1034 doPlotsBeforeFinalCycles = pexConfig.Field(
1035 doc="Make fgcm QA plots before the final two fit cycles? This only applies in"
1036 "multi-cycle mode, and if doPlots is True. These are typically the most"
1037 "important QA plots to inspect.",
1038 dtype=bool,
1039 default=False,
1040 )
1041 randomSeed = pexConfig.Field(
1042 doc="Random seed for fgcm for consistency in tests.",
1043 dtype=int,
1044 default=None,
1045 optional=True,
1046 )
1047 deltaAperFitMinNgoodObs = pexConfig.Field(
1048 doc="Minimum number of good observations to use mean delta-aper values in fits.",
1049 dtype=int,
1050 default=2,
1051 )
1052 deltaAperFitPerCcdNx = pexConfig.Field(
1053 doc=("Number of x bins per ccd when computing delta-aper background offsets. "
1054 "Only used when ``doComputeDeltaAperPerCcd`` is True."),
1055 dtype=int,
1056 default=10,
1057 )
1058 deltaAperFitPerCcdNy = pexConfig.Field(
1059 doc=("Number of y bins per ccd when computing delta-aper background offsets. "
1060 "Only used when ``doComputeDeltaAperPerCcd`` is True."),
1061 dtype=int,
1062 default=10,
1063 )
1064 deltaAperFitSpatialNside = pexConfig.Field(
1065 doc="Healpix nside to compute spatial delta-aper background offset maps.",
1066 dtype=int,
1067 default=64,
1068 )
1069 deltaAperInnerRadiusArcsec = pexConfig.Field(
1070 doc=("Inner radius used to compute deltaMagAper (arcseconds). "
1071 "Must be positive and less than ``deltaAperOuterRadiusArcsec`` if "
1072 "any of ``doComputeDeltaAperPerVisit``, ``doComputeDeltaAperPerStar``, "
1073 "``doComputeDeltaAperMap``, ``doComputeDeltaAperPerCcd`` are set."),
1074 dtype=float,
1075 default=0.0,
1076 )
1077 deltaAperOuterRadiusArcsec = pexConfig.Field(
1078 doc=("Outer radius used to compute deltaMagAper (arcseconds). "
1079 "Must be positive and greater than ``deltaAperInnerRadiusArcsec`` if "
1080 "any of ``doComputeDeltaAperPerVisit``, ``doComputeDeltaAperPerStar``, "
1081 "``doComputeDeltaAperMap``, ``doComputeDeltaAperPerCcd`` are set."),
1082 dtype=float,
1083 default=0.0,
1084 )
1085 doComputeDeltaAperPerVisit = pexConfig.Field(
1086 doc=("Do the computation of delta-aper background offsets per visit? "
1087 "Note: this option can be very slow when there are many visits."),
1088 dtype=bool,
1089 default=False,
1090 )
1091 doComputeDeltaAperPerStar = pexConfig.Field(
1092 doc="Do the computation of delta-aper mean values per star?",
1093 dtype=bool,
1094 default=True,
1095 )
1096 doComputeDeltaAperMap = pexConfig.Field(
1097 doc=("Do the computation of delta-aper spatial maps? "
1098 "This is only used if ``doComputeDeltaAperPerStar`` is True,"),
1099 dtype=bool,
1100 default=False,
1101 )
1102 doComputeDeltaAperPerCcd = pexConfig.Field(
1103 doc="Do the computation of per-ccd delta-aper background offsets?",
1104 dtype=bool,
1105 default=False,
1106 )
1107
1108 def validate(self):
1109 super().validate()
1110
1111 if self.connections.previousCycleNumber != str(self.cycleNumber - 1):
1112 msg = "cycleNumber in template must be connections.previousCycleNumber + 1"
1113 raise RuntimeError(msg)
1114 if self.connections.cycleNumber != str(self.cycleNumber):
1115 msg = "cycleNumber in template must be equal to connections.cycleNumber"
1116 raise RuntimeError(msg)
1117
1118 for band in self.fitBands:
1119 if band not in self.bands:
1120 msg = 'fitBand %s not in bands' % (band)
1121 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.fitBands, self, msg)
1122 for band in self.requiredBands:
1123 if band not in self.bands:
1124 msg = 'requiredBand %s not in bands' % (band)
1125 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.requiredBands, self, msg)
1126 for band in self.colorSplitBands:
1127 if band not in self.bands:
1128 msg = 'colorSplitBand %s not in bands' % (band)
1129 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.colorSplitBands, self, msg)
1130 for band in self.bands:
1131 if band not in self.superStarSubCcdDict:
1132 msg = 'band %s not in superStarSubCcdDict' % (band)
1133 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.superStarSubCcdDict,
1134 self, msg)
1135 if band not in self.ccdGraySubCcdDict:
1136 msg = 'band %s not in ccdGraySubCcdDict' % (band)
1137 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.ccdGraySubCcdDict,
1138 self, msg)
1139 if band not in self.expGrayPhotometricCutDict:
1140 msg = 'band %s not in expGrayPhotometricCutDict' % (band)
1141 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.expGrayPhotometricCutDict,
1142 self, msg)
1143 if band not in self.expGrayHighCutDict:
1144 msg = 'band %s not in expGrayHighCutDict' % (band)
1145 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.expGrayHighCutDict,
1146 self, msg)
1147 if band not in self.expVarGrayPhotometricCutDict:
1148 msg = 'band %s not in expVarGrayPhotometricCutDict' % (band)
1149 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.expVarGrayPhotometricCutDict,
1150 self, msg)
1151 if band not in self.sigFgcmMaxEGrayDict:
1152 msg = 'band %s not in sigFgcmMaxEGrayDict' % (band)
1153 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.sigFgcmMaxEGrayDict,
1154 self, msg)
1155 if band not in self.approxThroughputDict:
1156 msg = 'band %s not in approxThroughputDict' % (band)
1157 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.approxThroughputDict,
1158 self, msg)
1159 if band not in self.useRepeatabilityForExpGrayCutsDict:
1160 msg = 'band %s not in useRepeatabilityForExpGrayCutsDict' % (band)
1161 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.useRepeatabilityForExpGrayCutsDict,
1162 self, msg)
1163
1164 if self.doComputeDeltaAperPerVisit or self.doComputeDeltaAperMap \
1165 or self.doComputeDeltaAperPerCcd:
1166 if self.deltaAperInnerRadiusArcsec <= 0.0:
1167 msg = 'deltaAperInnerRadiusArcsec must be positive if deltaAper computations are turned on.'
1168 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.deltaAperInnerRadiusArcsec,
1169 self, msg)
1170 if self.deltaAperOuterRadiusArcsec <= 0.0:
1171 msg = 'deltaAperOuterRadiusArcsec must be positive if deltaAper computations are turned on.'
1172 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.deltaAperOuterRadiusArcsec,
1173 self, msg)
1174 if self.deltaAperOuterRadiusArcsec <= self.deltaAperInnerRadiusArcsec:
1175 msg = ('deltaAperOuterRadiusArcsec must be greater than deltaAperInnerRadiusArcsec if '
1176 'deltaAper computations are turned on.')
1177 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.deltaAperOuterRadiusArcsec,
1178 self, msg)
1179
1180
1181class FgcmFitCycleTask(pipeBase.PipelineTask):
1182 """
1183 Run Single fit cycle for FGCM global calibration
1184 """
1185
1186 ConfigClass = FgcmFitCycleConfig
1187 _DefaultName = "fgcmFitCycle"
1188
1189 def __init__(self, initInputs=None, **kwargs):
1190 super().__init__(**kwargs)
1191
1192 def runQuantum(self, butlerQC, inputRefs, outputRefs):
1193 camera = butlerQC.get(inputRefs.camera)
1194
1195 nCore = butlerQC.resources.num_cores
1196
1197 handleDict = {}
1198
1199 handleDict['fgcmLookUpTable'] = butlerQC.get(inputRefs.fgcmLookUpTable)
1200 handleDict['fgcmVisitCatalog'] = butlerQC.get(inputRefs.fgcmVisitCatalog)
1201
1202 if self.config.useParquetCatalogFormat:
1203 handleDict['fgcmStarObservations'] = butlerQC.get(inputRefs.fgcmStarObservationsParquet)
1204 handleDict['fgcmStarIds'] = butlerQC.get(inputRefs.fgcmStarIdsParquet)
1205 if self.config.doReferenceCalibration:
1206 handleDict['fgcmReferenceStars'] = butlerQC.get(inputRefs.fgcmReferenceStarsParquet)
1207 else:
1208 handleDict['fgcmStarObservations'] = butlerQC.get(inputRefs.fgcmStarObservations)
1209 handleDict['fgcmStarIds'] = butlerQC.get(inputRefs.fgcmStarIds)
1210 handleDict['fgcmStarIndices'] = butlerQC.get(inputRefs.fgcmStarIndices)
1211 if self.config.doReferenceCalibration:
1212 handleDict['fgcmReferenceStars'] = butlerQC.get(inputRefs.fgcmReferenceStars)
1213 if self.config.cycleNumber > 0:
1214 handleDict['fgcmFlaggedStars'] = butlerQC.get(inputRefs.fgcmFlaggedStarsInput)
1215 handleDict['fgcmFitParameters'] = butlerQC.get(inputRefs.fgcmFitParametersInput)
1216
1217 fgcmDatasetDict = None
1218 if self.config.doMultipleCycles:
1219 # Run multiple cycles at once.
1220 config = copy.copy(self.config)
1221 config.update(cycleNumber=0)
1222 for cycle in range(self.config.multipleCyclesFinalCycleNumber + 1):
1223 if cycle == self.config.multipleCyclesFinalCycleNumber:
1224 config.update(isFinalCycle=True)
1225
1226 if cycle > 0:
1227 handleDict['fgcmFlaggedStars'] = fgcmDatasetDict['fgcmFlaggedStars']
1228 handleDict['fgcmFitParameters'] = fgcmDatasetDict['fgcmFitParameters']
1229
1230 # Set up plot outputs.
1231 # Note that nothing will go in the dict if doPlots is False.
1232 plotHandleDict = {}
1233 for outputRefName in outputRefs.keys():
1234 if outputRefName.endswith("Plot") and f"Cycle{cycle}" in outputRefName:
1235 ref = getattr(outputRefs, outputRefName)
1236 if isinstance(ref, (tuple, list)):
1237 if "physical_filter" in ref[0].dimensions:
1238 for filterRef in ref:
1239 handleDictKey = f"{outputRefName}_{filterRef.dataId['physical_filter']}"
1240 plotHandleDict[handleDictKey] = filterRef
1241 if "band" in ref[0].dimensions:
1242 for bandRef in ref:
1243 handleDictKey = f"{outputRefName}_{bandRef.dataId['band']}"
1244 plotHandleDict[handleDictKey] = bandRef
1245 else:
1246 plotHandleDict[outputRefName] = ref
1247
1248 fgcmDatasetDict, config = self._fgcmFitCycle(
1249 camera,
1250 handleDict,
1251 butlerQC=butlerQC,
1252 plotHandleDict=plotHandleDict,
1253 config=config,
1254 nCore=nCore,
1255 )
1256 butlerQC.put(fgcmDatasetDict['fgcmFitParameters'],
1257 getattr(outputRefs, f'fgcm_Cycle{cycle}_FitParameters'))
1258 butlerQC.put(fgcmDatasetDict['fgcmFlaggedStars'],
1259 getattr(outputRefs, f'fgcm_Cycle{cycle}_FlaggedStars'))
1260 butlerQC.put(config,
1261 getattr(outputRefs, f'fgcm_Cycle{cycle}_OutputConfig'))
1262 if self.outputZeropoints:
1263 butlerQC.put(fgcmDatasetDict['fgcmZeropoints'],
1264 getattr(outputRefs, f'fgcm_Cycle{cycle}_Zeropoints'))
1265 butlerQC.put(fgcmDatasetDict['fgcmAtmosphereParameters'],
1266 getattr(outputRefs, f'fgcm_Cycle{cycle}_AtmosphereParameters'))
1267 if self.outputStandards:
1268 butlerQC.put(fgcmDatasetDict['fgcmStandardStars'],
1269 getattr(outputRefs, f'fgcm_Cycle{cycle}_StandardStars'))
1270 else:
1271 # Run a single cycle
1272
1273 # Set up plot outputs.
1274 # Note that nothing will go in the dict if doPlots is False.
1275 plotHandleDict = {}
1276 for outputRefName in outputRefs.keys():
1277 if outputRefName.endswith("Plot") and f"Cycle{self.config.cycleNumber}" in outputRefName:
1278 ref = getattr(outputRefs, outputRefName)
1279 if isinstance(ref, (tuple, list)):
1280 if "physical_filter" in ref[0].dimensions:
1281 for filterRef in ref:
1282 handleDictKey = f"{outputRefName}_{filterRef.dataId['physical_filter']}"
1283 plotHandleDict[handleDictKey] = filterRef
1284 if "band" in ref[0].dimensions:
1285 for bandRef in ref:
1286 handleDictKey = f"{outputRefName}_{bandRef.dataId['band']}"
1287 plotHandleDict[handleDictKey] = bandRef
1288 else:
1289 plotHandleDict[outputRefName] = ref
1290
1291 fgcmDatasetDict, _ = self._fgcmFitCycle(
1292 camera,
1293 handleDict,
1294 nCore=nCore,
1295 butlerQC=butlerQC,
1296 plotHandleDict=plotHandleDict,
1297 multiCycle=False,
1298 )
1299
1300 butlerQC.put(fgcmDatasetDict['fgcmFitParameters'], outputRefs.fgcmFitParameters)
1301 butlerQC.put(fgcmDatasetDict['fgcmFlaggedStars'], outputRefs.fgcmFlaggedStars)
1302 if self.outputZeropoints:
1303 butlerQC.put(fgcmDatasetDict['fgcmZeropoints'], outputRefs.fgcmZeropoints)
1304 butlerQC.put(fgcmDatasetDict['fgcmAtmosphereParameters'], outputRefs.fgcmAtmosphereParameters)
1305 if self.outputStandards:
1306 butlerQC.put(fgcmDatasetDict['fgcmStandardStars'], outputRefs.fgcmStandardStars)
1307
1308 def _fgcmFitCycle(
1309 self,
1310 camera,
1311 handleDict,
1312 butlerQC=None,
1313 plotHandleDict=None,
1314 config=None,
1315 nCore=1,
1316 multiCycle=True,
1317 ):
1318 """
1319 Run the fit cycle
1320
1321 Parameters
1322 ----------
1323 camera : `lsst.afw.cameraGeom.Camera`
1324 handleDict : `dict`
1325 All handles are `lsst.daf.butler.DeferredDatasetHandle`
1326 handle dictionary with keys:
1327
1328 ``"fgcmLookUpTable"``
1329 handle for the FGCM look-up table.
1330 ``"fgcmVisitCatalog"``
1331 handle for visit summary catalog.
1332 ``"fgcmStarObservations"``
1333 handle for star observation catalog.
1334 ``"fgcmStarIds"``
1335 handle for star id catalog.
1336 ``"fgcmStarIndices"``
1337 handle for star index catalog.
1338 ``"fgcmReferenceStars"``
1339 handle for matched reference star catalog.
1340 ``"fgcmFlaggedStars"``
1341 handle for flagged star catalog.
1342 ``"fgcmFitParameters"``
1343 handle for fit parameter catalog.
1344 butlerQC : `lsst.pipe.base.QuantumContext`, optional
1345 Quantum context used for serializing plots.
1346 plotHandleDict : `dict` [`str`, `lsst.daf.butler.DatasetRef`], optional
1347 Dictionary of plot dataset refs, keyed by plot name.
1348 config : `lsst.pex.config.Config`, optional
1349 Configuration to use to override self.config.
1350 nCore : `int`, optional
1351 Number of cores to use during fitting.
1352 multiCycle : `bool`, optional
1353 Is this part of a multicycle run?
1354
1355 Returns
1356 -------
1357 fgcmDatasetDict : `dict`
1358 Dictionary of datasets to persist.
1359 """
1360 if config is not None:
1361 _config = config
1362 else:
1363 _config = self.config
1364
1365 # Set defaults on whether to output standards and zeropoints
1366 self.maxIter = _config.maxIterBeforeFinalCycle
1367 self.outputStandards = _config.outputStandardsBeforeFinalCycle
1368 self.outputZeropoints = _config.outputZeropointsBeforeFinalCycle
1369 self.resetFitParameters = True
1370
1371 if _config.isFinalCycle:
1372 # This is the final fit cycle, so we do not want to reset fit
1373 # parameters, we want to run a final "clean-up" with 0 fit iterations,
1374 # and we always want to output standards and zeropoints
1375 self.maxIter = 0
1376 self.outputStandards = True
1377 self.outputZeropoints = True
1378 self.resetFitParameters = False
1379
1380 lutCat = handleDict['fgcmLookUpTable'].get()
1381 fgcmLut, lutIndexVals, lutStd = translateFgcmLut(lutCat,
1382 dict(_config.physicalFilterMap))
1383 del lutCat
1384
1385 # Check if we want to do plots.
1386 doPlots = _config.doPlots
1387 if doPlots and multiCycle:
1388 if _config.cycleNumber < (_config.multipleCyclesFinalCycleNumber - 1) \
1389 and not _config.doPlotsBeforeFinalCycles:
1390 doPlots = False
1391
1392 configDict = makeConfigDict(_config, self.log, camera,
1393 self.maxIter, self.resetFitParameters,
1394 self.outputZeropoints,
1395 lutIndexVals[0]['FILTERNAMES'],
1396 nCore=nCore,
1397 doPlots=doPlots)
1398
1399 # next we need the exposure/visit information
1400 visitCat = handleDict['fgcmVisitCatalog'].get()
1401 fgcmExpInfo = translateVisitCatalog(visitCat)
1402 del visitCat
1403
1404 focalPlaneProjector = FocalPlaneProjector(camera,
1405 self.config.defaultCameraOrientation)
1406
1407 noFitsDict = {'lutIndex': lutIndexVals,
1408 'lutStd': lutStd,
1409 'expInfo': fgcmExpInfo,
1410 'focalPlaneProjector': focalPlaneProjector}
1411
1412 # set up the fitter object
1413 fgcmFitCycle = fgcm.FgcmFitCycle(
1414 configDict,
1415 useFits=False,
1416 noFitsDict=noFitsDict,
1417 noOutput=True,
1418 butlerQC=butlerQC,
1419 plotHandleDict=plotHandleDict,
1420 )
1421
1422 # create the parameter object
1423 if (fgcmFitCycle.initialCycle):
1424 # cycle = 0, initial cycle
1425 fgcmPars = fgcm.FgcmParameters.newParsWithArrays(fgcmFitCycle.fgcmConfig,
1426 fgcmLut,
1427 fgcmExpInfo,
1428 butlerQC=butlerQC,
1429 plotHandleDict=plotHandleDict)
1430 else:
1431 if isinstance(handleDict['fgcmFitParameters'], afwTable.BaseCatalog):
1432 parCat = handleDict['fgcmFitParameters']
1433 else:
1434 parCat = handleDict['fgcmFitParameters'].get()
1435 inParInfo, inParams, inSuperStar = self._loadParameters(parCat)
1436 del parCat
1437 fgcmPars = fgcm.FgcmParameters.loadParsWithArrays(fgcmFitCycle.fgcmConfig,
1438 fgcmExpInfo,
1439 inParInfo,
1440 inParams,
1441 inSuperStar,
1442 butlerQC=butlerQC,
1443 plotHandleDict=plotHandleDict)
1444
1445 # set up the stars...
1446 fgcmStars = fgcm.FgcmStars(fgcmFitCycle.fgcmConfig, butlerQC=butlerQC, plotHandleDict=plotHandleDict)
1447
1448 starObs = handleDict['fgcmStarObservations'].get()
1449 starIds = handleDict['fgcmStarIds'].get()
1450 if not self.config.useParquetCatalogFormat:
1451 starIndices = handleDict['fgcmStarIndices'].get()
1452 else:
1453 starIndices = None
1454
1455 # grab the flagged stars if available
1456 if 'fgcmFlaggedStars' in handleDict:
1457 if isinstance(handleDict['fgcmFlaggedStars'], afwTable.BaseCatalog):
1458 flaggedStars = handleDict['fgcmFlaggedStars']
1459 else:
1460 flaggedStars = handleDict['fgcmFlaggedStars'].get()
1461 flagId = flaggedStars['objId'][:]
1462 flagFlag = flaggedStars['objFlag'][:]
1463
1464 del flaggedStars
1465 elif self.config.useParquetCatalogFormat:
1466 # If we are using the parquet catalog format, then that means that
1467 # reserved stars have already been flagged. We extract the flags here
1468 # to input to fgcm, which will then be persisted (with additional
1469 # quality flags) as the fgcmFlaggedStars datatype in subsequent
1470 # fit cycles.
1471 (flagged,) = (starIds['obj_flag'] > 0).nonzero()
1472 flagId = starIds['fgcm_id'][flagged]
1473 flagFlag = starIds['obj_flag'][flagged]
1474 else:
1475 flagId = None
1476 flagFlag = None
1477
1478 if _config.doReferenceCalibration:
1479 refStars = handleDict['fgcmReferenceStars'].get()
1480
1481 refMag, refMagErr = extractReferenceMags(refStars,
1482 _config.bands,
1483 _config.physicalFilterMap)
1484
1485 refId = refStars['fgcm_id'][:]
1486 else:
1487 refStars = None
1488 refId = None
1489 refMag = None
1490 refMagErr = None
1491
1492 # match star observations to visits
1493 # Only those star observations that match visits from fgcmExpInfo['VISIT'] will
1494 # actually be transferred into fgcm using the indexing below.
1495 if self.config.useParquetCatalogFormat:
1496 visitIndex = np.searchsorted(fgcmExpInfo['VISIT'], starObs['visit'])
1497 else:
1498 visitIndex = np.searchsorted(fgcmExpInfo['VISIT'], starObs['visit'][starIndices['obsIndex']])
1499
1500 # The fgcmStars.loadStars method will copy all the star information into
1501 # special shared memory objects that will not blow up the memory usage when
1502 # used with python multiprocessing. Once all the numbers are copied,
1503 # it is necessary to release all references to the objects that previously
1504 # stored the data to ensure that the garbage collector can clear the memory,
1505 # and ensure that this memory is not copied when multiprocessing kicks in.
1506
1507 if self.config.useParquetCatalogFormat:
1508 # Note that the ra/dec coordinates for the parquet format are in
1509 # degrees, which is what fgcm expects.
1510 fgcmStars.loadStars(fgcmPars,
1511 starObs['visit'][:],
1512 starObs['detector'][:],
1513 starObs['ra'][:],
1514 starObs['dec'][:],
1515 starObs['inst_mag'][:],
1516 starObs['inst_mag_err'][:],
1517 fgcmExpInfo['FILTERNAME'][visitIndex],
1518 starIds['fgcm_id'][:],
1519 starIds['ra'][:],
1520 starIds['dec'][:],
1521 starIds['obs_arr_index'][:],
1522 starIds['n_obs'][:],
1523 obsX=starObs['x'][:],
1524 obsY=starObs['y'][:],
1525 obsDeltaMagBkg=starObs['delta_mag_bkg'][:],
1526 obsDeltaAper=starObs['delta_mag_aper'][:],
1527 refID=refId,
1528 refMag=refMag,
1529 refMagErr=refMagErr,
1530 flagID=flagId,
1531 flagFlag=flagFlag,
1532 computeNobs=True,
1533 objIDAlternate=starIds['isolated_star_id'])
1534 else:
1535 # We determine the conversion from the native units (typically radians) to
1536 # degrees for the first star. This allows us to treat coord_ra/coord_dec as
1537 # numpy arrays rather than Angles, which would we approximately 600x slower.
1538 conv = starObs[0]['ra'].asDegrees() / float(starObs[0]['ra'])
1539
1540 fgcmStars.loadStars(fgcmPars,
1541 starObs['visit'][starIndices['obsIndex']],
1542 starObs['ccd'][starIndices['obsIndex']],
1543 starObs['ra'][starIndices['obsIndex']] * conv,
1544 starObs['dec'][starIndices['obsIndex']] * conv,
1545 starObs['instMag'][starIndices['obsIndex']],
1546 starObs['instMagErr'][starIndices['obsIndex']],
1547 fgcmExpInfo['FILTERNAME'][visitIndex],
1548 starIds['fgcm_id'][:],
1549 starIds['ra'][:],
1550 starIds['dec'][:],
1551 starIds['obsArrIndex'][:],
1552 starIds['nObs'][:],
1553 obsX=starObs['x'][starIndices['obsIndex']],
1554 obsY=starObs['y'][starIndices['obsIndex']],
1555 obsDeltaMagBkg=starObs['deltaMagBkg'][starIndices['obsIndex']],
1556 obsDeltaAper=starObs['deltaMagAper'][starIndices['obsIndex']],
1557 psfCandidate=starObs['psf_candidate'][starIndices['obsIndex']],
1558 refID=refId,
1559 refMag=refMag,
1560 refMagErr=refMagErr,
1561 flagID=flagId,
1562 flagFlag=flagFlag,
1563 computeNobs=True)
1564
1565 # Release all references to temporary objects holding star data (see above)
1566 del starObs
1567 del starIds
1568 del starIndices
1569 del flagId
1570 del flagFlag
1571 del refStars
1572 del refId
1573 del refMag
1574 del refMagErr
1575
1576 # and set the bits in the cycle object
1577 fgcmFitCycle.setLUT(fgcmLut)
1578 fgcmFitCycle.setStars(fgcmStars, fgcmPars)
1579 fgcmFitCycle.setPars(fgcmPars)
1580
1581 # finish the setup
1582 fgcmFitCycle.finishSetup()
1583
1584 # and run
1585 fgcmFitCycle.run()
1586
1587
1590
1591 fgcmDatasetDict = self._makeFgcmOutputDatasets(fgcmFitCycle)
1592
1593 # Output the config for the next cycle
1594 # We need to make a copy since the input one has been frozen
1595
1596 updatedPhotometricCutDict = {b: float(fgcmFitCycle.updatedPhotometricCut[i]) for
1597 i, b in enumerate(_config.bands)}
1598 updatedHighCutDict = {band: float(fgcmFitCycle.updatedHighCut[i]) for
1599 i, band in enumerate(_config.bands)}
1600
1601 outConfig = copy.copy(_config)
1602 outConfig.update(cycleNumber=(_config.cycleNumber + 1),
1603 precomputeSuperStarInitialCycle=False,
1604 freezeStdAtmosphere=False,
1605 expGrayPhotometricCutDict=updatedPhotometricCutDict,
1606 expGrayHighCutDict=updatedHighCutDict)
1607
1608 outConfig.connections.update(previousCycleNumber=str(_config.cycleNumber),
1609 cycleNumber=str(_config.cycleNumber + 1))
1610
1611 if not multiCycle:
1612 configFileName = '%s_cycle%02d_config.py' % (outConfig.outfileBase,
1613 outConfig.cycleNumber)
1614 outConfig.save(configFileName)
1615
1616 if _config.isFinalCycle == 1:
1617 # We are done, ready to output products
1618 self.log.info("Everything is in place to run fgcmOutputProducts.py")
1619 else:
1620 self.log.info("Saved config for next cycle to %s" % (configFileName))
1621 self.log.info("Be sure to look at:")
1622 self.log.info(" config.expGrayPhotometricCut")
1623 self.log.info(" config.expGrayHighCut")
1624 self.log.info("If you are satisfied with the fit, please set:")
1625 self.log.info(" config.isFinalCycle = True")
1626
1627 fgcmFitCycle.freeSharedMemory()
1628
1629 return fgcmDatasetDict, outConfig
1630
1631 def _loadParameters(self, parCat):
1632 """
1633 Load FGCM parameters from a previous fit cycle
1634
1635 Parameters
1636 ----------
1637 parCat : `lsst.afw.table.BaseCatalog`
1638 Parameter catalog in afw table form.
1639
1640 Returns
1641 -------
1642 inParInfo: `numpy.ndarray`
1643 Numpy array parameter information formatted for input to fgcm
1644 inParameters: `numpy.ndarray`
1645 Numpy array parameter values formatted for input to fgcm
1646 inSuperStar: `numpy.array`
1647 Superstar flat formatted for input to fgcm
1648 """
1649 parLutFilterNames = np.array(parCat[0]['lutFilterNames'].split(','))
1650 parFitBands = np.array(parCat[0]['fitBands'].split(','))
1651
1652 inParInfo = np.zeros(1, dtype=[('NCCD', 'i4'),
1653 ('LUTFILTERNAMES', parLutFilterNames.dtype.str,
1654 (parLutFilterNames.size, )),
1655 ('FITBANDS', parFitBands.dtype.str, (parFitBands.size, )),
1656 ('LNTAUUNIT', 'f8'),
1657 ('LNTAUSLOPEUNIT', 'f8'),
1658 ('ALPHAUNIT', 'f8'),
1659 ('LNPWVUNIT', 'f8'),
1660 ('LNPWVSLOPEUNIT', 'f8'),
1661 ('LNPWVQUADRATICUNIT', 'f8'),
1662 ('LNPWVGLOBALUNIT', 'f8'),
1663 ('O3UNIT', 'f8'),
1664 ('QESYSUNIT', 'f8'),
1665 ('FILTEROFFSETUNIT', 'f8'),
1666 ('HASEXTERNALPWV', 'i2'),
1667 ('HASEXTERNALTAU', 'i2')])
1668 inParInfo['NCCD'] = parCat['nCcd']
1669 inParInfo['LUTFILTERNAMES'][:] = parLutFilterNames
1670 inParInfo['FITBANDS'][:] = parFitBands
1671 inParInfo['HASEXTERNALPWV'] = parCat['hasExternalPwv']
1672 inParInfo['HASEXTERNALTAU'] = parCat['hasExternalTau']
1673
1674 inParams = np.zeros(1, dtype=[('PARALPHA', 'f8', (parCat['parAlpha'].size, )),
1675 ('PARO3', 'f8', (parCat['parO3'].size, )),
1676 ('PARLNTAUINTERCEPT', 'f8',
1677 (parCat['parLnTauIntercept'].size, )),
1678 ('PARLNTAUSLOPE', 'f8',
1679 (parCat['parLnTauSlope'].size, )),
1680 ('PARLNPWVINTERCEPT', 'f8',
1681 (parCat['parLnPwvIntercept'].size, )),
1682 ('PARLNPWVSLOPE', 'f8',
1683 (parCat['parLnPwvSlope'].size, )),
1684 ('PARLNPWVQUADRATIC', 'f8',
1685 (parCat['parLnPwvQuadratic'].size, )),
1686 ('PARQESYSINTERCEPT', 'f8',
1687 (parCat['parQeSysIntercept'].size, )),
1688 ('COMPQESYSSLOPE', 'f8',
1689 (parCat['compQeSysSlope'].size, )),
1690 ('PARFILTEROFFSET', 'f8',
1691 (parCat['parFilterOffset'].size, )),
1692 ('PARFILTEROFFSETFITFLAG', 'i2',
1693 (parCat['parFilterOffsetFitFlag'].size, )),
1694 ('PARRETRIEVEDLNPWVSCALE', 'f8'),
1695 ('PARRETRIEVEDLNPWVOFFSET', 'f8'),
1696 ('PARRETRIEVEDLNPWVNIGHTLYOFFSET', 'f8',
1697 (parCat['parRetrievedLnPwvNightlyOffset'].size, )),
1698 ('COMPABSTHROUGHPUT', 'f8',
1699 (parCat['compAbsThroughput'].size, )),
1700 ('COMPREFOFFSET', 'f8',
1701 (parCat['compRefOffset'].size, )),
1702 ('COMPREFSIGMA', 'f8',
1703 (parCat['compRefSigma'].size, )),
1704 ('COMPMIRRORCHROMATICITY', 'f8',
1705 (parCat['compMirrorChromaticity'].size, )),
1706 ('MIRRORCHROMATICITYPIVOT', 'f8',
1707 (parCat['mirrorChromaticityPivot'].size, )),
1708 ('COMPCCDCHROMATICITY', 'f8',
1709 (parCat['compCcdChromaticity'].size, )),
1710 ('COMPMEDIANSEDSLOPE', 'f8',
1711 (parCat['compMedianSedSlope'].size, )),
1712 ('COMPAPERCORRPIVOT', 'f8',
1713 (parCat['compAperCorrPivot'].size, )),
1714 ('COMPAPERCORRSLOPE', 'f8',
1715 (parCat['compAperCorrSlope'].size, )),
1716 ('COMPAPERCORRSLOPEERR', 'f8',
1717 (parCat['compAperCorrSlopeErr'].size, )),
1718 ('COMPAPERCORRRANGE', 'f8',
1719 (parCat['compAperCorrRange'].size, )),
1720 ('COMPMODELERREXPTIMEPIVOT', 'f8',
1721 (parCat['compModelErrExptimePivot'].size, )),
1722 ('COMPMODELERRFWHMPIVOT', 'f8',
1723 (parCat['compModelErrFwhmPivot'].size, )),
1724 ('COMPMODELERRSKYPIVOT', 'f8',
1725 (parCat['compModelErrSkyPivot'].size, )),
1726 ('COMPMODELERRPARS', 'f8',
1727 (parCat['compModelErrPars'].size, )),
1728 ('COMPEXPGRAY', 'f8',
1729 (parCat['compExpGray'].size, )),
1730 ('COMPVARGRAY', 'f8',
1731 (parCat['compVarGray'].size, )),
1732 ('COMPEXPDELTAMAGBKG', 'f8',
1733 (parCat['compExpDeltaMagBkg'].size, )),
1734 ('COMPNGOODSTARPEREXP', 'i4',
1735 (parCat['compNGoodStarPerExp'].size, )),
1736 ('COMPEXPREFOFFSET', 'f8',
1737 (parCat['compExpRefOffset'].size, )),
1738 ('COMPSIGFGCM', 'f8',
1739 (parCat['compSigFgcm'].size, )),
1740 ('COMPSIGMACAL', 'f8',
1741 (parCat['compSigmaCal'].size, )),
1742 ('COMPRETRIEVEDLNPWV', 'f8',
1743 (parCat['compRetrievedLnPwv'].size, )),
1744 ('COMPRETRIEVEDLNPWVRAW', 'f8',
1745 (parCat['compRetrievedLnPwvRaw'].size, )),
1746 ('COMPRETRIEVEDLNPWVFLAG', 'i2',
1747 (parCat['compRetrievedLnPwvFlag'].size, )),
1748 ('COMPRETRIEVEDTAUNIGHT', 'f8',
1749 (parCat['compRetrievedTauNight'].size, )),
1750 ('COMPEPSILON', 'f8',
1751 (parCat['compEpsilon'].size, )),
1752 ('COMPMEDDELTAAPER', 'f8',
1753 (parCat['compMedDeltaAper'].size, )),
1754 ('COMPGLOBALEPSILON', 'f4',
1755 (parCat['compGlobalEpsilon'].size, )),
1756 ('COMPEPSILONMAP', 'f4',
1757 (parCat['compEpsilonMap'].size, )),
1758 ('COMPEPSILONNSTARMAP', 'i4',
1759 (parCat['compEpsilonNStarMap'].size, )),
1760 ('COMPEPSILONCCDMAP', 'f4',
1761 (parCat['compEpsilonCcdMap'].size, )),
1762 ('COMPEPSILONCCDNSTARMAP', 'i4',
1763 (parCat['compEpsilonCcdNStarMap'].size, ))])
1764
1765 inParams['PARALPHA'][:] = parCat['parAlpha'][0, :]
1766 inParams['PARO3'][:] = parCat['parO3'][0, :]
1767 inParams['PARLNTAUINTERCEPT'][:] = parCat['parLnTauIntercept'][0, :]
1768 inParams['PARLNTAUSLOPE'][:] = parCat['parLnTauSlope'][0, :]
1769 inParams['PARLNPWVINTERCEPT'][:] = parCat['parLnPwvIntercept'][0, :]
1770 inParams['PARLNPWVSLOPE'][:] = parCat['parLnPwvSlope'][0, :]
1771 inParams['PARLNPWVQUADRATIC'][:] = parCat['parLnPwvQuadratic'][0, :]
1772 inParams['PARQESYSINTERCEPT'][:] = parCat['parQeSysIntercept'][0, :]
1773 inParams['COMPQESYSSLOPE'][:] = parCat['compQeSysSlope'][0, :]
1774 inParams['PARFILTEROFFSET'][:] = parCat['parFilterOffset'][0, :]
1775 inParams['PARFILTEROFFSETFITFLAG'][:] = parCat['parFilterOffsetFitFlag'][0, :]
1776 inParams['PARRETRIEVEDLNPWVSCALE'] = parCat['parRetrievedLnPwvScale']
1777 inParams['PARRETRIEVEDLNPWVOFFSET'] = parCat['parRetrievedLnPwvOffset']
1778 inParams['PARRETRIEVEDLNPWVNIGHTLYOFFSET'][:] = parCat['parRetrievedLnPwvNightlyOffset'][0, :]
1779 inParams['COMPABSTHROUGHPUT'][:] = parCat['compAbsThroughput'][0, :]
1780 inParams['COMPREFOFFSET'][:] = parCat['compRefOffset'][0, :]
1781 inParams['COMPREFSIGMA'][:] = parCat['compRefSigma'][0, :]
1782 inParams['COMPMIRRORCHROMATICITY'][:] = parCat['compMirrorChromaticity'][0, :]
1783 inParams['MIRRORCHROMATICITYPIVOT'][:] = parCat['mirrorChromaticityPivot'][0, :]
1784 inParams['COMPCCDCHROMATICITY'][:] = parCat['compCcdChromaticity'][0, :]
1785 inParams['COMPMEDIANSEDSLOPE'][:] = parCat['compMedianSedSlope'][0, :]
1786 inParams['COMPAPERCORRPIVOT'][:] = parCat['compAperCorrPivot'][0, :]
1787 inParams['COMPAPERCORRSLOPE'][:] = parCat['compAperCorrSlope'][0, :]
1788 inParams['COMPAPERCORRSLOPEERR'][:] = parCat['compAperCorrSlopeErr'][0, :]
1789 inParams['COMPAPERCORRRANGE'][:] = parCat['compAperCorrRange'][0, :]
1790 inParams['COMPMODELERREXPTIMEPIVOT'][:] = parCat['compModelErrExptimePivot'][0, :]
1791 inParams['COMPMODELERRFWHMPIVOT'][:] = parCat['compModelErrFwhmPivot'][0, :]
1792 inParams['COMPMODELERRSKYPIVOT'][:] = parCat['compModelErrSkyPivot'][0, :]
1793 inParams['COMPMODELERRPARS'][:] = parCat['compModelErrPars'][0, :]
1794 inParams['COMPEXPGRAY'][:] = parCat['compExpGray'][0, :]
1795 inParams['COMPVARGRAY'][:] = parCat['compVarGray'][0, :]
1796 inParams['COMPEXPDELTAMAGBKG'][:] = parCat['compExpDeltaMagBkg'][0, :]
1797 inParams['COMPNGOODSTARPEREXP'][:] = parCat['compNGoodStarPerExp'][0, :]
1798 inParams['COMPEXPREFOFFSET'][:] = parCat['compExpRefOffset'][0, :]
1799 inParams['COMPSIGFGCM'][:] = parCat['compSigFgcm'][0, :]
1800 inParams['COMPSIGMACAL'][:] = parCat['compSigmaCal'][0, :]
1801 inParams['COMPRETRIEVEDLNPWV'][:] = parCat['compRetrievedLnPwv'][0, :]
1802 inParams['COMPRETRIEVEDLNPWVRAW'][:] = parCat['compRetrievedLnPwvRaw'][0, :]
1803 inParams['COMPRETRIEVEDLNPWVFLAG'][:] = parCat['compRetrievedLnPwvFlag'][0, :]
1804 inParams['COMPRETRIEVEDTAUNIGHT'][:] = parCat['compRetrievedTauNight'][0, :]
1805 inParams['COMPEPSILON'][:] = parCat['compEpsilon'][0, :]
1806 inParams['COMPMEDDELTAAPER'][:] = parCat['compMedDeltaAper'][0, :]
1807 inParams['COMPGLOBALEPSILON'][:] = parCat['compGlobalEpsilon'][0, :]
1808 inParams['COMPEPSILONMAP'][:] = parCat['compEpsilonMap'][0, :]
1809 inParams['COMPEPSILONNSTARMAP'][:] = parCat['compEpsilonNStarMap'][0, :]
1810 inParams['COMPEPSILONCCDMAP'][:] = parCat['compEpsilonCcdMap'][0, :]
1811 inParams['COMPEPSILONCCDNSTARMAP'][:] = parCat['compEpsilonCcdNStarMap'][0, :]
1812
1813 inSuperStar = np.zeros(parCat['superstarSize'][0, :], dtype='f8')
1814 inSuperStar[:, :, :, :] = parCat['superstar'][0, :].reshape(inSuperStar.shape)
1815
1816 return (inParInfo, inParams, inSuperStar)
1817
1818 def _makeFgcmOutputDatasets(self, fgcmFitCycle):
1819 """
1820 Persist FGCM datasets through the butler.
1821
1822 Parameters
1823 ----------
1824 fgcmFitCycle: `lsst.fgcm.FgcmFitCycle`
1825 Fgcm Fit cycle object
1826 """
1827 fgcmDatasetDict = {}
1828
1829 # Save the parameters
1830 parInfo, pars = fgcmFitCycle.fgcmPars.parsToArrays()
1831
1832 parSchema = afwTable.Schema()
1833
1834 comma = ','
1835 lutFilterNameString = comma.join([n.decode('utf-8')
1836 for n in parInfo['LUTFILTERNAMES'][0]])
1837 fitBandString = comma.join([n.decode('utf-8')
1838 for n in parInfo['FITBANDS'][0]])
1839
1840 parSchema = self._makeParSchema(parInfo, pars, fgcmFitCycle.fgcmPars.parSuperStarFlat,
1841 lutFilterNameString, fitBandString)
1842 parCat = self._makeParCatalog(parSchema, parInfo, pars,
1843 fgcmFitCycle.fgcmPars.parSuperStarFlat,
1844 lutFilterNameString, fitBandString)
1845
1846 fgcmDatasetDict['fgcmFitParameters'] = parCat
1847
1848 # Save the indices of the flagged stars
1849 # (stars that have been (a) reserved from the fit for testing and
1850 # (b) bad stars that have failed quality checks.)
1851 flagStarSchema = self._makeFlagStarSchema()
1852 flagStarStruct = fgcmFitCycle.fgcmStars.getFlagStarIndices()
1853 flagStarCat = self._makeFlagStarCat(flagStarSchema, flagStarStruct)
1854
1855 fgcmDatasetDict['fgcmFlaggedStars'] = flagStarCat
1856
1857 # Save the zeropoint information and atmospheres only if desired
1858 if self.outputZeropoints:
1859 superStarChebSize = fgcmFitCycle.fgcmZpts.zpStruct['FGCM_FZPT_SSTAR_CHEB'].shape[1]
1860 zptChebSize = fgcmFitCycle.fgcmZpts.zpStruct['FGCM_FZPT_CHEB'].shape[1]
1861
1862 zptSchema = makeZptSchema(superStarChebSize, zptChebSize)
1863 zptCat = makeZptCat(zptSchema, fgcmFitCycle.fgcmZpts.zpStruct)
1864
1865 fgcmDatasetDict['fgcmZeropoints'] = zptCat
1866
1867 # Save atmosphere values
1868 # These are generated by the same code that generates zeropoints
1869 atmSchema = makeAtmSchema()
1870 atmCat = makeAtmCat(atmSchema, fgcmFitCycle.fgcmZpts.atmStruct)
1871
1872 fgcmDatasetDict['fgcmAtmosphereParameters'] = atmCat
1873
1874 # Save the standard stars (if configured)
1875 if self.outputStandards:
1876 stdStruct, goodBands = fgcmFitCycle.fgcmStars.retrieveStdStarCatalog(fgcmFitCycle.fgcmPars)
1877 stdSchema = makeStdSchema(len(goodBands))
1878 stdCat = makeStdCat(stdSchema, stdStruct, goodBands)
1879
1880 fgcmDatasetDict['fgcmStandardStars'] = stdCat
1881
1882 return fgcmDatasetDict
1883
1884 def _makeParSchema(self, parInfo, pars, parSuperStarFlat,
1885 lutFilterNameString, fitBandString):
1886 """
1887 Make the parameter persistence schema
1888
1889 Parameters
1890 ----------
1891 parInfo: `numpy.ndarray`
1892 Parameter information returned by fgcm
1893 pars: `numpy.ndarray`
1894 Parameter values returned by fgcm
1895 parSuperStarFlat: `numpy.array`
1896 Superstar flat values returned by fgcm
1897 lutFilterNameString: `str`
1898 Combined string of all the lutFilterNames
1899 fitBandString: `str`
1900 Combined string of all the fitBands
1901
1902 Returns
1903 -------
1904 parSchema: `afwTable.schema`
1905 """
1906
1907 parSchema = afwTable.Schema()
1908
1909 # parameter info section
1910 parSchema.addField('nCcd', type=np.int32, doc='Number of CCDs')
1911 parSchema.addField('lutFilterNames', type=str, doc='LUT Filter names in parameter file',
1912 size=len(lutFilterNameString))
1913 parSchema.addField('fitBands', type=str, doc='Bands that were fit',
1914 size=len(fitBandString))
1915 parSchema.addField('lnTauUnit', type=np.float64, doc='Step units for ln(AOD)')
1916 parSchema.addField('lnTauSlopeUnit', type=np.float64,
1917 doc='Step units for ln(AOD) slope')
1918 parSchema.addField('alphaUnit', type=np.float64, doc='Step units for alpha')
1919 parSchema.addField('lnPwvUnit', type=np.float64, doc='Step units for ln(pwv)')
1920 parSchema.addField('lnPwvSlopeUnit', type=np.float64,
1921 doc='Step units for ln(pwv) slope')
1922 parSchema.addField('lnPwvQuadraticUnit', type=np.float64,
1923 doc='Step units for ln(pwv) quadratic term')
1924 parSchema.addField('lnPwvGlobalUnit', type=np.float64,
1925 doc='Step units for global ln(pwv) parameters')
1926 parSchema.addField('o3Unit', type=np.float64, doc='Step units for O3')
1927 parSchema.addField('qeSysUnit', type=np.float64, doc='Step units for mirror gray')
1928 parSchema.addField('filterOffsetUnit', type=np.float64, doc='Step units for filter offset')
1929 parSchema.addField('hasExternalPwv', type=np.int32, doc='Parameters fit using external pwv')
1930 parSchema.addField('hasExternalTau', type=np.int32, doc='Parameters fit using external tau')
1931
1932 # parameter section
1933 parSchema.addField('parAlpha', type='ArrayD', doc='Alpha parameter vector',
1934 size=pars['PARALPHA'].size)
1935 parSchema.addField('parO3', type='ArrayD', doc='O3 parameter vector',
1936 size=pars['PARO3'].size)
1937 parSchema.addField('parLnTauIntercept', type='ArrayD',
1938 doc='ln(Tau) intercept parameter vector',
1939 size=pars['PARLNTAUINTERCEPT'].size)
1940 parSchema.addField('parLnTauSlope', type='ArrayD',
1941 doc='ln(Tau) slope parameter vector',
1942 size=pars['PARLNTAUSLOPE'].size)
1943 parSchema.addField('parLnPwvIntercept', type='ArrayD', doc='ln(pwv) intercept parameter vector',
1944 size=pars['PARLNPWVINTERCEPT'].size)
1945 parSchema.addField('parLnPwvSlope', type='ArrayD', doc='ln(pwv) slope parameter vector',
1946 size=pars['PARLNPWVSLOPE'].size)
1947 parSchema.addField('parLnPwvQuadratic', type='ArrayD', doc='ln(pwv) quadratic parameter vector',
1948 size=pars['PARLNPWVQUADRATIC'].size)
1949 parSchema.addField('parQeSysIntercept', type='ArrayD', doc='Mirror gray intercept parameter vector',
1950 size=pars['PARQESYSINTERCEPT'].size)
1951 parSchema.addField('compQeSysSlope', type='ArrayD', doc='Mirror gray slope parameter vector',
1952 size=pars[0]['COMPQESYSSLOPE'].size)
1953 parSchema.addField('parFilterOffset', type='ArrayD', doc='Filter offset parameter vector',
1954 size=pars['PARFILTEROFFSET'].size)
1955 parSchema.addField('parFilterOffsetFitFlag', type='ArrayI', doc='Filter offset parameter fit flag',
1956 size=pars['PARFILTEROFFSETFITFLAG'].size)
1957 parSchema.addField('parRetrievedLnPwvScale', type=np.float64,
1958 doc='Global scale for retrieved ln(pwv)')
1959 parSchema.addField('parRetrievedLnPwvOffset', type=np.float64,
1960 doc='Global offset for retrieved ln(pwv)')
1961 parSchema.addField('parRetrievedLnPwvNightlyOffset', type='ArrayD',
1962 doc='Nightly offset for retrieved ln(pwv)',
1963 size=pars['PARRETRIEVEDLNPWVNIGHTLYOFFSET'].size)
1964 parSchema.addField('compAbsThroughput', type='ArrayD',
1965 doc='Absolute throughput (relative to transmission curves)',
1966 size=pars['COMPABSTHROUGHPUT'].size)
1967 parSchema.addField('compRefOffset', type='ArrayD',
1968 doc='Offset between reference stars and calibrated stars',
1969 size=pars['COMPREFOFFSET'].size)
1970 parSchema.addField('compRefSigma', type='ArrayD',
1971 doc='Width of reference star/calibrated star distribution',
1972 size=pars['COMPREFSIGMA'].size)
1973 parSchema.addField('compMirrorChromaticity', type='ArrayD',
1974 doc='Computed mirror chromaticity terms',
1975 size=pars['COMPMIRRORCHROMATICITY'].size)
1976 parSchema.addField('mirrorChromaticityPivot', type='ArrayD',
1977 doc='Mirror chromaticity pivot mjd',
1978 size=pars['MIRRORCHROMATICITYPIVOT'].size)
1979 parSchema.addField('compCcdChromaticity', type='ArrayD',
1980 doc='Computed CCD chromaticity terms',
1981 size=pars['COMPCCDCHROMATICITY'].size)
1982 parSchema.addField('compMedianSedSlope', type='ArrayD',
1983 doc='Computed median SED slope (per band)',
1984 size=pars['COMPMEDIANSEDSLOPE'].size)
1985 parSchema.addField('compAperCorrPivot', type='ArrayD', doc='Aperture correction pivot',
1986 size=pars['COMPAPERCORRPIVOT'].size)
1987 parSchema.addField('compAperCorrSlope', type='ArrayD', doc='Aperture correction slope',
1988 size=pars['COMPAPERCORRSLOPE'].size)
1989 parSchema.addField('compAperCorrSlopeErr', type='ArrayD', doc='Aperture correction slope error',
1990 size=pars['COMPAPERCORRSLOPEERR'].size)
1991 parSchema.addField('compAperCorrRange', type='ArrayD', doc='Aperture correction range',
1992 size=pars['COMPAPERCORRRANGE'].size)
1993 parSchema.addField('compModelErrExptimePivot', type='ArrayD', doc='Model error exptime pivot',
1994 size=pars['COMPMODELERREXPTIMEPIVOT'].size)
1995 parSchema.addField('compModelErrFwhmPivot', type='ArrayD', doc='Model error fwhm pivot',
1996 size=pars['COMPMODELERRFWHMPIVOT'].size)
1997 parSchema.addField('compModelErrSkyPivot', type='ArrayD', doc='Model error sky pivot',
1998 size=pars['COMPMODELERRSKYPIVOT'].size)
1999 parSchema.addField('compModelErrPars', type='ArrayD', doc='Model error parameters',
2000 size=pars['COMPMODELERRPARS'].size)
2001 parSchema.addField('compExpGray', type='ArrayD', doc='Computed exposure gray',
2002 size=pars['COMPEXPGRAY'].size)
2003 parSchema.addField('compVarGray', type='ArrayD', doc='Computed exposure variance',
2004 size=pars['COMPVARGRAY'].size)
2005 parSchema.addField('compExpDeltaMagBkg', type='ArrayD',
2006 doc='Computed exposure offset due to background',
2007 size=pars['COMPEXPDELTAMAGBKG'].size)
2008 parSchema.addField('compNGoodStarPerExp', type='ArrayI',
2009 doc='Computed number of good stars per exposure',
2010 size=pars['COMPNGOODSTARPEREXP'].size)
2011 parSchema.addField('compExpRefOffset', type='ArrayD',
2012 doc='Computed per-visit median offset between standard stars and ref stars.',
2013 size=pars['COMPEXPREFOFFSET'].size)
2014 parSchema.addField('compSigFgcm', type='ArrayD', doc='Computed sigma_fgcm (intrinsic repeatability)',
2015 size=pars['COMPSIGFGCM'].size)
2016 parSchema.addField('compSigmaCal', type='ArrayD', doc='Computed sigma_cal (systematic error floor)',
2017 size=pars['COMPSIGMACAL'].size)
2018 parSchema.addField('compRetrievedLnPwv', type='ArrayD', doc='Retrieved ln(pwv) (smoothed)',
2019 size=pars['COMPRETRIEVEDLNPWV'].size)
2020 parSchema.addField('compRetrievedLnPwvRaw', type='ArrayD', doc='Retrieved ln(pwv) (raw)',
2021 size=pars['COMPRETRIEVEDLNPWVRAW'].size)
2022 parSchema.addField('compRetrievedLnPwvFlag', type='ArrayI', doc='Retrieved ln(pwv) Flag',
2023 size=pars['COMPRETRIEVEDLNPWVFLAG'].size)
2024 parSchema.addField('compRetrievedTauNight', type='ArrayD', doc='Retrieved tau (per night)',
2025 size=pars['COMPRETRIEVEDTAUNIGHT'].size)
2026 parSchema.addField('compEpsilon', type='ArrayD',
2027 doc='Computed epsilon background offset per visit (nJy/arcsec2)',
2028 size=pars['COMPEPSILON'].size)
2029 parSchema.addField('compMedDeltaAper', type='ArrayD',
2030 doc='Median delta mag aper per visit',
2031 size=pars['COMPMEDDELTAAPER'].size)
2032 parSchema.addField('compGlobalEpsilon', type='ArrayD',
2033 doc='Computed epsilon bkg offset (global) (nJy/arcsec2)',
2034 size=pars['COMPGLOBALEPSILON'].size)
2035 parSchema.addField('compEpsilonMap', type='ArrayD',
2036 doc='Computed epsilon maps (nJy/arcsec2)',
2037 size=pars['COMPEPSILONMAP'].size)
2038 parSchema.addField('compEpsilonNStarMap', type='ArrayI',
2039 doc='Number of stars per pixel in computed epsilon maps',
2040 size=pars['COMPEPSILONNSTARMAP'].size)
2041 parSchema.addField('compEpsilonCcdMap', type='ArrayD',
2042 doc='Computed epsilon ccd maps (nJy/arcsec2)',
2043 size=pars['COMPEPSILONCCDMAP'].size)
2044 parSchema.addField('compEpsilonCcdNStarMap', type='ArrayI',
2045 doc='Number of stars per ccd bin in epsilon ccd maps',
2046 size=pars['COMPEPSILONCCDNSTARMAP'].size)
2047 parSchema.addField('epochMjdStart', type='ArrayD',
2048 doc='Epoch MJD start times',
2049 size=pars['EPOCHMJDSTART'].size)
2050 parSchema.addField('epochMjdEnd', type='ArrayD',
2051 doc='EpochMJD end times',
2052 size=pars['EPOCHMJDEND'].size)
2053 # superstarflat section
2054 parSchema.addField('superstarSize', type='ArrayI', doc='Superstar matrix size',
2055 size=4)
2056 parSchema.addField('superstar', type='ArrayD', doc='Superstar matrix (flattened)',
2057 size=parSuperStarFlat.size)
2058
2059 return parSchema
2060
2061 def _makeParCatalog(self, parSchema, parInfo, pars, parSuperStarFlat,
2062 lutFilterNameString, fitBandString):
2063 """
2064 Make the FGCM parameter catalog for persistence
2065
2066 Parameters
2067 ----------
2068 parSchema: `lsst.afw.table.Schema`
2069 Parameter catalog schema
2070 pars: `numpy.ndarray`
2071 FGCM parameters to put into parCat
2072 parSuperStarFlat: `numpy.array`
2073 FGCM superstar flat array to put into parCat
2074 lutFilterNameString: `str`
2075 Combined string of all the lutFilterNames
2076 fitBandString: `str`
2077 Combined string of all the fitBands
2078
2079 Returns
2080 -------
2081 parCat: `afwTable.BasicCatalog`
2082 Atmosphere and instrumental model parameter catalog for persistence
2083 """
2084
2085 parCat = afwTable.BaseCatalog(parSchema)
2086 parCat.reserve(1)
2087
2088 # The parameter catalog just has one row, with many columns for all the
2089 # atmosphere and instrument fit parameters
2090 rec = parCat.addNew()
2091
2092 # info section
2093 rec['nCcd'] = parInfo['NCCD'][0]
2094 rec['lutFilterNames'] = lutFilterNameString
2095 rec['fitBands'] = fitBandString
2096 # note these are not currently supported here.
2097 rec['hasExternalPwv'] = 0
2098 rec['hasExternalTau'] = 0
2099
2100 # parameter section
2101
2102 scalarNames = ['parRetrievedLnPwvScale', 'parRetrievedLnPwvOffset']
2103
2104 arrNames = ['parAlpha', 'parO3', 'parLnTauIntercept', 'parLnTauSlope',
2105 'parLnPwvIntercept', 'parLnPwvSlope', 'parLnPwvQuadratic',
2106 'parQeSysIntercept', 'compQeSysSlope',
2107 'parRetrievedLnPwvNightlyOffset', 'compAperCorrPivot',
2108 'parFilterOffset', 'parFilterOffsetFitFlag',
2109 'compAbsThroughput', 'compRefOffset', 'compRefSigma',
2110 'compMirrorChromaticity', 'mirrorChromaticityPivot', 'compCcdChromaticity',
2111 'compAperCorrSlope', 'compAperCorrSlopeErr', 'compAperCorrRange',
2112 'compModelErrExptimePivot', 'compModelErrFwhmPivot',
2113 'compModelErrSkyPivot', 'compModelErrPars',
2114 'compExpGray', 'compVarGray', 'compNGoodStarPerExp', 'compSigFgcm',
2115 'compSigmaCal', 'compExpDeltaMagBkg', 'compMedianSedSlope',
2116 'compRetrievedLnPwv', 'compRetrievedLnPwvRaw', 'compRetrievedLnPwvFlag',
2117 'compRetrievedTauNight', 'compEpsilon', 'compMedDeltaAper',
2118 'compGlobalEpsilon', 'compEpsilonMap', 'compEpsilonNStarMap',
2119 'compEpsilonCcdMap', 'compEpsilonCcdNStarMap', 'compExpRefOffset',
2120 'epochMjdStart', 'epochMjdEnd']
2121
2122 for scalarName in scalarNames:
2123 rec[scalarName] = pars[scalarName.upper()][0]
2124
2125 for arrName in arrNames:
2126 rec[arrName][:] = np.atleast_1d(pars[0][arrName.upper()])[:]
2127
2128 # superstar section
2129 rec['superstarSize'][:] = parSuperStarFlat.shape
2130 rec['superstar'][:] = parSuperStarFlat.ravel()
2131
2132 return parCat
2133
2134 def _makeFlagStarSchema(self):
2135 """
2136 Make the flagged-stars schema
2137
2138 Returns
2139 -------
2140 flagStarSchema: `lsst.afw.table.Schema`
2141 """
2142
2143 flagStarSchema = afwTable.Schema()
2144
2145 flagStarSchema.addField('objId', type=np.int32, doc='FGCM object id')
2146 flagStarSchema.addField('objFlag', type=np.int32, doc='FGCM object flag')
2147
2148 return flagStarSchema
2149
2150 def _makeFlagStarCat(self, flagStarSchema, flagStarStruct):
2151 """
2152 Make the flagged star catalog for persistence
2153
2154 Parameters
2155 ----------
2156 flagStarSchema: `lsst.afw.table.Schema`
2157 Flagged star schema
2158 flagStarStruct: `numpy.ndarray`
2159 Flagged star structure from fgcm
2160
2161 Returns
2162 -------
2163 flagStarCat: `lsst.afw.table.BaseCatalog`
2164 Flagged star catalog for persistence
2165 """
2166
2167 flagStarCat = afwTable.BaseCatalog(flagStarSchema)
2168 flagStarCat.resize(flagStarStruct.size)
2169
2170 flagStarCat['objId'][:] = flagStarStruct['OBJID']
2171 flagStarCat['objFlag'][:] = flagStarStruct['OBJFLAG']
2172
2173 return flagStarCat
Defines the fields and offsets for a table.
Definition Schema.h:51