Coverage for python/lsst/fgcmcal/fgcmCalibrateTractBase.py: 14%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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"""Base class for running fgcmcal on a single tract using src tables
22or sourceTable_visit tables.
23"""
25import sys
26import traceback
27import abc
29import numpy as np
31import lsst.daf.persistence as dafPersist
32import lsst.pex.config as pexConfig
33import lsst.pipe.base as pipeBase
34from lsst.utils.timer import timeMethod
36from .fgcmBuildStars import FgcmBuildStarsTask, FgcmBuildStarsConfig
37from .fgcmFitCycle import FgcmFitCycleConfig
38from .fgcmOutputProducts import FgcmOutputProductsTask
39from .utilities import makeConfigDict, translateFgcmLut, translateVisitCatalog
40from .utilities import computeCcdOffsets, computeApertureRadiusFromDataRef, extractReferenceMags
41from .utilities import makeZptSchema, makeZptCat
42from .utilities import makeAtmSchema, makeAtmCat
43from .utilities import makeStdSchema, makeStdCat
45import fgcm
47__all__ = ['FgcmCalibrateTractConfigBase', 'FgcmCalibrateTractBaseTask', 'FgcmCalibrateTractRunner']
50class FgcmCalibrateTractConfigBase(pexConfig.Config):
51 """Config for FgcmCalibrateTract"""
53 fgcmBuildStars = pexConfig.ConfigurableField(
54 target=FgcmBuildStarsTask,
55 doc="Task to load and match stars for fgcm",
56 )
57 fgcmFitCycle = pexConfig.ConfigField(
58 dtype=FgcmFitCycleConfig,
59 doc="Config to run a single fgcm fit cycle",
60 )
61 fgcmOutputProducts = pexConfig.ConfigurableField(
62 target=FgcmOutputProductsTask,
63 doc="Task to output fgcm products",
64 )
65 convergenceTolerance = pexConfig.Field(
66 doc="Tolerance on repeatability convergence (per band)",
67 dtype=float,
68 default=0.005,
69 )
70 maxFitCycles = pexConfig.Field(
71 doc="Maximum number of fit cycles",
72 dtype=int,
73 default=5,
74 )
75 doDebuggingPlots = pexConfig.Field(
76 doc="Make plots for debugging purposes?",
77 dtype=bool,
78 default=False,
79 )
81 def setDefaults(self):
82 pexConfig.Config.setDefaults(self)
84 self.fgcmFitCycle.quietMode = True
85 self.fgcmFitCycle.doPlots = False
86 self.fgcmOutputProducts.doReferenceCalibration = False
87 self.fgcmOutputProducts.doRefcatOutput = False
88 self.fgcmOutputProducts.cycleNumber = 0
89 self.fgcmOutputProducts.photoCal.applyColorTerms = False
91 def validate(self):
92 super().validate()
94 for band in self.fgcmFitCycle.bands:
95 if not self.fgcmFitCycle.useRepeatabilityForExpGrayCutsDict[band]:
96 msg = 'Must set useRepeatabilityForExpGrayCutsDict[band]=True for all bands'
97 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.useRepeatabilityForExpGrayCutsDict,
98 self, msg)
101class FgcmCalibrateTractRunner(pipeBase.ButlerInitializedTaskRunner):
102 """Subclass of TaskRunner for FgcmCalibrateTractTask
104 fgcmCalibrateTractTask.run() takes a number of arguments, one of which is
105 the butler (for persistence and mapper data), and a list of dataRefs
106 extracted from the command line. This task runs on a constrained set
107 of dataRefs, typically a single tract.
108 This class transforms the process arguments generated by the ArgumentParser
109 into the arguments expected by FgcmCalibrateTractTask.run().
110 This runner does not use any parallelization.
111 """
113 @staticmethod
114 def getTargetList(parsedCmd):
115 """
116 Return a list with one element: a tuple with the butler and
117 list of dataRefs.
118 """
119 return [(parsedCmd.butler, parsedCmd.id.refList)]
121 def __call__(self, args):
122 """
123 Parameters
124 ----------
125 args: `tuple` with (butler, dataRefList)
127 Returns
128 -------
129 exitStatus: `list` with `lsst.pipe.base.Struct`
130 exitStatus (0: success; 1: failure)
131 May also contain results if `self.doReturnResults` is `True`.
132 """
133 butler, dataRefList = args
135 task = self.TaskClass(config=self.config, log=self.log)
137 exitStatus = 0
138 if self.doRaise:
139 results = task.runDataRef(butler, dataRefList)
140 else:
141 try:
142 results = task.runDataRef(butler, dataRefList)
143 except Exception as e:
144 exitStatus = 1
145 task.log.fatal("Failed: %s" % e)
146 if not isinstance(e, pipeBase.TaskError):
147 traceback.print_exc(file=sys.stderr)
149 task.writeMetadata(butler)
151 if self.doReturnResults:
152 return [pipeBase.Struct(exitStatus=exitStatus,
153 results=results)]
154 else:
155 return [pipeBase.Struct(exitStatus=exitStatus)]
157 def run(self, parsedCmd):
158 """
159 Run the task, with no multiprocessing
161 Parameters
162 ----------
163 parsedCmd: `lsst.pipe.base.ArgumentParser` parsed command line
164 """
166 resultList = []
168 if self.precall(parsedCmd):
169 targetList = self.getTargetList(parsedCmd)
170 resultList = self(targetList[0])
172 return resultList
175class FgcmCalibrateTractBaseTask(pipeBase.PipelineTask, pipeBase.CmdLineTask, abc.ABC):
176 """Base class to calibrate a single tract using fgcmcal
177 """
178 def __init__(self, initInputs=None, butler=None, **kwargs):
179 """
180 Instantiate an `FgcmCalibrateTractTask`.
182 Parameters
183 ----------
184 butler : `lsst.daf.persistence.Butler`, optional
185 """
186 super().__init__(**kwargs)
187 self.makeSubtask("fgcmBuildStars", initInputs=initInputs, butler=butler)
188 self.makeSubtask("fgcmOutputProducts", butler=butler)
190 # no saving of metadata for now
191 def _getMetadataName(self):
192 return None
194 @timeMethod
195 def runDataRef(self, butler, dataRefs):
196 """
197 Run full FGCM calibration on a single tract, including building star list,
198 fitting multiple cycles, and making outputs.
200 Parameters
201 ----------
202 butler: `lsst.daf.persistence.Butler`
203 dataRefs: `list` of `lsst.daf.persistence.ButlerDataRef`
204 Data references for the input visits.
205 These may be either per-ccd "src" or per-visit"sourceTable_visit"
206 references.
208 Raises
209 ------
210 RuntimeError: Raised if `config.fgcmBuildStars.doReferenceMatches` is
211 not True, or if fgcmLookUpTable is not available, or if
212 doSubtractLocalBackground is True and aperture radius cannot be
213 determined.
214 """
215 datasetType = dataRefs[0].butlerSubset.datasetType
216 self.log.info("Running with %d %s dataRefs" % (len(dataRefs), datasetType))
218 if not butler.datasetExists('fgcmLookUpTable'):
219 raise RuntimeError("Must run FgcmCalibrateTract with an fgcmLookUpTable")
221 if not self.config.fgcmBuildStars.doReferenceMatches:
222 raise RuntimeError("Must run FgcmCalibrateTract with fgcmBuildStars.doReferenceMatches")
223 if isinstance(self.config.fgcmBuildStars, FgcmBuildStarsConfig):
224 if self.config.fgcmBuildStars.checkAllCcds:
225 raise RuntimeError("Cannot run FgcmCalibrateTract with "
226 "fgcmBuildStars.checkAllCcds set to True")
228 tract = int(dataRefs[0].dataId['tract'])
229 camera = butler.get('camera')
231 dataRefDict = {}
232 dataRefDict['camera'] = camera
233 dataRefDict['source_catalogs'] = dataRefs
234 dataRefDict['sourceSchema'] = butler.get('src_schema', immediate=True).schema
235 dataRefDict['fgcmLookUpTable'] = butler.dataRef('fgcmLookUpTable')
237 struct = self.run(dataRefDict, tract, butler=butler, returnCatalogs=False)
239 visitDataRefName = self.config.fgcmBuildStars.visitDataRefName
240 ccdDataRefName = self.config.fgcmBuildStars.ccdDataRefName
242 if struct.photoCalibs is not None:
243 self.log.info("Outputting photoCalib files.")
245 for visit, detector, physicalFilter, photoCalib in struct.photoCalibs:
246 butler.put(photoCalib, 'fgcm_tract_photoCalib',
247 dataId={visitDataRefName: visit,
248 ccdDataRefName: detector,
249 'filter': physicalFilter,
250 'tract': tract})
252 self.log.info("Done outputting photoCalib files.")
254 if struct.atmospheres is not None:
255 self.log.info("Outputting atmosphere files.")
256 for visit, atm in struct.atmospheres:
257 butler.put(atm, "transmission_atmosphere_fgcm_tract",
258 dataId={visitDataRefName: visit,
259 'tract': tract})
260 self.log.info("Done outputting atmosphere transmissions.")
262 return pipeBase.Struct(repeatability=struct.repeatability)
264 def run(self, dataRefDict, tract,
265 buildStarsRefObjLoader=None, returnCatalogs=True, butler=None):
266 """Run the calibrations for a single tract with fgcm.
268 Parameters
269 ----------
270 dataRefDict : `dict`
271 All dataRefs are `lsst.daf.persistence.ButlerDataRef` (gen2) or
272 `lsst.daf.butler.DeferredDatasetHandle` (gen3)
273 dataRef dictionary with the following keys. Note that all
274 keys need not be set based on config parameters.
276 ``"camera"``
277 Camera object (`lsst.afw.cameraGeom.Camera`)
278 ``"source_catalogs"``
279 `list` of dataRefs for input source catalogs.
280 ``"sourceSchema"``
281 Schema for the source catalogs.
282 ``"fgcmLookUpTable"``
283 dataRef for the FGCM look-up table.
284 ``"calexps"``
285 `list` of dataRefs for the input calexps (Gen3 only)
286 ``"fgcmPhotoCalibs"``
287 `dict` of output photoCalib dataRefs. Key is
288 (tract, visit, detector). (Gen3 only)
289 Present if doZeropointOutput is True.
290 ``"fgcmTransmissionAtmospheres"``
291 `dict` of output atmosphere transmission dataRefs.
292 Key is (tract, visit). (Gen3 only)
293 Present if doAtmosphereOutput is True.
294 tract : `int`
295 Tract number
296 buildStarsRefObjLoader : `lsst.meas.algorithms.ReferenceObjectLoader`, optional
297 Reference object loader object for fgcmBuildStars.
298 returnCatalogs : `bool`, optional
299 Return photoCalibs as per-visit exposure catalogs.
300 butler : `lsst.daf.persistence.Butler`, optional
301 Gen2 butler used for reference star outputs
303 Returns
304 -------
305 outstruct : `lsst.pipe.base.Struct`
306 Output structure with keys:
308 offsets : `np.ndarray`
309 Final reference offsets, per band.
310 repeatability : `np.ndarray`
311 Raw fgcm repeatability for bright stars, per band.
312 atmospheres : `generator` [(`int`, `lsst.afw.image.TransmissionCurve`)]
313 Generator that returns (visit, transmissionCurve) tuples.
314 photoCalibs : `generator` [(`int`, `int`, `str`, `lsst.afw.image.PhotoCalib`)]
315 Generator that returns (visit, ccd, filtername, photoCalib) tuples.
316 (returned if returnCatalogs is False).
317 photoCalibCatalogs : `generator` [(`int`, `lsst.afw.table.ExposureCatalog`)]
318 Generator that returns (visit, exposureCatalog) tuples.
319 (returned if returnCatalogs is True).
320 """
321 self.log.info("Running on tract %d", (tract))
323 # Compute the aperture radius if necessary. This is useful to do now before
324 # any heavy lifting has happened (fail early).
325 calibFluxApertureRadius = None
326 if self.config.fgcmBuildStars.doSubtractLocalBackground:
327 try:
328 field = self.config.fgcmBuildStars.instFluxField
329 calibFluxApertureRadius = computeApertureRadiusFromDataRef(dataRefDict['source_catalogs'][0],
330 field)
331 except RuntimeError:
332 raise RuntimeError("Could not determine aperture radius from %s. "
333 "Cannot use doSubtractLocalBackground." %
334 (field))
336 # Run the build stars tasks
338 # Note that we will need visitCat at the end of the procedure for the outputs
339 if isinstance(butler, dafPersist.Butler):
340 # Gen2
341 groupedDataRefs = self.fgcmBuildStars._findAndGroupDataRefsGen2(butler, dataRefDict['camera'],
342 dataRefDict['source_catalogs'])
343 else:
344 # Gen3
345 groupedDataRefs = self.fgcmBuildStars._groupDataRefs(dataRefDict['sourceTableDataRefDict'],
346 dataRefDict['visitSummaryDataRefDict'])
347 visitCat = self.fgcmBuildStars.fgcmMakeVisitCatalog(dataRefDict['camera'], groupedDataRefs)
348 rad = calibFluxApertureRadius
349 fgcmStarObservationCat = self.fgcmBuildStars.fgcmMakeAllStarObservations(groupedDataRefs,
350 visitCat,
351 dataRefDict['sourceSchema'],
352 dataRefDict['camera'],
353 calibFluxApertureRadius=rad)
355 if self.fgcmBuildStars.config.doReferenceMatches:
356 lutDataRef = dataRefDict['fgcmLookUpTable']
357 if buildStarsRefObjLoader is not None:
358 self.fgcmBuildStars.makeSubtask("fgcmLoadReferenceCatalog",
359 refObjLoader=buildStarsRefObjLoader)
360 else:
361 self.fgcmBuildStars.makeSubtask("fgcmLoadReferenceCatalog", butler=butler)
362 else:
363 lutDataRef = None
365 fgcmStarIdCat, fgcmStarIndicesCat, fgcmRefCat = \
366 self.fgcmBuildStars.fgcmMatchStars(visitCat,
367 fgcmStarObservationCat,
368 lutDataRef=lutDataRef)
370 # Load the LUT
371 lutCat = dataRefDict['fgcmLookUpTable'].get()
372 fgcmLut, lutIndexVals, lutStd = translateFgcmLut(lutCat,
373 dict(self.config.fgcmFitCycle.physicalFilterMap))
374 del lutCat
376 # Translate the visit catalog into fgcm format
377 fgcmExpInfo = translateVisitCatalog(visitCat)
379 configDict = makeConfigDict(self.config.fgcmFitCycle, self.log, dataRefDict['camera'],
380 self.config.fgcmFitCycle.maxIterBeforeFinalCycle,
381 True, False, lutIndexVals[0]['FILTERNAMES'],
382 tract=tract)
384 # Use the first orientation.
385 # TODO: DM-21215 will generalize to arbitrary camera orientations
386 ccdOffsets = computeCcdOffsets(dataRefDict['camera'], fgcmExpInfo['TELROT'][0])
388 # Set up the fit cycle task
390 noFitsDict = {'lutIndex': lutIndexVals,
391 'lutStd': lutStd,
392 'expInfo': fgcmExpInfo,
393 'ccdOffsets': ccdOffsets}
395 fgcmFitCycle = fgcm.FgcmFitCycle(configDict, useFits=False,
396 noFitsDict=noFitsDict, noOutput=True)
398 # We determine the conversion from the native units (typically radians) to
399 # degrees for the first star. This allows us to treat coord_ra/coord_dec as
400 # numpy arrays rather than Angles, which would we approximately 600x slower.
401 conv = fgcmStarObservationCat[0]['ra'].asDegrees() / float(fgcmStarObservationCat[0]['ra'])
403 # To load the stars, we need an initial parameter object
404 fgcmPars = fgcm.FgcmParameters.newParsWithArrays(fgcmFitCycle.fgcmConfig,
405 fgcmLut,
406 fgcmExpInfo)
408 # Match star observations to visits
409 # Only those star observations that match visits from fgcmExpInfo['VISIT'] will
410 # actually be transferred into fgcm using the indexing below.
412 obsIndex = fgcmStarIndicesCat['obsIndex']
413 visitIndex = np.searchsorted(fgcmExpInfo['VISIT'],
414 fgcmStarObservationCat['visit'][obsIndex])
416 refMag, refMagErr = extractReferenceMags(fgcmRefCat,
417 self.config.fgcmFitCycle.bands,
418 self.config.fgcmFitCycle.physicalFilterMap)
419 refId = fgcmRefCat['fgcm_id'][:]
421 fgcmStars = fgcm.FgcmStars(fgcmFitCycle.fgcmConfig)
422 fgcmStars.loadStars(fgcmPars,
423 fgcmStarObservationCat['visit'][obsIndex],
424 fgcmStarObservationCat['ccd'][obsIndex],
425 fgcmStarObservationCat['ra'][obsIndex] * conv,
426 fgcmStarObservationCat['dec'][obsIndex] * conv,
427 fgcmStarObservationCat['instMag'][obsIndex],
428 fgcmStarObservationCat['instMagErr'][obsIndex],
429 fgcmExpInfo['FILTERNAME'][visitIndex],
430 fgcmStarIdCat['fgcm_id'][:],
431 fgcmStarIdCat['ra'][:],
432 fgcmStarIdCat['dec'][:],
433 fgcmStarIdCat['obsArrIndex'][:],
434 fgcmStarIdCat['nObs'][:],
435 obsX=fgcmStarObservationCat['x'][obsIndex],
436 obsY=fgcmStarObservationCat['y'][obsIndex],
437 obsDeltaMagBkg=fgcmStarObservationCat['deltaMagBkg'][obsIndex],
438 psfCandidate=fgcmStarObservationCat['psf_candidate'][obsIndex],
439 refID=refId,
440 refMag=refMag,
441 refMagErr=refMagErr,
442 flagID=None,
443 flagFlag=None,
444 computeNobs=True)
446 # Clear out some memory
447 del fgcmStarIdCat
448 del fgcmStarIndicesCat
449 del fgcmRefCat
451 fgcmFitCycle.setLUT(fgcmLut)
452 fgcmFitCycle.setStars(fgcmStars, fgcmPars)
454 converged = False
455 cycleNumber = 0
457 previousReservedRawRepeatability = np.zeros(fgcmPars.nBands) + 1000.0
458 previousParInfo = None
459 previousParams = None
460 previousSuperStar = None
462 while (not converged and cycleNumber < self.config.maxFitCycles):
464 fgcmFitCycle.fgcmConfig.updateCycleNumber(cycleNumber)
466 if cycleNumber > 0:
467 # Use parameters from previous cycle
468 fgcmPars = fgcm.FgcmParameters.loadParsWithArrays(fgcmFitCycle.fgcmConfig,
469 fgcmExpInfo,
470 previousParInfo,
471 previousParams,
472 previousSuperStar)
473 # We need to reset the star magnitudes and errors for the next
474 # cycle
475 fgcmFitCycle.fgcmStars.reloadStarMagnitudes(fgcmStarObservationCat['instMag'][obsIndex],
476 fgcmStarObservationCat['instMagErr'][obsIndex])
477 fgcmFitCycle.initialCycle = False
479 fgcmFitCycle.setPars(fgcmPars)
480 fgcmFitCycle.finishSetup()
482 fgcmFitCycle.run()
484 # Grab the parameters for the next cycle
485 previousParInfo, previousParams = fgcmFitCycle.fgcmPars.parsToArrays()
486 previousSuperStar = fgcmFitCycle.fgcmPars.parSuperStarFlat.copy()
488 self.log.info("Raw repeatability after cycle number %d is:" % (cycleNumber))
489 for i, band in enumerate(fgcmFitCycle.fgcmPars.bands):
490 if not fgcmFitCycle.fgcmPars.hasExposuresInBand[i]:
491 continue
492 rep = fgcmFitCycle.fgcmPars.compReservedRawRepeatability[i] * 1000.0
493 self.log.info(" Band %s, repeatability: %.2f mmag" % (band, rep))
495 # Check for convergence
496 if np.alltrue((previousReservedRawRepeatability
497 - fgcmFitCycle.fgcmPars.compReservedRawRepeatability)
498 < self.config.convergenceTolerance):
499 self.log.info("Raw repeatability has converged after cycle number %d." % (cycleNumber))
500 converged = True
501 else:
502 fgcmFitCycle.fgcmConfig.expGrayPhotometricCut[:] = fgcmFitCycle.updatedPhotometricCut
503 fgcmFitCycle.fgcmConfig.expGrayHighCut[:] = fgcmFitCycle.updatedHighCut
504 fgcmFitCycle.fgcmConfig.precomputeSuperStarInitialCycle = False
505 fgcmFitCycle.fgcmConfig.freezeStdAtmosphere = False
506 previousReservedRawRepeatability[:] = fgcmFitCycle.fgcmPars.compReservedRawRepeatability
507 self.log.info("Setting exposure gray photometricity cuts to:")
508 for i, band in enumerate(fgcmFitCycle.fgcmPars.bands):
509 if not fgcmFitCycle.fgcmPars.hasExposuresInBand[i]:
510 continue
511 cut = fgcmFitCycle.updatedPhotometricCut[i] * 1000.0
512 self.log.info(" Band %s, photometricity cut: %.2f mmag" % (band, cut))
514 cycleNumber += 1
516 # Log warning if not converged
517 if not converged:
518 self.log.warning("Maximum number of fit cycles exceeded (%d) without convergence.", cycleNumber)
520 # Do final clean-up iteration
521 fgcmFitCycle.fgcmConfig.freezeStdAtmosphere = False
522 fgcmFitCycle.fgcmConfig.resetParameters = False
523 fgcmFitCycle.fgcmConfig.maxIter = 0
524 fgcmFitCycle.fgcmConfig.outputZeropoints = True
525 fgcmFitCycle.fgcmConfig.outputStandards = True
526 fgcmFitCycle.fgcmConfig.doPlots = self.config.doDebuggingPlots
527 fgcmFitCycle.fgcmConfig.updateCycleNumber(cycleNumber)
528 fgcmFitCycle.initialCycle = False
530 fgcmPars = fgcm.FgcmParameters.loadParsWithArrays(fgcmFitCycle.fgcmConfig,
531 fgcmExpInfo,
532 previousParInfo,
533 previousParams,
534 previousSuperStar)
535 fgcmFitCycle.fgcmStars.reloadStarMagnitudes(fgcmStarObservationCat['instMag'][obsIndex],
536 fgcmStarObservationCat['instMagErr'][obsIndex])
537 fgcmFitCycle.setPars(fgcmPars)
538 fgcmFitCycle.finishSetup()
540 self.log.info("Running final clean-up fit cycle...")
541 fgcmFitCycle.run()
543 self.log.info("Raw repeatability after clean-up cycle is:")
544 for i, band in enumerate(fgcmFitCycle.fgcmPars.bands):
545 if not fgcmFitCycle.fgcmPars.hasExposuresInBand[i]:
546 continue
547 rep = fgcmFitCycle.fgcmPars.compReservedRawRepeatability[i] * 1000.0
548 self.log.info(" Band %s, repeatability: %.2f mmag" % (band, rep))
550 # Do the outputs. Need to keep track of tract.
552 superStarChebSize = fgcmFitCycle.fgcmZpts.zpStruct['FGCM_FZPT_SSTAR_CHEB'].shape[1]
553 zptChebSize = fgcmFitCycle.fgcmZpts.zpStruct['FGCM_FZPT_CHEB'].shape[1]
555 zptSchema = makeZptSchema(superStarChebSize, zptChebSize)
556 zptCat = makeZptCat(zptSchema, fgcmFitCycle.fgcmZpts.zpStruct)
558 atmSchema = makeAtmSchema()
559 atmCat = makeAtmCat(atmSchema, fgcmFitCycle.fgcmZpts.atmStruct)
561 stdStruct, goodBands = fgcmFitCycle.fgcmStars.retrieveStdStarCatalog(fgcmFitCycle.fgcmPars)
562 stdSchema = makeStdSchema(len(goodBands))
563 stdCat = makeStdCat(stdSchema, stdStruct, goodBands)
565 outStruct = self.fgcmOutputProducts.generateTractOutputProducts(dataRefDict,
566 tract,
567 visitCat,
568 zptCat, atmCat, stdCat,
569 self.config.fgcmBuildStars,
570 returnCatalogs=returnCatalogs,
571 butler=butler)
573 outStruct.repeatability = fgcmFitCycle.fgcmPars.compReservedRawRepeatability
575 fgcmFitCycle.freeSharedMemory()
577 return outStruct