Loading [MathJax]/extensions/tex2jax.js
LSST Applications g04a91732dc+a3f7a6a005,g07dc498a13+5ab4d22ec3,g0fba68d861+870ee37b31,g1409bbee79+5ab4d22ec3,g1a7e361dbc+5ab4d22ec3,g1fd858c14a+11200c7927,g20f46db602+25d63fd678,g35bb328faa+fcb1d3bbc8,g4d2262a081+cc8af5cafb,g4d39ba7253+6b9d64fe03,g4e0f332c67+5d362be553,g53246c7159+fcb1d3bbc8,g60b5630c4e+6b9d64fe03,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g8048e755c2+a1301e4c20,g8852436030+a750987b4a,g89139ef638+5ab4d22ec3,g89e1512fd8+a86d53a4aa,g8d6b6b353c+6b9d64fe03,g9125e01d80+fcb1d3bbc8,g989de1cb63+5ab4d22ec3,g9f33ca652e+38ca901d1a,ga9baa6287d+6b9d64fe03,gaaedd4e678+5ab4d22ec3,gabe3b4be73+1e0a283bba,gb1101e3267+aa269f591c,gb58c049af0+f03b321e39,gb90eeb9370+af74afe682,gc741bbaa4f+7f5db660ea,gcf25f946ba+a750987b4a,gd315a588df+b78635c672,gd6cbbdb0b4+c8606af20c,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+5839af1903,ge278dab8ac+932305ba37,ge82c20c137+76d20ab76d,w.2025.11
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 'superStarForceZeroMean': config.superStarForceZeroMean,
162 'focalPlaneSigmaClip': config.focalPlaneSigmaClip,
163 'ccdGraySubCCDDict': dict(config.ccdGraySubCcdDict),
164 'ccdGraySubCCDChebyshevOrder': config.ccdGraySubCcdChebyshevOrder,
165 'ccdGraySubCCDTriangular': config.ccdGraySubCcdTriangular,
166 'ccdGrayFocalPlaneDict': dict(config.ccdGrayFocalPlaneDict),
167 'ccdGrayFocalPlaneChebyshevOrder': config.ccdGrayFocalPlaneChebyshevOrder,
168 'ccdGrayFocalPlaneFitMinCcd': config.ccdGrayFocalPlaneFitMinCcd,
169 'cycleNumber': config.cycleNumber,
170 'maxIter': maxIter,
171 'deltaMagBkgOffsetPercentile': config.deltaMagBkgOffsetPercentile,
172 'deltaMagBkgPerCcd': config.deltaMagBkgPerCcd,
173 'UTBoundary': config.utBoundary,
174 'washMJDs': config.washMjds,
175 'epochMJDs': config.epochMjds,
176 'coatingMJDs': config.coatingMjds,
177 'minObsPerBand': config.minObsPerBand,
178 'latitude': config.latitude,
179 'defaultCameraOrientation': config.defaultCameraOrientation,
180 'brightObsGrayMax': config.brightObsGrayMax,
181 'minStarPerCCD': config.minStarPerCcd,
182 'minCCDPerExp': config.minCcdPerExp,
183 'maxCCDGrayErr': config.maxCcdGrayErr,
184 'minStarPerExp': config.minStarPerExp,
185 'minExpPerNight': config.minExpPerNight,
186 'expGrayInitialCut': config.expGrayInitialCut,
187 'expFwhmCutDict': dict(config.expFwhmCutDict),
188 'expGrayPhotometricCutDict': dict(config.expGrayPhotometricCutDict),
189 'expGrayHighCutDict': dict(config.expGrayHighCutDict),
190 'expGrayRecoverCut': config.expGrayRecoverCut,
191 'expVarGrayPhotometricCutDict': dict(config.expVarGrayPhotometricCutDict),
192 'expGrayErrRecoverCut': config.expGrayErrRecoverCut,
193 'refStarSnMin': config.refStarSnMin,
194 'refStarOutlierNSig': config.refStarOutlierNSig,
195 'applyRefStarColorCuts': config.applyRefStarColorCuts,
196 'refStarMaxFracUse': config.refStarMaxFracUse,
197 'useExposureReferenceOffset': config.useExposureReferenceOffset,
198 'illegalValue': FGCM_ILLEGAL_VALUE, # internally used by fgcm.
199 'starColorCuts': starColorCutList,
200 'refStarColorCuts': refStarColorCutList,
201 'aperCorrFitNBins': config.aperCorrFitNBins,
202 'aperCorrInputSlopeDict': dict(config.aperCorrInputSlopeDict),
203 'sedBoundaryTermDict': config.sedboundaryterms.toDict()['data'],
204 'sedTermDict': config.sedterms.toDict()['data'],
205 'colorSplitBands': list(config.colorSplitBands),
206 'sigFgcmMaxErr': config.sigFgcmMaxErr,
207 'sigFgcmMaxEGrayDict': dict(config.sigFgcmMaxEGrayDict),
208 'ccdGrayMaxStarErr': config.ccdGrayMaxStarErr,
209 'approxThroughputDict': dict(config.approxThroughputDict),
210 'sigmaCalRange': list(config.sigmaCalRange),
211 'sigmaCalFitPercentile': list(config.sigmaCalFitPercentile),
212 'sigmaCalPlotPercentile': list(config.sigmaCalPlotPercentile),
213 'sigma0Phot': config.sigma0Phot,
214 'mapLongitudeRef': config.mapLongitudeRef,
215 'mapNSide': config.mapNSide,
216 'varNSig': 100.0, # Turn off 'variable star selection' which doesn't work yet
217 'varMinBand': 2,
218 'useRetrievedPwv': False,
219 'useNightlyRetrievedPwv': False,
220 'pwvRetrievalSmoothBlock': 25,
221 'useQuadraticPwv': config.useQuadraticPwv,
222 'useRetrievedTauInit': False,
223 'tauRetrievalMinCCDPerNight': 500,
224 'modelMagErrors': config.modelMagErrors,
225 'instrumentParsPerBand': config.instrumentParsPerBand,
226 'instrumentSlopeMinDeltaT': config.instrumentSlopeMinDeltaT,
227 'fitMirrorChromaticity': config.fitMirrorChromaticity,
228 'fitCCDChromaticityDict': dict(config.fitCcdChromaticityDict),
229 'useRepeatabilityForExpGrayCutsDict': dict(config.useRepeatabilityForExpGrayCutsDict),
230 'autoPhotometricCutNSig': config.autoPhotometricCutNSig,
231 'autoHighCutNSig': config.autoHighCutNSig,
232 'deltaAperInnerRadiusArcsec': config.deltaAperInnerRadiusArcsec,
233 'deltaAperOuterRadiusArcsec': config.deltaAperOuterRadiusArcsec,
234 'deltaAperFitMinNgoodObs': config.deltaAperFitMinNgoodObs,
235 'deltaAperFitPerCcdNx': config.deltaAperFitPerCcdNx,
236 'deltaAperFitPerCcdNy': config.deltaAperFitPerCcdNy,
237 'deltaAperFitSpatialNside': config.deltaAperFitSpatialNside,
238 'doComputeDeltaAperExposures': config.doComputeDeltaAperPerVisit,
239 'doComputeDeltaAperStars': config.doComputeDeltaAperPerStar,
240 'doComputeDeltaAperMap': config.doComputeDeltaAperMap,
241 'doComputeDeltaAperPerCcd': config.doComputeDeltaAperPerCcd,
242 'printOnly': False,
243 'quietMode': config.quietMode,
244 'randomSeed': config.randomSeed,
245 'outputStars': False,
246 'outputPath': os.path.abspath('.'),
247 'clobber': True,
248 'useSedLUT': False,
249 'resetParameters': resetFitParameters,
250 'doPlots': doPlots,
251 'outputFgcmcalZpts': True, # when outputting zpts, use fgcmcal format
252 'outputZeropoints': outputZeropoints}
253
254 return configDict
255
256
257def translateFgcmLut(lutCat, physicalFilterMap):
258 """
259 Translate the FGCM look-up-table into an fgcm-compatible object
260
261 Parameters
262 ----------
263 lutCat: `lsst.afw.table.BaseCatalog`
264 Catalog describing the FGCM look-up table
265 physicalFilterMap: `dict`
266 Physical filter to band mapping
267
268 Returns
269 -------
270 fgcmLut: `lsst.fgcm.FgcmLut`
271 Lookup table for FGCM
272 lutIndexVals: `numpy.ndarray`
273 Numpy array with LUT index information for FGCM
274 lutStd: `numpy.ndarray`
275 Numpy array with LUT standard throughput values for FGCM
276
277 Notes
278 -----
279 After running this code, it is wise to `del lutCat` to clear the memory.
280 """
281
282 # first we need the lutIndexVals
283 lutFilterNames = np.array(lutCat[0]['physicalFilters'].split(','), dtype='U')
284 lutStdFilterNames = np.array(lutCat[0]['stdPhysicalFilters'].split(','), dtype='U')
285
286 # Note that any discrepancies between config values will raise relevant
287 # exceptions in the FGCM code.
288
289 lutIndexVals = np.zeros(1, dtype=[('FILTERNAMES', lutFilterNames.dtype.str,
290 lutFilterNames.size),
291 ('STDFILTERNAMES', lutStdFilterNames.dtype.str,
292 lutStdFilterNames.size),
293 ('PMB', 'f8', lutCat[0]['pmb'].size),
294 ('PMBFACTOR', 'f8', lutCat[0]['pmbFactor'].size),
295 ('PMBELEVATION', 'f8'),
296 ('LAMBDANORM', 'f8'),
297 ('PWV', 'f8', lutCat[0]['pwv'].size),
298 ('O3', 'f8', lutCat[0]['o3'].size),
299 ('TAU', 'f8', lutCat[0]['tau'].size),
300 ('ALPHA', 'f8', lutCat[0]['alpha'].size),
301 ('ZENITH', 'f8', lutCat[0]['zenith'].size),
302 ('NCCD', 'i4')])
303
304 lutIndexVals['FILTERNAMES'][:] = lutFilterNames
305 lutIndexVals['STDFILTERNAMES'][:] = lutStdFilterNames
306 lutIndexVals['PMB'][:] = lutCat[0]['pmb']
307 lutIndexVals['PMBFACTOR'][:] = lutCat[0]['pmbFactor']
308 lutIndexVals['PMBELEVATION'] = lutCat[0]['pmbElevation']
309 lutIndexVals['LAMBDANORM'] = lutCat[0]['lambdaNorm']
310 lutIndexVals['PWV'][:] = lutCat[0]['pwv']
311 lutIndexVals['O3'][:] = lutCat[0]['o3']
312 lutIndexVals['TAU'][:] = lutCat[0]['tau']
313 lutIndexVals['ALPHA'][:] = lutCat[0]['alpha']
314 lutIndexVals['ZENITH'][:] = lutCat[0]['zenith']
315 lutIndexVals['NCCD'] = lutCat[0]['nCcd']
316
317 # now we need the Standard Values
318 lutStd = np.zeros(1, dtype=[('PMBSTD', 'f8'),
319 ('PWVSTD', 'f8'),
320 ('O3STD', 'f8'),
321 ('TAUSTD', 'f8'),
322 ('ALPHASTD', 'f8'),
323 ('ZENITHSTD', 'f8'),
324 ('LAMBDARANGE', 'f8', 2),
325 ('LAMBDASTEP', 'f8'),
326 ('LAMBDASTD', 'f8', lutFilterNames.size),
327 ('LAMBDASTDFILTER', 'f8', lutStdFilterNames.size),
328 ('I0STD', 'f8', lutFilterNames.size),
329 ('I1STD', 'f8', lutFilterNames.size),
330 ('I10STD', 'f8', lutFilterNames.size),
331 ('I2STD', 'f8', lutFilterNames.size),
332 ('LAMBDAB', 'f8', lutFilterNames.size),
333 ('ATMLAMBDA', 'f8', lutCat[0]['atmLambda'].size),
334 ('ATMSTDTRANS', 'f8', lutCat[0]['atmStdTrans'].size)])
335 lutStd['PMBSTD'] = lutCat[0]['pmbStd']
336 lutStd['PWVSTD'] = lutCat[0]['pwvStd']
337 lutStd['O3STD'] = lutCat[0]['o3Std']
338 lutStd['TAUSTD'] = lutCat[0]['tauStd']
339 lutStd['ALPHASTD'] = lutCat[0]['alphaStd']
340 lutStd['ZENITHSTD'] = lutCat[0]['zenithStd']
341 lutStd['LAMBDARANGE'][:] = lutCat[0]['lambdaRange'][:]
342 lutStd['LAMBDASTEP'] = lutCat[0]['lambdaStep']
343 lutStd['LAMBDASTD'][:] = lutCat[0]['lambdaStd']
344 lutStd['LAMBDASTDFILTER'][:] = lutCat[0]['lambdaStdFilter']
345 lutStd['I0STD'][:] = lutCat[0]['i0Std']
346 lutStd['I1STD'][:] = lutCat[0]['i1Std']
347 lutStd['I10STD'][:] = lutCat[0]['i10Std']
348 lutStd['I2STD'][:] = lutCat[0]['i2Std']
349 lutStd['LAMBDAB'][:] = lutCat[0]['lambdaB']
350 lutStd['ATMLAMBDA'][:] = lutCat[0]['atmLambda'][:]
351 lutStd['ATMSTDTRANS'][:] = lutCat[0]['atmStdTrans'][:]
352
353 lutTypes = [row['luttype'] for row in lutCat]
354
355 # And the flattened look-up-table
356 lutFlat = np.zeros(lutCat[0]['lut'].size, dtype=[('I0', 'f4'),
357 ('I1', 'f4')])
358
359 lutFlat['I0'][:] = lutCat[lutTypes.index('I0')]['lut'][:]
360 lutFlat['I1'][:] = lutCat[lutTypes.index('I1')]['lut'][:]
361
362 lutDerivFlat = np.zeros(lutCat[0]['lut'].size, dtype=[('D_LNPWV', 'f4'),
363 ('D_O3', 'f4'),
364 ('D_LNTAU', 'f4'),
365 ('D_ALPHA', 'f4'),
366 ('D_SECZENITH', 'f4'),
367 ('D_LNPWV_I1', 'f4'),
368 ('D_O3_I1', 'f4'),
369 ('D_LNTAU_I1', 'f4'),
370 ('D_ALPHA_I1', 'f4'),
371 ('D_SECZENITH_I1', 'f4')])
372
373 for name in lutDerivFlat.dtype.names:
374 lutDerivFlat[name][:] = lutCat[lutTypes.index(name)]['lut'][:]
375
376 # The fgcm.FgcmLUT() class copies all the LUT information into special
377 # shared memory objects that will not blow up the memory usage when used
378 # with python multiprocessing. Once all the numbers are copied, the
379 # references to the temporary objects (lutCat, lutFlat, lutDerivFlat)
380 # will fall out of scope and can be cleaned up by the garbage collector.
381 fgcmLut = fgcm.FgcmLUT(lutIndexVals, lutFlat, lutDerivFlat, lutStd,
382 filterToBand=physicalFilterMap)
383
384 return fgcmLut, lutIndexVals, lutStd
385
386
388 """
389 Translate the FGCM visit catalog to an fgcm-compatible object
390
391 Parameters
392 ----------
393 visitCat: `lsst.afw.table.BaseCatalog`
394 FGCM visitCat from `lsst.fgcmcal.FgcmBuildStarsTask`
395
396 Returns
397 -------
398 fgcmExpInfo: `numpy.ndarray`
399 Numpy array for visit information for FGCM
400
401 Notes
402 -----
403 After running this code, it is wise to `del visitCat` to clear the memory.
404 """
405
406 fgcmExpInfo = np.zeros(len(visitCat), dtype=[('VISIT', 'i8'),
407 ('MJD', 'f8'),
408 ('EXPTIME', 'f8'),
409 ('PSFFWHM', 'f8'),
410 ('DELTA_APER', 'f8'),
411 ('SKYBACKGROUND', 'f8'),
412 ('DEEPFLAG', 'i2'),
413 ('TELHA', 'f8'),
414 ('TELRA', 'f8'),
415 ('TELDEC', 'f8'),
416 ('TELROT', 'f8'),
417 ('PMB', 'f8'),
418 ('FILTERNAME', 'S50')])
419 fgcmExpInfo['VISIT'][:] = visitCat['visit']
420 fgcmExpInfo['MJD'][:] = visitCat['mjd']
421 fgcmExpInfo['EXPTIME'][:] = visitCat['exptime']
422 fgcmExpInfo['DEEPFLAG'][:] = visitCat['deepFlag']
423 fgcmExpInfo['TELHA'][:] = visitCat['telha']
424 fgcmExpInfo['TELRA'][:] = visitCat['telra']
425 fgcmExpInfo['TELDEC'][:] = visitCat['teldec']
426 fgcmExpInfo['TELROT'][:] = visitCat['telrot']
427 fgcmExpInfo['PMB'][:] = visitCat['pmb']
428 fgcmExpInfo['PSFFWHM'][:] = visitCat['psfFwhm']
429 fgcmExpInfo['DELTA_APER'][:] = visitCat['deltaAper']
430 fgcmExpInfo['SKYBACKGROUND'][:] = visitCat['skyBackground']
431 # Note that we have to go through asAstropy() to get a string
432 # array out of an afwTable. This is faster than a row-by-row loop.
433 fgcmExpInfo['FILTERNAME'][:] = visitCat.asAstropy()['physicalFilter']
434
435 return fgcmExpInfo
436
437
439 """
440 Compute a default visitInfo for use with pixel scale / jacobians.
441
442 Returns
443 -------
444 visitInfo : `lsst.afw.image.VisitInfo`
445 The visit info object.
446 """
447 # Generate fake WCSs centered at 180/0 to avoid the RA=0/360 problem,
448 # since we are looking for relative scales
449 boresight = geom.SpherePoint(180.0*geom.degrees, 0.0*geom.degrees)
450 orientation = 0.0*geom.degrees
451
452 # Create a temporary visitInfo for input to createInitialSkyWcs
453 # The orientation does not matter for the area computation
454 visitInfo = afwImage.VisitInfo(boresightRaDec=boresight,
455 boresightRotAngle=orientation,
456 rotType=afwImage.RotType.SKY)
457 return visitInfo
458
459
461 """
462 Compute the median pixel scale in the camera
463
464 Returns
465 -------
466 pixelScale: `float`
467 Average pixel scale (arcsecond) over the camera
468 """
469 visitInfo = _computeDefaultVisitInfo()
470
471 pixelScales = np.zeros(len(camera))
472 for i, detector in enumerate(camera):
473 wcs = createInitialSkyWcs(visitInfo, detector, False)
474 pixelScales[i] = wcs.getPixelScale(detector.getBBox().getCenter()).asArcseconds()
475
476 ok, = np.where(pixelScales > 0.0)
477 return np.median(pixelScales[ok])
478
479
480def computePixelAreaFieldDetector(detector, visitInfo=None, areaScaling=1.0, approximate=False):
481 """
482 Compute the reference pixel area field for a detector.
483
484 Parameters
485 ----------
486 visitInfo : `lsst.afw.image.VisitInfo`
487 The visit info object.
488 detector : `lsst.afw.cameraGeom.detector`
489 The detector object.
490 visitInfo : `lsst.afw.image.VisitInfo`, optional
491 If not supplied, will compute a default visitInfo.
492 areaScaling : `float`, optional
493 Area scaling factor.
494 approximate : `bool`, optional
495 Compute Chebyshev approximation to pixel area field?
496
497 Returns
498 -------
499 pixelAreaField : `lsst.afw.math.BoundedField`
500 Bounded field describing the pixel area from the detector model.
501 """
502 if visitInfo is None:
503 visitInfo = _computeDefaultVisitInfo()
504
505 wcs = createInitialSkyWcs(visitInfo, detector, False)
506 bbox = detector.getBBox()
507
508 pixelAreaField = afwMath.PixelAreaBoundedField(
509 bbox,
510 wcs,
511 unit=geom.arcseconds,
512 scaling=areaScaling,
513 )
514
515 if approximate:
516 pixelAreaField = afwMath.ChebyshevBoundedField.approximate(pixelAreaField)
517
518 return pixelAreaField
519
520
522 """
523 Compute the approximate pixel area bounded fields from the camera
524 geometry.
525
526 Parameters
527 ----------
528 camera: `lsst.afw.cameraGeom.Camera`
529
530 Returns
531 -------
532 approxPixelAreaFields: `dict`
533 Dictionary of approximate area fields, keyed with detector ID
534 """
535
536 areaScaling = 1. / computeReferencePixelScale(camera)**2.
537
538 visitInfo = _computeDefaultVisitInfo()
539
540 approxPixelAreaFields = {}
541
542 for i, detector in enumerate(camera):
543 key = detector.getId()
544
545 approxAreaField = computePixelAreaFieldDetector(
546 detector,
547 visitInfo=visitInfo,
548 areaScaling=areaScaling,
549 approximate=True,
550 )
551
552 approxPixelAreaFields[key] = approxAreaField
553
554 return approxPixelAreaFields
555
556
557def makeZptSchema(superStarChebyshevSize, zptChebyshevSize):
558 """
559 Make the zeropoint schema
560
561 Parameters
562 ----------
563 superStarChebyshevSize: `int`
564 Length of the superstar chebyshev array
565 zptChebyshevSize: `int`
566 Length of the zeropoint chebyshev array
567
568 Returns
569 -------
570 zptSchema: `lsst.afw.table.schema`
571 """
572
573 zptSchema = afwTable.Schema()
574
575 zptSchema.addField('visit', type=np.int64, doc='Visit number')
576 zptSchema.addField('detector', type=np.int32, doc='Detector ID number')
577 zptSchema.addField('fgcmFlag', type=np.int32, doc=('FGCM flag value: '
578 '1: Photometric, used in fit; '
579 '2: Photometric, not used in fit; '
580 '4: Non-photometric, on partly photometric night; '
581 '8: Non-photometric, on non-photometric night; '
582 '16: No zeropoint could be determined; '
583 '32: Too few stars for reliable gray computation'))
584 zptSchema.addField('fgcmZpt', type=np.float64, doc='FGCM zeropoint (center of CCD)')
585 zptSchema.addField('fgcmZptErr', type=np.float64,
586 doc='Error on zeropoint, estimated from repeatability + number of obs')
587 zptSchema.addField('fgcmfZptChebXyMax', type='ArrayD', size=2,
588 doc='maximum x/maximum y to scale to apply chebyshev parameters')
589 zptSchema.addField('fgcmfZptCheb', type='ArrayD',
590 size=zptChebyshevSize,
591 doc='Chebyshev parameters (flattened) for zeropoint')
592 zptSchema.addField('fgcmfZptSstarCheb', type='ArrayD',
593 size=superStarChebyshevSize,
594 doc='Chebyshev parameters (flattened) for superStarFlat')
595 zptSchema.addField('fgcmI0', type=np.float64, doc='Integral of the passband')
596 zptSchema.addField('fgcmI10', type=np.float64, doc='Normalized chromatic integral')
597 zptSchema.addField('fgcmR0', type=np.float64,
598 doc='Retrieved i0 integral, estimated from stars (only for flag 1)')
599 zptSchema.addField('fgcmR10', type=np.float64,
600 doc='Retrieved i10 integral, estimated from stars (only for flag 1)')
601 zptSchema.addField('fgcmGry', type=np.float64,
602 doc='Estimated gray extinction relative to atmospheric solution; '
603 'only for fgcmFlag <= 4 (see fgcmFlag) ')
604 zptSchema.addField('fgcmDeltaChrom', type=np.float64,
605 doc='Mean chromatic correction for stars in this ccd; '
606 'only for fgcmFlag <= 4 (see fgcmFlag)')
607 zptSchema.addField('fgcmZptVar', type=np.float64, doc='Variance of zeropoint over ccd')
608 zptSchema.addField('fgcmTilings', type=np.float64,
609 doc='Number of photometric tilings used for solution for ccd')
610 zptSchema.addField('fgcmFpGry', type=np.float64,
611 doc='Average gray extinction over the full focal plane '
612 '(same for all ccds in a visit)')
613 zptSchema.addField('fgcmFpGryBlue', type=np.float64,
614 doc='Average gray extinction over the full focal plane '
615 'for 25% bluest stars')
616 zptSchema.addField('fgcmFpGryBlueErr', type=np.float64,
617 doc='Error on Average gray extinction over the full focal plane '
618 'for 25% bluest stars')
619 zptSchema.addField('fgcmFpGryRed', type=np.float64,
620 doc='Average gray extinction over the full focal plane '
621 'for 25% reddest stars')
622 zptSchema.addField('fgcmFpGryRedErr', type=np.float64,
623 doc='Error on Average gray extinction over the full focal plane '
624 'for 25% reddest stars')
625 zptSchema.addField('fgcmFpVar', type=np.float64,
626 doc='Variance of gray extinction over the full focal plane '
627 '(same for all ccds in a visit)')
628 zptSchema.addField('fgcmDust', type=np.float64,
629 doc='Gray dust extinction from the primary/corrector'
630 'at the time of the exposure')
631 zptSchema.addField('fgcmFlat', type=np.float64, doc='Superstarflat illumination correction')
632 zptSchema.addField('fgcmAperCorr', type=np.float64, doc='Aperture correction estimated by fgcm')
633 zptSchema.addField('fgcmDeltaMagBkg', type=np.float64,
634 doc=('Local background correction from brightest percentile '
635 '(value set by deltaMagBkgOffsetPercentile) calibration '
636 'stars.'))
637 zptSchema.addField('exptime', type=np.float32, doc='Exposure time')
638 zptSchema.addField('filtername', type=str, size=30, doc='Filter name')
639
640 return zptSchema
641
642
643def makeZptCat(zptSchema, zpStruct):
644 """
645 Make the zeropoint catalog for persistence
646
647 Parameters
648 ----------
649 zptSchema: `lsst.afw.table.Schema`
650 Zeropoint catalog schema
651 zpStruct: `numpy.ndarray`
652 Zeropoint structure from fgcm
653
654 Returns
655 -------
656 zptCat: `afwTable.BaseCatalog`
657 Zeropoint catalog for persistence
658 """
659
660 zptCat = afwTable.BaseCatalog(zptSchema)
661 zptCat.reserve(zpStruct.size)
662
663 for filterName in zpStruct['FILTERNAME']:
664 rec = zptCat.addNew()
665 rec['filtername'] = filterName.decode('utf-8')
666
667 zptCat['visit'][:] = zpStruct[FGCM_EXP_FIELD]
668 zptCat['detector'][:] = zpStruct[FGCM_CCD_FIELD]
669 zptCat['fgcmFlag'][:] = zpStruct['FGCM_FLAG']
670 zptCat['fgcmZpt'][:] = zpStruct['FGCM_ZPT']
671 zptCat['fgcmZptErr'][:] = zpStruct['FGCM_ZPTERR']
672 zptCat['fgcmfZptChebXyMax'][:, :] = zpStruct['FGCM_FZPT_XYMAX']
673 zptCat['fgcmfZptCheb'][:, :] = zpStruct['FGCM_FZPT_CHEB']
674 zptCat['fgcmfZptSstarCheb'][:, :] = zpStruct['FGCM_FZPT_SSTAR_CHEB']
675 zptCat['fgcmI0'][:] = zpStruct['FGCM_I0']
676 zptCat['fgcmI10'][:] = zpStruct['FGCM_I10']
677 zptCat['fgcmR0'][:] = zpStruct['FGCM_R0']
678 zptCat['fgcmR10'][:] = zpStruct['FGCM_R10']
679 zptCat['fgcmGry'][:] = zpStruct['FGCM_GRY']
680 zptCat['fgcmDeltaChrom'][:] = zpStruct['FGCM_DELTACHROM']
681 zptCat['fgcmZptVar'][:] = zpStruct['FGCM_ZPTVAR']
682 zptCat['fgcmTilings'][:] = zpStruct['FGCM_TILINGS']
683 zptCat['fgcmFpGry'][:] = zpStruct['FGCM_FPGRY']
684 zptCat['fgcmFpGryBlue'][:] = zpStruct['FGCM_FPGRY_CSPLIT'][:, 0]
685 zptCat['fgcmFpGryBlueErr'][:] = zpStruct['FGCM_FPGRY_CSPLITERR'][:, 0]
686 zptCat['fgcmFpGryRed'][:] = zpStruct['FGCM_FPGRY_CSPLIT'][:, 2]
687 zptCat['fgcmFpGryRedErr'][:] = zpStruct['FGCM_FPGRY_CSPLITERR'][:, 2]
688 zptCat['fgcmFpVar'][:] = zpStruct['FGCM_FPVAR']
689 zptCat['fgcmDust'][:] = zpStruct['FGCM_DUST']
690 zptCat['fgcmFlat'][:] = zpStruct['FGCM_FLAT']
691 zptCat['fgcmAperCorr'][:] = zpStruct['FGCM_APERCORR']
692 zptCat['fgcmDeltaMagBkg'][:] = zpStruct['FGCM_DELTAMAGBKG']
693 zptCat['exptime'][:] = zpStruct['EXPTIME']
694
695 return zptCat
696
697
699 """
700 Make the atmosphere schema
701
702 Returns
703 -------
704 atmSchema: `lsst.afw.table.Schema`
705 """
706
707 atmSchema = afwTable.Schema()
708
709 atmSchema.addField('visit', type=np.int64, doc='Visit number')
710 atmSchema.addField('pmb', type=np.float64, doc='Barometric pressure (mb)')
711 atmSchema.addField('pwv', type=np.float64, doc='Water vapor (mm)')
712 atmSchema.addField('tau', type=np.float64, doc='Aerosol optical depth')
713 atmSchema.addField('alpha', type=np.float64, doc='Aerosol slope')
714 atmSchema.addField('o3', type=np.float64, doc='Ozone (dobson)')
715 atmSchema.addField('secZenith', type=np.float64, doc='Secant(zenith) (~ airmass)')
716 atmSchema.addField('cTrans', type=np.float64, doc='Transmission correction factor')
717 atmSchema.addField('lamStd', type=np.float64, doc='Wavelength for transmission correction')
718
719 return atmSchema
720
721
722def makeAtmCat(atmSchema, atmStruct):
723 """
724 Make the atmosphere catalog for persistence
725
726 Parameters
727 ----------
728 atmSchema: `lsst.afw.table.Schema`
729 Atmosphere catalog schema
730 atmStruct: `numpy.ndarray`
731 Atmosphere structure from fgcm
732
733 Returns
734 -------
735 atmCat: `lsst.afw.table.BaseCatalog`
736 Atmosphere catalog for persistence
737 """
738
739 atmCat = afwTable.BaseCatalog(atmSchema)
740 atmCat.resize(atmStruct.size)
741
742 atmCat['visit'][:] = atmStruct['VISIT']
743 atmCat['pmb'][:] = atmStruct['PMB']
744 atmCat['pwv'][:] = atmStruct['PWV']
745 atmCat['tau'][:] = atmStruct['TAU']
746 atmCat['alpha'][:] = atmStruct['ALPHA']
747 atmCat['o3'][:] = atmStruct['O3']
748 atmCat['secZenith'][:] = atmStruct['SECZENITH']
749 atmCat['cTrans'][:] = atmStruct['CTRANS']
750 atmCat['lamStd'][:] = atmStruct['LAMSTD']
751
752 return atmCat
753
754
755def makeStdSchema(nBands):
756 """
757 Make the standard star schema
758
759 Parameters
760 ----------
761 nBands: `int`
762 Number of bands in standard star catalog
763
764 Returns
765 -------
766 stdSchema: `lsst.afw.table.Schema`
767 """
768
769 stdSchema = afwTable.SimpleTable.makeMinimalSchema()
770 stdSchema.addField('isolated_star_id', type='L', doc='ID from isolated star table')
771 stdSchema.addField('ngood', type='ArrayI', doc='Number of good observations',
772 size=nBands)
773 stdSchema.addField('ntotal', type='ArrayI', doc='Number of total observations',
774 size=nBands)
775 stdSchema.addField('mag_std_noabs', type='ArrayF',
776 doc='Standard magnitude (no absolute calibration)',
777 size=nBands)
778 stdSchema.addField('magErr_std', type='ArrayF',
779 doc='Standard magnitude error',
780 size=nBands)
781 stdSchema.addField('npsfcand', type='ArrayI',
782 doc='Number of observations flagged as psf candidates',
783 size=nBands)
784 stdSchema.addField('delta_aper', type='ArrayF',
785 doc='Delta mag (small - large aperture)',
786 size=nBands)
787
788 return stdSchema
789
790
791def makeStdCat(stdSchema, stdStruct, goodBands):
792 """
793 Make the standard star catalog for persistence
794
795 Parameters
796 ----------
797 stdSchema: `lsst.afw.table.Schema`
798 Standard star catalog schema
799 stdStruct: `numpy.ndarray`
800 Standard star structure in FGCM format
801 goodBands: `list`
802 List of good band names used in stdStruct
803
804 Returns
805 -------
806 stdCat: `lsst.afw.table.BaseCatalog`
807 Standard star catalog for persistence
808 """
809
810 stdCat = afwTable.SimpleCatalog(stdSchema)
811 stdCat.resize(stdStruct.size)
812
813 stdCat['id'][:] = stdStruct['FGCM_ID']
814 stdCat['isolated_star_id'][:] = stdStruct['ALTERNATE_ID']
815 stdCat['coord_ra'][:] = stdStruct['RA'] * geom.degrees
816 stdCat['coord_dec'][:] = stdStruct['DEC'] * geom.degrees
817 stdCat['ngood'][:, :] = stdStruct['NGOOD'][:, :]
818 stdCat['ntotal'][:, :] = stdStruct['NTOTAL'][:, :]
819 stdCat['mag_std_noabs'][:, :] = stdStruct['MAG_STD'][:, :]
820 stdCat['magErr_std'][:, :] = stdStruct['MAGERR_STD'][:, :]
821 if 'NPSFCAND' in stdStruct.dtype.names:
822 stdCat['npsfcand'][:, :] = stdStruct['NPSFCAND'][:, :]
823 stdCat['delta_aper'][:, :] = stdStruct['DELTA_APER'][:, :]
824
825 md = PropertyList()
826 md.set("BANDS", list(goodBands))
827 stdCat.setMetadata(md)
828
829 return stdCat
830
831
833 """
834 Compute the radius associated with a CircularApertureFlux or ApFlux field.
835
836 Parameters
837 ----------
838 fluxField : `str`
839 CircularApertureFlux or ApFlux
840
841 Returns
842 -------
843 apertureRadius : `float`
844 Radius of the aperture field, in pixels.
845
846 Raises
847 ------
848 RuntimeError: Raised if flux field is not a CircularApertureFlux,
849 ApFlux, or apFlux.
850 """
851 # TODO: Move this method to more general stack method in DM-25775
852 m = re.search(r'(CircularApertureFlux|ApFlux|apFlux)_(\d+)_(\d+)_', fluxField)
853
854 if m is None:
855 raise RuntimeError(f"Flux field {fluxField} does not correspond to a CircularApertureFlux or ApFlux")
856
857 apertureRadius = float(m.groups()[1]) + float(m.groups()[2])/10.
858
859 return apertureRadius
860
861
862def extractReferenceMags(refStars, bands, filterMap):
863 """
864 Extract reference magnitudes from refStars for given bands and
865 associated filterMap.
866
867 Parameters
868 ----------
869 refStars : `astropy.table.Table` or `lsst.afw.table.BaseCatalog`
870 FGCM reference star catalog.
871 bands : `list`
872 List of bands for calibration.
873 filterMap: `dict`
874 FGCM mapping of filter to band.
875
876 Returns
877 -------
878 refMag : `np.ndarray`
879 nstar x nband array of reference magnitudes.
880 refMagErr : `np.ndarray`
881 nstar x nband array of reference magnitude errors.
882 """
883 hasAstropyMeta = False
884 try:
885 meta = refStars.meta
886 hasAstropyMeta = True
887 except AttributeError:
888 meta = refStars.getMetadata()
889
890 if 'FILTERNAMES' in meta:
891 if hasAstropyMeta:
892 filternames = meta['FILTERNAMES']
893 else:
894 filternames = meta.getArray('FILTERNAMES')
895
896 # The reference catalog that fgcm wants has one entry per band
897 # in the config file
898 refMag = np.zeros((len(refStars), len(bands)),
899 dtype=refStars['refMag'].dtype) + 99.0
900 refMagErr = np.zeros_like(refMag) + 99.0
901 for i, filtername in enumerate(filternames):
902 # We are allowed to run the fit configured so that we do not
903 # use every column in the reference catalog.
904 try:
905 band = filterMap[filtername]
906 except KeyError:
907 continue
908 try:
909 ind = bands.index(band)
910 except ValueError:
911 continue
912
913 refMag[:, ind] = refStars['refMag'][:, i]
914 refMagErr[:, ind] = refStars['refMagErr'][:, i]
915 else:
916 raise RuntimeError("FGCM reference stars missing FILTERNAMES metadata.")
917
918 return refMag, refMagErr
919
920
921def lookupStaticCalibrations(datasetType, registry, quantumDataId, collections):
922 # For static calibrations, we search with a timespan that has unbounded
923 # begin and end; we'll get an error if there's more than one match (because
924 # then it's not static).
925 timespan = Timespan(begin=None, end=None)
926 result = []
927 # First iterate over all of the data IDs for this dataset type that are
928 # consistent with the quantum data ID.
929 for dataId in registry.queryDataIds(datasetType.dimensions, dataId=quantumDataId):
930 # Find the dataset with this data ID using the unbounded timespan.
931 if ref := registry.findDataset(datasetType, dataId, collections=collections, timespan=timespan):
932 result.append(ref)
933 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
Class for storing ordered metadata with comments.
Point in an unspecified spherical coordinate system.
Definition SpherePoint.h:57
translateVisitCatalog(visitCat)
Definition utilities.py:387
computeApertureRadiusFromName(fluxField)
Definition utilities.py:832
makeStdCat(stdSchema, stdStruct, goodBands)
Definition utilities.py:791
makeZptCat(zptSchema, zpStruct)
Definition utilities.py:643
lookupStaticCalibrations(datasetType, registry, quantumDataId, collections)
Definition utilities.py:921
computePixelAreaFieldDetector(detector, visitInfo=None, areaScaling=1.0, approximate=False)
Definition utilities.py:480
makeConfigDict(config, log, camera, maxIter, resetFitParameters, outputZeropoints, lutFilterNames, tract=None, nCore=1, doPlots=False)
Definition utilities.py:49
computeReferencePixelScale(camera)
Definition utilities.py:460
makeAtmCat(atmSchema, atmStruct)
Definition utilities.py:722
translateFgcmLut(lutCat, physicalFilterMap)
Definition utilities.py:257
computeApproxPixelAreaFields(camera)
Definition utilities.py:521
makeZptSchema(superStarChebyshevSize, zptChebyshevSize)
Definition utilities.py:557
extractReferenceMags(refStars, bands, filterMap)
Definition utilities.py:862