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