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