lsst.fgcmcal g5a14c14915+8662184370
Loading...
Searching...
No Matches
utilities.py
Go to the documentation of this file.
1# This file is part of fgcmcal.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21"""Utility functions for fgcmcal.
22
23This file contains utility functions that are used by more than one task,
24and do not need to be part of a task.
25"""
26
27import numpy as np
28import os
29import re
30
31from lsst.daf.base import PropertyList
32from lsst.daf.butler import Timespan
33import lsst.afw.cameraGeom as afwCameraGeom
34import lsst.afw.table as afwTable
35import lsst.afw.image as afwImage
36import lsst.afw.math as afwMath
37import lsst.geom as geom
38from lsst.obs.base import createInitialSkyWcs
39
40import fgcm
41
42
43FGCM_EXP_FIELD = 'VISIT'
44FGCM_CCD_FIELD = 'DETECTOR'
45FGCM_ILLEGAL_VALUE = -9999.0
46
47
48def makeConfigDict(config, log, camera, maxIter,
49 resetFitParameters, outputZeropoints,
50 lutFilterNames, tract=None, nCore=1, doPlots=False):
51 """
52 Make the FGCM fit cycle configuration dict
53
54 Parameters
55 ----------
56 config : `lsst.fgcmcal.FgcmFitCycleConfig`
57 Configuration object
58 log : `logging.Logger`
59 Log object.
60 camera : `lsst.afw.cameraGeom.Camera`
61 Camera from the butler
62 maxIter : `int`
63 Maximum number of iterations
64 resetFitParameters: `bool`
65 Reset fit parameters before fitting?
66 outputZeropoints : `bool`
67 Compute zeropoints for output?
68 lutFilterNames : array-like, `str`
69 Array of physical filter names in the LUT.
70 tract : `int`, optional
71 Tract number for extending the output file name for debugging.
72 Default is None.
73 nCore : `int`, optional
74 Number of cores to use.
75 doPlots : `bool`, optional
76 Make FGCM QA plots?
77
78 Returns
79 -------
80 configDict : `dict`
81 Configuration dictionary for fgcm
82 """
83 # Extract the bands that are _not_ being fit for fgcm configuration
84 notFitBands = [b for b in config.bands if b not in config.fitBands]
85
86 # process the starColorCuts
87 starColorCutList = []
88 for ccut in config.starColorCuts:
89 if ccut == 'NO_DATA':
90 # No color cuts to apply.
91 break
92 parts = ccut.split(',')
93 starColorCutList.append([parts[0], parts[1], float(parts[2]), float(parts[3])])
94
95 # process the refStarColorCuts
96 refStarColorCutList = []
97 for ccut in config.refStarColorCuts:
98 if ccut == 'NO_DATA':
99 # No color cuts to apply.
100 break
101 parts = ccut.split(',')
102 refStarColorCutList.append([parts[0], parts[1], float(parts[2]), float(parts[3])])
103
104 # TODO: Having direct access to the mirror area from the camera would be
105 # useful. See DM-16489.
106 # Mirror area in cm**2
107 if config.mirrorArea is None:
108 mirrorArea = np.pi*(camera.telescopeDiameter*100./2.)**2.
109 else:
110 # Convert to square cm.
111 mirrorArea = config.mirrorArea * 100.**2.
112
113 if config.cameraGain is None:
114 # Get approximate average camera gain:
115 gains = [amp.getGain() for detector in camera for amp in detector.getAmplifiers()]
116 cameraGain = float(np.median(gains))
117 else:
118 cameraGain = config.cameraGain
119
120 # Cut down the filter map to those that are in the LUT
121 filterToBand = {filterName: config.physicalFilterMap[filterName] for
122 filterName in lutFilterNames}
123
124 if tract is None:
125 outfileBase = config.outfileBase
126 else:
127 outfileBase = '%s-%06d' % (config.outfileBase, tract)
128
129 if config.aperCorrPerCcd:
130 if config.aperCorrUsePsfFwhm:
131 seeingField = 'PSFFWHM_DETECTOR'
132 else:
133 seeingField = 'DELTA_APER_DETECTOR'
134 seeingSubExposure = True
135 else:
136 if config.aperCorrUsePsfFwhm:
137 seeingField = 'PSFFWHM'
138 else:
139 seeingField = 'DELTA_APER'
140 seeingSubExposure = False
141
142 # create a configuration dictionary for fgcmFitCycle
143 configDict = {'outfileBase': outfileBase,
144 'logger': log,
145 'exposureFile': None,
146 'obsFile': None,
147 'indexFile': None,
148 'lutFile': None,
149 'mirrorArea': mirrorArea,
150 'cameraGain': cameraGain,
151 'ccdStartIndex': camera[0].getId(),
152 'expField': FGCM_EXP_FIELD,
153 'ccdField': FGCM_CCD_FIELD,
154 'seeingField': seeingField,
155 'fwhmField': 'PSFFWHM',
156 'skyBrightnessField': 'SKYBACKGROUND',
157 'deepFlag': 'DEEPFLAG', # unused
158 'bands': list(config.bands),
159 'fitBands': list(config.fitBands),
160 'notFitBands': notFitBands,
161 'requiredBands': list(config.requiredBands),
162 'filterToBand': filterToBand,
163 'logLevel': 'INFO',
164 'nCore': nCore,
165 'nStarPerRun': config.nStarPerRun,
166 'nStarPerGrayRun': config.nStarPerGrayRun,
167 'nObsPerRun': config.nObsPerRun,
168 'nObsPerGrayRun': config.nObsPerGrayRun,
169 'nExpPerRun': config.nExpPerRun,
170 'reserveFraction': config.reserveFraction,
171 'freezeStdAtmosphere': config.freezeStdAtmosphere,
172 'precomputeSuperStarInitialCycle': config.precomputeSuperStarInitialCycle,
173 'superStarSubCCDDict': dict(config.superStarSubCcdDict),
174 'superStarSubCCDChebyshevOrder': config.superStarSubCcdChebyshevOrder,
175 'superStarSubCCDTriangular': config.superStarSubCcdTriangular,
176 'superStarSigmaClip': config.superStarSigmaClip,
177 'superStarPlotCCDResiduals': config.superStarPlotCcdResiduals,
178 'superStarForceZeroMean': config.superStarForceZeroMean,
179 'focalPlaneSigmaClip': config.focalPlaneSigmaClip,
180 'ccdGraySubCCDDict': dict(config.ccdGraySubCcdDict),
181 'ccdGraySubCCDChebyshevOrder': config.ccdGraySubCcdChebyshevOrder,
182 'ccdGraySubCCDTriangular': config.ccdGraySubCcdTriangular,
183 'ccdGrayFocalPlaneDict': dict(config.ccdGrayFocalPlaneDict),
184 'ccdGrayFocalPlaneChebyshevOrder': config.ccdGrayFocalPlaneChebyshevOrder,
185 'ccdGrayFocalPlaneFitMinCcd': config.ccdGrayFocalPlaneFitMinCcd,
186 'ccdGrayFocalPlaneMaxStars': config.ccdGrayFocalPlaneMaxStars,
187 'cycleNumber': config.cycleNumber,
188 'maxIter': maxIter,
189 'deltaMagBkgOffsetPercentile': config.deltaMagBkgOffsetPercentile,
190 'deltaMagBkgPerCcd': config.deltaMagBkgPerCcd,
191 'UTBoundary': config.utBoundary,
192 'washMJDs': config.washMjds,
193 'epochMJDs': config.epochMjds,
194 'coatingMJDs': config.coatingMjds,
195 'minObsPerBand': config.minObsPerBand,
196 'latitude': config.latitude,
197 'defaultCameraOrientation': config.defaultCameraOrientation,
198 'brightObsGrayMax': config.brightObsGrayMax,
199 'minStarPerCCD': config.minStarPerCcd,
200 'minCCDPerExp': config.minCcdPerExp,
201 'maxCCDGrayErr': config.maxCcdGrayErr,
202 'minStarPerExp': config.minStarPerExp,
203 'minExpPerNight': config.minExpPerNight,
204 'expGrayInitialCut': config.expGrayInitialCut,
205 'expFwhmCutDict': dict(config.expFwhmCutDict),
206 'expGrayPhotometricCutDict': dict(config.expGrayPhotometricCutDict),
207 'expGrayHighCutDict': dict(config.expGrayHighCutDict),
208 'expGrayRecoverCut': config.expGrayRecoverCut,
209 'expVarGrayPhotometricCutDict': dict(config.expVarGrayPhotometricCutDict),
210 'expGrayErrRecoverCut': config.expGrayErrRecoverCut,
211 'refStarSnMin': config.refStarSnMin,
212 'refStarOutlierNSig': config.refStarOutlierNSig,
213 'applyRefStarColorCuts': config.applyRefStarColorCuts,
214 'refStarMaxFracUse': config.refStarMaxFracUse,
215 'useExposureReferenceOffset': config.useExposureReferenceOffset,
216 'illegalValue': FGCM_ILLEGAL_VALUE, # internally used by fgcm.
217 'starColorCuts': starColorCutList,
218 'refStarColorCuts': refStarColorCutList,
219 'aperCorrFitNBins': config.aperCorrFitNBins,
220 'aperCorrInputSlopeDict': dict(config.aperCorrInputSlopeDict),
221 'seeingSubExposure': seeingSubExposure,
222 'sedBoundaryTermDict': config.sedboundaryterms.toDict()['data'],
223 'sedTermDict': config.sedterms.toDict()['data'],
224 'colorSplitBands': list(config.colorSplitBands),
225 'sigFgcmMaxErr': config.sigFgcmMaxErr,
226 'sigFgcmMaxEGrayDict': dict(config.sigFgcmMaxEGrayDict),
227 'ccdGrayMaxStarErr': config.ccdGrayMaxStarErr,
228 'approxThroughputDict': dict(config.approxThroughputDict),
229 'sigmaCalRange': list(config.sigmaCalRange),
230 'sigmaCalFitPercentile': list(config.sigmaCalFitPercentile),
231 'sigmaCalPlotPercentile': list(config.sigmaCalPlotPercentile),
232 'sigma0Phot': config.sigma0Phot,
233 'mapLongitudeRef': config.mapLongitudeRef,
234 'mapNSide': config.mapNSide,
235 'varNSig': 100.0, # Turn off 'variable star selection' which doesn't work yet
236 'varMinBand': 2,
237 'useNightlyRetrievedPwv': False,
238 'useQuadraticPwv': config.useQuadraticPwv,
239 'useRetrievedPwv': config.useRetrievedPwv,
240 'pwvRetrievalSmoothBlock': config.retrievedPwvSmoothingBlock,
241 'retrievedPwvBands': list(config.retrievedPwvBands),
242 'useRetrievedTauInit': False,
243 'tauRetrievalMinCCDPerNight': 500,
244 'modelMagErrors': config.modelMagErrors,
245 'instrumentParsPerBand': config.instrumentParsPerBand,
246 'instrumentSlopeMinDeltaT': config.instrumentSlopeMinDeltaT,
247 'fitMirrorChromaticity': config.fitMirrorChromaticity,
248 'fitCCDChromaticityDict': dict(config.fitCcdChromaticityDict),
249 'useRepeatabilityForExpGrayCutsDict': dict(config.useRepeatabilityForExpGrayCutsDict),
250 'autoPhotometricCutNSig': config.autoPhotometricCutNSig,
251 'autoHighCutNSig': config.autoHighCutNSig,
252 'deltaAperInnerRadiusArcsec': config.deltaAperInnerRadiusArcsec,
253 'deltaAperOuterRadiusArcsec': config.deltaAperOuterRadiusArcsec,
254 'deltaAperFitMinNgoodObs': config.deltaAperFitMinNgoodObs,
255 'deltaAperFitPerCcdNx': config.deltaAperFitPerCcdNx,
256 'deltaAperFitPerCcdNy': config.deltaAperFitPerCcdNy,
257 'deltaAperFitSpatialNside': config.deltaAperFitSpatialNside,
258 'doComputeDeltaAperExposures': config.doComputeDeltaAperPerVisit,
259 'doComputeDeltaAperStars': config.doComputeDeltaAperPerStar,
260 'doComputeDeltaAperMap': config.doComputeDeltaAperMap,
261 'doComputeDeltaAperPerCcd': config.doComputeDeltaAperPerCcd,
262 'printOnly': False,
263 'quietMode': config.quietMode,
264 'randomSeed': config.randomSeed,
265 'outputStars': False,
266 'outputPath': os.path.abspath('.'),
267 'clobber': True,
268 'useSedLUT': False,
269 'resetParameters': resetFitParameters,
270 'doPlots': doPlots,
271 'outputFgcmcalZpts': True, # when outputting zpts, use fgcmcal format
272 'outputZeropoints': outputZeropoints}
273
274 return configDict
275
276
277def translateFgcmLut(lutCat, physicalFilterMap):
278 """
279 Translate the FGCM look-up-table into an fgcm-compatible object
280
281 Parameters
282 ----------
283 lutCat: `lsst.afw.table.BaseCatalog`
284 Catalog describing the FGCM look-up table
285 physicalFilterMap: `dict`
286 Physical filter to band mapping
287
288 Returns
289 -------
290 fgcmLut: `lsst.fgcm.FgcmLut`
291 Lookup table for FGCM
292 lutIndexVals: `numpy.ndarray`
293 Numpy array with LUT index information for FGCM
294 lutStd: `numpy.ndarray`
295 Numpy array with LUT standard throughput values for FGCM
296
297 Notes
298 -----
299 After running this code, it is wise to `del lutCat` to clear the memory.
300 """
301
302 # first we need the lutIndexVals
303 lutFilterNames = np.array(lutCat[0]['physicalFilters'].split(','), dtype='U')
304 lutStdFilterNames = np.array(lutCat[0]['stdPhysicalFilters'].split(','), dtype='U')
305
306 # Note that any discrepancies between config values will raise relevant
307 # exceptions in the FGCM code.
308
309 lutIndexVals = np.zeros(1, dtype=[('FILTERNAMES', lutFilterNames.dtype.str,
310 lutFilterNames.size),
311 ('STDFILTERNAMES', lutStdFilterNames.dtype.str,
312 lutStdFilterNames.size),
313 ('PMB', 'f8', lutCat[0]['pmb'].size),
314 ('PMBFACTOR', 'f8', lutCat[0]['pmbFactor'].size),
315 ('PMBELEVATION', 'f8'),
316 ('LAMBDANORM', 'f8'),
317 ('PWV', 'f8', lutCat[0]['pwv'].size),
318 ('O3', 'f8', lutCat[0]['o3'].size),
319 ('TAU', 'f8', lutCat[0]['tau'].size),
320 ('ALPHA', 'f8', lutCat[0]['alpha'].size),
321 ('ZENITH', 'f8', lutCat[0]['zenith'].size),
322 ('NCCD', 'i4')])
323
324 lutIndexVals['FILTERNAMES'][:] = lutFilterNames
325 lutIndexVals['STDFILTERNAMES'][:] = lutStdFilterNames
326 lutIndexVals['PMB'][:] = lutCat[0]['pmb']
327 lutIndexVals['PMBFACTOR'][:] = lutCat[0]['pmbFactor']
328 lutIndexVals['PMBELEVATION'] = lutCat[0]['pmbElevation']
329 lutIndexVals['LAMBDANORM'] = lutCat[0]['lambdaNorm']
330 lutIndexVals['PWV'][:] = lutCat[0]['pwv']
331 lutIndexVals['O3'][:] = lutCat[0]['o3']
332 lutIndexVals['TAU'][:] = lutCat[0]['tau']
333 lutIndexVals['ALPHA'][:] = lutCat[0]['alpha']
334 lutIndexVals['ZENITH'][:] = lutCat[0]['zenith']
335 lutIndexVals['NCCD'] = lutCat[0]['nCcd']
336
337 # now we need the Standard Values
338 lutStd = np.zeros(1, dtype=[('PMBSTD', 'f8'),
339 ('PWVSTD', 'f8'),
340 ('O3STD', 'f8'),
341 ('TAUSTD', 'f8'),
342 ('ALPHASTD', 'f8'),
343 ('ZENITHSTD', 'f8'),
344 ('LAMBDARANGE', 'f8', 2),
345 ('LAMBDASTEP', 'f8'),
346 ('LAMBDASTD', 'f8', lutFilterNames.size),
347 ('LAMBDASTDFILTER', 'f8', lutStdFilterNames.size),
348 ('I0STD', 'f8', lutFilterNames.size),
349 ('I1STD', 'f8', lutFilterNames.size),
350 ('I10STD', 'f8', lutFilterNames.size),
351 ('I2STD', 'f8', lutFilterNames.size),
352 ('LAMBDAB', 'f8', lutFilterNames.size),
353 ('ATMLAMBDA', 'f8', lutCat[0]['atmLambda'].size),
354 ('ATMSTDTRANS', 'f8', lutCat[0]['atmStdTrans'].size)])
355 lutStd['PMBSTD'] = lutCat[0]['pmbStd']
356 lutStd['PWVSTD'] = lutCat[0]['pwvStd']
357 lutStd['O3STD'] = lutCat[0]['o3Std']
358 lutStd['TAUSTD'] = lutCat[0]['tauStd']
359 lutStd['ALPHASTD'] = lutCat[0]['alphaStd']
360 lutStd['ZENITHSTD'] = lutCat[0]['zenithStd']
361 lutStd['LAMBDARANGE'][:] = lutCat[0]['lambdaRange'][:]
362 lutStd['LAMBDASTEP'] = lutCat[0]['lambdaStep']
363 lutStd['LAMBDASTD'][:] = lutCat[0]['lambdaStd']
364 lutStd['LAMBDASTDFILTER'][:] = lutCat[0]['lambdaStdFilter']
365 lutStd['I0STD'][:] = lutCat[0]['i0Std']
366 lutStd['I1STD'][:] = lutCat[0]['i1Std']
367 lutStd['I10STD'][:] = lutCat[0]['i10Std']
368 lutStd['I2STD'][:] = lutCat[0]['i2Std']
369 lutStd['LAMBDAB'][:] = lutCat[0]['lambdaB']
370 lutStd['ATMLAMBDA'][:] = lutCat[0]['atmLambda'][:]
371 lutStd['ATMSTDTRANS'][:] = lutCat[0]['atmStdTrans'][:]
372
373 lutTypes = [row['luttype'] for row in lutCat]
374
375 # And the flattened look-up-table
376 lutFlat = np.zeros(lutCat[0]['lut'].size, dtype=[('I0', 'f4'),
377 ('I1', 'f4')])
378
379 lutFlat['I0'][:] = lutCat[lutTypes.index('I0')]['lut'][:]
380 lutFlat['I1'][:] = lutCat[lutTypes.index('I1')]['lut'][:]
381
382 lutDerivFlat = np.zeros(lutCat[0]['lut'].size, dtype=[('D_LNPWV', 'f4'),
383 ('D_O3', 'f4'),
384 ('D_LNTAU', 'f4'),
385 ('D_ALPHA', 'f4'),
386 ('D_SECZENITH', 'f4'),
387 ('D_LNPWV_I1', 'f4'),
388 ('D_O3_I1', 'f4'),
389 ('D_LNTAU_I1', 'f4'),
390 ('D_ALPHA_I1', 'f4'),
391 ('D_SECZENITH_I1', 'f4')])
392
393 for name in lutDerivFlat.dtype.names:
394 lutDerivFlat[name][:] = lutCat[lutTypes.index(name)]['lut'][:]
395
396 # The fgcm.FgcmLUT() class copies all the LUT information into special
397 # shared memory objects that will not blow up the memory usage when used
398 # with python multiprocessing. Once all the numbers are copied, the
399 # references to the temporary objects (lutCat, lutFlat, lutDerivFlat)
400 # will fall out of scope and can be cleaned up by the garbage collector.
401 fgcmLut = fgcm.FgcmLUT(lutIndexVals, lutFlat, lutDerivFlat, lutStd,
402 filterToBand=physicalFilterMap)
403
404 return fgcmLut, lutIndexVals, lutStd
405
406
408 """
409 Translate the FGCM visit catalog to an fgcm-compatible object
410
411 Parameters
412 ----------
413 visitCat: `lsst.afw.table.BaseCatalog`
414 FGCM visitCat from `lsst.fgcmcal.FgcmBuildStarsTask`
415
416 Returns
417 -------
418 fgcmExpInfo: `numpy.ndarray`
419 Numpy array for visit information for FGCM
420
421 Notes
422 -----
423 After running this code, it is wise to `del visitCat` to clear the memory.
424 """
425
426 nDetector = visitCat.schema["deltaAperDetector"].asKey().getSize()
427
428 fgcmExpInfo = np.zeros(len(visitCat), dtype=[('VISIT', 'i8'),
429 ('MJD', 'f8'),
430 ('EXPTIME', 'f8'),
431 ('PSFFWHM', 'f8'),
432 ('PSFFWHM_DETECTOR', ('f8', nDetector)),
433 ('DELTA_APER', 'f8'),
434 ('DELTA_APER_DETECTOR', ('f8', nDetector)),
435 ('SKYBACKGROUND', 'f8'),
436 ('DEEPFLAG', 'i2'),
437 ('TELHA', 'f8'),
438 ('TELRA', 'f8'),
439 ('TELDEC', 'f8'),
440 ('TELROT', 'f8'),
441 ('PMB', 'f8'),
442 ('FILTERNAME', 'S50')])
443 fgcmExpInfo['VISIT'][:] = visitCat['visit']
444 fgcmExpInfo['MJD'][:] = visitCat['mjd']
445 fgcmExpInfo['EXPTIME'][:] = visitCat['exptime']
446 fgcmExpInfo['DEEPFLAG'][:] = visitCat['deepFlag']
447 fgcmExpInfo['TELHA'][:] = visitCat['telha']
448 fgcmExpInfo['TELRA'][:] = visitCat['telra']
449 fgcmExpInfo['TELDEC'][:] = visitCat['teldec']
450 fgcmExpInfo['TELROT'][:] = visitCat['telrot']
451 fgcmExpInfo['PMB'][:] = visitCat['pmb']
452 fgcmExpInfo['PSFFWHM'][:] = visitCat['psfFwhm']
453 fgcmExpInfo['PSFFWHM_DETECTOR'][:] = visitCat['psfFwhmDetector']
454 fgcmExpInfo['DELTA_APER'][:] = visitCat['deltaAper']
455 fgcmExpInfo['DELTA_APER_DETECTOR'][:, :] = visitCat['deltaAperDetector']
456 fgcmExpInfo['SKYBACKGROUND'][:] = visitCat['skyBackground']
457 # Note that we have to go through asAstropy() to get a string
458 # array out of an afwTable. This is faster than a row-by-row loop.
459 fgcmExpInfo['FILTERNAME'][:] = visitCat.asAstropy()['physicalFilter']
460
461 return fgcmExpInfo
462
463
465 """
466 Compute a default visitInfo for use with pixel scale / jacobians.
467
468 Returns
469 -------
470 visitInfo : `lsst.afw.image.VisitInfo`
471 The visit info object.
472 """
473 # Generate fake WCSs centered at 180/0 to avoid the RA=0/360 problem,
474 # since we are looking for relative scales
475 boresight = geom.SpherePoint(180.0*geom.degrees, 0.0*geom.degrees)
476 orientation = 0.0*geom.degrees
477
478 # Create a temporary visitInfo for input to createInitialSkyWcs
479 # The orientation does not matter for the area computation
480 visitInfo = afwImage.VisitInfo(boresightRaDec=boresight,
481 boresightRotAngle=orientation,
482 rotType=afwImage.RotType.SKY)
483 return visitInfo
484
485
486def computeReferencePixelScale(camera, useScienceDetectors=False):
487 """
488 Compute the median pixel scale in the camera
489
490 Returns
491 -------
492 pixelScale : `float`
493 Average pixel scale (arcsecond) over the camera
494 useScienceDetectors : `bool`, optional
495 Limit to just science detectors?
496 """
497 visitInfo = _computeDefaultVisitInfo()
498
499 pixelScales = np.zeros(len(camera))
500 for i, detector in enumerate(camera):
501 if useScienceDetectors:
502 if not detector.getType() == afwCameraGeom.DetectorType.SCIENCE:
503 continue
504 wcs = createInitialSkyWcs(visitInfo, detector, False)
505 pixelScales[i] = wcs.getPixelScale(detector.getBBox().getCenter()).asArcseconds()
506
507 ok, = np.where(pixelScales > 0.0)
508 return np.median(pixelScales[ok])
509
510
511def computePixelAreaFieldDetector(detector, visitInfo=None, areaScaling=1.0, approximate=False):
512 """
513 Compute the reference pixel area field for a detector.
514
515 Parameters
516 ----------
517 visitInfo : `lsst.afw.image.VisitInfo`
518 The visit info object.
519 detector : `lsst.afw.cameraGeom.detector`
520 The detector object.
521 visitInfo : `lsst.afw.image.VisitInfo`, optional
522 If not supplied, will compute a default visitInfo.
523 areaScaling : `float`, optional
524 Area scaling factor.
525 approximate : `bool`, optional
526 Compute Chebyshev approximation to pixel area field?
527
528 Returns
529 -------
530 pixelAreaField : `lsst.afw.math.BoundedField`
531 Bounded field describing the pixel area from the detector model.
532 """
533 if visitInfo is None:
534 visitInfo = _computeDefaultVisitInfo()
535
536 wcs = createInitialSkyWcs(visitInfo, detector, False)
537 bbox = detector.getBBox()
538
539 pixelAreaField = afwMath.PixelAreaBoundedField(
540 bbox,
541 wcs,
542 unit=geom.arcseconds,
543 scaling=areaScaling,
544 )
545
546 if approximate:
547 pixelAreaField = afwMath.ChebyshevBoundedField.approximate(pixelAreaField)
548
549 return pixelAreaField
550
551
553 """
554 Compute the approximate pixel area bounded fields from the camera
555 geometry.
556
557 Parameters
558 ----------
559 camera: `lsst.afw.cameraGeom.Camera`
560
561 Returns
562 -------
563 approxPixelAreaFields: `dict`
564 Dictionary of approximate area fields, keyed with detector ID
565 """
566
567 areaScaling = 1. / computeReferencePixelScale(camera)**2.
568
569 visitInfo = _computeDefaultVisitInfo()
570
571 approxPixelAreaFields = {}
572
573 for i, detector in enumerate(camera):
574 key = detector.getId()
575
576 approxAreaField = computePixelAreaFieldDetector(
577 detector,
578 visitInfo=visitInfo,
579 areaScaling=areaScaling,
580 approximate=True,
581 )
582
583 approxPixelAreaFields[key] = approxAreaField
584
585 return approxPixelAreaFields
586
587
588def makeZptSchema(superStarChebyshevSize, zptChebyshevSize):
589 """
590 Make the zeropoint schema
591
592 Parameters
593 ----------
594 superStarChebyshevSize: `int`
595 Length of the superstar chebyshev array
596 zptChebyshevSize: `int`
597 Length of the zeropoint chebyshev array
598
599 Returns
600 -------
601 zptSchema: `lsst.afw.table.schema`
602 """
603
604 zptSchema = afwTable.Schema()
605
606 zptSchema.addField('visit', type=np.int64, doc='Visit number')
607 zptSchema.addField('detector', type=np.int32, doc='Detector ID number')
608 zptSchema.addField('fgcmFlag', type=np.int32, doc=('FGCM flag value: '
609 '1: Photometric, used in fit; '
610 '2: Photometric, not used in fit; '
611 '4: Non-photometric, on partly photometric night; '
612 '8: Non-photometric, on non-photometric night; '
613 '16: No zeropoint could be determined; '
614 '32: Too few stars for reliable gray computation'))
615 zptSchema.addField('fgcmZpt', type=np.float64, doc='FGCM zeropoint (center of CCD)')
616 zptSchema.addField('fgcmZptErr', type=np.float64,
617 doc='Error on zeropoint, estimated from repeatability + number of obs')
618 zptSchema.addField('fgcmfZptChebXyMax', type='ArrayD', size=2,
619 doc='maximum x/maximum y to scale to apply chebyshev parameters')
620 zptSchema.addField('fgcmfZptCheb', type='ArrayD',
621 size=zptChebyshevSize,
622 doc='Chebyshev parameters (flattened) for zeropoint')
623 zptSchema.addField('fgcmfZptSstarCheb', type='ArrayD',
624 size=superStarChebyshevSize,
625 doc='Chebyshev parameters (flattened) for superStarFlat')
626 zptSchema.addField('fgcmI0', type=np.float64, doc='Integral of the passband')
627 zptSchema.addField('fgcmI10', type=np.float64, doc='Normalized chromatic integral')
628 zptSchema.addField('fgcmR0', type=np.float64,
629 doc='Retrieved i0 integral, estimated from stars (only for flag 1)')
630 zptSchema.addField('fgcmR10', type=np.float64,
631 doc='Retrieved i10 integral, estimated from stars (only for flag 1)')
632 zptSchema.addField('fgcmGry', type=np.float64,
633 doc='Estimated gray extinction relative to atmospheric solution; '
634 'only for fgcmFlag <= 4 (see fgcmFlag) ')
635 zptSchema.addField('fgcmDeltaChrom', type=np.float64,
636 doc='Mean chromatic correction for stars in this ccd; '
637 'only for fgcmFlag <= 4 (see fgcmFlag)')
638 zptSchema.addField('fgcmZptVar', type=np.float64, doc='Variance of zeropoint over ccd')
639 zptSchema.addField('fgcmTilings', type=np.float64,
640 doc='Number of photometric tilings used for solution for ccd')
641 zptSchema.addField('fgcmFpGry', type=np.float64,
642 doc='Average gray extinction over the full focal plane '
643 '(same for all ccds in a visit)')
644 zptSchema.addField('fgcmFpGryBlue', type=np.float64,
645 doc='Average gray extinction over the full focal plane '
646 'for 25% bluest stars')
647 zptSchema.addField('fgcmFpGryBlueErr', type=np.float64,
648 doc='Error on Average gray extinction over the full focal plane '
649 'for 25% bluest stars')
650 zptSchema.addField('fgcmFpGryRed', type=np.float64,
651 doc='Average gray extinction over the full focal plane '
652 'for 25% reddest stars')
653 zptSchema.addField('fgcmFpGryRedErr', type=np.float64,
654 doc='Error on Average gray extinction over the full focal plane '
655 'for 25% reddest stars')
656 zptSchema.addField('fgcmFpVar', type=np.float64,
657 doc='Variance of gray extinction over the full focal plane '
658 '(same for all ccds in a visit)')
659 zptSchema.addField('fgcmDust', type=np.float64,
660 doc='Gray dust extinction from the primary/corrector'
661 'at the time of the exposure')
662 zptSchema.addField('fgcmFlat', type=np.float64, doc='Superstarflat illumination correction')
663 zptSchema.addField('fgcmAperCorr', type=np.float64, doc='Aperture correction estimated by fgcm')
664 zptSchema.addField('fgcmDeltaMagBkg', type=np.float64,
665 doc=('Local background correction from brightest percentile '
666 '(value set by deltaMagBkgOffsetPercentile) calibration '
667 'stars.'))
668 zptSchema.addField('exptime', type=np.float32, doc='Exposure time')
669 zptSchema.addField('filtername', type=str, size=30, doc='Filter name')
670
671 return zptSchema
672
673
674def makeZptCat(zptSchema, zpStruct):
675 """
676 Make the zeropoint catalog for persistence
677
678 Parameters
679 ----------
680 zptSchema: `lsst.afw.table.Schema`
681 Zeropoint catalog schema
682 zpStruct: `numpy.ndarray`
683 Zeropoint structure from fgcm
684
685 Returns
686 -------
687 zptCat: `afwTable.BaseCatalog`
688 Zeropoint catalog for persistence
689 """
690
691 zptCat = afwTable.BaseCatalog(zptSchema)
692 zptCat.reserve(zpStruct.size)
693
694 for filterName in zpStruct['FILTERNAME']:
695 rec = zptCat.addNew()
696 rec['filtername'] = filterName.decode('utf-8')
697
698 zptCat['visit'][:] = zpStruct[FGCM_EXP_FIELD]
699 zptCat['detector'][:] = zpStruct[FGCM_CCD_FIELD]
700 zptCat['fgcmFlag'][:] = zpStruct['FGCM_FLAG']
701 zptCat['fgcmZpt'][:] = zpStruct['FGCM_ZPT']
702 zptCat['fgcmZptErr'][:] = zpStruct['FGCM_ZPTERR']
703 zptCat['fgcmfZptChebXyMax'][:, :] = zpStruct['FGCM_FZPT_XYMAX']
704 zptCat['fgcmfZptCheb'][:, :] = zpStruct['FGCM_FZPT_CHEB']
705 zptCat['fgcmfZptSstarCheb'][:, :] = zpStruct['FGCM_FZPT_SSTAR_CHEB']
706 zptCat['fgcmI0'][:] = zpStruct['FGCM_I0']
707 zptCat['fgcmI10'][:] = zpStruct['FGCM_I10']
708 zptCat['fgcmR0'][:] = zpStruct['FGCM_R0']
709 zptCat['fgcmR10'][:] = zpStruct['FGCM_R10']
710 zptCat['fgcmGry'][:] = zpStruct['FGCM_GRY']
711 zptCat['fgcmDeltaChrom'][:] = zpStruct['FGCM_DELTACHROM']
712 zptCat['fgcmZptVar'][:] = zpStruct['FGCM_ZPTVAR']
713 zptCat['fgcmTilings'][:] = zpStruct['FGCM_TILINGS']
714 zptCat['fgcmFpGry'][:] = zpStruct['FGCM_FPGRY']
715 zptCat['fgcmFpGryBlue'][:] = zpStruct['FGCM_FPGRY_CSPLIT'][:, 0]
716 zptCat['fgcmFpGryBlueErr'][:] = zpStruct['FGCM_FPGRY_CSPLITERR'][:, 0]
717 zptCat['fgcmFpGryRed'][:] = zpStruct['FGCM_FPGRY_CSPLIT'][:, 2]
718 zptCat['fgcmFpGryRedErr'][:] = zpStruct['FGCM_FPGRY_CSPLITERR'][:, 2]
719 zptCat['fgcmFpVar'][:] = zpStruct['FGCM_FPVAR']
720 zptCat['fgcmDust'][:] = zpStruct['FGCM_DUST']
721 zptCat['fgcmFlat'][:] = zpStruct['FGCM_FLAT']
722 zptCat['fgcmAperCorr'][:] = zpStruct['FGCM_APERCORR']
723 zptCat['fgcmDeltaMagBkg'][:] = zpStruct['FGCM_DELTAMAGBKG']
724 zptCat['exptime'][:] = zpStruct['EXPTIME']
725
726 return zptCat
727
728
730 """
731 Make the atmosphere schema
732
733 Returns
734 -------
735 atmSchema: `lsst.afw.table.Schema`
736 """
737
738 atmSchema = afwTable.Schema()
739
740 atmSchema.addField('visit', type=np.int64, doc='Visit number')
741 atmSchema.addField('pmb', type=np.float64, doc='Barometric pressure (mb)')
742 atmSchema.addField('pwv', type=np.float64, doc='Water vapor (mm)')
743 atmSchema.addField('tau', type=np.float64, doc='Aerosol optical depth')
744 atmSchema.addField('alpha', type=np.float64, doc='Aerosol slope')
745 atmSchema.addField('o3', type=np.float64, doc='Ozone (dobson)')
746 atmSchema.addField('secZenith', type=np.float64, doc='Secant(zenith) (~ airmass)')
747 atmSchema.addField('cTrans', type=np.float64, doc='Transmission correction factor')
748 atmSchema.addField('lamStd', type=np.float64, doc='Wavelength for transmission correction')
749
750 return atmSchema
751
752
753def makeAtmCat(atmSchema, atmStruct):
754 """
755 Make the atmosphere catalog for persistence
756
757 Parameters
758 ----------
759 atmSchema: `lsst.afw.table.Schema`
760 Atmosphere catalog schema
761 atmStruct: `numpy.ndarray`
762 Atmosphere structure from fgcm
763
764 Returns
765 -------
766 atmCat: `lsst.afw.table.BaseCatalog`
767 Atmosphere catalog for persistence
768 """
769
770 atmCat = afwTable.BaseCatalog(atmSchema)
771 atmCat.resize(atmStruct.size)
772
773 atmCat['visit'][:] = atmStruct['VISIT']
774 atmCat['pmb'][:] = atmStruct['PMB']
775 atmCat['pwv'][:] = atmStruct['PWV']
776 atmCat['tau'][:] = atmStruct['TAU']
777 atmCat['alpha'][:] = atmStruct['ALPHA']
778 atmCat['o3'][:] = atmStruct['O3']
779 atmCat['secZenith'][:] = atmStruct['SECZENITH']
780 atmCat['cTrans'][:] = atmStruct['CTRANS']
781 atmCat['lamStd'][:] = atmStruct['LAMSTD']
782
783 return atmCat
784
785
786def makeStdSchema(nBands):
787 """
788 Make the standard star schema
789
790 Parameters
791 ----------
792 nBands: `int`
793 Number of bands in standard star catalog
794
795 Returns
796 -------
797 stdSchema: `lsst.afw.table.Schema`
798 """
799
800 stdSchema = afwTable.SimpleTable.makeMinimalSchema()
801 stdSchema.addField('isolated_star_id', type='L', doc='ID from isolated star table')
802 stdSchema.addField('ngood', type='ArrayI', doc='Number of good observations',
803 size=nBands)
804 stdSchema.addField('ntotal', type='ArrayI', doc='Number of total observations',
805 size=nBands)
806 stdSchema.addField('mag_std_noabs', type='ArrayF',
807 doc='Standard magnitude (no absolute calibration)',
808 size=nBands)
809 stdSchema.addField('magErr_std', type='ArrayF',
810 doc='Standard magnitude error',
811 size=nBands)
812 stdSchema.addField('npsfcand', type='ArrayI',
813 doc='Number of observations flagged as psf candidates',
814 size=nBands)
815 stdSchema.addField('delta_aper', type='ArrayF',
816 doc='Delta mag (small - large aperture)',
817 size=nBands)
818
819 return stdSchema
820
821
822def makeStdCat(stdSchema, stdStruct, goodBands):
823 """
824 Make the standard star catalog for persistence
825
826 Parameters
827 ----------
828 stdSchema: `lsst.afw.table.Schema`
829 Standard star catalog schema
830 stdStruct: `numpy.ndarray`
831 Standard star structure in FGCM format
832 goodBands: `list`
833 List of good band names used in stdStruct
834
835 Returns
836 -------
837 stdCat: `lsst.afw.table.BaseCatalog`
838 Standard star catalog for persistence
839 """
840
841 stdCat = afwTable.SimpleCatalog(stdSchema)
842 stdCat.resize(stdStruct.size)
843
844 stdCat['id'][:] = stdStruct['FGCM_ID']
845 stdCat['isolated_star_id'][:] = stdStruct['ALTERNATE_ID']
846 stdCat['coord_ra'][:] = stdStruct['RA'] * geom.degrees
847 stdCat['coord_dec'][:] = stdStruct['DEC'] * geom.degrees
848 stdCat['ngood'][:, :] = stdStruct['NGOOD'][:, :]
849 stdCat['ntotal'][:, :] = stdStruct['NTOTAL'][:, :]
850 stdCat['mag_std_noabs'][:, :] = stdStruct['MAG_STD'][:, :]
851 stdCat['magErr_std'][:, :] = stdStruct['MAGERR_STD'][:, :]
852 if 'NPSFCAND' in stdStruct.dtype.names:
853 stdCat['npsfcand'][:, :] = stdStruct['NPSFCAND'][:, :]
854 stdCat['delta_aper'][:, :] = stdStruct['DELTA_APER'][:, :]
855
856 md = PropertyList()
857 md.set("BANDS", list(goodBands))
858 stdCat.setMetadata(md)
859
860 return stdCat
861
862
864 """
865 Compute the radius associated with a CircularApertureFlux or ApFlux field.
866
867 Parameters
868 ----------
869 fluxField : `str`
870 CircularApertureFlux or ApFlux
871
872 Returns
873 -------
874 apertureRadius : `float`
875 Radius of the aperture field, in pixels.
876
877 Raises
878 ------
879 RuntimeError: Raised if flux field is not a CircularApertureFlux,
880 ApFlux, or apFlux.
881 """
882 # TODO: Move this method to more general stack method in DM-25775
883 m = re.search(r'(CircularApertureFlux|ApFlux|apFlux)_(\d+)_(\d+)_', fluxField)
884
885 if m is None:
886 raise RuntimeError(f"Flux field {fluxField} does not correspond to a CircularApertureFlux or ApFlux")
887
888 apertureRadius = float(m.groups()[1]) + float(m.groups()[2])/10.
889
890 return apertureRadius
891
892
893def extractReferenceMags(refStars, bands, filterMap):
894 """
895 Extract reference magnitudes from refStars for given bands and
896 associated filterMap.
897
898 Parameters
899 ----------
900 refStars : `astropy.table.Table` or `lsst.afw.table.BaseCatalog`
901 FGCM reference star catalog.
902 bands : `list`
903 List of bands for calibration.
904 filterMap: `dict`
905 FGCM mapping of filter to band.
906
907 Returns
908 -------
909 refMag : `np.ndarray`
910 nstar x nband array of reference magnitudes.
911 refMagErr : `np.ndarray`
912 nstar x nband array of reference magnitude errors.
913 """
914 hasAstropyMeta = False
915 try:
916 meta = refStars.meta
917 hasAstropyMeta = True
918 except AttributeError:
919 meta = refStars.getMetadata()
920
921 if 'FILTERNAMES' in meta:
922 if hasAstropyMeta:
923 filternames = meta['FILTERNAMES']
924 else:
925 filternames = meta.getArray('FILTERNAMES')
926
927 # The reference catalog that fgcm wants has one entry per band
928 # in the config file
929 refMag = np.zeros((len(refStars), len(bands)),
930 dtype=refStars['refMag'].dtype) + 99.0
931 refMagErr = np.zeros_like(refMag) + 99.0
932 for i, filtername in enumerate(filternames):
933 # We are allowed to run the fit configured so that we do not
934 # use every column in the reference catalog.
935 try:
936 band = filterMap[filtername]
937 except KeyError:
938 continue
939 try:
940 ind = bands.index(band)
941 except ValueError:
942 continue
943
944 refMag[:, ind] = refStars['refMag'][:, i]
945 refMagErr[:, ind] = refStars['refMagErr'][:, i]
946 else:
947 raise RuntimeError("FGCM reference stars missing FILTERNAMES metadata.")
948
949 return refMag, refMagErr
950
951
952def lookupStaticCalibrations(datasetType, registry, quantumDataId, collections):
953 # For static calibrations, we search with a timespan that has unbounded
954 # begin and end; we'll get an error if there's more than one match (because
955 # then it's not static).
956 timespan = Timespan(begin=None, end=None)
957 result = []
958 # First iterate over all of the data IDs for this dataset type that are
959 # consistent with the quantum data ID.
960 for dataId in registry.queryDataIds(datasetType.dimensions, dataId=quantumDataId):
961 # Find the dataset with this data ID using the unbounded timespan.
962 if ref := registry.findDataset(datasetType, dataId, collections=collections, timespan=timespan):
963 result.append(ref)
964 return result
965
966
967def countDetectors(camera, useScienceDetectors):
968 """Count the detectors in the camera.
969
970 This may be limited to just the science detectors.
971
972 Parameters
973 ----------
974 camera : `lsst.afw.cameraGeom.Camera`
975 Camera object.
976 useScienceDetectors : `bool`, optional
977 Limit to just science detectors?
978 """
979 if not useScienceDetectors:
980 return len(camera)
981
982 nDetector = 0
983 for detector in camera:
984 if not detector.getType() == afwCameraGeom.DetectorType.SCIENCE:
985 continue
986 nDetector += 1
987
988 return nDetector
translateVisitCatalog(visitCat)
Definition utilities.py:407
computeApertureRadiusFromName(fluxField)
Definition utilities.py:863
makeStdCat(stdSchema, stdStruct, goodBands)
Definition utilities.py:822
makeZptCat(zptSchema, zpStruct)
Definition utilities.py:674
lookupStaticCalibrations(datasetType, registry, quantumDataId, collections)
Definition utilities.py:952
computePixelAreaFieldDetector(detector, visitInfo=None, areaScaling=1.0, approximate=False)
Definition utilities.py:511
makeConfigDict(config, log, camera, maxIter, resetFitParameters, outputZeropoints, lutFilterNames, tract=None, nCore=1, doPlots=False)
Definition utilities.py:50
countDetectors(camera, useScienceDetectors)
Definition utilities.py:967
makeAtmCat(atmSchema, atmStruct)
Definition utilities.py:753
translateFgcmLut(lutCat, physicalFilterMap)
Definition utilities.py:277
computeApproxPixelAreaFields(camera)
Definition utilities.py:552
makeZptSchema(superStarChebyshevSize, zptChebyshevSize)
Definition utilities.py:588
computeReferencePixelScale(camera, useScienceDetectors=False)
Definition utilities.py:486
extractReferenceMags(refStars, bands, filterMap)
Definition utilities.py:893