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