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